From a191631c32a9ea669eeff07aa7234ec718ddd822 Mon Sep 17 00:00:00 2001 From: gid9798 <30450294+gid9798@users.noreply.github.com> Date: Sun, 18 Jun 2023 16:44:45 +0300 Subject: [PATCH 001/364] DIR_NAME and Drivers --- applications/drivers/application.fam | 6 + applications/drivers/subghz/application.fam | 7 + .../drivers/subghz/cc1101_ext/cc1101_ext.c | 802 ++++++++++++++++++ .../drivers/subghz/cc1101_ext/cc1101_ext.h | 212 +++++ .../cc1101_ext/cc1101_ext_interconnect.c | 85 ++ .../cc1101_ext/cc1101_ext_interconnect.h | 8 + .../main/subghz/helpers/subghz_txrx.c | 14 +- documentation/FuriHalBus.md | 16 +- lib/subghz/types.h | 6 + site_scons/commandline.scons | 1 + 10 files changed, 1142 insertions(+), 15 deletions(-) create mode 100644 applications/drivers/application.fam create mode 100644 applications/drivers/subghz/application.fam create mode 100644 applications/drivers/subghz/cc1101_ext/cc1101_ext.c create mode 100644 applications/drivers/subghz/cc1101_ext/cc1101_ext.h create mode 100644 applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.c create mode 100644 applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h diff --git a/applications/drivers/application.fam b/applications/drivers/application.fam new file mode 100644 index 000000000..dc70e630c --- /dev/null +++ b/applications/drivers/application.fam @@ -0,0 +1,6 @@ +# Placeholder +App( + appid="drivers", + name="Drivers device", + apptype=FlipperAppType.METAPACKAGE, +) diff --git a/applications/drivers/subghz/application.fam b/applications/drivers/subghz/application.fam new file mode 100644 index 000000000..a293b99d3 --- /dev/null +++ b/applications/drivers/subghz/application.fam @@ -0,0 +1,7 @@ +App( + appid="radio_device_cc1101_ext", + apptype=FlipperAppType.PLUGIN, + entry_point="subghz_device_cc1101_ext_ep", + requires=["subghz"], + fap_libs=["hwdrivers"], +) diff --git a/applications/drivers/subghz/cc1101_ext/cc1101_ext.c b/applications/drivers/subghz/cc1101_ext/cc1101_ext.c new file mode 100644 index 000000000..b4e7e9eee --- /dev/null +++ b/applications/drivers/subghz/cc1101_ext/cc1101_ext.c @@ -0,0 +1,802 @@ +#include "cc1101_ext.h" +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#define TAG "SubGhz_Device_CC1101_Ext" + +#define SUBGHZ_DEVICE_CC1101_EXT_TX_GPIO &gpio_ext_pb2 + +/* DMA Channels definition */ +#define SUBGHZ_DEVICE_CC1101_EXT_DMA DMA2 +#define SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_CHANNEL LL_DMA_CHANNEL_3 +#define SUBGHZ_DEVICE_CC1101_EXT_DMA_CH4_CHANNEL LL_DMA_CHANNEL_4 +#define SUBGHZ_DEVICE_CC1101_EXT_DMA_CH5_CHANNEL LL_DMA_CHANNEL_5 +#define SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_IRQ FuriHalInterruptIdDma2Ch3 +#define SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_DEF \ + SUBGHZ_DEVICE_CC1101_EXT_DMA, SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_CHANNEL +#define SUBGHZ_DEVICE_CC1101_EXT_DMA_CH4_DEF \ + SUBGHZ_DEVICE_CC1101_EXT_DMA, SUBGHZ_DEVICE_CC1101_EXT_DMA_CH4_CHANNEL +#define SUBGHZ_DEVICE_CC1101_EXT_DMA_CH5_DEF \ + SUBGHZ_DEVICE_CC1101_EXT_DMA, SUBGHZ_DEVICE_CC1101_EXT_DMA_CH5_CHANNEL + +/** Low level buffer dimensions and guard times */ +#define SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_BUFFER_FULL (256) +#define SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_BUFFER_HALF \ + (SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_BUFFER_FULL / 2) +#define SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_GUARD_TIME 999 + +/** SubGhz state */ +typedef enum { + SubGhzDeviceCC1101ExtStateInit, /**< Init pending */ + SubGhzDeviceCC1101ExtStateIdle, /**< Idle, energy save mode */ + SubGhzDeviceCC1101ExtStateAsyncRx, /**< Async RX started */ + SubGhzDeviceCC1101ExtStateAsyncTx, /**< Async TX started, DMA and timer is on */ + SubGhzDeviceCC1101ExtStateAsyncTxEnd, /**< Async TX complete, cleanup needed */ +} SubGhzDeviceCC1101ExtState; + +/** SubGhz regulation, receive transmission on the current frequency for the + * region */ +typedef enum { + SubGhzDeviceCC1101ExtRegulationOnlyRx, /**only Rx*/ + SubGhzDeviceCC1101ExtRegulationTxRx, /**TxRx*/ +} SubGhzDeviceCC1101ExtRegulation; + +typedef struct { + uint32_t* buffer; + LevelDuration carry_ld; + SubGhzDeviceCC1101ExtCallback callback; + void* callback_context; + uint32_t gpio_tx_buff[2]; + uint32_t debug_gpio_buff[2]; +} SubGhzDeviceCC1101ExtAsyncTx; + +typedef struct { + uint32_t capture_delta_duration; + SubGhzDeviceCC1101ExtCaptureCallback capture_callback; + void* capture_callback_context; +} SubGhzDeviceCC1101ExtAsyncRx; + +typedef struct { + volatile SubGhzDeviceCC1101ExtState state; + volatile SubGhzDeviceCC1101ExtRegulation regulation; + volatile FuriHalSubGhzPreset preset; + const GpioPin* async_mirror_pin; + FuriHalSpiBusHandle* spi_bus_handle; + const GpioPin* g0_pin; + SubGhzDeviceCC1101ExtAsyncTx async_tx; + SubGhzDeviceCC1101ExtAsyncRx async_rx; +} SubGhzDeviceCC1101Ext; + +static SubGhzDeviceCC1101Ext* subghz_device_cc1101_ext = NULL; + +static bool subghz_device_cc1101_ext_check_init() { + furi_assert(subghz_device_cc1101_ext->state == SubGhzDeviceCC1101ExtStateInit); + subghz_device_cc1101_ext->state = SubGhzDeviceCC1101ExtStateIdle; + subghz_device_cc1101_ext->preset = FuriHalSubGhzPresetIDLE; + + bool ret = false; + + furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle); + FuriHalCortexTimer timer = furi_hal_cortex_timer_get(100 * 1000); + do { + // Reset + furi_hal_gpio_init( + subghz_device_cc1101_ext->g0_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + cc1101_reset(subghz_device_cc1101_ext->spi_bus_handle); + cc1101_write_reg( + subghz_device_cc1101_ext->spi_bus_handle, CC1101_IOCFG0, CC1101IocfgHighImpedance); + + // Prepare GD0 for power on self test + furi_hal_gpio_init( + subghz_device_cc1101_ext->g0_pin, GpioModeInput, GpioPullNo, GpioSpeedLow); + + // GD0 low + cc1101_write_reg(subghz_device_cc1101_ext->spi_bus_handle, CC1101_IOCFG0, CC1101IocfgHW); + while(furi_hal_gpio_read(subghz_device_cc1101_ext->g0_pin) != false) { + if(furi_hal_cortex_timer_is_expired(timer)) { + //timeout + break; + } + } + if(furi_hal_cortex_timer_is_expired(timer)) { + //timeout + break; + } + + // GD0 high + cc1101_write_reg( + subghz_device_cc1101_ext->spi_bus_handle, + CC1101_IOCFG0, + CC1101IocfgHW | CC1101_IOCFG_INV); + while(furi_hal_gpio_read(subghz_device_cc1101_ext->g0_pin) != true) { + if(furi_hal_cortex_timer_is_expired(timer)) { + //timeout + break; + } + } + if(furi_hal_cortex_timer_is_expired(timer)) { + //timeout + break; + } + + // Reset GD0 to floating state + cc1101_write_reg( + subghz_device_cc1101_ext->spi_bus_handle, CC1101_IOCFG0, CC1101IocfgHighImpedance); + furi_hal_gpio_init( + subghz_device_cc1101_ext->g0_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + + // RF switches + furi_hal_gpio_init(&gpio_rf_sw_0, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); + cc1101_write_reg(subghz_device_cc1101_ext->spi_bus_handle, CC1101_IOCFG2, CC1101IocfgHW); + + // Go to sleep + cc1101_shutdown(subghz_device_cc1101_ext->spi_bus_handle); + ret = true; + } while(false); + + furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); + + if(ret) { + FURI_LOG_I(TAG, "Init OK"); + } else { + FURI_LOG_E(TAG, "Init failed"); + } + return ret; +} + +bool subghz_device_cc1101_ext_alloc() { + furi_assert(subghz_device_cc1101_ext == NULL); + subghz_device_cc1101_ext = malloc(sizeof(SubGhzDeviceCC1101Ext)); + subghz_device_cc1101_ext->state = SubGhzDeviceCC1101ExtStateInit; + subghz_device_cc1101_ext->regulation = SubGhzDeviceCC1101ExtRegulationTxRx; + subghz_device_cc1101_ext->preset = FuriHalSubGhzPresetIDLE; + subghz_device_cc1101_ext->async_mirror_pin = NULL; + subghz_device_cc1101_ext->spi_bus_handle = &furi_hal_spi_bus_handle_external; + subghz_device_cc1101_ext->g0_pin = SUBGHZ_DEVICE_CC1101_EXT_TX_GPIO; + + subghz_device_cc1101_ext->async_rx.capture_delta_duration = 0; + + furi_hal_spi_bus_handle_init(subghz_device_cc1101_ext->spi_bus_handle); + return subghz_device_cc1101_ext_check_init(); +} + +void subghz_device_cc1101_ext_free() { + furi_assert(subghz_device_cc1101_ext != NULL); + furi_hal_spi_bus_handle_deinit(subghz_device_cc1101_ext->spi_bus_handle); + free(subghz_device_cc1101_ext); + subghz_device_cc1101_ext = NULL; +} + +void subghz_device_cc1101_ext_set_async_mirror_pin(const GpioPin* pin) { + subghz_device_cc1101_ext->async_mirror_pin = pin; +} + +const GpioPin* subghz_device_cc1101_ext_get_data_gpio() { + return subghz_device_cc1101_ext->g0_pin; +} + +bool subghz_device_cc1101_ext_is_connect() { + bool ret = false; + + if(subghz_device_cc1101_ext == NULL) { // not initialized + ret = subghz_device_cc1101_ext_alloc(); + subghz_device_cc1101_ext_free(); + } else { // initialized + furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle); + uint8_t partnumber = cc1101_get_partnumber(subghz_device_cc1101_ext->spi_bus_handle); + furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); + ret = (partnumber != 0) && (partnumber != 0xFF); + } + + return ret; +} + +void subghz_device_cc1101_ext_sleep() { + furi_assert(subghz_device_cc1101_ext->state == SubGhzDeviceCC1101ExtStateIdle); + furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle); + + cc1101_switch_to_idle(subghz_device_cc1101_ext->spi_bus_handle); + + cc1101_write_reg( + subghz_device_cc1101_ext->spi_bus_handle, CC1101_IOCFG0, CC1101IocfgHighImpedance); + furi_hal_gpio_init(subghz_device_cc1101_ext->g0_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + + cc1101_shutdown(subghz_device_cc1101_ext->spi_bus_handle); + + furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); + + subghz_device_cc1101_ext->preset = FuriHalSubGhzPresetIDLE; +} + +void subghz_device_cc1101_ext_dump_state() { + furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle); + printf( + "[subghz_device_cc1101_ext] cc1101 chip %d, version %d\r\n", + cc1101_get_partnumber(subghz_device_cc1101_ext->spi_bus_handle), + cc1101_get_version(subghz_device_cc1101_ext->spi_bus_handle)); + furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); +} + +void subghz_device_cc1101_ext_load_preset(FuriHalSubGhzPreset preset) { + if(preset == FuriHalSubGhzPresetOok650Async) { + subghz_device_cc1101_ext_load_registers( + (uint8_t*)subghz_device_cc1101_preset_ook_650khz_async_regs); + subghz_device_cc1101_ext_load_patable(subghz_device_cc1101_preset_ook_async_patable); + } else if(preset == FuriHalSubGhzPresetOok270Async) { + subghz_device_cc1101_ext_load_registers( + (uint8_t*)subghz_device_cc1101_preset_ook_270khz_async_regs); + subghz_device_cc1101_ext_load_patable(subghz_device_cc1101_preset_ook_async_patable); + } else if(preset == FuriHalSubGhzPreset2FSKDev238Async) { + subghz_device_cc1101_ext_load_registers( + (uint8_t*)subghz_device_cc1101_preset_2fsk_dev2_38khz_async_regs); + subghz_device_cc1101_ext_load_patable(subghz_device_cc1101_preset_2fsk_async_patable); + } else if(preset == FuriHalSubGhzPreset2FSKDev476Async) { + subghz_device_cc1101_ext_load_registers( + (uint8_t*)subghz_device_cc1101_preset_2fsk_dev47_6khz_async_regs); + subghz_device_cc1101_ext_load_patable(subghz_device_cc1101_preset_2fsk_async_patable); + } else if(preset == FuriHalSubGhzPresetMSK99_97KbAsync) { + subghz_device_cc1101_ext_load_registers( + (uint8_t*)subghz_device_cc1101_preset_msk_99_97kb_async_regs); + subghz_device_cc1101_ext_load_patable(subghz_device_cc1101_preset_msk_async_patable); + } else if(preset == FuriHalSubGhzPresetGFSK9_99KbAsync) { + subghz_device_cc1101_ext_load_registers( + (uint8_t*)subghz_device_cc1101_preset_gfsk_9_99kb_async_regs); + subghz_device_cc1101_ext_load_patable(subghz_device_cc1101_preset_gfsk_async_patable); + } else { + furi_crash("SubGhz: Missing config."); + } + subghz_device_cc1101_ext->preset = preset; +} + +void subghz_device_cc1101_ext_load_custom_preset(uint8_t* preset_data) { + //load config + furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle); + cc1101_reset(subghz_device_cc1101_ext->spi_bus_handle); + uint32_t i = 0; + uint8_t pa[8] = {0}; + while(preset_data[i]) { + cc1101_write_reg( + subghz_device_cc1101_ext->spi_bus_handle, preset_data[i], preset_data[i + 1]); + i += 2; + } + furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); + + //load pa table + memcpy(&pa[0], &preset_data[i + 2], 8); + subghz_device_cc1101_ext_load_patable(pa); + subghz_device_cc1101_ext->preset = FuriHalSubGhzPresetCustom; + + //show debug + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + i = 0; + FURI_LOG_D(TAG, "Loading custom preset"); + while(preset_data[i]) { + FURI_LOG_D(TAG, "Reg[%lu]: %02X=%02X", i, preset_data[i], preset_data[i + 1]); + i += 2; + } + for(uint8_t y = i; y < i + 10; y++) { + FURI_LOG_D(TAG, "PA[%u]: %02X", y, preset_data[y]); + } + } +} + +void subghz_device_cc1101_ext_load_registers(uint8_t* data) { + furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle); + cc1101_reset(subghz_device_cc1101_ext->spi_bus_handle); + uint32_t i = 0; + while(data[i]) { + cc1101_write_reg(subghz_device_cc1101_ext->spi_bus_handle, data[i], data[i + 1]); + i += 2; + } + furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); +} + +void subghz_device_cc1101_ext_load_patable(const uint8_t data[8]) { + furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle); + cc1101_set_pa_table(subghz_device_cc1101_ext->spi_bus_handle, data); + furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); +} + +void subghz_device_cc1101_ext_write_packet(const uint8_t* data, uint8_t size) { + furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle); + cc1101_flush_tx(subghz_device_cc1101_ext->spi_bus_handle); + cc1101_write_reg(subghz_device_cc1101_ext->spi_bus_handle, CC1101_FIFO, size); + cc1101_write_fifo(subghz_device_cc1101_ext->spi_bus_handle, data, size); + furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); +} + +void subghz_device_cc1101_ext_flush_rx() { + furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle); + cc1101_flush_rx(subghz_device_cc1101_ext->spi_bus_handle); + furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); +} + +void subghz_device_cc1101_ext_flush_tx() { + furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle); + cc1101_flush_tx(subghz_device_cc1101_ext->spi_bus_handle); + furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); +} + +bool subghz_device_cc1101_ext_rx_pipe_not_empty() { + CC1101RxBytes status[1]; + furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle); + cc1101_read_reg( + subghz_device_cc1101_ext->spi_bus_handle, + (CC1101_STATUS_RXBYTES) | CC1101_BURST, + (uint8_t*)status); + furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); + // TODO: you can add a buffer overflow flag if needed + if(status->NUM_RXBYTES > 0) { + return true; + } else { + return false; + } +} + +bool subghz_device_cc1101_ext_is_rx_data_crc_valid() { + furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle); + uint8_t data[1]; + cc1101_read_reg( + subghz_device_cc1101_ext->spi_bus_handle, CC1101_STATUS_LQI | CC1101_BURST, data); + furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); + if(((data[0] >> 7) & 0x01)) { + return true; + } else { + return false; + } +} + +void subghz_device_cc1101_ext_read_packet(uint8_t* data, uint8_t* size) { + furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle); + cc1101_read_fifo(subghz_device_cc1101_ext->spi_bus_handle, data, size); + furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); +} + +void subghz_device_cc1101_ext_shutdown() { + furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle); + // Reset and shutdown + cc1101_shutdown(subghz_device_cc1101_ext->spi_bus_handle); + furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); +} + +void subghz_device_cc1101_ext_reset() { + furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle); + furi_hal_gpio_init(subghz_device_cc1101_ext->g0_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + cc1101_switch_to_idle(subghz_device_cc1101_ext->spi_bus_handle); + cc1101_reset(subghz_device_cc1101_ext->spi_bus_handle); + cc1101_write_reg( + subghz_device_cc1101_ext->spi_bus_handle, CC1101_IOCFG0, CC1101IocfgHighImpedance); + furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); +} + +void subghz_device_cc1101_ext_idle() { + furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle); + cc1101_switch_to_idle(subghz_device_cc1101_ext->spi_bus_handle); + furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); +} + +void subghz_device_cc1101_ext_rx() { + furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle); + cc1101_switch_to_rx(subghz_device_cc1101_ext->spi_bus_handle); + furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); +} + +bool subghz_device_cc1101_ext_tx() { + if(subghz_device_cc1101_ext->regulation != SubGhzDeviceCC1101ExtRegulationTxRx) return false; + furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle); + cc1101_switch_to_tx(subghz_device_cc1101_ext->spi_bus_handle); + furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); + return true; +} + +float subghz_device_cc1101_ext_get_rssi() { + furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle); + int32_t rssi_dec = cc1101_get_rssi(subghz_device_cc1101_ext->spi_bus_handle); + furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); + + float rssi = rssi_dec; + if(rssi_dec >= 128) { + rssi = ((rssi - 256.0f) / 2.0f) - 74.0f; + } else { + rssi = (rssi / 2.0f) - 74.0f; + } + + return rssi; +} + +uint8_t subghz_device_cc1101_ext_get_lqi() { + furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle); + uint8_t data[1]; + cc1101_read_reg( + subghz_device_cc1101_ext->spi_bus_handle, CC1101_STATUS_LQI | CC1101_BURST, data); + furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); + return data[0] & 0x7F; +} + +bool subghz_device_cc1101_ext_is_frequency_valid(uint32_t value) { + if(!(value >= 299999755 && value <= 348000335) && + !(value >= 386999938 && value <= 464000000) && + !(value >= 778999847 && value <= 928000000)) { + return false; + } + + return true; +} + +uint32_t subghz_device_cc1101_ext_set_frequency(uint32_t value) { + if(furi_hal_region_is_frequency_allowed(value)) { + subghz_device_cc1101_ext->regulation = SubGhzDeviceCC1101ExtRegulationTxRx; + } else { + subghz_device_cc1101_ext->regulation = SubGhzDeviceCC1101ExtRegulationOnlyRx; + } + + furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle); + uint32_t real_frequency = + cc1101_set_frequency(subghz_device_cc1101_ext->spi_bus_handle, value); + cc1101_calibrate(subghz_device_cc1101_ext->spi_bus_handle); + + while(true) { + CC1101Status status = cc1101_get_status(subghz_device_cc1101_ext->spi_bus_handle); + if(status.STATE == CC1101StateIDLE) break; + } + + furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); + return real_frequency; +} + +static bool subghz_device_cc1101_ext_start_debug() { + bool ret = false; + if(subghz_device_cc1101_ext->async_mirror_pin != NULL) { + furi_hal_gpio_init( + subghz_device_cc1101_ext->async_mirror_pin, + GpioModeOutputPushPull, + GpioPullNo, + GpioSpeedVeryHigh); + ret = true; + } + return ret; +} + +static bool subghz_device_cc1101_ext_stop_debug() { + bool ret = false; + if(subghz_device_cc1101_ext->async_mirror_pin != NULL) { + furi_hal_gpio_init( + subghz_device_cc1101_ext->async_mirror_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + ret = true; + } + return ret; +} + +static void subghz_device_cc1101_ext_capture_ISR() { + if(!furi_hal_gpio_read(subghz_device_cc1101_ext->g0_pin)) { + if(subghz_device_cc1101_ext->async_rx.capture_callback) { + if(subghz_device_cc1101_ext->async_mirror_pin != NULL) + furi_hal_gpio_write(subghz_device_cc1101_ext->async_mirror_pin, false); + + subghz_device_cc1101_ext->async_rx.capture_callback( + true, + LL_TIM_GetCounter(TIM17), + (void*)subghz_device_cc1101_ext->async_rx.capture_callback_context); + } + } else { + if(subghz_device_cc1101_ext->async_rx.capture_callback) { + if(subghz_device_cc1101_ext->async_mirror_pin != NULL) + furi_hal_gpio_write(subghz_device_cc1101_ext->async_mirror_pin, true); + + subghz_device_cc1101_ext->async_rx.capture_callback( + false, + LL_TIM_GetCounter(TIM17), + (void*)subghz_device_cc1101_ext->async_rx.capture_callback_context); + } + } + LL_TIM_SetCounter(TIM17, 6); +} + +void subghz_device_cc1101_ext_start_async_rx( + SubGhzDeviceCC1101ExtCaptureCallback callback, + void* context) { + furi_assert(subghz_device_cc1101_ext->state == SubGhzDeviceCC1101ExtStateIdle); + subghz_device_cc1101_ext->state = SubGhzDeviceCC1101ExtStateAsyncRx; + + subghz_device_cc1101_ext->async_rx.capture_callback = callback; + subghz_device_cc1101_ext->async_rx.capture_callback_context = context; + + furi_hal_bus_enable(FuriHalBusTIM17); + + // Configure TIM + LL_TIM_SetPrescaler(TIM17, 64 - 1); + LL_TIM_SetCounterMode(TIM17, LL_TIM_COUNTERMODE_UP); + LL_TIM_SetAutoReload(TIM17, 0xFFFF); + LL_TIM_SetClockDivision(TIM17, LL_TIM_CLOCKDIVISION_DIV1); + + // Timer: advanced + LL_TIM_SetClockSource(TIM17, LL_TIM_CLOCKSOURCE_INTERNAL); + LL_TIM_DisableARRPreload(TIM17); + LL_TIM_DisableDMAReq_TRIG(TIM17); + LL_TIM_DisableIT_TRIG(TIM17); + + furi_hal_gpio_init( + subghz_device_cc1101_ext->g0_pin, GpioModeInterruptRiseFall, GpioPullUp, GpioSpeedVeryHigh); + furi_hal_gpio_remove_int_callback(subghz_device_cc1101_ext->g0_pin); + furi_hal_gpio_add_int_callback( + subghz_device_cc1101_ext->g0_pin, + subghz_device_cc1101_ext_capture_ISR, + subghz_device_cc1101_ext->async_rx.capture_callback); + + // Start timer + LL_TIM_SetCounter(TIM17, 0); + LL_TIM_EnableCounter(TIM17); + + // Start debug + subghz_device_cc1101_ext_start_debug(); + + // Switch to RX + subghz_device_cc1101_ext_rx(); + + //Clear the variable after the end of the session + subghz_device_cc1101_ext->async_rx.capture_delta_duration = 0; +} + +void subghz_device_cc1101_ext_stop_async_rx() { + furi_assert(subghz_device_cc1101_ext->state == SubGhzDeviceCC1101ExtStateAsyncRx); + subghz_device_cc1101_ext->state = SubGhzDeviceCC1101ExtStateIdle; + + // Shutdown radio + subghz_device_cc1101_ext_idle(); + + FURI_CRITICAL_ENTER(); + furi_hal_bus_disable(FuriHalBusTIM17); + + // Stop debug + subghz_device_cc1101_ext_stop_debug(); + + FURI_CRITICAL_EXIT(); + furi_hal_gpio_remove_int_callback(subghz_device_cc1101_ext->g0_pin); + furi_hal_gpio_init(subghz_device_cc1101_ext->g0_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); +} + +static void subghz_device_cc1101_ext_async_tx_refill(uint32_t* buffer, size_t samples) { + furi_assert(subghz_device_cc1101_ext->state == SubGhzDeviceCC1101ExtStateAsyncTx); + while(samples > 0) { + bool is_odd = samples % 2; + LevelDuration ld; + if(level_duration_is_reset(subghz_device_cc1101_ext->async_tx.carry_ld)) { + ld = subghz_device_cc1101_ext->async_tx.callback( + subghz_device_cc1101_ext->async_tx.callback_context); + } else { + ld = subghz_device_cc1101_ext->async_tx.carry_ld; + subghz_device_cc1101_ext->async_tx.carry_ld = level_duration_reset(); + } + + if(level_duration_is_wait(ld)) { + *buffer = SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_GUARD_TIME; + buffer++; + samples--; + } else if(level_duration_is_reset(ld)) { + *buffer = 0; + buffer++; + samples--; + LL_DMA_DisableIT_HT(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_DEF); + LL_DMA_DisableIT_TC(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_DEF); + LL_TIM_EnableIT_UPDATE(TIM17); + break; + } else { + bool level = level_duration_get_level(ld); + + // Inject guard time if level is incorrect + if(is_odd != level) { + *buffer = SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_GUARD_TIME; + buffer++; + samples--; + + // Special case: prevent buffer overflow if sample is last + if(samples == 0) { + subghz_device_cc1101_ext->async_tx.carry_ld = ld; + break; + } + } + + uint32_t duration = level_duration_get_duration(ld); + furi_assert(duration > 0); + *buffer = duration - 1; + buffer++; + samples--; + } + } +} + +static void subghz_device_cc1101_ext_async_tx_dma_isr() { + furi_assert(subghz_device_cc1101_ext->state == SubGhzDeviceCC1101ExtStateAsyncTx); + +#if SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_CHANNEL == LL_DMA_CHANNEL_3 + if(LL_DMA_IsActiveFlag_HT3(SUBGHZ_DEVICE_CC1101_EXT_DMA)) { + LL_DMA_ClearFlag_HT3(SUBGHZ_DEVICE_CC1101_EXT_DMA); + subghz_device_cc1101_ext_async_tx_refill( + subghz_device_cc1101_ext->async_tx.buffer, + SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_BUFFER_HALF); + } + if(LL_DMA_IsActiveFlag_TC3(SUBGHZ_DEVICE_CC1101_EXT_DMA)) { + LL_DMA_ClearFlag_TC3(SUBGHZ_DEVICE_CC1101_EXT_DMA); + subghz_device_cc1101_ext_async_tx_refill( + subghz_device_cc1101_ext->async_tx.buffer + + SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_BUFFER_HALF, + SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_BUFFER_HALF); + } +#else +#error Update this code. Would you kindly? +#endif +} + +static void subghz_device_cc1101_ext_async_tx_timer_isr() { + if(LL_TIM_IsActiveFlag_UPDATE(TIM17)) { + if(LL_TIM_GetAutoReload(TIM17) == 0) { + LL_DMA_DisableChannel(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_DEF); + furi_hal_gpio_write(subghz_device_cc1101_ext->g0_pin, false); + if(subghz_device_cc1101_ext->async_mirror_pin != NULL) + furi_hal_gpio_write(subghz_device_cc1101_ext->async_mirror_pin, false); + LL_TIM_DisableCounter(TIM17); + subghz_device_cc1101_ext->state = SubGhzDeviceCC1101ExtStateAsyncTxEnd; + } + LL_TIM_ClearFlag_UPDATE(TIM17); + } +} + +bool subghz_device_cc1101_ext_start_async_tx(SubGhzDeviceCC1101ExtCallback callback, void* context) { + furi_assert(subghz_device_cc1101_ext->state == SubGhzDeviceCC1101ExtStateIdle); + furi_assert(callback); + + //If transmission is prohibited by regional settings + if(subghz_device_cc1101_ext->regulation != SubGhzDeviceCC1101ExtRegulationTxRx) return false; + + subghz_device_cc1101_ext->async_tx.callback = callback; + subghz_device_cc1101_ext->async_tx.callback_context = context; + + subghz_device_cc1101_ext->state = SubGhzDeviceCC1101ExtStateAsyncTx; + + subghz_device_cc1101_ext->async_tx.buffer = + malloc(SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_BUFFER_FULL * sizeof(uint32_t)); + + //Signal generation with mem-to-mem DMA + furi_hal_gpio_write(subghz_device_cc1101_ext->g0_pin, false); + furi_hal_gpio_init( + subghz_device_cc1101_ext->g0_pin, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); + + // Configure DMA update timer + LL_DMA_SetMemoryAddress( + SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_DEF, (uint32_t)subghz_device_cc1101_ext->async_tx.buffer); + LL_DMA_SetPeriphAddress(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_DEF, (uint32_t) & (TIM17->ARR)); + LL_DMA_ConfigTransfer( + SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_DEF, + LL_DMA_DIRECTION_MEMORY_TO_PERIPH | LL_DMA_MODE_CIRCULAR | LL_DMA_PERIPH_NOINCREMENT | + LL_DMA_MEMORY_INCREMENT | LL_DMA_PDATAALIGN_WORD | LL_DMA_MDATAALIGN_WORD | + LL_DMA_MODE_NORMAL); + LL_DMA_SetDataLength( + SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_DEF, SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_BUFFER_FULL); + LL_DMA_SetPeriphRequest(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_DEF, LL_DMAMUX_REQ_TIM17_UP); + + LL_DMA_EnableIT_TC(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_DEF); + LL_DMA_EnableIT_HT(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_DEF); + LL_DMA_EnableChannel(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_DEF); + + furi_hal_interrupt_set_isr( + SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_IRQ, subghz_device_cc1101_ext_async_tx_dma_isr, NULL); + + furi_hal_bus_enable(FuriHalBusTIM17); + + // Configure TIM + LL_TIM_SetPrescaler(TIM17, 64 - 1); + LL_TIM_SetCounterMode(TIM17, LL_TIM_COUNTERMODE_UP); + LL_TIM_SetAutoReload(TIM17, 0xFFFF); + LL_TIM_SetClockDivision(TIM17, LL_TIM_CLOCKDIVISION_DIV1); + LL_TIM_SetClockSource(TIM17, LL_TIM_CLOCKSOURCE_INTERNAL); + LL_TIM_DisableARRPreload(TIM17); + + furi_hal_interrupt_set_isr( + FuriHalInterruptIdTim1TrgComTim17, subghz_device_cc1101_ext_async_tx_timer_isr, NULL); + + subghz_device_cc1101_ext_async_tx_refill( + subghz_device_cc1101_ext->async_tx.buffer, SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_BUFFER_FULL); + + // Configure tx gpio dma + const GpioPin* gpio = subghz_device_cc1101_ext->g0_pin; + + subghz_device_cc1101_ext->async_tx.gpio_tx_buff[0] = (uint32_t)gpio->pin << GPIO_NUMBER; + subghz_device_cc1101_ext->async_tx.gpio_tx_buff[1] = gpio->pin; + + LL_DMA_SetMemoryAddress( + SUBGHZ_DEVICE_CC1101_EXT_DMA_CH4_DEF, + (uint32_t)subghz_device_cc1101_ext->async_tx.gpio_tx_buff); + LL_DMA_SetPeriphAddress(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH4_DEF, (uint32_t) & (gpio->port->BSRR)); + LL_DMA_ConfigTransfer( + SUBGHZ_DEVICE_CC1101_EXT_DMA_CH4_DEF, + LL_DMA_DIRECTION_MEMORY_TO_PERIPH | LL_DMA_MODE_CIRCULAR | LL_DMA_PERIPH_NOINCREMENT | + LL_DMA_MEMORY_INCREMENT | LL_DMA_PDATAALIGN_WORD | LL_DMA_MDATAALIGN_WORD | + LL_DMA_PRIORITY_HIGH); + LL_DMA_SetDataLength(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH4_DEF, 2); + LL_DMA_SetPeriphRequest(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH4_DEF, LL_DMAMUX_REQ_TIM17_UP); + LL_DMA_EnableChannel(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH4_DEF); + + // Start debug + if(subghz_device_cc1101_ext_start_debug()) { + gpio = subghz_device_cc1101_ext->async_mirror_pin; + subghz_device_cc1101_ext->async_tx.debug_gpio_buff[0] = (uint32_t)gpio->pin << GPIO_NUMBER; + subghz_device_cc1101_ext->async_tx.debug_gpio_buff[1] = gpio->pin; + + LL_DMA_SetMemoryAddress( + SUBGHZ_DEVICE_CC1101_EXT_DMA_CH5_DEF, + (uint32_t)subghz_device_cc1101_ext->async_tx.debug_gpio_buff); + LL_DMA_SetPeriphAddress( + SUBGHZ_DEVICE_CC1101_EXT_DMA_CH5_DEF, (uint32_t) & (gpio->port->BSRR)); + LL_DMA_ConfigTransfer( + SUBGHZ_DEVICE_CC1101_EXT_DMA_CH5_DEF, + LL_DMA_DIRECTION_MEMORY_TO_PERIPH | LL_DMA_MODE_CIRCULAR | LL_DMA_PERIPH_NOINCREMENT | + LL_DMA_MEMORY_INCREMENT | LL_DMA_PDATAALIGN_WORD | LL_DMA_MDATAALIGN_WORD | + LL_DMA_PRIORITY_LOW); + LL_DMA_SetDataLength(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH5_DEF, 2); + LL_DMA_SetPeriphRequest(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH5_DEF, LL_DMAMUX_REQ_TIM17_UP); + LL_DMA_EnableChannel(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH5_DEF); + } + + // Start counter + LL_TIM_EnableDMAReq_UPDATE(TIM17); + LL_TIM_GenerateEvent_UPDATE(TIM17); + + subghz_device_cc1101_ext_tx(); + + LL_TIM_SetCounter(TIM17, 0); + LL_TIM_EnableCounter(TIM17); + + return true; +} + +bool subghz_device_cc1101_ext_is_async_tx_complete() { + return subghz_device_cc1101_ext->state == SubGhzDeviceCC1101ExtStateAsyncTxEnd; +} + +void subghz_device_cc1101_ext_stop_async_tx() { + furi_assert( + subghz_device_cc1101_ext->state == SubGhzDeviceCC1101ExtStateAsyncTx || + subghz_device_cc1101_ext->state == SubGhzDeviceCC1101ExtStateAsyncTxEnd); + + // Shutdown radio + subghz_device_cc1101_ext_idle(); + + // Deinitialize Timer + FURI_CRITICAL_ENTER(); + furi_hal_bus_disable(FuriHalBusTIM17); + furi_hal_interrupt_set_isr(FuriHalInterruptIdTim1TrgComTim17, NULL, NULL); + + // Deinitialize DMA + LL_DMA_DeInit(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_DEF); + LL_DMA_DisableChannel(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH4_DEF); + furi_hal_interrupt_set_isr(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_IRQ, NULL, NULL); + + // Deinitialize GPIO + furi_hal_gpio_write(subghz_device_cc1101_ext->g0_pin, false); + furi_hal_gpio_init(subghz_device_cc1101_ext->g0_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + + // Stop debug + if(subghz_device_cc1101_ext_stop_debug()) { + LL_DMA_DisableChannel(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH5_DEF); + } + + FURI_CRITICAL_EXIT(); + + free(subghz_device_cc1101_ext->async_tx.buffer); + + subghz_device_cc1101_ext->state = SubGhzDeviceCC1101ExtStateIdle; +} diff --git a/applications/drivers/subghz/cc1101_ext/cc1101_ext.h b/applications/drivers/subghz/cc1101_ext/cc1101_ext.h new file mode 100644 index 000000000..4fdfa5192 --- /dev/null +++ b/applications/drivers/subghz/cc1101_ext/cc1101_ext.h @@ -0,0 +1,212 @@ +/** + * @file furi_hal_subghz.h + * SubGhz HAL API + */ + +#pragma once +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Mirror RX/TX async modulation signal to specified pin + * + * @warning Configures pin to output mode. Make sure it is not connected + * directly to power or ground. + * + * @param[in] pin pointer to the gpio pin structure or NULL to disable + */ +void subghz_device_cc1101_ext_set_async_mirror_pin(const GpioPin* pin); + +/** Get data GPIO + * + * @return pointer to the gpio pin structure + */ +const GpioPin* subghz_device_cc1101_ext_get_data_gpio(); + +/** Initialize device + * + * @return true if success + */ +bool subghz_device_cc1101_ext_alloc(); + +/** Deinitialize device + */ +void subghz_device_cc1101_ext_free(); + +/** Check and switch to power save mode Used by internal API-HAL + * initialization routine Can be used to reinitialize device to safe state and + * send it to sleep + */ +bool subghz_device_cc1101_ext_is_connect(); + +/** Send device to sleep mode + */ +void subghz_device_cc1101_ext_sleep(); + +/** Dump info to stdout + */ +void subghz_device_cc1101_ext_dump_state(); + +/** Load registers from preset by preset name + * + * @param preset to load + */ +void subghz_device_cc1101_ext_load_preset(FuriHalSubGhzPreset preset); + +/** Load custom registers from preset + * + * @param preset_data registers to load + */ +void subghz_device_cc1101_ext_load_custom_preset(uint8_t* preset_data); + +/** Load registers + * + * @param data Registers data + */ +void subghz_device_cc1101_ext_load_registers(uint8_t* data); + +/** Load PATABLE + * + * @param data 8 uint8_t values + */ +void subghz_device_cc1101_ext_load_patable(const uint8_t data[8]); + +/** Write packet to FIFO + * + * @param data bytes array + * @param size size + */ +void subghz_device_cc1101_ext_write_packet(const uint8_t* data, uint8_t size); + +/** Check if receive pipe is not empty + * + * @return true if not empty + */ +bool subghz_device_cc1101_ext_rx_pipe_not_empty(); + +/** Check if received data crc is valid + * + * @return true if valid + */ +bool subghz_device_cc1101_ext_is_rx_data_crc_valid(); + +/** Read packet from FIFO + * + * @param data pointer + * @param size size + */ +void subghz_device_cc1101_ext_read_packet(uint8_t* data, uint8_t* size); + +/** Flush rx FIFO buffer + */ +void subghz_device_cc1101_ext_flush_rx(); + +/** Flush tx FIFO buffer + */ +void subghz_device_cc1101_ext_flush_tx(); + +/** Shutdown Issue SPWD command + * @warning registers content will be lost + */ +void subghz_device_cc1101_ext_shutdown(); + +/** Reset Issue reset command + * @warning registers content will be lost + */ +void subghz_device_cc1101_ext_reset(); + +/** Switch to Idle + */ +void subghz_device_cc1101_ext_idle(); + +/** Switch to Receive + */ +void subghz_device_cc1101_ext_rx(); + +/** Switch to Transmit + * + * @return true if the transfer is allowed by belonging to the region + */ +bool subghz_device_cc1101_ext_tx(); + +/** Get RSSI value in dBm + * + * @return RSSI value + */ +float subghz_device_cc1101_ext_get_rssi(); + +/** Get LQI + * + * @return LQI value + */ +uint8_t subghz_device_cc1101_ext_get_lqi(); + +/** Check if frequency is in valid range + * + * @param value frequency in Hz + * + * @return true if frequency is valid, otherwise false + */ +bool subghz_device_cc1101_ext_is_frequency_valid(uint32_t value); + +/** Set frequency + * + * @param value frequency in Hz + * + * @return real frequency in Hz + */ +uint32_t subghz_device_cc1101_ext_set_frequency(uint32_t value); + +/* High Level API */ + +/** Signal Timings Capture callback */ +typedef void (*SubGhzDeviceCC1101ExtCaptureCallback)(bool level, uint32_t duration, void* context); + +/** Enable signal timings capture Initializes GPIO and TIM2 for timings capture + * + * @param callback SubGhzDeviceCC1101ExtCaptureCallback + * @param context callback context + */ +void subghz_device_cc1101_ext_start_async_rx( + SubGhzDeviceCC1101ExtCaptureCallback callback, + void* context); + +/** Disable signal timings capture Resets GPIO and TIM2 + */ +void subghz_device_cc1101_ext_stop_async_rx(); + +/** Async TX callback type + * @param context callback context + * @return LevelDuration + */ +typedef LevelDuration (*SubGhzDeviceCC1101ExtCallback)(void* context); + +/** Start async TX Initializes GPIO, TIM2 and DMA1 for signal output + * + * @param callback SubGhzDeviceCC1101ExtCallback + * @param context callback context + * + * @return true if the transfer is allowed by belonging to the region + */ +bool subghz_device_cc1101_ext_start_async_tx(SubGhzDeviceCC1101ExtCallback callback, void* context); + +/** Wait for async transmission to complete + * + * @return true if TX complete + */ +bool subghz_device_cc1101_ext_is_async_tx_complete(); + +/** Stop async transmission and cleanup resources Resets GPIO, TIM2, and DMA1 + */ +void subghz_device_cc1101_ext_stop_async_tx(); + +#ifdef __cplusplus +} +#endif diff --git a/applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.c b/applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.c new file mode 100644 index 000000000..b087d4d53 --- /dev/null +++ b/applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.c @@ -0,0 +1,85 @@ +#include "cc1101_ext_interconnect.h" +#include "cc1101_ext.h" + +#define TAG "SubGhzDeviceCC1101Ext" + +static bool subghz_device_cc1101_ext_interconnect_is_frequency_valid(uint32_t frequency) { + bool ret = subghz_device_cc1101_ext_is_frequency_valid(frequency); + if(!ret) { + furi_crash("SubGhz: Incorrect frequency."); + } + return ret; +} + +static uint32_t subghz_device_cc1101_ext_interconnect_set_frequency(uint32_t frequency) { + subghz_device_cc1101_ext_interconnect_is_frequency_valid(frequency); + return subghz_device_cc1101_ext_set_frequency(frequency); +} + +static bool subghz_device_cc1101_ext_interconnect_start_async_tx(void* callback, void* context) { + return subghz_device_cc1101_ext_start_async_tx( + (SubGhzDeviceCC1101ExtCallback)callback, context); +} + +static void subghz_device_cc1101_ext_interconnect_start_async_rx(void* callback, void* context) { + subghz_device_cc1101_ext_start_async_rx( + (SubGhzDeviceCC1101ExtCaptureCallback)callback, context); +} + +static void subghz_device_cc1101_ext_interconnect_load_preset( + FuriHalSubGhzPreset preset, + uint8_t* preset_data) { + if(preset != FuriHalSubGhzPresetCustom) { + subghz_device_cc1101_ext_load_preset(preset); + } else { + subghz_device_cc1101_ext_load_custom_preset(preset_data); + } +} + +const SubGhzDeviceInterconnect subghz_device_cc1101_ext_interconnect = { + .begin = subghz_device_cc1101_ext_alloc, + .end = subghz_device_cc1101_ext_free, + .is_connect = subghz_device_cc1101_ext_is_connect, + .reset = subghz_device_cc1101_ext_reset, + .sleep = subghz_device_cc1101_ext_sleep, + .idle = subghz_device_cc1101_ext_idle, + .load_preset = subghz_device_cc1101_ext_interconnect_load_preset, + .set_frequency = subghz_device_cc1101_ext_interconnect_set_frequency, + .is_frequency_valid = subghz_device_cc1101_ext_is_frequency_valid, + .set_async_mirror_pin = subghz_device_cc1101_ext_set_async_mirror_pin, + .get_data_gpio = subghz_device_cc1101_ext_get_data_gpio, + + .set_tx = subghz_device_cc1101_ext_tx, + .flush_tx = subghz_device_cc1101_ext_flush_tx, + .start_async_tx = subghz_device_cc1101_ext_interconnect_start_async_tx, + .is_async_complete_tx = subghz_device_cc1101_ext_is_async_tx_complete, + .stop_async_tx = subghz_device_cc1101_ext_stop_async_tx, + + .set_rx = subghz_device_cc1101_ext_rx, + .flush_rx = subghz_device_cc1101_ext_flush_rx, + .start_async_rx = subghz_device_cc1101_ext_interconnect_start_async_rx, + .stop_async_rx = subghz_device_cc1101_ext_stop_async_rx, + + .get_rssi = subghz_device_cc1101_ext_get_rssi, + .get_lqi = subghz_device_cc1101_ext_get_lqi, + + .rx_pipe_not_empty = subghz_device_cc1101_ext_rx_pipe_not_empty, + .is_rx_data_crc_valid = subghz_device_cc1101_ext_is_rx_data_crc_valid, + .read_packet = subghz_device_cc1101_ext_read_packet, + .write_packet = subghz_device_cc1101_ext_write_packet, +}; + +const SubGhzDevice subghz_device_cc1101_ext = { + .name = SUBGHZ_DEVICE_CC1101_EXT_NAME, + .interconnect = &subghz_device_cc1101_ext_interconnect, +}; + +static const FlipperAppPluginDescriptor subghz_device_cc1101_ext_descriptor = { + .appid = SUBGHZ_RADIO_DEVICE_PLUGIN_APP_ID, + .ep_api_version = SUBGHZ_RADIO_DEVICE_PLUGIN_API_VERSION, + .entry_point = &subghz_device_cc1101_ext, +}; + +const FlipperAppPluginDescriptor* subghz_device_cc1101_ext_ep() { + return &subghz_device_cc1101_ext_descriptor; +} \ No newline at end of file diff --git a/applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h b/applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h new file mode 100644 index 000000000..cf1ff3ee0 --- /dev/null +++ b/applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h @@ -0,0 +1,8 @@ +#pragma once +#include + +#define SUBGHZ_DEVICE_CC1101_EXT_NAME "cc1101_ext" + +typedef struct SubGhzDeviceCC1101Ext SubGhzDeviceCC1101Ext; + +const FlipperAppPluginDescriptor* subghz_device_cc1101_ext_ep(); diff --git a/applications/main/subghz/helpers/subghz_txrx.c b/applications/main/subghz/helpers/subghz_txrx.c index 3275b7288..5b2a56993 100644 --- a/applications/main/subghz/helpers/subghz_txrx.c +++ b/applications/main/subghz/helpers/subghz_txrx.c @@ -24,16 +24,15 @@ SubGhzTxRx* subghz_txrx_alloc() { instance->fff_data = flipper_format_string_alloc(); instance->environment = subghz_environment_alloc(); - instance->is_database_loaded = subghz_environment_load_keystore( - instance->environment, EXT_PATH("subghz/assets/keeloq_mfcodes")); - subghz_environment_load_keystore( - instance->environment, EXT_PATH("subghz/assets/keeloq_mfcodes_user")); + instance->is_database_loaded = + subghz_environment_load_keystore(instance->environment, SUBGHZ_KEYSTORE_DIR_NAME); + subghz_environment_load_keystore(instance->environment, SUBGHZ_KEYSTORE_DIR_USER_NAME); subghz_environment_set_came_atomo_rainbow_table_file_name( - instance->environment, EXT_PATH("subghz/assets/came_atomo")); + instance->environment, SUBGHZ_CAME_ATOMO_DIR_NAME); subghz_environment_set_alutech_at_4n_rainbow_table_file_name( - instance->environment, EXT_PATH("subghz/assets/alutech_at_4n")); + instance->environment, SUBGHZ_ALUTECH_AT_4N_DIR_NAME); subghz_environment_set_nice_flor_s_rainbow_table_file_name( - instance->environment, EXT_PATH("subghz/assets/nice_flor_s")); + instance->environment, SUBGHZ_NICE_FLOR_S_DIR_NAME); subghz_environment_set_protocol_registry( instance->environment, (void*)&subghz_protocol_registry); instance->receiver = subghz_receiver_alloc_init(instance->environment); @@ -56,6 +55,7 @@ void subghz_txrx_free(SubGhzTxRx* instance) { flipper_format_free(instance->fff_data); furi_string_free(instance->preset->name); subghz_setting_free(instance->setting); + free(instance->preset); free(instance); } diff --git a/documentation/FuriHalBus.md b/documentation/FuriHalBus.md index 5c754018b..230a98050 100644 --- a/documentation/FuriHalBus.md +++ b/documentation/FuriHalBus.md @@ -78,9 +78,9 @@ The system will take over any given peripheral only when the respective feature | ADC | | | | QUADSPI | | | | TIM1 | yes | subghz, lfrfid, nfc, infrared, etc... | -| TIM2 | yes | -- | +| TIM2 | yes | subghz, infrared, etc... | | TIM16 | yes | speaker | -| TIM17 | | | +| TIM17 | yes | cc1101_ext | | LPTIM1 | yes | tickless idle timer | | LPTIM2 | yes | pwm | | SAI1 | | | @@ -104,10 +104,10 @@ Below is the list of DMA channels and their usage by the system. | -- | 5 | | | | -- | 6 | | | | -- | 7 | | | -| DMA2 | 1 | yes | infrared, lfrfid, subghz | +| DMA2 | 1 | yes | infrared, lfrfid, subghz, | | -- | 2 | yes | -- | -| -- | 3 | yes | SPI | -| -- | 4 | yes | SPI | -| -- | 5 | | | -| -- | 6 | | | -| -- | 7 | | | +| -- | 3 | yes | cc1101_ext | +| -- | 4 | yes | cc1101_ext | +| -- | 5 | yes | cc1101_ext | +| -- | 6 | yes | SPI | +| -- | 7 | yes | SPI | diff --git a/lib/subghz/types.h b/lib/subghz/types.h index ce65789a9..4c0e91e86 100644 --- a/lib/subghz/types.h +++ b/lib/subghz/types.h @@ -21,6 +21,12 @@ #define SUBGHZ_RAW_FILE_VERSION 1 #define SUBGHZ_RAW_FILE_TYPE "Flipper SubGhz RAW File" +#define SUBGHZ_KEYSTORE_DIR_NAME EXT_PATH("subghz/assets/keeloq_mfcodes") +#define SUBGHZ_KEYSTORE_DIR_USER_NAME EXT_PATH("subghz/assets/keeloq_mfcodes_user") +#define SUBGHZ_CAME_ATOMO_DIR_NAME EXT_PATH("subghz/assets/came_atomo") +#define SUBGHZ_NICE_FLOR_S_DIR_NAME EXT_PATH("subghz/assets/nice_flor_s") +#define SUBGHZ_ALUTECH_AT_4N_DIR_NAME EXT_PATH("subghz/assets/alutech_at_4n") + typedef struct SubGhzProtocolRegistry SubGhzProtocolRegistry; typedef struct SubGhzEnvironment SubGhzEnvironment; diff --git a/site_scons/commandline.scons b/site_scons/commandline.scons index 8ea43ca71..0e75cd908 100644 --- a/site_scons/commandline.scons +++ b/site_scons/commandline.scons @@ -233,6 +233,7 @@ vars.AddVariables( ("applications/debug", False), ("applications/external", False), ("applications/examples", False), + ("applications/drivers", False), ("applications_user", False), ], ), From 3000b8fd0d727d978e0a56fcf579608006c367ca Mon Sep 17 00:00:00 2001 From: gid9798 <30450294+gid9798@users.noreply.github.com> Date: Sun, 18 Jun 2023 20:25:16 +0300 Subject: [PATCH 002/364] prt1 --- firmware/targets/f7/furi_hal/furi_hal_spi.c | 28 +- .../f7/platform_specific/intrinsic_export.h | 2 + lib/subghz/devices/cc1101_configs.h | 314 ++++++++++++++++++ .../cc1101_int/cc1101_int_interconnect.c | 79 +++++ .../cc1101_int/cc1101_int_interconnect.h | 8 + lib/subghz/devices/device_registry.h | 13 + lib/subghz/devices/devices.c | 210 ++++++++++++ lib/subghz/devices/devices.h | 52 +++ lib/subghz/devices/preset.h | 13 + lib/subghz/devices/registry.c | 76 +++++ lib/subghz/devices/registry.h | 40 +++ lib/subghz/devices/types.h | 91 +++++ 12 files changed, 912 insertions(+), 14 deletions(-) create mode 100644 lib/subghz/devices/cc1101_configs.h create mode 100644 lib/subghz/devices/cc1101_int/cc1101_int_interconnect.c create mode 100644 lib/subghz/devices/cc1101_int/cc1101_int_interconnect.h create mode 100644 lib/subghz/devices/device_registry.h create mode 100644 lib/subghz/devices/devices.c create mode 100644 lib/subghz/devices/devices.h create mode 100644 lib/subghz/devices/preset.h create mode 100644 lib/subghz/devices/registry.c create mode 100644 lib/subghz/devices/registry.h create mode 100644 lib/subghz/devices/types.h diff --git a/firmware/targets/f7/furi_hal/furi_hal_spi.c b/firmware/targets/f7/furi_hal/furi_hal_spi.c index 42b854799..17769832b 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_spi.c +++ b/firmware/targets/f7/furi_hal/furi_hal_spi.c @@ -12,10 +12,10 @@ #define TAG "FuriHalSpi" #define SPI_DMA DMA2 -#define SPI_DMA_RX_CHANNEL LL_DMA_CHANNEL_3 -#define SPI_DMA_TX_CHANNEL LL_DMA_CHANNEL_4 -#define SPI_DMA_RX_IRQ FuriHalInterruptIdDma2Ch3 -#define SPI_DMA_TX_IRQ FuriHalInterruptIdDma2Ch4 +#define SPI_DMA_RX_CHANNEL LL_DMA_CHANNEL_6 +#define SPI_DMA_TX_CHANNEL LL_DMA_CHANNEL_7 +#define SPI_DMA_RX_IRQ FuriHalInterruptIdDma2Ch6 +#define SPI_DMA_TX_IRQ FuriHalInterruptIdDma2Ch7 #define SPI_DMA_RX_DEF SPI_DMA, SPI_DMA_RX_CHANNEL #define SPI_DMA_TX_DEF SPI_DMA, SPI_DMA_TX_CHANNEL @@ -170,18 +170,18 @@ bool furi_hal_spi_bus_trx( } static void spi_dma_isr() { -#if SPI_DMA_RX_CHANNEL == LL_DMA_CHANNEL_3 - if(LL_DMA_IsActiveFlag_TC3(SPI_DMA) && LL_DMA_IsEnabledIT_TC(SPI_DMA_RX_DEF)) { - LL_DMA_ClearFlag_TC3(SPI_DMA); +#if SPI_DMA_RX_CHANNEL == LL_DMA_CHANNEL_6 + if(LL_DMA_IsActiveFlag_TC6(SPI_DMA) && LL_DMA_IsEnabledIT_TC(SPI_DMA_RX_DEF)) { + LL_DMA_ClearFlag_TC6(SPI_DMA); furi_check(furi_semaphore_release(spi_dma_completed) == FuriStatusOk); } #else #error Update this code. Would you kindly? #endif -#if SPI_DMA_TX_CHANNEL == LL_DMA_CHANNEL_4 - if(LL_DMA_IsActiveFlag_TC4(SPI_DMA) && LL_DMA_IsEnabledIT_TC(SPI_DMA_TX_DEF)) { - LL_DMA_ClearFlag_TC4(SPI_DMA); +#if SPI_DMA_TX_CHANNEL == LL_DMA_CHANNEL_7 + if(LL_DMA_IsActiveFlag_TC7(SPI_DMA) && LL_DMA_IsEnabledIT_TC(SPI_DMA_TX_DEF)) { + LL_DMA_ClearFlag_TC7(SPI_DMA); furi_check(furi_semaphore_release(spi_dma_completed) == FuriStatusOk); } #else @@ -241,8 +241,8 @@ bool furi_hal_spi_bus_trx_dma( dma_config.Priority = LL_DMA_PRIORITY_MEDIUM; LL_DMA_Init(SPI_DMA_TX_DEF, &dma_config); -#if SPI_DMA_TX_CHANNEL == LL_DMA_CHANNEL_4 - LL_DMA_ClearFlag_TC4(SPI_DMA); +#if SPI_DMA_TX_CHANNEL == LL_DMA_CHANNEL_7 + LL_DMA_ClearFlag_TC7(SPI_DMA); #else #error Update this code. Would you kindly? #endif @@ -315,8 +315,8 @@ bool furi_hal_spi_bus_trx_dma( dma_config.Priority = LL_DMA_PRIORITY_MEDIUM; LL_DMA_Init(SPI_DMA_RX_DEF, &dma_config); -#if SPI_DMA_RX_CHANNEL == LL_DMA_CHANNEL_3 - LL_DMA_ClearFlag_TC3(SPI_DMA); +#if SPI_DMA_RX_CHANNEL == LL_DMA_CHANNEL_6 + LL_DMA_ClearFlag_TC6(SPI_DMA); #else #error Update this code. Would you kindly? #endif diff --git a/firmware/targets/f7/platform_specific/intrinsic_export.h b/firmware/targets/f7/platform_specific/intrinsic_export.h index 8dbc4bd03..ca343a128 100644 --- a/firmware/targets/f7/platform_specific/intrinsic_export.h +++ b/firmware/targets/f7/platform_specific/intrinsic_export.h @@ -1,10 +1,12 @@ #include +#include #ifdef __cplusplus extern "C" { #endif void __clear_cache(void*, void*); +void* __aeabi_uldivmod(uint64_t, uint64_t); #ifdef __cplusplus } diff --git a/lib/subghz/devices/cc1101_configs.h b/lib/subghz/devices/cc1101_configs.h new file mode 100644 index 000000000..6c262e682 --- /dev/null +++ b/lib/subghz/devices/cc1101_configs.h @@ -0,0 +1,314 @@ +#pragma once + +#include + +static const uint8_t subghz_device_cc1101_preset_ook_270khz_async_regs[][2] = { + // https://e2e.ti.com/support/wireless-connectivity/sub-1-ghz-group/sub-1-ghz/f/sub-1-ghz-forum/382066/cc1101---don-t-know-the-correct-registers-configuration + + /* GPIO GD0 */ + {CC1101_IOCFG0, 0x0D}, // GD0 as async serial data output/input + + /* FIFO and internals */ + {CC1101_FIFOTHR, 0x47}, // The only important bit is ADC_RETENTION, FIFO Tx=33 Rx=32 + + /* Packet engine */ + {CC1101_PKTCTRL0, 0x32}, // Async, continious, no whitening + + /* Frequency Synthesizer Control */ + {CC1101_FSCTRL1, 0x06}, // IF = (26*10^6) / (2^10) * 0x06 = 152343.75Hz + + // Modem Configuration + {CC1101_MDMCFG0, 0x00}, // Channel spacing is 25kHz + {CC1101_MDMCFG1, 0x00}, // Channel spacing is 25kHz + {CC1101_MDMCFG2, 0x30}, // Format ASK/OOK, No preamble/sync + {CC1101_MDMCFG3, 0x32}, // Data rate is 3.79372 kBaud + {CC1101_MDMCFG4, 0x67}, // Rx BW filter is 270.833333kHz + + /* Main Radio Control State Machine */ + {CC1101_MCSM0, 0x18}, // Autocalibrate on idle-to-rx/tx, PO_TIMEOUT is 64 cycles(149-155us) + + /* Frequency Offset Compensation Configuration */ + {CC1101_FOCCFG, + 0x18}, // no frequency offset compensation, POST_K same as PRE_K, PRE_K is 4K, GATE is off + + /* Automatic Gain Control */ + {CC1101_AGCCTRL0, + 0x40}, // 01 - Low hysteresis, small asymmetric dead zone, medium gain; 00 - 8 samples agc; 00 - Normal AGC, 00 - 4dB boundary + {CC1101_AGCCTRL1, + 0x00}, // 0; 0 - LNA 2 gain is decreased to minimum before decreasing LNA gain; 00 - Relative carrier sense threshold disabled; 0000 - RSSI to MAIN_TARGET + {CC1101_AGCCTRL2, 0x03}, // 00 - DVGA all; 000 - MAX LNA+LNA2; 011 - MAIN_TARGET 24 dB + + /* Wake on radio and timeouts control */ + {CC1101_WORCTRL, 0xFB}, // WOR_RES is 2^15 periods (0.91 - 0.94 s) 16.5 - 17.2 hours + + /* Frontend configuration */ + {CC1101_FREND0, 0x11}, // Adjusts current TX LO buffer + high is PATABLE[1] + {CC1101_FREND1, 0xB6}, // + + /* End */ + {0, 0}, +}; + +static const uint8_t subghz_device_cc1101_preset_ook_650khz_async_regs[][2] = { + // https://e2e.ti.com/support/wireless-connectivity/sub-1-ghz-group/sub-1-ghz/f/sub-1-ghz-forum/382066/cc1101---don-t-know-the-correct-registers-configuration + + /* GPIO GD0 */ + {CC1101_IOCFG0, 0x0D}, // GD0 as async serial data output/input + + /* FIFO and internals */ + {CC1101_FIFOTHR, 0x07}, // The only important bit is ADC_RETENTION + + /* Packet engine */ + {CC1101_PKTCTRL0, 0x32}, // Async, continious, no whitening + + /* Frequency Synthesizer Control */ + {CC1101_FSCTRL1, 0x06}, // IF = (26*10^6) / (2^10) * 0x06 = 152343.75Hz + + // Modem Configuration + {CC1101_MDMCFG0, 0x00}, // Channel spacing is 25kHz + {CC1101_MDMCFG1, 0x00}, // Channel spacing is 25kHz + {CC1101_MDMCFG2, 0x30}, // Format ASK/OOK, No preamble/sync + {CC1101_MDMCFG3, 0x32}, // Data rate is 3.79372 kBaud + {CC1101_MDMCFG4, 0x17}, // Rx BW filter is 650.000kHz + + /* Main Radio Control State Machine */ + {CC1101_MCSM0, 0x18}, // Autocalibrate on idle-to-rx/tx, PO_TIMEOUT is 64 cycles(149-155us) + + /* Frequency Offset Compensation Configuration */ + {CC1101_FOCCFG, + 0x18}, // no frequency offset compensation, POST_K same as PRE_K, PRE_K is 4K, GATE is off + + /* Automatic Gain Control */ + // {CC1101_AGCTRL0,0x40}, // 01 - Low hysteresis, small asymmetric dead zone, medium gain; 00 - 8 samples agc; 00 - Normal AGC, 00 - 4dB boundary + // {CC1101_AGCTRL1,0x00}, // 0; 0 - LNA 2 gain is decreased to minimum before decreasing LNA gain; 00 - Relative carrier sense threshold disabled; 0000 - RSSI to MAIN_TARGET + // {CC1101_AGCCTRL2, 0x03}, // 00 - DVGA all; 000 - MAX LNA+LNA2; 011 - MAIN_TARGET 24 dB + //MAGN_TARGET for RX filter BW =< 100 kHz is 0x3. For higher RX filter BW's MAGN_TARGET is 0x7. + {CC1101_AGCCTRL0, + 0x91}, // 10 - Medium hysteresis, medium asymmetric dead zone, medium gain ; 01 - 16 samples agc; 00 - Normal AGC, 01 - 8dB boundary + {CC1101_AGCCTRL1, + 0x0}, // 0; 0 - LNA 2 gain is decreased to minimum before decreasing LNA gain; 00 - Relative carrier sense threshold disabled; 0000 - RSSI to MAIN_TARGET + {CC1101_AGCCTRL2, 0x07}, // 00 - DVGA all; 000 - MAX LNA+LNA2; 111 - MAIN_TARGET 42 dB + + /* Wake on radio and timeouts control */ + {CC1101_WORCTRL, 0xFB}, // WOR_RES is 2^15 periods (0.91 - 0.94 s) 16.5 - 17.2 hours + + /* Frontend configuration */ + {CC1101_FREND0, 0x11}, // Adjusts current TX LO buffer + high is PATABLE[1] + {CC1101_FREND1, 0xB6}, // + + /* End */ + {0, 0}, +}; + +static const uint8_t subghz_device_cc1101_preset_2fsk_dev2_38khz_async_regs[][2] = { + + /* GPIO GD0 */ + {CC1101_IOCFG0, 0x0D}, // GD0 as async serial data output/input + + /* Frequency Synthesizer Control */ + {CC1101_FSCTRL1, 0x06}, // IF = (26*10^6) / (2^10) * 0x06 = 152343.75Hz + + /* Packet engine */ + {CC1101_PKTCTRL0, 0x32}, // Async, continious, no whitening + {CC1101_PKTCTRL1, 0x04}, + + // // Modem Configuration + {CC1101_MDMCFG0, 0x00}, + {CC1101_MDMCFG1, 0x02}, + {CC1101_MDMCFG2, 0x04}, // Format 2-FSK/FM, No preamble/sync, Disable (current optimized) + {CC1101_MDMCFG3, 0x83}, // Data rate is 4.79794 kBaud + {CC1101_MDMCFG4, 0x67}, //Rx BW filter is 270.833333 kHz + {CC1101_DEVIATN, 0x04}, //Deviation 2.380371 kHz + + /* Main Radio Control State Machine */ + {CC1101_MCSM0, 0x18}, // Autocalibrate on idle-to-rx/tx, PO_TIMEOUT is 64 cycles(149-155us) + + /* Frequency Offset Compensation Configuration */ + {CC1101_FOCCFG, + 0x16}, // no frequency offset compensation, POST_K same as PRE_K, PRE_K is 4K, GATE is off + + /* Automatic Gain Control */ + {CC1101_AGCCTRL0, + 0x91}, //10 - Medium hysteresis, medium asymmetric dead zone, medium gain ; 01 - 16 samples agc; 00 - Normal AGC, 01 - 8dB boundary + {CC1101_AGCCTRL1, + 0x00}, // 0; 0 - LNA 2 gain is decreased to minimum before decreasing LNA gain; 00 - Relative carrier sense threshold disabled; 0000 - RSSI to MAIN_TARGET + {CC1101_AGCCTRL2, 0x07}, // 00 - DVGA all; 000 - MAX LNA+LNA2; 111 - MAIN_TARGET 42 dB + + /* Wake on radio and timeouts control */ + {CC1101_WORCTRL, 0xFB}, // WOR_RES is 2^15 periods (0.91 - 0.94 s) 16.5 - 17.2 hours + + /* Frontend configuration */ + {CC1101_FREND0, 0x10}, // Adjusts current TX LO buffer + {CC1101_FREND1, 0x56}, + + /* End */ + {0, 0}, +}; + +static const uint8_t subghz_device_cc1101_preset_2fsk_dev47_6khz_async_regs[][2] = { + + /* GPIO GD0 */ + {CC1101_IOCFG0, 0x0D}, // GD0 as async serial data output/input + + /* Frequency Synthesizer Control */ + {CC1101_FSCTRL1, 0x06}, // IF = (26*10^6) / (2^10) * 0x06 = 152343.75Hz + + /* Packet engine */ + {CC1101_PKTCTRL0, 0x32}, // Async, continious, no whitening + {CC1101_PKTCTRL1, 0x04}, + + // // Modem Configuration + {CC1101_MDMCFG0, 0x00}, + {CC1101_MDMCFG1, 0x02}, + {CC1101_MDMCFG2, 0x04}, // Format 2-FSK/FM, No preamble/sync, Disable (current optimized) + {CC1101_MDMCFG3, 0x83}, // Data rate is 4.79794 kBaud + {CC1101_MDMCFG4, 0x67}, //Rx BW filter is 270.833333 kHz + {CC1101_DEVIATN, 0x47}, //Deviation 47.60742 kHz + + /* Main Radio Control State Machine */ + {CC1101_MCSM0, 0x18}, // Autocalibrate on idle-to-rx/tx, PO_TIMEOUT is 64 cycles(149-155us) + + /* Frequency Offset Compensation Configuration */ + {CC1101_FOCCFG, + 0x16}, // no frequency offset compensation, POST_K same as PRE_K, PRE_K is 4K, GATE is off + + /* Automatic Gain Control */ + {CC1101_AGCCTRL0, + 0x91}, //10 - Medium hysteresis, medium asymmetric dead zone, medium gain ; 01 - 16 samples agc; 00 - Normal AGC, 01 - 8dB boundary + {CC1101_AGCCTRL1, + 0x00}, // 0; 0 - LNA 2 gain is decreased to minimum before decreasing LNA gain; 00 - Relative carrier sense threshold disabled; 0000 - RSSI to MAIN_TARGET + {CC1101_AGCCTRL2, 0x07}, // 00 - DVGA all; 000 - MAX LNA+LNA2; 111 - MAIN_TARGET 42 dB + + /* Wake on radio and timeouts control */ + {CC1101_WORCTRL, 0xFB}, // WOR_RES is 2^15 periods (0.91 - 0.94 s) 16.5 - 17.2 hours + + /* Frontend configuration */ + {CC1101_FREND0, 0x10}, // Adjusts current TX LO buffer + {CC1101_FREND1, 0x56}, + + /* End */ + {0, 0}, +}; + +static const uint8_t subghz_device_cc1101_preset_msk_99_97kb_async_regs[][2] = { + /* GPIO GD0 */ + {CC1101_IOCFG0, 0x06}, + + {CC1101_FIFOTHR, 0x07}, // The only important bit is ADC_RETENTION + {CC1101_SYNC1, 0x46}, + {CC1101_SYNC0, 0x4C}, + {CC1101_ADDR, 0x00}, + {CC1101_PKTLEN, 0x00}, + {CC1101_CHANNR, 0x00}, + + {CC1101_PKTCTRL0, 0x05}, + + {CC1101_FSCTRL0, 0x23}, + {CC1101_FSCTRL1, 0x06}, + + {CC1101_MDMCFG0, 0xF8}, + {CC1101_MDMCFG1, 0x22}, + {CC1101_MDMCFG2, 0x72}, + {CC1101_MDMCFG3, 0xF8}, + {CC1101_MDMCFG4, 0x5B}, + {CC1101_DEVIATN, 0x47}, + + {CC1101_MCSM0, 0x18}, + {CC1101_FOCCFG, 0x16}, + + {CC1101_AGCCTRL0, 0xB2}, + {CC1101_AGCCTRL1, 0x00}, + {CC1101_AGCCTRL2, 0xC7}, + + {CC1101_FREND0, 0x10}, + {CC1101_FREND1, 0x56}, + + {CC1101_BSCFG, 0x1C}, + {CC1101_FSTEST, 0x59}, + + /* End */ + {0, 0}, +}; + +static const uint8_t subghz_device_cc1101_preset_gfsk_9_99kb_async_regs[][2] = { + + {CC1101_IOCFG0, 0x06}, //GDO0 Output Pin Configuration + {CC1101_FIFOTHR, 0x47}, //RX FIFO and TX FIFO Thresholds + + //1 : CRC calculation in TX and CRC check in RX enabled, + //1 : Variable packet length mode. Packet length configured by the first byte after sync word + {CC1101_PKTCTRL0, 0x05}, + + {CC1101_FSCTRL1, 0x06}, //Frequency Synthesizer Control + + {CC1101_SYNC1, 0x46}, + {CC1101_SYNC0, 0x4C}, + {CC1101_ADDR, 0x00}, + {CC1101_PKTLEN, 0x00}, + + {CC1101_MDMCFG4, 0xC8}, //Modem Configuration 9.99 + {CC1101_MDMCFG3, 0x93}, //Modem Configuration + {CC1101_MDMCFG2, 0x12}, // 2: 16/16 sync word bits detected + + {CC1101_DEVIATN, 0x34}, //Deviation = 19.042969 + {CC1101_MCSM0, 0x18}, //Main Radio Control State Machine Configuration + {CC1101_FOCCFG, 0x16}, //Frequency Offset Compensation Configuration + + {CC1101_AGCCTRL2, 0x43}, //AGC Control + {CC1101_AGCCTRL1, 0x40}, + {CC1101_AGCCTRL0, 0x91}, + + {CC1101_WORCTRL, 0xFB}, //Wake On Radio Control + /* End */ + {0, 0}, +}; + +static const uint8_t subghz_device_cc1101_preset_ook_async_patable[8] = { + 0x00, + 0xC0, // 12dBm 0xC0, 10dBm 0xC5, 7dBm 0xCD, 5dBm 0x86, 0dBm 0x50, -6dBm 0x37, -10dBm 0x26, -15dBm 0x1D, -20dBm 0x17, -30dBm 0x03 + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00}; + +static const uint8_t subghz_device_cc1101_preset_ook_async_patable_au[8] = { + 0x00, + 0x37, // 12dBm 0xC0, 10dBm 0xC5, 7dBm 0xCD, 5dBm 0x86, 0dBm 0x50, -6dBm 0x37, -10dBm 0x26, -15dBm 0x1D, -20dBm 0x17, -30dBm 0x03 + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00}; + +static const uint8_t subghz_device_cc1101_preset_2fsk_async_patable[8] = { + 0xC0, // 10dBm 0xC0, 7dBm 0xC8, 5dBm 0x84, 0dBm 0x60, -10dBm 0x34, -15dBm 0x1D, -20dBm 0x0E, -30dBm 0x12 + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00}; + +static const uint8_t subghz_device_cc1101_preset_msk_async_patable[8] = { + 0xC0, // 10dBm 0xC0, 7dBm 0xC8, 5dBm 0x84, 0dBm 0x60, -10dBm 0x34, -15dBm 0x1D, -20dBm 0x0E, -30dBm 0x12 + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00}; + +static const uint8_t subghz_device_cc1101_preset_gfsk_async_patable[8] = { + 0xC0, // 10dBm 0xC0, 7dBm 0xC8, 5dBm 0x84, 0dBm 0x60, -10dBm 0x34, -15dBm 0x1D, -20dBm 0x0E, -30dBm 0x12 + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00}; diff --git a/lib/subghz/devices/cc1101_int/cc1101_int_interconnect.c b/lib/subghz/devices/cc1101_int/cc1101_int_interconnect.c new file mode 100644 index 000000000..995a4b71d --- /dev/null +++ b/lib/subghz/devices/cc1101_int/cc1101_int_interconnect.c @@ -0,0 +1,79 @@ +#include "cc1101_int_interconnect.h" +#include + +#define TAG "SubGhzDeviceCC1101Int" + +static bool subghz_device_cc1101_int_interconnect_is_frequency_valid(uint32_t frequency) { + bool ret = furi_hal_subghz_is_frequency_valid(frequency); + if(!ret) { + furi_crash("SubGhz: Incorrect frequency."); + } + return ret; +} + +static uint32_t subghz_device_cc1101_int_interconnect_set_frequency(uint32_t frequency) { + subghz_device_cc1101_int_interconnect_is_frequency_valid(frequency); + return furi_hal_subghz_set_frequency_and_path(frequency); +} + +static bool subghz_device_cc1101_int_interconnect_start_async_tx(void* callback, void* context) { + return furi_hal_subghz_start_async_tx( + (FuriHalSubGhzAsyncTxCallback)callback, context); +} + +static void subghz_device_cc1101_int_interconnect_start_async_rx(void* callback, void* context) { + furi_hal_subghz_start_async_rx( + (FuriHalSubGhzCaptureCallback)callback, context); +} + +static void subghz_device_cc1101_int_interconnect_load_preset( + FuriHalSubGhzPreset preset, + uint8_t* preset_data) { + if(preset != FuriHalSubGhzPresetCustom) { + furi_hal_subghz_load_preset(preset); + } else { + furi_hal_subghz_load_custom_preset(preset_data); + } +} + +static bool subghz_device_cc1101_int_interconnect_is_connect(void) { + return true; +} + +const SubGhzDeviceInterconnect subghz_device_cc1101_int_interconnect = { + .begin = NULL, + .end = furi_hal_subghz_shutdown, + .is_connect = subghz_device_cc1101_int_interconnect_is_connect, + .reset = furi_hal_subghz_reset, + .sleep = furi_hal_subghz_sleep, + .idle = furi_hal_subghz_idle, + .load_preset = subghz_device_cc1101_int_interconnect_load_preset, + .set_frequency = subghz_device_cc1101_int_interconnect_set_frequency, + .is_frequency_valid = furi_hal_subghz_is_frequency_valid, + .set_async_mirror_pin = furi_hal_subghz_set_async_mirror_pin, + .get_data_gpio = furi_hal_subghz_get_data_gpio, + + .set_tx = furi_hal_subghz_tx, + .flush_tx = furi_hal_subghz_flush_tx, + .start_async_tx = subghz_device_cc1101_int_interconnect_start_async_tx, + .is_async_complete_tx = furi_hal_subghz_is_async_tx_complete, + .stop_async_tx = furi_hal_subghz_stop_async_tx, + + .set_rx = furi_hal_subghz_rx, + .flush_rx = furi_hal_subghz_flush_rx, + .start_async_rx = subghz_device_cc1101_int_interconnect_start_async_rx, + .stop_async_rx = furi_hal_subghz_stop_async_rx, + + .get_rssi = furi_hal_subghz_get_rssi, + .get_lqi = furi_hal_subghz_get_lqi, + + .rx_pipe_not_empty = furi_hal_subghz_rx_pipe_not_empty, + .is_rx_data_crc_valid = furi_hal_subghz_is_rx_data_crc_valid, + .read_packet = furi_hal_subghz_read_packet, + .write_packet = furi_hal_subghz_write_packet, +}; + +const SubGhzDevice subghz_device_cc1101_int = { + .name = SUBGHZ_DEVICE_CC1101_INT_NAME, + .interconnect = &subghz_device_cc1101_int_interconnect, +}; diff --git a/lib/subghz/devices/cc1101_int/cc1101_int_interconnect.h b/lib/subghz/devices/cc1101_int/cc1101_int_interconnect.h new file mode 100644 index 000000000..629d1264c --- /dev/null +++ b/lib/subghz/devices/cc1101_int/cc1101_int_interconnect.h @@ -0,0 +1,8 @@ +#pragma once +#include "../types.h" + +#define SUBGHZ_DEVICE_CC1101_INT_NAME "cc1101_int" + +typedef struct SubGhzDeviceCC1101Int SubGhzDeviceCC1101Int; + +extern const SubGhzDevice subghz_device_cc1101_int; diff --git a/lib/subghz/devices/device_registry.h b/lib/subghz/devices/device_registry.h new file mode 100644 index 000000000..70a0db4b2 --- /dev/null +++ b/lib/subghz/devices/device_registry.h @@ -0,0 +1,13 @@ +#pragma once + +#include "registry.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern const SubGhzDeviceRegistry subghz_device_registry; + +#ifdef __cplusplus +} +#endif diff --git a/lib/subghz/devices/devices.c b/lib/subghz/devices/devices.c new file mode 100644 index 000000000..69946d0f1 --- /dev/null +++ b/lib/subghz/devices/devices.c @@ -0,0 +1,210 @@ +#include "devices.h" + +#include "registry.h" + +void subghz_devices_init() { + furi_check(!subghz_device_registry_is_valid()); + subghz_device_registry_init(); +} + +void subghz_devices_deinit(void) { + furi_check(subghz_device_registry_is_valid()); + subghz_device_registry_deinit(); +} + +const SubGhzDevice* subghz_devices_get_by_name(const char* device_name) { + furi_check(subghz_device_registry_is_valid()); + const SubGhzDevice* device = subghz_device_registry_get_by_name(device_name); + return device; +} + +const char* subghz_devices_get_name(const SubGhzDevice* device) { + const char* ret = NULL; + if(device) { + ret = device->name; + } + return ret; +} + +bool subghz_devices_begin(const SubGhzDevice* device) { + bool ret = false; + if(device && device->interconnect->begin) { + ret = device->interconnect->begin(); + } + return ret; +} + +void subghz_devices_end(const SubGhzDevice* device) { + if(device && device->interconnect->end) { + device->interconnect->end(); + } +} + +bool subghz_devices_is_connect(const SubGhzDevice* device) { + bool ret = false; + if(device && device->interconnect->is_connect) { + ret = device->interconnect->is_connect(); + } + return ret; +} + +void subghz_devices_reset(const SubGhzDevice* device) { + if(device && device->interconnect->reset) { + device->interconnect->reset(); + } +} + +void subghz_devices_sleep(const SubGhzDevice* device) { + if(device && device->interconnect->sleep) { + device->interconnect->sleep(); + } +} + +void subghz_devices_idle(const SubGhzDevice* device) { + if(device && device->interconnect->idle) { + device->interconnect->idle(); + } +} + +void subghz_devices_load_preset( + const SubGhzDevice* device, + FuriHalSubGhzPreset preset, + uint8_t* preset_data) { + if(device && device->interconnect->load_preset) { + device->interconnect->load_preset(preset, preset_data); + } +} + +uint32_t subghz_devices_set_frequency(const SubGhzDevice* device, uint32_t frequency) { + uint32_t ret = 0; + if(device && device->interconnect->set_frequency) { + ret = device->interconnect->set_frequency(frequency); + } + return ret; +} + +bool subghz_devices_is_frequency_valid(const SubGhzDevice* device, uint32_t frequency) { + bool ret = false; + if(device && device->interconnect->is_frequency_valid) { + ret = device->interconnect->is_frequency_valid(frequency); + } + return ret; +} + +void subghz_devices_set_async_mirror_pin(const SubGhzDevice* device, const GpioPin* gpio) { + if(device && device->interconnect->set_async_mirror_pin) { + device->interconnect->set_async_mirror_pin(gpio); + } +} + +const GpioPin* subghz_devices_get_data_gpio(const SubGhzDevice* device) { + const GpioPin* ret = NULL; + if(device && device->interconnect->get_data_gpio) { + ret = device->interconnect->get_data_gpio(); + } + return ret; +} + +bool subghz_devices_set_tx(const SubGhzDevice* device) { + bool ret = 0; + if(device && device->interconnect->set_tx) { + ret = device->interconnect->set_tx(); + } + return ret; +} + +void subghz_devices_flush_tx(const SubGhzDevice* device) { + if(device && device->interconnect->flush_tx) { + device->interconnect->flush_tx(); + } +} + +bool subghz_devices_start_async_tx(const SubGhzDevice* device, void* callback, void* context) { + bool ret = false; + if(device && device->interconnect->start_async_tx) { + ret = device->interconnect->start_async_tx(callback, context); + } + return ret; +} + +bool subghz_devices_is_async_complete_tx(const SubGhzDevice* device) { + bool ret = false; + if(device && device->interconnect->is_async_complete_tx) { + ret = device->interconnect->is_async_complete_tx(); + } + return ret; +} + +void subghz_devices_stop_async_tx(const SubGhzDevice* device) { + if(device && device->interconnect->stop_async_tx) { + device->interconnect->stop_async_tx(); + } +} + +void subghz_devices_set_rx(const SubGhzDevice* device) { + if(device && device->interconnect->set_rx) { + device->interconnect->set_rx(); + } +} + +void subghz_devices_flush_rx(const SubGhzDevice* device) { + if(device && device->interconnect->flush_rx) { + device->interconnect->flush_rx(); + } +} + +void subghz_devices_start_async_rx(const SubGhzDevice* device, void* callback, void* context) { + if(device && device->interconnect->start_async_rx) { + device->interconnect->start_async_rx(callback, context); + } +} + +void subghz_devices_stop_async_rx(const SubGhzDevice* device) { + if(device && device->interconnect->stop_async_rx) { + device->interconnect->stop_async_rx(); + } +} + +float subghz_devices_get_rssi(const SubGhzDevice* device) { + float ret = 0; + if(device && device->interconnect->get_rssi) { + ret = device->interconnect->get_rssi(); + } + return ret; +} + +uint8_t subghz_devices_get_lqi(const SubGhzDevice* device) { + uint8_t ret = 0; + if(device && device->interconnect->get_lqi) { + ret = device->interconnect->get_lqi(); + } + return ret; +} + +bool subghz_devices_rx_pipe_not_empty(const SubGhzDevice* device) { + bool ret = false; + if(device && device->interconnect->rx_pipe_not_empty) { + ret = device->interconnect->rx_pipe_not_empty(); + } + return ret; +} + +bool subghz_devices_is_rx_data_crc_valid(const SubGhzDevice* device) { + bool ret = false; + if(device && device->interconnect->is_rx_data_crc_valid) { + ret = device->interconnect->is_rx_data_crc_valid(); + } + return ret; +} + +void subghz_devices_read_packet(const SubGhzDevice* device, uint8_t* data, uint8_t* size) { + if(device && device->interconnect->read_packet) { + device->interconnect->read_packet(data, size); + } +} + +void subghz_devices_write_packet(const SubGhzDevice* device, const uint8_t* data, uint8_t size) { + if(device && device->interconnect->write_packet) { + device->interconnect->write_packet(data, size); + } +} diff --git a/lib/subghz/devices/devices.h b/lib/subghz/devices/devices.h new file mode 100644 index 000000000..dad3c9aeb --- /dev/null +++ b/lib/subghz/devices/devices.h @@ -0,0 +1,52 @@ +#pragma once + +#include "types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct SubGhzDevice SubGhzDevice; + +void subghz_devices_init(); +void subghz_devices_deinit(void); + +const SubGhzDevice* subghz_devices_get_by_name(const char* device_name); +const char* subghz_devices_get_name(const SubGhzDevice* device); +bool subghz_devices_begin(const SubGhzDevice* device); +void subghz_devices_end(const SubGhzDevice* device); +bool subghz_devices_is_connect(const SubGhzDevice* device); +void subghz_devices_reset(const SubGhzDevice* device); +void subghz_devices_sleep(const SubGhzDevice* device); +void subghz_devices_idle(const SubGhzDevice* device); +void subghz_devices_load_preset( + const SubGhzDevice* device, + FuriHalSubGhzPreset preset, + uint8_t* preset_data); +uint32_t subghz_devices_set_frequency(const SubGhzDevice* device, uint32_t frequency); +bool subghz_devices_is_frequency_valid(const SubGhzDevice* device, uint32_t frequency); +void subghz_devices_set_async_mirror_pin(const SubGhzDevice* device, const GpioPin* gpio); +const GpioPin* subghz_devices_get_data_gpio(const SubGhzDevice* device); + +bool subghz_devices_set_tx(const SubGhzDevice* device); +void subghz_devices_flush_tx(const SubGhzDevice* device); +bool subghz_devices_start_async_tx(const SubGhzDevice* device, void* callback, void* context); +bool subghz_devices_is_async_complete_tx(const SubGhzDevice* device); +void subghz_devices_stop_async_tx(const SubGhzDevice* device); + +void subghz_devices_set_rx(const SubGhzDevice* device); +void subghz_devices_flush_rx(const SubGhzDevice* device); +void subghz_devices_start_async_rx(const SubGhzDevice* device, void* callback, void* context); +void subghz_devices_stop_async_rx(const SubGhzDevice* device); + +float subghz_devices_get_rssi(const SubGhzDevice* device); +uint8_t subghz_devices_get_lqi(const SubGhzDevice* device); + +bool subghz_devices_rx_pipe_not_empty(const SubGhzDevice* device); +bool subghz_devices_is_rx_data_crc_valid(const SubGhzDevice* device); +void subghz_devices_read_packet(const SubGhzDevice* device, uint8_t* data, uint8_t* size); +void subghz_devices_write_packet(const SubGhzDevice* device, const uint8_t* data, uint8_t size); + +#ifdef __cplusplus +} +#endif diff --git a/lib/subghz/devices/preset.h b/lib/subghz/devices/preset.h new file mode 100644 index 000000000..8716f2e23 --- /dev/null +++ b/lib/subghz/devices/preset.h @@ -0,0 +1,13 @@ +#pragma once + +/** Radio Presets */ +typedef enum { + FuriHalSubGhzPresetIDLE, /**< default configuration */ + FuriHalSubGhzPresetOok270Async, /**< OOK, bandwidth 270kHz, asynchronous */ + FuriHalSubGhzPresetOok650Async, /**< OOK, bandwidth 650kHz, asynchronous */ + FuriHalSubGhzPreset2FSKDev238Async, /**< FM, deviation 2.380371 kHz, asynchronous */ + FuriHalSubGhzPreset2FSKDev476Async, /**< FM, deviation 47.60742 kHz, asynchronous */ + FuriHalSubGhzPresetMSK99_97KbAsync, /**< MSK, deviation 47.60742 kHz, 99.97Kb/s, asynchronous */ + FuriHalSubGhzPresetGFSK9_99KbAsync, /**< GFSK, deviation 19.042969 kHz, 9.996Kb/s, asynchronous */ + FuriHalSubGhzPresetCustom, /**Custom Preset*/ +} FuriHalSubGhzPreset; diff --git a/lib/subghz/devices/registry.c b/lib/subghz/devices/registry.c new file mode 100644 index 000000000..c0d5bb292 --- /dev/null +++ b/lib/subghz/devices/registry.c @@ -0,0 +1,76 @@ +#include "registry.h" + +#include "cc1101_int/cc1101_int_interconnect.h" +#include +#include + +#define TAG "SubGhzDeviceRegistry" + +struct SubGhzDeviceRegistry { + const SubGhzDevice** items; + size_t size; + PluginManager* manager; +}; + +static SubGhzDeviceRegistry* subghz_device_registry = NULL; + +void subghz_device_registry_init(void) { + SubGhzDeviceRegistry* subghz_device = + (SubGhzDeviceRegistry*)malloc(sizeof(SubGhzDeviceRegistry)); + subghz_device->manager = plugin_manager_alloc( + SUBGHZ_RADIO_DEVICE_PLUGIN_APP_ID, + SUBGHZ_RADIO_DEVICE_PLUGIN_API_VERSION, + firmware_api_interface); + + //ToDo: fix path to plugins + if(plugin_manager_load_all(subghz_device->manager, "/any/apps_data/subghz/plugins") != + //if(plugin_manager_load_all(subghz_device->manager, APP_DATA_PATH("plugins")) != + PluginManagerErrorNone) { + FURI_LOG_E(TAG, "Failed to load all libs"); + } + + subghz_device->size = plugin_manager_get_count(subghz_device->manager) + 1; + subghz_device->items = + (const SubGhzDevice**)malloc(sizeof(SubGhzDevice*) * subghz_device->size); + subghz_device->items[0] = &subghz_device_cc1101_int; + for(uint32_t i = 1; i < subghz_device->size; i++) { + const SubGhzDevice* plugin = plugin_manager_get_ep(subghz_device->manager, i - 1); + subghz_device->items[i] = plugin; + } + + FURI_LOG_I(TAG, "Loaded %zu radio device", subghz_device->size); + subghz_device_registry = subghz_device; +} + +void subghz_device_registry_deinit(void) { + plugin_manager_free(subghz_device_registry->manager); + free(subghz_device_registry->items); + free(subghz_device_registry); + subghz_device_registry = NULL; +} + +bool subghz_device_registry_is_valid(void) { + return subghz_device_registry != NULL; +} + +const SubGhzDevice* subghz_device_registry_get_by_name(const char* name) { + furi_assert(subghz_device_registry); + + if(name != NULL) { + for(size_t i = 0; i < subghz_device_registry->size; i++) { + if(strcmp(name, subghz_device_registry->items[i]->name) == 0) { + return subghz_device_registry->items[i]; + } + } + } + return NULL; +} + +const SubGhzDevice* subghz_device_registry_get_by_index(size_t index) { + furi_assert(subghz_device_registry); + if(index < subghz_device_registry->size) { + return subghz_device_registry->items[index]; + } else { + return NULL; + } +} diff --git a/lib/subghz/devices/registry.h b/lib/subghz/devices/registry.h new file mode 100644 index 000000000..520058920 --- /dev/null +++ b/lib/subghz/devices/registry.h @@ -0,0 +1,40 @@ +#pragma once + +#include "types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct SubGhzDevice SubGhzDevice; + +void subghz_device_registry_init(void); + +void subghz_device_registry_deinit(void); + +bool subghz_device_registry_is_valid(void); + +/** + * Registration by name SubGhzDevice. + * @param name SubGhzDevice name + * @return SubGhzDevice* pointer to a SubGhzDevice instance + */ +const SubGhzDevice* subghz_device_registry_get_by_name(const char* name); + +/** + * Registration subghzdevice by index in array SubGhzDevice. + * @param index SubGhzDevice by index in array + * @return SubGhzDevice* pointer to a SubGhzDevice instance + */ +const SubGhzDevice* subghz_device_registry_get_by_index(size_t index); + +/** + * Getting the number of registered subghzdevices. + * @param subghz_device SubGhzDeviceRegistry + * @return Number of subghzdevices + */ +size_t subghz_device_registry_count(void); + +#ifdef __cplusplus +} +#endif diff --git a/lib/subghz/devices/types.h b/lib/subghz/devices/types.h new file mode 100644 index 000000000..8a4198426 --- /dev/null +++ b/lib/subghz/devices/types.h @@ -0,0 +1,91 @@ +#pragma once + +#include +#include +#include + +#include +#include + +#include "preset.h" + +#include + +#define SUBGHZ_RADIO_DEVICE_PLUGIN_APP_ID "subghz_radio_device" +#define SUBGHZ_RADIO_DEVICE_PLUGIN_API_VERSION 1 + +typedef struct SubGhzDeviceRegistry SubGhzDeviceRegistry; +typedef struct SubGhzDevice SubGhzDevice; + +typedef bool (*SubGhzBegin)(void); +typedef void (*SubGhzEnd)(void); +typedef bool (*SubGhzIsConnect)(void); +typedef void (*SubGhzReset)(void); +typedef void (*SubGhzSleep)(void); +typedef void (*SubGhzIdle)(void); +typedef void (*SubGhzLoadPreset)(FuriHalSubGhzPreset preset, uint8_t* preset_data); +typedef uint32_t (*SubGhzSetFrequency)(uint32_t frequency); +typedef bool (*SubGhzIsFrequencyValid)(uint32_t frequency); + +typedef void (*SubGhzSetAsyncMirrorPin)(const GpioPin* gpio); +typedef const GpioPin* (*SubGhzGetDataGpio)(void); + +typedef bool (*SubGhzSetTx)(void); +typedef void (*SubGhzFlushTx)(void); +typedef bool (*SubGhzStartAsyncTx)(void* callback, void* context); +typedef bool (*SubGhzIsAsyncCompleteTx)(void); +typedef void (*SubGhzStopAsyncTx)(void); + +typedef void (*SubGhzSetRx)(void); +typedef void (*SubGhzFlushRx)(void); +typedef void (*SubGhzStartAsyncRx)(void* callback, void* context); +typedef void (*SubGhzStopAsyncRx)(void); + +typedef float (*SubGhzGetRSSI)(void); +typedef uint8_t (*SubGhzGetLQI)(void); + +typedef bool (*SubGhzRxPipeNotEmpty)(void); +typedef bool (*SubGhzRxIsDataCrcValid)(void); +typedef void (*SubGhzReadPacket)(uint8_t* data, uint8_t* size); +typedef void (*SubGhzWritePacket)(const uint8_t* data, uint8_t size); + +typedef struct { + SubGhzBegin begin; + SubGhzEnd end; + + SubGhzIsConnect is_connect; + SubGhzReset reset; + SubGhzSleep sleep; + SubGhzIdle idle; + + SubGhzLoadPreset load_preset; + SubGhzSetFrequency set_frequency; + SubGhzIsFrequencyValid is_frequency_valid; + SubGhzSetAsyncMirrorPin set_async_mirror_pin; + SubGhzGetDataGpio get_data_gpio; + + SubGhzSetTx set_tx; + SubGhzFlushTx flush_tx; + SubGhzStartAsyncTx start_async_tx; + SubGhzIsAsyncCompleteTx is_async_complete_tx; + SubGhzStopAsyncTx stop_async_tx; + + SubGhzSetRx set_rx; + SubGhzFlushRx flush_rx; + SubGhzStartAsyncRx start_async_rx; + SubGhzStopAsyncRx stop_async_rx; + + SubGhzGetRSSI get_rssi; + SubGhzGetLQI get_lqi; + + SubGhzRxPipeNotEmpty rx_pipe_not_empty; + SubGhzRxIsDataCrcValid is_rx_data_crc_valid; + SubGhzReadPacket read_packet; + SubGhzWritePacket write_packet; + +} SubGhzDeviceInterconnect; + +struct SubGhzDevice { + const char* name; + const SubGhzDeviceInterconnect* interconnect; +}; From 5eb677aa55b705aec244a4c288ba5dca0264b736 Mon Sep 17 00:00:00 2001 From: gid9798 <30450294+gid9798@users.noreply.github.com> Date: Sun, 18 Jun 2023 20:25:40 +0300 Subject: [PATCH 003/364] prt2 --- .../debug/unit_tests/subghz/subghz_test.c | 8 +- .../main/subghz/helpers/subghz_chat.c | 7 +- .../main/subghz/helpers/subghz_chat.h | 6 +- .../subghz/helpers/subghz_threshold_rssi.c | 3 +- .../subghz/helpers/subghz_threshold_rssi.h | 3 +- .../main/subghz/helpers/subghz_txrx.c | 157 ++++-- .../main/subghz/helpers/subghz_txrx.h | 46 ++ .../main/subghz/helpers/subghz_txrx_i.h | 6 +- .../main/subghz/helpers/subghz_types.h | 7 + .../main/subghz/scenes/subghz_scene_config.h | 1 + .../subghz/scenes/subghz_scene_decode_raw.c | 4 +- .../scenes/subghz_scene_radio_setting.c | 65 +++ .../scenes/subghz_scene_radio_settings.c | 122 ++-- .../subghz/scenes/subghz_scene_read_raw.c | 11 +- .../subghz/scenes/subghz_scene_receiver.c | 11 +- .../subghz/scenes/subghz_scene_save_name.c | 3 +- .../main/subghz/scenes/subghz_scene_start.c | 84 +-- .../subghz/scenes/subghz_scene_transmitter.c | 2 + applications/main/subghz/subghz_cli.c | 234 +++++--- applications/main/subghz/subghz_i.c | 5 +- applications/main/subghz/views/receiver.c | 19 +- applications/main/subghz/views/receiver.h | 4 + .../subghz/views/subghz_frequency_analyzer.c | 2 + .../main/subghz/views/subghz_read_raw.c | 19 +- .../main/subghz/views/subghz_read_raw.h | 5 + applications/main/subghz/views/transmitter.c | 19 +- applications/main/subghz/views/transmitter.h | 5 + firmware/targets/f7/api_symbols.csv | 76 ++- .../targets/f7/furi_hal/furi_hal_resources.c | 5 - .../targets/f7/furi_hal/furi_hal_resources.h | 5 - .../targets/f7/furi_hal/furi_hal_spi_config.c | 21 +- .../targets/f7/furi_hal/furi_hal_spi_config.h | 6 +- .../targets/f7/furi_hal/furi_hal_subghz.c | 522 +++++++----------- .../targets/f7/furi_hal/furi_hal_subghz.h | 156 ++---- .../f7/furi_hal/furi_hal_subghz_configs.h | 304 ---------- lib/subghz/protocols/raw.c | 25 +- lib/subghz/protocols/raw.h | 6 +- lib/subghz/subghz_file_encoder_worker.c | 13 +- lib/subghz/subghz_file_encoder_worker.h | 7 +- lib/subghz/subghz_setting.c | 19 +- lib/subghz/subghz_tx_rx_worker.c | 63 ++- lib/subghz/subghz_tx_rx_worker.h | 7 +- 42 files changed, 993 insertions(+), 1100 deletions(-) create mode 100644 applications/main/subghz/scenes/subghz_scene_radio_setting.c delete mode 100644 firmware/targets/f7/furi_hal/furi_hal_subghz_configs.h diff --git a/applications/debug/unit_tests/subghz/subghz_test.c b/applications/debug/unit_tests/subghz/subghz_test.c index f1ab92653..c49aa2250 100644 --- a/applications/debug/unit_tests/subghz/subghz_test.c +++ b/applications/debug/unit_tests/subghz/subghz_test.c @@ -7,6 +7,7 @@ #include #include #include +#include #define TAG "SubGhz TEST" #define KEYSTORE_DIR_NAME EXT_PATH("subghz/assets/keeloq_mfcodes") @@ -49,12 +50,15 @@ static void subghz_test_init(void) { subghz_environment_set_protocol_registry( environment_handler, (void*)&subghz_protocol_registry); + subghz_devices_init(); + receiver_handler = subghz_receiver_alloc_init(environment_handler); subghz_receiver_set_filter(receiver_handler, SubGhzProtocolFlag_Decodable); subghz_receiver_set_rx_callback(receiver_handler, subghz_test_rx_callback, NULL); } static void subghz_test_deinit(void) { + subghz_devices_deinit(); subghz_receiver_free(receiver_handler); subghz_environment_free(environment_handler); } @@ -68,7 +72,7 @@ static bool subghz_decoder_test(const char* path, const char* name_decoder) { if(decoder) { file_worker_encoder_handler = subghz_file_encoder_worker_alloc(); - if(subghz_file_encoder_worker_start(file_worker_encoder_handler, path)) { + if(subghz_file_encoder_worker_start(file_worker_encoder_handler, path, NULL)) { // the worker needs a file in order to open and read part of the file furi_delay_ms(100); @@ -108,7 +112,7 @@ static bool subghz_decode_random_test(const char* path) { uint32_t test_start = furi_get_tick(); file_worker_encoder_handler = subghz_file_encoder_worker_alloc(); - if(subghz_file_encoder_worker_start(file_worker_encoder_handler, path)) { + if(subghz_file_encoder_worker_start(file_worker_encoder_handler, path, NULL)) { // the worker needs a file in order to open and read part of the file furi_delay_ms(100); diff --git a/applications/main/subghz/helpers/subghz_chat.c b/applications/main/subghz/helpers/subghz_chat.c index b589ba5d5..6e2ac7388 100644 --- a/applications/main/subghz/helpers/subghz_chat.c +++ b/applications/main/subghz/helpers/subghz_chat.c @@ -76,12 +76,15 @@ void subghz_chat_worker_free(SubGhzChatWorker* instance) { free(instance); } -bool subghz_chat_worker_start(SubGhzChatWorker* instance, uint32_t frequency) { +bool subghz_chat_worker_start( + SubGhzChatWorker* instance, + const SubGhzDevice* device, + uint32_t frequency) { furi_assert(instance); furi_assert(!instance->worker_running); bool res = false; - if(subghz_tx_rx_worker_start(instance->subghz_txrx, frequency)) { + if(subghz_tx_rx_worker_start(instance->subghz_txrx, device, frequency)) { furi_message_queue_reset(instance->event_queue); subghz_tx_rx_worker_set_callback_have_read( instance->subghz_txrx, subghz_chat_worker_update_rx_event_chat, instance); diff --git a/applications/main/subghz/helpers/subghz_chat.h b/applications/main/subghz/helpers/subghz_chat.h index b418bbdbf..2c454b75d 100644 --- a/applications/main/subghz/helpers/subghz_chat.h +++ b/applications/main/subghz/helpers/subghz_chat.h @@ -1,5 +1,6 @@ #pragma once #include "../subghz_i.h" +#include #include typedef struct SubGhzChatWorker SubGhzChatWorker; @@ -20,7 +21,10 @@ typedef struct { SubGhzChatWorker* subghz_chat_worker_alloc(Cli* cli); void subghz_chat_worker_free(SubGhzChatWorker* instance); -bool subghz_chat_worker_start(SubGhzChatWorker* instance, uint32_t frequency); +bool subghz_chat_worker_start( + SubGhzChatWorker* instance, + const SubGhzDevice* device, + uint32_t frequency); void subghz_chat_worker_stop(SubGhzChatWorker* instance); bool subghz_chat_worker_is_running(SubGhzChatWorker* instance); SubGhzChatEvent subghz_chat_worker_get_event_chat(SubGhzChatWorker* instance); diff --git a/applications/main/subghz/helpers/subghz_threshold_rssi.c b/applications/main/subghz/helpers/subghz_threshold_rssi.c index 04a06bc17..07d7bccf9 100644 --- a/applications/main/subghz/helpers/subghz_threshold_rssi.c +++ b/applications/main/subghz/helpers/subghz_threshold_rssi.c @@ -32,9 +32,8 @@ float subghz_threshold_rssi_get(SubGhzThresholdRssi* instance) { return instance->threshold_rssi; } -SubGhzThresholdRssiData subghz_threshold_get_rssi_data(SubGhzThresholdRssi* instance) { +SubGhzThresholdRssiData subghz_threshold_get_rssi_data(SubGhzThresholdRssi* instance, float rssi) { furi_assert(instance); - float rssi = furi_hal_subghz_get_rssi(); SubGhzThresholdRssiData ret = {.rssi = rssi, .is_above = false}; if(float_is_equal(instance->threshold_rssi, SUBGHZ_RAW_THRESHOLD_MIN)) { diff --git a/applications/main/subghz/helpers/subghz_threshold_rssi.h b/applications/main/subghz/helpers/subghz_threshold_rssi.h index e28092acb..1d588e271 100644 --- a/applications/main/subghz/helpers/subghz_threshold_rssi.h +++ b/applications/main/subghz/helpers/subghz_threshold_rssi.h @@ -38,6 +38,7 @@ float subghz_threshold_rssi_get(SubGhzThresholdRssi* instance); /** Check threshold * * @param instance Pointer to a SubGhzThresholdRssi + * @param rssi Current RSSI * @return SubGhzThresholdRssiData */ -SubGhzThresholdRssiData subghz_threshold_get_rssi_data(SubGhzThresholdRssi* instance); +SubGhzThresholdRssiData subghz_threshold_get_rssi_data(SubGhzThresholdRssi* instance, float rssi); diff --git a/applications/main/subghz/helpers/subghz_txrx.c b/applications/main/subghz/helpers/subghz_txrx.c index 5b2a56993..581c6db5c 100644 --- a/applications/main/subghz/helpers/subghz_txrx.c +++ b/applications/main/subghz/helpers/subghz_txrx.c @@ -1,9 +1,28 @@ #include "subghz_txrx_i.h" + #include +#include +#include + #include #define TAG "SubGhz" +static void subghz_txrx_radio_device_power_on(SubGhzTxRx* instance) { + UNUSED(instance); + uint8_t attempts = 0; + while(!furi_hal_power_is_otg_enabled() && attempts++ < 5) { + furi_hal_power_enable_otg(); + //CC1101 power-up time + furi_delay_ms(10); + } +} + +static void subghz_txrx_radio_device_power_off(SubGhzTxRx* instance) { + UNUSED(instance); + if(furi_hal_power_is_otg_enabled()) furi_hal_power_disable_otg(); +} + SubGhzTxRx* subghz_txrx_alloc() { SubGhzTxRx* instance = malloc(sizeof(SubGhzTxRx)); instance->setting = subghz_setting_alloc(); @@ -43,12 +62,24 @@ SubGhzTxRx* subghz_txrx_alloc() { instance->worker, (SubGhzWorkerPairCallback)subghz_receiver_decode); subghz_worker_set_context(instance->worker, instance->receiver); + //set default device External + subghz_devices_init(); + instance->radio_device_type = + subghz_txrx_radio_device_set(instance, SubGhzRadioDeviceTypeExternalCC1101); + return instance; } void subghz_txrx_free(SubGhzTxRx* instance) { furi_assert(instance); + if(instance->radio_device_type != SubGhzRadioDeviceTypeInternal) { + subghz_txrx_radio_device_power_off(instance); + subghz_devices_end(instance->radio_device); + } + + subghz_devices_deinit(); + subghz_worker_free(instance->worker); subghz_receiver_free(instance->receiver); subghz_environment_free(instance->environment); @@ -128,29 +159,29 @@ void subghz_txrx_get_frequency_and_modulation( static void subghz_txrx_begin(SubGhzTxRx* instance, uint8_t* preset_data) { furi_assert(instance); - furi_hal_subghz_reset(); - furi_hal_subghz_idle(); - furi_hal_subghz_load_custom_preset(preset_data); - furi_hal_gpio_init(furi_hal_subghz.cc1101_g0_pin, GpioModeInput, GpioPullNo, GpioSpeedLow); + subghz_devices_reset(instance->radio_device); + subghz_devices_idle(instance->radio_device); + subghz_devices_load_preset(instance->radio_device, FuriHalSubGhzPresetCustom, preset_data); instance->txrx_state = SubGhzTxRxStateIDLE; } static uint32_t subghz_txrx_rx(SubGhzTxRx* instance, uint32_t frequency) { furi_assert(instance); + // TODO if(!furi_hal_subghz_is_frequency_valid(frequency)) { furi_crash("SubGhz: Incorrect RX frequency."); } furi_assert( instance->txrx_state != SubGhzTxRxStateRx && instance->txrx_state != SubGhzTxRxStateSleep); - furi_hal_subghz_idle(); - uint32_t value = furi_hal_subghz_set_frequency_and_path(frequency); - furi_hal_gpio_init(furi_hal_subghz.cc1101_g0_pin, GpioModeInput, GpioPullNo, GpioSpeedLow); - furi_hal_subghz_flush_rx(); - subghz_txrx_speaker_on(instance); - furi_hal_subghz_rx(); + subghz_devices_idle(instance->radio_device); - furi_hal_subghz_start_async_rx(subghz_worker_rx_callback, instance->worker); + uint32_t value = subghz_devices_set_frequency(instance->radio_device, frequency); + subghz_devices_flush_rx(instance->radio_device); + subghz_txrx_speaker_on(instance); + + subghz_devices_start_async_rx( + instance->radio_device, subghz_worker_rx_callback, instance->worker); subghz_worker_start(instance->worker); instance->txrx_state = SubGhzTxRxStateRx; return value; @@ -159,7 +190,7 @@ static uint32_t subghz_txrx_rx(SubGhzTxRx* instance, uint32_t frequency) { static void subghz_txrx_idle(SubGhzTxRx* instance) { furi_assert(instance); furi_assert(instance->txrx_state != SubGhzTxRxStateSleep); - furi_hal_subghz_idle(); + subghz_devices_idle(instance->radio_device); subghz_txrx_speaker_off(instance); instance->txrx_state = SubGhzTxRxStateIDLE; } @@ -170,31 +201,30 @@ static void subghz_txrx_rx_end(SubGhzTxRx* instance) { if(subghz_worker_is_running(instance->worker)) { subghz_worker_stop(instance->worker); - furi_hal_subghz_stop_async_rx(); + subghz_devices_stop_async_rx(instance->radio_device); } - furi_hal_subghz_idle(); + subghz_devices_idle(instance->radio_device); subghz_txrx_speaker_off(instance); instance->txrx_state = SubGhzTxRxStateIDLE; } void subghz_txrx_sleep(SubGhzTxRx* instance) { furi_assert(instance); - furi_hal_subghz_sleep(); + subghz_devices_sleep(instance->radio_device); instance->txrx_state = SubGhzTxRxStateSleep; } static bool subghz_txrx_tx(SubGhzTxRx* instance, uint32_t frequency) { furi_assert(instance); + // TODO if(!furi_hal_subghz_is_frequency_valid(frequency)) { furi_crash("SubGhz: Incorrect TX frequency."); } furi_assert(instance->txrx_state != SubGhzTxRxStateSleep); - furi_hal_subghz_idle(); - furi_hal_subghz_set_frequency_and_path(frequency); - furi_hal_gpio_write(furi_hal_subghz.cc1101_g0_pin, false); - furi_hal_gpio_init( - furi_hal_subghz.cc1101_g0_pin, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); - bool ret = furi_hal_subghz_tx(); + subghz_devices_idle(instance->radio_device); + subghz_devices_set_frequency(instance->radio_device, frequency); + + bool ret = subghz_devices_set_tx(instance->radio_device); if(ret) { subghz_txrx_speaker_on(instance); instance->txrx_state = SubGhzTxRxStateTx; @@ -256,8 +286,8 @@ SubGhzTxRxStartTxState subghz_txrx_tx_start(SubGhzTxRx* instance, FlipperFormat* if(ret == SubGhzTxRxStartTxStateOk) { //Start TX - furi_hal_subghz_start_async_tx( - subghz_transmitter_yield, instance->transmitter); + subghz_devices_start_async_tx( + instance->radio_device, subghz_transmitter_yield, instance->transmitter); } } else { ret = SubGhzTxRxStartTxStateErrorParserOthers; @@ -300,7 +330,7 @@ static void subghz_txrx_tx_stop(SubGhzTxRx* instance) { furi_assert(instance); furi_assert(instance->txrx_state == SubGhzTxRxStateTx); //Stop TX - furi_hal_subghz_stop_async_tx(); + subghz_devices_stop_async_tx(instance->radio_device); subghz_transmitter_stop(instance->transmitter); subghz_transmitter_free(instance->transmitter); @@ -313,7 +343,6 @@ static void subghz_txrx_tx_stop(SubGhzTxRx* instance) { subghz_txrx_idle(instance); subghz_txrx_speaker_off(instance); //Todo: Show message - // notification_message(notifications, &sequence_reset_red); } FlipperFormat* subghz_txrx_get_fff_data(SubGhzTxRx* instance) { @@ -363,7 +392,7 @@ void subghz_txrx_hopper_update(SubGhzTxRx* instance) { float rssi = -127.0f; if(instance->hopper_state != SubGhzHopperStateRSSITimeOut) { // See RSSI Calculation timings in CC1101 17.3 RSSI - rssi = furi_hal_subghz_get_rssi(); + rssi = subghz_devices_get_rssi(instance->radio_device); // Stay if RSSI is high enough if(rssi > -90.0f) { @@ -420,13 +449,13 @@ void subghz_txrx_hopper_pause(SubGhzTxRx* instance) { void subghz_txrx_speaker_on(SubGhzTxRx* instance) { furi_assert(instance); if(instance->debug_pin_state) { - furi_hal_subghz_set_async_mirror_pin(&gpio_ibutton); + subghz_devices_set_async_mirror_pin(instance->radio_device, &gpio_ibutton); } if(instance->speaker_state == SubGhzSpeakerStateEnable) { if(furi_hal_speaker_acquire(30)) { if(!instance->debug_pin_state) { - furi_hal_subghz_set_async_mirror_pin(&gpio_speaker); + subghz_devices_set_async_mirror_pin(instance->radio_device, &gpio_speaker); } } else { instance->speaker_state = SubGhzSpeakerStateDisable; @@ -437,12 +466,12 @@ void subghz_txrx_speaker_on(SubGhzTxRx* instance) { void subghz_txrx_speaker_off(SubGhzTxRx* instance) { furi_assert(instance); if(instance->debug_pin_state) { - furi_hal_subghz_set_async_mirror_pin(NULL); + subghz_devices_set_async_mirror_pin(instance->radio_device, NULL); } if(instance->speaker_state != SubGhzSpeakerStateDisable) { if(furi_hal_speaker_is_mine()) { if(!instance->debug_pin_state) { - furi_hal_subghz_set_async_mirror_pin(NULL); + subghz_devices_set_async_mirror_pin(instance->radio_device, NULL); } furi_hal_speaker_release(); if(instance->speaker_state == SubGhzSpeakerStateShutdown) @@ -454,12 +483,12 @@ void subghz_txrx_speaker_off(SubGhzTxRx* instance) { void subghz_txrx_speaker_mute(SubGhzTxRx* instance) { furi_assert(instance); if(instance->debug_pin_state) { - furi_hal_subghz_set_async_mirror_pin(NULL); + subghz_devices_set_async_mirror_pin(instance->radio_device, NULL); } if(instance->speaker_state == SubGhzSpeakerStateEnable) { if(furi_hal_speaker_is_mine()) { if(!instance->debug_pin_state) { - furi_hal_subghz_set_async_mirror_pin(NULL); + subghz_devices_set_async_mirror_pin(instance->radio_device, NULL); } } } @@ -468,12 +497,12 @@ void subghz_txrx_speaker_mute(SubGhzTxRx* instance) { void subghz_txrx_speaker_unmute(SubGhzTxRx* instance) { furi_assert(instance); if(instance->debug_pin_state) { - furi_hal_subghz_set_async_mirror_pin(&gpio_ibutton); + subghz_devices_set_async_mirror_pin(instance->radio_device, &gpio_ibutton); } if(instance->speaker_state == SubGhzSpeakerStateEnable) { if(furi_hal_speaker_is_mine()) { if(!instance->debug_pin_state) { - furi_hal_subghz_set_async_mirror_pin(&gpio_speaker); + subghz_devices_set_async_mirror_pin(instance->radio_device, &gpio_speaker); } } } @@ -548,6 +577,66 @@ void subghz_txrx_set_raw_file_encoder_worker_callback_end( context); } +bool subghz_txrx_radio_device_is_connect_external(SubGhzTxRx* instance, const char* name) { + furi_assert(instance); + + bool is_connect = false; + bool is_otg_enabled = furi_hal_power_is_otg_enabled(); + + if(!is_otg_enabled) { + subghz_txrx_radio_device_power_on(instance); + } + + is_connect = subghz_devices_is_connect(subghz_devices_get_by_name(name)); + + if(!is_otg_enabled) { + subghz_txrx_radio_device_power_off(instance); + } + return is_connect; +} + +SubGhzRadioDeviceType + subghz_txrx_radio_device_set(SubGhzTxRx* instance, SubGhzRadioDeviceType radio_device_type) { + furi_assert(instance); + + if(radio_device_type == SubGhzRadioDeviceTypeExternalCC1101 && + subghz_txrx_radio_device_is_connect_external(instance, SUBGHZ_DEVICE_CC1101_EXT_NAME)) { + subghz_txrx_radio_device_power_on(instance); + instance->radio_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_EXT_NAME); + subghz_devices_begin(instance->radio_device); + instance->radio_device_type = SubGhzRadioDeviceTypeExternalCC1101; + } else { + subghz_txrx_radio_device_power_off(instance); + if(instance->radio_device_type != SubGhzRadioDeviceTypeInternal) { + subghz_devices_end(instance->radio_device); + } + instance->radio_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME); + instance->radio_device_type = SubGhzRadioDeviceTypeInternal; + } + + return instance->radio_device_type; +} + +SubGhzRadioDeviceType subghz_txrx_radio_device_get(SubGhzTxRx* instance) { + furi_assert(instance); + return instance->radio_device_type; +} + +float subghz_txrx_radio_device_get_rssi(SubGhzTxRx* instance) { + furi_assert(instance); + return subghz_devices_get_rssi(instance->radio_device); +} + +const char* subghz_txrx_radio_device_get_name(SubGhzTxRx* instance) { + furi_assert(instance); + return subghz_devices_get_name(instance->radio_device); +} + +bool subghz_txrx_radio_device_is_frequecy_valid(SubGhzTxRx* instance, uint32_t frequency) { + furi_assert(instance); + return subghz_devices_is_frequency_valid(instance->radio_device, frequency); +} + void subghz_txrx_set_debug_pin_state(SubGhzTxRx* instance, bool state) { furi_assert(instance); instance->debug_pin_state = state; diff --git a/applications/main/subghz/helpers/subghz_txrx.h b/applications/main/subghz/helpers/subghz_txrx.h index 6ad5d97bd..d03c618c4 100644 --- a/applications/main/subghz/helpers/subghz_txrx.h +++ b/applications/main/subghz/helpers/subghz_txrx.h @@ -7,6 +7,7 @@ #include #include #include +#include typedef struct SubGhzTxRx SubGhzTxRx; @@ -290,6 +291,51 @@ void subghz_txrx_set_raw_file_encoder_worker_callback_end( SubGhzProtocolEncoderRAWCallbackEnd callback, void* context); +/* Checking if an external radio device is connected +* +* @param instance Pointer to a SubGhzTxRx +* @param name Name of external radio device +* @return bool True if is connected to the external radio device +*/ +bool subghz_txrx_radio_device_is_connect_external(SubGhzTxRx* instance, const char* name); + +/* Set the selected radio device to use +* +* @param instance Pointer to a SubGhzTxRx +* @param radio_device_type Radio device type +* @return SubGhzRadioDeviceType Type of installed radio device +*/ +SubGhzRadioDeviceType + subghz_txrx_radio_device_set(SubGhzTxRx* instance, SubGhzRadioDeviceType radio_device_type); + +/* Get the selected radio device to use +* +* @param instance Pointer to a SubGhzTxRx +* @return SubGhzRadioDeviceType Type of installed radio device +*/ +SubGhzRadioDeviceType subghz_txrx_radio_device_get(SubGhzTxRx* instance); + +/* Get RSSI the selected radio device to use +* +* @param instance Pointer to a SubGhzTxRx +* @return float RSSI +*/ +float subghz_txrx_radio_device_get_rssi(SubGhzTxRx* instance); + +/* Get name the selected radio device to use +* +* @param instance Pointer to a SubGhzTxRx +* @return const char* Name of installed radio device +*/ +const char* subghz_txrx_radio_device_get_name(SubGhzTxRx* instance); + +/* Get get intelligence whether frequency the selected radio device to use +* +* @param instance Pointer to a SubGhzTxRx +* @return bool True if the frequency is valid +*/ +bool subghz_txrx_radio_device_is_frequecy_valid(SubGhzTxRx* instance, uint32_t frequency); + void subghz_txrx_set_debug_pin_state(SubGhzTxRx* instance, bool state); bool subghz_txrx_get_debug_pin_state(SubGhzTxRx* instance); diff --git a/applications/main/subghz/helpers/subghz_txrx_i.h b/applications/main/subghz/helpers/subghz_txrx_i.h index 680d27158..f058c2282 100644 --- a/applications/main/subghz/helpers/subghz_txrx_i.h +++ b/applications/main/subghz/helpers/subghz_txrx_i.h @@ -1,5 +1,5 @@ - #pragma once + #include "subghz_txrx.h" struct SubGhzTxRx { @@ -21,9 +21,11 @@ struct SubGhzTxRx { SubGhzTxRxState txrx_state; SubGhzSpeakerState speaker_state; + const SubGhzDevice* radio_device; + SubGhzRadioDeviceType radio_device_type; SubGhzTxRxNeedSaveCallback need_save_callback; void* need_save_context; bool debug_pin_state; -}; \ No newline at end of file +}; diff --git a/applications/main/subghz/helpers/subghz_types.h b/applications/main/subghz/helpers/subghz_types.h index 1cddfc8d5..5e5b4e5de 100644 --- a/applications/main/subghz/helpers/subghz_types.h +++ b/applications/main/subghz/helpers/subghz_types.h @@ -35,6 +35,13 @@ typedef enum { SubGhzSpeakerStateEnable, } SubGhzSpeakerState; +/** SubGhzRadioDeviceType */ +typedef enum { + SubGhzRadioDeviceTypeAuto, + SubGhzRadioDeviceTypeInternal, + SubGhzRadioDeviceTypeExternalCC1101, +} SubGhzRadioDeviceType; + /** SubGhzRxKeyState state */ typedef enum { SubGhzRxKeyStateIDLE, diff --git a/applications/main/subghz/scenes/subghz_scene_config.h b/applications/main/subghz/scenes/subghz_scene_config.h index 269ec4c72..ac2f2c599 100644 --- a/applications/main/subghz/scenes/subghz_scene_config.h +++ b/applications/main/subghz/scenes/subghz_scene_config.h @@ -30,3 +30,4 @@ ADD_SCENE(subghz, decode_raw, DecodeRAW) ADD_SCENE(subghz, delete_raw, DeleteRAW) ADD_SCENE(subghz, need_saving, NeedSaving) ADD_SCENE(subghz, rpc, Rpc) +ADD_SCENE(subghz, radio_setting, RadioSettings) diff --git a/applications/main/subghz/scenes/subghz_scene_decode_raw.c b/applications/main/subghz/scenes/subghz_scene_decode_raw.c index 102965df5..988a61c8b 100644 --- a/applications/main/subghz/scenes/subghz_scene_decode_raw.c +++ b/applications/main/subghz/scenes/subghz_scene_decode_raw.c @@ -93,7 +93,9 @@ bool subghz_scene_decode_raw_start(SubGhz* subghz) { subghz->decode_raw_file_worker_encoder = subghz_file_encoder_worker_alloc(); if(subghz_file_encoder_worker_start( - subghz->decode_raw_file_worker_encoder, furi_string_get_cstr(file_name))) { + subghz->decode_raw_file_worker_encoder, + furi_string_get_cstr(file_name), + subghz_txrx_radio_device_get_name(subghz->txrx))) { //the worker needs a file in order to open and read part of the file furi_delay_ms(100); } else { diff --git a/applications/main/subghz/scenes/subghz_scene_radio_setting.c b/applications/main/subghz/scenes/subghz_scene_radio_setting.c new file mode 100644 index 000000000..9f2a6ab58 --- /dev/null +++ b/applications/main/subghz/scenes/subghz_scene_radio_setting.c @@ -0,0 +1,65 @@ +#include "../subghz_i.h" +#include +#include + +enum SubGhzRadioSettingIndex { + SubGhzRadioSettingIndexDevice, +}; + +#define RADIO_DEVICE_COUNT 2 +const char* const radio_device_text[RADIO_DEVICE_COUNT] = { + "Internal", + "External", +}; + +const uint32_t radio_device_value[RADIO_DEVICE_COUNT] = { + SubGhzRadioDeviceTypeInternal, + SubGhzRadioDeviceTypeExternalCC1101, +}; + +static void subghz_scene_radio_setting_set_device(VariableItem* item) { + SubGhz* subghz = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + if(!subghz_txrx_radio_device_is_connect_external(subghz->txrx, SUBGHZ_DEVICE_CC1101_EXT_NAME) && + radio_device_value[index] == SubGhzRadioDeviceTypeExternalCC1101) { + //ToDo correct if there is more than 1 module + index = 0; + } + variable_item_set_current_value_text(item, radio_device_text[index]); + subghz_txrx_radio_device_set(subghz->txrx, radio_device_value[index]); +} + +void subghz_scene_radio_setting_on_enter(void* context) { + SubGhz* subghz = context; + VariableItem* item; + uint8_t value_index; + + item = variable_item_list_add( + subghz->variable_item_list, + "Module", + RADIO_DEVICE_COUNT, + subghz_scene_radio_setting_set_device, + subghz); + value_index = value_index_uint32( + subghz_txrx_radio_device_get(subghz->txrx), radio_device_value, RADIO_DEVICE_COUNT); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, radio_device_text[value_index]); + + view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdVariableItemList); +} + +bool subghz_scene_radio_setting_on_event(void* context, SceneManagerEvent event) { + SubGhz* subghz = context; + bool consumed = false; + UNUSED(subghz); + UNUSED(event); + + return consumed; +} + +void subghz_scene_radio_setting_on_exit(void* context) { + SubGhz* subghz = context; + variable_item_list_set_selected_item(subghz->variable_item_list, 0); + variable_item_list_reset(subghz->variable_item_list); +} diff --git a/applications/main/subghz/scenes/subghz_scene_radio_settings.c b/applications/main/subghz/scenes/subghz_scene_radio_settings.c index 3020c1b23..a3576b3e5 100644 --- a/applications/main/subghz/scenes/subghz_scene_radio_settings.c +++ b/applications/main/subghz/scenes/subghz_scene_radio_settings.c @@ -1,17 +1,17 @@ #include "../subghz_i.h" #include "../helpers/subghz_custom_event.h" -#define EXT_MODULES_COUNT (sizeof(radio_modules_variables_text) / sizeof(char* const)) -const char* const radio_modules_variables_text[] = { - "Internal", - "External", -}; +// #define EXT_MODULES_COUNT (sizeof(radio_modules_variables_text) / sizeof(char* const)) +// const char* const radio_modules_variables_text[] = { +// "Internal", +// "External", +// }; -#define EXT_MOD_POWER_COUNT 2 -const char* const ext_mod_power_text[EXT_MOD_POWER_COUNT] = { - "ON", - "OFF", -}; +// #define EXT_MOD_POWER_COUNT 2 +// const char* const ext_mod_power_text[EXT_MOD_POWER_COUNT] = { +// "ON", +// "OFF", +// }; #define TIMESTAMP_NAMES_COUNT 2 const char* const timestamp_names_text[TIMESTAMP_NAMES_COUNT] = { @@ -35,20 +35,20 @@ const char* const debug_counter_text[DEBUG_COUNTER_COUNT] = { "+10", }; -static void subghz_scene_ext_module_changed(VariableItem* item) { - SubGhz* subghz = variable_item_get_context(item); - uint8_t value_index_exm = variable_item_get_current_value_index(item); +// static void subghz_scene_ext_module_changed(VariableItem* item) { +// SubGhz* subghz = variable_item_get_context(item); +// uint8_t value_index_exm = variable_item_get_current_value_index(item); - variable_item_set_current_value_text(item, radio_modules_variables_text[value_index_exm]); +// variable_item_set_current_value_text(item, radio_modules_variables_text[value_index_exm]); - subghz->last_settings->external_module_enabled = value_index_exm == 1; - subghz_last_settings_save(subghz->last_settings); -} +// subghz->last_settings->external_module_enabled = value_index_exm == 1; +// subghz_last_settings_save(subghz->last_settings); +// } -static void subghz_ext_module_start_var_list_enter_callback(void* context, uint32_t index) { - SubGhz* subghz = context; - view_dispatcher_send_custom_event(subghz->view_dispatcher, index); -} +// static void subghz_ext_module_start_var_list_enter_callback(void* context, uint32_t index) { +// SubGhz* subghz = context; +// view_dispatcher_send_custom_event(subghz->view_dispatcher, index); +// } static void subghz_scene_receiver_config_set_debug_pin(VariableItem* item) { SubGhz* subghz = variable_item_get_context(item); @@ -88,22 +88,22 @@ static void subghz_scene_receiver_config_set_debug_counter(VariableItem* item) { } } -static void subghz_scene_receiver_config_set_ext_mod_power(VariableItem* item) { - SubGhz* subghz = variable_item_get_context(item); - uint8_t index = variable_item_get_current_value_index(item); +// static void subghz_scene_receiver_config_set_ext_mod_power(VariableItem* item) { +// SubGhz* subghz = variable_item_get_context(item); +// uint8_t index = variable_item_get_current_value_index(item); - variable_item_set_current_value_text(item, ext_mod_power_text[index]); +// variable_item_set_current_value_text(item, ext_mod_power_text[index]); - furi_hal_subghz_set_external_power_disable(index == 1); - if(index == 1) { - furi_hal_subghz_disable_ext_power(); - } else { - furi_hal_subghz_enable_ext_power(); - } +// furi_hal_subghz_set_external_power_disable(index == 1); +// if(index == 1) { +// furi_hal_subghz_disable_ext_power(); +// } else { +// furi_hal_subghz_enable_ext_power(); +// } - subghz->last_settings->external_module_power_5v_disable = index == 1; - subghz_last_settings_save(subghz->last_settings); -} +// subghz->last_settings->external_module_power_5v_disable = index == 1; +// subghz_last_settings_save(subghz->last_settings); +// } static void subghz_scene_receiver_config_set_timestamp_file_names(VariableItem* item) { SubGhz* subghz = variable_item_get_context(item); @@ -120,26 +120,26 @@ void subghz_scene_radio_settings_on_enter(void* context) { VariableItemList* variable_item_list = subghz->variable_item_list; uint8_t value_index; + VariableItem* item; - value_index = furi_hal_subghz.radio_type; - VariableItem* item = variable_item_list_add( - variable_item_list, "Module", EXT_MODULES_COUNT, subghz_scene_ext_module_changed, subghz); + // VariableItem* item = variable_item_list_add( + // variable_item_list, "Module", EXT_MODULES_COUNT, subghz_scene_ext_module_changed, subghz); - variable_item_list_set_enter_callback( - variable_item_list, subghz_ext_module_start_var_list_enter_callback, subghz); + // variable_item_list_set_enter_callback( + // variable_item_list, subghz_ext_module_start_var_list_enter_callback, subghz); + // value_index = furi_hal_subghz.radio_type; + // variable_item_set_current_value_index(item, value_index); + // variable_item_set_current_value_text(item, radio_modules_variables_text[value_index]); - variable_item_set_current_value_index(item, value_index); - variable_item_set_current_value_text(item, radio_modules_variables_text[value_index]); - - item = variable_item_list_add( - subghz->variable_item_list, - "Ext Radio 5v", - EXT_MOD_POWER_COUNT, - subghz_scene_receiver_config_set_ext_mod_power, - subghz); - value_index = furi_hal_subghz_get_external_power_disable(); - variable_item_set_current_value_index(item, value_index); - variable_item_set_current_value_text(item, ext_mod_power_text[value_index]); + // item = variable_item_list_add( + // subghz->variable_item_list, + // "Ext Radio 5v", + // EXT_MOD_POWER_COUNT, + // subghz_scene_receiver_config_set_ext_mod_power, + // subghz); + // value_index = furi_hal_subghz_get_external_power_disable(); + // variable_item_set_current_value_index(item, value_index); + // variable_item_set_current_value_text(item, ext_mod_power_text[value_index]); item = variable_item_list_add( subghz->variable_item_list, @@ -228,19 +228,19 @@ bool subghz_scene_radio_settings_on_event(void* context, SceneManagerEvent event UNUSED(event); // Set selected radio module - furi_hal_subghz_select_radio_type(subghz->last_settings->external_module_enabled); - furi_hal_subghz_init_radio_type(subghz->last_settings->external_module_enabled); + // furi_hal_subghz_select_radio_type(subghz->last_settings->external_module_enabled); + // furi_hal_subghz_init_radio_type(subghz->last_settings->external_module_enabled); - furi_hal_subghz_enable_ext_power(); + // furi_hal_subghz_enable_ext_power(); // Check if module is present, if no -> show error - if(!furi_hal_subghz_check_radio()) { - subghz->last_settings->external_module_enabled = false; - furi_hal_subghz_select_radio_type(SubGhzRadioInternal); - furi_hal_subghz_init_radio_type(SubGhzRadioInternal); - furi_string_set(subghz->error_str, "Please connect\nexternal radio"); - scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowErrorSub); - } + // if(!furi_hal_subghz_check_radio()) { + // subghz->last_settings->external_module_enabled = false; + // furi_hal_subghz_select_radio_type(SubGhzRadioInternal); + // furi_hal_subghz_init_radio_type(SubGhzRadioInternal); + // furi_string_set(subghz->error_str, "Please connect\nexternal radio"); + // scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowErrorSub); + // } return false; } diff --git a/applications/main/subghz/scenes/subghz_scene_read_raw.c b/applications/main/subghz/scenes/subghz_scene_read_raw.c index 3db66c64e..9dfb695e7 100644 --- a/applications/main/subghz/scenes/subghz_scene_read_raw.c +++ b/applications/main/subghz/scenes/subghz_scene_read_raw.c @@ -52,6 +52,9 @@ static void subghz_scene_read_raw_update_statusbar(void* context) { furi_string_free(frequency_str); furi_string_free(modulation_str); + + subghz_read_raw_set_radio_device_type( + subghz->subghz_read_raw, subghz_txrx_radio_device_get(subghz->txrx)); } void subghz_scene_read_raw_callback(SubGhzCustomEvent event, void* context) { @@ -247,7 +250,9 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { furi_string_printf( temp_str, "%s/%s%s", SUBGHZ_RAW_FOLDER, RAW_FILE_NAME, SUBGHZ_APP_EXTENSION); subghz_protocol_raw_gen_fff_data( - subghz_txrx_get_fff_data(subghz->txrx), furi_string_get_cstr(temp_str)); + subghz_txrx_get_fff_data(subghz->txrx), + furi_string_get_cstr(temp_str), + subghz_txrx_radio_device_get_name(subghz->txrx)); furi_string_free(temp_str); if(spl_count > 0) { @@ -307,8 +312,8 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { subghz_read_raw_update_sample_write( subghz->subghz_read_raw, subghz_protocol_raw_get_sample_write(decoder_raw)); - SubGhzThresholdRssiData ret_rssi = - subghz_threshold_get_rssi_data(subghz->threshold_rssi); + SubGhzThresholdRssiData ret_rssi = subghz_threshold_get_rssi_data( + subghz->threshold_rssi, subghz_txrx_radio_device_get_rssi(subghz->txrx)); subghz_read_raw_add_data_rssi( subghz->subghz_read_raw, ret_rssi.rssi, ret_rssi.is_above); subghz_protocol_raw_save_to_file_pause(decoder_raw, !ret_rssi.is_above); diff --git a/applications/main/subghz/scenes/subghz_scene_receiver.c b/applications/main/subghz/scenes/subghz_scene_receiver.c index d9fd38836..1cf5ecdd7 100644 --- a/applications/main/subghz/scenes/subghz_scene_receiver.c +++ b/applications/main/subghz/scenes/subghz_scene_receiver.c @@ -55,7 +55,9 @@ static void subghz_scene_receiver_update_statusbar(void* context) { furi_string_printf( modulation_str, "%s Mod: %s", - furi_hal_subghz_get_radio_type() ? "Ext" : "Int", + (subghz_txrx_radio_device_get(subghz->txrx) == SubGhzRadioDeviceTypeInternal) ? + "Int" : + "Ext", furi_string_get_cstr(temp_str)); furi_string_free(temp_str); } @@ -78,6 +80,9 @@ static void subghz_scene_receiver_update_statusbar(void* context) { subghz->state_notifications = SubGhzNotificationStateIDLE; } furi_string_free(history_stat_str); + + subghz_view_receiver_set_radio_device_type( + subghz->subghz_receiver, subghz_txrx_radio_device_get(subghz->txrx)); } void subghz_scene_receiver_callback(SubGhzCustomEvent event, void* context) { @@ -273,8 +278,8 @@ bool subghz_scene_receiver_on_event(void* context, SceneManagerEvent event) { subghz_scene_receiver_update_statusbar(subghz); } - //get RSSI - SubGhzThresholdRssiData ret_rssi = subghz_threshold_get_rssi_data(subghz->threshold_rssi); + SubGhzThresholdRssiData ret_rssi = subghz_threshold_get_rssi_data( + subghz->threshold_rssi, subghz_txrx_radio_device_get_rssi(subghz->txrx)); subghz_receiver_rssi(subghz->subghz_receiver, ret_rssi.rssi); subghz_protocol_decoder_bin_raw_data_input_rssi( diff --git a/applications/main/subghz/scenes/subghz_scene_save_name.c b/applications/main/subghz/scenes/subghz_scene_save_name.c index 5c52ed957..ba14720f3 100644 --- a/applications/main/subghz/scenes/subghz_scene_save_name.c +++ b/applications/main/subghz/scenes/subghz_scene_save_name.c @@ -165,7 +165,8 @@ bool subghz_scene_save_name_on_event(void* context, SceneManagerEvent event) { SubGhzCustomEventManagerNoSet) { subghz_protocol_raw_gen_fff_data( subghz_txrx_get_fff_data(subghz->txrx), - furi_string_get_cstr(subghz->file_path)); + furi_string_get_cstr(subghz->file_path), + subghz_txrx_radio_device_get_name(subghz->txrx)); scene_manager_set_scene_state( subghz->scene_manager, SubGhzSceneReadRAW, SubGhzCustomEventManagerNoSet); } else { diff --git a/applications/main/subghz/scenes/subghz_scene_start.c b/applications/main/subghz/scenes/subghz_scene_start.c index eceedda5e..b65fc38cf 100644 --- a/applications/main/subghz/scenes/subghz_scene_start.c +++ b/applications/main/subghz/scenes/subghz_scene_start.c @@ -1,6 +1,7 @@ #include "../subghz_i.h" #include +// TODO move RadioSettings to ExtraSettings #include enum SubmenuIndex { @@ -11,6 +12,7 @@ enum SubmenuIndex { SubmenuIndexFrequencyAnalyzer, SubmenuIndexReadRAW, SubmenuIndexExtSettings, + SubmenuIndexRadioSetting, }; void subghz_scene_start_submenu_callback(void* context, uint32_t index) { @@ -52,6 +54,12 @@ void subghz_scene_start_on_enter(void* context) { SubmenuIndexExtSettings, subghz_scene_start_submenu_callback, subghz); + submenu_add_item( + subghz->submenu, + "Radio Settings2", + SubmenuIndexRadioSetting, + subghz_scene_start_submenu_callback, + subghz); if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { submenu_add_item( subghz->submenu, "Test", SubmenuIndexTest, subghz_scene_start_submenu_callback, subghz); @@ -70,54 +78,48 @@ bool subghz_scene_start_on_event(void* context, SceneManagerEvent event) { view_dispatcher_stop(subghz->view_dispatcher); return true; } else if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SubmenuIndexExtSettings) { + if(event.event == SubmenuIndexReadRAW) { scene_manager_set_scene_state( - subghz->scene_manager, SubGhzSceneStart, SubmenuIndexExtSettings); - scene_manager_next_scene(subghz->scene_manager, SubGhzSceneExtModuleSettings); + subghz->scene_manager, SubGhzSceneStart, SubmenuIndexReadRAW); + subghz_rx_key_state_set(subghz, SubGhzRxKeyStateIDLE); + 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); + return true; + } else if(event.event == SubmenuIndexSaved) { + scene_manager_set_scene_state( + subghz->scene_manager, SubGhzSceneStart, SubmenuIndexSaved); + scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaved); return true; } else if(event.event == SubmenuIndexAddManually) { scene_manager_set_scene_state( subghz->scene_manager, SubGhzSceneStart, SubmenuIndexAddManually); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSetType); return true; - } else { - furi_hal_subghz_enable_ext_power(); - - if(!furi_hal_subghz_check_radio()) { - furi_hal_subghz_select_radio_type(SubGhzRadioInternal); - furi_hal_subghz_init_radio_type(SubGhzRadioInternal); - subghz->last_settings->external_module_enabled = false; - furi_string_set(subghz->error_str, "Please connect\nexternal radio"); - scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowErrorSub); - return true; - } else if(event.event == SubmenuIndexReadRAW) { - scene_manager_set_scene_state( - subghz->scene_manager, SubGhzSceneStart, SubmenuIndexReadRAW); - subghz_rx_key_state_set(subghz, SubGhzRxKeyStateIDLE); - 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); - return true; - } else if(event.event == SubmenuIndexSaved) { - scene_manager_set_scene_state( - subghz->scene_manager, SubGhzSceneStart, SubmenuIndexSaved); - scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaved); - return true; - } else if(event.event == SubmenuIndexFrequencyAnalyzer) { - scene_manager_set_scene_state( - subghz->scene_manager, SubGhzSceneStart, SubmenuIndexFrequencyAnalyzer); - scene_manager_next_scene(subghz->scene_manager, SubGhzSceneFrequencyAnalyzer); - dolphin_deed(DolphinDeedSubGhzFrequencyAnalyzer); - return true; - } else if(event.event == SubmenuIndexTest) { - scene_manager_set_scene_state( - subghz->scene_manager, SubGhzSceneStart, SubmenuIndexTest); - scene_manager_next_scene(subghz->scene_manager, SubGhzSceneTest); - return true; - } + } else if(event.event == SubmenuIndexFrequencyAnalyzer) { + scene_manager_set_scene_state( + subghz->scene_manager, SubGhzSceneStart, SubmenuIndexFrequencyAnalyzer); + scene_manager_next_scene(subghz->scene_manager, SubGhzSceneFrequencyAnalyzer); + dolphin_deed(DolphinDeedSubGhzFrequencyAnalyzer); + return true; + } else if(event.event == SubmenuIndexTest) { + scene_manager_set_scene_state( + subghz->scene_manager, SubGhzSceneStart, SubmenuIndexTest); + scene_manager_next_scene(subghz->scene_manager, SubGhzSceneTest); + return true; + } else if(event.event == SubmenuIndexExtSettings) { + scene_manager_set_scene_state( + subghz->scene_manager, SubGhzSceneStart, SubmenuIndexExtSettings); + scene_manager_next_scene(subghz->scene_manager, SubGhzSceneExtModuleSettings); + return true; + } else if(event.event == SubmenuIndexRadioSetting) { + scene_manager_set_scene_state( + subghz->scene_manager, SubGhzSceneStart, SubmenuIndexRadioSetting); + scene_manager_next_scene(subghz->scene_manager, SubGhzSceneRadioSettings); + return true; } } return false; diff --git a/applications/main/subghz/scenes/subghz_scene_transmitter.c b/applications/main/subghz/scenes/subghz_scene_transmitter.c index a5925e6e1..9a38cecd7 100644 --- a/applications/main/subghz/scenes/subghz_scene_transmitter.c +++ b/applications/main/subghz/scenes/subghz_scene_transmitter.c @@ -38,6 +38,8 @@ bool subghz_scene_transmitter_update_data_show(void* context) { furi_string_free(modulation_str); furi_string_free(key_str); } + subghz_view_transmitter_set_radio_device_type( + subghz->subghz_transmitter, subghz_txrx_radio_device_get(subghz->txrx)); return ret; } diff --git a/applications/main/subghz/subghz_cli.c b/applications/main/subghz/subghz_cli.c index 1e50dfc25..403d4bcd7 100644 --- a/applications/main/subghz/subghz_cli.c +++ b/applications/main/subghz/subghz_cli.c @@ -10,6 +10,9 @@ #include #include #include +#include +#include +#include #include "helpers/subghz_chat.h" @@ -19,6 +22,19 @@ #define SUBGHZ_FREQUENCY_RANGE_STR \ "299999755...348000000 or 386999938...464000000 or 778999847...928000000" +static void subghz_cli_radio_device_power_on() { + uint8_t attempts = 0; + while(!furi_hal_power_is_otg_enabled() && attempts++ < 5) { + furi_hal_power_enable_otg(); + //CC1101 power-up time + furi_delay_ms(10); + } +} + +static void subghz_cli_radio_device_power_off() { + if(furi_hal_power_is_otg_enabled()) furi_hal_power_disable_otg(); +} + void subghz_cli_command_tx_carrier(Cli* cli, FuriString* args, void* context) { UNUSED(context); uint32_t frequency = 433920000; @@ -41,10 +57,9 @@ void subghz_cli_command_tx_carrier(Cli* cli, FuriString* args, void* context) { furi_hal_subghz_reset(); furi_hal_subghz_load_preset(FuriHalSubGhzPresetOok650Async); frequency = furi_hal_subghz_set_frequency_and_path(frequency); - - furi_hal_gpio_init( - furi_hal_subghz.cc1101_g0_pin, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); - furi_hal_gpio_write(furi_hal_subghz.cc1101_g0_pin, true); + // TODO external device + furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_write(&gpio_cc1101_g0, true); furi_hal_power_suppress_charge_enter(); @@ -105,44 +120,70 @@ void subghz_cli_command_rx_carrier(Cli* cli, FuriString* args, void* context) { furi_hal_subghz_sleep(); } +static const SubGhzDevice* subghz_cli_command_get_device(uint32_t device_ind) { + const SubGhzDevice* device = NULL; + switch(device_ind) { + case 1: + subghz_cli_radio_device_power_on(); + device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_EXT_NAME); + break; + + default: + device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME); + break; + } + return device; +} + void subghz_cli_command_tx(Cli* cli, FuriString* args, void* context) { UNUSED(context); uint32_t frequency = 433920000; uint32_t key = 0x0074BADE; uint32_t repeat = 10; uint32_t te = 403; + uint32_t device_ind = 0; // 0 - CC1101_INT, 1 - CC1101_EXT if(furi_string_size(args)) { - int ret = - sscanf(furi_string_get_cstr(args), "%lx %lu %lu %lu", &key, &frequency, &te, &repeat); - if(ret != 4) { + int ret = sscanf( + furi_string_get_cstr(args), + "%lx %lu %lu %lu %lu", + &key, + &frequency, + &te, + &repeat, + &device_ind); + if(ret != 5) { printf( - "sscanf returned %d, key: %lx, frequency: %lu, te:%lu, repeat: %lu\r\n", + "sscanf returned %d, key: %lx, frequency: %lu, te: %lu, repeat: %lu, device: %lu\r\n ", ret, key, frequency, te, - repeat); + repeat, + device_ind); cli_print_usage( "subghz tx", - "<3 Byte Key: in hex> ", + "<3 Byte Key: in hex> ", furi_string_get_cstr(args)); return; } - if(!furi_hal_subghz_is_frequency_valid(frequency)) { - printf( - "Frequency must be in " SUBGHZ_FREQUENCY_RANGE_STR " range, not %lu\r\n", - frequency); - return; - } } - + subghz_devices_init(); + const SubGhzDevice* device = subghz_cli_command_get_device(device_ind); + if(!subghz_devices_is_frequency_valid(device, frequency)) { + printf( + "Frequency must be in " SUBGHZ_FREQUENCY_RANGE_STR " range, not %lu\r\n", frequency); + subghz_devices_deinit(); + subghz_cli_radio_device_power_off(); + return; + } printf( - "Transmitting at %lu, key %lx, te %lu, repeat %lu. Press CTRL+C to stop\r\n", + "Transmitting at %lu, key %lx, te %lu, repeat %lu device %lu. Press CTRL+C to stop\r\n", frequency, key, te, - repeat); + repeat, + device_ind); FuriString* flipper_format_string = furi_string_alloc_printf( "Protocol: Princeton\n" @@ -166,25 +207,30 @@ void subghz_cli_command_tx(Cli* cli, FuriString* args, void* context) { SubGhzTransmitter* transmitter = subghz_transmitter_alloc_init(environment, "Princeton"); subghz_transmitter_deserialize(transmitter, flipper_format); - furi_hal_subghz_reset(); - furi_hal_subghz_load_preset(FuriHalSubGhzPresetOok650Async); - frequency = furi_hal_subghz_set_frequency_and_path(frequency); + subghz_devices_begin(device); + subghz_devices_reset(device); + subghz_devices_load_preset(device, FuriHalSubGhzPresetOok650Async, NULL); + frequency = subghz_devices_set_frequency(device, frequency); furi_hal_power_suppress_charge_enter(); - - if(furi_hal_subghz_start_async_tx(subghz_transmitter_yield, transmitter)) { - while(!(furi_hal_subghz_is_async_tx_complete() || cli_cmd_interrupt_received(cli))) { + if(subghz_devices_start_async_tx(device, subghz_transmitter_yield, transmitter)) { + while(!(subghz_devices_is_async_complete_tx(device) || cli_cmd_interrupt_received(cli))) { printf("."); fflush(stdout); furi_delay_ms(333); } - furi_hal_subghz_stop_async_tx(); + subghz_devices_stop_async_tx(device); } else { printf("Transmission on this frequency is restricted in your region\r\n"); + // TODO region? } - furi_hal_subghz_sleep(); + subghz_devices_sleep(device); + subghz_devices_end(device); + subghz_devices_deinit(); + subghz_cli_radio_device_power_off(); + furi_hal_power_suppress_charge_exit(); flipper_format_free(flipper_format); @@ -228,21 +274,29 @@ static void subghz_cli_command_rx_callback( void subghz_cli_command_rx(Cli* cli, FuriString* args, void* context) { UNUSED(context); uint32_t frequency = 433920000; + uint32_t device_ind = 0; // 0 - CC1101_INT, 1 - CC1101_EXT if(furi_string_size(args)) { - int ret = sscanf(furi_string_get_cstr(args), "%lu", &frequency); - if(ret != 1) { - printf("sscanf returned %d, frequency: %lu\r\n", ret, frequency); - cli_print_usage("subghz rx", "", furi_string_get_cstr(args)); - return; - } - if(!furi_hal_subghz_is_frequency_valid(frequency)) { + int ret = sscanf(furi_string_get_cstr(args), "%lu %lu", &frequency, &device_ind); + if(ret != 2) { printf( - "Frequency must be in " SUBGHZ_FREQUENCY_RANGE_STR " range, not %lu\r\n", - frequency); + "sscanf returned %d, frequency: %lu device: %lu\r\n", ret, frequency, device_ind); + cli_print_usage( + "subghz rx", + " ", + furi_string_get_cstr(args)); return; } } + subghz_devices_init(); + const SubGhzDevice* device = subghz_cli_command_get_device(device_ind); + if(!subghz_devices_is_frequency_valid(device, frequency)) { + printf( + "Frequency must be in " SUBGHZ_FREQUENCY_RANGE_STR " range, not %lu\r\n", frequency); + subghz_devices_deinit(); + subghz_cli_radio_device_power_off(); + return; + } // Allocate context and buffers SubGhzCliCommandRx* instance = malloc(sizeof(SubGhzCliCommandRx)); @@ -251,14 +305,14 @@ void subghz_cli_command_rx(Cli* cli, FuriString* args, void* context) { furi_check(instance->stream); SubGhzEnvironment* environment = subghz_environment_alloc(); - subghz_environment_load_keystore(environment, EXT_PATH("subghz/assets/keeloq_mfcodes")); - subghz_environment_load_keystore(environment, EXT_PATH("subghz/assets/keeloq_mfcodes_user")); + subghz_environment_load_keystore(environment, SUBGHZ_KEYSTORE_DIR_NAME); + subghz_environment_load_keystore(environment, SUBGHZ_KEYSTORE_DIR_USER_NAME); subghz_environment_set_came_atomo_rainbow_table_file_name( - environment, EXT_PATH("subghz/assets/came_atomo")); + environment, SUBGHZ_CAME_ATOMO_DIR_NAME); subghz_environment_set_alutech_at_4n_rainbow_table_file_name( - environment, EXT_PATH("subghz/assets/alutech_at_4n")); + environment, SUBGHZ_ALUTECH_AT_4N_DIR_NAME); subghz_environment_set_nice_flor_s_rainbow_table_file_name( - environment, EXT_PATH("subghz/assets/nice_flor_s")); + environment, SUBGHZ_NICE_FLOR_S_DIR_NAME); subghz_environment_set_protocol_registry(environment, (void*)&subghz_protocol_registry); SubGhzReceiver* receiver = subghz_receiver_alloc_init(environment); @@ -266,18 +320,21 @@ void subghz_cli_command_rx(Cli* cli, FuriString* args, void* context) { subghz_receiver_set_rx_callback(receiver, subghz_cli_command_rx_callback, instance); // Configure radio - furi_hal_subghz_reset(); - furi_hal_subghz_load_preset(FuriHalSubGhzPresetOok650Async); - frequency = furi_hal_subghz_set_frequency_and_path(frequency); - furi_hal_gpio_init(furi_hal_subghz.cc1101_g0_pin, GpioModeInput, GpioPullNo, GpioSpeedLow); + subghz_devices_begin(device); + subghz_devices_reset(device); + subghz_devices_load_preset(device, FuriHalSubGhzPresetOok650Async, NULL); + frequency = subghz_devices_set_frequency(device, frequency); furi_hal_power_suppress_charge_enter(); // Prepare and start RX - furi_hal_subghz_start_async_rx(subghz_cli_command_rx_capture_callback, instance); + subghz_devices_start_async_rx(device, subghz_cli_command_rx_capture_callback, instance); // Wait for packets to arrive - printf("Listening at %lu. Press CTRL+C to stop\r\n", frequency); + printf( + "Listening at frequency: %lu device: %lu. Press CTRL+C to stop\r\n", + frequency, + device_ind); LevelDuration level_duration; while(!cli_cmd_interrupt_received(cli)) { int ret = furi_stream_buffer_receive( @@ -295,8 +352,11 @@ void subghz_cli_command_rx(Cli* cli, FuriString* args, void* context) { } // Shutdown radio - furi_hal_subghz_stop_async_rx(); - furi_hal_subghz_sleep(); + subghz_devices_stop_async_rx(device); + subghz_devices_sleep(device); + subghz_devices_end(device); + subghz_devices_deinit(); + subghz_cli_radio_device_power_off(); furi_hal_power_suppress_charge_exit(); @@ -338,7 +398,7 @@ void subghz_cli_command_rx_raw(Cli* cli, FuriString* args, void* context) { furi_hal_subghz_reset(); furi_hal_subghz_load_preset(FuriHalSubGhzPresetOok270Async); frequency = furi_hal_subghz_set_frequency_and_path(frequency); - furi_hal_gpio_init(furi_hal_subghz.cc1101_g0_pin, GpioModeInput, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow); furi_hal_power_suppress_charge_enter(); @@ -384,6 +444,7 @@ void subghz_cli_command_rx_raw(Cli* cli, FuriString* args, void* context) { furi_stream_buffer_free(instance->stream); free(instance); } + void subghz_cli_command_decode_raw(Cli* cli, FuriString* args, void* context) { UNUSED(context); FuriString* file_name = furi_string_alloc(); @@ -435,25 +496,23 @@ void subghz_cli_command_decode_raw(Cli* cli, FuriString* args, void* context) { SubGhzCliCommandRx* instance = malloc(sizeof(SubGhzCliCommandRx)); SubGhzEnvironment* environment = subghz_environment_alloc(); - if(subghz_environment_load_keystore( - environment, EXT_PATH("subghz/assets/keeloq_mfcodes"))) { + if(subghz_environment_load_keystore(environment, SUBGHZ_KEYSTORE_DIR_NAME)) { printf("SubGhz decode_raw: Load_keystore keeloq_mfcodes \033[0;32mOK\033[0m\r\n"); } else { printf("SubGhz decode_raw: Load_keystore keeloq_mfcodes \033[0;31mERROR\033[0m\r\n"); } - if(subghz_environment_load_keystore( - environment, EXT_PATH("subghz/assets/keeloq_mfcodes_user"))) { + if(subghz_environment_load_keystore(environment, SUBGHZ_KEYSTORE_DIR_USER_NAME)) { printf("SubGhz decode_raw: Load_keystore keeloq_mfcodes_user \033[0;32mOK\033[0m\r\n"); } else { printf( "SubGhz decode_raw: Load_keystore keeloq_mfcodes_user \033[0;31mERROR\033[0m\r\n"); } subghz_environment_set_came_atomo_rainbow_table_file_name( - environment, EXT_PATH("subghz/assets/came_atomo")); + environment, SUBGHZ_CAME_ATOMO_DIR_NAME); subghz_environment_set_alutech_at_4n_rainbow_table_file_name( - environment, EXT_PATH("subghz/assets/alutech_at_4n")); + environment, SUBGHZ_ALUTECH_AT_4N_DIR_NAME); subghz_environment_set_nice_flor_s_rainbow_table_file_name( - environment, EXT_PATH("subghz/assets/nice_flor_s")); + environment, SUBGHZ_NICE_FLOR_S_DIR_NAME); subghz_environment_set_protocol_registry(environment, (void*)&subghz_protocol_registry); SubGhzReceiver* receiver = subghz_receiver_alloc_init(environment); @@ -461,7 +520,8 @@ void subghz_cli_command_decode_raw(Cli* cli, FuriString* args, void* context) { subghz_receiver_set_rx_callback(receiver, subghz_cli_command_rx_callback, instance); SubGhzFileEncoderWorker* file_worker_encoder = subghz_file_encoder_worker_alloc(); - if(subghz_file_encoder_worker_start(file_worker_encoder, furi_string_get_cstr(file_name))) { + if(subghz_file_encoder_worker_start( + file_worker_encoder, furi_string_get_cstr(file_name), NULL)) { //the worker needs a file in order to open and read part of the file furi_delay_ms(100); } @@ -503,10 +563,11 @@ static void subghz_cli_command_print_usage() { printf("subghz \r\n"); printf("Cmd list:\r\n"); - printf("\tchat \t - Chat with other Flippers\r\n"); printf( - "\ttx <3 byte Key: in hex> \t - Transmitting key\r\n"); - printf("\trx \t - Receive\r\n"); + "\tchat \t - Chat with other Flippers\r\n"); + printf( + "\ttx <3 byte Key: in hex> \t - Transmitting key\r\n"); + printf("\trx \t - Receive\r\n"); printf("\trx_raw \t - Receive RAW\r\n"); printf("\tdecode_raw \t - Testing\r\n"); @@ -600,21 +661,31 @@ static void subghz_cli_command_encrypt_raw(Cli* cli, FuriString* args) { static void subghz_cli_command_chat(Cli* cli, FuriString* args) { uint32_t frequency = 433920000; + uint32_t device_ind = 0; // 0 - CC1101_INT, 1 - CC1101_EXT if(furi_string_size(args)) { - int ret = sscanf(furi_string_get_cstr(args), "%lu", &frequency); - if(ret != 1) { - printf("sscanf returned %d, frequency: %lu\r\n", ret, frequency); - cli_print_usage("subghz chat", "", furi_string_get_cstr(args)); - return; - } - if(!furi_hal_subghz_is_frequency_valid(frequency)) { - printf( - "Frequency must be in " SUBGHZ_FREQUENCY_RANGE_STR " range, not %lu\r\n", - frequency); + int ret = sscanf(furi_string_get_cstr(args), "%lu %lu", &frequency, &device_ind); + if(ret != 2) { + printf("sscanf returned %d, Frequency: %lu\r\n", ret, frequency); + printf("sscanf returned %d, Device: %lu\r\n", ret, device_ind); + cli_print_usage( + "subghz chat", + " ", + furi_string_get_cstr(args)); return; } } + subghz_devices_init(); + const SubGhzDevice* device = subghz_cli_command_get_device(device_ind); + if(!subghz_devices_is_frequency_valid(device, frequency)) { + printf( + "Frequency must be in " SUBGHZ_FREQUENCY_RANGE_STR " range, not %lu\r\n", frequency); + subghz_devices_deinit(); + subghz_cli_radio_device_power_off(); + return; + } + + // TODO if(!furi_hal_subghz_is_tx_allowed(frequency)) { printf( "In your settings, only reception on this frequency (%lu) is allowed,\r\n" @@ -624,7 +695,8 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) { } SubGhzChatWorker* subghz_chat = subghz_chat_worker_alloc(cli); - if(!subghz_chat_worker_start(subghz_chat, frequency)) { + + if(!subghz_chat_worker_start(subghz_chat, device, frequency)) { printf("Startup error SubGhzChatWorker\r\n"); if(subghz_chat_worker_is_running(subghz_chat)) { @@ -766,6 +838,10 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) { furi_string_free(name); furi_string_free(output); furi_string_free(sysmsg); + + subghz_devices_deinit(); + subghz_cli_radio_device_power_off(); + furi_hal_power_suppress_charge_exit(); furi_record_close(RECORD_NOTIFICATION); @@ -779,14 +855,7 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) { static void subghz_cli_command(Cli* cli, FuriString* args, void* context) { FuriString* cmd = furi_string_alloc(); - // Enable power for External CC1101 if it is connected - furi_hal_subghz_enable_ext_power(); - // Auto switch to internal radio if external radio is not available - furi_delay_ms(15); - if(!furi_hal_subghz_check_radio()) { - furi_hal_subghz_select_radio_type(SubGhzRadioInternal); - furi_hal_subghz_init_radio_type(SubGhzRadioInternal); - } + // TODO external do { if(!args_read_string_and_trim(args, cmd)) { @@ -844,11 +913,6 @@ static void subghz_cli_command(Cli* cli, FuriString* args, void* context) { subghz_cli_command_print_usage(); } while(false); - // Disable power for External CC1101 if it was enabled and module is connected - furi_hal_subghz_disable_ext_power(); - // Reinit SPI handles for internal radio / nfc - furi_hal_subghz_init_radio_type(SubGhzRadioInternal); - furi_string_free(cmd); } diff --git a/applications/main/subghz/subghz_i.c b/applications/main/subghz/subghz_i.c index 70812ed11..4e44302f7 100644 --- a/applications/main/subghz/subghz_i.c +++ b/applications/main/subghz/subghz_i.c @@ -111,7 +111,7 @@ bool subghz_key_load(SubGhz* subghz, const char* file_path, bool show_dialog) { break; } - if(!furi_hal_subghz_is_frequency_valid(temp_data32)) { + if(!subghz_txrx_radio_device_is_frequecy_valid(subghz->txrx, temp_data32)) { FURI_LOG_E(TAG, "Frequency not supported"); break; } @@ -165,7 +165,8 @@ bool subghz_key_load(SubGhz* subghz, const char* file_path, bool show_dialog) { if(!strcmp(furi_string_get_cstr(temp_str), "RAW")) { //if RAW subghz->load_type_file = SubGhzLoadTypeFileRaw; - subghz_protocol_raw_gen_fff_data(fff_data, file_path); + subghz_protocol_raw_gen_fff_data( + fff_data, file_path, subghz_txrx_radio_device_get_name(subghz->txrx)); } else { subghz->load_type_file = SubGhzLoadTypeFileKey; stream_copy_full( diff --git a/applications/main/subghz/views/receiver.c b/applications/main/subghz/views/receiver.c index 53921f866..7438315b9 100644 --- a/applications/main/subghz/views/receiver.c +++ b/applications/main/subghz/views/receiver.c @@ -70,6 +70,7 @@ typedef struct { SubGhzViewReceiverBarShow bar_show; SubGhzViewReceiverMode mode; uint8_t u_rssi; + SubGhzRadioDeviceType device_type; size_t scroll_counter; bool nodraw; } SubGhzViewReceiverModel; @@ -202,6 +203,17 @@ void subghz_view_receiver_add_data_progress( true); } +void subghz_view_receiver_set_radio_device_type( + SubGhzViewReceiver* subghz_receiver, + SubGhzRadioDeviceType device_type) { + furi_assert(subghz_receiver); + with_view_model( + subghz_receiver->view, + SubGhzViewReceiverModel * model, + { model->device_type = device_type; }, + true); +} + static void subghz_view_receiver_draw_frame(Canvas* canvas, uint16_t idx, bool scrollbar) { canvas_set_color(canvas, ColorBlack); canvas_draw_box(canvas, 0, 0 + idx * FRAME_HEIGHT, scrollbar ? 122 : 127, FRAME_HEIGHT); @@ -289,12 +301,14 @@ void subghz_view_receiver_draw(Canvas* canvas, SubGhzViewReceiverModel* model) { canvas_set_color(canvas, ColorBlack); if(model->history_item == 0) { + // TODO if(model->mode == SubGhzViewReceiverModeLive) { canvas_draw_icon( canvas, 0, 0, - furi_hal_subghz_get_radio_type() ? &I_Fishing_123x52 : &I_Scanning_123x52); + (model->device_type == SubGhzRadioDeviceTypeInternal) ? &I_Scanning_123x52 : + &I_Fishing_123x52); canvas_set_font(canvas, FontPrimary); canvas_draw_str(canvas, 63, 46, "Scanning..."); //canvas_draw_line(canvas, 46, 51, 125, 51); @@ -304,7 +318,8 @@ void subghz_view_receiver_draw(Canvas* canvas, SubGhzViewReceiverModel* model) { canvas, 0, 0, - furi_hal_subghz_get_radio_type() ? &I_Fishing_123x52 : &I_Scanning_123x52); + (model->device_type == SubGhzRadioDeviceTypeInternal) ? &I_Scanning_123x52 : + &I_Fishing_123x52); canvas_set_font(canvas, FontPrimary); canvas_draw_str(canvas, 63, 46, "Decoding..."); canvas_set_font(canvas, FontSecondary); diff --git a/applications/main/subghz/views/receiver.h b/applications/main/subghz/views/receiver.h index f239331d5..57718cfc4 100644 --- a/applications/main/subghz/views/receiver.h +++ b/applications/main/subghz/views/receiver.h @@ -33,6 +33,10 @@ void subghz_view_receiver_add_data_statusbar( const char* preset_str, const char* history_stat_str); +void subghz_view_receiver_set_radio_device_type( + SubGhzViewReceiver* subghz_receiver, + SubGhzRadioDeviceType device_type); + void subghz_view_receiver_add_data_progress( SubGhzViewReceiver* subghz_receiver, const char* progress_str); diff --git a/applications/main/subghz/views/subghz_frequency_analyzer.c b/applications/main/subghz/views/subghz_frequency_analyzer.c index 30545d4b7..92ba833c4 100644 --- a/applications/main/subghz/views/subghz_frequency_analyzer.c +++ b/applications/main/subghz/views/subghz_frequency_analyzer.c @@ -12,6 +12,8 @@ #include #include +// TODO remove furi_hal_subghz + #define TAG "frequency_analyzer" #define RSSI_MIN -97 diff --git a/applications/main/subghz/views/subghz_read_raw.c b/applications/main/subghz/views/subghz_read_raw.c index fcc077efa..45668a6fc 100644 --- a/applications/main/subghz/views/subghz_read_raw.c +++ b/applications/main/subghz/views/subghz_read_raw.c @@ -31,6 +31,7 @@ typedef struct { bool raw_send_only; float raw_threshold_rssi; bool not_showing_samples; + SubGhzRadioDeviceType device_type; } SubGhzReadRAWModel; void subghz_read_raw_set_callback( @@ -58,6 +59,14 @@ void subghz_read_raw_add_data_statusbar( true); } +void subghz_read_raw_set_radio_device_type( + SubGhzReadRAW* instance, + SubGhzRadioDeviceType device_type) { + furi_assert(instance); + with_view_model( + instance->view, SubGhzReadRAWModel * model, { model->device_type = device_type; }, true); +} + void subghz_read_raw_add_data_rssi(SubGhzReadRAW* instance, float rssi, bool trace) { furi_assert(instance); uint8_t u_rssi = 0; @@ -288,9 +297,15 @@ void subghz_read_raw_draw(Canvas* canvas, SubGhzReadRAWModel* model) { canvas_draw_str(canvas, 35, 7, furi_string_get_cstr(model->preset_str)); if(model->not_showing_samples) { - canvas_draw_str(canvas, 77, 7, furi_hal_subghz_get_radio_type() ? "R: Ext" : "R: Int"); + // TODO + canvas_draw_str( + canvas, + 77, + 7, + (model->device_type == SubGhzRadioDeviceTypeInternal) ? "R: Int" : "R: Ext"); } else { - canvas_draw_str(canvas, 70, 7, furi_hal_subghz_get_radio_type() ? "E" : "I"); + canvas_draw_str( + canvas, 70, 7, (model->device_type == SubGhzRadioDeviceTypeInternal) ? "I" : "E"); } canvas_draw_str_aligned( diff --git a/applications/main/subghz/views/subghz_read_raw.h b/applications/main/subghz/views/subghz_read_raw.h index 9d63870d5..c7d87f2d5 100644 --- a/applications/main/subghz/views/subghz_read_raw.h +++ b/applications/main/subghz/views/subghz_read_raw.h @@ -1,6 +1,7 @@ #pragma once #include +#include "../helpers/subghz_types.h" #include "../helpers/subghz_custom_event.h" #define SUBGHZ_RAW_THRESHOLD_MIN -90.0f @@ -36,6 +37,10 @@ void subghz_read_raw_add_data_statusbar( const char* frequency_str, const char* preset_str); +void subghz_read_raw_set_radio_device_type( + SubGhzReadRAW* instance, + SubGhzRadioDeviceType device_type); + void subghz_read_raw_update_sample_write(SubGhzReadRAW* instance, size_t sample); void subghz_read_raw_stop_send(SubGhzReadRAW* instance); diff --git a/applications/main/subghz/views/transmitter.c b/applications/main/subghz/views/transmitter.c index 7ae865064..16a2ea110 100644 --- a/applications/main/subghz/views/transmitter.c +++ b/applications/main/subghz/views/transmitter.c @@ -17,6 +17,7 @@ typedef struct { FuriString* preset_str; FuriString* key_str; bool show_button; + SubGhzRadioDeviceType device_type; FuriString* temp_button_id; bool draw_temp_button; } SubGhzViewTransmitterModel; @@ -50,6 +51,17 @@ void subghz_view_transmitter_add_data_to_show( true); } +void subghz_view_transmitter_set_radio_device_type( + SubGhzViewTransmitter* subghz_transmitter, + SubGhzRadioDeviceType device_type) { + furi_assert(subghz_transmitter); + with_view_model( + subghz_transmitter->view, + SubGhzViewTransmitterModel * model, + { model->device_type = device_type; }, + true); +} + static void subghz_view_transmitter_button_right(Canvas* canvas, const char* str) { const uint8_t button_height = 12; const uint8_t vertical_offset = 3; @@ -100,7 +112,12 @@ void subghz_view_transmitter_draw(Canvas* canvas, SubGhzViewTransmitterModel* mo } if(model->show_button) { - canvas_draw_str(canvas, 58, 62, furi_hal_subghz_get_radio_type() ? "R: Ext" : "R: Int"); + // TODO + canvas_draw_str( + canvas, + 58, + 62, + (model->device_type == SubGhzRadioDeviceTypeInternal) ? "R: Int" : "R: Ext"); subghz_view_transmitter_button_right(canvas, "Send"); } } diff --git a/applications/main/subghz/views/transmitter.h b/applications/main/subghz/views/transmitter.h index 06aae7c6b..19da3145c 100644 --- a/applications/main/subghz/views/transmitter.h +++ b/applications/main/subghz/views/transmitter.h @@ -1,6 +1,7 @@ #pragma once #include +#include "../helpers/subghz_types.h" #include "../helpers/subghz_custom_event.h" typedef struct SubGhzViewTransmitter SubGhzViewTransmitter; @@ -12,6 +13,10 @@ void subghz_view_transmitter_set_callback( SubGhzViewTransmitterCallback callback, void* context); +void subghz_view_transmitter_set_radio_device_type( + SubGhzViewTransmitter* subghz_transmitter, + SubGhzRadioDeviceType device_type); + SubGhzViewTransmitter* subghz_view_transmitter_alloc(); void subghz_view_transmitter_free(SubGhzViewTransmitter* subghz_transmitter); diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 26e4c0b3e..27842dd16 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,30.1,, +Version,+,32.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -56,7 +56,6 @@ Header,+,firmware/targets/f7/furi_hal/furi_hal_rfid.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_spi_config.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_spi_types.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_subghz.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_subghz_configs.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_target_hw.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_uart.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_usb_cdc.h,, @@ -316,6 +315,7 @@ Function,-,LL_mDelay,void,uint32_t Function,-,SystemCoreClockUpdate,void, Function,-,SystemInit,void, Function,-,_Exit,void,int +Function,+,__aeabi_uldivmod,void*,"uint64_t, uint64_t" Function,-,__assert,void,"const char*, int, const char*" Function,+,__assert_func,void,"const char*, int, const char*, const char*" Function,+,__clear_cache,void,"void*, void*" @@ -668,26 +668,6 @@ Function,+,canvas_width,uint8_t,const Canvas* Function,-,cbrt,double,double Function,-,cbrtf,float,float Function,-,cbrtl,long double,long double -Function,+,cc1101_calibrate,void,FuriHalSpiBusHandle* -Function,+,cc1101_flush_rx,void,FuriHalSpiBusHandle* -Function,+,cc1101_flush_tx,void,FuriHalSpiBusHandle* -Function,-,cc1101_get_partnumber,uint8_t,FuriHalSpiBusHandle* -Function,+,cc1101_get_rssi,uint8_t,FuriHalSpiBusHandle* -Function,+,cc1101_get_status,CC1101Status,FuriHalSpiBusHandle* -Function,-,cc1101_get_version,uint8_t,FuriHalSpiBusHandle* -Function,+,cc1101_read_fifo,uint8_t,"FuriHalSpiBusHandle*, uint8_t*, uint8_t*" -Function,+,cc1101_read_reg,CC1101Status,"FuriHalSpiBusHandle*, uint8_t, uint8_t*" -Function,+,cc1101_reset,void,FuriHalSpiBusHandle* -Function,+,cc1101_set_frequency,uint32_t,"FuriHalSpiBusHandle*, uint32_t" -Function,-,cc1101_set_intermediate_frequency,uint32_t,"FuriHalSpiBusHandle*, uint32_t" -Function,+,cc1101_set_pa_table,void,"FuriHalSpiBusHandle*, const uint8_t[8]" -Function,+,cc1101_shutdown,void,FuriHalSpiBusHandle* -Function,+,cc1101_strobe,CC1101Status,"FuriHalSpiBusHandle*, uint8_t" -Function,+,cc1101_switch_to_idle,void,FuriHalSpiBusHandle* -Function,+,cc1101_switch_to_rx,void,FuriHalSpiBusHandle* -Function,+,cc1101_switch_to_tx,void,FuriHalSpiBusHandle* -Function,+,cc1101_write_fifo,uint8_t,"FuriHalSpiBusHandle*, const uint8_t*, uint8_t" -Function,+,cc1101_write_reg,CC1101Status,"FuriHalSpiBusHandle*, uint8_t, uint8_t" Function,-,ceil,double,double Function,-,ceilf,float,float Function,-,ceill,long double,long double @@ -1394,21 +1374,15 @@ Function,-,furi_hal_spi_config_init,void, Function,-,furi_hal_spi_config_init_early,void, Function,-,furi_hal_spi_dma_init,void, Function,+,furi_hal_spi_release,void,FuriHalSpiBusHandle* -Function,+,furi_hal_subghz_check_radio,_Bool, -Function,+,furi_hal_subghz_disable_ext_power,void, Function,-,furi_hal_subghz_dump_state,void, -Function,+,furi_hal_subghz_enable_ext_power,_Bool, Function,+,furi_hal_subghz_flush_rx,void, Function,+,furi_hal_subghz_flush_tx,void, -Function,+,furi_hal_subghz_get_external_power_disable,_Bool, +Function,+,furi_hal_subghz_get_data_gpio,const GpioPin*, Function,+,furi_hal_subghz_get_lqi,uint8_t, -Function,+,furi_hal_subghz_get_radio_type,SubGhzRadioType, Function,+,furi_hal_subghz_get_rolling_counter_mult,uint8_t, Function,+,furi_hal_subghz_get_rssi,float, Function,+,furi_hal_subghz_idle,void, Function,-,furi_hal_subghz_init,void, -Function,-,furi_hal_subghz_init_check,_Bool, -Function,+,furi_hal_subghz_init_radio_type,_Bool,SubGhzRadioType Function,+,furi_hal_subghz_is_async_tx_complete,_Bool, Function,+,furi_hal_subghz_is_frequency_valid,_Bool,uint32_t Function,+,furi_hal_subghz_is_rx_data_crc_valid,_Bool, @@ -1421,9 +1395,7 @@ Function,+,furi_hal_subghz_read_packet,void,"uint8_t*, uint8_t*" Function,+,furi_hal_subghz_reset,void, Function,+,furi_hal_subghz_rx,void, Function,+,furi_hal_subghz_rx_pipe_not_empty,_Bool, -Function,+,furi_hal_subghz_select_radio_type,void,SubGhzRadioType Function,+,furi_hal_subghz_set_async_mirror_pin,void,const GpioPin* -Function,+,furi_hal_subghz_set_external_power_disable,void,_Bool Function,+,furi_hal_subghz_set_frequency,uint32_t,uint32_t Function,+,furi_hal_subghz_set_frequency_and_path,uint32_t,uint32_t Function,+,furi_hal_subghz_set_path,void,FuriHalSubGhzPath @@ -2706,6 +2678,36 @@ Function,+,subghz_block_generic_deserialize,SubGhzProtocolStatus,"SubGhzBlockGen Function,+,subghz_block_generic_deserialize_check_count_bit,SubGhzProtocolStatus,"SubGhzBlockGeneric*, FlipperFormat*, uint16_t" Function,+,subghz_block_generic_get_preset_name,void,"const char*, FuriString*" Function,+,subghz_block_generic_serialize,SubGhzProtocolStatus,"SubGhzBlockGeneric*, FlipperFormat*, SubGhzRadioPreset*" +Function,+,subghz_devices_begin,_Bool,const SubGhzDevice* +Function,+,subghz_devices_deinit,void, +Function,+,subghz_devices_end,void,const SubGhzDevice* +Function,+,subghz_devices_flush_rx,void,const SubGhzDevice* +Function,+,subghz_devices_flush_tx,void,const SubGhzDevice* +Function,+,subghz_devices_get_by_name,const SubGhzDevice*,const char* +Function,+,subghz_devices_get_data_gpio,const GpioPin*,const SubGhzDevice* +Function,+,subghz_devices_get_lqi,uint8_t,const SubGhzDevice* +Function,+,subghz_devices_get_name,const char*,const SubGhzDevice* +Function,+,subghz_devices_get_rssi,float,const SubGhzDevice* +Function,+,subghz_devices_idle,void,const SubGhzDevice* +Function,+,subghz_devices_init,void, +Function,+,subghz_devices_is_async_complete_tx,_Bool,const SubGhzDevice* +Function,+,subghz_devices_is_connect,_Bool,const SubGhzDevice* +Function,+,subghz_devices_is_frequency_valid,_Bool,"const SubGhzDevice*, uint32_t" +Function,+,subghz_devices_is_rx_data_crc_valid,_Bool,const SubGhzDevice* +Function,+,subghz_devices_load_preset,void,"const SubGhzDevice*, FuriHalSubGhzPreset, uint8_t*" +Function,+,subghz_devices_read_packet,void,"const SubGhzDevice*, uint8_t*, uint8_t*" +Function,+,subghz_devices_reset,void,const SubGhzDevice* +Function,+,subghz_devices_rx_pipe_not_empty,_Bool,const SubGhzDevice* +Function,+,subghz_devices_set_async_mirror_pin,void,"const SubGhzDevice*, const GpioPin*" +Function,+,subghz_devices_set_frequency,uint32_t,"const SubGhzDevice*, uint32_t" +Function,+,subghz_devices_set_rx,void,const SubGhzDevice* +Function,+,subghz_devices_set_tx,_Bool,const SubGhzDevice* +Function,+,subghz_devices_sleep,void,const SubGhzDevice* +Function,+,subghz_devices_start_async_rx,void,"const SubGhzDevice*, void*, void*" +Function,+,subghz_devices_start_async_tx,_Bool,"const SubGhzDevice*, void*, void*" +Function,+,subghz_devices_stop_async_rx,void,const SubGhzDevice* +Function,+,subghz_devices_stop_async_tx,void,const SubGhzDevice* +Function,+,subghz_devices_write_packet,void,"const SubGhzDevice*, const uint8_t*, uint8_t" Function,+,subghz_environment_alloc,SubGhzEnvironment*, Function,+,subghz_environment_free,void,SubGhzEnvironment* Function,+,subghz_environment_get_alutech_at_4n_rainbow_table_file_name,const char*,SubGhzEnvironment* @@ -2766,7 +2768,7 @@ Function,+,subghz_protocol_encoder_raw_free,void,void* Function,+,subghz_protocol_encoder_raw_stop,void,void* Function,+,subghz_protocol_encoder_raw_yield,LevelDuration,void* Function,+,subghz_protocol_raw_file_encoder_worker_set_callback_end,void,"SubGhzProtocolEncoderRAW*, SubGhzProtocolEncoderRAWCallbackEnd, void*" -Function,+,subghz_protocol_raw_gen_fff_data,void,"FlipperFormat*, const char*" +Function,+,subghz_protocol_raw_gen_fff_data,void,"FlipperFormat*, const char*, const char*" Function,+,subghz_protocol_raw_get_sample_write,size_t,SubGhzProtocolDecoderRAW* Function,+,subghz_protocol_raw_save_to_file_init,_Bool,"SubGhzProtocolDecoderRAW*, const char*, SubGhzRadioPreset*" Function,+,subghz_protocol_raw_save_to_file_pause,void,"SubGhzProtocolDecoderRAW*, _Bool" @@ -2811,7 +2813,7 @@ Function,+,subghz_tx_rx_worker_free,void,SubGhzTxRxWorker* Function,+,subghz_tx_rx_worker_is_running,_Bool,SubGhzTxRxWorker* Function,+,subghz_tx_rx_worker_read,size_t,"SubGhzTxRxWorker*, uint8_t*, size_t" Function,+,subghz_tx_rx_worker_set_callback_have_read,void,"SubGhzTxRxWorker*, SubGhzTxRxWorkerCallbackHaveRead, void*" -Function,+,subghz_tx_rx_worker_start,_Bool,"SubGhzTxRxWorker*, uint32_t" +Function,+,subghz_tx_rx_worker_start,_Bool,"SubGhzTxRxWorker*, const SubGhzDevice*, uint32_t" Function,+,subghz_tx_rx_worker_stop,void,SubGhzTxRxWorker* Function,+,subghz_tx_rx_worker_write,_Bool,"SubGhzTxRxWorker*, uint8_t*, size_t" Function,+,subghz_worker_alloc,SubGhzWorker*, @@ -3165,15 +3167,12 @@ Variable,+,furi_hal_spi_bus_handle_nfc,FuriHalSpiBusHandle, Variable,+,furi_hal_spi_bus_handle_sd_fast,FuriHalSpiBusHandle, Variable,+,furi_hal_spi_bus_handle_sd_slow,FuriHalSpiBusHandle, Variable,+,furi_hal_spi_bus_handle_subghz,FuriHalSpiBusHandle, -Variable,+,furi_hal_spi_bus_handle_subghz_ext,FuriHalSpiBusHandle, -Variable,+,furi_hal_spi_bus_handle_subghz_int,FuriHalSpiBusHandle, Variable,+,furi_hal_spi_bus_r,FuriHalSpiBus, Variable,+,furi_hal_spi_preset_1edge_low_16m,const LL_SPI_InitTypeDef, Variable,+,furi_hal_spi_preset_1edge_low_2m,const LL_SPI_InitTypeDef, Variable,+,furi_hal_spi_preset_1edge_low_4m,const LL_SPI_InitTypeDef, Variable,+,furi_hal_spi_preset_1edge_low_8m,const LL_SPI_InitTypeDef, Variable,+,furi_hal_spi_preset_2edge_low_8m,const LL_SPI_InitTypeDef, -Variable,+,furi_hal_subghz,volatile FuriHalSubGhz, Variable,+,gpio_button_back,const GpioPin, Variable,+,gpio_button_down,const GpioPin, Variable,+,gpio_button_left,const GpioPin, @@ -3181,7 +3180,6 @@ Variable,+,gpio_button_ok,const GpioPin, Variable,+,gpio_button_right,const GpioPin, Variable,+,gpio_button_up,const GpioPin, Variable,+,gpio_cc1101_g0,const GpioPin, -Variable,+,gpio_cc1101_g0_ext,const GpioPin, Variable,+,gpio_display_cs,const GpioPin, Variable,+,gpio_display_di,const GpioPin, Variable,+,gpio_display_rst_n,const GpioPin, @@ -3214,13 +3212,9 @@ Variable,+,gpio_spi_d_miso,const GpioPin, Variable,+,gpio_spi_d_mosi,const GpioPin, Variable,+,gpio_spi_d_sck,const GpioPin, Variable,+,gpio_spi_r_miso,const GpioPin, -Variable,+,gpio_spi_r_miso_ext,const GpioPin, Variable,+,gpio_spi_r_mosi,const GpioPin, -Variable,+,gpio_spi_r_mosi_ext,const GpioPin, Variable,+,gpio_spi_r_sck,const GpioPin, -Variable,+,gpio_spi_r_sck_ext,const GpioPin, Variable,+,gpio_subghz_cs,const GpioPin, -Variable,+,gpio_subghz_cs_ext,const GpioPin, Variable,+,gpio_swclk,const GpioPin, Variable,+,gpio_swdio,const GpioPin, Variable,+,gpio_usart_rx,const GpioPin, diff --git a/firmware/targets/f7/furi_hal/furi_hal_resources.c b/firmware/targets/f7/furi_hal/furi_hal_resources.c index 63507bd7b..34b26b831 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_resources.c +++ b/firmware/targets/f7/furi_hal/furi_hal_resources.c @@ -14,11 +14,9 @@ const GpioPin gpio_vibro = {.port = VIBRO_GPIO_Port, .pin = VIBRO_Pin}; const GpioPin gpio_ibutton = {.port = iBTN_GPIO_Port, .pin = iBTN_Pin}; const GpioPin gpio_cc1101_g0 = {.port = CC1101_G0_GPIO_Port, .pin = CC1101_G0_Pin}; -const GpioPin gpio_cc1101_g0_ext = {.port = GPIOB, .pin = LL_GPIO_PIN_2}; const GpioPin gpio_rf_sw_0 = {.port = RF_SW_0_GPIO_Port, .pin = RF_SW_0_Pin}; const GpioPin gpio_subghz_cs = {.port = CC1101_CS_GPIO_Port, .pin = CC1101_CS_Pin}; -const GpioPin gpio_subghz_cs_ext = {.port = GPIOA, .pin = LL_GPIO_PIN_4}; const GpioPin gpio_display_cs = {.port = DISPLAY_CS_GPIO_Port, .pin = DISPLAY_CS_Pin}; const GpioPin gpio_display_rst_n = {.port = DISPLAY_RST_GPIO_Port, .pin = DISPLAY_RST_Pin}; const GpioPin gpio_display_di = {.port = DISPLAY_DI_GPIO_Port, .pin = DISPLAY_DI_Pin}; @@ -39,9 +37,6 @@ const GpioPin gpio_spi_d_sck = {.port = SPI_D_SCK_GPIO_Port, .pin = SPI_D_SCK_Pi const GpioPin gpio_spi_r_miso = {.port = SPI_R_MISO_GPIO_Port, .pin = SPI_R_MISO_Pin}; const GpioPin gpio_spi_r_mosi = {.port = SPI_R_MOSI_GPIO_Port, .pin = SPI_R_MOSI_Pin}; const GpioPin gpio_spi_r_sck = {.port = SPI_R_SCK_GPIO_Port, .pin = SPI_R_SCK_Pin}; -const GpioPin gpio_spi_r_miso_ext = {.port = GPIOA, .pin = LL_GPIO_PIN_6}; -const GpioPin gpio_spi_r_mosi_ext = {.port = GPIOA, .pin = LL_GPIO_PIN_7}; -const GpioPin gpio_spi_r_sck_ext = {.port = GPIOB, .pin = LL_GPIO_PIN_3}; const GpioPin gpio_ext_pc0 = {.port = GPIOC, .pin = LL_GPIO_PIN_0}; const GpioPin gpio_ext_pc1 = {.port = GPIOC, .pin = LL_GPIO_PIN_1}; diff --git a/firmware/targets/f7/furi_hal/furi_hal_resources.h b/firmware/targets/f7/furi_hal/furi_hal_resources.h index 391f8f4ff..6e585c518 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_resources.h +++ b/firmware/targets/f7/furi_hal/furi_hal_resources.h @@ -57,11 +57,9 @@ extern const GpioPin gpio_vibro; extern const GpioPin gpio_ibutton; extern const GpioPin gpio_cc1101_g0; -extern const GpioPin gpio_cc1101_g0_ext; extern const GpioPin gpio_rf_sw_0; extern const GpioPin gpio_subghz_cs; -extern const GpioPin gpio_subghz_cs_ext; extern const GpioPin gpio_display_cs; extern const GpioPin gpio_display_rst_n; extern const GpioPin gpio_display_di; @@ -82,9 +80,6 @@ extern const GpioPin gpio_spi_d_sck; extern const GpioPin gpio_spi_r_miso; extern const GpioPin gpio_spi_r_mosi; extern const GpioPin gpio_spi_r_sck; -extern const GpioPin gpio_spi_r_miso_ext; -extern const GpioPin gpio_spi_r_mosi_ext; -extern const GpioPin gpio_spi_r_sck_ext; extern const GpioPin gpio_ext_pc0; extern const GpioPin gpio_ext_pc1; diff --git a/firmware/targets/f7/furi_hal/furi_hal_spi_config.c b/firmware/targets/f7/furi_hal/furi_hal_spi_config.c index c73c71a4f..09ac79d2a 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_spi_config.c +++ b/firmware/targets/f7/furi_hal/furi_hal_spi_config.c @@ -3,7 +3,6 @@ #include #include #include -#include #define TAG "FuriHalSpiConfig" @@ -91,7 +90,7 @@ void furi_hal_spi_config_deinit_early() { void furi_hal_spi_config_init() { furi_hal_spi_bus_init(&furi_hal_spi_bus_r); - furi_hal_spi_bus_handle_init(furi_hal_subghz.spi_bus_handle); + furi_hal_spi_bus_handle_init(&furi_hal_spi_bus_handle_subghz); furi_hal_spi_bus_handle_init(&furi_hal_spi_bus_handle_nfc); furi_hal_spi_bus_handle_init(&furi_hal_spi_bus_handle_sd_fast); furi_hal_spi_bus_handle_init(&furi_hal_spi_bus_handle_sd_slow); @@ -265,15 +264,6 @@ static void furi_hal_spi_bus_handle_subghz_event_callback( furi_hal_spi_bus_r_handle_event_callback(handle, event, &furi_hal_spi_preset_1edge_low_8m); } -FuriHalSpiBusHandle furi_hal_spi_bus_handle_subghz_int = { - .bus = &furi_hal_spi_bus_r, - .callback = furi_hal_spi_bus_handle_subghz_event_callback, - .miso = &gpio_spi_r_miso, - .mosi = &gpio_spi_r_mosi, - .sck = &gpio_spi_r_sck, - .cs = &gpio_subghz_cs, -}; - FuriHalSpiBusHandle furi_hal_spi_bus_handle_subghz = { .bus = &furi_hal_spi_bus_r, .callback = furi_hal_spi_bus_handle_subghz_event_callback, @@ -283,15 +273,6 @@ FuriHalSpiBusHandle furi_hal_spi_bus_handle_subghz = { .cs = &gpio_subghz_cs, }; -FuriHalSpiBusHandle furi_hal_spi_bus_handle_subghz_ext = { - .bus = &furi_hal_spi_bus_r, - .callback = furi_hal_spi_bus_handle_subghz_event_callback, - .miso = &gpio_ext_pa6, - .mosi = &gpio_ext_pa7, - .sck = &gpio_ext_pb3, - .cs = &gpio_ext_pa4, -}; - static void furi_hal_spi_bus_handle_nfc_event_callback( FuriHalSpiBusHandle* handle, FuriHalSpiBusHandleEvent event) { diff --git a/firmware/targets/f7/furi_hal/furi_hal_spi_config.h b/firmware/targets/f7/furi_hal/furi_hal_spi_config.h index 8ea138bdc..eab633a19 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_spi_config.h +++ b/firmware/targets/f7/furi_hal/furi_hal_spi_config.h @@ -27,12 +27,8 @@ extern FuriHalSpiBus furi_hal_spi_bus_r; /** Furi Hal Spi Bus D (Display, SdCard) */ extern FuriHalSpiBus furi_hal_spi_bus_d; -/** CC1101 on current SPI bus */ -extern FuriHalSpiBusHandle furi_hal_spi_bus_handle_subghz; /** CC1101 on `furi_hal_spi_bus_r` */ -extern FuriHalSpiBusHandle furi_hal_spi_bus_handle_subghz_int; -/** CC1101 on external `furi_hal_spi_bus_r` */ -extern FuriHalSpiBusHandle furi_hal_spi_bus_handle_subghz_ext; +extern FuriHalSpiBusHandle furi_hal_spi_bus_handle_subghz; /** ST25R3916 on `furi_hal_spi_bus_r` */ extern FuriHalSpiBusHandle furi_hal_spi_bus_handle_nfc; diff --git a/firmware/targets/f7/furi_hal/furi_hal_subghz.c b/firmware/targets/f7/furi_hal/furi_hal_subghz.c index aa7438b0b..d6b08d7b7 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_subghz.c +++ b/firmware/targets/f7/furi_hal/furi_hal_subghz.c @@ -1,25 +1,22 @@ #include -#include +#include #include #include #include #include #include -#include #include #include -#include +#include // TODO #include #include #include #define TAG "FuriHalSubGhz" -//Initialisation timeout (ms) -#define INIT_TIMEOUT 10 static uint32_t furi_hal_subghz_debug_gpio_buff[2]; @@ -31,48 +28,47 @@ static uint32_t furi_hal_subghz_debug_gpio_buff[2]; #define SUBGHZ_DMA_CH1_DEF SUBGHZ_DMA, SUBGHZ_DMA_CH1_CHANNEL #define SUBGHZ_DMA_CH2_DEF SUBGHZ_DMA, SUBGHZ_DMA_CH2_CHANNEL +/** SubGhz state */ +typedef enum { + SubGhzStateInit, /**< Init pending */ + + SubGhzStateIdle, /**< Idle, energy save mode */ + + SubGhzStateAsyncRx, /**< Async RX started */ + + SubGhzStateAsyncTx, /**< Async TX started, DMA and timer is on */ + SubGhzStateAsyncTxLast, /**< Async TX continue, DMA completed and timer got last value to go */ + SubGhzStateAsyncTxEnd, /**< Async TX complete, cleanup needed */ + +} SubGhzState; + +/** SubGhz regulation, receive transmission on the current frequency for the + * region */ +typedef enum { + SubGhzRegulationOnlyRx, /**only Rx*/ + SubGhzRegulationTxRx, /**TxRx*/ +} SubGhzRegulation; + +typedef struct { + volatile SubGhzState state; + volatile SubGhzRegulation regulation; + volatile FuriHalSubGhzPreset preset; + const GpioPin* async_mirror_pin; + + uint8_t rolling_counter_mult; + bool timestamp_file_names : 1; + bool dangerous_frequency_i : 1; +} FuriHalSubGhz; + volatile FuriHalSubGhz furi_hal_subghz = { .state = SubGhzStateInit, .regulation = SubGhzRegulationTxRx, .preset = FuriHalSubGhzPresetIDLE, .async_mirror_pin = NULL, - .radio_type = SubGhzRadioInternal, - .spi_bus_handle = &furi_hal_spi_bus_handle_subghz, - .cc1101_g0_pin = &gpio_cc1101_g0, .rolling_counter_mult = 1, - .ext_module_power_disabled = false, .dangerous_frequency_i = false, }; -void furi_hal_subghz_select_radio_type(SubGhzRadioType state) { - furi_hal_subghz.radio_type = state; -} - -bool furi_hal_subghz_init_radio_type(SubGhzRadioType state) { - if(state == SubGhzRadioInternal && furi_hal_subghz.cc1101_g0_pin == &gpio_cc1101_g0) { - return true; - } else if(state == SubGhzRadioExternal && furi_hal_subghz.cc1101_g0_pin == &gpio_cc1101_g0_ext) { - return true; - } - furi_hal_spi_bus_handle_deinit(furi_hal_subghz.spi_bus_handle); - - if(state == SubGhzRadioInternal) { - furi_hal_subghz.spi_bus_handle = &furi_hal_spi_bus_handle_subghz; - furi_hal_subghz.cc1101_g0_pin = &gpio_cc1101_g0; - } else { - furi_hal_subghz.spi_bus_handle = &furi_hal_spi_bus_handle_subghz_ext; - furi_hal_subghz.cc1101_g0_pin = &gpio_cc1101_g0_ext; - } - - furi_hal_spi_bus_handle_init(furi_hal_subghz.spi_bus_handle); - furi_hal_subghz_init_check(); - return true; -} - -SubGhzRadioType furi_hal_subghz_get_radio_type(void) { - return furi_hal_subghz.radio_type; -} - uint8_t furi_hal_subghz_get_rolling_counter_mult(void) { return furi_hal_subghz.rolling_counter_mult; } @@ -81,14 +77,6 @@ void furi_hal_subghz_set_rolling_counter_mult(uint8_t mult) { furi_hal_subghz.rolling_counter_mult = mult; } -void furi_hal_subghz_set_external_power_disable(bool state) { - furi_hal_subghz.ext_module_power_disabled = state; -} - -bool furi_hal_subghz_get_external_power_disable(void) { - return furi_hal_subghz.ext_module_power_disabled; -} - void furi_hal_subghz_set_dangerous_frequency(bool state_i) { furi_hal_subghz.dangerous_frequency_i = state_i; } @@ -97,158 +85,105 @@ void furi_hal_subghz_set_async_mirror_pin(const GpioPin* pin) { furi_hal_subghz.async_mirror_pin = pin; } -void furi_hal_subghz_init(void) { - furi_hal_subghz_init_check(); +const GpioPin* furi_hal_subghz_get_data_gpio() { + return &gpio_cc1101_g0; } -bool furi_hal_subghz_enable_ext_power(void) { - if(furi_hal_subghz.ext_module_power_disabled) { - return false; - } - if(furi_hal_subghz.radio_type != SubGhzRadioInternal) { - uint8_t attempts = 0; - while(!furi_hal_power_is_otg_enabled() && attempts++ < 5) { - furi_hal_power_enable_otg(); - //CC1101 power-up time - furi_delay_ms(10); - } - } - return furi_hal_power_is_otg_enabled(); -} - -void furi_hal_subghz_disable_ext_power(void) { - if(furi_hal_power_is_otg_enabled()) { - furi_hal_power_disable_otg(); - } -} - -bool furi_hal_subghz_check_radio(void) { - bool result = true; - - furi_hal_subghz_init_radio_type(furi_hal_subghz.radio_type); - - furi_hal_spi_acquire(furi_hal_subghz.spi_bus_handle); - - uint8_t ver = cc1101_get_version(furi_hal_subghz.spi_bus_handle); - furi_hal_spi_release(furi_hal_subghz.spi_bus_handle); - - if((ver != 0) && (ver != 255)) { - FURI_LOG_D(TAG, "Radio check ok"); - } else { - FURI_LOG_D(TAG, "Radio check failed, revert to default"); - - result = false; - } - return result; -} - -bool furi_hal_subghz_init_check(void) { - bool result = true; - +void furi_hal_subghz_init() { furi_assert(furi_hal_subghz.state == SubGhzStateInit); furi_hal_subghz.state = SubGhzStateIdle; furi_hal_subghz.preset = FuriHalSubGhzPresetIDLE; - furi_hal_spi_acquire(furi_hal_subghz.spi_bus_handle); + furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); #ifdef FURI_HAL_SUBGHZ_TX_GPIO furi_hal_gpio_init(&FURI_HAL_SUBGHZ_TX_GPIO, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); #endif // Reset - furi_hal_gpio_init(furi_hal_subghz.cc1101_g0_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); - cc1101_reset(furi_hal_subghz.spi_bus_handle); - cc1101_write_reg(furi_hal_subghz.spi_bus_handle, CC1101_IOCFG0, CC1101IocfgHighImpedance); + furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + cc1101_reset(&furi_hal_spi_bus_handle_subghz); + cc1101_write_reg(&furi_hal_spi_bus_handle_subghz, CC1101_IOCFG0, CC1101IocfgHighImpedance); // Prepare GD0 for power on self test - furi_hal_gpio_init(furi_hal_subghz.cc1101_g0_pin, GpioModeInput, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow); // GD0 low - cc1101_write_reg(furi_hal_subghz.spi_bus_handle, CC1101_IOCFG0, CC1101IocfgHW); - uint32_t test_start_time = furi_get_tick(); - while(furi_hal_gpio_read(furi_hal_subghz.cc1101_g0_pin) != false && result) { - if(furi_get_tick() - test_start_time > INIT_TIMEOUT) { - result = false; - } - } + cc1101_write_reg(&furi_hal_spi_bus_handle_subghz, CC1101_IOCFG0, CC1101IocfgHW); + while(furi_hal_gpio_read(&gpio_cc1101_g0) != false) + ; // GD0 high cc1101_write_reg( - furi_hal_subghz.spi_bus_handle, CC1101_IOCFG0, CC1101IocfgHW | CC1101_IOCFG_INV); - test_start_time = furi_get_tick(); - while(furi_hal_gpio_read(furi_hal_subghz.cc1101_g0_pin) != true && result) { - if(furi_get_tick() - test_start_time > INIT_TIMEOUT) { - result = false; - } - } + &furi_hal_spi_bus_handle_subghz, CC1101_IOCFG0, CC1101IocfgHW | CC1101_IOCFG_INV); + while(furi_hal_gpio_read(&gpio_cc1101_g0) != true) + ; // Reset GD0 to floating state - cc1101_write_reg(furi_hal_subghz.spi_bus_handle, CC1101_IOCFG0, CC1101IocfgHighImpedance); - furi_hal_gpio_init(furi_hal_subghz.cc1101_g0_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + cc1101_write_reg(&furi_hal_spi_bus_handle_subghz, CC1101_IOCFG0, CC1101IocfgHighImpedance); + furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeAnalog, GpioPullNo, GpioSpeedLow); // RF switches furi_hal_gpio_init(&gpio_rf_sw_0, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); - cc1101_write_reg(furi_hal_subghz.spi_bus_handle, CC1101_IOCFG2, CC1101IocfgHW); + cc1101_write_reg(&furi_hal_spi_bus_handle_subghz, CC1101_IOCFG2, CC1101IocfgHW); // Go to sleep - cc1101_shutdown(furi_hal_subghz.spi_bus_handle); + cc1101_shutdown(&furi_hal_spi_bus_handle_subghz); - furi_hal_spi_release(furi_hal_subghz.spi_bus_handle); - - if(result) { - FURI_LOG_I(TAG, "Init OK"); - } else { - FURI_LOG_E(TAG, "Selected CC1101 module init failed, revert to default"); - } - return result; + furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); + FURI_LOG_I(TAG, "Init OK"); } void furi_hal_subghz_sleep() { furi_assert(furi_hal_subghz.state == SubGhzStateIdle); - furi_hal_spi_acquire(furi_hal_subghz.spi_bus_handle); + furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); - cc1101_switch_to_idle(furi_hal_subghz.spi_bus_handle); + cc1101_switch_to_idle(&furi_hal_spi_bus_handle_subghz); - cc1101_write_reg(furi_hal_subghz.spi_bus_handle, CC1101_IOCFG0, CC1101IocfgHighImpedance); - furi_hal_gpio_init(furi_hal_subghz.cc1101_g0_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + cc1101_write_reg(&furi_hal_spi_bus_handle_subghz, CC1101_IOCFG0, CC1101IocfgHighImpedance); + furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeAnalog, GpioPullNo, GpioSpeedLow); - cc1101_shutdown(furi_hal_subghz.spi_bus_handle); + cc1101_shutdown(&furi_hal_spi_bus_handle_subghz); - furi_hal_spi_release(furi_hal_subghz.spi_bus_handle); + furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); furi_hal_subghz.preset = FuriHalSubGhzPresetIDLE; } void furi_hal_subghz_dump_state() { - furi_hal_spi_acquire(furi_hal_subghz.spi_bus_handle); + furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); printf( "[furi_hal_subghz] cc1101 chip %d, version %d\r\n", - cc1101_get_partnumber(furi_hal_subghz.spi_bus_handle), - cc1101_get_version(furi_hal_subghz.spi_bus_handle)); - furi_hal_spi_release(furi_hal_subghz.spi_bus_handle); + cc1101_get_partnumber(&furi_hal_spi_bus_handle_subghz), + cc1101_get_version(&furi_hal_spi_bus_handle_subghz)); + furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); } void furi_hal_subghz_load_preset(FuriHalSubGhzPreset preset) { if(preset == FuriHalSubGhzPresetOok650Async) { - furi_hal_subghz_load_registers((uint8_t*)furi_hal_subghz_preset_ook_650khz_async_regs); - furi_hal_subghz_load_patable(furi_hal_subghz_preset_ook_async_patable); + furi_hal_subghz_load_registers( + (uint8_t*)subghz_device_cc1101_preset_ook_650khz_async_regs); + furi_hal_subghz_load_patable(subghz_device_cc1101_preset_ook_async_patable); } else if(preset == FuriHalSubGhzPresetOok270Async) { - furi_hal_subghz_load_registers((uint8_t*)furi_hal_subghz_preset_ook_270khz_async_regs); - furi_hal_subghz_load_patable(furi_hal_subghz_preset_ook_async_patable); + furi_hal_subghz_load_registers( + (uint8_t*)subghz_device_cc1101_preset_ook_270khz_async_regs); + furi_hal_subghz_load_patable(subghz_device_cc1101_preset_ook_async_patable); } else if(preset == FuriHalSubGhzPreset2FSKDev238Async) { furi_hal_subghz_load_registers( - (uint8_t*)furi_hal_subghz_preset_2fsk_dev2_38khz_async_regs); - furi_hal_subghz_load_patable(furi_hal_subghz_preset_2fsk_async_patable); + (uint8_t*)subghz_device_cc1101_preset_2fsk_dev2_38khz_async_regs); + furi_hal_subghz_load_patable(subghz_device_cc1101_preset_2fsk_async_patable); } else if(preset == FuriHalSubGhzPreset2FSKDev476Async) { furi_hal_subghz_load_registers( - (uint8_t*)furi_hal_subghz_preset_2fsk_dev47_6khz_async_regs); - furi_hal_subghz_load_patable(furi_hal_subghz_preset_2fsk_async_patable); + (uint8_t*)subghz_device_cc1101_preset_2fsk_dev47_6khz_async_regs); + furi_hal_subghz_load_patable(subghz_device_cc1101_preset_2fsk_async_patable); } else if(preset == FuriHalSubGhzPresetMSK99_97KbAsync) { - furi_hal_subghz_load_registers((uint8_t*)furi_hal_subghz_preset_msk_99_97kb_async_regs); - furi_hal_subghz_load_patable(furi_hal_subghz_preset_msk_async_patable); + furi_hal_subghz_load_registers( + (uint8_t*)subghz_device_cc1101_preset_msk_99_97kb_async_regs); + furi_hal_subghz_load_patable(subghz_device_cc1101_preset_msk_async_patable); } else if(preset == FuriHalSubGhzPresetGFSK9_99KbAsync) { - furi_hal_subghz_load_registers((uint8_t*)furi_hal_subghz_preset_gfsk_9_99kb_async_regs); - furi_hal_subghz_load_patable(furi_hal_subghz_preset_gfsk_async_patable); + furi_hal_subghz_load_registers( + (uint8_t*)subghz_device_cc1101_preset_gfsk_9_99kb_async_regs); + furi_hal_subghz_load_patable(subghz_device_cc1101_preset_gfsk_async_patable); } else { furi_crash("SubGhz: Missing config."); } @@ -257,15 +192,15 @@ void furi_hal_subghz_load_preset(FuriHalSubGhzPreset preset) { void furi_hal_subghz_load_custom_preset(uint8_t* preset_data) { //load config - furi_hal_spi_acquire(furi_hal_subghz.spi_bus_handle); - cc1101_reset(furi_hal_subghz.spi_bus_handle); + furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); + cc1101_reset(&furi_hal_spi_bus_handle_subghz); uint32_t i = 0; uint8_t pa[8] = {0}; while(preset_data[i]) { - cc1101_write_reg(furi_hal_subghz.spi_bus_handle, preset_data[i], preset_data[i + 1]); + cc1101_write_reg(&furi_hal_spi_bus_handle_subghz, preset_data[i], preset_data[i + 1]); i += 2; } - furi_hal_spi_release(furi_hal_subghz.spi_bus_handle); + furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); //load pa table memcpy(&pa[0], &preset_data[i + 2], 8); @@ -287,48 +222,48 @@ void furi_hal_subghz_load_custom_preset(uint8_t* preset_data) { } void furi_hal_subghz_load_registers(uint8_t* data) { - furi_hal_spi_acquire(furi_hal_subghz.spi_bus_handle); - cc1101_reset(furi_hal_subghz.spi_bus_handle); + furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); + cc1101_reset(&furi_hal_spi_bus_handle_subghz); uint32_t i = 0; while(data[i]) { - cc1101_write_reg(furi_hal_subghz.spi_bus_handle, data[i], data[i + 1]); + cc1101_write_reg(&furi_hal_spi_bus_handle_subghz, data[i], data[i + 1]); i += 2; } - furi_hal_spi_release(furi_hal_subghz.spi_bus_handle); + furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); } void furi_hal_subghz_load_patable(const uint8_t data[8]) { - furi_hal_spi_acquire(furi_hal_subghz.spi_bus_handle); - cc1101_set_pa_table(furi_hal_subghz.spi_bus_handle, data); - furi_hal_spi_release(furi_hal_subghz.spi_bus_handle); + furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); + cc1101_set_pa_table(&furi_hal_spi_bus_handle_subghz, data); + furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); } void furi_hal_subghz_write_packet(const uint8_t* data, uint8_t size) { - furi_hal_spi_acquire(furi_hal_subghz.spi_bus_handle); - cc1101_flush_tx(furi_hal_subghz.spi_bus_handle); - cc1101_write_reg(furi_hal_subghz.spi_bus_handle, CC1101_FIFO, size); - cc1101_write_fifo(furi_hal_subghz.spi_bus_handle, data, size); - furi_hal_spi_release(furi_hal_subghz.spi_bus_handle); + furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); + cc1101_flush_tx(&furi_hal_spi_bus_handle_subghz); + cc1101_write_reg(&furi_hal_spi_bus_handle_subghz, CC1101_FIFO, size); + cc1101_write_fifo(&furi_hal_spi_bus_handle_subghz, data, size); + furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); } void furi_hal_subghz_flush_rx() { - furi_hal_spi_acquire(furi_hal_subghz.spi_bus_handle); - cc1101_flush_rx(furi_hal_subghz.spi_bus_handle); - furi_hal_spi_release(furi_hal_subghz.spi_bus_handle); + furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); + cc1101_flush_rx(&furi_hal_spi_bus_handle_subghz); + furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); } void furi_hal_subghz_flush_tx() { - furi_hal_spi_acquire(furi_hal_subghz.spi_bus_handle); - cc1101_flush_tx(furi_hal_subghz.spi_bus_handle); - furi_hal_spi_release(furi_hal_subghz.spi_bus_handle); + furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); + cc1101_flush_tx(&furi_hal_spi_bus_handle_subghz); + furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); } bool furi_hal_subghz_rx_pipe_not_empty() { CC1101RxBytes status[1]; - furi_hal_spi_acquire(furi_hal_subghz.spi_bus_handle); + furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); cc1101_read_reg( - furi_hal_subghz.spi_bus_handle, (CC1101_STATUS_RXBYTES) | CC1101_BURST, (uint8_t*)status); - furi_hal_spi_release(furi_hal_subghz.spi_bus_handle); + &furi_hal_spi_bus_handle_subghz, (CC1101_STATUS_RXBYTES) | CC1101_BURST, (uint8_t*)status); + furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); // TODO: you can add a buffer overflow flag if needed if(status->NUM_RXBYTES > 0) { return true; @@ -338,10 +273,10 @@ bool furi_hal_subghz_rx_pipe_not_empty() { } bool furi_hal_subghz_is_rx_data_crc_valid() { - furi_hal_spi_acquire(furi_hal_subghz.spi_bus_handle); + furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); uint8_t data[1]; - cc1101_read_reg(furi_hal_subghz.spi_bus_handle, CC1101_STATUS_LQI | CC1101_BURST, data); - furi_hal_spi_release(furi_hal_subghz.spi_bus_handle); + cc1101_read_reg(&furi_hal_spi_bus_handle_subghz, CC1101_STATUS_LQI | CC1101_BURST, data); + furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); if(((data[0] >> 7) & 0x01)) { return true; } else { @@ -350,51 +285,51 @@ bool furi_hal_subghz_is_rx_data_crc_valid() { } void furi_hal_subghz_read_packet(uint8_t* data, uint8_t* size) { - furi_hal_spi_acquire(furi_hal_subghz.spi_bus_handle); - cc1101_read_fifo(furi_hal_subghz.spi_bus_handle, data, size); - furi_hal_spi_release(furi_hal_subghz.spi_bus_handle); + furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); + cc1101_read_fifo(&furi_hal_spi_bus_handle_subghz, data, size); + furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); } void furi_hal_subghz_shutdown() { - furi_hal_spi_acquire(furi_hal_subghz.spi_bus_handle); + furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); // Reset and shutdown - cc1101_shutdown(furi_hal_subghz.spi_bus_handle); - furi_hal_spi_release(furi_hal_subghz.spi_bus_handle); + cc1101_shutdown(&furi_hal_spi_bus_handle_subghz); + furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); } void furi_hal_subghz_reset() { - furi_hal_spi_acquire(furi_hal_subghz.spi_bus_handle); - furi_hal_gpio_init(furi_hal_subghz.cc1101_g0_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); - cc1101_switch_to_idle(furi_hal_subghz.spi_bus_handle); - cc1101_reset(furi_hal_subghz.spi_bus_handle); - cc1101_write_reg(furi_hal_subghz.spi_bus_handle, CC1101_IOCFG0, CC1101IocfgHighImpedance); - furi_hal_spi_release(furi_hal_subghz.spi_bus_handle); + furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); + furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + cc1101_switch_to_idle(&furi_hal_spi_bus_handle_subghz); + cc1101_reset(&furi_hal_spi_bus_handle_subghz); + cc1101_write_reg(&furi_hal_spi_bus_handle_subghz, CC1101_IOCFG0, CC1101IocfgHighImpedance); + furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); } void furi_hal_subghz_idle() { - furi_hal_spi_acquire(furi_hal_subghz.spi_bus_handle); - cc1101_switch_to_idle(furi_hal_subghz.spi_bus_handle); - furi_hal_spi_release(furi_hal_subghz.spi_bus_handle); + furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); + cc1101_switch_to_idle(&furi_hal_spi_bus_handle_subghz); + furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); } void furi_hal_subghz_rx() { - furi_hal_spi_acquire(furi_hal_subghz.spi_bus_handle); - cc1101_switch_to_rx(furi_hal_subghz.spi_bus_handle); - furi_hal_spi_release(furi_hal_subghz.spi_bus_handle); + furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); + cc1101_switch_to_rx(&furi_hal_spi_bus_handle_subghz); + furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); } bool furi_hal_subghz_tx() { if(furi_hal_subghz.regulation != SubGhzRegulationTxRx) return false; - furi_hal_spi_acquire(furi_hal_subghz.spi_bus_handle); - cc1101_switch_to_tx(furi_hal_subghz.spi_bus_handle); - furi_hal_spi_release(furi_hal_subghz.spi_bus_handle); + furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); + cc1101_switch_to_tx(&furi_hal_spi_bus_handle_subghz); + furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); return true; } float furi_hal_subghz_get_rssi() { - furi_hal_spi_acquire(furi_hal_subghz.spi_bus_handle); - int32_t rssi_dec = cc1101_get_rssi(furi_hal_subghz.spi_bus_handle); - furi_hal_spi_release(furi_hal_subghz.spi_bus_handle); + furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); + int32_t rssi_dec = cc1101_get_rssi(&furi_hal_spi_bus_handle_subghz); + furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); float rssi = rssi_dec; if(rssi_dec >= 128) { @@ -407,10 +342,10 @@ float furi_hal_subghz_get_rssi() { } uint8_t furi_hal_subghz_get_lqi() { - furi_hal_spi_acquire(furi_hal_subghz.spi_bus_handle); + furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); uint8_t data[1]; - cc1101_read_reg(furi_hal_subghz.spi_bus_handle, CC1101_STATUS_LQI | CC1101_BURST, data); - furi_hal_spi_release(furi_hal_subghz.spi_bus_handle); + cc1101_read_reg(&furi_hal_spi_bus_handle_subghz, CC1101_STATUS_LQI | CC1101_BURST, data); + furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); return data[0] & 0x7F; } @@ -468,39 +403,39 @@ bool furi_hal_subghz_is_tx_allowed(uint32_t value) { uint32_t furi_hal_subghz_set_frequency(uint32_t value) { furi_hal_subghz.regulation = SubGhzRegulationTxRx; - furi_hal_spi_acquire(furi_hal_subghz.spi_bus_handle); - uint32_t real_frequency = cc1101_set_frequency(furi_hal_subghz.spi_bus_handle, value); - cc1101_calibrate(furi_hal_subghz.spi_bus_handle); + furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); + uint32_t real_frequency = cc1101_set_frequency(&furi_hal_spi_bus_handle_subghz, value); + cc1101_calibrate(&furi_hal_spi_bus_handle_subghz); while(true) { - CC1101Status status = cc1101_get_status(furi_hal_subghz.spi_bus_handle); + CC1101Status status = cc1101_get_status(&furi_hal_spi_bus_handle_subghz); if(status.STATE == CC1101StateIDLE) break; } - furi_hal_spi_release(furi_hal_subghz.spi_bus_handle); + furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); return real_frequency; } void furi_hal_subghz_set_path(FuriHalSubGhzPath path) { - furi_hal_spi_acquire(furi_hal_subghz.spi_bus_handle); + furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); if(path == FuriHalSubGhzPath433) { furi_hal_gpio_write(&gpio_rf_sw_0, 0); cc1101_write_reg( - furi_hal_subghz.spi_bus_handle, CC1101_IOCFG2, CC1101IocfgHW | CC1101_IOCFG_INV); + &furi_hal_spi_bus_handle_subghz, CC1101_IOCFG2, CC1101IocfgHW | CC1101_IOCFG_INV); } else if(path == FuriHalSubGhzPath315) { furi_hal_gpio_write(&gpio_rf_sw_0, 1); - cc1101_write_reg(furi_hal_subghz.spi_bus_handle, CC1101_IOCFG2, CC1101IocfgHW); + cc1101_write_reg(&furi_hal_spi_bus_handle_subghz, CC1101_IOCFG2, CC1101IocfgHW); } else if(path == FuriHalSubGhzPath868) { furi_hal_gpio_write(&gpio_rf_sw_0, 1); cc1101_write_reg( - furi_hal_subghz.spi_bus_handle, CC1101_IOCFG2, CC1101IocfgHW | CC1101_IOCFG_INV); + &furi_hal_spi_bus_handle_subghz, CC1101_IOCFG2, CC1101IocfgHW | CC1101_IOCFG_INV); } else if(path == FuriHalSubGhzPathIsolate) { furi_hal_gpio_write(&gpio_rf_sw_0, 0); - cc1101_write_reg(furi_hal_subghz.spi_bus_handle, CC1101_IOCFG2, CC1101IocfgHW); + cc1101_write_reg(&furi_hal_spi_bus_handle_subghz, CC1101_IOCFG2, CC1101IocfgHW); } else { furi_crash("SubGhz: Incorrect path during set."); } - furi_hal_spi_release(furi_hal_subghz.spi_bus_handle); + furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); } static bool furi_hal_subghz_start_debug() { @@ -530,7 +465,7 @@ volatile uint32_t furi_hal_subghz_capture_delta_duration = 0; volatile FuriHalSubGhzCaptureCallback furi_hal_subghz_capture_callback = NULL; volatile void* furi_hal_subghz_capture_callback_context = NULL; -static void furi_hal_subghz_capture_int_ISR() { +static void furi_hal_subghz_capture_ISR() { // Channel 1 if(LL_TIM_IsActiveFlag_CC1(TIM2)) { LL_TIM_ClearFlag_CC1(TIM2); @@ -560,27 +495,6 @@ static void furi_hal_subghz_capture_int_ISR() { } } -static void furi_hal_subghz_capture_ext_ISR() { - if(!furi_hal_gpio_read(furi_hal_subghz.cc1101_g0_pin)) { - if(furi_hal_subghz_capture_callback) { - if(furi_hal_subghz.async_mirror_pin != NULL) - furi_hal_gpio_write(furi_hal_subghz.async_mirror_pin, false); - - furi_hal_subghz_capture_callback( - true, TIM2->CNT, (void*)furi_hal_subghz_capture_callback_context); - } - } else { - if(furi_hal_subghz_capture_callback) { - if(furi_hal_subghz.async_mirror_pin != NULL) - furi_hal_gpio_write(furi_hal_subghz.async_mirror_pin, true); - - furi_hal_subghz_capture_callback( - false, TIM2->CNT, (void*)furi_hal_subghz_capture_callback_context); - } - } - TIM2->CNT = 6; -} - void furi_hal_subghz_start_async_rx(FuriHalSubGhzCaptureCallback callback, void* context) { furi_assert(furi_hal_subghz.state == SubGhzStateIdle); furi_hal_subghz.state = SubGhzStateAsyncRx; @@ -588,6 +502,9 @@ void furi_hal_subghz_start_async_rx(FuriHalSubGhzCaptureCallback callback, void* furi_hal_subghz_capture_callback = callback; furi_hal_subghz_capture_callback_context = context; + furi_hal_gpio_init_ex( + &gpio_cc1101_g0, GpioModeAltFunctionPushPull, GpioPullNo, GpioSpeedLow, GpioAltFn1TIM2); + furi_hal_bus_enable(FuriHalBusTIM2); // Timer: base @@ -595,62 +512,42 @@ void furi_hal_subghz_start_async_rx(FuriHalSubGhzCaptureCallback callback, void* TIM_InitStruct.Prescaler = 64 - 1; TIM_InitStruct.CounterMode = LL_TIM_COUNTERMODE_UP; TIM_InitStruct.Autoreload = 0x7FFFFFFE; - // Clock division for capture filter (for internal radio) + // Clock division for capture filter TIM_InitStruct.ClockDivision = LL_TIM_CLOCKDIVISION_DIV4; LL_TIM_Init(TIM2, &TIM_InitStruct); // Timer: advanced LL_TIM_SetClockSource(TIM2, LL_TIM_CLOCKSOURCE_INTERNAL); LL_TIM_DisableARRPreload(TIM2); + LL_TIM_SetTriggerInput(TIM2, LL_TIM_TS_TI2FP2); + LL_TIM_SetSlaveMode(TIM2, LL_TIM_SLAVEMODE_RESET); + LL_TIM_SetTriggerOutput(TIM2, LL_TIM_TRGO_RESET); + LL_TIM_EnableMasterSlaveMode(TIM2); LL_TIM_DisableDMAReq_TRIG(TIM2); LL_TIM_DisableIT_TRIG(TIM2); - if(furi_hal_subghz.radio_type == SubGhzRadioInternal) { - LL_TIM_SetTriggerInput(TIM2, LL_TIM_TS_TI2FP2); - LL_TIM_SetSlaveMode(TIM2, LL_TIM_SLAVEMODE_RESET); - LL_TIM_SetTriggerOutput(TIM2, LL_TIM_TRGO_RESET); - LL_TIM_EnableMasterSlaveMode(TIM2); + // Timer: channel 1 indirect + LL_TIM_IC_SetActiveInput(TIM2, LL_TIM_CHANNEL_CH1, LL_TIM_ACTIVEINPUT_INDIRECTTI); + LL_TIM_IC_SetPrescaler(TIM2, LL_TIM_CHANNEL_CH1, LL_TIM_ICPSC_DIV1); + LL_TIM_IC_SetPolarity(TIM2, LL_TIM_CHANNEL_CH1, LL_TIM_IC_POLARITY_FALLING); - // Timer: channel 1 indirect - LL_TIM_IC_SetActiveInput(TIM2, LL_TIM_CHANNEL_CH1, LL_TIM_ACTIVEINPUT_INDIRECTTI); - LL_TIM_IC_SetPrescaler(TIM2, LL_TIM_CHANNEL_CH1, LL_TIM_ICPSC_DIV1); - LL_TIM_IC_SetPolarity(TIM2, LL_TIM_CHANNEL_CH1, LL_TIM_IC_POLARITY_FALLING); - LL_TIM_IC_SetFilter(TIM2, LL_TIM_CHANNEL_CH1, LL_TIM_IC_FILTER_FDIV1); + // Timer: channel 2 direct + LL_TIM_IC_SetActiveInput(TIM2, LL_TIM_CHANNEL_CH2, LL_TIM_ACTIVEINPUT_DIRECTTI); + LL_TIM_IC_SetPrescaler(TIM2, LL_TIM_CHANNEL_CH2, LL_TIM_ICPSC_DIV1); + LL_TIM_IC_SetPolarity(TIM2, LL_TIM_CHANNEL_CH2, LL_TIM_IC_POLARITY_RISING); + LL_TIM_IC_SetFilter( + TIM2, + LL_TIM_CHANNEL_CH2, + LL_TIM_IC_FILTER_FDIV32_N8); // Capture filter: 1/(64000000/64/4/32*8) = 16us - // Timer: channel 2 direct - LL_TIM_IC_SetActiveInput(TIM2, LL_TIM_CHANNEL_CH2, LL_TIM_ACTIVEINPUT_DIRECTTI); - LL_TIM_IC_SetPrescaler(TIM2, LL_TIM_CHANNEL_CH2, LL_TIM_ICPSC_DIV1); - LL_TIM_IC_SetPolarity(TIM2, LL_TIM_CHANNEL_CH2, LL_TIM_IC_POLARITY_RISING); - LL_TIM_IC_SetFilter(TIM2, LL_TIM_CHANNEL_CH2, LL_TIM_IC_FILTER_FDIV32_N8); + // ISR setup + furi_hal_interrupt_set_isr(FuriHalInterruptIdTIM2, furi_hal_subghz_capture_ISR, NULL); - // ISR setup - furi_hal_interrupt_set_isr(FuriHalInterruptIdTIM2, furi_hal_subghz_capture_int_ISR, NULL); - - // Interrupts and channels - LL_TIM_EnableIT_CC1(TIM2); - LL_TIM_EnableIT_CC2(TIM2); - LL_TIM_CC_EnableChannel(TIM2, LL_TIM_CHANNEL_CH1); - LL_TIM_CC_EnableChannel(TIM2, LL_TIM_CHANNEL_CH2); - - furi_hal_gpio_init_ex( - furi_hal_subghz.cc1101_g0_pin, - GpioModeAltFunctionPushPull, - GpioPullNo, - GpioSpeedLow, - GpioAltFn1TIM2); - } else { - furi_hal_gpio_init( - furi_hal_subghz.cc1101_g0_pin, - GpioModeInterruptRiseFall, - GpioPullUp, - GpioSpeedVeryHigh); - furi_hal_gpio_disable_int_callback(furi_hal_subghz.cc1101_g0_pin); - furi_hal_gpio_remove_int_callback(furi_hal_subghz.cc1101_g0_pin); - furi_hal_gpio_add_int_callback( - furi_hal_subghz.cc1101_g0_pin, - furi_hal_subghz_capture_ext_ISR, - furi_hal_subghz_capture_callback); - } + // Interrupts and channels + LL_TIM_EnableIT_CC1(TIM2); + LL_TIM_EnableIT_CC2(TIM2); + LL_TIM_CC_EnableChannel(TIM2, LL_TIM_CHANNEL_CH1); + LL_TIM_CC_EnableChannel(TIM2, LL_TIM_CHANNEL_CH2); // Start timer LL_TIM_SetCounter(TIM2, 0); @@ -680,14 +577,9 @@ void furi_hal_subghz_stop_async_rx() { furi_hal_subghz_stop_debug(); FURI_CRITICAL_EXIT(); - if(furi_hal_subghz.radio_type == SubGhzRadioInternal) { - furi_hal_interrupt_set_isr(FuriHalInterruptIdTIM2, NULL, NULL); - } else { - furi_hal_gpio_disable_int_callback(furi_hal_subghz.cc1101_g0_pin); - furi_hal_gpio_remove_int_callback(furi_hal_subghz.cc1101_g0_pin); - } + furi_hal_interrupt_set_isr(FuriHalInterruptIdTIM2, NULL, NULL); - furi_hal_gpio_init(furi_hal_subghz.cc1101_g0_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeAnalog, GpioPullNo, GpioSpeedLow); } typedef struct { @@ -791,8 +683,7 @@ static void furi_hal_subghz_async_tx_timer_isr() { } else if(furi_hal_subghz.state == SubGhzStateAsyncTxLast) { furi_hal_subghz.state = SubGhzStateAsyncTxEnd; //forcibly pulls the pin to the ground so that there is no carrier - furi_hal_gpio_init( - furi_hal_subghz.cc1101_g0_pin, GpioModeInput, GpioPullDown, GpioSpeedLow); + furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullDown, GpioSpeedLow); LL_TIM_DisableCounter(TIM2); } else { furi_crash(NULL); @@ -819,20 +710,9 @@ bool furi_hal_subghz_start_async_tx(FuriHalSubGhzAsyncTxCallback callback, void* furi_hal_subghz_async_tx.buffer = malloc(API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * sizeof(uint32_t)); - if(furi_hal_subghz.radio_type == SubGhzRadioInternal) { - // Connect CC1101_GD0 to TIM2 as output - furi_hal_gpio_init_ex( - furi_hal_subghz.cc1101_g0_pin, - GpioModeAltFunctionPushPull, - GpioPullDown, - GpioSpeedLow, - GpioAltFn1TIM2); - } else { - //Signal generation with mem-to-mem DMA - furi_hal_gpio_write(furi_hal_subghz.cc1101_g0_pin, true); - furi_hal_gpio_init( - furi_hal_subghz.cc1101_g0_pin, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); - } + // Connect CC1101_GD0 to TIM2 as output + furi_hal_gpio_init_ex( + &gpio_cc1101_g0, GpioModeAltFunctionPushPull, GpioPullDown, GpioSpeedLow, GpioAltFn1TIM2); // Configure DMA LL_DMA_InitTypeDef dma_config = {0}; @@ -895,27 +775,15 @@ bool furi_hal_subghz_start_async_tx(FuriHalSubGhzAsyncTxCallback callback, void* LL_TIM_EnableCounter(TIM2); // Start debug - if(furi_hal_subghz_start_debug() || furi_hal_subghz.radio_type == SubGhzRadioExternal) { - const GpioPin* gpio = furi_hal_subghz.cc1101_g0_pin; - //Preparing bit mask - //Debug pin is may be only PORTB! (PB0, PB1, .., PB15) - furi_hal_subghz_debug_gpio_buff[0] = 0; - furi_hal_subghz_debug_gpio_buff[1] = 0; + if(furi_hal_subghz_start_debug()) { + const GpioPin* gpio = furi_hal_subghz.async_mirror_pin; + // //Preparing bit mask + // //Debug pin is may be only PORTB! (PB0, PB1, .., PB15) + // furi_hal_subghz_debug_gpio_buff[0] = 0; + // furi_hal_subghz_debug_gpio_buff[1] = 0; - //Mirror pin (for example, speaker) - if(furi_hal_subghz.async_mirror_pin != NULL) { - furi_hal_subghz_debug_gpio_buff[0] |= (uint32_t)furi_hal_subghz.async_mirror_pin->pin - << GPIO_NUMBER; - furi_hal_subghz_debug_gpio_buff[1] |= furi_hal_subghz.async_mirror_pin->pin; - gpio = furi_hal_subghz.async_mirror_pin; - } - - //G0 singnal generation for external radio - if(furi_hal_subghz.radio_type == SubGhzRadioExternal) { - furi_hal_subghz_debug_gpio_buff[0] |= (uint32_t)furi_hal_subghz.cc1101_g0_pin->pin - << GPIO_NUMBER; - furi_hal_subghz_debug_gpio_buff[1] |= furi_hal_subghz.cc1101_g0_pin->pin; - } + furi_hal_subghz_debug_gpio_buff[0] = (uint32_t)gpio->pin << GPIO_NUMBER; + furi_hal_subghz_debug_gpio_buff[1] = gpio->pin; dma_config.MemoryOrM2MDstAddress = (uint32_t)furi_hal_subghz_debug_gpio_buff; dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (gpio->port->BSRR); @@ -948,9 +816,9 @@ void furi_hal_subghz_stop_async_tx() { // Shutdown radio furi_hal_subghz_idle(); - if(furi_hal_subghz.radio_type == SubGhzRadioExternal) { - furi_hal_gpio_write(furi_hal_subghz.cc1101_g0_pin, false); - } +#ifdef FURI_HAL_SUBGHZ_TX_GPIO + furi_hal_gpio_write(&FURI_HAL_SUBGHZ_TX_GPIO, false); +#endif // Deinitialize Timer FURI_CRITICAL_ENTER(); @@ -963,14 +831,10 @@ void furi_hal_subghz_stop_async_tx() { furi_hal_interrupt_set_isr(SUBGHZ_DMA_CH1_IRQ, NULL, NULL); // Deinitialize GPIO - furi_hal_gpio_init(furi_hal_subghz.cc1101_g0_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeAnalog, GpioPullNo, GpioSpeedLow); // Stop debug - furi_hal_subghz_stop_debug(); - - if(((furi_hal_subghz.async_mirror_pin != NULL) && - (furi_hal_subghz.radio_type == SubGhzRadioInternal)) || - (furi_hal_subghz.radio_type == SubGhzRadioExternal)) { + if(furi_hal_subghz_stop_debug()) { LL_DMA_DisableChannel(SUBGHZ_DMA_CH2_DEF); } diff --git a/firmware/targets/f7/furi_hal/furi_hal_subghz.h b/firmware/targets/f7/furi_hal/furi_hal_subghz.h index ae6876d45..6eeba6f7d 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_subghz.h +++ b/firmware/targets/f7/furi_hal/furi_hal_subghz.h @@ -5,12 +5,14 @@ #pragma once +#include + #include #include #include #include #include -#include +// #include #ifdef __cplusplus extern "C" { @@ -21,18 +23,6 @@ extern "C" { #define API_HAL_SUBGHZ_ASYNC_TX_BUFFER_HALF (API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL / 2) #define API_HAL_SUBGHZ_ASYNC_TX_GUARD_TIME 999 -/** Radio Presets */ -typedef enum { - FuriHalSubGhzPresetIDLE, /**< default configuration */ - FuriHalSubGhzPresetOok270Async, /**< OOK, bandwidth 270kHz, asynchronous */ - FuriHalSubGhzPresetOok650Async, /**< OOK, bandwidth 650kHz, asynchronous */ - FuriHalSubGhzPreset2FSKDev238Async, /**< FM, deviation 2.380371 kHz, asynchronous */ - FuriHalSubGhzPreset2FSKDev476Async, /**< FM, deviation 47.60742 kHz, asynchronous */ - FuriHalSubGhzPresetMSK99_97KbAsync, /**< MSK, deviation 47.60742 kHz, 99.97Kb/s, asynchronous */ - FuriHalSubGhzPresetGFSK9_99KbAsync, /**< GFSK, deviation 19.042969 kHz, 9.996Kb/s, asynchronous */ - FuriHalSubGhzPresetCustom, /**Custom Preset*/ -} FuriHalSubGhzPreset; - /** Switchable Radio Paths */ typedef enum { FuriHalSubGhzPathIsolate, /**< Isolate Radio from antenna */ @@ -41,50 +31,6 @@ typedef enum { FuriHalSubGhzPath868, /**< Center Frequency: 868MHz. Path 3: SW1RF3-SW2RF3, LCLC */ } FuriHalSubGhzPath; -/** SubGhz state */ -typedef enum { - SubGhzStateInit, /**< Init pending */ - - SubGhzStateIdle, /**< Idle, energy save mode */ - - SubGhzStateAsyncRx, /**< Async RX started */ - - SubGhzStateAsyncTx, /**< Async TX started, DMA and timer is on */ - SubGhzStateAsyncTxLast, /**< Async TX continue, DMA completed and timer got last value to go */ - SubGhzStateAsyncTxEnd, /**< Async TX complete, cleanup needed */ - -} SubGhzState; - -/** SubGhz regulation, receive transmission on the current frequency for the - * region */ -typedef enum { - SubGhzRegulationOnlyRx, /**only Rx*/ - SubGhzRegulationTxRx, /**TxRx*/ -} SubGhzRegulation; - -/** SubGhz radio types */ -typedef enum { - SubGhzRadioInternal, - SubGhzRadioExternal, -} SubGhzRadioType; - -/** Structure for accessing SubGhz settings*/ -typedef struct { - volatile SubGhzState state; - volatile SubGhzRegulation regulation; - volatile FuriHalSubGhzPreset preset; - const GpioPin* async_mirror_pin; - SubGhzRadioType radio_type; - FuriHalSpiBusHandle* spi_bus_handle; - const GpioPin* cc1101_g0_pin; - uint8_t rolling_counter_mult; - bool ext_module_power_disabled : 1; - bool timestamp_file_names : 1; - bool dangerous_frequency_i : 1; -} FuriHalSubGhz; - -extern volatile FuriHalSubGhz furi_hal_subghz; - /* Mirror RX/TX async modulation signal to specified pin * * @warning Configures pin to output mode. Make sure it is not connected @@ -94,19 +40,18 @@ extern volatile FuriHalSubGhz furi_hal_subghz; */ void furi_hal_subghz_set_async_mirror_pin(const GpioPin* pin); +/** Get data GPIO + * + * @return pointer to the gpio pin structure + */ +const GpioPin* furi_hal_subghz_get_data_gpio(); + /** Initialize and switch to power save mode Used by internal API-HAL * initialization routine Can be used to reinitialize device to safe state and * send it to sleep */ void furi_hal_subghz_init(); -/** Initialize and switch to power save mode Used by internal API-HAL - * initialization routine Can be used to reinitialize device to safe state and - * send it to sleep - * @return true if initialisation is successfully - */ -bool furi_hal_subghz_init_check(void); - /** Send device to sleep mode */ void furi_hal_subghz_sleep(); @@ -234,6 +179,16 @@ uint32_t furi_hal_subghz_set_frequency_and_path(uint32_t value); */ bool furi_hal_subghz_is_tx_allowed(uint32_t value); +/** Get the current rolling protocols counter ++ value + * @return uint8_t current value + */ +uint8_t furi_hal_subghz_get_rolling_counter_mult(void); + +/** Set the current rolling protocols counter ++ value + * @param mult uint8_t = 1, 2, 4, 8 + */ +void furi_hal_subghz_set_rolling_counter_mult(uint8_t mult); + /** Set frequency * * @param value frequency in Hz @@ -289,52 +244,49 @@ bool furi_hal_subghz_is_async_tx_complete(); */ void furi_hal_subghz_stop_async_tx(); -/** Switching between internal and external radio - * @param state SubGhzRadioInternal or SubGhzRadioExternal - * @return true if switching is successful - */ -bool furi_hal_subghz_init_radio_type(SubGhzRadioType state); +// /** Initialize and switch to power save mode Used by internal API-HAL +// * initialization routine Can be used to reinitialize device to safe state and +// * send it to sleep +// * @return true if initialisation is successfully +// */ +// bool furi_hal_subghz_init_check(void); -/** Get current radio - * @return SubGhzRadioInternal or SubGhzRadioExternal - */ -SubGhzRadioType furi_hal_subghz_get_radio_type(void); +// /** Switching between internal and external radio +// * @param state SubGhzRadioInternal or SubGhzRadioExternal +// * @return true if switching is successful +// */ +// bool furi_hal_subghz_init_radio_type(SubGhzRadioType state); -/** Check for a radio module - * @return true if check is successful - */ -bool furi_hal_subghz_check_radio(void); +// /** Get current radio +// * @return SubGhzRadioInternal or SubGhzRadioExternal +// */ +// SubGhzRadioType furi_hal_subghz_get_radio_type(void); -/** Turn on the power of the external radio module - * @return true if power-up is successful - */ -bool furi_hal_subghz_enable_ext_power(void); +// /** Check for a radio module +// * @return true if check is successful +// */ +// bool furi_hal_subghz_check_radio(void); -/** Turn off the power of the external radio module - */ -void furi_hal_subghz_disable_ext_power(void); +// /** Turn on the power of the external radio module +// * @return true if power-up is successful +// */ +// bool furi_hal_subghz_enable_ext_power(void); -/** Get the current rolling protocols counter ++ value - * @return uint8_t current value - */ -uint8_t furi_hal_subghz_get_rolling_counter_mult(void); +// /** Turn off the power of the external radio module +// */ +// void furi_hal_subghz_disable_ext_power(void); -/** Set the current rolling protocols counter ++ value - * @param mult uint8_t = 1, 2, 4, 8 - */ -void furi_hal_subghz_set_rolling_counter_mult(uint8_t mult); +// /** If true - disable 5v power of the external radio module +// */ +// void furi_hal_subghz_set_external_power_disable(bool state); -/** If true - disable 5v power of the external radio module - */ -void furi_hal_subghz_set_external_power_disable(bool state); +// /** Get the current state of the external power disable flag +// */ +// bool furi_hal_subghz_get_external_power_disable(void); -/** Get the current state of the external power disable flag - */ -bool furi_hal_subghz_get_external_power_disable(void); - -/** Set what radio module we will be using - */ -void furi_hal_subghz_select_radio_type(SubGhzRadioType state); +// /** Set what radio module we will be using +// */ +// void furi_hal_subghz_select_radio_type(SubGhzRadioType state); #ifdef __cplusplus } diff --git a/firmware/targets/f7/furi_hal/furi_hal_subghz_configs.h b/firmware/targets/f7/furi_hal/furi_hal_subghz_configs.h deleted file mode 100644 index b2b5760fd..000000000 --- a/firmware/targets/f7/furi_hal/furi_hal_subghz_configs.h +++ /dev/null @@ -1,304 +0,0 @@ -#pragma once - -#include - -static const uint8_t furi_hal_subghz_preset_ook_270khz_async_regs[][2] = { - // https://e2e.ti.com/support/wireless-connectivity/sub-1-ghz-group/sub-1-ghz/f/sub-1-ghz-forum/382066/cc1101---don-t-know-the-correct-registers-configuration - - /* GPIO GD0 */ - {CC1101_IOCFG0, 0x0D}, // GD0 as async serial data output/input - - /* FIFO and internals */ - {CC1101_FIFOTHR, 0x47}, // The only important bit is ADC_RETENTION, FIFO Tx=33 Rx=32 - - /* Packet engine */ - {CC1101_PKTCTRL0, 0x32}, // Async, continious, no whitening - - /* Frequency Synthesizer Control */ - {CC1101_FSCTRL1, 0x06}, // IF = (26*10^6) / (2^10) * 0x06 = 152343.75Hz - - // Modem Configuration - {CC1101_MDMCFG0, 0x00}, // Channel spacing is 25kHz - {CC1101_MDMCFG1, 0x00}, // Channel spacing is 25kHz - {CC1101_MDMCFG2, 0x30}, // Format ASK/OOK, No preamble/sync - {CC1101_MDMCFG3, 0x32}, // Data rate is 3.79372 kBaud - {CC1101_MDMCFG4, 0x67}, // Rx BW filter is 270.833333kHz - - /* Main Radio Control State Machine */ - {CC1101_MCSM0, 0x18}, // Autocalibrate on idle-to-rx/tx, PO_TIMEOUT is 64 cycles(149-155us) - - /* Frequency Offset Compensation Configuration */ - {CC1101_FOCCFG, - 0x18}, // no frequency offset compensation, POST_K same as PRE_K, PRE_K is 4K, GATE is off - - /* Automatic Gain Control */ - {CC1101_AGCCTRL0, - 0x40}, // 01 - Low hysteresis, small asymmetric dead zone, medium gain; 00 - 8 samples agc; 00 - Normal AGC, 00 - 4dB boundary - {CC1101_AGCCTRL1, - 0x00}, // 0; 0 - LNA 2 gain is decreased to minimum before decreasing LNA gain; 00 - Relative carrier sense threshold disabled; 0000 - RSSI to MAIN_TARGET - {CC1101_AGCCTRL2, 0x03}, // 00 - DVGA all; 000 - MAX LNA+LNA2; 011 - MAIN_TARGET 24 dB - - /* Wake on radio and timeouts control */ - {CC1101_WORCTRL, 0xFB}, // WOR_RES is 2^15 periods (0.91 - 0.94 s) 16.5 - 17.2 hours - - /* Frontend configuration */ - {CC1101_FREND0, 0x11}, // Adjusts current TX LO buffer + high is PATABLE[1] - {CC1101_FREND1, 0xB6}, // - - /* End */ - {0, 0}, -}; - -static const uint8_t furi_hal_subghz_preset_ook_650khz_async_regs[][2] = { - // https://e2e.ti.com/support/wireless-connectivity/sub-1-ghz-group/sub-1-ghz/f/sub-1-ghz-forum/382066/cc1101---don-t-know-the-correct-registers-configuration - - /* GPIO GD0 */ - {CC1101_IOCFG0, 0x0D}, // GD0 as async serial data output/input - - /* FIFO and internals */ - {CC1101_FIFOTHR, 0x07}, // The only important bit is ADC_RETENTION - - /* Packet engine */ - {CC1101_PKTCTRL0, 0x32}, // Async, continious, no whitening - - /* Frequency Synthesizer Control */ - {CC1101_FSCTRL1, 0x06}, // IF = (26*10^6) / (2^10) * 0x06 = 152343.75Hz - - // Modem Configuration - {CC1101_MDMCFG0, 0x00}, // Channel spacing is 25kHz - {CC1101_MDMCFG1, 0x00}, // Channel spacing is 25kHz - {CC1101_MDMCFG2, 0x30}, // Format ASK/OOK, No preamble/sync - {CC1101_MDMCFG3, 0x32}, // Data rate is 3.79372 kBaud - {CC1101_MDMCFG4, 0x17}, // Rx BW filter is 650.000kHz - - /* Main Radio Control State Machine */ - {CC1101_MCSM0, 0x18}, // Autocalibrate on idle-to-rx/tx, PO_TIMEOUT is 64 cycles(149-155us) - - /* Frequency Offset Compensation Configuration */ - {CC1101_FOCCFG, - 0x18}, // no frequency offset compensation, POST_K same as PRE_K, PRE_K is 4K, GATE is off - - /* Automatic Gain Control */ - // {CC1101_AGCTRL0,0x40}, // 01 - Low hysteresis, small asymmetric dead zone, medium gain; 00 - 8 samples agc; 00 - Normal AGC, 00 - 4dB boundary - // {CC1101_AGCTRL1,0x00}, // 0; 0 - LNA 2 gain is decreased to minimum before decreasing LNA gain; 00 - Relative carrier sense threshold disabled; 0000 - RSSI to MAIN_TARGET - // {CC1101_AGCCTRL2, 0x03}, // 00 - DVGA all; 000 - MAX LNA+LNA2; 011 - MAIN_TARGET 24 dB - //MAGN_TARGET for RX filter BW =< 100 kHz is 0x3. For higher RX filter BW's MAGN_TARGET is 0x7. - {CC1101_AGCCTRL0, - 0x91}, // 10 - Medium hysteresis, medium asymmetric dead zone, medium gain ; 01 - 16 samples agc; 00 - Normal AGC, 01 - 8dB boundary - {CC1101_AGCCTRL1, - 0x0}, // 0; 0 - LNA 2 gain is decreased to minimum before decreasing LNA gain; 00 - Relative carrier sense threshold disabled; 0000 - RSSI to MAIN_TARGET - {CC1101_AGCCTRL2, 0x07}, // 00 - DVGA all; 000 - MAX LNA+LNA2; 111 - MAIN_TARGET 42 dB - - /* Wake on radio and timeouts control */ - {CC1101_WORCTRL, 0xFB}, // WOR_RES is 2^15 periods (0.91 - 0.94 s) 16.5 - 17.2 hours - - /* Frontend configuration */ - {CC1101_FREND0, 0x11}, // Adjusts current TX LO buffer + high is PATABLE[1] - {CC1101_FREND1, 0xB6}, // - - /* End */ - {0, 0}, -}; - -static const uint8_t furi_hal_subghz_preset_2fsk_dev2_38khz_async_regs[][2] = { - - /* GPIO GD0 */ - {CC1101_IOCFG0, 0x0D}, // GD0 as async serial data output/input - - /* Frequency Synthesizer Control */ - {CC1101_FSCTRL1, 0x06}, // IF = (26*10^6) / (2^10) * 0x06 = 152343.75Hz - - /* Packet engine */ - {CC1101_PKTCTRL0, 0x32}, // Async, continious, no whitening - {CC1101_PKTCTRL1, 0x04}, - - // // Modem Configuration - {CC1101_MDMCFG0, 0x00}, - {CC1101_MDMCFG1, 0x02}, - {CC1101_MDMCFG2, 0x04}, // Format 2-FSK/FM, No preamble/sync, Disable (current optimized) - {CC1101_MDMCFG3, 0x83}, // Data rate is 4.79794 kBaud - {CC1101_MDMCFG4, 0x67}, //Rx BW filter is 270.833333 kHz - {CC1101_DEVIATN, 0x04}, //Deviation 2.380371 kHz - - /* Main Radio Control State Machine */ - {CC1101_MCSM0, 0x18}, // Autocalibrate on idle-to-rx/tx, PO_TIMEOUT is 64 cycles(149-155us) - - /* Frequency Offset Compensation Configuration */ - {CC1101_FOCCFG, - 0x16}, // no frequency offset compensation, POST_K same as PRE_K, PRE_K is 4K, GATE is off - - /* Automatic Gain Control */ - {CC1101_AGCCTRL0, - 0x91}, //10 - Medium hysteresis, medium asymmetric dead zone, medium gain ; 01 - 16 samples agc; 00 - Normal AGC, 01 - 8dB boundary - {CC1101_AGCCTRL1, - 0x00}, // 0; 0 - LNA 2 gain is decreased to minimum before decreasing LNA gain; 00 - Relative carrier sense threshold disabled; 0000 - RSSI to MAIN_TARGET - {CC1101_AGCCTRL2, 0x07}, // 00 - DVGA all; 000 - MAX LNA+LNA2; 111 - MAIN_TARGET 42 dB - - /* Wake on radio and timeouts control */ - {CC1101_WORCTRL, 0xFB}, // WOR_RES is 2^15 periods (0.91 - 0.94 s) 16.5 - 17.2 hours - - /* Frontend configuration */ - {CC1101_FREND0, 0x10}, // Adjusts current TX LO buffer - {CC1101_FREND1, 0x56}, - - /* End */ - {0, 0}, -}; - -static const uint8_t furi_hal_subghz_preset_2fsk_dev47_6khz_async_regs[][2] = { - - /* GPIO GD0 */ - {CC1101_IOCFG0, 0x0D}, // GD0 as async serial data output/input - - /* Frequency Synthesizer Control */ - {CC1101_FSCTRL1, 0x06}, // IF = (26*10^6) / (2^10) * 0x06 = 152343.75Hz - - /* Packet engine */ - {CC1101_PKTCTRL0, 0x32}, // Async, continious, no whitening - {CC1101_PKTCTRL1, 0x04}, - - // // Modem Configuration - {CC1101_MDMCFG0, 0x00}, - {CC1101_MDMCFG1, 0x02}, - {CC1101_MDMCFG2, 0x04}, // Format 2-FSK/FM, No preamble/sync, Disable (current optimized) - {CC1101_MDMCFG3, 0x83}, // Data rate is 4.79794 kBaud - {CC1101_MDMCFG4, 0x67}, //Rx BW filter is 270.833333 kHz - {CC1101_DEVIATN, 0x47}, //Deviation 47.60742 kHz - - /* Main Radio Control State Machine */ - {CC1101_MCSM0, 0x18}, // Autocalibrate on idle-to-rx/tx, PO_TIMEOUT is 64 cycles(149-155us) - - /* Frequency Offset Compensation Configuration */ - {CC1101_FOCCFG, - 0x16}, // no frequency offset compensation, POST_K same as PRE_K, PRE_K is 4K, GATE is off - - /* Automatic Gain Control */ - {CC1101_AGCCTRL0, - 0x91}, //10 - Medium hysteresis, medium asymmetric dead zone, medium gain ; 01 - 16 samples agc; 00 - Normal AGC, 01 - 8dB boundary - {CC1101_AGCCTRL1, - 0x00}, // 0; 0 - LNA 2 gain is decreased to minimum before decreasing LNA gain; 00 - Relative carrier sense threshold disabled; 0000 - RSSI to MAIN_TARGET - {CC1101_AGCCTRL2, 0x07}, // 00 - DVGA all; 000 - MAX LNA+LNA2; 111 - MAIN_TARGET 42 dB - - /* Wake on radio and timeouts control */ - {CC1101_WORCTRL, 0xFB}, // WOR_RES is 2^15 periods (0.91 - 0.94 s) 16.5 - 17.2 hours - - /* Frontend configuration */ - {CC1101_FREND0, 0x10}, // Adjusts current TX LO buffer - {CC1101_FREND1, 0x56}, - - /* End */ - {0, 0}, -}; - -static const uint8_t furi_hal_subghz_preset_msk_99_97kb_async_regs[][2] = { - /* GPIO GD0 */ - {CC1101_IOCFG0, 0x06}, - - {CC1101_FIFOTHR, 0x07}, // The only important bit is ADC_RETENTION - {CC1101_SYNC1, 0x46}, - {CC1101_SYNC0, 0x4C}, - {CC1101_ADDR, 0x00}, - {CC1101_PKTLEN, 0x00}, - {CC1101_CHANNR, 0x00}, - - {CC1101_PKTCTRL0, 0x05}, - - {CC1101_FSCTRL0, 0x23}, - {CC1101_FSCTRL1, 0x06}, - - {CC1101_MDMCFG0, 0xF8}, - {CC1101_MDMCFG1, 0x22}, - {CC1101_MDMCFG2, 0x72}, - {CC1101_MDMCFG3, 0xF8}, - {CC1101_MDMCFG4, 0x5B}, - {CC1101_DEVIATN, 0x47}, - - {CC1101_MCSM0, 0x18}, - {CC1101_FOCCFG, 0x16}, - - {CC1101_AGCCTRL0, 0xB2}, - {CC1101_AGCCTRL1, 0x00}, - {CC1101_AGCCTRL2, 0xC7}, - - {CC1101_FREND0, 0x10}, - {CC1101_FREND1, 0x56}, - - {CC1101_BSCFG, 0x1C}, - {CC1101_FSTEST, 0x59}, - - /* End */ - {0, 0}, -}; - -static const uint8_t furi_hal_subghz_preset_gfsk_9_99kb_async_regs[][2] = { - - {CC1101_IOCFG0, 0x06}, //GDO0 Output Pin Configuration - {CC1101_FIFOTHR, 0x47}, //RX FIFO and TX FIFO Thresholds - - //1 : CRC calculation in TX and CRC check in RX enabled, - //1 : Variable packet length mode. Packet length configured by the first byte after sync word - {CC1101_PKTCTRL0, 0x05}, - - {CC1101_FSCTRL1, 0x06}, //Frequency Synthesizer Control - - {CC1101_SYNC1, 0x46}, - {CC1101_SYNC0, 0x4C}, - {CC1101_ADDR, 0x00}, - {CC1101_PKTLEN, 0x00}, - - {CC1101_MDMCFG4, 0xC8}, //Modem Configuration 9.99 - {CC1101_MDMCFG3, 0x93}, //Modem Configuration - {CC1101_MDMCFG2, 0x12}, // 2: 16/16 sync word bits detected - - {CC1101_DEVIATN, 0x34}, //Deviation = 19.042969 - {CC1101_MCSM0, 0x18}, //Main Radio Control State Machine Configuration - {CC1101_FOCCFG, 0x16}, //Frequency Offset Compensation Configuration - - {CC1101_AGCCTRL2, 0x43}, //AGC Control - {CC1101_AGCCTRL1, 0x40}, - {CC1101_AGCCTRL0, 0x91}, - - {CC1101_WORCTRL, 0xFB}, //Wake On Radio Control - /* End */ - {0, 0}, -}; - -static const uint8_t furi_hal_subghz_preset_ook_async_patable[8] = { - 0x00, - 0xC0, // 12dBm 0xC0, 10dBm 0xC5, 7dBm 0xCD, 5dBm 0x86, 0dBm 0x50, -6dBm 0x37, -10dBm 0x26, -15dBm 0x1D, -20dBm 0x17, -30dBm 0x03 - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00}; - -static const uint8_t furi_hal_subghz_preset_2fsk_async_patable[8] = { - 0xC0, // 10dBm 0xC0, 7dBm 0xC8, 5dBm 0x84, 0dBm 0x60, -10dBm 0x34, -15dBm 0x1D, -20dBm 0x0E, -30dBm 0x12 - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00}; - -static const uint8_t furi_hal_subghz_preset_msk_async_patable[8] = { - 0xC0, // 10dBm 0xC0, 7dBm 0xC8, 5dBm 0x84, 0dBm 0x60, -10dBm 0x34, -15dBm 0x1D, -20dBm 0x0E, -30dBm 0x12 - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00}; - -static const uint8_t furi_hal_subghz_preset_gfsk_async_patable[8] = { - 0xC0, // 10dBm 0xC0, 7dBm 0xC8, 5dBm 0x84, 0dBm 0x60, -10dBm 0x34, -15dBm 0x1D, -20dBm 0x0E, -30dBm 0x12 - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00}; diff --git a/lib/subghz/protocols/raw.c b/lib/subghz/protocols/raw.c index a82c9cf83..d945d19a1 100644 --- a/lib/subghz/protocols/raw.c +++ b/lib/subghz/protocols/raw.c @@ -40,6 +40,7 @@ struct SubGhzProtocolEncoderRAW { bool is_running; FuriString* file_name; + FuriString* radio_device_name; SubGhzFileEncoderWorker* file_worker_encoder; }; @@ -282,6 +283,7 @@ void* subghz_protocol_encoder_raw_alloc(SubGhzEnvironment* environment) { instance->base.protocol = &subghz_protocol_raw; instance->file_name = furi_string_alloc(); + instance->radio_device_name = furi_string_alloc(); instance->is_running = false; return instance; } @@ -300,6 +302,7 @@ void subghz_protocol_encoder_raw_free(void* context) { SubGhzProtocolEncoderRAW* instance = context; subghz_protocol_encoder_raw_stop(instance); furi_string_free(instance->file_name); + furi_string_free(instance->radio_device_name); free(instance); } @@ -318,7 +321,9 @@ static bool subghz_protocol_encoder_raw_worker_init(SubGhzProtocolEncoderRAW* in instance->file_worker_encoder = subghz_file_encoder_worker_alloc(); if(subghz_file_encoder_worker_start( - instance->file_worker_encoder, furi_string_get_cstr(instance->file_name))) { + instance->file_worker_encoder, + furi_string_get_cstr(instance->file_name), + furi_string_get_cstr(instance->radio_device_name))) { //the worker needs a file in order to open and read part of the file furi_delay_ms(100); instance->is_running = true; @@ -328,7 +333,10 @@ static bool subghz_protocol_encoder_raw_worker_init(SubGhzProtocolEncoderRAW* in return instance->is_running; } -void subghz_protocol_raw_gen_fff_data(FlipperFormat* flipper_format, const char* file_path) { +void subghz_protocol_raw_gen_fff_data( + FlipperFormat* flipper_format, + const char* file_path, + const char* radio_device_name) { do { stream_clean(flipper_format_get_raw_stream(flipper_format)); if(!flipper_format_write_string_cstr(flipper_format, "Protocol", "RAW")) { @@ -340,6 +348,12 @@ void subghz_protocol_raw_gen_fff_data(FlipperFormat* flipper_format, const char* FURI_LOG_E(TAG, "Unable to add File_name"); break; } + + if(!flipper_format_write_string_cstr( + flipper_format, "Radio_device_name", radio_device_name)) { + FURI_LOG_E(TAG, "Unable to add Radio_device_name"); + break; + } } while(false); } @@ -364,6 +378,13 @@ SubGhzProtocolStatus } furi_string_set(instance->file_name, temp_str); + if(!flipper_format_read_string(flipper_format, "Radio_device_name", temp_str)) { + FURI_LOG_E(TAG, "Missing Radio_device_name"); + res = SubGhzProtocolStatusErrorParserOthers; + break; + } + furi_string_set(instance->radio_device_name, temp_str); + if(!subghz_protocol_encoder_raw_worker_init(instance)) { res = SubGhzProtocolStatusErrorEncoderGetUpload; break; diff --git a/lib/subghz/protocols/raw.h b/lib/subghz/protocols/raw.h index 4f67a4e2f..6d791bb36 100644 --- a/lib/subghz/protocols/raw.h +++ b/lib/subghz/protocols/raw.h @@ -126,8 +126,12 @@ void subghz_protocol_raw_file_encoder_worker_set_callback_end( * File generation for RAW work. * @param flipper_format Pointer to a FlipperFormat instance * @param file_path File path + * @param radio_dev_name Radio device name */ -void subghz_protocol_raw_gen_fff_data(FlipperFormat* flipper_format, const char* file_path); +void subghz_protocol_raw_gen_fff_data( + FlipperFormat* flipper_format, + const char* file_path, + const char* radio_dev_name); /** * Deserialize and generating an upload to send. diff --git a/lib/subghz/subghz_file_encoder_worker.c b/lib/subghz/subghz_file_encoder_worker.c index fce4e8592..3534745b7 100644 --- a/lib/subghz/subghz_file_encoder_worker.c +++ b/lib/subghz/subghz_file_encoder_worker.c @@ -3,6 +3,7 @@ #include #include #include +#include #define TAG "SubGhzFileEncoderWorker" @@ -21,6 +22,7 @@ struct SubGhzFileEncoderWorker { bool is_storage_slow; FuriString* str_data; FuriString* file_path; + const SubGhzDevice* device; SubGhzFileEncoderWorkerCallbackEnd callback_end; void* context_end; @@ -180,10 +182,13 @@ static int32_t subghz_file_encoder_worker_thread(void* context) { if(instance->is_storage_slow) { FURI_LOG_E(TAG, "Storage is slow"); } + FURI_LOG_I(TAG, "End read file"); - while(!furi_hal_subghz_is_async_tx_complete() && instance->worker_running) { + while(instance->device && !subghz_devices_is_async_complete_tx(instance->device) && + instance->worker_running) { furi_delay_ms(5); } + FURI_LOG_I(TAG, "End transmission"); while(instance->worker_running) { if(instance->worker_stopping) { @@ -230,12 +235,16 @@ void subghz_file_encoder_worker_free(SubGhzFileEncoderWorker* instance) { free(instance); } -bool subghz_file_encoder_worker_start(SubGhzFileEncoderWorker* instance, const char* file_path) { +bool subghz_file_encoder_worker_start( + SubGhzFileEncoderWorker* instance, + const char* file_path, + const char* radio_device_name) { furi_assert(instance); furi_assert(!instance->worker_running); furi_stream_buffer_reset(instance->stream); furi_string_set(instance->file_path, file_path); + instance->device = subghz_devices_get_by_name(radio_device_name); instance->worker_running = true; furi_thread_start(instance->thread); diff --git a/lib/subghz/subghz_file_encoder_worker.h b/lib/subghz/subghz_file_encoder_worker.h index 19a46f1e6..e3373eb9b 100644 --- a/lib/subghz/subghz_file_encoder_worker.h +++ b/lib/subghz/subghz_file_encoder_worker.h @@ -47,9 +47,14 @@ LevelDuration subghz_file_encoder_worker_get_level_duration(void* context); /** * Start SubGhzFileEncoderWorker. * @param instance Pointer to a SubGhzFileEncoderWorker instance + * @param file_path File path + * @param radio_device_name Radio device name * @return bool - true if ok */ -bool subghz_file_encoder_worker_start(SubGhzFileEncoderWorker* instance, const char* file_path); +bool subghz_file_encoder_worker_start( + SubGhzFileEncoderWorker* instance, + const char* file_path, + const char* radio_device_name); /** * Stop SubGhzFileEncoderWorker diff --git a/lib/subghz/subghz_setting.c b/lib/subghz/subghz_setting.c index cd9d0466e..17d659d04 100644 --- a/lib/subghz/subghz_setting.c +++ b/lib/subghz/subghz_setting.c @@ -4,7 +4,7 @@ #include #include -#include +#include #define TAG "SubGhzSetting" @@ -195,23 +195,23 @@ static void subghz_setting_load_default_region( subghz_setting_load_default_preset( instance, "AM270", - (uint8_t*)furi_hal_subghz_preset_ook_270khz_async_regs, - furi_hal_subghz_preset_ook_async_patable); + (uint8_t*)subghz_device_cc1101_preset_ook_270khz_async_regs, + subghz_device_cc1101_preset_ook_async_patable); subghz_setting_load_default_preset( instance, "AM650", - (uint8_t*)furi_hal_subghz_preset_ook_650khz_async_regs, - furi_hal_subghz_preset_ook_async_patable); + (uint8_t*)subghz_device_cc1101_preset_ook_650khz_async_regs, + subghz_device_cc1101_preset_ook_async_patable); subghz_setting_load_default_preset( instance, "FM238", - (uint8_t*)furi_hal_subghz_preset_2fsk_dev2_38khz_async_regs, - furi_hal_subghz_preset_2fsk_async_patable); + (uint8_t*)subghz_device_cc1101_preset_2fsk_dev2_38khz_async_regs, + subghz_device_cc1101_preset_2fsk_async_patable); subghz_setting_load_default_preset( instance, "FM476", - (uint8_t*)furi_hal_subghz_preset_2fsk_dev47_6khz_async_regs, - furi_hal_subghz_preset_2fsk_async_patable); + (uint8_t*)subghz_device_cc1101_preset_2fsk_dev47_6khz_async_regs, + subghz_device_cc1101_preset_2fsk_async_patable); } // Region check removed @@ -270,6 +270,7 @@ void subghz_setting_load(SubGhzSetting* instance, const char* file_path) { } while(flipper_format_read_uint32( fff_data_file, "Frequency", (uint32_t*)&temp_data32, 1)) { + //Todo: add a frequency support check depending on the selected radio device if(furi_hal_subghz_is_frequency_valid(temp_data32)) { FURI_LOG_I(TAG, "Frequency loaded %lu", temp_data32); FrequencyList_push_back(instance->frequencies, temp_data32); diff --git a/lib/subghz/subghz_tx_rx_worker.c b/lib/subghz/subghz_tx_rx_worker.c index 21568dab1..4eca17419 100644 --- a/lib/subghz/subghz_tx_rx_worker.c +++ b/lib/subghz/subghz_tx_rx_worker.c @@ -21,6 +21,8 @@ struct SubGhzTxRxWorker { SubGhzTxRxWorkerStatus status; uint32_t frequency; + const SubGhzDevice* device; + const GpioPin* device_data_gpio; SubGhzTxRxWorkerCallbackHaveRead callback_have_read; void* context_have_read; @@ -65,33 +67,33 @@ bool subghz_tx_rx_worker_rx(SubGhzTxRxWorker* instance, uint8_t* data, uint8_t* uint8_t timeout = 100; bool ret = false; if(instance->status != SubGhzTxRxWorkerStatusRx) { - furi_hal_subghz_rx(); + subghz_devices_set_rx(instance->device); instance->status = SubGhzTxRxWorkerStatusRx; furi_delay_tick(1); } //waiting for reception to complete - while(furi_hal_gpio_read(furi_hal_subghz.cc1101_g0_pin)) { + while(furi_hal_gpio_read(instance->device_data_gpio)) { furi_delay_tick(1); if(!--timeout) { FURI_LOG_W(TAG, "RX cc1101_g0 timeout"); - furi_hal_subghz_flush_rx(); - furi_hal_subghz_rx(); + subghz_devices_flush_rx(instance->device); + subghz_devices_set_rx(instance->device); break; } } - if(furi_hal_subghz_rx_pipe_not_empty()) { + if(subghz_devices_rx_pipe_not_empty(instance->device)) { FURI_LOG_I( TAG, "RSSI: %03.1fdbm LQI: %d", - (double)furi_hal_subghz_get_rssi(), - furi_hal_subghz_get_lqi()); - if(furi_hal_subghz_is_rx_data_crc_valid()) { - furi_hal_subghz_read_packet(data, size); + (double)subghz_devices_get_rssi(instance->device), + subghz_devices_get_lqi(instance->device)); + if(subghz_devices_is_rx_data_crc_valid(instance->device)) { + subghz_devices_read_packet(instance->device, data, size); ret = true; } - furi_hal_subghz_flush_rx(); - furi_hal_subghz_rx(); + subghz_devices_flush_rx(instance->device); + subghz_devices_set_rx(instance->device); } return ret; } @@ -99,13 +101,13 @@ bool subghz_tx_rx_worker_rx(SubGhzTxRxWorker* instance, uint8_t* data, uint8_t* void subghz_tx_rx_worker_tx(SubGhzTxRxWorker* instance, uint8_t* data, size_t size) { uint8_t timeout = 200; if(instance->status != SubGhzTxRxWorkerStatusIDLE) { - furi_hal_subghz_idle(); + subghz_devices_idle(instance->device); } - furi_hal_subghz_write_packet(data, size); - furi_hal_subghz_tx(); //start send + subghz_devices_write_packet(instance->device, data, size); + subghz_devices_set_tx(instance->device); //start send instance->status = SubGhzTxRxWorkerStatusTx; while(!furi_hal_gpio_read( - furi_hal_subghz.cc1101_g0_pin)) { // Wait for GDO0 to be set -> sync transmitted + instance->device_data_gpio)) { // Wait for GDO0 to be set -> sync transmitted furi_delay_tick(1); if(!--timeout) { FURI_LOG_W(TAG, "TX !cc1101_g0 timeout"); @@ -113,14 +115,14 @@ void subghz_tx_rx_worker_tx(SubGhzTxRxWorker* instance, uint8_t* data, size_t si } } while(furi_hal_gpio_read( - furi_hal_subghz.cc1101_g0_pin)) { // Wait for GDO0 to be cleared -> end of packet + instance->device_data_gpio)) { // Wait for GDO0 to be cleared -> end of packet furi_delay_tick(1); if(!--timeout) { FURI_LOG_W(TAG, "TX cc1101_g0 timeout"); break; } } - furi_hal_subghz_idle(); + subghz_devices_idle(instance->device); instance->status = SubGhzTxRxWorkerStatusIDLE; } /** Worker thread @@ -130,16 +132,19 @@ void subghz_tx_rx_worker_tx(SubGhzTxRxWorker* instance, uint8_t* data, size_t si */ static int32_t subghz_tx_rx_worker_thread(void* context) { SubGhzTxRxWorker* instance = context; + furi_assert(instance->device); FURI_LOG_I(TAG, "Worker start"); - furi_hal_subghz_reset(); - furi_hal_subghz_idle(); - furi_hal_subghz_load_preset(FuriHalSubGhzPresetGFSK9_99KbAsync); - //furi_hal_subghz_load_preset(FuriHalSubGhzPresetMSK99_97KbAsync); - furi_hal_gpio_init(furi_hal_subghz.cc1101_g0_pin, GpioModeInput, GpioPullNo, GpioSpeedLow); + subghz_devices_begin(instance->device); + instance->device_data_gpio = subghz_devices_get_data_gpio(instance->device); + subghz_devices_reset(instance->device); + subghz_devices_idle(instance->device); + subghz_devices_load_preset(instance->device, FuriHalSubGhzPresetGFSK9_99KbAsync, NULL); - furi_hal_subghz_set_frequency_and_path(instance->frequency); - furi_hal_subghz_flush_rx(); + furi_hal_gpio_init(instance->device_data_gpio, GpioModeInput, GpioPullNo, GpioSpeedLow); + + subghz_devices_set_frequency(instance->device, instance->frequency); + subghz_devices_flush_rx(instance->device); uint8_t data[SUBGHZ_TXRX_WORKER_MAX_TXRX_SIZE + 1] = {0}; size_t size_tx = 0; @@ -193,8 +198,8 @@ static int32_t subghz_tx_rx_worker_thread(void* context) { furi_delay_tick(1); } - furi_hal_subghz_set_path(FuriHalSubGhzPathIsolate); - furi_hal_subghz_sleep(); + subghz_devices_sleep(instance->device); + subghz_devices_end(instance->device); FURI_LOG_I(TAG, "Worker stop"); return 0; @@ -226,7 +231,10 @@ void subghz_tx_rx_worker_free(SubGhzTxRxWorker* instance) { free(instance); } -bool subghz_tx_rx_worker_start(SubGhzTxRxWorker* instance, uint32_t frequency) { +bool subghz_tx_rx_worker_start( + SubGhzTxRxWorker* instance, + const SubGhzDevice* device, + uint32_t frequency) { furi_assert(instance); furi_assert(!instance->worker_running); bool res = false; @@ -237,6 +245,7 @@ bool subghz_tx_rx_worker_start(SubGhzTxRxWorker* instance, uint32_t frequency) { if(furi_hal_subghz_is_tx_allowed(frequency)) { instance->frequency = frequency; + instance->device = device; res = true; } diff --git a/lib/subghz/subghz_tx_rx_worker.h b/lib/subghz/subghz_tx_rx_worker.h index ddc02e749..56bdb0a1f 100644 --- a/lib/subghz/subghz_tx_rx_worker.h +++ b/lib/subghz/subghz_tx_rx_worker.h @@ -1,6 +1,7 @@ #pragma once #include +#include #ifdef __cplusplus extern "C" { @@ -67,9 +68,13 @@ void subghz_tx_rx_worker_free(SubGhzTxRxWorker* instance); /** * Start SubGhzTxRxWorker * @param instance Pointer to a SubGhzTxRxWorker instance + * @param device Pointer to a SubGhzDevice instance * @return bool - true if ok */ -bool subghz_tx_rx_worker_start(SubGhzTxRxWorker* instance, uint32_t frequency); +bool subghz_tx_rx_worker_start( + SubGhzTxRxWorker* instance, + const SubGhzDevice* device, + uint32_t frequency); /** * Stop SubGhzTxRxWorker From 2817913e63fe32edce6eb0f7441c1934030d48f1 Mon Sep 17 00:00:00 2001 From: gid9798 <30450294+gid9798@users.noreply.github.com> Date: Sun, 18 Jun 2023 21:09:07 +0300 Subject: [PATCH 004/364] prt3 --- .../subghz_frequency_analyzer_worker.c | 66 ++++++++++--------- .../scenes/subghz_scene_radio_settings.c | 10 +-- applications/main/subghz/subghz.c | 13 ---- .../main/subghz/subghz_last_settings.c | 12 ---- .../main/subghz/subghz_last_settings.h | 2 + .../subghz/views/subghz_frequency_analyzer.c | 3 +- .../main/subghz/views/subghz_test_carrier.c | 17 ++--- .../main/subghz/views/subghz_test_static.c | 7 +- .../subghz_remote/helpers/subrem_presets.c | 4 +- .../main/subghz_remote/subghz_remote_app.c | 24 +++---- 10 files changed, 70 insertions(+), 88 deletions(-) diff --git a/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.c b/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.c index 6fec29565..e672542bb 100644 --- a/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.c +++ b/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.c @@ -4,6 +4,8 @@ #include #include +// TODO add external module + #define TAG "SubghzFrequencyAnalyzerWorker" #define SUBGHZ_FREQUENCY_ANALYZER_THRESHOLD -97.0f @@ -36,13 +38,13 @@ struct SubGhzFrequencyAnalyzerWorker { }; static void subghz_frequency_analyzer_worker_load_registers(const uint8_t data[][2]) { - furi_hal_spi_acquire(furi_hal_subghz.spi_bus_handle); + furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); size_t i = 0; while(data[i][0]) { - cc1101_write_reg(furi_hal_subghz.spi_bus_handle, data[i][0], data[i][1]); + cc1101_write_reg(&furi_hal_spi_bus_handle_subghz, data[i][0], data[i][1]); i++; } - furi_hal_spi_release(furi_hal_subghz.spi_bus_handle); + furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); } // running average with adaptive coefficient @@ -80,26 +82,26 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) { //Start CC1101 furi_hal_subghz_reset(); - furi_hal_spi_acquire(furi_hal_subghz.spi_bus_handle); - cc1101_flush_rx(furi_hal_subghz.spi_bus_handle); - cc1101_flush_tx(furi_hal_subghz.spi_bus_handle); - cc1101_write_reg(furi_hal_subghz.spi_bus_handle, CC1101_IOCFG0, CC1101IocfgHW); - cc1101_write_reg(furi_hal_subghz.spi_bus_handle, CC1101_MDMCFG3, + furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); + cc1101_flush_rx(&furi_hal_spi_bus_handle_subghz); + cc1101_flush_tx(&furi_hal_spi_bus_handle_subghz); + cc1101_write_reg(&furi_hal_spi_bus_handle_subghz, CC1101_IOCFG0, CC1101IocfgHW); + cc1101_write_reg(&furi_hal_spi_bus_handle_subghz, CC1101_MDMCFG3, 0b01111111); // symbol rate cc1101_write_reg( - furi_hal_subghz.spi_bus_handle, + &furi_hal_spi_bus_handle_subghz, CC1101_AGCCTRL2, 0b00000111); // 00 - DVGA all; 000 - MAX LNA+LNA2; 111 - MAGN_TARGET 42 dB cc1101_write_reg( - furi_hal_subghz.spi_bus_handle, + &furi_hal_spi_bus_handle_subghz, CC1101_AGCCTRL1, 0b00001000); // 0; 0 - LNA 2 gain is decreased to minimum before decreasing LNA gain; 00 - Relative carrier sense threshold disabled; 1000 - Absolute carrier sense threshold disabled cc1101_write_reg( - furi_hal_subghz.spi_bus_handle, + &furi_hal_spi_bus_handle_subghz, CC1101_AGCCTRL0, 0b00110000); // 00 - No hysteresis, medium asymmetric dead zone, medium gain ; 11 - 64 samples agc; 00 - Normal AGC, 00 - 4dB boundary - furi_hal_spi_release(furi_hal_subghz.spi_bus_handle); + furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); furi_hal_subghz_set_path(FuriHalSubGhzPathIsolate); @@ -119,23 +121,25 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) { for(size_t i = 0; i < subghz_setting_get_frequency_count(instance->setting); i++) { uint32_t current_frequency = subghz_setting_get_frequency(instance->setting, i); if(furi_hal_subghz_is_frequency_valid(current_frequency) && - (current_frequency != 467750000) && (current_frequency != 464000000) && - !((furi_hal_subghz.radio_type == SubGhzRadioExternal) && - ((current_frequency == 390000000) || (current_frequency == 312000000) || - (current_frequency == 312100000) || (current_frequency == 312200000) || - (current_frequency == 440175000)))) { - furi_hal_spi_acquire(furi_hal_subghz.spi_bus_handle); - cc1101_switch_to_idle(furi_hal_subghz.spi_bus_handle); + (current_frequency != 467750000) && (current_frequency != 464000000) + // && + // !((furi_hal_subghz.radio_type == SubGhzRadioExternal) && + // ((current_frequency == 390000000) || (current_frequency == 312000000) || + // (current_frequency == 312100000) || (current_frequency == 312200000) || + // (current_frequency == 440175000))) + ) { + furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); + cc1101_switch_to_idle(&furi_hal_spi_bus_handle_subghz); frequency = - cc1101_set_frequency(furi_hal_subghz.spi_bus_handle, current_frequency); + cc1101_set_frequency(&furi_hal_spi_bus_handle_subghz, current_frequency); - cc1101_calibrate(furi_hal_subghz.spi_bus_handle); + cc1101_calibrate(&furi_hal_spi_bus_handle_subghz); do { - status = cc1101_get_status(furi_hal_subghz.spi_bus_handle); + status = cc1101_get_status(&furi_hal_spi_bus_handle_subghz); } while(status.STATE != CC1101StateIDLE); - cc1101_switch_to_rx(furi_hal_subghz.spi_bus_handle); - furi_hal_spi_release(furi_hal_subghz.spi_bus_handle); + cc1101_switch_to_rx(&furi_hal_spi_bus_handle_subghz); + furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); furi_delay_ms(2); @@ -170,17 +174,17 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) { i < frequency_rssi.frequency_coarse + 300000; i += 20000) { if(furi_hal_subghz_is_frequency_valid(i)) { - furi_hal_spi_acquire(furi_hal_subghz.spi_bus_handle); - cc1101_switch_to_idle(furi_hal_subghz.spi_bus_handle); - frequency = cc1101_set_frequency(furi_hal_subghz.spi_bus_handle, i); + furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); + cc1101_switch_to_idle(&furi_hal_spi_bus_handle_subghz); + frequency = cc1101_set_frequency(&furi_hal_spi_bus_handle_subghz, i); - cc1101_calibrate(furi_hal_subghz.spi_bus_handle); + cc1101_calibrate(&furi_hal_spi_bus_handle_subghz); do { - status = cc1101_get_status(furi_hal_subghz.spi_bus_handle); + status = cc1101_get_status(&furi_hal_spi_bus_handle_subghz); } while(status.STATE != CC1101StateIDLE); - cc1101_switch_to_rx(furi_hal_subghz.spi_bus_handle); - furi_hal_spi_release(furi_hal_subghz.spi_bus_handle); + cc1101_switch_to_rx(&furi_hal_spi_bus_handle_subghz); + furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); furi_delay_ms(2); diff --git a/applications/main/subghz/scenes/subghz_scene_radio_settings.c b/applications/main/subghz/scenes/subghz_scene_radio_settings.c index a3576b3e5..7c78d07c4 100644 --- a/applications/main/subghz/scenes/subghz_scene_radio_settings.c +++ b/applications/main/subghz/scenes/subghz_scene_radio_settings.c @@ -132,7 +132,7 @@ void subghz_scene_radio_settings_on_enter(void* context) { // variable_item_set_current_value_text(item, radio_modules_variables_text[value_index]); // item = variable_item_list_add( - // subghz->variable_item_list, + // variable_item_list, // "Ext Radio 5v", // EXT_MOD_POWER_COUNT, // subghz_scene_receiver_config_set_ext_mod_power, @@ -142,7 +142,7 @@ void subghz_scene_radio_settings_on_enter(void* context) { // variable_item_set_current_value_text(item, ext_mod_power_text[value_index]); item = variable_item_list_add( - subghz->variable_item_list, + variable_item_list, "Time in names", TIMESTAMP_NAMES_COUNT, subghz_scene_receiver_config_set_timestamp_file_names, @@ -153,7 +153,7 @@ void subghz_scene_radio_settings_on_enter(void* context) { if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { item = variable_item_list_add( - subghz->variable_item_list, + variable_item_list, "Counter incr.", DEBUG_COUNTER_COUNT, subghz_scene_receiver_config_set_debug_counter, @@ -182,7 +182,7 @@ void subghz_scene_radio_settings_on_enter(void* context) { } } else { item = variable_item_list_add( - subghz->variable_item_list, + variable_item_list, "Counter incr.", 3, subghz_scene_receiver_config_set_debug_counter, @@ -209,7 +209,7 @@ void subghz_scene_radio_settings_on_enter(void* context) { if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { item = variable_item_list_add( - subghz->variable_item_list, + variable_item_list, "Debug Pin", DEBUG_P_COUNT, subghz_scene_receiver_config_set_debug_pin, diff --git a/applications/main/subghz/subghz.c b/applications/main/subghz/subghz.c index 97e2e8c34..2af1328ab 100644 --- a/applications/main/subghz/subghz.c +++ b/applications/main/subghz/subghz.c @@ -387,15 +387,6 @@ int32_t subghz_app(void* p) { subghz->raw_send_only = false; } - // Call enable power for external module - furi_hal_subghz_enable_ext_power(); - - // Auto switch to internal radio if external radio is not available - if(!furi_hal_subghz_check_radio()) { - subghz->last_settings->external_module_enabled = false; - furi_hal_subghz_select_radio_type(SubGhzRadioInternal); - furi_hal_subghz_init_radio_type(SubGhzRadioInternal); - } // Check argument and run corresponding scene if(p && strlen(p)) { uint32_t rpc_ctx = 0; @@ -448,10 +439,6 @@ int32_t subghz_app(void* p) { view_dispatcher_run(subghz->view_dispatcher); furi_hal_power_suppress_charge_exit(); - // Disable power for External CC1101 if it was enabled and module is connected - furi_hal_subghz_disable_ext_power(); - // Reinit SPI handles for internal radio / nfc - furi_hal_subghz_init_radio_type(SubGhzRadioInternal); subghz_free(subghz, alloc_for_tx); diff --git a/applications/main/subghz/subghz_last_settings.c b/applications/main/subghz/subghz_last_settings.c index 8cf0f063a..b61ed3769 100644 --- a/applications/main/subghz/subghz_last_settings.c +++ b/applications/main/subghz/subghz_last_settings.c @@ -119,18 +119,6 @@ void subghz_last_settings_load(SubGhzLastSettings* instance, size_t preset_count instance->timestamp_file_names = temp_timestamp_file_names; - // Set globally - if(instance->external_module_power_5v_disable) { - furi_hal_subghz_set_external_power_disable(true); - furi_hal_subghz_disable_ext_power(); - } - - // Set selected radio module - if(instance->external_module_enabled) { - furi_hal_subghz_select_radio_type(SubGhzRadioExternal); - furi_hal_subghz_init_radio_type(SubGhzRadioExternal); - } - /*/} else { instance->preset = temp_preset; }*/ diff --git a/applications/main/subghz/subghz_last_settings.h b/applications/main/subghz/subghz_last_settings.h index 260c879f4..d1a5b495f 100644 --- a/applications/main/subghz/subghz_last_settings.h +++ b/applications/main/subghz/subghz_last_settings.h @@ -10,8 +10,10 @@ typedef struct { int32_t preset; uint32_t frequency_analyzer_feedback_level; float frequency_analyzer_trigger; + // TODO not using but saved so as not to change the version bool external_module_enabled; bool external_module_power_5v_disable; + // saved so as not to change the version bool timestamp_file_names; } SubGhzLastSettings; diff --git a/applications/main/subghz/views/subghz_frequency_analyzer.c b/applications/main/subghz/views/subghz_frequency_analyzer.c index 92ba833c4..b3822feab 100644 --- a/applications/main/subghz/views/subghz_frequency_analyzer.c +++ b/applications/main/subghz/views/subghz_frequency_analyzer.c @@ -168,7 +168,8 @@ void subghz_frequency_analyzer_draw(Canvas* canvas, SubGhzFrequencyAnalyzerModel // Title canvas_set_color(canvas, ColorBlack); canvas_set_font(canvas, FontSecondary); - canvas_draw_str(canvas, 0, 7, furi_hal_subghz_get_radio_type() ? "Ext" : "Int"); + // TODO + // canvas_draw_str(canvas, 0, 7, furi_hal_subghz_get_radio_type() ? "Ext" : "Int"); canvas_draw_str(canvas, 20, 7, "Frequency Analyzer"); // RSSI diff --git a/applications/main/subghz/views/subghz_test_carrier.c b/applications/main/subghz/views/subghz_test_carrier.c index 2cbde6e32..3815e8ff0 100644 --- a/applications/main/subghz/views/subghz_test_carrier.c +++ b/applications/main/subghz/views/subghz_test_carrier.c @@ -7,6 +7,8 @@ #include #include +// TODO add external module + struct SubGhzTestCarrier { View* view; FuriTimer* timer; @@ -115,19 +117,14 @@ bool subghz_test_carrier_input(InputEvent* event, void* context) { furi_hal_subghz_set_path(model->path); if(model->status == SubGhzTestCarrierModelStatusRx) { - furi_hal_gpio_init( - furi_hal_subghz.cc1101_g0_pin, GpioModeInput, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow); furi_hal_subghz_rx(); } else { furi_hal_gpio_init( - furi_hal_subghz.cc1101_g0_pin, - GpioModeOutputPushPull, - GpioPullNo, - GpioSpeedLow); - furi_hal_gpio_write(furi_hal_subghz.cc1101_g0_pin, true); + &gpio_cc1101_g0, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_write(&gpio_cc1101_g0, true); if(!furi_hal_subghz_tx()) { - furi_hal_gpio_init( - furi_hal_subghz.cc1101_g0_pin, GpioModeInput, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow); subghz_test_carrier->callback( SubGhzTestCarrierEventOnlyRx, subghz_test_carrier->context); } @@ -145,7 +142,7 @@ void subghz_test_carrier_enter(void* context) { furi_hal_subghz_reset(); furi_hal_subghz_load_preset(FuriHalSubGhzPresetOok650Async); - furi_hal_gpio_init(furi_hal_subghz.cc1101_g0_pin, GpioModeInput, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow); with_view_model( subghz_test_carrier->view, diff --git a/applications/main/subghz/views/subghz_test_static.c b/applications/main/subghz/views/subghz_test_static.c index b9e5a8c9c..815d0ff9b 100644 --- a/applications/main/subghz/views/subghz_test_static.c +++ b/applications/main/subghz/views/subghz_test_static.c @@ -11,6 +11,8 @@ #define TAG "SubGhzTestStatic" +// TODO add external module + typedef enum { SubGhzTestStaticStatusIDLE, SubGhzTestStaticStatusTX, @@ -143,9 +145,8 @@ void subghz_test_static_enter(void* context) { furi_hal_subghz_reset(); furi_hal_subghz_load_preset(FuriHalSubGhzPresetOok650Async); - furi_hal_gpio_init( - furi_hal_subghz.cc1101_g0_pin, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); - furi_hal_gpio_write(furi_hal_subghz.cc1101_g0_pin, false); + furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_write(&gpio_cc1101_g0, false); instance->status_tx = SubGhzTestStaticStatusIDLE; with_view_model( diff --git a/applications/main/subghz_remote/helpers/subrem_presets.c b/applications/main/subghz_remote/helpers/subrem_presets.c index e5823b721..45da793d7 100644 --- a/applications/main/subghz_remote/helpers/subrem_presets.c +++ b/applications/main/subghz_remote/helpers/subrem_presets.c @@ -124,7 +124,9 @@ SubRemLoadSubState subrem_sub_preset_load( if(!strcmp(furi_string_get_cstr(temp_str), "RAW")) { //if RAW subghz_protocol_raw_gen_fff_data( - fff_data, furi_string_get_cstr(sub_preset->file_path)); + fff_data, + furi_string_get_cstr(sub_preset->file_path), + subghz_txrx_radio_device_get_name(txrx)); } else { stream_copy_full( flipper_format_get_raw_stream(fff_data_file), diff --git a/applications/main/subghz_remote/subghz_remote_app.c b/applications/main/subghz_remote/subghz_remote_app.c index 624a602ae..937025b05 100644 --- a/applications/main/subghz_remote/subghz_remote_app.c +++ b/applications/main/subghz_remote/subghz_remote_app.c @@ -29,14 +29,14 @@ SubGhzRemoteApp* subghz_remote_app_alloc() { } furi_record_close(RECORD_STORAGE); - // Enable power for External CC1101 if it is connected - furi_hal_subghz_enable_ext_power(); - // Auto switch to internal radio if external radio is not available - furi_delay_ms(15); - if(!furi_hal_subghz_check_radio()) { - furi_hal_subghz_select_radio_type(SubGhzRadioInternal); - furi_hal_subghz_init_radio_type(SubGhzRadioInternal); - } + // // Enable power for External CC1101 if it is connected + // furi_hal_subghz_enable_ext_power(); + // // Auto switch to internal radio if external radio is not available + // furi_delay_ms(15); + // if(!furi_hal_subghz_check_radio()) { + // furi_hal_subghz_select_radio_type(SubGhzRadioInternal); + // furi_hal_subghz_init_radio_type(SubGhzRadioInternal); + // } furi_hal_power_suppress_charge_enter(); @@ -105,10 +105,10 @@ void subghz_remote_app_free(SubGhzRemoteApp* app) { furi_hal_power_suppress_charge_exit(); - // Disable power for External CC1101 if it was enabled and module is connected - furi_hal_subghz_disable_ext_power(); - // Reinit SPI handles for internal radio / nfc - furi_hal_subghz_init_radio_type(SubGhzRadioInternal); + // // Disable power for External CC1101 if it was enabled and module is connected + // furi_hal_subghz_disable_ext_power(); + // // Reinit SPI handles for internal radio / nfc + // furi_hal_subghz_init_radio_type(SubGhzRadioInternal); // Submenu view_dispatcher_remove_view(app->view_dispatcher, SubRemViewIDSubmenu); From 72712d9f07bede43adb675736fde223ad074390f Mon Sep 17 00:00:00 2001 From: gid9798 <30450294+gid9798@users.noreply.github.com> Date: Mon, 19 Jun 2023 12:51:02 +0300 Subject: [PATCH 005/364] updated TODO descriptions --- .../subghz/helpers/subghz_frequency_analyzer_worker.c | 2 ++ applications/main/subghz/subghz_cli.c | 9 ++++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.c b/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.c index e672542bb..7ba6999fb 100644 --- a/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.c +++ b/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.c @@ -85,6 +85,8 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) { furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); cc1101_flush_rx(&furi_hal_spi_bus_handle_subghz); cc1101_flush_tx(&furi_hal_spi_bus_handle_subghz); + + // TODO probably can be used device.load_preset(FuriHalSubGhzPresetCustom, ...) for external cc1101 cc1101_write_reg(&furi_hal_spi_bus_handle_subghz, CC1101_IOCFG0, CC1101IocfgHW); cc1101_write_reg(&furi_hal_spi_bus_handle_subghz, CC1101_MDMCFG3, 0b01111111); // symbol rate diff --git a/applications/main/subghz/subghz_cli.c b/applications/main/subghz/subghz_cli.c index 403d4bcd7..fb7453f06 100644 --- a/applications/main/subghz/subghz_cli.c +++ b/applications/main/subghz/subghz_cli.c @@ -22,6 +22,11 @@ #define SUBGHZ_FREQUENCY_RANGE_STR \ "299999755...348000000 or 386999938...464000000 or 778999847...928000000" +// Tx/Rx Carrier | only internal module +// Tx/Rx command | both +// Rx RAW | only internal module +// Chat | both + static void subghz_cli_radio_device_power_on() { uint8_t attempts = 0; while(!furi_hal_power_is_otg_enabled() && attempts++ < 5) { @@ -57,7 +62,7 @@ void subghz_cli_command_tx_carrier(Cli* cli, FuriString* args, void* context) { furi_hal_subghz_reset(); furi_hal_subghz_load_preset(FuriHalSubGhzPresetOok650Async); frequency = furi_hal_subghz_set_frequency_and_path(frequency); - // TODO external device + furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); furi_hal_gpio_write(&gpio_cc1101_g0, true); @@ -855,8 +860,6 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) { static void subghz_cli_command(Cli* cli, FuriString* args, void* context) { FuriString* cmd = furi_string_alloc(); - // TODO external - do { if(!args_read_string_and_trim(args, cmd)) { subghz_cli_command_print_usage(); From ab12c8c33963d87e8cbd2b4ce46c850e463b5acc Mon Sep 17 00:00:00 2001 From: gid9798 <30450294+gid9798@users.noreply.github.com> Date: Mon, 19 Jun 2023 13:26:01 +0300 Subject: [PATCH 006/364] SubRem configurator update --- .../helpers/subrem_presets.c | 5 +- .../helpers/txrx/subghz_txrx.c | 179 ++++++++++++++---- .../helpers/txrx/subghz_txrx.h | 55 ++++++ .../helpers/txrx/subghz_txrx_i.h | 6 +- .../subghz_remote_app.c | 22 +-- 5 files changed, 205 insertions(+), 62 deletions(-) diff --git a/applications/external/subghz_remote_configurator/helpers/subrem_presets.c b/applications/external/subghz_remote_configurator/helpers/subrem_presets.c index e5823b721..d491af2f7 100644 --- a/applications/external/subghz_remote_configurator/helpers/subrem_presets.c +++ b/applications/external/subghz_remote_configurator/helpers/subrem_presets.c @@ -85,6 +85,7 @@ SubRemLoadSubState subrem_sub_preset_load( FURI_LOG_W(TAG, "Cannot read frequency. Set default frequency"); sub_preset->freq_preset.frequency = subghz_setting_get_default_frequency(setting); } else if(!furi_hal_subghz_is_tx_allowed(temp_data32)) { + // TODO FURI_LOG_E(TAG, "This frequency can only be used for RX"); break; } @@ -124,7 +125,9 @@ SubRemLoadSubState subrem_sub_preset_load( if(!strcmp(furi_string_get_cstr(temp_str), "RAW")) { //if RAW subghz_protocol_raw_gen_fff_data( - fff_data, furi_string_get_cstr(sub_preset->file_path)); + fff_data, + furi_string_get_cstr(sub_preset->file_path), + subghz_txrx_radio_device_get_name(txrx)); } else { stream_copy_full( flipper_format_get_raw_stream(fff_data_file), diff --git a/applications/external/subghz_remote_configurator/helpers/txrx/subghz_txrx.c b/applications/external/subghz_remote_configurator/helpers/txrx/subghz_txrx.c index c1f519ba0..581c6db5c 100644 --- a/applications/external/subghz_remote_configurator/helpers/txrx/subghz_txrx.c +++ b/applications/external/subghz_remote_configurator/helpers/txrx/subghz_txrx.c @@ -1,8 +1,28 @@ #include "subghz_txrx_i.h" + #include +#include +#include + +#include #define TAG "SubGhz" +static void subghz_txrx_radio_device_power_on(SubGhzTxRx* instance) { + UNUSED(instance); + uint8_t attempts = 0; + while(!furi_hal_power_is_otg_enabled() && attempts++ < 5) { + furi_hal_power_enable_otg(); + //CC1101 power-up time + furi_delay_ms(10); + } +} + +static void subghz_txrx_radio_device_power_off(SubGhzTxRx* instance) { + UNUSED(instance); + if(furi_hal_power_is_otg_enabled()) furi_hal_power_disable_otg(); +} + SubGhzTxRx* subghz_txrx_alloc() { SubGhzTxRx* instance = malloc(sizeof(SubGhzTxRx)); instance->setting = subghz_setting_alloc(); @@ -23,16 +43,15 @@ SubGhzTxRx* subghz_txrx_alloc() { instance->fff_data = flipper_format_string_alloc(); instance->environment = subghz_environment_alloc(); - instance->is_database_loaded = subghz_environment_load_keystore( - instance->environment, EXT_PATH("subghz/assets/keeloq_mfcodes")); - subghz_environment_load_keystore( - instance->environment, EXT_PATH("subghz/assets/keeloq_mfcodes_user")); + instance->is_database_loaded = + subghz_environment_load_keystore(instance->environment, SUBGHZ_KEYSTORE_DIR_NAME); + subghz_environment_load_keystore(instance->environment, SUBGHZ_KEYSTORE_DIR_USER_NAME); subghz_environment_set_came_atomo_rainbow_table_file_name( - instance->environment, EXT_PATH("subghz/assets/came_atomo")); + instance->environment, SUBGHZ_CAME_ATOMO_DIR_NAME); subghz_environment_set_alutech_at_4n_rainbow_table_file_name( - instance->environment, EXT_PATH("subghz/assets/alutech_at_4n")); + instance->environment, SUBGHZ_ALUTECH_AT_4N_DIR_NAME); subghz_environment_set_nice_flor_s_rainbow_table_file_name( - instance->environment, EXT_PATH("subghz/assets/nice_flor_s")); + instance->environment, SUBGHZ_NICE_FLOR_S_DIR_NAME); subghz_environment_set_protocol_registry( instance->environment, (void*)&subghz_protocol_registry); instance->receiver = subghz_receiver_alloc_init(instance->environment); @@ -43,18 +62,31 @@ SubGhzTxRx* subghz_txrx_alloc() { instance->worker, (SubGhzWorkerPairCallback)subghz_receiver_decode); subghz_worker_set_context(instance->worker, instance->receiver); + //set default device External + subghz_devices_init(); + instance->radio_device_type = + subghz_txrx_radio_device_set(instance, SubGhzRadioDeviceTypeExternalCC1101); + return instance; } void subghz_txrx_free(SubGhzTxRx* instance) { furi_assert(instance); + if(instance->radio_device_type != SubGhzRadioDeviceTypeInternal) { + subghz_txrx_radio_device_power_off(instance); + subghz_devices_end(instance->radio_device); + } + + subghz_devices_deinit(); + subghz_worker_free(instance->worker); subghz_receiver_free(instance->receiver); subghz_environment_free(instance->environment); flipper_format_free(instance->fff_data); furi_string_free(instance->preset->name); subghz_setting_free(instance->setting); + free(instance->preset); free(instance); } @@ -127,29 +159,29 @@ void subghz_txrx_get_frequency_and_modulation( static void subghz_txrx_begin(SubGhzTxRx* instance, uint8_t* preset_data) { furi_assert(instance); - furi_hal_subghz_reset(); - furi_hal_subghz_idle(); - furi_hal_subghz_load_custom_preset(preset_data); - furi_hal_gpio_init(furi_hal_subghz.cc1101_g0_pin, GpioModeInput, GpioPullNo, GpioSpeedLow); + subghz_devices_reset(instance->radio_device); + subghz_devices_idle(instance->radio_device); + subghz_devices_load_preset(instance->radio_device, FuriHalSubGhzPresetCustom, preset_data); instance->txrx_state = SubGhzTxRxStateIDLE; } static uint32_t subghz_txrx_rx(SubGhzTxRx* instance, uint32_t frequency) { furi_assert(instance); + // TODO if(!furi_hal_subghz_is_frequency_valid(frequency)) { furi_crash("SubGhz: Incorrect RX frequency."); } furi_assert( instance->txrx_state != SubGhzTxRxStateRx && instance->txrx_state != SubGhzTxRxStateSleep); - furi_hal_subghz_idle(); - uint32_t value = furi_hal_subghz_set_frequency_and_path(frequency); - furi_hal_gpio_init(furi_hal_subghz.cc1101_g0_pin, GpioModeInput, GpioPullNo, GpioSpeedLow); - furi_hal_subghz_flush_rx(); - subghz_txrx_speaker_on(instance); - furi_hal_subghz_rx(); + subghz_devices_idle(instance->radio_device); - furi_hal_subghz_start_async_rx(subghz_worker_rx_callback, instance->worker); + uint32_t value = subghz_devices_set_frequency(instance->radio_device, frequency); + subghz_devices_flush_rx(instance->radio_device); + subghz_txrx_speaker_on(instance); + + subghz_devices_start_async_rx( + instance->radio_device, subghz_worker_rx_callback, instance->worker); subghz_worker_start(instance->worker); instance->txrx_state = SubGhzTxRxStateRx; return value; @@ -158,7 +190,7 @@ static uint32_t subghz_txrx_rx(SubGhzTxRx* instance, uint32_t frequency) { static void subghz_txrx_idle(SubGhzTxRx* instance) { furi_assert(instance); furi_assert(instance->txrx_state != SubGhzTxRxStateSleep); - furi_hal_subghz_idle(); + subghz_devices_idle(instance->radio_device); subghz_txrx_speaker_off(instance); instance->txrx_state = SubGhzTxRxStateIDLE; } @@ -169,31 +201,30 @@ static void subghz_txrx_rx_end(SubGhzTxRx* instance) { if(subghz_worker_is_running(instance->worker)) { subghz_worker_stop(instance->worker); - furi_hal_subghz_stop_async_rx(); + subghz_devices_stop_async_rx(instance->radio_device); } - furi_hal_subghz_idle(); + subghz_devices_idle(instance->radio_device); subghz_txrx_speaker_off(instance); instance->txrx_state = SubGhzTxRxStateIDLE; } void subghz_txrx_sleep(SubGhzTxRx* instance) { furi_assert(instance); - furi_hal_subghz_sleep(); + subghz_devices_sleep(instance->radio_device); instance->txrx_state = SubGhzTxRxStateSleep; } static bool subghz_txrx_tx(SubGhzTxRx* instance, uint32_t frequency) { furi_assert(instance); + // TODO if(!furi_hal_subghz_is_frequency_valid(frequency)) { furi_crash("SubGhz: Incorrect TX frequency."); } furi_assert(instance->txrx_state != SubGhzTxRxStateSleep); - furi_hal_subghz_idle(); - furi_hal_subghz_set_frequency_and_path(frequency); - furi_hal_gpio_write(furi_hal_subghz.cc1101_g0_pin, false); - furi_hal_gpio_init( - furi_hal_subghz.cc1101_g0_pin, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); - bool ret = furi_hal_subghz_tx(); + subghz_devices_idle(instance->radio_device); + subghz_devices_set_frequency(instance->radio_device, frequency); + + bool ret = subghz_devices_set_tx(instance->radio_device); if(ret) { subghz_txrx_speaker_on(instance); instance->txrx_state = SubGhzTxRxStateTx; @@ -255,8 +286,8 @@ SubGhzTxRxStartTxState subghz_txrx_tx_start(SubGhzTxRx* instance, FlipperFormat* if(ret == SubGhzTxRxStartTxStateOk) { //Start TX - furi_hal_subghz_start_async_tx( - subghz_transmitter_yield, instance->transmitter); + subghz_devices_start_async_tx( + instance->radio_device, subghz_transmitter_yield, instance->transmitter); } } else { ret = SubGhzTxRxStartTxStateErrorParserOthers; @@ -299,7 +330,7 @@ static void subghz_txrx_tx_stop(SubGhzTxRx* instance) { furi_assert(instance); furi_assert(instance->txrx_state == SubGhzTxRxStateTx); //Stop TX - furi_hal_subghz_stop_async_tx(); + subghz_devices_stop_async_tx(instance->radio_device); subghz_transmitter_stop(instance->transmitter); subghz_transmitter_free(instance->transmitter); @@ -312,7 +343,6 @@ static void subghz_txrx_tx_stop(SubGhzTxRx* instance) { subghz_txrx_idle(instance); subghz_txrx_speaker_off(instance); //Todo: Show message - // notification_message(notifications, &sequence_reset_red); } FlipperFormat* subghz_txrx_get_fff_data(SubGhzTxRx* instance) { @@ -362,7 +392,7 @@ void subghz_txrx_hopper_update(SubGhzTxRx* instance) { float rssi = -127.0f; if(instance->hopper_state != SubGhzHopperStateRSSITimeOut) { // See RSSI Calculation timings in CC1101 17.3 RSSI - rssi = furi_hal_subghz_get_rssi(); + rssi = subghz_devices_get_rssi(instance->radio_device); // Stay if RSSI is high enough if(rssi > -90.0f) { @@ -419,13 +449,13 @@ void subghz_txrx_hopper_pause(SubGhzTxRx* instance) { void subghz_txrx_speaker_on(SubGhzTxRx* instance) { furi_assert(instance); if(instance->debug_pin_state) { - furi_hal_subghz_set_async_mirror_pin(&gpio_ibutton); + subghz_devices_set_async_mirror_pin(instance->radio_device, &gpio_ibutton); } if(instance->speaker_state == SubGhzSpeakerStateEnable) { if(furi_hal_speaker_acquire(30)) { if(!instance->debug_pin_state) { - furi_hal_subghz_set_async_mirror_pin(&gpio_speaker); + subghz_devices_set_async_mirror_pin(instance->radio_device, &gpio_speaker); } } else { instance->speaker_state = SubGhzSpeakerStateDisable; @@ -436,12 +466,12 @@ void subghz_txrx_speaker_on(SubGhzTxRx* instance) { void subghz_txrx_speaker_off(SubGhzTxRx* instance) { furi_assert(instance); if(instance->debug_pin_state) { - furi_hal_subghz_set_async_mirror_pin(NULL); + subghz_devices_set_async_mirror_pin(instance->radio_device, NULL); } if(instance->speaker_state != SubGhzSpeakerStateDisable) { if(furi_hal_speaker_is_mine()) { if(!instance->debug_pin_state) { - furi_hal_subghz_set_async_mirror_pin(NULL); + subghz_devices_set_async_mirror_pin(instance->radio_device, NULL); } furi_hal_speaker_release(); if(instance->speaker_state == SubGhzSpeakerStateShutdown) @@ -453,12 +483,12 @@ void subghz_txrx_speaker_off(SubGhzTxRx* instance) { void subghz_txrx_speaker_mute(SubGhzTxRx* instance) { furi_assert(instance); if(instance->debug_pin_state) { - furi_hal_subghz_set_async_mirror_pin(NULL); + subghz_devices_set_async_mirror_pin(instance->radio_device, NULL); } if(instance->speaker_state == SubGhzSpeakerStateEnable) { if(furi_hal_speaker_is_mine()) { if(!instance->debug_pin_state) { - furi_hal_subghz_set_async_mirror_pin(NULL); + subghz_devices_set_async_mirror_pin(instance->radio_device, NULL); } } } @@ -467,12 +497,12 @@ void subghz_txrx_speaker_mute(SubGhzTxRx* instance) { void subghz_txrx_speaker_unmute(SubGhzTxRx* instance) { furi_assert(instance); if(instance->debug_pin_state) { - furi_hal_subghz_set_async_mirror_pin(&gpio_ibutton); + subghz_devices_set_async_mirror_pin(instance->radio_device, &gpio_ibutton); } if(instance->speaker_state == SubGhzSpeakerStateEnable) { if(furi_hal_speaker_is_mine()) { if(!instance->debug_pin_state) { - furi_hal_subghz_set_async_mirror_pin(&gpio_speaker); + subghz_devices_set_async_mirror_pin(instance->radio_device, &gpio_speaker); } } } @@ -547,6 +577,66 @@ void subghz_txrx_set_raw_file_encoder_worker_callback_end( context); } +bool subghz_txrx_radio_device_is_connect_external(SubGhzTxRx* instance, const char* name) { + furi_assert(instance); + + bool is_connect = false; + bool is_otg_enabled = furi_hal_power_is_otg_enabled(); + + if(!is_otg_enabled) { + subghz_txrx_radio_device_power_on(instance); + } + + is_connect = subghz_devices_is_connect(subghz_devices_get_by_name(name)); + + if(!is_otg_enabled) { + subghz_txrx_radio_device_power_off(instance); + } + return is_connect; +} + +SubGhzRadioDeviceType + subghz_txrx_radio_device_set(SubGhzTxRx* instance, SubGhzRadioDeviceType radio_device_type) { + furi_assert(instance); + + if(radio_device_type == SubGhzRadioDeviceTypeExternalCC1101 && + subghz_txrx_radio_device_is_connect_external(instance, SUBGHZ_DEVICE_CC1101_EXT_NAME)) { + subghz_txrx_radio_device_power_on(instance); + instance->radio_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_EXT_NAME); + subghz_devices_begin(instance->radio_device); + instance->radio_device_type = SubGhzRadioDeviceTypeExternalCC1101; + } else { + subghz_txrx_radio_device_power_off(instance); + if(instance->radio_device_type != SubGhzRadioDeviceTypeInternal) { + subghz_devices_end(instance->radio_device); + } + instance->radio_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME); + instance->radio_device_type = SubGhzRadioDeviceTypeInternal; + } + + return instance->radio_device_type; +} + +SubGhzRadioDeviceType subghz_txrx_radio_device_get(SubGhzTxRx* instance) { + furi_assert(instance); + return instance->radio_device_type; +} + +float subghz_txrx_radio_device_get_rssi(SubGhzTxRx* instance) { + furi_assert(instance); + return subghz_devices_get_rssi(instance->radio_device); +} + +const char* subghz_txrx_radio_device_get_name(SubGhzTxRx* instance) { + furi_assert(instance); + return subghz_devices_get_name(instance->radio_device); +} + +bool subghz_txrx_radio_device_is_frequecy_valid(SubGhzTxRx* instance, uint32_t frequency) { + furi_assert(instance); + return subghz_devices_is_frequency_valid(instance->radio_device, frequency); +} + void subghz_txrx_set_debug_pin_state(SubGhzTxRx* instance, bool state) { furi_assert(instance); instance->debug_pin_state = state; @@ -557,6 +647,13 @@ bool subghz_txrx_get_debug_pin_state(SubGhzTxRx* instance) { return instance->debug_pin_state; } +void subghz_txrx_reset_dynamic_and_custom_btns(SubGhzTxRx* instance) { + furi_assert(instance); + subghz_environment_reset_keeloq(instance->environment); + + subghz_custom_btns_reset(); +} + SubGhzReceiver* subghz_txrx_get_receiver(SubGhzTxRx* instance) { furi_assert(instance); return instance->receiver; diff --git a/applications/external/subghz_remote_configurator/helpers/txrx/subghz_txrx.h b/applications/external/subghz_remote_configurator/helpers/txrx/subghz_txrx.h index b2ebcc5f3..46f09a665 100644 --- a/applications/external/subghz_remote_configurator/helpers/txrx/subghz_txrx.h +++ b/applications/external/subghz_remote_configurator/helpers/txrx/subghz_txrx.h @@ -5,6 +5,7 @@ #include #include #include +#include typedef struct SubGhzTxRx SubGhzTxRx; @@ -40,6 +41,13 @@ typedef enum { SubGhzSpeakerStateEnable, } SubGhzSpeakerState; +/** SubGhzRadioDeviceType */ +typedef enum { + SubGhzRadioDeviceTypeAuto, + SubGhzRadioDeviceTypeInternal, + SubGhzRadioDeviceTypeExternalCC1101, +} SubGhzRadioDeviceType; + /** * Allocate SubGhzTxRx * @@ -312,7 +320,54 @@ void subghz_txrx_set_raw_file_encoder_worker_callback_end( SubGhzProtocolEncoderRAWCallbackEnd callback, void* context); +/* Checking if an external radio device is connected +* +* @param instance Pointer to a SubGhzTxRx +* @param name Name of external radio device +* @return bool True if is connected to the external radio device +*/ +bool subghz_txrx_radio_device_is_connect_external(SubGhzTxRx* instance, const char* name); + +/* Set the selected radio device to use +* +* @param instance Pointer to a SubGhzTxRx +* @param radio_device_type Radio device type +* @return SubGhzRadioDeviceType Type of installed radio device +*/ +SubGhzRadioDeviceType + subghz_txrx_radio_device_set(SubGhzTxRx* instance, SubGhzRadioDeviceType radio_device_type); + +/* Get the selected radio device to use +* +* @param instance Pointer to a SubGhzTxRx +* @return SubGhzRadioDeviceType Type of installed radio device +*/ +SubGhzRadioDeviceType subghz_txrx_radio_device_get(SubGhzTxRx* instance); + +/* Get RSSI the selected radio device to use +* +* @param instance Pointer to a SubGhzTxRx +* @return float RSSI +*/ +float subghz_txrx_radio_device_get_rssi(SubGhzTxRx* instance); + +/* Get name the selected radio device to use +* +* @param instance Pointer to a SubGhzTxRx +* @return const char* Name of installed radio device +*/ +const char* subghz_txrx_radio_device_get_name(SubGhzTxRx* instance); + +/* Get get intelligence whether frequency the selected radio device to use +* +* @param instance Pointer to a SubGhzTxRx +* @return bool True if the frequency is valid +*/ +bool subghz_txrx_radio_device_is_frequecy_valid(SubGhzTxRx* instance, uint32_t frequency); + void subghz_txrx_set_debug_pin_state(SubGhzTxRx* instance, bool state); bool subghz_txrx_get_debug_pin_state(SubGhzTxRx* instance); +void subghz_txrx_reset_dynamic_and_custom_btns(SubGhzTxRx* instance); + SubGhzReceiver* subghz_txrx_get_receiver(SubGhzTxRx* instance); // TODO use only in DecodeRaw diff --git a/applications/external/subghz_remote_configurator/helpers/txrx/subghz_txrx_i.h b/applications/external/subghz_remote_configurator/helpers/txrx/subghz_txrx_i.h index 680d27158..f058c2282 100644 --- a/applications/external/subghz_remote_configurator/helpers/txrx/subghz_txrx_i.h +++ b/applications/external/subghz_remote_configurator/helpers/txrx/subghz_txrx_i.h @@ -1,5 +1,5 @@ - #pragma once + #include "subghz_txrx.h" struct SubGhzTxRx { @@ -21,9 +21,11 @@ struct SubGhzTxRx { SubGhzTxRxState txrx_state; SubGhzSpeakerState speaker_state; + const SubGhzDevice* radio_device; + SubGhzRadioDeviceType radio_device_type; SubGhzTxRxNeedSaveCallback need_save_callback; void* need_save_context; bool debug_pin_state; -}; \ No newline at end of file +}; diff --git a/applications/external/subghz_remote_configurator/subghz_remote_app.c b/applications/external/subghz_remote_configurator/subghz_remote_app.c index 84100a233..ba71b134b 100644 --- a/applications/external/subghz_remote_configurator/subghz_remote_app.c +++ b/applications/external/subghz_remote_configurator/subghz_remote_app.c @@ -28,18 +28,9 @@ SubGhzRemoteApp* subghz_remote_app_alloc() { //FURI_LOG_E(TAG, "Could not create folder %s", SUBREM_APP_FOLDER); } furi_record_close(RECORD_STORAGE); - /* - // Enable power for External CC1101 if it is connected - furi_hal_subghz_enable_ext_power(); - // Auto switch to internal radio if external radio is not available - furi_delay_ms(15); - if(!furi_hal_subghz_check_radio()) { - furi_hal_subghz_select_radio_type(SubGhzRadioInternal); - furi_hal_subghz_init_radio_type(SubGhzRadioInternal); - } - furi_hal_power_suppress_charge_enter(); -*/ + // furi_hal_power_suppress_charge_enter(); + app->file_path = furi_string_alloc(); furi_string_set(app->file_path, SUBREM_APP_FOLDER); @@ -125,14 +116,9 @@ SubGhzRemoteApp* subghz_remote_app_alloc() { void subghz_remote_app_free(SubGhzRemoteApp* app) { furi_assert(app); - /* - furi_hal_power_suppress_charge_exit(); - // Disable power for External CC1101 if it was enabled and module is connected - furi_hal_subghz_disable_ext_power(); - // Reinit SPI handles for internal radio / nfc - furi_hal_subghz_init_radio_type(SubGhzRadioInternal); -*/ + // furi_hal_power_suppress_charge_exit(); + // Submenu view_dispatcher_remove_view(app->view_dispatcher, SubRemViewIDSubmenu); submenu_free(app->submenu); From a519a242d65c941ca87e0fe03b6fdb794a733627 Mon Sep 17 00:00:00 2001 From: gid9798 <30450294+gid9798@users.noreply.github.com> Date: Mon, 19 Jun 2023 13:37:08 +0300 Subject: [PATCH 007/364] SubRem Config internal module by default --- .../subghz_remote_configurator/helpers/txrx/subghz_txrx.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/applications/external/subghz_remote_configurator/helpers/txrx/subghz_txrx.c b/applications/external/subghz_remote_configurator/helpers/txrx/subghz_txrx.c index 581c6db5c..e4309dcad 100644 --- a/applications/external/subghz_remote_configurator/helpers/txrx/subghz_txrx.c +++ b/applications/external/subghz_remote_configurator/helpers/txrx/subghz_txrx.c @@ -62,10 +62,10 @@ SubGhzTxRx* subghz_txrx_alloc() { instance->worker, (SubGhzWorkerPairCallback)subghz_receiver_decode); subghz_worker_set_context(instance->worker, instance->receiver); - //set default device External + //set default device Internal subghz_devices_init(); instance->radio_device_type = - subghz_txrx_radio_device_set(instance, SubGhzRadioDeviceTypeExternalCC1101); + subghz_txrx_radio_device_set(instance, SubGhzRadioDeviceTypeInternal); return instance; } From f9472effe38abe37f8e731cb648897d99226fefe Mon Sep 17 00:00:00 2001 From: gid9798 <30450294+gid9798@users.noreply.github.com> Date: Tue, 20 Jun 2023 11:02:14 +0300 Subject: [PATCH 008/364] Now really block transmission at dangerous freq --- .../drivers/subghz/cc1101_ext/cc1101_ext.c | 27 +++++++++++++++---- .../targets/f7/furi_hal/furi_hal_subghz.c | 12 ++++----- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/applications/drivers/subghz/cc1101_ext/cc1101_ext.c b/applications/drivers/subghz/cc1101_ext/cc1101_ext.c index b4e7e9eee..acce0989d 100644 --- a/applications/drivers/subghz/cc1101_ext/cc1101_ext.c +++ b/applications/drivers/subghz/cc1101_ext/cc1101_ext.c @@ -1,7 +1,6 @@ #include "cc1101_ext.h" #include -#include #include #include #include @@ -19,6 +18,7 @@ #define TAG "SubGhz_Device_CC1101_Ext" #define SUBGHZ_DEVICE_CC1101_EXT_TX_GPIO &gpio_ext_pb2 +#define SUBGHZ_DEVICE_CC1101_EXT_DANGEROUS_RANGE false /* DMA Channels definition */ #define SUBGHZ_DEVICE_CC1101_EXT_DMA DMA2 @@ -428,9 +428,26 @@ uint8_t subghz_device_cc1101_ext_get_lqi() { } bool subghz_device_cc1101_ext_is_frequency_valid(uint32_t value) { - if(!(value >= 299999755 && value <= 348000335) && - !(value >= 386999938 && value <= 464000000) && + if(!(value >= 281000000 && value <= 361000000) && + !(value >= 378000000 && value <= 481000000) && + !(value >= 749000000 && value <= 962000000)) { + return false; + } + + return true; +} + +bool subghz_device_cc1101_ext_is_tx_allowed(uint32_t value) { + if(!(SUBGHZ_DEVICE_CC1101_EXT_DANGEROUS_RANGE) && + !(value >= 299999755 && value <= 350000335) && // was increased from 348 to 350 + !(value >= 386999938 && value <= 467750000) && // was increased from 464 to 467.75 !(value >= 778999847 && value <= 928000000)) { + FURI_LOG_I(TAG, "Frequency blocked - outside default range"); + return false; + } else if( + (SUBGHZ_DEVICE_CC1101_EXT_DANGEROUS_RANGE) && + !subghz_device_cc1101_ext_is_frequency_valid(value)) { + FURI_LOG_I(TAG, "Frequency blocked - outside dangerous range"); return false; } @@ -438,10 +455,10 @@ bool subghz_device_cc1101_ext_is_frequency_valid(uint32_t value) { } uint32_t subghz_device_cc1101_ext_set_frequency(uint32_t value) { - if(furi_hal_region_is_frequency_allowed(value)) { + if(subghz_device_cc1101_ext_is_tx_allowed(value)) { subghz_device_cc1101_ext->regulation = SubGhzDeviceCC1101ExtRegulationTxRx; } else { - subghz_device_cc1101_ext->regulation = SubGhzDeviceCC1101ExtRegulationOnlyRx; + subghz_device_cc1101_ext->regulation = SubGhzDeviceCC1101ExtRegulationTxRx; } furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle); diff --git a/firmware/targets/f7/furi_hal/furi_hal_subghz.c b/firmware/targets/f7/furi_hal/furi_hal_subghz.c index d6b08d7b7..dc26cffb5 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_subghz.c +++ b/firmware/targets/f7/furi_hal/furi_hal_subghz.c @@ -10,8 +10,6 @@ #include -#include // TODO - #include #include #include @@ -390,9 +388,7 @@ bool furi_hal_subghz_is_tx_allowed(uint32_t value) { return false; } else if( (allow_extended_for_int) && // - !(value >= 281000000 && value <= 361000000) && - !(value >= 378000000 && value <= 481000000) && - !(value >= 749000000 && value <= 962000000)) { + !furi_hal_subghz_is_frequency_valid(value)) { FURI_LOG_I(TAG, "Frequency blocked - outside dangerous range"); return false; } @@ -401,7 +397,11 @@ bool furi_hal_subghz_is_tx_allowed(uint32_t value) { } uint32_t furi_hal_subghz_set_frequency(uint32_t value) { - furi_hal_subghz.regulation = SubGhzRegulationTxRx; + if(furi_hal_subghz_is_tx_allowed(value)) { + furi_hal_subghz.regulation = SubGhzRegulationTxRx; + } else { + furi_hal_subghz.regulation = SubGhzRegulationOnlyRx; + } furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); uint32_t real_frequency = cc1101_set_frequency(&furi_hal_spi_bus_handle_subghz, value); From e2e9e53b6a6e78ad3037e33c4f531a0e82883bb0 Mon Sep 17 00:00:00 2001 From: gid9798 <30450294+gid9798@users.noreply.github.com> Date: Tue, 20 Jun 2023 12:38:50 +0300 Subject: [PATCH 009/364] Sub playlist app: new external and some fixes --- .../playlist/helpers/radio_device_loader.c | 61 ++++++++++++++++++ .../playlist/helpers/radio_device_loader.h | 15 +++++ applications/external/playlist/playlist.c | 64 +++++++++++-------- 3 files changed, 115 insertions(+), 25 deletions(-) create mode 100644 applications/external/playlist/helpers/radio_device_loader.c create mode 100644 applications/external/playlist/helpers/radio_device_loader.h diff --git a/applications/external/playlist/helpers/radio_device_loader.c b/applications/external/playlist/helpers/radio_device_loader.c new file mode 100644 index 000000000..cdf34bd38 --- /dev/null +++ b/applications/external/playlist/helpers/radio_device_loader.c @@ -0,0 +1,61 @@ +#include "radio_device_loader.h" + +#include +#include + +static void radio_device_loader_power_on() { + uint8_t attempts = 0; + while(!furi_hal_power_is_otg_enabled() && attempts++ < 5) { + furi_hal_power_enable_otg(); + //CC1101 power-up time + furi_delay_ms(10); + } +} + +static void radio_device_loader_power_off() { + if(furi_hal_power_is_otg_enabled()) furi_hal_power_disable_otg(); +} + +bool radio_device_loader_is_connect_external(const char* name) { + bool is_connect = false; + bool is_otg_enabled = furi_hal_power_is_otg_enabled(); + + if(!is_otg_enabled) { + radio_device_loader_power_on(); + } + + is_connect = subghz_devices_is_connect(subghz_devices_get_by_name(name)); + + if(!is_otg_enabled) { + radio_device_loader_power_off(); + } + return is_connect; +} + +const SubGhzDevice* radio_device_loader_set( + const SubGhzDevice* current_radio_device, + SubGhzRadioDeviceType radio_device_type) { + const SubGhzDevice* radio_device; + + if(radio_device_type == SubGhzRadioDeviceTypeExternalCC1101 && + radio_device_loader_is_connect_external(SUBGHZ_DEVICE_CC1101_EXT_NAME)) { + radio_device_loader_power_on(); + radio_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_EXT_NAME); + subghz_devices_begin(radio_device); + } else if(current_radio_device == NULL) { + radio_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME); + } else { + radio_device_loader_end(current_radio_device); + radio_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME); + } + + return radio_device; +} + +void radio_device_loader_end(const SubGhzDevice* radio_device) { + furi_assert(radio_device); + radio_device_loader_power_off(); + if(radio_device != subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME)) { + subghz_devices_end(radio_device); + } +} \ No newline at end of file diff --git a/applications/external/playlist/helpers/radio_device_loader.h b/applications/external/playlist/helpers/radio_device_loader.h new file mode 100644 index 000000000..bee4e2c36 --- /dev/null +++ b/applications/external/playlist/helpers/radio_device_loader.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +/** SubGhzRadioDeviceType */ +typedef enum { + SubGhzRadioDeviceTypeInternal, + SubGhzRadioDeviceTypeExternalCC1101, +} SubGhzRadioDeviceType; + +const SubGhzDevice* radio_device_loader_set( + const SubGhzDevice* current_radio_device, + SubGhzRadioDeviceType radio_device_type); + +void radio_device_loader_end(const SubGhzDevice* radio_device); \ No newline at end of file diff --git a/applications/external/playlist/playlist.c b/applications/external/playlist/playlist.c index 5bf376ccd..ba35cfe42 100644 --- a/applications/external/playlist/playlist.c +++ b/applications/external/playlist/playlist.c @@ -10,7 +10,8 @@ #include #include -#include + +#include "helpers/radio_device_loader.h" #include "flipper_format_stream.h" #include "flipper_format_stream_i.h" @@ -58,6 +59,7 @@ typedef struct { DisplayMeta* meta; FuriString* file_path; // path to the playlist file + const SubGhzDevice* radio_device; bool ctl_request_exit; // can be set to true if the worker should exit bool ctl_pause; // can be set to true if the worker should pause @@ -135,7 +137,9 @@ static int playlist_worker_process( FURI_LOG_W(TAG, " (TX) Missing Frequency, defaulting to 433.92MHz"); frequency = 433920000; } - if(!furi_hal_subghz_is_tx_allowed(frequency)) { + if(!subghz_devices_is_frequency_valid(worker->radio_device, frequency)) { + FURI_LOG_E( + TAG, " (TX) The SubGhz device used does not support the frequency %lu", frequency); return -2; } @@ -152,12 +156,13 @@ static int playlist_worker_process( } if(!furi_string_cmp_str(protocol, "RAW")) { - subghz_protocol_raw_gen_fff_data(fff_data, path); + subghz_protocol_raw_gen_fff_data( + fff_data, path, subghz_devices_get_name(worker->radio_device)); } else { stream_copy_full( flipper_format_get_raw_stream(fff_file), flipper_format_get_raw_stream(fff_data)); } - flipper_format_free(fff_file); + flipper_format_file_close(fff_file); // (try to) send file SubGhzEnvironment* environment = subghz_environment_alloc(); @@ -167,16 +172,23 @@ static int playlist_worker_process( subghz_transmitter_deserialize(transmitter, fff_data); - furi_hal_subghz_reset(); - furi_hal_subghz_load_preset(str_to_preset(preset)); - - frequency = furi_hal_subghz_set_frequency_and_path(frequency); + subghz_devices_load_preset(worker->radio_device, str_to_preset(preset), NULL); + // there is no check for a custom preset + frequency = subghz_devices_set_frequency(worker->radio_device, frequency); + // Set device to TX and check frequency is alowed to TX + if(!subghz_devices_set_tx(worker->radio_device)) { + FURI_LOG_E( + TAG, + " (TX) The SubGhz device used does not support the frequency for transmitеing, %lu", + frequency); + return -5; + } FURI_LOG_D(TAG, " (TX) Start sending ..."); int status = 0; - furi_hal_subghz_start_async_tx(subghz_transmitter_yield, transmitter); - while(!furi_hal_subghz_is_async_tx_complete()) { + subghz_devices_start_async_tx(worker->radio_device, subghz_transmitter_yield, transmitter); + while(!subghz_devices_is_async_complete_tx(worker->radio_device)) { if(worker->ctl_request_exit) { FURI_LOG_D(TAG, " (TX) Requested to exit. Cancelling sending..."); status = 2; @@ -204,8 +216,8 @@ static int playlist_worker_process( FURI_LOG_D(TAG, " (TX) Done sending."); - furi_hal_subghz_stop_async_tx(); - furi_hal_subghz_sleep(); + subghz_devices_stop_async_tx(worker->radio_device); + subghz_devices_idle(worker->radio_device); subghz_transmitter_free(transmitter); @@ -287,6 +299,7 @@ static bool playlist_worker_play_playlist_once( // if there was an error, fff_file is not already freed if(status < 0) { + flipper_format_file_close(fff_file); flipper_format_free(fff_file); } @@ -437,6 +450,14 @@ PlaylistWorker* playlist_worker_alloc(DisplayMeta* meta) { instance->file_path = furi_string_alloc(); + subghz_devices_init(); + + instance->radio_device = + radio_device_loader_set(instance->radio_device, SubGhzRadioDeviceTypeExternalCC1101); + + subghz_devices_reset(instance->radio_device); + subghz_devices_idle(instance->radio_device); + return instance; } @@ -444,6 +465,12 @@ void playlist_worker_free(PlaylistWorker* instance) { furi_assert(instance); furi_thread_free(instance->thread); furi_string_free(instance->file_path); + + subghz_devices_sleep(instance->radio_device); + radio_device_loader_end(instance->radio_device); + + subghz_devices_deinit(); + free(instance); } @@ -711,15 +738,6 @@ int32_t playlist_app(void* p) { Playlist* app = playlist_alloc(meta); meta->view_port = app->view_port; - // Enable power for External CC1101 if it is connected - furi_hal_subghz_enable_ext_power(); - // Auto switch to internal radio if external radio is not available - furi_delay_ms(15); - if(!furi_hal_subghz_check_radio()) { - furi_hal_subghz_select_radio_type(SubGhzRadioInternal); - furi_hal_subghz_init_radio_type(SubGhzRadioInternal); - } - furi_hal_power_suppress_charge_enter(); // select playlist file @@ -808,10 +826,6 @@ int32_t playlist_app(void* p) { exit_cleanup: furi_hal_power_suppress_charge_exit(); - // Disable power for External CC1101 if it was enabled and module is connected - furi_hal_subghz_disable_ext_power(); - // Reinit SPI handles for internal radio / nfc - furi_hal_subghz_init_radio_type(SubGhzRadioInternal); if(app->worker != NULL) { if(playlist_worker_running(app->worker)) { From 6e26de3763d1af09b2a38d90d37eecedb318e060 Mon Sep 17 00:00:00 2001 From: gid9798 <30450294+gid9798@users.noreply.github.com> Date: Tue, 20 Jun 2023 14:28:03 +0300 Subject: [PATCH 010/364] SubGhz app: fix is_tx_allowed and freq check --- .../main/subghz/helpers/subghz_txrx.c | 18 ++++++++++--- .../main/subghz/helpers/subghz_txrx.h | 2 ++ .../main/subghz/helpers/subghz_types.h | 1 + applications/main/subghz/subghz_cli.c | 3 +-- applications/main/subghz/subghz_i.c | 27 +++++++++++++------ applications/main/subghz/subghz_i.h | 2 +- 6 files changed, 38 insertions(+), 15 deletions(-) diff --git a/applications/main/subghz/helpers/subghz_txrx.c b/applications/main/subghz/helpers/subghz_txrx.c index 581c6db5c..c9bebeacf 100644 --- a/applications/main/subghz/helpers/subghz_txrx.c +++ b/applications/main/subghz/helpers/subghz_txrx.c @@ -216,11 +216,8 @@ void subghz_txrx_sleep(SubGhzTxRx* instance) { static bool subghz_txrx_tx(SubGhzTxRx* instance, uint32_t frequency) { furi_assert(instance); - // TODO - if(!furi_hal_subghz_is_frequency_valid(frequency)) { - furi_crash("SubGhz: Incorrect TX frequency."); - } furi_assert(instance->txrx_state != SubGhzTxRxStateSleep); + subghz_devices_idle(instance->radio_device); subghz_devices_set_frequency(instance->radio_device, frequency); @@ -637,6 +634,19 @@ bool subghz_txrx_radio_device_is_frequecy_valid(SubGhzTxRx* instance, uint32_t f return subghz_devices_is_frequency_valid(instance->radio_device, frequency); } +bool subghz_txrx_radio_device_is_tx_alowed(SubGhzTxRx* instance, uint32_t frequency) { + furi_assert(instance); + furi_assert(instance->txrx_state != SubGhzTxRxStateSleep); + + subghz_devices_idle(instance->radio_device); + subghz_devices_set_frequency(instance->radio_device, frequency); + + bool ret = subghz_devices_set_tx(instance->radio_device); + subghz_devices_idle(instance->radio_device); + + return ret; +} + void subghz_txrx_set_debug_pin_state(SubGhzTxRx* instance, bool state) { furi_assert(instance); instance->debug_pin_state = state; diff --git a/applications/main/subghz/helpers/subghz_txrx.h b/applications/main/subghz/helpers/subghz_txrx.h index d03c618c4..fc09ab8ea 100644 --- a/applications/main/subghz/helpers/subghz_txrx.h +++ b/applications/main/subghz/helpers/subghz_txrx.h @@ -336,6 +336,8 @@ const char* subghz_txrx_radio_device_get_name(SubGhzTxRx* instance); */ bool subghz_txrx_radio_device_is_frequecy_valid(SubGhzTxRx* instance, uint32_t frequency); +bool subghz_txrx_radio_device_is_tx_alowed(SubGhzTxRx* instance, uint32_t frequency); + void subghz_txrx_set_debug_pin_state(SubGhzTxRx* instance, bool state); bool subghz_txrx_get_debug_pin_state(SubGhzTxRx* instance); diff --git a/applications/main/subghz/helpers/subghz_types.h b/applications/main/subghz/helpers/subghz_types.h index 5e5b4e5de..3c928c3b5 100644 --- a/applications/main/subghz/helpers/subghz_types.h +++ b/applications/main/subghz/helpers/subghz_types.h @@ -61,6 +61,7 @@ typedef enum { SubGhzLoadKeyStateOK, SubGhzLoadKeyStateParseErr, SubGhzLoadKeyStateOnlyRx, + SubGhzLoadKeyStateUnsuportedFreq, SubGhzLoadKeyStateProtocolDescriptionErr, } SubGhzLoadKeyState; diff --git a/applications/main/subghz/subghz_cli.c b/applications/main/subghz/subghz_cli.c index fb7453f06..810729dab 100644 --- a/applications/main/subghz/subghz_cli.c +++ b/applications/main/subghz/subghz_cli.c @@ -227,8 +227,7 @@ void subghz_cli_command_tx(Cli* cli, FuriString* args, void* context) { subghz_devices_stop_async_tx(device); } else { - printf("Transmission on this frequency is restricted in your region\r\n"); - // TODO region? + printf("Frequency is outside of default range. Check docs.\r\n"); } subghz_devices_sleep(device); diff --git a/applications/main/subghz/subghz_i.c b/applications/main/subghz/subghz_i.c index 4e44302f7..f91f128cf 100644 --- a/applications/main/subghz/subghz_i.c +++ b/applications/main/subghz/subghz_i.c @@ -46,7 +46,7 @@ bool subghz_tx_start(SubGhz* subghz, FlipperFormat* flipper_format) { subghz->dialogs, "Error in protocol\nparameters\ndescription"); break; case SubGhzTxRxStartTxStateErrorOnlyRx: - subghz_dialog_message_show_only_rx(subghz); + subghz_dialog_message_freq_error(subghz, true); break; default: @@ -56,12 +56,16 @@ bool subghz_tx_start(SubGhz* subghz, FlipperFormat* flipper_format) { return false; } -void subghz_dialog_message_show_only_rx(SubGhz* subghz) { +void subghz_dialog_message_freq_error(SubGhz* subghz, bool only_rx) { DialogsApp* dialogs = subghz->dialogs; DialogMessage* message = dialog_message_alloc(); + const char* header_text = "Frequency not supported"; + const char* message_text = "Frequency\nis outside of\nsuported range."; - const char* header_text = "Transmission is blocked"; - const char* message_text = "Frequency\nis outside of\ndefault range.\nCheck docs."; + if(only_rx) { + header_text = "Transmission is blocked"; + message_text = "Frequency\nis outside of\ndefault range.\nCheck docs."; + } dialog_message_set_header(message, header_text, 63, 3, AlignCenter, AlignTop); dialog_message_set_text(message, message_text, 0, 17, AlignLeft, AlignTop); @@ -112,12 +116,13 @@ bool subghz_key_load(SubGhz* subghz, const char* file_path, bool show_dialog) { } if(!subghz_txrx_radio_device_is_frequecy_valid(subghz->txrx, temp_data32)) { - FURI_LOG_E(TAG, "Frequency not supported"); + FURI_LOG_E(TAG, "Frequency not supported on chosen radio module"); + load_key_state = SubGhzLoadKeyStateUnsuportedFreq; break; } - if(!furi_hal_subghz_is_tx_allowed(temp_data32)) { - FURI_LOG_E(TAG, "This frequency can only be used for RX"); + if(!subghz_txrx_radio_device_is_tx_alowed(subghz->txrx, temp_data32)) { + FURI_LOG_E(TAG, "This frequency can only be used for RX on chosen radio module"); load_key_state = SubGhzLoadKeyStateOnlyRx; break; } @@ -207,9 +212,15 @@ bool subghz_key_load(SubGhz* subghz, const char* file_path, bool show_dialog) { } return false; + case SubGhzLoadKeyStateUnsuportedFreq: + if(show_dialog) { + subghz_dialog_message_freq_error(subghz, false); + } + return false; + case SubGhzLoadKeyStateOnlyRx: if(show_dialog) { - subghz_dialog_message_show_only_rx(subghz); + subghz_dialog_message_freq_error(subghz, true); } return false; diff --git a/applications/main/subghz/subghz_i.h b/applications/main/subghz/subghz_i.h index 3d1c2db9c..329c6e35f 100644 --- a/applications/main/subghz/subghz_i.h +++ b/applications/main/subghz/subghz_i.h @@ -115,7 +115,7 @@ void subghz_blink_start(SubGhz* subghz); void subghz_blink_stop(SubGhz* subghz); bool subghz_tx_start(SubGhz* subghz, FlipperFormat* flipper_format); -void subghz_dialog_message_show_only_rx(SubGhz* subghz); +void subghz_dialog_message_freq_error(SubGhz* subghz, bool only_rx); bool subghz_key_load(SubGhz* subghz, const char* file_path, bool show_dialog); bool subghz_get_next_name_file(SubGhz* subghz, uint8_t max_len); From 7bd0273fd522c316f1d34cd6e1c726d3cf4d8ccb Mon Sep 17 00:00:00 2001 From: gid9798 <30450294+gid9798@users.noreply.github.com> Date: Tue, 20 Jun 2023 14:41:59 +0300 Subject: [PATCH 011/364] SubGhz app: deleted extra check --- applications/main/subghz/helpers/subghz_txrx.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/applications/main/subghz/helpers/subghz_txrx.c b/applications/main/subghz/helpers/subghz_txrx.c index c9bebeacf..35c4c565a 100644 --- a/applications/main/subghz/helpers/subghz_txrx.c +++ b/applications/main/subghz/helpers/subghz_txrx.c @@ -167,10 +167,6 @@ static void subghz_txrx_begin(SubGhzTxRx* instance, uint8_t* preset_data) { static uint32_t subghz_txrx_rx(SubGhzTxRx* instance, uint32_t frequency) { furi_assert(instance); - // TODO - if(!furi_hal_subghz_is_frequency_valid(frequency)) { - furi_crash("SubGhz: Incorrect RX frequency."); - } furi_assert( instance->txrx_state != SubGhzTxRxStateRx && instance->txrx_state != SubGhzTxRxStateSleep); From 1e76c2d840c1b9d5f9d09080cd3b4f0abeeb5139 Mon Sep 17 00:00:00 2001 From: gid9798 <30450294+gid9798@users.noreply.github.com> Date: Wed, 21 Jun 2023 11:24:16 +0300 Subject: [PATCH 012/364] SubRem Apps: update --- .../helpers/subrem_presets.c | 5 ++--- .../helpers/txrx/subghz_txrx.c | 22 ++++++++++++------- .../helpers/txrx/subghz_txrx.h | 2 ++ .../subghz_remote/helpers/subrem_presets.c | 4 ++-- .../scenes/subrem_scene_remote.c | 2 +- 5 files changed, 21 insertions(+), 14 deletions(-) diff --git a/applications/external/subghz_remote_configurator/helpers/subrem_presets.c b/applications/external/subghz_remote_configurator/helpers/subrem_presets.c index d491af2f7..75ced8e00 100644 --- a/applications/external/subghz_remote_configurator/helpers/subrem_presets.c +++ b/applications/external/subghz_remote_configurator/helpers/subrem_presets.c @@ -84,9 +84,8 @@ SubRemLoadSubState subrem_sub_preset_load( if(!flipper_format_read_uint32(fff_data_file, "Frequency", &temp_data32, 1)) { FURI_LOG_W(TAG, "Cannot read frequency. Set default frequency"); sub_preset->freq_preset.frequency = subghz_setting_get_default_frequency(setting); - } else if(!furi_hal_subghz_is_tx_allowed(temp_data32)) { - // TODO - FURI_LOG_E(TAG, "This frequency can only be used for RX"); + } else if(!subghz_txrx_radio_device_is_frequecy_valid(txrx, temp_data32)) { + FURI_LOG_E(TAG, "Frequency not supported on chosen radio module"); break; } sub_preset->freq_preset.frequency = temp_data32; diff --git a/applications/external/subghz_remote_configurator/helpers/txrx/subghz_txrx.c b/applications/external/subghz_remote_configurator/helpers/txrx/subghz_txrx.c index e4309dcad..223876c36 100644 --- a/applications/external/subghz_remote_configurator/helpers/txrx/subghz_txrx.c +++ b/applications/external/subghz_remote_configurator/helpers/txrx/subghz_txrx.c @@ -167,10 +167,6 @@ static void subghz_txrx_begin(SubGhzTxRx* instance, uint8_t* preset_data) { static uint32_t subghz_txrx_rx(SubGhzTxRx* instance, uint32_t frequency) { furi_assert(instance); - // TODO - if(!furi_hal_subghz_is_frequency_valid(frequency)) { - furi_crash("SubGhz: Incorrect RX frequency."); - } furi_assert( instance->txrx_state != SubGhzTxRxStateRx && instance->txrx_state != SubGhzTxRxStateSleep); @@ -216,11 +212,8 @@ void subghz_txrx_sleep(SubGhzTxRx* instance) { static bool subghz_txrx_tx(SubGhzTxRx* instance, uint32_t frequency) { furi_assert(instance); - // TODO - if(!furi_hal_subghz_is_frequency_valid(frequency)) { - furi_crash("SubGhz: Incorrect TX frequency."); - } furi_assert(instance->txrx_state != SubGhzTxRxStateSleep); + subghz_devices_idle(instance->radio_device); subghz_devices_set_frequency(instance->radio_device, frequency); @@ -637,6 +630,19 @@ bool subghz_txrx_radio_device_is_frequecy_valid(SubGhzTxRx* instance, uint32_t f return subghz_devices_is_frequency_valid(instance->radio_device, frequency); } +bool subghz_txrx_radio_device_is_tx_alowed(SubGhzTxRx* instance, uint32_t frequency) { + furi_assert(instance); + furi_assert(instance->txrx_state != SubGhzTxRxStateSleep); + + subghz_devices_idle(instance->radio_device); + subghz_devices_set_frequency(instance->radio_device, frequency); + + bool ret = subghz_devices_set_tx(instance->radio_device); + subghz_devices_idle(instance->radio_device); + + return ret; +} + void subghz_txrx_set_debug_pin_state(SubGhzTxRx* instance, bool state) { furi_assert(instance); instance->debug_pin_state = state; diff --git a/applications/external/subghz_remote_configurator/helpers/txrx/subghz_txrx.h b/applications/external/subghz_remote_configurator/helpers/txrx/subghz_txrx.h index 46f09a665..93748e2de 100644 --- a/applications/external/subghz_remote_configurator/helpers/txrx/subghz_txrx.h +++ b/applications/external/subghz_remote_configurator/helpers/txrx/subghz_txrx.h @@ -365,6 +365,8 @@ const char* subghz_txrx_radio_device_get_name(SubGhzTxRx* instance); */ bool subghz_txrx_radio_device_is_frequecy_valid(SubGhzTxRx* instance, uint32_t frequency); +bool subghz_txrx_radio_device_is_tx_alowed(SubGhzTxRx* instance, uint32_t frequency); + void subghz_txrx_set_debug_pin_state(SubGhzTxRx* instance, bool state); bool subghz_txrx_get_debug_pin_state(SubGhzTxRx* instance); diff --git a/applications/main/subghz_remote/helpers/subrem_presets.c b/applications/main/subghz_remote/helpers/subrem_presets.c index 45da793d7..75ced8e00 100644 --- a/applications/main/subghz_remote/helpers/subrem_presets.c +++ b/applications/main/subghz_remote/helpers/subrem_presets.c @@ -84,8 +84,8 @@ SubRemLoadSubState subrem_sub_preset_load( if(!flipper_format_read_uint32(fff_data_file, "Frequency", &temp_data32, 1)) { FURI_LOG_W(TAG, "Cannot read frequency. Set default frequency"); sub_preset->freq_preset.frequency = subghz_setting_get_default_frequency(setting); - } else if(!furi_hal_subghz_is_tx_allowed(temp_data32)) { - FURI_LOG_E(TAG, "This frequency can only be used for RX"); + } else if(!subghz_txrx_radio_device_is_frequecy_valid(txrx, temp_data32)) { + FURI_LOG_E(TAG, "Frequency not supported on chosen radio module"); break; } sub_preset->freq_preset.frequency = temp_data32; diff --git a/applications/main/subghz_remote/scenes/subrem_scene_remote.c b/applications/main/subghz_remote/scenes/subrem_scene_remote.c index a2e307fd9..ebc582991 100644 --- a/applications/main/subghz_remote/scenes/subrem_scene_remote.c +++ b/applications/main/subghz_remote/scenes/subrem_scene_remote.c @@ -80,7 +80,7 @@ bool subrem_scene_remote_on_event(void* context, SceneManagerEvent event) { } else { subrem_view_remote_set_state( app->subrem_remote_view, SubRemViewRemoteStateIdle, 0); - notification_message(app->notifications, &sequence_blink_stop); + notification_message(app->notifications, &sequence_blink_red_100); } return true; } else if(event.event == SubRemCustomEventViewRemoteForcedStop) { From 91ab2fd98422b681803d176be8e054e73225574b Mon Sep 17 00:00:00 2001 From: gid9798 <30450294+gid9798@users.noreply.github.com> Date: Wed, 21 Jun 2023 14:00:25 +0300 Subject: [PATCH 013/364] Pocsaq pager App: new radio driver --- .../helpers/radio_device_loader.c | 66 +++++++++++++++++++ .../helpers/radio_device_loader.h | 17 +++++ .../external/pocsag_pager/pocsag_pager_app.c | 29 ++++---- .../pocsag_pager/pocsag_pager_app_i.c | 39 ++++++----- .../pocsag_pager/pocsag_pager_app_i.h | 4 ++ .../scenes/pocsag_pager_receiver.c | 7 +- .../views/pocsag_pager_receiver.c | 13 ++-- .../views/pocsag_pager_receiver.h | 2 + 8 files changed, 138 insertions(+), 39 deletions(-) create mode 100644 applications/external/pocsag_pager/helpers/radio_device_loader.c create mode 100644 applications/external/pocsag_pager/helpers/radio_device_loader.h diff --git a/applications/external/pocsag_pager/helpers/radio_device_loader.c b/applications/external/pocsag_pager/helpers/radio_device_loader.c new file mode 100644 index 000000000..ce0920755 --- /dev/null +++ b/applications/external/pocsag_pager/helpers/radio_device_loader.c @@ -0,0 +1,66 @@ +#include "radio_device_loader.h" + +#include +#include + +static void radio_device_loader_power_on() { + uint8_t attempts = 0; + while(!furi_hal_power_is_otg_enabled() && attempts++ < 5) { + furi_hal_power_enable_otg(); + //CC1101 power-up time + furi_delay_ms(10); + } +} + +static void radio_device_loader_power_off() { + if(furi_hal_power_is_otg_enabled()) furi_hal_power_disable_otg(); +} + +bool radio_device_loader_is_connect_external(const char* name) { + bool is_connect = false; + bool is_otg_enabled = furi_hal_power_is_otg_enabled(); + + if(!is_otg_enabled) { + radio_device_loader_power_on(); + } + + is_connect = subghz_devices_is_connect(subghz_devices_get_by_name(name)); + + if(!is_otg_enabled) { + radio_device_loader_power_off(); + } + return is_connect; +} + +const SubGhzDevice* radio_device_loader_set( + const SubGhzDevice* current_radio_device, + SubGhzRadioDeviceType radio_device_type) { + const SubGhzDevice* radio_device; + + if(radio_device_type == SubGhzRadioDeviceTypeExternalCC1101 && + radio_device_loader_is_connect_external(SUBGHZ_DEVICE_CC1101_EXT_NAME)) { + radio_device_loader_power_on(); + radio_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_EXT_NAME); + subghz_devices_begin(radio_device); + } else if(current_radio_device == NULL) { + radio_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME); + } else { + radio_device_loader_end(current_radio_device); + radio_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME); + } + + return radio_device; +} + +bool radio_device_loader_is_external(const SubGhzDevice* radio_device) { + furi_assert(radio_device); + return (radio_device != subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME)); +} + +void radio_device_loader_end(const SubGhzDevice* radio_device) { + furi_assert(radio_device); + radio_device_loader_power_off(); + if(radio_device != subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME)) { + subghz_devices_end(radio_device); + } +} \ No newline at end of file diff --git a/applications/external/pocsag_pager/helpers/radio_device_loader.h b/applications/external/pocsag_pager/helpers/radio_device_loader.h new file mode 100644 index 000000000..bae4bacf2 --- /dev/null +++ b/applications/external/pocsag_pager/helpers/radio_device_loader.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +/** SubGhzRadioDeviceType */ +typedef enum { + SubGhzRadioDeviceTypeInternal, + SubGhzRadioDeviceTypeExternalCC1101, +} SubGhzRadioDeviceType; + +const SubGhzDevice* radio_device_loader_set( + const SubGhzDevice* current_radio_device, + SubGhzRadioDeviceType radio_device_type); + +bool radio_device_loader_is_external(const SubGhzDevice* radio_device); + +void radio_device_loader_end(const SubGhzDevice* radio_device); \ No newline at end of file diff --git a/applications/external/pocsag_pager/pocsag_pager_app.c b/applications/external/pocsag_pager/pocsag_pager_app.c index 1013164f2..70ff49f1a 100644 --- a/applications/external/pocsag_pager/pocsag_pager_app.c +++ b/applications/external/pocsag_pager/pocsag_pager_app.c @@ -91,6 +91,16 @@ POCSAGPagerApp* pocsag_pager_app_alloc() { app->txrx->preset = malloc(sizeof(SubGhzRadioPreset)); app->txrx->preset->name = furi_string_alloc(); + furi_hal_power_suppress_charge_enter(); + + // Radio Devices init & load + subghz_devices_init(); + app->txrx->radio_device = + radio_device_loader_set(app->txrx->radio_device, SubGhzRadioDeviceTypeExternalCC1101); + + subghz_devices_reset(app->txrx->radio_device); + subghz_devices_idle(app->txrx->radio_device); + // Custom Presets load without using config file FlipperFormat* temp_fm_preset = flipper_format_string_alloc(); @@ -122,17 +132,6 @@ POCSAGPagerApp* pocsag_pager_app_alloc() { app->txrx->worker, (SubGhzWorkerPairCallback)subghz_receiver_decode); subghz_worker_set_context(app->txrx->worker, app->txrx->receiver); - // Enable power for External CC1101 if it is connected - furi_hal_subghz_enable_ext_power(); - // Auto switch to internal radio if external radio is not available - furi_delay_ms(15); - if(!furi_hal_subghz_check_radio()) { - furi_hal_subghz_select_radio_type(SubGhzRadioInternal); - furi_hal_subghz_init_radio_type(SubGhzRadioInternal); - } - - furi_hal_power_suppress_charge_enter(); - scene_manager_next_scene(app->scene_manager, POCSAGPagerSceneStart); return app; @@ -141,13 +140,11 @@ POCSAGPagerApp* pocsag_pager_app_alloc() { void pocsag_pager_app_free(POCSAGPagerApp* app) { furi_assert(app); - //CC1101 off + // Radio Devices sleep & off pcsg_sleep(app); + radio_device_loader_end(app->txrx->radio_device); - // Disable power for External CC1101 if it was enabled and module is connected - furi_hal_subghz_disable_ext_power(); - // Reinit SPI handles for internal radio / nfc - furi_hal_subghz_init_radio_type(SubGhzRadioInternal); + subghz_devices_deinit(); // Submenu view_dispatcher_remove_view(app->view_dispatcher, POCSAGPagerViewSubmenu); diff --git a/applications/external/pocsag_pager/pocsag_pager_app_i.c b/applications/external/pocsag_pager/pocsag_pager_app_i.c index ff73ab50e..8dda1d8b6 100644 --- a/applications/external/pocsag_pager/pocsag_pager_app_i.c +++ b/applications/external/pocsag_pager/pocsag_pager_app_i.c @@ -36,29 +36,34 @@ void pcsg_get_frequency_modulation( void pcsg_begin(POCSAGPagerApp* app, uint8_t* preset_data) { furi_assert(app); - UNUSED(preset_data); - furi_hal_subghz_reset(); - furi_hal_subghz_idle(); - furi_hal_subghz_load_custom_preset(preset_data); - furi_hal_gpio_init(furi_hal_subghz.cc1101_g0_pin, GpioModeInput, GpioPullNo, GpioSpeedLow); + + subghz_devices_reset(app->txrx->radio_device); + subghz_devices_idle(app->txrx->radio_device); + subghz_devices_load_preset(app->txrx->radio_device, FuriHalSubGhzPresetCustom, preset_data); + + // furi_hal_gpio_init(furi_hal_subghz.cc1101_g0_pin, GpioModeInput, GpioPullNo, GpioSpeedLow); app->txrx->txrx_state = PCSGTxRxStateIDLE; } uint32_t pcsg_rx(POCSAGPagerApp* app, uint32_t frequency) { furi_assert(app); - if(!furi_hal_subghz_is_frequency_valid(frequency)) { + if(!subghz_devices_is_frequency_valid(app->txrx->radio_device, frequency)) { furi_crash("POCSAGPager: Incorrect RX frequency."); } furi_assert( app->txrx->txrx_state != PCSGTxRxStateRx && app->txrx->txrx_state != PCSGTxRxStateSleep); - furi_hal_subghz_idle(); - uint32_t value = furi_hal_subghz_set_frequency_and_path(frequency); - furi_hal_gpio_init(furi_hal_subghz.cc1101_g0_pin, GpioModeInput, GpioPullNo, GpioSpeedLow); - furi_hal_subghz_flush_rx(); - furi_hal_subghz_rx(); + subghz_devices_idle(app->txrx->radio_device); + uint32_t value = subghz_devices_set_frequency(app->txrx->radio_device, frequency); - furi_hal_subghz_start_async_rx(subghz_worker_rx_callback, app->txrx->worker); + // Not need. init in subghz_devices_start_async_tx + // furi_hal_gpio_init(furi_hal_subghz.cc1101_g0_pin, GpioModeInput, GpioPullNo, GpioSpeedLow); + + subghz_devices_flush_rx(app->txrx->radio_device); + subghz_devices_set_rx(app->txrx->radio_device); + + subghz_devices_start_async_rx( + app->txrx->radio_device, subghz_worker_rx_callback, app->txrx->worker); subghz_worker_start(app->txrx->worker); app->txrx->txrx_state = PCSGTxRxStateRx; return value; @@ -67,7 +72,7 @@ uint32_t pcsg_rx(POCSAGPagerApp* app, uint32_t frequency) { void pcsg_idle(POCSAGPagerApp* app) { furi_assert(app); furi_assert(app->txrx->txrx_state != PCSGTxRxStateSleep); - furi_hal_subghz_idle(); + subghz_devices_idle(app->txrx->radio_device); app->txrx->txrx_state = PCSGTxRxStateIDLE; } @@ -76,15 +81,15 @@ void pcsg_rx_end(POCSAGPagerApp* app) { furi_assert(app->txrx->txrx_state == PCSGTxRxStateRx); if(subghz_worker_is_running(app->txrx->worker)) { subghz_worker_stop(app->txrx->worker); - furi_hal_subghz_stop_async_rx(); + subghz_devices_stop_async_rx(app->txrx->radio_device); } - furi_hal_subghz_idle(); + subghz_devices_idle(app->txrx->radio_device); app->txrx->txrx_state = PCSGTxRxStateIDLE; } void pcsg_sleep(POCSAGPagerApp* app) { furi_assert(app); - furi_hal_subghz_sleep(); + subghz_devices_sleep(app->txrx->radio_device); app->txrx->txrx_state = PCSGTxRxStateSleep; } @@ -110,7 +115,7 @@ void pcsg_hopper_update(POCSAGPagerApp* app) { float rssi = -127.0f; if(app->txrx->hopper_state != PCSGHopperStateRSSITimeOut) { // See RSSI Calculation timings in CC1101 17.3 RSSI - rssi = furi_hal_subghz_get_rssi(); + rssi = subghz_devices_get_rssi(app->txrx->radio_device); // Stay if RSSI is high enough if(rssi > -90.0f) { diff --git a/applications/external/pocsag_pager/pocsag_pager_app_i.h b/applications/external/pocsag_pager/pocsag_pager_app_i.h index 8a0426dc5..c31e5ae1a 100644 --- a/applications/external/pocsag_pager/pocsag_pager_app_i.h +++ b/applications/external/pocsag_pager/pocsag_pager_app_i.h @@ -1,6 +1,7 @@ #pragma once #include "helpers/pocsag_pager_types.h" +#include "helpers/radio_device_loader.h" #include "scenes/pocsag_pager_scene.h" #include @@ -19,6 +20,7 @@ #include #include #include +#include typedef struct POCSAGPagerApp POCSAGPagerApp; @@ -35,6 +37,8 @@ struct POCSAGPagerTxRx { uint8_t hopper_timeout; uint8_t hopper_idx_frequency; PCSGRxKeyState rx_key_state; + + const SubGhzDevice* radio_device; }; typedef struct POCSAGPagerTxRx POCSAGPagerTxRx; diff --git a/applications/external/pocsag_pager/scenes/pocsag_pager_receiver.c b/applications/external/pocsag_pager/scenes/pocsag_pager_receiver.c index 658b70fea..cc2abd7e0 100644 --- a/applications/external/pocsag_pager/scenes/pocsag_pager_receiver.c +++ b/applications/external/pocsag_pager/scenes/pocsag_pager_receiver.c @@ -112,6 +112,8 @@ void pocsag_pager_scene_receiver_on_enter(void* context) { } pcsg_view_receiver_set_lock(app->pcsg_receiver, app->lock); + pcsg_view_receiver_set_ext_module_state( + app->pcsg_receiver, radio_device_loader_is_external(app->txrx->radio_device)); //Load history to receiver pcsg_view_receiver_exit(app->pcsg_receiver); @@ -136,6 +138,7 @@ void pocsag_pager_scene_receiver_on_enter(void* context) { }; if((app->txrx->txrx_state == PCSGTxRxStateIDLE) || (app->txrx->txrx_state == PCSGTxRxStateSleep)) { + // Start RX pcsg_begin( app, subghz_setting_get_preset_data_by_name( @@ -157,7 +160,7 @@ bool pocsag_pager_scene_receiver_on_event(void* context, SceneManagerEvent event // Stop CC1101 Rx if(app->txrx->txrx_state == PCSGTxRxStateRx) { pcsg_rx_end(app); - pcsg_sleep(app); + pcsg_idle(app); }; app->txrx->hopper_state = PCSGHopperStateOFF; app->txrx->idx_menu_chosen = 0; @@ -196,7 +199,7 @@ bool pocsag_pager_scene_receiver_on_event(void* context, SceneManagerEvent event pocsag_pager_scene_receiver_update_statusbar(app); } // Get current RSSI - float rssi = furi_hal_subghz_get_rssi(); + float rssi = subghz_devices_get_rssi(app->txrx->radio_device); pcsg_receiver_rssi(app->pcsg_receiver, rssi); if(app->txrx->txrx_state == PCSGTxRxStateRx) { diff --git a/applications/external/pocsag_pager/views/pocsag_pager_receiver.c b/applications/external/pocsag_pager/views/pocsag_pager_receiver.c index 64939a956..fed752de9 100644 --- a/applications/external/pocsag_pager/views/pocsag_pager_receiver.c +++ b/applications/external/pocsag_pager/views/pocsag_pager_receiver.c @@ -61,6 +61,7 @@ typedef struct { uint16_t history_item; PCSGReceiverBarShow bar_show; uint8_t u_rssi; + bool ext_module; } PCSGReceiverModel; void pcsg_receiver_rssi(PCSGReceiver* instance, float rssi) { @@ -98,6 +99,12 @@ void pcsg_view_receiver_set_lock(PCSGReceiver* pcsg_receiver, PCSGLock lock) { } } +void pcsg_view_receiver_set_ext_module_state(PCSGReceiver* pcsg_receiver, bool is_external) { + furi_assert(pcsg_receiver); + with_view_model( + pcsg_receiver->view, PCSGReceiverModel * model, { model->ext_module = is_external; }, true); +} + void pcsg_view_receiver_set_callback( PCSGReceiver* pcsg_receiver, PCSGReceiverCallback callback, @@ -207,8 +214,6 @@ void pcsg_view_receiver_draw(Canvas* canvas, PCSGReceiverModel* model) { FuriString* str_buff; str_buff = furi_string_alloc(); - bool ext_module = furi_hal_subghz_get_radio_type(); - PCSGReceiverMenuItem* item_menu; for(size_t i = 0; i < MIN(model->history_item, MENU_ITEMS); ++i) { @@ -234,11 +239,11 @@ void pcsg_view_receiver_draw(Canvas* canvas, PCSGReceiverModel* model) { canvas_set_color(canvas, ColorBlack); if(model->history_item == 0) { - canvas_draw_icon(canvas, 0, 0, ext_module ? &I_Fishing_123x52 : &I_Scanning_123x52); + canvas_draw_icon(canvas, 0, 0, model->ext_module ? &I_Fishing_123x52 : &I_Scanning_123x52); canvas_set_font(canvas, FontPrimary); canvas_draw_str(canvas, 63, 46, "Scanning..."); canvas_set_font(canvas, FontSecondary); - canvas_draw_str(canvas, 44, 10, ext_module ? "Ext" : "Int"); + canvas_draw_str(canvas, 44, 10, model->ext_module ? "Ext" : "Int"); } // Draw RSSI diff --git a/applications/external/pocsag_pager/views/pocsag_pager_receiver.h b/applications/external/pocsag_pager/views/pocsag_pager_receiver.h index a13f5cdc7..87900748f 100644 --- a/applications/external/pocsag_pager/views/pocsag_pager_receiver.h +++ b/applications/external/pocsag_pager/views/pocsag_pager_receiver.h @@ -12,6 +12,8 @@ void pcsg_receiver_rssi(PCSGReceiver* instance, float rssi); void pcsg_view_receiver_set_lock(PCSGReceiver* pcsg_receiver, PCSGLock keyboard); +void pcsg_view_receiver_set_ext_module_state(PCSGReceiver* pcsg_receiver, bool is_external); + void pcsg_view_receiver_set_callback( PCSGReceiver* pcsg_receiver, PCSGReceiverCallback callback, From 2fb57529a0f61131e8fda5caca337ba8f310cde2 Mon Sep 17 00:00:00 2001 From: gid9798 <30450294+gid9798@users.noreply.github.com> Date: Mon, 3 Jul 2023 12:35:27 +0300 Subject: [PATCH 014/364] SubGhz: fiz module seletion, furi_assert(device). drivers targets --- applications/drivers/subghz/application.fam | 1 + .../main/subghz/helpers/subghz_txrx.c | 13 ++- .../main/subghz/helpers/subghz_txrx.h | 2 +- .../scenes/subghz_scene_radio_setting.c | 11 ++- lib/subghz/devices/devices.c | 79 +++++++++++++------ 5 files changed, 72 insertions(+), 34 deletions(-) diff --git a/applications/drivers/subghz/application.fam b/applications/drivers/subghz/application.fam index a293b99d3..aaf0e1bd9 100644 --- a/applications/drivers/subghz/application.fam +++ b/applications/drivers/subghz/application.fam @@ -1,6 +1,7 @@ App( appid="radio_device_cc1101_ext", apptype=FlipperAppType.PLUGIN, + targets=["f7"], entry_point="subghz_device_cc1101_ext_ep", requires=["subghz"], fap_libs=["hwdrivers"], diff --git a/applications/main/subghz/helpers/subghz_txrx.c b/applications/main/subghz/helpers/subghz_txrx.c index 35c4c565a..c678965c1 100644 --- a/applications/main/subghz/helpers/subghz_txrx.c +++ b/applications/main/subghz/helpers/subghz_txrx.c @@ -570,7 +570,7 @@ void subghz_txrx_set_raw_file_encoder_worker_callback_end( context); } -bool subghz_txrx_radio_device_is_connect_external(SubGhzTxRx* instance, const char* name) { +bool subghz_txrx_radio_device_is_external_connected(SubGhzTxRx* instance, const char* name) { furi_assert(instance); bool is_connect = false; @@ -580,7 +580,10 @@ bool subghz_txrx_radio_device_is_connect_external(SubGhzTxRx* instance, const ch subghz_txrx_radio_device_power_on(instance); } - is_connect = subghz_devices_is_connect(subghz_devices_get_by_name(name)); + const SubGhzDevice* device = subghz_devices_get_by_name(name); + if(device) { + is_connect = subghz_devices_is_connect(device); + } if(!is_otg_enabled) { subghz_txrx_radio_device_power_off(instance); @@ -593,7 +596,7 @@ SubGhzRadioDeviceType furi_assert(instance); if(radio_device_type == SubGhzRadioDeviceTypeExternalCC1101 && - subghz_txrx_radio_device_is_connect_external(instance, SUBGHZ_DEVICE_CC1101_EXT_NAME)) { + subghz_txrx_radio_device_is_external_connected(instance, SUBGHZ_DEVICE_CC1101_EXT_NAME)) { subghz_txrx_radio_device_power_on(instance); instance->radio_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_EXT_NAME); subghz_devices_begin(instance->radio_device); @@ -601,7 +604,9 @@ SubGhzRadioDeviceType } else { subghz_txrx_radio_device_power_off(instance); if(instance->radio_device_type != SubGhzRadioDeviceTypeInternal) { - subghz_devices_end(instance->radio_device); + if(instance->radio_device) { + subghz_devices_end(instance->radio_device); + } } instance->radio_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME); instance->radio_device_type = SubGhzRadioDeviceTypeInternal; diff --git a/applications/main/subghz/helpers/subghz_txrx.h b/applications/main/subghz/helpers/subghz_txrx.h index fc09ab8ea..76c7c8ead 100644 --- a/applications/main/subghz/helpers/subghz_txrx.h +++ b/applications/main/subghz/helpers/subghz_txrx.h @@ -297,7 +297,7 @@ void subghz_txrx_set_raw_file_encoder_worker_callback_end( * @param name Name of external radio device * @return bool True if is connected to the external radio device */ -bool subghz_txrx_radio_device_is_connect_external(SubGhzTxRx* instance, const char* name); +bool subghz_txrx_radio_device_is_external_connected(SubGhzTxRx* instance, const char* name); /* Set the selected radio device to use * diff --git a/applications/main/subghz/scenes/subghz_scene_radio_setting.c b/applications/main/subghz/scenes/subghz_scene_radio_setting.c index 9f2a6ab58..ee438727b 100644 --- a/applications/main/subghz/scenes/subghz_scene_radio_setting.c +++ b/applications/main/subghz/scenes/subghz_scene_radio_setting.c @@ -21,7 +21,8 @@ static void subghz_scene_radio_setting_set_device(VariableItem* item) { SubGhz* subghz = variable_item_get_context(item); uint8_t index = variable_item_get_current_value_index(item); - if(!subghz_txrx_radio_device_is_connect_external(subghz->txrx, SUBGHZ_DEVICE_CC1101_EXT_NAME) && + if(!subghz_txrx_radio_device_is_external_connected( + subghz->txrx, SUBGHZ_DEVICE_CC1101_EXT_NAME) && radio_device_value[index] == SubGhzRadioDeviceTypeExternalCC1101) { //ToDo correct if there is more than 1 module index = 0; @@ -35,14 +36,18 @@ void subghz_scene_radio_setting_on_enter(void* context) { VariableItem* item; uint8_t value_index; + uint8_t value_count_device = RADIO_DEVICE_COUNT; + if(subghz_txrx_radio_device_get(subghz->txrx) == SubGhzRadioDeviceTypeInternal && + !subghz_txrx_radio_device_is_external_connected(subghz->txrx, SUBGHZ_DEVICE_CC1101_EXT_NAME)) + value_count_device = 1; // Only 1 item if external disconnected item = variable_item_list_add( subghz->variable_item_list, "Module", - RADIO_DEVICE_COUNT, + value_count_device, subghz_scene_radio_setting_set_device, subghz); value_index = value_index_uint32( - subghz_txrx_radio_device_get(subghz->txrx), radio_device_value, RADIO_DEVICE_COUNT); + subghz_txrx_radio_device_get(subghz->txrx), radio_device_value, value_count_device); variable_item_set_current_value_index(item, value_index); variable_item_set_current_value_text(item, radio_device_text[value_index]); diff --git a/lib/subghz/devices/devices.c b/lib/subghz/devices/devices.c index 69946d0f1..a90bf73a3 100644 --- a/lib/subghz/devices/devices.c +++ b/lib/subghz/devices/devices.c @@ -28,40 +28,46 @@ const char* subghz_devices_get_name(const SubGhzDevice* device) { bool subghz_devices_begin(const SubGhzDevice* device) { bool ret = false; - if(device && device->interconnect->begin) { + furi_assert(device); + if(device->interconnect->begin) { ret = device->interconnect->begin(); } return ret; } void subghz_devices_end(const SubGhzDevice* device) { - if(device && device->interconnect->end) { + furi_assert(device); + if(device->interconnect->end) { device->interconnect->end(); } } bool subghz_devices_is_connect(const SubGhzDevice* device) { bool ret = false; - if(device && device->interconnect->is_connect) { + furi_assert(device); + if(device->interconnect->is_connect) { ret = device->interconnect->is_connect(); } return ret; } void subghz_devices_reset(const SubGhzDevice* device) { - if(device && device->interconnect->reset) { + furi_assert(device); + if(device->interconnect->reset) { device->interconnect->reset(); } } void subghz_devices_sleep(const SubGhzDevice* device) { - if(device && device->interconnect->sleep) { + furi_assert(device); + if(device->interconnect->sleep) { device->interconnect->sleep(); } } void subghz_devices_idle(const SubGhzDevice* device) { - if(device && device->interconnect->idle) { + furi_assert(device); + if(device->interconnect->idle) { device->interconnect->idle(); } } @@ -70,14 +76,16 @@ void subghz_devices_load_preset( const SubGhzDevice* device, FuriHalSubGhzPreset preset, uint8_t* preset_data) { - if(device && device->interconnect->load_preset) { + furi_assert(device); + if(device->interconnect->load_preset) { device->interconnect->load_preset(preset, preset_data); } } uint32_t subghz_devices_set_frequency(const SubGhzDevice* device, uint32_t frequency) { uint32_t ret = 0; - if(device && device->interconnect->set_frequency) { + furi_assert(device); + if(device->interconnect->set_frequency) { ret = device->interconnect->set_frequency(frequency); } return ret; @@ -85,21 +93,24 @@ uint32_t subghz_devices_set_frequency(const SubGhzDevice* device, uint32_t frequ bool subghz_devices_is_frequency_valid(const SubGhzDevice* device, uint32_t frequency) { bool ret = false; - if(device && device->interconnect->is_frequency_valid) { + furi_assert(device); + if(device->interconnect->is_frequency_valid) { ret = device->interconnect->is_frequency_valid(frequency); } return ret; } void subghz_devices_set_async_mirror_pin(const SubGhzDevice* device, const GpioPin* gpio) { - if(device && device->interconnect->set_async_mirror_pin) { + furi_assert(device); + if(device->interconnect->set_async_mirror_pin) { device->interconnect->set_async_mirror_pin(gpio); } } const GpioPin* subghz_devices_get_data_gpio(const SubGhzDevice* device) { const GpioPin* ret = NULL; - if(device && device->interconnect->get_data_gpio) { + furi_assert(device); + if(device->interconnect->get_data_gpio) { ret = device->interconnect->get_data_gpio(); } return ret; @@ -107,21 +118,24 @@ const GpioPin* subghz_devices_get_data_gpio(const SubGhzDevice* device) { bool subghz_devices_set_tx(const SubGhzDevice* device) { bool ret = 0; - if(device && device->interconnect->set_tx) { + furi_assert(device); + if(device->interconnect->set_tx) { ret = device->interconnect->set_tx(); } return ret; } void subghz_devices_flush_tx(const SubGhzDevice* device) { - if(device && device->interconnect->flush_tx) { + furi_assert(device); + if(device->interconnect->flush_tx) { device->interconnect->flush_tx(); } } bool subghz_devices_start_async_tx(const SubGhzDevice* device, void* callback, void* context) { bool ret = false; - if(device && device->interconnect->start_async_tx) { + furi_assert(device); + if(device->interconnect->start_async_tx) { ret = device->interconnect->start_async_tx(callback, context); } return ret; @@ -129,45 +143,52 @@ bool subghz_devices_start_async_tx(const SubGhzDevice* device, void* callback, v bool subghz_devices_is_async_complete_tx(const SubGhzDevice* device) { bool ret = false; - if(device && device->interconnect->is_async_complete_tx) { + furi_assert(device); + if(device->interconnect->is_async_complete_tx) { ret = device->interconnect->is_async_complete_tx(); } return ret; } void subghz_devices_stop_async_tx(const SubGhzDevice* device) { - if(device && device->interconnect->stop_async_tx) { + furi_assert(device); + if(device->interconnect->stop_async_tx) { device->interconnect->stop_async_tx(); } } void subghz_devices_set_rx(const SubGhzDevice* device) { - if(device && device->interconnect->set_rx) { + furi_assert(device); + if(device->interconnect->set_rx) { device->interconnect->set_rx(); } } void subghz_devices_flush_rx(const SubGhzDevice* device) { - if(device && device->interconnect->flush_rx) { + furi_assert(device); + if(device->interconnect->flush_rx) { device->interconnect->flush_rx(); } } void subghz_devices_start_async_rx(const SubGhzDevice* device, void* callback, void* context) { - if(device && device->interconnect->start_async_rx) { + furi_assert(device); + if(device->interconnect->start_async_rx) { device->interconnect->start_async_rx(callback, context); } } void subghz_devices_stop_async_rx(const SubGhzDevice* device) { - if(device && device->interconnect->stop_async_rx) { + furi_assert(device); + if(device->interconnect->stop_async_rx) { device->interconnect->stop_async_rx(); } } float subghz_devices_get_rssi(const SubGhzDevice* device) { float ret = 0; - if(device && device->interconnect->get_rssi) { + furi_assert(device); + if(device->interconnect->get_rssi) { ret = device->interconnect->get_rssi(); } return ret; @@ -175,7 +196,8 @@ float subghz_devices_get_rssi(const SubGhzDevice* device) { uint8_t subghz_devices_get_lqi(const SubGhzDevice* device) { uint8_t ret = 0; - if(device && device->interconnect->get_lqi) { + furi_assert(device); + if(device->interconnect->get_lqi) { ret = device->interconnect->get_lqi(); } return ret; @@ -183,7 +205,8 @@ uint8_t subghz_devices_get_lqi(const SubGhzDevice* device) { bool subghz_devices_rx_pipe_not_empty(const SubGhzDevice* device) { bool ret = false; - if(device && device->interconnect->rx_pipe_not_empty) { + furi_assert(device); + if(device->interconnect->rx_pipe_not_empty) { ret = device->interconnect->rx_pipe_not_empty(); } return ret; @@ -191,20 +214,24 @@ bool subghz_devices_rx_pipe_not_empty(const SubGhzDevice* device) { bool subghz_devices_is_rx_data_crc_valid(const SubGhzDevice* device) { bool ret = false; - if(device && device->interconnect->is_rx_data_crc_valid) { + furi_assert(device); + if(device->interconnect->is_rx_data_crc_valid) { ret = device->interconnect->is_rx_data_crc_valid(); } return ret; } void subghz_devices_read_packet(const SubGhzDevice* device, uint8_t* data, uint8_t* size) { - if(device && device->interconnect->read_packet) { + furi_assert(device); + furi_assert(device); + if(device->interconnect->read_packet) { device->interconnect->read_packet(data, size); } } void subghz_devices_write_packet(const SubGhzDevice* device, const uint8_t* data, uint8_t size) { - if(device && device->interconnect->write_packet) { + furi_assert(device); + if(device->interconnect->write_packet) { device->interconnect->write_packet(data, size); } } From 0d6e6c4d85e7b12dac3783b2b6c0a6b6ea8cb1c1 Mon Sep 17 00:00:00 2001 From: gid9798 <30450294+gid9798@users.noreply.github.com> Date: Mon, 3 Jul 2023 12:48:47 +0300 Subject: [PATCH 015/364] SubGhz: fix split h and c --- lib/subghz/devices/cc1101_configs.c | 313 ++++++++++++++++++++++++++ lib/subghz/devices/cc1101_configs.h | 334 ++-------------------------- 2 files changed, 334 insertions(+), 313 deletions(-) create mode 100644 lib/subghz/devices/cc1101_configs.c diff --git a/lib/subghz/devices/cc1101_configs.c b/lib/subghz/devices/cc1101_configs.c new file mode 100644 index 000000000..274b9c4d9 --- /dev/null +++ b/lib/subghz/devices/cc1101_configs.c @@ -0,0 +1,313 @@ +#include "cc1101_configs.h" +#include + +const uint8_t subghz_device_cc1101_preset_ook_270khz_async_regs[][2] = { + // https://e2e.ti.com/support/wireless-connectivity/sub-1-ghz-group/sub-1-ghz/f/sub-1-ghz-forum/382066/cc1101---don-t-know-the-correct-registers-configuration + + /* GPIO GD0 */ + {CC1101_IOCFG0, 0x0D}, // GD0 as async serial data output/input + + /* FIFO and internals */ + {CC1101_FIFOTHR, 0x47}, // The only important bit is ADC_RETENTION, FIFO Tx=33 Rx=32 + + /* Packet engine */ + {CC1101_PKTCTRL0, 0x32}, // Async, continious, no whitening + + /* Frequency Synthesizer Control */ + {CC1101_FSCTRL1, 0x06}, // IF = (26*10^6) / (2^10) * 0x06 = 152343.75Hz + + // Modem Configuration + {CC1101_MDMCFG0, 0x00}, // Channel spacing is 25kHz + {CC1101_MDMCFG1, 0x00}, // Channel spacing is 25kHz + {CC1101_MDMCFG2, 0x30}, // Format ASK/OOK, No preamble/sync + {CC1101_MDMCFG3, 0x32}, // Data rate is 3.79372 kBaud + {CC1101_MDMCFG4, 0x67}, // Rx BW filter is 270.833333kHz + + /* Main Radio Control State Machine */ + {CC1101_MCSM0, 0x18}, // Autocalibrate on idle-to-rx/tx, PO_TIMEOUT is 64 cycles(149-155us) + + /* Frequency Offset Compensation Configuration */ + {CC1101_FOCCFG, + 0x18}, // no frequency offset compensation, POST_K same as PRE_K, PRE_K is 4K, GATE is off + + /* Automatic Gain Control */ + {CC1101_AGCCTRL0, + 0x40}, // 01 - Low hysteresis, small asymmetric dead zone, medium gain; 00 - 8 samples agc; 00 - Normal AGC, 00 - 4dB boundary + {CC1101_AGCCTRL1, + 0x00}, // 0; 0 - LNA 2 gain is decreased to minimum before decreasing LNA gain; 00 - Relative carrier sense threshold disabled; 0000 - RSSI to MAIN_TARGET + {CC1101_AGCCTRL2, 0x03}, // 00 - DVGA all; 000 - MAX LNA+LNA2; 011 - MAIN_TARGET 24 dB + + /* Wake on radio and timeouts control */ + {CC1101_WORCTRL, 0xFB}, // WOR_RES is 2^15 periods (0.91 - 0.94 s) 16.5 - 17.2 hours + + /* Frontend configuration */ + {CC1101_FREND0, 0x11}, // Adjusts current TX LO buffer + high is PATABLE[1] + {CC1101_FREND1, 0xB6}, // + + /* End */ + {0, 0}, +}; + +const uint8_t subghz_device_cc1101_preset_ook_650khz_async_regs[][2] = { + // https://e2e.ti.com/support/wireless-connectivity/sub-1-ghz-group/sub-1-ghz/f/sub-1-ghz-forum/382066/cc1101---don-t-know-the-correct-registers-configuration + + /* GPIO GD0 */ + {CC1101_IOCFG0, 0x0D}, // GD0 as async serial data output/input + + /* FIFO and internals */ + {CC1101_FIFOTHR, 0x07}, // The only important bit is ADC_RETENTION + + /* Packet engine */ + {CC1101_PKTCTRL0, 0x32}, // Async, continious, no whitening + + /* Frequency Synthesizer Control */ + {CC1101_FSCTRL1, 0x06}, // IF = (26*10^6) / (2^10) * 0x06 = 152343.75Hz + + // Modem Configuration + {CC1101_MDMCFG0, 0x00}, // Channel spacing is 25kHz + {CC1101_MDMCFG1, 0x00}, // Channel spacing is 25kHz + {CC1101_MDMCFG2, 0x30}, // Format ASK/OOK, No preamble/sync + {CC1101_MDMCFG3, 0x32}, // Data rate is 3.79372 kBaud + {CC1101_MDMCFG4, 0x17}, // Rx BW filter is 650.000kHz + + /* Main Radio Control State Machine */ + {CC1101_MCSM0, 0x18}, // Autocalibrate on idle-to-rx/tx, PO_TIMEOUT is 64 cycles(149-155us) + + /* Frequency Offset Compensation Configuration */ + {CC1101_FOCCFG, + 0x18}, // no frequency offset compensation, POST_K same as PRE_K, PRE_K is 4K, GATE is off + + /* Automatic Gain Control */ + // {CC1101_AGCTRL0,0x40}, // 01 - Low hysteresis, small asymmetric dead zone, medium gain; 00 - 8 samples agc; 00 - Normal AGC, 00 - 4dB boundary + // {CC1101_AGCTRL1,0x00}, // 0; 0 - LNA 2 gain is decreased to minimum before decreasing LNA gain; 00 - Relative carrier sense threshold disabled; 0000 - RSSI to MAIN_TARGET + // {CC1101_AGCCTRL2, 0x03}, // 00 - DVGA all; 000 - MAX LNA+LNA2; 011 - MAIN_TARGET 24 dB + //MAGN_TARGET for RX filter BW =< 100 kHz is 0x3. For higher RX filter BW's MAGN_TARGET is 0x7. + {CC1101_AGCCTRL0, + 0x91}, // 10 - Medium hysteresis, medium asymmetric dead zone, medium gain ; 01 - 16 samples agc; 00 - Normal AGC, 01 - 8dB boundary + {CC1101_AGCCTRL1, + 0x0}, // 0; 0 - LNA 2 gain is decreased to minimum before decreasing LNA gain; 00 - Relative carrier sense threshold disabled; 0000 - RSSI to MAIN_TARGET + {CC1101_AGCCTRL2, 0x07}, // 00 - DVGA all; 000 - MAX LNA+LNA2; 111 - MAIN_TARGET 42 dB + + /* Wake on radio and timeouts control */ + {CC1101_WORCTRL, 0xFB}, // WOR_RES is 2^15 periods (0.91 - 0.94 s) 16.5 - 17.2 hours + + /* Frontend configuration */ + {CC1101_FREND0, 0x11}, // Adjusts current TX LO buffer + high is PATABLE[1] + {CC1101_FREND1, 0xB6}, // + + /* End */ + {0, 0}, +}; + +const uint8_t subghz_device_cc1101_preset_2fsk_dev2_38khz_async_regs[][2] = { + + /* GPIO GD0 */ + {CC1101_IOCFG0, 0x0D}, // GD0 as async serial data output/input + + /* Frequency Synthesizer Control */ + {CC1101_FSCTRL1, 0x06}, // IF = (26*10^6) / (2^10) * 0x06 = 152343.75Hz + + /* Packet engine */ + {CC1101_PKTCTRL0, 0x32}, // Async, continious, no whitening + {CC1101_PKTCTRL1, 0x04}, + + // // Modem Configuration + {CC1101_MDMCFG0, 0x00}, + {CC1101_MDMCFG1, 0x02}, + {CC1101_MDMCFG2, 0x04}, // Format 2-FSK/FM, No preamble/sync, Disable (current optimized) + {CC1101_MDMCFG3, 0x83}, // Data rate is 4.79794 kBaud + {CC1101_MDMCFG4, 0x67}, //Rx BW filter is 270.833333 kHz + {CC1101_DEVIATN, 0x04}, //Deviation 2.380371 kHz + + /* Main Radio Control State Machine */ + {CC1101_MCSM0, 0x18}, // Autocalibrate on idle-to-rx/tx, PO_TIMEOUT is 64 cycles(149-155us) + + /* Frequency Offset Compensation Configuration */ + {CC1101_FOCCFG, + 0x16}, // no frequency offset compensation, POST_K same as PRE_K, PRE_K is 4K, GATE is off + + /* Automatic Gain Control */ + {CC1101_AGCCTRL0, + 0x91}, //10 - Medium hysteresis, medium asymmetric dead zone, medium gain ; 01 - 16 samples agc; 00 - Normal AGC, 01 - 8dB boundary + {CC1101_AGCCTRL1, + 0x00}, // 0; 0 - LNA 2 gain is decreased to minimum before decreasing LNA gain; 00 - Relative carrier sense threshold disabled; 0000 - RSSI to MAIN_TARGET + {CC1101_AGCCTRL2, 0x07}, // 00 - DVGA all; 000 - MAX LNA+LNA2; 111 - MAIN_TARGET 42 dB + + /* Wake on radio and timeouts control */ + {CC1101_WORCTRL, 0xFB}, // WOR_RES is 2^15 periods (0.91 - 0.94 s) 16.5 - 17.2 hours + + /* Frontend configuration */ + {CC1101_FREND0, 0x10}, // Adjusts current TX LO buffer + {CC1101_FREND1, 0x56}, + + /* End */ + {0, 0}, +}; + +const uint8_t subghz_device_cc1101_preset_2fsk_dev47_6khz_async_regs[][2] = { + + /* GPIO GD0 */ + {CC1101_IOCFG0, 0x0D}, // GD0 as async serial data output/input + + /* Frequency Synthesizer Control */ + {CC1101_FSCTRL1, 0x06}, // IF = (26*10^6) / (2^10) * 0x06 = 152343.75Hz + + /* Packet engine */ + {CC1101_PKTCTRL0, 0x32}, // Async, continious, no whitening + {CC1101_PKTCTRL1, 0x04}, + + // // Modem Configuration + {CC1101_MDMCFG0, 0x00}, + {CC1101_MDMCFG1, 0x02}, + {CC1101_MDMCFG2, 0x04}, // Format 2-FSK/FM, No preamble/sync, Disable (current optimized) + {CC1101_MDMCFG3, 0x83}, // Data rate is 4.79794 kBaud + {CC1101_MDMCFG4, 0x67}, //Rx BW filter is 270.833333 kHz + {CC1101_DEVIATN, 0x47}, //Deviation 47.60742 kHz + + /* Main Radio Control State Machine */ + {CC1101_MCSM0, 0x18}, // Autocalibrate on idle-to-rx/tx, PO_TIMEOUT is 64 cycles(149-155us) + + /* Frequency Offset Compensation Configuration */ + {CC1101_FOCCFG, + 0x16}, // no frequency offset compensation, POST_K same as PRE_K, PRE_K is 4K, GATE is off + + /* Automatic Gain Control */ + {CC1101_AGCCTRL0, + 0x91}, //10 - Medium hysteresis, medium asymmetric dead zone, medium gain ; 01 - 16 samples agc; 00 - Normal AGC, 01 - 8dB boundary + {CC1101_AGCCTRL1, + 0x00}, // 0; 0 - LNA 2 gain is decreased to minimum before decreasing LNA gain; 00 - Relative carrier sense threshold disabled; 0000 - RSSI to MAIN_TARGET + {CC1101_AGCCTRL2, 0x07}, // 00 - DVGA all; 000 - MAX LNA+LNA2; 111 - MAIN_TARGET 42 dB + + /* Wake on radio and timeouts control */ + {CC1101_WORCTRL, 0xFB}, // WOR_RES is 2^15 periods (0.91 - 0.94 s) 16.5 - 17.2 hours + + /* Frontend configuration */ + {CC1101_FREND0, 0x10}, // Adjusts current TX LO buffer + {CC1101_FREND1, 0x56}, + + /* End */ + {0, 0}, +}; + +const uint8_t subghz_device_cc1101_preset_msk_99_97kb_async_regs[][2] = { + /* GPIO GD0 */ + {CC1101_IOCFG0, 0x06}, + + {CC1101_FIFOTHR, 0x07}, // The only important bit is ADC_RETENTION + {CC1101_SYNC1, 0x46}, + {CC1101_SYNC0, 0x4C}, + {CC1101_ADDR, 0x00}, + {CC1101_PKTLEN, 0x00}, + {CC1101_CHANNR, 0x00}, + + {CC1101_PKTCTRL0, 0x05}, + + {CC1101_FSCTRL0, 0x23}, + {CC1101_FSCTRL1, 0x06}, + + {CC1101_MDMCFG0, 0xF8}, + {CC1101_MDMCFG1, 0x22}, + {CC1101_MDMCFG2, 0x72}, + {CC1101_MDMCFG3, 0xF8}, + {CC1101_MDMCFG4, 0x5B}, + {CC1101_DEVIATN, 0x47}, + + {CC1101_MCSM0, 0x18}, + {CC1101_FOCCFG, 0x16}, + + {CC1101_AGCCTRL0, 0xB2}, + {CC1101_AGCCTRL1, 0x00}, + {CC1101_AGCCTRL2, 0xC7}, + + {CC1101_FREND0, 0x10}, + {CC1101_FREND1, 0x56}, + + {CC1101_BSCFG, 0x1C}, + {CC1101_FSTEST, 0x59}, + + /* End */ + {0, 0}, +}; + +const uint8_t subghz_device_cc1101_preset_gfsk_9_99kb_async_regs[][2] = { + + {CC1101_IOCFG0, 0x06}, //GDO0 Output Pin Configuration + {CC1101_FIFOTHR, 0x47}, //RX FIFO and TX FIFO Thresholds + + //1 : CRC calculation in TX and CRC check in RX enabled, + //1 : Variable packet length mode. Packet length configured by the first byte after sync word + {CC1101_PKTCTRL0, 0x05}, + + {CC1101_FSCTRL1, 0x06}, //Frequency Synthesizer Control + + {CC1101_SYNC1, 0x46}, + {CC1101_SYNC0, 0x4C}, + {CC1101_ADDR, 0x00}, + {CC1101_PKTLEN, 0x00}, + + {CC1101_MDMCFG4, 0xC8}, //Modem Configuration 9.99 + {CC1101_MDMCFG3, 0x93}, //Modem Configuration + {CC1101_MDMCFG2, 0x12}, // 2: 16/16 sync word bits detected + + {CC1101_DEVIATN, 0x34}, //Deviation = 19.042969 + {CC1101_MCSM0, 0x18}, //Main Radio Control State Machine Configuration + {CC1101_FOCCFG, 0x16}, //Frequency Offset Compensation Configuration + + {CC1101_AGCCTRL2, 0x43}, //AGC Control + {CC1101_AGCCTRL1, 0x40}, + {CC1101_AGCCTRL0, 0x91}, + + {CC1101_WORCTRL, 0xFB}, //Wake On Radio Control + /* End */ + {0, 0}, +}; + +const uint8_t subghz_device_cc1101_preset_ook_async_patable[8] = { + 0x00, + 0xC0, // 12dBm 0xC0, 10dBm 0xC5, 7dBm 0xCD, 5dBm 0x86, 0dBm 0x50, -6dBm 0x37, -10dBm 0x26, -15dBm 0x1D, -20dBm 0x17, -30dBm 0x03 + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00}; + +const uint8_t subghz_device_cc1101_preset_ook_async_patable_au[8] = { + 0x00, + 0x37, // 12dBm 0xC0, 10dBm 0xC5, 7dBm 0xCD, 5dBm 0x86, 0dBm 0x50, -6dBm 0x37, -10dBm 0x26, -15dBm 0x1D, -20dBm 0x17, -30dBm 0x03 + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00}; + +const uint8_t subghz_device_cc1101_preset_2fsk_async_patable[8] = { + 0xC0, // 10dBm 0xC0, 7dBm 0xC8, 5dBm 0x84, 0dBm 0x60, -10dBm 0x34, -15dBm 0x1D, -20dBm 0x0E, -30dBm 0x12 + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00}; + +const uint8_t subghz_device_cc1101_preset_msk_async_patable[8] = { + 0xC0, // 10dBm 0xC0, 7dBm 0xC8, 5dBm 0x84, 0dBm 0x60, -10dBm 0x34, -15dBm 0x1D, -20dBm 0x0E, -30dBm 0x12 + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00}; + +const uint8_t subghz_device_cc1101_preset_gfsk_async_patable[8] = { + 0xC0, // 10dBm 0xC0, 7dBm 0xC8, 5dBm 0x84, 0dBm 0x60, -10dBm 0x34, -15dBm 0x1D, -20dBm 0x0E, -30dBm 0x12 + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00}; \ No newline at end of file diff --git a/lib/subghz/devices/cc1101_configs.h b/lib/subghz/devices/cc1101_configs.h index 6c262e682..302ac778f 100644 --- a/lib/subghz/devices/cc1101_configs.h +++ b/lib/subghz/devices/cc1101_configs.h @@ -1,314 +1,22 @@ #pragma once - -#include - -static const uint8_t subghz_device_cc1101_preset_ook_270khz_async_regs[][2] = { - // https://e2e.ti.com/support/wireless-connectivity/sub-1-ghz-group/sub-1-ghz/f/sub-1-ghz-forum/382066/cc1101---don-t-know-the-correct-registers-configuration - - /* GPIO GD0 */ - {CC1101_IOCFG0, 0x0D}, // GD0 as async serial data output/input - - /* FIFO and internals */ - {CC1101_FIFOTHR, 0x47}, // The only important bit is ADC_RETENTION, FIFO Tx=33 Rx=32 - - /* Packet engine */ - {CC1101_PKTCTRL0, 0x32}, // Async, continious, no whitening - - /* Frequency Synthesizer Control */ - {CC1101_FSCTRL1, 0x06}, // IF = (26*10^6) / (2^10) * 0x06 = 152343.75Hz - - // Modem Configuration - {CC1101_MDMCFG0, 0x00}, // Channel spacing is 25kHz - {CC1101_MDMCFG1, 0x00}, // Channel spacing is 25kHz - {CC1101_MDMCFG2, 0x30}, // Format ASK/OOK, No preamble/sync - {CC1101_MDMCFG3, 0x32}, // Data rate is 3.79372 kBaud - {CC1101_MDMCFG4, 0x67}, // Rx BW filter is 270.833333kHz - - /* Main Radio Control State Machine */ - {CC1101_MCSM0, 0x18}, // Autocalibrate on idle-to-rx/tx, PO_TIMEOUT is 64 cycles(149-155us) - - /* Frequency Offset Compensation Configuration */ - {CC1101_FOCCFG, - 0x18}, // no frequency offset compensation, POST_K same as PRE_K, PRE_K is 4K, GATE is off - - /* Automatic Gain Control */ - {CC1101_AGCCTRL0, - 0x40}, // 01 - Low hysteresis, small asymmetric dead zone, medium gain; 00 - 8 samples agc; 00 - Normal AGC, 00 - 4dB boundary - {CC1101_AGCCTRL1, - 0x00}, // 0; 0 - LNA 2 gain is decreased to minimum before decreasing LNA gain; 00 - Relative carrier sense threshold disabled; 0000 - RSSI to MAIN_TARGET - {CC1101_AGCCTRL2, 0x03}, // 00 - DVGA all; 000 - MAX LNA+LNA2; 011 - MAIN_TARGET 24 dB - - /* Wake on radio and timeouts control */ - {CC1101_WORCTRL, 0xFB}, // WOR_RES is 2^15 periods (0.91 - 0.94 s) 16.5 - 17.2 hours - - /* Frontend configuration */ - {CC1101_FREND0, 0x11}, // Adjusts current TX LO buffer + high is PATABLE[1] - {CC1101_FREND1, 0xB6}, // - - /* End */ - {0, 0}, -}; - -static const uint8_t subghz_device_cc1101_preset_ook_650khz_async_regs[][2] = { - // https://e2e.ti.com/support/wireless-connectivity/sub-1-ghz-group/sub-1-ghz/f/sub-1-ghz-forum/382066/cc1101---don-t-know-the-correct-registers-configuration - - /* GPIO GD0 */ - {CC1101_IOCFG0, 0x0D}, // GD0 as async serial data output/input - - /* FIFO and internals */ - {CC1101_FIFOTHR, 0x07}, // The only important bit is ADC_RETENTION - - /* Packet engine */ - {CC1101_PKTCTRL0, 0x32}, // Async, continious, no whitening - - /* Frequency Synthesizer Control */ - {CC1101_FSCTRL1, 0x06}, // IF = (26*10^6) / (2^10) * 0x06 = 152343.75Hz - - // Modem Configuration - {CC1101_MDMCFG0, 0x00}, // Channel spacing is 25kHz - {CC1101_MDMCFG1, 0x00}, // Channel spacing is 25kHz - {CC1101_MDMCFG2, 0x30}, // Format ASK/OOK, No preamble/sync - {CC1101_MDMCFG3, 0x32}, // Data rate is 3.79372 kBaud - {CC1101_MDMCFG4, 0x17}, // Rx BW filter is 650.000kHz - - /* Main Radio Control State Machine */ - {CC1101_MCSM0, 0x18}, // Autocalibrate on idle-to-rx/tx, PO_TIMEOUT is 64 cycles(149-155us) - - /* Frequency Offset Compensation Configuration */ - {CC1101_FOCCFG, - 0x18}, // no frequency offset compensation, POST_K same as PRE_K, PRE_K is 4K, GATE is off - - /* Automatic Gain Control */ - // {CC1101_AGCTRL0,0x40}, // 01 - Low hysteresis, small asymmetric dead zone, medium gain; 00 - 8 samples agc; 00 - Normal AGC, 00 - 4dB boundary - // {CC1101_AGCTRL1,0x00}, // 0; 0 - LNA 2 gain is decreased to minimum before decreasing LNA gain; 00 - Relative carrier sense threshold disabled; 0000 - RSSI to MAIN_TARGET - // {CC1101_AGCCTRL2, 0x03}, // 00 - DVGA all; 000 - MAX LNA+LNA2; 011 - MAIN_TARGET 24 dB - //MAGN_TARGET for RX filter BW =< 100 kHz is 0x3. For higher RX filter BW's MAGN_TARGET is 0x7. - {CC1101_AGCCTRL0, - 0x91}, // 10 - Medium hysteresis, medium asymmetric dead zone, medium gain ; 01 - 16 samples agc; 00 - Normal AGC, 01 - 8dB boundary - {CC1101_AGCCTRL1, - 0x0}, // 0; 0 - LNA 2 gain is decreased to minimum before decreasing LNA gain; 00 - Relative carrier sense threshold disabled; 0000 - RSSI to MAIN_TARGET - {CC1101_AGCCTRL2, 0x07}, // 00 - DVGA all; 000 - MAX LNA+LNA2; 111 - MAIN_TARGET 42 dB - - /* Wake on radio and timeouts control */ - {CC1101_WORCTRL, 0xFB}, // WOR_RES is 2^15 periods (0.91 - 0.94 s) 16.5 - 17.2 hours - - /* Frontend configuration */ - {CC1101_FREND0, 0x11}, // Adjusts current TX LO buffer + high is PATABLE[1] - {CC1101_FREND1, 0xB6}, // - - /* End */ - {0, 0}, -}; - -static const uint8_t subghz_device_cc1101_preset_2fsk_dev2_38khz_async_regs[][2] = { - - /* GPIO GD0 */ - {CC1101_IOCFG0, 0x0D}, // GD0 as async serial data output/input - - /* Frequency Synthesizer Control */ - {CC1101_FSCTRL1, 0x06}, // IF = (26*10^6) / (2^10) * 0x06 = 152343.75Hz - - /* Packet engine */ - {CC1101_PKTCTRL0, 0x32}, // Async, continious, no whitening - {CC1101_PKTCTRL1, 0x04}, - - // // Modem Configuration - {CC1101_MDMCFG0, 0x00}, - {CC1101_MDMCFG1, 0x02}, - {CC1101_MDMCFG2, 0x04}, // Format 2-FSK/FM, No preamble/sync, Disable (current optimized) - {CC1101_MDMCFG3, 0x83}, // Data rate is 4.79794 kBaud - {CC1101_MDMCFG4, 0x67}, //Rx BW filter is 270.833333 kHz - {CC1101_DEVIATN, 0x04}, //Deviation 2.380371 kHz - - /* Main Radio Control State Machine */ - {CC1101_MCSM0, 0x18}, // Autocalibrate on idle-to-rx/tx, PO_TIMEOUT is 64 cycles(149-155us) - - /* Frequency Offset Compensation Configuration */ - {CC1101_FOCCFG, - 0x16}, // no frequency offset compensation, POST_K same as PRE_K, PRE_K is 4K, GATE is off - - /* Automatic Gain Control */ - {CC1101_AGCCTRL0, - 0x91}, //10 - Medium hysteresis, medium asymmetric dead zone, medium gain ; 01 - 16 samples agc; 00 - Normal AGC, 01 - 8dB boundary - {CC1101_AGCCTRL1, - 0x00}, // 0; 0 - LNA 2 gain is decreased to minimum before decreasing LNA gain; 00 - Relative carrier sense threshold disabled; 0000 - RSSI to MAIN_TARGET - {CC1101_AGCCTRL2, 0x07}, // 00 - DVGA all; 000 - MAX LNA+LNA2; 111 - MAIN_TARGET 42 dB - - /* Wake on radio and timeouts control */ - {CC1101_WORCTRL, 0xFB}, // WOR_RES is 2^15 periods (0.91 - 0.94 s) 16.5 - 17.2 hours - - /* Frontend configuration */ - {CC1101_FREND0, 0x10}, // Adjusts current TX LO buffer - {CC1101_FREND1, 0x56}, - - /* End */ - {0, 0}, -}; - -static const uint8_t subghz_device_cc1101_preset_2fsk_dev47_6khz_async_regs[][2] = { - - /* GPIO GD0 */ - {CC1101_IOCFG0, 0x0D}, // GD0 as async serial data output/input - - /* Frequency Synthesizer Control */ - {CC1101_FSCTRL1, 0x06}, // IF = (26*10^6) / (2^10) * 0x06 = 152343.75Hz - - /* Packet engine */ - {CC1101_PKTCTRL0, 0x32}, // Async, continious, no whitening - {CC1101_PKTCTRL1, 0x04}, - - // // Modem Configuration - {CC1101_MDMCFG0, 0x00}, - {CC1101_MDMCFG1, 0x02}, - {CC1101_MDMCFG2, 0x04}, // Format 2-FSK/FM, No preamble/sync, Disable (current optimized) - {CC1101_MDMCFG3, 0x83}, // Data rate is 4.79794 kBaud - {CC1101_MDMCFG4, 0x67}, //Rx BW filter is 270.833333 kHz - {CC1101_DEVIATN, 0x47}, //Deviation 47.60742 kHz - - /* Main Radio Control State Machine */ - {CC1101_MCSM0, 0x18}, // Autocalibrate on idle-to-rx/tx, PO_TIMEOUT is 64 cycles(149-155us) - - /* Frequency Offset Compensation Configuration */ - {CC1101_FOCCFG, - 0x16}, // no frequency offset compensation, POST_K same as PRE_K, PRE_K is 4K, GATE is off - - /* Automatic Gain Control */ - {CC1101_AGCCTRL0, - 0x91}, //10 - Medium hysteresis, medium asymmetric dead zone, medium gain ; 01 - 16 samples agc; 00 - Normal AGC, 01 - 8dB boundary - {CC1101_AGCCTRL1, - 0x00}, // 0; 0 - LNA 2 gain is decreased to minimum before decreasing LNA gain; 00 - Relative carrier sense threshold disabled; 0000 - RSSI to MAIN_TARGET - {CC1101_AGCCTRL2, 0x07}, // 00 - DVGA all; 000 - MAX LNA+LNA2; 111 - MAIN_TARGET 42 dB - - /* Wake on radio and timeouts control */ - {CC1101_WORCTRL, 0xFB}, // WOR_RES is 2^15 periods (0.91 - 0.94 s) 16.5 - 17.2 hours - - /* Frontend configuration */ - {CC1101_FREND0, 0x10}, // Adjusts current TX LO buffer - {CC1101_FREND1, 0x56}, - - /* End */ - {0, 0}, -}; - -static const uint8_t subghz_device_cc1101_preset_msk_99_97kb_async_regs[][2] = { - /* GPIO GD0 */ - {CC1101_IOCFG0, 0x06}, - - {CC1101_FIFOTHR, 0x07}, // The only important bit is ADC_RETENTION - {CC1101_SYNC1, 0x46}, - {CC1101_SYNC0, 0x4C}, - {CC1101_ADDR, 0x00}, - {CC1101_PKTLEN, 0x00}, - {CC1101_CHANNR, 0x00}, - - {CC1101_PKTCTRL0, 0x05}, - - {CC1101_FSCTRL0, 0x23}, - {CC1101_FSCTRL1, 0x06}, - - {CC1101_MDMCFG0, 0xF8}, - {CC1101_MDMCFG1, 0x22}, - {CC1101_MDMCFG2, 0x72}, - {CC1101_MDMCFG3, 0xF8}, - {CC1101_MDMCFG4, 0x5B}, - {CC1101_DEVIATN, 0x47}, - - {CC1101_MCSM0, 0x18}, - {CC1101_FOCCFG, 0x16}, - - {CC1101_AGCCTRL0, 0xB2}, - {CC1101_AGCCTRL1, 0x00}, - {CC1101_AGCCTRL2, 0xC7}, - - {CC1101_FREND0, 0x10}, - {CC1101_FREND1, 0x56}, - - {CC1101_BSCFG, 0x1C}, - {CC1101_FSTEST, 0x59}, - - /* End */ - {0, 0}, -}; - -static const uint8_t subghz_device_cc1101_preset_gfsk_9_99kb_async_regs[][2] = { - - {CC1101_IOCFG0, 0x06}, //GDO0 Output Pin Configuration - {CC1101_FIFOTHR, 0x47}, //RX FIFO and TX FIFO Thresholds - - //1 : CRC calculation in TX and CRC check in RX enabled, - //1 : Variable packet length mode. Packet length configured by the first byte after sync word - {CC1101_PKTCTRL0, 0x05}, - - {CC1101_FSCTRL1, 0x06}, //Frequency Synthesizer Control - - {CC1101_SYNC1, 0x46}, - {CC1101_SYNC0, 0x4C}, - {CC1101_ADDR, 0x00}, - {CC1101_PKTLEN, 0x00}, - - {CC1101_MDMCFG4, 0xC8}, //Modem Configuration 9.99 - {CC1101_MDMCFG3, 0x93}, //Modem Configuration - {CC1101_MDMCFG2, 0x12}, // 2: 16/16 sync word bits detected - - {CC1101_DEVIATN, 0x34}, //Deviation = 19.042969 - {CC1101_MCSM0, 0x18}, //Main Radio Control State Machine Configuration - {CC1101_FOCCFG, 0x16}, //Frequency Offset Compensation Configuration - - {CC1101_AGCCTRL2, 0x43}, //AGC Control - {CC1101_AGCCTRL1, 0x40}, - {CC1101_AGCCTRL0, 0x91}, - - {CC1101_WORCTRL, 0xFB}, //Wake On Radio Control - /* End */ - {0, 0}, -}; - -static const uint8_t subghz_device_cc1101_preset_ook_async_patable[8] = { - 0x00, - 0xC0, // 12dBm 0xC0, 10dBm 0xC5, 7dBm 0xCD, 5dBm 0x86, 0dBm 0x50, -6dBm 0x37, -10dBm 0x26, -15dBm 0x1D, -20dBm 0x17, -30dBm 0x03 - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00}; - -static const uint8_t subghz_device_cc1101_preset_ook_async_patable_au[8] = { - 0x00, - 0x37, // 12dBm 0xC0, 10dBm 0xC5, 7dBm 0xCD, 5dBm 0x86, 0dBm 0x50, -6dBm 0x37, -10dBm 0x26, -15dBm 0x1D, -20dBm 0x17, -30dBm 0x03 - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00}; - -static const uint8_t subghz_device_cc1101_preset_2fsk_async_patable[8] = { - 0xC0, // 10dBm 0xC0, 7dBm 0xC8, 5dBm 0x84, 0dBm 0x60, -10dBm 0x34, -15dBm 0x1D, -20dBm 0x0E, -30dBm 0x12 - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00}; - -static const uint8_t subghz_device_cc1101_preset_msk_async_patable[8] = { - 0xC0, // 10dBm 0xC0, 7dBm 0xC8, 5dBm 0x84, 0dBm 0x60, -10dBm 0x34, -15dBm 0x1D, -20dBm 0x0E, -30dBm 0x12 - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00}; - -static const uint8_t subghz_device_cc1101_preset_gfsk_async_patable[8] = { - 0xC0, // 10dBm 0xC0, 7dBm 0xC8, 5dBm 0x84, 0dBm 0x60, -10dBm 0x34, -15dBm 0x1D, -20dBm 0x0E, -30dBm 0x12 - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00}; +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern const uint8_t subghz_device_cc1101_preset_ook_270khz_async_regs[][2]; +extern const uint8_t subghz_device_cc1101_preset_ook_650khz_async_regs[][2]; +extern const uint8_t subghz_device_cc1101_preset_2fsk_dev2_38khz_async_regs[][2]; +extern const uint8_t subghz_device_cc1101_preset_2fsk_dev47_6khz_async_regs[][2]; +extern const uint8_t subghz_device_cc1101_preset_msk_99_97kb_async_regs[][2]; +extern const uint8_t subghz_device_cc1101_preset_gfsk_9_99kb_async_regs[][2]; +extern const uint8_t subghz_device_cc1101_preset_ook_async_patable[8]; +extern const uint8_t subghz_device_cc1101_preset_ook_async_patable_au[8]; +extern const uint8_t subghz_device_cc1101_preset_2fsk_async_patable[8]; +extern const uint8_t subghz_device_cc1101_preset_msk_async_patable[8]; +extern const uint8_t subghz_device_cc1101_preset_gfsk_async_patable[8]; + +#ifdef __cplusplus +} +#endif \ No newline at end of file From 01d7beef4e40999e1a096c3308873243e2565f58 Mon Sep 17 00:00:00 2001 From: gid9798 <30450294+gid9798@users.noreply.github.com> Date: Mon, 3 Jul 2023 13:36:26 +0300 Subject: [PATCH 016/364] SubGhz: furi_hal_subghz remove preset load function by name --- .../debug/unit_tests/subghz/subghz_test.c | 2 +- .../drivers/subghz/cc1101_ext/cc1101_ext.c | 37 - .../drivers/subghz/cc1101_ext/cc1101_ext.h | 6 - .../cc1101_ext/cc1101_ext_interconnect.c | 31 +- .../main/subghz/helpers/subghz_txrx.c | 5 +- applications/main/subghz/subghz_cli.c | 10 +- .../main/subghz/views/subghz_test_carrier.c | 4 +- .../main/subghz/views/subghz_test_packet.c | 4 +- .../main/subghz/views/subghz_test_static.c | 4 +- firmware/targets/f7/api_symbols.csv | 10 +- .../targets/f7/furi_hal/furi_hal_subghz.c | 37 - .../targets/f7/furi_hal/furi_hal_subghz.h | 6 - lib/subghz/SConscript | 1 + lib/subghz/devices/cc1101_configs.c | 679 +++++++++++------- lib/subghz/devices/cc1101_configs.h | 23 +- .../cc1101_int/cc1101_int_interconnect.c | 37 +- lib/subghz/subghz_setting.c | 29 +- 17 files changed, 529 insertions(+), 396 deletions(-) diff --git a/applications/debug/unit_tests/subghz/subghz_test.c b/applications/debug/unit_tests/subghz/subghz_test.c index c49aa2250..b84baf26d 100644 --- a/applications/debug/unit_tests/subghz/subghz_test.c +++ b/applications/debug/unit_tests/subghz/subghz_test.c @@ -322,7 +322,7 @@ bool subghz_hal_async_tx_test_run(SubGhzHalAsyncTxTestType type) { SubGhzHalAsyncTxTest test = {0}; test.type = type; furi_hal_subghz_reset(); - furi_hal_subghz_load_preset(FuriHalSubGhzPresetOok650Async); + furi_hal_subghz_load_custom_preset(subghz_device_cc1101_preset_ook_650khz_async_regs); furi_hal_subghz_set_frequency_and_path(433920000); if(!furi_hal_subghz_start_async_tx(subghz_hal_async_tx_test_yield, &test)) { diff --git a/applications/drivers/subghz/cc1101_ext/cc1101_ext.c b/applications/drivers/subghz/cc1101_ext/cc1101_ext.c index acce0989d..b002cb32d 100644 --- a/applications/drivers/subghz/cc1101_ext/cc1101_ext.c +++ b/applications/drivers/subghz/cc1101_ext/cc1101_ext.c @@ -73,7 +73,6 @@ typedef struct { typedef struct { volatile SubGhzDeviceCC1101ExtState state; volatile SubGhzDeviceCC1101ExtRegulation regulation; - volatile FuriHalSubGhzPreset preset; const GpioPin* async_mirror_pin; FuriHalSpiBusHandle* spi_bus_handle; const GpioPin* g0_pin; @@ -86,7 +85,6 @@ static SubGhzDeviceCC1101Ext* subghz_device_cc1101_ext = NULL; static bool subghz_device_cc1101_ext_check_init() { furi_assert(subghz_device_cc1101_ext->state == SubGhzDeviceCC1101ExtStateInit); subghz_device_cc1101_ext->state = SubGhzDeviceCC1101ExtStateIdle; - subghz_device_cc1101_ext->preset = FuriHalSubGhzPresetIDLE; bool ret = false; @@ -163,7 +161,6 @@ bool subghz_device_cc1101_ext_alloc() { subghz_device_cc1101_ext = malloc(sizeof(SubGhzDeviceCC1101Ext)); subghz_device_cc1101_ext->state = SubGhzDeviceCC1101ExtStateInit; subghz_device_cc1101_ext->regulation = SubGhzDeviceCC1101ExtRegulationTxRx; - subghz_device_cc1101_ext->preset = FuriHalSubGhzPresetIDLE; subghz_device_cc1101_ext->async_mirror_pin = NULL; subghz_device_cc1101_ext->spi_bus_handle = &furi_hal_spi_bus_handle_external; subghz_device_cc1101_ext->g0_pin = SUBGHZ_DEVICE_CC1101_EXT_TX_GPIO; @@ -218,8 +215,6 @@ void subghz_device_cc1101_ext_sleep() { cc1101_shutdown(subghz_device_cc1101_ext->spi_bus_handle); furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); - - subghz_device_cc1101_ext->preset = FuriHalSubGhzPresetIDLE; } void subghz_device_cc1101_ext_dump_state() { @@ -231,37 +226,6 @@ void subghz_device_cc1101_ext_dump_state() { furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); } -void subghz_device_cc1101_ext_load_preset(FuriHalSubGhzPreset preset) { - if(preset == FuriHalSubGhzPresetOok650Async) { - subghz_device_cc1101_ext_load_registers( - (uint8_t*)subghz_device_cc1101_preset_ook_650khz_async_regs); - subghz_device_cc1101_ext_load_patable(subghz_device_cc1101_preset_ook_async_patable); - } else if(preset == FuriHalSubGhzPresetOok270Async) { - subghz_device_cc1101_ext_load_registers( - (uint8_t*)subghz_device_cc1101_preset_ook_270khz_async_regs); - subghz_device_cc1101_ext_load_patable(subghz_device_cc1101_preset_ook_async_patable); - } else if(preset == FuriHalSubGhzPreset2FSKDev238Async) { - subghz_device_cc1101_ext_load_registers( - (uint8_t*)subghz_device_cc1101_preset_2fsk_dev2_38khz_async_regs); - subghz_device_cc1101_ext_load_patable(subghz_device_cc1101_preset_2fsk_async_patable); - } else if(preset == FuriHalSubGhzPreset2FSKDev476Async) { - subghz_device_cc1101_ext_load_registers( - (uint8_t*)subghz_device_cc1101_preset_2fsk_dev47_6khz_async_regs); - subghz_device_cc1101_ext_load_patable(subghz_device_cc1101_preset_2fsk_async_patable); - } else if(preset == FuriHalSubGhzPresetMSK99_97KbAsync) { - subghz_device_cc1101_ext_load_registers( - (uint8_t*)subghz_device_cc1101_preset_msk_99_97kb_async_regs); - subghz_device_cc1101_ext_load_patable(subghz_device_cc1101_preset_msk_async_patable); - } else if(preset == FuriHalSubGhzPresetGFSK9_99KbAsync) { - subghz_device_cc1101_ext_load_registers( - (uint8_t*)subghz_device_cc1101_preset_gfsk_9_99kb_async_regs); - subghz_device_cc1101_ext_load_patable(subghz_device_cc1101_preset_gfsk_async_patable); - } else { - furi_crash("SubGhz: Missing config."); - } - subghz_device_cc1101_ext->preset = preset; -} - void subghz_device_cc1101_ext_load_custom_preset(uint8_t* preset_data) { //load config furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle); @@ -278,7 +242,6 @@ void subghz_device_cc1101_ext_load_custom_preset(uint8_t* preset_data) { //load pa table memcpy(&pa[0], &preset_data[i + 2], 8); subghz_device_cc1101_ext_load_patable(pa); - subghz_device_cc1101_ext->preset = FuriHalSubGhzPresetCustom; //show debug if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { diff --git a/applications/drivers/subghz/cc1101_ext/cc1101_ext.h b/applications/drivers/subghz/cc1101_ext/cc1101_ext.h index 4fdfa5192..6d91373ad 100644 --- a/applications/drivers/subghz/cc1101_ext/cc1101_ext.h +++ b/applications/drivers/subghz/cc1101_ext/cc1101_ext.h @@ -54,12 +54,6 @@ void subghz_device_cc1101_ext_sleep(); */ void subghz_device_cc1101_ext_dump_state(); -/** Load registers from preset by preset name - * - * @param preset to load - */ -void subghz_device_cc1101_ext_load_preset(FuriHalSubGhzPreset preset); - /** Load custom registers from preset * * @param preset_data registers to load diff --git a/applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.c b/applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.c index b087d4d53..15c1686a7 100644 --- a/applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.c +++ b/applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.c @@ -1,5 +1,6 @@ #include "cc1101_ext_interconnect.h" #include "cc1101_ext.h" +#include #define TAG "SubGhzDeviceCC1101Ext" @@ -29,9 +30,33 @@ static void subghz_device_cc1101_ext_interconnect_start_async_rx(void* callback, static void subghz_device_cc1101_ext_interconnect_load_preset( FuriHalSubGhzPreset preset, uint8_t* preset_data) { - if(preset != FuriHalSubGhzPresetCustom) { - subghz_device_cc1101_ext_load_preset(preset); - } else { + switch(preset) { + case FuriHalSubGhzPresetOok650Async: + subghz_device_cc1101_ext_load_custom_preset( + (uint8_t*)subghz_device_cc1101_preset_ook_650khz_async_regs); + break; + case FuriHalSubGhzPresetOok270Async: + subghz_device_cc1101_ext_load_custom_preset( + (uint8_t*)subghz_device_cc1101_preset_ook_270khz_async_regs); + break; + case FuriHalSubGhzPreset2FSKDev238Async: + subghz_device_cc1101_ext_load_custom_preset( + (uint8_t*)subghz_device_cc1101_preset_2fsk_dev2_38khz_async_regs); + break; + case FuriHalSubGhzPreset2FSKDev476Async: + subghz_device_cc1101_ext_load_custom_preset( + (uint8_t*)subghz_device_cc1101_preset_2fsk_dev47_6khz_async_regs); + break; + case FuriHalSubGhzPresetMSK99_97KbAsync: + subghz_device_cc1101_ext_load_custom_preset( + (uint8_t*)subghz_device_cc1101_preset_msk_99_97kb_async_regs); + break; + case FuriHalSubGhzPresetGFSK9_99KbAsync: + subghz_device_cc1101_ext_load_custom_preset( + (uint8_t*)subghz_device_cc1101_preset_gfsk_9_99kb_async_regs); + break; + + default: subghz_device_cc1101_ext_load_custom_preset(preset_data); } } diff --git a/applications/main/subghz/helpers/subghz_txrx.c b/applications/main/subghz/helpers/subghz_txrx.c index c678965c1..d878c0e04 100644 --- a/applications/main/subghz/helpers/subghz_txrx.c +++ b/applications/main/subghz/helpers/subghz_txrx.c @@ -64,6 +64,7 @@ SubGhzTxRx* subghz_txrx_alloc() { //set default device External subghz_devices_init(); + instance->radio_device_type = SubGhzRadioDeviceTypeInternal; instance->radio_device_type = subghz_txrx_radio_device_set(instance, SubGhzRadioDeviceTypeExternalCC1101); @@ -604,9 +605,7 @@ SubGhzRadioDeviceType } else { subghz_txrx_radio_device_power_off(instance); if(instance->radio_device_type != SubGhzRadioDeviceTypeInternal) { - if(instance->radio_device) { - subghz_devices_end(instance->radio_device); - } + subghz_devices_end(instance->radio_device); } instance->radio_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME); instance->radio_device_type = SubGhzRadioDeviceTypeInternal; diff --git a/applications/main/subghz/subghz_cli.c b/applications/main/subghz/subghz_cli.c index 810729dab..efb191ce4 100644 --- a/applications/main/subghz/subghz_cli.c +++ b/applications/main/subghz/subghz_cli.c @@ -13,6 +13,7 @@ #include #include #include +#include #include "helpers/subghz_chat.h" @@ -60,7 +61,8 @@ void subghz_cli_command_tx_carrier(Cli* cli, FuriString* args, void* context) { } furi_hal_subghz_reset(); - furi_hal_subghz_load_preset(FuriHalSubGhzPresetOok650Async); + furi_hal_subghz_load_custom_preset( + (uint8_t*)subghz_device_cc1101_preset_ook_650khz_async_regs); frequency = furi_hal_subghz_set_frequency_and_path(frequency); furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); @@ -104,7 +106,8 @@ void subghz_cli_command_rx_carrier(Cli* cli, FuriString* args, void* context) { } furi_hal_subghz_reset(); - furi_hal_subghz_load_preset(FuriHalSubGhzPresetOok650Async); + furi_hal_subghz_load_custom_preset( + (uint8_t*)subghz_device_cc1101_preset_ook_650khz_async_regs); frequency = furi_hal_subghz_set_frequency_and_path(frequency); printf("Receiving at frequency %lu Hz\r\n", frequency); printf("Press CTRL+C to stop\r\n"); @@ -400,7 +403,8 @@ void subghz_cli_command_rx_raw(Cli* cli, FuriString* args, void* context) { // Configure radio furi_hal_subghz_reset(); - furi_hal_subghz_load_preset(FuriHalSubGhzPresetOok270Async); + furi_hal_subghz_load_custom_preset( + (uint8_t*)subghz_device_cc1101_preset_ook_650khz_async_regs); frequency = furi_hal_subghz_set_frequency_and_path(frequency); furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow); diff --git a/applications/main/subghz/views/subghz_test_carrier.c b/applications/main/subghz/views/subghz_test_carrier.c index 3815e8ff0..6017dd237 100644 --- a/applications/main/subghz/views/subghz_test_carrier.c +++ b/applications/main/subghz/views/subghz_test_carrier.c @@ -1,6 +1,7 @@ #include "subghz_test_carrier.h" #include "../subghz_i.h" #include "../helpers/subghz_testing.h" +#include #include #include @@ -140,7 +141,8 @@ void subghz_test_carrier_enter(void* context) { SubGhzTestCarrier* subghz_test_carrier = context; furi_hal_subghz_reset(); - furi_hal_subghz_load_preset(FuriHalSubGhzPresetOok650Async); + furi_hal_subghz_load_custom_preset( + (uint8_t*)subghz_device_cc1101_preset_ook_650khz_async_regs); furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow); diff --git a/applications/main/subghz/views/subghz_test_packet.c b/applications/main/subghz/views/subghz_test_packet.c index 43502180c..16e33d121 100644 --- a/applications/main/subghz/views/subghz_test_packet.c +++ b/applications/main/subghz/views/subghz_test_packet.c @@ -1,6 +1,7 @@ #include "subghz_test_packet.h" #include "../subghz_i.h" #include "../helpers/subghz_testing.h" +#include #include #include @@ -194,7 +195,8 @@ void subghz_test_packet_enter(void* context) { SubGhzTestPacket* instance = context; furi_hal_subghz_reset(); - furi_hal_subghz_load_preset(FuriHalSubGhzPresetOok650Async); + furi_hal_subghz_load_custom_preset( + (uint8_t*)subghz_device_cc1101_preset_ook_650khz_async_regs); with_view_model( instance->view, diff --git a/applications/main/subghz/views/subghz_test_static.c b/applications/main/subghz/views/subghz_test_static.c index 815d0ff9b..e63cb3576 100644 --- a/applications/main/subghz/views/subghz_test_static.c +++ b/applications/main/subghz/views/subghz_test_static.c @@ -1,6 +1,7 @@ #include "subghz_test_static.h" #include "../subghz_i.h" #include "../helpers/subghz_testing.h" +#include #include #include @@ -143,7 +144,8 @@ void subghz_test_static_enter(void* context) { SubGhzTestStatic* instance = context; furi_hal_subghz_reset(); - furi_hal_subghz_load_preset(FuriHalSubGhzPresetOok650Async); + furi_hal_subghz_load_custom_preset( + (uint8_t*)subghz_device_cc1101_preset_ook_650khz_async_regs); furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); furi_hal_gpio_write(&gpio_cc1101_g0, false); diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index b042719b3..636441a64 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,32.0,, +Version,+,33.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -185,6 +185,7 @@ Header,+,lib/subghz/blocks/decoder.h,, Header,+,lib/subghz/blocks/encoder.h,, Header,+,lib/subghz/blocks/generic.h,, Header,+,lib/subghz/blocks/math.h,, +Header,+,lib/subghz/devices/cc1101_configs.h,, Header,+,lib/subghz/environment.h,, Header,+,lib/subghz/protocols/raw.h,, Header,+,lib/subghz/receiver.h,, @@ -1398,7 +1399,6 @@ Function,+,furi_hal_subghz_is_rx_data_crc_valid,_Bool, Function,+,furi_hal_subghz_is_tx_allowed,_Bool,uint32_t Function,+,furi_hal_subghz_load_custom_preset,void,uint8_t* Function,+,furi_hal_subghz_load_patable,void,const uint8_t[8] -Function,+,furi_hal_subghz_load_preset,void,FuriHalSubGhzPreset Function,+,furi_hal_subghz_load_registers,void,uint8_t* Function,+,furi_hal_subghz_read_packet,void,"uint8_t*, uint8_t*" Function,+,furi_hal_subghz_reset,void, @@ -3443,6 +3443,12 @@ Variable,+,sequence_set_vibro_on,const NotificationSequence, Variable,+,sequence_single_vibro,const NotificationSequence, Variable,+,sequence_solid_yellow,const NotificationSequence, Variable,+,sequence_success,const NotificationSequence, +Variable,+,subghz_device_cc1101_preset_2fsk_dev2_38khz_async_regs,const uint8_t[], +Variable,+,subghz_device_cc1101_preset_2fsk_dev47_6khz_async_regs,const uint8_t[], +Variable,+,subghz_device_cc1101_preset_gfsk_9_99kb_async_regs,const uint8_t[], +Variable,+,subghz_device_cc1101_preset_msk_99_97kb_async_regs,const uint8_t[], +Variable,+,subghz_device_cc1101_preset_ook_270khz_async_regs,const uint8_t[], +Variable,+,subghz_device_cc1101_preset_ook_650khz_async_regs,const uint8_t[], Variable,+,subghz_protocol_raw,const SubGhzProtocol, Variable,+,subghz_protocol_raw_decoder,const SubGhzProtocolDecoder, Variable,+,subghz_protocol_raw_encoder,const SubGhzProtocolEncoder, diff --git a/firmware/targets/f7/furi_hal/furi_hal_subghz.c b/firmware/targets/f7/furi_hal/furi_hal_subghz.c index dc26cffb5..bf002fb75 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_subghz.c +++ b/firmware/targets/f7/furi_hal/furi_hal_subghz.c @@ -50,7 +50,6 @@ typedef enum { typedef struct { volatile SubGhzState state; volatile SubGhzRegulation regulation; - volatile FuriHalSubGhzPreset preset; const GpioPin* async_mirror_pin; uint8_t rolling_counter_mult; @@ -61,7 +60,6 @@ typedef struct { volatile FuriHalSubGhz furi_hal_subghz = { .state = SubGhzStateInit, .regulation = SubGhzRegulationTxRx, - .preset = FuriHalSubGhzPresetIDLE, .async_mirror_pin = NULL, .rolling_counter_mult = 1, .dangerous_frequency_i = false, @@ -90,7 +88,6 @@ const GpioPin* furi_hal_subghz_get_data_gpio() { void furi_hal_subghz_init() { furi_assert(furi_hal_subghz.state == SubGhzStateInit); furi_hal_subghz.state = SubGhzStateIdle; - furi_hal_subghz.preset = FuriHalSubGhzPresetIDLE; furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); @@ -144,8 +141,6 @@ void furi_hal_subghz_sleep() { cc1101_shutdown(&furi_hal_spi_bus_handle_subghz); furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); - - furi_hal_subghz.preset = FuriHalSubGhzPresetIDLE; } void furi_hal_subghz_dump_state() { @@ -157,37 +152,6 @@ void furi_hal_subghz_dump_state() { furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); } -void furi_hal_subghz_load_preset(FuriHalSubGhzPreset preset) { - if(preset == FuriHalSubGhzPresetOok650Async) { - furi_hal_subghz_load_registers( - (uint8_t*)subghz_device_cc1101_preset_ook_650khz_async_regs); - furi_hal_subghz_load_patable(subghz_device_cc1101_preset_ook_async_patable); - } else if(preset == FuriHalSubGhzPresetOok270Async) { - furi_hal_subghz_load_registers( - (uint8_t*)subghz_device_cc1101_preset_ook_270khz_async_regs); - furi_hal_subghz_load_patable(subghz_device_cc1101_preset_ook_async_patable); - } else if(preset == FuriHalSubGhzPreset2FSKDev238Async) { - furi_hal_subghz_load_registers( - (uint8_t*)subghz_device_cc1101_preset_2fsk_dev2_38khz_async_regs); - furi_hal_subghz_load_patable(subghz_device_cc1101_preset_2fsk_async_patable); - } else if(preset == FuriHalSubGhzPreset2FSKDev476Async) { - furi_hal_subghz_load_registers( - (uint8_t*)subghz_device_cc1101_preset_2fsk_dev47_6khz_async_regs); - furi_hal_subghz_load_patable(subghz_device_cc1101_preset_2fsk_async_patable); - } else if(preset == FuriHalSubGhzPresetMSK99_97KbAsync) { - furi_hal_subghz_load_registers( - (uint8_t*)subghz_device_cc1101_preset_msk_99_97kb_async_regs); - furi_hal_subghz_load_patable(subghz_device_cc1101_preset_msk_async_patable); - } else if(preset == FuriHalSubGhzPresetGFSK9_99KbAsync) { - furi_hal_subghz_load_registers( - (uint8_t*)subghz_device_cc1101_preset_gfsk_9_99kb_async_regs); - furi_hal_subghz_load_patable(subghz_device_cc1101_preset_gfsk_async_patable); - } else { - furi_crash("SubGhz: Missing config."); - } - furi_hal_subghz.preset = preset; -} - void furi_hal_subghz_load_custom_preset(uint8_t* preset_data) { //load config furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); @@ -203,7 +167,6 @@ void furi_hal_subghz_load_custom_preset(uint8_t* preset_data) { //load pa table memcpy(&pa[0], &preset_data[i + 2], 8); furi_hal_subghz_load_patable(pa); - furi_hal_subghz.preset = FuriHalSubGhzPresetCustom; //show debug if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { diff --git a/firmware/targets/f7/furi_hal/furi_hal_subghz.h b/firmware/targets/f7/furi_hal/furi_hal_subghz.h index 6eeba6f7d..dee7192f6 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_subghz.h +++ b/firmware/targets/f7/furi_hal/furi_hal_subghz.h @@ -60,12 +60,6 @@ void furi_hal_subghz_sleep(); */ void furi_hal_subghz_dump_state(); -/** Load registers from preset by preset name - * - * @param preset to load - */ -void furi_hal_subghz_load_preset(FuriHalSubGhzPreset preset); - /** Load custom registers from preset * * @param preset_data registers to load diff --git a/lib/subghz/SConscript b/lib/subghz/SConscript index 3a0325b71..2c42a5157 100644 --- a/lib/subghz/SConscript +++ b/lib/subghz/SConscript @@ -19,6 +19,7 @@ env.Append( File("blocks/math.h"), File("subghz_setting.h"), File("subghz_protocol_registry.h"), + File("devices/cc1101_configs.h"), ], ) diff --git a/lib/subghz/devices/cc1101_configs.c b/lib/subghz/devices/cc1101_configs.c index 274b9c4d9..6b5b9f1f7 100644 --- a/lib/subghz/devices/cc1101_configs.c +++ b/lib/subghz/devices/cc1101_configs.c @@ -1,268 +1,68 @@ #include "cc1101_configs.h" #include -const uint8_t subghz_device_cc1101_preset_ook_270khz_async_regs[][2] = { +const uint8_t subghz_device_cc1101_preset_ook_270khz_async_regs[] = { // https://e2e.ti.com/support/wireless-connectivity/sub-1-ghz-group/sub-1-ghz/f/sub-1-ghz-forum/382066/cc1101---don-t-know-the-correct-registers-configuration /* GPIO GD0 */ - {CC1101_IOCFG0, 0x0D}, // GD0 as async serial data output/input + CC1101_IOCFG0, + 0x0D, // GD0 as async serial data output/input /* FIFO and internals */ - {CC1101_FIFOTHR, 0x47}, // The only important bit is ADC_RETENTION, FIFO Tx=33 Rx=32 + CC1101_FIFOTHR, + 0x47, // The only important bit is ADC_RETENTION, FIFO Tx=33 Rx=32 /* Packet engine */ - {CC1101_PKTCTRL0, 0x32}, // Async, continious, no whitening + CC1101_PKTCTRL0, + 0x32, // Async, continious, no whitening /* Frequency Synthesizer Control */ - {CC1101_FSCTRL1, 0x06}, // IF = (26*10^6) / (2^10) * 0x06 = 152343.75Hz + CC1101_FSCTRL1, + 0x06, // IF = (26*10^6) / (2^10) * 0x06 = 152343.75Hz // Modem Configuration - {CC1101_MDMCFG0, 0x00}, // Channel spacing is 25kHz - {CC1101_MDMCFG1, 0x00}, // Channel spacing is 25kHz - {CC1101_MDMCFG2, 0x30}, // Format ASK/OOK, No preamble/sync - {CC1101_MDMCFG3, 0x32}, // Data rate is 3.79372 kBaud - {CC1101_MDMCFG4, 0x67}, // Rx BW filter is 270.833333kHz + CC1101_MDMCFG0, + 0x00, // Channel spacing is 25kHz + CC1101_MDMCFG1, + 0x00, // Channel spacing is 25kHz + CC1101_MDMCFG2, + 0x30, // Format ASK/OOK, No preamble/sync + CC1101_MDMCFG3, + 0x32, // Data rate is 3.79372 kBaud + CC1101_MDMCFG4, + 0x67, // Rx BW filter is 270.833333kHz /* Main Radio Control State Machine */ - {CC1101_MCSM0, 0x18}, // Autocalibrate on idle-to-rx/tx, PO_TIMEOUT is 64 cycles(149-155us) + CC1101_MCSM0, + 0x18, // Autocalibrate on idle-to-rx/tx, PO_TIMEOUT is 64 cycles(149-155us) /* Frequency Offset Compensation Configuration */ - {CC1101_FOCCFG, - 0x18}, // no frequency offset compensation, POST_K same as PRE_K, PRE_K is 4K, GATE is off + CC1101_FOCCFG, + 0x18, // no frequency offset compensation, POST_K same as PRE_K, PRE_K is 4K, GATE is off /* Automatic Gain Control */ - {CC1101_AGCCTRL0, - 0x40}, // 01 - Low hysteresis, small asymmetric dead zone, medium gain; 00 - 8 samples agc; 00 - Normal AGC, 00 - 4dB boundary - {CC1101_AGCCTRL1, - 0x00}, // 0; 0 - LNA 2 gain is decreased to minimum before decreasing LNA gain; 00 - Relative carrier sense threshold disabled; 0000 - RSSI to MAIN_TARGET - {CC1101_AGCCTRL2, 0x03}, // 00 - DVGA all; 000 - MAX LNA+LNA2; 011 - MAIN_TARGET 24 dB + CC1101_AGCCTRL0, + 0x40, // 01 - Low hysteresis, small asymmetric dead zone, medium gain; 00 - 8 samples agc; 00 - Normal AGC, 00 - 4dB boundary + CC1101_AGCCTRL1, + 0x00, // 0; 0 - LNA 2 gain is decreased to minimum before decreasing LNA gain; 00 - Relative carrier sense threshold disabled; 0000 - RSSI to MAIN_TARGET + CC1101_AGCCTRL2, + 0x03, // 00 - DVGA all; 000 - MAX LNA+LNA2; 011 - MAIN_TARGET 24 dB /* Wake on radio and timeouts control */ - {CC1101_WORCTRL, 0xFB}, // WOR_RES is 2^15 periods (0.91 - 0.94 s) 16.5 - 17.2 hours + CC1101_WORCTRL, + 0xFB, // WOR_RES is 2^15 periods (0.91 - 0.94 s) 16.5 - 17.2 hours /* Frontend configuration */ - {CC1101_FREND0, 0x11}, // Adjusts current TX LO buffer + high is PATABLE[1] - {CC1101_FREND1, 0xB6}, // + CC1101_FREND0, + 0x11, // Adjusts current TX LO buffer + high is PATABLE[1] + CC1101_FREND1, + 0xB6, // - /* End */ - {0, 0}, -}; + /* End load reg */ + 0, + 0, -const uint8_t subghz_device_cc1101_preset_ook_650khz_async_regs[][2] = { - // https://e2e.ti.com/support/wireless-connectivity/sub-1-ghz-group/sub-1-ghz/f/sub-1-ghz-forum/382066/cc1101---don-t-know-the-correct-registers-configuration - - /* GPIO GD0 */ - {CC1101_IOCFG0, 0x0D}, // GD0 as async serial data output/input - - /* FIFO and internals */ - {CC1101_FIFOTHR, 0x07}, // The only important bit is ADC_RETENTION - - /* Packet engine */ - {CC1101_PKTCTRL0, 0x32}, // Async, continious, no whitening - - /* Frequency Synthesizer Control */ - {CC1101_FSCTRL1, 0x06}, // IF = (26*10^6) / (2^10) * 0x06 = 152343.75Hz - - // Modem Configuration - {CC1101_MDMCFG0, 0x00}, // Channel spacing is 25kHz - {CC1101_MDMCFG1, 0x00}, // Channel spacing is 25kHz - {CC1101_MDMCFG2, 0x30}, // Format ASK/OOK, No preamble/sync - {CC1101_MDMCFG3, 0x32}, // Data rate is 3.79372 kBaud - {CC1101_MDMCFG4, 0x17}, // Rx BW filter is 650.000kHz - - /* Main Radio Control State Machine */ - {CC1101_MCSM0, 0x18}, // Autocalibrate on idle-to-rx/tx, PO_TIMEOUT is 64 cycles(149-155us) - - /* Frequency Offset Compensation Configuration */ - {CC1101_FOCCFG, - 0x18}, // no frequency offset compensation, POST_K same as PRE_K, PRE_K is 4K, GATE is off - - /* Automatic Gain Control */ - // {CC1101_AGCTRL0,0x40}, // 01 - Low hysteresis, small asymmetric dead zone, medium gain; 00 - 8 samples agc; 00 - Normal AGC, 00 - 4dB boundary - // {CC1101_AGCTRL1,0x00}, // 0; 0 - LNA 2 gain is decreased to minimum before decreasing LNA gain; 00 - Relative carrier sense threshold disabled; 0000 - RSSI to MAIN_TARGET - // {CC1101_AGCCTRL2, 0x03}, // 00 - DVGA all; 000 - MAX LNA+LNA2; 011 - MAIN_TARGET 24 dB - //MAGN_TARGET for RX filter BW =< 100 kHz is 0x3. For higher RX filter BW's MAGN_TARGET is 0x7. - {CC1101_AGCCTRL0, - 0x91}, // 10 - Medium hysteresis, medium asymmetric dead zone, medium gain ; 01 - 16 samples agc; 00 - Normal AGC, 01 - 8dB boundary - {CC1101_AGCCTRL1, - 0x0}, // 0; 0 - LNA 2 gain is decreased to minimum before decreasing LNA gain; 00 - Relative carrier sense threshold disabled; 0000 - RSSI to MAIN_TARGET - {CC1101_AGCCTRL2, 0x07}, // 00 - DVGA all; 000 - MAX LNA+LNA2; 111 - MAIN_TARGET 42 dB - - /* Wake on radio and timeouts control */ - {CC1101_WORCTRL, 0xFB}, // WOR_RES is 2^15 periods (0.91 - 0.94 s) 16.5 - 17.2 hours - - /* Frontend configuration */ - {CC1101_FREND0, 0x11}, // Adjusts current TX LO buffer + high is PATABLE[1] - {CC1101_FREND1, 0xB6}, // - - /* End */ - {0, 0}, -}; - -const uint8_t subghz_device_cc1101_preset_2fsk_dev2_38khz_async_regs[][2] = { - - /* GPIO GD0 */ - {CC1101_IOCFG0, 0x0D}, // GD0 as async serial data output/input - - /* Frequency Synthesizer Control */ - {CC1101_FSCTRL1, 0x06}, // IF = (26*10^6) / (2^10) * 0x06 = 152343.75Hz - - /* Packet engine */ - {CC1101_PKTCTRL0, 0x32}, // Async, continious, no whitening - {CC1101_PKTCTRL1, 0x04}, - - // // Modem Configuration - {CC1101_MDMCFG0, 0x00}, - {CC1101_MDMCFG1, 0x02}, - {CC1101_MDMCFG2, 0x04}, // Format 2-FSK/FM, No preamble/sync, Disable (current optimized) - {CC1101_MDMCFG3, 0x83}, // Data rate is 4.79794 kBaud - {CC1101_MDMCFG4, 0x67}, //Rx BW filter is 270.833333 kHz - {CC1101_DEVIATN, 0x04}, //Deviation 2.380371 kHz - - /* Main Radio Control State Machine */ - {CC1101_MCSM0, 0x18}, // Autocalibrate on idle-to-rx/tx, PO_TIMEOUT is 64 cycles(149-155us) - - /* Frequency Offset Compensation Configuration */ - {CC1101_FOCCFG, - 0x16}, // no frequency offset compensation, POST_K same as PRE_K, PRE_K is 4K, GATE is off - - /* Automatic Gain Control */ - {CC1101_AGCCTRL0, - 0x91}, //10 - Medium hysteresis, medium asymmetric dead zone, medium gain ; 01 - 16 samples agc; 00 - Normal AGC, 01 - 8dB boundary - {CC1101_AGCCTRL1, - 0x00}, // 0; 0 - LNA 2 gain is decreased to minimum before decreasing LNA gain; 00 - Relative carrier sense threshold disabled; 0000 - RSSI to MAIN_TARGET - {CC1101_AGCCTRL2, 0x07}, // 00 - DVGA all; 000 - MAX LNA+LNA2; 111 - MAIN_TARGET 42 dB - - /* Wake on radio and timeouts control */ - {CC1101_WORCTRL, 0xFB}, // WOR_RES is 2^15 periods (0.91 - 0.94 s) 16.5 - 17.2 hours - - /* Frontend configuration */ - {CC1101_FREND0, 0x10}, // Adjusts current TX LO buffer - {CC1101_FREND1, 0x56}, - - /* End */ - {0, 0}, -}; - -const uint8_t subghz_device_cc1101_preset_2fsk_dev47_6khz_async_regs[][2] = { - - /* GPIO GD0 */ - {CC1101_IOCFG0, 0x0D}, // GD0 as async serial data output/input - - /* Frequency Synthesizer Control */ - {CC1101_FSCTRL1, 0x06}, // IF = (26*10^6) / (2^10) * 0x06 = 152343.75Hz - - /* Packet engine */ - {CC1101_PKTCTRL0, 0x32}, // Async, continious, no whitening - {CC1101_PKTCTRL1, 0x04}, - - // // Modem Configuration - {CC1101_MDMCFG0, 0x00}, - {CC1101_MDMCFG1, 0x02}, - {CC1101_MDMCFG2, 0x04}, // Format 2-FSK/FM, No preamble/sync, Disable (current optimized) - {CC1101_MDMCFG3, 0x83}, // Data rate is 4.79794 kBaud - {CC1101_MDMCFG4, 0x67}, //Rx BW filter is 270.833333 kHz - {CC1101_DEVIATN, 0x47}, //Deviation 47.60742 kHz - - /* Main Radio Control State Machine */ - {CC1101_MCSM0, 0x18}, // Autocalibrate on idle-to-rx/tx, PO_TIMEOUT is 64 cycles(149-155us) - - /* Frequency Offset Compensation Configuration */ - {CC1101_FOCCFG, - 0x16}, // no frequency offset compensation, POST_K same as PRE_K, PRE_K is 4K, GATE is off - - /* Automatic Gain Control */ - {CC1101_AGCCTRL0, - 0x91}, //10 - Medium hysteresis, medium asymmetric dead zone, medium gain ; 01 - 16 samples agc; 00 - Normal AGC, 01 - 8dB boundary - {CC1101_AGCCTRL1, - 0x00}, // 0; 0 - LNA 2 gain is decreased to minimum before decreasing LNA gain; 00 - Relative carrier sense threshold disabled; 0000 - RSSI to MAIN_TARGET - {CC1101_AGCCTRL2, 0x07}, // 00 - DVGA all; 000 - MAX LNA+LNA2; 111 - MAIN_TARGET 42 dB - - /* Wake on radio and timeouts control */ - {CC1101_WORCTRL, 0xFB}, // WOR_RES is 2^15 periods (0.91 - 0.94 s) 16.5 - 17.2 hours - - /* Frontend configuration */ - {CC1101_FREND0, 0x10}, // Adjusts current TX LO buffer - {CC1101_FREND1, 0x56}, - - /* End */ - {0, 0}, -}; - -const uint8_t subghz_device_cc1101_preset_msk_99_97kb_async_regs[][2] = { - /* GPIO GD0 */ - {CC1101_IOCFG0, 0x06}, - - {CC1101_FIFOTHR, 0x07}, // The only important bit is ADC_RETENTION - {CC1101_SYNC1, 0x46}, - {CC1101_SYNC0, 0x4C}, - {CC1101_ADDR, 0x00}, - {CC1101_PKTLEN, 0x00}, - {CC1101_CHANNR, 0x00}, - - {CC1101_PKTCTRL0, 0x05}, - - {CC1101_FSCTRL0, 0x23}, - {CC1101_FSCTRL1, 0x06}, - - {CC1101_MDMCFG0, 0xF8}, - {CC1101_MDMCFG1, 0x22}, - {CC1101_MDMCFG2, 0x72}, - {CC1101_MDMCFG3, 0xF8}, - {CC1101_MDMCFG4, 0x5B}, - {CC1101_DEVIATN, 0x47}, - - {CC1101_MCSM0, 0x18}, - {CC1101_FOCCFG, 0x16}, - - {CC1101_AGCCTRL0, 0xB2}, - {CC1101_AGCCTRL1, 0x00}, - {CC1101_AGCCTRL2, 0xC7}, - - {CC1101_FREND0, 0x10}, - {CC1101_FREND1, 0x56}, - - {CC1101_BSCFG, 0x1C}, - {CC1101_FSTEST, 0x59}, - - /* End */ - {0, 0}, -}; - -const uint8_t subghz_device_cc1101_preset_gfsk_9_99kb_async_regs[][2] = { - - {CC1101_IOCFG0, 0x06}, //GDO0 Output Pin Configuration - {CC1101_FIFOTHR, 0x47}, //RX FIFO and TX FIFO Thresholds - - //1 : CRC calculation in TX and CRC check in RX enabled, - //1 : Variable packet length mode. Packet length configured by the first byte after sync word - {CC1101_PKTCTRL0, 0x05}, - - {CC1101_FSCTRL1, 0x06}, //Frequency Synthesizer Control - - {CC1101_SYNC1, 0x46}, - {CC1101_SYNC0, 0x4C}, - {CC1101_ADDR, 0x00}, - {CC1101_PKTLEN, 0x00}, - - {CC1101_MDMCFG4, 0xC8}, //Modem Configuration 9.99 - {CC1101_MDMCFG3, 0x93}, //Modem Configuration - {CC1101_MDMCFG2, 0x12}, // 2: 16/16 sync word bits detected - - {CC1101_DEVIATN, 0x34}, //Deviation = 19.042969 - {CC1101_MCSM0, 0x18}, //Main Radio Control State Machine Configuration - {CC1101_FOCCFG, 0x16}, //Frequency Offset Compensation Configuration - - {CC1101_AGCCTRL2, 0x43}, //AGC Control - {CC1101_AGCCTRL1, 0x40}, - {CC1101_AGCCTRL0, 0x91}, - - {CC1101_WORCTRL, 0xFB}, //Wake On Radio Control - /* End */ - {0, 0}, -}; - -const uint8_t subghz_device_cc1101_preset_ook_async_patable[8] = { + //ook_async_patable[8] 0x00, 0xC0, // 12dBm 0xC0, 10dBm 0xC5, 7dBm 0xCD, 5dBm 0x86, 0dBm 0x50, -6dBm 0x37, -10dBm 0x26, -15dBm 0x1D, -20dBm 0x17, -30dBm 0x03 0x00, @@ -270,19 +70,146 @@ const uint8_t subghz_device_cc1101_preset_ook_async_patable[8] = { 0x00, 0x00, 0x00, - 0x00}; + 0x00, +}; -const uint8_t subghz_device_cc1101_preset_ook_async_patable_au[8] = { - 0x00, - 0x37, // 12dBm 0xC0, 10dBm 0xC5, 7dBm 0xCD, 5dBm 0x86, 0dBm 0x50, -6dBm 0x37, -10dBm 0x26, -15dBm 0x1D, -20dBm 0x17, -30dBm 0x03 - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00}; +const uint8_t subghz_device_cc1101_preset_ook_650khz_async_regs[] = { + // https://e2e.ti.com/support/wireless-connectivity/sub-1-ghz-group/sub-1-ghz/f/sub-1-ghz-forum/382066/cc1101---don-t-know-the-correct-registers-configuration -const uint8_t subghz_device_cc1101_preset_2fsk_async_patable[8] = { + /* GPIO GD0 */ + CC1101_IOCFG0, + 0x0D, // GD0 as async serial data output/input + + /* FIFO and internals */ + CC1101_FIFOTHR, + 0x07, // The only important bit is ADC_RETENTION + + /* Packet engine */ + CC1101_PKTCTRL0, + 0x32, // Async, continious, no whitening + + /* Frequency Synthesizer Control */ + CC1101_FSCTRL1, + 0x06, // IF = (26*10^6) / (2^10) * 0x06 = 152343.75Hz + + // Modem Configuration + CC1101_MDMCFG0, + 0x00, // Channel spacing is 25kHz + CC1101_MDMCFG1, + 0x00, // Channel spacing is 25kHz + CC1101_MDMCFG2, + 0x30, // Format ASK/OOK, No preamble/sync + CC1101_MDMCFG3, + 0x32, // Data rate is 3.79372 kBaud + CC1101_MDMCFG4, + 0x17, // Rx BW filter is 650.000kHz + + /* Main Radio Control State Machine */ + CC1101_MCSM0, + 0x18, // Autocalibrate on idle-to-rx/tx, PO_TIMEOUT is 64 cycles(149-155us) + + /* Frequency Offset Compensation Configuration */ + CC1101_FOCCFG, + 0x18, // no frequency offset compensation, POST_K same as PRE_K, PRE_K is 4K, GATE is off + + /* Automatic Gain Control */ + // CC1101_AGCTRL0,0x40, // 01 - Low hysteresis, small asymmetric dead zone, medium gain; 00 - 8 samples agc; 00 - Normal AGC, 00 - 4dB boundary + // CC1101_AGCTRL1,0x00, // 0; 0 - LNA 2 gain is decreased to minimum before decreasing LNA gain; 00 - Relative carrier sense threshold disabled; 0000 - RSSI to MAIN_TARGET + // CC1101_AGCCTRL2, 0x03, // 00 - DVGA all; 000 - MAX LNA+LNA2; 011 - MAIN_TARGET 24 dB + //MAGN_TARGET for RX filter BW =< 100 kHz is 0x3. For higher RX filter BW's MAGN_TARGET is 0x7. + CC1101_AGCCTRL0, + 0x91, // 10 - Medium hysteresis, medium asymmetric dead zone, medium gain ; 01 - 16 samples agc; 00 - Normal AGC, 01 - 8dB boundary + CC1101_AGCCTRL1, + 0x0, // 0; 0 - LNA 2 gain is decreased to minimum before decreasing LNA gain; 00 - Relative carrier sense threshold disabled; 0000 - RSSI to MAIN_TARGET + CC1101_AGCCTRL2, + 0x07, // 00 - DVGA all; 000 - MAX LNA+LNA2; 111 - MAIN_TARGET 42 dB + + /* Wake on radio and timeouts control */ + CC1101_WORCTRL, + 0xFB, // WOR_RES is 2^15 periods (0.91 - 0.94 s) 16.5 - 17.2 hours + + /* Frontend configuration */ + CC1101_FREND0, + 0x11, // Adjusts current TX LO buffer + high is PATABLE[1] + CC1101_FREND1, + 0xB6, // + + /* End load reg */ + 0, + 0, + + //ook_async_patable[8] + 0x00, + 0xC0, // 12dBm 0xC0, 10dBm 0xC5, 7dBm 0xCD, 5dBm 0x86, 0dBm 0x50, -6dBm 0x37, -10dBm 0x26, -15dBm 0x1D, -20dBm 0x17, -30dBm 0x03 + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, +}; + +const uint8_t subghz_device_cc1101_preset_2fsk_dev2_38khz_async_regs[] = { + + /* GPIO GD0 */ + CC1101_IOCFG0, + 0x0D, // GD0 as async serial data output/input + + /* Frequency Synthesizer Control */ + CC1101_FSCTRL1, + 0x06, // IF = (26*10^6) / (2^10) * 0x06 = 152343.75Hz + + /* Packet engine */ + CC1101_PKTCTRL0, + 0x32, // Async, continious, no whitening + CC1101_PKTCTRL1, + 0x04, + + // // Modem Configuration + CC1101_MDMCFG0, + 0x00, + CC1101_MDMCFG1, + 0x02, + CC1101_MDMCFG2, + 0x04, // Format 2-FSK/FM, No preamble/sync, Disable (current optimized) + CC1101_MDMCFG3, + 0x83, // Data rate is 4.79794 kBaud + CC1101_MDMCFG4, + 0x67, //Rx BW filter is 270.833333 kHz + CC1101_DEVIATN, + 0x04, //Deviation 2.380371 kHz + + /* Main Radio Control State Machine */ + CC1101_MCSM0, + 0x18, // Autocalibrate on idle-to-rx/tx, PO_TIMEOUT is 64 cycles(149-155us) + + /* Frequency Offset Compensation Configuration */ + CC1101_FOCCFG, + 0x16, // no frequency offset compensation, POST_K same as PRE_K, PRE_K is 4K, GATE is off + + /* Automatic Gain Control */ + CC1101_AGCCTRL0, + 0x91, //10 - Medium hysteresis, medium asymmetric dead zone, medium gain ; 01 - 16 samples agc; 00 - Normal AGC, 01 - 8dB boundary + CC1101_AGCCTRL1, + 0x00, // 0; 0 - LNA 2 gain is decreased to minimum before decreasing LNA gain; 00 - Relative carrier sense threshold disabled; 0000 - RSSI to MAIN_TARGET + CC1101_AGCCTRL2, + 0x07, // 00 - DVGA all; 000 - MAX LNA+LNA2; 111 - MAIN_TARGET 42 dB + + /* Wake on radio and timeouts control */ + CC1101_WORCTRL, + 0xFB, // WOR_RES is 2^15 periods (0.91 - 0.94 s) 16.5 - 17.2 hours + + /* Frontend configuration */ + CC1101_FREND0, + 0x10, // Adjusts current TX LO buffer + CC1101_FREND1, + 0x56, + + /* End load reg */ + 0, + 0, + + // 2fsk_async_patable[8] 0xC0, // 10dBm 0xC0, 7dBm 0xC8, 5dBm 0x84, 0dBm 0x60, -10dBm 0x34, -15dBm 0x1D, -20dBm 0x0E, -30dBm 0x12 0x00, 0x00, @@ -290,9 +217,70 @@ const uint8_t subghz_device_cc1101_preset_2fsk_async_patable[8] = { 0x00, 0x00, 0x00, - 0x00}; + 0x00, +}; -const uint8_t subghz_device_cc1101_preset_msk_async_patable[8] = { +const uint8_t subghz_device_cc1101_preset_2fsk_dev47_6khz_async_regs[] = { + + /* GPIO GD0 */ + CC1101_IOCFG0, + 0x0D, // GD0 as async serial data output/input + + /* Frequency Synthesizer Control */ + CC1101_FSCTRL1, + 0x06, // IF = (26*10^6) / (2^10) * 0x06 = 152343.75Hz + + /* Packet engine */ + CC1101_PKTCTRL0, + 0x32, // Async, continious, no whitening + CC1101_PKTCTRL1, + 0x04, + + // // Modem Configuration + CC1101_MDMCFG0, + 0x00, + CC1101_MDMCFG1, + 0x02, + CC1101_MDMCFG2, + 0x04, // Format 2-FSK/FM, No preamble/sync, Disable (current optimized) + CC1101_MDMCFG3, + 0x83, // Data rate is 4.79794 kBaud + CC1101_MDMCFG4, + 0x67, //Rx BW filter is 270.833333 kHz + CC1101_DEVIATN, + 0x47, //Deviation 47.60742 kHz + + /* Main Radio Control State Machine */ + CC1101_MCSM0, + 0x18, // Autocalibrate on idle-to-rx/tx, PO_TIMEOUT is 64 cycles(149-155us) + + /* Frequency Offset Compensation Configuration */ + CC1101_FOCCFG, + 0x16, // no frequency offset compensation, POST_K same as PRE_K, PRE_K is 4K, GATE is off + + /* Automatic Gain Control */ + CC1101_AGCCTRL0, + 0x91, //10 - Medium hysteresis, medium asymmetric dead zone, medium gain ; 01 - 16 samples agc; 00 - Normal AGC, 01 - 8dB boundary + CC1101_AGCCTRL1, + 0x00, // 0; 0 - LNA 2 gain is decreased to minimum before decreasing LNA gain; 00 - Relative carrier sense threshold disabled; 0000 - RSSI to MAIN_TARGET + CC1101_AGCCTRL2, + 0x07, // 00 - DVGA all; 000 - MAX LNA+LNA2; 111 - MAIN_TARGET 42 dB + + /* Wake on radio and timeouts control */ + CC1101_WORCTRL, + 0xFB, // WOR_RES is 2^15 periods (0.91 - 0.94 s) 16.5 - 17.2 hours + + /* Frontend configuration */ + CC1101_FREND0, + 0x10, // Adjusts current TX LO buffer + CC1101_FREND1, + 0x56, + + /* End load reg */ + 0, + 0, + + // 2fsk_async_patable[8] 0xC0, // 10dBm 0xC0, 7dBm 0xC8, 5dBm 0x84, 0dBm 0x60, -10dBm 0x34, -15dBm 0x1D, -20dBm 0x0E, -30dBm 0x12 0x00, 0x00, @@ -300,9 +288,75 @@ const uint8_t subghz_device_cc1101_preset_msk_async_patable[8] = { 0x00, 0x00, 0x00, - 0x00}; + 0x00, +}; -const uint8_t subghz_device_cc1101_preset_gfsk_async_patable[8] = { +const uint8_t subghz_device_cc1101_preset_msk_99_97kb_async_regs[] = { + /* GPIO GD0 */ + CC1101_IOCFG0, + 0x06, + + CC1101_FIFOTHR, + 0x07, // The only important bit is ADC_RETENTION + CC1101_SYNC1, + 0x46, + CC1101_SYNC0, + 0x4C, + CC1101_ADDR, + 0x00, + CC1101_PKTLEN, + 0x00, + CC1101_CHANNR, + 0x00, + + CC1101_PKTCTRL0, + 0x05, + + CC1101_FSCTRL0, + 0x23, + CC1101_FSCTRL1, + 0x06, + + CC1101_MDMCFG0, + 0xF8, + CC1101_MDMCFG1, + 0x22, + CC1101_MDMCFG2, + 0x72, + CC1101_MDMCFG3, + 0xF8, + CC1101_MDMCFG4, + 0x5B, + CC1101_DEVIATN, + 0x47, + + CC1101_MCSM0, + 0x18, + CC1101_FOCCFG, + 0x16, + + CC1101_AGCCTRL0, + 0xB2, + CC1101_AGCCTRL1, + 0x00, + CC1101_AGCCTRL2, + 0xC7, + + CC1101_FREND0, + 0x10, + CC1101_FREND1, + 0x56, + + CC1101_BSCFG, + 0x1C, + CC1101_FSTEST, + 0x59, + + /* End load reg */ + 0, + 0, + + // msk_async_patable[8] 0xC0, // 10dBm 0xC0, 7dBm 0xC8, 5dBm 0x84, 0dBm 0x60, -10dBm 0x34, -15dBm 0x1D, -20dBm 0x0E, -30dBm 0x12 0x00, 0x00, @@ -310,4 +364,119 @@ const uint8_t subghz_device_cc1101_preset_gfsk_async_patable[8] = { 0x00, 0x00, 0x00, - 0x00}; \ No newline at end of file + 0x00, +}; + +const uint8_t subghz_device_cc1101_preset_gfsk_9_99kb_async_regs[] = { + + CC1101_IOCFG0, + 0x06, //GDO0 Output Pin Configuration + CC1101_FIFOTHR, + 0x47, //RX FIFO and TX FIFO Thresholds + + //1 : CRC calculation in TX and CRC check in RX enabled, + //1 : Variable packet length mode. Packet length configured by the first byte after sync word + CC1101_PKTCTRL0, + 0x05, + + CC1101_FSCTRL1, + 0x06, //Frequency Synthesizer Control + + CC1101_SYNC1, + 0x46, + CC1101_SYNC0, + 0x4C, + CC1101_ADDR, + 0x00, + CC1101_PKTLEN, + 0x00, + + CC1101_MDMCFG4, + 0xC8, //Modem Configuration 9.99 + CC1101_MDMCFG3, + 0x93, //Modem Configuration + CC1101_MDMCFG2, + 0x12, // 2: 16/16 sync word bits detected + + CC1101_DEVIATN, + 0x34, //Deviation = 19.042969 + CC1101_MCSM0, + 0x18, //Main Radio Control State Machine Configuration + CC1101_FOCCFG, + 0x16, //Frequency Offset Compensation Configuration + + CC1101_AGCCTRL2, + 0x43, //AGC Control + CC1101_AGCCTRL1, + 0x40, + CC1101_AGCCTRL0, + 0x91, + + CC1101_WORCTRL, + 0xFB, //Wake On Radio Control + + /* End load reg */ + 0, + 0, + + // gfsk_async_patable[8] + 0xC0, // 10dBm 0xC0, 7dBm 0xC8, 5dBm 0x84, 0dBm 0x60, -10dBm 0x34, -15dBm 0x1D, -20dBm 0x0E, -30dBm 0x12 + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, +}; + +// Shpargalka +// const uint8_t subghz_device_cc1101_preset_ook_async_patable[8] = { +// 0x00, +// 0xC0, // 12dBm 0xC0, 10dBm 0xC5, 7dBm 0xCD, 5dBm 0x86, 0dBm 0x50, -6dBm 0x37, -10dBm 0x26, -15dBm 0x1D, -20dBm 0x17, -30dBm 0x03 +// 0x00, +// 0x00, +// 0x00, +// 0x00, +// 0x00, +// 0x00}; + +// const uint8_t subghz_device_cc1101_preset_ook_async_patable_au[8] = { +// 0x00, +// 0x37, // 12dBm 0xC0, 10dBm 0xC5, 7dBm 0xCD, 5dBm 0x86, 0dBm 0x50, -6dBm 0x37, -10dBm 0x26, -15dBm 0x1D, -20dBm 0x17, -30dBm 0x03 +// 0x00, +// 0x00, +// 0x00, +// 0x00, +// 0x00, +// 0x00}; + +// const uint8_t subghz_device_cc1101_preset_2fsk_async_patable[8] = { +// 0xC0, // 10dBm 0xC0, 7dBm 0xC8, 5dBm 0x84, 0dBm 0x60, -10dBm 0x34, -15dBm 0x1D, -20dBm 0x0E, -30dBm 0x12 +// 0x00, +// 0x00, +// 0x00, +// 0x00, +// 0x00, +// 0x00, +// 0x00}; + +// const uint8_t subghz_device_cc1101_preset_msk_async_patable[8] = { +// 0xC0, // 10dBm 0xC0, 7dBm 0xC8, 5dBm 0x84, 0dBm 0x60, -10dBm 0x34, -15dBm 0x1D, -20dBm 0x0E, -30dBm 0x12 +// 0x00, +// 0x00, +// 0x00, +// 0x00, +// 0x00, +// 0x00, +// 0x00}; + +// const uint8_t subghz_device_cc1101_preset_gfsk_async_patable[8] = { +// 0xC0, // 10dBm 0xC0, 7dBm 0xC8, 5dBm 0x84, 0dBm 0x60, -10dBm 0x34, -15dBm 0x1D, -20dBm 0x0E, -30dBm 0x12 +// 0x00, +// 0x00, +// 0x00, +// 0x00, +// 0x00, +// 0x00, +// 0x00}; \ No newline at end of file diff --git a/lib/subghz/devices/cc1101_configs.h b/lib/subghz/devices/cc1101_configs.h index 302ac778f..0e1ffb0c7 100644 --- a/lib/subghz/devices/cc1101_configs.h +++ b/lib/subghz/devices/cc1101_configs.h @@ -5,17 +5,18 @@ extern "C" { #endif -extern const uint8_t subghz_device_cc1101_preset_ook_270khz_async_regs[][2]; -extern const uint8_t subghz_device_cc1101_preset_ook_650khz_async_regs[][2]; -extern const uint8_t subghz_device_cc1101_preset_2fsk_dev2_38khz_async_regs[][2]; -extern const uint8_t subghz_device_cc1101_preset_2fsk_dev47_6khz_async_regs[][2]; -extern const uint8_t subghz_device_cc1101_preset_msk_99_97kb_async_regs[][2]; -extern const uint8_t subghz_device_cc1101_preset_gfsk_9_99kb_async_regs[][2]; -extern const uint8_t subghz_device_cc1101_preset_ook_async_patable[8]; -extern const uint8_t subghz_device_cc1101_preset_ook_async_patable_au[8]; -extern const uint8_t subghz_device_cc1101_preset_2fsk_async_patable[8]; -extern const uint8_t subghz_device_cc1101_preset_msk_async_patable[8]; -extern const uint8_t subghz_device_cc1101_preset_gfsk_async_patable[8]; +extern const uint8_t subghz_device_cc1101_preset_ook_270khz_async_regs[]; +extern const uint8_t subghz_device_cc1101_preset_ook_650khz_async_regs[]; +extern const uint8_t subghz_device_cc1101_preset_2fsk_dev2_38khz_async_regs[]; +extern const uint8_t subghz_device_cc1101_preset_2fsk_dev47_6khz_async_regs[]; +extern const uint8_t subghz_device_cc1101_preset_msk_99_97kb_async_regs[]; +extern const uint8_t subghz_device_cc1101_preset_gfsk_9_99kb_async_regs[]; + +// extern const uint8_t subghz_device_cc1101_preset_ook_async_patable[8]; +// extern const uint8_t subghz_device_cc1101_preset_ook_async_patable_au[8]; +// extern const uint8_t subghz_device_cc1101_preset_2fsk_async_patable[8]; +// extern const uint8_t subghz_device_cc1101_preset_msk_async_patable[8]; +// extern const uint8_t subghz_device_cc1101_preset_gfsk_async_patable[8]; #ifdef __cplusplus } diff --git a/lib/subghz/devices/cc1101_int/cc1101_int_interconnect.c b/lib/subghz/devices/cc1101_int/cc1101_int_interconnect.c index 995a4b71d..2299eea50 100644 --- a/lib/subghz/devices/cc1101_int/cc1101_int_interconnect.c +++ b/lib/subghz/devices/cc1101_int/cc1101_int_interconnect.c @@ -1,5 +1,6 @@ #include "cc1101_int_interconnect.h" #include +#include "../cc1101_configs.h" #define TAG "SubGhzDeviceCC1101Int" @@ -17,21 +18,43 @@ static uint32_t subghz_device_cc1101_int_interconnect_set_frequency(uint32_t fre } static bool subghz_device_cc1101_int_interconnect_start_async_tx(void* callback, void* context) { - return furi_hal_subghz_start_async_tx( - (FuriHalSubGhzAsyncTxCallback)callback, context); + return furi_hal_subghz_start_async_tx((FuriHalSubGhzAsyncTxCallback)callback, context); } static void subghz_device_cc1101_int_interconnect_start_async_rx(void* callback, void* context) { - furi_hal_subghz_start_async_rx( - (FuriHalSubGhzCaptureCallback)callback, context); + furi_hal_subghz_start_async_rx((FuriHalSubGhzCaptureCallback)callback, context); } static void subghz_device_cc1101_int_interconnect_load_preset( FuriHalSubGhzPreset preset, uint8_t* preset_data) { - if(preset != FuriHalSubGhzPresetCustom) { - furi_hal_subghz_load_preset(preset); - } else { + switch(preset) { + case FuriHalSubGhzPresetOok650Async: + furi_hal_subghz_load_custom_preset( + (uint8_t*)subghz_device_cc1101_preset_ook_650khz_async_regs); + break; + case FuriHalSubGhzPresetOok270Async: + furi_hal_subghz_load_custom_preset( + (uint8_t*)subghz_device_cc1101_preset_ook_270khz_async_regs); + break; + case FuriHalSubGhzPreset2FSKDev238Async: + furi_hal_subghz_load_custom_preset( + (uint8_t*)subghz_device_cc1101_preset_2fsk_dev2_38khz_async_regs); + break; + case FuriHalSubGhzPreset2FSKDev476Async: + furi_hal_subghz_load_custom_preset( + (uint8_t*)subghz_device_cc1101_preset_2fsk_dev47_6khz_async_regs); + break; + case FuriHalSubGhzPresetMSK99_97KbAsync: + furi_hal_subghz_load_custom_preset( + (uint8_t*)subghz_device_cc1101_preset_msk_99_97kb_async_regs); + break; + case FuriHalSubGhzPresetGFSK9_99KbAsync: + furi_hal_subghz_load_custom_preset( + (uint8_t*)subghz_device_cc1101_preset_gfsk_9_99kb_async_regs); + break; + + default: furi_hal_subghz_load_custom_preset(preset_data); } } diff --git a/lib/subghz/subghz_setting.c b/lib/subghz/subghz_setting.c index 3644e34d4..da4b8f728 100644 --- a/lib/subghz/subghz_setting.c +++ b/lib/subghz/subghz_setting.c @@ -149,8 +149,7 @@ void subghz_setting_free(SubGhzSetting* instance) { static void subghz_setting_load_default_preset( SubGhzSetting* instance, const char* preset_name, - const uint8_t* preset_data, - const uint8_t preset_pa_table[8]) { + const uint8_t* preset_data) { furi_assert(instance); furi_assert(preset_data); uint32_t preset_data_count = 0; @@ -166,10 +165,8 @@ static void subghz_setting_load_default_preset( preset_data_count += 2; item->custom_preset_data_size = sizeof(uint8_t) * preset_data_count + sizeof(uint8_t) * 8; item->custom_preset_data = malloc(item->custom_preset_data_size); - //load preset register - memcpy(&item->custom_preset_data[0], &preset_data[0], preset_data_count); - //load pa table - memcpy(&item->custom_preset_data[preset_data_count], &preset_pa_table[0], 8); + //load preset register + pa table + memcpy(&item->custom_preset_data[0], &preset_data[0], item->custom_preset_data_size); } static void subghz_setting_load_default_region( @@ -193,25 +190,13 @@ static void subghz_setting_load_default_region( } subghz_setting_load_default_preset( - instance, - "AM270", - (uint8_t*)subghz_device_cc1101_preset_ook_270khz_async_regs, - subghz_device_cc1101_preset_ook_async_patable); + instance, "AM270", subghz_device_cc1101_preset_ook_270khz_async_regs); subghz_setting_load_default_preset( - instance, - "AM650", - (uint8_t*)subghz_device_cc1101_preset_ook_650khz_async_regs, - subghz_device_cc1101_preset_ook_async_patable); + instance, "AM650", subghz_device_cc1101_preset_ook_650khz_async_regs); subghz_setting_load_default_preset( - instance, - "FM238", - (uint8_t*)subghz_device_cc1101_preset_2fsk_dev2_38khz_async_regs, - subghz_device_cc1101_preset_2fsk_async_patable); + instance, "FM238", subghz_device_cc1101_preset_2fsk_dev2_38khz_async_regs); subghz_setting_load_default_preset( - instance, - "FM476", - (uint8_t*)subghz_device_cc1101_preset_2fsk_dev47_6khz_async_regs, - subghz_device_cc1101_preset_2fsk_async_patable); + instance, "FM476", subghz_device_cc1101_preset_2fsk_dev47_6khz_async_regs); } // Region check removed From 2ef07a7a6c67ab0ce8570787caf9ab3938839537 Mon Sep 17 00:00:00 2001 From: gid9798 <30450294+gid9798@users.noreply.github.com> Date: Mon, 3 Jul 2023 14:02:57 +0300 Subject: [PATCH 017/364] SubGhz: add some consts and fix unit tests --- .../debug/unit_tests/subghz/subghz_test.c | 1 + .../drivers/subghz/cc1101_ext/cc1101_ext.c | 4 ++-- .../drivers/subghz/cc1101_ext/cc1101_ext.h | 4 ++-- .../cc1101_ext/cc1101_ext_interconnect.c | 12 ++++++------ applications/main/subghz/subghz_cli.c | 9 +++------ .../main/subghz/views/subghz_test_carrier.c | 3 +-- .../main/subghz/views/subghz_test_packet.c | 3 +-- .../main/subghz/views/subghz_test_static.c | 3 +-- firmware/targets/f7/api_symbols.csv | 4 ++-- firmware/targets/f7/furi_hal/furi_hal_subghz.c | 4 ++-- firmware/targets/f7/furi_hal/furi_hal_subghz.h | 4 ++-- .../cc1101_int/cc1101_int_interconnect.c | 18 ++++++------------ 12 files changed, 29 insertions(+), 40 deletions(-) diff --git a/applications/debug/unit_tests/subghz/subghz_test.c b/applications/debug/unit_tests/subghz/subghz_test.c index b84baf26d..6bdaa641e 100644 --- a/applications/debug/unit_tests/subghz/subghz_test.c +++ b/applications/debug/unit_tests/subghz/subghz_test.c @@ -8,6 +8,7 @@ #include #include #include +#include #define TAG "SubGhz TEST" #define KEYSTORE_DIR_NAME EXT_PATH("subghz/assets/keeloq_mfcodes") diff --git a/applications/drivers/subghz/cc1101_ext/cc1101_ext.c b/applications/drivers/subghz/cc1101_ext/cc1101_ext.c index b002cb32d..10905e110 100644 --- a/applications/drivers/subghz/cc1101_ext/cc1101_ext.c +++ b/applications/drivers/subghz/cc1101_ext/cc1101_ext.c @@ -226,7 +226,7 @@ void subghz_device_cc1101_ext_dump_state() { furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); } -void subghz_device_cc1101_ext_load_custom_preset(uint8_t* preset_data) { +void subghz_device_cc1101_ext_load_custom_preset(const uint8_t* preset_data) { //load config furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle); cc1101_reset(subghz_device_cc1101_ext->spi_bus_handle); @@ -257,7 +257,7 @@ void subghz_device_cc1101_ext_load_custom_preset(uint8_t* preset_data) { } } -void subghz_device_cc1101_ext_load_registers(uint8_t* data) { +void subghz_device_cc1101_ext_load_registers(const uint8_t* data) { furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle); cc1101_reset(subghz_device_cc1101_ext->spi_bus_handle); uint32_t i = 0; diff --git a/applications/drivers/subghz/cc1101_ext/cc1101_ext.h b/applications/drivers/subghz/cc1101_ext/cc1101_ext.h index 6d91373ad..d972fcb66 100644 --- a/applications/drivers/subghz/cc1101_ext/cc1101_ext.h +++ b/applications/drivers/subghz/cc1101_ext/cc1101_ext.h @@ -58,13 +58,13 @@ void subghz_device_cc1101_ext_dump_state(); * * @param preset_data registers to load */ -void subghz_device_cc1101_ext_load_custom_preset(uint8_t* preset_data); +void subghz_device_cc1101_ext_load_custom_preset(const uint8_t* preset_data); /** Load registers * * @param data Registers data */ -void subghz_device_cc1101_ext_load_registers(uint8_t* data); +void subghz_device_cc1101_ext_load_registers(const uint8_t* data); /** Load PATABLE * diff --git a/applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.c b/applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.c index 15c1686a7..51f5a0d1d 100644 --- a/applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.c +++ b/applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.c @@ -33,27 +33,27 @@ static void subghz_device_cc1101_ext_interconnect_load_preset( switch(preset) { case FuriHalSubGhzPresetOok650Async: subghz_device_cc1101_ext_load_custom_preset( - (uint8_t*)subghz_device_cc1101_preset_ook_650khz_async_regs); + subghz_device_cc1101_preset_ook_650khz_async_regs); break; case FuriHalSubGhzPresetOok270Async: subghz_device_cc1101_ext_load_custom_preset( - (uint8_t*)subghz_device_cc1101_preset_ook_270khz_async_regs); + subghz_device_cc1101_preset_ook_270khz_async_regs); break; case FuriHalSubGhzPreset2FSKDev238Async: subghz_device_cc1101_ext_load_custom_preset( - (uint8_t*)subghz_device_cc1101_preset_2fsk_dev2_38khz_async_regs); + subghz_device_cc1101_preset_2fsk_dev2_38khz_async_regs); break; case FuriHalSubGhzPreset2FSKDev476Async: subghz_device_cc1101_ext_load_custom_preset( - (uint8_t*)subghz_device_cc1101_preset_2fsk_dev47_6khz_async_regs); + subghz_device_cc1101_preset_2fsk_dev47_6khz_async_regs); break; case FuriHalSubGhzPresetMSK99_97KbAsync: subghz_device_cc1101_ext_load_custom_preset( - (uint8_t*)subghz_device_cc1101_preset_msk_99_97kb_async_regs); + subghz_device_cc1101_preset_msk_99_97kb_async_regs); break; case FuriHalSubGhzPresetGFSK9_99KbAsync: subghz_device_cc1101_ext_load_custom_preset( - (uint8_t*)subghz_device_cc1101_preset_gfsk_9_99kb_async_regs); + subghz_device_cc1101_preset_gfsk_9_99kb_async_regs); break; default: diff --git a/applications/main/subghz/subghz_cli.c b/applications/main/subghz/subghz_cli.c index efb191ce4..838297acd 100644 --- a/applications/main/subghz/subghz_cli.c +++ b/applications/main/subghz/subghz_cli.c @@ -61,8 +61,7 @@ void subghz_cli_command_tx_carrier(Cli* cli, FuriString* args, void* context) { } furi_hal_subghz_reset(); - furi_hal_subghz_load_custom_preset( - (uint8_t*)subghz_device_cc1101_preset_ook_650khz_async_regs); + furi_hal_subghz_load_custom_preset(subghz_device_cc1101_preset_ook_650khz_async_regs); frequency = furi_hal_subghz_set_frequency_and_path(frequency); furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); @@ -106,8 +105,7 @@ void subghz_cli_command_rx_carrier(Cli* cli, FuriString* args, void* context) { } furi_hal_subghz_reset(); - furi_hal_subghz_load_custom_preset( - (uint8_t*)subghz_device_cc1101_preset_ook_650khz_async_regs); + furi_hal_subghz_load_custom_preset(subghz_device_cc1101_preset_ook_650khz_async_regs); frequency = furi_hal_subghz_set_frequency_and_path(frequency); printf("Receiving at frequency %lu Hz\r\n", frequency); printf("Press CTRL+C to stop\r\n"); @@ -403,8 +401,7 @@ void subghz_cli_command_rx_raw(Cli* cli, FuriString* args, void* context) { // Configure radio furi_hal_subghz_reset(); - furi_hal_subghz_load_custom_preset( - (uint8_t*)subghz_device_cc1101_preset_ook_650khz_async_regs); + furi_hal_subghz_load_custom_preset(subghz_device_cc1101_preset_ook_650khz_async_regs); frequency = furi_hal_subghz_set_frequency_and_path(frequency); furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow); diff --git a/applications/main/subghz/views/subghz_test_carrier.c b/applications/main/subghz/views/subghz_test_carrier.c index 6017dd237..8c26f478c 100644 --- a/applications/main/subghz/views/subghz_test_carrier.c +++ b/applications/main/subghz/views/subghz_test_carrier.c @@ -141,8 +141,7 @@ void subghz_test_carrier_enter(void* context) { SubGhzTestCarrier* subghz_test_carrier = context; furi_hal_subghz_reset(); - furi_hal_subghz_load_custom_preset( - (uint8_t*)subghz_device_cc1101_preset_ook_650khz_async_regs); + furi_hal_subghz_load_custom_preset(subghz_device_cc1101_preset_ook_650khz_async_regs); furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow); diff --git a/applications/main/subghz/views/subghz_test_packet.c b/applications/main/subghz/views/subghz_test_packet.c index 16e33d121..bc2c474b5 100644 --- a/applications/main/subghz/views/subghz_test_packet.c +++ b/applications/main/subghz/views/subghz_test_packet.c @@ -195,8 +195,7 @@ void subghz_test_packet_enter(void* context) { SubGhzTestPacket* instance = context; furi_hal_subghz_reset(); - furi_hal_subghz_load_custom_preset( - (uint8_t*)subghz_device_cc1101_preset_ook_650khz_async_regs); + furi_hal_subghz_load_custom_preset(subghz_device_cc1101_preset_ook_650khz_async_regs); with_view_model( instance->view, diff --git a/applications/main/subghz/views/subghz_test_static.c b/applications/main/subghz/views/subghz_test_static.c index e63cb3576..d696eb1dd 100644 --- a/applications/main/subghz/views/subghz_test_static.c +++ b/applications/main/subghz/views/subghz_test_static.c @@ -144,8 +144,7 @@ void subghz_test_static_enter(void* context) { SubGhzTestStatic* instance = context; furi_hal_subghz_reset(); - furi_hal_subghz_load_custom_preset( - (uint8_t*)subghz_device_cc1101_preset_ook_650khz_async_regs); + furi_hal_subghz_load_custom_preset(subghz_device_cc1101_preset_ook_650khz_async_regs); furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); furi_hal_gpio_write(&gpio_cc1101_g0, false); diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 636441a64..81b0e7bf7 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1397,9 +1397,9 @@ Function,+,furi_hal_subghz_is_async_tx_complete,_Bool, Function,+,furi_hal_subghz_is_frequency_valid,_Bool,uint32_t Function,+,furi_hal_subghz_is_rx_data_crc_valid,_Bool, Function,+,furi_hal_subghz_is_tx_allowed,_Bool,uint32_t -Function,+,furi_hal_subghz_load_custom_preset,void,uint8_t* +Function,+,furi_hal_subghz_load_custom_preset,void,const uint8_t* Function,+,furi_hal_subghz_load_patable,void,const uint8_t[8] -Function,+,furi_hal_subghz_load_registers,void,uint8_t* +Function,+,furi_hal_subghz_load_registers,void,const uint8_t* Function,+,furi_hal_subghz_read_packet,void,"uint8_t*, uint8_t*" Function,+,furi_hal_subghz_reset,void, Function,+,furi_hal_subghz_rx,void, diff --git a/firmware/targets/f7/furi_hal/furi_hal_subghz.c b/firmware/targets/f7/furi_hal/furi_hal_subghz.c index bf002fb75..936d685fe 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_subghz.c +++ b/firmware/targets/f7/furi_hal/furi_hal_subghz.c @@ -152,7 +152,7 @@ void furi_hal_subghz_dump_state() { furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); } -void furi_hal_subghz_load_custom_preset(uint8_t* preset_data) { +void furi_hal_subghz_load_custom_preset(const uint8_t* preset_data) { //load config furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); cc1101_reset(&furi_hal_spi_bus_handle_subghz); @@ -182,7 +182,7 @@ void furi_hal_subghz_load_custom_preset(uint8_t* preset_data) { } } -void furi_hal_subghz_load_registers(uint8_t* data) { +void furi_hal_subghz_load_registers(const uint8_t* data) { furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); cc1101_reset(&furi_hal_spi_bus_handle_subghz); uint32_t i = 0; diff --git a/firmware/targets/f7/furi_hal/furi_hal_subghz.h b/firmware/targets/f7/furi_hal/furi_hal_subghz.h index dee7192f6..c7249e1a6 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_subghz.h +++ b/firmware/targets/f7/furi_hal/furi_hal_subghz.h @@ -64,13 +64,13 @@ void furi_hal_subghz_dump_state(); * * @param preset_data registers to load */ -void furi_hal_subghz_load_custom_preset(uint8_t* preset_data); +void furi_hal_subghz_load_custom_preset(const uint8_t* preset_data); /** Load registers * * @param data Registers data */ -void furi_hal_subghz_load_registers(uint8_t* data); +void furi_hal_subghz_load_registers(const uint8_t* data); /** Load PATABLE * diff --git a/lib/subghz/devices/cc1101_int/cc1101_int_interconnect.c b/lib/subghz/devices/cc1101_int/cc1101_int_interconnect.c index 2299eea50..41a0609df 100644 --- a/lib/subghz/devices/cc1101_int/cc1101_int_interconnect.c +++ b/lib/subghz/devices/cc1101_int/cc1101_int_interconnect.c @@ -30,28 +30,22 @@ static void subghz_device_cc1101_int_interconnect_load_preset( uint8_t* preset_data) { switch(preset) { case FuriHalSubGhzPresetOok650Async: - furi_hal_subghz_load_custom_preset( - (uint8_t*)subghz_device_cc1101_preset_ook_650khz_async_regs); + furi_hal_subghz_load_custom_preset(subghz_device_cc1101_preset_ook_650khz_async_regs); break; case FuriHalSubGhzPresetOok270Async: - furi_hal_subghz_load_custom_preset( - (uint8_t*)subghz_device_cc1101_preset_ook_270khz_async_regs); + furi_hal_subghz_load_custom_preset(subghz_device_cc1101_preset_ook_270khz_async_regs); break; case FuriHalSubGhzPreset2FSKDev238Async: - furi_hal_subghz_load_custom_preset( - (uint8_t*)subghz_device_cc1101_preset_2fsk_dev2_38khz_async_regs); + furi_hal_subghz_load_custom_preset(subghz_device_cc1101_preset_2fsk_dev2_38khz_async_regs); break; case FuriHalSubGhzPreset2FSKDev476Async: - furi_hal_subghz_load_custom_preset( - (uint8_t*)subghz_device_cc1101_preset_2fsk_dev47_6khz_async_regs); + furi_hal_subghz_load_custom_preset(subghz_device_cc1101_preset_2fsk_dev47_6khz_async_regs); break; case FuriHalSubGhzPresetMSK99_97KbAsync: - furi_hal_subghz_load_custom_preset( - (uint8_t*)subghz_device_cc1101_preset_msk_99_97kb_async_regs); + furi_hal_subghz_load_custom_preset(subghz_device_cc1101_preset_msk_99_97kb_async_regs); break; case FuriHalSubGhzPresetGFSK9_99KbAsync: - furi_hal_subghz_load_custom_preset( - (uint8_t*)subghz_device_cc1101_preset_gfsk_9_99kb_async_regs); + furi_hal_subghz_load_custom_preset(subghz_device_cc1101_preset_gfsk_9_99kb_async_regs); break; default: From 90ed11e5e196d0dba38b13222377dc38971e0483 Mon Sep 17 00:00:00 2001 From: gid9798 <30450294+gid9798@users.noreply.github.com> Date: Mon, 3 Jul 2023 14:16:43 +0300 Subject: [PATCH 018/364] subrem_configurator app: Upd TXRX --- .../helpers/txrx/subghz_txrx.c | 10 +++++++--- .../helpers/txrx/subghz_txrx.h | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/applications/external/subghz_remote_configurator/helpers/txrx/subghz_txrx.c b/applications/external/subghz_remote_configurator/helpers/txrx/subghz_txrx.c index 223876c36..db485a2aa 100644 --- a/applications/external/subghz_remote_configurator/helpers/txrx/subghz_txrx.c +++ b/applications/external/subghz_remote_configurator/helpers/txrx/subghz_txrx.c @@ -64,6 +64,7 @@ SubGhzTxRx* subghz_txrx_alloc() { //set default device Internal subghz_devices_init(); + instance->radio_device_type = SubGhzRadioDeviceTypeInternal; instance->radio_device_type = subghz_txrx_radio_device_set(instance, SubGhzRadioDeviceTypeInternal); @@ -570,7 +571,7 @@ void subghz_txrx_set_raw_file_encoder_worker_callback_end( context); } -bool subghz_txrx_radio_device_is_connect_external(SubGhzTxRx* instance, const char* name) { +bool subghz_txrx_radio_device_is_external_connected(SubGhzTxRx* instance, const char* name) { furi_assert(instance); bool is_connect = false; @@ -580,7 +581,10 @@ bool subghz_txrx_radio_device_is_connect_external(SubGhzTxRx* instance, const ch subghz_txrx_radio_device_power_on(instance); } - is_connect = subghz_devices_is_connect(subghz_devices_get_by_name(name)); + const SubGhzDevice* device = subghz_devices_get_by_name(name); + if(device) { + is_connect = subghz_devices_is_connect(device); + } if(!is_otg_enabled) { subghz_txrx_radio_device_power_off(instance); @@ -593,7 +597,7 @@ SubGhzRadioDeviceType furi_assert(instance); if(radio_device_type == SubGhzRadioDeviceTypeExternalCC1101 && - subghz_txrx_radio_device_is_connect_external(instance, SUBGHZ_DEVICE_CC1101_EXT_NAME)) { + subghz_txrx_radio_device_is_external_connected(instance, SUBGHZ_DEVICE_CC1101_EXT_NAME)) { subghz_txrx_radio_device_power_on(instance); instance->radio_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_EXT_NAME); subghz_devices_begin(instance->radio_device); diff --git a/applications/external/subghz_remote_configurator/helpers/txrx/subghz_txrx.h b/applications/external/subghz_remote_configurator/helpers/txrx/subghz_txrx.h index 93748e2de..8bb7f2aee 100644 --- a/applications/external/subghz_remote_configurator/helpers/txrx/subghz_txrx.h +++ b/applications/external/subghz_remote_configurator/helpers/txrx/subghz_txrx.h @@ -326,7 +326,7 @@ void subghz_txrx_set_raw_file_encoder_worker_callback_end( * @param name Name of external radio device * @return bool True if is connected to the external radio device */ -bool subghz_txrx_radio_device_is_connect_external(SubGhzTxRx* instance, const char* name); +bool subghz_txrx_radio_device_is_external_connected(SubGhzTxRx* instance, const char* name); /* Set the selected radio device to use * From d208b69f42b4340031f397257600ed3306e6a7c2 Mon Sep 17 00:00:00 2001 From: gid9798 <30450294+gid9798@users.noreply.github.com> Date: Mon, 3 Jul 2023 14:26:47 +0300 Subject: [PATCH 019/364] subghz_playlist app: Upd external module init --- applications/external/playlist/helpers/radio_device_loader.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/applications/external/playlist/helpers/radio_device_loader.c b/applications/external/playlist/helpers/radio_device_loader.c index cdf34bd38..d2cffde58 100644 --- a/applications/external/playlist/helpers/radio_device_loader.c +++ b/applications/external/playlist/helpers/radio_device_loader.c @@ -24,7 +24,10 @@ bool radio_device_loader_is_connect_external(const char* name) { radio_device_loader_power_on(); } - is_connect = subghz_devices_is_connect(subghz_devices_get_by_name(name)); + const SubGhzDevice* device = subghz_devices_get_by_name(name); + if(device) { + is_connect = subghz_devices_is_connect(device); + } if(!is_otg_enabled) { radio_device_loader_power_off(); From d4300a10dd57e72165f8f987a2dec0ad43714987 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 4 Jul 2023 14:03:11 +0300 Subject: [PATCH 020/364] fix --- applications/external/swd_probe/swd_probe_app.h | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/applications/external/swd_probe/swd_probe_app.h b/applications/external/swd_probe/swd_probe_app.h index ff7154caf..5a45a4fd9 100644 --- a/applications/external/swd_probe/swd_probe_app.h +++ b/applications/external/swd_probe/swd_probe_app.h @@ -1,5 +1,5 @@ -#ifndef __ARHA_FLIPPERAPP_DEMO -#define __ARHA_FLIPPERAPP_DEMO +#ifndef __SWD_PROBE_APP_H +#define __SWD_PROBE_APP_H #include #include @@ -18,10 +18,6 @@ #include #include #include -#include -#include -#include -#include #include "usb_uart.h" From dc4f264bcb4eff868f725b3f7e3e3cf27915c2a4 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Wed, 5 Jul 2023 02:13:55 +0200 Subject: [PATCH 021/364] Case insensitive search (damn you furi strings) --- applications/main/archive/scenes/archive_scene_search.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/main/archive/scenes/archive_scene_search.c b/applications/main/archive/scenes/archive_scene_search.c index 458708bae..1d821f684 100644 --- a/applications/main/archive/scenes/archive_scene_search.c +++ b/applications/main/archive/scenes/archive_scene_search.c @@ -67,7 +67,7 @@ uint32_t archive_scene_search_dirwalk(void* context) { if(!file_info_is_dir(&fileinfo)) { furi_string_set( name, furi_string_get_cstr(path) + furi_string_search_rchar(path, '/') + 1); - if(furi_string_search_str(name, archive->text_store) != FURI_STRING_FAILURE) { + if(strcasestr(furi_string_get_cstr(name), archive->text_store) != NULL) { archive_add_file_item(archive->browser, false, furi_string_get_cstr(path)); archive_set_item_count(archive->browser, ++count); } From 08bafc478e98f6d179e059759cc13d9bf199a151 Mon Sep 17 00:00:00 2001 From: Eric Betts Date: Wed, 5 Jul 2023 02:26:50 -0700 Subject: [PATCH 022/364] Picopass fix ice (#2836) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix copypaste error * Add iCE key to dictionary * Write iCE key as elite, others with standard kdf Co-authored-by: ã‚ã --- applications/external/picopass/picopass_device.c | 3 +++ applications/external/picopass/picopass_device.h | 1 + applications/external/picopass/picopass_worker.c | 3 ++- .../external/picopass/scenes/picopass_scene_key_menu.c | 8 ++++++-- .../apps_data/picopass/assets/iclass_elite_dict.txt | 4 ++++ 5 files changed, 16 insertions(+), 3 deletions(-) diff --git a/applications/external/picopass/picopass_device.c b/applications/external/picopass/picopass_device.c index 53778cfb3..de43b0bb7 100644 --- a/applications/external/picopass/picopass_device.c +++ b/applications/external/picopass/picopass_device.c @@ -16,6 +16,7 @@ PicopassDevice* picopass_device_alloc() { PicopassDevice* picopass_dev = malloc(sizeof(PicopassDevice)); picopass_dev->dev_data.pacs.legacy = false; picopass_dev->dev_data.pacs.se_enabled = false; + picopass_dev->dev_data.pacs.elite_kdf = false; picopass_dev->dev_data.pacs.pin_length = 0; picopass_dev->storage = furi_record_open(RECORD_STORAGE); picopass_dev->dialogs = furi_record_open(RECORD_DIALOGS); @@ -77,6 +78,7 @@ static bool picopass_device_save_file( break; } } + // TODO: Add elite if(!flipper_format_write_comment_cstr(file, "Picopass blocks")) break; bool block_saved = true; @@ -256,6 +258,7 @@ void picopass_device_data_clear(PicopassDeviceData* dev_data) { } dev_data->pacs.legacy = false; dev_data->pacs.se_enabled = false; + dev_data->pacs.elite_kdf = false; dev_data->pacs.pin_length = 0; } diff --git a/applications/external/picopass/picopass_device.h b/applications/external/picopass/picopass_device.h index 7fc35ebda..b45df346c 100644 --- a/applications/external/picopass/picopass_device.h +++ b/applications/external/picopass/picopass_device.h @@ -62,6 +62,7 @@ typedef struct { bool sio; bool biometrics; uint8_t key[8]; + bool elite_kdf; uint8_t pin_length; PicopassEncryption encryption; uint8_t credential[8]; diff --git a/applications/external/picopass/picopass_worker.c b/applications/external/picopass/picopass_worker.c index e671552c5..6301704ca 100644 --- a/applications/external/picopass/picopass_worker.c +++ b/applications/external/picopass/picopass_worker.c @@ -550,6 +550,7 @@ void picopass_worker_elite_dict_attack(PicopassWorker* picopass_worker) { if(err == ERR_NONE) { FURI_LOG_I(TAG, "Found key"); memcpy(pacs->key, key, PICOPASS_BLOCK_LEN); + pacs->elite_kdf = elite; err = picopass_read_card(AA1); if(err != ERR_NONE) { FURI_LOG_E(TAG, "picopass_read_card error %d", err); @@ -720,7 +721,7 @@ void picopass_worker_write_key(PicopassWorker* picopass_worker) { uint8_t* oldKey = AA1[PICOPASS_KD_BLOCK_INDEX].data; uint8_t newKey[PICOPASS_BLOCK_LEN] = {0}; - loclass_iclass_calc_div_key(csn, pacs->key, newKey, false); + loclass_iclass_calc_div_key(csn, pacs->key, newKey, pacs->elite_kdf); if((fuses & 0x80) == 0x80) { FURI_LOG_D(TAG, "Plain write for personalized mode key change"); diff --git a/applications/external/picopass/scenes/picopass_scene_key_menu.c b/applications/external/picopass/scenes/picopass_scene_key_menu.c index 8aac6cb24..15a32ff44 100644 --- a/applications/external/picopass/scenes/picopass_scene_key_menu.c +++ b/applications/external/picopass/scenes/picopass_scene_key_menu.c @@ -60,24 +60,28 @@ bool picopass_scene_key_menu_on_event(void* context, SceneManagerEvent event) { scene_manager_set_scene_state( picopass->scene_manager, PicopassSceneKeyMenu, SubmenuIndexWriteStandard); memcpy(picopass->dev->dev_data.pacs.key, picopass_iclass_key, PICOPASS_BLOCK_LEN); + picopass->dev->dev_data.pacs.elite_kdf = false; scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteKey); consumed = true; } else if(event.event == SubmenuIndexWriteiCE) { scene_manager_set_scene_state( picopass->scene_manager, PicopassSceneKeyMenu, SubmenuIndexWriteiCE); memcpy(picopass->dev->dev_data.pacs.key, picopass_xice_key, PICOPASS_BLOCK_LEN); + picopass->dev->dev_data.pacs.elite_kdf = true; scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteKey); consumed = true; } else if(event.event == SubmenuIndexWriteiCL) { scene_manager_set_scene_state( - picopass->scene_manager, PicopassSceneKeyMenu, SubmenuIndexWriteiCE); + picopass->scene_manager, PicopassSceneKeyMenu, SubmenuIndexWriteiCL); memcpy(picopass->dev->dev_data.pacs.key, picopass_xicl_key, PICOPASS_BLOCK_LEN); + picopass->dev->dev_data.pacs.elite_kdf = false; scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteKey); consumed = true; } else if(event.event == SubmenuIndexWriteiCS) { scene_manager_set_scene_state( - picopass->scene_manager, PicopassSceneKeyMenu, SubmenuIndexWriteiCE); + picopass->scene_manager, PicopassSceneKeyMenu, SubmenuIndexWriteiCS); memcpy(picopass->dev->dev_data.pacs.key, picopass_xics_key, PICOPASS_BLOCK_LEN); + picopass->dev->dev_data.pacs.elite_kdf = false; scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteKey); consumed = true; } diff --git a/assets/resources/apps_data/picopass/assets/iclass_elite_dict.txt b/assets/resources/apps_data/picopass/assets/iclass_elite_dict.txt index d11892372..908889aec 100644 --- a/assets/resources/apps_data/picopass/assets/iclass_elite_dict.txt +++ b/assets/resources/apps_data/picopass/assets/iclass_elite_dict.txt @@ -34,4 +34,8 @@ C1B74D7478053AE2 # default iCLASS RFIDeas 6B65797374726B72 +# CTF key 5C100DF7042EAE64 + +# iCopy-X DRM key (iCE product) +2020666666668888 From 9e67fe6794ca6da66600d97a2a7d77ce58a21dd1 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 5 Jul 2023 14:41:28 +0300 Subject: [PATCH 023/364] BLE stuff + BadBT fixes by Willy-JL --- .../external/bad_bt/helpers/ducky_script.c | 51 +++++++++++-------- .../bad_bt/scenes/bad_bt_scene_config_mac.c | 7 ++- firmware/targets/f7/api_symbols.csv | 3 +- firmware/targets/f7/ble_glue/gap.h | 2 +- firmware/targets/f7/furi_hal/furi_hal_bt.c | 43 +++++++++------- .../targets/f7/furi_hal/furi_hal_version.c | 6 ++- .../targets/furi_hal_include/furi_hal_bt.h | 13 ++++- .../furi_hal_include/furi_hal_version.h | 3 +- 8 files changed, 84 insertions(+), 44 deletions(-) diff --git a/applications/external/bad_bt/helpers/ducky_script.c b/applications/external/bad_bt/helpers/ducky_script.c index a59d377f2..96807f44d 100644 --- a/applications/external/bad_bt/helpers/ducky_script.c +++ b/applications/external/bad_bt/helpers/ducky_script.c @@ -64,10 +64,11 @@ static inline void update_bt_timeout(Bt* bt) { } typedef enum { - WorkerEvtToggle = (1 << 0), - WorkerEvtEnd = (1 << 1), - WorkerEvtConnect = (1 << 2), - WorkerEvtDisconnect = (1 << 3), + WorkerEvtStartStop = (1 << 0), + WorkerEvtPauseResume = (1 << 1), + WorkerEvtEnd = (1 << 2), + WorkerEvtConnect = (1 << 3), + WorkerEvtDisconnect = (1 << 4), } WorkerEvtFlags; static const char ducky_cmd_id[] = {"ID"}; @@ -280,6 +281,7 @@ static bool ducky_set_bt_id(BadBtScript* bad_bt, const char* line) { return false; } } + furi_hal_bt_reverse_mac_addr(mac); furi_hal_bt_set_profile_adv_name(FuriHalBtProfileHidKeyboard, line + mac_len); bt_set_profile_mac_address(bad_bt->bt, mac); @@ -498,24 +500,26 @@ static int32_t bad_bt_worker(void* context) { } else if(worker_state == BadBtStateNotConnected) { // State: Not connected uint32_t flags = bad_bt_flags_get( - WorkerEvtEnd | WorkerEvtConnect | WorkerEvtToggle, FuriWaitForever); + WorkerEvtEnd | WorkerEvtConnect | WorkerEvtDisconnect | WorkerEvtStartStop, + FuriWaitForever); if(flags & WorkerEvtEnd) { break; } else if(flags & WorkerEvtConnect) { worker_state = BadBtStateIdle; // Ready to run - } else if(flags & WorkerEvtToggle) { + } else if(flags & WorkerEvtStartStop) { worker_state = BadBtStateWillRun; // Will run when connected } bad_bt->st.state = worker_state; } else if(worker_state == BadBtStateIdle) { // State: ready to start uint32_t flags = bad_bt_flags_get( - WorkerEvtEnd | WorkerEvtToggle | WorkerEvtDisconnect, FuriWaitForever); + WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtConnect | WorkerEvtDisconnect, + FuriWaitForever); if(flags & WorkerEvtEnd) { break; - } else if(flags & WorkerEvtToggle) { // Start executing script + } else if(flags & WorkerEvtStartStop) { // Start executing script delay_val = 0; bad_bt->buf_len = 0; bad_bt->st.line_cur = 0; @@ -534,7 +538,8 @@ static int32_t bad_bt_worker(void* context) { } else if(worker_state == BadBtStateWillRun) { // State: start on connection uint32_t flags = bad_bt_flags_get( - WorkerEvtEnd | WorkerEvtConnect | WorkerEvtToggle, FuriWaitForever); + WorkerEvtEnd | WorkerEvtConnect | WorkerEvtDisconnect | WorkerEvtStartStop, + FuriWaitForever); if(flags & WorkerEvtEnd) { break; @@ -549,21 +554,21 @@ static int32_t bad_bt_worker(void* context) { storage_file_seek(script_file, 0, true); // extra time for PC to recognize Flipper as keyboard flags = furi_thread_flags_wait( - WorkerEvtEnd | WorkerEvtDisconnect | WorkerEvtToggle, + WorkerEvtEnd | WorkerEvtDisconnect | WorkerEvtStartStop, FuriFlagWaitAny | FuriFlagNoClear, 1500); if(flags == (unsigned)FuriFlagErrorTimeout) { // If nothing happened - start script execution worker_state = BadBtStateRunning; - } else if(flags & WorkerEvtToggle) { + } else if(flags & WorkerEvtStartStop) { worker_state = BadBtStateIdle; - furi_thread_flags_clear(WorkerEvtToggle); + furi_thread_flags_clear(WorkerEvtStartStop); } update_bt_timeout(bad_bt->bt); bad_bt_script_set_keyboard_layout(bad_bt, bad_bt->keyboard_layout); - } else if(flags & WorkerEvtToggle) { // Cancel scheduled execution + } else if(flags & WorkerEvtStartStop) { // Cancel scheduled execution worker_state = BadBtStateNotConnected; } bad_bt->st.state = worker_state; @@ -571,13 +576,15 @@ static int32_t bad_bt_worker(void* context) { } else if(worker_state == BadBtStateRunning) { // State: running uint16_t delay_cur = (delay_val > 1000) ? (1000) : (delay_val); uint32_t flags = furi_thread_flags_wait( - WorkerEvtEnd | WorkerEvtToggle | WorkerEvtDisconnect, FuriFlagWaitAny, delay_cur); + WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtConnect | WorkerEvtDisconnect, + FuriFlagWaitAny, + delay_cur); delay_val -= delay_cur; if(!(flags & FuriFlagError)) { if(flags & WorkerEvtEnd) { break; - } else if(flags & WorkerEvtToggle) { + } else if(flags & WorkerEvtStartStop) { worker_state = BadBtStateIdle; // Stop executing script furi_hal_bt_hid_kb_release_all(); @@ -630,11 +637,14 @@ static int32_t bad_bt_worker(void* context) { } else if(worker_state == BadBtStateWaitForBtn) { // State: Wait for button Press uint16_t delay_cur = (delay_val > 1000) ? (1000) : (delay_val); uint32_t flags = furi_thread_flags_wait( - WorkerEvtEnd | WorkerEvtToggle | WorkerEvtDisconnect, FuriFlagWaitAny, delay_cur); + WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtPauseResume | WorkerEvtConnect | + WorkerEvtDisconnect, + FuriFlagWaitAny, + delay_cur); if(!(flags & FuriFlagError)) { if(flags & WorkerEvtEnd) { break; - } else if(flags & WorkerEvtToggle) { + } else if(flags & WorkerEvtStartStop) { delay_val = 0; worker_state = BadBtStateRunning; } else if(flags & WorkerEvtDisconnect) { @@ -646,14 +656,15 @@ static int32_t bad_bt_worker(void* context) { } } else if(worker_state == BadBtStateStringDelay) { // State: print string with delays uint32_t flags = furi_thread_flags_wait( - WorkerEvtEnd | WorkerEvtToggle | WorkerEvtDisconnect, + WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtPauseResume | WorkerEvtConnect | + WorkerEvtDisconnect, FuriFlagWaitAny, bad_bt->stringdelay); if(!(flags & FuriFlagError)) { if(flags & WorkerEvtEnd) { break; - } else if(flags & WorkerEvtToggle) { + } else if(flags & WorkerEvtStartStop) { worker_state = BadBtStateIdle; // Stop executing script furi_hal_bt_hid_kb_release_all(); @@ -768,7 +779,7 @@ void bad_bt_script_set_keyboard_layout(BadBtScript* bad_bt, FuriString* layout_p void bad_bt_script_toggle(BadBtScript* bad_bt) { furi_assert(bad_bt); - furi_thread_flags_set(furi_thread_get_id(bad_bt->thread), WorkerEvtToggle); + furi_thread_flags_set(furi_thread_get_id(bad_bt->thread), WorkerEvtStartStop); } BadBtState* bad_bt_script_get_state(BadBtScript* bad_bt) { diff --git a/applications/external/bad_bt/scenes/bad_bt_scene_config_mac.c b/applications/external/bad_bt/scenes/bad_bt_scene_config_mac.c index 47f63e08c..dcc783f0f 100644 --- a/applications/external/bad_bt/scenes/bad_bt_scene_config_mac.c +++ b/applications/external/bad_bt/scenes/bad_bt_scene_config_mac.c @@ -11,6 +11,8 @@ void bad_bt_scene_config_mac_byte_input_callback(void* context) { void bad_bt_scene_config_mac_on_enter(void* context) { BadBtApp* bad_bt = context; + furi_hal_bt_reverse_mac_addr(bad_bt->config.bt_mac); + // Setup view ByteInput* byte_input = bad_bt->byte_input; byte_input_set_header_text(byte_input, "Set BT MAC address"); @@ -30,7 +32,6 @@ bool bad_bt_scene_config_mac_on_event(void* context, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { if(event.event == BadBtAppCustomEventByteInputDone) { - bt_set_profile_mac_address(bad_bt->bt, bad_bt->config.bt_mac); scene_manager_previous_scene(bad_bt->scene_manager); consumed = true; } @@ -41,6 +42,10 @@ bool bad_bt_scene_config_mac_on_event(void* context, SceneManagerEvent event) { void bad_bt_scene_config_mac_on_exit(void* context) { BadBtApp* bad_bt = context; + furi_hal_bt_reverse_mac_addr(bad_bt->config.bt_mac); + + bt_set_profile_mac_address(bad_bt->bt, bad_bt->config.bt_mac); + // Clear view byte_input_set_result_callback(bad_bt->byte_input, NULL, NULL, NULL, NULL, 0); byte_input_set_header_text(bad_bt->byte_input, ""); diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index f8d7ef279..a636a757a 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1072,6 +1072,7 @@ Function,+,furi_hal_bt_lock_core2,void, Function,+,furi_hal_bt_nvm_sram_sem_acquire,void, Function,+,furi_hal_bt_nvm_sram_sem_release,void, Function,+,furi_hal_bt_reinit,void, +Function,+,furi_hal_bt_reverse_mac_addr,void,uint8_t[( 6 )] Function,+,furi_hal_bt_serial_notify_buffer_is_empty,void, Function,+,furi_hal_bt_serial_set_event_callback,void,"uint16_t, FuriHalBtSerialCallback, void*" Function,+,furi_hal_bt_serial_set_rpc_status,void,FuriHalBtSerialRpcStatus @@ -1079,7 +1080,7 @@ Function,+,furi_hal_bt_serial_start,void, Function,+,furi_hal_bt_serial_stop,void, Function,+,furi_hal_bt_serial_tx,_Bool,"uint8_t*, uint16_t" Function,+,furi_hal_bt_set_key_storage_change_callback,void,"BleGlueKeyStorageChangedCallback, void*" -Function,+,furi_hal_bt_set_profile_adv_name,void,"FuriHalBtProfile, const char[( 18 + 1 )]" +Function,+,furi_hal_bt_set_profile_adv_name,void,"FuriHalBtProfile, const char[( ( 1 + 8 + ( 8 + 1 ) ) + 1 )]" Function,+,furi_hal_bt_set_profile_mac_addr,void,"FuriHalBtProfile, const uint8_t[( 6 )]" Function,+,furi_hal_bt_set_profile_pairing_method,void,"FuriHalBtProfile, GapPairing" Function,+,furi_hal_bt_start_advertising,void, diff --git a/firmware/targets/f7/ble_glue/gap.h b/firmware/targets/f7/ble_glue/gap.h index 7b317e06c..396d64e67 100644 --- a/firmware/targets/f7/ble_glue/gap.h +++ b/firmware/targets/f7/ble_glue/gap.h @@ -67,7 +67,7 @@ typedef struct { bool bonding_mode; GapPairing pairing_method; uint8_t mac_address[GAP_MAC_ADDR_SIZE]; - char adv_name[FURI_HAL_VERSION_DEVICE_NAME_LENGTH]; + char adv_name[FURI_HAL_BT_ADV_NAME_LENGTH]; GapConnectionParamsRequest conn_param; } GapConfig; diff --git a/firmware/targets/f7/furi_hal/furi_hal_bt.c b/firmware/targets/f7/furi_hal/furi_hal_bt.c index 2fc028d7c..5a150d388 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_bt.c +++ b/firmware/targets/f7/furi_hal/furi_hal_bt.c @@ -14,9 +14,6 @@ #define TAG "FuriHalBt" -#define FURI_HAL_BT_DEFAULT_MAC_ADDR \ - { 0x6c, 0x7a, 0xd8, 0xac, 0x57, 0x72 } - /* Time, in ms, to wait for mode transition before crashing */ #define C2_MODE_SWITCH_TIMEOUT 10000 @@ -238,28 +235,29 @@ bool furi_hal_bt_start_app(FuriHalBtProfile profile, GapEventCallback event_cb, strlcpy( config->adv_name, furi_hal_version_get_ble_local_device_name_ptr(), - FURI_HAL_VERSION_DEVICE_NAME_LENGTH); + FURI_HAL_BT_ADV_NAME_LENGTH); config->adv_service_uuid |= furi_hal_version_get_hw_color(); } else if(profile == FuriHalBtProfileHidKeyboard) { // Change MAC address for HID profile - uint8_t default_mac[sizeof(config->mac_address)] = FURI_HAL_BT_DEFAULT_MAC_ADDR; const uint8_t* normal_mac = furi_hal_version_get_ble_mac(); - if(memcmp(config->mac_address, default_mac, sizeof(config->mac_address)) == 0) { + uint8_t empty_mac[sizeof(config->mac_address)] = FURI_HAL_BT_EMPTY_MAC_ADDR; + uint8_t default_mac[sizeof(config->mac_address)] = FURI_HAL_BT_DEFAULT_MAC_ADDR; + if(memcmp(config->mac_address, empty_mac, sizeof(config->mac_address)) == 0 || + memcmp(config->mac_address, normal_mac, sizeof(config->mac_address)) == 0 || + memcmp(config->mac_address, default_mac, sizeof(config->mac_address)) == 0) { memcpy(config->mac_address, normal_mac, sizeof(config->mac_address)); - } - if(memcmp(config->mac_address, normal_mac, sizeof(config->mac_address)) == 0) { config->mac_address[2]++; } // Change name Flipper -> Control - if(strnlen(config->adv_name, FURI_HAL_VERSION_DEVICE_NAME_LENGTH) < 2 || - strnlen(config->adv_name + 1, FURI_HAL_VERSION_DEVICE_NAME_LENGTH) < 1) { + if(strnlen(config->adv_name, FURI_HAL_BT_ADV_NAME_LENGTH) < 2 || + strnlen(config->adv_name + 1, FURI_HAL_BT_ADV_NAME_LENGTH - 1) < 1) { snprintf( config->adv_name, - FURI_HAL_VERSION_DEVICE_NAME_LENGTH, + FURI_HAL_BT_ADV_NAME_LENGTH, "%cControl %s", - *furi_hal_version_get_ble_local_device_name_ptr(), - furi_hal_version_get_ble_local_device_name_ptr() + 9); + AD_TYPE_COMPLETE_LOCAL_NAME, + furi_hal_version_get_name_ptr()); } } if(!gap_init(config, event_cb, context)) { @@ -485,6 +483,15 @@ uint32_t furi_hal_bt_get_conn_rssi(uint8_t* rssi) { return since; } +void furi_hal_bt_reverse_mac_addr(uint8_t mac_addr[GAP_MAC_ADDR_SIZE]) { + uint8_t tmp; + for(size_t i = 0; i < GAP_MAC_ADDR_SIZE / 2; i++) { + tmp = mac_addr[i]; + mac_addr[i] = mac_addr[GAP_MAC_ADDR_SIZE - 1 - i]; + mac_addr[GAP_MAC_ADDR_SIZE - 1 - i] = tmp; + } +} + void furi_hal_bt_set_profile_adv_name( FuriHalBtProfile profile, const char name[FURI_HAL_BT_ADV_NAME_LENGTH]) { @@ -492,13 +499,13 @@ void furi_hal_bt_set_profile_adv_name( furi_assert(name); if(strlen(name) == 0) { - memset( - &(profile_config[profile].config.adv_name[1]), - 0, - strlen(&(profile_config[profile].config.adv_name[1]))); + memset(&(profile_config[profile].config.adv_name[1]), 0, FURI_HAL_BT_ADV_NAME_LENGTH - 1); } else { profile_config[profile].config.adv_name[0] = AD_TYPE_COMPLETE_LOCAL_NAME; - memcpy(&(profile_config[profile].config.adv_name[1]), name, FURI_HAL_BT_ADV_NAME_LENGTH); + strlcpy( + &(profile_config[profile].config.adv_name[1]), + name, + FURI_HAL_BT_ADV_NAME_LENGTH - 1 /* BLE symbol */); } } diff --git a/firmware/targets/f7/furi_hal/furi_hal_version.c b/firmware/targets/f7/furi_hal/furi_hal_version.c index 0e5f428ba..b6460cc5f 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_version.c +++ b/firmware/targets/f7/furi_hal/furi_hal_version.c @@ -111,7 +111,11 @@ void furi_hal_version_set_name(const char* name) { } uint32_t company_id = LL_FLASH_GetSTCompanyID(); - uint32_t device_id = LL_FLASH_GetDeviceID(); + // uint32_t device_id = LL_FLASH_GetDeviceID(); + // Some flippers return 0x27 (flippers with chip revision 2003 6495) instead of 0x26 (flippers with chip revision 2001 6495) + // Mobile apps expects it to return 0x26 + // Hardcoded here temporarily until mobile apps is updated to handle 0x27 + uint32_t device_id = 0x26; furi_hal_version.ble_mac[0] = (uint8_t)(udn & 0x000000FF); furi_hal_version.ble_mac[1] = (uint8_t)((udn & 0x0000FF00) >> 8); furi_hal_version.ble_mac[2] = (uint8_t)((udn & 0x00FF0000) >> 16); diff --git a/firmware/targets/furi_hal_include/furi_hal_bt.h b/firmware/targets/furi_hal_include/furi_hal_bt.h index f128b1064..7354d8770 100644 --- a/firmware/targets/furi_hal_include/furi_hal_bt.h +++ b/firmware/targets/furi_hal_include/furi_hal_bt.h @@ -18,6 +18,12 @@ #define FURI_HAL_BT_STACK_VERSION_MINOR (12) #define FURI_HAL_BT_C2_START_TIMEOUT 1000 +#define FURI_HAL_BT_EMPTY_MAC_ADDR \ + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } + +#define FURI_HAL_BT_DEFAULT_MAC_ADDR \ + { 0x6c, 0x7a, 0xd8, 0xac, 0x57, 0x72 } + #ifdef __cplusplus extern "C" { #endif @@ -218,7 +224,12 @@ float furi_hal_bt_get_rssi(); */ uint32_t furi_hal_bt_get_transmitted_packets(); -// BadBT stuff +// BadBT Stuff +/** Reverse a MAC address byte order in-place + * @param[in] mac mac address to reverse +*/ +void furi_hal_bt_reverse_mac_addr(uint8_t mac_addr[GAP_MAC_ADDR_SIZE]); + /** Modify profile advertisement name and restart bluetooth * @param[in] profile profile type * @param[in] name new adv name diff --git a/firmware/targets/furi_hal_include/furi_hal_version.h b/firmware/targets/furi_hal_include/furi_hal_version.h index 4a3f4c170..351a7849b 100644 --- a/firmware/targets/furi_hal_include/furi_hal_version.h +++ b/firmware/targets/furi_hal_include/furi_hal_version.h @@ -16,9 +16,10 @@ extern "C" { #define FURI_HAL_VERSION_NAME_LENGTH 8 #define FURI_HAL_VERSION_ARRAY_NAME_LENGTH (FURI_HAL_VERSION_NAME_LENGTH + 1) -#define FURI_HAL_BT_ADV_NAME_LENGTH (18 + 1) // 18 characters + null terminator /** BLE symbol + "Flipper " + name */ #define FURI_HAL_VERSION_DEVICE_NAME_LENGTH (1 + 8 + FURI_HAL_VERSION_ARRAY_NAME_LENGTH) +// 18 characters + null terminator +#define FURI_HAL_BT_ADV_NAME_LENGTH (FURI_HAL_VERSION_DEVICE_NAME_LENGTH + 1) /** OTP Versions enum */ typedef enum { From bbbb371d357a78c2a97b9f19628d1a677d0e89c7 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 5 Jul 2023 14:51:22 +0300 Subject: [PATCH 024/364] fix typos --- firmware/targets/f7/ble_glue/gap.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/firmware/targets/f7/ble_glue/gap.c b/firmware/targets/f7/ble_glue/gap.c index 55ddc5faf..ab7b9d16c 100644 --- a/firmware/targets/f7/ble_glue/gap.c +++ b/firmware/targets/f7/ble_glue/gap.c @@ -500,7 +500,7 @@ void gap_stop_advertising() { furi_mutex_release(gap->state_mutex); } -static void gap_advetise_timer_callback(void* context) { +static void gap_advertise_timer_callback(void* context) { UNUSED(context); GapCommand command = GapCommandAdvLowPower; furi_check(furi_message_queue_put(gap->command_queue, &command, 0) == FuriStatusOk); @@ -514,7 +514,7 @@ bool gap_init(GapConfig* config, GapEventCallback on_event_cb, void* context) { gap = malloc(sizeof(Gap)); gap->config = config; // Create advertising timer - gap->advertise_timer = furi_timer_alloc(gap_advetise_timer_callback, FuriTimerTypeOnce, NULL); + gap->advertise_timer = furi_timer_alloc(gap_advertise_timer_callback, FuriTimerTypeOnce, NULL); // Initialization of GATT & GAP layer gap->service.adv_name = config->adv_name; gap_init_svc(gap); From ae545cdb0911bcf14b8a4d4d0562ccf528f1a6a4 Mon Sep 17 00:00:00 2001 From: Nikita Vostokov Date: Wed, 5 Jul 2023 15:04:56 +0300 Subject: [PATCH 025/364] Fix name reset after bt apps exit --- firmware/targets/f7/furi_hal/furi_hal_bt.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/firmware/targets/f7/furi_hal/furi_hal_bt.c b/firmware/targets/f7/furi_hal/furi_hal_bt.c index 5a150d388..870d9c967 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_bt.c +++ b/firmware/targets/f7/furi_hal/furi_hal_bt.c @@ -54,6 +54,7 @@ FuriHalBtProfileConfig profile_config[FuriHalBtProfileNumber] = { .bonding_mode = true, .pairing_method = GapPairingPinCodeShow, .mac_address = FURI_HAL_BT_DEFAULT_MAC_ADDR, + .adv_name = "\0", .conn_param = { .conn_int_min = 0x18, // 30 ms @@ -74,6 +75,7 @@ FuriHalBtProfileConfig profile_config[FuriHalBtProfileNumber] = { .bonding_mode = true, .pairing_method = GapPairingPinCodeVerifyYesNo, .mac_address = FURI_HAL_BT_DEFAULT_MAC_ADDR, + .adv_name = "\0", .conn_param = { .conn_int_min = 0x18, // 30 ms @@ -231,11 +233,12 @@ bool furi_hal_bt_start_app(FuriHalBtProfile profile, GapEventCallback event_cb, // Set mac address memcpy( config->mac_address, furi_hal_version_get_ble_mac(), sizeof(config->mac_address)); - // Set advertise name - strlcpy( - config->adv_name, - furi_hal_version_get_ble_local_device_name_ptr(), - FURI_HAL_BT_ADV_NAME_LENGTH); + // Set stock advertise name only once on bootup + if(!strnlen(config->adv_name, FURI_HAL_BT_ADV_NAME_LENGTH)) + strlcpy( + config->adv_name, + furi_hal_version_get_ble_local_device_name_ptr(), + FURI_HAL_BT_ADV_NAME_LENGTH); config->adv_service_uuid |= furi_hal_version_get_hw_color(); } else if(profile == FuriHalBtProfileHidKeyboard) { From 0980424bb9447d7969ccfa1c3ff957352644c0ed Mon Sep 17 00:00:00 2001 From: Nikita Vostokov Date: Wed, 5 Jul 2023 15:04:56 +0300 Subject: [PATCH 026/364] Revert "Fix name reset after bt apps exit" This reverts commit ae545cdb0911bcf14b8a4d4d0562ccf528f1a6a4. --- firmware/targets/f7/furi_hal/furi_hal_bt.c | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/firmware/targets/f7/furi_hal/furi_hal_bt.c b/firmware/targets/f7/furi_hal/furi_hal_bt.c index 870d9c967..5a150d388 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_bt.c +++ b/firmware/targets/f7/furi_hal/furi_hal_bt.c @@ -54,7 +54,6 @@ FuriHalBtProfileConfig profile_config[FuriHalBtProfileNumber] = { .bonding_mode = true, .pairing_method = GapPairingPinCodeShow, .mac_address = FURI_HAL_BT_DEFAULT_MAC_ADDR, - .adv_name = "\0", .conn_param = { .conn_int_min = 0x18, // 30 ms @@ -75,7 +74,6 @@ FuriHalBtProfileConfig profile_config[FuriHalBtProfileNumber] = { .bonding_mode = true, .pairing_method = GapPairingPinCodeVerifyYesNo, .mac_address = FURI_HAL_BT_DEFAULT_MAC_ADDR, - .adv_name = "\0", .conn_param = { .conn_int_min = 0x18, // 30 ms @@ -233,12 +231,11 @@ bool furi_hal_bt_start_app(FuriHalBtProfile profile, GapEventCallback event_cb, // Set mac address memcpy( config->mac_address, furi_hal_version_get_ble_mac(), sizeof(config->mac_address)); - // Set stock advertise name only once on bootup - if(!strnlen(config->adv_name, FURI_HAL_BT_ADV_NAME_LENGTH)) - strlcpy( - config->adv_name, - furi_hal_version_get_ble_local_device_name_ptr(), - FURI_HAL_BT_ADV_NAME_LENGTH); + // Set advertise name + strlcpy( + config->adv_name, + furi_hal_version_get_ble_local_device_name_ptr(), + FURI_HAL_BT_ADV_NAME_LENGTH); config->adv_service_uuid |= furi_hal_version_get_hw_color(); } else if(profile == FuriHalBtProfileHidKeyboard) { From 97fbd84e08d77a3ef2edf7a059c8d3635cd7facc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Wed, 5 Jul 2023 22:56:52 +0900 Subject: [PATCH 027/364] FuriHal, Infrared, Protobuf: various fixes and improvements (#2845) * Infrared: fix crash caused by null-ptr dereference in interrupt call. FuriHal: callback checks in interrupt handlers. Protobuf: bump to latest. * DesktopSettings: allow application browser as favourite * Format sources * FuriHal: fix pvs warnings --- .../scenes/desktop_settings_scene_favorite.c | 27 ++++++++++++++++--- assets/protobuf | 2 +- .../targets/f7/furi_hal/furi_hal_infrared.c | 3 --- .../targets/f7/furi_hal/furi_hal_interrupt.c | 11 ++++---- 4 files changed, 30 insertions(+), 13 deletions(-) diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c index c43f8e350..c35a10568 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c @@ -5,8 +5,11 @@ #include #include +#define EXTERNAL_BROWSER_NAME ("Applications") +#define EXTERNAL_BROWSER_INDEX (FLIPPER_APPS_COUNT + 1) + #define EXTERNAL_APPLICATION_NAME ("[External Application]") -#define EXTERNAL_APPLICATION_INDEX (FLIPPER_APPS_COUNT + 1) +#define EXTERNAL_APPLICATION_INDEX (FLIPPER_APPS_COUNT + 2) static bool favorite_fap_selector_item_callback( FuriString* file_path, @@ -58,14 +61,28 @@ void desktop_settings_scene_favorite_on_enter(void* context) { } } + // Special case: Application browser + submenu_add_item( + submenu, + EXTERNAL_BROWSER_NAME, + EXTERNAL_BROWSER_INDEX, + desktop_settings_scene_favorite_submenu_callback, + app); + + // Special case: Specific application submenu_add_item( submenu, EXTERNAL_APPLICATION_NAME, EXTERNAL_APPLICATION_INDEX, desktop_settings_scene_favorite_submenu_callback, app); + if(curr_favorite_app->is_external) { - pre_select_item = EXTERNAL_APPLICATION_INDEX; + if(curr_favorite_app->name_or_path[0] == '\0') { + pre_select_item = EXTERNAL_BROWSER_INDEX; + } else { + pre_select_item = EXTERNAL_APPLICATION_INDEX; + } } submenu_set_header( @@ -86,7 +103,11 @@ bool desktop_settings_scene_favorite_on_event(void* context, SceneManagerEvent e &app->settings.favorite_secondary; if(event.type == SceneManagerEventTypeCustom) { - if(event.event == EXTERNAL_APPLICATION_INDEX) { + if(event.event == EXTERNAL_BROWSER_INDEX) { + curr_favorite_app->is_external = true; + curr_favorite_app->name_or_path[0] = '\0'; + consumed = true; + } else if(event.event == EXTERNAL_APPLICATION_INDEX) { const DialogsFileBrowserOptions browser_options = { .extension = ".fap", .icon = &I_unknown_10px, diff --git a/assets/protobuf b/assets/protobuf index f71c4b7f7..08a907d95 160000 --- a/assets/protobuf +++ b/assets/protobuf @@ -1 +1 @@ -Subproject commit f71c4b7f750f2539a1fed08925d8da3abdc80ff9 +Subproject commit 08a907d95733600becc41c0602ef5ee4c4baf782 diff --git a/firmware/targets/f7/furi_hal/furi_hal_infrared.c b/firmware/targets/f7/furi_hal/furi_hal_infrared.c index c60db5f20..d3e36c2b5 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_infrared.c +++ b/firmware/targets/f7/furi_hal/furi_hal_infrared.c @@ -373,9 +373,6 @@ static void furi_hal_infrared_configure_tim_pwm_tx(uint32_t freq, float duty_cyc LL_TIM_EnableAllOutputs(INFRARED_DMA_TIMER); LL_TIM_DisableIT_UPDATE(INFRARED_DMA_TIMER); LL_TIM_EnableDMAReq_UPDATE(INFRARED_DMA_TIMER); - - NVIC_SetPriority(TIM1_UP_TIM16_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 5, 0)); - NVIC_EnableIRQ(TIM1_UP_TIM16_IRQn); } static void furi_hal_infrared_configure_tim_cmgr2_dma_tx(void) { diff --git a/firmware/targets/f7/furi_hal/furi_hal_interrupt.c b/firmware/targets/f7/furi_hal/furi_hal_interrupt.c index b5639d230..9f4d43316 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_interrupt.c +++ b/firmware/targets/f7/furi_hal/furi_hal_interrupt.c @@ -62,7 +62,7 @@ const IRQn_Type furi_hal_interrupt_irqn[FuriHalInterruptIdMax] = { __attribute__((always_inline)) static inline void furi_hal_interrupt_call(FuriHalInterruptId index) { - furi_assert(furi_hal_interrupt_isr[index].isr); + furi_check(furi_hal_interrupt_isr[index].isr); furi_hal_interrupt_isr[index].isr(furi_hal_interrupt_isr[index].context); } @@ -127,16 +127,15 @@ void furi_hal_interrupt_set_isr_ex( uint16_t priority, FuriHalInterruptISR isr, void* context) { - furi_assert(index < FuriHalInterruptIdMax); - furi_assert(priority < 15); - furi_assert(furi_hal_interrupt_irqn[index]); + furi_check(index < FuriHalInterruptIdMax); + furi_check(priority < 15); if(isr) { // Pre ISR set - furi_assert(furi_hal_interrupt_isr[index].isr == NULL); + furi_check(furi_hal_interrupt_isr[index].isr == NULL); } else { // Pre ISR clear - furi_assert(furi_hal_interrupt_isr[index].isr != NULL); + furi_check(furi_hal_interrupt_isr[index].isr != NULL); furi_hal_interrupt_disable(index); furi_hal_interrupt_clear_pending(index); } From 255830ffab64aa2aa03b9f108426508f5f252e05 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 5 Jul 2023 17:56:08 +0300 Subject: [PATCH 028/364] FAAC ui and counter with no seed fixes --- lib/subghz/protocols/faac_slh.c | 56 ++++++++++++++++++++++----------- 1 file changed, 38 insertions(+), 18 deletions(-) diff --git a/lib/subghz/protocols/faac_slh.c b/lib/subghz/protocols/faac_slh.c index c1c96b7a7..62e8c37d2 100644 --- a/lib/subghz/protocols/faac_slh.c +++ b/lib/subghz/protocols/faac_slh.c @@ -110,7 +110,9 @@ void subghz_protocol_encoder_faac_slh_free(void* context) { } static bool subghz_protocol_faac_slh_gen_data(SubGhzProtocolEncoderFaacSLH* instance) { - instance->generic.cnt += furi_hal_subghz_get_rolling_counter_mult(); + if(instance->generic.seed != 0x0) { + instance->generic.cnt += furi_hal_subghz_get_rolling_counter_mult(); + } uint32_t fix = instance->generic.serial << 4 | instance->generic.btn; uint32_t hop = 0; uint32_t decrypt = 0; @@ -499,21 +501,39 @@ void subghz_protocol_decoder_faac_slh_get_string(void* context, FuriString* outp uint32_t code_fix = instance->generic.data >> 32; uint32_t code_hop = instance->generic.data & 0xFFFFFFFF; - furi_string_cat_printf( - output, - "%s %dbit\r\n" - "Key:%lX%08lX\r\n" - "Fix:%08lX Cnt:%05lX\r\n" - "Hop:%08lX Btn:%X\r\n" - "Sn:%07lX Sd:%08lX", - instance->generic.protocol_name, - instance->generic.data_count_bit, - (uint32_t)(instance->generic.data >> 32), - (uint32_t)instance->generic.data, - code_fix, - instance->generic.cnt, - code_hop, - instance->generic.btn, - instance->generic.serial, - instance->generic.seed); + if(instance->generic.seed == 0x0) { + furi_string_cat_printf( + output, + "%s %dbit\r\n" + "Key:%lX%08lX\r\n" + "Fix:%08lX\r\n" + "Hop:%08lX Btn:%X\r\n" + "Sn:%07lX Sd:Unknown", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data >> 32), + (uint32_t)instance->generic.data, + code_fix, + code_hop, + instance->generic.btn, + instance->generic.serial); + } else { + furi_string_cat_printf( + output, + "%s %dbit\r\n" + "Key:%lX%08lX\r\n" + "Fix:%08lX Cnt:%05lX\r\n" + "Hop:%08lX Btn:%X\r\n" + "Sn:%07lX Sd:%08lX", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data >> 32), + (uint32_t)instance->generic.data, + code_fix, + instance->generic.cnt, + code_hop, + instance->generic.btn, + instance->generic.serial, + instance->generic.seed); + } } From 000bc845a811ae1242697dc62f175c9b6c79c30e Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 5 Jul 2023 17:59:09 +0300 Subject: [PATCH 029/364] Multiple Fixes by hedger --- applications/external/bomberduck/bomberduck.c | 1 - .../flappy_bird/assets/bird/frame_rate | 1 - .../assets/{bird/frame_01.png => bird_01.png} | Bin .../assets/{bird/frame_02.png => bird_02.png} | Bin .../assets/{bird/frame_03.png => bird_03.png} | Bin .../external/flappy_bird/flappy_bird.c | 27 +++++--- .../external/heap_defence_game/heap_defence.c | 1 - .../external/minesweeper/minesweeper.c | 1 - .../external/swd_probe/swd_probe_app.c | 65 ++++++++++-------- 9 files changed, 53 insertions(+), 43 deletions(-) delete mode 100644 applications/external/flappy_bird/assets/bird/frame_rate rename applications/external/flappy_bird/assets/{bird/frame_01.png => bird_01.png} (100%) rename applications/external/flappy_bird/assets/{bird/frame_02.png => bird_02.png} (100%) rename applications/external/flappy_bird/assets/{bird/frame_03.png => bird_03.png} (100%) diff --git a/applications/external/bomberduck/bomberduck.c b/applications/external/bomberduck/bomberduck.c index 79471c99e..3e9d52f56 100644 --- a/applications/external/bomberduck/bomberduck.c +++ b/applications/external/bomberduck/bomberduck.c @@ -5,7 +5,6 @@ #include #include #include -#include #include "bomberduck_icons.h" #include diff --git a/applications/external/flappy_bird/assets/bird/frame_rate b/applications/external/flappy_bird/assets/bird/frame_rate deleted file mode 100644 index e440e5c84..000000000 --- a/applications/external/flappy_bird/assets/bird/frame_rate +++ /dev/null @@ -1 +0,0 @@ -3 \ No newline at end of file diff --git a/applications/external/flappy_bird/assets/bird/frame_01.png b/applications/external/flappy_bird/assets/bird_01.png similarity index 100% rename from applications/external/flappy_bird/assets/bird/frame_01.png rename to applications/external/flappy_bird/assets/bird_01.png diff --git a/applications/external/flappy_bird/assets/bird/frame_02.png b/applications/external/flappy_bird/assets/bird_02.png similarity index 100% rename from applications/external/flappy_bird/assets/bird/frame_02.png rename to applications/external/flappy_bird/assets/bird_02.png diff --git a/applications/external/flappy_bird/assets/bird/frame_03.png b/applications/external/flappy_bird/assets/bird_03.png similarity index 100% rename from applications/external/flappy_bird/assets/bird/frame_03.png rename to applications/external/flappy_bird/assets/bird_03.png diff --git a/applications/external/flappy_bird/flappy_bird.c b/applications/external/flappy_bird/flappy_bird.c index 2a50868f2..7ec152b99 100644 --- a/applications/external/flappy_bird/flappy_bird.c +++ b/applications/external/flappy_bird/flappy_bird.c @@ -3,7 +3,6 @@ #include #include #include -#include #include #include @@ -30,6 +29,19 @@ typedef enum { EventTypeKey, } EventType; +typedef enum { + BirdState0 = 0, + BirdState1, + BirdState2, + BirdStateMAX +} BirdState; + +const Icon* bird_states[BirdStateMAX] = { + &I_bird_01, + &I_bird_02, + &I_bird_03, +}; + typedef struct { int x; int y; @@ -38,7 +50,6 @@ typedef struct { typedef struct { float gravity; POINT point; - IconAnimation* sprite; } BIRD; typedef struct { @@ -93,7 +104,6 @@ static void flappy_game_state_init(GameState* const game_state) { bird.gravity = 0.0f; bird.point.x = 15; bird.point.y = 32; - bird.sprite = icon_animation_alloc(&A_bird); game_state->debug = DEBUG; game_state->bird = bird; @@ -106,7 +116,6 @@ static void flappy_game_state_init(GameState* const game_state) { } static void flappy_game_state_free(GameState* const game_state) { - icon_animation_free(game_state->bird.sprite); free(game_state); } @@ -224,14 +233,14 @@ static void flappy_game_render_callback(Canvas* const canvas, void* ctx) { } // Switch animation - game_state->bird.sprite->frame = 1; + BirdState bird_state = BirdState1; if(game_state->bird.gravity < -0.5) - game_state->bird.sprite->frame = 0; + bird_state = BirdState0; else if(game_state->bird.gravity > 0.5) - game_state->bird.sprite->frame = 2; + bird_state = BirdState2; - canvas_draw_icon_animation( - canvas, game_state->bird.point.x, game_state->bird.point.y, game_state->bird.sprite); + canvas_draw_icon( + canvas, game_state->bird.point.x, game_state->bird.point.y, bird_states[bird_state]); canvas_set_font(canvas, FontSecondary); char buffer[12]; diff --git a/applications/external/heap_defence_game/heap_defence.c b/applications/external/heap_defence_game/heap_defence.c index 3c8483c75..111c22dce 100644 --- a/applications/external/heap_defence_game/heap_defence.c +++ b/applications/external/heap_defence_game/heap_defence.c @@ -14,7 +14,6 @@ #include #include #include -#include #include #define Y_FIELD_SIZE 6 diff --git a/applications/external/minesweeper/minesweeper.c b/applications/external/minesweeper/minesweeper.c index c169b075e..4e92fba33 100644 --- a/applications/external/minesweeper/minesweeper.c +++ b/applications/external/minesweeper/minesweeper.c @@ -6,7 +6,6 @@ #include #include -#include #include diff --git a/applications/external/swd_probe/swd_probe_app.c b/applications/external/swd_probe/swd_probe_app.c index 27ef03f5d..63f219508 100644 --- a/applications/external/swd_probe/swd_probe_app.c +++ b/applications/external/swd_probe/swd_probe_app.c @@ -679,7 +679,7 @@ static bool swd_apscan_test(AppFSM* const ctx, uint32_t ap) { static void swd_script_log(ScriptContext* ctx, FuriLogLevel level, const char* format, ...) { bool commandline = false; ScriptContext* cur = ctx; - char buffer[256]; + FuriString* buffer = furi_string_alloc(); va_list argp; va_start(argp, format); @@ -704,17 +704,19 @@ static void swd_script_log(ScriptContext* ctx, FuriLogLevel level, const char* f break; } - strcpy(buffer, prefix); - size_t pos = strlen(buffer); - vsnprintf(&buffer[pos], sizeof(buffer) - pos - 2, format, argp); - strcat(buffer, "\n"); - if(!usb_uart_tx_data(ctx->app->uart, (uint8_t*)buffer, strlen(buffer))) { + furi_string_cat_str(buffer, prefix); + furi_string_cat_printf(buffer, format, argp); + furi_string_cat_str(buffer, "\n"); + + if(!usb_uart_tx_data( + ctx->app->uart, (uint8_t*)furi_string_get_cstr(buffer), furi_string_size(buffer))) { DBGS("Sending via USB failed"); } } else { - LOG(buffer); + LOG(furi_string_get_cstr(buffer)); } va_end(argp); + furi_string_free(buffer); } /* read characters until newline was read */ @@ -939,41 +941,44 @@ static bool swd_scriptfunc_goto(ScriptContext* ctx) { return true; } +#include + static bool swd_scriptfunc_call(ScriptContext* ctx) { DBGS("call"); swd_script_skip_whitespace(ctx); /* fetch previous file directory */ - char filename[MAX_FILE_LENGTH]; - strncpy(filename, ctx->filename, sizeof(filename)); - char* path = strrchr(filename, '/'); - path[1] = '\000'; + FuriString* filepath = furi_string_alloc(); + path_extract_dirname(ctx->filename, filepath); + // strncpy(filename, ctx->filename, sizeof(filename)); - /* append filename */ - if(!swd_script_get_string(ctx, &path[1], sizeof(filename) - strlen(path))) { - swd_script_log(ctx, FuriLogLevelError, "failed to parse filename"); - return false; - } + char filename[MAX_FILE_LENGTH] = {}; + bool success = false; + do { + /* append filename */ + if(!swd_script_get_string(ctx, filename, sizeof(filename))) { + swd_script_log(ctx, FuriLogLevelError, "failed to parse filename"); + break; + } - swd_script_seek_newline(ctx); + swd_script_seek_newline(ctx); + /* append extension */ + furi_string_cat_str(filepath, ".swd"); - /* append extension */ - if(strlen(filename) + 5 >= sizeof(filename)) { - swd_script_log(ctx, FuriLogLevelError, "name too long"); - return false; - } + bool ret = swd_execute_script(ctx->app, furi_string_get_cstr(filepath)); - strcat(filename, ".swd"); + if(!ret) { + swd_script_log( + ctx, FuriLogLevelError, "failed to exec '%s'", furi_string_get_cstr(filepath)); + break; + } - bool ret = swd_execute_script(ctx->app, filename); + success = true; + } while(false); + furi_string_free(filepath); - if(!ret) { - swd_script_log(ctx, FuriLogLevelError, "failed to exec '%s'", filename); - return false; - } - - return true; + return success; } static bool swd_scriptfunc_status(ScriptContext* ctx) { From 0cff0d603ef6d765edb4eddc70f55f67634f1cdc Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 5 Jul 2023 19:21:42 +0300 Subject: [PATCH 030/364] categories --- .../barcode_generator/application.fam | 4 +-- .../external/hex_viewer/application.fam | 2 +- applications/external/hid_app/application.fam | 4 +-- .../external/multi_converter/application.fam | 2 +- .../external/multi_fuzzer/application.fam | 36 +++++++++---------- .../external/text_viewer/application.fam | 2 +- applications/external/totp/application.fam | 12 ++----- 7 files changed, 27 insertions(+), 35 deletions(-) diff --git a/applications/external/barcode_generator/application.fam b/applications/external/barcode_generator/application.fam index 61d6b7394..9bb44915c 100644 --- a/applications/external/barcode_generator/application.fam +++ b/applications/external/barcode_generator/application.fam @@ -10,8 +10,8 @@ App( stack_size=1 * 1024, order=50, fap_icon="barcode_10px.png", - fap_category="Misc", + fap_category="Tools", fap_author="@xMasterX & @msvsergey & @McAzzaMan", fap_version="1.0", fap_description="App displays Barcode on flipper screen and allows to edit it", -) \ No newline at end of file +) diff --git a/applications/external/hex_viewer/application.fam b/applications/external/hex_viewer/application.fam index 03967ffa5..5d6d3959d 100644 --- a/applications/external/hex_viewer/application.fam +++ b/applications/external/hex_viewer/application.fam @@ -10,7 +10,7 @@ App( stack_size=2 * 1024, order=20, fap_icon="icons/hex_10px.png", - fap_category="Misc", + fap_category="Tools", fap_icon_assets="icons", fap_author="@QtRoS", fap_version="1.0", diff --git a/applications/external/hid_app/application.fam b/applications/external/hid_app/application.fam index e96e956d8..c27ad9c81 100644 --- a/applications/external/hid_app/application.fam +++ b/applications/external/hid_app/application.fam @@ -4,7 +4,7 @@ App( apptype=FlipperAppType.EXTERNAL, entry_point="hid_usb_app", stack_size=1 * 1024, - fap_category="Misc", + fap_category="USB", fap_icon="hid_usb_10px.png", fap_icon_assets="assets", fap_icon_assets_symbol="hid", @@ -17,7 +17,7 @@ App( apptype=FlipperAppType.EXTERNAL, entry_point="hid_ble_app", stack_size=1 * 1024, - fap_category="Misc", + fap_category="Bluetooth", fap_icon="hid_ble_10px.png", fap_icon_assets="assets", fap_icon_assets_symbol="hid", diff --git a/applications/external/multi_converter/application.fam b/applications/external/multi_converter/application.fam index 84eaa805b..3a8a488b4 100644 --- a/applications/external/multi_converter/application.fam +++ b/applications/external/multi_converter/application.fam @@ -7,7 +7,7 @@ App( stack_size=1 * 1024, order=19, fap_icon="converter_10px.png", - fap_category="Misc", + fap_category="Tools", fap_author="@theisolinearchip", fap_version="1.0", fap_description="A multi-unit converter written with an easy and expandable system for adding new units and conversion methods", diff --git a/applications/external/multi_fuzzer/application.fam b/applications/external/multi_fuzzer/application.fam index a0ce1be0a..1fa33943f 100644 --- a/applications/external/multi_fuzzer/application.fam +++ b/applications/external/multi_fuzzer/application.fam @@ -4,21 +4,21 @@ App( apptype=FlipperAppType.EXTERNAL, entry_point="fuzzer_start_ibtn", requires=[ - "gui", + "gui", "storage", - "dialogs", - "input", + "dialogs", + "input", "notification", ], stack_size=2 * 1024, fap_icon="icons/ibutt_10px.png", - fap_category="Tools", + fap_category="iButton", fap_private_libs=[ - Lib( - name="worker", - cdefines=["IBUTTON_PROTOCOL"], - ), - ], + Lib( + name="worker", + cdefines=["IBUTTON_PROTOCOL"], + ), + ], fap_icon_assets="icons", fap_icon_assets_symbol="fuzzer", ) @@ -29,21 +29,21 @@ App( apptype=FlipperAppType.EXTERNAL, entry_point="fuzzer_start_rfid", requires=[ - "gui", + "gui", "storage", - "dialogs", - "input", + "dialogs", + "input", "notification", ], stack_size=2 * 1024, fap_icon="icons/rfid_10px.png", - fap_category="Tools", + fap_category="RFID 125", fap_private_libs=[ - Lib( - name="worker", - cdefines=["RFID_125_PROTOCOL"], - ), - ], + Lib( + name="worker", + cdefines=["RFID_125_PROTOCOL"], + ), + ], fap_icon_assets="icons", fap_icon_assets_symbol="fuzzer", ) diff --git a/applications/external/text_viewer/application.fam b/applications/external/text_viewer/application.fam index e36b7a870..6222dc643 100644 --- a/applications/external/text_viewer/application.fam +++ b/applications/external/text_viewer/application.fam @@ -10,7 +10,7 @@ App( stack_size=2 * 1024, order=20, fap_icon="icons/text_10px.png", - fap_category="Misc", + fap_category="Tools", fap_icon_assets="icons", fap_author="@kowalski7cc & @kyhwana", fap_version="1.0", diff --git a/applications/external/totp/application.fam b/applications/external/totp/application.fam index 4f71c2ce8..fcac78700 100644 --- a/applications/external/totp/application.fam +++ b/applications/external/totp/application.fam @@ -3,21 +3,13 @@ App( name="Authenticator", apptype=FlipperAppType.EXTERNAL, entry_point="totp_app", - requires=[ - "gui", - "cli", - "dialogs", - "storage", - "input", - "notification", - "bt" - ], + requires=["gui", "cli", "dialogs", "storage", "input", "notification", "bt"], stack_size=2 * 1024, order=20, fap_author="Alexander Kopachov (@akopachov)", fap_description="Software-based TOTP authenticator for Flipper Zero device", fap_weburl="https://github.com/akopachov/flipper-zero_authenticator", - fap_category="Misc", + fap_category="Tools", fap_icon_assets="images", fap_icon="totp_10px.png", fap_private_libs=[ From f6d0b1d399d15411f8d436f93ed0537b4233e6e3 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 5 Jul 2023 19:33:27 +0300 Subject: [PATCH 031/364] Change LED and volume settings by 5% steps We discussed such change before with Svarich, but while I was busy with other stuff looks like this Idea has been already implemented by cokyrain So credits goes to cokyrain (github) --- .../notification_settings_app.c | 30 ++++++++----------- 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/applications/settings/notification_settings/notification_settings_app.c b/applications/settings/notification_settings/notification_settings_app.c index 565d4f1f6..195501210 100644 --- a/applications/settings/notification_settings/notification_settings_app.c +++ b/applications/settings/notification_settings/notification_settings_app.c @@ -48,31 +48,25 @@ const int32_t contrast_value[CONTRAST_COUNT] = { 5, }; -#define BACKLIGHT_COUNT 5 +#define BACKLIGHT_COUNT 21 const char* const backlight_text[BACKLIGHT_COUNT] = { - "0%", - "25%", - "50%", - "75%", - "100%", + "0%", "5%", "10%", "15%", "20%", "25%", "30%", "35%", "40%", "45%", "50%", + "55%", "60%", "65%", "70%", "75%", "80%", "85%", "90%", "95%", "100%", }; const float backlight_value[BACKLIGHT_COUNT] = { - 0.0f, - 0.25f, - 0.5f, - 0.75f, - 1.0f, + 0.00f, 0.05f, 0.10f, 0.15f, 0.20f, 0.25f, 0.30f, 0.35f, 0.40f, 0.45f, 0.50f, + 0.55f, 0.60f, 0.65f, 0.70f, 0.75f, 0.80f, 0.85f, 0.90f, 0.95f, 1.00f, }; -#define VOLUME_COUNT 5 +#define VOLUME_COUNT 21 const char* const volume_text[VOLUME_COUNT] = { - "0%", - "25%", - "50%", - "75%", - "100%", + "0%", "5%", "10%", "15%", "20%", "25%", "30%", "35%", "40%", "45%", "50%", + "55%", "60%", "65%", "70%", "75%", "80%", "85%", "90%", "95%", "100%", +}; +const float volume_value[VOLUME_COUNT] = { + 0.00f, 0.05f, 0.10f, 0.15f, 0.20f, 0.25f, 0.30f, 0.35f, 0.40f, 0.45f, 0.50f, + 0.55f, 0.60f, 0.65f, 0.70f, 0.75f, 0.80f, 0.85f, 0.90f, 0.95f, 1.00f, }; -const float volume_value[VOLUME_COUNT] = {0.0f, 0.25f, 0.5f, 0.75f, 1.0f}; #define DELAY_COUNT 11 const char* const delay_text[DELAY_COUNT] = { From f3ae09cc16550025d42766f87d1db521e03e92d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Thu, 6 Jul 2023 01:39:17 +0900 Subject: [PATCH 032/364] FuriHal: allow nulling null isr (#2846) * FuriHal: allow nulling null isr * FuriHal: include interrupt priority 15 as allowed * Furi: prevent compiler from optimizing arg in r0 of RESTORE_REGISTERS_AND_HALT_MCU --- firmware/targets/f7/furi_hal/furi_hal_interrupt.c | 3 +-- furi/core/check.c | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/firmware/targets/f7/furi_hal/furi_hal_interrupt.c b/firmware/targets/f7/furi_hal/furi_hal_interrupt.c index 9f4d43316..c508dac72 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_interrupt.c +++ b/firmware/targets/f7/furi_hal/furi_hal_interrupt.c @@ -128,14 +128,13 @@ void furi_hal_interrupt_set_isr_ex( FuriHalInterruptISR isr, void* context) { furi_check(index < FuriHalInterruptIdMax); - furi_check(priority < 15); + furi_check(priority <= 15); if(isr) { // Pre ISR set furi_check(furi_hal_interrupt_isr[index].isr == NULL); } else { // Pre ISR clear - furi_check(furi_hal_interrupt_isr[index].isr != NULL); furi_hal_interrupt_disable(index); furi_hal_interrupt_clear_pending(index); } diff --git a/furi/core/check.c b/furi/core/check.c index c5c4ef1a4..f7dcfc595 100644 --- a/furi/core/check.c +++ b/furi/core/check.c @@ -36,7 +36,7 @@ PLACE_IN_SECTION("MB_MEM2") uint32_t __furi_check_registers[13] = {0}; * */ #define RESTORE_REGISTERS_AND_HALT_MCU(debug) \ - register const bool r0 asm("r0") = debug; \ + register bool r0 asm("r0") = debug; \ asm volatile("cbnz r0, with_debugger%= \n" \ "ldr r12, =__furi_check_registers\n" \ "ldm r12, {r0-r11} \n" \ From 94efd48cd0dd600f3cdb04978e0904338fbaa8aa Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 5 Jul 2023 19:43:05 +0300 Subject: [PATCH 033/364] categories again --- applications/external/ir_scope/application.fam | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/external/ir_scope/application.fam b/applications/external/ir_scope/application.fam index f6fb6fbdc..00e161d97 100644 --- a/applications/external/ir_scope/application.fam +++ b/applications/external/ir_scope/application.fam @@ -7,7 +7,7 @@ App( requires=["gui"], stack_size=2 * 1024, fap_icon="ir_scope.png", - fap_category="Tools", + fap_category="Infrared", fap_author="@kallanreed", fap_version="1.0", fap_description="App allows to see incoming IR signals.", From 906cca8f242c7864565464a50b0a2ab6cbfa0475 Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Wed, 5 Jul 2023 20:48:02 +0400 Subject: [PATCH 034/364] Furi_Power: fix furi_hal_power_enable_otg (#2842) * Furi_Power: fix furi_hal_power_enable_otg * SubGhz: fix error output connected USB * Furi_Hal: fix target F18 * Fix api_symbols.csv version for F7 Co-authored-by: Aleksandr Kutuzov --- applications/main/subghz/helpers/subghz_txrx.c | 16 +++++++++++----- firmware/targets/f18/api_symbols.csv | 5 +++-- firmware/targets/f7/api_symbols.csv | 5 +++-- firmware/targets/f7/furi_hal/furi_hal_power.c | 14 +++++++++++++- .../targets/furi_hal_include/furi_hal_power.h | 6 +++++- lib/drivers/bq25896.c | 8 +++++++- lib/drivers/bq25896.h | 3 +++ lib/drivers/bq25896_reg.h | 18 ++++++++++-------- 8 files changed, 55 insertions(+), 20 deletions(-) diff --git a/applications/main/subghz/helpers/subghz_txrx.c b/applications/main/subghz/helpers/subghz_txrx.c index b911f4434..cbd47f5e5 100644 --- a/applications/main/subghz/helpers/subghz_txrx.c +++ b/applications/main/subghz/helpers/subghz_txrx.c @@ -8,11 +8,17 @@ static void subghz_txrx_radio_device_power_on(SubGhzTxRx* instance) { UNUSED(instance); - uint8_t attempts = 0; - while(!furi_hal_power_is_otg_enabled() && attempts++ < 5) { - furi_hal_power_enable_otg(); - //CC1101 power-up time - furi_delay_ms(10); + uint8_t attempts = 5; + while(--attempts > 0) { + if(furi_hal_power_enable_otg()) break; + } + if(attempts == 0) { + if(furi_hal_power_get_usb_voltage() < 4.5f) { + FURI_LOG_E( + TAG, + "Error power otg enable. BQ2589 check otg fault = %d", + furi_hal_power_check_otg_fault() ? 1 : 0); + } } } diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index 1884fe433..46099799b 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,32.0,, +Version,+,33.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -1030,12 +1030,13 @@ Function,+,furi_hal_mpu_protect_no_access,void,"FuriHalMpuRegion, uint32_t, Furi Function,+,furi_hal_mpu_protect_read_only,void,"FuriHalMpuRegion, uint32_t, FuriHalMPURegionSize" Function,-,furi_hal_os_init,void, Function,+,furi_hal_os_tick,void, +Function,+,furi_hal_power_check_otg_fault,_Bool, Function,+,furi_hal_power_check_otg_status,void, Function,+,furi_hal_power_debug_get,void,"PropertyValueCallback, void*" Function,+,furi_hal_power_disable_external_3_3v,void, Function,+,furi_hal_power_disable_otg,void, Function,+,furi_hal_power_enable_external_3_3v,void, -Function,+,furi_hal_power_enable_otg,void, +Function,+,furi_hal_power_enable_otg,_Bool, Function,+,furi_hal_power_gauge_is_ok,_Bool, Function,+,furi_hal_power_get_bat_health_pct,uint8_t, Function,+,furi_hal_power_get_battery_charge_voltage_limit,float, diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index f4dec15a7..dbaeb8e4c 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,32.0,, +Version,+,33.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -1235,12 +1235,13 @@ Function,+,furi_hal_nfc_tx_rx,_Bool,"FuriHalNfcTxRxContext*, uint16_t" Function,+,furi_hal_nfc_tx_rx_full,_Bool,FuriHalNfcTxRxContext* Function,-,furi_hal_os_init,void, Function,+,furi_hal_os_tick,void, +Function,+,furi_hal_power_check_otg_fault,_Bool, Function,+,furi_hal_power_check_otg_status,void, Function,+,furi_hal_power_debug_get,void,"PropertyValueCallback, void*" Function,+,furi_hal_power_disable_external_3_3v,void, Function,+,furi_hal_power_disable_otg,void, Function,+,furi_hal_power_enable_external_3_3v,void, -Function,+,furi_hal_power_enable_otg,void, +Function,+,furi_hal_power_enable_otg,_Bool, Function,+,furi_hal_power_gauge_is_ok,_Bool, Function,+,furi_hal_power_get_bat_health_pct,uint8_t, Function,+,furi_hal_power_get_battery_charge_voltage_limit,float, diff --git a/firmware/targets/f7/furi_hal/furi_hal_power.c b/firmware/targets/f7/furi_hal/furi_hal_power.c index ec405f108..5edde86e9 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_power.c +++ b/firmware/targets/f7/furi_hal/furi_hal_power.c @@ -284,10 +284,15 @@ void furi_hal_power_reset() { NVIC_SystemReset(); } -void furi_hal_power_enable_otg() { +bool furi_hal_power_enable_otg() { furi_hal_i2c_acquire(&furi_hal_i2c_handle_power); + bq25896_set_boost_lim(&furi_hal_i2c_handle_power, BoostLim_2150); bq25896_enable_otg(&furi_hal_i2c_handle_power); + furi_delay_ms(30); + bool ret = bq25896_is_otg_enabled(&furi_hal_i2c_handle_power); + bq25896_set_boost_lim(&furi_hal_i2c_handle_power, BoostLim_1400); furi_hal_i2c_release(&furi_hal_i2c_handle_power); + return ret; } void furi_hal_power_disable_otg() { @@ -317,6 +322,13 @@ void furi_hal_power_set_battery_charge_voltage_limit(float voltage) { furi_hal_i2c_release(&furi_hal_i2c_handle_power); } +bool furi_hal_power_check_otg_fault() { + furi_hal_i2c_acquire(&furi_hal_i2c_handle_power); + bool ret = bq25896_check_otg_fault(&furi_hal_i2c_handle_power); + furi_hal_i2c_release(&furi_hal_i2c_handle_power); + return ret; +} + void furi_hal_power_check_otg_status() { furi_hal_i2c_acquire(&furi_hal_i2c_handle_power); if(bq25896_check_otg_fault(&furi_hal_i2c_handle_power)) diff --git a/firmware/targets/furi_hal_include/furi_hal_power.h b/firmware/targets/furi_hal_include/furi_hal_power.h index 00182fa28..6b4b6cd89 100644 --- a/firmware/targets/furi_hal_include/furi_hal_power.h +++ b/firmware/targets/furi_hal_include/furi_hal_power.h @@ -99,12 +99,16 @@ void furi_hal_power_reset(); /** OTG enable */ -void furi_hal_power_enable_otg(); +bool furi_hal_power_enable_otg(); /** OTG disable */ void furi_hal_power_disable_otg(); +/** Check OTG status fault + */ +bool furi_hal_power_check_otg_fault(); + /** Check OTG status and disable it if falt happened */ void furi_hal_power_check_otg_status(); diff --git a/lib/drivers/bq25896.c b/lib/drivers/bq25896.c index 4c1d687cb..f675233bd 100644 --- a/lib/drivers/bq25896.c +++ b/lib/drivers/bq25896.c @@ -61,7 +61,7 @@ void bq25896_init(FuriHalI2cBusHandle* handle) { // OTG power configuration bq25896_regs.r0A.BOOSTV = 0x8; // BOOST Voltage: 5.062V - bq25896_regs.r0A.BOOST_LIM = BOOST_LIM_1400; // BOOST Current limit: 1.4A + bq25896_regs.r0A.BOOST_LIM = BoostLim_1400; // BOOST Current limit: 1.4A furi_hal_i2c_write_reg_8( handle, BQ25896_ADDRESS, 0x0A, *(uint8_t*)&bq25896_regs.r0A, BQ25896_I2C_TIMEOUT); @@ -74,6 +74,12 @@ void bq25896_init(FuriHalI2cBusHandle* handle) { BQ25896_I2C_TIMEOUT); } +void bq25896_set_boost_lim(FuriHalI2cBusHandle* handle, BoostLim boost_lim) { + bq25896_regs.r0A.BOOST_LIM = boost_lim; + furi_hal_i2c_write_reg_8( + handle, BQ25896_ADDRESS, 0x0A, *(uint8_t*)&bq25896_regs.r0A, BQ25896_I2C_TIMEOUT); +} + void bq25896_poweroff(FuriHalI2cBusHandle* handle) { bq25896_regs.r09.BATFET_DIS = 1; furi_hal_i2c_write_reg_8( diff --git a/lib/drivers/bq25896.h b/lib/drivers/bq25896.h index f3d1d0e05..ce7293960 100644 --- a/lib/drivers/bq25896.h +++ b/lib/drivers/bq25896.h @@ -9,6 +9,9 @@ /** Initialize Driver */ void bq25896_init(FuriHalI2cBusHandle* handle); +/** Set boost lim*/ +void bq25896_set_boost_lim(FuriHalI2cBusHandle* handle, BoostLim boost_lim); + /** Send device into shipping mode */ void bq25896_poweroff(FuriHalI2cBusHandle* handle); diff --git a/lib/drivers/bq25896_reg.h b/lib/drivers/bq25896_reg.h index 3cb3d7140..a6ca3e1c7 100644 --- a/lib/drivers/bq25896_reg.h +++ b/lib/drivers/bq25896_reg.h @@ -159,14 +159,16 @@ typedef struct { #define BOOSTV_128 (1 << 1) #define BOOSTV_64 (1 << 0) -#define BOOST_LIM_500 (0b000) -#define BOOST_LIM_750 (0b001) -#define BOOST_LIM_1200 (0b010) -#define BOOST_LIM_1400 (0b011) -#define BOOST_LIM_1650 (0b100) -#define BOOST_LIM_1875 (0b101) -#define BOOST_LIM_2150 (0b110) -#define BOOST_LIM_RSVD (0b111) +typedef enum { + BoostLim_500 = 0b000, + BoostLim_750 = 0b001, + BoostLim_1200 = 0b010, + BoostLim_1400 = 0b011, + BoostLim_1650 = 0b100, + BoostLim_1875 = 0b101, + BoostLim_2150 = 0b110, + BoostLim_Rsvd = 0b111, +} BoostLim; typedef struct { uint8_t BOOST_LIM : 3; // Boost Mode Current Limit From e6b0494d4d225658cef09d3e6e86a9778ad876d5 Mon Sep 17 00:00:00 2001 From: gornekich Date: Wed, 5 Jul 2023 20:55:04 +0400 Subject: [PATCH 035/364] [FL-3407] NFC: Mf Ultralight emulation optimization (#2847) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: ã‚ã --- lib/nfc/nfc_worker.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/nfc/nfc_worker.c b/lib/nfc/nfc_worker.c index a39531c8c..d2834fa46 100644 --- a/lib/nfc/nfc_worker.c +++ b/lib/nfc/nfc_worker.c @@ -724,6 +724,8 @@ void nfc_worker_emulate_mf_ultralight(NfcWorker* nfc_worker) { emulator.auth_received_callback = nfc_worker_mf_ultralight_auth_received_callback; emulator.context = nfc_worker; + rfal_platform_spi_acquire(); + while(nfc_worker->state == NfcWorkerStateMfUltralightEmulate) { mf_ul_reset_emulation(&emulator, true); furi_hal_nfc_emulate_nfca( @@ -743,6 +745,8 @@ void nfc_worker_emulate_mf_ultralight(NfcWorker* nfc_worker) { emulator.data_changed = false; } } + + rfal_platform_spi_release(); } static bool nfc_worker_mf_get_b_key_from_sector_trailer( From 93acba9ae3ef4190f8ca3ec7057f7f5c2d6ebacd Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 5 Jul 2023 20:07:43 +0300 Subject: [PATCH 036/364] i2c tools update --- .../external/flipper_i2ctools/LICENSE | 674 ++++++++++++++++++ .../external/flipper_i2ctools/application.fam | 4 +- .../external/flipper_i2ctools/i2ctools.c | 4 + .../external/flipper_i2ctools/i2ctools_i.h | 1 + .../flipper_i2ctools/images/Voltage_16x16.png | Bin 0 -> 294 bytes .../flipper_i2ctools/views/infos_view.c | 15 + .../flipper_i2ctools/views/infos_view.h | 8 + .../flipper_i2ctools/views/main_view.c | 20 + .../flipper_i2ctools/views/main_view.h | 13 +- .../flipper_i2ctools/views/scanner_view.h | 2 +- .../flipper_i2ctools/views/sender_view.h | 2 +- .../flipper_i2ctools/views/sniffer_view.h | 2 +- 12 files changed, 736 insertions(+), 9 deletions(-) create mode 100644 applications/external/flipper_i2ctools/LICENSE create mode 100644 applications/external/flipper_i2ctools/images/Voltage_16x16.png create mode 100644 applications/external/flipper_i2ctools/views/infos_view.c create mode 100644 applications/external/flipper_i2ctools/views/infos_view.h diff --git a/applications/external/flipper_i2ctools/LICENSE b/applications/external/flipper_i2ctools/LICENSE new file mode 100644 index 000000000..f288702d2 --- /dev/null +++ b/applications/external/flipper_i2ctools/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/applications/external/flipper_i2ctools/application.fam b/applications/external/flipper_i2ctools/application.fam index f91bcceba..c576c71b0 100644 --- a/applications/external/flipper_i2ctools/application.fam +++ b/applications/external/flipper_i2ctools/application.fam @@ -1,5 +1,5 @@ App( - appid="i2c_tools", + appid="i2ctools", name="[GPIO] i2c Tools", apptype=FlipperAppType.EXTERNAL, entry_point="i2ctools_app", @@ -10,6 +10,6 @@ App( fap_category="GPIO", fap_icon_assets="images", fap_author="@NaejEL", - fap_version="1.0", + fap_version="1.1", fap_description="Set of i2c tools", ) \ No newline at end of file diff --git a/applications/external/flipper_i2ctools/i2ctools.c b/applications/external/flipper_i2ctools/i2ctools.c index 6d4cc739f..7be6e5961 100644 --- a/applications/external/flipper_i2ctools/i2ctools.c +++ b/applications/external/flipper_i2ctools/i2ctools.c @@ -22,6 +22,10 @@ void i2ctools_draw_callback(Canvas* canvas, void* ctx) { draw_sender_view(canvas, i2ctools->sender); break; + case INFOS_VIEW: + draw_infos_view(canvas); + break; + default: break; } diff --git a/applications/external/flipper_i2ctools/i2ctools_i.h b/applications/external/flipper_i2ctools/i2ctools_i.h index f31e42478..2e32d3fd4 100644 --- a/applications/external/flipper_i2ctools/i2ctools_i.h +++ b/applications/external/flipper_i2ctools/i2ctools_i.h @@ -10,6 +10,7 @@ #include "views/sniffer_view.h" #include "views/scanner_view.h" #include "views/sender_view.h" +#include "views/infos_view.h" // App datas typedef struct { diff --git a/applications/external/flipper_i2ctools/images/Voltage_16x16.png b/applications/external/flipper_i2ctools/images/Voltage_16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..94e79687295fe8af81fa8984083c9ed9c40627ae GIT binary patch literal 294 zcmeAS@N?(olHy`uVBq!ia0vp^0wBx?BpA#)4xIr~Ea{HEjtmSN`?>!lvI6-E$sR$z z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8ZEE?e4Vure^!HUN?acl*3zP&DM`r(~v8;?}U{@}~zt4Gf;HelF{r5}E)Ni&owM literal 0 HcmV?d00001 diff --git a/applications/external/flipper_i2ctools/views/infos_view.c b/applications/external/flipper_i2ctools/views/infos_view.c new file mode 100644 index 000000000..108bdeb46 --- /dev/null +++ b/applications/external/flipper_i2ctools/views/infos_view.c @@ -0,0 +1,15 @@ +#include "infos_view.h" + +void draw_infos_view(Canvas* canvas) { + canvas_clear(canvas); + canvas_set_color(canvas, ColorBlack); + canvas_draw_rframe(canvas, 0, 0, 128, 64, 3); + canvas_set_font(canvas, FontSecondary); + + canvas_draw_str_aligned(canvas, 3, 3, AlignLeft, AlignTop, "Wiring: "); + canvas_draw_str_aligned(canvas, 43, 3, AlignLeft, AlignTop, "C0->SCL"); + canvas_draw_str_aligned(canvas, 43, 13, AlignLeft, AlignTop, "C1->SDA"); + canvas_draw_str_aligned(canvas, 43, 23, AlignLeft, AlignTop, "GND->GND"); + canvas_draw_icon(canvas, 15, 33, &I_Voltage_16x16); + canvas_draw_str_aligned(canvas, 30, 38, AlignLeft, AlignTop, "Only use 3v3 levels"); +} \ No newline at end of file diff --git a/applications/external/flipper_i2ctools/views/infos_view.h b/applications/external/flipper_i2ctools/views/infos_view.h new file mode 100644 index 000000000..11b388c02 --- /dev/null +++ b/applications/external/flipper_i2ctools/views/infos_view.h @@ -0,0 +1,8 @@ +#include +#include +#include +#include + +#define INFOS_TEXT "INFOS" + +void draw_infos_view(Canvas* canvas); \ No newline at end of file diff --git a/applications/external/flipper_i2ctools/views/main_view.c b/applications/external/flipper_i2ctools/views/main_view.c index abcb26224..909a3904b 100644 --- a/applications/external/flipper_i2ctools/views/main_view.c +++ b/applications/external/flipper_i2ctools/views/main_view.c @@ -14,6 +14,8 @@ void draw_main_view(Canvas* canvas, i2cMainView* main_view) { canvas, SNIFF_MENU_X, SNIFF_MENU_Y, AlignLeft, AlignTop, SNIFF_MENU_TEXT); canvas_draw_str_aligned( canvas, SEND_MENU_X, SEND_MENU_Y, AlignLeft, AlignTop, SEND_MENU_TEXT); + canvas_draw_str_aligned( + canvas, INFOS_MENU_X, INFOS_MENU_Y, AlignLeft, AlignTop, INFOS_MENU_TEXT); canvas_draw_rbox(canvas, 80, SCAN_MENU_Y - 2, 43, 13, 3); canvas_set_color(canvas, ColorWhite); canvas_draw_str_aligned( @@ -26,6 +28,8 @@ void draw_main_view(Canvas* canvas, i2cMainView* main_view) { canvas, SCAN_MENU_X, SCAN_MENU_Y, AlignLeft, AlignTop, SCAN_MENU_TEXT); canvas_draw_str_aligned( canvas, SEND_MENU_X, SEND_MENU_Y, AlignLeft, AlignTop, SEND_MENU_TEXT); + canvas_draw_str_aligned( + canvas, INFOS_MENU_X, INFOS_MENU_Y, AlignLeft, AlignTop, INFOS_MENU_TEXT); canvas_draw_rbox(canvas, 80, SNIFF_MENU_Y - 2, 43, 13, 3); canvas_set_color(canvas, ColorWhite); canvas_draw_str_aligned( @@ -38,12 +42,28 @@ void draw_main_view(Canvas* canvas, i2cMainView* main_view) { canvas, SCAN_MENU_X, SCAN_MENU_Y, AlignLeft, AlignTop, SCAN_MENU_TEXT); canvas_draw_str_aligned( canvas, SNIFF_MENU_X, SNIFF_MENU_Y, AlignLeft, AlignTop, SNIFF_MENU_TEXT); + canvas_draw_str_aligned( + canvas, INFOS_MENU_X, INFOS_MENU_Y, AlignLeft, AlignTop, INFOS_MENU_TEXT); canvas_draw_rbox(canvas, 80, SEND_MENU_Y - 2, 43, 13, 3); canvas_set_color(canvas, ColorWhite); canvas_draw_str_aligned( canvas, SEND_MENU_X, SEND_MENU_Y, AlignLeft, AlignTop, SEND_MENU_TEXT); break; + case INFOS_VIEW: + canvas_set_color(canvas, ColorBlack); + canvas_draw_str_aligned( + canvas, SCAN_MENU_X, SCAN_MENU_Y, AlignLeft, AlignTop, SCAN_MENU_TEXT); + canvas_draw_str_aligned( + canvas, SNIFF_MENU_X, SNIFF_MENU_Y, AlignLeft, AlignTop, SNIFF_MENU_TEXT); + canvas_draw_str_aligned( + canvas, SEND_MENU_X, SEND_MENU_Y, AlignLeft, AlignTop, SEND_MENU_TEXT); + canvas_draw_rbox(canvas, 80, INFOS_MENU_Y - 2, 43, 13, 3); + canvas_set_color(canvas, ColorWhite); + canvas_draw_str_aligned( + canvas, INFOS_MENU_X, INFOS_MENU_Y, AlignLeft, AlignTop, INFOS_MENU_TEXT); + break; + default: break; } diff --git a/applications/external/flipper_i2ctools/views/main_view.h b/applications/external/flipper_i2ctools/views/main_view.h index f0ce5e8ad..118a8f5f9 100644 --- a/applications/external/flipper_i2ctools/views/main_view.h +++ b/applications/external/flipper_i2ctools/views/main_view.h @@ -1,20 +1,24 @@ #include #include #include -#include +#include #define APP_NAME "I2C Tools" #define SCAN_MENU_TEXT "Scan" #define SCAN_MENU_X 90 -#define SCAN_MENU_Y 13 +#define SCAN_MENU_Y 7 #define SNIFF_MENU_TEXT "Sniff" #define SNIFF_MENU_X 90 -#define SNIFF_MENU_Y 27 +#define SNIFF_MENU_Y 21 #define SEND_MENU_TEXT "Send" #define SEND_MENU_X 90 -#define SEND_MENU_Y 41 +#define SEND_MENU_Y 35 + +#define INFOS_MENU_TEXT "Infos" +#define INFOS_MENU_X 90 +#define INFOS_MENU_Y 49 // Menu typedef enum { @@ -22,6 +26,7 @@ typedef enum { SCAN_VIEW, SNIFF_VIEW, SEND_VIEW, + INFOS_VIEW, /* Know menu Size*/ MENU_SIZE diff --git a/applications/external/flipper_i2ctools/views/scanner_view.h b/applications/external/flipper_i2ctools/views/scanner_view.h index 52f30a7bf..4c59f8836 100644 --- a/applications/external/flipper_i2ctools/views/scanner_view.h +++ b/applications/external/flipper_i2ctools/views/scanner_view.h @@ -1,7 +1,7 @@ #include #include #include -#include +#include #include "../i2cscanner.h" #define SCAN_TEXT "SCAN" diff --git a/applications/external/flipper_i2ctools/views/sender_view.h b/applications/external/flipper_i2ctools/views/sender_view.h index c4fdd98a2..c5192d0b6 100644 --- a/applications/external/flipper_i2ctools/views/sender_view.h +++ b/applications/external/flipper_i2ctools/views/sender_view.h @@ -1,7 +1,7 @@ #include #include #include -#include +#include #include "../i2csender.h" #define SEND_TEXT "SEND" diff --git a/applications/external/flipper_i2ctools/views/sniffer_view.h b/applications/external/flipper_i2ctools/views/sniffer_view.h index 8f2140bba..db4dbd1ff 100644 --- a/applications/external/flipper_i2ctools/views/sniffer_view.h +++ b/applications/external/flipper_i2ctools/views/sniffer_view.h @@ -1,7 +1,7 @@ #include #include #include -#include +#include #include "../i2csniffer.h" #define SNIFF_TEXT "SNIFF" From a9d4897dfbaf5003c2cc550b7d0a519a0e28d0eb Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 5 Jul 2023 20:26:47 +0300 Subject: [PATCH 037/364] Update wifi marauder --- .../external/wifi_marauder_companion/LICENSE | 674 ++++++++++++++++++ .../wifi_marauder_companion/ReadMe.md | 35 +- .../wifi_marauder_companion/application.fam | 17 + .../wifi_marauder_companion/docs/README.md | 9 + .../wifi_marauder_companion/docs/changelog.md | 5 + .../lib/esp-serial-flasher/LICENSE | 202 ++++++ .../lib/esp-serial-flasher/README.md | 154 ++++ .../esp-serial-flasher/include/esp_loader.h | 313 ++++++++ .../include/esp_loader_io.h | 112 +++ .../esp-serial-flasher/include/serial_io.h | 24 + .../lib/esp-serial-flasher/port/esp32_port.c | 181 +++++ .../lib/esp-serial-flasher/port/esp32_port.h | 59 ++ .../esp-serial-flasher/port/esp32_spi_port.c | 298 ++++++++ .../esp-serial-flasher/port/esp32_spi_port.h | 60 ++ .../esp-serial-flasher/port/raspberry_port.c | 302 ++++++++ .../esp-serial-flasher/port/raspberry_port.h | 36 + .../lib/esp-serial-flasher/port/stm32_port.c | 140 ++++ .../lib/esp-serial-flasher/port/stm32_port.h | 38 + .../lib/esp-serial-flasher/port/zephyr_port.c | 170 +++++ .../lib/esp-serial-flasher/port/zephyr_port.h | 39 + .../private_include/esp_targets.h | 33 + .../private_include/md5_hash.h | 28 + .../private_include/protocol.h | 230 ++++++ .../private_include/protocol_prv.h | 31 + .../esp-serial-flasher/private_include/slip.h | 28 + .../lib/esp-serial-flasher/src/esp_loader.c | 415 +++++++++++ .../lib/esp-serial-flasher/src/esp_targets.c | 263 +++++++ .../lib/esp-serial-flasher/src/md5_hash.c | 262 +++++++ .../esp-serial-flasher/src/protocol_common.c | 301 ++++++++ .../lib/esp-serial-flasher/src/protocol_spi.c | 312 ++++++++ .../esp-serial-flasher/src/protocol_uart.c | 114 +++ .../lib/esp-serial-flasher/src/slip.c | 125 ++++ .../esp-serial-flasher/zephyr/CMakeLists.txt | 30 + .../lib/esp-serial-flasher/zephyr/Kconfig | 16 + .../lib/esp-serial-flasher/zephyr/module.yml | 5 + .../scenes/wifi_marauder_scene_config.h | 1 + .../wifi_marauder_scene_console_output.c | 23 +- .../scenes/wifi_marauder_scene_flasher.c | 92 +++ .../scenes/wifi_marauder_scene_start.c | 16 + .../scenes/wifi_marauder_scene_text_input.c | 4 +- .../wifi_marauder_app.c | 2 + .../wifi_marauder_app.h | 2 +- .../wifi_marauder_app_i.h | 9 +- .../wifi_marauder_custom_event.h | 3 +- .../wifi_marauder_flasher.c | 200 ++++++ .../wifi_marauder_flasher.h | 10 + .../wifi_marauder_uart.c | 2 +- 47 files changed, 5416 insertions(+), 9 deletions(-) create mode 100644 applications/external/wifi_marauder_companion/LICENSE create mode 100644 applications/external/wifi_marauder_companion/docs/README.md create mode 100644 applications/external/wifi_marauder_companion/docs/changelog.md create mode 100644 applications/external/wifi_marauder_companion/lib/esp-serial-flasher/LICENSE create mode 100644 applications/external/wifi_marauder_companion/lib/esp-serial-flasher/README.md create mode 100644 applications/external/wifi_marauder_companion/lib/esp-serial-flasher/include/esp_loader.h create mode 100644 applications/external/wifi_marauder_companion/lib/esp-serial-flasher/include/esp_loader_io.h create mode 100644 applications/external/wifi_marauder_companion/lib/esp-serial-flasher/include/serial_io.h create mode 100644 applications/external/wifi_marauder_companion/lib/esp-serial-flasher/port/esp32_port.c create mode 100644 applications/external/wifi_marauder_companion/lib/esp-serial-flasher/port/esp32_port.h create mode 100644 applications/external/wifi_marauder_companion/lib/esp-serial-flasher/port/esp32_spi_port.c create mode 100644 applications/external/wifi_marauder_companion/lib/esp-serial-flasher/port/esp32_spi_port.h create mode 100644 applications/external/wifi_marauder_companion/lib/esp-serial-flasher/port/raspberry_port.c create mode 100644 applications/external/wifi_marauder_companion/lib/esp-serial-flasher/port/raspberry_port.h create mode 100644 applications/external/wifi_marauder_companion/lib/esp-serial-flasher/port/stm32_port.c create mode 100644 applications/external/wifi_marauder_companion/lib/esp-serial-flasher/port/stm32_port.h create mode 100644 applications/external/wifi_marauder_companion/lib/esp-serial-flasher/port/zephyr_port.c create mode 100644 applications/external/wifi_marauder_companion/lib/esp-serial-flasher/port/zephyr_port.h create mode 100644 applications/external/wifi_marauder_companion/lib/esp-serial-flasher/private_include/esp_targets.h create mode 100644 applications/external/wifi_marauder_companion/lib/esp-serial-flasher/private_include/md5_hash.h create mode 100644 applications/external/wifi_marauder_companion/lib/esp-serial-flasher/private_include/protocol.h create mode 100644 applications/external/wifi_marauder_companion/lib/esp-serial-flasher/private_include/protocol_prv.h create mode 100644 applications/external/wifi_marauder_companion/lib/esp-serial-flasher/private_include/slip.h create mode 100644 applications/external/wifi_marauder_companion/lib/esp-serial-flasher/src/esp_loader.c create mode 100644 applications/external/wifi_marauder_companion/lib/esp-serial-flasher/src/esp_targets.c create mode 100644 applications/external/wifi_marauder_companion/lib/esp-serial-flasher/src/md5_hash.c create mode 100644 applications/external/wifi_marauder_companion/lib/esp-serial-flasher/src/protocol_common.c create mode 100644 applications/external/wifi_marauder_companion/lib/esp-serial-flasher/src/protocol_spi.c create mode 100644 applications/external/wifi_marauder_companion/lib/esp-serial-flasher/src/protocol_uart.c create mode 100644 applications/external/wifi_marauder_companion/lib/esp-serial-flasher/src/slip.c create mode 100644 applications/external/wifi_marauder_companion/lib/esp-serial-flasher/zephyr/CMakeLists.txt create mode 100644 applications/external/wifi_marauder_companion/lib/esp-serial-flasher/zephyr/Kconfig create mode 100644 applications/external/wifi_marauder_companion/lib/esp-serial-flasher/zephyr/module.yml create mode 100644 applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_flasher.c create mode 100644 applications/external/wifi_marauder_companion/wifi_marauder_flasher.c create mode 100644 applications/external/wifi_marauder_companion/wifi_marauder_flasher.h diff --git a/applications/external/wifi_marauder_companion/LICENSE b/applications/external/wifi_marauder_companion/LICENSE new file mode 100644 index 000000000..f288702d2 --- /dev/null +++ b/applications/external/wifi_marauder_companion/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/applications/external/wifi_marauder_companion/ReadMe.md b/applications/external/wifi_marauder_companion/ReadMe.md index fd690ece6..bc14e0f8c 100644 --- a/applications/external/wifi_marauder_companion/ReadMe.md +++ b/applications/external/wifi_marauder_companion/ReadMe.md @@ -1,8 +1,41 @@ # WiFi Marauder companion app for Flipper Zero +Requires a connected dev board running Marauder FW. [See install instructions from UberGuidoZ here.](https://github.com/UberGuidoZ/Flipper/tree/main/Wifi_DevBoard#marauder-install-information) + ## https://github.com/0xchocolate/flipperzero-wifi-marauder -Requires a connected dev board running Marauder FW. [See install instructions from UberGuidoZ here.](https://github.com/UberGuidoZ/Flipper/tree/main/Wifi_DevBoard#marauder-install-information) +## Get the app +1. Make sure you're logged in with a github account (otherwise the downloads in step 2 won't work) +2. Navigate to the [FAP Build](https://github.com/0xchocolate/flipperzero-wifi-marauder/actions/workflows/build.yml) + GitHub action workflow, and select the most recent run, scroll down to artifacts. +3. The FAP is built for the `dev` and `release` channels of both official and unleashed + firmware. Download the artifact corresponding to your firmware version. +4. (Optional step to avoid confusion) Go to "Apps/GPIO" on the Flipper SD Card, delete any existing Marauder app, on some firmwares there will be a `ESP32CAM_Marauder.fap` or similar. +5. Extract `esp32_wifi_marauder.fap` from the ZIP file downloaded in step 3 to your Flipper Zero SD card, preferably under Apps/GPIO along with the rest of the GPIO apps. (If you're using qFlipper to transfer files you need to extract the content of the ZIP file to your computer before you drag it to qFlipper, as qFlipper does not support direct dragging from a ZIP file (at least on Windows)). + +From a local clone of this repo, you can also build the app yourself using ufbt. + +## In-app ESP32 flasher (WIP) +Guide by [@francis2054](https://github.com/francis2054) + +The app now contains a work-in-progress of an ESP32 flasher (close to the bottom of the marauder menu). Use at your own risk. This hardcodes addresses for non-S3 ESP32 chips. + +To use this method: +1. Make sure you follow the instructions for how to get the Marauder app on your Flipper Zero, they can be found on the top of this page. Latest release needs to be downloaded and installed. +2. Go to [Justcallmekoko's firmware page](https://github.com/justcallmekoko/ESP32Marauder/wiki/update-firmware#using-spacehuhn-web-updater) and download all files necessary for the board you are flashing, most boards will want all 4 files but for the Wifi Devboard you want to download these 3 files: `0x1000` (Bootloader), `0x8000` (partitions), `0x10000` (Firmware). The `Boot App` is not needed for the Wifi Devboard with this method. The Firmware one will redirect you to the releases page where you'll need to pick the one relevant to the board you're flashing, if you are using the official Wi-Fi Devboard you want to pick the one ending in `_flipper_sd_serial.bin`. +3. Place all files downloaded in step 2 in a new folder on your desktop, the name does not matter. Rename the `_flipper_sd_serial.bin` file you downloaded in step 2 to `Firmware.bin`. +4. Now for transferring the files to the Flipper Zero, drag all the files from the folder on your desktop to the "Marauder" folder inside "apps_data" folder on the Flipper Zero SD card. Preferred method to transfer these files is plugging the SD card into your computer with an adapter, but qFlipper works as well. Insert the Flipper Zero SD Card back into the Flipper before proceeding to the next step. +5. Plug your Wi-Fi Devboard into the Flipper. +6. Press and keep holding the boot button while you press the reset button once, release the boot button after 2 seconds. +7. Open the Marauder app on your Flipper Zero, it should be named "esp32_wifi_marauder" and be located under Apps->GPIO from the main menu if you followed the instructions for how to install the app further up on this page. (You might get an API mismatch error if the Flipper firmware you are running doesn't match the files you've downloaded, you can try "Continue" anyway, otherwise the app needs to be rebuilt or you might need to update the firmware on your Flipper). +8. Press the up arrow on the Flipper three times to get to "Reflash ESP32 (WIP)" and open it. +9. For "Bootloader" scroll down in the list and select `esp32_marauder.ino.bootloader.bin`, for "Paritition table" select `esp32_marauder.ino.partitions.bin` and for "Firmware" select `Firmware.bin`. +10. Scroll down and click "[>] FLASH" and wait for it to complete. (If you get errors here, press back button once and repeat step 6 then try "[>] FLASH" again). +11. Once it says "Done flashing" on the screen, restart the Flipper and you are done :) + +## For future updates, just repeat from step 2 and only download the new "Firmware" bin + +This process will improve with future updates! :) ## Support diff --git a/applications/external/wifi_marauder_companion/application.fam b/applications/external/wifi_marauder_companion/application.fam index 59e53b725..6b3403f39 100644 --- a/applications/external/wifi_marauder_companion/application.fam +++ b/applications/external/wifi_marauder_companion/application.fam @@ -8,5 +8,22 @@ App( order=90, fap_icon="wifi_10px.png", fap_category="GPIO", + fap_private_libs=[ + Lib( + name="esp-serial-flasher", + fap_include_paths=["include"], + sources=[ + "src/esp_loader.c", + "src/esp_targets.c", + "src/md5_hash.c", + "src/protocol_common.c", + "src/protocol_uart.c", + "src/slip.c" + ], + cincludes=["lib/esp-serial-flasher/private_include"], + cdefines=["SERIAL_FLASHER_INTERFACE_UART=1", "MD5_ENABLED=1"], + ), + ], + cdefines=["SERIAL_FLASHER_INTERFACE_UART=1"], fap_icon_assets="assets", ) diff --git a/applications/external/wifi_marauder_companion/docs/README.md b/applications/external/wifi_marauder_companion/docs/README.md new file mode 100644 index 000000000..40b7a5ad0 --- /dev/null +++ b/applications/external/wifi_marauder_companion/docs/README.md @@ -0,0 +1,9 @@ +## WiFi Marauder companion app for Flipper Zero + +Requires a connected dev board running Marauder FW. See install instructions from UberGuidoZ here: https://github.com/UberGuidoZ/Flipper/tree/main/Wifi_DevBoard#marauder-install-information + +## Support + +For app feedback, bugs, and feature requests, please create an issue here: https://github.com/0xchocolate/flipperzero-wifi-marauder/issues + +You can find me (0xchocolate) on discord as @cococode. diff --git a/applications/external/wifi_marauder_companion/docs/changelog.md b/applications/external/wifi_marauder_companion/docs/changelog.md new file mode 100644 index 000000000..ae0f328e7 --- /dev/null +++ b/applications/external/wifi_marauder_companion/docs/changelog.md @@ -0,0 +1,5 @@ +## v0.4.0 + +Added Signal Monitor (thanks @justcallmekoko!) to support new sigmon command in Marauder v0.10.5: https://github.com/justcallmekoko/ESP32Marauder/releases + +Added keyboard and +5V support from unleashed (thanks @xMasterX!) diff --git a/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/LICENSE b/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/LICENSE new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/README.md b/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/README.md new file mode 100644 index 000000000..b1ae448d7 --- /dev/null +++ b/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/README.md @@ -0,0 +1,154 @@ +# esp-serial-flasher + +`esp-serial-flasher` is a portable C library for flashing or loading apps to RAM of Espressif SoCs from other host microcontrollers. + +## Using the library +Espressif SoCs are normally programmed via serial interface (UART). The port layer for the given host microcontroller has to be implemented if not available. Details can be found in section below. + +Supported **host** microcontrollers: + +- STM32 +- Raspberry Pi SBC +- ESP32 +- Any MCU running Zephyr OS + +Supported **target** microcontrollers: + +- ESP32 +- ESP8266 +- ESP32-S2 +- ESP32-S3 +- ESP32-C3 +- ESP32-C2 +- ESP32-H2 + +Supported hardware interfaces: +- UART +- SPI (only for RAM download, experimental) + +For example usage check the `examples` directory. + +## Supporting a new host target + +In order to support a new target, following functions have to be implemented by user: + +- `loader_port_read()` +- `loader_port_write()` +- `loader_port_enter_bootloader()` +- `loader_port_delay_ms()` +- `loader_port_start_timer()` +- `loader_port_remaining_time()` + +For the SPI interface ports +- `loader_port_spi_set_cs()` +needs to be implemented as well. + +The following functions are part of the [io.h](include/io.h) header for convenience, however, the user does not have to strictly follow function signatures, as there are not called directly from library. + +- `loader_port_change_transmission_rate()` +- `loader_port_reset_target()` +- `loader_port_debug_print()` + +Prototypes of all functions mentioned above can be found in [io.h](include/io.h). + +## Configuration + +These are the configuration toggles available to the user: + +* `SERIAL_FLASHER_INTERFACE_UART/SERIAL_FLASHER_INTERFACE_SPI` + +This defines the hardware interface to use. SPI interface only supports RAM download mode and is in experimental stage and can undergo changes. + +Default: SERIAL_FLASHER_INTERFACE_UART + +* `MD5_ENABLED` + +If enabled, `esp-serial-flasher` is capable of verifying flash integrity after writing to flash. + +Default: Enabled +> Warning: As ROM bootloader of the ESP8266 does not support MD5_CHECK, this option has to be disabled! + +* `SERIAL_FLASHER_RESET_HOLD_TIME_MS` + +This is the time for which the reset pin is asserted when doing a hard reset in milliseconds. + +Default: 100 + +* `SERIAL_FLASHER_BOOT_HOLD_TIME_MS` + +This is the time for which the boot pin is asserted when doing a hard reset in milliseconds. + +Default: 50 + +Configuration can be passed to `cmake` via command line: + +``` +cmake -DMD5_ENABLED=1 .. && cmake --build . +``` + +### STM32 support + +The STM32 port makes use of STM32 HAL libraries, and these do not come with CMake support. In order to compile the project, `stm32-cmake` (a `CMake` support package) has to be pulled as submodule. + +``` +git clone --recursive https://github.com/espressif/esp-serial-flasher.git +``` + +If you have cloned this repository without the `--recursive` flag, you can initialize the submodule using the following command: + +``` +git submodule update --init +``` + +In addition to configuration parameters mentioned above, following definitions has to be set: + +- TOOLCHAIN_PREFIX: path to arm toolchain (i.e /home/user/gcc-arm-none-eabi-9-2019-q4-major) +- STM32Cube_DIR: path to STM32 Cube libraries (i.e /home/user/STM32Cube/Repository/STM32Cube_FW_F4_V1.25.0) +- STM32_CHIP: name of STM32 for which project should be compiled (i.e STM32F407VG) +- PORT: STM32 + +This can be achieved by passing definitions to the command line, such as: + +``` +cmake -DTOOLCHAIN_PREFIX="/path_to_toolchain" -DSTM32Cube_DIR="path_to_stm32Cube" -DSTM32_CHIP="STM32F407VG" -DPORT="STM32" .. && cmake --build . +``` + +Alternatively, those variables can be set in the top level `cmake` directory: + +``` +set(TOOLCHAIN_PREFIX path_to_toolchain) +set(STM32Cube_DIR path_to_stm32_HAL) +set(STM32_CHIP STM32F407VG) +set(PORT STM32) +``` + +### Zephyr support + +The Zephyr port is ready to be integrated into Zephyr apps as a Zephyr module. In the manifest file (west.yml), add: + +``` + - name: esp-flasher + url: https://github.com/espressif/esp-serial-flasher + revision: master + path: modules/lib/esp_flasher +``` + +And add + +``` +CONFIG_ESP_SERIAL_FLASHER=y +CONFIG_CONSOLE_GETCHAR=y +CONFIG_SERIAL_FLASHER_MD5_ENABLED=y +``` + +to the project configuration `prj.conf`. + +For the C/C++ source code, the example code provided in `examples/zephyr_example` can be used as a starting point. + +## Licence + +Code is distributed under Apache 2.0 license. + +## Known limitations + +Size of new binary image has to be known before flashing. diff --git a/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/include/esp_loader.h b/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/include/esp_loader.h new file mode 100644 index 000000000..43f4fb9d9 --- /dev/null +++ b/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/include/esp_loader.h @@ -0,0 +1,313 @@ +/* Copyright 2020-2023 Espressif Systems (Shanghai) CO LTD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Used for backwards compatibility with the previous API */ +#define esp_loader_change_baudrate esp_loader_change_transmission_rate + +/** + * Macro which can be used to check the error code, + * and return in case the code is not ESP_LOADER_SUCCESS. + */ +#define RETURN_ON_ERROR(x) do { \ + esp_loader_error_t _err_ = (x); \ + if (_err_ != ESP_LOADER_SUCCESS) { \ + return _err_; \ + } \ +} while(0) + +/** + * @brief Error codes + */ +typedef enum { + ESP_LOADER_SUCCESS, /*!< Success */ + ESP_LOADER_ERROR_FAIL, /*!< Unspecified error */ + ESP_LOADER_ERROR_TIMEOUT, /*!< Timeout elapsed */ + ESP_LOADER_ERROR_IMAGE_SIZE, /*!< Image size to flash is larger than flash size */ + ESP_LOADER_ERROR_INVALID_MD5, /*!< Computed and received MD5 does not match */ + ESP_LOADER_ERROR_INVALID_PARAM, /*!< Invalid parameter passed to function */ + ESP_LOADER_ERROR_INVALID_TARGET, /*!< Connected target is invalid */ + ESP_LOADER_ERROR_UNSUPPORTED_CHIP, /*!< Attached chip is not supported */ + ESP_LOADER_ERROR_UNSUPPORTED_FUNC, /*!< Function is not supported on attached target */ + ESP_LOADER_ERROR_INVALID_RESPONSE /*!< Internal error */ +} esp_loader_error_t; + +/** + * @brief Supported targets + */ +typedef enum { + ESP8266_CHIP = 0, + ESP32_CHIP = 1, + ESP32S2_CHIP = 2, + ESP32C3_CHIP = 3, + ESP32S3_CHIP = 4, + ESP32C2_CHIP = 5, + ESP32H4_CHIP = 6, + ESP32H2_CHIP = 7, + ESP_MAX_CHIP = 8, + ESP_UNKNOWN_CHIP = 8 +} target_chip_t; + +/** + * @brief Application binary header + */ +typedef struct { + uint8_t magic; + uint8_t segments; + uint8_t flash_mode; + uint8_t flash_size_freq; + uint32_t entrypoint; +} esp_loader_bin_header_t; + +/** + * @brief Segment binary header + */ +typedef struct { + uint32_t addr; + uint32_t size; + uint8_t *data; +} esp_loader_bin_segment_t; + +/** + * @brief SPI pin configuration arguments + */ +typedef union { + struct { + uint32_t pin_clk: 6; + uint32_t pin_q: 6; + uint32_t pin_d: 6; + uint32_t pin_cs: 6; + uint32_t pin_hd: 6; + uint32_t zero: 2; + }; + uint32_t val; +} esp_loader_spi_config_t; + +/** + * @brief Connection arguments + */ +typedef struct { + uint32_t sync_timeout; /*!< Maximum time to wait for response from serial interface. */ + int32_t trials; /*!< Number of trials to connect to target. If greater than 1, + 100 millisecond delay is inserted after each try. */ +} esp_loader_connect_args_t; + +#define ESP_LOADER_CONNECT_DEFAULT() { \ + .sync_timeout = 100, \ + .trials = 10, \ +} + +/** + * @brief Connects to the target + * + * @param connect_args[in] Timing parameters to be used for connecting to target. + * + * @return + * - ESP_LOADER_SUCCESS Success + * - ESP_LOADER_ERROR_TIMEOUT Timeout + * - ESP_LOADER_ERROR_INVALID_RESPONSE Internal error + */ +esp_loader_error_t esp_loader_connect(esp_loader_connect_args_t *connect_args); + +/** + * @brief Returns attached target chip. + * + * @warning This function can only be called after connection with target + * has been successfully established by calling esp_loader_connect(). + * + * @return One of target_chip_t + */ +target_chip_t esp_loader_get_target(void); + + +#ifdef SERIAL_FLASHER_INTERFACE_UART +/** + * @brief Initiates flash operation + * + * @param offset[in] Address from which flash operation will be performed. + * @param image_size[in] Size of the whole binary to be loaded into flash. + * @param block_size[in] Size of buffer used in subsequent calls to esp_loader_flash_write. + * + * @note image_size is size of the whole image, whereas, block_size is chunk of data sent + * to the target, each time esp_loader_flash_write function is called. + * + * @return + * - ESP_LOADER_SUCCESS Success + * - ESP_LOADER_ERROR_TIMEOUT Timeout + * - ESP_LOADER_ERROR_INVALID_RESPONSE Internal error + */ +esp_loader_error_t esp_loader_flash_start(uint32_t offset, uint32_t image_size, uint32_t block_size); + +/** + * @brief Writes supplied data to target's flash memory. + * + * @param payload[in] Data to be flashed into target's memory. + * @param size[in] Size of payload in bytes. + * + * @note size must not be greater that block_size supplied to previously called + * esp_loader_flash_start function. If size is less than block_size, + * remaining bytes of payload buffer will be padded with 0xff. + * Therefore, size of payload buffer has to be equal or greater than block_size. + * + * @return + * - ESP_LOADER_SUCCESS Success + * - ESP_LOADER_ERROR_TIMEOUT Timeout + * - ESP_LOADER_ERROR_INVALID_RESPONSE Internal error + */ +esp_loader_error_t esp_loader_flash_write(void *payload, uint32_t size); + +/** + * @brief Ends flash operation. + * + * @param reboot[in] reboot the target if true. + * + * @return + * - ESP_LOADER_SUCCESS Success + * - ESP_LOADER_ERROR_TIMEOUT Timeout + * - ESP_LOADER_ERROR_INVALID_RESPONSE Internal error + */ +esp_loader_error_t esp_loader_flash_finish(bool reboot); +#endif /* SERIAL_FLASHER_INTERFACE_UART */ + + +/** + * @brief Initiates mem operation, initiates loading for program into target RAM + * + * @param offset[in] Address from which mem operation will be performed. + * @param size[in] Size of the whole binary to be loaded into mem. + * @param block_size[in] Size of buffer used in subsequent calls to esp_loader_mem_write. + * + * @note image_size is size of the whole image, whereas, block_size is chunk of data sent + * to the target, each time esp_mem_flash_write function is called. + * + * @return + * - ESP_LOADER_SUCCESS Success + * - ESP_LOADER_ERROR_TIMEOUT Timeout + * - ESP_LOADER_ERROR_INVALID_RESPONSE Internal error + */ +esp_loader_error_t esp_loader_mem_start(uint32_t offset, uint32_t size, uint32_t block_size); + + +/** + * @brief Writes supplied data to target's mem memory. + * + * @param payload[in] Data to be loaded into target's memory. + * @param size[in] Size of data in bytes. + * + * @note size must not be greater that block_size supplied to previously called + * esp_loader_mem_start function. + * Therefore, size of data buffer has to be equal or greater than block_size. + * + * @return + * - ESP_LOADER_SUCCESS Success + * - ESP_LOADER_ERROR_TIMEOUT Timeout + * - ESP_LOADER_ERROR_INVALID_RESPONSE Internal error + */ +esp_loader_error_t esp_loader_mem_write(const void *payload, uint32_t size); + + +/** + * @brief Ends mem operation, finish loading for program into target RAM + * and send the entrypoint of ram_loadable app + * + * @param entrypoint[in] entrypoint of ram program. + * + * @return + * - ESP_LOADER_SUCCESS Success + * - ESP_LOADER_ERROR_TIMEOUT Timeout + * - ESP_LOADER_ERROR_INVALID_RESPONSE Internal error + */ +esp_loader_error_t esp_loader_mem_finish(uint32_t entrypoint); + + +/** + * @brief Writes register. + * + * @param address[in] Address of register. + * @param reg_value[in] New register value. + * + * @return + * - ESP_LOADER_SUCCESS Success + * - ESP_LOADER_ERROR_TIMEOUT Timeout + * - ESP_LOADER_ERROR_INVALID_RESPONSE Internal error + */ +esp_loader_error_t esp_loader_write_register(uint32_t address, uint32_t reg_value); + +/** + * @brief Reads register. + * + * @param address[in] Address of register. + * @param reg_value[out] Register value. + * + * @return + * - ESP_LOADER_SUCCESS Success + * - ESP_LOADER_ERROR_TIMEOUT Timeout + * - ESP_LOADER_ERROR_INVALID_RESPONSE Internal error + */ +esp_loader_error_t esp_loader_read_register(uint32_t address, uint32_t *reg_value); + +/** + * @brief Change baud rate. + * + * @note Baud rate has to be also adjusted accordingly on host MCU, as + * target's baud rate is changed upon return from this function. + * + * @param transmission_rate[in] new baud rate to be set. + * + * @return + * - ESP_LOADER_SUCCESS Success + * - ESP_LOADER_ERROR_TIMEOUT Timeout + * - ESP_LOADER_ERROR_INVALID_RESPONSE Internal error + * - ESP_LOADER_ERROR_UNSUPPORTED_FUNC Unsupported on the target + */ +esp_loader_error_t esp_loader_change_transmission_rate(uint32_t transmission_rate); + +/** + * @brief Verify target's flash integrity by checking MD5. + * MD5 checksum is computed from data pushed to target's memory by calling + * esp_loader_flash_write() function and compared against target's MD5. + * Target computes checksum based on offset and image_size passed to + * esp_loader_flash_start() function. + * + * @note This function is only available if MD5_ENABLED is set. + * + * @return + * - ESP_LOADER_SUCCESS Success + * - ESP_LOADER_ERROR_INVALID_MD5 MD5 does not match + * - ESP_LOADER_ERROR_TIMEOUT Timeout + * - ESP_LOADER_ERROR_INVALID_RESPONSE Internal error + * - ESP_LOADER_ERROR_UNSUPPORTED_FUNC Unsupported on the target + */ +#if MD5_ENABLED +esp_loader_error_t esp_loader_flash_verify(void); +#endif +/** + * @brief Toggles reset pin. + */ +void esp_loader_reset_target(void); + + + +#ifdef __cplusplus +} +#endif diff --git a/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/include/esp_loader_io.h b/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/include/esp_loader_io.h new file mode 100644 index 000000000..2ff99b4f9 --- /dev/null +++ b/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/include/esp_loader_io.h @@ -0,0 +1,112 @@ +/* Copyright 2020-2023 Espressif Systems (Shanghai) CO LTD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + #pragma once + +#include +#include "esp_loader.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Changes the transmission rate of the used peripheral. + */ +esp_loader_error_t loader_port_change_transmission_rate(uint32_t transmission_rate); + +/** + * @brief Writes data over the io interface. + * + * @param data[in] Buffer with data to be written. + * @param size[in] Size of data in bytes. + * @param timeout[in] Timeout in milliseconds. + * + * @return + * - ESP_LOADER_SUCCESS Success + * - ESP_LOADER_ERROR_TIMEOUT Timeout elapsed + */ +esp_loader_error_t loader_port_write(const uint8_t *data, uint16_t size, uint32_t timeout); + +/** + * @brief Reads data from the io interface. + * + * @param data[out] Buffer into which received data will be written. + * @param size[in] Number of bytes to read. + * @param timeout[in] Timeout in milliseconds. + * + * @return + * - ESP_LOADER_SUCCESS Success + * - ESP_LOADER_ERROR_TIMEOUT Timeout elapsed + */ +esp_loader_error_t loader_port_read(uint8_t *data, uint16_t size, uint32_t timeout); + +/** + * @brief Delay in milliseconds. + * + * @param ms[in] Number of milliseconds. + * + */ +void loader_port_delay_ms(uint32_t ms); + +/** + * @brief Starts timeout timer. + * + * @param ms[in] Number of milliseconds. + * + */ +void loader_port_start_timer(uint32_t ms); + +/** + * @brief Returns remaining time since timer was started by calling esp_loader_start_timer. + * 0 if timer has elapsed. + * + * @return Number of milliseconds. + * + */ +uint32_t loader_port_remaining_time(void); + +/** + * @brief Asserts bootstrap pins to enter boot mode and toggles reset pin. + * + * @note Reset pin should stay asserted for at least 20 milliseconds. + */ +void loader_port_enter_bootloader(void); + +/** + * @brief Toggles reset pin. + * + * @note Reset pin should stay asserted for at least 20 milliseconds. + */ +void loader_port_reset_target(void); + +/** + * @brief Function can be defined by user to print debug message. + * + * @note Empty weak function is used, otherwise. + * + */ +void loader_port_debug_print(const char *str); + +#ifdef SERIAL_FLASHER_INTERFACE_SPI +/** + * @brief Sets the chip select to a defined level + */ +void loader_port_spi_set_cs(uint32_t level); +#endif /* SERIAL_FLASHER_INTERFACE_SPI */ + +#ifdef __cplusplus +} +#endif diff --git a/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/include/serial_io.h b/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/include/serial_io.h new file mode 100644 index 000000000..e34939300 --- /dev/null +++ b/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/include/serial_io.h @@ -0,0 +1,24 @@ +/* Copyright 2020-2023 Espressif Systems (Shanghai) CO LTD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#warning Please replace serial_io.h with esp_loader_io.h and change the function names \ + to match the new API + +/* Defines used to avoid breaking existing ports */ +#define loader_port_change_baudrate loader_port_change_transmission_rate +#define loader_port_serial_write loader_port_write +#define loader_port_serial_read loader_port_read + +#include "esp_loader_io.h" diff --git a/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/port/esp32_port.c b/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/port/esp32_port.c new file mode 100644 index 000000000..a6c8687c6 --- /dev/null +++ b/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/port/esp32_port.c @@ -0,0 +1,181 @@ +/* Copyright 2020-2023 Espressif Systems (Shanghai) CO LTD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "esp32_port.h" +#include "driver/uart.h" +#include "driver/gpio.h" +#include "esp_timer.h" +#include "esp_log.h" +#include "esp_idf_version.h" +#include + +#ifdef SERIAL_FLASHER_DEBUG_TRACE +static void transfer_debug_print(const uint8_t *data, uint16_t size, bool write) +{ + static bool write_prev = false; + + if (write_prev != write) { + write_prev = write; + printf("\n--- %s ---\n", write ? "WRITE" : "READ"); + } + + for (uint32_t i = 0; i < size; i++) { + printf("%02x ", data[i]); + } +} +#endif + +static int64_t s_time_end; +static int32_t s_uart_port; +static int32_t s_reset_trigger_pin; +static int32_t s_gpio0_trigger_pin; + +esp_loader_error_t loader_port_esp32_init(const loader_esp32_config_t *config) +{ + s_uart_port = config->uart_port; + s_reset_trigger_pin = config->reset_trigger_pin; + s_gpio0_trigger_pin = config->gpio0_trigger_pin; + + // Initialize UART + uart_config_t uart_config = { + .baud_rate = config->baud_rate, + .data_bits = UART_DATA_8_BITS, + .parity = UART_PARITY_DISABLE, + .stop_bits = UART_STOP_BITS_1, + .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) + .source_clk = UART_SCLK_DEFAULT, +#endif + }; + + int rx_buffer_size = config->rx_buffer_size ? config->rx_buffer_size : 400; + int tx_buffer_size = config->tx_buffer_size ? config->tx_buffer_size : 400; + QueueHandle_t *uart_queue = config->uart_queue ? config->uart_queue : NULL; + int queue_size = config->queue_size ? config->queue_size : 0; + + if ( uart_param_config(s_uart_port, &uart_config) != ESP_OK ) { + return ESP_LOADER_ERROR_FAIL; + } + if ( uart_set_pin(s_uart_port, config->uart_tx_pin, config->uart_rx_pin, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE) != ESP_OK ) { + return ESP_LOADER_ERROR_FAIL; + } + if ( uart_driver_install(s_uart_port, rx_buffer_size, tx_buffer_size, queue_size, uart_queue, 0) != ESP_OK ) { + return ESP_LOADER_ERROR_FAIL; + } + + // Initialize boot pin selection pins + gpio_reset_pin(s_reset_trigger_pin); + gpio_set_pull_mode(s_reset_trigger_pin, GPIO_PULLUP_ONLY); + gpio_set_direction(s_reset_trigger_pin, GPIO_MODE_OUTPUT); + + gpio_reset_pin(s_gpio0_trigger_pin); + gpio_set_pull_mode(s_gpio0_trigger_pin, GPIO_PULLUP_ONLY); + gpio_set_direction(s_gpio0_trigger_pin, GPIO_MODE_OUTPUT); + + return ESP_LOADER_SUCCESS; +} + +void loader_port_esp32_deinit(void) +{ + uart_driver_delete(s_uart_port); +} + + +esp_loader_error_t loader_port_write(const uint8_t *data, uint16_t size, uint32_t timeout) +{ + uart_write_bytes(s_uart_port, (const char *)data, size); + esp_err_t err = uart_wait_tx_done(s_uart_port, pdMS_TO_TICKS(timeout)); + + if (err == ESP_OK) { +#ifdef SERIAL_FLASHER_DEBUG_TRACE + transfer_debug_print(data, size, true); +#endif + return ESP_LOADER_SUCCESS; + } else if (err == ESP_ERR_TIMEOUT) { + return ESP_LOADER_ERROR_TIMEOUT; + } else { + return ESP_LOADER_ERROR_FAIL; + } +} + + +esp_loader_error_t loader_port_read(uint8_t *data, uint16_t size, uint32_t timeout) +{ + int read = uart_read_bytes(s_uart_port, data, size, pdMS_TO_TICKS(timeout)); + + if (read < 0) { + return ESP_LOADER_ERROR_FAIL; + } else if (read < size) { +#ifdef SERIAL_FLASHER_DEBUG_TRACE + transfer_debug_print(data, read, false); +#endif + return ESP_LOADER_ERROR_TIMEOUT; + } else { +#ifdef SERIAL_FLASHER_DEBUG_TRACE + transfer_debug_print(data, read, false); +#endif + return ESP_LOADER_SUCCESS; + } +} + + +// Set GPIO0 LOW, then +// assert reset pin for 50 milliseconds. +void loader_port_enter_bootloader(void) +{ + gpio_set_level(s_gpio0_trigger_pin, 0); + loader_port_reset_target(); + loader_port_delay_ms(SERIAL_FLASHER_BOOT_HOLD_TIME_MS); + gpio_set_level(s_gpio0_trigger_pin, 1); +} + + +void loader_port_reset_target(void) +{ + gpio_set_level(s_reset_trigger_pin, 0); + loader_port_delay_ms(SERIAL_FLASHER_RESET_HOLD_TIME_MS); + gpio_set_level(s_reset_trigger_pin, 1); +} + + +void loader_port_delay_ms(uint32_t ms) +{ + usleep(ms * 1000); +} + + +void loader_port_start_timer(uint32_t ms) +{ + s_time_end = esp_timer_get_time() + ms * 1000; +} + + +uint32_t loader_port_remaining_time(void) +{ + int64_t remaining = (s_time_end - esp_timer_get_time()) / 1000; + return (remaining > 0) ? (uint32_t)remaining : 0; +} + + +void loader_port_debug_print(const char *str) +{ + printf("DEBUG: %s\n", str); +} + +esp_loader_error_t loader_port_change_transmission_rate(uint32_t baudrate) +{ + esp_err_t err = uart_set_baudrate(s_uart_port, baudrate); + return (err == ESP_OK) ? ESP_LOADER_SUCCESS : ESP_LOADER_ERROR_FAIL; +} \ No newline at end of file diff --git a/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/port/esp32_port.h b/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/port/esp32_port.h new file mode 100644 index 000000000..c098762d2 --- /dev/null +++ b/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/port/esp32_port.h @@ -0,0 +1,59 @@ +/* Copyright 2020-2023 Espressif Systems (Shanghai) CO LTD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + #pragma once + +#include "esp_loader_io.h" +#include "freertos/FreeRTOS.h" +#include "freertos/queue.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct +{ + uint32_t baud_rate; /*!< Initial baud rate, can be changed later */ + uint32_t uart_port; /*!< UART port */ + uint32_t uart_rx_pin; /*!< This pin will be configured as UART Rx pin */ + uint32_t uart_tx_pin; /*!< This pin will be configured as UART Tx pin */ + uint32_t reset_trigger_pin; /*!< This pin will be used to reset target chip */ + uint32_t gpio0_trigger_pin; /*!< This pin will be used to toggle set IO0 of target chip */ + uint32_t rx_buffer_size; /*!< Set to zero for default RX buffer size */ + uint32_t tx_buffer_size; /*!< Set to zero for default TX buffer size */ + uint32_t queue_size; /*!< Set to zero for default UART queue size */ + QueueHandle_t *uart_queue; /*!< Set to NULL, if UART queue handle is not + necessary. Otherwise, it will be assigned here */ +} loader_esp32_config_t; + +/** + * @brief Initializes serial interface. + * + * @param baud_rate[in] Communication speed. + * + * @return + * - ESP_LOADER_SUCCESS Success + * - ESP_LOADER_ERROR_FAIL Initialization failure + */ +esp_loader_error_t loader_port_esp32_init(const loader_esp32_config_t *config); + +/** + * @brief Deinitialize serial interface. + */ +void loader_port_esp32_deinit(void); + +#ifdef __cplusplus +} +#endif diff --git a/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/port/esp32_spi_port.c b/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/port/esp32_spi_port.c new file mode 100644 index 000000000..55e8f3e57 --- /dev/null +++ b/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/port/esp32_spi_port.c @@ -0,0 +1,298 @@ +/* Copyright 2020-2023 Espressif Systems (Shanghai) CO LTD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "esp32_spi_port.h" +#include "esp_log.h" +#include "driver/gpio.h" +#include "esp_timer.h" +#include "esp_log.h" +#include "esp_idf_version.h" +#include + +// #define SERIAL_DEBUG_ENABLE + +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 3, 0) +#define DMA_CHAN SPI_DMA_CH_AUTO +#else +#define DMA_CHAN 1 +#endif + +#define WORD_ALIGNED(ptr) ((size_t)ptr % sizeof(size_t) == 0) + +#ifdef SERIAL_DEBUG_ENABLE + +static void dec_to_hex_str(const uint8_t dec, uint8_t hex_str[3]) +{ + static const uint8_t dec_to_hex[] = { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' + }; + + hex_str[0] = dec_to_hex[dec >> 4]; + hex_str[1] = dec_to_hex[dec & 0xF]; + hex_str[2] = '\0'; +} + +static void serial_debug_print(const uint8_t *data, uint16_t size, bool write) +{ + static bool write_prev = false; + uint8_t hex_str[3]; + + if(write_prev != write) { + write_prev = write; + printf("\n--- %s ---\n", write ? "WRITE" : "READ"); + } + + for(uint32_t i = 0; i < size; i++) { + dec_to_hex_str(data[i], hex_str); + printf("%s ", hex_str); + } +} + +#else +static void serial_debug_print(const uint8_t *data, uint16_t size, bool write) { } +#endif + +static spi_host_device_t s_spi_bus; +static spi_bus_config_t s_spi_config; +static spi_device_handle_t s_device_h; +static spi_device_interface_config_t s_device_config; +static int64_t s_time_end; +static uint32_t s_reset_trigger_pin; +static uint32_t s_strap_bit0_pin; +static uint32_t s_strap_bit1_pin; +static uint32_t s_strap_bit2_pin; +static uint32_t s_strap_bit3_pin; +static uint32_t s_spi_cs_pin; + +esp_loader_error_t loader_port_esp32_spi_init(const loader_esp32_spi_config_t *config) +{ + /* Initialize the global static variables*/ + s_spi_bus = config->spi_bus; + s_reset_trigger_pin = config->reset_trigger_pin; + s_strap_bit0_pin = config->strap_bit0_pin; + s_strap_bit1_pin = config->strap_bit1_pin; + s_strap_bit2_pin = config->strap_bit2_pin; + s_strap_bit3_pin = config->strap_bit3_pin; + s_spi_cs_pin = config->spi_cs_pin; + + /* Configure and initialize the SPI bus*/ + s_spi_config.mosi_io_num = config->spi_mosi_pin; + s_spi_config.miso_io_num = config->spi_miso_pin; + s_spi_config.sclk_io_num = config->spi_clk_pin; + s_spi_config.quadwp_io_num = config->spi_quadwp_pin; + s_spi_config.quadhd_io_num = config->spi_quadhd_pin; + s_spi_config.max_transfer_sz = 4096 * 4; + + if (spi_bus_initialize(s_spi_bus, &s_spi_config, DMA_CHAN) != ESP_OK) { + return ESP_LOADER_ERROR_FAIL; + } + + /* Configure and add the device */ + s_device_config.clock_speed_hz = config->frequency; + s_device_config.spics_io_num = -1; /* We're using the chip select pin as GPIO as we need to + chain multiple transactions with CS pulled down */ + s_device_config.flags = SPI_DEVICE_HALFDUPLEX; + s_device_config.queue_size = 16; + + if (spi_bus_add_device(s_spi_bus, &s_device_config, &s_device_h) != ESP_OK) { + return ESP_LOADER_ERROR_FAIL; + } + + /* Initialize the pins except for the strapping ones */ + gpio_reset_pin(s_reset_trigger_pin); + gpio_set_pull_mode(s_reset_trigger_pin, GPIO_PULLUP_ONLY); + gpio_set_direction(s_reset_trigger_pin, GPIO_MODE_OUTPUT); + gpio_set_level(s_reset_trigger_pin, 1); + + gpio_reset_pin(s_spi_cs_pin); + gpio_set_pull_mode(s_spi_cs_pin, GPIO_PULLUP_ONLY); + gpio_set_direction(s_spi_cs_pin, GPIO_MODE_OUTPUT); + gpio_set_level(s_spi_cs_pin, 1); + + return ESP_LOADER_SUCCESS; +} + + +void loader_port_esp32_spi_deinit(void) +{ + gpio_reset_pin(s_reset_trigger_pin); + gpio_reset_pin(s_spi_cs_pin); + spi_bus_remove_device(s_device_h); + spi_bus_free(s_spi_bus); +} + + +void loader_port_spi_set_cs(const uint32_t level) { + gpio_set_level(s_spi_cs_pin, level); +} + + +esp_loader_error_t loader_port_write(const uint8_t *data, const uint16_t size, const uint32_t timeout) +{ + /* Due to the fact that the SPI driver uses DMA for larger transfers, + and the DMA requirements, the buffer must be word aligned */ + if (data == NULL || !WORD_ALIGNED(data)) { + return ESP_LOADER_ERROR_INVALID_PARAM; + } + + serial_debug_print(data, size, true); + + spi_transaction_t transaction = { + .tx_buffer = data, + .rx_buffer = NULL, + .length = size * 8U, + .rxlength = 0, + }; + + esp_err_t err = spi_device_transmit(s_device_h, &transaction); + + if (err == ESP_OK) { + serial_debug_print(data, size, false); + return ESP_LOADER_SUCCESS; + } else if (err == ESP_ERR_TIMEOUT) { + return ESP_LOADER_ERROR_TIMEOUT; + } else { + return ESP_LOADER_ERROR_FAIL; + } +} + + +esp_loader_error_t loader_port_read(uint8_t *data, const uint16_t size, const uint32_t timeout) +{ + /* Due to the fact that the SPI driver uses DMA for larger transfers, + and the DMA requirements, the buffer must be word aligned */ + if (data == NULL || !WORD_ALIGNED(data)) { + return ESP_LOADER_ERROR_INVALID_PARAM; + } + + serial_debug_print(data, size, true); + + spi_transaction_t transaction = { + .tx_buffer = NULL, + .rx_buffer = data, + .rxlength = size * 8, + }; + + esp_err_t err = spi_device_transmit(s_device_h, &transaction); + + if (err == ESP_OK) { + serial_debug_print(data, size, false); + return ESP_LOADER_SUCCESS; + } else if (err == ESP_ERR_TIMEOUT) { + return ESP_LOADER_ERROR_TIMEOUT; + } else { + return ESP_LOADER_ERROR_FAIL; + } +} + + +void loader_port_enter_bootloader(void) +{ + /* + We have to initialize the GPIO pins for the target strapping pins here, + as they may overlap with target SPI pins. + For instance in the case of ESP32C3 MISO and strapping bit 0 pins overlap. + */ + spi_bus_remove_device(s_device_h); + spi_bus_free(s_spi_bus); + + gpio_reset_pin(s_strap_bit0_pin); + gpio_set_pull_mode(s_strap_bit0_pin, GPIO_PULLUP_ONLY); + gpio_set_direction(s_strap_bit0_pin, GPIO_MODE_OUTPUT); + + gpio_reset_pin(s_strap_bit1_pin); + gpio_set_pull_mode(s_strap_bit1_pin, GPIO_PULLUP_ONLY); + gpio_set_direction(s_strap_bit1_pin, GPIO_MODE_OUTPUT); + + gpio_reset_pin(s_strap_bit2_pin); + gpio_set_pull_mode(s_strap_bit2_pin, GPIO_PULLUP_ONLY); + gpio_set_direction(s_strap_bit2_pin, GPIO_MODE_OUTPUT); + + gpio_reset_pin(s_strap_bit3_pin); + gpio_set_pull_mode(s_strap_bit3_pin, GPIO_PULLUP_ONLY); + gpio_set_direction(s_strap_bit3_pin, GPIO_MODE_OUTPUT); + + /* Set the strapping pins and perform the reset sequence */ + gpio_set_level(s_strap_bit0_pin, 1); + gpio_set_level(s_strap_bit1_pin, 0); + gpio_set_level(s_strap_bit2_pin, 0); + gpio_set_level(s_strap_bit3_pin, 0); + loader_port_reset_target(); + loader_port_delay_ms(SERIAL_FLASHER_BOOT_HOLD_TIME_MS); + gpio_set_level(s_strap_bit3_pin, 1); + gpio_set_level(s_strap_bit0_pin, 0); + + /* Disable the strapping pins so they can be used by the slave later */ + gpio_reset_pin(s_strap_bit0_pin); + gpio_reset_pin(s_strap_bit1_pin); + gpio_reset_pin(s_strap_bit2_pin); + gpio_reset_pin(s_strap_bit3_pin); + + /* Restore the SPI bus pins */ + spi_bus_initialize(s_spi_bus, &s_spi_config, DMA_CHAN); + spi_bus_add_device(s_spi_bus, &s_device_config, &s_device_h); +} + + +void loader_port_reset_target(void) +{ + gpio_set_level(s_reset_trigger_pin, 0); + loader_port_delay_ms(SERIAL_FLASHER_RESET_HOLD_TIME_MS); + gpio_set_level(s_reset_trigger_pin, 1); +} + + +void loader_port_delay_ms(const uint32_t ms) +{ + usleep(ms * 1000); +} + + +void loader_port_start_timer(const uint32_t ms) +{ + s_time_end = esp_timer_get_time() + ms * 1000; +} + + +uint32_t loader_port_remaining_time(void) +{ + int64_t remaining = (s_time_end - esp_timer_get_time()) / 1000; + return (remaining > 0) ? (uint32_t)remaining : 0; +} + + +void loader_port_debug_print(const char *str) +{ + printf("DEBUG: %s\n", str); +} + + +esp_loader_error_t loader_port_change_transmission_rate(const uint32_t frequency) +{ + if (spi_bus_remove_device(s_device_h) != ESP_OK) { + return ESP_LOADER_ERROR_FAIL; + } + + uint32_t old_frequency = s_device_config.clock_speed_hz; + s_device_config.clock_speed_hz = frequency; + + if (spi_bus_add_device(s_spi_bus, &s_device_config, &s_device_h) != ESP_OK) { + s_device_config.clock_speed_hz = old_frequency; + return ESP_LOADER_ERROR_FAIL; + } + + return ESP_LOADER_SUCCESS; +} diff --git a/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/port/esp32_spi_port.h b/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/port/esp32_spi_port.h new file mode 100644 index 000000000..72304c5df --- /dev/null +++ b/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/port/esp32_spi_port.h @@ -0,0 +1,60 @@ +/* Copyright 2020-2023 Espressif Systems (Shanghai) CO LTD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + #pragma once + +#include "esp_loader_io.h" +#include "driver/spi_master.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct +{ + spi_host_device_t spi_bus; + uint32_t frequency; + uint32_t spi_clk_pin; + uint32_t spi_miso_pin; + uint32_t spi_mosi_pin; + uint32_t spi_cs_pin; + uint32_t spi_quadwp_pin; + uint32_t spi_quadhd_pin; + uint32_t reset_trigger_pin; + uint32_t strap_bit0_pin; + uint32_t strap_bit1_pin; + uint32_t strap_bit2_pin; + uint32_t strap_bit3_pin; +} loader_esp32_spi_config_t; + +/** + * @brief Initializes the SPI interface. + * + * @param config[in] Configuration structure + * + * @return + * - ESP_LOADER_SUCCESS Success + * - ESP_LOADER_ERROR_FAIL Initialization failure + */ +esp_loader_error_t loader_port_esp32_spi_init(const loader_esp32_spi_config_t *config); + +/** + * @brief Deinitializes the SPI interface. + */ +void loader_port_esp32_spi_deinit(void); + +#ifdef __cplusplus +} +#endif diff --git a/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/port/raspberry_port.c b/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/port/raspberry_port.c new file mode 100644 index 000000000..d733db702 --- /dev/null +++ b/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/port/raspberry_port.c @@ -0,0 +1,302 @@ +/* Copyright 2020-2023 Espressif Systems (Shanghai) CO LTD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "esp_loader_io.h" +#include "protocol.h" +#include +#include "raspberry_port.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef SERIAL_FLASHER_DEBUG_TRACE +static void transfer_debug_print(const uint8_t *data, uint16_t size, bool write) +{ + static bool write_prev = false; + + if (write_prev != write) { + write_prev = write; + printf("\n--- %s ---\n", write ? "WRITE" : "READ"); + } + + for (uint32_t i = 0; i < size; i++) { + printf("%02x ", data[i]); + } +} +#endif + +static int serial; +static int64_t s_time_end; +static int32_t s_reset_trigger_pin; +static int32_t s_gpio0_trigger_pin; + + +static speed_t convert_baudrate(int baud) +{ + switch (baud) { + case 50: return B50; + case 75: return B75; + case 110: return B110; + case 134: return B134; + case 150: return B150; + case 200: return B200; + case 300: return B300; + case 600: return B600; + case 1200: return B1200; + case 1800: return B1800; + case 2400: return B2400; + case 4800: return B4800; + case 9600: return B9600; + case 19200: return B19200; + case 38400: return B38400; + case 57600: return B57600; + case 115200: return B115200; + case 230400: return B230400; + case 460800: return B460800; + case 500000: return B500000; + case 576000: return B576000; + case 921600: return B921600; + case 1000000: return B1000000; + case 1152000: return B1152000; + case 1500000: return B1500000; + case 2000000: return B2000000; + case 2500000: return B2500000; + case 3000000: return B3000000; + case 3500000: return B3500000; + case 4000000: return B4000000; + default: return -1; + } +} + +static int serialOpen (const char *device, uint32_t baudrate) +{ + struct termios options; + int status, fd; + + if ((fd = open (device, O_RDWR | O_NOCTTY | O_NDELAY | O_NONBLOCK)) == -1) { + printf("Error occured while opening serial port !\n"); + return -1 ; + } + + fcntl (fd, F_SETFL, O_RDWR) ; + + // Get and modify current options: + + tcgetattr (fd, &options); + speed_t baud = convert_baudrate(baudrate); + + if(baud < 0) { + printf("Invalid baudrate!\n"); + return -1; + } + + cfmakeraw (&options) ; + cfsetispeed (&options, baud) ; + cfsetospeed (&options, baud) ; + + options.c_cflag |= (CLOCAL | CREAD) ; + options.c_cflag &= ~(PARENB | CSTOPB | CSIZE) ; + options.c_cflag |= CS8 ; + options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG) ; + options.c_oflag &= ~OPOST ; + options.c_iflag &= ~(IXON | IXOFF | IXANY); // Turn off s/w flow ctrl + options.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL); // Disable any special handling of received bytes + + options.c_cc [VMIN] = 0 ; + options.c_cc [VTIME] = 10 ; // 1 Second + + tcsetattr (fd, TCSANOW, &options) ; + + ioctl (fd, TIOCMGET, &status); + + status |= TIOCM_DTR ; + status |= TIOCM_RTS ; + + ioctl (fd, TIOCMSET, &status); + + usleep (10000) ; // 10mS + + return fd ; +} + +static esp_loader_error_t change_baudrate(int file_desc, int baudrate) +{ + struct termios options; + speed_t baud = convert_baudrate(baudrate); + + if(baud < 0) { + return ESP_LOADER_ERROR_INVALID_PARAM; + } + + tcgetattr (file_desc, &options); + + cfmakeraw (&options) ; + cfsetispeed (&options, baud); + cfsetospeed (&options, baud); + + tcsetattr (file_desc, TCSANOW, &options); + + return ESP_LOADER_SUCCESS; +} + +static void set_timeout(uint32_t timeout) +{ + struct termios options; + + timeout /= 100; + timeout = MAX(timeout, 1); + + tcgetattr(serial, &options); + options.c_cc[VTIME] = timeout; + tcsetattr(serial, TCSANOW, &options); +} + +static esp_loader_error_t read_char(char *c, uint32_t timeout) +{ + set_timeout(timeout); + int read_bytes = read(serial, c, 1); + + if (read_bytes == 1) { + return ESP_LOADER_SUCCESS; + } else if (read_bytes == 0) { + return ESP_LOADER_ERROR_TIMEOUT; + } else { + return ESP_LOADER_ERROR_FAIL; + } +} + +static esp_loader_error_t read_data(char *buffer, uint32_t size) +{ + for (int i = 0; i < size; i++) { + uint32_t remaining_time = loader_port_remaining_time(); + RETURN_ON_ERROR( read_char(&buffer[i], remaining_time) ); + } + + return ESP_LOADER_SUCCESS; +} + +esp_loader_error_t loader_port_raspberry_init(const loader_raspberry_config_t *config) +{ + s_reset_trigger_pin = config->reset_trigger_pin; + s_gpio0_trigger_pin = config->gpio0_trigger_pin; + + serial = serialOpen(config->device, config->baudrate); + if (serial < 0) { + printf("Serial port could not be opened!\n"); + return ESP_LOADER_ERROR_FAIL; + } + + if (gpioInitialise() < 0) { + printf("pigpio initialisation failed\n"); + return ESP_LOADER_ERROR_FAIL; + } + + gpioSetMode(config->reset_trigger_pin, PI_OUTPUT); + gpioSetMode(config->gpio0_trigger_pin, PI_OUTPUT); + + return ESP_LOADER_SUCCESS; +} + + +esp_loader_error_t loader_port_write(const uint8_t *data, uint16_t size, uint32_t timeout) +{ + int written = write(serial, data, size); + + if (written < 0) { + return ESP_LOADER_ERROR_FAIL; + } else if (written < size) { +#ifdef SERIAL_FLASHER_DEBUG_TRACE + transfer_debug_print(data, written, true); +#endif + return ESP_LOADER_ERROR_TIMEOUT; + } else { +#ifdef SERIAL_FLASHER_DEBUG_TRACE + transfer_debug_print(data, written, true); +#endif + return ESP_LOADER_SUCCESS; + } +} + + +esp_loader_error_t loader_port_read(uint8_t *data, uint16_t size, uint32_t timeout) +{ + RETURN_ON_ERROR( read_data(data, size) ); + +#ifdef SERIAL_FLASHER_DEBUG_TRACE + transfer_debug_print(data, size, false); +#endif + + return ESP_LOADER_SUCCESS; +} + + +// Set GPIO0 LOW, then assert reset pin for 50 milliseconds. +void loader_port_enter_bootloader(void) +{ + gpioWrite(s_gpio0_trigger_pin, 0); + loader_port_reset_target(); + loader_port_delay_ms(SERIAL_FLASHER_BOOT_HOLD_TIME_MS); + gpioWrite(s_gpio0_trigger_pin, 1); +} + + +void loader_port_reset_target(void) +{ + gpioWrite(s_reset_trigger_pin, 0); + loader_port_delay_ms(SERIAL_FLASHER_RESET_HOLD_TIME_MS); + gpioWrite(s_reset_trigger_pin, 1); +} + + +void loader_port_delay_ms(uint32_t ms) +{ + usleep(ms * 1000); +} + + +void loader_port_start_timer(uint32_t ms) +{ + s_time_end = clock() + (ms * (CLOCKS_PER_SEC / 1000)); +} + + +uint32_t loader_port_remaining_time(void) +{ + int64_t remaining = (s_time_end - clock()) / 1000; + return (remaining > 0) ? (uint32_t)remaining : 0; +} + + +void loader_port_debug_print(const char *str) +{ + printf("DEBUG: %s\n", str); +} + +esp_loader_error_t loader_port_change_transmission_rate(uint32_t baudrate) +{ + return change_baudrate(serial, baudrate); +} \ No newline at end of file diff --git a/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/port/raspberry_port.h b/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/port/raspberry_port.h new file mode 100644 index 000000000..803ab72dd --- /dev/null +++ b/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/port/raspberry_port.h @@ -0,0 +1,36 @@ +/* Copyright 2020-2023 Espressif Systems (Shanghai) CO LTD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include "esp_loader_io.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + const char *device; + uint32_t baudrate; + uint32_t reset_trigger_pin; + uint32_t gpio0_trigger_pin; +} loader_raspberry_config_t; + +esp_loader_error_t loader_port_raspberry_init(const loader_raspberry_config_t *config); + +#ifdef __cplusplus +} +#endif diff --git a/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/port/stm32_port.c b/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/port/stm32_port.c new file mode 100644 index 000000000..716c9c91b --- /dev/null +++ b/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/port/stm32_port.c @@ -0,0 +1,140 @@ +/* Copyright 2020-2023 Espressif Systems (Shanghai) CO LTD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include "stm32_port.h" + +static UART_HandleTypeDef *uart; +static GPIO_TypeDef* gpio_port_io0, *gpio_port_rst; +static uint16_t gpio_num_io0, gpio_num_rst; + +#ifdef SERIAL_FLASHER_DEBUG_TRACE +static void transfer_debug_print(const uint8_t *data, uint16_t size, bool write) +{ + static bool write_prev = false; + + if (write_prev != write) { + write_prev = write; + printf("\n--- %s ---\n", write ? "WRITE" : "READ"); + } + + for (uint32_t i = 0; i < size; i++) { + printf("%02x ", data[i]); + } +} +#endif + +static uint32_t s_time_end; + +esp_loader_error_t loader_port_write(const uint8_t *data, uint16_t size, uint32_t timeout) +{ + HAL_StatusTypeDef err = HAL_UART_Transmit(uart, (uint8_t *)data, size, timeout); + + if (err == HAL_OK) { +#ifdef SERIAL_FLASHER_DEBUG_TRACE + transfer_debug_print(data, size, true); +#endif + return ESP_LOADER_SUCCESS; + } else if (err == HAL_TIMEOUT) { + return ESP_LOADER_ERROR_TIMEOUT; + } else { + return ESP_LOADER_ERROR_FAIL; + } +} + + +esp_loader_error_t loader_port_read(uint8_t *data, uint16_t size, uint32_t timeout) +{ + HAL_StatusTypeDef err = HAL_UART_Receive(uart, data, size, timeout); + + if (err == HAL_OK) { +#ifdef SERIAL_FLASHER_DEBUG_TRACE + transfer_debug_print(data, size, false); +#endif + return ESP_LOADER_SUCCESS; + } else if (err == HAL_TIMEOUT) { + return ESP_LOADER_ERROR_TIMEOUT; + } else { + return ESP_LOADER_ERROR_FAIL; + } +} + +void loader_port_stm32_init(loader_stm32_config_t *config) + +{ + uart = config->huart; + gpio_port_io0 = config->port_io0; + gpio_port_rst = config->port_rst; + gpio_num_io0 = config->pin_num_io0; + gpio_num_rst = config->pin_num_rst; +} + +// Set GPIO0 LOW, then +// assert reset pin for 100 milliseconds. +void loader_port_enter_bootloader(void) +{ + HAL_GPIO_WritePin(gpio_port_io0, gpio_num_io0, GPIO_PIN_RESET); + loader_port_reset_target(); + HAL_Delay(SERIAL_FLASHER_BOOT_HOLD_TIME_MS); + HAL_GPIO_WritePin(gpio_port_io0, gpio_num_io0, GPIO_PIN_SET); +} + + +void loader_port_reset_target(void) +{ + HAL_GPIO_WritePin(gpio_port_rst, gpio_num_rst, GPIO_PIN_RESET); + HAL_Delay(SERIAL_FLASHER_RESET_HOLD_TIME_MS); + HAL_GPIO_WritePin(gpio_port_rst, gpio_num_rst, GPIO_PIN_SET); +} + + +void loader_port_delay_ms(uint32_t ms) +{ + HAL_Delay(ms); +} + + +void loader_port_start_timer(uint32_t ms) +{ + s_time_end = HAL_GetTick() + ms; +} + + +uint32_t loader_port_remaining_time(void) +{ + int32_t remaining = s_time_end - HAL_GetTick(); + return (remaining > 0) ? (uint32_t)remaining : 0; +} + + +void loader_port_debug_print(const char *str) +{ + printf("DEBUG: %s", str); +} + +esp_loader_error_t loader_port_change_transmission_rate(uint32_t baudrate) +{ + uart->Init.BaudRate = baudrate; + + if( HAL_UART_Init(uart) != HAL_OK ) { + return ESP_LOADER_ERROR_FAIL; + } + + return ESP_LOADER_SUCCESS; +} diff --git a/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/port/stm32_port.h b/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/port/stm32_port.h new file mode 100644 index 000000000..a3a89a733 --- /dev/null +++ b/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/port/stm32_port.h @@ -0,0 +1,38 @@ +/* Copyright 2020-2023 Espressif Systems (Shanghai) CO LTD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include "esp_loader_io.h" +#include "stm32f4xx_hal.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + UART_HandleTypeDef *huart; + GPIO_TypeDef *port_io0; + uint16_t pin_num_io0; + GPIO_TypeDef *port_rst; + uint16_t pin_num_rst; +} loader_stm32_config_t; + +void loader_port_stm32_init(loader_stm32_config_t *config); + +#ifdef __cplusplus +} +#endif diff --git a/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/port/zephyr_port.c b/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/port/zephyr_port.c new file mode 100644 index 000000000..21270ba0a --- /dev/null +++ b/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/port/zephyr_port.c @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2022 KT-Elektronik, Klaucke und Partner GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "zephyr_port.h" +#include +#include + +static const struct device *uart_dev; +static struct gpio_dt_spec enable_spec; +static struct gpio_dt_spec boot_spec; + +static struct tty_serial tty; +static char tty_rx_buf[CONFIG_ESP_SERIAL_FLASHER_UART_BUFSIZE]; +static char tty_tx_buf[CONFIG_ESP_SERIAL_FLASHER_UART_BUFSIZE]; + +#ifdef SERIAL_FLASHER_DEBUG_TRACE +static void transfer_debug_print(const uint8_t *data, uint16_t size, bool write) +{ + static bool write_prev = false; + + if (write_prev != write) { + write_prev = write; + printk("\n--- %s ---\n", write ? "WRITE" : "READ"); + } + + for (uint32_t i = 0; i < size; i++) { + printk("%02x ", data[i]); + } +} +#endif + +esp_loader_error_t configure_tty() +{ + if (tty_init(&tty, uart_dev) < 0 || + tty_set_rx_buf(&tty, tty_rx_buf, sizeof(tty_rx_buf)) < 0 || + tty_set_tx_buf(&tty, tty_tx_buf, sizeof(tty_tx_buf)) < 0) { + return ESP_LOADER_ERROR_FAIL; + } + + return ESP_LOADER_SUCCESS; +} + +esp_loader_error_t loader_port_read(uint8_t *data, const uint16_t size, const uint32_t timeout) +{ + if (!device_is_ready(uart_dev) || data == NULL || size == 0) { + return ESP_LOADER_ERROR_FAIL; + } + + ssize_t total_read = 0; + ssize_t remaining = size; + + tty_set_rx_timeout(&tty, timeout); + while (remaining > 0) { + const uint16_t chunk_size = remaining < CONFIG_ESP_SERIAL_FLASHER_UART_BUFSIZE ? + remaining : CONFIG_ESP_SERIAL_FLASHER_UART_BUFSIZE; + ssize_t read = tty_read(&tty, &data[total_read], chunk_size); + if (read < 0) { + return ESP_LOADER_ERROR_TIMEOUT; + } +#ifdef SERIAL_FLASHER_DEBUG_TRACE + transfer_debug_print(data, read, false); +#endif + total_read += read; + remaining -= read; + } + + return ESP_LOADER_SUCCESS; +} + +esp_loader_error_t loader_port_write(const uint8_t *data, const uint16_t size, const uint32_t timeout) +{ + if (!device_is_ready(uart_dev) || data == NULL || size == 0) { + return ESP_LOADER_ERROR_FAIL; + } + + ssize_t total_written = 0; + ssize_t remaining = size; + + tty_set_tx_timeout(&tty, timeout); + while (remaining > 0) { + const uint16_t chunk_size = remaining < CONFIG_ESP_SERIAL_FLASHER_UART_BUFSIZE ? + remaining : CONFIG_ESP_SERIAL_FLASHER_UART_BUFSIZE; + ssize_t written = tty_write(&tty, &data[total_written], chunk_size); + if (written < 0) { + return ESP_LOADER_ERROR_TIMEOUT; + } +#ifdef SERIAL_FLASHER_DEBUG_TRACE + transfer_debug_print(data, written, true); +#endif + total_written += written; + remaining -= written; + } + + return ESP_LOADER_SUCCESS; +} + +esp_loader_error_t loader_port_zephyr_init(const loader_zephyr_config_t *config) +{ + uart_dev = config->uart_dev; + enable_spec = config->enable_spec; + boot_spec = config->boot_spec; + return configure_tty(); +} + +void loader_port_reset_target(void) +{ + gpio_pin_set_dt(&enable_spec, false); + loader_port_delay_ms(CONFIG_SERIAL_FLASHER_RESET_HOLD_TIME_MS); + gpio_pin_set_dt(&enable_spec, true); +} + +void loader_port_enter_bootloader(void) +{ + gpio_pin_set_dt(&boot_spec, false); + loader_port_reset_target(); + loader_port_delay_ms(CONFIG_SERIAL_FLASHER_BOOT_HOLD_TIME_MS); + gpio_pin_set_dt(&boot_spec, true); +} + +void loader_port_delay_ms(uint32_t ms) +{ + k_msleep(ms); +} + +static uint64_t s_time_end; + +void loader_port_start_timer(uint32_t ms) +{ + s_time_end = sys_clock_timeout_end_calc(Z_TIMEOUT_MS(ms)); +} + +uint32_t loader_port_remaining_time(void) +{ + int64_t remaining = k_ticks_to_ms_floor64(s_time_end - k_uptime_ticks()); + return (remaining > 0) ? (uint32_t)remaining : 0; +} + +esp_loader_error_t loader_port_change_transmission_rate(uint32_t baudrate) +{ + struct uart_config uart_config; + + if (!device_is_ready(uart_dev)) { + return ESP_LOADER_ERROR_FAIL; + } + + if (uart_config_get(uart_dev, &uart_config) != 0) { + return ESP_LOADER_ERROR_FAIL; + } + uart_config.baudrate = baudrate; + + if (uart_configure(uart_dev, &uart_config) != 0) { + return ESP_LOADER_ERROR_FAIL; + } + + /* bitrate-change can require tty re-configuration */ + return configure_tty(); +} diff --git a/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/port/zephyr_port.h b/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/port/zephyr_port.h new file mode 100644 index 000000000..0b104e375 --- /dev/null +++ b/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/port/zephyr_port.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2022 KT-Elektronik, Klaucke und Partner GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "esp_loader_io.h" +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + const struct device *uart_dev; + const struct gpio_dt_spec enable_spec; + const struct gpio_dt_spec boot_spec; +} loader_zephyr_config_t; + +esp_loader_error_t loader_port_zephyr_init(const loader_zephyr_config_t *config); + +#ifdef __cplusplus +} +#endif + diff --git a/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/private_include/esp_targets.h b/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/private_include/esp_targets.h new file mode 100644 index 000000000..cf8af91fa --- /dev/null +++ b/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/private_include/esp_targets.h @@ -0,0 +1,33 @@ +/* Copyright 2020 Espressif Systems (Shanghai) PTE LTD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include "esp_loader.h" + +typedef struct { + uint32_t cmd; + uint32_t usr; + uint32_t usr1; + uint32_t usr2; + uint32_t w0; + uint32_t mosi_dlen; + uint32_t miso_dlen; +} target_registers_t; + +esp_loader_error_t loader_detect_chip(target_chip_t *target, const target_registers_t **regs); +esp_loader_error_t loader_read_spi_config(target_chip_t target_chip, uint32_t *spi_config); +bool encryption_in_begin_flash_cmd(target_chip_t target); \ No newline at end of file diff --git a/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/private_include/md5_hash.h b/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/private_include/md5_hash.h new file mode 100644 index 000000000..eb5738c8b --- /dev/null +++ b/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/private_include/md5_hash.h @@ -0,0 +1,28 @@ +/* + * MD5 hash implementation and interface functions + * Copyright (c) 2003-2005, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif +struct MD5Context { + uint32_t buf[4]; + uint32_t bits[2]; + uint8_t in[64]; +}; + +void MD5Init(struct MD5Context *context); +void MD5Update(struct MD5Context *context, unsigned char const *buf, unsigned len); +void MD5Final(unsigned char digest[16], struct MD5Context *context); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/private_include/protocol.h b/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/private_include/protocol.h new file mode 100644 index 000000000..fb8b086fd --- /dev/null +++ b/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/private_include/protocol.h @@ -0,0 +1,230 @@ +/* Copyright 2020-2023 Espressif Systems (Shanghai) CO LTD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include "esp_loader.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define STATUS_FAILURE 1 +#define STATUS_SUCCESS 0 + +#define READ_DIRECTION 1 +#define WRITE_DIRECTION 0 + +#define MD5_SIZE 32 + +typedef enum __attribute__((packed)) +{ + FLASH_BEGIN = 0x02, + FLASH_DATA = 0x03, + FLASH_END = 0x04, + MEM_BEGIN = 0x05, + MEM_END = 0x06, + MEM_DATA = 0x07, + SYNC = 0x08, + WRITE_REG = 0x09, + READ_REG = 0x0a, + + SPI_SET_PARAMS = 0x0b, + SPI_ATTACH = 0x0d, + CHANGE_BAUDRATE = 0x0f, + FLASH_DEFL_BEGIN = 0x10, + FLASH_DEFL_DATA = 0x11, + FLASH_DEFL_END = 0x12, + SPI_FLASH_MD5 = 0x13, +} command_t; + +typedef enum __attribute__((packed)) +{ + RESPONSE_OK = 0x00, + INVALID_COMMAND = 0x05, // parameters or length field is invalid + COMMAND_FAILED = 0x06, // Failed to act on received message + INVALID_CRC = 0x07, // Invalid CRC in message + FLASH_WRITE_ERR = 0x08, // After writing a block of data to flash, the ROM loader reads the value back and the 8-bit CRC is compared to the data read from flash. If they don't match, this error is returned. + FLASH_READ_ERR = 0x09, // SPI read failed + READ_LENGTH_ERR = 0x0a, // SPI read request length is too long + DEFLATE_ERROR = 0x0b, // ESP32 compressed uploads only +} error_code_t; + +typedef struct __attribute__((packed)) +{ + uint8_t direction; + uint8_t command; // One of command_t + uint16_t size; + uint32_t checksum; +} command_common_t; + +typedef struct __attribute__((packed)) +{ + command_common_t common; + uint32_t erase_size; + uint32_t packet_count; + uint32_t packet_size; + uint32_t offset; + uint32_t encrypted; +} flash_begin_command_t; + +typedef struct __attribute__((packed)) +{ + command_common_t common; + uint32_t data_size; + uint32_t sequence_number; + uint32_t zero_0; + uint32_t zero_1; +} data_command_t; + +typedef struct __attribute__((packed)) +{ + command_common_t common; + uint32_t stay_in_loader; +} flash_end_command_t; + +typedef struct __attribute__((packed)) +{ + command_common_t common; + uint32_t total_size; + uint32_t blocks; + uint32_t block_size; + uint32_t offset; +} mem_begin_command_t; + +typedef struct __attribute__((packed)) +{ + command_common_t common; + uint32_t stay_in_loader; + uint32_t entry_point_address; +} mem_end_command_t; + +typedef struct __attribute__((packed)) +{ + command_common_t common; + uint8_t sync_sequence[36]; +} sync_command_t; + +typedef struct __attribute__((packed)) +{ + command_common_t common; + uint32_t address; + uint32_t value; + uint32_t mask; + uint32_t delay_us; +} write_reg_command_t; + +typedef struct __attribute__((packed)) +{ + command_common_t common; + uint32_t address; +} read_reg_command_t; + +typedef struct __attribute__((packed)) +{ + command_common_t common; + uint32_t configuration; + uint32_t zero; // ESP32 ROM only +} spi_attach_command_t; + +typedef struct __attribute__((packed)) +{ + command_common_t common; + uint32_t new_baudrate; + uint32_t old_baudrate; +} change_baudrate_command_t; + +typedef struct __attribute__((packed)) +{ + command_common_t common; + uint32_t address; + uint32_t size; + uint32_t reserved_0; + uint32_t reserved_1; +} spi_flash_md5_command_t; + +typedef struct __attribute__((packed)) +{ + uint8_t direction; + uint8_t command; // One of command_t + uint16_t size; + uint32_t value; +} common_response_t; + +typedef struct __attribute__((packed)) +{ + uint8_t failed; + uint8_t error; +} response_status_t; + +typedef struct __attribute__((packed)) +{ + common_response_t common; + response_status_t status; +} response_t; + +typedef struct __attribute__((packed)) +{ + common_response_t common; + uint8_t md5[MD5_SIZE]; // ROM only + response_status_t status; +} rom_md5_response_t; + +typedef struct __attribute__((packed)) +{ + command_common_t common; + uint32_t id; + uint32_t total_size; + uint32_t block_size; + uint32_t sector_size; + uint32_t page_size; + uint32_t status_mask; +} write_spi_command_t; + +esp_loader_error_t loader_initialize_conn(esp_loader_connect_args_t *connect_args); + +#ifdef SERIAL_FLASHER_INTERFACE_UART +esp_loader_error_t loader_flash_begin_cmd(uint32_t offset, uint32_t erase_size, uint32_t block_size, uint32_t blocks_to_write, bool encryption); + +esp_loader_error_t loader_flash_data_cmd(const uint8_t *data, uint32_t size); + +esp_loader_error_t loader_flash_end_cmd(bool stay_in_loader); + +esp_loader_error_t loader_sync_cmd(void); + +esp_loader_error_t loader_spi_attach_cmd(uint32_t config); + +esp_loader_error_t loader_md5_cmd(uint32_t address, uint32_t size, uint8_t *md5_out); + +esp_loader_error_t loader_spi_parameters(uint32_t total_size); +#endif /* SERIAL_FLASHER_INTERFACE_UART */ + +esp_loader_error_t loader_mem_begin_cmd(uint32_t offset, uint32_t size, uint32_t blocks_to_write, uint32_t block_size); + +esp_loader_error_t loader_mem_data_cmd(const uint8_t *data, uint32_t size); + +esp_loader_error_t loader_mem_end_cmd(uint32_t entrypoint); + +esp_loader_error_t loader_write_reg_cmd(uint32_t address, uint32_t value, uint32_t mask, uint32_t delay_us); + +esp_loader_error_t loader_read_reg_cmd(uint32_t address, uint32_t *reg); + +esp_loader_error_t loader_change_baudrate_cmd(uint32_t baudrate); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/private_include/protocol_prv.h b/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/private_include/protocol_prv.h new file mode 100644 index 000000000..3b9575606 --- /dev/null +++ b/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/private_include/protocol_prv.h @@ -0,0 +1,31 @@ +/* Copyright 2020-2023 Espressif Systems (Shanghai) CO LTD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include "esp_loader.h" +#include "protocol.h" + +void log_loader_internal_error(error_code_t error); + +esp_loader_error_t send_cmd(const void *cmd_data, uint32_t size, uint32_t *reg_value); + +esp_loader_error_t send_cmd_with_data(const void *cmd_data, size_t cmd_size, + const void *data, size_t data_size); + +esp_loader_error_t send_cmd_md5(const void *cmd_data, size_t cmd_size, uint8_t md5_out[MD5_SIZE]); diff --git a/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/private_include/slip.h b/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/private_include/slip.h new file mode 100644 index 000000000..00f1f7d0d --- /dev/null +++ b/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/private_include/slip.h @@ -0,0 +1,28 @@ +/* Copyright 2020-2023 Espressif Systems (Shanghai) CO LTD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "esp_loader.h" +#include +#include + +esp_loader_error_t SLIP_receive_data(uint8_t *buff, size_t size); + +esp_loader_error_t SLIP_receive_packet(uint8_t *buff, size_t size); + +esp_loader_error_t SLIP_send(const uint8_t *data, size_t size); + +esp_loader_error_t SLIP_send_delimiter(void); diff --git a/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/src/esp_loader.c b/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/src/esp_loader.c new file mode 100644 index 000000000..6ef32673c --- /dev/null +++ b/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/src/esp_loader.c @@ -0,0 +1,415 @@ +/* Copyright 2020-2023 Espressif Systems (Shanghai) CO LTD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "protocol.h" +#include "esp_loader_io.h" +#include "esp_loader.h" +#include "esp_targets.h" +#include "md5_hash.h" +#include +#include + +#ifndef MAX +#define MAX(a, b) ((a) > (b)) ? (a) : (b) +#endif + +#ifndef MIN +#define MIN(a, b) ((a) < (b)) ? (a) : (b) +#endif + +#ifndef ROUNDUP +#define ROUNDUP(a, b) (((int)a + (int)b - 1) / (int)b) +#endif + +static const uint32_t DEFAULT_TIMEOUT = 1000; +static const uint32_t DEFAULT_FLASH_TIMEOUT = 3000; // timeout for most flash operations +static const uint32_t LOAD_RAM_TIMEOUT_PER_MB = 2000000; // timeout (per megabyte) for erasing a region + +typedef enum { + SPI_FLASH_READ_ID = 0x9F +} spi_flash_cmd_t; + +static const target_registers_t *s_reg = NULL; +static target_chip_t s_target = ESP_UNKNOWN_CHIP; + +#if MD5_ENABLED + +static const uint32_t MD5_TIMEOUT_PER_MB = 800; +static struct MD5Context s_md5_context; +static uint32_t s_start_address; +static uint32_t s_image_size; + +static inline void init_md5(uint32_t address, uint32_t size) +{ + s_start_address = address; + s_image_size = size; + MD5Init(&s_md5_context); +} + +static inline void md5_update(const uint8_t *data, uint32_t size) +{ + MD5Update(&s_md5_context, data, size); +} + +static inline void md5_final(uint8_t digets[16]) +{ + MD5Final(digets, &s_md5_context); +} + +#else + +static inline void init_md5(uint32_t address, uint32_t size) { } +static inline void md5_update(const uint8_t *data, uint32_t size) { } +static inline void md5_final(uint8_t digets[16]) { } + +#endif + + +static uint32_t timeout_per_mb(uint32_t size_bytes, uint32_t time_per_mb) +{ + uint32_t timeout = time_per_mb * (size_bytes / 1e6); + return MAX(timeout, DEFAULT_FLASH_TIMEOUT); +} + +esp_loader_error_t esp_loader_connect(esp_loader_connect_args_t *connect_args) +{ + loader_port_enter_bootloader(); + + RETURN_ON_ERROR(loader_initialize_conn(connect_args)); + + RETURN_ON_ERROR(loader_detect_chip(&s_target, &s_reg)); + +#ifdef SERIAL_FLASHER_INTERFACE_UART + esp_loader_error_t err; + uint32_t spi_config; + if (s_target == ESP8266_CHIP) { + err = loader_flash_begin_cmd(0, 0, 0, 0, s_target); + } else { + RETURN_ON_ERROR( loader_read_spi_config(s_target, &spi_config) ); + loader_port_start_timer(DEFAULT_TIMEOUT); + err = loader_spi_attach_cmd(spi_config); + } + return err; +#endif /* SERIAL_FLASHER_INTERFACE_UART */ + return ESP_LOADER_SUCCESS; +} + +target_chip_t esp_loader_get_target(void) +{ + return s_target; +} + +#ifdef SERIAL_FLASHER_INTERFACE_UART +static uint32_t s_flash_write_size = 0; + +static esp_loader_error_t spi_set_data_lengths(size_t mosi_bits, size_t miso_bits) +{ + if (mosi_bits > 0) { + RETURN_ON_ERROR( esp_loader_write_register(s_reg->mosi_dlen, mosi_bits - 1) ); + } + if (miso_bits > 0) { + RETURN_ON_ERROR( esp_loader_write_register(s_reg->miso_dlen, miso_bits - 1) ); + } + + return ESP_LOADER_SUCCESS; +} + +static esp_loader_error_t spi_set_data_lengths_8266(size_t mosi_bits, size_t miso_bits) +{ + uint32_t mosi_mask = (mosi_bits == 0) ? 0 : mosi_bits - 1; + uint32_t miso_mask = (miso_bits == 0) ? 0 : miso_bits - 1; + return esp_loader_write_register(s_reg->usr1, (miso_mask << 8) | (mosi_mask << 17)); +} + +static esp_loader_error_t spi_flash_command(spi_flash_cmd_t cmd, void *data_tx, size_t tx_size, void *data_rx, size_t rx_size) +{ + assert(rx_size <= 32); // Reading more than 32 bits back from a SPI flash operation is unsupported + assert(tx_size <= 64); // Writing more than 64 bytes of data with one SPI command is unsupported + + uint32_t SPI_USR_CMD = (1 << 31); + uint32_t SPI_USR_MISO = (1 << 28); + uint32_t SPI_USR_MOSI = (1 << 27); + uint32_t SPI_CMD_USR = (1 << 18); + uint32_t CMD_LEN_SHIFT = 28; + + // Save SPI configuration + uint32_t old_spi_usr; + uint32_t old_spi_usr2; + RETURN_ON_ERROR( esp_loader_read_register(s_reg->usr, &old_spi_usr) ); + RETURN_ON_ERROR( esp_loader_read_register(s_reg->usr2, &old_spi_usr2) ); + + if (s_target == ESP8266_CHIP) { + RETURN_ON_ERROR( spi_set_data_lengths_8266(tx_size, rx_size) ); + } else { + RETURN_ON_ERROR( spi_set_data_lengths(tx_size, rx_size) ); + } + + uint32_t usr_reg_2 = (7 << CMD_LEN_SHIFT) | cmd; + uint32_t usr_reg = SPI_USR_CMD; + if (rx_size > 0) { + usr_reg |= SPI_USR_MISO; + } + if (tx_size > 0) { + usr_reg |= SPI_USR_MOSI; + } + + RETURN_ON_ERROR( esp_loader_write_register(s_reg->usr, usr_reg) ); + RETURN_ON_ERROR( esp_loader_write_register(s_reg->usr2, usr_reg_2 ) ); + + if (tx_size == 0) { + // clear data register before we read it + RETURN_ON_ERROR( esp_loader_write_register(s_reg->w0, 0) ); + } else { + uint32_t *data = (uint32_t *)data_tx; + uint32_t words_to_write = (tx_size + 31) / (8 * 4); + uint32_t data_reg_addr = s_reg->w0; + + while (words_to_write--) { + uint32_t word = *data++; + RETURN_ON_ERROR( esp_loader_write_register(data_reg_addr, word) ); + data_reg_addr += 4; + } + } + + RETURN_ON_ERROR( esp_loader_write_register(s_reg->cmd, SPI_CMD_USR) ); + + uint32_t trials = 10; + while (trials--) { + uint32_t cmd_reg; + RETURN_ON_ERROR( esp_loader_read_register(s_reg->cmd, &cmd_reg) ); + if ((cmd_reg & SPI_CMD_USR) == 0) { + break; + } + } + + if (trials == 0) { + return ESP_LOADER_ERROR_TIMEOUT; + } + + RETURN_ON_ERROR( esp_loader_read_register(s_reg->w0, data_rx) ); + + // Restore SPI configuration + RETURN_ON_ERROR( esp_loader_write_register(s_reg->usr, old_spi_usr) ); + RETURN_ON_ERROR( esp_loader_write_register(s_reg->usr2, old_spi_usr2) ); + + return ESP_LOADER_SUCCESS; +} + +static esp_loader_error_t detect_flash_size(size_t *flash_size) +{ + uint32_t flash_id = 0; + + RETURN_ON_ERROR( spi_flash_command(SPI_FLASH_READ_ID, NULL, 0, &flash_id, 24) ); + uint32_t size_id = flash_id >> 16; + + if (size_id < 0x12 || size_id > 0x18) { + return ESP_LOADER_ERROR_UNSUPPORTED_CHIP; + } + + *flash_size = 1 << size_id; + + return ESP_LOADER_SUCCESS; +} + +static uint32_t calc_erase_size(const target_chip_t target, const uint32_t offset, + const uint32_t image_size) +{ + if (target != ESP8266_CHIP) { + return image_size; + } else { + /* Needed to fix a bug in the ESP8266 ROM */ + const uint32_t sectors_per_block = 16U; + const uint32_t sector_size = 4096U; + + const uint32_t num_sectors = (image_size + sector_size - 1) / sector_size; + const uint32_t start_sector = offset / sector_size; + + uint32_t head_sectors = sectors_per_block - (start_sector % sectors_per_block); + + /* The ROM bug deletes extra num_sectors if we don't cross the block boundary + and extra head_sectors if we do */ + if (num_sectors <= head_sectors) { + return ((num_sectors + 1) / 2) * sector_size; + } else { + return (num_sectors - head_sectors) * sector_size; + } + } +} + +esp_loader_error_t esp_loader_flash_start(uint32_t offset, uint32_t image_size, uint32_t block_size) +{ + s_flash_write_size = block_size; + + size_t flash_size = 0; + if (detect_flash_size(&flash_size) == ESP_LOADER_SUCCESS) { + if (image_size > flash_size) { + return ESP_LOADER_ERROR_IMAGE_SIZE; + } + loader_port_start_timer(DEFAULT_TIMEOUT); + RETURN_ON_ERROR( loader_spi_parameters(flash_size) ); + } else { + loader_port_debug_print("Flash size detection failed, falling back to default"); + } + + init_md5(offset, image_size); + + bool encryption_in_cmd = encryption_in_begin_flash_cmd(s_target); + const uint32_t erase_size = calc_erase_size(esp_loader_get_target(), offset, image_size); + const uint32_t blocks_to_write = (image_size + block_size - 1) / block_size; + + const uint32_t erase_region_timeout_per_mb = 10000; + loader_port_start_timer(timeout_per_mb(erase_size, erase_region_timeout_per_mb)); + return loader_flash_begin_cmd(offset, erase_size, block_size, blocks_to_write, encryption_in_cmd); +} + + +esp_loader_error_t esp_loader_flash_write(void *payload, uint32_t size) +{ + uint32_t padding_bytes = s_flash_write_size - size; + uint8_t *data = (uint8_t *)payload; + uint32_t padding_index = size; + + if (size > s_flash_write_size) { + return ESP_LOADER_ERROR_INVALID_PARAM; + } + + const uint8_t padding_pattern = 0xFF; + while (padding_bytes--) { + data[padding_index++] = padding_pattern; + } + + md5_update(payload, (size + 3) & ~3); + + loader_port_start_timer(DEFAULT_TIMEOUT); + + return loader_flash_data_cmd(data, s_flash_write_size); +} + + +esp_loader_error_t esp_loader_flash_finish(bool reboot) +{ + loader_port_start_timer(DEFAULT_TIMEOUT); + + return loader_flash_end_cmd(!reboot); +} +#endif /* SERIAL_FLASHER_INTERFACE_UART */ + +esp_loader_error_t esp_loader_mem_start(uint32_t offset, uint32_t size, uint32_t block_size) +{ + uint32_t blocks_to_write = ROUNDUP(size, block_size); + loader_port_start_timer(timeout_per_mb(size, LOAD_RAM_TIMEOUT_PER_MB)); + return loader_mem_begin_cmd(offset, size, blocks_to_write, block_size); +} + + +esp_loader_error_t esp_loader_mem_write(const void *payload, uint32_t size) +{ + const uint8_t *data = (const uint8_t *)payload; + loader_port_start_timer(timeout_per_mb(size, LOAD_RAM_TIMEOUT_PER_MB)); + return loader_mem_data_cmd(data, size); +} + + +esp_loader_error_t esp_loader_mem_finish(uint32_t entrypoint) +{ + loader_port_start_timer(DEFAULT_TIMEOUT); + return loader_mem_end_cmd(entrypoint); +} + + +esp_loader_error_t esp_loader_read_register(uint32_t address, uint32_t *reg_value) +{ + loader_port_start_timer(DEFAULT_TIMEOUT); + + return loader_read_reg_cmd(address, reg_value); +} + + +esp_loader_error_t esp_loader_write_register(uint32_t address, uint32_t reg_value) +{ + loader_port_start_timer(DEFAULT_TIMEOUT); + + return loader_write_reg_cmd(address, reg_value, 0xFFFFFFFF, 0); +} + +esp_loader_error_t esp_loader_change_transmission_rate(uint32_t transmission_rate) +{ + if (s_target == ESP8266_CHIP) { + return ESP_LOADER_ERROR_UNSUPPORTED_FUNC; + } + + loader_port_start_timer(DEFAULT_TIMEOUT); + + return loader_change_baudrate_cmd(transmission_rate); +} + +#if MD5_ENABLED + +static void hexify(const uint8_t raw_md5[16], uint8_t hex_md5_out[32]) +{ + static const uint8_t dec_to_hex[] = { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' + }; + for (int i = 0; i < 16; i++) { + *hex_md5_out++ = dec_to_hex[raw_md5[i] >> 4]; + *hex_md5_out++ = dec_to_hex[raw_md5[i] & 0xF]; + } +} + + +esp_loader_error_t esp_loader_flash_verify(void) +{ + if (s_target == ESP8266_CHIP) { + return ESP_LOADER_ERROR_UNSUPPORTED_FUNC; + } + + uint8_t raw_md5[16] = {0}; + + /* Zero termination and new line character require 2 bytes */ + uint8_t hex_md5[MD5_SIZE + 2] = {0}; + uint8_t received_md5[MD5_SIZE + 2] = {0}; + + md5_final(raw_md5); + hexify(raw_md5, hex_md5); + + loader_port_start_timer(timeout_per_mb(s_image_size, MD5_TIMEOUT_PER_MB)); + + RETURN_ON_ERROR( loader_md5_cmd(s_start_address, s_image_size, received_md5) ); + + bool md5_match = memcmp(hex_md5, received_md5, MD5_SIZE) == 0; + + if (!md5_match) { + hex_md5[MD5_SIZE] = '\n'; + received_md5[MD5_SIZE] = '\n'; + + loader_port_debug_print("Error: MD5 checksum does not match:\n"); + loader_port_debug_print("Expected:\n"); + loader_port_debug_print((char *)received_md5); + loader_port_debug_print("Actual:\n"); + loader_port_debug_print((char *)hex_md5); + + return ESP_LOADER_ERROR_INVALID_MD5; + } + + return ESP_LOADER_SUCCESS; +} + +#endif + +void esp_loader_reset_target(void) +{ + loader_port_reset_target(); +} diff --git a/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/src/esp_targets.c b/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/src/esp_targets.c new file mode 100644 index 000000000..8764d2369 --- /dev/null +++ b/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/src/esp_targets.c @@ -0,0 +1,263 @@ +/* Copyright 2020 Espressif Systems (Shanghai) PTE LTD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "esp_targets.h" +#include + +#define MAX_MAGIC_VALUES 2 + +typedef esp_loader_error_t (*read_spi_config_t)(uint32_t efuse_base, uint32_t *spi_config); + +typedef struct { + target_registers_t regs; + uint32_t efuse_base; + uint32_t chip_magic_value[MAX_MAGIC_VALUES]; + read_spi_config_t read_spi_config; + bool encryption_in_begin_flash_cmd; +} esp_target_t; + +// This ROM address has a different value on each chip model +#define CHIP_DETECT_MAGIC_REG_ADDR 0x40001000 + +#define ESP8266_SPI_REG_BASE 0x60000200 +#define ESP32S2_SPI_REG_BASE 0x3f402000 +#define ESP32xx_SPI_REG_BASE 0x60002000 +#define ESP32_SPI_REG_BASE 0x3ff42000 + +static esp_loader_error_t spi_config_esp32(uint32_t efuse_base, uint32_t *spi_config); +static esp_loader_error_t spi_config_esp32xx(uint32_t efuse_base, uint32_t *spi_config); + +static const esp_target_t esp_target[ESP_MAX_CHIP] = { + + // ESP8266 + { + .regs = { + .cmd = ESP8266_SPI_REG_BASE + 0x00, + .usr = ESP8266_SPI_REG_BASE + 0x1c, + .usr1 = ESP8266_SPI_REG_BASE + 0x20, + .usr2 = ESP8266_SPI_REG_BASE + 0x24, + .w0 = ESP8266_SPI_REG_BASE + 0x40, + .mosi_dlen = 0, + .miso_dlen = 0, + }, + .efuse_base = 0, // Not used + .chip_magic_value = { 0xfff0c101, 0 }, + .read_spi_config = NULL, // Not used + }, + + // ESP32 + { + .regs = { + .cmd = ESP32_SPI_REG_BASE + 0x00, + .usr = ESP32_SPI_REG_BASE + 0x1c, + .usr1 = ESP32_SPI_REG_BASE + 0x20, + .usr2 = ESP32_SPI_REG_BASE + 0x24, + .w0 = ESP32_SPI_REG_BASE + 0x80, + .mosi_dlen = ESP32_SPI_REG_BASE + 0x28, + .miso_dlen = ESP32_SPI_REG_BASE + 0x2c, + }, + .efuse_base = 0x3ff5A000, + .chip_magic_value = { 0x00f01d83, 0 }, + .read_spi_config = spi_config_esp32, + }, + + // ESP32S2 + { + .regs = { + .cmd = ESP32S2_SPI_REG_BASE + 0x00, + .usr = ESP32S2_SPI_REG_BASE + 0x18, + .usr1 = ESP32S2_SPI_REG_BASE + 0x1c, + .usr2 = ESP32S2_SPI_REG_BASE + 0x20, + .w0 = ESP32S2_SPI_REG_BASE + 0x58, + .mosi_dlen = ESP32S2_SPI_REG_BASE + 0x24, + .miso_dlen = ESP32S2_SPI_REG_BASE + 0x28, + }, + .efuse_base = 0x3f41A000, + .chip_magic_value = { 0x000007c6, 0 }, + .read_spi_config = spi_config_esp32xx, + }, + + // ESP32C3 + { + .regs = { + .cmd = ESP32xx_SPI_REG_BASE + 0x00, + .usr = ESP32xx_SPI_REG_BASE + 0x18, + .usr1 = ESP32xx_SPI_REG_BASE + 0x1c, + .usr2 = ESP32xx_SPI_REG_BASE + 0x20, + .w0 = ESP32xx_SPI_REG_BASE + 0x58, + .mosi_dlen = ESP32xx_SPI_REG_BASE + 0x24, + .miso_dlen = ESP32xx_SPI_REG_BASE + 0x28, + }, + .efuse_base = 0x60008800, + .chip_magic_value = { 0x6921506f, 0x1b31506f }, + .read_spi_config = spi_config_esp32xx, + }, + + // ESP32S3 + { + .regs = { + .cmd = ESP32xx_SPI_REG_BASE + 0x00, + .usr = ESP32xx_SPI_REG_BASE + 0x18, + .usr1 = ESP32xx_SPI_REG_BASE + 0x1c, + .usr2 = ESP32xx_SPI_REG_BASE + 0x20, + .w0 = ESP32xx_SPI_REG_BASE + 0x58, + .mosi_dlen = ESP32xx_SPI_REG_BASE + 0x24, + .miso_dlen = ESP32xx_SPI_REG_BASE + 0x28, + }, + .efuse_base = 0x60007000, + .chip_magic_value = { 0x00000009, 0 }, + .read_spi_config = spi_config_esp32xx, + }, + + // ESP32C2 + { + .regs = { + .cmd = ESP32xx_SPI_REG_BASE + 0x00, + .usr = ESP32xx_SPI_REG_BASE + 0x18, + .usr1 = ESP32xx_SPI_REG_BASE + 0x1c, + .usr2 = ESP32xx_SPI_REG_BASE + 0x20, + .w0 = ESP32xx_SPI_REG_BASE + 0x58, + .mosi_dlen = ESP32xx_SPI_REG_BASE + 0x24, + .miso_dlen = ESP32xx_SPI_REG_BASE + 0x28, + }, + .efuse_base = 0x60008800, + .chip_magic_value = { 0x6f51306f, 0x7c41a06f }, + .read_spi_config = spi_config_esp32xx, + }, + // ESP32H4 + { + .regs = { + .cmd = ESP32xx_SPI_REG_BASE + 0x00, + .usr = ESP32xx_SPI_REG_BASE + 0x18, + .usr1 = ESP32xx_SPI_REG_BASE + 0x1c, + .usr2 = ESP32xx_SPI_REG_BASE + 0x20, + .w0 = ESP32xx_SPI_REG_BASE + 0x58, + .mosi_dlen = ESP32xx_SPI_REG_BASE + 0x24, + .miso_dlen = ESP32xx_SPI_REG_BASE + 0x28, + }, + .efuse_base = 0x6001A000, + .chip_magic_value = {0xca26cc22, 0x6881b06f}, // ESP32H4-BETA1, ESP32H4-BETA2 + .read_spi_config = spi_config_esp32xx, + }, + // ESP32H2 + { + .regs = { + .cmd = ESP32xx_SPI_REG_BASE + 0x00, + .usr = ESP32xx_SPI_REG_BASE + 0x18, + .usr1 = ESP32xx_SPI_REG_BASE + 0x1c, + .usr2 = ESP32xx_SPI_REG_BASE + 0x20, + .w0 = ESP32xx_SPI_REG_BASE + 0x58, + .mosi_dlen = ESP32xx_SPI_REG_BASE + 0x24, + .miso_dlen = ESP32xx_SPI_REG_BASE + 0x28, + }, + .efuse_base = 0x6001A000, + .chip_magic_value = {0xd7b73e80, 0}, + .read_spi_config = spi_config_esp32xx, + }, +}; + +const target_registers_t *get_esp_target_data(target_chip_t chip) +{ + return (const target_registers_t *)&esp_target[chip]; +} + +esp_loader_error_t loader_detect_chip(target_chip_t *target_chip, const target_registers_t **target_data) +{ + uint32_t magic_value; + RETURN_ON_ERROR( esp_loader_read_register(CHIP_DETECT_MAGIC_REG_ADDR, &magic_value) ); + + for (int chip = 0; chip < ESP_MAX_CHIP; chip++) { + for(int index = 0; index < MAX_MAGIC_VALUES; index++) { + if (magic_value == esp_target[chip].chip_magic_value[index]) { + *target_chip = (target_chip_t)chip; + *target_data = (target_registers_t *)&esp_target[chip]; + return ESP_LOADER_SUCCESS; + } + } + } + + return ESP_LOADER_ERROR_INVALID_TARGET; +} + +esp_loader_error_t loader_read_spi_config(target_chip_t target_chip, uint32_t *spi_config) +{ + const esp_target_t *target = &esp_target[target_chip]; + return target->read_spi_config(target->efuse_base, spi_config); +} + +static inline uint32_t efuse_word_addr(uint32_t efuse_base, uint32_t n) +{ + return efuse_base + (n * 4); +} + +// 30->GPIO32 | 31->GPIO33 +static inline uint8_t adjust_pin_number(uint8_t num) +{ + return (num >= 30) ? num + 2 : num; +} + + +static esp_loader_error_t spi_config_esp32(uint32_t efuse_base, uint32_t *spi_config) +{ + *spi_config = 0; + + uint32_t reg5, reg3; + RETURN_ON_ERROR( esp_loader_read_register(efuse_word_addr(efuse_base, 5), ®5) ); + RETURN_ON_ERROR( esp_loader_read_register(efuse_word_addr(efuse_base, 3), ®3) ); + + uint32_t pins = reg5 & 0xfffff; + + if (pins == 0 || pins == 0xfffff) { + return ESP_LOADER_SUCCESS; + } + + uint8_t clk = adjust_pin_number( (pins >> 0) & 0x1f ); + uint8_t q = adjust_pin_number( (pins >> 5) & 0x1f ); + uint8_t d = adjust_pin_number( (pins >> 10) & 0x1f ); + uint8_t cs = adjust_pin_number( (pins >> 15) & 0x1f ); + uint8_t hd = adjust_pin_number( (reg3 >> 4) & 0x1f ); + + if (clk == cs || clk == d || clk == q || q == cs || q == d || q == d) { + return ESP_LOADER_SUCCESS; + } + + *spi_config = (hd << 24) | (cs << 18) | (d << 12) | (q << 6) | clk; + + return ESP_LOADER_SUCCESS; +} + +// Applies for esp32s2, esp32c3 and esp32c3 +static esp_loader_error_t spi_config_esp32xx(uint32_t efuse_base, uint32_t *spi_config) +{ + *spi_config = 0; + + uint32_t reg1, reg2; + RETURN_ON_ERROR( esp_loader_read_register(efuse_word_addr(efuse_base, 18), ®1) ); + RETURN_ON_ERROR( esp_loader_read_register(efuse_word_addr(efuse_base, 19), ®2) ); + + uint32_t pins = ((reg1 >> 16) | ((reg2 & 0xfffff) << 16)) & 0x3fffffff; + + if (pins == 0 || pins == 0xffffffff) { + return ESP_LOADER_SUCCESS; + } + + *spi_config = pins; + return ESP_LOADER_SUCCESS; +} + +bool encryption_in_begin_flash_cmd(target_chip_t target) +{ + return target == ESP32_CHIP || target == ESP8266_CHIP; +} diff --git a/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/src/md5_hash.c b/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/src/md5_hash.c new file mode 100644 index 000000000..4da76bcb5 --- /dev/null +++ b/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/src/md5_hash.c @@ -0,0 +1,262 @@ +/* + * MD5 hash implementation and interface functions + * Copyright (c) 2003-2005, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + + +#include "md5_hash.h" +#include +#include + + +static void MD5Transform(uint32_t buf[4], uint32_t const in[16]); + + +/* ===== start - public domain MD5 implementation ===== */ +/* + * This code implements the MD5 message-digest algorithm. + * The algorithm is due to Ron Rivest. This code was + * written by Colin Plumb in 1993, no copyright is claimed. + * This code is in the public domain; do with it what you wish. + * + * Equivalent code is available from RSA Data Security, Inc. + * This code has been tested against that, and is equivalent, + * except that you don't need to include two pages of legalese + * with every copy. + * + * To compute the message digest of a chunk of bytes, declare an + * MD5Context structure, pass it to MD5Init, call MD5Update as + * needed on buffers full of bytes, and then call MD5Final, which + * will fill a supplied 16-byte array with the digest. + */ + +#ifndef WORDS_BIGENDIAN +#define byteReverse(buf, len) /* Nothing */ +#else +/* + * Note: this code is harmless on little-endian machines. + */ +static void byteReverse(unsigned char *buf, unsigned longs) +{ + uint32_t t; + do { + t = (uint32_t) ((unsigned) buf[3] << 8 | buf[2]) << 16 | + ((unsigned) buf[1] << 8 | buf[0]); + *(uint32_t *) buf = t; + buf += 4; + } while (--longs); +} +#endif + +/* + * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious + * initialization constants. + */ +void MD5Init(struct MD5Context *ctx) +{ + ctx->buf[0] = 0x67452301; + ctx->buf[1] = 0xefcdab89; + ctx->buf[2] = 0x98badcfe; + ctx->buf[3] = 0x10325476; + + ctx->bits[0] = 0; + ctx->bits[1] = 0; +} + +/* + * Update context to reflect the concatenation of another buffer full + * of bytes. + */ +void MD5Update(struct MD5Context *ctx, unsigned char const *buf, unsigned len) +{ + uint32_t t; + + /* Update bitcount */ + + t = ctx->bits[0]; + if ((ctx->bits[0] = t + ((uint32_t) len << 3)) < t) { + ctx->bits[1]++; /* Carry from low to high */ + } + ctx->bits[1] += len >> 29; + + t = (t >> 3) & 0x3f; /* Bytes already in shsInfo->data */ + + /* Handle any leading odd-sized chunks */ + + if (t) { + unsigned char *p = (unsigned char *) ctx->in + t; + + t = 64 - t; + if (len < t) { + memcpy(p, buf, len); + return; + } + memcpy(p, buf, t); + byteReverse(ctx->in, 16); + MD5Transform((uint32_t *)ctx->buf, (uint32_t *) ctx->in); + buf += t; + len -= t; + } + /* Process data in 64-byte chunks */ + + while (len >= 64) { + memcpy(ctx->in, buf, 64); + byteReverse(ctx->in, 16); + MD5Transform((uint32_t *)ctx->buf, (uint32_t *) ctx->in); + buf += 64; + len -= 64; + } + + /* Handle any remaining bytes of data. */ + + memcpy(ctx->in, buf, len); +} + +/* + * Final wrapup - pad to 64-byte boundary with the bit pattern + * 1 0* (64-bit count of bits processed, MSB-first) + */ +void MD5Final(unsigned char digest[16], struct MD5Context *ctx) +{ + unsigned count; + unsigned char *p; + + /* Compute number of bytes mod 64 */ + count = (ctx->bits[0] >> 3) & 0x3F; + + /* Set the first char of padding to 0x80. This is safe since there is + always at least one byte free */ + p = ctx->in + count; + *p++ = 0x80; + + /* Bytes of padding needed to make 64 bytes */ + count = 64 - 1 - count; + + /* Pad out to 56 mod 64 */ + if (count < 8) { + /* Two lots of padding: Pad the first block to 64 bytes */ + memset(p, 0, count); + byteReverse(ctx->in, 16); + MD5Transform((uint32_t *)ctx->buf, (uint32_t *) ctx->in); + + /* Now fill the next block with 56 bytes */ + memset(ctx->in, 0, 56); + } else { + /* Pad block to 56 bytes */ + memset(p, 0, count - 8); + } + byteReverse(ctx->in, 14); + + /* Append length in bits and transform */ + ((uint32_t *) ctx->in)[14] = ctx->bits[0]; + ((uint32_t *) ctx->in)[15] = ctx->bits[1]; + + MD5Transform((uint32_t *)ctx->buf, (uint32_t *) ctx->in); + byteReverse((unsigned char *) ctx->buf, 4); + memcpy(digest, ctx->buf, 16); + memset(ctx, 0, sizeof(struct MD5Context)); /* In case it's sensitive */ +} + +/* The four core functions - F1 is optimized somewhat */ + +/* #define F1(x, y, z) (x & y | ~x & z) */ +#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) (x ^ y ^ z) +#define F4(x, y, z) (y ^ (x | ~z)) + +/* This is the central step in the MD5 algorithm. */ +#define MD5STEP(f, w, x, y, z, data, s) \ + ( w += f(x, y, z) + data, w = w<>(32-s), w += x ) + +/* + * The core of the MD5 algorithm, this alters an existing MD5 hash to + * reflect the addition of 16 longwords of new data. MD5Update blocks + * the data and converts bytes into longwords for this routine. + */ +static void MD5Transform(uint32_t buf[4], uint32_t const in[16]) +{ + register uint32_t a, b, c, d; + + a = buf[0]; + b = buf[1]; + c = buf[2]; + d = buf[3]; + + MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7); + MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12); + MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17); + MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22); + MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7); + MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12); + MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17); + MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22); + MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7); + MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12); + MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17); + MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22); + MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7); + MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12); + MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17); + MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22); + + MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5); + MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9); + MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14); + MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20); + MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5); + MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9); + MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14); + MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20); + MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5); + MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9); + MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14); + MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20); + MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5); + MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9); + MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14); + MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20); + + MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4); + MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11); + MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16); + MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23); + MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4); + MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11); + MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16); + MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23); + MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4); + MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11); + MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16); + MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23); + MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4); + MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11); + MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16); + MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23); + + MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6); + MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10); + MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15); + MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21); + MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6); + MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10); + MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15); + MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21); + MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6); + MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10); + MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15); + MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21); + MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6); + MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10); + MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15); + MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21); + + buf[0] += a; + buf[1] += b; + buf[2] += c; + buf[3] += d; +} +/* ===== end - public domain MD5 implementation ===== */ diff --git a/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/src/protocol_common.c b/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/src/protocol_common.c new file mode 100644 index 000000000..8d1d9dd53 --- /dev/null +++ b/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/src/protocol_common.c @@ -0,0 +1,301 @@ +/* Copyright 2020-2023 Espressif Systems (Shanghai) CO LTD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "protocol.h" +#include "protocol_prv.h" +#include "esp_loader_io.h" +#include +#include + +#define CMD_SIZE(cmd) ( sizeof(cmd) - sizeof(command_common_t) ) + +static uint32_t s_sequence_number = 0; + +static uint8_t compute_checksum(const uint8_t *data, uint32_t size) +{ + uint8_t checksum = 0xEF; + + while (size--) { + checksum ^= *data++; + } + + return checksum; +} + +void log_loader_internal_error(error_code_t error) +{ + loader_port_debug_print("Error: "); + + switch (error) { + case INVALID_CRC: loader_port_debug_print("INVALID_CRC"); break; + case INVALID_COMMAND: loader_port_debug_print("INVALID_COMMAND"); break; + case COMMAND_FAILED: loader_port_debug_print("COMMAND_FAILED"); break; + case FLASH_WRITE_ERR: loader_port_debug_print("FLASH_WRITE_ERR"); break; + case FLASH_READ_ERR: loader_port_debug_print("FLASH_READ_ERR"); break; + case READ_LENGTH_ERR: loader_port_debug_print("READ_LENGTH_ERR"); break; + case DEFLATE_ERROR: loader_port_debug_print("DEFLATE_ERROR"); break; + default: loader_port_debug_print("UNKNOWN ERROR"); break; + } + + loader_port_debug_print("\n"); +} + + +esp_loader_error_t loader_flash_begin_cmd(uint32_t offset, + uint32_t erase_size, + uint32_t block_size, + uint32_t blocks_to_write, + bool encryption) +{ + uint32_t encryption_size = encryption ? sizeof(uint32_t) : 0; + + flash_begin_command_t flash_begin_cmd = { + .common = { + .direction = WRITE_DIRECTION, + .command = FLASH_BEGIN, + .size = CMD_SIZE(flash_begin_cmd) - encryption_size, + .checksum = 0 + }, + .erase_size = erase_size, + .packet_count = blocks_to_write, + .packet_size = block_size, + .offset = offset, + .encrypted = 0 + }; + + s_sequence_number = 0; + + return send_cmd(&flash_begin_cmd, sizeof(flash_begin_cmd) - encryption_size, NULL); +} + + +esp_loader_error_t loader_flash_data_cmd(const uint8_t *data, uint32_t size) +{ + data_command_t data_cmd = { + .common = { + .direction = WRITE_DIRECTION, + .command = FLASH_DATA, + .size = CMD_SIZE(data_cmd) + size, + .checksum = compute_checksum(data, size) + }, + .data_size = size, + .sequence_number = s_sequence_number++, + }; + + return send_cmd_with_data(&data_cmd, sizeof(data_cmd), data, size); +} + + +esp_loader_error_t loader_flash_end_cmd(bool stay_in_loader) +{ + flash_end_command_t end_cmd = { + .common = { + .direction = WRITE_DIRECTION, + .command = FLASH_END, + .size = CMD_SIZE(end_cmd), + .checksum = 0 + }, + .stay_in_loader = stay_in_loader + }; + + return send_cmd(&end_cmd, sizeof(end_cmd), NULL); +} + + +esp_loader_error_t loader_mem_begin_cmd(uint32_t offset, uint32_t size, uint32_t blocks_to_write, uint32_t block_size) +{ + + mem_begin_command_t mem_begin_cmd = { + .common = { + .direction = WRITE_DIRECTION, + .command = MEM_BEGIN, + .size = CMD_SIZE(mem_begin_cmd), + .checksum = 0 + }, + .total_size = size, + .blocks = blocks_to_write, + .block_size = block_size, + .offset = offset + }; + + s_sequence_number = 0; + + return send_cmd(&mem_begin_cmd, sizeof(mem_begin_cmd), NULL); +} + + +esp_loader_error_t loader_mem_data_cmd(const uint8_t *data, uint32_t size) +{ + data_command_t data_cmd = { + .common = { + .direction = WRITE_DIRECTION, + .command = MEM_DATA, + .size = CMD_SIZE(data_cmd) + size, + .checksum = compute_checksum(data, size) + }, + .data_size = size, + .sequence_number = s_sequence_number++, + }; + return send_cmd_with_data(&data_cmd, sizeof(data_cmd), data, size); +} + +esp_loader_error_t loader_mem_end_cmd(uint32_t entrypoint) +{ + mem_end_command_t end_cmd = { + .common = { + .direction = WRITE_DIRECTION, + .command = MEM_END, + .size = CMD_SIZE(end_cmd), + }, + .stay_in_loader = (entrypoint == 0), + .entry_point_address = entrypoint + }; + + return send_cmd(&end_cmd, sizeof(end_cmd), NULL); +} + + +esp_loader_error_t loader_sync_cmd(void) +{ + sync_command_t sync_cmd = { + .common = { + .direction = WRITE_DIRECTION, + .command = SYNC, + .size = CMD_SIZE(sync_cmd), + .checksum = 0 + }, + .sync_sequence = { + 0x07, 0x07, 0x12, 0x20, + 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, + 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, + 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, + 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, + } + }; + + return send_cmd(&sync_cmd, sizeof(sync_cmd), NULL); +} + + +esp_loader_error_t loader_write_reg_cmd(uint32_t address, uint32_t value, + uint32_t mask, uint32_t delay_us) +{ + write_reg_command_t write_cmd = { + .common = { + .direction = WRITE_DIRECTION, + .command = WRITE_REG, + .size = CMD_SIZE(write_cmd), + .checksum = 0 + }, + .address = address, + .value = value, + .mask = mask, + .delay_us = delay_us + }; + + return send_cmd(&write_cmd, sizeof(write_cmd), NULL); +} + + +esp_loader_error_t loader_read_reg_cmd(uint32_t address, uint32_t *reg) +{ + read_reg_command_t read_cmd = { + .common = { + .direction = WRITE_DIRECTION, + .command = READ_REG, + .size = CMD_SIZE(read_cmd), + .checksum = 0 + }, + .address = address, + }; + + return send_cmd(&read_cmd, sizeof(read_cmd), reg); +} + + +esp_loader_error_t loader_spi_attach_cmd(uint32_t config) +{ + spi_attach_command_t attach_cmd = { + .common = { + .direction = WRITE_DIRECTION, + .command = SPI_ATTACH, + .size = CMD_SIZE(attach_cmd), + .checksum = 0 + }, + .configuration = config, + .zero = 0 + }; + + return send_cmd(&attach_cmd, sizeof(attach_cmd), NULL); +} + +esp_loader_error_t loader_change_baudrate_cmd(uint32_t baudrate) +{ + change_baudrate_command_t baudrate_cmd = { + .common = { + .direction = WRITE_DIRECTION, + .command = CHANGE_BAUDRATE, + .size = CMD_SIZE(baudrate_cmd), + .checksum = 0 + }, + .new_baudrate = baudrate, + .old_baudrate = 0 // ESP32 ROM only + }; + + return send_cmd(&baudrate_cmd, sizeof(baudrate_cmd), NULL); +} + +esp_loader_error_t loader_md5_cmd(uint32_t address, uint32_t size, uint8_t *md5_out) +{ + spi_flash_md5_command_t md5_cmd = { + .common = { + .direction = WRITE_DIRECTION, + .command = SPI_FLASH_MD5, + .size = CMD_SIZE(md5_cmd), + .checksum = 0 + }, + .address = address, + .size = size, + .reserved_0 = 0, + .reserved_1 = 0 + }; + + return send_cmd_md5(&md5_cmd, sizeof(md5_cmd), md5_out); +} + +esp_loader_error_t loader_spi_parameters(uint32_t total_size) +{ + write_spi_command_t spi_cmd = { + .common = { + .direction = WRITE_DIRECTION, + .command = SPI_SET_PARAMS, + .size = 24, + .checksum = 0 + }, + .id = 0, + .total_size = total_size, + .block_size = 64 * 1024, + .sector_size = 4 * 1024, + .page_size = 0x100, + .status_mask = 0xFFFF, + }; + + return send_cmd(&spi_cmd, sizeof(spi_cmd), NULL); +} + +__attribute__ ((weak)) void loader_port_debug_print(const char *str) +{ + (void)(str); +} diff --git a/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/src/protocol_spi.c b/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/src/protocol_spi.c new file mode 100644 index 000000000..bd04fe79f --- /dev/null +++ b/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/src/protocol_spi.c @@ -0,0 +1,312 @@ +/* Copyright 2020-2023 Espressif Systems (Shanghai) CO LTD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "protocol.h" +#include "protocol_prv.h" +#include "esp_loader_io.h" +#include +#include + +typedef struct __attribute__((packed)) { + uint8_t cmd; + uint8_t addr; + uint8_t dummy; +} transaction_preamble_t; + +typedef enum { + TRANS_CMD_WRBUF = 0x01, + TRANS_CMD_RDBUF = 0x02, + TRANS_CMD_WRDMA = 0x03, + TRANS_CMD_RDDMA = 0x04, + TRANS_CMD_SEG_DONE = 0x05, + TRANS_CMD_ENQPI = 0x06, + TRANS_CMD_WR_DONE = 0x07, + TRANS_CMD_CMD8 = 0x08, + TRANS_CMD_CMD9 = 0x09, + TRANS_CMD_CMDA = 0x0A, + TRANS_CMD_EXQPI = 0xDD, +} transaction_cmd_t; + +/* Slave protocol registers */ +typedef enum { + SLAVE_REGISTER_VER = 0, + SLAVE_REGISTER_RXSTA = 4, + SLAVE_REGISTER_TXSTA = 8, + SLAVE_REGISTER_CMD = 12, +} slave_register_addr_t; + +#define SLAVE_STA_TOGGLE_BIT (0x01U << 0) +#define SLAVE_STA_INIT_BIT (0x01U << 1) +#define SLAVE_STA_BUF_LENGTH_POS 2U + +typedef enum { + SLAVE_STATE_INIT = SLAVE_STA_TOGGLE_BIT | SLAVE_STA_INIT_BIT, + SLAVE_STATE_FIRST_PACKET = SLAVE_STA_INIT_BIT, +} slave_state_t; + +typedef enum { + /* Target to host */ + SLAVE_CMD_IDLE = 0xAA, + SLAVE_CMD_READY = 0xA5, + /* Host to target */ + SLAVE_CMD_REBOOT = 0xFE, + SLAVE_CMD_COMM_REINIT = 0x5A, + SLAVE_CMD_DONE = 0x55, +} slave_cmd_t; + +static uint8_t s_slave_seq_tx; +static uint8_t s_slave_seq_rx; + +static esp_loader_error_t write_slave_reg(const uint8_t *data, const uint32_t addr, + const uint8_t size); +static esp_loader_error_t read_slave_reg(uint8_t *out_data, const uint32_t addr, + const uint8_t size); +static esp_loader_error_t handle_slave_state(const uint32_t status_reg_addr, uint8_t *seq_state, + bool *slave_ready, uint32_t *buf_size); +static esp_loader_error_t check_response(command_t cmd, uint32_t *reg_value); + +esp_loader_error_t loader_initialize_conn(esp_loader_connect_args_t *connect_args) +{ + for (uint8_t trial = 0; trial < connect_args->trials; trial++) { + uint8_t slave_ready_flag; + RETURN_ON_ERROR(read_slave_reg(&slave_ready_flag, SLAVE_REGISTER_CMD, + sizeof(slave_ready_flag))); + + if (slave_ready_flag != SLAVE_CMD_IDLE) { + loader_port_debug_print("Waiting for Slave to be idle...\n"); + loader_port_delay_ms(100); + } else { + break; + } + } + + const uint8_t reg_val = SLAVE_CMD_READY; + RETURN_ON_ERROR(write_slave_reg(®_val, SLAVE_REGISTER_CMD, sizeof(reg_val))); + + for (uint8_t trial = 0; trial < connect_args->trials; trial++) { + uint8_t slave_ready_flag; + RETURN_ON_ERROR(read_slave_reg(&slave_ready_flag, SLAVE_REGISTER_CMD, + sizeof(slave_ready_flag))); + + if (slave_ready_flag != SLAVE_CMD_READY) { + loader_port_debug_print("Waiting for Slave to be ready...\n"); + loader_port_delay_ms(100); + } else { + break; + } + } + + return ESP_LOADER_SUCCESS; +} + + +esp_loader_error_t send_cmd(const void *cmd_data, uint32_t size, uint32_t *reg_value) +{ + command_t command = ((const command_common_t *)cmd_data)->command; + + uint32_t buf_size; + bool slave_ready = false; + while (!slave_ready) { + RETURN_ON_ERROR(handle_slave_state(SLAVE_REGISTER_RXSTA, &s_slave_seq_rx, &slave_ready, + &buf_size)); + } + + if (size > buf_size) { + return ESP_LOADER_ERROR_INVALID_PARAM; + } + + /* Start and write the command */ + transaction_preamble_t preamble = {.cmd = TRANS_CMD_WRDMA}; + + loader_port_spi_set_cs(0); + RETURN_ON_ERROR(loader_port_write((const uint8_t *)&preamble, sizeof(preamble), + loader_port_remaining_time())); + RETURN_ON_ERROR(loader_port_write((const uint8_t *)cmd_data, size, + loader_port_remaining_time())); + loader_port_spi_set_cs(1); + + /* Terminate the write */ + loader_port_spi_set_cs(0); + preamble.cmd = TRANS_CMD_WR_DONE; + RETURN_ON_ERROR(loader_port_write((const uint8_t *)&preamble, sizeof(preamble), + loader_port_remaining_time())); + loader_port_spi_set_cs(1); + + return check_response(command, reg_value); +} + + +esp_loader_error_t send_cmd_with_data(const void *cmd_data, size_t cmd_size, + const void *data, size_t data_size) +{ + uint32_t buf_size; + bool slave_ready = false; + while (!slave_ready) { + RETURN_ON_ERROR(handle_slave_state(SLAVE_REGISTER_RXSTA, &s_slave_seq_rx, &slave_ready, + &buf_size)); + } + + if (cmd_size + data_size > buf_size) { + return ESP_LOADER_ERROR_INVALID_PARAM; + } + + /* Start and write the command and the data */ + transaction_preamble_t preamble = {.cmd = TRANS_CMD_WRDMA}; + + loader_port_spi_set_cs(0); + RETURN_ON_ERROR(loader_port_write((const uint8_t *)&preamble, sizeof(preamble), + loader_port_remaining_time())); + RETURN_ON_ERROR(loader_port_write((const uint8_t *)cmd_data, cmd_size, + loader_port_remaining_time())); + RETURN_ON_ERROR(loader_port_write((const uint8_t *)data, data_size, + loader_port_remaining_time())); + loader_port_spi_set_cs(1); + + /* Terminate the write */ + loader_port_spi_set_cs(0); + preamble.cmd = TRANS_CMD_WR_DONE; + RETURN_ON_ERROR(loader_port_write((const uint8_t *)&preamble, sizeof(preamble), + loader_port_remaining_time())); + loader_port_spi_set_cs(1); + + command_t command = ((const command_common_t *)cmd_data)->command; + return check_response(command, NULL); +} + + +static esp_loader_error_t read_slave_reg(uint8_t *out_data, const uint32_t addr, + const uint8_t size) +{ + transaction_preamble_t preamble = { + .cmd = TRANS_CMD_RDBUF, + .addr = addr, + }; + + loader_port_spi_set_cs(0); + RETURN_ON_ERROR(loader_port_write((const uint8_t *)&preamble, sizeof(preamble), + loader_port_remaining_time())); + RETURN_ON_ERROR(loader_port_read(out_data, size, loader_port_remaining_time())); + loader_port_spi_set_cs(1); + + return ESP_LOADER_SUCCESS; +} + + +static esp_loader_error_t write_slave_reg(const uint8_t *data, const uint32_t addr, + const uint8_t size) +{ + transaction_preamble_t preamble = { + .cmd = TRANS_CMD_WRBUF, + .addr = addr, + }; + + loader_port_spi_set_cs(0); + RETURN_ON_ERROR(loader_port_write((const uint8_t *)&preamble, sizeof(preamble), + loader_port_remaining_time())); + RETURN_ON_ERROR(loader_port_write(data, size, loader_port_remaining_time())); + loader_port_spi_set_cs(1); + + return ESP_LOADER_SUCCESS; +} + + +static esp_loader_error_t handle_slave_state(const uint32_t status_reg_addr, uint8_t *seq_state, + bool *slave_ready, uint32_t *buf_size) +{ + uint32_t status_reg; + RETURN_ON_ERROR(read_slave_reg((uint8_t *)&status_reg, status_reg_addr, + sizeof(status_reg))); + const slave_state_t state = status_reg & (SLAVE_STA_TOGGLE_BIT | SLAVE_STA_INIT_BIT); + + switch(state) { + case SLAVE_STATE_INIT: { + const uint32_t initial = 0U; + RETURN_ON_ERROR(write_slave_reg((uint8_t *)&initial, status_reg_addr, sizeof(initial))); + break; + } + + case SLAVE_STATE_FIRST_PACKET: { + *seq_state = state & SLAVE_STA_TOGGLE_BIT; + *buf_size = status_reg >> SLAVE_STA_BUF_LENGTH_POS; + *slave_ready = true; + break; + } + + default: { + const uint8_t new_seq = state & SLAVE_STA_TOGGLE_BIT; + if (new_seq != *seq_state) { + *seq_state = new_seq; + *buf_size = status_reg >> SLAVE_STA_BUF_LENGTH_POS; + *slave_ready = true; + } + break; + } + } + + return ESP_LOADER_SUCCESS; +} + + +static esp_loader_error_t check_response(command_t cmd, uint32_t *reg_value) +{ + response_t resp; + + uint32_t buf_size; + bool slave_ready = false; + while (!slave_ready) { + RETURN_ON_ERROR(handle_slave_state(SLAVE_REGISTER_TXSTA, &s_slave_seq_tx, &slave_ready, + &buf_size)); + } + + if (sizeof(resp) > buf_size) { + return ESP_LOADER_ERROR_INVALID_PARAM; + } + + transaction_preamble_t preamble = { + .cmd = TRANS_CMD_RDDMA, + }; + + loader_port_spi_set_cs(0); + RETURN_ON_ERROR(loader_port_write((const uint8_t *)&preamble, sizeof(preamble), + loader_port_remaining_time())); + RETURN_ON_ERROR(loader_port_read((uint8_t *)&resp, sizeof(resp), + loader_port_remaining_time())); + loader_port_spi_set_cs(1); + + /* Terminate the read */ + loader_port_spi_set_cs(0); + preamble.cmd = TRANS_CMD_CMD8; + RETURN_ON_ERROR(loader_port_write((const uint8_t *)&preamble, sizeof(preamble), + loader_port_remaining_time())); + loader_port_spi_set_cs(1); + + common_response_t *common = (common_response_t *)&resp; + if ((common->direction != READ_DIRECTION) || (common->command != cmd)) { + return ESP_LOADER_ERROR_INVALID_RESPONSE; + } + + response_status_t *status = + (response_status_t *)((uint8_t *)&resp + sizeof(resp) - sizeof(response_status_t)); + if (status->failed) { + log_loader_internal_error(status->error); + return ESP_LOADER_ERROR_INVALID_RESPONSE; + } + + if (reg_value != NULL) { + *reg_value = common->value; + } + + return ESP_LOADER_SUCCESS; +} diff --git a/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/src/protocol_uart.c b/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/src/protocol_uart.c new file mode 100644 index 000000000..c5a51cec6 --- /dev/null +++ b/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/src/protocol_uart.c @@ -0,0 +1,114 @@ +/* Copyright 2020-2023 Espressif Systems (Shanghai) CO LTD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "protocol.h" +#include "protocol_prv.h" +#include "esp_loader_io.h" +#include "slip.h" +#include +#include + +static esp_loader_error_t check_response(command_t cmd, uint32_t *reg_value, void* resp, uint32_t resp_size); + +esp_loader_error_t loader_initialize_conn(esp_loader_connect_args_t *connect_args) { + esp_loader_error_t err; + int32_t trials = connect_args->trials; + + do { + loader_port_start_timer(connect_args->sync_timeout); + err = loader_sync_cmd(); + if (err == ESP_LOADER_ERROR_TIMEOUT) { + if (--trials == 0) { + return ESP_LOADER_ERROR_TIMEOUT; + } + loader_port_delay_ms(100); + } else if (err != ESP_LOADER_SUCCESS) { + return err; + } + } while (err != ESP_LOADER_SUCCESS); + + return err; +} + +esp_loader_error_t send_cmd(const void *cmd_data, uint32_t size, uint32_t *reg_value) +{ + response_t response; + command_t command = ((const command_common_t *)cmd_data)->command; + + RETURN_ON_ERROR( SLIP_send_delimiter() ); + RETURN_ON_ERROR( SLIP_send((const uint8_t *)cmd_data, size) ); + RETURN_ON_ERROR( SLIP_send_delimiter() ); + + return check_response(command, reg_value, &response, sizeof(response)); +} + + +esp_loader_error_t send_cmd_with_data(const void *cmd_data, size_t cmd_size, + const void *data, size_t data_size) +{ + response_t response; + command_t command = ((const command_common_t *)cmd_data)->command; + + RETURN_ON_ERROR( SLIP_send_delimiter() ); + RETURN_ON_ERROR( SLIP_send((const uint8_t *)cmd_data, cmd_size) ); + RETURN_ON_ERROR( SLIP_send(data, data_size) ); + RETURN_ON_ERROR( SLIP_send_delimiter() ); + + return check_response(command, NULL, &response, sizeof(response)); +} + + +esp_loader_error_t send_cmd_md5(const void *cmd_data, size_t cmd_size, uint8_t md5_out[MD5_SIZE]) +{ + rom_md5_response_t response; + command_t command = ((const command_common_t *)cmd_data)->command; + + RETURN_ON_ERROR( SLIP_send_delimiter() ); + RETURN_ON_ERROR( SLIP_send((const uint8_t *)cmd_data, cmd_size) ); + RETURN_ON_ERROR( SLIP_send_delimiter() ); + + RETURN_ON_ERROR( check_response(command, NULL, &response, sizeof(response)) ); + + memcpy(md5_out, response.md5, MD5_SIZE); + + return ESP_LOADER_SUCCESS; +} + + +static esp_loader_error_t check_response(command_t cmd, uint32_t *reg_value, void* resp, uint32_t resp_size) +{ + esp_loader_error_t err; + common_response_t *response = (common_response_t *)resp; + + do { + err = SLIP_receive_packet(resp, resp_size); + if (err != ESP_LOADER_SUCCESS) { + return err; + } + } while ((response->direction != READ_DIRECTION) || (response->command != cmd)); + + response_status_t *status = (response_status_t *)((uint8_t *)resp + resp_size - sizeof(response_status_t)); + + if (status->failed) { + log_loader_internal_error(status->error); + return ESP_LOADER_ERROR_INVALID_RESPONSE; + } + + if (reg_value != NULL) { + *reg_value = response->value; + } + + return ESP_LOADER_SUCCESS; +} diff --git a/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/src/slip.c b/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/src/slip.c new file mode 100644 index 000000000..ba926169c --- /dev/null +++ b/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/src/slip.c @@ -0,0 +1,125 @@ +/* Copyright 2020-2023 Espressif Systems (Shanghai) CO LTD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "slip.h" +#include "esp_loader_io.h" + +static const uint8_t DELIMITER = 0xC0; +static const uint8_t C0_REPLACEMENT[2] = {0xDB, 0xDC}; +static const uint8_t DB_REPLACEMENT[2] = {0xDB, 0xDD}; + +static inline esp_loader_error_t peripheral_read(uint8_t *buff, const size_t size) +{ + return loader_port_read(buff, size, loader_port_remaining_time()); +} + +static inline esp_loader_error_t peripheral_write(const uint8_t *buff, const size_t size) +{ + return loader_port_write(buff, size, loader_port_remaining_time()); +} + +esp_loader_error_t SLIP_receive_data(uint8_t *buff, const size_t size) +{ + uint8_t ch; + + for (uint32_t i = 0; i < size; i++) { + RETURN_ON_ERROR( peripheral_read(&ch, 1) ); + + if (ch == 0xDB) { + RETURN_ON_ERROR( peripheral_read(&ch, 1) ); + if (ch == 0xDC) { + buff[i] = 0xC0; + } else if (ch == 0xDD) { + buff[i] = 0xDB; + } else { + return ESP_LOADER_ERROR_INVALID_RESPONSE; + } + } else { + buff[i] = ch; + } + } + + return ESP_LOADER_SUCCESS; +} + + +esp_loader_error_t SLIP_receive_packet(uint8_t *buff, const size_t size) +{ + uint8_t ch; + + // Wait for delimiter + do { + RETURN_ON_ERROR( peripheral_read(&ch, 1) ); + } while (ch != DELIMITER); + + // Workaround: bootloader sends two dummy(0xC0) bytes after response when baud rate is changed. + do { + RETURN_ON_ERROR( peripheral_read(&ch, 1) ); + } while (ch == DELIMITER); + + buff[0] = ch; + + RETURN_ON_ERROR( SLIP_receive_data(&buff[1], size - 1) ); + + // Wait for delimiter + do { + RETURN_ON_ERROR( peripheral_read(&ch, 1) ); + } while (ch != DELIMITER); + + return ESP_LOADER_SUCCESS; +} + + +esp_loader_error_t SLIP_send(const uint8_t *data, const size_t size) +{ + uint32_t to_write = 0; // Bytes ready to write as they are + uint32_t written = 0; // Bytes already written + + for (uint32_t i = 0; i < size; i++) { + if (data[i] != 0xC0 && data[i] != 0xDB) { + to_write++; // Queue this byte for writing + continue; + } + + // We have a byte that needs encoding, write the queue first + if (to_write > 0) { + RETURN_ON_ERROR( peripheral_write(&data[written], to_write) ); + } + + // Write the encoded byte + if (data[i] == 0xC0) { + RETURN_ON_ERROR( peripheral_write(C0_REPLACEMENT, 2) ); + } else { + RETURN_ON_ERROR( peripheral_write(DB_REPLACEMENT, 2) ); + } + + // Update to start again after the encoded byte + written = i + 1; + to_write = 0; + } + + // Write the rest of the bytes that didn't need encoding + if (to_write > 0) { + RETURN_ON_ERROR( peripheral_write(&data[written], to_write) ); + } + + return ESP_LOADER_SUCCESS; +} + + +esp_loader_error_t SLIP_send_delimiter(void) +{ + return peripheral_write(&DELIMITER, 1); +} diff --git a/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/zephyr/CMakeLists.txt b/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/zephyr/CMakeLists.txt new file mode 100644 index 000000000..97da4eaae --- /dev/null +++ b/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/zephyr/CMakeLists.txt @@ -0,0 +1,30 @@ +cmake_minimum_required(VERSION 3.5) + +if (CONFIG_ESP_SERIAL_FLASHER) + zephyr_include_directories( + "${ZEPHYR_CURRENT_MODULE_DIR}/include" + "${ZEPHYR_CURRENT_MODULE_DIR}/port" + "${ZEPHYR_CURRENT_MODULE_DIR}/private_include" + ) + + zephyr_interface_library_named(esp_flasher) + + zephyr_library() + + zephyr_library_sources(${ZEPHYR_CURRENT_MODULE_DIR}/src/esp_loader.c + ${ZEPHYR_CURRENT_MODULE_DIR}/src/esp_targets.c + ${ZEPHYR_CURRENT_MODULE_DIR}/src/protocol_common.c + ${ZEPHYR_CURRENT_MODULE_DIR}/src/protocol_uart.c + ${ZEPHYR_CURRENT_MODULE_DIR}/src/slip.c + ${ZEPHYR_CURRENT_MODULE_DIR}/src/md5_hash.c + ${ZEPHYR_CURRENT_MODULE_DIR}/port/zephyr_port.c + ) + + target_compile_definitions(esp_flasher INTERFACE SERIAL_FLASHER_INTERFACE_UART) + + zephyr_library_link_libraries(esp_flasher) + + if(DEFINED MD5_ENABLED OR CONFIG_SERIAL_FLASHER_MD5_ENABLED) + target_compile_definitions(esp_flasher INTERFACE -DMD5_ENABLED=1) + endif() +endif() diff --git a/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/zephyr/Kconfig b/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/zephyr/Kconfig new file mode 100644 index 000000000..ebb524c86 --- /dev/null +++ b/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/zephyr/Kconfig @@ -0,0 +1,16 @@ +config ESP_SERIAL_FLASHER + bool "Enable ESP serial flasher library" + default y + select CONSOLE_SUBSYS + help + Select this option to enable the ESP serial flasher library. + +config ESP_SERIAL_FLASHER_UART_BUFSIZE + int "ESP Serial Flasher UART buffer size" + default 512 + help + Buffer size for UART TX and RX packets + +if ESP_SERIAL_FLASHER + rsource "../Kconfig" +endif diff --git a/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/zephyr/module.yml b/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/zephyr/module.yml new file mode 100644 index 000000000..01d484fcb --- /dev/null +++ b/applications/external/wifi_marauder_companion/lib/esp-serial-flasher/zephyr/module.yml @@ -0,0 +1,5 @@ +name: esp-flasher + +build: + cmake: zephyr + kconfig: zephyr/Kconfig diff --git a/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_config.h b/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_config.h index d223af79a..402fca479 100644 --- a/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_config.h +++ b/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_config.h @@ -13,3 +13,4 @@ ADD_SCENE(wifi_marauder, script_stage_edit, ScriptStageEdit) ADD_SCENE(wifi_marauder, script_stage_add, ScriptStageAdd) ADD_SCENE(wifi_marauder, script_stage_edit_list, ScriptStageEditList) ADD_SCENE(wifi_marauder, sniffpmkid_options, SniffPmkidOptions) +ADD_SCENE(wifi_marauder, flasher, Flasher) diff --git a/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_console_output.c b/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_console_output.c index 05d94fe80..236fe4eff 100644 --- a/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_console_output.c +++ b/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_console_output.c @@ -1,5 +1,7 @@ #include "../wifi_marauder_app_i.h" +#include "../wifi_marauder_flasher.h" + char* _wifi_marauder_get_prefix_from_cmd(const char* command) { int end = strcspn(command, " "); char* prefix = (char*)malloc(sizeof(char) * (end + 1)); @@ -101,13 +103,24 @@ void wifi_marauder_scene_console_output_on_enter(void* context) { view_dispatcher_switch_to_view(app->view_dispatcher, WifiMarauderAppViewConsoleOutput); // Register callbacks to receive data - wifi_marauder_uart_set_handle_rx_data_cb( - app->uart, - wifi_marauder_console_output_handle_rx_data_cb); // setup callback for general log rx thread + // setup callback for general log rx thread + if(app->flash_mode) { + wifi_marauder_uart_set_handle_rx_data_cb( + app->uart, + wifi_marauder_flash_handle_rx_data_cb); // setup callback for general log rx thread + } else { + wifi_marauder_uart_set_handle_rx_data_cb( + app->uart, + wifi_marauder_console_output_handle_rx_data_cb); // setup callback for general log rx thread + } wifi_marauder_uart_set_handle_rx_data_cb( app->lp_uart, wifi_marauder_console_output_handle_rx_packets_cb); // setup callback for packets rx thread + if(app->flash_mode) { + wifi_marauder_flash_start_thread(app); + } + // Get ready to send command if((app->is_command && app->selected_tx_string) || app->script) { const char* prefix = @@ -183,6 +196,10 @@ void wifi_marauder_scene_console_output_on_exit(void* context) { furi_delay_ms(50); } + if(app->flash_mode) { + wifi_marauder_flash_stop_thread(app); + } + // Unregister rx callback wifi_marauder_uart_set_handle_rx_data_cb(app->uart, NULL); wifi_marauder_uart_set_handle_rx_data_cb(app->lp_uart, NULL); diff --git a/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_flasher.c b/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_flasher.c new file mode 100644 index 000000000..79682879d --- /dev/null +++ b/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_flasher.c @@ -0,0 +1,92 @@ +#include "../wifi_marauder_app_i.h" + +enum SubmenuIndex { + SubmenuIndexBoot, + SubmenuIndexPart, + SubmenuIndexApp, + SubmenuIndexFlash, +}; + +static void wifi_marauder_scene_flasher_callback(void* context, uint32_t index) { + WifiMarauderApp* app = context; + + scene_manager_set_scene_state(app->scene_manager, WifiMarauderSceneFlasher, index); + + // browse for files + FuriString* predefined_filepath = furi_string_alloc_set_str(MARAUDER_APP_FOLDER); + FuriString* selected_filepath = furi_string_alloc(); + DialogsFileBrowserOptions browser_options; + dialog_file_browser_set_basic_options(&browser_options, ".bin", &I_Text_10x10); + + // TODO refactor + switch(index) { + case SubmenuIndexBoot: + if(dialog_file_browser_show( + app->dialogs, selected_filepath, predefined_filepath, &browser_options)) { + strncpy( + app->bin_file_path_boot, + furi_string_get_cstr(selected_filepath), + sizeof(app->bin_file_path_boot)); + } + break; + case SubmenuIndexPart: + if(dialog_file_browser_show( + app->dialogs, selected_filepath, predefined_filepath, &browser_options)) { + strncpy( + app->bin_file_path_part, + furi_string_get_cstr(selected_filepath), + sizeof(app->bin_file_path_part)); + } + break; + case SubmenuIndexApp: + if(dialog_file_browser_show( + app->dialogs, selected_filepath, predefined_filepath, &browser_options)) { + strncpy( + app->bin_file_path_app, + furi_string_get_cstr(selected_filepath), + sizeof(app->bin_file_path_app)); + } + break; + case SubmenuIndexFlash: + // TODO error checking + scene_manager_next_scene(app->scene_manager, WifiMarauderSceneConsoleOutput); + break; + } + + furi_string_free(selected_filepath); + furi_string_free(predefined_filepath); +} + +void wifi_marauder_scene_flasher_on_enter(void* context) { + WifiMarauderApp* app = context; + + Submenu* submenu = app->submenu; + + submenu_set_header(submenu, "Browse for files to flash"); + submenu_add_item( + submenu, "Bootloader", SubmenuIndexBoot, wifi_marauder_scene_flasher_callback, app); + submenu_add_item( + submenu, "Partition Table", SubmenuIndexPart, wifi_marauder_scene_flasher_callback, app); + submenu_add_item( + submenu, "Application", SubmenuIndexApp, wifi_marauder_scene_flasher_callback, app); + submenu_add_item( + submenu, "[>] FLASH", SubmenuIndexFlash, wifi_marauder_scene_flasher_callback, app); + + submenu_set_selected_item( + submenu, scene_manager_get_scene_state(app->scene_manager, WifiMarauderSceneFlasher)); + view_dispatcher_switch_to_view(app->view_dispatcher, WifiMarauderAppViewSubmenu); +} + +bool wifi_marauder_scene_flasher_on_event(void* context, SceneManagerEvent event) { + //WifiMarauderApp* app = context; + UNUSED(context); + UNUSED(event); + bool consumed = false; + + return consumed; +} + +void wifi_marauder_scene_flasher_on_exit(void* context) { + WifiMarauderApp* app = context; + submenu_reset(app->submenu); +} diff --git a/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_start.c b/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_start.c index b2bd39d3e..06dcd7fd7 100644 --- a/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_start.c +++ b/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_start.c @@ -121,6 +121,7 @@ const WifiMarauderItem items[NUM_MENU_ITEMS] = { {"Update", {"ota", "sd"}, 2, {"update -w", "update -s"}, NO_ARGS, FOCUS_CONSOLE_END, NO_TIP}, {"Reboot", {""}, 1, {"reboot"}, NO_ARGS, FOCUS_CONSOLE_END, NO_TIP}, {"Help", {""}, 1, {"help"}, NO_ARGS, FOCUS_CONSOLE_START, SHOW_STOPSCAN_TIP}, + {"Reflash ESP32 (WIP)", {""}, 1, {""}, NO_ARGS, FOCUS_CONSOLE_END, NO_TIP}, {"Scripts", {""}, 1, {""}, NO_ARGS, FOCUS_CONSOLE_END, NO_TIP}, {"Save to flipper sdcard", // keep as last entry or change logic in callback below {""}, @@ -149,6 +150,17 @@ static void wifi_marauder_scene_start_var_list_enter_callback(void* context, uin item->focus_console; app->show_stopscan_tip = item->show_stopscan_tip; + // TODO cleanup + if(index == NUM_MENU_ITEMS - 3) { + // flasher + app->is_command = false; + app->flash_mode = true; + view_dispatcher_send_custom_event(app->view_dispatcher, WifiMarauderEventStartFlasher); + return; + } + + app->flash_mode = false; + if(!app->is_command && selected_option_index == 0) { // View Log from start view_dispatcher_send_custom_event(app->view_dispatcher, WifiMarauderEventStartLogViewer); @@ -260,6 +272,10 @@ bool wifi_marauder_scene_start_on_event(void* context, SceneManagerEvent event) scene_manager_set_scene_state( app->scene_manager, WifiMarauderSceneStart, app->selected_menu_index); scene_manager_next_scene(app->scene_manager, WifiMarauderSceneSniffPmkidOptions); + } else if(event.event == WifiMarauderEventStartFlasher) { + scene_manager_set_scene_state( + app->scene_manager, WifiMarauderSceneStart, app->selected_menu_index); + scene_manager_next_scene(app->scene_manager, WifiMarauderSceneFlasher); } consumed = true; } else if(event.type == SceneManagerEventTypeTick) { diff --git a/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_text_input.c b/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_text_input.c index e6091a410..66a508f66 100644 --- a/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_text_input.c +++ b/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_text_input.c @@ -46,7 +46,9 @@ void wifi_marauder_scene_text_input_on_enter(void* context) { // Setup view WIFI_TextInput* text_input = app->text_input; // Add help message to header - if(app->special_case_input_step == 1) { + if(app->flash_mode) { + wifi_text_input_set_header_text(text_input, "Enter destination address"); + } else if(app->special_case_input_step == 1) { wifi_text_input_set_header_text(text_input, "Enter source MAC"); } else if(0 == strncmp("ssid -a -g", app->selected_tx_string, strlen("ssid -a -g"))) { wifi_text_input_set_header_text(text_input, "Enter # SSIDs to generate"); diff --git a/applications/external/wifi_marauder_companion/wifi_marauder_app.c b/applications/external/wifi_marauder_companion/wifi_marauder_app.c index 97b1d9715..91fcb2372 100644 --- a/applications/external/wifi_marauder_companion/wifi_marauder_app.c +++ b/applications/external/wifi_marauder_companion/wifi_marauder_app.c @@ -86,6 +86,8 @@ WifiMarauderApp* wifi_marauder_app_alloc() { view_dispatcher_add_view( app->view_dispatcher, WifiMarauderAppViewSubmenu, submenu_get_view(app->submenu)); + app->flash_mode = false; + scene_manager_next_scene(app->scene_manager, WifiMarauderSceneStart); return app; diff --git a/applications/external/wifi_marauder_companion/wifi_marauder_app.h b/applications/external/wifi_marauder_companion/wifi_marauder_app.h index 3a342bbba..b6664fdab 100644 --- a/applications/external/wifi_marauder_companion/wifi_marauder_app.h +++ b/applications/external/wifi_marauder_companion/wifi_marauder_app.h @@ -4,7 +4,7 @@ extern "C" { #endif -#define WIFI_MARAUDER_APP_VERSION "v0.4.0" +#define WIFI_MARAUDER_APP_VERSION "v0.5.0" typedef struct WifiMarauderApp WifiMarauderApp; diff --git a/applications/external/wifi_marauder_companion/wifi_marauder_app_i.h b/applications/external/wifi_marauder_companion/wifi_marauder_app_i.h index d6a0d37c7..cd248648a 100644 --- a/applications/external/wifi_marauder_companion/wifi_marauder_app_i.h +++ b/applications/external/wifi_marauder_companion/wifi_marauder_app_i.h @@ -26,7 +26,7 @@ #include #include -#define NUM_MENU_ITEMS (18) +#define NUM_MENU_ITEMS (19) #define WIFI_MARAUDER_TEXT_BOX_STORE_SIZE (4096) #define WIFI_MARAUDER_TEXT_INPUT_STORE_SIZE (512) @@ -113,6 +113,13 @@ struct WifiMarauderApp { int special_case_input_step; char special_case_input_src_addr[20]; char special_case_input_dst_addr[20]; + + // For flashing - TODO: put into its own struct? + char bin_file_path_boot[100]; + char bin_file_path_part[100]; + char bin_file_path_app[100]; + FuriThread* flash_worker; + bool flash_mode; }; // Supported commands: diff --git a/applications/external/wifi_marauder_companion/wifi_marauder_custom_event.h b/applications/external/wifi_marauder_companion/wifi_marauder_custom_event.h index b6d9f8274..8f020b754 100644 --- a/applications/external/wifi_marauder_companion/wifi_marauder_custom_event.h +++ b/applications/external/wifi_marauder_companion/wifi_marauder_custom_event.h @@ -9,5 +9,6 @@ typedef enum { WifiMarauderEventStartSettingsInit, WifiMarauderEventStartLogViewer, WifiMarauderEventStartScriptSelect, - WifiMarauderEventStartSniffPmkidOptions + WifiMarauderEventStartSniffPmkidOptions, + WifiMarauderEventStartFlasher } WifiMarauderCustomEvent; diff --git a/applications/external/wifi_marauder_companion/wifi_marauder_flasher.c b/applications/external/wifi_marauder_companion/wifi_marauder_flasher.c new file mode 100644 index 000000000..8d30c1539 --- /dev/null +++ b/applications/external/wifi_marauder_companion/wifi_marauder_flasher.c @@ -0,0 +1,200 @@ +#include "wifi_marauder_flasher.h" + +FuriStreamBuffer* flash_rx_stream; // TODO make safe +WifiMarauderApp* global_app; // TODO make safe +FuriTimer* timer; // TODO make + +static uint32_t _remaining_time = 0; +static void _timer_callback(void* context) { + UNUSED(context); + if(_remaining_time > 0) { + _remaining_time--; + } +} + +static esp_loader_error_t _flash_file(WifiMarauderApp* app, char* filepath, uint32_t addr) { + // TODO cleanup + esp_loader_error_t err; + static uint8_t payload[1024]; + File* bin_file = storage_file_alloc(app->storage); + + // open file + if(!storage_file_open(bin_file, filepath, FSAM_READ, FSOM_OPEN_EXISTING)) { + storage_file_close(bin_file); + storage_file_free(bin_file); + dialog_message_show_storage_error(app->dialogs, "Cannot open file"); + return ESP_LOADER_ERROR_FAIL; + } + + uint64_t size = storage_file_size(bin_file); + + /* + // TODO packet drops with higher BR? + err = esp_loader_change_transmission_rate(230400); + if (err != ESP_LOADER_SUCCESS) { + char err_msg[256]; + snprintf( + err_msg, + sizeof(err_msg), + "Cannot change transmission rate. Error: %u\n", + err); + storage_file_close(bin_file); + storage_file_free(bin_file); + loader_port_debug_print(err_msg); + return; + } + + furi_hal_uart_set_br(FuriHalUartIdUSART1, 230400); + // TODO remember to change BR back! + */ + + loader_port_debug_print("Erasing flash...this may take a while\n"); + err = esp_loader_flash_start(addr, size, sizeof(payload)); + if(err != ESP_LOADER_SUCCESS) { + storage_file_close(bin_file); + storage_file_free(bin_file); + char err_msg[256]; + snprintf(err_msg, sizeof(err_msg), "Erasing flash failed with error %d\n", err); + loader_port_debug_print(err_msg); + return err; + } + + loader_port_debug_print("Start programming\n"); + while(size > 0) { + size_t to_read = MIN(size, sizeof(payload)); + uint16_t num_bytes = storage_file_read(bin_file, payload, to_read); + err = esp_loader_flash_write(payload, num_bytes); + if(err != ESP_LOADER_SUCCESS) { + char err_msg[256]; + snprintf(err_msg, sizeof(err_msg), "Packet could not be written! Error: %u\n", err); + storage_file_close(bin_file); + storage_file_free(bin_file); + loader_port_debug_print(err_msg); + return err; + } + + size -= num_bytes; + } + + loader_port_debug_print("Finished programming\n"); + + // TODO verify + + storage_file_close(bin_file); + storage_file_free(bin_file); + + return ESP_LOADER_SUCCESS; +} + +static int32_t wifi_marauder_flash_bin(void* context) { + WifiMarauderApp* app = (void*)context; + esp_loader_error_t err; + + // alloc global objects + flash_rx_stream = furi_stream_buffer_alloc(RX_BUF_SIZE, 1); + timer = furi_timer_alloc(_timer_callback, FuriTimerTypePeriodic, app); + + loader_port_debug_print("Connecting\n"); + esp_loader_connect_args_t connect_config = ESP_LOADER_CONNECT_DEFAULT(); + err = esp_loader_connect(&connect_config); + if(err != ESP_LOADER_SUCCESS) { + char err_msg[256]; + snprintf(err_msg, sizeof(err_msg), "Cannot connect to target. Error: %u\n", err); + loader_port_debug_print(err_msg); + } + + if(!err) { + loader_port_debug_print("Connected\n"); + loader_port_debug_print("Flashing bootloader (1/3)\n"); + err = _flash_file(app, app->bin_file_path_boot, 0x1000); + } + if(!err) { + loader_port_debug_print("Flashing partition table (2/3)\n"); + err = _flash_file(app, app->bin_file_path_part, 0x8000); + } + if(!err) { + loader_port_debug_print("Flashing app (3/3)\n"); + err = _flash_file(app, app->bin_file_path_app, 0x10000); + loader_port_debug_print("Done flashing. Please reset the board manually.\n"); + } + + // done + + // cleanup + furi_stream_buffer_free(flash_rx_stream); + flash_rx_stream = NULL; + furi_timer_free(timer); + return 0; +} + +void wifi_marauder_flash_start_thread(WifiMarauderApp* app) { + global_app = app; + + app->flash_worker = furi_thread_alloc(); + furi_thread_set_name(app->flash_worker, "WifiMarauderFlashWorker"); + furi_thread_set_stack_size(app->flash_worker, 2048); + furi_thread_set_context(app->flash_worker, app); + furi_thread_set_callback(app->flash_worker, wifi_marauder_flash_bin); + furi_thread_start(app->flash_worker); +} + +void wifi_marauder_flash_stop_thread(WifiMarauderApp* app) { + furi_thread_join(app->flash_worker); + furi_thread_free(app->flash_worker); +} + +esp_loader_error_t loader_port_read(uint8_t* data, uint16_t size, uint32_t timeout) { + size_t read = furi_stream_buffer_receive(flash_rx_stream, data, size, pdMS_TO_TICKS(timeout)); + if(read < size) { + return ESP_LOADER_ERROR_TIMEOUT; + } else { + return ESP_LOADER_SUCCESS; + } +} + +esp_loader_error_t loader_port_write(const uint8_t* data, uint16_t size, uint32_t timeout) { + UNUSED(timeout); + wifi_marauder_uart_tx((uint8_t*)data, size); + return ESP_LOADER_SUCCESS; +} + +void loader_port_enter_bootloader(void) { + // unimplemented +} + +void loader_port_delay_ms(uint32_t ms) { + furi_delay_ms(ms); +} + +void loader_port_start_timer(uint32_t ms) { + _remaining_time = ms; + furi_timer_start(timer, pdMS_TO_TICKS(1)); +} + +uint32_t loader_port_remaining_time(void) { + return _remaining_time; +} + +extern void wifi_marauder_console_output_handle_rx_data_cb( + uint8_t* buf, + size_t len, + void* context); // TODO cleanup +void loader_port_debug_print(const char* str) { + if(global_app) + wifi_marauder_console_output_handle_rx_data_cb((uint8_t*)str, strlen(str), global_app); +} + +void loader_port_spi_set_cs(uint32_t level) { + UNUSED(level); + // unimplemented +} + +void wifi_marauder_flash_handle_rx_data_cb(uint8_t* buf, size_t len, void* context) { + UNUSED(context); + if(flash_rx_stream) { + furi_stream_buffer_send(flash_rx_stream, buf, len, 0); + } else { + // done flashing + if(global_app) wifi_marauder_console_output_handle_rx_data_cb(buf, len, global_app); + } +} \ No newline at end of file diff --git a/applications/external/wifi_marauder_companion/wifi_marauder_flasher.h b/applications/external/wifi_marauder_companion/wifi_marauder_flasher.h new file mode 100644 index 000000000..796e258e5 --- /dev/null +++ b/applications/external/wifi_marauder_companion/wifi_marauder_flasher.h @@ -0,0 +1,10 @@ +#pragma once + +#include "wifi_marauder_app_i.h" +#include "wifi_marauder_uart.h" +#define SERIAL_FLASHER_INTERFACE_UART /* TODO why is application.fam not passing this via cdefines */ +#include "esp_loader_io.h" + +void wifi_marauder_flash_start_thread(WifiMarauderApp* app); +void wifi_marauder_flash_stop_thread(WifiMarauderApp* app); +void wifi_marauder_flash_handle_rx_data_cb(uint8_t* buf, size_t len, void* context); \ No newline at end of file diff --git a/applications/external/wifi_marauder_companion/wifi_marauder_uart.c b/applications/external/wifi_marauder_companion/wifi_marauder_uart.c index 080280b5f..0c9147752 100644 --- a/applications/external/wifi_marauder_companion/wifi_marauder_uart.c +++ b/applications/external/wifi_marauder_companion/wifi_marauder_uart.c @@ -112,4 +112,4 @@ void wifi_marauder_uart_free(WifiMarauderUart* uart) { furi_hal_console_enable(); free(uart); -} \ No newline at end of file +} From ab6c1d0d8ca66b2cd757edb067321ba33b10377b Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 5 Jul 2023 20:26:52 +0300 Subject: [PATCH 038/364] format --- applications/external/flappy_bird/flappy_bird.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/applications/external/flappy_bird/flappy_bird.c b/applications/external/flappy_bird/flappy_bird.c index 7ec152b99..2052488bf 100644 --- a/applications/external/flappy_bird/flappy_bird.c +++ b/applications/external/flappy_bird/flappy_bird.c @@ -29,12 +29,7 @@ typedef enum { EventTypeKey, } EventType; -typedef enum { - BirdState0 = 0, - BirdState1, - BirdState2, - BirdStateMAX -} BirdState; +typedef enum { BirdState0 = 0, BirdState1, BirdState2, BirdStateMAX } BirdState; const Icon* bird_states[BirdStateMAX] = { &I_bird_01, From 550edc366661f49e0b7cf1a850b3408b0a97ca7a Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 5 Jul 2023 20:35:57 +0300 Subject: [PATCH 039/364] Fix Issue #532 --- lib/nfc/helpers/mf_classic_dict.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/nfc/helpers/mf_classic_dict.c b/lib/nfc/helpers/mf_classic_dict.c index 7bdfa9e78..937addb46 100644 --- a/lib/nfc/helpers/mf_classic_dict.c +++ b/lib/nfc/helpers/mf_classic_dict.c @@ -336,8 +336,8 @@ bool mf_classic_dict_delete_index(MfClassicDict* dict, uint32_t target) { if(furi_string_get_char(next_line, 0) == '#') continue; if(furi_string_size(next_line) != NFC_MF_CLASSIC_KEY_LEN) continue; if(index++ != target) continue; - stream_seek(dict->stream, -(NFC_MF_CLASSIC_KEY_LEN + 1), StreamOffsetFromCurrent); - if(!stream_delete(dict->stream, (NFC_MF_CLASSIC_KEY_LEN + 1))) break; + stream_seek(dict->stream, -NFC_MF_CLASSIC_KEY_LEN, StreamOffsetFromCurrent); + if(!stream_delete(dict->stream, NFC_MF_CLASSIC_KEY_LEN)) break; dict->total_keys--; key_removed = true; } From e6ae2c03ca45033a15f6dd93410673ee03618657 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 6 Jul 2023 00:49:54 +0300 Subject: [PATCH 040/364] Keeloq: Centurion Nova support --- ReadMe.md | 1 + .../main/subghz/helpers/subghz_custom_event.h | 1 + .../subghz/scenes/subghz_scene_set_type.c | 15 +++ assets/resources/subghz/assets/keeloq_mfcodes | 111 +++++++++--------- lib/subghz/protocols/keeloq.c | 49 +++++++- 5 files changed, 117 insertions(+), 60 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index 3ab8e4b4a..28c580a3c 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -93,6 +93,7 @@ Encoders or sending made by @xMasterX: - Keeloq: Stilmatic - Keeloq: CAME Space - Keeloq: Aprimatic (model TR and similar) +- Keeloq: Centurion Nova (thanks Carlos !) Encoders or sending made by @Eng1n33r(first implementation in Q2 2022) & @xMasterX (current version): - CAME Atomo -> Update! check out new [instructions](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/SubGHzRemoteProg.md) diff --git a/applications/main/subghz/helpers/subghz_custom_event.h b/applications/main/subghz/helpers/subghz_custom_event.h index 83c16fbbc..e5321264e 100644 --- a/applications/main/subghz/helpers/subghz_custom_event.h +++ b/applications/main/subghz/helpers/subghz_custom_event.h @@ -15,6 +15,7 @@ typedef enum { SubmenuIndexBeninca868, SubmenuIndexAllmatic433, SubmenuIndexAllmatic868, + SubmenuIndexCenturion433, SubmenuIndexIronLogic, SubmenuIndexElmesElectronic, SubmenuIndexSommer_FM_434, diff --git a/applications/main/subghz/scenes/subghz_scene_set_type.c b/applications/main/subghz/scenes/subghz_scene_set_type.c index 0219f406b..64e927250 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_type.c +++ b/applications/main/subghz/scenes/subghz_scene_set_type.c @@ -91,6 +91,12 @@ void subghz_scene_set_type_on_enter(void* context) { SubmenuIndexAllmatic868, subghz_scene_set_type_submenu_callback, subghz); + submenu_add_item( + subghz->submenu, + "KL: Centurion 433MHz", + SubmenuIndexCenturion433, + subghz_scene_set_type_submenu_callback, + subghz); submenu_add_item( subghz->submenu, "KL: Sommer 434MHz", @@ -444,6 +450,15 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError); } break; + case SubmenuIndexCenturion433: + generated_protocol = subghz_txrx_gen_keeloq_protocol( + subghz->txrx, "AM650", 433920000, (key & 0x0000FFFF), 0x2, 0x0003, "Centurion"); + if(!generated_protocol) { + furi_string_set( + subghz->error_str, "Function requires\nan SD card with\nfresh databases."); + scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError); + } + break; case SubmenuIndexElmesElectronic: generated_protocol = subghz_txrx_gen_keeloq_protocol( subghz->txrx, diff --git a/assets/resources/subghz/assets/keeloq_mfcodes b/assets/resources/subghz/assets/keeloq_mfcodes index e65eb0868..27ab5aaaf 100644 --- a/assets/resources/subghz/assets/keeloq_mfcodes +++ b/assets/resources/subghz/assets/keeloq_mfcodes @@ -1,58 +1,59 @@ Filetype: Flipper SubGhz Keystore File Version: 0 Encryption: 1 -IV: AF 0B A3 13 56 FA F7 46 76 78 25 28 34 16 3D 62 -77995F096640A6CA8735DE839975FA3573145DDB995E45F58AECCD6A6F2D6FCB -A062DD58F9957EC098075344DFF69FB3B2A3C9893D4240C74BE32299F330290B -2AD2CCC4FB760D772001A903A995435260F442152BDCD5B075FBC61015BEC7E1 -34AE78CF87A10211C8E6F6E2EE18C4F0BBE3B677094B7118E03AD9E89AF70E28 -41943E7507D37A344F56EDF4BBDCDA75FAA10A6E97DF801ACF2A0E97E41782053CA74E31E3488EA1AFE29369E7A542C7 -9FA67B118BC1FE289F38A78DA4E1FFBAEB4498404B49CBD9690B9421FC05564D -3A872E97A668C644D3827273ADDA6B1BC689A3AD09F5980EC7461E40624653BE -5E1F4D865E5F4176DBF7832992B60947812E05701E647CF36427C2EE04F97FE2 -7FCF6E437D0DA231A2937C46622C4939F0045AEF5CF7FCF5D97E24B67995F0D3 -D09F230FEDB9CB690B5AC7C6BCE86B0779D9C233D2823562EABE340FF06C819D -84F0A81ABB7857438BF52BE8988C1A471EFEDFE16EC11851BFC39F34EB26236F318CFAAEC9A53AD5500D48CAE21E777A -F3FDDDD5DB6038D0E2FC02750530325976ADA2600DE19BF736AF6CB7E810D7627B4F396963F0288F486182228B9AAE22 -87E07B23B3B2740D93C82696C020057CC7F3864ABD6E6967656F44427C529DD1 -20C35809F7F5161C21E643A606DC48A5CC85BCEB546A03023DF778C4499426E3 -81CCF1CA68E2B6663DA9D12FEE241307A2E449440901793A955CB5D5915819DD -1B66D0664451153D0124364834E1543960A756351330523C3FFE83DA4EE7F0E6 -7025D550A40466B472BA71F3248C37E6DE1FD59ED5C11CBB26238795DD44F4B21BD447F3CA72AED6A25B977982100A1E -99F38C3F7C89D2805FB36F931AC5D1B248A56838AC29E13B1255CAE706B68216 -D138C616E4E1F6053177118C94F65C0BA6B155286CE63E0728E3F2D2BF6C6A98 -276E646DBF54B341F93AB4E1C36525AF983879F7251D84BB02DD54F4A5E0FA85A3278891F7ADA9ADE2F8AAD010F6F6D4 -3F3598143FE40E04FE25EADE1EC2B4CDAE339AF7730AE9DC45C97C0E44B299DBCC0E9DA6F4B0EE00F25D2FDD9E1C6BF5 -20FA8F62628FEA51A584CE298F22E60FF85BFE193AF1C5DE57605FF02E90739C -B14B4896088EF58E0CE659511C93782FB5F94BC69B64E5011EBD10DF18FFB3B1 -90BBE045FFEA06A77B55B0B6D0CFA8F12C4E1B35FB0111DD0C2CF1637AE8924A -04B87BC1D09E8EE3C8A91CE75169546D37868B2D87BF2D712623F84937ACE974 -B8C5B04070FFB27B4686057C57F762FA3CAF2BBD3E5BEBE462C1C2FC283AE118 -A40B154CEC2F5F989CA6F30703A0133217530D41F12739B25E2C1BEF54E6AC7C -4F5B9A68E8DEDB00410F5AD7FB7F7CA8F43B75F0457DA2AFAA8279A8C4AF34AB -9A7B185F6A157B1886DC6AA98B1F3D6899331D8BDDBAAC9620321E16BB4CC7E8 -4A710E11F1C7A7138065801BDB4E72B07608220BBCF7455111FDD41DC0290B94 -3A5B715089F926049077172755B0C48B4A4420031787D7DB113CF402C7D3D0AE -EE0B90EC27EC0F4A8DCD3C747E17594E0A27A92E05F2DE7B0457873C7154E075 -0B9B201C209072676A47225BE4E43B4631B080A85F9FCFAA5683A4F9A727187A -13A15C606AA2EA40F2DCDE7F44217F02D2D9796CAC9164100B211D7A22CF333B -DF5292BBF35AB1408956D439A81EF12F53573F985489727A10FB652B7BD8B10D -50E59C9DD3A08EA8752656B753B8D9D2BFD674EB4C5F0DCAE9870E81D7F00F6AAC133FA7C7307FA197D551EE877F8CD7 -E173C1798596A31F697D63E0CCDDFAB14B1B1E299DD642102A7858ECC795CF1B -92D3326A93AA6B37041F219C8035F37A057C1B69BECA7881098BED2A49C58751 -27D17F007115734FA0F55F2BDF016ECE8DFA703FA6D61729456E95B78FC8AC29 -4FF7306A426B7DE021D59968FAEF3453F555A9A952D81C4008D5000513799DEA -660CB0D4634EABC6CAA9E321CB08FC0C8C8F6BAE0FAF0C10C1FCCBA93B68D9B4 -86D84C91BFBF0DE22088C0F5A8598A2C2807033E60BC11333E8D1A6188F043D5 -F3E0E8566E12ADBA44974A3CA1D6D60456649031DCAD4365D0AE80FEDBC80AAA106A9BAB39448CD62EF916A59ECA9579 -F4D6EB6D241B17CA0A9E73E93DA3B58B6B257CC0484FC92E285984A09FD4CEA9 -094265CB574E0C9B8954B3130A2017492B1149C3FB9239A6B690A9C7B6635E5A -BE67B61B2F99BAA4AF94B71CB5F2386417D5F3B187899222D2671B1147BA9932 -74840B34C9F27A76FCB593629C8114BCABD1B1CE96E22CC378DC9E7BEEF263FB -2511F44F0A13D94B55D7FF3297194E47D6987890F9170BCBC14A7607C5A38E01 -FD0CF9314CB9B949CEFE1DA3FA05A18FBEEF751B4DC900DBAE068EE211C4492C -22ECD6934472760CF806E7C9E86885D0C0AAE501EDBF9DCB7ADC7AE53F3B73C38F2B6FB3FD0F867C5B5BFD00440CB43A -325CA78241AE4EE784CC867815403E342F77BB428EB1FE189AD569F10170CB98 -BF065D29EC8E2BB411F0131DF3A06BDF07B1436A14004D0E11E1261F0E232CB8 -CE015802FCE9AFD9807F855D813FD06D5446A8953057A79BC4A452BDAB8E9DD7 -C6B569EB172EC4609966E2C9426BE99A86529073A57824B1752392658C4E87F08ED8675A32F44E413CD6037CA4A0DE71 +IV: AD 0B A4 A1 51 C0 C0 41 36 78 26 82 17 24 9D 62 +0D18FF475E57A67CC5C0B430664E8EF6E07CB6AF72454995F17DE84E2E876D87 +C9BC55E1E3A9B312E341D7E2663C66C2479D5C51AE2EB83BAE47D8C6C79DB8A0 +776E01A7B4FCB929FA59CFE1F16D2D600F6FD9FD8BE1E9E41667144E61E023E4 +C354F854F14AAC08C6A51606582CD73EECEED54779927DC1E04A0D72C4E6A58E +09BA4DC551CDB0141F4A053133DBF9B7B99CBAA402C7B6B2AC8ADB516CA2FCB29FF9744DD95FB009BED6A09AE3303317 +675777F65042358100A9A2BE142C42E10CC5CF98CA6BFF82284FFD9BF7E7FB11 +4739972DCC08EFF0E8547694A67E116F3EC8638F286E333E7D89F2D61218F65D +FE8D5A0A9ED36545004CEE70D3C1FE477F34212364CF7C9EBA0FDD4F6B8F1D3E +453D5C6FE0EA7FD779B54696E66DE6BD6AEF3B1EE347142A3DB7505609077219 +A52339EE40D046E7DF6C130CEC7F9F686753A73A5F72EF344E01B00C3A3CF5F2 +94029CE386CC0403984F7C9D80B40FF12BE6E50412891FD45B347742340D1E6E679102DEC6F7D36C36863701A2BBDA7B +793633A3D97D704779E2E841A5F616ED0BB0BD50740C0B196D72C6C6453124183CA93ADCAC1A5042EBD579AC238B9DBC +B1C6EBB7320CAE97E28A244228B288BEEA44C9FD262E2C448219DAC6E194CB14 +A6C374B3FB7E8BB6F3052ED9DB67E5BCFA6CCC42F5A0D7F20BEDB7C0B58592A5 +DF4FCBDCA3414B7551797A856048625186B2EC7B836EB35B4B080CEE4A72C39F +4127F58BDEB18D79EA8317B1858F7575CF4B648E3C53338975B4A093FA9730C0 +294659EBD39D7CC01DC4E0E3B0B90A3555704E736D3BE4485BAAACBC58A84652CF3A7DA3C271E3198C72561332F2A141 +9E2CC77756BE7AB3D500C0B4A91271C0A4DED5BCB78484217EA922B7878AA8F5 +BF13D9949C783CF6B2F0E489A6912E56DDD9183D651D7F36FD0342981596A85C +8441C62006E1334FC31F53DAFF5281260C463AF3E92B1E797CB67882C49566846825606804C14C49FFD440F4E05CE692 +BE7E62E9CFA1C703CCB971F2B0C6C0F339C78A951CA8DB287B40C3BA76EA7179E8B62C29EAC8293FD218CF981BB84BEF +DA53C52101FAC8FD8F9545768CF1DB59F3B31C9249A2FC64B66DB259C721ABE1 +C9842AD0A8A8E94FCE94F46BDE89DAA8D11C41F9C83A7F6394D595028829AB92 +904A94542BCD85F72D470640A220FD28EBC337C0673C189C96BFB8CE373B4F84 +EEEE187B03FE77B955F22214707017DB8A60B2E3979D5C2F6BED61D0623D4D50 +CEF91BE6EBD8F331E2422B6B948053E8DA143F3DD907C922E0328D49B0ADA8A6 +A6F0F8A4F03B45062CEA86B291E80EE15906C0B226ABDB77E222EB95B026952D +62280870E48A2E8B8A18AD5DFF0089E927BEACE1F81A8BB1A8A2BE8EB0E92BFD +C9A3DE8165D47D1D41715F00192313755226C1B0A3D04BCD7A121B1CB4999FBD +3CAEEB62FCF601F34D3320E3EACE9EFC5D627BF69FEE965F8B84A258A765B6FE +8D83CC36514CE5CC46B181AE28089BE5BC99CE3096C569751B4E07A7D956AFA3 +0F81DDB18F60E238C7FF751E70431AA5328EFDBBDFF03D5F360A524AA968CEE9 +1F1E498A279E9BA15CB6BB836E90FEE522F2BE16572A4D057DF9C2B104487458 +68C60FF95056BE4D814CE9B8A4175625DB3E365712C2D3E42CEEBA2E0977CDB9 +D95FB21943FCAD5A21F157E629314986D92F568FD3067B65911135E6335FB7E8 +17908EF142C4B6740CBE7DCA43DA3C23C7912739299786473A994E5752E7E9459B23653EE0D7775FCECB7B7608CB0495 +6D17B6BA17DBFFAA4E90953CE6A73AB967076B8FBC14A9361D93A01C0850CC38 +8723740BCC0A5CCEE52B6EF73DD441672FB6728965CC588044C78D495AB0675F +CA548FA44A444C8F45490A11DBC8FB24E6DB38F910EC60520A3890C8B45664FE +591356440344AAFEC21FAA0C85B6A8F354D45074932E37E0972F851E08937469 +DE3A54CAAC8014625EE502F547A93754AFAD0A7EB6028599D03CCB0473BC8D5C +B2C2F6F971034E1591AD374793D6D17A595D6544DF5A9585780C6B2E3505BCFF +54447BE6C626D1CA37FD799B76B35ED266D12757B5DA1AB9277F671BBF7F07B461D0CE26593F1372354979E836972F85 +45FBD88AA7C26B967BC3638F6083A6B83AA82D5B974B37DF1C3F52839DDA020C +33B9890FBE46FDB7AEE404B71C893DC20059F96224CF48F284A66D3A8D91918E +CCEA5BC148BC84DB4825320A2B8D39A30BAB4641FFEE33BD4B8700339F15892D +4BC4E0D1263E9B02DA401C884923778D1A6FACACFFE7F660381ECD64CCA5CCD2 +0284EA911FB1B37F623F92B1662E10D79AEBD0009C107A9C554D417F7553B28E +FE48F26C44FA4C3EB3B2F3497FE99AC30A0A7BF9FF261740E177D7A2A5BD7880 +1EA96FEBB62543A8731D19BBDD1C7FE323CADBAB7242E5A8F4B1D706964B4C32 +4AB7EEF02FD59EA5D16837A50282A6254B93A34F31FE9335DA9FABEF76EA714591029C64967506B99860358E5CBF4EDA +A0F25C5387FE9B871E246F33FE64396A5DC0A9BC9AD9DF9E219F3482ADD6497E +0130F99FA395F4364491E6718B53E9D6983B68E29A70035B158CA7629C31C33E +3CDAEDA88534C2E31D1235C99DEC466221C89F63E1F8F59C9CE224573E39E2D4 +F5ED2863D8B51DA620484CDE23D590F4A208DA6D155E645FCD6BCF5FEB39EE551EAE9C0366F7FFD4CBF1DCA063D154E1 diff --git a/lib/subghz/protocols/keeloq.c b/lib/subghz/protocols/keeloq.c index 0d17fa4b5..db46936c2 100644 --- a/lib/subghz/protocols/keeloq.c +++ b/lib/subghz/protocols/keeloq.c @@ -231,6 +231,9 @@ static bool subghz_protocol_keeloq_gen_data( } else if(strcmp(instance->manufacture_name, "Beninca") == 0) { decrypt = btn << 28 | (0x000) << 16 | instance->generic.cnt; // Beninca / Allmatic -> no serial - simple XOR + } else if(strcmp(instance->manufacture_name, "Centurion") == 0) { + decrypt = btn << 28 | (0x1CE) << 16 | instance->generic.cnt; + // Centurion -> no serial in hop, uses fixed value 0x1CE - normal learning } uint8_t kl_type_en = instance->keystore->kl_type; for @@ -302,7 +305,7 @@ static bool subghz_protocol_keeloq_gen_data( uint64_t yek = (uint64_t)fix << 32 | hop; instance->generic.data = subghz_protocol_blocks_reverse_key(yek, instance->generic.data_count_bit); - } // What should happen if seed = 0 in bft programming mode + } // What should happen if seed = 0 in bft programming mode -> collapse of the universe I think :) return true; // Always return true } @@ -693,6 +696,33 @@ static inline bool subghz_protocol_keeloq_check_decrypt( if((decrypt >> 28 == btn) && (((((uint16_t)(decrypt >> 16)) & 0xFF) == end_serial) || ((((uint16_t)(decrypt >> 16)) & 0xFF) == 0))) { instance->cnt = decrypt & 0x0000FFFF; + /*FURI_LOG_I( + "KL", + "decrypt: 0x%08lX, btn: %d, end_serial: 0x%03lX, cnt: %ld", + decrypt, + btn, + end_serial, + instance->cnt);*/ + return true; + } + return false; +} +// Centurion specific check +static inline bool subghz_protocol_keeloq_check_decrypt_centurion( + SubGhzBlockGeneric* instance, + uint32_t decrypt, + uint8_t btn) { + furi_assert(instance); + + if((decrypt >> 28 == btn) && (((((uint16_t)(decrypt >> 16)) & 0x3FF) == 0x1CE))) { + instance->cnt = decrypt & 0x0000FFFF; + /*FURI_LOG_I( + "KL", + "decrypt: 0x%08lX, btn: %d, end_serial: 0x%03lX, cnt: %ld", + decrypt, + btn, + end_serial, + instance->cnt);*/ return true; } return false; @@ -753,10 +783,19 @@ static uint8_t subghz_protocol_keeloq_check_remote_controller_selector( man = subghz_protocol_keeloq_common_normal_learning(fix, manufacture_code->key); decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); - if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { - *manufacture_name = furi_string_get_cstr(manufacture_code->name); - keystore->mfname = *manufacture_name; - return 1; + if((strcmp(furi_string_get_cstr(manufacture_code->name), "Centurion") == 0)) { + if(subghz_protocol_keeloq_check_decrypt_centurion(instance, decrypt, btn)) { + *manufacture_name = furi_string_get_cstr(manufacture_code->name); + keystore->mfname = *manufacture_name; + return 1; + } + } else { + if(subghz_protocol_keeloq_check_decrypt( + instance, decrypt, btn, end_serial)) { + *manufacture_name = furi_string_get_cstr(manufacture_code->name); + keystore->mfname = *manufacture_name; + return 1; + } } break; case KEELOQ_LEARNING_SECURE: From e83764ef4e5d3b13fa706a5feaed6d07a802b1f4 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 6 Jul 2023 01:09:32 +0300 Subject: [PATCH 041/364] RCA protocol unit test --- .../debug/unit_tests/infrared/infrared_test.c | 12 ++ assets/unit_tests/infrared/test_rca.irtest | 105 ++++++++++++++++++ 2 files changed, 117 insertions(+) create mode 100644 assets/unit_tests/infrared/test_rca.irtest diff --git a/applications/debug/unit_tests/infrared/infrared_test.c b/applications/debug/unit_tests/infrared/infrared_test.c index 2bcb95da8..b2acad470 100644 --- a/applications/debug/unit_tests/infrared/infrared_test.c +++ b/applications/debug/unit_tests/infrared/infrared_test.c @@ -425,6 +425,7 @@ MU_TEST(infrared_test_decoder_mixed) { infrared_test_run_decoder(InfraredProtocolSamsung32, 1); infrared_test_run_decoder(InfraredProtocolSIRC, 3); infrared_test_run_decoder(InfraredProtocolKaseikyo, 1); + infrared_test_run_decoder(InfraredProtocolRCA, 1); } MU_TEST(infrared_test_decoder_nec) { @@ -499,6 +500,15 @@ MU_TEST(infrared_test_decoder_kaseikyo) { infrared_test_run_decoder(InfraredProtocolKaseikyo, 6); } +MU_TEST(infrared_test_decoder_rca) { + infrared_test_run_decoder(InfraredProtocolRCA, 1); + infrared_test_run_decoder(InfraredProtocolRCA, 2); + infrared_test_run_decoder(InfraredProtocolRCA, 3); + infrared_test_run_decoder(InfraredProtocolRCA, 4); + infrared_test_run_decoder(InfraredProtocolRCA, 5); + infrared_test_run_decoder(InfraredProtocolRCA, 6); +} + MU_TEST(infrared_test_encoder_decoder_all) { infrared_test_run_encoder_decoder(InfraredProtocolNEC, 1); infrared_test_run_encoder_decoder(InfraredProtocolNECext, 1); @@ -509,6 +519,7 @@ MU_TEST(infrared_test_encoder_decoder_all) { infrared_test_run_encoder_decoder(InfraredProtocolRC5, 1); infrared_test_run_encoder_decoder(InfraredProtocolSIRC, 1); infrared_test_run_encoder_decoder(InfraredProtocolKaseikyo, 1); + infrared_test_run_encoder_decoder(InfraredProtocolRCA, 1); } MU_TEST_SUITE(infrared_test) { @@ -527,6 +538,7 @@ MU_TEST_SUITE(infrared_test) { MU_RUN_TEST(infrared_test_decoder_samsung32); MU_RUN_TEST(infrared_test_decoder_necext1); MU_RUN_TEST(infrared_test_decoder_kaseikyo); + MU_RUN_TEST(infrared_test_decoder_rca); MU_RUN_TEST(infrared_test_decoder_mixed); MU_RUN_TEST(infrared_test_encoder_decoder_all); } diff --git a/assets/unit_tests/infrared/test_rca.irtest b/assets/unit_tests/infrared/test_rca.irtest new file mode 100644 index 000000000..bfe34e8e4 --- /dev/null +++ b/assets/unit_tests/infrared/test_rca.irtest @@ -0,0 +1,105 @@ +Filetype: IR tests file +Version: 1 +# +name: decoder_input1 +type: raw +data: 1000000 3994 3969 552 1945 551 1945 552 1945 551 1945 552 946 551 947 550 1947 548 951 546 1953 542 979 518 1979 517 981 492 1006 491 1006 492 1006 492 1006 492 2005 492 2005 492 1006 492 2005 492 1006 492 2005 492 1006 492 2006 491 +# +name: decoder_expected1 +type: parsed_array +count: 1 +# +protocol: RCA +address: 0F 00 00 00 +command: 54 00 00 00 +repeat: false +# +name: decoder_input2 +type: raw +data: 1000000 4055 3941 605 1891 551 1947 550 1946 551 1946 551 947 551 947 550 1947 549 949 548 1951 545 1977 519 1978 519 1979 518 980 518 980 518 980 518 980 518 1979 518 1979 518 981 517 1979 518 980 518 980 518 980 518 980 518 +# +name: decoder_expected2 +type: parsed_array +count: 1 +# +protocol: RCA +address: 0F 00 00 00 +command: F4 00 00 00 +repeat: false +# +name: decoder_input3 +type: raw +data: 1000000 4027 3970 551 1946 550 1946 551 1946 551 1946 551 946 551 947 550 1947 549 949 547 1951 545 1978 518 1979 492 1006 492 1007 491 1006 492 1006 492 1006 492 2006 491 2006 491 1006 492 2006 491 1007 491 1007 491 1006 492 2006 491 +# +name: decoder_expected3 +type: parsed_array +count: 1 +# +protocol: RCA +address: 0F 00 00 00 +command: 74 00 00 00 +repeat: false +# +name: decoder_input4 +type: raw +data: 1000000 4021 3941 551 1946 550 1946 551 1946 551 1945 552 946 551 947 550 1947 549 950 547 1952 544 1977 519 979 519 1979 518 980 518 980 518 980 518 980 518 1979 518 1979 518 980 518 1979 518 980 518 980 518 1979 518 980 518 +# +name: decoder_expected4 +type: parsed_array +count: 1 +# +protocol: RCA +address: 0F 00 00 00 +command: B4 00 00 00 +repeat: false +# +name: decoder_input5 +type: raw +data: 1000000 4022 3941 551 1946 551 1946 577 1919 578 1919 578 920 552 946 551 1946 550 947 550 1949 547 1952 544 978 520 979 519 980 518 980 518 980 518 980 518 1979 518 1979 518 980 518 1979 518 980 518 980 518 1979 518 1980 517 +# +name: decoder_expected5 +type: parsed_array +count: 1 +# +protocol: RCA +address: 0F 00 00 00 +command: 34 00 00 00 +repeat: false +# +name: decoder_input6 +type: raw +data: 1000000 3995 3968 552 1944 552 1946 550 1946 550 1946 551 947 550 948 549 1947 549 1949 547 1952 544 1978 518 1979 492 2005 492 1006 492 1006 492 1006 492 1006 492 2005 492 2005 492 1006 492 1006 492 1006 492 1006 492 1006 492 1006 492 +# +name: decoder_expected6 +type: parsed_array +count: 1 +# +protocol: RCA +address: 0F 00 00 00 +command: FC 00 00 00 +repeat: false +# +name: encoder_decoder_input1 +type: parsed_array +count: 4 +# +protocol: RCA +address: 0F 00 00 00 +command: 74 00 00 00 +repeat: false +# +protocol: RCA +address: 0F 00 00 00 +command: B4 00 00 00 +repeat: false +# +protocol: RCA +address: 0F 00 00 00 +command: 34 00 00 00 +repeat: false +# +protocol: RCA +address: 0F 00 00 00 +command: FC 00 00 00 +repeat: false +# From 4876e03932675787dcc33cb5e238508a7dc7604d Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 6 Jul 2023 01:14:41 +0300 Subject: [PATCH 042/364] OFW PR 2829: Decode only supported Oregon 3 sensor by wosk --- applications/external/weather_station/protocols/oregon3.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/applications/external/weather_station/protocols/oregon3.c b/applications/external/weather_station/protocols/oregon3.c index a211c5ad3..bd35c2fd5 100644 --- a/applications/external/weather_station/protocols/oregon3.c +++ b/applications/external/weather_station/protocols/oregon3.c @@ -116,9 +116,11 @@ static ManchesterEvent level_and_duration_to_event(bool level, uint32_t duration static uint8_t oregon3_sensor_id_var_bits(uint16_t sensor_id) { switch(sensor_id) { case ID_THGR221: - default: // nibbles: temp + hum + '0' return (4 + 2 + 1) * 4; + default: + FURI_LOG_D(TAG, "Unsupported sensor id 0x%x", sensor_id); + return 0; } } @@ -198,10 +200,8 @@ void ws_protocol_decoder_oregon3_feed(void* context, bool level, uint32_t durati oregon3_sensor_id_var_bits(OREGON3_SENSOR_ID(instance->generic.data)); if(!instance->var_bits) { - // sensor is not supported, stop decoding, but showing the decoded fixed part + // sensor is not supported, stop decoding instance->decoder.parser_step = Oregon3DecoderStepReset; - if(instance->base.callback) - instance->base.callback(&instance->base, instance->base.context); } else { instance->decoder.parser_step = Oregon3DecoderStepVarData; } From 0f5fc11f8f265006ca9a946efacf056ad7193e55 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 6 Jul 2023 01:20:09 +0300 Subject: [PATCH 043/364] Dbg stuff --- applications/main/application.fam | 13 +++++++------ documentation/HowToBuild.md | 6 ++++++ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/applications/main/application.fam b/applications/main/application.fam index c8884f934..43aea2e5d 100644 --- a/applications/main/application.fam +++ b/applications/main/application.fam @@ -18,19 +18,20 @@ App( ], ) +# Enable apps that you need in DEBUG firmware here: App( appid="main_apps_default", name="Basic applications for main menu", apptype=FlipperAppType.METAPACKAGE, provides=[ - #"gpio", - #"ibutton", - #"infrared", + # "gpio", + # "ibutton", + # "infrared", "lfrfid", - "nfc", + # "nfc", "subghz", - #"bad_usb", - #"u2f", + # "bad_usb", + # "u2f", "archive", ], ) diff --git a/documentation/HowToBuild.md b/documentation/HowToBuild.md index 80b93b1c3..76830063f 100644 --- a/documentation/HowToBuild.md +++ b/documentation/HowToBuild.md @@ -31,6 +31,9 @@ Check out `documentation/fbt.md` for details on building and flashing firmware. ### Compile everything for development +Edit this file to enable/disable Main apps that you need in DEBUG mode, flash space doesn't allows us to fit them all in DEBUG currently +- `applications/main/application.fam` + ```sh ./fbt FIRMWARE_APP_SET=debug_pack updater_package ``` @@ -52,6 +55,9 @@ Check out `documentation/fbt.md` for details on building and flashing firmware. ### Compile everything for development +Edit this file to enable/disable Main apps that you need in DEBUG mode, flash space doesn't allows us to fit them all in DEBUG currently +- `applications/main/application.fam` + ```sh ./fbt.cmd FIRMWARE_APP_SET=debug_pack updater_package ``` From cebc56dc23229da68100ef6844eb4c9f1a9388af Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 6 Jul 2023 02:07:07 +0300 Subject: [PATCH 044/364] move --- applications/external/barcode_generator/barcode_generator.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/external/barcode_generator/barcode_generator.h b/applications/external/barcode_generator/barcode_generator.h index 5d2c8307e..9f2e10c16 100644 --- a/applications/external/barcode_generator/barcode_generator.h +++ b/applications/external/barcode_generator/barcode_generator.h @@ -7,7 +7,7 @@ #include #include -#define BARCODE_SETTINGS_FILE_NAME "apps/Misc/barcodegen.save" +#define BARCODE_SETTINGS_FILE_NAME "apps/Tools/barcodegen.save" #define BARCODE_SETTINGS_VER (1) #define BARCODE_SETTINGS_PATH EXT_PATH(BARCODE_SETTINGS_FILE_NAME) From 45f3a7754850a595bfaeaa3a82f49b1cc69d8be7 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 6 Jul 2023 02:26:56 +0300 Subject: [PATCH 045/364] barcodes --- ReadMe.md | 2 +- applications/external/barcode_gen/LICENSE | 22 + applications/external/barcode_gen/README.md | 88 +++ .../external/barcode_gen/application.fam | 16 + .../external/barcode_gen/barcode_app.c | 348 ++++++++++++ .../external/barcode_gen/barcode_app.h | 91 +++ .../external/barcode_gen/barcode_utils.c | 147 +++++ .../external/barcode_gen/barcode_utils.h | 55 ++ .../external/barcode_gen/barcode_validator.c | 532 ++++++++++++++++++ .../external/barcode_gen/barcode_validator.h | 15 + applications/external/barcode_gen/encodings.c | 52 ++ applications/external/barcode_gen/encodings.h | 6 + .../barcode_gen/images/barcode_10.png | Bin 0 -> 161 bytes .../screenshots/Codabar Data Example.png | Bin 0 -> 1672 bytes .../screenshots/Creating Barcode.png | Bin 0 -> 1681 bytes .../screenshots/Flipper Barcode.png | Bin 0 -> 1207 bytes .../screenshots/Flipper Box Barcode.png | Bin 0 -> 1372 bytes .../external/barcode_gen/views/barcode_view.c | 510 +++++++++++++++++ .../external/barcode_gen/views/barcode_view.h | 23 + .../external/barcode_gen/views/create_view.c | 494 ++++++++++++++++ .../external/barcode_gen/views/create_view.h | 46 ++ .../external/barcode_gen/views/message_view.c | 66 +++ .../external/barcode_gen/views/message_view.h | 22 + .../barcode_generator/application.fam | 17 - .../barcode_generator/barcode_10px.png | Bin 2363 -> 0 bytes .../barcode_generator/barcode_generator.c | 447 --------------- .../barcode_generator/barcode_generator.h | 115 ---- .../barcode_data/codabar_encodings.txt | 22 + .../barcode_data/code128_encodings.txt | 202 +++++++ .../barcode_data/code128c_encodings.txt | 106 ++++ .../barcode_data/code39_encodings.txt | 44 ++ 31 files changed, 2908 insertions(+), 580 deletions(-) create mode 100644 applications/external/barcode_gen/LICENSE create mode 100644 applications/external/barcode_gen/README.md create mode 100644 applications/external/barcode_gen/application.fam create mode 100644 applications/external/barcode_gen/barcode_app.c create mode 100644 applications/external/barcode_gen/barcode_app.h create mode 100644 applications/external/barcode_gen/barcode_utils.c create mode 100644 applications/external/barcode_gen/barcode_utils.h create mode 100644 applications/external/barcode_gen/barcode_validator.c create mode 100644 applications/external/barcode_gen/barcode_validator.h create mode 100644 applications/external/barcode_gen/encodings.c create mode 100644 applications/external/barcode_gen/encodings.h create mode 100644 applications/external/barcode_gen/images/barcode_10.png create mode 100644 applications/external/barcode_gen/screenshots/Codabar Data Example.png create mode 100644 applications/external/barcode_gen/screenshots/Creating Barcode.png create mode 100644 applications/external/barcode_gen/screenshots/Flipper Barcode.png create mode 100644 applications/external/barcode_gen/screenshots/Flipper Box Barcode.png create mode 100644 applications/external/barcode_gen/views/barcode_view.c create mode 100644 applications/external/barcode_gen/views/barcode_view.h create mode 100644 applications/external/barcode_gen/views/create_view.c create mode 100644 applications/external/barcode_gen/views/create_view.h create mode 100644 applications/external/barcode_gen/views/message_view.c create mode 100644 applications/external/barcode_gen/views/message_view.h delete mode 100644 applications/external/barcode_generator/application.fam delete mode 100644 applications/external/barcode_generator/barcode_10px.png delete mode 100644 applications/external/barcode_generator/barcode_generator.c delete mode 100644 applications/external/barcode_generator/barcode_generator.h create mode 100644 assets/resources/apps_data/barcode_data/codabar_encodings.txt create mode 100644 assets/resources/apps_data/barcode_data/code128_encodings.txt create mode 100644 assets/resources/apps_data/barcode_data/code128c_encodings.txt create mode 100644 assets/resources/apps_data/barcode_data/code39_encodings.txt diff --git a/ReadMe.md b/ReadMe.md index 28c580a3c..b589e164e 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -145,7 +145,7 @@ You can support us by using links or addresses below: - WiFi Scanner plugin [(by SequoiaSan)](https://github.com/SequoiaSan/FlipperZero-WiFi-Scanner_Module) - MultiConverter plugin [(by theisolinearchip)](https://github.com/theisolinearchip/flipperzero_stuff) - WAV Player [(OFW: DrZlo13)](https://github.com/flipperdevices/flipperzero-firmware/tree/zlo/wav-player) - Fixed and improved by [LTVA1](https://github.com/LTVA1/wav_player) -> Also outputs audio on `PA6` - `3(A6)` pin -- Barcode generator plugin [(original by McAzzaMan)](https://github.com/McAzzaMan/flipperzero-firmware/tree/UPC-A_Barcode_Generator/applications/barcode_generator) - [EAN-8 and refactoring](https://github.com/DarkFlippers/unleashed-firmware/pull/154) by @msvsergey +- Barcode Generator [(by Kingal1337)](https://github.com/Kingal1337/flipper-barcode-generator) - GPIO: Sentry Safe plugin [(by H4ckd4ddy)](https://github.com/H4ckd4ddy/flipperzero-sentry-safe-plugin) - ESP32: WiFi Marauder companion plugin [(by 0xchocolate)](https://github.com/0xchocolate/flipperzero-wifi-marauder) - Saving .pcap on flipper microSD [by tcpassos](https://github.com/tcpassos/flipperzero-firmware-with-wifi-marauder-companion) -> Only with custom marauder build (It is necessary to uncomment "#define WRITE_PACKETS_SERIAL" in configs.h (in marauder fw) and compile the firmware for the wifi board.) Or download precompiled build -> [Download esp32_marauder_ver_flipper_sd_serial.bin](https://github.com/justcallmekoko/ESP32Marauder/releases/latest) - NRF24: Sniffer & MouseJacker (with changes) [(by mothball187)](https://github.com/mothball187/flipperzero-nrf24/tree/main/mousejacker) diff --git a/applications/external/barcode_gen/LICENSE b/applications/external/barcode_gen/LICENSE new file mode 100644 index 000000000..4c02d8221 --- /dev/null +++ b/applications/external/barcode_gen/LICENSE @@ -0,0 +1,22 @@ + +MIT License + +Copyright (c) 2023 Alan Tsui + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/applications/external/barcode_gen/README.md b/applications/external/barcode_gen/README.md new file mode 100644 index 000000000..ec944cb26 --- /dev/null +++ b/applications/external/barcode_gen/README.md @@ -0,0 +1,88 @@ +

+

Barcode Generator

+

+ +A barcode generator for the Flipper Zero that supports **UPC-A**, **EAN-8**, **EAN-13**, **Code-39**, **Codabar**, and **Code-128**[1] +

+ +Note: Barcode save locations have been moved from `/barcodes` to `/apps_data/barcodes` + +## Table of Contents +- [Table of Contents](#table-of-contents) +- [Installing](#installing) +- [Building](#building) +- [Usage](#usage) + - [Creating a barcode](#creating-a-barcode) + - [Editing a barcode](#editing-a-barcode) + - [Deleting a barcode](#deleting-a-barcode) + - [Viewing a barcode](#viewing-a-barcode) +- [Screenshots](#screenshots) +- [Credits](#credits) + + +## Installing +1) Download the `.zip` file from the release section +2) Extract/unzip the `.zip` file onto your computer +3) Open qFlipper and go to the file manager +4) Navigate to the `apps` folder +5) Drag & drop the `.fap` file into the `apps` folder +6) Navigate back to the root folder of the SD card and create the folder `apps_data`, if not already there +7) Navigate into `apps_data` and create another folder called `barcode_data` +8) Navigate into `barcode_data` +9) Drag & drop the encoding txts (`code39_encodings.txt`, `code128_encodings.txt` & `codabar_encodings.txt`) into the `barcode_data` folder + +## Building +1) Clone the [flipperzero-firmware](https://github.com/flipperdevices/flipperzero-firmware) repository or a firmware of your choice +2) Clone this repository and put it in the `applications_user` folder +3) Build this app by using the command `./fbt fap_Barcode_App` +4) Copy the `.fap` from `build\f7-firmware-D\.extapps\Barcode_App.fap` to `apps\Misc` using the qFlipper app +5) While still in the qFlipper app, navigate to the root folder of the SD card and create the folder `apps_data`, if not already there +6) Navigate into `apps_data` and create another folder called `barcode_data` +7) Navigate into `barcode_data` +8) Drag & drop the encoding txts (`code39_encodings.txt`, `code128_encodings.txt` & `codabar_encodings.txt`) from the `encoding_tables` folder in this repository into the `barcode_data` folder + +## Usage + +### Creating a barcode +1) To create a barcode click on `Create Barcode` +2) Next select your type using the left and right arrows +3) Enter your filename and then your barcode data +4) Click save + +**Note**: For Codabar barcodes, you must manually add the start and stop codes to the barcode data +Start/Stop codes can be A, B, C, or D +For example, if you wanted to represent `1234` as a barcode you will need to enter something like `A1234A`. (You can replace the letters A with either A, B, C, or D) + +![Codabar Data Example](screenshots/Codabar%20Data%20Example.png "Codabar Data Example") + +### Editing a barcode +1) To edit a barcode click on `Edit Barcode` +2) Next select the barcode file you want to edit +3) Edit the type, name, or data +4) Click save + +### Deleting a barcode +1) To delete a barcode click on `Edit Barcode` +2) Next select the barcode file you want to delete +3) Scroll all the way to the bottom +4) Click delete + +### Viewing a barcode +1) To view a barcode click on `Load Barcode` +2) Next select the barcode file you want to view + +## Screenshots +![Barcode Create Screen](screenshots/Creating%20Barcode.png "Barcode Create Screen") + +![Flipper Code-128 Barcode](screenshots/Flipper%20Barcode.png "Flipper Code-128 Barcode") + +![Flipper Box EAN-13 Barcode](screenshots/Flipper%20Box%20Barcode.png "Flipper Box EAN-13 Barcode") + +## Credits + +- [Kingal1337](https://github.com/Kingal1337) - Developer +- [Z0wl](https://github.com/Z0wl) - Added Code128-C Support +- [@teeebor](https://github.com/teeebor) - Menu Code Snippet + + +[1] - supports Set B (only the characters from 0-94). Also supports Set C diff --git a/applications/external/barcode_gen/application.fam b/applications/external/barcode_gen/application.fam new file mode 100644 index 000000000..371e52c04 --- /dev/null +++ b/applications/external/barcode_gen/application.fam @@ -0,0 +1,16 @@ +App( + appid="barcode_app", + name="Barcode App", + apptype=FlipperAppType.EXTERNAL, + entry_point="barcode_main", + requires=["gui", "storage"], + stack_size=2 * 1024, + fap_category="Tools", + fap_icon="images/barcode_10.png", + fap_icon_assets="images", + fap_icon_assets_symbol="barcode_app", + fap_author="@Kingal1337", + fap_weburl="https://github.com/Kingal1337/flipper-barcode-generator", + fap_version="1.0", + fap_description="App allows you to display various barcodes on flipper screen", +) diff --git a/applications/external/barcode_gen/barcode_app.c b/applications/external/barcode_gen/barcode_app.c new file mode 100644 index 000000000..99e5769d2 --- /dev/null +++ b/applications/external/barcode_gen/barcode_app.c @@ -0,0 +1,348 @@ +#include "barcode_app.h" + +#include "barcode_app_icons.h" + +/** + * Opens a file browser dialog and returns the filepath of the selected file + * + * @param folder the folder to view when the browser opens + * @param file_path a string pointer for the file_path when a file is selected, + * file_path will be the folder path is nothing is selected + * @returns true if a file is selected +*/ +static bool select_file(const char* folder, FuriString* file_path) { + DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); + DialogsFileBrowserOptions browser_options; + dialog_file_browser_set_basic_options(&browser_options, "", &I_barcode_10); + browser_options.base_path = DEFAULT_USER_BARCODES; + furi_string_set(file_path, folder); + + bool res = dialog_file_browser_show(dialogs, file_path, file_path, &browser_options); + + furi_record_close(RECORD_DIALOGS); + + return res; +} + +/** + * Reads the data from a file and stores them in the FuriStrings raw_type and raw_data +*/ +ErrorCode read_raw_data(FuriString* file_path, FuriString* raw_type, FuriString* raw_data) { + //Open Storage + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* ff = flipper_format_file_alloc(storage); + + ErrorCode reason = OKCode; + + if(!flipper_format_file_open_existing(ff, furi_string_get_cstr(file_path))) { + FURI_LOG_E(TAG, "Could not open file %s", furi_string_get_cstr(file_path)); + reason = FileOpening; + } else { + if(!flipper_format_read_string(ff, "Type", raw_type)) { + FURI_LOG_E(TAG, "Could not read \"Type\" string"); + reason = InvalidFileData; + } + if(!flipper_format_read_string(ff, "Data", raw_data)) { + FURI_LOG_E(TAG, "Could not read \"Data\" string"); + reason = InvalidFileData; + } + } + + //Close Storage + flipper_format_free(ff); + furi_record_close(RECORD_STORAGE); + + return reason; +} + +/** + * Gets the file name from a file path + * @param file_path the file path + * @param file_name the FuriString to store the file name + * @param remove_extension true if the extension should be removed, otherwise false +*/ +bool get_file_name_from_path(FuriString* file_path, FuriString* file_name, bool remove_extension) { + if(file_path == NULL || file_name == NULL) { + return false; + } + uint32_t slash_index = furi_string_search_rchar(file_path, '/', 0); + if(slash_index == FURI_STRING_FAILURE || slash_index >= (furi_string_size(file_path) - 1)) { + return false; + } + + furi_string_set(file_name, file_path); + furi_string_right(file_name, slash_index + 1); + if(remove_extension) { + uint32_t ext_index = furi_string_search_rchar(file_name, '.', 0); + if(ext_index != FURI_STRING_FAILURE && ext_index < (furi_string_size(file_path))) { + furi_string_left(file_name, ext_index); + } + } + + return true; +} + +/** + * Creates the barcode folder +*/ +void init_folder() { + Storage* storage = furi_record_open(RECORD_STORAGE); + FURI_LOG_I(TAG, "Creating barcodes folder"); + if(storage_simply_mkdir(storage, DEFAULT_USER_BARCODES)) { + FURI_LOG_I(TAG, "Barcodes folder successfully created!"); + } else { + FURI_LOG_I(TAG, "Barcodes folder already exists."); + } + furi_record_close(RECORD_STORAGE); +} + +void select_barcode_item(BarcodeApp* app) { + FuriString* file_path = furi_string_alloc(); + FuriString* raw_type = furi_string_alloc(); + FuriString* raw_data = furi_string_alloc(); + + //this determines if the data was read correctly or if the + bool loaded_success = true; + ErrorCode reason = OKCode; + + bool file_selected = select_file(DEFAULT_USER_BARCODES, file_path); + if(file_selected) { + FURI_LOG_I(TAG, "The file selected is %s", furi_string_get_cstr(file_path)); + Barcode* barcode = app->barcode_view; + + reason = read_raw_data(file_path, raw_type, raw_data); + if(reason != OKCode) { + loaded_success = false; + FURI_LOG_E(TAG, "Could not read data correctly"); + } + + //Free the data from the previous barcode + barcode_free_model(barcode); + + with_view_model( + barcode->view, + BarcodeModel * model, + { + model->file_path = furi_string_alloc_set(file_path); + + model->data = malloc(sizeof(BarcodeData)); + model->data->valid = loaded_success; + + if(loaded_success) { + model->data->raw_data = furi_string_alloc_set(raw_data); + model->data->correct_data = furi_string_alloc(); + + model->data->type_obj = get_type(raw_type); + + barcode_loader(model->data); + } else { + model->data->reason = reason; + } + }, + true); + + view_dispatcher_switch_to_view(app->view_dispatcher, BarcodeView); + } + + furi_string_free(raw_type); + furi_string_free(raw_data); + furi_string_free(file_path); +} + +void edit_barcode_item(BarcodeApp* app) { + FuriString* file_path = furi_string_alloc(); + FuriString* file_name = furi_string_alloc(); + FuriString* raw_type = furi_string_alloc(); + FuriString* raw_data = furi_string_alloc(); + + //this determines if the data was read correctly or if the + ErrorCode reason = OKCode; + + bool file_selected = select_file(DEFAULT_USER_BARCODES, file_path); + if(file_selected) { + FURI_LOG_I(TAG, "The file selected is %s", furi_string_get_cstr(file_path)); + CreateView* create_view_object = app->create_view; + + reason = read_raw_data(file_path, raw_type, raw_data); + if(reason != OKCode) { + FURI_LOG_E(TAG, "Could not read data correctly"); + with_view_model( + app->message_view->view, + MessageViewModel * model, + { model->message = get_error_code_message(reason); }, + true); + + view_dispatcher_switch_to_view( + create_view_object->barcode_app->view_dispatcher, MessageErrorView); + + } else { + BarcodeTypeObj* type_obj = get_type(raw_type); + if(type_obj->type == UNKNOWN) { + type_obj = barcode_type_objs[0]; + } + get_file_name_from_path(file_path, file_name, true); + + create_view_free_model(create_view_object); + with_view_model( + create_view_object->view, + CreateViewModel * model, + { + model->selected_menu_item = 0; + model->barcode_type = type_obj; + model->file_path = furi_string_alloc_set(file_path); + model->file_name = furi_string_alloc_set(file_name); + model->barcode_data = furi_string_alloc_set(raw_data); + model->mode = EditMode; + }, + true); + view_dispatcher_switch_to_view(app->view_dispatcher, CreateBarcodeView); + } + } + + furi_string_free(raw_type); + furi_string_free(raw_data); + furi_string_free(file_name); + furi_string_free(file_path); +} + +void create_barcode_item(BarcodeApp* app) { + CreateView* create_view_object = app->create_view; + + create_view_free_model(create_view_object); + + with_view_model( + create_view_object->view, + CreateViewModel * model, + { + model->selected_menu_item = 0; + model->barcode_type = barcode_type_objs[0]; + model->file_path = furi_string_alloc(); + model->file_name = furi_string_alloc(); + model->barcode_data = furi_string_alloc(); + model->mode = NewMode; + }, + true); + view_dispatcher_switch_to_view(app->view_dispatcher, CreateBarcodeView); +} + +void submenu_callback(void* context, uint32_t index) { + furi_assert(context); + + BarcodeApp* app = context; + + if(index == SelectBarcodeItem) { + select_barcode_item(app); + } else if(index == EditBarcodeItem) { + edit_barcode_item(app); + } else if(index == CreateBarcodeItem) { + create_barcode_item(app); + } +} + +uint32_t create_view_callback(void* context) { + UNUSED(context); + return CreateBarcodeView; +} + +uint32_t main_menu_callback(void* context) { + UNUSED(context); + return MainMenuView; +} + +uint32_t exit_callback(void* context) { + UNUSED(context); + return VIEW_NONE; +} + +void free_app(BarcodeApp* app) { + FURI_LOG_I(TAG, "Freeing Data"); + + init_folder(); + free_types(); + + view_dispatcher_remove_view(app->view_dispatcher, TextInputView); + text_input_free(app->text_input); + + view_dispatcher_remove_view(app->view_dispatcher, MessageErrorView); + message_view_free(app->message_view); + + view_dispatcher_remove_view(app->view_dispatcher, MainMenuView); + submenu_free(app->main_menu); + + view_dispatcher_remove_view(app->view_dispatcher, CreateBarcodeView); + create_view_free(app->create_view); + + view_dispatcher_remove_view(app->view_dispatcher, BarcodeView); + barcode_free(app->barcode_view); + + //free the dispatcher + view_dispatcher_free(app->view_dispatcher); + + furi_message_queue_free(app->event_queue); + + furi_record_close(RECORD_GUI); + app->gui = NULL; + + free(app); +} + +int32_t barcode_main(void* p) { + UNUSED(p); + BarcodeApp* app = malloc(sizeof(BarcodeApp)); + init_types(); + app->event_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); + + // Register view port in GUI + app->gui = furi_record_open(RECORD_GUI); + + app->view_dispatcher = view_dispatcher_alloc(); + view_dispatcher_enable_queue(app->view_dispatcher); + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + + app->main_menu = submenu_alloc(); + submenu_add_item(app->main_menu, "Load Barcode", SelectBarcodeItem, submenu_callback, app); + view_set_previous_callback(submenu_get_view(app->main_menu), exit_callback); + view_dispatcher_add_view(app->view_dispatcher, MainMenuView, submenu_get_view(app->main_menu)); + + submenu_add_item(app->main_menu, "Edit Barcode", EditBarcodeItem, submenu_callback, app); + + /***************************** + * Creating Text Input View + ******************************/ + app->text_input = text_input_alloc(); + view_set_previous_callback(text_input_get_view(app->text_input), create_view_callback); + view_dispatcher_add_view( + app->view_dispatcher, TextInputView, text_input_get_view(app->text_input)); + + /***************************** + * Creating Message View + ******************************/ + app->message_view = message_view_allocate(app); + view_dispatcher_add_view( + app->view_dispatcher, MessageErrorView, message_get_view(app->message_view)); + + /***************************** + * Creating Create View + ******************************/ + app->create_view = create_view_allocate(app); + submenu_add_item(app->main_menu, "Create Barcode", CreateBarcodeItem, submenu_callback, app); + view_set_previous_callback(create_get_view(app->create_view), main_menu_callback); + view_dispatcher_add_view( + app->view_dispatcher, CreateBarcodeView, create_get_view(app->create_view)); + + /***************************** + * Creating Barcode View + ******************************/ + app->barcode_view = barcode_view_allocate(app); + view_set_previous_callback(barcode_get_view(app->barcode_view), main_menu_callback); + view_dispatcher_add_view( + app->view_dispatcher, BarcodeView, barcode_get_view(app->barcode_view)); + + //switch view to submenu and run dispatcher + view_dispatcher_switch_to_view(app->view_dispatcher, MainMenuView); + view_dispatcher_run(app->view_dispatcher); + + free_app(app); + + return 0; +} diff --git a/applications/external/barcode_gen/barcode_app.h b/applications/external/barcode_gen/barcode_app.h new file mode 100644 index 000000000..3150bff1f --- /dev/null +++ b/applications/external/barcode_gen/barcode_app.h @@ -0,0 +1,91 @@ +#pragma once +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "barcode_utils.h" + +#define TAG "BARCODE" +#define VERSION "1.1" +#define FILE_VERSION "1" + +#define TEXT_BUFFER_SIZE 128 + +#define BARCODE_HEIGHT 50 +#define BARCODE_Y_START 3 + +//the folder where the encodings are located +#define BARCODE_DATA_FILE_DIR_PATH EXT_PATH("apps_data/barcode_data") + +//the folder where the codabar encoding table is located +#define CODABAR_DICT_FILE_PATH BARCODE_DATA_FILE_DIR_PATH "/codabar_encodings.txt" + +//the folder where the code 39 encoding table is located +#define CODE39_DICT_FILE_PATH BARCODE_DATA_FILE_DIR_PATH "/code39_encodings.txt" + +//the folder where the code 128 encoding table is located +#define CODE128_DICT_FILE_PATH BARCODE_DATA_FILE_DIR_PATH "/code128_encodings.txt" + +//the folder where the code 128 C encoding table is located +#define CODE128C_DICT_FILE_PATH BARCODE_DATA_FILE_DIR_PATH "/code128c_encodings.txt" + +//the folder where the user stores their barcodes +#define DEFAULT_USER_BARCODES EXT_PATH("apps_data/barcodes") + +//The extension barcode files use +#define BARCODE_EXTENSION ".txt" +#define BARCODE_EXTENSION_LENGTH 4 + +#include "views/barcode_view.h" +#include "views/create_view.h" +#include "views/message_view.h" +#include "barcode_validator.h" + +typedef struct BarcodeApp BarcodeApp; + +struct BarcodeApp { + Submenu* main_menu; + ViewDispatcher* view_dispatcher; + Gui* gui; + + FuriMessageQueue* event_queue; + + CreateView* create_view; + Barcode* barcode_view; + + MessageView* message_view; + TextInput* text_input; +}; + +enum SubmenuItems { + SelectBarcodeItem, + EditBarcodeItem, + + CreateBarcodeItem +}; + +enum Views { + TextInputView, + MessageErrorView, + MainMenuView, + CreateBarcodeView, + + BarcodeView +}; + +void submenu_callback(void* context, uint32_t index); + +uint32_t main_menu_callback(void* context); + +uint32_t exit_callback(void* context); + +int32_t barcode_main(void* p); diff --git a/applications/external/barcode_gen/barcode_utils.c b/applications/external/barcode_gen/barcode_utils.c new file mode 100644 index 000000000..31274c1fe --- /dev/null +++ b/applications/external/barcode_gen/barcode_utils.c @@ -0,0 +1,147 @@ +#include "barcode_utils.h" + +BarcodeTypeObj* barcode_type_objs[NUMBER_OF_BARCODE_TYPES] = {NULL}; + +void init_types() { + BarcodeTypeObj* upc_a = malloc(sizeof(BarcodeTypeObj)); + upc_a->name = "UPC-A"; + upc_a->type = UPCA; + upc_a->min_digits = 11; + upc_a->max_digits = 12; + upc_a->start_pos = 16; + barcode_type_objs[UPCA] = upc_a; + + BarcodeTypeObj* ean_8 = malloc(sizeof(BarcodeTypeObj)); + ean_8->name = "EAN-8"; + ean_8->type = EAN8; + ean_8->min_digits = 7; + ean_8->max_digits = 8; + ean_8->start_pos = 32; + barcode_type_objs[EAN8] = ean_8; + + BarcodeTypeObj* ean_13 = malloc(sizeof(BarcodeTypeObj)); + ean_13->name = "EAN-13"; + ean_13->type = EAN13; + ean_13->min_digits = 12; + ean_13->max_digits = 13; + ean_13->start_pos = 16; + barcode_type_objs[EAN13] = ean_13; + + BarcodeTypeObj* code_39 = malloc(sizeof(BarcodeTypeObj)); + code_39->name = "CODE-39"; + code_39->type = CODE39; + code_39->min_digits = 1; + code_39->max_digits = -1; + code_39->start_pos = 0; + barcode_type_objs[CODE39] = code_39; + + BarcodeTypeObj* code_128 = malloc(sizeof(BarcodeTypeObj)); + code_128->name = "CODE-128"; + code_128->type = CODE128; + code_128->min_digits = 1; + code_128->max_digits = -1; + code_128->start_pos = 0; + barcode_type_objs[CODE128] = code_128; + + BarcodeTypeObj* code_128c = malloc(sizeof(BarcodeTypeObj)); + code_128c->name = "CODE-128C"; + code_128c->type = CODE128C; + code_128c->min_digits = 2; + code_128c->max_digits = -1; + code_128c->start_pos = 0; + barcode_type_objs[CODE128C] = code_128c; + + BarcodeTypeObj* codabar = malloc(sizeof(BarcodeTypeObj)); + codabar->name = "Codabar"; + codabar->type = CODABAR; + codabar->min_digits = 1; + codabar->max_digits = -1; + codabar->start_pos = 0; + barcode_type_objs[CODABAR] = codabar; + + BarcodeTypeObj* unknown = malloc(sizeof(BarcodeTypeObj)); + unknown->name = "Unknown"; + unknown->type = UNKNOWN; + unknown->min_digits = 0; + unknown->max_digits = 0; + unknown->start_pos = 0; + barcode_type_objs[UNKNOWN] = unknown; +} + +void free_types() { + for(int i = 0; i < NUMBER_OF_BARCODE_TYPES; i++) { + free(barcode_type_objs[i]); + } +} + +BarcodeTypeObj* get_type(FuriString* type_string) { + if(furi_string_cmp_str(type_string, "UPC-A") == 0) { + return barcode_type_objs[UPCA]; + } + if(furi_string_cmp_str(type_string, "EAN-8") == 0) { + return barcode_type_objs[EAN8]; + } + if(furi_string_cmp_str(type_string, "EAN-13") == 0) { + return barcode_type_objs[EAN13]; + } + if(furi_string_cmp_str(type_string, "CODE-39") == 0) { + return barcode_type_objs[CODE39]; + } + if(furi_string_cmp_str(type_string, "CODE-128") == 0) { + return barcode_type_objs[CODE128]; + } + if(furi_string_cmp_str(type_string, "CODE-128C") == 0) { + return barcode_type_objs[CODE128C]; + } + if(furi_string_cmp_str(type_string, "Codabar") == 0) { + return barcode_type_objs[CODABAR]; + } + + return barcode_type_objs[UNKNOWN]; +} + +const char* get_error_code_name(ErrorCode error_code) { + switch(error_code) { + case WrongNumberOfDigits: + return "Wrong Number Of Digits"; + case InvalidCharacters: + return "Invalid Characters"; + case UnsupportedType: + return "Unsupported Type"; + case FileOpening: + return "File Opening Error"; + case InvalidFileData: + return "Invalid File Data"; + case MissingEncodingTable: + return "Missing Encoding Table"; + case EncodingTableError: + return "Encoding Table Error"; + case OKCode: + return "OK"; + default: + return "Unknown Code"; + }; +} + +const char* get_error_code_message(ErrorCode error_code) { + switch(error_code) { + case WrongNumberOfDigits: + return "Wrong # of characters"; + case InvalidCharacters: + return "Invalid characters"; + case UnsupportedType: + return "Unsupported barcode type"; + case FileOpening: + return "Could not open file"; + case InvalidFileData: + return "Invalid file data"; + case MissingEncodingTable: + return "Missing encoding table"; + case EncodingTableError: + return "Encoding table error"; + case OKCode: + return "OK"; + default: + return "Could not read barcode data"; + }; +} diff --git a/applications/external/barcode_gen/barcode_utils.h b/applications/external/barcode_gen/barcode_utils.h new file mode 100644 index 000000000..0a27785cf --- /dev/null +++ b/applications/external/barcode_gen/barcode_utils.h @@ -0,0 +1,55 @@ + +#pragma once +#include +#include + +#define NUMBER_OF_BARCODE_TYPES 8 + +typedef enum { + WrongNumberOfDigits, //There is too many or too few digits in the barcode + InvalidCharacters, //The barcode contains invalid characters + UnsupportedType, //the barcode type is not supported + FileOpening, //A problem occurred when opening the barcode data file + InvalidFileData, //One of the key in the file doesn't exist or there is a typo + MissingEncodingTable, //The encoding table txt for the barcode type is missing + EncodingTableError, //Something is wrong with the encoding table, probably missing data or typo + OKCode +} ErrorCode; + +typedef enum { + UPCA, + EAN8, + EAN13, + CODE39, + CODE128, + CODE128C, + CODABAR, + + UNKNOWN +} BarcodeType; + +typedef struct { + char* name; //The name of the barcode type + BarcodeType type; //The barcode type enum + int min_digits; //the minimum number of digits + int max_digits; //the maximum number of digits + int start_pos; //where to start drawing the barcode, set to -1 to dynamically draw barcode +} BarcodeTypeObj; + +typedef struct { + BarcodeTypeObj* type_obj; + int check_digit; //A place to store the check digit + FuriString* raw_data; //the data directly from the file + FuriString* correct_data; //the corrected/processed data + bool valid; //true if the raw data is correctly formatted, such as correct num of digits, valid characters, etc. + ErrorCode reason; //the reason why this barcode is invalid +} BarcodeData; + +//All available barcode types +extern BarcodeTypeObj* barcode_type_objs[NUMBER_OF_BARCODE_TYPES]; + +void init_types(); +void free_types(); +BarcodeTypeObj* get_type(FuriString* type_string); +const char* get_error_code_name(ErrorCode error_code); +const char* get_error_code_message(ErrorCode error_code); diff --git a/applications/external/barcode_gen/barcode_validator.c b/applications/external/barcode_gen/barcode_validator.c new file mode 100644 index 000000000..cb493f3e9 --- /dev/null +++ b/applications/external/barcode_gen/barcode_validator.c @@ -0,0 +1,532 @@ +#include "barcode_validator.h" + +void barcode_loader(BarcodeData* barcode_data) { + switch(barcode_data->type_obj->type) { + case UPCA: + case EAN8: + case EAN13: + ean_upc_loader(barcode_data); + break; + case CODE39: + code_39_loader(barcode_data); + break; + case CODE128: + code_128_loader(barcode_data); + break; + case CODE128C: + code_128c_loader(barcode_data); + break; + case CODABAR: + codabar_loader(barcode_data); + break; + case UNKNOWN: + barcode_data->reason = UnsupportedType; + barcode_data->valid = false; + default: + break; + } +} + +/** + * Calculates the check digit of a barcode if they have one + * @param barcode_data the barcode data + * @returns a check digit or -1 for either an invalid +*/ +int calculate_check_digit(BarcodeData* barcode_data) { + int check_digit = -1; + switch(barcode_data->type_obj->type) { + case UPCA: + case EAN8: + case EAN13: + check_digit = calculate_ean_upc_check_digit(barcode_data); + break; + case CODE39: + case CODE128: + case CODE128C: + case CODABAR: + case UNKNOWN: + default: + break; + } + + return check_digit; +} + +/** + * Calculates the check digit of barcode types UPC-A, EAN-8, & EAN-13 +*/ +int calculate_ean_upc_check_digit(BarcodeData* barcode_data) { + int check_digit = 0; + int odd = 0; + int even = 0; + + int length = barcode_data->type_obj->min_digits; + + //Get sum of odd digits + for(int i = 0; i < length; i += 2) { + odd += furi_string_get_char(barcode_data->raw_data, i) - '0'; + } + + //Get sum of even digits + for(int i = 1; i < length; i += 2) { + even += furi_string_get_char(barcode_data->raw_data, i) - '0'; + } + + if(barcode_data->type_obj->type == EAN13) { + check_digit = even * 3 + odd; + } else { + check_digit = odd * 3 + even; + } + + check_digit = check_digit % 10; + + return (10 - check_digit) % 10; +} + +/** + * Loads and validates Barcode Types EAN-8, EAN-13, and UPC-A + * barcode_data and its strings should already be allocated; +*/ +void ean_upc_loader(BarcodeData* barcode_data) { + int barcode_length = furi_string_size(barcode_data->raw_data); + + int min_digits = barcode_data->type_obj->min_digits; + int max_digit = barcode_data->type_obj->max_digits; + + //check the length of the barcode + if(barcode_length < min_digits || barcode_length > max_digit) { + barcode_data->reason = WrongNumberOfDigits; + barcode_data->valid = false; + return; + } + + //checks if the barcode contains any characters that aren't a number + for(int i = 0; i < barcode_length; i++) { + char character = furi_string_get_char(barcode_data->raw_data, i); + int digit = character - '0'; //convert the number into an int (also the index) + if(digit < 0 || digit > 9) { + barcode_data->reason = InvalidCharacters; + barcode_data->valid = false; + return; + } + } + + int check_digit = calculate_check_digit(barcode_data); + char check_digit_char = check_digit + '0'; + + barcode_data->check_digit = check_digit; + + //if the barcode length is at max length then we will verify if the check digit is correct + if(barcode_length == max_digit) { + //append the raw_data to the correct data string + furi_string_cat(barcode_data->correct_data, barcode_data->raw_data); + + //append the check digit to the correct data string + furi_string_set_char(barcode_data->correct_data, min_digits, check_digit_char); + } + //if the barcode length is at min length, we will calculate the check digit + if(barcode_length == min_digits) { + //append the raw_data to the correct data string + furi_string_cat(barcode_data->correct_data, barcode_data->raw_data); + + //append the check digit to the correct data string + furi_string_push_back(barcode_data->correct_data, check_digit_char); + } +} + +void code_39_loader(BarcodeData* barcode_data) { + int barcode_length = furi_string_size(barcode_data->raw_data); + + int min_digits = barcode_data->type_obj->min_digits; + + //check the length of the barcode, must contain atleast a character, + //this can have as many characters as it wants, it might not fit on the screen + if(barcode_length < min_digits) { + barcode_data->reason = WrongNumberOfDigits; + barcode_data->valid = false; + return; + } + + FuriString* barcode_bits = furi_string_alloc(); + FuriString* temp_string = furi_string_alloc(); + + //add starting and ending * + if(!furi_string_start_with(barcode_data->raw_data, "*")) { + furi_string_push_back(temp_string, '*'); + furi_string_cat(temp_string, barcode_data->raw_data); + furi_string_set(barcode_data->raw_data, temp_string); + } + + if(!furi_string_end_with(barcode_data->raw_data, "*")) { + furi_string_push_back(barcode_data->raw_data, '*'); + } + + furi_string_free(temp_string); + barcode_length = furi_string_size(barcode_data->raw_data); + + //Open Storage + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* ff = flipper_format_file_alloc(storage); + + if(!flipper_format_file_open_existing(ff, CODE39_DICT_FILE_PATH)) { + FURI_LOG_E(TAG, "Could not open file %s", CODE39_DICT_FILE_PATH); + barcode_data->reason = MissingEncodingTable; + barcode_data->valid = false; + } else { + FuriString* char_bits = furi_string_alloc(); + for(int i = 0; i < barcode_length; i++) { + char barcode_char = toupper(furi_string_get_char(barcode_data->raw_data, i)); + + //convert a char into a string so it used in flipper_format_read_string + char current_character[2]; + snprintf(current_character, 2, "%c", barcode_char); + + if(!flipper_format_read_string(ff, current_character, char_bits)) { + FURI_LOG_E(TAG, "Could not read \"%c\" string", barcode_char); + barcode_data->reason = InvalidCharacters; + barcode_data->valid = false; + break; + } else { + FURI_LOG_I( + TAG, "\"%c\" string: %s", barcode_char, furi_string_get_cstr(char_bits)); + furi_string_cat(barcode_bits, char_bits); + } + flipper_format_rewind(ff); + } + furi_string_free(char_bits); + } + + //Close Storage + flipper_format_free(ff); + furi_record_close(RECORD_STORAGE); + + furi_string_cat(barcode_data->correct_data, barcode_bits); + furi_string_free(barcode_bits); +} + +/** + * Loads a code 128 barcode + * + * Only supports character set B +*/ +void code_128_loader(BarcodeData* barcode_data) { + int barcode_length = furi_string_size(barcode_data->raw_data); + + //the start code for character set B + int start_code_value = 104; + + //The bits for the start code + const char* start_code_bits = "11010010000"; + + //The bits for the stop code + const char* stop_code_bits = "1100011101011"; + + int min_digits = barcode_data->type_obj->min_digits; + + /** + * A sum of all of the characters values + * Ex: + * Barcode Data : ABC + * A has a value of 33 + * B has a value of 34 + * C has a value of 35 + * + * the checksum_adder would be (33 * 1) + (34 * 2) + (35 * 3) + 104 = 310 + * + * Add 104 since we are using set B + */ + int checksum_adder = start_code_value; + /** + * Checksum digits is the number of characters it has read so far + * In the above example the checksum_digits would be 3 + */ + int checksum_digits = 0; + + //the calculated check digit + int final_check_digit = 0; + + //check the length of the barcode, must contain atleast a character, + //this can have as many characters as it wants, it might not fit on the screen + if(barcode_length < min_digits) { + barcode_data->reason = WrongNumberOfDigits; + barcode_data->valid = false; + return; + } + + //Open Storage + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* ff = flipper_format_file_alloc(storage); + + FuriString* barcode_bits = furi_string_alloc(); + + //add the start code + furi_string_cat(barcode_bits, start_code_bits); + + if(!flipper_format_file_open_existing(ff, CODE128_DICT_FILE_PATH)) { + FURI_LOG_E(TAG, "Could not open file %s", CODE128_DICT_FILE_PATH); + barcode_data->reason = MissingEncodingTable; + barcode_data->valid = false; + } else { + FuriString* value = furi_string_alloc(); + FuriString* char_bits = furi_string_alloc(); + for(int i = 0; i < barcode_length; i++) { + char barcode_char = furi_string_get_char(barcode_data->raw_data, i); + + //convert a char into a string so it used in flipper_format_read_string + char current_character[2]; + snprintf(current_character, 2, "%c", barcode_char); + + //get the value of the character + if(!flipper_format_read_string(ff, current_character, value)) { + FURI_LOG_E(TAG, "Could not read \"%c\" string", barcode_char); + barcode_data->reason = InvalidCharacters; + barcode_data->valid = false; + break; + } + //using the value of the character, get the characters bits + if(!flipper_format_read_string(ff, furi_string_get_cstr(value), char_bits)) { + FURI_LOG_E(TAG, "Could not read \"%c\" string", barcode_char); + barcode_data->reason = EncodingTableError; + barcode_data->valid = false; + break; + } else { + //add the bits to the full barcode + furi_string_cat(barcode_bits, char_bits); + + //calculate the checksum + checksum_digits += 1; + checksum_adder += (atoi(furi_string_get_cstr(value)) * checksum_digits); + + FURI_LOG_D( + TAG, + "\"%c\" string: %s : %s : %d : %d : %d", + barcode_char, + furi_string_get_cstr(char_bits), + furi_string_get_cstr(value), + checksum_digits, + (atoi(furi_string_get_cstr(value)) * checksum_digits), + checksum_adder); + } + //bring the file pointer back to the beginning + flipper_format_rewind(ff); + } + + //calculate the check digit and convert it into a c string for lookup in the encoding table + final_check_digit = checksum_adder % 103; + int length = snprintf(NULL, 0, "%d", final_check_digit); + char* final_check_digit_string = malloc(length + 1); + snprintf(final_check_digit_string, length + 1, "%d", final_check_digit); + + //after the checksum has been calculated, add the bits to the full barcode + if(!flipper_format_read_string(ff, final_check_digit_string, char_bits)) { + FURI_LOG_E(TAG, "Could not read \"%s\" string", final_check_digit_string); + barcode_data->reason = EncodingTableError; + barcode_data->valid = false; + } else { + //add the check digit bits to the full barcode + furi_string_cat(barcode_bits, char_bits); + + FURI_LOG_D( + TAG, + "\"%s\" string: %s", + final_check_digit_string, + furi_string_get_cstr(char_bits)); + } + + free(final_check_digit_string); + furi_string_free(value); + furi_string_free(char_bits); + } + + //add the stop code + furi_string_cat(barcode_bits, stop_code_bits); + + //Close Storage + flipper_format_free(ff); + furi_record_close(RECORD_STORAGE); + + furi_string_cat(barcode_data->correct_data, barcode_bits); + furi_string_free(barcode_bits); +} + +/** + * Loads a code 128 C barcode +*/ +void code_128c_loader(BarcodeData* barcode_data) { + int barcode_length = furi_string_size(barcode_data->raw_data); + + //the start code for character set C + int start_code_value = 105; + + //The bits for the start code + const char* start_code_bits = "11010011100"; + + //The bits for the stop code + const char* stop_code_bits = "1100011101011"; + + int min_digits = barcode_data->type_obj->min_digits; + + int checksum_adder = start_code_value; + int checksum_digits = 0; + + //the calculated check digit + int final_check_digit = 0; + + // check the length of the barcode, must contain atleast 2 character, + // this can have as many characters as it wants, it might not fit on the screen + // code 128 C: the length must be even + if((barcode_length < min_digits) || (barcode_length & 1)) { + barcode_data->reason = WrongNumberOfDigits; + barcode_data->valid = false; + return; + } + //Open Storage + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* ff = flipper_format_file_alloc(storage); + + FuriString* barcode_bits = furi_string_alloc(); + + //add the start code + furi_string_cat(barcode_bits, start_code_bits); + + if(!flipper_format_file_open_existing(ff, CODE128C_DICT_FILE_PATH)) { + FURI_LOG_E(TAG, "c128c Could not open file %s", CODE128C_DICT_FILE_PATH); + barcode_data->reason = MissingEncodingTable; + barcode_data->valid = false; + } else { + FuriString* value = furi_string_alloc(); + FuriString* char_bits = furi_string_alloc(); + for(int i = 0; i < barcode_length; i += 2) { + char barcode_char1 = furi_string_get_char(barcode_data->raw_data, i); + char barcode_char2 = furi_string_get_char(barcode_data->raw_data, i + 1); + FURI_LOG_I(TAG, "c128c bc1='%c' bc2='%c'", barcode_char1, barcode_char2); + + char current_chars[4]; + snprintf(current_chars, 3, "%c%c", barcode_char1, barcode_char2); + FURI_LOG_I(TAG, "c128c current_chars='%s'", current_chars); + + //using the value of the characters, get the characters bits + if(!flipper_format_read_string(ff, current_chars, char_bits)) { + FURI_LOG_E(TAG, "c128c Could not read \"%s\" string", current_chars); + barcode_data->reason = EncodingTableError; + barcode_data->valid = false; + break; + } else { + //add the bits to the full barcode + furi_string_cat(barcode_bits, char_bits); + + // calculate the checksum + checksum_digits += 1; + checksum_adder += (atoi(current_chars) * checksum_digits); + + FURI_LOG_I( + TAG, + "c128c \"%s\" string: %s : %s : %d : %d : %d", + current_chars, + furi_string_get_cstr(char_bits), + furi_string_get_cstr(value), + checksum_digits, + (atoi(furi_string_get_cstr(value)) * checksum_digits), + checksum_adder); + } + //bring the file pointer back to the begining + flipper_format_rewind(ff); + } + //calculate the check digit and convert it into a c string for lookup in the encoding table + final_check_digit = checksum_adder % 103; + FURI_LOG_I(TAG, "c128c finale_check_digit=%d", final_check_digit); + + int length = snprintf(NULL, 0, "%d", final_check_digit); + if(final_check_digit < 100) length = 2; + char* final_check_digit_string = malloc(length + 1); + snprintf(final_check_digit_string, length + 1, "%02d", final_check_digit); + + //after the checksum has been calculated, add the bits to the full barcode + if(!flipper_format_read_string(ff, final_check_digit_string, char_bits)) { + FURI_LOG_E(TAG, "c128c cksum Could not read \"%s\" string", final_check_digit_string); + barcode_data->reason = EncodingTableError; + barcode_data->valid = false; + } else { + //add the check digit bits to the full barcode + furi_string_cat(barcode_bits, char_bits); + + FURI_LOG_I( + TAG, + "check digit \"%s\" string: %s", + final_check_digit_string, + furi_string_get_cstr(char_bits)); + } + + free(final_check_digit_string); + furi_string_free(value); + furi_string_free(char_bits); + } + + //add the stop code + furi_string_cat(barcode_bits, stop_code_bits); + + //Close Storage + flipper_format_free(ff); + furi_record_close(RECORD_STORAGE); + + FURI_LOG_I(TAG, "c128c %s", furi_string_get_cstr(barcode_bits)); + furi_string_cat(barcode_data->correct_data, barcode_bits); + furi_string_free(barcode_bits); +} + +void codabar_loader(BarcodeData* barcode_data) { + int barcode_length = furi_string_size(barcode_data->raw_data); + + int min_digits = barcode_data->type_obj->min_digits; + + //check the length of the barcode, must contain atleast a character, + //this can have as many characters as it wants, it might not fit on the screen + if(barcode_length < min_digits) { + barcode_data->reason = WrongNumberOfDigits; + barcode_data->valid = false; + return; + } + + FuriString* barcode_bits = furi_string_alloc(); + + barcode_length = furi_string_size(barcode_data->raw_data); + + //Open Storage + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* ff = flipper_format_file_alloc(storage); + + if(!flipper_format_file_open_existing(ff, CODABAR_DICT_FILE_PATH)) { + FURI_LOG_E(TAG, "Could not open file %s", CODABAR_DICT_FILE_PATH); + barcode_data->reason = MissingEncodingTable; + barcode_data->valid = false; + } else { + FuriString* char_bits = furi_string_alloc(); + for(int i = 0; i < barcode_length; i++) { + char barcode_char = toupper(furi_string_get_char(barcode_data->raw_data, i)); + + //convert a char into a string so it used in flipper_format_read_string + char current_character[2]; + snprintf(current_character, 2, "%c", barcode_char); + + if(!flipper_format_read_string(ff, current_character, char_bits)) { + FURI_LOG_E(TAG, "Could not read \"%c\" string", barcode_char); + barcode_data->reason = InvalidCharacters; + barcode_data->valid = false; + break; + } else { + FURI_LOG_I( + TAG, "\"%c\" string: %s", barcode_char, furi_string_get_cstr(char_bits)); + furi_string_cat(barcode_bits, char_bits); + } + flipper_format_rewind(ff); + } + furi_string_free(char_bits); + } + + //Close Storage + flipper_format_free(ff); + furi_record_close(RECORD_STORAGE); + + furi_string_cat(barcode_data->correct_data, barcode_bits); + furi_string_free(barcode_bits); +} diff --git a/applications/external/barcode_gen/barcode_validator.h b/applications/external/barcode_gen/barcode_validator.h new file mode 100644 index 000000000..2138124dd --- /dev/null +++ b/applications/external/barcode_gen/barcode_validator.h @@ -0,0 +1,15 @@ +#pragma once + +#include "barcode_app.h" + +int calculate_check_digit(BarcodeData* barcode_data); +int calculate_ean_upc_check_digit(BarcodeData* barcode_data); +void ean_upc_loader(BarcodeData* barcode_data); +void upc_a_loader(BarcodeData* barcode_data); +void ean_8_loader(BarcodeData* barcode_data); +void ean_13_loader(BarcodeData* barcode_data); +void code_39_loader(BarcodeData* barcode_data); +void code_128_loader(BarcodeData* barcode_data); +void code_128c_loader(BarcodeData* barcode_data); +void codabar_loader(BarcodeData* barcode_data); +void barcode_loader(BarcodeData* barcode_data); diff --git a/applications/external/barcode_gen/encodings.c b/applications/external/barcode_gen/encodings.c new file mode 100644 index 000000000..764fde796 --- /dev/null +++ b/applications/external/barcode_gen/encodings.c @@ -0,0 +1,52 @@ +#include "encodings.h" + +const char EAN_13_STRUCTURE_CODES[10][6] = { + "LLLLLL", + "LLGLGG", + "LLGGLG", + "LLGGGL", + "LGLLGG", + "LGGLLG", + "LGGGLL", + "LGLGLG", + "LGLGGL", + "LGGLGL"}; + +const char UPC_EAN_L_CODES[10][8] = { + "0001101", // 0 + "0011001", // 1 + "0010011", // 2 + "0111101", // 3 + "0100011", // 4 + "0110001", // 5 + "0101111", // 6 + "0111011", // 7 + "0110111", // 8 + "0001011" // 9 +}; + +const char EAN_G_CODES[10][8] = { + "0100111", // 0 + "0110011", // 1 + "0011011", // 2 + "0100001", // 3 + "0011101", // 4 + "0111001", // 5 + "0000101", // 6 + "0010001", // 7 + "0001001", // 8 + "0010111" // 9 +}; + +const char UPC_EAN_R_CODES[10][8] = { + "1110010", // 0 + "1100110", // 1 + "1101100", // 2 + "1000010", // 3 + "1011100", // 4 + "1001110", // 5 + "1010000", // 6 + "1000100", // 7 + "1001000", // 8 + "1110100" // 9 +}; \ No newline at end of file diff --git a/applications/external/barcode_gen/encodings.h b/applications/external/barcode_gen/encodings.h new file mode 100644 index 000000000..c5b8d61ff --- /dev/null +++ b/applications/external/barcode_gen/encodings.h @@ -0,0 +1,6 @@ +#pragma once + +extern const char EAN_13_STRUCTURE_CODES[10][6]; +extern const char UPC_EAN_L_CODES[10][8]; +extern const char EAN_G_CODES[10][8]; +extern const char UPC_EAN_R_CODES[10][8]; \ No newline at end of file diff --git a/applications/external/barcode_gen/images/barcode_10.png b/applications/external/barcode_gen/images/barcode_10.png new file mode 100644 index 0000000000000000000000000000000000000000..32d4971ad355874ef62be4cf9eb9586fdef48ad7 GIT binary patch literal 161 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2ZGmxy8xzq=w7>k44ofy`glX(f`u%tWsIx;Y9 z?C1WI$O`1M1^9%x0_p$%|1Z5c|1OZlS>O>_%)r2R2!t6$HM|;tf-0Uajv*44lM@6) zL{btGSSB$ks7+8-IB?=bM{COorw}H~CLRWdH%zMMw$JzmRL|h)>gTe~DWM4f+!!l! literal 0 HcmV?d00001 diff --git a/applications/external/barcode_gen/screenshots/Codabar Data Example.png b/applications/external/barcode_gen/screenshots/Codabar Data Example.png new file mode 100644 index 0000000000000000000000000000000000000000..907dfd901317f06d5cc034e9a30866959506edb1 GIT binary patch literal 1672 zcmeAS@N?(olHy`uVBq!ia0y~yU;;8388|?c*QZDWAjMhW5n0T@z;^_M8K-LVNi#68 zS$VoRhE&XXdpA1owgrR3Mb8OOQ)(k!u1){S^4Yu~-EC!r*R{vHGyFV8zV!95JQ6o(bS6V7uj|0R#)|!eqY|!WcvJK z;PcauZ)}q~EO-8-jWOJ$6%TD2mVUL)u9@-J}Ub`mpevDPx>g*NM z=N+$lZ5va|Tl@a|hcoF_Y3B~^J-+Ao`>x$a%y8#fc9roUOT4sYczx)5+1I}f+I=&# z4N=YhGViXp+k4%8&q~*Bzjt2ppa1q<{(sL@S3KKe{n_&Ld?eLXmQ@V)Gk4lsI2=3^ zw@i}3VG09-3nzoa70q|7PpWDfo*N^X{Pj5l!;hOUYM0jil)0*>%=F+33xk3e1H&SG z)V*tW+?g7FBiTuO992oz9sowLeSO^LSf<6toew^)-1ub0=hXh5!gyk9%(48rxx zzA;R@c5BT1#QU)vdMklb2HR2TjU?yO6+^IS?$ePgqBwu-m}h`Pw-oyG#BUuWxQMjwp`H`C51H@tzqsZzs${3X8;Q2IJRX7hhM)-)hcKZE?6d z|C!X!i+kH`*B!S%eEvfhQtbJCv}Dk@{n*YjJ7=rd{_pp0&w6h8D!rleoxn_v?+Hxubln;c{&>;BLef|*QB|8 z5AKQWum0I_kfFea=wgQW0*MHfE4D9YXZZi^*T=87llR%*pKlkPQ5z`%M)B0Xno zJ;V7FgwIp?8!qqswYTBhjpPQ~8HfnK$^1a%+@59juUOt^pBD2MV(V9UHJH8P33ft*|iGXh-O*l}B63G!RV) z9eV&(9?|{*i)$Mo4uloD;#QrdKS;1$8Mf=t7I}=&biPDPzIDq@n?2NZR zsA^yO?QZj76MjjE4Vjx~+7d0_p2XD!b)V+(^UFMylS;wCDy7&f;A zEUR_H!bX^B#`7@fj)E@&pbool=wVMDy10ZFN;)k!HS?QiqUrp4&TJ#GJ2QfWyw2_u zn;5j?0J$#kVvHTe$UxV6EvFXWr{PWODK%!EB#PFaDR@iP&odL)g&Nzt(YxV$G)Z;b zMUHJ6#~LWpTGcOUQ#k`-%tFhrGF|M`XCvdcvN50fCeUM`<>l=VwCONW?ghCYqgzvJ zeuPKj^sh`)rFe;Yy`zCcJ@TMn0p*lTYq4uvuXrX(Qm9eQ4}Vqd*F|W>_jXoJRk%x6 z@BqXU0N);fgRw&Q5~4wsNAx?r#Na>a(U;fjS@qOJmtC%Ej?Aay)(g1wMgt!5HWv|H z=WL^DpjIA9z~xnS0{;mricDER@%wu8X6^l-fkN1~h&ys`YZ=F!n$_e)rrEf) z$^j-htx^#j*tkuEBdrDn)Y(WEnp9>2QgUEhReMrN-@8+$@ITR?-cSwctF^0Kb!iCm zHB6bdFx-M>6N|ZmVH!rUu@H*-n*zs!-{YaAi`jj;RUEaMD~YlUKm5EtH*=iF9+68) z2Bkt4q}U*b?46Ie9Xv5-xs!jXd(>N(O3qP|43(;DT~4`muF;{g&<{J~EfsE|Z8G}W zH7C|T9QC}jmU=gr-ZWMqGopw2f_r;Z&-0RXZYTSoqH3MT%DPm3Sv>pCUu1 zl4@I?uVBV}0Rm?u)^1kThW>X4o}#iiN`ogj@(2Nx@;s=NDieIGGv$w{a z_#U6Zh=k?V+Og0>uA(h_mF;-`e+7b!fHHLJ1eI`N1y=o(R9m`zegAW5 z2HWqO{TUjHIT#$KFfh1q;-PH&><;|>`}z3w>E{)q_NBkQ>7x8z(z1QxM&;*wzD7hp zmznwe#p?}bWtr>G7rwqB7;fJ_v(oL&*Lw3}U$k^IdiTD4yxi_P^-<@H zKUo=^=VDOsVqjRr!k}uGMS-v2W>+w^asLAucWW)t~#lw{dgg;Bx3M%^>bP0l+XkK2Z=AV literal 0 HcmV?d00001 diff --git a/applications/external/barcode_gen/screenshots/Flipper Box Barcode.png b/applications/external/barcode_gen/screenshots/Flipper Box Barcode.png new file mode 100644 index 0000000000000000000000000000000000000000..8dbd7d2a9c1b0564a1a2188e6de48fe9370ad426 GIT binary patch literal 1372 zcmeAS@N?(olHy`uVBq!ia0y~yU;;8388|?c*QZDWAjMhW5n0T@z;^_M8K-LVNi#68 z@_D*AhE&XXd)M3VwE=^}MfN1iHT|v|?5;K6<0%!=419F!rEPl4Dzo{=UTEdb-t)Jv z{vY##=e6J17!2AO7+g3R9H!u*t{i{EYw`N;>*w#^AAc>CYrF63E%)kq`=3jBRd379 z-s`?S_e)jy?Ka!>dtZHATWTxy@t&OQ;c(gGf{#D1`af6j{LRmQ7N6e9ozB1z$iyI^ zI!cWO!H`M?`TOtB-@N_dl<({3@82)~J@s|rtRCTSD|&^IQndW$us;^dx21lGx!qB@ zclEdI^F?m@|6f!WO;4S-@zV^;UF#x`Kk7^V9cszU&>_Uopuxz{QS~aF!Tq^>`q}OC zBA<2qon`ue_w%3Sk$~yq>ajDi76?7FzF9ZuDnE;s&yBsGoVxYB z>H2N@L_AKW^J5uTZKKS~}5{XBJZ&y8os_ov?b{P*>0yYPxoyXX8zo#$ z{h9gt{2Bvsn@6?Mg@5O)j2BYgfF$?abI_=KPCw;X0F^`vvZs1y6c9o=N(pl*O@i*eBpG%&FQh5>$Y!9k4^tp tzSVHE5mM-0wqs^U&_fcySC0H=oN}tJOPWI=6Ifg_c)I$ztaD0e0suzQQWgLJ literal 0 HcmV?d00001 diff --git a/applications/external/barcode_gen/views/barcode_view.c b/applications/external/barcode_gen/views/barcode_view.c new file mode 100644 index 000000000..55ab52046 --- /dev/null +++ b/applications/external/barcode_gen/views/barcode_view.c @@ -0,0 +1,510 @@ +#include "../barcode_app.h" +#include "barcode_view.h" +#include "../encodings.h" + +/** + * @brief Draws a single bit from a barcode at a specified location + * @param canvas + * @param bit a 1 or a 0 to signify a bit of data + * @param x the top left x coordinate + * @param y the top left y coordinate + * @param width the width of the bit + * @param height the height of the bit + */ +static void draw_bit(Canvas* canvas, int bit, int x, int y, int width, int height) { + if(bit == 1) { + canvas_set_color(canvas, ColorBlack); + } else { + canvas_set_color(canvas, ColorWhite); + } + canvas_draw_box(canvas, x, y, width, height); +} + +/** + * +*/ +static void draw_error_str(Canvas* canvas, const char* error) { + canvas_clear(canvas); + canvas_draw_str_aligned(canvas, 62, 30, AlignCenter, AlignCenter, error); +} + +/** + * @param bits a string of 1's and 0's + * @returns the x coordinate after the bits have been drawn, useful for drawing the next section of bits +*/ +static int draw_bits(Canvas* canvas, const char* bits, int x, int y, int width, int height) { + int bits_length = strlen(bits); + for(int i = 0; i < bits_length; i++) { + char c = bits[i]; + int num = c - '0'; + + draw_bit(canvas, num, x, y, width, height); + + x += width; + } + return x; +} + +/** + * Draws an EAN-8 type barcode, does not check if the barcode is valid + * @param canvas the canvas + * @param barcode_digits the digits in the barcode, must be 8 characters long +*/ +static void draw_ean_8(Canvas* canvas, BarcodeData* barcode_data) { + FuriString* barcode_digits = barcode_data->correct_data; + BarcodeTypeObj* type_obj = barcode_data->type_obj; + + int barcode_length = furi_string_size(barcode_digits); + + int x = type_obj->start_pos; + int y = BARCODE_Y_START; + int width = 1; + int height = BARCODE_HEIGHT; + + //the guard patterns for the beginning, center, ending + const char* end_bits = "101"; + const char* center_bits = "01010"; + + //draw the starting guard pattern + x = draw_bits(canvas, end_bits, x, y, width, height + 5); + + FuriString* code_part = furi_string_alloc(); + + //loop through each digit, find the encoding, and draw it + for(int i = 0; i < barcode_length; i++) { + char current_digit = furi_string_get_char(barcode_digits, i); + + //the actual number and the index of the bits + int index = current_digit - '0'; + //use the L-codes for the first 4 digits and the R-Codes for the last 4 digits + if(i <= 3) { + furi_string_set_str(code_part, UPC_EAN_L_CODES[index]); + } else { + furi_string_set_str(code_part, UPC_EAN_R_CODES[index]); + } + + //convert the current_digit char into a string so it can be printed + char current_digit_string[2]; + snprintf(current_digit_string, 2, "%c", current_digit); + + //set the canvas color to black to print the digit + canvas_set_color(canvas, ColorBlack); + canvas_draw_str(canvas, x + 1, y + height + 8, current_digit_string); + + //draw the bits of the barcode + x = draw_bits(canvas, furi_string_get_cstr(code_part), x, y, width, height); + + //if the index has reached 3, that means 4 digits have been drawn and now draw the center guard pattern + if(i == 3) { + x = draw_bits(canvas, center_bits, x, y, width, height + 5); + } + } + furi_string_free(code_part); + + //draw the ending guard pattern + x = draw_bits(canvas, end_bits, x, y, width, height + 5); +} + +static void draw_ean_13(Canvas* canvas, BarcodeData* barcode_data) { + FuriString* barcode_digits = barcode_data->correct_data; + BarcodeTypeObj* type_obj = barcode_data->type_obj; + + int barcode_length = furi_string_size(barcode_digits); + + int x = type_obj->start_pos; + int y = BARCODE_Y_START; + int width = 1; + int height = BARCODE_HEIGHT; + + //the guard patterns for the beginning, center, ending + const char* end_bits = "101"; + const char* center_bits = "01010"; + + //draw the starting guard pattern + x = draw_bits(canvas, end_bits, x, y, width, height + 5); + + FuriString* left_structure = furi_string_alloc(); + FuriString* code_part = furi_string_alloc(); + + //loop through each digit, find the encoding, and draw it + for(int i = 0; i < barcode_length; i++) { + char current_digit = furi_string_get_char(barcode_digits, i); + int index = current_digit - '0'; + + if(i == 0) { + furi_string_set_str(left_structure, EAN_13_STRUCTURE_CODES[index]); + + //convert the current_digit char into a string so it can be printed + char current_digit_string[2]; + snprintf(current_digit_string, 2, "%c", current_digit); + + //set the canvas color to black to print the digit + canvas_set_color(canvas, ColorBlack); + canvas_draw_str(canvas, x - 10, y + height + 8, current_digit_string); + + continue; + } else { + //use the L-codes for the first 6 digits and the R-Codes for the last 6 digits + if(i <= 6) { + //get the encoding type at the current barcode bit position + char encoding_type = furi_string_get_char(left_structure, i - 1); + if(encoding_type == 'L') { + furi_string_set_str(code_part, UPC_EAN_L_CODES[index]); + } else { + furi_string_set_str(code_part, EAN_G_CODES[index]); + } + } else { + furi_string_set_str(code_part, UPC_EAN_R_CODES[index]); + } + + //convert the current_digit char into a string so it can be printed + char current_digit_string[2]; + snprintf(current_digit_string, 2, "%c", current_digit); + + //set the canvas color to black to print the digit + canvas_set_color(canvas, ColorBlack); + canvas_draw_str(canvas, x + 1, y + height + 8, current_digit_string); + + //draw the bits of the barcode + x = draw_bits(canvas, furi_string_get_cstr(code_part), x, y, width, height); + + //if the index has reached 6, that means 6 digits have been drawn and we now draw the center guard pattern + if(i == 6) { + x = draw_bits(canvas, center_bits, x, y, width, height + 5); + } + } + } + + furi_string_free(left_structure); + furi_string_free(code_part); + + //draw the ending guard pattern + x = draw_bits(canvas, end_bits, x, y, width, height + 5); +} + +/** + * Draw a UPC-A barcode +*/ +static void draw_upc_a(Canvas* canvas, BarcodeData* barcode_data) { + FuriString* barcode_digits = barcode_data->correct_data; + BarcodeTypeObj* type_obj = barcode_data->type_obj; + + int barcode_length = furi_string_size(barcode_digits); + + int x = type_obj->start_pos; + int y = BARCODE_Y_START; + int width = 1; + int height = BARCODE_HEIGHT; + + //the guard patterns for the beginning, center, ending + char* end_bits = "101"; + char* center_bits = "01010"; + + //draw the starting guard pattern + x = draw_bits(canvas, end_bits, x, y, width, height + 5); + + FuriString* code_part = furi_string_alloc(); + + //loop through each digit, find the encoding, and draw it + for(int i = 0; i < barcode_length; i++) { + char current_digit = furi_string_get_char(barcode_digits, i); + int index = current_digit - '0'; //convert the number into an int (also the index) + + //use the L-codes for the first 6 digits and the R-Codes for the last 6 digits + if(i <= 5) { + furi_string_set_str(code_part, UPC_EAN_L_CODES[index]); + } else { + furi_string_set_str(code_part, UPC_EAN_R_CODES[index]); + } + + //convert the current_digit char into a string so it can be printed + char current_digit_string[2]; + snprintf(current_digit_string, 2, "%c", current_digit); + + //set the canvas color to black to print the digit + canvas_set_color(canvas, ColorBlack); + canvas_draw_str(canvas, x + 1, y + height + 8, current_digit_string); + + //draw the bits of the barcode + x = draw_bits(canvas, furi_string_get_cstr(code_part), x, y, width, height); + + //if the index has reached 6, that means 6 digits have been drawn and we now draw the center guard pattern + if(i == 5) { + x = draw_bits(canvas, center_bits, x, y, width, height + 5); + } + } + + furi_string_free(code_part); + + //draw the ending guard pattern + x = draw_bits(canvas, end_bits, x, y, width, height + 5); +} + +static void draw_code_39(Canvas* canvas, BarcodeData* barcode_data) { + FuriString* raw_data = barcode_data->raw_data; + FuriString* barcode_digits = barcode_data->correct_data; + //BarcodeTypeObj* type_obj = barcode_data->type_obj; + + int barcode_length = furi_string_size(barcode_digits); + int total_pixels = 0; + + for(int i = 0; i < barcode_length; i++) { + //1 for wide, 0 for narrow + char wide_or_narrow = furi_string_get_char(barcode_digits, i); + int wn_digit = wide_or_narrow - '0'; //wide(1) or narrow(0) digit + + if(wn_digit == 1) { + total_pixels += 3; + } else { + total_pixels += 1; + } + if((i + 1) % 9 == 0) { + total_pixels += 1; + } + } + + int x = (128 - total_pixels) / 2; + int y = BARCODE_Y_START; + int width = 1; + int height = BARCODE_HEIGHT; + bool filled_in = true; + + //set the canvas color to black to print the digit + canvas_set_color(canvas, ColorBlack); + // canvas_draw_str_aligned(canvas, 62, 30, AlignCenter, AlignCenter, error); + canvas_draw_str_aligned( + canvas, 62, y + height + 8, AlignCenter, AlignBottom, furi_string_get_cstr(raw_data)); + + for(int i = 0; i < barcode_length; i++) { + //1 for wide, 0 for narrow + char wide_or_narrow = furi_string_get_char(barcode_digits, i); + int wn_digit = wide_or_narrow - '0'; //wide(1) or narrow(0) digit + + if(filled_in) { + if(wn_digit == 1) { + x = draw_bits(canvas, "111", x, y, width, height); + } else { + x = draw_bits(canvas, "1", x, y, width, height); + } + filled_in = false; + } else { + if(wn_digit == 1) { + x = draw_bits(canvas, "000", x, y, width, height); + } else { + x = draw_bits(canvas, "0", x, y, width, height); + } + filled_in = true; + } + if((i + 1) % 9 == 0) { + x = draw_bits(canvas, "0", x, y, width, height); + filled_in = true; + } + } +} + +static void draw_code_128(Canvas* canvas, BarcodeData* barcode_data) { + FuriString* raw_data = barcode_data->raw_data; + FuriString* barcode_digits = barcode_data->correct_data; + + int barcode_length = furi_string_size(barcode_digits); + + int x = (128 - barcode_length) / 2; + int y = BARCODE_Y_START; + int width = 1; + int height = BARCODE_HEIGHT; + + x = draw_bits(canvas, furi_string_get_cstr(barcode_digits), x, y, width, height); + + //set the canvas color to black to print the digit + canvas_set_color(canvas, ColorBlack); + // canvas_draw_str_aligned(canvas, 62, 30, AlignCenter, AlignCenter, error); + canvas_draw_str_aligned( + canvas, 62, y + height + 8, AlignCenter, AlignBottom, furi_string_get_cstr(raw_data)); +} + +static void draw_codabar(Canvas* canvas, BarcodeData* barcode_data) { + FuriString* raw_data = barcode_data->raw_data; + FuriString* barcode_digits = barcode_data->correct_data; + //BarcodeTypeObj* type_obj = barcode_data->type_obj; + + int barcode_length = furi_string_size(barcode_digits); + int total_pixels = 0; + + for(int i = 0; i < barcode_length; i++) { + //1 for wide, 0 for narrow + char wide_or_narrow = furi_string_get_char(barcode_digits, i); + int wn_digit = wide_or_narrow - '0'; //wide(1) or narrow(0) digit + + if(wn_digit == 1) { + total_pixels += 3; + } else { + total_pixels += 1; + } + if((i + 1) % 7 == 0) { + total_pixels += 1; + } + } + + int x = (128 - total_pixels) / 2; + int y = BARCODE_Y_START; + int width = 1; + int height = BARCODE_HEIGHT; + bool filled_in = true; + + //set the canvas color to black to print the digit + canvas_set_color(canvas, ColorBlack); + // canvas_draw_str_aligned(canvas, 62, 30, AlignCenter, AlignCenter, error); + canvas_draw_str_aligned( + canvas, 62, y + height + 8, AlignCenter, AlignBottom, furi_string_get_cstr(raw_data)); + + for(int i = 0; i < barcode_length; i++) { + //1 for wide, 0 for narrow + char wide_or_narrow = furi_string_get_char(barcode_digits, i); + int wn_digit = wide_or_narrow - '0'; //wide(1) or narrow(0) digit + + if(filled_in) { + if(wn_digit == 1) { + x = draw_bits(canvas, "111", x, y, width, height); + } else { + x = draw_bits(canvas, "1", x, y, width, height); + } + filled_in = false; + } else { + if(wn_digit == 1) { + x = draw_bits(canvas, "000", x, y, width, height); + } else { + x = draw_bits(canvas, "0", x, y, width, height); + } + filled_in = true; + } + if((i + 1) % 7 == 0) { + x = draw_bits(canvas, "0", x, y, width, height); + filled_in = true; + } + } +} + +static void barcode_draw_callback(Canvas* canvas, void* ctx) { + furi_assert(ctx); + BarcodeModel* barcode_model = ctx; + BarcodeData* data = barcode_model->data; + // const char* barcode_digits =; + + canvas_clear(canvas); + if(data->valid) { + switch(data->type_obj->type) { + case UPCA: + draw_upc_a(canvas, data); + break; + case EAN8: + draw_ean_8(canvas, data); + break; + case EAN13: + draw_ean_13(canvas, data); + break; + case CODE39: + draw_code_39(canvas, data); + break; + case CODE128: + case CODE128C: + draw_code_128(canvas, data); + break; + case CODABAR: + draw_codabar(canvas, data); + break; + case UNKNOWN: + default: + break; + } + } else { + switch(data->reason) { + case WrongNumberOfDigits: + draw_error_str(canvas, "Wrong # of characters"); + break; + case InvalidCharacters: + draw_error_str(canvas, "Invalid characters"); + break; + case UnsupportedType: + draw_error_str(canvas, "Unsupported barcode type"); + break; + case FileOpening: + draw_error_str(canvas, "Could not open file"); + break; + case InvalidFileData: + draw_error_str(canvas, "Invalid file data"); + break; + case MissingEncodingTable: + draw_error_str(canvas, "Missing encoding table"); + break; + case EncodingTableError: + draw_error_str(canvas, "Encoding table error"); + break; + default: + draw_error_str(canvas, "Could not read barcode data"); + break; + } + } +} + +bool barcode_input_callback(InputEvent* input_event, void* ctx) { + UNUSED(ctx); + //furi_assert(ctx); + + //Barcode* test_view_object = ctx; + + if(input_event->key == InputKeyBack) { + return false; + } else { + return true; + } +} + +Barcode* barcode_view_allocate(BarcodeApp* barcode_app) { + furi_assert(barcode_app); + + Barcode* barcode = malloc(sizeof(Barcode)); + + barcode->view = view_alloc(); + barcode->barcode_app = barcode_app; + + view_set_context(barcode->view, barcode); + view_allocate_model(barcode->view, ViewModelTypeLocking, sizeof(BarcodeModel)); + view_set_draw_callback(barcode->view, barcode_draw_callback); + view_set_input_callback(barcode->view, barcode_input_callback); + + return barcode; +} + +void barcode_free_model(Barcode* barcode) { + with_view_model( + barcode->view, + BarcodeModel * model, + { + if(model->file_path != NULL) { + furi_string_free(model->file_path); + } + if(model->data != NULL) { + if(model->data->raw_data != NULL) { + furi_string_free(model->data->raw_data); + } + if(model->data->correct_data != NULL) { + furi_string_free(model->data->correct_data); + } + free(model->data); + } + }, + false); +} + +void barcode_free(Barcode* barcode) { + furi_assert(barcode); + + barcode_free_model(barcode); + view_free(barcode->view); + free(barcode); +} + +View* barcode_get_view(Barcode* barcode) { + furi_assert(barcode); + return barcode->view; +} diff --git a/applications/external/barcode_gen/views/barcode_view.h b/applications/external/barcode_gen/views/barcode_view.h new file mode 100644 index 000000000..828428c08 --- /dev/null +++ b/applications/external/barcode_gen/views/barcode_view.h @@ -0,0 +1,23 @@ +#pragma once + +#include + +typedef struct BarcodeApp BarcodeApp; + +typedef struct { + View* view; + BarcodeApp* barcode_app; +} Barcode; + +typedef struct { + FuriString* file_path; + BarcodeData* data; +} BarcodeModel; + +Barcode* barcode_view_allocate(BarcodeApp* barcode_app); + +void barcode_free_model(Barcode* barcode); + +void barcode_free(Barcode* barcode); + +View* barcode_get_view(Barcode* barcode); diff --git a/applications/external/barcode_gen/views/create_view.c b/applications/external/barcode_gen/views/create_view.c new file mode 100644 index 000000000..e4e489113 --- /dev/null +++ b/applications/external/barcode_gen/views/create_view.c @@ -0,0 +1,494 @@ +#include "../barcode_app.h" +#include "create_view.h" +#include + +#define LINE_HEIGHT 16 +#define TEXT_PADDING 4 +#define TOTAL_MENU_ITEMS 5 + +typedef enum { + TypeMenuItem, + FileNameMenuItem, + BarcodeDataMenuItem, + SaveMenuButton, + DeleteMenuButton +} MenuItems; + +/** + * Took this function from blackjack + * @author @teeebor +*/ +void draw_menu_item( + Canvas* const canvas, + const char* text, + const char* value, + int y, + bool left_caret, + bool right_caret, + bool selected) { + UNUSED(selected); + if(y < 0 || y >= 64) { + return; + } + + if(selected) { + canvas_set_color(canvas, ColorBlack); + canvas_draw_box(canvas, 0, y, 123, LINE_HEIGHT); + canvas_set_color(canvas, ColorWhite); + } + + canvas_draw_str_aligned(canvas, 4, y + TEXT_PADDING, AlignLeft, AlignTop, text); + if(left_caret) { + canvas_draw_str_aligned(canvas, 60, y + TEXT_PADDING, AlignLeft, AlignTop, "<"); + } + + canvas_draw_str_aligned(canvas, 90, y + TEXT_PADDING, AlignCenter, AlignTop, value); + if(right_caret) { + canvas_draw_str_aligned(canvas, 120, y + TEXT_PADDING, AlignRight, AlignTop, ">"); + } + + canvas_set_color(canvas, ColorBlack); +} + +void draw_button(Canvas* const canvas, const char* text, int y, bool selected) { + if(selected) { + canvas_set_color(canvas, ColorBlack); + canvas_draw_box(canvas, 0, y, 123, LINE_HEIGHT); + canvas_set_color(canvas, ColorWhite); + } + + canvas_draw_str_aligned(canvas, 64, y + TEXT_PADDING, AlignCenter, AlignTop, text); + + canvas_set_color(canvas, ColorBlack); +} + +static void app_draw_callback(Canvas* canvas, void* ctx) { + furi_assert(ctx); + + CreateViewModel* create_view_model = ctx; + + BarcodeTypeObj* type_obj = create_view_model->barcode_type; + if(create_view_model->barcode_type == NULL) { + return; + } + BarcodeType selected_type = type_obj->type; + + int selected_menu_item = create_view_model->selected_menu_item; + + int total_menu_items = create_view_model->mode == EditMode ? TOTAL_MENU_ITEMS : + TOTAL_MENU_ITEMS - 1; + + int startY = 0; + + //the menu items index that is/would be in view + //int current_last_menu_item = selected_menu_item + 3; + if(selected_menu_item > 1) { + int offset = 2; + if(selected_menu_item + offset > total_menu_items) { + offset = 3; + } + startY -= (LINE_HEIGHT * (selected_menu_item - offset)); + } + + //ensure that the scroll height is atleast 1 + int scrollHeight = ceil(64.0 / total_menu_items); + int scrollPos = scrollHeight * selected_menu_item; + + canvas_set_color(canvas, ColorBlack); + //draw the scroll bar box + canvas_draw_box(canvas, 125, scrollPos, 3, scrollHeight); + //draw the scroll bar track + canvas_draw_box(canvas, 126, 0, 1, 64); + + draw_menu_item( + canvas, + "Type", + type_obj->name, + TypeMenuItem * LINE_HEIGHT + startY, + selected_type > 0, + selected_type < NUMBER_OF_BARCODE_TYPES - 2, + selected_menu_item == TypeMenuItem); + + draw_menu_item( + canvas, + "Name", + furi_string_empty(create_view_model->file_name) ? + "--" : + furi_string_get_cstr(create_view_model->file_name), + FileNameMenuItem * LINE_HEIGHT + startY, + false, + false, + selected_menu_item == FileNameMenuItem); + + draw_menu_item( + canvas, + "Data", + furi_string_empty(create_view_model->barcode_data) ? + "--" : + furi_string_get_cstr(create_view_model->barcode_data), + BarcodeDataMenuItem * LINE_HEIGHT + startY, + false, + false, + selected_menu_item == BarcodeDataMenuItem); + + draw_button( + canvas, + "Save", + SaveMenuButton * LINE_HEIGHT + startY, + selected_menu_item == SaveMenuButton); + + if(create_view_model->mode == EditMode) { + draw_button( + canvas, + "Delete", + DeleteMenuButton * LINE_HEIGHT + startY, + selected_menu_item == DeleteMenuButton); + } +} + +void text_input_callback(void* ctx) { + CreateView* create_view_object = ctx; + + with_view_model( + create_view_object->view, + CreateViewModel * model, + { + if(create_view_object->setter == FileNameSetter) { + furi_string_set_str(model->file_name, create_view_object->input); + } + if(create_view_object->setter == BarcodeDataSetter) { + furi_string_set_str(model->barcode_data, create_view_object->input); + } + }, + true); + + view_dispatcher_switch_to_view( + create_view_object->barcode_app->view_dispatcher, CreateBarcodeView); +} + +static bool app_input_callback(InputEvent* input_event, void* ctx) { + furi_assert(ctx); + + if(input_event->key == InputKeyBack) { + return false; + } + + CreateView* create_view_object = ctx; + + //get the currently selected menu item from the model + int selected_menu_item = 0; + BarcodeTypeObj* barcode_type = NULL; + FuriString* file_name; + FuriString* barcode_data; + CreateMode mode; + + with_view_model( + create_view_object->view, + CreateViewModel * model, + { + selected_menu_item = model->selected_menu_item; + barcode_type = model->barcode_type; + file_name = model->file_name; + barcode_data = model->barcode_data; + mode = model->mode; + }, + true); + + int total_menu_items = mode == EditMode ? TOTAL_MENU_ITEMS : TOTAL_MENU_ITEMS - 1; + + if(input_event->type == InputTypePress) { + if(input_event->key == InputKeyUp && selected_menu_item > 0) { + selected_menu_item--; + } else if(input_event->key == InputKeyDown && selected_menu_item < total_menu_items - 1) { + selected_menu_item++; + } else if(input_event->key == InputKeyLeft) { + if(selected_menu_item == TypeMenuItem && barcode_type != NULL) { //Select Barcode Type + if(barcode_type->type > 0) { + barcode_type = barcode_type_objs[barcode_type->type - 1]; + } + } + } else if(input_event->key == InputKeyRight) { + if(selected_menu_item == TypeMenuItem && barcode_type != NULL) { //Select Barcode Type + if(barcode_type->type < NUMBER_OF_BARCODE_TYPES - 2) { + barcode_type = barcode_type_objs[barcode_type->type + 1]; + } + } + } else if(input_event->key == InputKeyOk) { + if(selected_menu_item == FileNameMenuItem && barcode_type != NULL) { + create_view_object->setter = FileNameSetter; + + snprintf( + create_view_object->input, + sizeof(create_view_object->input), + "%s", + furi_string_get_cstr(file_name)); + + text_input_set_result_callback( + create_view_object->barcode_app->text_input, + text_input_callback, + create_view_object, + create_view_object->input, + TEXT_BUFFER_SIZE - BARCODE_EXTENSION_LENGTH, //remove the barcode length + //clear default text + false); + text_input_set_header_text( + create_view_object->barcode_app->text_input, "File Name"); + + view_dispatcher_switch_to_view( + create_view_object->barcode_app->view_dispatcher, TextInputView); + } + if(selected_menu_item == BarcodeDataMenuItem && barcode_type != NULL) { + create_view_object->setter = BarcodeDataSetter; + + snprintf( + create_view_object->input, + sizeof(create_view_object->input), + "%s", + furi_string_get_cstr(barcode_data)); + + text_input_set_result_callback( + create_view_object->barcode_app->text_input, + text_input_callback, + create_view_object, + create_view_object->input, + TEXT_BUFFER_SIZE, + //clear default text + false); + text_input_set_header_text( + create_view_object->barcode_app->text_input, "Barcode Data"); + + view_dispatcher_switch_to_view( + create_view_object->barcode_app->view_dispatcher, TextInputView); + } + if(selected_menu_item == SaveMenuButton && barcode_type != NULL) { + save_barcode(create_view_object); + } + if(selected_menu_item == DeleteMenuButton && barcode_type != NULL) { + if(mode == EditMode) { + remove_barcode(create_view_object); + } else if(mode == NewMode) { + view_dispatcher_switch_to_view( + create_view_object->barcode_app->view_dispatcher, MainMenuView); + } + } + } + } + + //change the currently selected menu item + with_view_model( + create_view_object->view, + CreateViewModel * model, + { + model->selected_menu_item = selected_menu_item; + model->barcode_type = barcode_type; + }, + true); + + return true; +} + +CreateView* create_view_allocate(BarcodeApp* barcode_app) { + furi_assert(barcode_app); + + CreateView* create_view_object = malloc(sizeof(CreateView)); + + create_view_object->view = view_alloc(); + create_view_object->barcode_app = barcode_app; + + view_set_context(create_view_object->view, create_view_object); + view_allocate_model(create_view_object->view, ViewModelTypeLocking, sizeof(CreateViewModel)); + view_set_draw_callback(create_view_object->view, app_draw_callback); + view_set_input_callback(create_view_object->view, app_input_callback); + + return create_view_object; +} + +void create_view_free_model(CreateView* create_view_object) { + with_view_model( + create_view_object->view, + CreateViewModel * model, + { + if(model->file_path != NULL) { + furi_string_free(model->file_path); + } + if(model->file_name != NULL) { + furi_string_free(model->file_name); + } + if(model->barcode_data != NULL) { + furi_string_free(model->barcode_data); + } + }, + true); +} + +void remove_barcode(CreateView* create_view_object) { + Storage* storage = furi_record_open(RECORD_STORAGE); + + bool success = false; + + with_view_model( + create_view_object->view, + CreateViewModel * model, + { + FURI_LOG_I(TAG, "Attempting to remove file"); + if(model->file_path != NULL) { + FURI_LOG_I(TAG, "Removing File: %s", furi_string_get_cstr(model->file_path)); + if(storage_simply_remove(storage, furi_string_get_cstr(model->file_path))) { + FURI_LOG_I( + TAG, + "File: \"%s\" was successfully removed", + furi_string_get_cstr(model->file_path)); + success = true; + } else { + FURI_LOG_E(TAG, "Unable to remove file!"); + success = false; + } + } else { + FURI_LOG_E(TAG, "Could not remove barcode file"); + success = false; + } + }, + true); + furi_record_close(RECORD_STORAGE); + + with_view_model( + create_view_object->barcode_app->message_view->view, + MessageViewModel * model, + { + if(success) { + model->message = "File Deleted"; + } else { + model->message = "Could not delete file"; + } + }, + true); + + view_dispatcher_switch_to_view( + create_view_object->barcode_app->view_dispatcher, MessageErrorView); +} + +void save_barcode(CreateView* create_view_object) { + BarcodeTypeObj* barcode_type = NULL; + FuriString* file_path; //this may be empty + FuriString* file_name; + FuriString* barcode_data; + CreateMode mode; + + with_view_model( + create_view_object->view, + CreateViewModel * model, + { + file_path = model->file_path; + file_name = model->file_name; + barcode_data = model->barcode_data; + barcode_type = model->barcode_type; + mode = model->mode; + }, + true); + + if(file_name == NULL || furi_string_empty(file_name)) { + FURI_LOG_E(TAG, "File Name cannot be empty"); + return; + } + if(barcode_data == NULL || furi_string_empty(barcode_data)) { + FURI_LOG_E(TAG, "Barcode Data cannot be empty"); + return; + } + if(barcode_type == NULL) { + FURI_LOG_E(TAG, "Type not defined"); + return; + } + + bool success = false; + + FuriString* full_file_path = furi_string_alloc_set(DEFAULT_USER_BARCODES); + furi_string_push_back(full_file_path, '/'); + furi_string_cat(full_file_path, file_name); + furi_string_cat_str(full_file_path, BARCODE_EXTENSION); + + Storage* storage = furi_record_open(RECORD_STORAGE); + + if(mode == EditMode) { + if(!furi_string_empty(file_path)) { + if(!furi_string_equal(file_path, full_file_path)) { + FS_Error error = storage_common_rename( + storage, + furi_string_get_cstr(file_path), + furi_string_get_cstr(full_file_path)); + if(error != FSE_OK) { + FURI_LOG_E(TAG, "Rename error: %s", storage_error_get_desc(error)); + } else { + FURI_LOG_I(TAG, "Rename Success"); + } + } + } + } + + FlipperFormat* ff = flipper_format_file_alloc(storage); + + FURI_LOG_I(TAG, "Saving Barcode to: %s", furi_string_get_cstr(full_file_path)); + + bool file_opened_status = false; + if(mode == NewMode) { + file_opened_status = + flipper_format_file_open_new(ff, furi_string_get_cstr(full_file_path)); + } else if(mode == EditMode) { + file_opened_status = + flipper_format_file_open_always(ff, furi_string_get_cstr(full_file_path)); + } + + if(file_opened_status) { + // Filetype: Barcode + // Version: 1 + + // # Types - UPC-A, EAN-8, EAN-13, CODE-39 + // Type: CODE-39 + // Data: AB + flipper_format_write_string_cstr(ff, "Filetype", "Barcode"); + + flipper_format_write_string_cstr(ff, "Version", FILE_VERSION); + + flipper_format_write_comment_cstr( + ff, "Types - UPC-A, EAN-8, EAN-13, CODE-39, CODE-128, Codabar"); + + flipper_format_write_string_cstr(ff, "Type", barcode_type->name); + + flipper_format_write_string_cstr(ff, "Data", furi_string_get_cstr(barcode_data)); + + success = true; + } else { + FURI_LOG_E(TAG, "Save error"); + success = false; + } + furi_string_free(full_file_path); + flipper_format_free(ff); + furi_record_close(RECORD_STORAGE); + + with_view_model( + create_view_object->barcode_app->message_view->view, + MessageViewModel * model, + { + if(success) { + model->message = "File Saved!"; + } else { + model->message = "A saving error has occurred"; + } + }, + true); + + view_dispatcher_switch_to_view( + create_view_object->barcode_app->view_dispatcher, MessageErrorView); +} + +void create_view_free(CreateView* create_view_object) { + furi_assert(create_view_object); + + create_view_free_model(create_view_object); + view_free(create_view_object->view); + free(create_view_object); +} + +View* create_get_view(CreateView* create_view_object) { + furi_assert(create_view_object); + return create_view_object->view; +} \ No newline at end of file diff --git a/applications/external/barcode_gen/views/create_view.h b/applications/external/barcode_gen/views/create_view.h new file mode 100644 index 000000000..6063786d9 --- /dev/null +++ b/applications/external/barcode_gen/views/create_view.h @@ -0,0 +1,46 @@ +#pragma once + +#include + +typedef struct BarcodeApp BarcodeApp; + +typedef enum { + FileNameSetter, + BarcodeDataSetter +} InputSetter; //what value to set for the text input view + +typedef enum { + EditMode, + + NewMode +} CreateMode; + +typedef struct { + View* view; + BarcodeApp* barcode_app; + + InputSetter setter; + char input[TEXT_BUFFER_SIZE]; +} CreateView; + +typedef struct { + int selected_menu_item; + + CreateMode mode; + BarcodeTypeObj* barcode_type; + FuriString* file_path; //the current file that is opened + FuriString* file_name; + FuriString* barcode_data; +} CreateViewModel; + +CreateView* create_view_allocate(BarcodeApp* barcode_app); + +void remove_barcode(CreateView* create_view_object); + +void save_barcode(CreateView* create_view_object); + +void create_view_free_model(CreateView* create_view_object); + +void create_view_free(CreateView* create_view_object); + +View* create_get_view(CreateView* create_view_object); diff --git a/applications/external/barcode_gen/views/message_view.c b/applications/external/barcode_gen/views/message_view.c new file mode 100644 index 000000000..3a9aa90b3 --- /dev/null +++ b/applications/external/barcode_gen/views/message_view.c @@ -0,0 +1,66 @@ +#include "../barcode_app.h" +#include "message_view.h" + +static void app_draw_callback(Canvas* canvas, void* ctx) { + furi_assert(ctx); + + MessageViewModel* message_view_model = ctx; + + canvas_clear(canvas); + if(message_view_model->message != NULL) { + canvas_draw_str_aligned( + canvas, 62, 30, AlignCenter, AlignCenter, message_view_model->message); + } + + canvas_set_color(canvas, ColorBlack); + canvas_draw_box(canvas, 100, 52, 28, 12); + canvas_set_color(canvas, ColorWhite); + canvas_draw_str_aligned(canvas, 114, 58, AlignCenter, AlignCenter, "OK"); +} + +static bool app_input_callback(InputEvent* input_event, void* ctx) { + furi_assert(ctx); + + MessageView* message_view_object = ctx; + + if(input_event->key == InputKeyBack) { + view_dispatcher_switch_to_view( + message_view_object->barcode_app->view_dispatcher, MainMenuView); + } + if(input_event->type == InputTypeShort) { + if(input_event->key == InputKeyOk) { + view_dispatcher_switch_to_view( + message_view_object->barcode_app->view_dispatcher, MainMenuView); + } + } + + return true; +} + +MessageView* message_view_allocate(BarcodeApp* barcode_app) { + furi_assert(barcode_app); + + MessageView* message_view_object = malloc(sizeof(MessageView)); + + message_view_object->view = view_alloc(); + message_view_object->barcode_app = barcode_app; + + view_set_context(message_view_object->view, message_view_object); + view_allocate_model(message_view_object->view, ViewModelTypeLocking, sizeof(MessageViewModel)); + view_set_draw_callback(message_view_object->view, app_draw_callback); + view_set_input_callback(message_view_object->view, app_input_callback); + + return message_view_object; +} + +void message_view_free(MessageView* message_view_object) { + furi_assert(message_view_object); + + view_free(message_view_object->view); + free(message_view_object); +} + +View* message_get_view(MessageView* message_view_object) { + furi_assert(message_view_object); + return message_view_object->view; +} \ No newline at end of file diff --git a/applications/external/barcode_gen/views/message_view.h b/applications/external/barcode_gen/views/message_view.h new file mode 100644 index 000000000..33acc3d0c --- /dev/null +++ b/applications/external/barcode_gen/views/message_view.h @@ -0,0 +1,22 @@ +#pragma once + +#include + +typedef struct BarcodeApp BarcodeApp; + +typedef struct { + View* view; + BarcodeApp* barcode_app; +} MessageView; + +typedef struct { + const char* message; +} MessageViewModel; + +MessageView* message_view_allocate(BarcodeApp* barcode_app); + +void message_view_free_model(MessageView* message_view_object); + +void message_view_free(MessageView* message_view_object); + +View* message_get_view(MessageView* message_view_object); diff --git a/applications/external/barcode_generator/application.fam b/applications/external/barcode_generator/application.fam deleted file mode 100644 index 9bb44915c..000000000 --- a/applications/external/barcode_generator/application.fam +++ /dev/null @@ -1,17 +0,0 @@ -App( - appid="barcode_generator", - name="Barcode Generator", - apptype=FlipperAppType.EXTERNAL, - entry_point="barcode_generator_app", - requires=[ - "gui", - "dialogs", - ], - stack_size=1 * 1024, - order=50, - fap_icon="barcode_10px.png", - fap_category="Tools", - fap_author="@xMasterX & @msvsergey & @McAzzaMan", - fap_version="1.0", - fap_description="App displays Barcode on flipper screen and allows to edit it", -) diff --git a/applications/external/barcode_generator/barcode_10px.png b/applications/external/barcode_generator/barcode_10px.png deleted file mode 100644 index 7c19c665687e0e6f81a805511af4d8ed409b54bc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2363 zcmcIleQeZZ81FWfF+QB4FvMlG7#NXWKkwSN?ikzM?JL~4Z6|D~fnNLeR(IEyYq#AN z%oaruB@i%+Xe3Ao#)CCTV+^Ork;KJQyBnW;32#3Z*#BN*U?_IKaeUzS z(mh7`Q{OyWxU&9K%`bDe6`dY_KCpfM-7jDH?cf#g@cDuxu}cScwCuRreyr!qeaC+| zcX^3+pm4DHz|o##-B{_$tsj?NnJ6B-6doDp&s-m1E8c1?Xn7AZ;Hz?we2wykvY;APuiu^WudPi-z(SN?R@;HLcx zzv`HXo_hy&To_+b^XT3=ukAU{ovhfot7*~2FItwIohaJER3wD*lN;WD_rq%gfBEmb zbQKzh>WkN|_AlwENgwtMJW=-eD?i6Rv6e2Jko%+Rh+v&4n0F%d!KN2}U0k(r02@2A z{?Dsz)n`#A=UNRNHipexmF-j2N>s1GtI`r8NJ2-()5(Nk zOKCrm;g!(Zy-g8#2I6%2iEKgJ!x21SrXVhO7*e5W8fO@vM+B;mlj+qsOS25cFcd42 zEF;lC;y66_AyB-Os!6TEx?D7Lg|^w9QedbD8qV^3FssvaOuN&(gSN@XwZJ((Pu#-JdnCYAT3h zGMkbHfmhKWPx68YNKWHpB=89W$)F&DLi0Qq6Z35AQ#$G$IWd*hRiz?EFAG&o0D#nF zD3F@KE2QWZASo!K07OLqng|H|X54_8FjHYuMcL&DTva5^OeJOHA3Z6@A!QkH*Ug9> zouWavQR(_}{m}-u=2i(E&zMe<71!&2LUD@>RU&s=pC)^=)0w61?SzPQhT>;owy8P2 zathYOQKtXZT-2S!+j7s$(tTc$<9v#jgtU(%V*<~UKm;rac%W!BOZx;LlRx_ZB0ZN@ zrBgQI5WPAn;=dneb}pW_4*%?{+19~3waK%aZaWO+zAv*5qi#3fP4ng(pa!+3YfU>> z(^@$`S=}|$_ZtmF=mQKSQNQ>|p64|ZXsSxGKn9G)sv>06s*H++ev>QTWdUDpD8SOZ z;HwiECKM1uAW+NlLC#mlhXQr%l${xltRQM;hMI%NYqvc2H!SJ|umAu6 diff --git a/applications/external/barcode_generator/barcode_generator.c b/applications/external/barcode_generator/barcode_generator.c deleted file mode 100644 index 2645bbcea..000000000 --- a/applications/external/barcode_generator/barcode_generator.c +++ /dev/null @@ -1,447 +0,0 @@ -#include "barcode_generator.h" - -static BarcodeType* barcodeTypes[NUMBER_OF_BARCODE_TYPES]; - -void init_types() { - BarcodeType* upcA = malloc(sizeof(BarcodeType)); - upcA->name = "UPC-A"; - upcA->numberOfDigits = 12; - upcA->startPos = 19; - upcA->bartype = BarTypeUPCA; - barcodeTypes[0] = upcA; - - BarcodeType* ean8 = malloc(sizeof(BarcodeType)); - ean8->name = "EAN-8"; - ean8->numberOfDigits = 8; - ean8->startPos = 33; - ean8->bartype = BarTypeEAN8; - barcodeTypes[1] = ean8; - - BarcodeType* ean13 = malloc(sizeof(BarcodeType)); - ean13->name = "EAN-13"; - ean13->numberOfDigits = 13; - ean13->startPos = 19; - ean13->bartype = BarTypeEAN13; - barcodeTypes[2] = ean13; -} - -void draw_digit( - Canvas* canvas, - int digit, - BarEncodingType rightHand, - int startingPosition, - bool drawlines) { - char digitStr[2]; - snprintf(digitStr, 2, "%u", digit); - canvas_set_color(canvas, ColorBlack); - canvas_draw_str( - canvas, startingPosition, BARCODE_Y_START + BARCODE_HEIGHT + BARCODE_TEXT_OFFSET, digitStr); - - if(drawlines) { - switch(rightHand) { - case BarEncodingTypeLeft: - case BarEncodingTypeRight: - canvas_set_color( - canvas, (rightHand == BarEncodingTypeRight) ? ColorBlack : ColorWhite); - //int count = 0; - for(int i = 0; i < 4; i++) { - canvas_draw_box( - canvas, startingPosition, BARCODE_Y_START, DIGITS[digit][i], BARCODE_HEIGHT); - canvas_invert_color(canvas); - startingPosition += DIGITS[digit][i]; - } - break; - case BarEncodingTypeG: - canvas_set_color(canvas, ColorWhite); - //int count = 0; - for(int i = 3; i >= 0; i--) { - canvas_draw_box( - canvas, startingPosition, BARCODE_Y_START, DIGITS[digit][i], BARCODE_HEIGHT); - canvas_invert_color(canvas); - startingPosition += DIGITS[digit][i]; - } - break; - default: - break; - } - } -} - -int get_digit_position(int index, BarcodeType* type) { - int pos = 0; - switch(type->bartype) { - case BarTypeEAN8: - case BarTypeUPCA: - pos = type->startPos + index * 7; - if(index >= type->numberOfDigits / 2) { - pos += 5; - } - break; - case BarTypeEAN13: - if(index == 0) - pos = type->startPos - 10; - else { - pos = type->startPos + (index - 1) * 7; - if((index - 1) >= type->numberOfDigits / 2) { - pos += 5; - } - } - break; - default: - break; - } - return pos; -} - -int get_menu_text_location(int index) { - return 20 + 10 * index; -} - -int get_barcode_max_index(PluginState* plugin_state) { - return plugin_state->barcode_state.doParityCalculation ? - barcodeTypes[plugin_state->barcode_state.barcodeTypeIndex]->numberOfDigits - 1 : - barcodeTypes[plugin_state->barcode_state.barcodeTypeIndex]->numberOfDigits; -} - -int calculate_check_digit(PluginState* plugin_state, BarcodeType* type) { - int checkDigit = 0; - int checkDigitOdd = 0; - int checkDigitEven = 0; - //add all odd positions. Confusing because 0index - for(int i = 0; i < type->numberOfDigits - 1; i += 2) { - checkDigitOdd += plugin_state->barcode_state.barcodeNumeral[i]; - } - - //add all even positions to above. Confusing because 0index - for(int i = 1; i < type->numberOfDigits - 1; i += 2) { - checkDigitEven += plugin_state->barcode_state.barcodeNumeral[i]; - } - - if(type->bartype == BarTypeEAN13) { - checkDigit = checkDigitEven * 3 + checkDigitOdd; - } else { - checkDigit = checkDigitOdd * 3 + checkDigitEven; - } - - checkDigit = checkDigit % 10; //mod 10 - - //if m = 0 then x12 = 0, otherwise x12 is 10 - m - return (10 - checkDigit) % 10; -} - -static void render_callback(Canvas* const canvas, void* ctx) { - furi_assert(ctx); - PluginState* plugin_state = ctx; - furi_mutex_acquire(plugin_state->mutex, FuriWaitForever); - - if(plugin_state->mode == MenuMode) { - canvas_set_color(canvas, ColorBlack); - canvas_draw_str_aligned(canvas, 64, 6, AlignCenter, AlignCenter, "MENU"); - canvas_draw_frame(canvas, 50, 0, 29, 11); //box around Menu - canvas_draw_str_aligned( - canvas, 64, get_menu_text_location(0), AlignCenter, AlignCenter, "View"); - canvas_draw_str_aligned( - canvas, 64, get_menu_text_location(1), AlignCenter, AlignCenter, "Edit"); - canvas_draw_str_aligned( - canvas, 64, get_menu_text_location(2), AlignCenter, AlignCenter, "Parity?"); - - canvas_draw_frame(canvas, 83, get_menu_text_location(2) - 3, 6, 6); - if(plugin_state->barcode_state.doParityCalculation == true) { - canvas_draw_box(canvas, 85, get_menu_text_location(2) - 1, 2, 2); - } - canvas_draw_str_aligned( - canvas, - 64, - get_menu_text_location(3), - AlignCenter, - AlignCenter, - (barcodeTypes[plugin_state->barcode_state.barcodeTypeIndex])->name); - canvas_draw_disc( - canvas, - 40, - get_menu_text_location(plugin_state->menuIndex) - 1, - 2); //draw menu cursor - } else { - BarcodeType* type = barcodeTypes[plugin_state->barcode_state.barcodeTypeIndex]; - - //start saftey - canvas_set_color(canvas, ColorBlack); - canvas_draw_box(canvas, type->startPos - 3, BARCODE_Y_START, 1, BARCODE_HEIGHT + 2); - canvas_draw_box(canvas, (type->startPos - 1), BARCODE_Y_START, 1, BARCODE_HEIGHT + 2); - - int startpos = 0; - int endpos = type->numberOfDigits; - if(type->bartype == BarTypeEAN13) { - startpos++; - draw_digit( - canvas, - plugin_state->barcode_state.barcodeNumeral[0], - BarEncodingTypeRight, - get_digit_position(0, barcodeTypes[plugin_state->barcode_state.barcodeTypeIndex]), - false); - } - if(plugin_state->barcode_state.doParityCalculation) { //calculate the check digit - plugin_state->barcode_state.barcodeNumeral[type->numberOfDigits - 1] = - calculate_check_digit(plugin_state, type); - } - for(int index = startpos; index < endpos; index++) { - BarEncodingType barEncodingType = BarEncodingTypeLeft; - if(type->bartype == BarTypeEAN13) { - if(index - 1 >= (type->numberOfDigits - 1) / 2) { - barEncodingType = BarEncodingTypeRight; - } else { - barEncodingType = - (FURI_BIT( - EAN13ENCODE[plugin_state->barcode_state.barcodeNumeral[0]], - index - 1)) ? - BarEncodingTypeG : - BarEncodingTypeLeft; - } - } else { - if(index >= type->numberOfDigits / 2) { - barEncodingType = BarEncodingTypeRight; - } - } - - int digitPosition = get_digit_position( - index, barcodeTypes[plugin_state->barcode_state.barcodeTypeIndex]); - draw_digit( - canvas, - plugin_state->barcode_state.barcodeNumeral[index], - barEncodingType, - digitPosition, - true); - } - - //central separator - canvas_set_color(canvas, ColorBlack); - canvas_draw_box(canvas, 62, BARCODE_Y_START, 1, BARCODE_HEIGHT + 2); - canvas_draw_box(canvas, 64, BARCODE_Y_START, 1, BARCODE_HEIGHT + 2); - - if(plugin_state->mode == EditMode) { - canvas_set_color(canvas, ColorBlack); - canvas_draw_box( - canvas, - get_digit_position( - plugin_state->editingIndex, - barcodeTypes[plugin_state->barcode_state.barcodeTypeIndex]) - - 1, - 63, - 7, - 1); //draw editing cursor - } - - //end safety - int endSafetyPosition = get_digit_position(type->numberOfDigits - 1, type) + 7; - canvas_set_color(canvas, ColorBlack); - canvas_draw_box(canvas, endSafetyPosition, BARCODE_Y_START, 1, BARCODE_HEIGHT + 2); - canvas_draw_box(canvas, (endSafetyPosition + 2), BARCODE_Y_START, 1, BARCODE_HEIGHT + 2); - } - - furi_mutex_release(plugin_state->mutex); -} - -static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) { - furi_assert(event_queue); - - PluginEvent event = {.type = EventTypeKey, .input = *input_event}; - furi_message_queue_put(event_queue, &event, FuriWaitForever); -} - -static void barcode_generator_state_init(PluginState* plugin_state) { - plugin_state->editingIndex = 0; - plugin_state->mode = ViewMode; - plugin_state->menuIndex = MENU_INDEX_VIEW; - if(!LOAD_BARCODE_SETTINGS(&plugin_state->barcode_state)) { - for(int i = 0; i < BARCODE_MAX_LENS; ++i) { - plugin_state->barcode_state.barcodeNumeral[i] = i % 10; - } - plugin_state->barcode_state.doParityCalculation = true; - plugin_state->barcode_state.barcodeTypeIndex = 0; - } -} - -static bool handle_key_press_view(InputKey key, PluginState* plugin_state) { - switch(key) { - case InputKeyOk: - case InputKeyBack: - plugin_state->mode = MenuMode; - break; - - default: - break; - } - - return true; -} - -static bool handle_key_press_edit(InputKey key, PluginState* plugin_state) { - int barcodeMaxIndex = get_barcode_max_index(plugin_state); - - switch(key) { - case InputKeyUp: - plugin_state->barcode_state.barcodeNumeral[plugin_state->editingIndex] = - (plugin_state->barcode_state.barcodeNumeral[plugin_state->editingIndex] + 1) % 10; - break; - - case InputKeyDown: - plugin_state->barcode_state.barcodeNumeral[plugin_state->editingIndex] = - (plugin_state->barcode_state.barcodeNumeral[plugin_state->editingIndex] == 0) ? - 9 : - plugin_state->barcode_state.barcodeNumeral[plugin_state->editingIndex] - 1; - break; - - case InputKeyRight: - plugin_state->editingIndex = (plugin_state->editingIndex + 1) % barcodeMaxIndex; - break; - - case InputKeyLeft: - plugin_state->editingIndex = (plugin_state->editingIndex == 0) ? - barcodeMaxIndex - 1 : - plugin_state->editingIndex - 1; - break; - - case InputKeyOk: - case InputKeyBack: - plugin_state->mode = MenuMode; - break; - - default: - break; - } - - return true; -} - -static bool handle_key_press_menu(InputKey key, PluginState* plugin_state) { - switch(key) { - case InputKeyUp: - plugin_state->menuIndex = (plugin_state->menuIndex == MENU_INDEX_VIEW) ? - MENU_INDEX_TYPE : - plugin_state->menuIndex - 1; - break; - - case InputKeyDown: - plugin_state->menuIndex = (plugin_state->menuIndex + 1) % 4; - break; - - case InputKeyRight: - if(plugin_state->menuIndex == MENU_INDEX_TYPE) { - plugin_state->barcode_state.barcodeTypeIndex = - (plugin_state->barcode_state.barcodeTypeIndex == NUMBER_OF_BARCODE_TYPES - 1) ? - 0 : - plugin_state->barcode_state.barcodeTypeIndex + 1; - } else if(plugin_state->menuIndex == MENU_INDEX_PARITY) { - plugin_state->barcode_state.doParityCalculation = - !plugin_state->barcode_state.doParityCalculation; - } - break; - case InputKeyLeft: - if(plugin_state->menuIndex == MENU_INDEX_TYPE) { - plugin_state->barcode_state.barcodeTypeIndex = - (plugin_state->barcode_state.barcodeTypeIndex == 0) ? - NUMBER_OF_BARCODE_TYPES - 1 : - plugin_state->barcode_state.barcodeTypeIndex - 1; - } else if(plugin_state->menuIndex == MENU_INDEX_PARITY) { - plugin_state->barcode_state.doParityCalculation = - !plugin_state->barcode_state.doParityCalculation; - } - break; - - case InputKeyOk: - if(plugin_state->menuIndex == MENU_INDEX_VIEW) { - plugin_state->mode = ViewMode; - } else if(plugin_state->menuIndex == MENU_INDEX_EDIT) { - plugin_state->mode = EditMode; - } else if(plugin_state->menuIndex == MENU_INDEX_PARITY) { - plugin_state->barcode_state.doParityCalculation = - !plugin_state->barcode_state.doParityCalculation; - } else if(plugin_state->menuIndex == MENU_INDEX_TYPE) { - plugin_state->barcode_state.barcodeTypeIndex = - (plugin_state->barcode_state.barcodeTypeIndex == NUMBER_OF_BARCODE_TYPES - 1) ? - 0 : - plugin_state->barcode_state.barcodeTypeIndex + 1; - } - break; - - case InputKeyBack: - return false; - - default: - break; - } - int barcodeMaxIndex = get_barcode_max_index(plugin_state); - if(plugin_state->editingIndex >= barcodeMaxIndex) - plugin_state->editingIndex = barcodeMaxIndex - 1; - - return true; -} - -int32_t barcode_generator_app(void* p) { - UNUSED(p); - - init_types(); - - FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent)); - - PluginState* plugin_state = malloc(sizeof(PluginState)); - barcode_generator_state_init(plugin_state); - - plugin_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal); - if(!plugin_state->mutex) { - FURI_LOG_E("barcode_generator", "cannot create mutex\r\n"); - furi_message_queue_free(event_queue); - free(plugin_state); - return 255; - } - - // Set system callbacks - ViewPort* view_port = view_port_alloc(); - view_port_draw_callback_set(view_port, render_callback, plugin_state); - view_port_input_callback_set(view_port, input_callback, event_queue); - - // Open GUI and register view_port - Gui* gui = furi_record_open(RECORD_GUI); - gui_add_view_port(gui, view_port, GuiLayerFullscreen); - - PluginEvent event; - for(bool processing = true; processing;) { - FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100); - furi_mutex_acquire(plugin_state->mutex, FuriWaitForever); - - if(event_status == FuriStatusOk) { - // press events - if(event.type == EventTypeKey && - ((event.input.type == InputTypePress) || (event.input.type == InputTypeRepeat))) { - switch(plugin_state->mode) { - case ViewMode: - processing = handle_key_press_view(event.input.key, plugin_state); - break; - case EditMode: - processing = handle_key_press_edit(event.input.key, plugin_state); - break; - case MenuMode: - processing = handle_key_press_menu(event.input.key, plugin_state); - break; - default: - break; - } - } - } - - view_port_update(view_port); - furi_mutex_release(plugin_state->mutex); - } - - view_port_enabled_set(view_port, false); - gui_remove_view_port(gui, view_port); - furi_record_close(RECORD_GUI); - view_port_free(view_port); - furi_message_queue_free(event_queue); - furi_mutex_free(plugin_state->mutex); - // save settings - SAVE_BARCODE_SETTINGS(&plugin_state->barcode_state); - free(plugin_state); - - return 0; -} diff --git a/applications/external/barcode_generator/barcode_generator.h b/applications/external/barcode_generator/barcode_generator.h deleted file mode 100644 index 9f2e10c16..000000000 --- a/applications/external/barcode_generator/barcode_generator.h +++ /dev/null @@ -1,115 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include - -#define BARCODE_SETTINGS_FILE_NAME "apps/Tools/barcodegen.save" - -#define BARCODE_SETTINGS_VER (1) -#define BARCODE_SETTINGS_PATH EXT_PATH(BARCODE_SETTINGS_FILE_NAME) -#define BARCODE_SETTINGS_MAGIC (0xC2) - -#define SAVE_BARCODE_SETTINGS(x) \ - saved_struct_save( \ - BARCODE_SETTINGS_PATH, \ - (x), \ - sizeof(BarcodeState), \ - BARCODE_SETTINGS_MAGIC, \ - BARCODE_SETTINGS_VER) - -#define LOAD_BARCODE_SETTINGS(x) \ - saved_struct_load( \ - BARCODE_SETTINGS_PATH, \ - (x), \ - sizeof(BarcodeState), \ - BARCODE_SETTINGS_MAGIC, \ - BARCODE_SETTINGS_VER) - -#define BARCODE_HEIGHT 50 -#define BARCODE_Y_START 3 -#define BARCODE_TEXT_OFFSET 9 -#define BARCODE_MAX_LENS 13 -#define NUMBER_OF_BARCODE_TYPES 3 -#define MENU_INDEX_VIEW 0 -#define MENU_INDEX_EDIT 1 -#define MENU_INDEX_PARITY 2 -#define MENU_INDEX_TYPE 3 - -typedef enum { - EventTypeTick, - EventTypeKey, -} EventType; - -typedef struct { - EventType type; - InputEvent input; -} PluginEvent; - -typedef enum { - ViewMode, - EditMode, - MenuMode, -} Mode; - -typedef enum { - BarEncodingTypeLeft, - BarEncodingTypeRight, - BarEncodingTypeG, -} BarEncodingType; - -typedef enum { - BarTypeEAN8, - BarTypeUPCA, - BarTypeEAN13, -} BarType; - -typedef struct { - char* name; - int numberOfDigits; - int startPos; - BarType bartype; -} BarcodeType; - -typedef struct { - int barcodeNumeral[BARCODE_MAX_LENS]; //The current barcode number - bool doParityCalculation; //Should do parity check? - int barcodeTypeIndex; -} BarcodeState; - -typedef struct { - FuriMutex* mutex; - BarcodeState barcode_state; - int editingIndex; //The index of the editing symbol - int menuIndex; //The index of the menu cursor - Mode mode; //View, edit or menu -} PluginState; - -static const int DIGITS[10][4] = { - {3, 2, 1, 1}, - {2, 2, 2, 1}, - {2, 1, 2, 2}, - {1, 4, 1, 1}, - {1, 1, 3, 2}, - {1, 2, 3, 1}, - {1, 1, 1, 4}, - {1, 3, 1, 2}, - {1, 2, 1, 3}, - {3, 1, 1, 2}, -}; - -static const uint8_t EAN13ENCODE[10] = { - 0b000000, - 0b110100, - 0b101100, - 0b011100, - 0b110010, - 0b100110, - 0b001110, - 0b101010, - 0b011010, - 0b010110, -}; \ No newline at end of file diff --git a/assets/resources/apps_data/barcode_data/codabar_encodings.txt b/assets/resources/apps_data/barcode_data/codabar_encodings.txt new file mode 100644 index 000000000..5f0684cbd --- /dev/null +++ b/assets/resources/apps_data/barcode_data/codabar_encodings.txt @@ -0,0 +1,22 @@ +# alternates between bars and spaces, always begins with bar +# 0 for narrow, 1 for wide +0: 0000011 +1: 0000110 +2: 0001001 +3: 1100000 +4: 0010010 +5: 1000010 +6: 0100001 +7: 0100100 +8: 0110000 +9: 1001000 +-: 0001100 +$: 0011000 +:: 1000101 +/: 1010001 +.: 1010100 ++: 0010101 +A: 0011010 +B: 0101001 +C: 0001011 +D: 0001110 \ No newline at end of file diff --git a/assets/resources/apps_data/barcode_data/code128_encodings.txt b/assets/resources/apps_data/barcode_data/code128_encodings.txt new file mode 100644 index 000000000..394a34520 --- /dev/null +++ b/assets/resources/apps_data/barcode_data/code128_encodings.txt @@ -0,0 +1,202 @@ + : 00 +!: 01 +": 02 +#: 03 +$: 04 +%: 05 +&: 06 +': 07 +(: 08 +): 09 +*: 10 ++: 11 +,: 12 +-: 13 +.: 14 +/: 15 +0: 16 +1: 17 +2: 18 +3: 19 +4: 20 +5: 21 +6: 22 +7: 23 +8: 24 +9: 25 +:: 26 +;: 27 +<: 28 +=: 29 +>: 30 +?: 31 +@: 32 +A: 33 +B: 34 +C: 35 +D: 36 +E: 37 +F: 38 +G: 39 +H: 40 +I: 41 +J: 42 +K: 43 +L: 44 +M: 45 +N: 46 +O: 47 +P: 48 +Q: 49 +R: 50 +S: 51 +T: 52 +U: 53 +V: 54 +W: 55 +X: 56 +Y: 57 +Z: 58 +[: 59 +\: 60 +]: 61 +^: 62 +_: 63 +`: 64 +a: 65 +b: 66 +c: 67 +d: 68 +e: 69 +f: 70 +g: 71 +h: 72 +i: 73 +j: 74 +k: 75 +l: 76 +m: 77 +n: 78 +o: 79 +p: 80 +q: 81 +r: 82 +s: 83 +t: 84 +u: 85 +v: 86 +w: 87 +x: 88 +y: 89 +z: 90 +{: 91 +|: 92 +}: 93 +~: 94 + +00: 11011001100 +01: 11001101100 +02: 11001100110 +03: 10010011000 +04: 10010001100 +05: 10001001100 +06: 10011001000 +07: 10011000100 +08: 10001100100 +09: 11001001000 +10: 11001000100 +11: 11000100100 +12: 10110011100 +13: 10011011100 +14: 10011001110 +15: 10111001100 +16: 10011101100 +17: 10011100110 +18: 11001110010 +19: 11001011100 +20: 11001001110 +21: 11011100100 +22: 11001110100 +23: 11101101110 +24: 11101001100 +25: 11100101100 +26: 11100100110 +27: 11101100100 +28: 11100110100 +29: 11100110010 +30: 11011011000 +31: 11011000110 +32: 11000110110 +33: 10100011000 +34: 10001011000 +35: 10001000110 +36: 10110001000 +37: 10001101000 +38: 10001100010 +39: 11010001000 +40: 11000101000 +41: 11000100010 +42: 10110111000 +43: 10110001110 +44: 10001101110 +45: 10111011000 +46: 10111000110 +47: 10001110110 +48: 11101110110 +49: 11010001110 +50: 11000101110 +51: 11011101000 +52: 11011100010 +53: 11011101110 +54: 11101011000 +55: 11101000110 +56: 11100010110 +57: 11101101000 +58: 11101100010 +59: 11100011010 +60: 11101111010 +61: 11001000010 +62: 11110001010 +63: 10100110000 +64: 10100001100 +65: 10010110000 +66: 10010000110 +67: 10000101100 +68: 10000100110 +69: 10110010000 +70: 10110000100 +71: 10011010000 +72: 10011000010 +73: 10000110100 +74: 10000110010 +75: 11000010010 +76: 11001010000 +77: 11110111010 +78: 11000010100 +79: 10001111010 +80: 10100111100 +81: 10010111100 +82: 10010011110 +83: 10111100100 +84: 10011110100 +85: 10011110010 +86: 11110100100 +87: 11110010100 +88: 11110010010 +89: 11011011110 +90: 11011110110 +91: 11110110110 +92: 10101111000 +93: 10100011110 +94: 10001011110 +95: 10111101000 +96: 10111100010 +97: 11110101000 +98: 11110100010 +99: 10111011110 +100: 10111101110 +101: 11101011110 +102: 11110101110 +103: 11010000100 +104: 11010010000 +105: 11010011100 \ No newline at end of file diff --git a/assets/resources/apps_data/barcode_data/code128c_encodings.txt b/assets/resources/apps_data/barcode_data/code128c_encodings.txt new file mode 100644 index 000000000..75cc71135 --- /dev/null +++ b/assets/resources/apps_data/barcode_data/code128c_encodings.txt @@ -0,0 +1,106 @@ +00: 11011001100 +01: 11001101100 +02: 11001100110 +03: 10010011000 +04: 10010001100 +05: 10001001100 +06: 10011001000 +07: 10011000100 +08: 10001100100 +09: 11001001000 +10: 11001000100 +11: 11000100100 +12: 10110011100 +13: 10011011100 +14: 10011001110 +15: 10111001100 +16: 10011101100 +17: 10011100110 +18: 11001110010 +19: 11001011100 +20: 11001001110 +21: 11011100100 +22: 11001110100 +23: 11101101110 +24: 11101001100 +25: 11100101100 +26: 11100100110 +27: 11101100100 +28: 11100110100 +29: 11100110010 +30: 11011011000 +31: 11011000110 +32: 11000110110 +33: 10100011000 +34: 10001011000 +35: 10001000110 +36: 10110001000 +37: 10001101000 +38: 10001100010 +39: 11010001000 +40: 11000101000 +41: 11000100010 +42: 10110111000 +43: 10110001110 +44: 10001101110 +45: 10111011000 +46: 10111000110 +47: 10001110110 +48: 11101110110 +49: 11010001110 +50: 11000101110 +51: 11011101000 +52: 11011100010 +53: 11011101110 +54: 11101011000 +55: 11101000110 +56: 11100010110 +57: 11101101000 +58: 11101100010 +59: 11100011010 +60: 11101111010 +61: 11001000010 +62: 11110001010 +63: 10100110000 +64: 10100001100 +65: 10010110000 +66: 10010000110 +67: 10000101100 +68: 10000100110 +69: 10110010000 +70: 10110000100 +71: 10011010000 +72: 10011000010 +73: 10000110100 +74: 10000110010 +75: 11000010010 +76: 11001010000 +77: 11110111010 +78: 11000010100 +79: 10001111010 +80: 10100111100 +81: 10010111100 +82: 10010011110 +83: 10111100100 +84: 10011110100 +85: 10011110010 +86: 11110100100 +87: 11110010100 +88: 11110010010 +89: 11011011110 +90: 11011110110 +91: 11110110110 +92: 10101111000 +93: 10100011110 +94: 10001011110 +95: 10111101000 +96: 10111100010 +97: 11110101000 +98: 11110100010 +99: 10111011110 +100: 10111101110 +101: 11101011110 +102: 11110101110 +103: 11010000100 +104: 11010010000 +105: 11010011100 diff --git a/assets/resources/apps_data/barcode_data/code39_encodings.txt b/assets/resources/apps_data/barcode_data/code39_encodings.txt new file mode 100644 index 000000000..a41ad16e9 --- /dev/null +++ b/assets/resources/apps_data/barcode_data/code39_encodings.txt @@ -0,0 +1,44 @@ +0: 000110100 +1: 100100001 +2: 001100001 +3: 101100000 +4: 000110001 +5: 100110000 +6: 001110000 +7: 000100101 +8: 100100100 +9: 001100100 +A: 100001001 +B: 001001001 +C: 101001000 +D: 000011001 +E: 100011000 +F: 001011000 +G: 000001101 +H: 100001100 +I: 001001100 +J: 000011100 +K: 100000011 +L: 001000011 +M: 101000010 +N: 000010011 +O: 100010010 +P: 001010010 +Q: 000000111 +R: 100000110 +S: 001000110 +T: 000010110 +U: 110000001 +V: 011000001 +W: 111000000 +X: 010010001 +Y: 110010000 +Z: 011010000 +-: 010000101 +.: 110000100 + : 011000100 +*: 010010100 +$: 010101000 +/: 010100010 ++: 010001010 +%: 000101010 \ No newline at end of file From 2e7d074c0c01515c4f63759c2186a24aafd30d8f Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Thu, 6 Jul 2023 01:28:28 +0200 Subject: [PATCH 046/364] Remove xtreme/private.h and rewire some things --- applications/main/xtreme_app/xtreme_app.c | 4 +--- applications/main/xtreme_app/xtreme_app.h | 1 + applications/services/loader/loader.c | 1 + .../services/power/power_service/power.h | 11 ----------- firmware/targets/f7/api_symbols.csv | 3 +++ furi/flipper.c | 3 ++- lib/xtreme/assets.c | 2 +- lib/xtreme/namespoof.h | 2 ++ lib/xtreme/private.h | 6 ------ lib/xtreme/settings.c | 1 - lib/xtreme/xtreme.h | 19 +++++++++++++++++-- 11 files changed, 28 insertions(+), 25 deletions(-) delete mode 100644 lib/xtreme/private.h diff --git a/applications/main/xtreme_app/xtreme_app.c b/applications/main/xtreme_app/xtreme_app.c index 01822084f..9a84d485f 100644 --- a/applications/main/xtreme_app/xtreme_app.c +++ b/applications/main/xtreme_app/xtreme_app.c @@ -120,9 +120,7 @@ bool xtreme_app_apply(XtremeApp* app) { if(app->show_slideshow) { callback_reboot(NULL); - } - - if(app->require_reboot) { + } else if(app->require_reboot) { popup_set_header(app->popup, "Rebooting...", 64, 26, AlignCenter, AlignCenter); popup_set_text(app->popup, "Applying changes...", 64, 40, AlignCenter, AlignCenter); popup_set_callback(app->popup, callback_reboot); diff --git a/applications/main/xtreme_app/xtreme_app.h b/applications/main/xtreme_app/xtreme_app.h index da6029ed1..c223d495b 100644 --- a/applications/main/xtreme_app/xtreme_app.h +++ b/applications/main/xtreme_app/xtreme_app.h @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include diff --git a/applications/services/loader/loader.c b/applications/services/loader/loader.c index f8fe9f699..f3c606fa0 100644 --- a/applications/services/loader/loader.c +++ b/applications/services/loader/loader.c @@ -11,6 +11,7 @@ #include #include #include +#include #define TAG "Loader" #define LOADER_MAGIC_THREAD_VALUE 0xDEADBEEF diff --git a/applications/services/power/power_service/power.h b/applications/services/power/power_service/power.h index a2dc34f90..ffe756f97 100644 --- a/applications/services/power/power_service/power.h +++ b/applications/services/power/power_service/power.h @@ -25,17 +25,6 @@ typedef enum { PowerEventTypeBatteryLevelChanged, } PowerEventType; -typedef enum { - BatteryIconOff, - BatteryIconBar, - BatteryIconPercent, - BatteryIconInvertedPercent, - BatteryIconRetro3, - BatteryIconRetro5, - BatteryIconBarPercent, - BatteryIconCount, -} BatteryIcon; - typedef union { uint8_t battery_level; } PowerEventData; diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 88d52d6df..a81793f5b 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -327,7 +327,10 @@ Function,-,SK6805_set_led_color,void,"uint8_t, uint8_t, uint8_t, uint8_t" Function,-,SK6805_update,void, Function,-,SystemCoreClockUpdate,void, Function,-,SystemInit,void, +Function,+,XTREME_ASSETS_FREE,void, +Function,+,XTREME_ASSETS_LOAD,void, Function,+,XTREME_SETTINGS,XtremeSettings*, +Function,-,XTREME_SETTINGS_LOAD,void, Function,+,XTREME_SETTINGS_SAVE,void, Function,-,_Exit,void,int Function,-,__assert,void,"const char*, int, const char*" diff --git a/furi/flipper.c b/furi/flipper.c index bdb80c851..f74287bc6 100644 --- a/furi/flipper.c +++ b/furi/flipper.c @@ -13,7 +13,8 @@ #include #include #include -#include +#include +#include #define TAG "Flipper" diff --git a/lib/xtreme/assets.c b/lib/xtreme/assets.c index 59df1206d..821665ec4 100644 --- a/lib/xtreme/assets.c +++ b/lib/xtreme/assets.c @@ -1,6 +1,6 @@ #include "xtreme.h" -#include "private.h" #include +#include #include #include #include diff --git a/lib/xtreme/namespoof.h b/lib/xtreme/namespoof.h index 5d7e91a1f..15a280bd9 100644 --- a/lib/xtreme/namespoof.h +++ b/lib/xtreme/namespoof.h @@ -3,3 +3,5 @@ #define NAMESPOOF_HEADER "Flipper Name File" #define NAMESPOOF_VERSION 1 #define NAMESPOOF_PATH EXT_PATH("dolphin/name.txt") + +void NAMESPOOF_INIT(); diff --git a/lib/xtreme/private.h b/lib/xtreme/private.h deleted file mode 100644 index 4bed4e4e7..000000000 --- a/lib/xtreme/private.h +++ /dev/null @@ -1,6 +0,0 @@ -#pragma once - -void NAMESPOOF_INIT(); -void XTREME_SETTINGS_LOAD(); -void XTREME_ASSETS_LOAD(); -void XTREME_ASSETS_FREE(); diff --git a/lib/xtreme/settings.c b/lib/xtreme/settings.c index 57e5670e0..e9326fb8c 100644 --- a/lib/xtreme/settings.c +++ b/lib/xtreme/settings.c @@ -1,5 +1,4 @@ #include "xtreme.h" -#include "private.h" #include #include diff --git a/lib/xtreme/xtreme.h b/lib/xtreme/xtreme.h index 134f737e1..cfef8173a 100644 --- a/lib/xtreme/xtreme.h +++ b/lib/xtreme/xtreme.h @@ -1,7 +1,7 @@ #pragma once -#include -#include +#include +#include #ifdef __cplusplus extern "C" { @@ -12,6 +12,17 @@ extern "C" { #define XTREME_APPS_PATH CFG_PATH("xtreme_apps.txt") #define XTREME_ASSETS_PACK_NAME_LEN 32 +typedef enum { + BatteryIconOff, + BatteryIconBar, + BatteryIconPercent, + BatteryIconInvertedPercent, + BatteryIconRetro3, + BatteryIconRetro5, + BatteryIconBarPercent, + BatteryIconCount, +} BatteryIcon; + typedef struct { bool is_nsfw; // TODO: replace with packs text support @@ -46,9 +57,13 @@ typedef struct { uint32_t charge_cap; } XtremeSettings; +void XTREME_SETTINGS_LOAD(); void XTREME_SETTINGS_SAVE(); XtremeSettings* XTREME_SETTINGS(); +void XTREME_ASSETS_LOAD(); +void XTREME_ASSETS_FREE(); + #ifdef __cplusplus } #endif From 97c11926a13276cc1753151b46403029bbc0de9d Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Thu, 6 Jul 2023 01:37:12 +0200 Subject: [PATCH 047/364] Fix switching asset pack not updating icons/text --- .../scenes/xtreme_app_scene_interface_graphics.c | 1 + applications/main/xtreme_app/xtreme_app.c | 10 ++++++++++ applications/main/xtreme_app/xtreme_app.h | 1 + 3 files changed, 12 insertions(+) diff --git a/applications/main/xtreme_app/scenes/xtreme_app_scene_interface_graphics.c b/applications/main/xtreme_app/scenes/xtreme_app_scene_interface_graphics.c index 5b016815f..450b78bc2 100644 --- a/applications/main/xtreme_app/scenes/xtreme_app_scene_interface_graphics.c +++ b/applications/main/xtreme_app/scenes/xtreme_app_scene_interface_graphics.c @@ -24,6 +24,7 @@ static void xtreme_app_scene_interface_graphics_asset_pack_changed(VariableItem* XTREME_ASSETS_PACK_NAME_LEN); app->asset_pack_index = index; app->save_settings = true; + app->apply_pack = true; } const char* const anim_speed_names[] = diff --git a/applications/main/xtreme_app/xtreme_app.c b/applications/main/xtreme_app/xtreme_app.c index 9a84d485f..444341a87 100644 --- a/applications/main/xtreme_app/xtreme_app.c +++ b/applications/main/xtreme_app/xtreme_app.c @@ -129,6 +129,16 @@ bool xtreme_app_apply(XtremeApp* app) { popup_enable_timeout(app->popup); view_dispatcher_switch_to_view(app->view_dispatcher, XtremeAppViewPopup); return true; + } else if(app->apply_pack) { + popup_set_header(app->popup, "Reloading...", 64, 26, AlignCenter, AlignCenter); + popup_set_text(app->popup, "Applying asset pack...", 64, 40, AlignCenter, AlignCenter); + popup_set_callback(app->popup, NULL); + popup_set_context(app->popup, NULL); + popup_set_timeout(app->popup, 0); + popup_disable_timeout(app->popup); + view_dispatcher_switch_to_view(app->view_dispatcher, XtremeAppViewPopup); + XTREME_ASSETS_FREE(); + XTREME_ASSETS_LOAD(); } furi_record_close(RECORD_STORAGE); diff --git a/applications/main/xtreme_app/xtreme_app.h b/applications/main/xtreme_app/xtreme_app.h index c223d495b..4089eb820 100644 --- a/applications/main/xtreme_app/xtreme_app.h +++ b/applications/main/xtreme_app/xtreme_app.h @@ -65,6 +65,7 @@ typedef struct { bool save_angry; bool save_backlight; bool save_settings; + bool apply_pack; bool show_slideshow; bool require_reboot; } XtremeApp; From d9b95fd156894f0be12e9c047f79bc19dd728901 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 6 Jul 2023 02:42:05 +0300 Subject: [PATCH 048/364] Misc folder issues --- .../system/storage_move_to_sd/storage_move_to_sd.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/applications/system/storage_move_to_sd/storage_move_to_sd.c b/applications/system/storage_move_to_sd/storage_move_to_sd.c index 4a0f3f4ad..5aaacd736 100644 --- a/applications/system/storage_move_to_sd/storage_move_to_sd.c +++ b/applications/system/storage_move_to_sd/storage_move_to_sd.c @@ -28,6 +28,14 @@ static void storage_move_to_sd_remove_region() { if(storage_common_exists(storage, INT_PATH(".region_data"))) { storage_common_remove(storage, INT_PATH(".region_data")); } + if(storage_common_exists(storage, EXT_PATH("apps/Misc/totp.conf"))) { + storage_common_rename( + storage, EXT_PATH("apps/Misc/totp.conf"), EXT_PATH("authenticator/totp.conf")); + } + if(storage_common_exists(storage, EXT_PATH("apps/Misc/barcodegen.save"))) { + storage_common_remove(storage, EXT_PATH("apps/Misc/barcodegen.save")); + storage_common_remove(storage, EXT_PATH("apps/Misc")); + } furi_record_close(RECORD_STORAGE); } From a6978bfd2d8fc30045e1d9dc20a14c3f230fad10 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 6 Jul 2023 02:44:28 +0300 Subject: [PATCH 049/364] WIP OFW PR 2825: NFC: Improved MFC emulation on some readers Not finished yet, added in current condition for more tests by AloneLiberty --- lib/nfc/nfc_worker.c | 11 ++++++---- lib/nfc/protocols/mifare_classic.c | 33 +++++++++++++++--------------- lib/nfc/protocols/nfca.c | 12 +++++------ lib/nfc/protocols/nfca.h | 3 +++ 4 files changed, 32 insertions(+), 27 deletions(-) diff --git a/lib/nfc/nfc_worker.c b/lib/nfc/nfc_worker.c index 61212540d..959696e7c 100644 --- a/lib/nfc/nfc_worker.c +++ b/lib/nfc/nfc_worker.c @@ -1107,7 +1107,9 @@ void nfc_worker_emulate_mf_classic(NfcWorker* nfc_worker) { furi_hal_nfc_listen_start(nfc_data); while(nfc_worker->state == NfcWorkerStateMfClassicEmulate) { //-V1044 if(furi_hal_nfc_listen_rx(&tx_rx, 300)) { - mf_classic_emulator(&emulator, &tx_rx, false); + if(!mf_classic_emulator(&emulator, &tx_rx, false)) { + furi_hal_nfc_listen_start(nfc_data); + } } } if(emulator.data_changed) { @@ -1382,8 +1384,6 @@ void nfc_worker_analyze_reader(NfcWorker* nfc_worker) { bool reader_no_data_notified = true; while(nfc_worker->state == NfcWorkerStateAnalyzeReader) { - furi_hal_nfc_stop_cmd(); - furi_delay_ms(5); furi_hal_nfc_listen_start(nfc_data); if(furi_hal_nfc_listen_rx(&tx_rx, 300)) { if(reader_no_data_notified) { @@ -1394,7 +1394,9 @@ void nfc_worker_analyze_reader(NfcWorker* nfc_worker) { NfcProtocol protocol = reader_analyzer_guess_protocol(reader_analyzer, tx_rx.rx_data, tx_rx.rx_bits / 8); if(protocol == NfcDeviceProtocolMifareClassic) { - mf_classic_emulator(&emulator, &tx_rx, true); + if(!mf_classic_emulator(&emulator, &tx_rx, true)) { + furi_hal_nfc_listen_start(nfc_data); + } } } else { reader_no_data_received_cnt++; @@ -1406,6 +1408,7 @@ void nfc_worker_analyze_reader(NfcWorker* nfc_worker) { FURI_LOG_D(TAG, "No data from reader"); continue; } + furi_delay_ms(1); } rfal_platform_spi_release(); diff --git a/lib/nfc/protocols/mifare_classic.c b/lib/nfc/protocols/mifare_classic.c index 9547c68d0..a6d521dc5 100644 --- a/lib/nfc/protocols/mifare_classic.c +++ b/lib/nfc/protocols/mifare_classic.c @@ -869,7 +869,7 @@ bool mf_classic_emulator( if(!furi_hal_nfc_tx_rx(tx_rx, 300)) { FURI_LOG_D( TAG, - "Error in tx rx. Tx :%d bits, Rx: %d bits", + "Error in tx rx. Tx: %d bits, Rx: %d bits", tx_rx->tx_bits, tx_rx->rx_bits); break; @@ -883,12 +883,17 @@ bool mf_classic_emulator( break; } - if(cmd == 0x50 && plain_data[1] == 0x00) { + if(cmd == NFCA_CMD_HALT && plain_data[1] == 0x00) { FURI_LOG_T(TAG, "Halt received"); - furi_hal_nfc_listen_sleep(); - command_processed = true; + return false; + } + + if(cmd == NFCA_CMD_RATS && !is_encrypted) { + // Mifare Classic doesn't support ATS, NACK it and start listening again + FURI_LOG_T(TAG, "RATS received"); break; } + if(cmd == MF_CLASSIC_AUTH_KEY_A_CMD || cmd == MF_CLASSIC_AUTH_KEY_B_CMD) { uint8_t block = plain_data[1]; uint64_t key = 0; @@ -903,8 +908,7 @@ bool mf_classic_emulator( access_key = MfClassicKeyA; } else { FURI_LOG_D(TAG, "Key not known"); - command_processed = true; - break; + return false; } } else { if(mf_classic_is_key_found( @@ -914,8 +918,7 @@ bool mf_classic_emulator( access_key = MfClassicKeyB; } else { FURI_LOG_D(TAG, "Key not known"); - command_processed = true; - break; + return false; } } @@ -943,16 +946,14 @@ bool mf_classic_emulator( tx_rx->tx_bits = sizeof(nt) * 8; tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; } + if(!furi_hal_nfc_tx_rx(tx_rx, 500)) { FURI_LOG_E(TAG, "Error in NT exchange"); - command_processed = true; - break; + return false; } if(tx_rx->rx_bits != 64) { - FURI_LOG_W(TAG, "Incorrect nr + ar length: %d", tx_rx->rx_bits); - command_processed = true; - break; + return false; } uint32_t nr = nfc_util_bytes2num(tx_rx->rx_data, 4); @@ -963,8 +964,7 @@ bool mf_classic_emulator( if(cardRr != prng_successor(nonce, 64)) { FURI_LOG_T(TAG, "Wrong AUTH! %08lX != %08lX", cardRr, prng_successor(nonce, 64)); // Don't send NACK, as the tag doesn't send it - command_processed = true; - break; + return false; } uint32_t ans = prng_successor(nonce, 96); @@ -1156,6 +1156,7 @@ bool mf_classic_emulator( tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; tx_rx->tx_bits = 4; furi_hal_nfc_tx_rx(tx_rx, 300); + return false; } return true; @@ -1164,7 +1165,7 @@ bool mf_classic_emulator( void mf_classic_halt(FuriHalNfcTxRxContext* tx_rx, Crypto1* crypto) { furi_assert(tx_rx); - uint8_t plain_data[4] = {0x50, 0x00, 0x00, 0x00}; + uint8_t plain_data[4] = {NFCA_CMD_HALT, 0x00, 0x00, 0x00}; nfca_append_crc16(plain_data, 2); if(crypto) { diff --git a/lib/nfc/protocols/nfca.c b/lib/nfc/protocols/nfca.c index c401f8cc5..ab4f3f23c 100644 --- a/lib/nfc/protocols/nfca.c +++ b/lib/nfc/protocols/nfca.c @@ -3,8 +3,6 @@ #include #include -#define NFCA_CMD_RATS (0xE0U) - #define NFCA_CRC_INIT (0x6363) #define NFCA_F_SIG (13560000.0) @@ -22,7 +20,7 @@ typedef struct { static uint8_t nfca_default_ats[] = {0x05, 0x78, 0x80, 0x80, 0x00}; -static uint8_t nfca_sleep_req[] = {0x50, 0x00}; +static uint8_t nfca_halt_req[] = {NFCA_CMD_HALT, 0x00}; uint16_t nfca_get_crc16(uint8_t* buff, uint16_t len) { uint16_t crc = NFCA_CRC_INIT; @@ -50,17 +48,17 @@ bool nfca_emulation_handler( uint16_t buff_rx_len, uint8_t* buff_tx, uint16_t* buff_tx_len) { - bool sleep = false; + bool halt = false; uint8_t rx_bytes = buff_rx_len / 8; - if(rx_bytes == sizeof(nfca_sleep_req) && !memcmp(buff_rx, nfca_sleep_req, rx_bytes)) { - sleep = true; + if(rx_bytes == sizeof(nfca_halt_req) && !memcmp(buff_rx, nfca_halt_req, rx_bytes)) { + halt = true; } else if(rx_bytes == sizeof(nfca_cmd_rats) && buff_rx[0] == NFCA_CMD_RATS) { memcpy(buff_tx, nfca_default_ats, sizeof(nfca_default_ats)); *buff_tx_len = sizeof(nfca_default_ats) * 8; } - return sleep; + return halt; } static void nfca_add_bit(DigitalSignal* signal, bool bit) { diff --git a/lib/nfc/protocols/nfca.h b/lib/nfc/protocols/nfca.h index 498ef2843..e4978a3e0 100644 --- a/lib/nfc/protocols/nfca.h +++ b/lib/nfc/protocols/nfca.h @@ -5,6 +5,9 @@ #include +#define NFCA_CMD_RATS (0xE0U) +#define NFCA_CMD_HALT (0x50U) + typedef struct { DigitalSignal* one; DigitalSignal* zero; From 6b99a117ccf7cfbbb35ff17732a8362b76b7f652 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Thu, 6 Jul 2023 01:51:10 +0200 Subject: [PATCH 050/364] Fix cli bridge crash on open --- .../external/cli_bridge/cligui_main.c | 14 ++++----- .../external/cli_bridge/internal_defs.h | 31 +------------------ 2 files changed, 7 insertions(+), 38 deletions(-) diff --git a/applications/external/cli_bridge/cligui_main.c b/applications/external/cli_bridge/cligui_main.c index ef53203f0..60a9b12dd 100644 --- a/applications/external/cli_bridge/cligui_main.c +++ b/applications/external/cli_bridge/cligui_main.c @@ -2,6 +2,7 @@ #include "cli_control.h" #include "text_input.h" #include "console_output.h" +#include static bool cligui_custom_event_cb(void* context, uint32_t event) { UNUSED(event); @@ -69,9 +70,8 @@ int32_t cligui_main(void* p) { // Unlock loader-lock and save app thread FuriThread* temp_save_appthr; Loader* loader = furi_record_open(RECORD_LOADER); - Loader_internal* loader_i = (Loader_internal*)loader; - temp_save_appthr = loader_i->app.thread; - loader_unlock(loader); + temp_save_appthr = loader->app.thread; + loader->app.thread = NULL; furi_record_close(RECORD_LOADER); CliguiApp* cligui = malloc(sizeof(CliguiApp)); @@ -138,11 +138,9 @@ int32_t cligui_main(void* p) { free(cligui->data); free(cligui); - // Don't touch system loader!!! We restoring previous app thread here, we love kostily and velosipedy, bydlo kod forever! - - Loader* loader1 = furi_record_open(RECORD_LOADER); - Loader_internal* loader_ii = (Loader_internal*)loader1; - loader_ii->app.thread = temp_save_appthr; + // We restoring previous app thread here, we love kostily and velosipedy, bydlo kod forever! + loader = furi_record_open(RECORD_LOADER); + loader->app.thread = temp_save_appthr; furi_record_close(RECORD_LOADER); return 0; diff --git a/applications/external/cli_bridge/internal_defs.h b/applications/external/cli_bridge/internal_defs.h index 9758b9aae..25b1448aa 100644 --- a/applications/external/cli_bridge/internal_defs.h +++ b/applications/external/cli_bridge/internal_defs.h @@ -71,35 +71,6 @@ typedef struct { void* input_callback_context; } ViewPort_internal; -typedef struct { - Gui* gui; - ViewDispatcher* view_dispatcher; - Menu* primary_menu; - Submenu* settings_menu; - - void (*closed_callback)(void*); - void* closed_callback_context; - - void (*click_callback)(const char*, void*); - void* click_callback_context; - - FuriThread* thread; -} LoaderMenu_internal; - -typedef struct { - char* args; - char* name; - FuriThread* thread; - bool insomniac; -} LoaderAppData_internal; - -typedef struct { - FuriPubSub* pubsub; - FuriMessageQueue* queue; - LoaderMenu_internal* loader_menu; - LoaderAppData_internal app; -} Loader_internal; - typedef struct { CliCallback callback; void* context; @@ -126,4 +97,4 @@ typedef struct { void* session; size_t cursor_position; -} Cli_internal; \ No newline at end of file +} Cli_internal; From 1a283cde3eeab922eedfdda43b91e41286353397 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 6 Jul 2023 03:03:10 +0300 Subject: [PATCH 051/364] Update changelog --- CHANGELOG.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 37d8c680d..05aba5e16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,28 @@ ## New changes +* SubGHz: Keeloq: Centurion Nova read and emulate support (+ add manually) +* SubGHz: FAAC SLH - UI Fixes, Fix sending signals with no seed +* SubGHz: Code cleanup, proper handling of protocols ignore options (by @gid9798 | PR #516) +* SubGHz: Various UI fixes (by @wosk | PR #527) +* NFC: Fixed issue #532 (Mifare classic user dict - delete removes more than selected key) +* Infrared: Updated universal remote assets (by @amec0e | PR #529) +* Plugins: Use correct categories for all plugins (extra pack too) +* Plugins: Various fixes for uFBT (by @hedger) +* Plugins: Moved Barcode Generator [(by Kingal1337)](https://github.com/Kingal1337/flipper-barcode-generator) from extra pack into base fw, old barcode generator was removed +* Plugins: Updated ESP32: WiFi Marauder companion plugin [(by 0xchocolate)](https://github.com/0xchocolate/flipperzero-wifi-marauder) +* Plugins: Updated i2c Tools [(by NaejEL)](https://github.com/NaejEL/flipperzero-i2ctools) +* Settings: Change LED and volume settings by 5% steps (by @cokyrain) +* BLE: BadBT fixes and furi_hal_bt cleanup (by @Willy-JL) +* WIP OFW PR 2825: NFC: Improved MFC emulation on some readers (by AloneLiberty) +* OFW PR 2829: Decode only supported Oregon 3 sensor (by @wosk) * OFW PR: Update OFW PR 2782 +* OFW: NFC: Mf Ultralight emulation optimization +* OFW: Furi_Power: fix furi_hal_power_enable_otg +* OFW: FuriHal: allow nulling null isr +* OFW: FuriHal, Infrared, Protobuf: various fixes and improvements +* OFW: Picopass fix ice +* OFW: Desktop settings: show icon and name for external applications +* OFW: Furi,FuriHal: various improvements +* OFW: Debug apps: speaker, uart_echo with baudrate * OFW: Add Mitsubishi MSZ-AP25VGK universal ac remote * OFW: Fix roll-over in file browser and archive * OFW: Fix fr-FR-mac keylayout From 1c7719f6b04331ac9e4aa37a1c82f13b592c0920 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Thu, 6 Jul 2023 02:40:11 +0200 Subject: [PATCH 052/364] Expose thread interfaces --- furi/core/thread.c | 36 +----------------------------------- furi/core/thread_i.h | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 35 deletions(-) create mode 100644 furi/core/thread_i.h diff --git a/furi/core/thread.c b/furi/core/thread.c index 657b867d1..2b27a81f4 100644 --- a/furi/core/thread.c +++ b/furi/core/thread.c @@ -1,4 +1,5 @@ #include "thread.h" +#include "thread_i.h" #include "kernel.h" #include "memmgr.h" #include "memmgr_heap.h" @@ -16,41 +17,6 @@ #define THREAD_NOTIFY_INDEX 1 // Index 0 is used for stream buffers -typedef struct FuriThreadStdout FuriThreadStdout; - -struct FuriThreadStdout { - FuriThreadStdoutWriteCallback write_callback; - FuriString* buffer; -}; - -struct FuriThread { - FuriThreadState state; - int32_t ret; - - FuriThreadCallback callback; - void* context; - - FuriThreadStateCallback state_callback; - void* state_context; - - char* name; - char* appid; - - FuriThreadPriority priority; - - TaskHandle_t task_handle; - size_t heap_size; - - FuriThreadStdout output; - - // Keep all non-alignable byte types in one place, - // this ensures that the size of this structure is minimal - bool is_service; - bool heap_trace_enabled; - - configSTACK_DEPTH_TYPE stack_size; -}; - static size_t __furi_thread_stdout_write(FuriThread* thread, const char* data, size_t size); static int32_t __furi_thread_stdout_flush(FuriThread* thread); diff --git a/furi/core/thread_i.h b/furi/core/thread_i.h new file mode 100644 index 000000000..2760d6a26 --- /dev/null +++ b/furi/core/thread_i.h @@ -0,0 +1,36 @@ +#pragma once + +#include "thread.h" + +typedef struct { + FuriThreadStdoutWriteCallback write_callback; + FuriString* buffer; +} FuriThreadStdout; + +struct FuriThread { + FuriThreadState state; + int32_t ret; + + FuriThreadCallback callback; + void* context; + + FuriThreadStateCallback state_callback; + void* state_context; + + char* name; + char* appid; + + FuriThreadPriority priority; + + TaskHandle_t task_handle; + size_t heap_size; + + FuriThreadStdout output; + + // Keep all non-alignable byte types in one place, + // this ensures that the size of this structure is minimal + bool is_service; + bool heap_trace_enabled; + + configSTACK_DEPTH_TYPE stack_size; +}; From df7a39cc587161280683e0e34980154d1e582b28 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Thu, 6 Jul 2023 02:42:43 +0200 Subject: [PATCH 053/364] =?UTF-8?q?Fix=20cli=20bridge=20"internal"=20defin?= =?UTF-8?q?itions=20=F0=9F=A4=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Seriously. Who. THE FUCK. Thought this was a good idea??? You DO NOT keep a copy of memory structures. You import them. Definitions change, stop intentionally handicapping your code. Don't be a RogueMaster V2. --- .../external/cli_bridge/cli_control.c | 14 +-- .../external/cli_bridge/cligui_main.c | 7 +- .../external/cli_bridge/cligui_main_i.h | 4 +- .../external/cli_bridge/internal_defs.h | 100 ------------------ 4 files changed, 11 insertions(+), 114 deletions(-) delete mode 100644 applications/external/cli_bridge/internal_defs.h diff --git a/applications/external/cli_bridge/cli_control.c b/applications/external/cli_bridge/cli_control.c index 80a0fd490..d4760275b 100644 --- a/applications/external/cli_bridge/cli_control.c +++ b/applications/external/cli_bridge/cli_control.c @@ -3,6 +3,7 @@ #include #include #include +#include #include "cligui_main_i.h" #include @@ -33,11 +34,10 @@ static size_t real_rx_handler(uint8_t* buffer, size_t size, uint32_t timeout) { return rx_cnt; } -static CliCommand_internal* getInternalCliCommand(Cli* cli, const char* name) { +static CliCommand* getCliCommand(Cli* cli, const char* name) { FuriString* target_command = furi_string_alloc(); furi_string_set_str(target_command, name); - CliCommand_internal* command = - CliCommandTree_internal_get(((Cli_internal*)cli)->commands, target_command); + CliCommand* command = CliCommandTree_get(cli->commands, target_command); furi_string_free(target_command); return command; } @@ -53,12 +53,12 @@ static CliSession session; void latch_tx_handler() { Cli* global_cli = furi_record_open(RECORD_CLI); - CliCommand_internal* help_command = getInternalCliCommand(global_cli, "help"); + CliCommand* help_command = getCliCommand(global_cli, "help"); cliThread = help_command->context; furi_thread_set_stdout_callback(tx_handler_stdout); if(cliThread != NULL) { - ((FuriThread_internal*)cliThread)->output.write_callback = &tx_handler_stdout; + cliThread->output.write_callback = &tx_handler_stdout; } rx_stream = furi_stream_buffer_alloc(128, 1); @@ -78,7 +78,7 @@ void unlatch_tx_handler(bool persist) { Cli* global_cli = furi_record_open(RECORD_CLI); // Stash cliThread if not null if(cliThread != NULL) { - CliCommand_internal* help_command = getInternalCliCommand(global_cli, "help"); + CliCommand* help_command = getCliCommand(global_cli, "help"); help_command->context = cliThread; } // Switch to new session @@ -96,7 +96,7 @@ void unlatch_tx_handler(bool persist) { furi_stream_buffer_send(rx_stream, "_", 1, FuriWaitForever); // Reconfigure stdout_callback to cli_vcp if(cliThread != NULL) { - ((FuriThread_internal*)cliThread)->output.write_callback = cli_vcp.tx_stdout; + cliThread->output.write_callback = cli_vcp.tx_stdout; } // At this point, all cli_vcp functions should be back. furi_stream_buffer_free(rx_stream); diff --git a/applications/external/cli_bridge/cligui_main.c b/applications/external/cli_bridge/cligui_main.c index 60a9b12dd..f275e1d4f 100644 --- a/applications/external/cli_bridge/cligui_main.c +++ b/applications/external/cli_bridge/cligui_main.c @@ -3,6 +3,7 @@ #include "text_input.h" #include "console_output.h" #include +#include static bool cligui_custom_event_cb(void* context, uint32_t event) { UNUSED(event); @@ -83,11 +84,9 @@ int32_t cligui_main(void* p) { cligui->gui = furi_record_open(RECORD_GUI); cligui->view_dispatcher = view_dispatcher_alloc(); - cligui->view_dispatcher_i = (ViewDispatcher_internal*)(cligui->view_dispatcher); - prev_input_callback = - ((ViewPort_internal*)cligui->view_dispatcher_i->view_port)->input_callback; + prev_input_callback = cligui->view_dispatcher->view_port->input_callback; view_port_input_callback_set( - cligui->view_dispatcher_i->view_port, input_callback_wrapper, cligui); + cligui->view_dispatcher->view_port, input_callback_wrapper, cligui); view_dispatcher_enable_queue(cligui->view_dispatcher); view_dispatcher_set_event_callback_context(cligui->view_dispatcher, cligui); view_dispatcher_set_custom_event_callback(cligui->view_dispatcher, cligui_custom_event_cb); diff --git a/applications/external/cli_bridge/cligui_main_i.h b/applications/external/cli_bridge/cligui_main_i.h index b3b5823c9..36e3e156a 100644 --- a/applications/external/cli_bridge/cligui_main_i.h +++ b/applications/external/cli_bridge/cligui_main_i.h @@ -11,7 +11,6 @@ #include #include #include -#include "internal_defs.h" #define TEXT_BOX_STORE_SIZE (4096) #define TEXT_INPUT_STORE_SIZE (512) @@ -37,5 +36,4 @@ typedef struct { char text_input_store[TEXT_INPUT_STORE_SIZE + 1]; TextInput* text_input; ViewDispatcher* view_dispatcher; - ViewDispatcher_internal* view_dispatcher_i; -} CliguiApp; \ No newline at end of file +} CliguiApp; diff --git a/applications/external/cli_bridge/internal_defs.h b/applications/external/cli_bridge/internal_defs.h deleted file mode 100644 index 25b1448aa..000000000 --- a/applications/external/cli_bridge/internal_defs.h +++ /dev/null @@ -1,100 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -typedef struct { - FuriThreadStdoutWriteCallback write_callback; - FuriString* buffer; -} FuriThreadStdout_internal; - -typedef struct { - bool is_service; - FuriThreadState state; - int32_t ret; - - FuriThreadCallback callback; - void* context; - - FuriThreadStateCallback state_callback; - void* state_context; - - char* name; - configSTACK_DEPTH_TYPE stack_size; - FuriThreadPriority priority; - - TaskHandle_t task_handle; - bool heap_trace_enabled; - size_t heap_size; - - FuriThreadStdout_internal output; -} FuriThread_internal; - -DICT_DEF2(ViewDict, uint32_t, M_DEFAULT_OPLIST, View*, M_PTR_OPLIST) -typedef struct { - FuriMessageQueue* queue; - Gui* gui; - ViewPort* view_port; - ViewDict_t views; - - View* current_view; - - View* ongoing_input_view; - uint8_t ongoing_input; - - ViewDispatcherCustomEventCallback custom_event_callback; - ViewDispatcherNavigationEventCallback navigation_event_callback; - ViewDispatcherTickEventCallback tick_event_callback; - uint32_t tick_period; - void* event_context; -} ViewDispatcher_internal; - -typedef struct { - Gui* gui; - bool is_enabled; - ViewPortOrientation orientation; - - uint8_t width; - uint8_t height; - - ViewPortDrawCallback draw_callback; - void* draw_callback_context; - - ViewPortInputCallback input_callback; - void* input_callback_context; -} ViewPort_internal; - -typedef struct { - CliCallback callback; - void* context; - uint32_t flags; -} CliCommand_internal; - -#define CLI_COMMANDS_TREE_RANK 4 -BPTREE_DEF2( - CliCommandTree_internal, - CLI_COMMANDS_TREE_RANK, - FuriString*, - FURI_STRING_OPLIST, - CliCommand_internal, - M_POD_OPLIST) - -#define M_OPL_CliCommandTree_internal_t() BPTREE_OPLIST(CliCommandTree_internal, M_POD_OPLIST) - -typedef struct { - CliCommandTree_internal_t commands; - void* mutex; - void* idle_sem; - void* last_line; - void* line; - void* session; - - size_t cursor_position; -} Cli_internal; From eab5813f62ffa2da48368766d0f4f34b2d5ee42d Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Thu, 6 Jul 2023 02:47:41 +0200 Subject: [PATCH 054/364] Fix build --- furi/core/thread_i.h | 1 + 1 file changed, 1 insertion(+) diff --git a/furi/core/thread_i.h b/furi/core/thread_i.h index 2760d6a26..c3289f7ad 100644 --- a/furi/core/thread_i.h +++ b/furi/core/thread_i.h @@ -1,6 +1,7 @@ #pragma once #include "thread.h" +#include "string.h" typedef struct { FuriThreadStdoutWriteCallback write_callback; From 2be6e330eb569445d5620dbbc9592b583271a4e4 Mon Sep 17 00:00:00 2001 From: gid9798 <30450294+gid9798@users.noreply.github.com> Date: Thu, 6 Jul 2023 10:19:58 +0300 Subject: [PATCH 055/364] Protoview: new ext_cc1101 driver --- applications/external/protoview/app.c | 27 ++++---- applications/external/protoview/app.h | 3 + applications/external/protoview/app_subghz.c | 64 ++++++++++--------- .../protoview/helpers/radio_device_loader.c | 64 +++++++++++++++++++ .../protoview/helpers/radio_device_loader.h | 15 +++++ .../external/protoview/view_direct_sampling.c | 19 ++++-- 6 files changed, 142 insertions(+), 50 deletions(-) create mode 100644 applications/external/protoview/helpers/radio_device_loader.c create mode 100644 applications/external/protoview/helpers/radio_device_loader.h diff --git a/applications/external/protoview/app.c b/applications/external/protoview/app.c index 3e22b3781..868ec8f16 100644 --- a/applications/external/protoview/app.c +++ b/applications/external/protoview/app.c @@ -118,6 +118,8 @@ static void app_switch_view(ProtoViewApp* app, ProtoViewCurrentView switchto) { /* Allocate the application state and initialize a number of stuff. * This is called in the entry point to create the application state. */ ProtoViewApp* protoview_app_alloc() { + furi_hal_power_suppress_charge_enter(); + ProtoViewApp* app = malloc(sizeof(ProtoViewApp)); // Init shared data structures @@ -167,16 +169,14 @@ ProtoViewApp* protoview_app_alloc() { app->frequency = subghz_setting_get_default_frequency(app->setting); app->modulation = 0; /* Defaults to ProtoViewModulations[0]. */ - // Enable power for External CC1101 if it is connected - furi_hal_subghz_enable_ext_power(); - // Auto switch to internal radio if external radio is not available - furi_delay_ms(15); - if(!furi_hal_subghz_check_radio()) { - furi_hal_subghz_select_radio_type(SubGhzRadioInternal); - furi_hal_subghz_init_radio_type(SubGhzRadioInternal); - } + // Init & set radio_device + subghz_devices_init(); + app->radio_device = + radio_device_loader_set(app->radio_device, SubGhzRadioDeviceTypeExternalCC1101); + + subghz_devices_reset(app->radio_device); + subghz_devices_idle(app->radio_device); - furi_hal_power_suppress_charge_enter(); app->running = 1; return app; @@ -188,13 +188,10 @@ ProtoViewApp* protoview_app_alloc() { void protoview_app_free(ProtoViewApp* app) { furi_assert(app); - // Put CC1101 on sleep, this also restores charging. - radio_sleep(app); + subghz_devices_sleep(app->radio_device); + radio_device_loader_end(app->radio_device); - // Disable power for External CC1101 if it was enabled and module is connected - furi_hal_subghz_disable_ext_power(); - // Reinit SPI handles for internal radio / nfc - furi_hal_subghz_init_radio_type(SubGhzRadioInternal); + subghz_devices_deinit(); // View related. view_port_enabled_set(app->view_port, false); diff --git a/applications/external/protoview/app.h b/applications/external/protoview/app.h index 624c118e2..5fb0adf34 100644 --- a/applications/external/protoview/app.h +++ b/applications/external/protoview/app.h @@ -19,6 +19,7 @@ #include #include #include "raw_samples.h" +#include "helpers/radio_device_loader.h" #define TAG "ProtoView" #define PROTOVIEW_RAW_VIEW_DEFAULT_SCALE 100 // 100us is 1 pixel by default @@ -132,6 +133,8 @@ struct ProtoViewApp { ProtoViewTxRx* txrx; /* Radio state. */ SubGhzSetting* setting; /* A list of valid frequencies. */ + const SubGhzDevice* radio_device; + /* Generic app state. */ int running; /* Once false exists the app. */ uint32_t signal_bestlen; /* Longest coherent signal observed so far. */ diff --git a/applications/external/protoview/app_subghz.c b/applications/external/protoview/app_subghz.c index dcb6d492b..204dcba0d 100644 --- a/applications/external/protoview/app_subghz.c +++ b/applications/external/protoview/app_subghz.c @@ -37,22 +37,23 @@ ProtoViewModulation ProtoViewModulations[] = { * subghz system and put it into idle state. */ void radio_begin(ProtoViewApp* app) { furi_assert(app); - furi_hal_subghz_reset(); - furi_hal_subghz_idle(); - - /* Power circuits are noisy. Suppressing the charge while we use - * ProtoView will improve the RF performances. */ - furi_hal_power_suppress_charge_enter(); + subghz_devices_reset(app->radio_device); + subghz_devices_idle(app->radio_device); /* The CC1101 preset can be either one of the standard presets, if * the modulation "custom" field is NULL, or a custom preset we * defined in custom_presets.h. */ if(ProtoViewModulations[app->modulation].custom == NULL) { - furi_hal_subghz_load_preset(ProtoViewModulations[app->modulation].preset); + subghz_devices_load_preset( + app->radio_device, ProtoViewModulations[app->modulation].preset, NULL); } else { - furi_hal_subghz_load_custom_preset(ProtoViewModulations[app->modulation].custom); + subghz_devices_load_preset( + app->radio_device, + FuriHalSubGhzPresetCustom, + ProtoViewModulations[app->modulation].custom); } - furi_hal_gpio_init(furi_hal_subghz.cc1101_g0_pin, GpioModeInput, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_init( + subghz_devices_get_data_gpio(app->radio_device), GpioModeInput, GpioPullNo, GpioSpeedLow); app->txrx->txrx_state = TxRxStateIDLE; } @@ -71,21 +72,28 @@ void protoview_rx_callback(bool level, uint32_t duration, void* context) { /* Setup the CC1101 to start receiving using a background worker. */ uint32_t radio_rx(ProtoViewApp* app) { furi_assert(app); - if(!furi_hal_subghz_is_frequency_valid(app->frequency)) { + + if(!subghz_devices_is_frequency_valid(app->radio_device, app->frequency)) { furi_crash(TAG " Incorrect RX frequency."); } if(app->txrx->txrx_state == TxRxStateRx) return app->frequency; - furi_hal_subghz_idle(); /* Put it into idle state in case it is sleeping. */ - uint32_t value = furi_hal_subghz_set_frequency_and_path(app->frequency); + subghz_devices_idle(app->radio_device); /* Put it into idle state in case it is sleeping. */ + uint32_t value = subghz_devices_set_frequency(app->radio_device, app->frequency); FURI_LOG_E(TAG, "Switched to frequency: %lu", value); - furi_hal_gpio_init(furi_hal_subghz.cc1101_g0_pin, GpioModeInput, GpioPullNo, GpioSpeedLow); - furi_hal_subghz_flush_rx(); - furi_hal_subghz_rx(); + + subghz_devices_flush_rx(app->radio_device); + subghz_devices_set_rx(app->radio_device); + if(!app->txrx->debug_timer_sampling) { - furi_hal_subghz_start_async_rx(protoview_rx_callback, NULL); + subghz_devices_start_async_rx(app->radio_device, protoview_rx_callback, NULL); } else { + furi_hal_gpio_init( + subghz_devices_get_data_gpio(app->radio_device), + GpioModeInput, + GpioPullNo, + GpioSpeedLow); raw_sampling_worker_start(app); } app->txrx->txrx_state = TxRxStateRx; @@ -98,12 +106,12 @@ void radio_rx_end(ProtoViewApp* app) { if(app->txrx->txrx_state == TxRxStateRx) { if(!app->txrx->debug_timer_sampling) { - furi_hal_subghz_stop_async_rx(); + subghz_devices_stop_async_rx(app->radio_device); } else { raw_sampling_worker_stop(app); } } - furi_hal_subghz_idle(); + subghz_devices_idle(app->radio_device); app->txrx->txrx_state = TxRxStateIDLE; } @@ -115,9 +123,8 @@ void radio_sleep(ProtoViewApp* app) { * chip into sleep. */ radio_rx_end(app); } - furi_hal_subghz_sleep(); + subghz_devices_sleep(app->radio_device); app->txrx->txrx_state = TxRxStateSleep; - furi_hal_power_suppress_charge_exit(); } /* =============================== Transmission ============================= */ @@ -131,17 +138,14 @@ void radio_tx_signal(ProtoViewApp* app, FuriHalSubGhzAsyncTxCallback data_feeder if(oldstate == TxRxStateRx) radio_rx_end(app); radio_begin(app); - furi_hal_subghz_idle(); - uint32_t value = furi_hal_subghz_set_frequency_and_path(app->frequency); + subghz_devices_idle(app->radio_device); + uint32_t value = subghz_devices_set_frequency(app->radio_device, app->frequency); FURI_LOG_E(TAG, "Switched to frequency: %lu", value); - furi_hal_gpio_write(furi_hal_subghz.cc1101_g0_pin, false); - furi_hal_gpio_init( - furi_hal_subghz.cc1101_g0_pin, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); - furi_hal_subghz_start_async_tx(data_feeder, ctx); - while(!furi_hal_subghz_is_async_tx_complete()) furi_delay_ms(10); - furi_hal_subghz_stop_async_tx(); - furi_hal_subghz_idle(); + subghz_devices_start_async_tx(app->radio_device, data_feeder, ctx); + while(!subghz_devices_is_async_complete_tx(app->radio_device)) furi_delay_ms(10); + subghz_devices_stop_async_tx(app->radio_device); + subghz_devices_idle(app->radio_device); radio_begin(app); if(oldstate == TxRxStateRx) radio_rx(app); @@ -157,7 +161,7 @@ void radio_tx_signal(ProtoViewApp* app, FuriHalSubGhzAsyncTxCallback data_feeder void protoview_timer_isr(void* ctx) { ProtoViewApp* app = ctx; - bool level = furi_hal_gpio_read(furi_hal_subghz.cc1101_g0_pin); + bool level = furi_hal_gpio_read(subghz_devices_get_data_gpio(app->radio_device)); if(app->txrx->last_g0_value != level) { uint32_t now = DWT->CYCCNT; uint32_t dur = now - app->txrx->last_g0_change_time; diff --git a/applications/external/protoview/helpers/radio_device_loader.c b/applications/external/protoview/helpers/radio_device_loader.c new file mode 100644 index 000000000..d2cffde58 --- /dev/null +++ b/applications/external/protoview/helpers/radio_device_loader.c @@ -0,0 +1,64 @@ +#include "radio_device_loader.h" + +#include +#include + +static void radio_device_loader_power_on() { + uint8_t attempts = 0; + while(!furi_hal_power_is_otg_enabled() && attempts++ < 5) { + furi_hal_power_enable_otg(); + //CC1101 power-up time + furi_delay_ms(10); + } +} + +static void radio_device_loader_power_off() { + if(furi_hal_power_is_otg_enabled()) furi_hal_power_disable_otg(); +} + +bool radio_device_loader_is_connect_external(const char* name) { + bool is_connect = false; + bool is_otg_enabled = furi_hal_power_is_otg_enabled(); + + if(!is_otg_enabled) { + radio_device_loader_power_on(); + } + + const SubGhzDevice* device = subghz_devices_get_by_name(name); + if(device) { + is_connect = subghz_devices_is_connect(device); + } + + if(!is_otg_enabled) { + radio_device_loader_power_off(); + } + return is_connect; +} + +const SubGhzDevice* radio_device_loader_set( + const SubGhzDevice* current_radio_device, + SubGhzRadioDeviceType radio_device_type) { + const SubGhzDevice* radio_device; + + if(radio_device_type == SubGhzRadioDeviceTypeExternalCC1101 && + radio_device_loader_is_connect_external(SUBGHZ_DEVICE_CC1101_EXT_NAME)) { + radio_device_loader_power_on(); + radio_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_EXT_NAME); + subghz_devices_begin(radio_device); + } else if(current_radio_device == NULL) { + radio_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME); + } else { + radio_device_loader_end(current_radio_device); + radio_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME); + } + + return radio_device; +} + +void radio_device_loader_end(const SubGhzDevice* radio_device) { + furi_assert(radio_device); + radio_device_loader_power_off(); + if(radio_device != subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME)) { + subghz_devices_end(radio_device); + } +} \ No newline at end of file diff --git a/applications/external/protoview/helpers/radio_device_loader.h b/applications/external/protoview/helpers/radio_device_loader.h new file mode 100644 index 000000000..bee4e2c36 --- /dev/null +++ b/applications/external/protoview/helpers/radio_device_loader.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +/** SubGhzRadioDeviceType */ +typedef enum { + SubGhzRadioDeviceTypeInternal, + SubGhzRadioDeviceTypeExternalCC1101, +} SubGhzRadioDeviceType; + +const SubGhzDevice* radio_device_loader_set( + const SubGhzDevice* current_radio_device, + SubGhzRadioDeviceType radio_device_type); + +void radio_device_loader_end(const SubGhzDevice* radio_device); \ No newline at end of file diff --git a/applications/external/protoview/view_direct_sampling.c b/applications/external/protoview/view_direct_sampling.c index 84486dc51..dc812901e 100644 --- a/applications/external/protoview/view_direct_sampling.c +++ b/applications/external/protoview/view_direct_sampling.c @@ -88,14 +88,18 @@ void view_enter_direct_sampling(ProtoViewApp* app) { privdata->show_usage_info = true; if(app->txrx->txrx_state == TxRxStateRx && !app->txrx->debug_timer_sampling) { - furi_hal_subghz_stop_async_rx(); + subghz_devices_stop_async_rx(app->radio_device); /* To read data asynchronously directly from the view, we need * to put the CC1101 back into reception mode (the previous call * to stop the async RX will put it into idle) and configure the * G0 pin for reading. */ - furi_hal_subghz_rx(); - furi_hal_gpio_init(furi_hal_subghz.cc1101_g0_pin, GpioModeInput, GpioPullNo, GpioSpeedLow); + subghz_devices_set_rx(app->radio_device); + furi_hal_gpio_init( + subghz_devices_get_data_gpio(app->radio_device), + GpioModeInput, + GpioPullNo, + GpioSpeedLow); } else { raw_sampling_worker_stop(app); } @@ -114,8 +118,13 @@ void view_exit_direct_sampling(ProtoViewApp* app) { /* Restart normal data feeding. */ if(app->txrx->txrx_state == TxRxStateRx && !app->txrx->debug_timer_sampling) { - furi_hal_subghz_start_async_rx(protoview_rx_callback, NULL); + subghz_devices_start_async_rx(app->radio_device, protoview_rx_callback, NULL); } else { + furi_hal_gpio_init( + subghz_devices_get_data_gpio(app->radio_device), + GpioModeInput, + GpioPullNo, + GpioSpeedLow); raw_sampling_worker_start(app); } } @@ -127,7 +136,7 @@ static void ds_timer_isr(void* ctx) { DirectSamplingViewPrivData* privdata = app->view_privdata; if(app->direct_sampling_enabled) { - bool level = furi_hal_gpio_read(furi_hal_subghz.cc1101_g0_pin); + bool level = furi_hal_gpio_read(subghz_devices_get_data_gpio(app->radio_device)); bitmap_set(privdata->captured, CAPTURED_BITMAP_BYTES, privdata->captured_idx, level); privdata->captured_idx = (privdata->captured_idx + 1) % CAPTURED_BITMAP_BITS; } From d8500510bed982f20a3ebfafdf48ceb62c5b87c0 Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Thu, 6 Jul 2023 14:49:53 +0300 Subject: [PATCH 056/364] API: explicitly add math.h (#2852) * API: explicitly add math.h * sync target api versions --- firmware/targets/f18/api_symbols.csv | 218 +++++++++++++++++- firmware/targets/f7/api_symbols.csv | 3 +- .../f7/platform_specific/intrinsic_export.h | 1 + .../f7/platform_specific/math_wrapper.h | 2 + 4 files changed, 222 insertions(+), 2 deletions(-) create mode 100644 firmware/targets/f7/platform_specific/math_wrapper.h diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index 46099799b..62b237a51 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,33.0,, +Version,+,33.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -56,6 +56,7 @@ Header,+,firmware/targets/f7/furi_hal/furi_hal_spi_types.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_uart.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_usb_cdc.h,, Header,+,firmware/targets/f7/platform_specific/intrinsic_export.h,, +Header,+,firmware/targets/f7/platform_specific/math_wrapper.h,, Header,+,firmware/targets/furi_hal_include/furi_hal.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_bt.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_bt_hid.h,, @@ -289,12 +290,18 @@ Function,+,__assert_func,void,"const char*, int, const char*, const char*" Function,+,__clear_cache,void,"void*, void*" Function,-,__eprintf,void,"const char*, const char*, unsigned int, const char*" Function,+,__errno,int*, +Function,-,__fpclassifyd,int,double +Function,-,__fpclassifyf,int,float Function,+,__furi_crash,void, Function,+,__furi_critical_enter,__FuriCriticalInfo, Function,+,__furi_critical_exit,void,__FuriCriticalInfo Function,+,__furi_halt,void, Function,-,__getdelim,ssize_t,"char**, size_t*, int, FILE*" Function,-,__getline,ssize_t,"char**, size_t*, FILE*" +Function,-,__isinfd,int,double +Function,-,__isinff,int,float +Function,-,__isnand,int,double +Function,-,__isnanf,int,float Function,-,__itoa,char*,"int, char*, int" Function,-,__locale_mb_cur_max,int, Function,+,__retarget_lock_acquire,void,_LOCK_T @@ -307,6 +314,9 @@ Function,+,__retarget_lock_release,void,_LOCK_T Function,+,__retarget_lock_release_recursive,void,_LOCK_T Function,-,__retarget_lock_try_acquire,int,_LOCK_T Function,-,__retarget_lock_try_acquire_recursive,int,_LOCK_T +Function,-,__signbitd,int,double +Function,-,__signbitf,int,float +Function,-,__signgam,int*, Function,-,__srget_r,int,"_reent*, FILE*" Function,-,__swbuf_r,int,"_reent*, int, FILE*" Function,-,__utoa,char*,"unsigned, char*, int" @@ -459,6 +469,12 @@ Function,-,_wctomb_r,int,"_reent*, char*, wchar_t, _mbstate_t*" Function,-,a64l,long,const char* Function,+,abort,void, Function,-,abs,int,int +Function,-,acos,double,double +Function,-,acosf,float,float +Function,-,acosh,double,double +Function,-,acoshf,float,float +Function,-,acoshl,long double,long double +Function,-,acosl,long double,long double Function,-,aligned_alloc,void*,"size_t, size_t" Function,+,aligned_free,void,void* Function,+,aligned_malloc,void*,"size_t, size_t" @@ -474,11 +490,26 @@ Function,+,args_read_probably_quoted_string_and_trim,_Bool,"FuriString*, FuriStr Function,+,args_read_string_and_trim,_Bool,"FuriString*, FuriString*" Function,-,asctime,char*,const tm* Function,-,asctime_r,char*,"const tm*, char*" +Function,-,asin,double,double +Function,-,asinf,float,float +Function,-,asinh,double,double +Function,-,asinhf,float,float +Function,-,asinhl,long double,long double +Function,-,asinl,long double,long double Function,-,asiprintf,int,"char**, const char*, ..." Function,-,asniprintf,char*,"char*, size_t*, const char*, ..." Function,-,asnprintf,char*,"char*, size_t*, const char*, ..." Function,-,asprintf,int,"char**, const char*, ..." Function,-,at_quick_exit,int,void (*)() +Function,-,atan,double,double +Function,-,atan2,double,"double, double" +Function,-,atan2f,float,"float, float" +Function,-,atan2l,long double,"long double, long double" +Function,-,atanf,float,float +Function,-,atanh,double,double +Function,-,atanhf,float,float +Function,-,atanhl,long double,long double +Function,-,atanl,long double,long double Function,-,atexit,int,void (*)() Function,-,atof,double,const char* Function,-,atoff,float,const char* @@ -571,6 +602,12 @@ Function,+,canvas_set_font,void,"Canvas*, Font" Function,+,canvas_set_font_direction,void,"Canvas*, CanvasDirection" Function,+,canvas_string_width,uint16_t,"Canvas*, const char*" Function,+,canvas_width,uint8_t,const Canvas* +Function,-,cbrt,double,double +Function,-,cbrtf,float,float +Function,-,cbrtl,long double,long double +Function,-,ceil,double,double +Function,-,ceilf,float,float +Function,-,ceill,long double,long double Function,-,cfree,void,void* Function,-,clearerr,void,FILE* Function,-,clearerr_unlocked,void,FILE* @@ -591,6 +628,15 @@ Function,+,composite_api_resolver_add,void,"CompositeApiResolver*, const ElfApiI Function,+,composite_api_resolver_alloc,CompositeApiResolver*, Function,+,composite_api_resolver_free,void,CompositeApiResolver* Function,+,composite_api_resolver_get,const ElfApiInterface*,CompositeApiResolver* +Function,-,copysign,double,"double, double" +Function,-,copysignf,float,"float, float" +Function,-,copysignl,long double,"long double, long double" +Function,-,cos,double,double +Function,-,cosf,float,float +Function,-,cosh,double,double +Function,-,coshf,float,float +Function,-,coshl,long double,long double +Function,-,cosl,long double,long double Function,+,crc32_calc_buffer,uint32_t,"uint32_t, const void*, size_t" Function,+,crc32_calc_file,uint32_t,"File*, const FileCrcProgressCb, void*" Function,-,ctermid,char*,char* @@ -660,6 +706,8 @@ Function,+,dolphin_stats,DolphinStats,Dolphin* Function,+,dolphin_upgrade_level,void,Dolphin* Function,-,dprintf,int,"int, const char*, ..." Function,-,drand48,double, +Function,-,drem,double,"double, double" +Function,-,dremf,float,"float, float" Function,-,eTaskConfirmSleepModeStatus,eSleepModeStatus, Function,-,eTaskGetState,eTaskState,TaskHandle_t Function,+,elements_bold_rounded_frame,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t" @@ -687,10 +735,33 @@ Function,+,empty_screen_alloc,EmptyScreen*, Function,+,empty_screen_free,void,EmptyScreen* Function,+,empty_screen_get_view,View*,EmptyScreen* Function,-,erand48,double,unsigned short[3] +Function,-,erf,double,double +Function,-,erfc,double,double +Function,-,erfcf,float,float +Function,-,erfcl,long double,long double +Function,-,erff,float,float +Function,-,erfl,long double,long double Function,-,exit,void,int +Function,-,exp,double,double +Function,-,exp10,double,double +Function,-,exp10f,float,float +Function,-,exp2,double,double +Function,-,exp2f,float,float +Function,-,exp2l,long double,long double +Function,-,expf,float,float +Function,-,expl,long double,long double Function,-,explicit_bzero,void,"void*, size_t" +Function,-,expm1,double,double +Function,-,expm1f,float,float +Function,-,expm1l,long double,long double +Function,-,fabs,double,double +Function,-,fabsf,float,float +Function,-,fabsl,long double,long double Function,-,fclose,int,FILE* Function,-,fcloseall,int, +Function,-,fdim,double,"double, double" +Function,-,fdimf,float,"float, float" +Function,-,fdiml,long double,"long double, long double" Function,-,fdopen,FILE*,"int, const char*" Function,-,feof,int,FILE* Function,-,feof_unlocked,int,FILE* @@ -735,6 +806,9 @@ Function,+,file_stream_open,_Bool,"Stream*, const char*, FS_AccessMode, FS_OpenM Function,-,fileno,int,FILE* Function,-,fileno_unlocked,int,FILE* Function,+,filesystem_api_error_get_desc,const char*,FS_Error +Function,-,finite,int,double +Function,-,finitef,int,float +Function,-,finitel,int,long double Function,-,fiprintf,int,"FILE*, const char*, ..." Function,-,fiscanf,int,"FILE*, const char*, ..." Function,+,flipper_application_alloc,FlipperApplication*,"Storage*, const ElfApiInterface*" @@ -812,10 +886,25 @@ Function,+,flipper_format_write_string_cstr,_Bool,"FlipperFormat*, const char*, Function,+,flipper_format_write_uint32,_Bool,"FlipperFormat*, const char*, const uint32_t*, const uint16_t" Function,+,float_is_equal,_Bool,"float, float" Function,-,flockfile,void,FILE* +Function,-,floor,double,double +Function,-,floorf,float,float +Function,-,floorl,long double,long double Function,-,fls,int,int Function,-,flsl,int,long Function,-,flsll,int,long long +Function,-,fma,double,"double, double, double" +Function,-,fmaf,float,"float, float, float" +Function,-,fmal,long double,"long double, long double, long double" +Function,-,fmax,double,"double, double" +Function,-,fmaxf,float,"float, float" +Function,-,fmaxl,long double,"long double, long double" Function,-,fmemopen,FILE*,"void*, size_t, const char*" +Function,-,fmin,double,"double, double" +Function,-,fminf,float,"float, float" +Function,-,fminl,long double,"long double, long double" +Function,-,fmod,double,"double, double" +Function,-,fmodf,float,"float, float" +Function,-,fmodl,long double,"long double, long double" Function,-,fopen,FILE*,"const char*, const char*" Function,-,fopencookie,FILE*,"void*, const char*, cookie_io_functions_t" Function,-,fprintf,int,"FILE*, const char*, ..." @@ -828,6 +917,9 @@ Function,-,fread,size_t,"void*, size_t, size_t, FILE*" Function,-,fread_unlocked,size_t,"void*, size_t, size_t, FILE*" Function,+,free,void,void* Function,-,freopen,FILE*,"const char*, const char*, FILE*" +Function,-,frexp,double,"double, int*" +Function,-,frexpf,float,"float, int*" +Function,-,frexpl,long double,"long double, int*" Function,-,fscanf,int,"FILE*, const char*, ..." Function,-,fseek,int,"FILE*, long, int" Function,-,fseeko,int,"FILE*, off_t, int" @@ -1338,6 +1430,10 @@ Function,+,furi_timer_start,FuriStatus,"FuriTimer*, uint32_t" Function,+,furi_timer_stop,FuriStatus,FuriTimer* Function,-,fwrite,size_t,"const void*, size_t, size_t, FILE*" Function,-,fwrite_unlocked,size_t,"const void*, size_t, size_t, FILE*" +Function,-,gamma,double,double +Function,-,gamma_r,double,"double, int*" +Function,-,gammaf,float,float +Function,-,gammaf_r,float,"float, int*" Function,-,gap_get_state,GapState, Function,-,gap_init,_Bool,"GapConfig*, GapEventCallback, void*" Function,-,gap_start_advertising,void, @@ -1370,6 +1466,9 @@ Function,+,hex_char_to_hex_nibble,_Bool,"char, uint8_t*" Function,+,hex_char_to_uint8,_Bool,"char, char, uint8_t*" Function,+,hex_chars_to_uint64,_Bool,"const char*, uint64_t*" Function,+,hex_chars_to_uint8,_Bool,"const char*, uint8_t*" +Function,-,hypot,double,"double, double" +Function,-,hypotf,float,"float, float" +Function,-,hypotl,long double,"long double, long double" Function,+,icon_animation_alloc,IconAnimation*,const Icon* Function,+,icon_animation_free,void,IconAnimation* Function,+,icon_animation_get_height,uint8_t,const IconAnimation* @@ -1381,7 +1480,12 @@ Function,+,icon_animation_stop,void,IconAnimation* Function,+,icon_get_data,const uint8_t*,const Icon* Function,+,icon_get_height,uint8_t,const Icon* Function,+,icon_get_width,uint8_t,const Icon* +Function,-,ilogb,int,double +Function,-,ilogbf,int,float +Function,-,ilogbl,int,long double Function,-,index,char*,"const char*, int" +Function,-,infinity,double, +Function,-,infinityf,float, Function,-,initstate,char*,"unsigned, char*, size_t" Function,+,input_get_key_name,const char*,InputKey Function,+,input_get_type_name,const char*,InputType @@ -1401,8 +1505,12 @@ Function,-,isdigit,int,int Function,-,isdigit_l,int,"int, locale_t" Function,-,isgraph,int,int Function,-,isgraph_l,int,"int, locale_t" +Function,-,isinf,int,double +Function,-,isinff,int,float Function,-,islower,int,int Function,-,islower_l,int,"int, locale_t" +Function,-,isnan,int,double +Function,-,isnanf,int,float Function,-,isprint,int,int Function,-,isprint_l,int,"int, locale_t" Function,-,ispunct,int,int @@ -1414,13 +1522,33 @@ Function,-,isupper_l,int,"int, locale_t" Function,-,isxdigit,int,int Function,-,isxdigit_l,int,"int, locale_t" Function,-,itoa,char*,"int, char*, int" +Function,-,j0,double,double +Function,-,j0f,float,float +Function,-,j1,double,double +Function,-,j1f,float,float +Function,-,jn,double,"int, double" +Function,-,jnf,float,"int, float" Function,-,jrand48,long,unsigned short[3] Function,-,l64a,char*,long Function,-,labs,long,long Function,-,lcong48,void,unsigned short[7] +Function,-,ldexp,double,"double, int" +Function,-,ldexpf,float,"float, int" +Function,-,ldexpl,long double,"long double, int" Function,-,ldiv,ldiv_t,"long, long" +Function,-,lgamma,double,double +Function,-,lgamma_r,double,"double, int*" +Function,-,lgammaf,float,float +Function,-,lgammaf_r,float,"float, int*" +Function,-,lgammal,long double,long double Function,-,llabs,long long,long long Function,-,lldiv,lldiv_t,"long long, long long" +Function,-,llrint,long long int,double +Function,-,llrintf,long long int,float +Function,-,llrintl,long long int,long double +Function,-,llround,long long int,double +Function,-,llroundf,long long int,float +Function,-,llroundl,long long int,long double Function,+,loader_get_pubsub,FuriPubSub*,Loader* Function,+,loader_is_locked,_Bool,Loader* Function,+,loader_lock,_Bool,Loader* @@ -1443,7 +1571,28 @@ Function,+,locale_set_measurement_unit,void,LocaleMeasurementUnits Function,+,locale_set_time_format,void,LocaleTimeFormat Function,-,localtime,tm*,const time_t* Function,-,localtime_r,tm*,"const time_t*, tm*" +Function,-,log,double,double +Function,-,log10,double,double +Function,-,log10f,float,float +Function,-,log10l,long double,long double +Function,-,log1p,double,double +Function,-,log1pf,float,float +Function,-,log1pl,long double,long double +Function,-,log2,double,double +Function,-,log2f,float,float +Function,-,log2l,long double,long double +Function,-,logb,double,double +Function,-,logbf,float,float +Function,-,logbl,long double,long double +Function,-,logf,float,float +Function,-,logl,long double,long double Function,-,lrand48,long, +Function,-,lrint,long int,double +Function,-,lrintf,long int,float +Function,-,lrintl,long int,long double +Function,-,lround,long int,double +Function,-,lroundf,long int,float +Function,-,lroundl,long,long double Function,+,malloc,void*,size_t Function,+,manchester_advance,_Bool,"ManchesterState, ManchesterEvent, ManchesterState*, _Bool*" Function,+,manchester_encoder_advance,_Bool,"ManchesterEncoderState*, const _Bool, ManchesterEncoderResult*" @@ -1521,6 +1670,9 @@ Function,-,mkstemp,int,char* Function,-,mkstemps,int,"char*, int" Function,-,mktemp,char*,char* Function,-,mktime,time_t,tm* +Function,-,modf,double,"double, double*" +Function,-,modff,float,"float, float*" +Function,-,modfl,long double,"long double, long double*" Function,-,mrand48,long, Function,-,music_worker_alloc,MusicWorker*, Function,-,music_worker_clear,void,MusicWorker* @@ -1534,6 +1686,18 @@ Function,-,music_worker_set_callback,void,"MusicWorker*, MusicWorkerCallback, vo Function,-,music_worker_set_volume,void,"MusicWorker*, float" Function,-,music_worker_start,void,MusicWorker* Function,-,music_worker_stop,void,MusicWorker* +Function,-,nan,double,const char* +Function,-,nanf,float,const char* +Function,-,nanl,long double,const char* +Function,-,nearbyint,double,double +Function,-,nearbyintf,float,float +Function,-,nearbyintl,long double,long double +Function,-,nextafter,double,"double, double" +Function,-,nextafterf,float,"float, float" +Function,-,nextafterl,long double,"long double, long double" +Function,-,nexttoward,double,"double, long double" +Function,-,nexttowardf,float,"float, long double" +Function,-,nexttowardl,long double,"long double, long double" Function,+,notification_internal_message,void,"NotificationApp*, const NotificationSequence*" Function,+,notification_internal_message_block,void,"NotificationApp*, const NotificationSequence*" Function,+,notification_message,void,"NotificationApp*, const NotificationSequence*" @@ -1601,12 +1765,17 @@ Function,+,popup_set_icon,void,"Popup*, uint8_t, uint8_t, const Icon*" Function,+,popup_set_text,void,"Popup*, const char*, uint8_t, uint8_t, Align, Align" Function,+,popup_set_timeout,void,"Popup*, uint32_t" Function,-,posix_memalign,int,"void**, size_t, size_t" +Function,-,pow,double,"double, double" +Function,-,pow10,double,double +Function,-,pow10f,float,float Function,+,power_enable_low_battery_level_notification,void,"Power*, _Bool" Function,+,power_get_info,void,"Power*, PowerInfo*" Function,+,power_get_pubsub,FuriPubSub*,Power* Function,+,power_is_battery_healthy,_Bool,Power* Function,+,power_off,void,Power* Function,+,power_reboot,void,PowerBootMode +Function,+,powf,float,"float, float" +Function,-,powl,long double,"long double, long double" Function,+,pretty_format_bytes_hex_canonical,void,"FuriString*, size_t, const char*, const uint8_t*, size_t" Function,-,printf,int,"const char*, ..." Function,+,property_value_out,void,"PropertyValueContext*, const char*, unsigned int, ..." @@ -1664,11 +1833,23 @@ Function,+,realloc,void*,"void*, size_t" Function,-,reallocarray,void*,"void*, size_t, size_t" Function,-,reallocf,void*,"void*, size_t" Function,-,realpath,char*,"const char*, char*" +Function,-,remainder,double,"double, double" +Function,-,remainderf,float,"float, float" +Function,-,remainderl,long double,"long double, long double" Function,-,remove,int,const char* +Function,-,remquo,double,"double, double, int*" +Function,-,remquof,float,"float, float, int*" +Function,-,remquol,long double,"long double, long double, int*" Function,-,rename,int,"const char*, const char*" Function,-,renameat,int,"int, const char*, int, const char*" Function,-,rewind,void,FILE* Function,-,rindex,char*,"const char*, int" +Function,-,rint,double,double +Function,-,rintf,float,float +Function,-,rintl,long double,long double +Function,-,round,double,double +Function,+,roundf,float,float +Function,-,roundl,long double,long double Function,+,rpc_session_close,void,RpcSession* Function,+,rpc_session_feed,size_t,"RpcSession*, uint8_t*, size_t, TickType_t" Function,+,rpc_session_get_available_size,size_t,RpcSession* @@ -1693,6 +1874,12 @@ Function,-,rpmatch,int,const char* Function,+,saved_struct_get_payload_size,_Bool,"const char*, uint8_t, uint8_t, size_t*" Function,+,saved_struct_load,_Bool,"const char*, void*, size_t, uint8_t, uint8_t" Function,+,saved_struct_save,_Bool,"const char*, void*, size_t, uint8_t, uint8_t" +Function,-,scalbln,double,"double, long int" +Function,-,scalblnf,float,"float, long int" +Function,-,scalblnl,long double,"long double, long" +Function,-,scalbn,double,"double, int" +Function,+,scalbnf,float,"float, int" +Function,-,scalbnl,long double,"long double, int" Function,-,scanf,int,"const char*, ..." Function,+,scene_manager_alloc,SceneManager*,"const SceneManagerHandlers*, void*" Function,+,scene_manager_free,void,SceneManager* @@ -1732,11 +1919,22 @@ Function,+,sha256_finish,void,"sha256_context*, unsigned char[32]" Function,+,sha256_process,void,sha256_context* Function,+,sha256_start,void,sha256_context* Function,+,sha256_update,void,"sha256_context*, const unsigned char*, unsigned int" +Function,-,sin,double,double +Function,-,sincos,void,"double, double*, double*" +Function,-,sincosf,void,"float, float*, float*" +Function,-,sinf,float,float +Function,-,sinh,double,double +Function,-,sinhf,float,float +Function,-,sinhl,long double,long double +Function,-,sinl,long double,long double Function,-,siprintf,int,"char*, const char*, ..." Function,-,siscanf,int,"const char*, const char*, ..." Function,-,sniprintf,int,"char*, size_t, const char*, ..." Function,+,snprintf,int,"char*, size_t, const char*, ..." Function,-,sprintf,int,"char*, const char*, ..." +Function,-,sqrt,double,double +Function,-,sqrtf,float,float +Function,-,sqrtl,long double,long double Function,+,srand,void,unsigned Function,-,srand48,void,long Function,-,srandom,void,unsigned @@ -1891,6 +2089,12 @@ Function,+,submenu_reset,void,Submenu* Function,+,submenu_set_header,void,"Submenu*, const char*" Function,+,submenu_set_selected_item,void,"Submenu*, uint32_t" Function,-,system,int,const char* +Function,-,tan,double,double +Function,-,tanf,float,float +Function,-,tanh,double,double +Function,-,tanhf,float,float +Function,-,tanhl,long double,long double +Function,-,tanl,long double,long double Function,+,tar_archive_add_dir,_Bool,"TarArchive*, const char*, const char*" Function,+,tar_archive_add_file,_Bool,"TarArchive*, const char*, const char*, const int32_t" Function,+,tar_archive_alloc,TarArchive*,Storage* @@ -1923,6 +2127,9 @@ Function,+,text_input_reset,void,TextInput* Function,+,text_input_set_header_text,void,"TextInput*, const char*" Function,+,text_input_set_result_callback,void,"TextInput*, TextInputCallback, void*, char*, size_t, _Bool" Function,+,text_input_set_validator,void,"TextInput*, TextInputValidatorCallback, void*" +Function,-,tgamma,double,double +Function,-,tgammaf,float,float +Function,-,tgammal,long double,long double Function,-,time,time_t,time_t* Function,-,timingsafe_bcmp,int,"const void*, const void*, size_t" Function,-,timingsafe_memcmp,int,"const void*, const void*, size_t" @@ -1934,6 +2141,9 @@ Function,-,tolower,int,int Function,-,tolower_l,int,"int, locale_t" Function,-,toupper,int,int Function,-,toupper_l,int,"int, locale_t" +Function,-,trunc,double,double +Function,-,truncf,float,float +Function,-,truncl,long double,long double Function,-,tzset,void, Function,-,uECC_compress,void,"const uint8_t*, uint8_t*, uECC_Curve" Function,+,uECC_compute_public_key,int,"const uint8_t*, uint8_t*, uECC_Curve" @@ -2166,6 +2376,12 @@ Function,-,xTimerGetTimerDaemonTaskHandle,TaskHandle_t, Function,-,xTimerIsTimerActive,BaseType_t,TimerHandle_t Function,-,xTimerPendFunctionCall,BaseType_t,"PendedFunction_t, void*, uint32_t, TickType_t" Function,-,xTimerPendFunctionCallFromISR,BaseType_t,"PendedFunction_t, void*, uint32_t, BaseType_t*" +Function,-,y0,double,double +Function,-,y0f,float,float +Function,-,y1,double,double +Function,-,y1f,float,float +Function,-,yn,double,"int, double" +Function,-,ynf,float,"int, float" Variable,-,AHBPrescTable,const uint32_t[16], Variable,-,APBPrescTable,const uint32_t[8], Variable,-,ITM_RxBuffer,volatile int32_t, diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index dbaeb8e4c..a44e663ba 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,33.0,, +Version,+,33.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -60,6 +60,7 @@ Header,+,firmware/targets/f7/furi_hal/furi_hal_target_hw.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_uart.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_usb_cdc.h,, Header,+,firmware/targets/f7/platform_specific/intrinsic_export.h,, +Header,+,firmware/targets/f7/platform_specific/math_wrapper.h,, Header,+,firmware/targets/furi_hal_include/furi_hal.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_bt.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_bt_hid.h,, diff --git a/firmware/targets/f7/platform_specific/intrinsic_export.h b/firmware/targets/f7/platform_specific/intrinsic_export.h index ca343a128..d3c7be5e0 100644 --- a/firmware/targets/f7/platform_specific/intrinsic_export.h +++ b/firmware/targets/f7/platform_specific/intrinsic_export.h @@ -1,3 +1,4 @@ +#pragma once #include #include diff --git a/firmware/targets/f7/platform_specific/math_wrapper.h b/firmware/targets/f7/platform_specific/math_wrapper.h new file mode 100644 index 000000000..83f5a8b75 --- /dev/null +++ b/firmware/targets/f7/platform_specific/math_wrapper.h @@ -0,0 +1,2 @@ +#pragma once +#include \ No newline at end of file From cb08b84197bbf450d7dd1b089a70a72565728b1e Mon Sep 17 00:00:00 2001 From: gid9798 <30450294+gid9798@users.noreply.github.com> Date: Thu, 6 Jul 2023 16:23:14 +0300 Subject: [PATCH 057/364] Spectrum analyzer: new ext radio driver (#2) * Sub Analyzer app: UPD to new driver * Sub Analyzer: fix working on start --- .../helpers/radio_device_loader.c | 64 ++++++++++++++ .../helpers/radio_device_loader.h | 15 ++++ .../spectrum_analyzer/spectrum_analyzer.c | 23 ++--- .../spectrum_analyzer_worker.c | 86 ++++++++++++++----- 4 files changed, 150 insertions(+), 38 deletions(-) create mode 100644 applications/external/spectrum_analyzer/helpers/radio_device_loader.c create mode 100644 applications/external/spectrum_analyzer/helpers/radio_device_loader.h diff --git a/applications/external/spectrum_analyzer/helpers/radio_device_loader.c b/applications/external/spectrum_analyzer/helpers/radio_device_loader.c new file mode 100644 index 000000000..d2cffde58 --- /dev/null +++ b/applications/external/spectrum_analyzer/helpers/radio_device_loader.c @@ -0,0 +1,64 @@ +#include "radio_device_loader.h" + +#include +#include + +static void radio_device_loader_power_on() { + uint8_t attempts = 0; + while(!furi_hal_power_is_otg_enabled() && attempts++ < 5) { + furi_hal_power_enable_otg(); + //CC1101 power-up time + furi_delay_ms(10); + } +} + +static void radio_device_loader_power_off() { + if(furi_hal_power_is_otg_enabled()) furi_hal_power_disable_otg(); +} + +bool radio_device_loader_is_connect_external(const char* name) { + bool is_connect = false; + bool is_otg_enabled = furi_hal_power_is_otg_enabled(); + + if(!is_otg_enabled) { + radio_device_loader_power_on(); + } + + const SubGhzDevice* device = subghz_devices_get_by_name(name); + if(device) { + is_connect = subghz_devices_is_connect(device); + } + + if(!is_otg_enabled) { + radio_device_loader_power_off(); + } + return is_connect; +} + +const SubGhzDevice* radio_device_loader_set( + const SubGhzDevice* current_radio_device, + SubGhzRadioDeviceType radio_device_type) { + const SubGhzDevice* radio_device; + + if(radio_device_type == SubGhzRadioDeviceTypeExternalCC1101 && + radio_device_loader_is_connect_external(SUBGHZ_DEVICE_CC1101_EXT_NAME)) { + radio_device_loader_power_on(); + radio_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_EXT_NAME); + subghz_devices_begin(radio_device); + } else if(current_radio_device == NULL) { + radio_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME); + } else { + radio_device_loader_end(current_radio_device); + radio_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME); + } + + return radio_device; +} + +void radio_device_loader_end(const SubGhzDevice* radio_device) { + furi_assert(radio_device); + radio_device_loader_power_off(); + if(radio_device != subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME)) { + subghz_devices_end(radio_device); + } +} \ No newline at end of file diff --git a/applications/external/spectrum_analyzer/helpers/radio_device_loader.h b/applications/external/spectrum_analyzer/helpers/radio_device_loader.h new file mode 100644 index 000000000..bee4e2c36 --- /dev/null +++ b/applications/external/spectrum_analyzer/helpers/radio_device_loader.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +/** SubGhzRadioDeviceType */ +typedef enum { + SubGhzRadioDeviceTypeInternal, + SubGhzRadioDeviceTypeExternalCC1101, +} SubGhzRadioDeviceType; + +const SubGhzDevice* radio_device_loader_set( + const SubGhzDevice* current_radio_device, + SubGhzRadioDeviceType radio_device_type); + +void radio_device_loader_end(const SubGhzDevice* radio_device); \ No newline at end of file diff --git a/applications/external/spectrum_analyzer/spectrum_analyzer.c b/applications/external/spectrum_analyzer/spectrum_analyzer.c index 26e41f0ce..84ef293c3 100644 --- a/applications/external/spectrum_analyzer/spectrum_analyzer.c +++ b/applications/external/spectrum_analyzer/spectrum_analyzer.c @@ -389,14 +389,6 @@ void spectrum_analyzer_free(SpectrumAnalyzer* instance) { free(instance->model); free(instance); - - furi_hal_subghz_idle(); - furi_hal_subghz_sleep(); - - // Disable power for External CC1101 if it was enabled and module is connected - furi_hal_subghz_disable_ext_power(); - // Reinit SPI handles for internal radio / nfc - furi_hal_subghz_init_radio_type(SubGhzRadioInternal); } int32_t spectrum_analyzer_app(void* p) { @@ -405,21 +397,18 @@ int32_t spectrum_analyzer_app(void* p) { SpectrumAnalyzer* spectrum_analyzer = spectrum_analyzer_alloc(); InputEvent input; - // Enable power for External CC1101 if it is connected - furi_hal_subghz_enable_ext_power(); - // Auto switch to internal radio if external radio is not available - furi_delay_ms(15); - if(!furi_hal_subghz_check_radio()) { - furi_hal_subghz_select_radio_type(SubGhzRadioInternal); - furi_hal_subghz_init_radio_type(SubGhzRadioInternal); - } - furi_hal_power_suppress_charge_enter(); FURI_LOG_D("Spectrum", "Main Loop - Starting worker"); furi_delay_ms(50); spectrum_analyzer_worker_start(spectrum_analyzer->worker); + spectrum_analyzer_calculate_frequencies(spectrum_analyzer->model); + spectrum_analyzer_worker_set_frequencies( + spectrum_analyzer->worker, + spectrum_analyzer->model->channel0_frequency, + spectrum_analyzer->model->spacing, + spectrum_analyzer->model->width); FURI_LOG_D("Spectrum", "Main Loop - Wait on queue"); furi_delay_ms(50); diff --git a/applications/external/spectrum_analyzer/spectrum_analyzer_worker.c b/applications/external/spectrum_analyzer/spectrum_analyzer_worker.c index e670d2808..5b35a47a2 100644 --- a/applications/external/spectrum_analyzer/spectrum_analyzer_worker.c +++ b/applications/external/spectrum_analyzer/spectrum_analyzer_worker.c @@ -4,6 +4,8 @@ #include #include +#include "helpers/radio_device_loader.h" + #include struct SpectrumAnalyzerWorker { @@ -13,6 +15,8 @@ struct SpectrumAnalyzerWorker { SpectrumAnalyzerWorkerCallback callback; void* callback_context; + const SubGhzDevice* radio_device; + uint32_t channel0_frequency; uint32_t spacing; uint8_t width; @@ -44,7 +48,9 @@ void spectrum_analyzer_worker_set_filter(SpectrumAnalyzerWorker* instance) { filter_config[0][1] = 0x6C; /* 196 kHz / .8 = 245 kHz --> 270 kHz */ break; } - furi_hal_subghz_load_registers((uint8_t*)filter_config); + + UNUSED(filter_config); + // furi_hal_subghz_load_registers((uint8_t*)filter_config); } static int32_t spectrum_analyzer_worker_thread(void* context) { @@ -54,32 +60,53 @@ static int32_t spectrum_analyzer_worker_thread(void* context) { FURI_LOG_D("SpectrumWorker", "spectrum_analyzer_worker_thread: Start"); // Start CC1101 - furi_hal_subghz_reset(); - furi_hal_subghz_load_preset(FuriHalSubGhzPresetOok650Async); - furi_hal_subghz_set_frequency(433920000); - furi_hal_subghz_flush_rx(); - furi_hal_subghz_rx(); + subghz_devices_reset(instance->radio_device); + subghz_devices_load_preset(instance->radio_device, FuriHalSubGhzPresetOok650Async, NULL); + subghz_devices_set_frequency(instance->radio_device, 433920000); + subghz_devices_flush_rx(instance->radio_device); + subghz_devices_set_rx(instance->radio_device); - static const uint8_t radio_config[][2] = { - {CC1101_FSCTRL1, 0x12}, - {CC1101_FSCTRL0, 0x00}, + const uint8_t radio_config[] = { - {CC1101_AGCCTRL2, 0xC0}, + CC1101_FSCTRL0, + 0x00, + CC1101_FSCTRL1, + 0x12, + + CC1101_AGCCTRL2, + 0xC0, + + CC1101_MDMCFG4, + 0x6C, + CC1101_TEST2, + 0x88, + CC1101_TEST1, + 0x31, + CC1101_TEST0, + 0x09, - {CC1101_MDMCFG4, 0x6C}, - {CC1101_TEST2, 0x88}, - {CC1101_TEST1, 0x31}, - {CC1101_TEST0, 0x09}, /* End */ - {0, 0}, + 0, + 0, + + // ook_async_patable + 0x00, + 0xC0, // 12dBm 0xC0, 10dBm 0xC5, 7dBm 0xCD, 5dBm 0x86, 0dBm 0x50, -6dBm 0x37, -10dBm 0x26, -15dBm 0x1D, -20dBm 0x17, -30dBm 0x03 + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, }; while(instance->should_work) { furi_delay_ms(50); // FURI_LOG_T("SpectrumWorker", "spectrum_analyzer_worker_thread: Worker Loop"); - furi_hal_subghz_idle(); - furi_hal_subghz_load_registers((uint8_t*)radio_config); + subghz_devices_idle(instance->radio_device); + subghz_devices_load_preset( + instance->radio_device, FuriHalSubGhzPresetCustom, (uint8_t*)radio_config); // TODO: Check filter! // spectrum_analyzer_worker_set_filter(instance); @@ -90,9 +117,15 @@ static int32_t spectrum_analyzer_worker_thread(void* context) { for(uint8_t ch_offset = 0, chunk = 0; ch_offset < CHUNK_SIZE; ++chunk >= NUM_CHUNKS && ++ch_offset && (chunk = 0)) { uint8_t ch = chunk * CHUNK_SIZE + ch_offset; - furi_hal_subghz_set_frequency(instance->channel0_frequency + (ch * instance->spacing)); - furi_hal_subghz_rx(); + if(subghz_devices_is_frequency_valid( + instance->radio_device, + instance->channel0_frequency + (ch * instance->spacing))) + subghz_devices_set_frequency( + instance->radio_device, + instance->channel0_frequency + (ch * instance->spacing)); + + subghz_devices_set_rx(instance->radio_device); furi_delay_ms(3); // dec dBm @@ -100,7 +133,7 @@ static int32_t spectrum_analyzer_worker_thread(void* context) { //max_ss = 0 -> -74.0 //max_ss = 255 -> -74.5 //max_ss = 128 -> -138.0 - instance->channel_ss[ch] = (furi_hal_subghz_get_rssi() + 138) * 2; + instance->channel_ss[ch] = (subghz_devices_get_rssi(instance->radio_device) + 138) * 2; if(instance->channel_ss[ch] > instance->max_rssi_dec) { instance->max_rssi_dec = instance->channel_ss[ch]; @@ -108,7 +141,7 @@ static int32_t spectrum_analyzer_worker_thread(void* context) { instance->max_rssi_channel = ch; } - furi_hal_subghz_idle(); + subghz_devices_idle(instance->radio_device); } // FURI_LOG_T("SpectrumWorker", "channel_ss[0]: %u", instance->channel_ss[0]); @@ -138,6 +171,11 @@ SpectrumAnalyzerWorker* spectrum_analyzer_worker_alloc() { furi_thread_set_context(instance->thread, instance); furi_thread_set_callback(instance->thread, spectrum_analyzer_worker_thread); + subghz_devices_init(); + + instance->radio_device = + radio_device_loader_set(instance->radio_device, SubGhzRadioDeviceTypeExternalCC1101); + FURI_LOG_D("Spectrum", "spectrum_analyzer_worker_alloc: End"); return instance; @@ -147,6 +185,12 @@ void spectrum_analyzer_worker_free(SpectrumAnalyzerWorker* instance) { FURI_LOG_D("Spectrum", "spectrum_analyzer_worker_free"); furi_assert(instance); furi_thread_free(instance->thread); + + subghz_devices_sleep(instance->radio_device); + radio_device_loader_end(instance->radio_device); + + subghz_devices_deinit(); + free(instance); } From b1850fd7006b15b0d1c8dcf3670b38b063a8332c Mon Sep 17 00:00:00 2001 From: gid9798 <30450294+gid9798@users.noreply.github.com> Date: Thu, 6 Jul 2023 16:23:48 +0300 Subject: [PATCH 058/364] Weather station: new external radio driver (#3) * Weather station: new external radio driver --- .../helpers/radio_device_loader.c | 69 +++++++++++++++++++ .../helpers/radio_device_loader.h | 17 +++++ .../scenes/weather_station_receiver.c | 11 ++- .../views/weather_station_receiver.c | 13 ++-- .../views/weather_station_receiver.h | 3 +- .../weather_station/weather_station_app.c | 26 +++---- .../weather_station/weather_station_app_i.c | 33 +++++---- .../weather_station/weather_station_app_i.h | 3 + 8 files changed, 135 insertions(+), 40 deletions(-) create mode 100644 applications/external/weather_station/helpers/radio_device_loader.c create mode 100644 applications/external/weather_station/helpers/radio_device_loader.h diff --git a/applications/external/weather_station/helpers/radio_device_loader.c b/applications/external/weather_station/helpers/radio_device_loader.c new file mode 100644 index 000000000..0d99549eb --- /dev/null +++ b/applications/external/weather_station/helpers/radio_device_loader.c @@ -0,0 +1,69 @@ +#include "radio_device_loader.h" + +#include +#include + +static void radio_device_loader_power_on() { + uint8_t attempts = 0; + while(!furi_hal_power_is_otg_enabled() && attempts++ < 5) { + furi_hal_power_enable_otg(); + //CC1101 power-up time + furi_delay_ms(10); + } +} + +static void radio_device_loader_power_off() { + if(furi_hal_power_is_otg_enabled()) furi_hal_power_disable_otg(); +} + +bool radio_device_loader_is_connect_external(const char* name) { + bool is_connect = false; + bool is_otg_enabled = furi_hal_power_is_otg_enabled(); + + if(!is_otg_enabled) { + radio_device_loader_power_on(); + } + + const SubGhzDevice* device = subghz_devices_get_by_name(name); + if(device) { + is_connect = subghz_devices_is_connect(device); + } + + if(!is_otg_enabled) { + radio_device_loader_power_off(); + } + return is_connect; +} + +const SubGhzDevice* radio_device_loader_set( + const SubGhzDevice* current_radio_device, + SubGhzRadioDeviceType radio_device_type) { + const SubGhzDevice* radio_device; + + if(radio_device_type == SubGhzRadioDeviceTypeExternalCC1101 && + radio_device_loader_is_connect_external(SUBGHZ_DEVICE_CC1101_EXT_NAME)) { + radio_device_loader_power_on(); + radio_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_EXT_NAME); + subghz_devices_begin(radio_device); + } else if(current_radio_device == NULL) { + radio_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME); + } else { + radio_device_loader_end(current_radio_device); + radio_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME); + } + + return radio_device; +} + +bool radio_device_loader_is_external(const SubGhzDevice* radio_device) { + furi_assert(radio_device); + return (radio_device != subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME)); +} + +void radio_device_loader_end(const SubGhzDevice* radio_device) { + furi_assert(radio_device); + radio_device_loader_power_off(); + if(radio_device != subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME)) { + subghz_devices_end(radio_device); + } +} \ No newline at end of file diff --git a/applications/external/weather_station/helpers/radio_device_loader.h b/applications/external/weather_station/helpers/radio_device_loader.h new file mode 100644 index 000000000..bae4bacf2 --- /dev/null +++ b/applications/external/weather_station/helpers/radio_device_loader.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +/** SubGhzRadioDeviceType */ +typedef enum { + SubGhzRadioDeviceTypeInternal, + SubGhzRadioDeviceTypeExternalCC1101, +} SubGhzRadioDeviceType; + +const SubGhzDevice* radio_device_loader_set( + const SubGhzDevice* current_radio_device, + SubGhzRadioDeviceType radio_device_type); + +bool radio_device_loader_is_external(const SubGhzDevice* radio_device); + +void radio_device_loader_end(const SubGhzDevice* radio_device); \ No newline at end of file diff --git a/applications/external/weather_station/scenes/weather_station_receiver.c b/applications/external/weather_station/scenes/weather_station_receiver.c index e76810430..76d808e7e 100644 --- a/applications/external/weather_station/scenes/weather_station_receiver.c +++ b/applications/external/weather_station/scenes/weather_station_receiver.c @@ -48,13 +48,18 @@ static void weather_station_scene_receiver_update_statusbar(void* context) { app->ws_receiver, furi_string_get_cstr(frequency_str), furi_string_get_cstr(modulation_str), - furi_string_get_cstr(history_stat_str)); + furi_string_get_cstr(history_stat_str), + radio_device_loader_is_external(app->txrx->radio_device)); furi_string_free(frequency_str); furi_string_free(modulation_str); } else { ws_view_receiver_add_data_statusbar( - app->ws_receiver, furi_string_get_cstr(history_stat_str), "", ""); + app->ws_receiver, + furi_string_get_cstr(history_stat_str), + "", + "", + radio_device_loader_is_external(app->txrx->radio_device)); } furi_string_free(history_stat_str); } @@ -196,7 +201,7 @@ bool weather_station_scene_receiver_on_event(void* context, SceneManagerEvent ev weather_station_scene_receiver_update_statusbar(app); } // Get current RSSI - float rssi = furi_hal_subghz_get_rssi(); + float rssi = subghz_devices_get_rssi(app->txrx->radio_device); ws_view_receiver_set_rssi(app->ws_receiver, rssi); if(app->txrx->txrx_state == WSTxRxStateRx) { diff --git a/applications/external/weather_station/views/weather_station_receiver.c b/applications/external/weather_station/views/weather_station_receiver.c index e994e7830..a29ff68f6 100644 --- a/applications/external/weather_station/views/weather_station_receiver.c +++ b/applications/external/weather_station/views/weather_station_receiver.c @@ -61,6 +61,7 @@ typedef struct { uint16_t history_item; WSReceiverBarShow bar_show; uint8_t u_rssi; + bool external_redio; } WSReceiverModel; void ws_view_receiver_set_rssi(WSReceiver* instance, float rssi) { @@ -154,7 +155,8 @@ void ws_view_receiver_add_data_statusbar( WSReceiver* ws_receiver, const char* frequency_str, const char* preset_str, - const char* history_stat_str) { + const char* history_stat_str, + bool external) { furi_assert(ws_receiver); with_view_model( ws_receiver->view, @@ -163,6 +165,7 @@ void ws_view_receiver_add_data_statusbar( furi_string_set_str(model->frequency_str, frequency_str); furi_string_set_str(model->preset_str, preset_str); furi_string_set_str(model->history_stat_str, history_stat_str); + model->external_redio = external; }, true); } @@ -202,7 +205,7 @@ void ws_view_receiver_draw(Canvas* canvas, WSReceiverModel* model) { FuriString* str_buff; str_buff = furi_string_alloc(); - bool ext_module = furi_hal_subghz_get_radio_type(); + // bool ext_module = furi_hal_subghz_get_radio_type(); WSReceiverMenuItem* item_menu; @@ -228,11 +231,12 @@ void ws_view_receiver_draw(Canvas* canvas, WSReceiverModel* model) { canvas_set_color(canvas, ColorBlack); if(model->history_item == 0) { - canvas_draw_icon(canvas, 0, 0, ext_module ? &I_Fishing_123x52 : &I_Scanning_123x52); + canvas_draw_icon( + canvas, 0, 0, model->external_redio ? &I_Fishing_123x52 : &I_Scanning_123x52); canvas_set_font(canvas, FontPrimary); canvas_draw_str(canvas, 63, 46, "Scanning..."); canvas_set_font(canvas, FontSecondary); - canvas_draw_str(canvas, 44, 10, ext_module ? "Ext" : "Int"); + canvas_draw_str(canvas, 44, 10, model->external_redio ? "Ext" : "Int"); } // Draw RSSI @@ -408,6 +412,7 @@ WSReceiver* ws_view_receiver_alloc() { model->history_stat_str = furi_string_alloc(); model->bar_show = WSReceiverBarShowDefault; model->history = malloc(sizeof(WSReceiverHistory)); + model->external_redio = false; WSReceiverMenuItemArray_init(model->history->data); }, true); diff --git a/applications/external/weather_station/views/weather_station_receiver.h b/applications/external/weather_station/views/weather_station_receiver.h index f81aa1f5e..ade61e2dc 100644 --- a/applications/external/weather_station/views/weather_station_receiver.h +++ b/applications/external/weather_station/views/weather_station_receiver.h @@ -27,7 +27,8 @@ void ws_view_receiver_add_data_statusbar( WSReceiver* ws_receiver, const char* frequency_str, const char* preset_str, - const char* history_stat_str); + const char* history_stat_str, + bool external); void ws_view_receiver_add_item_to_menu(WSReceiver* ws_receiver, const char* name, uint8_t type); diff --git a/applications/external/weather_station/weather_station_app.c b/applications/external/weather_station/weather_station_app.c index 8bea4961d..2305fa77b 100644 --- a/applications/external/weather_station/weather_station_app.c +++ b/applications/external/weather_station/weather_station_app.c @@ -98,6 +98,14 @@ WeatherStationApp* weather_station_app_alloc() { app->txrx->environment, (void*)&weather_station_protocol_registry); app->txrx->receiver = subghz_receiver_alloc_init(app->txrx->environment); + subghz_devices_init(); + + app->txrx->radio_device = + radio_device_loader_set(app->txrx->radio_device, SubGhzRadioDeviceTypeExternalCC1101); + + subghz_devices_reset(app->txrx->radio_device); + subghz_devices_idle(app->txrx->radio_device); + subghz_receiver_set_filter(app->txrx->receiver, SubGhzProtocolFlag_Decodable); subghz_worker_set_overrun_callback( app->txrx->worker, (SubGhzWorkerOverrunCallback)subghz_receiver_reset); @@ -105,15 +113,6 @@ WeatherStationApp* weather_station_app_alloc() { app->txrx->worker, (SubGhzWorkerPairCallback)subghz_receiver_decode); subghz_worker_set_context(app->txrx->worker, app->txrx->receiver); - // Enable power for External CC1101 if it is connected - furi_hal_subghz_enable_ext_power(); - // Auto switch to internal radio if external radio is not available - furi_delay_ms(15); - if(!furi_hal_subghz_check_radio()) { - furi_hal_subghz_select_radio_type(SubGhzRadioInternal); - furi_hal_subghz_init_radio_type(SubGhzRadioInternal); - } - furi_hal_power_suppress_charge_enter(); scene_manager_next_scene(app->scene_manager, WeatherStationSceneStart); @@ -124,13 +123,10 @@ WeatherStationApp* weather_station_app_alloc() { void weather_station_app_free(WeatherStationApp* app) { furi_assert(app); - //CC1101 off - ws_sleep(app); + subghz_devices_sleep(app->txrx->radio_device); + radio_device_loader_end(app->txrx->radio_device); - // Disable power for External CC1101 if it was enabled and module is connected - furi_hal_subghz_disable_ext_power(); - // Reinit SPI handles for internal radio / nfc - furi_hal_subghz_init_radio_type(SubGhzRadioInternal); + subghz_devices_deinit(); // Submenu view_dispatcher_remove_view(app->view_dispatcher, WeatherStationViewSubmenu); diff --git a/applications/external/weather_station/weather_station_app_i.c b/applications/external/weather_station/weather_station_app_i.c index 7236b6625..e98c61ee5 100644 --- a/applications/external/weather_station/weather_station_app_i.c +++ b/applications/external/weather_station/weather_station_app_i.c @@ -54,29 +54,28 @@ void ws_get_frequency_modulation( void ws_begin(WeatherStationApp* app, uint8_t* preset_data) { furi_assert(app); - UNUSED(preset_data); - furi_hal_subghz_reset(); - furi_hal_subghz_idle(); - furi_hal_subghz_load_custom_preset(preset_data); - furi_hal_gpio_init(furi_hal_subghz.cc1101_g0_pin, GpioModeInput, GpioPullNo, GpioSpeedLow); + subghz_devices_reset(app->txrx->radio_device); + subghz_devices_idle(app->txrx->radio_device); + subghz_devices_load_preset(app->txrx->radio_device, FuriHalSubGhzPresetCustom, preset_data); app->txrx->txrx_state = WSTxRxStateIDLE; } uint32_t ws_rx(WeatherStationApp* app, uint32_t frequency) { furi_assert(app); - if(!furi_hal_subghz_is_frequency_valid(frequency)) { + if(!subghz_devices_is_frequency_valid(app->txrx->radio_device, frequency)) { furi_crash("WeatherStation: Incorrect RX frequency."); } furi_assert( app->txrx->txrx_state != WSTxRxStateRx && app->txrx->txrx_state != WSTxRxStateSleep); - furi_hal_subghz_idle(); - uint32_t value = furi_hal_subghz_set_frequency_and_path(frequency); - furi_hal_gpio_init(furi_hal_subghz.cc1101_g0_pin, GpioModeInput, GpioPullNo, GpioSpeedLow); - furi_hal_subghz_flush_rx(); - furi_hal_subghz_rx(); + subghz_devices_idle(app->txrx->radio_device); + uint32_t value = subghz_devices_set_frequency(app->txrx->radio_device, frequency); + subghz_devices_flush_rx(app->txrx->radio_device); + subghz_devices_set_rx(app->txrx->radio_device); + + subghz_devices_start_async_rx( + app->txrx->radio_device, subghz_worker_rx_callback, app->txrx->worker); - furi_hal_subghz_start_async_rx(subghz_worker_rx_callback, app->txrx->worker); subghz_worker_start(app->txrx->worker); app->txrx->txrx_state = WSTxRxStateRx; return value; @@ -85,7 +84,7 @@ uint32_t ws_rx(WeatherStationApp* app, uint32_t frequency) { void ws_idle(WeatherStationApp* app) { furi_assert(app); furi_assert(app->txrx->txrx_state != WSTxRxStateSleep); - furi_hal_subghz_idle(); + subghz_devices_idle(app->txrx->radio_device); app->txrx->txrx_state = WSTxRxStateIDLE; } @@ -94,15 +93,15 @@ void ws_rx_end(WeatherStationApp* app) { furi_assert(app->txrx->txrx_state == WSTxRxStateRx); if(subghz_worker_is_running(app->txrx->worker)) { subghz_worker_stop(app->txrx->worker); - furi_hal_subghz_stop_async_rx(); + subghz_devices_stop_async_rx(app->txrx->radio_device); } - furi_hal_subghz_idle(); + subghz_devices_idle(app->txrx->radio_device); app->txrx->txrx_state = WSTxRxStateIDLE; } void ws_sleep(WeatherStationApp* app) { furi_assert(app); - furi_hal_subghz_sleep(); + subghz_devices_sleep(app->txrx->radio_device); app->txrx->txrx_state = WSTxRxStateSleep; } @@ -125,7 +124,7 @@ void ws_hopper_update(WeatherStationApp* app) { float rssi = -127.0f; if(app->txrx->hopper_state != WSHopperStateRSSITimeOut) { // See RSSI Calculation timings in CC1101 17.3 RSSI - rssi = furi_hal_subghz_get_rssi(); + rssi = subghz_devices_get_rssi(app->txrx->radio_device); // Stay if RSSI is high enough if(rssi > -90.0f) { diff --git a/applications/external/weather_station/weather_station_app_i.h b/applications/external/weather_station/weather_station_app_i.h index 41e248112..0950f5975 100644 --- a/applications/external/weather_station/weather_station_app_i.h +++ b/applications/external/weather_station/weather_station_app_i.h @@ -20,11 +20,14 @@ #include #include +#include "helpers/radio_device_loader.h" + typedef struct WeatherStationApp WeatherStationApp; struct WeatherStationTxRx { SubGhzWorker* worker; + const SubGhzDevice* radio_device; SubGhzEnvironment* environment; SubGhzReceiver* receiver; SubGhzRadioPreset* preset; From dd2cad0c20c678cb07f74016f39fec3cbf6ba6fc Mon Sep 17 00:00:00 2001 From: gid9798 <30450294+gid9798@users.noreply.github.com> Date: Thu, 6 Jul 2023 16:50:25 +0300 Subject: [PATCH 059/364] External radio driver in frequency analyzer & test carrier (#5) * SubGhz App: add support ext_cc1101 in freq analyzer * SubGhz App: add support ext_cc1101 in test_carrier * SubGhz app: Deleted the temporary menu --- .../subghz_frequency_analyzer_worker.c | 129 +++++++++++------- .../subghz_frequency_analyzer_worker.h | 5 +- .../main/subghz/scenes/subghz_scene_config.h | 1 - .../scenes/subghz_scene_radio_setting.c | 70 ---------- .../scenes/subghz_scene_radio_settings.c | 94 ++++++------- .../main/subghz/scenes/subghz_scene_start.c | 12 -- .../subghz/scenes/subghz_scene_test_carrier.c | 3 + applications/main/subghz/subghz.c | 7 +- .../subghz/views/subghz_frequency_analyzer.c | 28 ++-- .../subghz/views/subghz_frequency_analyzer.h | 3 +- .../main/subghz/views/subghz_test_carrier.c | 69 +++++++--- .../main/subghz/views/subghz_test_carrier.h | 5 + 12 files changed, 206 insertions(+), 220 deletions(-) delete mode 100644 applications/main/subghz/scenes/subghz_scene_radio_setting.c diff --git a/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.c b/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.c index 7ba6999fb..6551e0425 100644 --- a/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.c +++ b/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.c @@ -4,8 +4,6 @@ #include #include -// TODO add external module - #define TAG "SubghzFrequencyAnalyzerWorker" #define SUBGHZ_FREQUENCY_ANALYZER_THRESHOLD -97.0f @@ -30,6 +28,10 @@ struct SubGhzFrequencyAnalyzerWorker { FrequencyRSSI frequency_rssi_buf; SubGhzSetting* setting; + const SubGhzDevice* radio_device; + FuriHalSpiBusHandle* spi_bus; + bool ext_radio; + float filVal; float trigger_level; @@ -37,14 +39,16 @@ struct SubGhzFrequencyAnalyzerWorker { void* context; }; -static void subghz_frequency_analyzer_worker_load_registers(const uint8_t data[][2]) { - furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); +static void subghz_frequency_analyzer_worker_load_registers( + FuriHalSpiBusHandle* spi_bus, + const uint8_t data[][2]) { + furi_hal_spi_acquire(spi_bus); size_t i = 0; while(data[i][0]) { - cc1101_write_reg(&furi_hal_spi_bus_handle_subghz, data[i][0], data[i][1]); + cc1101_write_reg(spi_bus, data[i][0], data[i][1]); i++; } - furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); + furi_hal_spi_release(spi_bus); } // running average with adaptive coefficient @@ -79,31 +83,35 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) { uint32_t frequency_temp = 0; CC1101Status status; - //Start CC1101 - furi_hal_subghz_reset(); + FuriHalSpiBusHandle* spi_bus = instance->spi_bus; + const SubGhzDevice* radio_device = instance->radio_device; - furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); - cc1101_flush_rx(&furi_hal_spi_bus_handle_subghz); - cc1101_flush_tx(&furi_hal_spi_bus_handle_subghz); + //Start CC1101 + // furi_hal_subghz_reset(); + subghz_devices_reset(radio_device); + + furi_hal_spi_acquire(spi_bus); + cc1101_flush_rx(spi_bus); + cc1101_flush_tx(spi_bus); // TODO probably can be used device.load_preset(FuriHalSubGhzPresetCustom, ...) for external cc1101 - cc1101_write_reg(&furi_hal_spi_bus_handle_subghz, CC1101_IOCFG0, CC1101IocfgHW); - cc1101_write_reg(&furi_hal_spi_bus_handle_subghz, CC1101_MDMCFG3, + cc1101_write_reg(spi_bus, CC1101_IOCFG0, CC1101IocfgHW); + cc1101_write_reg(spi_bus, CC1101_MDMCFG3, 0b01111111); // symbol rate cc1101_write_reg( - &furi_hal_spi_bus_handle_subghz, + spi_bus, CC1101_AGCCTRL2, 0b00000111); // 00 - DVGA all; 000 - MAX LNA+LNA2; 111 - MAGN_TARGET 42 dB cc1101_write_reg( - &furi_hal_spi_bus_handle_subghz, + spi_bus, CC1101_AGCCTRL1, 0b00001000); // 0; 0 - LNA 2 gain is decreased to minimum before decreasing LNA gain; 00 - Relative carrier sense threshold disabled; 1000 - Absolute carrier sense threshold disabled cc1101_write_reg( - &furi_hal_spi_bus_handle_subghz, + spi_bus, CC1101_AGCCTRL0, 0b00110000); // 00 - No hysteresis, medium asymmetric dead zone, medium gain ; 11 - 64 samples agc; 00 - Normal AGC, 00 - 4dB boundary - furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); + furi_hal_spi_release(spi_bus); furi_hal_subghz_set_path(FuriHalSubGhzPathIsolate); @@ -116,36 +124,36 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) { frequency_rssi.rssi_coarse = -127.0f; frequency_rssi.rssi_fine = -127.0f; - furi_hal_subghz_idle(); - subghz_frequency_analyzer_worker_load_registers(subghz_preset_ook_650khz); + // furi_hal_subghz_idle(); + subghz_devices_idle(radio_device); + subghz_frequency_analyzer_worker_load_registers(spi_bus, subghz_preset_ook_650khz); // First stage: coarse scan for(size_t i = 0; i < subghz_setting_get_frequency_count(instance->setting); i++) { uint32_t current_frequency = subghz_setting_get_frequency(instance->setting, i); - if(furi_hal_subghz_is_frequency_valid(current_frequency) && - (current_frequency != 467750000) && (current_frequency != 464000000) - // && - // !((furi_hal_subghz.radio_type == SubGhzRadioExternal) && - // ((current_frequency == 390000000) || (current_frequency == 312000000) || - // (current_frequency == 312100000) || (current_frequency == 312200000) || - // (current_frequency == 440175000))) - ) { - furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); - cc1101_switch_to_idle(&furi_hal_spi_bus_handle_subghz); - frequency = - cc1101_set_frequency(&furi_hal_spi_bus_handle_subghz, current_frequency); + // if(furi_hal_subghz_is_frequency_valid(current_frequency) && + if(subghz_devices_is_frequency_valid(radio_device, current_frequency) && + (current_frequency != 467750000) && (current_frequency != 464000000) && + !((instance->ext_radio) && + ((current_frequency == 390000000) || (current_frequency == 312000000) || + (current_frequency == 312100000) || (current_frequency == 312200000) || + (current_frequency == 440175000)))) { + furi_hal_spi_acquire(spi_bus); + cc1101_switch_to_idle(spi_bus); + frequency = cc1101_set_frequency(spi_bus, current_frequency); - cc1101_calibrate(&furi_hal_spi_bus_handle_subghz); + cc1101_calibrate(spi_bus); do { - status = cc1101_get_status(&furi_hal_spi_bus_handle_subghz); + status = cc1101_get_status(spi_bus); } while(status.STATE != CC1101StateIDLE); - cc1101_switch_to_rx(&furi_hal_spi_bus_handle_subghz); - furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); + cc1101_switch_to_rx(spi_bus); + furi_hal_spi_release(spi_bus); furi_delay_ms(2); - rssi = furi_hal_subghz_get_rssi(); + // rssi = furi_hal_subghz_get_rssi(); + rssi = subghz_devices_get_rssi(radio_device); rssi_avg += rssi; rssi_avg_samples++; @@ -169,28 +177,31 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) { // Second stage: fine scan if(frequency_rssi.rssi_coarse > instance->trigger_level) { - furi_hal_subghz_idle(); - subghz_frequency_analyzer_worker_load_registers(subghz_preset_ook_58khz); + // furi_hal_subghz_idle(); + subghz_devices_idle(radio_device); + subghz_frequency_analyzer_worker_load_registers(spi_bus, subghz_preset_ook_58khz); //for example -0.3 ... 433.92 ... +0.3 step 20KHz for(uint32_t i = frequency_rssi.frequency_coarse - 300000; i < frequency_rssi.frequency_coarse + 300000; i += 20000) { - if(furi_hal_subghz_is_frequency_valid(i)) { - furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); - cc1101_switch_to_idle(&furi_hal_spi_bus_handle_subghz); - frequency = cc1101_set_frequency(&furi_hal_spi_bus_handle_subghz, i); + // if(furi_hal_subghz_is_frequency_valid(i)) { + if(subghz_devices_is_frequency_valid(radio_device, i)) { + furi_hal_spi_acquire(spi_bus); + cc1101_switch_to_idle(spi_bus); + frequency = cc1101_set_frequency(spi_bus, i); - cc1101_calibrate(&furi_hal_spi_bus_handle_subghz); + cc1101_calibrate(spi_bus); do { - status = cc1101_get_status(&furi_hal_spi_bus_handle_subghz); + status = cc1101_get_status(spi_bus); } while(status.STATE != CC1101StateIDLE); - cc1101_switch_to_rx(&furi_hal_spi_bus_handle_subghz); - furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); + cc1101_switch_to_rx(spi_bus); + furi_hal_spi_release(spi_bus); furi_delay_ms(2); - rssi = furi_hal_subghz_get_rssi(); + // rssi = furi_hal_subghz_get_rssi(); + rssi = subghz_devices_get_rssi(radio_device); FURI_LOG_T(TAG, "#:%lu:%f", frequency, (double)rssi); @@ -267,8 +278,10 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) { } //Stop CC1101 - furi_hal_subghz_idle(); - furi_hal_subghz_sleep(); + // furi_hal_subghz_idle(); + // furi_hal_subghz_sleep(); + subghz_devices_idle(radio_device); + subghz_devices_sleep(radio_device); return 0; } @@ -307,10 +320,26 @@ void subghz_frequency_analyzer_worker_set_pair_callback( instance->context = context; } -void subghz_frequency_analyzer_worker_start(SubGhzFrequencyAnalyzerWorker* instance) { +void subghz_frequency_analyzer_worker_start( + SubGhzFrequencyAnalyzerWorker* instance, + SubGhzTxRx* txrx) { furi_assert(instance); furi_assert(!instance->worker_running); + SubGhzRadioDeviceType radio_type = subghz_txrx_radio_device_get(txrx); + + if(radio_type == SubGhzRadioDeviceTypeExternalCC1101) { + instance->spi_bus = &furi_hal_spi_bus_handle_external; + instance->ext_radio = true; + } else if(radio_type == SubGhzRadioDeviceTypeInternal) { + instance->spi_bus = &furi_hal_spi_bus_handle_subghz; + instance->ext_radio = false; + } else { + furi_crash("Unsuported external module"); + } + + instance->radio_device = subghz_devices_get_by_name(subghz_txrx_radio_device_get_name(txrx)); + instance->worker_running = true; furi_thread_start(instance->thread); diff --git a/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.h b/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.h index eba4409ce..eeb1804d9 100644 --- a/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.h +++ b/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.h @@ -45,8 +45,11 @@ void subghz_frequency_analyzer_worker_set_pair_callback( /** Start SubGhzFrequencyAnalyzerWorker * * @param instance SubGhzFrequencyAnalyzerWorker instance + * @param txrx pointer to SubGhzTxRx */ -void subghz_frequency_analyzer_worker_start(SubGhzFrequencyAnalyzerWorker* instance); +void subghz_frequency_analyzer_worker_start( + SubGhzFrequencyAnalyzerWorker* instance, + SubGhzTxRx* txrx); /** Stop SubGhzFrequencyAnalyzerWorker * diff --git a/applications/main/subghz/scenes/subghz_scene_config.h b/applications/main/subghz/scenes/subghz_scene_config.h index ac2f2c599..269ec4c72 100644 --- a/applications/main/subghz/scenes/subghz_scene_config.h +++ b/applications/main/subghz/scenes/subghz_scene_config.h @@ -30,4 +30,3 @@ ADD_SCENE(subghz, decode_raw, DecodeRAW) ADD_SCENE(subghz, delete_raw, DeleteRAW) ADD_SCENE(subghz, need_saving, NeedSaving) ADD_SCENE(subghz, rpc, Rpc) -ADD_SCENE(subghz, radio_setting, RadioSettings) diff --git a/applications/main/subghz/scenes/subghz_scene_radio_setting.c b/applications/main/subghz/scenes/subghz_scene_radio_setting.c deleted file mode 100644 index ee438727b..000000000 --- a/applications/main/subghz/scenes/subghz_scene_radio_setting.c +++ /dev/null @@ -1,70 +0,0 @@ -#include "../subghz_i.h" -#include -#include - -enum SubGhzRadioSettingIndex { - SubGhzRadioSettingIndexDevice, -}; - -#define RADIO_DEVICE_COUNT 2 -const char* const radio_device_text[RADIO_DEVICE_COUNT] = { - "Internal", - "External", -}; - -const uint32_t radio_device_value[RADIO_DEVICE_COUNT] = { - SubGhzRadioDeviceTypeInternal, - SubGhzRadioDeviceTypeExternalCC1101, -}; - -static void subghz_scene_radio_setting_set_device(VariableItem* item) { - SubGhz* subghz = variable_item_get_context(item); - uint8_t index = variable_item_get_current_value_index(item); - - if(!subghz_txrx_radio_device_is_external_connected( - subghz->txrx, SUBGHZ_DEVICE_CC1101_EXT_NAME) && - radio_device_value[index] == SubGhzRadioDeviceTypeExternalCC1101) { - //ToDo correct if there is more than 1 module - index = 0; - } - variable_item_set_current_value_text(item, radio_device_text[index]); - subghz_txrx_radio_device_set(subghz->txrx, radio_device_value[index]); -} - -void subghz_scene_radio_setting_on_enter(void* context) { - SubGhz* subghz = context; - VariableItem* item; - uint8_t value_index; - - uint8_t value_count_device = RADIO_DEVICE_COUNT; - if(subghz_txrx_radio_device_get(subghz->txrx) == SubGhzRadioDeviceTypeInternal && - !subghz_txrx_radio_device_is_external_connected(subghz->txrx, SUBGHZ_DEVICE_CC1101_EXT_NAME)) - value_count_device = 1; // Only 1 item if external disconnected - item = variable_item_list_add( - subghz->variable_item_list, - "Module", - value_count_device, - subghz_scene_radio_setting_set_device, - subghz); - value_index = value_index_uint32( - subghz_txrx_radio_device_get(subghz->txrx), radio_device_value, value_count_device); - variable_item_set_current_value_index(item, value_index); - variable_item_set_current_value_text(item, radio_device_text[value_index]); - - view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdVariableItemList); -} - -bool subghz_scene_radio_setting_on_event(void* context, SceneManagerEvent event) { - SubGhz* subghz = context; - bool consumed = false; - UNUSED(subghz); - UNUSED(event); - - return consumed; -} - -void subghz_scene_radio_setting_on_exit(void* context) { - SubGhz* subghz = context; - variable_item_list_set_selected_item(subghz->variable_item_list, 0); - variable_item_list_reset(subghz->variable_item_list); -} diff --git a/applications/main/subghz/scenes/subghz_scene_radio_settings.c b/applications/main/subghz/scenes/subghz_scene_radio_settings.c index 7c78d07c4..6fb6e5089 100644 --- a/applications/main/subghz/scenes/subghz_scene_radio_settings.c +++ b/applications/main/subghz/scenes/subghz_scene_radio_settings.c @@ -1,17 +1,18 @@ #include "../subghz_i.h" #include "../helpers/subghz_custom_event.h" +#include +#include -// #define EXT_MODULES_COUNT (sizeof(radio_modules_variables_text) / sizeof(char* const)) -// const char* const radio_modules_variables_text[] = { -// "Internal", -// "External", -// }; +#define RADIO_DEVICE_COUNT 2 +const char* const radio_device_text[RADIO_DEVICE_COUNT] = { + "Internal", + "External", +}; -// #define EXT_MOD_POWER_COUNT 2 -// const char* const ext_mod_power_text[EXT_MOD_POWER_COUNT] = { -// "ON", -// "OFF", -// }; +const uint32_t radio_device_value[RADIO_DEVICE_COUNT] = { + SubGhzRadioDeviceTypeInternal, + SubGhzRadioDeviceTypeExternalCC1101, +}; #define TIMESTAMP_NAMES_COUNT 2 const char* const timestamp_names_text[TIMESTAMP_NAMES_COUNT] = { @@ -35,20 +36,19 @@ const char* const debug_counter_text[DEBUG_COUNTER_COUNT] = { "+10", }; -// static void subghz_scene_ext_module_changed(VariableItem* item) { -// SubGhz* subghz = variable_item_get_context(item); -// uint8_t value_index_exm = variable_item_get_current_value_index(item); +static void subghz_scene_radio_settings_set_device(VariableItem* item) { + SubGhz* subghz = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); -// variable_item_set_current_value_text(item, radio_modules_variables_text[value_index_exm]); - -// subghz->last_settings->external_module_enabled = value_index_exm == 1; -// subghz_last_settings_save(subghz->last_settings); -// } - -// static void subghz_ext_module_start_var_list_enter_callback(void* context, uint32_t index) { -// SubGhz* subghz = context; -// view_dispatcher_send_custom_event(subghz->view_dispatcher, index); -// } + if(!subghz_txrx_radio_device_is_external_connected( + subghz->txrx, SUBGHZ_DEVICE_CC1101_EXT_NAME) && + radio_device_value[index] == SubGhzRadioDeviceTypeExternalCC1101) { + //ToDo correct if there is more than 1 module + index = 0; + } + variable_item_set_current_value_text(item, radio_device_text[index]); + subghz_txrx_radio_device_set(subghz->txrx, radio_device_value[index]); +} static void subghz_scene_receiver_config_set_debug_pin(VariableItem* item) { SubGhz* subghz = variable_item_get_context(item); @@ -122,24 +122,20 @@ void subghz_scene_radio_settings_on_enter(void* context) { uint8_t value_index; VariableItem* item; - // VariableItem* item = variable_item_list_add( - // variable_item_list, "Module", EXT_MODULES_COUNT, subghz_scene_ext_module_changed, subghz); - - // variable_item_list_set_enter_callback( - // variable_item_list, subghz_ext_module_start_var_list_enter_callback, subghz); - // value_index = furi_hal_subghz.radio_type; - // variable_item_set_current_value_index(item, value_index); - // variable_item_set_current_value_text(item, radio_modules_variables_text[value_index]); - - // item = variable_item_list_add( - // variable_item_list, - // "Ext Radio 5v", - // EXT_MOD_POWER_COUNT, - // subghz_scene_receiver_config_set_ext_mod_power, - // subghz); - // value_index = furi_hal_subghz_get_external_power_disable(); - // variable_item_set_current_value_index(item, value_index); - // variable_item_set_current_value_text(item, ext_mod_power_text[value_index]); + uint8_t value_count_device = RADIO_DEVICE_COUNT; + if(subghz_txrx_radio_device_get(subghz->txrx) == SubGhzRadioDeviceTypeInternal && + !subghz_txrx_radio_device_is_external_connected(subghz->txrx, SUBGHZ_DEVICE_CC1101_EXT_NAME)) + value_count_device = 1; // Only 1 item if external disconnected + item = variable_item_list_add( + subghz->variable_item_list, + "Module", + value_count_device, + subghz_scene_radio_settings_set_device, + subghz); + value_index = value_index_uint32( + subghz_txrx_radio_device_get(subghz->txrx), radio_device_value, value_count_device); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, radio_device_text[value_index]); item = variable_item_list_add( variable_item_list, @@ -227,25 +223,11 @@ bool subghz_scene_radio_settings_on_event(void* context, SceneManagerEvent event UNUSED(subghz); UNUSED(event); - // Set selected radio module - // furi_hal_subghz_select_radio_type(subghz->last_settings->external_module_enabled); - // furi_hal_subghz_init_radio_type(subghz->last_settings->external_module_enabled); - - // furi_hal_subghz_enable_ext_power(); - - // Check if module is present, if no -> show error - // if(!furi_hal_subghz_check_radio()) { - // subghz->last_settings->external_module_enabled = false; - // furi_hal_subghz_select_radio_type(SubGhzRadioInternal); - // furi_hal_subghz_init_radio_type(SubGhzRadioInternal); - // furi_string_set(subghz->error_str, "Please connect\nexternal radio"); - // scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowErrorSub); - // } - return false; } void subghz_scene_radio_settings_on_exit(void* context) { SubGhz* subghz = context; + variable_item_list_set_selected_item(subghz->variable_item_list, 0); variable_item_list_reset(subghz->variable_item_list); } diff --git a/applications/main/subghz/scenes/subghz_scene_start.c b/applications/main/subghz/scenes/subghz_scene_start.c index b65fc38cf..08159f8dc 100644 --- a/applications/main/subghz/scenes/subghz_scene_start.c +++ b/applications/main/subghz/scenes/subghz_scene_start.c @@ -1,7 +1,6 @@ #include "../subghz_i.h" #include -// TODO move RadioSettings to ExtraSettings #include enum SubmenuIndex { @@ -54,12 +53,6 @@ void subghz_scene_start_on_enter(void* context) { SubmenuIndexExtSettings, subghz_scene_start_submenu_callback, subghz); - submenu_add_item( - subghz->submenu, - "Radio Settings2", - SubmenuIndexRadioSetting, - subghz_scene_start_submenu_callback, - subghz); if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { submenu_add_item( subghz->submenu, "Test", SubmenuIndexTest, subghz_scene_start_submenu_callback, subghz); @@ -115,11 +108,6 @@ bool subghz_scene_start_on_event(void* context, SceneManagerEvent event) { subghz->scene_manager, SubGhzSceneStart, SubmenuIndexExtSettings); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneExtModuleSettings); return true; - } else if(event.event == SubmenuIndexRadioSetting) { - scene_manager_set_scene_state( - subghz->scene_manager, SubGhzSceneStart, SubmenuIndexRadioSetting); - scene_manager_next_scene(subghz->scene_manager, SubGhzSceneRadioSettings); - return true; } } return false; diff --git a/applications/main/subghz/scenes/subghz_scene_test_carrier.c b/applications/main/subghz/scenes/subghz_scene_test_carrier.c index 2e1ec4d9c..6d294ca2c 100644 --- a/applications/main/subghz/scenes/subghz_scene_test_carrier.c +++ b/applications/main/subghz/scenes/subghz_scene_test_carrier.c @@ -11,6 +11,9 @@ void subghz_scene_test_carrier_on_enter(void* context) { SubGhz* subghz = context; subghz_test_carrier_set_callback( subghz->subghz_test_carrier, subghz_scene_test_carrier_callback, subghz); + subghz_test_carrier_set_radio( + subghz->subghz_test_carrier, + subghz_devices_get_by_name(subghz_txrx_radio_device_get_name(subghz->txrx))); view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdTestCarrier); } diff --git a/applications/main/subghz/subghz.c b/applications/main/subghz/subghz.c index 0c886a021..16bc496b5 100644 --- a/applications/main/subghz/subghz.c +++ b/applications/main/subghz/subghz.c @@ -112,6 +112,8 @@ SubGhz* subghz_alloc(bool alloc_for_tx_only) { // Open Notification record subghz->notifications = furi_record_open(RECORD_NOTIFICATION); + subghz->txrx = subghz_txrx_alloc(); + if(!alloc_for_tx_only) { // SubMenu subghz->submenu = submenu_alloc(); @@ -167,7 +169,8 @@ SubGhz* subghz_alloc(bool alloc_for_tx_only) { variable_item_list_get_view(subghz->variable_item_list)); // Frequency Analyzer - subghz->subghz_frequency_analyzer = subghz_frequency_analyzer_alloc(); + // View knows too much + subghz->subghz_frequency_analyzer = subghz_frequency_analyzer_alloc(subghz->txrx); view_dispatcher_add_view( subghz->view_dispatcher, SubGhzViewIdFrequencyAnalyzer, @@ -209,8 +212,6 @@ SubGhz* subghz_alloc(bool alloc_for_tx_only) { //init TxRx & Protocol & History & KeyBoard subghz_unlock(subghz); - subghz->txrx = subghz_txrx_alloc(); - SubGhzSetting* setting = subghz_txrx_get_setting(subghz->txrx); subghz_load_custom_presets(setting); diff --git a/applications/main/subghz/views/subghz_frequency_analyzer.c b/applications/main/subghz/views/subghz_frequency_analyzer.c index b3822feab..7800c9081 100644 --- a/applications/main/subghz/views/subghz_frequency_analyzer.c +++ b/applications/main/subghz/views/subghz_frequency_analyzer.c @@ -12,8 +12,6 @@ #include #include -// TODO remove furi_hal_subghz - #define TAG "frequency_analyzer" #define RSSI_MIN -97 @@ -40,6 +38,7 @@ struct SubGhzFrequencyAnalyzer { SubGhzFrequencyAnalyzerWorker* worker; SubGhzFrequencyAnalyzerCallback callback; void* context; + SubGhzTxRx* txrx; bool locked; SubGHzFrequencyAnalyzerFeedbackLevel feedback_level; // 0 - no feedback, 1 - vibro only, 2 - vibro and sound @@ -62,6 +61,7 @@ typedef struct { uint8_t selected_index; uint8_t max_index; bool show_frame; + bool is_ext_radio; } SubGhzFrequencyAnalyzerModel; void subghz_frequency_analyzer_set_callback( @@ -168,8 +168,8 @@ void subghz_frequency_analyzer_draw(Canvas* canvas, SubGhzFrequencyAnalyzerModel // Title canvas_set_color(canvas, ColorBlack); canvas_set_font(canvas, FontSecondary); - // TODO - // canvas_draw_str(canvas, 0, 7, furi_hal_subghz_get_radio_type() ? "Ext" : "Int"); + + canvas_draw_str(canvas, 0, 7, model->is_ext_radio ? "Ext" : "Int"); canvas_draw_str(canvas, 20, 7, "Frequency Analyzer"); // RSSI @@ -314,7 +314,9 @@ bool subghz_frequency_analyzer_input(InputEvent* event, void* context) { uint32_t prev_freq_to_save = model->frequency_to_save; uint32_t frequency_candidate = model->history_frequency[model->selected_index]; if(frequency_candidate == 0 || - !furi_hal_subghz_is_frequency_valid(frequency_candidate) || + // !furi_hal_subghz_is_frequency_valid(frequency_candidate) || + !subghz_txrx_radio_device_is_frequecy_valid( + instance->txrx, frequency_candidate) || prev_freq_to_save == frequency_candidate) { frequency_candidate = 0; } else { @@ -336,7 +338,9 @@ bool subghz_frequency_analyzer_input(InputEvent* event, void* context) { uint32_t prev_freq_to_save = model->frequency_to_save; uint32_t frequency_candidate = subghz_frequency_find_correct(model->frequency); if(frequency_candidate == 0 || - !furi_hal_subghz_is_frequency_valid(frequency_candidate) || + // !furi_hal_subghz_is_frequency_valid(frequency_candidate) || + !subghz_txrx_radio_device_is_frequecy_valid( + instance->txrx, frequency_candidate) || prev_freq_to_save == frequency_candidate) { frequency_candidate = 0; } else { @@ -351,7 +355,9 @@ bool subghz_frequency_analyzer_input(InputEvent* event, void* context) { uint32_t prev_freq_to_save = model->frequency_to_save; uint32_t frequency_candidate = subghz_frequency_find_correct(model->frequency); if(frequency_candidate == 0 || - !furi_hal_subghz_is_frequency_valid(frequency_candidate) || + // !furi_hal_subghz_is_frequency_valid(frequency_candidate) || + !subghz_txrx_radio_device_is_frequecy_valid( + instance->txrx, frequency_candidate) || prev_freq_to_save == frequency_candidate) { frequency_candidate = 0; } else { @@ -542,7 +548,7 @@ void subghz_frequency_analyzer_enter(void* context) { (SubGhzFrequencyAnalyzerWorkerPairCallback)subghz_frequency_analyzer_pair_callback, instance); - subghz_frequency_analyzer_worker_start(instance->worker); + subghz_frequency_analyzer_worker_start(instance->worker, instance->txrx); instance->rssi_last = 0; instance->selected_index = 0; @@ -570,6 +576,8 @@ void subghz_frequency_analyzer_enter(void* context) { model->history_frequency_rx_count[0] = 0; model->frequency_to_save = 0; model->trigger = RSSI_MIN; + model->is_ext_radio = + (subghz_txrx_radio_device_get(instance->txrx) != SubGhzRadioDeviceTypeInternal); }, true); } @@ -587,7 +595,7 @@ void subghz_frequency_analyzer_exit(void* context) { furi_record_close(RECORD_NOTIFICATION); } -SubGhzFrequencyAnalyzer* subghz_frequency_analyzer_alloc() { +SubGhzFrequencyAnalyzer* subghz_frequency_analyzer_alloc(SubGhzTxRx* txrx) { SubGhzFrequencyAnalyzer* instance = malloc(sizeof(SubGhzFrequencyAnalyzer)); instance->feedback_level = 2; @@ -602,6 +610,8 @@ SubGhzFrequencyAnalyzer* subghz_frequency_analyzer_alloc() { view_set_enter_callback(instance->view, subghz_frequency_analyzer_enter); view_set_exit_callback(instance->view, subghz_frequency_analyzer_exit); + instance->txrx = txrx; + return instance; } diff --git a/applications/main/subghz/views/subghz_frequency_analyzer.h b/applications/main/subghz/views/subghz_frequency_analyzer.h index 95169c08d..f8c643222 100644 --- a/applications/main/subghz/views/subghz_frequency_analyzer.h +++ b/applications/main/subghz/views/subghz_frequency_analyzer.h @@ -2,6 +2,7 @@ #include #include "../helpers/subghz_custom_event.h" +#include "../helpers/subghz_txrx.h" typedef enum { SubGHzFrequencyAnalyzerFeedbackLevelAll, @@ -18,7 +19,7 @@ void subghz_frequency_analyzer_set_callback( SubGhzFrequencyAnalyzerCallback callback, void* context); -SubGhzFrequencyAnalyzer* subghz_frequency_analyzer_alloc(); +SubGhzFrequencyAnalyzer* subghz_frequency_analyzer_alloc(SubGhzTxRx* txrx); void subghz_frequency_analyzer_free(SubGhzFrequencyAnalyzer* subghz_static); diff --git a/applications/main/subghz/views/subghz_test_carrier.c b/applications/main/subghz/views/subghz_test_carrier.c index 8c26f478c..87ab81ca4 100644 --- a/applications/main/subghz/views/subghz_test_carrier.c +++ b/applications/main/subghz/views/subghz_test_carrier.c @@ -8,12 +8,11 @@ #include #include -// TODO add external module - struct SubGhzTestCarrier { View* view; FuriTimer* timer; SubGhzTestCarrierCallback callback; + const SubGhzDevice* radio_device; void* context; }; @@ -86,6 +85,7 @@ void subghz_test_carrier_draw(Canvas* canvas, SubGhzTestCarrierModel* model) { bool subghz_test_carrier_input(InputEvent* event, void* context) { furi_assert(context); SubGhzTestCarrier* subghz_test_carrier = context; + const SubGhzDevice* radio_device = subghz_test_carrier->radio_device; if(event->key == InputKeyBack || event->type != InputTypeShort) { return false; @@ -95,7 +95,8 @@ bool subghz_test_carrier_input(InputEvent* event, void* context) { subghz_test_carrier->view, SubGhzTestCarrierModel * model, { - furi_hal_subghz_idle(); + // furi_hal_subghz_idle(); + subghz_devices_idle(radio_device); if(event->key == InputKeyLeft) { if(model->frequency > 0) model->frequency--; @@ -113,19 +114,33 @@ bool subghz_test_carrier_input(InputEvent* event, void* context) { } } - model->real_frequency = - furi_hal_subghz_set_frequency(subghz_frequencies_testing[model->frequency]); + // model->real_frequency = + // furi_hal_subghz_set_frequency(subghz_frequencies_testing[model->frequency]); furi_hal_subghz_set_path(model->path); + model->real_frequency = subghz_devices_set_frequency( + radio_device, subghz_frequencies_testing[model->frequency]); if(model->status == SubGhzTestCarrierModelStatusRx) { - furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow); - furi_hal_subghz_rx(); + // furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow); + // furi_hal_subghz_rx(); + furi_hal_gpio_init( + subghz_devices_get_data_gpio(radio_device), + GpioModeInput, + GpioPullNo, + GpioSpeedLow); + subghz_devices_set_rx(radio_device); } else { furi_hal_gpio_init( &gpio_cc1101_g0, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); furi_hal_gpio_write(&gpio_cc1101_g0, true); - if(!furi_hal_subghz_tx()) { - furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow); + // if(!furi_hal_subghz_tx()) { + // furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow); + if(!subghz_devices_set_tx(radio_device)) { + furi_hal_gpio_init( + subghz_devices_get_data_gpio(radio_device), + GpioModeInput, + GpioPullNo, + GpioSpeedLow); subghz_test_carrier->callback( SubGhzTestCarrierEventOnlyRx, subghz_test_carrier->context); } @@ -139,26 +154,37 @@ bool subghz_test_carrier_input(InputEvent* event, void* context) { void subghz_test_carrier_enter(void* context) { furi_assert(context); SubGhzTestCarrier* subghz_test_carrier = context; + furi_assert(subghz_test_carrier->radio_device); + const SubGhzDevice* radio_device = subghz_test_carrier->radio_device; - furi_hal_subghz_reset(); - furi_hal_subghz_load_custom_preset(subghz_device_cc1101_preset_ook_650khz_async_regs); + // furi_hal_subghz_reset(); + // furi_hal_subghz_load_custom_preset(subghz_device_cc1101_preset_ook_650khz_async_regs); - furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow); + // furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow); + + subghz_devices_reset(radio_device); + subghz_devices_load_preset(radio_device, FuriHalSubGhzPresetOok650Async, NULL); + + furi_hal_gpio_init( + subghz_devices_get_data_gpio(radio_device), GpioModeInput, GpioPullNo, GpioSpeedLow); with_view_model( subghz_test_carrier->view, SubGhzTestCarrierModel * model, { model->frequency = subghz_frequencies_433_92_testing; // 433 - model->real_frequency = - furi_hal_subghz_set_frequency(subghz_frequencies_testing[model->frequency]); + // model->real_frequency = + // furi_hal_subghz_set_frequency(subghz_frequencies_testing[model->frequency]); + model->real_frequency = subghz_devices_set_frequency( + radio_device, subghz_frequencies_testing[model->frequency]); model->path = FuriHalSubGhzPathIsolate; // isolate model->rssi = 0.0f; model->status = SubGhzTestCarrierModelStatusRx; }, true); - furi_hal_subghz_rx(); + // furi_hal_subghz_rx(); + subghz_devices_set_rx(radio_device); furi_timer_start(subghz_test_carrier->timer, furi_kernel_get_tick_frequency() / 4); } @@ -170,7 +196,8 @@ void subghz_test_carrier_exit(void* context) { furi_timer_stop(subghz_test_carrier->timer); // Reinitialize IC to default state - furi_hal_subghz_sleep(); + // furi_hal_subghz_sleep(); + subghz_devices_sleep(subghz_test_carrier->radio_device); } void subghz_test_carrier_rssi_timer_callback(void* context) { @@ -182,7 +209,8 @@ void subghz_test_carrier_rssi_timer_callback(void* context) { SubGhzTestCarrierModel * model, { if(model->status == SubGhzTestCarrierModelStatusRx) { - model->rssi = furi_hal_subghz_get_rssi(); + // model->rssi = furi_hal_subghz_get_rssi(); + model->rssi = subghz_devices_get_rssi(subghz_test_carrier->radio_device); } }, false); @@ -218,3 +246,10 @@ View* subghz_test_carrier_get_view(SubGhzTestCarrier* subghz_test_carrier) { furi_assert(subghz_test_carrier); return subghz_test_carrier->view; } + +void subghz_test_carrier_set_radio( + SubGhzTestCarrier* subghz_test_carrier, + const SubGhzDevice* radio_device) { + furi_assert(subghz_test_carrier); + subghz_test_carrier->radio_device = radio_device; +} diff --git a/applications/main/subghz/views/subghz_test_carrier.h b/applications/main/subghz/views/subghz_test_carrier.h index 7db3343ed..52d6b6f18 100644 --- a/applications/main/subghz/views/subghz_test_carrier.h +++ b/applications/main/subghz/views/subghz_test_carrier.h @@ -1,6 +1,7 @@ #pragma once #include +#include typedef enum { SubGhzTestCarrierEventOnlyRx, @@ -20,3 +21,7 @@ SubGhzTestCarrier* subghz_test_carrier_alloc(); void subghz_test_carrier_free(SubGhzTestCarrier* subghz_test_carrier); View* subghz_test_carrier_get_view(SubGhzTestCarrier* subghz_test_carrier); + +void subghz_test_carrier_set_radio( + SubGhzTestCarrier* subghz_test_carrier, + const SubGhzDevice* radio_device); From 55149f6d4ca43c4f44b0b98f81217a21b743aa88 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 6 Jul 2023 16:58:53 +0300 Subject: [PATCH 060/364] Move --- applications/external/multi_fuzzer/application.fam | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/external/multi_fuzzer/application.fam b/applications/external/multi_fuzzer/application.fam index 1fa33943f..b5205803d 100644 --- a/applications/external/multi_fuzzer/application.fam +++ b/applications/external/multi_fuzzer/application.fam @@ -37,7 +37,7 @@ App( ], stack_size=2 * 1024, fap_icon="icons/rfid_10px.png", - fap_category="RFID 125", + fap_category="RFID", fap_private_libs=[ Lib( name="worker", From f81f4edad36f3af488df97b6320102a580906215 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 6 Jul 2023 17:42:18 +0300 Subject: [PATCH 061/364] Fix loader hangup on exit if api mismatch happened --- applications/services/loader/loader.c | 5 ++++- applications/services/loader/loader.h | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/applications/services/loader/loader.c b/applications/services/loader/loader.c index 833954bf7..94b699748 100644 --- a/applications/services/loader/loader.c +++ b/applications/services/loader/loader.c @@ -273,7 +273,7 @@ static LoaderStatus loader_start_external_app( DialogMessage* message = dialog_message_alloc(); dialog_message_set_header( message, "API Mismatch", 64, 0, AlignCenter, AlignTop); - dialog_message_set_buttons(message, "Cancel", NULL, "Continue"); + dialog_message_set_buttons(message, NULL, NULL, "Continue"); dialog_message_set_text( message, "This app might not\nwork correctly\nContinue anyways?", @@ -284,6 +284,9 @@ static LoaderStatus loader_start_external_app( if(dialog_message_show(dialogs, message) == DialogMessageButtonRight) { status = loader_make_status_error( LoaderStatusErrorApiMismatch, error_message, "API Mismatch"); + } else { + status = loader_make_status_error( + LoaderStatusErrorApiMismatchExit, error_message, "API Mismatch"); } dialog_message_free(message); furi_record_close(RECORD_DIALOGS); diff --git a/applications/services/loader/loader.h b/applications/services/loader/loader.h index 550d3d508..62c198b37 100644 --- a/applications/services/loader/loader.h +++ b/applications/services/loader/loader.h @@ -16,6 +16,7 @@ typedef enum { LoaderStatusErrorUnknownApp, LoaderStatusErrorInternal, LoaderStatusErrorApiMismatch, + LoaderStatusErrorApiMismatchExit, } LoaderStatus; typedef enum { From cef59887ed367d4d1d22274de42996e385eedaea Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Thu, 6 Jul 2023 19:15:03 +0400 Subject: [PATCH 062/364] [FL-3401, FL-3402] SubGhz: add "SubGhz test" external application and the ability to work "SubGhz" as an external application (#2851) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [FL-3401] SubGhz: add "SubGhz test" external application * SubGhz: delete test test functionality from SubGhz app * [FL-3402] SubGhz: move func protocol creation API Co-authored-by: ã‚ã --- .../debug/subghz_test/application.fam | 14 + .../subghz_test/helpers/subghz_test_event.h | 7 + .../helpers/subghz_test_frequency.c} | 2 +- .../helpers/subghz_test_frequency.h} | 2 +- .../subghz_test/helpers/subghz_test_types.h | 18 ++ .../images/DolphinCommon_56x48.png | Bin 0 -> 1416 bytes .../debug/subghz_test/protocol/math.c | 244 ++++++++++++++++++ .../debug/subghz_test/protocol/math.h | 222 ++++++++++++++++ .../protocol}/princeton_for_testing.c | 2 +- .../protocol}/princeton_for_testing.h | 4 +- .../subghz_test/scenes/subghz_test_scene.c | 30 +++ .../subghz_test/scenes/subghz_test_scene.h | 29 +++ .../scenes/subghz_test_scene_about.c | 66 +++++ .../scenes/subghz_test_scene_carrier.c | 29 +++ .../scenes/subghz_test_scene_config.h | 6 + .../scenes/subghz_test_scene_packet.c | 29 +++ .../scenes/subghz_test_scene_show_only_rx.c | 49 ++++ .../scenes/subghz_test_scene_start.c | 77 ++++++ .../scenes/subghz_test_scene_static.c | 29 +++ .../debug/subghz_test/subghz_test_10px.png | Bin 0 -> 181 bytes .../debug/subghz_test/subghz_test_app.c | 139 ++++++++++ .../debug/subghz_test/subghz_test_app_i.c | 5 + .../debug/subghz_test/subghz_test_app_i.h | 32 +++ .../subghz_test}/views/subghz_test_carrier.c | 4 +- .../subghz_test}/views/subghz_test_carrier.h | 0 .../subghz_test}/views/subghz_test_packet.c | 6 +- .../subghz_test}/views/subghz_test_packet.h | 0 .../subghz_test}/views/subghz_test_static.c | 6 +- .../subghz_test}/views/subghz_test_static.h | 0 .../main/subghz/helpers/subghz_types.h | 3 - .../main/subghz/scenes/subghz_scene_config.h | 4 - .../main/subghz/scenes/subghz_scene_start.c | 10 - .../main/subghz/scenes/subghz_scene_test.c | 61 ----- .../subghz/scenes/subghz_scene_test_carrier.c | 30 --- .../subghz/scenes/subghz_scene_test_packet.c | 30 --- .../subghz/scenes/subghz_scene_test_static.c | 30 --- applications/main/subghz/subghz.c | 33 --- applications/main/subghz/subghz_i.h | 7 - firmware/targets/f7/api_symbols.csv | 8 +- lib/subghz/subghz_protocol_registry.h | 25 ++ 40 files changed, 1070 insertions(+), 222 deletions(-) create mode 100644 applications/debug/subghz_test/application.fam create mode 100644 applications/debug/subghz_test/helpers/subghz_test_event.h rename applications/{main/subghz/helpers/subghz_testing.c => debug/subghz_test/helpers/subghz_test_frequency.c} (95%) rename applications/{main/subghz/helpers/subghz_testing.h => debug/subghz_test/helpers/subghz_test_frequency.h} (84%) create mode 100644 applications/debug/subghz_test/helpers/subghz_test_types.h create mode 100644 applications/debug/subghz_test/images/DolphinCommon_56x48.png create mode 100644 applications/debug/subghz_test/protocol/math.c create mode 100644 applications/debug/subghz_test/protocol/math.h rename {lib/subghz/protocols => applications/debug/subghz_test/protocol}/princeton_for_testing.c (99%) rename {lib/subghz/protocols => applications/debug/subghz_test/protocol}/princeton_for_testing.h (97%) create mode 100644 applications/debug/subghz_test/scenes/subghz_test_scene.c create mode 100644 applications/debug/subghz_test/scenes/subghz_test_scene.h create mode 100644 applications/debug/subghz_test/scenes/subghz_test_scene_about.c create mode 100644 applications/debug/subghz_test/scenes/subghz_test_scene_carrier.c create mode 100644 applications/debug/subghz_test/scenes/subghz_test_scene_config.h create mode 100644 applications/debug/subghz_test/scenes/subghz_test_scene_packet.c create mode 100644 applications/debug/subghz_test/scenes/subghz_test_scene_show_only_rx.c create mode 100644 applications/debug/subghz_test/scenes/subghz_test_scene_start.c create mode 100644 applications/debug/subghz_test/scenes/subghz_test_scene_static.c create mode 100644 applications/debug/subghz_test/subghz_test_10px.png create mode 100644 applications/debug/subghz_test/subghz_test_app.c create mode 100644 applications/debug/subghz_test/subghz_test_app_i.c create mode 100644 applications/debug/subghz_test/subghz_test_app_i.h rename applications/{main/subghz => debug/subghz_test}/views/subghz_test_carrier.c (98%) rename applications/{main/subghz => debug/subghz_test}/views/subghz_test_carrier.h (100%) rename applications/{main/subghz => debug/subghz_test}/views/subghz_test_packet.c (98%) rename applications/{main/subghz => debug/subghz_test}/views/subghz_test_packet.h (100%) rename applications/{main/subghz => debug/subghz_test}/views/subghz_test_static.c (98%) rename applications/{main/subghz => debug/subghz_test}/views/subghz_test_static.h (100%) delete mode 100644 applications/main/subghz/scenes/subghz_scene_test.c delete mode 100644 applications/main/subghz/scenes/subghz_scene_test_carrier.c delete mode 100644 applications/main/subghz/scenes/subghz_scene_test_packet.c delete mode 100644 applications/main/subghz/scenes/subghz_scene_test_static.c diff --git a/applications/debug/subghz_test/application.fam b/applications/debug/subghz_test/application.fam new file mode 100644 index 000000000..1b3e19d73 --- /dev/null +++ b/applications/debug/subghz_test/application.fam @@ -0,0 +1,14 @@ +App( + appid="subghz_test", + name="Sub-Ghz test", + apptype=FlipperAppType.DEBUG, + targets=["f7"], + entry_point="subghz_test_app", + requires=["gui"], + stack_size=4 * 1024, + order=50, + fap_icon="subghz_test_10px.png", + fap_category="Debug", + fap_icon_assets="images", + fap_version="0.1", +) diff --git a/applications/debug/subghz_test/helpers/subghz_test_event.h b/applications/debug/subghz_test/helpers/subghz_test_event.h new file mode 100644 index 000000000..a0a851976 --- /dev/null +++ b/applications/debug/subghz_test/helpers/subghz_test_event.h @@ -0,0 +1,7 @@ +#pragma once + +typedef enum { + //SubGhzTestCustomEvent + SubGhzTestCustomEventStartId = 100, + SubGhzTestCustomEventSceneShowOnlyRX, +} SubGhzTestCustomEvent; diff --git a/applications/main/subghz/helpers/subghz_testing.c b/applications/debug/subghz_test/helpers/subghz_test_frequency.c similarity index 95% rename from applications/main/subghz/helpers/subghz_testing.c rename to applications/debug/subghz_test/helpers/subghz_test_frequency.c index 8afa868e0..ed1ba704e 100644 --- a/applications/main/subghz/helpers/subghz_testing.c +++ b/applications/debug/subghz_test/helpers/subghz_test_frequency.c @@ -1,4 +1,4 @@ -#include "subghz_testing.h" +#include "subghz_test_frequency.h" const uint32_t subghz_frequencies_testing[] = { /* 300 - 348 */ diff --git a/applications/main/subghz/helpers/subghz_testing.h b/applications/debug/subghz_test/helpers/subghz_test_frequency.h similarity index 84% rename from applications/main/subghz/helpers/subghz_testing.h rename to applications/debug/subghz_test/helpers/subghz_test_frequency.h index 29ce578a0..7dd1423f9 100644 --- a/applications/main/subghz/helpers/subghz_testing.h +++ b/applications/debug/subghz_test/helpers/subghz_test_frequency.h @@ -1,5 +1,5 @@ #pragma once -#include "../subghz_i.h" +#include "../subghz_test_app_i.h" extern const uint32_t subghz_frequencies_testing[]; extern const uint32_t subghz_frequencies_count_testing; diff --git a/applications/debug/subghz_test/helpers/subghz_test_types.h b/applications/debug/subghz_test/helpers/subghz_test_types.h new file mode 100644 index 000000000..03be6459e --- /dev/null +++ b/applications/debug/subghz_test/helpers/subghz_test_types.h @@ -0,0 +1,18 @@ +#pragma once + +#include +#include + +#define SUBGHZ_TEST_VERSION_APP "0.1" +#define SUBGHZ_TEST_DEVELOPED "SkorP" +#define SUBGHZ_TEST_GITHUB "https://github.com/flipperdevices/flipperzero-firmware" + +typedef enum { + SubGhzTestViewVariableItemList, + SubGhzTestViewSubmenu, + SubGhzTestViewStatic, + SubGhzTestViewCarrier, + SubGhzTestViewPacket, + SubGhzTestViewWidget, + SubGhzTestViewPopup, +} SubGhzTestView; diff --git a/applications/debug/subghz_test/images/DolphinCommon_56x48.png b/applications/debug/subghz_test/images/DolphinCommon_56x48.png new file mode 100644 index 0000000000000000000000000000000000000000..089aaed83507431993a76ca25d32fdd9664c1c84 GIT binary patch literal 1416 zcmaJ>eNYr-7(dh;KXS5&nWVIBjS_NizYg|x=Pr^vz*7zxJO|P-dw2IeZq?gec9-rD zoPZchQ_6}yP{Slc4I!!28K==nodOJ_nsCY-(wOq2uZbLx!rlYU{KIi)_Wj!D_j`WN z^FGgREXdEDF)ewT&1Re7Tj(uBvlG44lnH3;I%IzsO|z`*Vr!`uv?9QOwgs{#Ld+Ki zC9n_zxxBOkx@@+IwMwAaD)#3Ik`}gun2kLe))Crfb7e+#AgzHGCc+X$b>qJuIf`S7 z?8b}I{ghw#z>uiaLknQh@LJUrqHcVYS3v97F^OZN zCe|7^J|?QzUx0Zu17e(=CM1fYFpjtLk|a4~$g}e?hGH0!VoBOT&<=s(1ct%J9~?O} z$)jW_dkX9yTX~%W*i_IM%0{ z7EmP^_pKn`<5>E(SixgJU};7`)7Hidp&+DLnizsebUk}_-GfgbN^il9b`v)f+ z{o5Zry)d<7`fHQ^uw_;+x>mcPw0&8iW69x{k92O{Q}`yFdH=5d$pbf49w1&NS)G+vhr6y}5TMsofQirRDUmKilk5=(KGouJ{H9hW=$X zgi;)vI!jl!_4H3jD(?Jz=8By|i47I&tKA1y9{nfp;_|FxKBDNWp{hN9hJ1nU?z%J6 z?>UxyzWvO}Pgc~rCZ#5%Eq+_hNS~bBdiGlT&f%%e`hHjSySR2=JuK2^+%;$R3#Wz~ z=e_mfqW23bPa0fhe)HdE5+GelU&!jS3ckUZOQ)CC5?mo zo=tzG_4|RuvPUO|mhCwA>y)1c%SWC%a4?a-x|J*?ch~+n=R7o@>p6J2dE=$stKZmK z-xoTRwET2^Wu)&1U7!Ebw!!D?x`xwQX3pMnrRwCT?`4GHt4&?|cIiI{_^XYp-np>6 xE^lPSXzOYCC4X`6tl@OB1M5_S7jml-Y~(TPp{aTIejNKZ`m*!Atyxdk{0EAy49frj literal 0 HcmV?d00001 diff --git a/applications/debug/subghz_test/protocol/math.c b/applications/debug/subghz_test/protocol/math.c new file mode 100644 index 000000000..24202ad1c --- /dev/null +++ b/applications/debug/subghz_test/protocol/math.c @@ -0,0 +1,244 @@ +#include "math.h" + +uint64_t subghz_protocol_blocks_reverse_key(uint64_t key, uint8_t bit_count) { + uint64_t reverse_key = 0; + for(uint8_t i = 0; i < bit_count; i++) { + reverse_key = reverse_key << 1 | bit_read(key, i); + } + return reverse_key; +} + +uint8_t subghz_protocol_blocks_get_parity(uint64_t key, uint8_t bit_count) { + uint8_t parity = 0; + for(uint8_t i = 0; i < bit_count; i++) { + parity += bit_read(key, i); + } + return parity & 0x01; +} + +uint8_t subghz_protocol_blocks_crc4( + uint8_t const message[], + size_t size, + uint8_t polynomial, + uint8_t init) { + uint8_t remainder = init << 4; // LSBs are unused + uint8_t poly = polynomial << 4; + uint8_t bit; + + while(size--) { + remainder ^= *message++; + for(bit = 0; bit < 8; bit++) { + if(remainder & 0x80) { + remainder = (remainder << 1) ^ poly; + } else { + remainder = (remainder << 1); + } + } + } + return remainder >> 4 & 0x0f; // discard the LSBs +} + +uint8_t subghz_protocol_blocks_crc7( + uint8_t const message[], + size_t size, + uint8_t polynomial, + uint8_t init) { + uint8_t remainder = init << 1; // LSB is unused + uint8_t poly = polynomial << 1; + + for(size_t byte = 0; byte < size; ++byte) { + remainder ^= message[byte]; + for(uint8_t bit = 0; bit < 8; ++bit) { + if(remainder & 0x80) { + remainder = (remainder << 1) ^ poly; + } else { + remainder = (remainder << 1); + } + } + } + return remainder >> 1 & 0x7f; // discard the LSB +} + +uint8_t subghz_protocol_blocks_crc8( + uint8_t const message[], + size_t size, + uint8_t polynomial, + uint8_t init) { + uint8_t remainder = init; + + for(size_t byte = 0; byte < size; ++byte) { + remainder ^= message[byte]; + for(uint8_t bit = 0; bit < 8; ++bit) { + if(remainder & 0x80) { + remainder = (remainder << 1) ^ polynomial; + } else { + remainder = (remainder << 1); + } + } + } + return remainder; +} + +uint8_t subghz_protocol_blocks_crc8le( + uint8_t const message[], + size_t size, + uint8_t polynomial, + uint8_t init) { + uint8_t remainder = subghz_protocol_blocks_reverse_key(init, 8); + polynomial = subghz_protocol_blocks_reverse_key(polynomial, 8); + + for(size_t byte = 0; byte < size; ++byte) { + remainder ^= message[byte]; + for(uint8_t bit = 0; bit < 8; ++bit) { + if(remainder & 1) { + remainder = (remainder >> 1) ^ polynomial; + } else { + remainder = (remainder >> 1); + } + } + } + return remainder; +} + +uint16_t subghz_protocol_blocks_crc16lsb( + uint8_t const message[], + size_t size, + uint16_t polynomial, + uint16_t init) { + uint16_t remainder = init; + + for(size_t byte = 0; byte < size; ++byte) { + remainder ^= message[byte]; + for(uint8_t bit = 0; bit < 8; ++bit) { + if(remainder & 1) { + remainder = (remainder >> 1) ^ polynomial; + } else { + remainder = (remainder >> 1); + } + } + } + return remainder; +} + +uint16_t subghz_protocol_blocks_crc16( + uint8_t const message[], + size_t size, + uint16_t polynomial, + uint16_t init) { + uint16_t remainder = init; + + for(size_t byte = 0; byte < size; ++byte) { + remainder ^= message[byte] << 8; + for(uint8_t bit = 0; bit < 8; ++bit) { + if(remainder & 0x8000) { + remainder = (remainder << 1) ^ polynomial; + } else { + remainder = (remainder << 1); + } + } + } + return remainder; +} + +uint8_t subghz_protocol_blocks_lfsr_digest8( + uint8_t const message[], + size_t size, + uint8_t gen, + uint8_t key) { + uint8_t sum = 0; + for(size_t byte = 0; byte < size; ++byte) { + uint8_t data = message[byte]; + for(int i = 7; i >= 0; --i) { + // XOR key into sum if data bit is set + if((data >> i) & 1) sum ^= key; + + // roll the key right (actually the LSB is dropped here) + // and apply the gen (needs to include the dropped LSB as MSB) + if(key & 1) + key = (key >> 1) ^ gen; + else + key = (key >> 1); + } + } + return sum; +} + +uint8_t subghz_protocol_blocks_lfsr_digest8_reflect( + uint8_t const message[], + size_t size, + uint8_t gen, + uint8_t key) { + uint8_t sum = 0; + // Process message from last byte to first byte (reflected) + for(int byte = size - 1; byte >= 0; --byte) { + uint8_t data = message[byte]; + // Process individual bits of each byte (reflected) + for(uint8_t i = 0; i < 8; ++i) { + // XOR key into sum if data bit is set + if((data >> i) & 1) { + sum ^= key; + } + + // roll the key left (actually the LSB is dropped here) + // and apply the gen (needs to include the dropped lsb as MSB) + if(key & 0x80) + key = (key << 1) ^ gen; + else + key = (key << 1); + } + } + return sum; +} + +uint16_t subghz_protocol_blocks_lfsr_digest16( + uint8_t const message[], + size_t size, + uint16_t gen, + uint16_t key) { + uint16_t sum = 0; + for(size_t byte = 0; byte < size; ++byte) { + uint8_t data = message[byte]; + for(int8_t i = 7; i >= 0; --i) { + // if data bit is set then xor with key + if((data >> i) & 1) sum ^= key; + + // roll the key right (actually the LSB is dropped here) + // and apply the gen (needs to include the dropped LSB as MSB) + if(key & 1) + key = (key >> 1) ^ gen; + else + key = (key >> 1); + } + } + return sum; +} + +uint8_t subghz_protocol_blocks_add_bytes(uint8_t const message[], size_t size) { + uint32_t result = 0; + for(size_t i = 0; i < size; ++i) { + result += message[i]; + } + return (uint8_t)result; +} + +uint8_t subghz_protocol_blocks_parity8(uint8_t byte) { + byte ^= byte >> 4; + byte &= 0xf; + return (0x6996 >> byte) & 1; +} + +uint8_t subghz_protocol_blocks_parity_bytes(uint8_t const message[], size_t size) { + uint8_t result = 0; + for(size_t i = 0; i < size; ++i) { + result ^= subghz_protocol_blocks_parity8(message[i]); + } + return result; +} + +uint8_t subghz_protocol_blocks_xor_bytes(uint8_t const message[], size_t size) { + uint8_t result = 0; + for(size_t i = 0; i < size; ++i) { + result ^= message[i]; + } + return result; +} \ No newline at end of file diff --git a/applications/debug/subghz_test/protocol/math.h b/applications/debug/subghz_test/protocol/math.h new file mode 100644 index 000000000..dcea3da5f --- /dev/null +++ b/applications/debug/subghz_test/protocol/math.h @@ -0,0 +1,222 @@ +#pragma once + +#include +#include +#include + +#define bit_read(value, bit) (((value) >> (bit)) & 0x01) +#define bit_set(value, bit) \ + ({ \ + __typeof__(value) _one = (1); \ + (value) |= (_one << (bit)); \ + }) +#define bit_clear(value, bit) \ + ({ \ + __typeof__(value) _one = (1); \ + (value) &= ~(_one << (bit)); \ + }) +#define bit_write(value, bit, bitvalue) (bitvalue ? bit_set(value, bit) : bit_clear(value, bit)) +#define DURATION_DIFF(x, y) (((x) < (y)) ? ((y) - (x)) : ((x) - (y))) + +#ifdef __cplusplus +extern "C" { +#endif + +/** Flip the data bitwise + * + * @param key In data + * @param bit_count number of data bits + * + * @return Reverse data + */ +uint64_t subghz_protocol_blocks_reverse_key(uint64_t key, uint8_t bit_count); + +/** Get parity the data bitwise + * + * @param key In data + * @param bit_count number of data bits + * + * @return parity + */ +uint8_t subghz_protocol_blocks_get_parity(uint64_t key, uint8_t bit_count); + +/** CRC-4 + * + * @param message array of bytes to check + * @param size number of bytes in message + * @param polynomial CRC polynomial + * @param init starting crc value + * + * @return CRC value + */ +uint8_t subghz_protocol_blocks_crc4( + uint8_t const message[], + size_t size, + uint8_t polynomial, + uint8_t init); + +/** CRC-7 + * + * @param message array of bytes to check + * @param size number of bytes in message + * @param polynomial CRC polynomial + * @param init starting crc value + * + * @return CRC value + */ +uint8_t subghz_protocol_blocks_crc7( + uint8_t const message[], + size_t size, + uint8_t polynomial, + uint8_t init); + +/** Generic Cyclic Redundancy Check CRC-8. Example polynomial: 0x31 = x8 + x5 + + * x4 + 1 (x8 is implicit) Example polynomial: 0x80 = x8 + x7 (a normal + * bit-by-bit parity XOR) + * + * @param message array of bytes to check + * @param size number of bytes in message + * @param polynomial byte is from x^7 to x^0 (x^8 is implicitly one) + * @param init starting crc value + * + * @return CRC value + */ +uint8_t subghz_protocol_blocks_crc8( + uint8_t const message[], + size_t size, + uint8_t polynomial, + uint8_t init); + +/** "Little-endian" Cyclic Redundancy Check CRC-8 LE Input and output are + * reflected, i.e. least significant bit is shifted in first + * + * @param message array of bytes to check + * @param size number of bytes in message + * @param polynomial CRC polynomial + * @param init starting crc value + * + * @return CRC value + */ +uint8_t subghz_protocol_blocks_crc8le( + uint8_t const message[], + size_t size, + uint8_t polynomial, + uint8_t init); + +/** CRC-16 LSB. Input and output are reflected, i.e. least significant bit is + * shifted in first. Note that poly and init already need to be reflected + * + * @param message array of bytes to check + * @param size number of bytes in message + * @param polynomial CRC polynomial + * @param init starting crc value + * + * @return CRC value + */ +uint16_t subghz_protocol_blocks_crc16lsb( + uint8_t const message[], + size_t size, + uint16_t polynomial, + uint16_t init); + +/** CRC-16 + * + * @param message array of bytes to check + * @param size number of bytes in message + * @param polynomial CRC polynomial + * @param init starting crc value + * + * @return CRC value + */ +uint16_t subghz_protocol_blocks_crc16( + uint8_t const message[], + size_t size, + uint16_t polynomial, + uint16_t init); + +/** Digest-8 by "LFSR-based Toeplitz hash" + * + * @param message bytes of message data + * @param size number of bytes to digest + * @param gen key stream generator, needs to includes the MSB if the + * LFSR is rolling + * @param key initial key + * + * @return digest value + */ +uint8_t subghz_protocol_blocks_lfsr_digest8( + uint8_t const message[], + size_t size, + uint8_t gen, + uint8_t key); + +/** Digest-8 by "LFSR-based Toeplitz hash", byte reflect, bit reflect + * + * @param message bytes of message data + * @param size number of bytes to digest + * @param gen key stream generator, needs to includes the MSB if the + * LFSR is rolling + * @param key initial key + * + * @return digest value + */ +uint8_t subghz_protocol_blocks_lfsr_digest8_reflect( + uint8_t const message[], + size_t size, + uint8_t gen, + uint8_t key); + +/** Digest-16 by "LFSR-based Toeplitz hash" + * + * @param message bytes of message data + * @param size number of bytes to digest + * @param gen key stream generator, needs to includes the MSB if the + * LFSR is rolling + * @param key initial key + * + * @return digest value + */ +uint16_t subghz_protocol_blocks_lfsr_digest16( + uint8_t const message[], + size_t size, + uint16_t gen, + uint16_t key); + +/** Compute Addition of a number of bytes + * + * @param message bytes of message data + * @param size number of bytes to sum + * + * @return summation value + */ +uint8_t subghz_protocol_blocks_add_bytes(uint8_t const message[], size_t size); + +/** Compute bit parity of a single byte (8 bits) + * + * @param byte single byte to check + * + * @return 1 odd parity, 0 even parity + */ +uint8_t subghz_protocol_blocks_parity8(uint8_t byte); + +/** Compute bit parity of a number of bytes + * + * @param message bytes of message data + * @param size number of bytes to sum + * + * @return 1 odd parity, 0 even parity + */ +uint8_t subghz_protocol_blocks_parity_bytes(uint8_t const message[], size_t size); + +/** Compute XOR (byte-wide parity) of a number of bytes + * + * @param message bytes of message data + * @param size number of bytes to sum + * + * @return summation value, per bit-position 1 odd parity, 0 even parity + */ +uint8_t subghz_protocol_blocks_xor_bytes(uint8_t const message[], size_t size); + +#ifdef __cplusplus +} +#endif diff --git a/lib/subghz/protocols/princeton_for_testing.c b/applications/debug/subghz_test/protocol/princeton_for_testing.c similarity index 99% rename from lib/subghz/protocols/princeton_for_testing.c rename to applications/debug/subghz_test/protocol/princeton_for_testing.c index 478d14cdf..334a8241b 100644 --- a/lib/subghz/protocols/princeton_for_testing.c +++ b/applications/debug/subghz_test/protocol/princeton_for_testing.c @@ -1,7 +1,7 @@ #include "princeton_for_testing.h" #include -#include "../blocks/math.h" +#include "math.h" /* * Help diff --git a/lib/subghz/protocols/princeton_for_testing.h b/applications/debug/subghz_test/protocol/princeton_for_testing.h similarity index 97% rename from lib/subghz/protocols/princeton_for_testing.h rename to applications/debug/subghz_test/protocol/princeton_for_testing.h index 07a37ec5f..7b4201d38 100644 --- a/lib/subghz/protocols/princeton_for_testing.h +++ b/applications/debug/subghz_test/protocol/princeton_for_testing.h @@ -1,6 +1,8 @@ #pragma once -#include "base.h" +//#include "base.h" +#include +#include /** SubGhzDecoderPrinceton anonymous type */ typedef struct SubGhzDecoderPrinceton SubGhzDecoderPrinceton; diff --git a/applications/debug/subghz_test/scenes/subghz_test_scene.c b/applications/debug/subghz_test/scenes/subghz_test_scene.c new file mode 100644 index 000000000..ff439ef0f --- /dev/null +++ b/applications/debug/subghz_test/scenes/subghz_test_scene.c @@ -0,0 +1,30 @@ +#include "../subghz_test_app_i.h" + +// Generate scene on_enter handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, +void (*const subghz_test_scene_on_enter_handlers[])(void*) = { +#include "subghz_test_scene_config.h" +}; +#undef ADD_SCENE + +// Generate scene on_event handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event, +bool (*const subghz_test_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = { +#include "subghz_test_scene_config.h" +}; +#undef ADD_SCENE + +// Generate scene on_exit handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit, +void (*const subghz_test_scene_on_exit_handlers[])(void* context) = { +#include "subghz_test_scene_config.h" +}; +#undef ADD_SCENE + +// Initialize scene handlers configuration structure +const SceneManagerHandlers subghz_test_scene_handlers = { + .on_enter_handlers = subghz_test_scene_on_enter_handlers, + .on_event_handlers = subghz_test_scene_on_event_handlers, + .on_exit_handlers = subghz_test_scene_on_exit_handlers, + .scene_num = SubGhzTestSceneNum, +}; diff --git a/applications/debug/subghz_test/scenes/subghz_test_scene.h b/applications/debug/subghz_test/scenes/subghz_test_scene.h new file mode 100644 index 000000000..0e6e06481 --- /dev/null +++ b/applications/debug/subghz_test/scenes/subghz_test_scene.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +// Generate scene id and total number +#define ADD_SCENE(prefix, name, id) SubGhzTestScene##id, +typedef enum { +#include "subghz_test_scene_config.h" + SubGhzTestSceneNum, +} SubGhzTestScene; +#undef ADD_SCENE + +extern const SceneManagerHandlers subghz_test_scene_handlers; + +// Generate scene on_enter handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); +#include "subghz_test_scene_config.h" +#undef ADD_SCENE + +// Generate scene on_event handlers declaration +#define ADD_SCENE(prefix, name, id) \ + bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event); +#include "subghz_test_scene_config.h" +#undef ADD_SCENE + +// Generate scene on_exit handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context); +#include "subghz_test_scene_config.h" +#undef ADD_SCENE diff --git a/applications/debug/subghz_test/scenes/subghz_test_scene_about.c b/applications/debug/subghz_test/scenes/subghz_test_scene_about.c new file mode 100644 index 000000000..64263d738 --- /dev/null +++ b/applications/debug/subghz_test/scenes/subghz_test_scene_about.c @@ -0,0 +1,66 @@ +#include "../subghz_test_app_i.h" + +void subghz_test_scene_about_widget_callback(GuiButtonType result, InputType type, void* context) { + SubGhzTestApp* app = context; + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(app->view_dispatcher, result); + } +} + +void subghz_test_scene_about_on_enter(void* context) { + SubGhzTestApp* app = context; + + FuriString* temp_str; + temp_str = furi_string_alloc(); + furi_string_printf(temp_str, "\e#%s\n", "Information"); + + furi_string_cat_printf(temp_str, "Version: %s\n", SUBGHZ_TEST_VERSION_APP); + furi_string_cat_printf(temp_str, "Developed by: %s\n", SUBGHZ_TEST_DEVELOPED); + furi_string_cat_printf(temp_str, "Github: %s\n\n", SUBGHZ_TEST_GITHUB); + + furi_string_cat_printf(temp_str, "\e#%s\n", "Description"); + furi_string_cat_printf( + temp_str, + "This application is designed\nto test the functionality of the\nbuilt-in CC1101 module.\n\n"); + + widget_add_text_box_element( + app->widget, + 0, + 0, + 128, + 14, + AlignCenter, + AlignBottom, + "\e#\e! \e!\n", + false); + widget_add_text_box_element( + app->widget, + 0, + 2, + 128, + 14, + AlignCenter, + AlignBottom, + "\e#\e! Sub-Ghz Test \e!\n", + false); + widget_add_text_scroll_element(app->widget, 0, 16, 128, 50, furi_string_get_cstr(temp_str)); + furi_string_free(temp_str); + + view_dispatcher_switch_to_view(app->view_dispatcher, SubGhzTestViewWidget); +} + +bool subghz_test_scene_about_on_event(void* context, SceneManagerEvent event) { + SubGhzTestApp* app = context; + bool consumed = false; + UNUSED(app); + UNUSED(event); + + return consumed; +} + +void subghz_test_scene_about_on_exit(void* context) { + SubGhzTestApp* app = context; + + // Clear views + widget_reset(app->widget); +} diff --git a/applications/debug/subghz_test/scenes/subghz_test_scene_carrier.c b/applications/debug/subghz_test/scenes/subghz_test_scene_carrier.c new file mode 100644 index 000000000..41ff5c8c6 --- /dev/null +++ b/applications/debug/subghz_test/scenes/subghz_test_scene_carrier.c @@ -0,0 +1,29 @@ +#include "../subghz_test_app_i.h" + +void subghz_test_scene_carrier_callback(SubGhzTestCarrierEvent event, void* context) { + furi_assert(context); + SubGhzTestApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, event); +} + +void subghz_test_scene_carrier_on_enter(void* context) { + SubGhzTestApp* app = context; + subghz_test_carrier_set_callback( + app->subghz_test_carrier, subghz_test_scene_carrier_callback, app); + view_dispatcher_switch_to_view(app->view_dispatcher, SubGhzTestViewCarrier); +} + +bool subghz_test_scene_carrier_on_event(void* context, SceneManagerEvent event) { + SubGhzTestApp* app = context; + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubGhzTestCarrierEventOnlyRx) { + scene_manager_next_scene(app->scene_manager, SubGhzTestSceneShowOnlyRx); + return true; + } + } + return false; +} + +void subghz_test_scene_carrier_on_exit(void* context) { + UNUSED(context); +} diff --git a/applications/debug/subghz_test/scenes/subghz_test_scene_config.h b/applications/debug/subghz_test/scenes/subghz_test_scene_config.h new file mode 100644 index 000000000..80a42c376 --- /dev/null +++ b/applications/debug/subghz_test/scenes/subghz_test_scene_config.h @@ -0,0 +1,6 @@ +ADD_SCENE(subghz_test, start, Start) +ADD_SCENE(subghz_test, about, About) +ADD_SCENE(subghz_test, carrier, Carrier) +ADD_SCENE(subghz_test, packet, Packet) +ADD_SCENE(subghz_test, static, Static) +ADD_SCENE(subghz_test, show_only_rx, ShowOnlyRx) diff --git a/applications/debug/subghz_test/scenes/subghz_test_scene_packet.c b/applications/debug/subghz_test/scenes/subghz_test_scene_packet.c new file mode 100644 index 000000000..b43a4d0cb --- /dev/null +++ b/applications/debug/subghz_test/scenes/subghz_test_scene_packet.c @@ -0,0 +1,29 @@ +#include "../subghz_test_app_i.h" + +void subghz_test_scene_packet_callback(SubGhzTestPacketEvent event, void* context) { + furi_assert(context); + SubGhzTestApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, event); +} + +void subghz_test_scene_packet_on_enter(void* context) { + SubGhzTestApp* app = context; + subghz_test_packet_set_callback( + app->subghz_test_packet, subghz_test_scene_packet_callback, app); + view_dispatcher_switch_to_view(app->view_dispatcher, SubGhzTestViewPacket); +} + +bool subghz_test_scene_packet_on_event(void* context, SceneManagerEvent event) { + SubGhzTestApp* app = context; + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubGhzTestPacketEventOnlyRx) { + scene_manager_next_scene(app->scene_manager, SubGhzTestSceneShowOnlyRx); + return true; + } + } + return false; +} + +void subghz_test_scene_packet_on_exit(void* context) { + UNUSED(context); +} diff --git a/applications/debug/subghz_test/scenes/subghz_test_scene_show_only_rx.c b/applications/debug/subghz_test/scenes/subghz_test_scene_show_only_rx.c new file mode 100644 index 000000000..3d5a54355 --- /dev/null +++ b/applications/debug/subghz_test/scenes/subghz_test_scene_show_only_rx.c @@ -0,0 +1,49 @@ +#include "../subghz_test_app_i.h" +#include + +void subghz_test_scene_show_only_rx_popup_callback(void* context) { + SubGhzTestApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, SubGhzTestCustomEventSceneShowOnlyRX); +} + +void subghz_test_scene_show_only_rx_on_enter(void* context) { + SubGhzTestApp* app = context; + + // Setup view + Popup* popup = app->popup; + + const char* header_text = "Transmission is blocked"; + const char* message_text = "Transmission on\nthis frequency is\nrestricted in\nyour region"; + if(!furi_hal_region_is_provisioned()) { + header_text = "Firmware update needed"; + message_text = "Please update\nfirmware before\nusing this feature\nflipp.dev/upd"; + } + + popup_set_header(popup, header_text, 63, 3, AlignCenter, AlignTop); + popup_set_text(popup, message_text, 0, 17, AlignLeft, AlignTop); + popup_set_icon(popup, 72, 17, &I_DolphinCommon_56x48); + + popup_set_timeout(popup, 1500); + popup_set_context(popup, app); + popup_set_callback(popup, subghz_test_scene_show_only_rx_popup_callback); + popup_enable_timeout(popup); + view_dispatcher_switch_to_view(app->view_dispatcher, SubGhzTestViewPopup); +} + +bool subghz_test_scene_show_only_rx_on_event(void* context, SceneManagerEvent event) { + SubGhzTestApp* app = context; + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubGhzTestCustomEventSceneShowOnlyRX) { + scene_manager_previous_scene(app->scene_manager); + return true; + } + } + return false; +} + +void subghz_test_scene_show_only_rx_on_exit(void* context) { + SubGhzTestApp* app = context; + Popup* popup = app->popup; + + popup_reset(popup); +} diff --git a/applications/debug/subghz_test/scenes/subghz_test_scene_start.c b/applications/debug/subghz_test/scenes/subghz_test_scene_start.c new file mode 100644 index 000000000..cf3b08163 --- /dev/null +++ b/applications/debug/subghz_test/scenes/subghz_test_scene_start.c @@ -0,0 +1,77 @@ +#include "../subghz_test_app_i.h" + +typedef enum { + SubmenuIndexSubGhzTestCarrier, + SubmenuIndexSubGhzTestPacket, + SubmenuIndexSubGhzTestStatic, + SubmenuIndexSubGhzTestAbout, +} SubmenuIndex; + +void subghz_test_scene_start_submenu_callback(void* context, uint32_t index) { + SubGhzTestApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, index); +} + +void subghz_test_scene_start_on_enter(void* context) { + SubGhzTestApp* app = context; + Submenu* submenu = app->submenu; + + submenu_add_item( + submenu, + "Carrier", + SubmenuIndexSubGhzTestCarrier, + subghz_test_scene_start_submenu_callback, + app); + submenu_add_item( + submenu, + "Packet", + SubmenuIndexSubGhzTestPacket, + subghz_test_scene_start_submenu_callback, + app); + submenu_add_item( + submenu, + "Static", + SubmenuIndexSubGhzTestStatic, + subghz_test_scene_start_submenu_callback, + app); + submenu_add_item( + submenu, + "About", + SubmenuIndexSubGhzTestAbout, + subghz_test_scene_start_submenu_callback, + app); + + submenu_set_selected_item( + submenu, scene_manager_get_scene_state(app->scene_manager, SubGhzTestSceneStart)); + + view_dispatcher_switch_to_view(app->view_dispatcher, SubGhzTestViewSubmenu); +} + +bool subghz_test_scene_start_on_event(void* context, SceneManagerEvent event) { + SubGhzTestApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubmenuIndexSubGhzTestAbout) { + scene_manager_next_scene(app->scene_manager, SubGhzTestSceneAbout); + consumed = true; + } else if(event.event == SubmenuIndexSubGhzTestCarrier) { + scene_manager_next_scene(app->scene_manager, SubGhzTestSceneCarrier); + consumed = true; + } else if(event.event == SubmenuIndexSubGhzTestPacket) { + scene_manager_next_scene(app->scene_manager, SubGhzTestScenePacket); + consumed = true; + } else if(event.event == SubmenuIndexSubGhzTestStatic) { + scene_manager_next_scene(app->scene_manager, SubGhzTestSceneStatic); + consumed = true; + } + scene_manager_set_scene_state(app->scene_manager, SubGhzTestSceneStart, event.event); + } + + return consumed; +} + +void subghz_test_scene_start_on_exit(void* context) { + SubGhzTestApp* app = context; + submenu_reset(app->submenu); +} diff --git a/applications/debug/subghz_test/scenes/subghz_test_scene_static.c b/applications/debug/subghz_test/scenes/subghz_test_scene_static.c new file mode 100644 index 000000000..a008d2438 --- /dev/null +++ b/applications/debug/subghz_test/scenes/subghz_test_scene_static.c @@ -0,0 +1,29 @@ +#include "../subghz_test_app_i.h" + +void subghz_test_scene_static_callback(SubGhzTestStaticEvent event, void* context) { + furi_assert(context); + SubGhzTestApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, event); +} + +void subghz_test_scene_static_on_enter(void* context) { + SubGhzTestApp* app = context; + subghz_test_static_set_callback( + app->subghz_test_static, subghz_test_scene_static_callback, app); + view_dispatcher_switch_to_view(app->view_dispatcher, SubGhzTestViewStatic); +} + +bool subghz_test_scene_static_on_event(void* context, SceneManagerEvent event) { + SubGhzTestApp* app = context; + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubGhzTestStaticEventOnlyRx) { + scene_manager_next_scene(app->scene_manager, SubGhzTestSceneShowOnlyRx); + return true; + } + } + return false; +} + +void subghz_test_scene_static_on_exit(void* context) { + UNUSED(context); +} diff --git a/applications/debug/subghz_test/subghz_test_10px.png b/applications/debug/subghz_test/subghz_test_10px.png new file mode 100644 index 0000000000000000000000000000000000000000..10dac0ecaac608aba6d445bc5fef45d8cc1efff4 GIT binary patch literal 181 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V8<6ZZI=>f4F%}28J29*~C-V}>VM%xNb!1@J z*w6hZkrl}2EbxddW? +#include + +static bool subghz_test_app_custom_event_callback(void* context, uint32_t event) { + furi_assert(context); + SubGhzTestApp* app = context; + return scene_manager_handle_custom_event(app->scene_manager, event); +} + +static bool subghz_test_app_back_event_callback(void* context) { + furi_assert(context); + SubGhzTestApp* app = context; + return scene_manager_handle_back_event(app->scene_manager); +} + +static void subghz_test_app_tick_event_callback(void* context) { + furi_assert(context); + SubGhzTestApp* app = context; + scene_manager_handle_tick_event(app->scene_manager); +} + +SubGhzTestApp* subghz_test_app_alloc() { + SubGhzTestApp* app = malloc(sizeof(SubGhzTestApp)); + + // GUI + app->gui = furi_record_open(RECORD_GUI); + + // View Dispatcher + app->view_dispatcher = view_dispatcher_alloc(); + app->scene_manager = scene_manager_alloc(&subghz_test_scene_handlers, app); + view_dispatcher_enable_queue(app->view_dispatcher); + + view_dispatcher_set_event_callback_context(app->view_dispatcher, app); + view_dispatcher_set_custom_event_callback( + app->view_dispatcher, subghz_test_app_custom_event_callback); + view_dispatcher_set_navigation_event_callback( + app->view_dispatcher, subghz_test_app_back_event_callback); + view_dispatcher_set_tick_event_callback( + app->view_dispatcher, subghz_test_app_tick_event_callback, 100); + + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + + // Open Notification record + app->notifications = furi_record_open(RECORD_NOTIFICATION); + + // SubMenu + app->submenu = submenu_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, SubGhzTestViewSubmenu, submenu_get_view(app->submenu)); + + // Widget + app->widget = widget_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, SubGhzTestViewWidget, widget_get_view(app->widget)); + + // Popup + app->popup = popup_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, SubGhzTestViewPopup, popup_get_view(app->popup)); + + // Carrier Test Module + app->subghz_test_carrier = subghz_test_carrier_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + SubGhzTestViewCarrier, + subghz_test_carrier_get_view(app->subghz_test_carrier)); + + // Packet Test + app->subghz_test_packet = subghz_test_packet_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + SubGhzTestViewPacket, + subghz_test_packet_get_view(app->subghz_test_packet)); + + // Static send + app->subghz_test_static = subghz_test_static_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + SubGhzTestViewStatic, + subghz_test_static_get_view(app->subghz_test_static)); + + scene_manager_next_scene(app->scene_manager, SubGhzTestSceneStart); + + return app; +} + +void subghz_test_app_free(SubGhzTestApp* app) { + furi_assert(app); + + // Submenu + view_dispatcher_remove_view(app->view_dispatcher, SubGhzTestViewSubmenu); + submenu_free(app->submenu); + + // Widget + view_dispatcher_remove_view(app->view_dispatcher, SubGhzTestViewWidget); + widget_free(app->widget); + + // Popup + view_dispatcher_remove_view(app->view_dispatcher, SubGhzTestViewPopup); + popup_free(app->popup); + + // Carrier Test + view_dispatcher_remove_view(app->view_dispatcher, SubGhzTestViewCarrier); + subghz_test_carrier_free(app->subghz_test_carrier); + + // Packet Test + view_dispatcher_remove_view(app->view_dispatcher, SubGhzTestViewPacket); + subghz_test_packet_free(app->subghz_test_packet); + + // Static + view_dispatcher_remove_view(app->view_dispatcher, SubGhzTestViewStatic); + subghz_test_static_free(app->subghz_test_static); + + // View dispatcher + view_dispatcher_free(app->view_dispatcher); + scene_manager_free(app->scene_manager); + + // Notifications + furi_record_close(RECORD_NOTIFICATION); + app->notifications = NULL; + + // Close records + furi_record_close(RECORD_GUI); + + free(app); +} + +int32_t subghz_test_app(void* p) { + UNUSED(p); + SubGhzTestApp* subghz_test_app = subghz_test_app_alloc(); + + view_dispatcher_run(subghz_test_app->view_dispatcher); + + subghz_test_app_free(subghz_test_app); + + return 0; +} diff --git a/applications/debug/subghz_test/subghz_test_app_i.c b/applications/debug/subghz_test/subghz_test_app_i.c new file mode 100644 index 000000000..0ec6635a0 --- /dev/null +++ b/applications/debug/subghz_test/subghz_test_app_i.c @@ -0,0 +1,5 @@ +#include "subghz_test_app_i.h" + +#include + +#define TAG "SubGhzTest" diff --git a/applications/debug/subghz_test/subghz_test_app_i.h b/applications/debug/subghz_test/subghz_test_app_i.h new file mode 100644 index 000000000..c96f9c4ee --- /dev/null +++ b/applications/debug/subghz_test/subghz_test_app_i.h @@ -0,0 +1,32 @@ +#pragma once + +#include "helpers/subghz_test_types.h" +#include "helpers/subghz_test_event.h" + +#include "scenes/subghz_test_scene.h" +#include +#include +#include +#include +#include +#include +#include + +#include "views/subghz_test_static.h" +#include "views/subghz_test_carrier.h" +#include "views/subghz_test_packet.h" + +typedef struct SubGhzTestApp SubGhzTestApp; + +struct SubGhzTestApp { + Gui* gui; + ViewDispatcher* view_dispatcher; + SceneManager* scene_manager; + NotificationApp* notifications; + Submenu* submenu; + Widget* widget; + Popup* popup; + SubGhzTestStatic* subghz_test_static; + SubGhzTestCarrier* subghz_test_carrier; + SubGhzTestPacket* subghz_test_packet; +}; diff --git a/applications/main/subghz/views/subghz_test_carrier.c b/applications/debug/subghz_test/views/subghz_test_carrier.c similarity index 98% rename from applications/main/subghz/views/subghz_test_carrier.c rename to applications/debug/subghz_test/views/subghz_test_carrier.c index 254a4127b..53e309b7c 100644 --- a/applications/main/subghz/views/subghz_test_carrier.c +++ b/applications/debug/subghz_test/views/subghz_test_carrier.c @@ -1,6 +1,6 @@ #include "subghz_test_carrier.h" -#include "../subghz_i.h" -#include "../helpers/subghz_testing.h" +#include "../subghz_test_app_i.h" +#include "../helpers/subghz_test_frequency.h" #include #include diff --git a/applications/main/subghz/views/subghz_test_carrier.h b/applications/debug/subghz_test/views/subghz_test_carrier.h similarity index 100% rename from applications/main/subghz/views/subghz_test_carrier.h rename to applications/debug/subghz_test/views/subghz_test_carrier.h diff --git a/applications/main/subghz/views/subghz_test_packet.c b/applications/debug/subghz_test/views/subghz_test_packet.c similarity index 98% rename from applications/main/subghz/views/subghz_test_packet.c rename to applications/debug/subghz_test/views/subghz_test_packet.c index bc2c474b5..bab83ab5b 100644 --- a/applications/main/subghz/views/subghz_test_packet.c +++ b/applications/debug/subghz_test/views/subghz_test_packet.c @@ -1,6 +1,6 @@ #include "subghz_test_packet.h" -#include "../subghz_i.h" -#include "../helpers/subghz_testing.h" +#include "../subghz_test_app_i.h" +#include "../helpers/subghz_test_frequency.h" #include #include @@ -8,7 +8,7 @@ #include #include #include -#include +#include "../protocol/princeton_for_testing.h" #define SUBGHZ_TEST_PACKET_COUNT 500 diff --git a/applications/main/subghz/views/subghz_test_packet.h b/applications/debug/subghz_test/views/subghz_test_packet.h similarity index 100% rename from applications/main/subghz/views/subghz_test_packet.h rename to applications/debug/subghz_test/views/subghz_test_packet.h diff --git a/applications/main/subghz/views/subghz_test_static.c b/applications/debug/subghz_test/views/subghz_test_static.c similarity index 98% rename from applications/main/subghz/views/subghz_test_static.c rename to applications/debug/subghz_test/views/subghz_test_static.c index 197af21fb..6764fd5ca 100644 --- a/applications/main/subghz/views/subghz_test_static.c +++ b/applications/debug/subghz_test/views/subghz_test_static.c @@ -1,6 +1,6 @@ #include "subghz_test_static.h" -#include "../subghz_i.h" -#include "../helpers/subghz_testing.h" +#include "../subghz_test_app_i.h" +#include "../helpers/subghz_test_frequency.h" #include #include @@ -8,7 +8,7 @@ #include #include #include -#include +#include "../protocol/princeton_for_testing.h" #define TAG "SubGhzTestStatic" diff --git a/applications/main/subghz/views/subghz_test_static.h b/applications/debug/subghz_test/views/subghz_test_static.h similarity index 100% rename from applications/main/subghz/views/subghz_test_static.h rename to applications/debug/subghz_test/views/subghz_test_static.h diff --git a/applications/main/subghz/helpers/subghz_types.h b/applications/main/subghz/helpers/subghz_types.h index 8beb7b9ed..e71c22dd5 100644 --- a/applications/main/subghz/helpers/subghz_types.h +++ b/applications/main/subghz/helpers/subghz_types.h @@ -80,9 +80,6 @@ typedef enum { SubGhzViewIdFrequencyAnalyzer, SubGhzViewIdReadRAW, - SubGhzViewIdStatic, - SubGhzViewIdTestCarrier, - SubGhzViewIdTestPacket, } SubGhzViewId; /** SubGhz load type file */ diff --git a/applications/main/subghz/scenes/subghz_scene_config.h b/applications/main/subghz/scenes/subghz_scene_config.h index 97aa946e8..47655958b 100644 --- a/applications/main/subghz/scenes/subghz_scene_config.h +++ b/applications/main/subghz/scenes/subghz_scene_config.h @@ -12,10 +12,6 @@ ADD_SCENE(subghz, show_only_rx, ShowOnlyRx) ADD_SCENE(subghz, saved_menu, SavedMenu) ADD_SCENE(subghz, delete, Delete) ADD_SCENE(subghz, delete_success, DeleteSuccess) -ADD_SCENE(subghz, test, Test) -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) ADD_SCENE(subghz, read_raw, ReadRAW) diff --git a/applications/main/subghz/scenes/subghz_scene_start.c b/applications/main/subghz/scenes/subghz_scene_start.c index ce631b398..951c756d8 100644 --- a/applications/main/subghz/scenes/subghz_scene_start.c +++ b/applications/main/subghz/scenes/subghz_scene_start.c @@ -4,7 +4,6 @@ enum SubmenuIndex { SubmenuIndexRead = 10, SubmenuIndexSaved, - SubmenuIndexTest, SubmenuIndexAddManually, SubmenuIndexFrequencyAnalyzer, SubmenuIndexReadRAW, @@ -56,10 +55,6 @@ void subghz_scene_start_on_enter(void* context) { SubmenuIndexRadioSetting, subghz_scene_start_submenu_callback, subghz); - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - submenu_add_item( - subghz->submenu, "Test", SubmenuIndexTest, subghz_scene_start_submenu_callback, subghz); - } submenu_set_selected_item( subghz->submenu, scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneStart)); @@ -101,11 +96,6 @@ bool subghz_scene_start_on_event(void* context, SceneManagerEvent event) { scene_manager_next_scene(subghz->scene_manager, SubGhzSceneFrequencyAnalyzer); dolphin_deed(DolphinDeedSubGhzFrequencyAnalyzer); return true; - } else if(event.event == SubmenuIndexTest) { - scene_manager_set_scene_state( - subghz->scene_manager, SubGhzSceneStart, SubmenuIndexTest); - scene_manager_next_scene(subghz->scene_manager, SubGhzSceneTest); - return true; } else if(event.event == SubmenuIndexShowRegionInfo) { scene_manager_set_scene_state( subghz->scene_manager, SubGhzSceneStart, SubmenuIndexShowRegionInfo); diff --git a/applications/main/subghz/scenes/subghz_scene_test.c b/applications/main/subghz/scenes/subghz_scene_test.c deleted file mode 100644 index 65f9bbdef..000000000 --- a/applications/main/subghz/scenes/subghz_scene_test.c +++ /dev/null @@ -1,61 +0,0 @@ -#include "../subghz_i.h" - -enum SubmenuIndex { - SubmenuIndexCarrier, - SubmenuIndexPacket, - SubmenuIndexStatic, -}; - -void subghz_scene_test_submenu_callback(void* context, uint32_t index) { - SubGhz* subghz = context; - view_dispatcher_send_custom_event(subghz->view_dispatcher, index); -} - -void subghz_scene_test_on_enter(void* context) { - SubGhz* subghz = context; - - submenu_add_item( - subghz->submenu, - "Carrier", - SubmenuIndexCarrier, - subghz_scene_test_submenu_callback, - subghz); - submenu_add_item( - subghz->submenu, "Packet", SubmenuIndexPacket, subghz_scene_test_submenu_callback, subghz); - submenu_add_item( - subghz->submenu, "Static", SubmenuIndexStatic, subghz_scene_test_submenu_callback, subghz); - - submenu_set_selected_item( - subghz->submenu, scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneTest)); - - view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdMenu); -} - -bool subghz_scene_test_on_event(void* context, SceneManagerEvent event) { - SubGhz* subghz = context; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SubmenuIndexCarrier) { - scene_manager_set_scene_state( - subghz->scene_manager, SubGhzSceneTest, SubmenuIndexCarrier); - scene_manager_next_scene(subghz->scene_manager, SubGhzSceneTestCarrier); - return true; - } else if(event.event == SubmenuIndexPacket) { - scene_manager_set_scene_state( - subghz->scene_manager, SubGhzSceneTest, SubmenuIndexPacket); - scene_manager_next_scene(subghz->scene_manager, SubGhzSceneTestPacket); - return true; - } else if(event.event == SubmenuIndexStatic) { - scene_manager_set_scene_state( - subghz->scene_manager, SubGhzSceneTest, SubmenuIndexStatic); - scene_manager_next_scene(subghz->scene_manager, SubGhzSceneTestStatic); - return true; - } - } - return false; -} - -void subghz_scene_test_on_exit(void* context) { - SubGhz* subghz = context; - submenu_reset(subghz->submenu); -} diff --git a/applications/main/subghz/scenes/subghz_scene_test_carrier.c b/applications/main/subghz/scenes/subghz_scene_test_carrier.c deleted file mode 100644 index 9677792ba..000000000 --- a/applications/main/subghz/scenes/subghz_scene_test_carrier.c +++ /dev/null @@ -1,30 +0,0 @@ -#include "../subghz_i.h" -#include "../views/subghz_test_carrier.h" - -void subghz_scene_test_carrier_callback(SubGhzTestCarrierEvent event, void* context) { - furi_assert(context); - SubGhz* subghz = context; - view_dispatcher_send_custom_event(subghz->view_dispatcher, event); -} - -void subghz_scene_test_carrier_on_enter(void* context) { - SubGhz* subghz = context; - subghz_test_carrier_set_callback( - subghz->subghz_test_carrier, subghz_scene_test_carrier_callback, subghz); - view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdTestCarrier); -} - -bool subghz_scene_test_carrier_on_event(void* context, SceneManagerEvent event) { - SubGhz* subghz = context; - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SubGhzTestCarrierEventOnlyRx) { - scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowOnlyRx); - return true; - } - } - return false; -} - -void subghz_scene_test_carrier_on_exit(void* context) { - UNUSED(context); -} diff --git a/applications/main/subghz/scenes/subghz_scene_test_packet.c b/applications/main/subghz/scenes/subghz_scene_test_packet.c deleted file mode 100644 index 99f0ab179..000000000 --- a/applications/main/subghz/scenes/subghz_scene_test_packet.c +++ /dev/null @@ -1,30 +0,0 @@ -#include "../subghz_i.h" -#include "../views/subghz_test_packet.h" - -void subghz_scene_test_packet_callback(SubGhzTestPacketEvent event, void* context) { - furi_assert(context); - SubGhz* subghz = context; - view_dispatcher_send_custom_event(subghz->view_dispatcher, event); -} - -void subghz_scene_test_packet_on_enter(void* context) { - SubGhz* subghz = context; - subghz_test_packet_set_callback( - subghz->subghz_test_packet, subghz_scene_test_packet_callback, subghz); - view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdTestPacket); -} - -bool subghz_scene_test_packet_on_event(void* context, SceneManagerEvent event) { - SubGhz* subghz = context; - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SubGhzTestPacketEventOnlyRx) { - scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowOnlyRx); - return true; - } - } - return false; -} - -void subghz_scene_test_packet_on_exit(void* context) { - UNUSED(context); -} diff --git a/applications/main/subghz/scenes/subghz_scene_test_static.c b/applications/main/subghz/scenes/subghz_scene_test_static.c deleted file mode 100644 index 10e6d02a1..000000000 --- a/applications/main/subghz/scenes/subghz_scene_test_static.c +++ /dev/null @@ -1,30 +0,0 @@ -#include "../subghz_i.h" -#include "../views/subghz_test_static.h" - -void subghz_scene_test_static_callback(SubGhzTestStaticEvent event, void* context) { - furi_assert(context); - SubGhz* subghz = context; - view_dispatcher_send_custom_event(subghz->view_dispatcher, event); -} - -void subghz_scene_test_static_on_enter(void* context) { - SubGhz* subghz = context; - subghz_test_static_set_callback( - subghz->subghz_test_static, subghz_scene_test_static_callback, subghz); - view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdStatic); -} - -bool subghz_scene_test_static_on_event(void* context, SceneManagerEvent event) { - SubGhz* subghz = context; - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SubGhzTestStaticEventOnlyRx) { - scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowOnlyRx); - return true; - } - } - return false; -} - -void subghz_scene_test_static_on_exit(void* context) { - UNUSED(context); -} diff --git a/applications/main/subghz/subghz.c b/applications/main/subghz/subghz.c index 09963584a..e8148798e 100644 --- a/applications/main/subghz/subghz.c +++ b/applications/main/subghz/subghz.c @@ -129,27 +129,6 @@ SubGhz* subghz_alloc() { SubGhzViewIdReadRAW, subghz_read_raw_get_view(subghz->subghz_read_raw)); - // Carrier Test Module - subghz->subghz_test_carrier = subghz_test_carrier_alloc(); - view_dispatcher_add_view( - subghz->view_dispatcher, - SubGhzViewIdTestCarrier, - subghz_test_carrier_get_view(subghz->subghz_test_carrier)); - - // Packet Test - subghz->subghz_test_packet = subghz_test_packet_alloc(); - view_dispatcher_add_view( - subghz->view_dispatcher, - SubGhzViewIdTestPacket, - subghz_test_packet_get_view(subghz->subghz_test_packet)); - - // Static send - subghz->subghz_test_static = subghz_test_static_alloc(); - view_dispatcher_add_view( - subghz->view_dispatcher, - SubGhzViewIdStatic, - subghz_test_static_get_view(subghz->subghz_test_static)); - //init threshold rssi subghz->threshold_rssi = subghz_threshold_rssi_alloc(); @@ -183,18 +162,6 @@ void subghz_free(SubGhz* subghz) { subghz_txrx_stop(subghz->txrx); subghz_txrx_sleep(subghz->txrx); - // Packet Test - view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdTestPacket); - subghz_test_packet_free(subghz->subghz_test_packet); - - // Carrier Test - view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdTestCarrier); - subghz_test_carrier_free(subghz->subghz_test_carrier); - - // Static - view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdStatic); - subghz_test_static_free(subghz->subghz_test_static); - // Receiver view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdReceiver); subghz_view_receiver_free(subghz->subghz_receiver); diff --git a/applications/main/subghz/subghz_i.h b/applications/main/subghz/subghz_i.h index fc3404c07..9e58f3947 100644 --- a/applications/main/subghz/subghz_i.h +++ b/applications/main/subghz/subghz_i.h @@ -9,10 +9,6 @@ #include "views/subghz_frequency_analyzer.h" #include "views/subghz_read_raw.h" -#include "views/subghz_test_static.h" -#include "views/subghz_test_carrier.h" -#include "views/subghz_test_packet.h" - #include #include #include @@ -64,9 +60,6 @@ struct SubGhz { SubGhzFrequencyAnalyzer* subghz_frequency_analyzer; SubGhzReadRAW* subghz_read_raw; - SubGhzTestStatic* subghz_test_static; - SubGhzTestCarrier* subghz_test_carrier; - SubGhzTestPacket* subghz_test_packet; SubGhzProtocolFlag filter; FuriString* error_str; diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index a44e663ba..ffbc0104b 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1384,8 +1384,8 @@ Function,+,furi_hal_subghz_rx_pipe_not_empty,_Bool, Function,+,furi_hal_subghz_set_async_mirror_pin,void,const GpioPin* Function,+,furi_hal_subghz_set_frequency,uint32_t,uint32_t Function,+,furi_hal_subghz_set_frequency_and_path,uint32_t,uint32_t -Function,-,furi_hal_subghz_set_path,void,FuriHalSubGhzPath -Function,-,furi_hal_subghz_shutdown,void, +Function,+,furi_hal_subghz_set_path,void,FuriHalSubGhzPath +Function,+,furi_hal_subghz_shutdown,void, Function,+,furi_hal_subghz_sleep,void, Function,+,furi_hal_subghz_start_async_rx,void,"FuriHalSubGhzCaptureCallback, void*" Function,+,furi_hal_subghz_start_async_tx,_Bool,"FuriHalSubGhzAsyncTxCallback, void*" @@ -2745,6 +2745,7 @@ Function,+,subghz_protocol_decoder_base_get_hash_data,uint8_t,SubGhzProtocolDeco Function,+,subghz_protocol_decoder_base_get_string,_Bool,"SubGhzProtocolDecoderBase*, FuriString*" Function,+,subghz_protocol_decoder_base_serialize,SubGhzProtocolStatus,"SubGhzProtocolDecoderBase*, FlipperFormat*, SubGhzRadioPreset*" Function,-,subghz_protocol_decoder_base_set_decoder_callback,void,"SubGhzProtocolDecoderBase*, SubGhzProtocolDecoderBaseRxCallback, void*" +Function,+,subghz_protocol_decoder_bin_raw_data_input_rssi,void,"SubGhzProtocolDecoderBinRAW*, float" Function,+,subghz_protocol_decoder_raw_alloc,void*,SubGhzEnvironment* Function,+,subghz_protocol_decoder_raw_deserialize,SubGhzProtocolStatus,"void*, FlipperFormat*" Function,+,subghz_protocol_decoder_raw_feed,void,"void*, _Bool, uint32_t" @@ -2756,6 +2757,7 @@ Function,+,subghz_protocol_encoder_raw_deserialize,SubGhzProtocolStatus,"void*, Function,+,subghz_protocol_encoder_raw_free,void,void* Function,+,subghz_protocol_encoder_raw_stop,void,void* Function,+,subghz_protocol_encoder_raw_yield,LevelDuration,void* +Function,+,subghz_protocol_keeloq_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint8_t, uint16_t, const char*, SubGhzRadioPreset*" Function,+,subghz_protocol_raw_file_encoder_worker_set_callback_end,void,"SubGhzProtocolEncoderRAW*, SubGhzProtocolEncoderRAWCallbackEnd, void*" Function,+,subghz_protocol_raw_gen_fff_data,void,"FlipperFormat*, const char*, const char*" Function,+,subghz_protocol_raw_get_sample_write,size_t,SubGhzProtocolDecoderRAW* @@ -2765,6 +2767,8 @@ Function,+,subghz_protocol_raw_save_to_file_stop,void,SubGhzProtocolDecoderRAW* Function,+,subghz_protocol_registry_count,size_t,const SubGhzProtocolRegistry* Function,+,subghz_protocol_registry_get_by_index,const SubGhzProtocol*,"const SubGhzProtocolRegistry*, size_t" Function,+,subghz_protocol_registry_get_by_name,const SubGhzProtocol*,"const SubGhzProtocolRegistry*, const char*" +Function,+,subghz_protocol_secplus_v1_check_fixed,_Bool,uint32_t +Function,+,subghz_protocol_secplus_v2_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint8_t, uint32_t, SubGhzRadioPreset*" Function,+,subghz_receiver_alloc_init,SubGhzReceiver*,SubGhzEnvironment* Function,+,subghz_receiver_decode,void,"SubGhzReceiver*, _Bool, uint32_t" Function,+,subghz_receiver_free,void,SubGhzReceiver* diff --git a/lib/subghz/subghz_protocol_registry.h b/lib/subghz/subghz_protocol_registry.h index 6a27da992..8e80071b5 100644 --- a/lib/subghz/subghz_protocol_registry.h +++ b/lib/subghz/subghz_protocol_registry.h @@ -8,6 +8,31 @@ extern "C" { extern const SubGhzProtocolRegistry subghz_protocol_registry; +typedef struct SubGhzProtocolDecoderBinRAW SubGhzProtocolDecoderBinRAW; + +bool subghz_protocol_secplus_v2_create_data( + void* context, + FlipperFormat* flipper_format, + uint32_t serial, + uint8_t btn, + uint32_t cnt, + SubGhzRadioPreset* preset); + +bool subghz_protocol_keeloq_create_data( + void* context, + FlipperFormat* flipper_format, + uint32_t serial, + uint8_t btn, + uint16_t cnt, + const char* manufacture_name, + SubGhzRadioPreset* preset); + +void subghz_protocol_decoder_bin_raw_data_input_rssi( + SubGhzProtocolDecoderBinRAW* instance, + float rssi); + +bool subghz_protocol_secplus_v1_check_fixed(uint32_t fixed); + #ifdef __cplusplus } #endif From 79bcd5a1c6b780752a9511e9c70fd5d259c5f80f Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 6 Jul 2023 18:44:45 +0300 Subject: [PATCH 063/364] Jetpack joyride and NFC Maker apps --- ReadMe.md | 2 + .../external/jetpack_joyride/application.fam | 15 + .../jetpack_joyride/assets/air_vent.png | Bin 0 -> 1838 bytes .../jetpack_joyride/assets/alert/frame_01.png | Bin 0 -> 1820 bytes .../jetpack_joyride/assets/alert/frame_02.png | Bin 0 -> 1807 bytes .../jetpack_joyride/assets/alert/frame_rate | 1 + .../jetpack_joyride/assets/barry/frame_01.png | Bin 0 -> 1932 bytes .../jetpack_joyride/assets/barry/frame_02.png | Bin 0 -> 1930 bytes .../jetpack_joyride/assets/barry/frame_03.png | Bin 0 -> 1929 bytes .../jetpack_joyride/assets/barry/frame_rate | 1 + .../jetpack_joyride/assets/barry_infill.png | Bin 0 -> 1614 bytes .../external/jetpack_joyride/assets/bg1.png | Bin 0 -> 835 bytes .../external/jetpack_joyride/assets/bg2.png | Bin 0 -> 968 bytes .../external/jetpack_joyride/assets/bg3.png | Bin 0 -> 886 bytes .../external/jetpack_joyride/assets/coin.png | Bin 0 -> 1842 bytes .../jetpack_joyride/assets/coin_infill.png | Bin 0 -> 1974 bytes .../jetpack_joyride/assets/dead_scientist.png | Bin 0 -> 1768 bytes .../assets/dead_scientist_infill.png | Bin 0 -> 1900 bytes .../external/jetpack_joyride/assets/door.png | Bin 0 -> 1961 bytes .../assets/missile/frame_01.png | Bin 0 -> 1898 bytes .../jetpack_joyride/assets/missile/frame_rate | 1 + .../jetpack_joyride/assets/missile_infill.png | Bin 0 -> 1591 bytes .../jetpack_joyride/assets/pillar.png | Bin 0 -> 1880 bytes .../jetpack_joyride/assets/scientist_left.png | Bin 0 -> 2056 bytes .../assets/scientist_left_infill.png | Bin 0 -> 2211 bytes .../assets/scientist_right.png | Bin 0 -> 1917 bytes .../assets/scientist_right_infill.png | Bin 0 -> 2210 bytes .../external/jetpack_joyride/icon.png | Bin 0 -> 1836 bytes .../includes/background_asset.c | 81 ++++ .../includes/background_assets.h | 34 ++ .../external/jetpack_joyride/includes/barry.c | 33 ++ .../external/jetpack_joyride/includes/barry.h | 23 ++ .../external/jetpack_joyride/includes/coin.c | 98 +++++ .../external/jetpack_joyride/includes/coin.h | 26 ++ .../jetpack_joyride/includes/game_sprites.h | 35 ++ .../jetpack_joyride/includes/game_state.c | 5 + .../jetpack_joyride/includes/game_state.h | 34 ++ .../jetpack_joyride/includes/missile.c | 86 ++++ .../jetpack_joyride/includes/missile.h | 24 ++ .../jetpack_joyride/includes/particle.c | 57 +++ .../jetpack_joyride/includes/particle.h | 21 + .../external/jetpack_joyride/includes/point.h | 14 + .../jetpack_joyride/includes/scientist.c | 77 ++++ .../jetpack_joyride/includes/scientist.h | 29 ++ .../jetpack_joyride/includes/states.h | 9 + .../external/jetpack_joyride/jetpack.c | 379 ++++++++++++++++++ .../external/nfc_maker/application.fam | 14 + applications/external/nfc_maker/nfc_maker.c | 81 ++++ applications/external/nfc_maker/nfc_maker.h | 59 +++ .../external/nfc_maker/nfc_maker_10px.png | Bin 0 -> 4142 bytes .../nfc_maker/scenes/nfc_maker_scene.c | 30 ++ .../nfc_maker/scenes/nfc_maker_scene.h | 29 ++ .../scenes/nfc_maker_scene_bluetooth.c | 57 +++ .../nfc_maker/scenes/nfc_maker_scene_config.h | 13 + .../nfc_maker/scenes/nfc_maker_scene_https.c | 53 +++ .../nfc_maker/scenes/nfc_maker_scene_mail.c | 53 +++ .../nfc_maker/scenes/nfc_maker_scene_menu.c | 61 +++ .../nfc_maker/scenes/nfc_maker_scene_name.c | 57 +++ .../nfc_maker/scenes/nfc_maker_scene_phone.c | 53 +++ .../nfc_maker/scenes/nfc_maker_scene_result.c | 359 +++++++++++++++++ .../nfc_maker/scenes/nfc_maker_scene_text.c | 53 +++ .../nfc_maker/scenes/nfc_maker_scene_url.c | 53 +++ .../nfc_maker/scenes/nfc_maker_scene_wifi.c | 55 +++ .../scenes/nfc_maker_scene_wifi_auth.c | 83 ++++ .../scenes/nfc_maker_scene_wifi_encr.c | 48 +++ .../scenes/nfc_maker_scene_wifi_pass.c | 53 +++ 66 files changed, 2349 insertions(+) create mode 100644 applications/external/jetpack_joyride/application.fam create mode 100644 applications/external/jetpack_joyride/assets/air_vent.png create mode 100644 applications/external/jetpack_joyride/assets/alert/frame_01.png create mode 100644 applications/external/jetpack_joyride/assets/alert/frame_02.png create mode 100644 applications/external/jetpack_joyride/assets/alert/frame_rate create mode 100644 applications/external/jetpack_joyride/assets/barry/frame_01.png create mode 100644 applications/external/jetpack_joyride/assets/barry/frame_02.png create mode 100644 applications/external/jetpack_joyride/assets/barry/frame_03.png create mode 100644 applications/external/jetpack_joyride/assets/barry/frame_rate create mode 100644 applications/external/jetpack_joyride/assets/barry_infill.png create mode 100644 applications/external/jetpack_joyride/assets/bg1.png create mode 100644 applications/external/jetpack_joyride/assets/bg2.png create mode 100644 applications/external/jetpack_joyride/assets/bg3.png create mode 100644 applications/external/jetpack_joyride/assets/coin.png create mode 100644 applications/external/jetpack_joyride/assets/coin_infill.png create mode 100644 applications/external/jetpack_joyride/assets/dead_scientist.png create mode 100644 applications/external/jetpack_joyride/assets/dead_scientist_infill.png create mode 100644 applications/external/jetpack_joyride/assets/door.png create mode 100644 applications/external/jetpack_joyride/assets/missile/frame_01.png create mode 100644 applications/external/jetpack_joyride/assets/missile/frame_rate create mode 100644 applications/external/jetpack_joyride/assets/missile_infill.png create mode 100644 applications/external/jetpack_joyride/assets/pillar.png create mode 100644 applications/external/jetpack_joyride/assets/scientist_left.png create mode 100644 applications/external/jetpack_joyride/assets/scientist_left_infill.png create mode 100644 applications/external/jetpack_joyride/assets/scientist_right.png create mode 100644 applications/external/jetpack_joyride/assets/scientist_right_infill.png create mode 100644 applications/external/jetpack_joyride/icon.png create mode 100644 applications/external/jetpack_joyride/includes/background_asset.c create mode 100644 applications/external/jetpack_joyride/includes/background_assets.h create mode 100644 applications/external/jetpack_joyride/includes/barry.c create mode 100644 applications/external/jetpack_joyride/includes/barry.h create mode 100644 applications/external/jetpack_joyride/includes/coin.c create mode 100644 applications/external/jetpack_joyride/includes/coin.h create mode 100644 applications/external/jetpack_joyride/includes/game_sprites.h create mode 100644 applications/external/jetpack_joyride/includes/game_state.c create mode 100644 applications/external/jetpack_joyride/includes/game_state.h create mode 100644 applications/external/jetpack_joyride/includes/missile.c create mode 100644 applications/external/jetpack_joyride/includes/missile.h create mode 100644 applications/external/jetpack_joyride/includes/particle.c create mode 100644 applications/external/jetpack_joyride/includes/particle.h create mode 100644 applications/external/jetpack_joyride/includes/point.h create mode 100644 applications/external/jetpack_joyride/includes/scientist.c create mode 100644 applications/external/jetpack_joyride/includes/scientist.h create mode 100644 applications/external/jetpack_joyride/includes/states.h create mode 100644 applications/external/jetpack_joyride/jetpack.c create mode 100644 applications/external/nfc_maker/application.fam create mode 100644 applications/external/nfc_maker/nfc_maker.c create mode 100644 applications/external/nfc_maker/nfc_maker.h create mode 100644 applications/external/nfc_maker/nfc_maker_10px.png create mode 100644 applications/external/nfc_maker/scenes/nfc_maker_scene.c create mode 100644 applications/external/nfc_maker/scenes/nfc_maker_scene.h create mode 100644 applications/external/nfc_maker/scenes/nfc_maker_scene_bluetooth.c create mode 100644 applications/external/nfc_maker/scenes/nfc_maker_scene_config.h create mode 100644 applications/external/nfc_maker/scenes/nfc_maker_scene_https.c create mode 100644 applications/external/nfc_maker/scenes/nfc_maker_scene_mail.c create mode 100644 applications/external/nfc_maker/scenes/nfc_maker_scene_menu.c create mode 100644 applications/external/nfc_maker/scenes/nfc_maker_scene_name.c create mode 100644 applications/external/nfc_maker/scenes/nfc_maker_scene_phone.c create mode 100644 applications/external/nfc_maker/scenes/nfc_maker_scene_result.c create mode 100644 applications/external/nfc_maker/scenes/nfc_maker_scene_text.c create mode 100644 applications/external/nfc_maker/scenes/nfc_maker_scene_url.c create mode 100644 applications/external/nfc_maker/scenes/nfc_maker_scene_wifi.c create mode 100644 applications/external/nfc_maker/scenes/nfc_maker_scene_wifi_auth.c create mode 100644 applications/external/nfc_maker/scenes/nfc_maker_scene_wifi_encr.c create mode 100644 applications/external/nfc_maker/scenes/nfc_maker_scene_wifi_pass.c diff --git a/ReadMe.md b/ReadMe.md index b589e164e..f57256713 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -170,6 +170,7 @@ You can support us by using links or addresses below: - IR Scope [(by kallanreed)](https://github.com/DarkFlippers/unleashed-firmware/pull/407) - **BadBT** plugin (BT version of BadKB) [(by Willy-JL, ClaraCrazy, XFW contributors)](https://github.com/ClaraCrazy/Flipper-Xtreme/tree/dev/applications/main/bad_kb) (See in Applications->Tools) - (aka BadUSB via Bluetooth) - **Mifare Nested** [(by AloneLiberty)](https://github.com/AloneLiberty/FlipperNested) - Works with PC and python app `FlipperNested` +- **NFC Maker** plugin (make tags with URLs, Wifi and other things) [(by Willy-JL)](https://github.com/ClaraCrazy/Flipper-Xtreme/tree/dev/applications/external/nfc_maker) Games: - DOOM (fixed) [(by p4nic4ttack)](https://github.com/p4nic4ttack/doom-flipper-zero/) @@ -185,6 +186,7 @@ Games: - BlackJack [(by teeebor)](https://github.com/teeebor/flipper_games) - 2048 game [(by eugene-kirzhanov)](https://github.com/eugene-kirzhanov/flipper-zero-2048-game) - Bomberduck [(by leo-need-more-coffee)](https://github.com/leo-need-more-coffee/flipperzero-bomberduck) +- JetPack Joyride [(by timstrasser)](https://github.com/timstrasser) # Instructions diff --git a/applications/external/jetpack_joyride/application.fam b/applications/external/jetpack_joyride/application.fam new file mode 100644 index 000000000..1b98e11ce --- /dev/null +++ b/applications/external/jetpack_joyride/application.fam @@ -0,0 +1,15 @@ +# For details & more options, see documentation/AppManifests.md in firmware repo + +App( + appid="jetpack_joyride", + name="Jetpack Joyride", + apptype=FlipperAppType.EXTERNAL, + entry_point="jetpack_game_app", + cdefines=["APP_JETPACK_GAME"], + requires=["gui"], + stack_size=4 * 1024, + order=100, + fap_icon="icon.png", + fap_category="Games", + fap_icon_assets="assets", +) diff --git a/applications/external/jetpack_joyride/assets/air_vent.png b/applications/external/jetpack_joyride/assets/air_vent.png new file mode 100644 index 0000000000000000000000000000000000000000..a7fcf0b203a79875b27f4a90870bf66b8ec4e597 GIT binary patch literal 1838 zcma)7eNYr-7+>=vJkTK&D$80|6O6gry@NZr?G^^^PVR(b2#y2?!R79~yN$cuZFi45 z&{CWMzs4aHwWh|=88TCYMy9E3nlw#?$Q&RiDQiNQoW`Q2X0kz}clYo(B%S`)yM3SE z@A-V6=h+=4#RUsgOH>GgEU?=wrSLxiKJjr6!QXR9<8cTwH<@u(NEMDE(!~Wdw4191 zns9)JID(jR!#wS(0}@&bybMcWV;_Htp^Tft*6JOEgEs>oV`~&ZS!1!&)mY~;y0P3G zl_^X@0|6k>XgJ_!MKVlbid_=E%VHcu6^c|xVHJ)N)XWJ0)ob(`0?SdMCc*6?OD(G> zmEnoPe3HbII35axG@%R)CwOtK(P+d89j?=Md> z23=x2_o2(k3_7f}!|~uO0)c6Xh?2D)7Gkof(*nAix9Du(0bUA3E-1KwwH~n2a-|l~ z8)+5*EddLBC`?D_GSq}YO=LQ?*(6~g_4;%|M-Wl6gL5;UhW{jIky-;Jld){r9&TEq z{|`S}a8hPRf`YC2z=)=F%$%PSU~n*p3?9puJ!%(d1O$N(nzF@5hYG&P(+bADg!S? zPLZRZQ3eP_l{O-2m+YUyc!6^VUBIoB0Gf-SBIl7pv;gwFu!ATp&*NcWqzx#XO~%Ut z0o9?frf~45*$nvrw0O@&#&iol867T<_1*AH_>2Th3L$0>K)r|z?iaYI@aU$3hv=>Y zEbJlyZn7=y*P9Sz))of#!;C&akhHd7+ZToTd5gE@K24>aJ(QxZvN?|@q`x{eXVvq0 zme=ySc30Ol4UJYm(-FA*LfZDBKi3V`lze+An7G)Q7^qu6cx0r$Q?+K~@k?)VuVnm0 zi1SVyZr^j|muj2$cg77@v0x*Xzb|YR;KpecAZR7%{x7$ zxmi5OceL#5*q^fhz`)+((e~b#`wnE+zTVuh0BIF043jZSAqOWsOgK(ABspe6;G?kK0-vJ$vg$ z^O2=#DGArdzae&}ZrIpzyhy+KPTcAoEAD*%pqXdSd>$;{b@R-qL89KF)m3}^!&kyj ze*e{>z~&xx^-%! literal 0 HcmV?d00001 diff --git a/applications/external/jetpack_joyride/assets/alert/frame_01.png b/applications/external/jetpack_joyride/assets/alert/frame_01.png new file mode 100644 index 0000000000000000000000000000000000000000..ac4cca1b1f85f1ac36afa559e601804c50074838 GIT binary patch literal 1820 zcma)7eM}o=7(Y-phI~v%Hc{iwBam&--nEP_Jz5I1eALmV<3q}#lcRmx9?-kn-IbQ= z;+Vpexf#s9m`W=8hcg5WI?b4ah$cqI2Gg*qTa1pGOcsNgK@*3;_pVUN>K6Za+rIDb z_x_%b_j#VaVzZW~Bt4RZAV`Y2!c+yn6W~ftjEA3D@jnIOejQz77wwix(!qL_l#^`) z%7B-HID+U50giGs0}*WmZko|!vu8#zly>T|x_k>^;f%mTR|I)b9kkXsg3S)C6EhSh z=>jAa@B)!S1Kt)!AOm_#)+OO@=@`dQnIbmpF}uZv8d)Bo`O17HffXj9I^O9bt4w8! zm*Gy2c|?&Taoq3sEB$#&mUrVStyYT@YFw>WK!!qSV?-*TV1!IbBCKHof`g|yk!BfG z(xe($pQy($h@(rMdAW!P_^MD5^4p%7&JetgTSdkSx>|L%z;FyabKrhFz!iC zkwzX+A~3n;quH=LoRmoY zAATg^q{I$81xxdu9xdN7vMnqRor5vt$<8iAW~0%@vo5*?VnS7U32H7iYE)W{Mxj=! zWUlmXq>T=M7Q2b|0!Dx}rpNN{!0Mwgm3kSJO;^Ltg8E@S>0wE;mvVzT+9`ULq?V5> z0ppg=7iDBXdYu-ZRNl1=4c{|tL5A$hm>$z=@F*+wF_6?Ss7@{;vVs;wmT!}f!d+Ai zc87Q2Z3`{84~oh~L{bjPz8>Rv*6DKqryL}d3!?(-68#hpiruh&^jNXWMMF>9P}rHY zn*ltkMqx%_+po~{djM3t;~ArB1z+?ImzsLXJslnsfKDbvO#yf>ds}<+8!SqTl$zhrZExa?(xSpkfAp&pSybc404nd`j6=%@@=qWbvKCg>9of!g1|= zM{Q49Mk7$MgDc%v`_v^xom=o9(Oeemp(Cb8h0Ff*<-+ z&$M^0|9MU6)X>Lxo?df zGNqI^+09Kf)u!F^YmN2f)i0knKbQL40^uVb^2hM8|78Ix{vHx*kX~4)s|_ z&Kx$J8@|5fTJ0@Qe>ZAATD(K3(r2rb!Ae)xr{6D(?wOj-?B(ZL@$YsePrtMAgg;~_ zUY&J(zNvTbT*iQUYJM*B;oIqFZBu`zem0V@;c~5FX!WrxlYhM1d5SX+b=6>$E73u#FpD{7M6IbM`l+2nbuHr;Y!^LaSsl=N1xj6#^4{)8m^*VK#V`e}7&46BmEU=$>0gKdwfQ{apHAAAQxUf^5WG LYBdd%H0=8q6;5LQ literal 0 HcmV?d00001 diff --git a/applications/external/jetpack_joyride/assets/alert/frame_02.png b/applications/external/jetpack_joyride/assets/alert/frame_02.png new file mode 100644 index 0000000000000000000000000000000000000000..c3955f090f5c9e496e536ae34cb206384191a0bb GIT binary patch literal 1807 zcma)7ZA=?w94{km40+MXUQiP+M_A^dy=yzV^ypEb<)x02ju$D5PLB3zd(eB?-Ic!R z;@AQz8#9=_$1RBa;0ytSnP%W1qRA4612t^w7NcVyCX2z$;0K4n=dQez)h&Lw_MYeW zdw&0y=l}oT+m5R8w3KI3R4P@Pt-@Lj|C8X8n!E=7=B)W80@qK|wNA-tuOwU?qoLef z6VQYh9^xvM$sFP-R|}AkCg7!65}h3$MG@LfqV@T9+|FBokFJOape9mP>x#6vjBeCi zm|_YMP=Emvg@hPCD-t0RRdfmXE^lKfqEMt35_Q@gh=mgXlCR0v;AmkAViMdQqS{)v zco?oo)F(+ifnmX5P!rT^IKhi)jYcDe>oA>84H;@N%t};9&5GHwL{!5HM3+GG63wxQ ztVuO-0f|IWh$Bm$G5oSPD=tP4^MHjY9@A=YESAjsIEfQ|+#`-G!*K^eh+c_dl^Gd{ zqf4~s5p)@eqr+U=?T?RwVO9kqN~NtZ5sOJ(<xXm0Un; zghc?91g!8OQ5~++t8s%G&#%>K3EV*JFy`Vq9A75eIXCSI|0j7Tp~oSah-Jg_a8nZX zfB4G@CuDZiDOj41^=Rdch4XU)bPmR#SDameY!-_{;5@V+Vq$f931Ta?7_>%%L9Nqh z6|Q`5go6$Nztc)HfE8hlk!b!ySVIh^)vbVv=^EHsP(P|CUo2r`C@-j|-I8xfYGu0; zuwHq8QAPpe+wlkF@~&fPc%M-V3gleDNYrS+Vyra8L2|<&CZ&vs3R;vnA*^ggyQl{2 zi>{(eKdrP6f+$5qP%ha%iSh#H4!D3@2@=XhQIYdVK}rC{URXaQTI})A(9!Q!lw^!r}rjDTJ6Q0MAAD;Br1Bw$G#tH>*{O1-_L*cZfbe^MEkn@OFe&FzEJ-4yhrFAH*aluzb7N_?ATFj zT6we6)=X1jTeX@B)1rv6U}en|#_ezp!9V+|bxmlyvB9hN}2{;>Imq(^us0n60$R+F#Om@LxZYToM2P literal 0 HcmV?d00001 diff --git a/applications/external/jetpack_joyride/assets/alert/frame_rate b/applications/external/jetpack_joyride/assets/alert/frame_rate new file mode 100644 index 000000000..e440e5c84 --- /dev/null +++ b/applications/external/jetpack_joyride/assets/alert/frame_rate @@ -0,0 +1 @@ +3 \ No newline at end of file diff --git a/applications/external/jetpack_joyride/assets/barry/frame_01.png b/applications/external/jetpack_joyride/assets/barry/frame_01.png new file mode 100644 index 0000000000000000000000000000000000000000..8abdcaf6150ec448cba1279fd790c03985e4778e GIT binary patch literal 1932 zcma)7eM}Q~7(O>=80r*1rgJg59Y&_6z4rP+dbA+4d=xoWp=u#+di1XCNqcv@yULX@ z+{|!nGcr(v5dIKMoI|6iac*u7KjN4l(@X_JTy(l6nl0)aDC(H*cUL|IxBYSL{odz! zzn**Vy^mbEnG<7Y#X=A?(V6AQ1OMZ}H7Vvf@OyYglOKX!a4>F<IxHR;fVem*%>?rNv$gg`_0M zT4fvr2+Q(UOu9ZHYn434T;>u~<+YhGLioFf?L>l}K5`it$Q_XbcA}dIg4;7>htl}~Cvjqc;|D4l<}(0~aFiI|Gx8|N z2s)5&^b9h|k47elf2W5+!`cv~^wmI!K2e9Y(4RnbM|c{|qeU((cGP{UJ@({6VOoR0|t zOw7wngPrMiv)*DhYcOpzhoU3y43U01hi3g!03?sb9|V>$lxiR1pfnf^D-*JWWq>4@ znpbu43}QAU5krS~6^sgjhlUeix+zj39i#**#dMfKqcdqRy&J=D%zzt>3v?K+)2Ufh zK1VUW$bYewiH5C!PY`$(BTAeQQBBb?&!^vxo}$Mfqt3T7;R>JhD&5%-Uf`&(m!{Oz z>ijFH$oZraQlL})-~ro^RG*IlIz(Xb>M(wm7GMmlvwbUp-b-*#iA?S|k%)bcjoO3SXTQ#>% zOzJ7S-E&1e)U&)L6I<1><8G?C_R{gjtyogxo-g0x&*#2+U~E%!b#r1Z_ zecD-7TV6`1Z!WvaJ)U#AX6LD0d)J+vGVlDacPkGe?Ng_0Y)YUg&;CbG4(`biwzhf> z>toLPFO=OGcgZ~Mi=3|Czc-F=`&nLldbwrI!^z`P99QQT-+n%D<-M+*`(D0W@d}&c=wpaE;=gffOX7#% z6ZRsXRUm?|-c;op1JyUo1jdz4N2+_~=CCXEr6cf!DHoosshh3a@Z;{!3Ma?FUj?BD z8|O5{wfK52oVj_uzKcJC7RJik9+cOfc`fPaBiB**mAaa18HVE~$KSVpncrUDEN#0r myL(Gp`ofF%$nM_V-B6P&|8eVyd6Sfns53p+u{&+$+J6AnqoRNS literal 0 HcmV?d00001 diff --git a/applications/external/jetpack_joyride/assets/barry/frame_02.png b/applications/external/jetpack_joyride/assets/barry/frame_02.png new file mode 100644 index 0000000000000000000000000000000000000000..5a4587ac8f56f2c9ee981d4811def57ed952329e GIT binary patch literal 1930 zcma)7du$VR96uDv81fhoAv4CyjZgvGYj0g=Z|&yR?ors)F$(Jlg16qU?QQMdd3R;k zNe~yth7w^sGC=--B@$kd0U`!P5Tlca5RsJ(2#7I4FagE}lSih%yY9(``p32R`+h#( z*XO?Xy;Vi-{LzWYi3oy>b`>~_;r}RjjZJtC{vKW27(kF0oUF$yd#OUw$A=9x!kl#xYc*$Q5?XOBJCGUIb{GA$kLoY=+nO5|il%j@+$8ZA4C$fUf@*Jwh zq|5k-Y{xK&qus%Q!uCbyr0({i5V(rqCW8_0PZxr`%u7LD=&fjgPcJ;kQD*(m$fF{I z=up0)Gbpq$6q%y_ogNMkXhV{7mp~!9MIF#WPXfsk6#!lgBt9bgK<*O2$tju=sxG91 z5Aot$I2yo?nflB{tO7z_CmRNw1Vx9~(_m?TFirF96)hf>;u#n;^(n~Za1@EWpAA7w zD$dVAUAYd6$!f9a2}3-Gsw3$N(*aNjxPTml$>Z_+fD)F`?EM_n215~bLgsNSl!Vao zYA)VB%#NiKct5YwAtCtCND59jL(8<2mSLrYkx11W)AWSNLl7j9P7-M|jRa{lYFRWs z&#?aJf3ekxMs1K!QDgxt$-EfVOz|N;>QrH&9^$?DxdbL-PtigHWPmhUr8gv-g~$(a)#+!V8~XUtwR>0!%np88|@ zSjzRJro#y{cO`y2uW3u{;E8+UmDV#KE;zF0{<rPuhbsi)WNIkk8H%CqCApZ~qB?g-X0Vf^|=9m9AJKKlFczC3Z;G4D}R z!r8ztOYe-hXqos$Vf&vynnyMNs#Kp|Xnp_TxDi>-YcnctKOelhynWAsmoC?QMBP*_ zG*)$#h;^6OyOfNY9oFE{4<6N&otyP|tL;tZ?Qd=+KX|Y`|7<$mg|Np1H%@IzuZ5qm zkwQVK1iyL{)N2^BW$P4ZT;y_Qd!}rRterFFxG^E;)3&Y>|EsURl6|9LV~Ke=Q#$Of z&h&Ob{9%5kA-h_<;j1!+qx{#+L$X=JEoesvS9^&X=LZPN1rrDZe42Z$kpB3 i)Uiwd`P3bMxW`_&Pj#6`)hz-_=Nw4ky_x+yV zefPWfoJDz);%CQ05H!h=Z!ZSF6TtOc+*t5^bXl_xg2vfdms@s|g@lI>>M5G9VDw5* z05}AhvlM~yR5CJL!T4Ctf^>a-9D!Ndf|RC_7%A8oKbv1IG9}eTE>Cr($3!Dpnek?Y z011MOOuh0)e7cRaN?`6g@BcP=m>2LNOf0aUJl`NnuW=6dfleswtv5?2P0QSwUub4pwtg z6?{mxAPB(WzT}L;4rb@1zVQJOsEVKlJ%$c-7yP`;OMYG$=xA8X06Zd4X1!0Tqaq{R z0ez!aP$*$EXOjGP_h4|?7?PZ`0ua$h>aY>|3rMc8z@Wv9#D_!=le2>1l#eN7IG^kX#iRKLfl`*%>_Y<70RtgbA&WQ`P=af9 zwGj6pWzS`K%=KVpuapWnRL(89hah0anwus&Lhx@~G2U5JBYWkcXkQ(whEN zRN}pI6(urOAGpC5#On33fQK*)9v#-lF(Qn^xE|A|4v(txGazICbOxgfHxQVCNW=Q3 zLr3jLQ{7YhiK73jAMjN6_Y;j8-p@$3`}#4X>K5py%Z!d7H0m&9ZGbtVHn5JsUPA{K z8#oNCMur3XPy~yw{FXpM(3sUM5bFq$LeMMqq54b7_Jz|=XQg=7KdQ?vbAOY%$D6w2 zOkh*}x`x!;`v1@mF+E;cvQrWg^ zW#_WWx}=FS0-=+(T`f0iPp?f{+}(64<799-F?(j_w0k#2>-CHU8)n>Z`^{Bs2B>i3R3L7uv zmr3BQH%-09Kuud`0pn7K-RhdPIpnlXK7qx}|M2l+JmZHiV?Irq5rgFGlcCIpwJBp4 z|8=bCg{4bEvCr0g-m+@usfSnS$sOY()^TfSx?tU$3lrO?-0gX(bZ6vFLHf_VYhLU= fEO*iJ`OTS~pE-LAw;pO#-=L11BKsHFZ>;_YytSIB literal 0 HcmV?d00001 diff --git a/applications/external/jetpack_joyride/assets/barry/frame_rate b/applications/external/jetpack_joyride/assets/barry/frame_rate new file mode 100644 index 000000000..e440e5c84 --- /dev/null +++ b/applications/external/jetpack_joyride/assets/barry/frame_rate @@ -0,0 +1 @@ +3 \ No newline at end of file diff --git a/applications/external/jetpack_joyride/assets/barry_infill.png b/applications/external/jetpack_joyride/assets/barry_infill.png new file mode 100644 index 0000000000000000000000000000000000000000..9462801f02c43ffc7bc9fc70ab9b9ea902a908e4 GIT binary patch literal 1614 zcmcIlJ#W)M7mTfsOwFD?`=to}JGo6cz?MxqF`X>$&${zuekfo1U7P5(Hto zv|iYT-y87UoPd`wvpV@+5UzbAdPUmLu2a1R=K+`IdcvnYfQ@#51Iy zU%pBraSUlUW2&Z~$91ya4)9KUvuw8y?5ra#El%Yk9UQorSz_ciyikt}DfX+wf4;3q zVoYHNhEy@P#5@hKn2|HGDlJZlxxlIF+lAGRF{})!&X}(&O0(INn`xN_HATy2vxu+z72sbJP;$klZ(E+;*?hS&O*s>>D z;?<-MI89%IO8NX&K&zwySh&5mB9@Bzc`Z9XpF(mHgX^f5T&spRuvcSs2%h-w1G~hD z_XiAkfsI4nA&)%*lAt(lY*OiChO~f`0js^S5FDCrLU%hBvkDf2Op%JxDK(Qq3uRT) zRivlq9>H5x<0vtoI;7hA8=H5um;=6P>gyzAG-$9IzoRSE~ecXo+xjNX^56h(si-^4zdhvyQ=Iw>oXU zjG#JA&!jumDgQSgxRqDsgS0Y2I!fzvUA4#O6W@YA>XV@Vq|ZP;iPf+^qo?1=C#UeK z)IGkOGIGEPVPK4buE`#ZN0_rpZ_vV`H>$A*^ErS){ObPaA27tP`|Eol%>CQ^nGoK- zy9b+Msjyt0d}IH*cs_A_{!l%8a&r3d4os(eD;=bcIWPc58vBW{uwA0Hw&Lu Ho*w@ODWKxk literal 0 HcmV?d00001 diff --git a/applications/external/jetpack_joyride/assets/bg1.png b/applications/external/jetpack_joyride/assets/bg1.png new file mode 100644 index 0000000000000000000000000000000000000000..82d614e1cc45e9a74c6d44d4ae45b772807aae8d GIT binary patch literal 835 zcmeAS@N?(olHy`uVBq!ia0vp^4M6O`!2~2@x4h6`U|?*>baoCX4sv%=@N{)HGqf}_ z(>2mFWMI&kSUT~%wo9PMaeHGQWw$Aj8e;m1rY3H_Ia6k~Ofqqk%t@Q~bK$e&ulG(& zS)H?7W8=r{gYgH#BDGzYq@130!@%}=WtK{D-t&3q|K6+i`#z8RTt?Vg$*pxKo_1HI zm@GMS?3u`SVZB=)g8J>(N!|KzIL|r}qH=DQH)0x$M?uQAhUb;Rs zeAqd4QQF688^X^X5pVTxsofP5@Z;DeVKe*aIop0-PO_EaY~CCh;raj35&baZD!+(0 zzjVFbYyRCiUe&2SbN-wzHmPi|;UB7-Mv{pZT(m zywh3cInT9!w$@5i2=5KKX!U(TrmUa7R#cMoIkN>{YPx1s;*b3=De8Ak0{?)V>TD zL7AQ|jv*Cu-p)BViAj-%b-M2V|IMv0h^{JmuI#%rSa=m@{%nKHon~S}#Y*?=Ru=#TBivFikZ4_Zx%pk#fK!btGLf((FCW)LCj5w*^b@qH$g|R*3$>ZJA zpMTGrwIO`binj-UOzldv?mo@$XZ&-@1WBtft$*(x xShl0Tw<`T`xX?Esf3NW-AFq#6TnHEY&z`#_Q`kX&5-=thJYD@<);T3K0RWSjY2^R_ literal 0 HcmV?d00001 diff --git a/applications/external/jetpack_joyride/assets/bg2.png b/applications/external/jetpack_joyride/assets/bg2.png new file mode 100644 index 0000000000000000000000000000000000000000..ec8590d3ada26094c39a3019b72084423c75ec41 GIT binary patch literal 968 zcmeAS@N?(olHy`uVBq!ia0vp^4M6O`!2~2@x4h6`U|?*>baoCX4sv%=@N{)HGqf}_ z(>2mFWMI&kSUT~%wo9PMaeHGQWw$Aj8e;m1rY3H_Ia6k~Ofqqk%t@Q~bK$e&ulG(& zS)H?7W8=r{gYgH#BDGzYq@130!@%}=WtK{D-t&3q|K6+i`#z8RTt?Vg$*pxKo_1HI zm@GMS?3u`SVZB=)g8J>(N!|KzIL|r}qH=DQH)0x$M?uQAhUb;Rs zeAqd4QQF688^X^X5pVTxsofP5@Z;DeVKe*aIop0-PO_EaY~CCh;raj35&baZD!+(0 zzjVFbYyRCiUe&2SbN-wzHmPi|;UB7-Mv{pZT(m zywh3cInT9!w$@5i2=5KKX!U(TrmUa7R#cMoIkN>{YPx1s;*b3=De8Ak0{?)V>TD zLGL_W978JRyq$BguSJ2!HF5L*|EJ#^PT^STk-f0G^UmiK9)qp!+I?*?UY;z+`K=l* zFJv$fVQ6DI5Wp}a-h1(!;u4uWoy`{Uf0`%W<=ojf^D6TS`)v{~fy z!Q9RB_XYBvb1(l>u6$l)iS@G=TsLffPu;z_)b5}4R%1@Vi?6;noZUP>NNXvNUenAU z%6+jery~U-q9+S^IbGfmwOJO_DO zEzBW0?p0a&^Nk5k9_x2n5%>BjQ(JEzy~+5G3!s-tD=)+hD+FX>9_pJov#eO^bb ztK~C9SA`apSFC#ExB8|IE0a_*E$7%bnb?*JlzA|Ls0@Y+se_JJX-n z)~kM~J+XY-6|Um#g+Ht%?bsVQ>-hrw|6gGErvB~I_9x$;{F=OoRY3F}^MOB%JgZEz TaygEGawvnRtDnm{r-UW|@zu8~ literal 0 HcmV?d00001 diff --git a/applications/external/jetpack_joyride/assets/bg3.png b/applications/external/jetpack_joyride/assets/bg3.png new file mode 100644 index 0000000000000000000000000000000000000000..ebb1dd66ba4e92493aacc09d90e44096d5035693 GIT binary patch literal 886 zcmeAS@N?(olHy`uVBq!ia0vp^4M6O`!2~2@x4h6`U|?*>baoCX4sv%=@N{)HGqf}_ z(>2mFWMI&kSUT~%wo9PMaeHGQWw$Aj8e;m1rY3H_Ia6k~Ofqqk%t@Q~bK$e&ulG(& zS)H?7W8=r{gYgH#BDGzYq@130!@%}=WtK{D-t&3q|K6+i`#z8RTt?Vg$*pxKo_1HI zm@GMS?3u`SVZB=)g8J>(N!|KzIL|r}qH=DQH)0x$M?uQAhUb;Rs zeAqd4QQF688^X^X5pVTxsofP5@Z;DeVKe*aIop0-PO_EaY~CCh;raj35&baZD!+(0 zzjVFbYyRCiUe&2SbN-wzHmPi|;UB7-Mv{pZT(m zywh3cInT9!w$@5i2=5KKX!U(TrmUa7R#cMoIkN>{YPx1s;*b3=De8Ak0{?)V>TD zL32D^978JRyq$9}?~s82>+Dtk|DS%xc+%-eB>Vd5mIlQxKPpdNImmZaZtYh2hd-8b z|0ok-Sj-^7dO(BW+52;|`yPAv3BTDo>0x$|1|kfk%CpEu|~|2b{%-)Kk1G3C{h=b)tTch2l(m*?rUELq0F~>*cz6 z?r+ZMU#zT$jn6i%b5s4zx|gSBabj@WuD-~^3bp+!o_&`xJ6gKNcc*#1RgEr?KXqDa hP}l7MgmZqf2kd!Rba~T-7GUHtc)I$ztaD0e0sz#Bj4uEH literal 0 HcmV?d00001 diff --git a/applications/external/jetpack_joyride/assets/coin.png b/applications/external/jetpack_joyride/assets/coin.png new file mode 100644 index 0000000000000000000000000000000000000000..a2b5a409e43ce74ce6d84d83d8dda61c67b74d5b GIT binary patch literal 1842 zcma)6eM}Q)7(a20fx)N~U6hRT93aE?u7^~5_Q29sVbBIj8K7?K(Y|d@dLO$xXd#9v z_z~v(gDG*$uR*4Psl+U9#%#kab1c(se(Xct7KquhEsn70Viu=^y?5nP)a;M9*Z2AT zp3nDro|Y2l+QfuK2?&BD78clC@EfO|n3?eRwE4_x1c{x?x+|4RM=|9UI6dPNs)0Vj zi4aE+b6!Mbyfr{Ut3iO}Y3$Dr&R{6(qp^x?2jLK{AjlRpNT962>Gn3%cuhVmFE_y) zp`ZZ{C=43mLcB~xXiT$9!T+il$54%;)X-R^qXe}I5Dl(0( zu>Ob8Rb(0+*4p8CbQT;pB@tP%*TF)J7IjKMV|mN&dJ*6*APcpU7uf3nuPoJS0liUH z2`~zf3~B)#fheS@@cy=3G;$oWN+zWW*;qshS3<GVpTr7QKd8 zZFo>rYaxp9s{UzAlmuU`7x=Ugpt&e23w|ZcNWc<+-9uv*zn_JX)}wGP*#HkDltf8A zpanAF5#Nrw^+VCa;0;)>4(RUH@~&4DY*WRPGjmRIg7w9h`(<7YoBRHdzPog z-?qR1mu(w*yX;MkpKl2DU%Iz7Y1Ipx(|dlu5y?sJ?D=TmvZt@+RMpYp)2|%oGP*M_ zUc}5ty4ICOTsbFQZb$#Tq36k)sn?%m_NFbI-)p#SYdhp{R^0iHRyMThYekVuJ-qX@2e8;HO{BdAh1Jec!VOHEt*;&Lurxi#x9aVzbgH4b@b3zz7`H?FH*{Pp)8zrDDt=BChH`qS&reRAHQ zoc*{l_V}#t4*okRD{+^~@4QoV_1d->CSm=(m{t1%mxpt9!^c44ON!lg_#GJ;Nwa@} zo`{6uWv1eCmW_-cSI#9`RE}6sB_qh}v+9}AmN|Pd6fP*VS=>*wdqof9srAf>-;AL@ aZiz(}u8qk%++N2+22yBu+IsRmP5%LQYG}3q literal 0 HcmV?d00001 diff --git a/applications/external/jetpack_joyride/assets/coin_infill.png b/applications/external/jetpack_joyride/assets/coin_infill.png new file mode 100644 index 0000000000000000000000000000000000000000..ab37874ff8b25de5a5d955a376dff3534ee4ed41 GIT binary patch literal 1974 zcma)7e{2(V6u)g@8zozun90zX;}%4j?OktcS&w#OYd2cp_N$!~voN~$+xA?0SMILs zt>9$17?8zeFoJ|>1Q$t!kc|l>fK1~MGEoqf;K2C<`NJ8(1at_P`Mv9Yl@9TbyY}Aa z^WOKp&%O6jfRABK*v%9D7|M7|*lHb3&>=JMGu9{%?9mE`JKE?rc(CFk zl~JN#fFKZEs1yva0wtL+B`yX3%Vr!y6^hts!kly!YUX%=>NGkHffcDxBk%E2HJ0*m zXZU2o{Gu45a6A%;Xd?LOZW zj_GRP!lDVoAdZd)2NE+CofXE*heF^of@?JdKAj%&b0R1BxzJ=raXyo9f}_ZIA99Y2 zB+#LJGoK*2LNk$R`oHPHU|bo3Sk?rE7#B6Jgoy(LM{@|^H9+9Pyc?7?0anaal#q3y z%v^xu?XWd~3DZuQs~8CcoE9bsSOJP|!j?RMtx3QpC5y$RIS&k(ueeP?7MBRjJb-uD zc_cyRs|lT2YjBVRMQEu4eI7wl1o42A&1|mXIWH4{0fn07rKqjUtk)X!dNrwu?!sbV3hHZ2T10-+_DxXEW~r3up4+3Ih65|QGxS{5f=|ie6W8^Sc%umK-HR2IGK!( z1w2Zkq=wKG#Ct{j07i^|&ssf2lH=9kGv=c-^+WTCq5mr%IP??w#JuA3NmP40A0sX= znF+a!c!HoY!nCaf>xkLlwu5UEpWK`9VRG{V7B)Q(_v)qXSvL_RX%hn*JE5TwToy@Z|GX_gx%1`A2Wh z51n&V^-H7s&Q@mbf8+j7JvrMlTTfJ9-gWo%*7}1Ny)Qb~R&3Dsb+_EOc5-0HIhtM( zm^<2#1=@(B&*pip(TjBdxdq9$FD%R({3Wgb)*@$T-NL?4RL{=ow(dBCk8F2TuK1>` za`0@=&$q|8di6)g_B+3y`}zlIS6Y7EG?H=Wh0A~4I~9EOnPsO(gLD>Gd9Y&_4y>fi5M_UFhA61HgAceTiqkY>Rw0HIHD%TF3 zGs7V|5!5jWf1pd8Q!^3cG)#wK*_fh`=ztIwoW?}O{ zjYvWP7Z4~^bU7KG6t$SFOTu@_jAN)w5z4ig*_4awSq`8H$^@kf(*>g%&T1p`4H=Wp zaHYi@g5V}`yrQB)SrMmXIXfP!R;zIpffIxRG8DX*5hzi?@KKV4U&8=+3rD*Jnq^Q) zlPYCBf)>Ldj!udLk)Bp(_{sEPA#e%7W0fj=CS9Oy&!J1m9J<@V3M}tn-BSt7+F}Zx z;|R<5Y!{JoKZ|TK{Wsm^nw17Gq;Gi*+CI)6&zDi)Anq@ z*d@R(Bip68oF1vMg$xZdA*Q9w)0h@ZBJdfz6h9N1>H;YTXJV}|wm4aE8Zzqjxg2Yw zoe<;mGt*FGx;`;hotUT~l>QxH%Sh-;+q?!!fGk2q#3@t>3L>_EAjr6QG9e*GMUX0$ ztSqzPBv>hd`aiyOo>2|-Fqz0Knip8kE1Ud>d@b1MU-_3#T0Y$R(}PnvOS8fOpyKoM!zZJ| zrI~vgo(7+jfJP?F7*goxx543uw+KFUjljj!MFbdl`*84@*;qD6AjrIJG;GbBmLSL* zb)LGO7(-rIXG)x9*F<$%vH4v5aa;V6i_U#@+w0>q#!nycj;_A4tN*>Ii|Ynfu5wP4 z{2AU-*taTV#X#%w#Oxlgt!()LK6$M2&e#y&HnzSclPK#uGLo9u(EshhW+FN1#MyV; zS94xH9dPLA!9{CKrrciL-IK*5edWDJx7>NRygK@&rA|+W{^;S`Rh=J3uN!UrF1f>1 zMn;C~LLUrssW*~W*Df9Jy;)G#uIP)p5z=xdDCUdc-GwcUeWyD9;(i*qTvO6kd%ymB z17j;GZ4jdaskDH+2>ZT}iF+Y?25K9&_(1yp%IoZtWnJ~hE*(F)y?e=utG7m~+pyly zC3_D=Tdn3(5C1-MB7b762yc^h`81_~6=x;y7FYNqT+}5>T{r>pNfhmUT zE6eY^;27FEdaU)8!K$66Ve!X9zI#Pn_26Ekm|S&O?KuCz!>ZDrRgao9Z&}~|=GVvv z4-RH_C*e;J`hxxTrB9PSf**_p?yO=SK^BHe&pf2DDFPaEjE2;Li2a-QV#GvP{nlmqk mxl*{Mb7*k)@VQ%$F3%_PQXgr5c`SX$5Mz3d;mfoQ+x`KUOK@NS literal 0 HcmV?d00001 diff --git a/applications/external/jetpack_joyride/assets/dead_scientist_infill.png b/applications/external/jetpack_joyride/assets/dead_scientist_infill.png new file mode 100644 index 0000000000000000000000000000000000000000..6f036fde2f35d695c8eb2bb4ec27bb84f44de90c GIT binary patch literal 1900 zcmb7FeM}Q)7(ZN8EP=$a5p-rdk2t5Sy>g{UPs&GIutko~GO7}>v~Sy!wpZ@1^gzbc zm}Pz?GBM79%XB0rgAtI4*<_MsI0<8uv5zqPn46k7m&r15INj#nyH>v4vOn(QeSXjT zd!FZa&->i&{M>bsvZXQvK_X2?Lm_-d!D~_YJb2%a?@2|F`8wKc;Vo1SX=6MJtDPwa z3c=%pID(|42|lZ>0`O=#aME5aHhQK9LutDfD^XKQ%BKe|+E~MaqMBT@t){}Jv14he zvJ`=Y1RlU!QNiQ(a-^WeBwiA}i)I`{B^SOzi&?0ARL`&gRV&mAC6+2fQ&_u$EHq?= zHp7(`bMd^7#PL8Npa`fG4C}-bG#U-AByfU|Lk~Gu?d7e4+{?v@6hRIH;A|}I<7vi= zik#MR#?Nap4B}{r90>FzyO#@<4~4)*1W!;X@u}`U7sE50i}6ilG;PlWJZmFQJ0259 zL}s}|`R0xwSbcLjQ`En^dpy(1;P}icC`3rqX(c=gz?rLk051d_<7aIkvkG|mI7tan z7gEo-8MX+v2GC*&ljeL{0B(zc_5d#jMTc183~<8~P$OXzngvNI#tu1EGa!>*pU*N5 z+6^(TaGee{W$Kd>G)YNvLJ^EF%1D|#Rwu{-UMKI;VnO;zdkJk9&7qVeyI7vvFZM^V zmxh86QdkMHOk!GW4S`RIvSCgLeln7Ry=}MhR)dv?i4saeC0DBDM6#JslImnqsft$; zq*5tGk^C7u?Wq1QZ?T`z6zES;q><)$hOL%N!6q*PmBCeT>87Rb7W-TDx7x(&w3v@& z?0y@tOQEIi7g3IJ@Bu3eGMw=9YOxH5gN8a(qwwI+PA_0l0)_g*Q#p+@-~y0wqPhe# zL6E9MQmqbEhtHXh)N_x`Cy4&ve1f}X3n0? zH~SW-I|{ONrmtc_do}lYV}a`0-7ChE#a%@W!}m^I#kaJy9>NMw0?Iwey;Tr*gj+ja zdwcu}_u+V{?>w=(qIkjb#?KnRtZ!$?)N3tu%`%+Y6~48fSP)lV**|hIQn7OL{HBdJ zF8o#Dzhhdzqa{m7U*gy-n1xymlia=P(($2@?q56Fzi)_;m94Eg-j^43V$YvHv@h8k zwc|{|@ZtOCc9*pdIbO1C&fS*O*<5$~T6gck0gB3YM~{~;2D_B0pT;_jHA7U-!18%x zgDc|uuP^Mmv(nN~x}x)A+4B+2#)IeaTd$k*vcJyE>+fs-aqJ;eCjaR43Crc^SKnQD zvF@ksw<7PoIQ;vAvz}L;O+Tk?x{>|I-fe^9t80!mpBmMDvb3q`@cX?nO>O$0j~+WR za@xH7_P2f^b?5!$#4_D4?ha?#gJoBA?&7nz=5+<$F8X54z{_F%ZLa9=z@;bQ?`x6I zxPe2Eh-mQ&L*8tPg+_zPkYSEJwEw_*8Im5Z^W6Nqq$;LhY}57Czpbi2&`XW>J#8*c z8ojwSht8=S+n7_>dAYlG6YVLD+SK|^*U@Fj2M_N)aqsFg&y{ZjIu`BT1p~-tMLC|QUL6-ym-vT2cItu)@-`>9mf?kg!%=v7-DHF4i9+AaPT5(ap zLjfFuRI>w=#a4>5uoZU_UNv(6==TUr*wsj$+$1(pTHHk#%4s~eJj-k=FSRM{$n121 zDu4k49-Otn0gv0uU;#D4+r_{)w~Qh%Phm^dNWLi>){-<1%SCdL7)ckvD%$SAa&&Y1 zHiMZOaj`6gp{U>Q7x`r(l6ImJrBaECrKnUY1Pmb)^s<(K(92BaOoVOdaK=Ux6ibj^ zn6qiIl0H_AAOMG-N9Lg-#=T5m_Mi@Ez(S!Ckr*9Frd%XTGA{BZTO#m~jd+0Q&tZ@o z4jDpc3CBz595RFsYHc#TIt!0yP$CSgF9U_>E9#(tp6AV&gA|VD;0)=bZMeP+_p(#@ zS^#gDmc}hCt^-4jNX1f_P^=J&Q_YfDn0OYJnwlb(ip3GKiL?`r;7iDI86aZ=*+3q4 z3v2m5{7AtujvWpPNb{8u_3zP=ZjuJUfgEIfu>FuxtIeiK2jK=7lanzAHtMwsiBh2u zN<|W$%bgpRO$2avzK-zVUIu6kcuso(EEotX`a#}zE@&;_AZ(C3SSY4_Q1J0Ait7e*PBVVS3WJ)0#vf$U@4~rqz9_ATTF}1eP_C_?ho?e^B%tC61MUH^FT4is7jRLb zk(&xkB6l6`1uCM!O@_zy$RTLhG6Lvfhz=kqx!PBKcK#gAxYe`YQ70d7h!++a%==76Lamn(|p5nxi=i-OSYdZffyj+rft-&{DoPLa_bm8UQ-DS;!dEFDw zZzGq;{tz=Gns#j5aOKZpgY!Tkb?JU_QDIrycbC>oN)kn<9Kq7l(zFYolpnL6e6Olt z-_yfOzmg|(#O+TVRW+k=b6ow{`d>~rPQNek&upj6kBSbJ)Ql4R_Coe(O`*;4n`=92vU)b2sW?|V%UZW8xc0_P5xXMu=BXc3-yHsz8cnBoN~K+@%+cFP4kde^aleylp3Z3)bCj>rt@kFD=5N z4epy)0&o9%p}{i}^X|ZRU2i>;zNczT=f;*J%X?qDRrB!tPbK$Soh_j$Z5P3dG?FqD zFc1{`12=|kN{Qv3vu2}CV>YbqS#UOXnK*joiD%Dn)w>&Zjk~_>f%W~<4PA-3vYJti zGw)28sMGZx?Pz--+?jW62U9CKJi_?~6v}YqT|IcHUe;)>Snf)N4(^RAt<}^eY9^#T zi0j%?ebzkj>nnHaw%_h;>X<<8Nn*Ms=QA%Pbj3bZsxut_G+eEilDuW!;k=zoW41Tl zdwjJLQzU#==c|}vA0O)IGR8$E_EMAYKcC9UI5vFS+>28;T~r{9P3N*i;}V&}X;3G7?urG*H2ZM9KF{y> z{NJA6|9@|nl@=u>q$VH;l4L8kl*4x_d=j6CgTK={tb+*hWD4VOicWh8>EZ%vistHo zS_%Xqjv$5tDM-0|Kt$_+hhdG_(Bb14%FsrvI>)ZD2hG6C6xZ{hqQ2DOs`t6{G**zG zV30^?AOJ)Pl>&ZNASENF*d^hAc^k)2g(CWlnA2W{nmHbzIqDp>2Fp)C4Lt28%Pq^s z4#SlZ^NM1S#PM)AtPW?XIo^Y7^?E(7A#j3FL550*up%X?SRqX|5w&3ff{SN@BEzw$ zY?G?vLZT7FAdWtaED($tXN9rsVI6RZ3gTL|2A@a{dO493yxb#>#Na6hfy9jGFv*RK zOreX6`w?^*nL>xPw%Z?{MIbOK5ka)BfrS_=>ZE`k<}ElPL4cP7feZ02U|j=PF-@rj z^hTO_K#9NtA0tL+h%A*RM@48IL>8I#JgL=WY6y)cMz(V_-R&Y{gM}vaWd~8JHXUv?R<6&?xhb$%7amZ#im+_pN@k2}~FDgWBRTMIgf8kBFAv;`;+sAg!<8?!aO zUjkT<42;=P0Qr9WAz9#+ECX*e>Oz5>+lI~YNkMP%b*5DNMtc1h#VhLwxWGh z0bY%+qDw!cG!Tj^G9oFL?B9q5d5#Xb0Ig&R%|%gxbBke$2PO|xkP$Pv-3*L0g2LWp zJS^Z*0);h&jX%j|*bC%xPL|dLwcumX;c{C)49|d1Nx+~GCd>hNF1iQj3mjB0BJS1cJA}MDYj51S zB=p=_)mcQW?%R9Qq}$&AW$P{?FL(c^F9pw+E;uy3t-Uqr1-rehH-D(B=4PL-w|(7> zmwk;Hi7Ecj33L1Is|_dL%%~dP`E}ljz-lseUjCfh*Lc&FycJC;BfY;mnvbjc(yk@95>H0?ZiceH!|GJe-_=TU9^S+j)-G;hz%kOGyNrg5RW>WhD+P ze27aOiqTP||8$Z`<^+pt1O%D&mHbTGmN{zy+_c#&CP(~^g`2x( zBHF~bl;Zl_OBsugRWJQ<{kHI`h4DXs@oawjKHnnK($5<4oGNWa{+{_4=X^6R#$y2NQ3`ghPF^TS{9%;$eTx`@VVe-Z$^P=PSz#VbV&(`H*F89JUINcox2Gp%jH(H5yu zSI55&KVU>{9j|8$#qbKGP8aumvbMimw)VHJoQ-FuNAjTt4jjTw6gmwz&_W$Yel_?P zW*MW1!nbu?F;-B4`2=N>nWTcJM^N6ktJ-SuL1#0pbX@1$(`32XY$luOB=c)>DwoU2 ziYlvW0vL&)<#IDjxWS}w5&I~T!1AfbDRYtVX>PGyu44>1>NqDMJ@f7co%BHnSs-#M zsmQ%_ug*9N>dfm#)VHS#pWDc3^^$W0a*hu2U3@}ldKZxmJT>o(RHk52f&RU zFr`LdSG3rx!EA7v9)OA_Ck3Ct8){Eg)O140B-B({O>61ndtw#m%_{=Kr2(v*8zCflP$#Ai0Gwyt7} zra^h&8-}(>1J3+bWQsd!jqJp$c-f#)3yCHYeA5zI=-Bg_y=xIWx`wF51RAg^Z<;=t ztw9axc(z)lpjrz-chZ_me54|{4ru*8pUpZE_0xaTIZaVJ*~u5i6BYKQ@xhmN#)O{91bM_Zn0PQcHC iw_N$t!O02w+`cA#d5qq_dp72Xd0(1eF20@HeDW83_Th8@ literal 0 HcmV?d00001 diff --git a/applications/external/jetpack_joyride/assets/pillar.png b/applications/external/jetpack_joyride/assets/pillar.png new file mode 100644 index 0000000000000000000000000000000000000000..61979b393acb5f4397d25af4c2590f7f4b4bb1cc GIT binary patch literal 1880 zcma)74Qvxt950GE%J>Mv&?#~p$TpYWUAuLqXK$oy3oWd3jv^Zw8@KkgJ=Wfxch~h+ zl&^$OLk1fk8WBe_5&75*i%~I#5tNTPaPqO>bbjE7K!9mP#USdt>*zKch)rJG_kO?k z`+vOu|Gf>xMRP`L)3q9nX0*#`FM+>f;mA#S2!3~u{7uwo9+}8^N~KbFA?fA9813V# z049fdh-);KSu#(1tAT`6fdIo&Xy3<2P=xVO=t85LaPu}0WSlVp%#Rg$ys>Jp*@w=` z*IHx}3WR|~BXT&zilj`TsxArtE6X^Fs1&K1LQCDnh>a5fV#JIXf#z!wi{SH0wF&v91 znsgNxkth^}IC9UkFrN@-#s27F9&nlFaXm)hgUNi5lQ=QR-DgVzPTB}$W*~+kW)vie zE;0W5&=n+!4s-2xKRAkTct{|k}eZ z+5|vLzzzq68VEyyvyxgPyYk}#3EGqVW;K_tj-&d2y`??X14AekJ@hUMX-CHnvH z6A333cHAjgnh*47V2_OpaRPJ>#*m{rI{>+Cwqk+vGa-nHC36Z8m&2B)H|OQ)447W! zD(6NPGcpL3+LL}Xdo`AoGj@I(*dxM@kzmi*1&op zU;_%!ucHFW`Gg`$fiGYgc%pF=DpWdvQK&f&A7rL12~t`Hv8aVaIvG*ogj#he-beGn zOYvEJ8e-H2LJ+l#NZPB|r%+ztd=W43sZm0?I4W{}DM|~#8h{l5pz65vf%(s8;%qZzi6f%TA72O3T0mdKXR=M`8dteZ83$~@MXrYm=P_NQiV?jD}? zg4O<-wPjO9<*M$>70)zV&2+NzE}S68#LLdX7p*J&z|Bvx641QY#62e?u}Se2R9?5{V(sKH0_AC zZtQx|dA@1wwx;cA+jpF6D7w70{gsX#Gpk--Q@f$(5+#E!uZt`f*3yDd!f`oqs-PiM=fEE${hZ-P%dV zZeLlmXL4p*>W_CW5*ss?EL*$3(757m%Itgx?s^`n>bB2Xy^}n|b X8$D^dPo8AzRUSl_qsZP|uw?bWG&6$} literal 0 HcmV?d00001 diff --git a/applications/external/jetpack_joyride/assets/scientist_left.png b/applications/external/jetpack_joyride/assets/scientist_left.png new file mode 100644 index 0000000000000000000000000000000000000000..a9e880b6b2e1b8ee4a19c47c48b66bbcf88bb29c GIT binary patch literal 2056 zcmcIleNYr-7+)$HC4pu<9Y$tsXU(+eZtoWE;I>-{xI>QN2;q>AzSz5a?`{Wfcimm& zz(i}9Qe!G;s4%I)X>ueGqbZosH2i2rK}I?eYDO!WbTGnE#y&=+cXyEwQR|=n*xh@d z-}C;S=lQ+Q``q@N?6r{*%OVg2iL_@}bK!R(e4-XS0N=v(=1BwzA7Gq?QlTS@baMe6 z?crQN7Yy(aM-WSLkf+_HKtf%>$FLMO-hBu|84raO7#(^Cp9cI)MwI~as;&??xg{~rA#|b{%U^bg^J%JO17J6vKN>-wST2@>kQ>YwPAi4#H zml%#kWlq|~l}i+cK^&bT2SH|rofW6bheF^of*W*te6~C9=Oj+_bNqBhbM{Qb^EOJ1 z_a1R%WS%>e@9q(TG=Dc|hvWY4fxw(HM9H=h3Na<>oDyya5S^7gz;l7fl?!fQ+Xz@` zg`$M43z^21aY7z!4M1Ur8FLO31Z9O*CIDCwicVqXJFuczm?1#{r!`Ya9h?Vp#@_+i z)6#MT&dZcROw3)IirQ^yCWF~z(h@o~zAPhY570i41z4Zthk2>|Ge803k?m?uidinN ztX%Goe3pTN5K34DDxAS6ERn!xMJc)~1doh#z~1)I5^bd=m?)tq;uKUIyw=iNcG+_*fvI1WM@iI#tN&T@`)+ z8E;o-FgXbWsW*_u_^Imf|2H3aC+Ai%Hy^^FsBIt(iTBM%xdZpqM@9dWKC|*scioZC zywji3$AZf<>Gtti@OcNUI?NhkP*>Fkha;S_`1F{Ci|IiPSU8;pIEKFmAFM}^uvZy) zcjnz01X*2IUe_OIU8ngj*}LtAUvsSImzAxx53e+x$Xb>;5Cir%R$lX__rwuU(dl$UM)M>Y^x;&|9gnOfpUybbZ(kuUZ(|xGW(fIZL1G{w8!It-a zzi{A_btilKGJn&IAI7YYCP%zmySQXqb-HJH&q-u+$L`c`+TTg;+cDBY#J0LSvg$QO zRkjx!Mt2^qxwNx3;d1um+o0SyPG%0UN!%Ci|iP?Vd(n!CnE3E z%jTM&qfd*V?J{Edkgv`kbanHehmXCT9;#jXcxP8!-PX=L|Bh!*MuzaEo7VquaU`rK z_GZQ z=lMO)^Ly@j-?ge zSb15tEk;9v5KtI27UIJ)9kXK^FAe{z+c<`5CQ6GP^SW!%N} z31L*_WSWJDV#hFuqe*fQ(sf>pBq~KwW3g>@B z92FU64&{4zgcu_}%-HRIym=@zqzqYcu7W}&MIBPYU;x?EDgwM7$U;Q&0p}_ZRtnOU zV0|fnF(UC=OIRNcco2f7GE9=xL#jH`l>#qF4e(O}c8t_$wC!3h26(T7gDx^`k{z=? zfHkFI)K=Xi+8^;L6Fm$TABS}hg>Wz^%VveT{*69Dr- zIG_Y!UONAMppj$MeLW{_Ta|;4sNd6qFb4&sw6GfFy^q%Up6-#==D_HH*_&B=-&wQ5`X zP&9y*`buB&oHh;WJgD}JPp!_5iITuZe1O$LYty2lvfx*uj07qIu*Y_+!tdvx4y`C0 zG%gSZ5=xaasY?JZQr8Fw z!@EO*m&BNJBPtLiql1H!I&4A_WXAf)`rcwkUH-{(Q|I`)0@t{_W7*wxd6Pilj$Pck zTP>#kYn!(``{^D327c3x{O2s~hQD*ZD{6Va^DVw9Z_@3pzwYT_&)oQ-|KR1e-rq0D zN2k;jPdI#WWDmbIr~BR#xj$#$#px@8yT?vnGRE0hwfgAHqKo$o7Sqx@J)akCyLVw? z^U5U|i{HK3xpPJ9m8>g!Hhs0?*4yIi8@i6KnZKsZd~o~C?mbn8c8it!Yxk)|rTIwP zf&IlzC;F&`^^4|um%cH3qjAQOJLIyTIs<{gv{hZv?Rm|U%Z-H-3Oe4*cohuvy|%f% zsc!1!{*C4C8fk3Lf`xtFLRXjV@Xm3&;x9JOx&7t3S$qA7#K402qvD@jA6`^XX2Z2ht3Qa89-hJVg-XBg_@d3c)7ieO zXk{CgfuB5E`sA7750`xwA2`(8_sp6LSN9s3z|--oFMWHi1iTmcW!%fAZg|;e)z0%c z;X9E?6gp3#`(shK7^&3PG$azph10pPUr3f%-6SJOPM-Q?AX~PMhnq89jtWm^k1sPf zvOJR!muE)c)4^1s+s0HMK)8%)&u1S0_=U;W5oGG!>`ZP){s44BT+SNDzB$WZ`3G@U B(K-MC literal 0 HcmV?d00001 diff --git a/applications/external/jetpack_joyride/assets/scientist_right.png b/applications/external/jetpack_joyride/assets/scientist_right.png new file mode 100644 index 0000000000000000000000000000000000000000..dc40b560de9f5d5212491fe62a09f849a9ba8b56 GIT binary patch literal 1917 zcma)7dr%a09AAwX#B2g28T)5j8u8KH-Y#&=Ew>QdAsn)VaN?n8F1LGkD|frs?&9sC zW9EdBj}RZ3jyd8Vj-e&K(k#Pp%6!kL;E;`GW20r`BMe4kq~GrH6s-Q(yZwDXpYQ9l z-~H|fMTNQJq9;Z}5H!w_XDUi>j5H%uutp^i z1ULj)GnF9iu4HAng7tDdiQN0D8G$*5L}sT_CMsxSeOz9($d*(WI^ETkZVQ8CW<*;R z0t5)KG7T#MKQ9pqiD+>NaIczC1lA~WC5gDGBG@K~ESzdgHJXr&XxJ(;9-`Pjz1JC> zNW>@0K>|fXp^!0@Y!pNO$BA zzaW-?(O^jg8!#7f3hQ^-xd6*cKy(tZJcX4F!mtz#?AJ^WN(l^znfw&wu-S@4!Nd6h zCKcyq!;T!A8MBzp2HdFUS7js|0ouzJu)J6Hfx7hg1Hf#KQSEw7npthnU!~5+ES>{` z;96P@at&Z4l7^#$vXqSofk#GAV73`rrtP#03dK!$vcZ&Uz%eI|6Ui8XrA;&8gvq33 z(f9(xdBXq2R_7VE0zO3%d7LB*VpucjLtetZt)KLxpVOvWoo|&-yVdSUBq$0@m78U> z)Y|l`s3dsg5G}GQ}5wnf~a|=5WW1bE~f`PCQlGJ~iLpJ?H*}rde(IF;m+2Pcj!=2zwSz z+AO7aExFruO*-0D-js_kY}@r9%e?8ziH7ZXdfMJ2^Me-)UpW-9qp@M!n-o>lnsIMm z*@L#q*2blG->R%ldLiCl)naShd8_8+dr74oTfR$g2`nTgPRNMsye($kNS{#`|ETpB zXMMAwE%8Q7)8VLTpGJQ;t7%KyftEkTpW4r^oO879uXW$sdC#1RO-fQEogFbV!Mh=* z`(e!5_PR|=tJ$25ORft~UOTgH_vtb&>Tl6#R?%;Uc(=(zJk%Gi@XE6dN6Ti$u}Qe>w6`qaw1&-tz`>)5^j<*PNT zsN2fr9n}wKi?vtRJCyXAofhA*cOTbOT*&`>tMv_L-Z#Hb?CfmFJ)ed?fw<$|TceZ8mW*G@S8qnqOke}oSv zG<_C(oIEmrv}M7WHIXyOWpks?mM`vFbn4qn=NzSz<1Zh))19ADzHMdmw%>4b5_e0m ch89C7N^}2Mv+B#a>Q~c|Q)vG@d%^O50K+qvi~s-t literal 0 HcmV?d00001 diff --git a/applications/external/jetpack_joyride/assets/scientist_right_infill.png b/applications/external/jetpack_joyride/assets/scientist_right_infill.png new file mode 100644 index 0000000000000000000000000000000000000000..e4bc7def8808e256ca957c218ce88ae9ab722036 GIT binary patch literal 2210 zcmcIlYj6`)6yCz<(3T=jd4srawba&RA0cV8BrR`o zr=<6~s7@)QPsP^!$}puk3qc+gIgeNIy64h9A!KA& zp&l4xArayTVk?S?jIR+WXgvsUVLSH6{sS1wv39K1>?Yh|DF|}qaS7DKD?Gk zMTKd$7!3(RKw;2Wh!4wj%#LZiH2kk_;~1)$D2;Z^>#jsg1qq;LquEGcg=wfwV*PZr zW6ppxT-mXpqKGt(N25_=)MON-08U!1R-B-4iZVb8gWMEWn3y3fXQ>oAhXcqyi4zr0 z2%{<|Q!hjmJBC3V9UuoW?k;;+9w;9QfvX5k8VNkvTnq||AO{68kX>h24{7SnDx3oN5B z4n~2AQUql(5M~3F?;!}~RCw-Fg(Y@};XIS?K^s(XU88+BH8$P_jHr7WgoWg^ob{F%?PdWXEbucCUcn0e5)%ST2~rQja{~-o$UzDlp3JXXCLOWRMc;!*H@pMz#3cY1iE9Le z;oTv@OJd>JhTjn+rImw|I&4A_BxikOeRrOtYTWT6Q`@AfEZ2n0BWWE~nNvabyF0kG zHyTZSS2k~X^5fh74g99wanD#<41cA6m)rPW+v|K?=G2>8f7#W^p6dOfZ{LOH?qAQ# zho)ELP3}53vXftu-f?HK+?T%R+>D0c&haxAKklq8TYc!++;evf7Sod3ouB2tbLZ@o z`sIsL7QJ=7ZF@u0rPNEiHhtM};|=lE4edwQ%v;lJ-nZ>~$F4F%i^ah~iBT{&D&sNnn7&zsHLoh>_Z zmp5Z6`0>*PkDbc3vo>+7C@@^v&m>9qO!nfb#gSP`ePk7PP0WbU1%DEmV zeE0YFXFE@zdt*_!7^zfO)b#fwXHRAnt2SbJos1yqnd*~*Y}q;qZsxcg#hy`}zEK&G zW#Y0&>c{quI+TJwckSTC!&eVp%xOQka`vHX3+>%;chDBm z=?DWh4dD+qokSL55FK$67bL+*2wM!Y4`jmP$7cVSae{6TH#al4`rZ`^SXunzKHm5D zdp_UidEPctNkN<BTHI^d)W8+cIGP*QGfsIh8nSnf_m zj+cZA+(4vIuiM24q*sf{dP(>%ZQ~d!Q^XoAR%tY$I+h1$hB8B`!geaq9NuOp&H7!7 z!f>UqISvK^$H3%*}H#Ar%ejEo zNF5I-5$NHg#RwIVo~Fu3Q)iSDYEqp+rf2U^5h_)PY-DY;-S=N)BAZOlf@E?f8aYX+F}UWf>joVtMEsj3Hfib{Q(t=}bIpr(F;e%mw*qkwKTK)?{X; z5lXepmClVc(O%%H)YERj2(ZSqm^v8sGH6)?lx>&6)`A9t3Z;`Ji`}u% z+Tbd)lU;!7f~dgSMGwV;JO`{GEtY4u)6i2N3VV}w zFn~u1lu)Xa&##v9I04kS`XEq54W!0H=218{r+C+ZG5ug)6MG*8_rhL7`Ao_7o#_*KRbW%^O38G z^^Va~y|>R)%|!iLJfmoTtn=L){pN*2&Fe{hJ*Ai30zvecr>-&cLkp??nbg;A?muN| z{ekqRHn)hEzgD!zl=j<<$FrKZ-uYkbd|Qk|Mc;MsE6El=CP;dXJU7> z?SJC_xrV9Tu@e)t@xxAT~bYY)eKQ<~L8+)X&yQet@- z8<~6I;E|N^X^=Ti4HO>QW+C-e$7XBW4&SIve3oYdfj_4+8(vjR*EE?P`m=GjX&|E8 z_m%G_PuHc$`Kx1=-gidMJRW(v=a-{<=al!Z_^*G_cW%%xK6&D1@r9``Q@V$)sQiCL z&-P4mG1Efx`wxB-ys@(%H%>>nZoN3%b@$T22tLU%H!=8oHoTHzxI&A7Ah8M36M>xQ z+5$IKMf$w*EuGfC1A*M+ + +#include "background_assets.h" + +static AssetProperties assetProperties[BG_ASSETS_MAX] = { + {.width = 27, .spawn_chance = 1, .x_offset = 24, .y_offset = 36, .sprite = &I_door}, + {.width = 12, .spawn_chance = 6, .x_offset = 33, .y_offset = 14, .sprite = &I_air_vent}}; + +void background_assets_tick(BackgroundAsset* const assets) { + // Move assets towards the player + for(int i = 0; i < BG_ASSETS_MAX; i++) { + if(assets[i].visible) { + assets[i].point.x -= 1; // move left by 2 units + if(assets[i].point.x <= + -assets[i].properties->width) { // if the asset is out of screen + assets[i].visible = false; // set asset x coordinate to 0 to mark it as "inactive" + } + } + } +} + +void spawn_random_background_asset(BackgroundAsset* const assets) { + // Calculate the total spawn chances for all assets + int total_spawn_chance = 0; + for(int i = 0; i < BG_ASSETS_MAX; ++i) { + total_spawn_chance += assetProperties[i].spawn_chance; + } + + // Generate a random number between 0 and total_spawn_chance + int random_number = rand() % total_spawn_chance; + + // Select the asset based on the random number + int chosen_asset = -1; + int accumulated_chance = 0; + for(int i = 0; i < BG_ASSETS_MAX; ++i) { + accumulated_chance += assetProperties[i].spawn_chance; + if(random_number < accumulated_chance) { + chosen_asset = i; + break; + } + } + + // If no asset is chosen, return + if(chosen_asset == -1) { + return; + } + + // Look for an available slot for the chosen asset + for(int i = 0; i < BG_ASSETS_MAX; ++i) { + if(assets[i].visible == false) { + // Spawn the asset + assets[i].point.x = 127 + assetProperties[chosen_asset].x_offset; + assets[i].point.y = assetProperties[chosen_asset].y_offset; + assets[i].properties = &assetProperties[chosen_asset]; + assets[i].visible = true; + break; + } + } +} + +void draw_background_assets(const BackgroundAsset* assets, Canvas* const canvas, int distance) { + canvas_draw_box(canvas, 0, 6, 128, 1); + canvas_draw_box(canvas, 0, 56, 128, 2); + + // Calculate the pillar offset based on the traveled distance + int pillar_offset = distance % 64; + + // Draw pillars + for(int x = -pillar_offset; x < 128; x += 64) { + canvas_draw_icon(canvas, x, 6, &I_pillar); + } + + // Draw assets + for(int i = 0; i < BG_ASSETS_MAX; ++i) { + if(assets[i].visible) { + canvas_set_color(canvas, ColorBlack); + canvas_draw_icon( + canvas, assets[i].point.x, assets[i].point.y, assets[i].properties->sprite); + } + } +} diff --git a/applications/external/jetpack_joyride/includes/background_assets.h b/applications/external/jetpack_joyride/includes/background_assets.h new file mode 100644 index 000000000..d42fcfd71 --- /dev/null +++ b/applications/external/jetpack_joyride/includes/background_assets.h @@ -0,0 +1,34 @@ +#ifndef BACKGROUND_ASSETS_H +#define BACKGROUND_ASSETS_H + +#include +#include + +#include + +#include "point.h" +#include "states.h" +#include "game_sprites.h" +#include + +#define BG_ASSETS_MAX 3 + +typedef struct { + int width; + int spawn_chance; + int x_offset; + int y_offset; + const Icon* sprite; +} AssetProperties; + +typedef struct { + POINT point; + AssetProperties* properties; + bool visible; +} BackgroundAsset; + +void background_assets_tick(BackgroundAsset* const assets); +void spawn_random_background_asset(BackgroundAsset* const assets); +void draw_background_assets(const BackgroundAsset* assets, Canvas* const canvas, int distance); + +#endif // BACKGROUND_ASSETS_H diff --git a/applications/external/jetpack_joyride/includes/barry.c b/applications/external/jetpack_joyride/includes/barry.c new file mode 100644 index 000000000..61d3a6fc4 --- /dev/null +++ b/applications/external/jetpack_joyride/includes/barry.c @@ -0,0 +1,33 @@ +#include "barry.h" +#include "game_sprites.h" + +#include +#include + +void barry_tick(BARRY* const barry) { + // Do jetpack things + if(barry->isBoosting) { + barry->gravity += GRAVITY_BOOST; // Increase upward momentum + } else { + barry->gravity += GRAVITY_FALL; // Increase downward momentum faster + } + + barry->point.y += barry->gravity; + + // Constrain barry's height within sprite_height and 64 - sprite_height + if(barry->point.y > (64 - BARRY_HEIGHT)) { + barry->point.y = 64 - BARRY_HEIGHT; + barry->gravity = 0; // stop upward momentum + } else if(barry->point.y < 0) { + barry->point.y = 0; + barry->gravity = 0; // stop downward momentum + } +} + +void draw_barry(const BARRY* barry, Canvas* const canvas, const GameSprites* sprites) { + canvas_set_color(canvas, ColorBlack); + canvas_draw_icon_animation(canvas, barry->point.x, barry->point.y, sprites->barry); + + canvas_set_color(canvas, ColorWhite); + canvas_draw_icon(canvas, barry->point.x, barry->point.y, sprites->barry_infill); +} \ No newline at end of file diff --git a/applications/external/jetpack_joyride/includes/barry.h b/applications/external/jetpack_joyride/includes/barry.h new file mode 100644 index 000000000..494af434d --- /dev/null +++ b/applications/external/jetpack_joyride/includes/barry.h @@ -0,0 +1,23 @@ +#ifndef BARRY_H +#define BARRY_H + +#include + +#include +#include "point.h" +#include "game_sprites.h" + +#define GRAVITY_TICK 0.2 +#define GRAVITY_BOOST -0.4 +#define GRAVITY_FALL 0.3 + +typedef struct { + float gravity; + POINT point; + bool isBoosting; +} BARRY; + +void barry_tick(BARRY* const barry); +void draw_barry(const BARRY* barry, Canvas* const canvas, const GameSprites* sprites); + +#endif // BARRY_H \ No newline at end of file diff --git a/applications/external/jetpack_joyride/includes/coin.c b/applications/external/jetpack_joyride/includes/coin.c new file mode 100644 index 000000000..7a3811a8c --- /dev/null +++ b/applications/external/jetpack_joyride/includes/coin.c @@ -0,0 +1,98 @@ +#include +#include + +#include +#include + +#include "coin.h" +#include "barry.h" + +#define PATTERN_MAX_HEIGHT 40 + +// Patterns +const COIN_PATTERN coin_patterns[] = { + {// Square pattern + .count = 9, + .coins = {{0, 0}, {8, 0}, {16, 0}, {0, 8}, {8, 8}, {16, 8}, {0, 16}, {8, 16}, {16, 16}}}, + {// Wavy pattern (approximate sine wave) + .count = 8, + .coins = {{0, 8}, {8, 16}, {16, 24}, {24, 16}, {32, 8}, {40, 0}, {48, 8}, {56, 16}}}, + {// Diagonal pattern + .count = 5, + .coins = {{0, 0}, {8, 8}, {16, 16}, {24, 24}, {32, 32}}}, + // Add more patterns here +}; + +void coin_tick(COIN* const coins, BARRY* const barry, int* const total_coins) { + // Move coins towards the player + for(int i = 0; i < COINS_MAX; i++) { + if(coin_colides(&coins[i], barry)) { + coins[i].point.x = 0; // Remove the coin + (*total_coins)++; + } + if(coins[i].point.x > 0) { + coins[i].point.x -= 1; // move left by 1 unit + if(coins[i].point.x < -COIN_WIDTH) { // if the coin is out of screen + coins[i].point.x = 0; // set coin x coordinate to 0 to mark it as "inactive" + } + } + } +} + +bool coin_colides(COIN* const coin, BARRY* const barry) { + return !( + barry->point.x > coin->point.x + COIN_WIDTH || // Barry is to the right of the coin + barry->point.x + BARRY_WIDTH < coin->point.x || // Barry is to the left of the coin + barry->point.y > coin->point.y + COIN_WIDTH || // Barry is below the coin + barry->point.y + BARRY_HEIGHT < coin->point.y); // Barry is above the coin +} + +void spawn_random_coin(COIN* const coins) { + // Select a random pattern + int pattern_index = rand() % (sizeof(coin_patterns) / sizeof(coin_patterns[0])); + const COIN_PATTERN* pattern = &coin_patterns[pattern_index]; + + // Count available slots for new coins + int available_slots = 0; + for(int i = 0; i < COINS_MAX; ++i) { + if(coins[i].point.x <= 0) { + ++available_slots; + } + } + + // If there aren't enough slots, return without spawning coins + if(available_slots < pattern->count) return; + + // Spawn coins according to the selected pattern + int coin_index = 0; + int random_offset = rand() % (SCREEN_HEIGHT - PATTERN_MAX_HEIGHT); + int random_offset_x = rand() % 16; + for(int i = 0; i < pattern->count; ++i) { + // Find an available slot for a new coin + while(coins[coin_index].point.x > 0 && coin_index < COINS_MAX) { + ++coin_index; + } + // If no slot is available, stop spawning coins + if(coin_index == COINS_MAX) break; + + // Spawn the coin + coins[coin_index].point.x = SCREEN_WIDTH - 1 + pattern->coins[i].x + random_offset_x; + coins[coin_index].point.y = + random_offset + + pattern->coins[i] + .y; // The pattern is spawned at a random y position, but not too close to the screen edge + } +} + +void draw_coins(const COIN* coins, Canvas* const canvas, const GameSprites* sprites) { + canvas_set_color(canvas, ColorBlack); + for(int i = 0; i < COINS_MAX; ++i) { + if(coins[i].point.x > 0) { + canvas_set_color(canvas, ColorBlack); + canvas_draw_icon(canvas, coins[i].point.x, coins[i].point.y, sprites->coin); + + canvas_set_color(canvas, ColorWhite); + canvas_draw_icon(canvas, coins[i].point.x, coins[i].point.y, sprites->coin_infill); + } + } +} diff --git a/applications/external/jetpack_joyride/includes/coin.h b/applications/external/jetpack_joyride/includes/coin.h new file mode 100644 index 000000000..41fd21ddc --- /dev/null +++ b/applications/external/jetpack_joyride/includes/coin.h @@ -0,0 +1,26 @@ +#ifndef COIN_H +#define COIN_H + +#include + +#include "point.h" +#include "barry.h" + +#define COINS_MAX 15 + +typedef struct { + float gravity; + POINT point; +} COIN; + +typedef struct { + int count; + POINT coins[COINS_MAX]; +} COIN_PATTERN; + +void coin_tick(COIN* const coins, BARRY* const barry, int* const poins); +void spawn_random_coin(COIN* const coins); +bool coin_colides(COIN* const coin, BARRY* const barry); +void draw_coins(const COIN* coins, Canvas* const canvas, const GameSprites* sprites); + +#endif // COIN_H \ No newline at end of file diff --git a/applications/external/jetpack_joyride/includes/game_sprites.h b/applications/external/jetpack_joyride/includes/game_sprites.h new file mode 100644 index 000000000..d38494bf8 --- /dev/null +++ b/applications/external/jetpack_joyride/includes/game_sprites.h @@ -0,0 +1,35 @@ +#ifndef GAME_SPRITES_H +#define GAME_SPRITES_H + +#include "point.h" +#include + +#define SCREEN_WIDTH 128 +#define SCREEN_HEIGHT 64 + +#define BARRY_WIDTH 11 +#define BARRY_HEIGHT 15 + +#define MISSILE_WIDTH 26 +#define MISSILE_HEIGHT 12 + +#define SCIENTIST_WIDTH 9 +#define SCIENTIST_HEIGHT 14 + +#define COIN_WIDTH 7 + +typedef struct { + IconAnimation* barry; + const Icon* barry_infill; + const Icon* scientist_left; + const Icon* scientist_left_infill; + const Icon* scientist_right; + const Icon* scientist_right_infill; + const Icon* coin; + const Icon* coin_infill; + IconAnimation* missile; + IconAnimation* alert; + const Icon* missile_infill; +} GameSprites; + +#endif // GAME_SPRITES_H \ No newline at end of file diff --git a/applications/external/jetpack_joyride/includes/game_state.c b/applications/external/jetpack_joyride/includes/game_state.c new file mode 100644 index 000000000..a8a9db618 --- /dev/null +++ b/applications/external/jetpack_joyride/includes/game_state.c @@ -0,0 +1,5 @@ +#include "game_state.h" + +void game_state_tick(GameState* const game_state) { + game_state->distance++; +} diff --git a/applications/external/jetpack_joyride/includes/game_state.h b/applications/external/jetpack_joyride/includes/game_state.h new file mode 100644 index 000000000..1e97aaf18 --- /dev/null +++ b/applications/external/jetpack_joyride/includes/game_state.h @@ -0,0 +1,34 @@ +#ifndef GAMESTATE_H +#define GAMESTATE_H + +#include +#include + +#include "barry.h" +#include "scientist.h" +#include "coin.h" +#include "particle.h" +#include "game_sprites.h" +#include "states.h" +#include "missile.h" +#include "background_assets.h" +typedef struct { + int total_coins; + int distance; + bool new_highscore; + BARRY barry; + COIN coins[COINS_MAX]; + PARTICLE particles[PARTICLES_MAX]; + SCIENTIST scientists[SCIENTISTS_MAX]; + MISSILE missiles[MISSILES_MAX]; + BackgroundAsset bg_assets[BG_ASSETS_MAX]; + State state; + GameSprites sprites; + FuriMutex* mutex; + FuriTimer* timer; + void (*death_handler)(); +} GameState; + +void game_state_tick(GameState* const game_state); + +#endif // GAMESTATE_H \ No newline at end of file diff --git a/applications/external/jetpack_joyride/includes/missile.c b/applications/external/jetpack_joyride/includes/missile.c new file mode 100644 index 000000000..af47e8478 --- /dev/null +++ b/applications/external/jetpack_joyride/includes/missile.c @@ -0,0 +1,86 @@ +#include +#include + +#include +#include + +#include "states.h" +#include "game_sprites.h" +#include "missile.h" +#include "barry.h" + +void missile_tick(MISSILE* const missiles, BARRY* const barry, void (*death_handler)()) { + // Move missiles towards the player + for(int i = 0; i < MISSILES_MAX; i++) { + if(missiles[i].visible && missile_colides(&missiles[i], barry)) { + death_handler(); + } + if(missiles[i].visible) { + missiles[i].point.x -= 2; // move left by 2 units + if(missiles[i].point.x < -MISSILE_WIDTH) { // if the missile is out of screen + missiles[i].visible = false; // set missile as "inactive" + } + } + } +} + +void spawn_random_missile(MISSILE* const missiles) { + // Check for an available slot for a new missile + for(int i = 0; i < MISSILES_MAX; ++i) { + if(!missiles[i].visible) { + missiles[i].point.x = 2 * SCREEN_WIDTH; + missiles[i].point.y = rand() % (SCREEN_HEIGHT - MISSILE_HEIGHT); + missiles[i].visible = true; + break; + } + } +} + +void draw_missiles(const MISSILE* missiles, Canvas* const canvas, const GameSprites* sprites) { + for(int i = 0; i < MISSILES_MAX; ++i) { + if(missiles[i].visible) { + canvas_set_color(canvas, ColorBlack); + + if(missiles[i].point.x > 128) { + canvas_draw_icon_animation( + canvas, SCREEN_WIDTH - 7, missiles[i].point.y, sprites->alert); + } else { + canvas_draw_icon_animation( + canvas, missiles[i].point.x, missiles[i].point.y, sprites->missile); + + canvas_set_color(canvas, ColorWhite); + canvas_draw_icon( + canvas, missiles[i].point.x, missiles[i].point.y, sprites->missile_infill); + } + } + } +} + +bool missile_colides(MISSILE* const missile, BARRY* const barry) { + return !( + barry->point.x > + missile->point.x + MISSILE_WIDTH - 14 || // Barry is to the right of the missile + barry->point.x + BARRY_WIDTH - 3 < + missile->point.x || // Barry is to the left of the missile + barry->point.y > missile->point.y + MISSILE_HEIGHT || // Barry is below the missile + barry->point.y + BARRY_HEIGHT < missile->point.y); // Barry is above the missile +} + +int get_rocket_spawn_distance(int player_distance) { + // Define the start and end points for rocket spawn distance + int start_distance = 256; + int end_distance = 24; + + // Define the maximum player distance at which the spawn distance should be at its minimum + int max_player_distance = 5000; // Adjust this value based on your game's difficulty curve + + if(player_distance >= max_player_distance) { + return end_distance; + } + + // Calculate the linear interpolation factor + float t = (float)player_distance / max_player_distance; + + // Interpolate the rocket spawn distance + return start_distance + t * (end_distance - start_distance); +} diff --git a/applications/external/jetpack_joyride/includes/missile.h b/applications/external/jetpack_joyride/includes/missile.h new file mode 100644 index 000000000..a5af4e885 --- /dev/null +++ b/applications/external/jetpack_joyride/includes/missile.h @@ -0,0 +1,24 @@ +#ifndef MISSILE_H +#define MISSILE_H + +#include +#include "game_sprites.h" + +#include "states.h" +#include "point.h" +#include "barry.h" + +#define MISSILES_MAX 5 + +typedef struct { + POINT point; + bool visible; +} MISSILE; + +void missile_tick(MISSILE* const missiles, BARRY* const barry, void (*death_handler)()); +void spawn_random_missile(MISSILE* const MISSILEs); +bool missile_colides(MISSILE* const MISSILE, BARRY* const barry); +int get_rocket_spawn_distance(int player_distance); +void draw_missiles(const MISSILE* missiles, Canvas* const canvas, const GameSprites* sprites); + +#endif // MISSILE_H \ No newline at end of file diff --git a/applications/external/jetpack_joyride/includes/particle.c b/applications/external/jetpack_joyride/includes/particle.c new file mode 100644 index 000000000..cf8e6e0a6 --- /dev/null +++ b/applications/external/jetpack_joyride/includes/particle.c @@ -0,0 +1,57 @@ +#include + +#include "particle.h" +#include "scientist.h" +#include "barry.h" + +void particle_tick(PARTICLE* const particles, SCIENTIST* const scientists) { + // Move particles + for(int i = 0; i < PARTICLES_MAX; i++) { + if(particles[i].point.y > 0) { + particles[i].point.y += PARTICLE_VELOCITY; + + // Check collision with scientists + for(int j = 0; j < SCIENTISTS_MAX; j++) { + if(scientists[j].state == ScientistStateAlive && scientists[j].point.x > 0) { + // Check whether the particle lies within the scientist's bounding box + if(!(particles[i].point.x > scientists[j].point.x + SCIENTIST_WIDTH || + particles[i].point.x < scientists[j].point.x || + particles[i].point.y > scientists[j].point.y + SCIENTIST_HEIGHT || + particles[i].point.y < scientists[j].point.y)) { + scientists[j].state = ScientistStateDead; + // (*points) += 2; // Increase the score by 2 + } + } + } + + if(particles[i].point.x < 0 || particles[i].point.x > SCREEN_WIDTH || + particles[i].point.y < 0 || particles[i].point.y > SCREEN_HEIGHT) { + particles[i].point.y = 0; + } + } + } +} + +void spawn_random_particles(PARTICLE* const particles, BARRY* const barry) { + for(int i = 0; i < PARTICLES_MAX; i++) { + if(particles[i].point.y <= 0) { + particles[i].point.x = barry->point.x + (rand() % 4); + particles[i].point.y = barry->point.y + 14; + break; + } + } +} + +void draw_particles(const PARTICLE* particles, Canvas* const canvas) { + canvas_set_color(canvas, ColorBlack); + for(int i = 0; i < PARTICLES_MAX; i++) { + if(particles[i].point.y > 0) { + canvas_draw_line( + canvas, + particles[i].point.x, + particles[i].point.y, + particles[i].point.x, + particles[i].point.y + 3); + } + } +} diff --git a/applications/external/jetpack_joyride/includes/particle.h b/applications/external/jetpack_joyride/includes/particle.h new file mode 100644 index 000000000..3442c9c4e --- /dev/null +++ b/applications/external/jetpack_joyride/includes/particle.h @@ -0,0 +1,21 @@ + + +#ifndef PARTICLE_H +#define PARTICLE_H + +#include "point.h" +#include "scientist.h" +#include "barry.h" + +#define PARTICLES_MAX 50 +#define PARTICLE_VELOCITY 2 + +typedef struct { + POINT point; +} PARTICLE; + +void particle_tick(PARTICLE* const particles, SCIENTIST* const scientists); +void spawn_random_particles(PARTICLE* const particles, BARRY* const barry); +void draw_particles(const PARTICLE* particles, Canvas* const canvas); + +#endif // PARTICLE_H \ No newline at end of file diff --git a/applications/external/jetpack_joyride/includes/point.h b/applications/external/jetpack_joyride/includes/point.h new file mode 100644 index 000000000..02c9a6ce4 --- /dev/null +++ b/applications/external/jetpack_joyride/includes/point.h @@ -0,0 +1,14 @@ +#ifndef POINT_H +#define POINT_H + +typedef struct { + int x; + int y; +} POINT; + +typedef struct { + float x; + float y; +} POINTF; + +#endif // POINT_H \ No newline at end of file diff --git a/applications/external/jetpack_joyride/includes/scientist.c b/applications/external/jetpack_joyride/includes/scientist.c new file mode 100644 index 000000000..b1a8a14c0 --- /dev/null +++ b/applications/external/jetpack_joyride/includes/scientist.c @@ -0,0 +1,77 @@ +#include "scientist.h" +#include "game_sprites.h" + +#include +#include + +void scientist_tick(SCIENTIST* const scientists) { + for(int i = 0; i < SCIENTISTS_MAX; i++) { + if(scientists[i].visible) { + if(scientists[i].point.x < 64) scientists[i].velocity_x = 0.5f; + + scientists[i].point.x -= scientists[i].state == ScientistStateAlive ? + 1 - scientists[i].velocity_x : + 1; // move based on velocity_x + int width = (scientists[i].state == ScientistStateAlive) ? SCIENTIST_WIDTH : + SCIENTIST_HEIGHT; + if(scientists[i].point.x <= -width) { // if the scientist is out of screen + scientists[i].visible = false; + } + } + } +} + +void spawn_random_scientist(SCIENTIST* const scientists) { + float velocities[] = {-0.5f, 0.0f, 0.5f, -1.0f}; + // Check for an available slot for a new scientist + for(int i = 0; i < SCIENTISTS_MAX; ++i) { + if(!scientists[i].visible && + (rand() % 1000) < 10) { // Spawn rate is less frequent than coins + scientists[i].state = ScientistStateAlive; + scientists[i].point.x = 127; + scientists[i].point.y = 49; + scientists[i].velocity_x = velocities[rand() % 4]; + scientists[i].visible = true; + break; + } + } +} + +void draw_scientists(const SCIENTIST* scientists, Canvas* const canvas, const GameSprites* sprites) { + for(int i = 0; i < SCIENTISTS_MAX; ++i) { + if(scientists[i].visible) { + canvas_set_color(canvas, ColorBlack); + if(scientists[i].state == ScientistStateAlive) { + canvas_draw_icon( + canvas, + (int)scientists[i].point.x, + scientists[i].point.y, + scientists[i].velocity_x >= 0 ? sprites->scientist_right : + sprites->scientist_left); + + canvas_set_color(canvas, ColorWhite); + canvas_draw_icon( + canvas, + (int)scientists[i].point.x, + scientists[i].point.y, + scientists[i].velocity_x >= 0 ? sprites->scientist_right_infill : + sprites->scientist_left_infill); + + } else { + canvas_set_color(canvas, ColorBlack); + canvas_draw_icon( + canvas, + (int)scientists[i].point.x, + scientists[i].point.y + 5, + &I_dead_scientist); + + canvas_set_color(canvas, ColorWhite); + canvas_draw_icon( + canvas, + (int)scientists[i].point.x, + scientists[i].point.y + 5, + &I_dead_scientist_infill); + } + } + } +} \ No newline at end of file diff --git a/applications/external/jetpack_joyride/includes/scientist.h b/applications/external/jetpack_joyride/includes/scientist.h new file mode 100644 index 000000000..a49e8028c --- /dev/null +++ b/applications/external/jetpack_joyride/includes/scientist.h @@ -0,0 +1,29 @@ +#ifndef SCIENTIST_H +#define SCIENTIST_H + +#include "point.h" +#include "game_sprites.h" +#include + +#define SCIENTIST_VELOCITY_MIN -0.5f +#define SCIENTIST_VELOCITY_MAX 0.5f + +#define SCIENTISTS_MAX 6 + +typedef enum { + ScientistStateAlive, + ScientistStateDead, +} ScientistState; + +typedef struct { + bool visible; + POINTF point; + float velocity_x; + ScientistState state; +} SCIENTIST; + +void scientist_tick(SCIENTIST* const scientist); +void spawn_random_scientist(SCIENTIST* const scientists); +void draw_scientists(const SCIENTIST* scientists, Canvas* const canvas, const GameSprites* sprites); + +#endif // SCIENTIST_H \ No newline at end of file diff --git a/applications/external/jetpack_joyride/includes/states.h b/applications/external/jetpack_joyride/includes/states.h new file mode 100644 index 000000000..d58e3e1f6 --- /dev/null +++ b/applications/external/jetpack_joyride/includes/states.h @@ -0,0 +1,9 @@ +#ifndef STATE_H +#define STATE_H + +typedef enum { + GameStateLife, + GameStateGameOver, +} State; + +#endif // STATE_H \ No newline at end of file diff --git a/applications/external/jetpack_joyride/jetpack.c b/applications/external/jetpack_joyride/jetpack.c new file mode 100644 index 000000000..c12f094c9 --- /dev/null +++ b/applications/external/jetpack_joyride/jetpack.c @@ -0,0 +1,379 @@ +#include + +#include +#include +#include +#include +#include +#include + +#include "includes/point.h" +#include "includes/barry.h" +#include "includes/scientist.h" +#include "includes/particle.h" +#include "includes/coin.h" +#include "includes/missile.h" +#include "includes/background_assets.h" + +#include "includes/game_state.h" + +#define TAG "Jetpack Joyride" +#define SAVING_DIRECTORY "/ext/apps/Games" +#define SAVING_FILENAME SAVING_DIRECTORY "/jetpack.save" +static GameState* global_state; + +typedef enum { + EventTypeTick, + EventTypeKey, +} EventType; + +typedef struct { + EventType type; + InputEvent input; +} GameEvent; + +typedef struct { + int max_distance; + int total_coins; +} SaveGame; + +static SaveGame save_game; + +static bool storage_game_state_load() { + Storage* storage = furi_record_open(RECORD_STORAGE); + File* file = storage_file_alloc(storage); + + uint16_t bytes_readed = 0; + if(storage_file_open(file, SAVING_FILENAME, FSAM_READ, FSOM_OPEN_EXISTING)) + bytes_readed = storage_file_read(file, &save_game, sizeof(SaveGame)); + storage_file_close(file); + storage_file_free(file); + furi_record_close(RECORD_STORAGE); + return bytes_readed == sizeof(SaveGame); +} + +static void storage_game_state_save() { + Storage* storage = furi_record_open(RECORD_STORAGE); + + if(storage_common_stat(storage, SAVING_DIRECTORY, NULL) == FSE_NOT_EXIST) { + if(!storage_simply_mkdir(storage, SAVING_DIRECTORY)) { + return; + } + } + + File* file = storage_file_alloc(storage); + if(storage_file_open(file, SAVING_FILENAME, FSAM_WRITE, FSOM_CREATE_ALWAYS)) { + storage_file_write(file, &save_game, sizeof(SaveGame)); + } + storage_file_close(file); + storage_file_free(file); + furi_record_close(RECORD_STORAGE); +} + +void handle_death() { + global_state->state = GameStateGameOver; + global_state->new_highscore = global_state->distance > save_game.max_distance; + + if(global_state->distance > save_game.max_distance) { + save_game.max_distance = global_state->distance; + } + + save_game.total_coins += global_state->total_coins; + + storage_game_state_save(); +} + +static void jetpack_game_state_init(GameState* const game_state) { + UNUSED(game_state); + UNUSED(storage_game_state_save); + BARRY barry; + barry.gravity = 0; + barry.point.x = 32 + 5; + barry.point.y = 32; + barry.isBoosting = false; + + GameSprites sprites; + sprites.barry = icon_animation_alloc(&A_barry); + sprites.barry_infill = &I_barry_infill; + + sprites.scientist_left = (&I_scientist_left); + sprites.scientist_left_infill = (&I_scientist_left_infill); + sprites.scientist_right = (&I_scientist_right); + sprites.scientist_right_infill = (&I_scientist_right_infill); + + sprites.coin = (&I_coin); + sprites.coin_infill = (&I_coin_infill); + + sprites.missile = icon_animation_alloc(&A_missile); + sprites.missile_infill = &I_missile_infill; + + sprites.alert = icon_animation_alloc(&A_alert); + + icon_animation_start(sprites.barry); + icon_animation_start(sprites.missile); + icon_animation_start(sprites.alert); + + game_state->barry = barry; + game_state->total_coins = 0; + game_state->distance = 0; + game_state->new_highscore = false; + game_state->sprites = sprites; + game_state->state = GameStateLife; + game_state->death_handler = handle_death; + + memset(game_state->bg_assets, 0, sizeof(game_state->bg_assets)); + + memset(game_state->scientists, 0, sizeof(game_state->scientists)); + memset(game_state->coins, 0, sizeof(game_state->coins)); + memset(game_state->particles, 0, sizeof(game_state->particles)); + memset(game_state->missiles, 0, sizeof(game_state->missiles)); +} + +static void jetpack_game_state_free(GameState* const game_state) { + icon_animation_free(game_state->sprites.barry); + icon_animation_free(game_state->sprites.missile); + icon_animation_free(game_state->sprites.alert); + + free(game_state); +} + +static void jetpack_game_tick(GameState* const game_state) { + if(game_state->state == GameStateGameOver) return; + barry_tick(&game_state->barry); + game_state_tick(game_state); + coin_tick(game_state->coins, &game_state->barry, &game_state->total_coins); + particle_tick(game_state->particles, game_state->scientists); + scientist_tick(game_state->scientists); + missile_tick(game_state->missiles, &game_state->barry, game_state->death_handler); + + background_assets_tick(game_state->bg_assets); + + // generate background every 64px aka. ticks + if(game_state->distance % 64 == 0 && rand() % 3 == 0) { + spawn_random_background_asset(game_state->bg_assets); + } + + if(game_state->distance % 48 == 0 && rand() % 2 == 0) { + spawn_random_coin(game_state->coins); + } + + if(game_state->distance % get_rocket_spawn_distance(game_state->distance) == 0 && + rand() % 2 == 0) { + spawn_random_missile(game_state->missiles); + } + + spawn_random_scientist(game_state->scientists); + + if(game_state->barry.isBoosting) { + spawn_random_particles(game_state->particles, &game_state->barry); + } +} + +static void jetpack_game_render_callback(Canvas* const canvas, void* ctx) { + furi_assert(ctx); + const GameState* game_state = ctx; + furi_mutex_acquire(game_state->mutex, FuriWaitForever); + + if(game_state->state == GameStateLife) { + canvas_set_bitmap_mode(canvas, false); + + draw_background_assets(game_state->bg_assets, canvas, game_state->distance); + + canvas_set_bitmap_mode(canvas, true); + + draw_coins(game_state->coins, canvas, &game_state->sprites); + draw_scientists(game_state->scientists, canvas, &game_state->sprites); + draw_particles(game_state->particles, canvas); + draw_missiles(game_state->missiles, canvas, &game_state->sprites); + + draw_barry(&game_state->barry, canvas, &game_state->sprites); + + canvas_set_color(canvas, ColorBlack); + canvas_set_font(canvas, FontSecondary); + char buffer[12]; + snprintf(buffer, sizeof(buffer), "%u m", game_state->distance / 10); + canvas_draw_str_aligned(canvas, 123, 15, AlignRight, AlignBottom, buffer); + + snprintf(buffer, sizeof(buffer), "$%u", game_state->total_coins); + canvas_draw_str_aligned(canvas, 5, 15, AlignLeft, AlignBottom, buffer); + } + + if(game_state->state == GameStateGameOver) { + // Show highscore + char buffer[64]; + + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignTop, "You flew"); + + snprintf( + buffer, + sizeof(buffer), + game_state->new_highscore ? "%u m (new best)" : "%u m", + game_state->distance / 10); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 64, 16, AlignCenter, AlignTop, buffer); + + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned(canvas, 64, 30, AlignCenter, AlignTop, "and collected"); + + snprintf(buffer, sizeof(buffer), "$%u", game_state->total_coins); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 64, 41, AlignCenter, AlignTop, buffer); + + snprintf( + buffer, + sizeof(buffer), + "Best: %u m, Tot: $%u", + save_game.max_distance / 10, + save_game.total_coins); + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned(canvas, 64, 63, AlignCenter, AlignBottom, buffer); + + canvas_draw_rframe(canvas, 0, 3, 128, 49, 5); + + // char buffer[12]; + // snprintf(buffer, sizeof(buffer), "Dist: %u", game_state->distance); + // canvas_draw_str_aligned(canvas, 123, 12, AlignRight, AlignBottom, buffer); + + // snprintf(buffer, sizeof(buffer), "Score: %u", game_state->points); + // canvas_draw_str_aligned(canvas, 5, 12, AlignLeft, AlignBottom, buffer); + + // canvas_draw_str_aligned(canvas, 64, 34, AlignCenter, AlignCenter, "Highscore:"); + // snprintf(buffer, sizeof(buffer), "Dist: %u", save_game.max_distance); + // canvas_draw_str_aligned(canvas, 123, 50, AlignRight, AlignBottom, buffer); + + // snprintf(buffer, sizeof(buffer), "Score: %u", save_game.max_score); + // canvas_draw_str_aligned(canvas, 5, 50, AlignLeft, AlignBottom, buffer); + + // canvas_draw_str_aligned(canvas, 64, 32, AlignCenter, AlignCenter, "boom."); + + // if(furi_timer_is_running(game_state->timer)) { + // furi_timer_start(game_state->timer, 0); + // } + } + + // canvas_draw_frame(canvas, 0, 0, 128, 64); + + furi_mutex_release(game_state->mutex); +} + +static void jetpack_game_input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) { + furi_assert(event_queue); + + GameEvent event = {.type = EventTypeKey, .input = *input_event}; + furi_message_queue_put(event_queue, &event, FuriWaitForever); +} + +static void jetpack_game_update_timer_callback(FuriMessageQueue* event_queue) { + furi_assert(event_queue); + + GameEvent event = {.type = EventTypeTick}; + furi_message_queue_put(event_queue, &event, 0); +} + +int32_t jetpack_game_app(void* p) { + UNUSED(p); + int32_t return_code = 0; + + if(!storage_game_state_load()) { + memset(&save_game, 0, sizeof(save_game)); + } + + FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(GameEvent)); + + GameState* game_state = malloc(sizeof(GameState)); + + global_state = game_state; + jetpack_game_state_init(game_state); + + game_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal); + if(!game_state->mutex) { + FURI_LOG_E(TAG, "cannot create mutex\r\n"); + return_code = 255; + goto free_and_exit; + } + + // Set system callbacks + ViewPort* view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, jetpack_game_render_callback, game_state); + view_port_input_callback_set(view_port, jetpack_game_input_callback, event_queue); + + FuriTimer* timer = + furi_timer_alloc(jetpack_game_update_timer_callback, FuriTimerTypePeriodic, event_queue); + furi_timer_start(timer, furi_kernel_get_tick_frequency() / 25); + + game_state->timer = timer; + + // Open GUI and register view_port + Gui* gui = furi_record_open(RECORD_GUI); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); + + GameEvent event; + for(bool processing = true; processing;) { + FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100); + furi_mutex_acquire(game_state->mutex, FuriWaitForever); + + if(event_status == FuriStatusOk) { + // press events + if(event.type == EventTypeKey) { + if(event.input.type == InputTypeRelease && event.input.key == InputKeyOk) { + game_state->barry.isBoosting = false; + } + + // Reset highscore, for debug purposes + if(event.input.type == InputTypeLong && event.input.key == InputKeyLeft) { + save_game.max_distance = 0; + save_game.total_coins = 0; + storage_game_state_save(); + } + + if(event.input.type == InputTypePress) { + switch(event.input.key) { + case InputKeyUp: + break; + case InputKeyDown: + break; + case InputKeyRight: + break; + case InputKeyLeft: + break; + case InputKeyOk: + if(game_state->state == GameStateGameOver) { + jetpack_game_state_init(game_state); + } + + if(game_state->state == GameStateLife) { + // Do something + game_state->barry.isBoosting = true; + } + + break; + case InputKeyBack: + processing = false; + break; + default: + break; + } + } + } else if(event.type == EventTypeTick) { + jetpack_game_tick(game_state); + } + } + + view_port_update(view_port); + furi_mutex_release(game_state->mutex); + } + + furi_timer_free(timer); + view_port_enabled_set(view_port, false); + gui_remove_view_port(gui, view_port); + furi_record_close(RECORD_GUI); + view_port_free(view_port); + furi_mutex_free(game_state->mutex); + +free_and_exit: + jetpack_game_state_free(game_state); + furi_message_queue_free(event_queue); + + return return_code; +} \ No newline at end of file diff --git a/applications/external/nfc_maker/application.fam b/applications/external/nfc_maker/application.fam new file mode 100644 index 000000000..89bbb202e --- /dev/null +++ b/applications/external/nfc_maker/application.fam @@ -0,0 +1,14 @@ +App( + appid="nfc_maker", + name="NFC Maker", + apptype=FlipperAppType.EXTERNAL, + entry_point="nfc_maker", + cdefines=["APP_NFC_MAKER"], + requires=[ + "storage", + "gui", + ], + stack_size=1 * 1024, + fap_icon="nfc_maker_10px.png", + fap_category="NFC", +) diff --git a/applications/external/nfc_maker/nfc_maker.c b/applications/external/nfc_maker/nfc_maker.c new file mode 100644 index 000000000..578054ade --- /dev/null +++ b/applications/external/nfc_maker/nfc_maker.c @@ -0,0 +1,81 @@ +#include "nfc_maker.h" + +static bool nfc_maker_custom_event_callback(void* context, uint32_t event) { + furi_assert(context); + NfcMaker* app = context; + return scene_manager_handle_custom_event(app->scene_manager, event); +} + +static bool nfc_maker_back_event_callback(void* context) { + furi_assert(context); + NfcMaker* app = context; + + return scene_manager_handle_back_event(app->scene_manager); +} + +NfcMaker* nfc_maker_alloc() { + NfcMaker* app = malloc(sizeof(NfcMaker)); + app->gui = furi_record_open(RECORD_GUI); + + // View Dispatcher and Scene Manager + app->view_dispatcher = view_dispatcher_alloc(); + app->scene_manager = scene_manager_alloc(&nfc_maker_scene_handlers, app); + view_dispatcher_enable_queue(app->view_dispatcher); + view_dispatcher_set_event_callback_context(app->view_dispatcher, app); + + view_dispatcher_set_custom_event_callback( + app->view_dispatcher, nfc_maker_custom_event_callback); + view_dispatcher_set_navigation_event_callback( + app->view_dispatcher, nfc_maker_back_event_callback); + + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + + // Gui Modules + app->submenu = submenu_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, NfcMakerViewSubmenu, submenu_get_view(app->submenu)); + + app->text_input = text_input_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, NfcMakerViewTextInput, text_input_get_view(app->text_input)); + + app->byte_input = byte_input_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, NfcMakerViewByteInput, byte_input_get_view(app->byte_input)); + + app->popup = popup_alloc(); + view_dispatcher_add_view(app->view_dispatcher, NfcMakerViewPopup, popup_get_view(app->popup)); + + return app; +} + +void nfc_maker_free(NfcMaker* app) { + furi_assert(app); + + // Gui modules + view_dispatcher_remove_view(app->view_dispatcher, NfcMakerViewSubmenu); + submenu_free(app->submenu); + view_dispatcher_remove_view(app->view_dispatcher, NfcMakerViewTextInput); + text_input_free(app->text_input); + view_dispatcher_remove_view(app->view_dispatcher, NfcMakerViewByteInput); + byte_input_free(app->byte_input); + view_dispatcher_remove_view(app->view_dispatcher, NfcMakerViewPopup); + popup_free(app->popup); + + // View Dispatcher and Scene Manager + view_dispatcher_free(app->view_dispatcher); + scene_manager_free(app->scene_manager); + + // Records + furi_record_close(RECORD_GUI); + free(app); +} + +extern int32_t nfc_maker(void* p) { + UNUSED(p); + NfcMaker* app = nfc_maker_alloc(); + scene_manager_next_scene(app->scene_manager, NfcMakerSceneMenu); + view_dispatcher_run(app->view_dispatcher); + nfc_maker_free(app); + return 0; +} diff --git a/applications/external/nfc_maker/nfc_maker.h b/applications/external/nfc_maker/nfc_maker.h new file mode 100644 index 000000000..388dc7196 --- /dev/null +++ b/applications/external/nfc_maker/nfc_maker.h @@ -0,0 +1,59 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "scenes/nfc_maker_scene.h" +#include +#include +#include +#include + +#define TEXT_INPUT_LEN 248 +#define WIFI_INPUT_LEN 90 + +typedef enum { + WifiAuthenticationOpen = 0x01, + WifiAuthenticationWpa2Personal = 0x20, + WifiAuthenticationWpa2Enterprise = 0x10, + WifiAuthenticationWpaPersonal = 0x02, + WifiAuthenticationWpaEnterprise = 0x08, + WifiAuthenticationShared = 0x04, +} WifiAuthentication; + +typedef enum { + WifiEncryptionAes = 0x08, + WifiEncryptionWep = 0x02, + WifiEncryptionTkip = 0x04, + WifiEncryptionNone = 0x01, +} WifiEncryption; + +typedef struct { + Gui* gui; + SceneManager* scene_manager; + ViewDispatcher* view_dispatcher; + Submenu* submenu; + TextInput* text_input; + ByteInput* byte_input; + Popup* popup; + + uint8_t mac_buf[GAP_MAC_ADDR_SIZE]; + char text_buf[TEXT_INPUT_LEN]; + char pass_buf[WIFI_INPUT_LEN]; + char name_buf[TEXT_INPUT_LEN]; +} NfcMaker; + +typedef enum { + NfcMakerViewSubmenu, + NfcMakerViewTextInput, + NfcMakerViewByteInput, + NfcMakerViewPopup, +} NfcMakerView; diff --git a/applications/external/nfc_maker/nfc_maker_10px.png b/applications/external/nfc_maker/nfc_maker_10px.png new file mode 100644 index 0000000000000000000000000000000000000000..a9e2443f1790f0b3a595767feb8622ff3cf7ab31 GIT binary patch literal 4142 zcmeH~d2ka|9LIxVTMCs!E&*YiGEkAt9@%WNYg4F6OCt@Gl4;8T%4YYa-D#2y$)-u` zw47xu$T0}YB`Bg*1cm`RGNYB5wt}_gK2$E{vgI0()=@?}_;z~_%s8Eq|C-6M``&jy z@Avz@-+Pl+nm%nxO!SCojYbn=OSNXeU*$SFsyF-||9T`1f2s=|*>VOKLVSM7CAtA3 z7x(}I!lFx~37_|*Ck{&dxC;rWUClX9DjCN+(S z-&lHktbO*>h|F`R&^6Z^M%Gu4+?E%2=*-Ol%h%5{CafRY{Em4{L~(G%t(w%!v2|HV z%%dBw!Tq^3$HeT))T*fOOGZVQ$9X24emLxR!`#^7+%*k!Egrc0L8EYC_RfkW8JWUZ zeqlxAwS_0p+`})EBktaw=cwOMJ@mnWvR9fS=O3H3`mdt%vPRP@PPACkZ5B&gJ}`?d zAFZi)vwHI2l0&=pE&rlqK}q(Gz4FwVvkp$^ofLa`$ei8#Y(s~=KYZ=P71D2;8>-x~ zQQs}DSX`d7y`)4Ob#Tk-+KT$Reh2Q1O{@K-D0Tl%U$gh|z6IcT#YtC5FhBdnO7dRe zi!lbZ|}mUg?=!zaGHZJ%;OnB zJ1H?bARSDF^xp zy@KVbdWFQRGx%y(bto4o(*q4daTXrlD68BWs|7KTo$8idH z;lH2|JS;VxS#%U0w4QTLonqCjpU_}^2=Ds%QfCD;n!baSPp?y#iXXwoNZDpjj;xOu zGz3MDUV0Bxj%PM&k|XM;xvOgWXz+ej*H1KO<&W|FaOK0e!F$~)OG{Ty9y#Kaqp_bg t_Wr$-tJBizZ`5U#ZOn{0H@d&CSIm|E10x2OB9No8B~P>Nd1Kz + +// Generate scene id and total number +#define ADD_SCENE(prefix, name, id) NfcMakerScene##id, +typedef enum { +#include "nfc_maker_scene_config.h" + NfcMakerSceneNum, +} NfcMakerScene; +#undef ADD_SCENE + +extern const SceneManagerHandlers nfc_maker_scene_handlers; + +// Generate scene on_enter handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); +#include "nfc_maker_scene_config.h" +#undef ADD_SCENE + +// Generate scene on_event handlers declaration +#define ADD_SCENE(prefix, name, id) \ + bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event); +#include "nfc_maker_scene_config.h" +#undef ADD_SCENE + +// Generate scene on_exit handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context); +#include "nfc_maker_scene_config.h" +#undef ADD_SCENE diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_bluetooth.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_bluetooth.c new file mode 100644 index 000000000..4e70a184d --- /dev/null +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_bluetooth.c @@ -0,0 +1,57 @@ +#include "../nfc_maker.h" + +enum ByteInputResult { + ByteInputResultOk, +}; + +static void nfc_maker_scene_bluetooth_byte_input_callback(void* context) { + NfcMaker* app = context; + + view_dispatcher_send_custom_event(app->view_dispatcher, ByteInputResultOk); +} + +void nfc_maker_scene_bluetooth_on_enter(void* context) { + NfcMaker* app = context; + ByteInput* byte_input = app->byte_input; + + byte_input_set_header_text(byte_input, "Enter Bluetooth MAC:"); + + for(size_t i = 0; i < GAP_MAC_ADDR_SIZE; i++) { + app->mac_buf[i] = 0x69; + } + + byte_input_set_result_callback( + byte_input, + nfc_maker_scene_bluetooth_byte_input_callback, + NULL, + app, + app->mac_buf, + GAP_MAC_ADDR_SIZE); + + view_dispatcher_switch_to_view(app->view_dispatcher, NfcMakerViewByteInput); +} + +bool nfc_maker_scene_bluetooth_on_event(void* context, SceneManagerEvent event) { + NfcMaker* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + switch(event.event) { + case ByteInputResultOk: + furi_hal_bt_reverse_mac_addr(app->mac_buf); + scene_manager_next_scene(app->scene_manager, NfcMakerSceneName); + break; + default: + break; + } + } + + return consumed; +} + +void nfc_maker_scene_bluetooth_on_exit(void* context) { + NfcMaker* app = context; + byte_input_set_result_callback(app->byte_input, NULL, NULL, NULL, NULL, 0); + byte_input_set_header_text(app->byte_input, ""); +} diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_config.h b/applications/external/nfc_maker/scenes/nfc_maker_scene_config.h new file mode 100644 index 000000000..a89b4198c --- /dev/null +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_config.h @@ -0,0 +1,13 @@ +ADD_SCENE(nfc_maker, menu, Menu) +ADD_SCENE(nfc_maker, bluetooth, Bluetooth) +ADD_SCENE(nfc_maker, https, Https) +ADD_SCENE(nfc_maker, mail, Mail) +ADD_SCENE(nfc_maker, phone, Phone) +ADD_SCENE(nfc_maker, text, Text) +ADD_SCENE(nfc_maker, url, Url) +ADD_SCENE(nfc_maker, wifi, Wifi) +ADD_SCENE(nfc_maker, wifi_auth, WifiAuth) +ADD_SCENE(nfc_maker, wifi_encr, WifiEncr) +ADD_SCENE(nfc_maker, wifi_pass, WifiPass) +ADD_SCENE(nfc_maker, name, Name) +ADD_SCENE(nfc_maker, result, Result) diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_https.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_https.c new file mode 100644 index 000000000..77b32afe1 --- /dev/null +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_https.c @@ -0,0 +1,53 @@ +#include "../nfc_maker.h" + +enum TextInputResult { + TextInputResultOk, +}; + +static void nfc_maker_scene_https_text_input_callback(void* context) { + NfcMaker* app = context; + + view_dispatcher_send_custom_event(app->view_dispatcher, TextInputResultOk); +} + +void nfc_maker_scene_https_on_enter(void* context) { + NfcMaker* app = context; + TextInput* text_input = app->text_input; + + text_input_set_header_text(text_input, "Enter HTTPS Link:"); + + strlcpy(app->text_buf, "google.com", TEXT_INPUT_LEN); + + text_input_set_result_callback( + text_input, + nfc_maker_scene_https_text_input_callback, + app, + app->text_buf, + TEXT_INPUT_LEN, + true); + + view_dispatcher_switch_to_view(app->view_dispatcher, NfcMakerViewTextInput); +} + +bool nfc_maker_scene_https_on_event(void* context, SceneManagerEvent event) { + NfcMaker* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + switch(event.event) { + case TextInputResultOk: + scene_manager_next_scene(app->scene_manager, NfcMakerSceneName); + break; + default: + break; + } + } + + return consumed; +} + +void nfc_maker_scene_https_on_exit(void* context) { + NfcMaker* app = context; + text_input_reset(app->text_input); +} diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_mail.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_mail.c new file mode 100644 index 000000000..98c648f6a --- /dev/null +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_mail.c @@ -0,0 +1,53 @@ +#include "../nfc_maker.h" + +enum TextInputResult { + TextInputResultOk, +}; + +static void nfc_maker_scene_mail_text_input_callback(void* context) { + NfcMaker* app = context; + + view_dispatcher_send_custom_event(app->view_dispatcher, TextInputResultOk); +} + +void nfc_maker_scene_mail_on_enter(void* context) { + NfcMaker* app = context; + TextInput* text_input = app->text_input; + + text_input_set_header_text(text_input, "Enter EMail Address:"); + + strlcpy(app->text_buf, "ben.dover@example.com", TEXT_INPUT_LEN); + + text_input_set_result_callback( + text_input, + nfc_maker_scene_mail_text_input_callback, + app, + app->text_buf, + TEXT_INPUT_LEN, + true); + + view_dispatcher_switch_to_view(app->view_dispatcher, NfcMakerViewTextInput); +} + +bool nfc_maker_scene_mail_on_event(void* context, SceneManagerEvent event) { + NfcMaker* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + switch(event.event) { + case TextInputResultOk: + scene_manager_next_scene(app->scene_manager, NfcMakerSceneName); + break; + default: + break; + } + } + + return consumed; +} + +void nfc_maker_scene_mail_on_exit(void* context) { + NfcMaker* app = context; + text_input_reset(app->text_input); +} diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_menu.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_menu.c new file mode 100644 index 000000000..4268e7e2f --- /dev/null +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_menu.c @@ -0,0 +1,61 @@ +#include "../nfc_maker.h" + +void nfc_maker_scene_menu_submenu_callback(void* context, uint32_t index) { + NfcMaker* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, index); +} + +void nfc_maker_scene_menu_on_enter(void* context) { + NfcMaker* app = context; + Submenu* submenu = app->submenu; + + submenu_set_header(submenu, "NFC Tag Maker:"); + + submenu_add_item( + submenu, + "Bluetooth MAC", + NfcMakerSceneBluetooth, + nfc_maker_scene_menu_submenu_callback, + app); + + submenu_add_item( + submenu, "HTTPS Link", NfcMakerSceneHttps, nfc_maker_scene_menu_submenu_callback, app); + + submenu_add_item( + submenu, "Mail Address", NfcMakerSceneMail, nfc_maker_scene_menu_submenu_callback, app); + + submenu_add_item( + submenu, "Phone Number", NfcMakerScenePhone, nfc_maker_scene_menu_submenu_callback, app); + + submenu_add_item( + submenu, "Text Note", NfcMakerSceneText, nfc_maker_scene_menu_submenu_callback, app); + + submenu_add_item( + submenu, "Plain URL", NfcMakerSceneUrl, nfc_maker_scene_menu_submenu_callback, app); + + submenu_add_item( + submenu, "WiFi Login", NfcMakerSceneWifi, nfc_maker_scene_menu_submenu_callback, app); + + submenu_set_selected_item( + submenu, scene_manager_get_scene_state(app->scene_manager, NfcMakerSceneMenu)); + + view_dispatcher_switch_to_view(app->view_dispatcher, NfcMakerViewSubmenu); +} + +bool nfc_maker_scene_menu_on_event(void* context, SceneManagerEvent event) { + NfcMaker* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + scene_manager_set_scene_state(app->scene_manager, NfcMakerSceneMenu, event.event); + consumed = true; + scene_manager_next_scene(app->scene_manager, event.event); + } + + return consumed; +} + +void nfc_maker_scene_menu_on_exit(void* context) { + NfcMaker* app = context; + submenu_reset(app->submenu); +} diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_name.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_name.c new file mode 100644 index 000000000..dd5170d94 --- /dev/null +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_name.c @@ -0,0 +1,57 @@ +#include "../nfc_maker.h" + +enum TextInputResult { + TextInputResultOk, +}; + +static void nfc_maker_scene_name_text_input_callback(void* context) { + NfcMaker* app = context; + + view_dispatcher_send_custom_event(app->view_dispatcher, TextInputResultOk); +} + +void nfc_maker_scene_name_on_enter(void* context) { + NfcMaker* app = context; + TextInput* text_input = app->text_input; + + text_input_set_header_text(text_input, "Name the NFC tag:"); + + set_random_name(app->name_buf, TEXT_INPUT_LEN); + + text_input_set_result_callback( + text_input, + nfc_maker_scene_name_text_input_callback, + app, + app->name_buf, + TEXT_INPUT_LEN, + true); + + ValidatorIsFile* validator_is_file = + validator_is_file_alloc_init(NFC_APP_FOLDER, NFC_APP_EXTENSION, NULL); + text_input_set_validator(text_input, validator_is_file_callback, validator_is_file); + + view_dispatcher_switch_to_view(app->view_dispatcher, NfcMakerViewTextInput); +} + +bool nfc_maker_scene_name_on_event(void* context, SceneManagerEvent event) { + NfcMaker* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + switch(event.event) { + case TextInputResultOk: + scene_manager_next_scene(app->scene_manager, NfcMakerSceneResult); + break; + default: + break; + } + } + + return consumed; +} + +void nfc_maker_scene_name_on_exit(void* context) { + NfcMaker* app = context; + text_input_reset(app->text_input); +} diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_phone.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_phone.c new file mode 100644 index 000000000..4e70bcb09 --- /dev/null +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_phone.c @@ -0,0 +1,53 @@ +#include "../nfc_maker.h" + +enum TextInputResult { + TextInputResultOk, +}; + +static void nfc_maker_scene_phone_text_input_callback(void* context) { + NfcMaker* app = context; + + view_dispatcher_send_custom_event(app->view_dispatcher, TextInputResultOk); +} + +void nfc_maker_scene_phone_on_enter(void* context) { + NfcMaker* app = context; + TextInput* text_input = app->text_input; + + text_input_set_header_text(text_input, "Enter Phone Number:"); + + strlcpy(app->text_buf, "+", TEXT_INPUT_LEN); + + text_input_set_result_callback( + text_input, + nfc_maker_scene_phone_text_input_callback, + app, + app->text_buf, + TEXT_INPUT_LEN, + false); + + view_dispatcher_switch_to_view(app->view_dispatcher, NfcMakerViewTextInput); +} + +bool nfc_maker_scene_phone_on_event(void* context, SceneManagerEvent event) { + NfcMaker* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + switch(event.event) { + case TextInputResultOk: + scene_manager_next_scene(app->scene_manager, NfcMakerSceneName); + break; + default: + break; + } + } + + return consumed; +} + +void nfc_maker_scene_phone_on_exit(void* context) { + NfcMaker* app = context; + text_input_reset(app->text_input); +} diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_result.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_result.c new file mode 100644 index 000000000..912bf3c9f --- /dev/null +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_result.c @@ -0,0 +1,359 @@ +#include "../nfc_maker.h" + +enum PopupEvent { + PopupEventExit, +}; + +static void nfc_maker_scene_result_popup_callback(void* context) { + NfcMaker* app = context; + + view_dispatcher_send_custom_event(app->view_dispatcher, PopupEventExit); +} + +void nfc_maker_scene_result_on_enter(void* context) { + NfcMaker* app = context; + Popup* popup = app->popup; + bool success = false; + + FlipperFormat* file = flipper_format_file_alloc(furi_record_open(RECORD_STORAGE)); + FuriString* path = furi_string_alloc(); + furi_string_printf(path, NFC_APP_FOLDER "/%s" NFC_APP_EXTENSION, app->name_buf); + do { + if(!flipper_format_file_open_new(file, furi_string_get_cstr(path))) break; + + uint32_t pages = 42; + size_t size = pages * 4; + uint8_t* buf = malloc(size); + + if(!flipper_format_write_header_cstr(file, "Flipper NFC device", 3)) break; + if(!flipper_format_write_string_cstr(file, "Device type", "NTAG203")) break; + + // Serial number + buf[0] = 0x04; + furi_hal_random_fill_buf(&buf[1], 8); + uint8_t uid[7]; + memcpy(&uid[0], &buf[0], 3); + memcpy(&uid[3], &buf[4], 4); + + if(!flipper_format_write_hex(file, "UID", uid, sizeof(uid))) break; + if(!flipper_format_write_string_cstr(file, "ATQA", "00 44")) break; + if(!flipper_format_write_string_cstr(file, "SAK", "00")) break; + // TODO: Maybe randomize? + if(!flipper_format_write_string_cstr( + file, + "Signature", + "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00")) + break; + if(!flipper_format_write_string_cstr(file, "Mifare version", "00 00 00 00 00 00 00 00")) + break; + + if(!flipper_format_write_string_cstr(file, "Counter 0", "0")) break; + if(!flipper_format_write_string_cstr(file, "Tearing 0", "00")) break; + if(!flipper_format_write_string_cstr(file, "Counter 1", "0")) break; + if(!flipper_format_write_string_cstr(file, "Tearing 1", "00")) break; + if(!flipper_format_write_string_cstr(file, "Counter 2", "0")) break; + if(!flipper_format_write_string_cstr(file, "Tearing 2", "00")) break; + if(!flipper_format_write_uint32(file, "Pages total", &pages, 1)) break; + + // Static data + buf[9] = 0x48; // Internal + buf[10] = 0x00; // Lock bytes + buf[11] = 0x00; // ... + buf[12] = 0xE1; // Capability container + buf[13] = 0x10; // ... + buf[14] = 0x12; // ... + buf[15] = 0x00; // ... + buf[16] = 0x01; // ... + buf[17] = 0x03; // ... + buf[18] = 0xA0; // ... + buf[19] = 0x10; // ... + buf[20] = 0x44; // ... + buf[21] = 0x03; // Message flags + + size_t msg_len = 0; + switch(scene_manager_get_scene_state(app->scene_manager, NfcMakerSceneMenu)) { + case NfcMakerSceneBluetooth: { + msg_len = 0x2B; + + buf[23] = 0xD2; + buf[24] = 0x20; + buf[25] = 0x08; + buf[26] = 0x61; + buf[27] = 0x70; + + buf[28] = 0x70; + buf[29] = 0x6C; + buf[30] = 0x69; + buf[31] = 0x63; + + buf[32] = 0x61; + buf[33] = 0x74; + buf[34] = 0x69; + buf[35] = 0x6F; + + buf[36] = 0x6E; + buf[37] = 0x2F; + buf[38] = 0x76; + buf[39] = 0x6E; + + buf[40] = 0x64; + buf[41] = 0x2E; + buf[42] = 0x62; + buf[43] = 0x6C; + + buf[44] = 0x75; + buf[45] = 0x65; + buf[46] = 0x74; + buf[47] = 0x6F; + + buf[48] = 0x6F; + buf[49] = 0x74; + buf[50] = 0x68; + buf[51] = 0x2E; + + buf[52] = 0x65; + buf[53] = 0x70; + buf[54] = 0x2E; + buf[55] = 0x6F; + + buf[56] = 0x6F; + buf[57] = 0x62; + buf[58] = 0x08; + buf[59] = 0x00; + + memcpy(&buf[60], app->mac_buf, GAP_MAC_ADDR_SIZE); + break; + } + case NfcMakerSceneHttps: { + uint8_t data_len = strnlen(app->text_buf, TEXT_INPUT_LEN); + msg_len = data_len + 5; + + buf[23] = 0xD1; + buf[24] = 0x01; + buf[25] = data_len + 1; + buf[26] = 0x55; + + buf[27] = 0x04; // Prepend "https://" + memcpy(&buf[28], app->text_buf, data_len); + break; + } + case NfcMakerSceneMail: { + uint8_t data_len = strnlen(app->text_buf, TEXT_INPUT_LEN); + msg_len = data_len + 5; + + buf[23] = 0xD1; + buf[24] = 0x01; + buf[25] = data_len + 1; + buf[26] = 0x55; + + buf[27] = 0x06; // Prepend "mailto:" + memcpy(&buf[28], app->text_buf, data_len); + break; + } + case NfcMakerScenePhone: { + uint8_t data_len = strnlen(app->text_buf, TEXT_INPUT_LEN); + msg_len = data_len + 5; + + buf[23] = 0xD1; + buf[24] = 0x01; + buf[25] = data_len + 1; + buf[26] = 0x55; + + buf[27] = 0x05; // Prepend "tel:" + memcpy(&buf[28], app->text_buf, data_len); + break; + } + case NfcMakerSceneText: { + uint8_t data_len = strnlen(app->text_buf, TEXT_INPUT_LEN); + msg_len = data_len + 7; + + buf[23] = 0xD1; + buf[24] = 0x01; + buf[25] = data_len + 3; + buf[26] = 0x54; + + buf[27] = 0x02; + buf[28] = 0x65; // e + buf[29] = 0x6E; // n + memcpy(&buf[30], app->text_buf, data_len); + break; + } + case NfcMakerSceneUrl: { + uint8_t data_len = strnlen(app->text_buf, TEXT_INPUT_LEN); + msg_len = data_len + 5; + + buf[23] = 0xD1; + buf[24] = 0x01; + buf[25] = data_len + 1; + buf[26] = 0x55; + + buf[27] = 0x00; // No prepend + memcpy(&buf[28], app->text_buf, data_len); + break; + } + case NfcMakerSceneWifi: { + uint8_t ssid_len = strnlen(app->text_buf, WIFI_INPUT_LEN); + uint8_t pass_len = strnlen(app->pass_buf, WIFI_INPUT_LEN); + uint8_t data_len = ssid_len + pass_len; + msg_len = data_len + 73; + + buf[23] = 0xD2; + buf[24] = 0x17; + buf[25] = data_len + 47; + buf[26] = 0x61; + buf[27] = 0x70; + + buf[28] = 0x70; + buf[29] = 0x6C; + buf[30] = 0x69; + buf[31] = 0x63; + + buf[32] = 0x61; + buf[33] = 0x74; + buf[34] = 0x69; + buf[35] = 0x6F; + + buf[36] = 0x6E; + buf[37] = 0x2F; + buf[38] = 0x76; + buf[39] = 0x6E; + + buf[40] = 0x64; + buf[41] = 0x2E; + buf[42] = 0x77; + buf[43] = 0x66; + + buf[44] = 0x61; + buf[45] = 0x2E; + buf[46] = 0x77; + buf[47] = 0x73; + + buf[48] = 0x63; + buf[49] = 0x10; + buf[50] = 0x0E; + buf[51] = 0x00; + + buf[52] = data_len + 43; + buf[53] = 0x10; + buf[54] = 0x26; + buf[55] = 0x00; + + buf[56] = 0x01; + buf[57] = 0x01; + buf[58] = 0x10; + buf[59] = 0x45; + + buf[60] = 0x00; + buf[61] = ssid_len; + memcpy(&buf[62], app->text_buf, ssid_len); + size_t ssid = 62 + ssid_len; + buf[ssid + 0] = 0x10; + buf[ssid + 1] = 0x03; + + buf[ssid + 2] = 0x00; + buf[ssid + 3] = 0x02; + buf[ssid + 4] = 0x00; + buf[ssid + 5] = + scene_manager_get_scene_state(app->scene_manager, NfcMakerSceneWifiAuth); + + buf[ssid + 6] = 0x10; + buf[ssid + 7] = 0x0F; + buf[ssid + 8] = 0x00; + buf[ssid + 9] = 0x02; + + buf[ssid + 10] = 0x00; + buf[ssid + 11] = + scene_manager_get_scene_state(app->scene_manager, NfcMakerSceneWifiEncr); + buf[ssid + 12] = 0x10; + buf[ssid + 13] = 0x27; + + buf[ssid + 14] = 0x00; + buf[ssid + 15] = pass_len; + memcpy(&buf[ssid + 16], app->pass_buf, pass_len); + size_t pass = ssid + 16 + pass_len; + buf[pass + 0] = 0x10; + buf[pass + 1] = 0x20; + + buf[pass + 2] = 0x00; + buf[pass + 3] = 0x06; + buf[pass + 4] = 0xFF; + buf[pass + 5] = 0xFF; + + buf[pass + 6] = 0xFF; + buf[pass + 7] = 0xFF; + buf[pass + 8] = 0xFF; + buf[pass + 9] = 0xFF; + + break; + } + default: + break; + } + + // Message length and terminator + buf[22] = msg_len; + size_t msg_end = 23 + msg_len; + buf[msg_end] = 0xFE; + + // Padding + for(size_t i = msg_end + 1; i < size; i++) { + buf[i] = 0x00; + } + + char str[16]; + bool ok = true; + for(size_t page = 0; page < pages; page++) { + snprintf(str, sizeof(str), "Page %u", page); + if(!flipper_format_write_hex(file, str, &buf[page * 4], 4)) { + ok = false; + break; + } + } + if(!ok) break; + + free(buf); + success = true; + + } while(false); + furi_string_free(path); + flipper_format_free(file); + furi_record_close(RECORD_STORAGE); + + if(success) { + popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); + popup_set_header(popup, "Saved!", 13, 22, AlignLeft, AlignBottom); + } else { + popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); + popup_set_header(popup, "Saved!", 13, 22, AlignLeft, AlignBottom); + } + popup_set_timeout(popup, 1500); + popup_set_context(popup, app); + popup_set_callback(popup, nfc_maker_scene_result_popup_callback); + popup_enable_timeout(popup); + + view_dispatcher_switch_to_view(app->view_dispatcher, NfcMakerViewPopup); +} + +bool nfc_maker_scene_result_on_event(void* context, SceneManagerEvent event) { + NfcMaker* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + switch(event.event) { + case PopupEventExit: + scene_manager_search_and_switch_to_previous_scene( + app->scene_manager, NfcMakerSceneMenu); + break; + default: + break; + } + } + + return consumed; +} + +void nfc_maker_scene_result_on_exit(void* context) { + NfcMaker* app = context; + popup_reset(app->popup); +} diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_text.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_text.c new file mode 100644 index 000000000..17d84e0a9 --- /dev/null +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_text.c @@ -0,0 +1,53 @@ +#include "../nfc_maker.h" + +enum TextInputResult { + TextInputResultOk, +}; + +static void nfc_maker_scene_text_text_input_callback(void* context) { + NfcMaker* app = context; + + view_dispatcher_send_custom_event(app->view_dispatcher, TextInputResultOk); +} + +void nfc_maker_scene_text_on_enter(void* context) { + NfcMaker* app = context; + TextInput* text_input = app->text_input; + + text_input_set_header_text(text_input, "Enter Text Note:"); + + strlcpy(app->text_buf, "Lorem ipsum", TEXT_INPUT_LEN); + + text_input_set_result_callback( + text_input, + nfc_maker_scene_text_text_input_callback, + app, + app->text_buf, + TEXT_INPUT_LEN, + true); + + view_dispatcher_switch_to_view(app->view_dispatcher, NfcMakerViewTextInput); +} + +bool nfc_maker_scene_text_on_event(void* context, SceneManagerEvent event) { + NfcMaker* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + switch(event.event) { + case TextInputResultOk: + scene_manager_next_scene(app->scene_manager, NfcMakerSceneName); + break; + default: + break; + } + } + + return consumed; +} + +void nfc_maker_scene_text_on_exit(void* context) { + NfcMaker* app = context; + text_input_reset(app->text_input); +} diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_url.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_url.c new file mode 100644 index 000000000..e5a4996b2 --- /dev/null +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_url.c @@ -0,0 +1,53 @@ +#include "../nfc_maker.h" + +enum TextInputResult { + TextInputResultOk, +}; + +static void nfc_maker_scene_url_text_input_callback(void* context) { + NfcMaker* app = context; + + view_dispatcher_send_custom_event(app->view_dispatcher, TextInputResultOk); +} + +void nfc_maker_scene_url_on_enter(void* context) { + NfcMaker* app = context; + TextInput* text_input = app->text_input; + + text_input_set_header_text(text_input, "Enter Plain URL:"); + + strlcpy(app->text_buf, "https://google.com", TEXT_INPUT_LEN); + + text_input_set_result_callback( + text_input, + nfc_maker_scene_url_text_input_callback, + app, + app->text_buf, + TEXT_INPUT_LEN, + true); + + view_dispatcher_switch_to_view(app->view_dispatcher, NfcMakerViewTextInput); +} + +bool nfc_maker_scene_url_on_event(void* context, SceneManagerEvent event) { + NfcMaker* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + switch(event.event) { + case TextInputResultOk: + scene_manager_next_scene(app->scene_manager, NfcMakerSceneName); + break; + default: + break; + } + } + + return consumed; +} + +void nfc_maker_scene_url_on_exit(void* context) { + NfcMaker* app = context; + text_input_reset(app->text_input); +} diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_wifi.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_wifi.c new file mode 100644 index 000000000..68efaf7f6 --- /dev/null +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_wifi.c @@ -0,0 +1,55 @@ +#include "../nfc_maker.h" + +enum TextInputResult { + TextInputResultOk, +}; + +static void nfc_maker_scene_wifi_text_input_callback(void* context) { + NfcMaker* app = context; + + view_dispatcher_send_custom_event(app->view_dispatcher, TextInputResultOk); +} + +void nfc_maker_scene_wifi_on_enter(void* context) { + NfcMaker* app = context; + TextInput* text_input = app->text_input; + + text_input_set_header_text(text_input, "Enter WiFi SSID:"); + + strlcpy(app->text_buf, "Bill Wi the Science Fi", WIFI_INPUT_LEN); + + text_input_set_result_callback( + text_input, + nfc_maker_scene_wifi_text_input_callback, + app, + app->text_buf, + WIFI_INPUT_LEN, + true); + + view_dispatcher_switch_to_view(app->view_dispatcher, NfcMakerViewTextInput); +} + +bool nfc_maker_scene_wifi_on_event(void* context, SceneManagerEvent event) { + NfcMaker* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + switch(event.event) { + case TextInputResultOk: + scene_manager_set_scene_state( + app->scene_manager, NfcMakerSceneWifiAuth, WifiAuthenticationWpa2Personal); + scene_manager_next_scene(app->scene_manager, NfcMakerSceneWifiAuth); + break; + default: + break; + } + } + + return consumed; +} + +void nfc_maker_scene_wifi_on_exit(void* context) { + NfcMaker* app = context; + text_input_reset(app->text_input); +} diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_wifi_auth.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_wifi_auth.c new file mode 100644 index 000000000..4cb90dabe --- /dev/null +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_wifi_auth.c @@ -0,0 +1,83 @@ +#include "../nfc_maker.h" + +void nfc_maker_scene_wifi_auth_submenu_callback(void* context, uint32_t index) { + NfcMaker* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, index); +} + +void nfc_maker_scene_wifi_auth_on_enter(void* context) { + NfcMaker* app = context; + Submenu* submenu = app->submenu; + + submenu_set_header(submenu, "Authentication Type:"); + + submenu_add_item( + submenu, "Open", WifiAuthenticationOpen, nfc_maker_scene_wifi_auth_submenu_callback, app); + + submenu_add_item( + submenu, + "WPA 2 Personal", + WifiAuthenticationWpa2Personal, + nfc_maker_scene_wifi_auth_submenu_callback, + app); + + submenu_add_item( + submenu, + "WPA 2 Enterprise", + WifiAuthenticationWpa2Enterprise, + nfc_maker_scene_wifi_auth_submenu_callback, + app); + + submenu_add_item( + submenu, + "WPA Personal", + WifiAuthenticationWpaPersonal, + nfc_maker_scene_wifi_auth_submenu_callback, + app); + + submenu_add_item( + submenu, + "WPA Enterprise", + WifiAuthenticationWpaEnterprise, + nfc_maker_scene_wifi_auth_submenu_callback, + app); + + submenu_add_item( + submenu, + "Shared", + WifiAuthenticationShared, + nfc_maker_scene_wifi_auth_submenu_callback, + app); + + submenu_set_selected_item( + submenu, scene_manager_get_scene_state(app->scene_manager, NfcMakerSceneWifiAuth)); + + view_dispatcher_switch_to_view(app->view_dispatcher, NfcMakerViewSubmenu); +} + +bool nfc_maker_scene_wifi_auth_on_event(void* context, SceneManagerEvent event) { + NfcMaker* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + scene_manager_set_scene_state(app->scene_manager, NfcMakerSceneWifiAuth, event.event); + consumed = true; + if(event.event == WifiAuthenticationOpen) { + scene_manager_set_scene_state( + app->scene_manager, NfcMakerSceneWifiEncr, WifiEncryptionNone); + strcpy(app->pass_buf, ""); + scene_manager_next_scene(app->scene_manager, NfcMakerSceneName); + } else { + scene_manager_set_scene_state( + app->scene_manager, NfcMakerSceneWifiEncr, WifiEncryptionAes); + scene_manager_next_scene(app->scene_manager, NfcMakerSceneWifiEncr); + } + } + + return consumed; +} + +void nfc_maker_scene_wifi_auth_on_exit(void* context) { + NfcMaker* app = context; + submenu_reset(app->submenu); +} diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_wifi_encr.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_wifi_encr.c new file mode 100644 index 000000000..d1a21f51a --- /dev/null +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_wifi_encr.c @@ -0,0 +1,48 @@ +#include "../nfc_maker.h" + +void nfc_maker_scene_wifi_encr_submenu_callback(void* context, uint32_t index) { + NfcMaker* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, index); +} + +void nfc_maker_scene_wifi_encr_on_enter(void* context) { + NfcMaker* app = context; + Submenu* submenu = app->submenu; + + submenu_set_header(submenu, "Encryption Type:"); + + submenu_add_item( + submenu, "AES", WifiEncryptionAes, nfc_maker_scene_wifi_encr_submenu_callback, app); + + submenu_add_item( + submenu, "WEP", WifiEncryptionWep, nfc_maker_scene_wifi_encr_submenu_callback, app); + + submenu_add_item( + submenu, "TKIP", WifiEncryptionTkip, nfc_maker_scene_wifi_encr_submenu_callback, app); + + submenu_add_item( + submenu, "None", WifiEncryptionNone, nfc_maker_scene_wifi_encr_submenu_callback, app); + + submenu_set_selected_item( + submenu, scene_manager_get_scene_state(app->scene_manager, NfcMakerSceneWifiEncr)); + + view_dispatcher_switch_to_view(app->view_dispatcher, NfcMakerViewSubmenu); +} + +bool nfc_maker_scene_wifi_encr_on_event(void* context, SceneManagerEvent event) { + NfcMaker* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + scene_manager_set_scene_state(app->scene_manager, NfcMakerSceneWifiEncr, event.event); + consumed = true; + scene_manager_next_scene(app->scene_manager, NfcMakerSceneWifiPass); + } + + return consumed; +} + +void nfc_maker_scene_wifi_encr_on_exit(void* context) { + NfcMaker* app = context; + submenu_reset(app->submenu); +} diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_wifi_pass.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_wifi_pass.c new file mode 100644 index 000000000..3f5b4bd76 --- /dev/null +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_wifi_pass.c @@ -0,0 +1,53 @@ +#include "../nfc_maker.h" + +enum TextInputResult { + TextInputResultOk, +}; + +static void nfc_maker_scene_wifi_pass_text_input_callback(void* context) { + NfcMaker* app = context; + + view_dispatcher_send_custom_event(app->view_dispatcher, TextInputResultOk); +} + +void nfc_maker_scene_wifi_pass_on_enter(void* context) { + NfcMaker* app = context; + TextInput* text_input = app->text_input; + + text_input_set_header_text(text_input, "Enter WiFi Password:"); + + strlcpy(app->pass_buf, "244466666", WIFI_INPUT_LEN); + + text_input_set_result_callback( + text_input, + nfc_maker_scene_wifi_pass_text_input_callback, + app, + app->pass_buf, + WIFI_INPUT_LEN, + true); + + view_dispatcher_switch_to_view(app->view_dispatcher, NfcMakerViewTextInput); +} + +bool nfc_maker_scene_wifi_pass_on_event(void* context, SceneManagerEvent event) { + NfcMaker* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + switch(event.event) { + case TextInputResultOk: + scene_manager_next_scene(app->scene_manager, NfcMakerSceneName); + break; + default: + break; + } + } + + return consumed; +} + +void nfc_maker_scene_wifi_pass_on_exit(void* context) { + NfcMaker* app = context; + text_input_reset(app->text_input); +} From 2211314900138dadb8c9027e982b2feac1ed91fb Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 6 Jul 2023 18:58:34 +0300 Subject: [PATCH 064/364] merge fixes --- applications/main/subghz/scenes/subghz_scene_start.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/applications/main/subghz/scenes/subghz_scene_start.c b/applications/main/subghz/scenes/subghz_scene_start.c index 45505d5a4..5abab8f61 100644 --- a/applications/main/subghz/scenes/subghz_scene_start.c +++ b/applications/main/subghz/scenes/subghz_scene_start.c @@ -107,11 +107,6 @@ bool subghz_scene_start_on_event(void* context, SceneManagerEvent event) { scene_manager_next_scene(subghz->scene_manager, SubGhzSceneFrequencyAnalyzer); dolphin_deed(DolphinDeedSubGhzFrequencyAnalyzer); return true; - } else if(event.event == SubmenuIndexTest) { - scene_manager_set_scene_state( - subghz->scene_manager, SubGhzSceneStart, SubmenuIndexTest); - scene_manager_next_scene(subghz->scene_manager, SubGhzSceneTest); - return true; } } } From 3f4887e8d3bb91a10652a21ef261e2e48eacd600 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 6 Jul 2023 19:28:29 +0300 Subject: [PATCH 065/364] Fix nfc maker includes --- applications/external/nfc_maker/application.fam | 1 + .../nfc_maker/assets/DolphinNice_96x59.png | Bin 0 -> 2459 bytes applications/external/nfc_maker/nfc_maker.h | 3 ++- applications/external/nfc_maker/strnlen.c | 11 +++++++++++ applications/external/nfc_maker/strnlen.h | 6 ++++++ 5 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 applications/external/nfc_maker/assets/DolphinNice_96x59.png create mode 100644 applications/external/nfc_maker/strnlen.c create mode 100644 applications/external/nfc_maker/strnlen.h diff --git a/applications/external/nfc_maker/application.fam b/applications/external/nfc_maker/application.fam index 89bbb202e..95bcd8878 100644 --- a/applications/external/nfc_maker/application.fam +++ b/applications/external/nfc_maker/application.fam @@ -11,4 +11,5 @@ App( stack_size=1 * 1024, fap_icon="nfc_maker_10px.png", fap_category="NFC", + fap_icon_assets="assets", ) diff --git a/applications/external/nfc_maker/assets/DolphinNice_96x59.png b/applications/external/nfc_maker/assets/DolphinNice_96x59.png new file mode 100644 index 0000000000000000000000000000000000000000..a299d3630239b4486e249cc501872bed5996df3b GIT binary patch literal 2459 zcmbVO3s4i+8V(M(gEFORwSrA`4O0uPn|M|5y* zB*aMDxC&7(gP9JN;POOi-9khrC>Z9YJs2U!LnVcQEEC0fDtKo&ILlzb30%M}3J^;~ zv7RzcsilOs4Mq@tD*&R;!LMSk2A~{(`HK9|hQBqEX)3sQr9Je6SZU*F-^fD-p+~Hs; zHLkO%v?>ZoxEv+F#whudr%615FkA0DYR0tMEo}3OOY#xecLWe>xV?u5KtSmC^ z7)Fmj6gjfKstiEV-*Cxbbb+&rRWuI_rBJ)ybs_f1Rn&f2>q3pYwI^|J(hdn{j{0EZIm_F zpIyIWLsRUgOItR-dUbVd|6Zo=_BU_Tj4|{{jxO#=JH4o8er(5{!nZD_j4}MH&zh~9 zVLC~y(0-D6GO0ghZD8BYzP?o{>22~lT6^d@X{SwQ8vrNY-PPIMajIwC)`s14Ep72@ zeq7YOzM`?U{+W)ocXBr`eSOcpk?Rxc=ou5&)fWW|pD};-Z0mvk9}=&`Rb&y<77W~a z(>6YM;6Y5aIU~JKZ}mQZynKHiSTQ#Bczn@&jTiN^?vPJ(jhm7cXLx0oum5P$`TceG zU+wR;OO^)8CVlnM)5p$CO&e94KJt>HccCaHGusmW_b`T6m| z-R6V6Db1pErTot?^d22ojm+2>_)FbD`_+WbDGMx9f@hO27maS2`csiV(D&Fs`PS2& zvrq18du_&zXID(!KIxsU$)iuTYuZ?zmYiP&n&i@Be{IdbS-jA2c0QAlu5NXQv_0K< z3Hvs4eeu6B7yD&CNT~gIkMV&UkRU=V!iQ(+_(O&u^ah$+s{_yn(yBYeD40HeU{xGsIT6W Zfq!wOp!Q #include #include -#include +#include "nfc_maker_icons.h" #include #include #include @@ -16,6 +16,7 @@ #include #include #include +#include "strnlen.h" #define TEXT_INPUT_LEN 248 #define WIFI_INPUT_LEN 90 diff --git a/applications/external/nfc_maker/strnlen.c b/applications/external/nfc_maker/strnlen.c new file mode 100644 index 000000000..54d183895 --- /dev/null +++ b/applications/external/nfc_maker/strnlen.c @@ -0,0 +1,11 @@ +#include "strnlen.h" + +size_t strnlen(const char* s, size_t maxlen) { + size_t len; + + for(len = 0; len < maxlen; len++, s++) { + if(!*s) break; + } + + return len; +} \ No newline at end of file diff --git a/applications/external/nfc_maker/strnlen.h b/applications/external/nfc_maker/strnlen.h new file mode 100644 index 000000000..4fe0d540c --- /dev/null +++ b/applications/external/nfc_maker/strnlen.h @@ -0,0 +1,6 @@ +#pragma once +#pragma weak strnlen + +#include + +size_t strnlen(const char* s, size_t maxlen); \ No newline at end of file From 7e14f997e72bfbd2d0f3b9f45e384edda69dcd1f Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 6 Jul 2023 19:41:07 +0300 Subject: [PATCH 066/364] Update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 05aba5e16..74bf3dc9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ * Infrared: Updated universal remote assets (by @amec0e | PR #529) * Plugins: Use correct categories for all plugins (extra pack too) * Plugins: Various fixes for uFBT (by @hedger) +* Plugins: Added **NFC Maker** plugin (make tags with URLs, Wifi and other things) [(by Willy-JL)](https://github.com/ClaraCrazy/Flipper-Xtreme/tree/dev/applications/external/nfc_maker) +* Plugins: Added JetPack Joyride [(by timstrasser)](https://github.com/timstrasser) * Plugins: Moved Barcode Generator [(by Kingal1337)](https://github.com/Kingal1337/flipper-barcode-generator) from extra pack into base fw, old barcode generator was removed * Plugins: Updated ESP32: WiFi Marauder companion plugin [(by 0xchocolate)](https://github.com/0xchocolate/flipperzero-wifi-marauder) * Plugins: Updated i2c Tools [(by NaejEL)](https://github.com/NaejEL/flipperzero-i2ctools) @@ -15,6 +17,8 @@ * WIP OFW PR 2825: NFC: Improved MFC emulation on some readers (by AloneLiberty) * OFW PR 2829: Decode only supported Oregon 3 sensor (by @wosk) * OFW PR: Update OFW PR 2782 +* OFW: SubGhz: add "SubGhz test" external application and the ability to work "SubGhz" as an external application +* OFW: API: explicitly add math.h * OFW: NFC: Mf Ultralight emulation optimization * OFW: Furi_Power: fix furi_hal_power_enable_otg * OFW: FuriHal: allow nulling null isr From 65faffb9218a5a3b3b48131a0d38ff9bf34eaeac Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 7 Jul 2023 00:34:09 +0200 Subject: [PATCH 067/364] Fix up/down on last odd wii menu item --- applications/services/gui/modules/menu.c | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/applications/services/gui/modules/menu.c b/applications/services/gui/modules/menu.c index cf42524af..fecefb3c0 100644 --- a/applications/services/gui/modules/menu.c +++ b/applications/services/gui/modules/menu.c @@ -353,14 +353,12 @@ static void menu_process_up(Menu* menu) { } if(XTREME_SETTINGS()->wii_menu) { - if(!(model->position == count - 1 && count % 2)) { - if(model->position % 2) { - model->position--; - } else { - model->position++; - } - model->scroll_counter = 0; + if(model->position % 2 || (model->position == count - 1 && count % 2)) { + model->position--; + } else { + model->position++; } + model->scroll_counter = 0; } else { if(model->position > 0) { model->position--; @@ -389,14 +387,12 @@ static void menu_process_down(Menu* menu) { } if(XTREME_SETTINGS()->wii_menu) { - if(!(model->position == count - 1 && count % 2)) { - if(model->position % 2) { - model->position--; - } else { - model->position++; - } - model->scroll_counter = 0; + if(model->position % 2 || (model->position == count - 1 && count % 2)) { + model->position--; + } else { + model->position++; } + model->scroll_counter = 0; } else { if(model->position < count - 1) { model->position++; From 0109361fe96bb774ca61e599578eeba6071f9959 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sat, 8 Jul 2023 19:52:04 +0300 Subject: [PATCH 068/364] nfc maker - add keyboard --- .../assets/KeyBackspaceSelected_16x9.png | Bin 0 -> 1812 bytes .../nfc_maker/assets/KeyBackspace_16x9.png | Bin 0 -> 1829 bytes .../assets/KeyKeyboardSelected_10x11.png | Bin 0 -> 7210 bytes .../nfc_maker/assets/KeyKeyboard_10x11.png | Bin 0 -> 7763 bytes .../assets/KeySaveSelected_24x11.png | Bin 0 -> 1853 bytes .../nfc_maker/assets/KeySave_24x11.png | Bin 0 -> 1863 bytes .../nfc_maker/assets/WarningDolphin_45x42.png | Bin 0 -> 1139 bytes applications/external/nfc_maker/nfc_maker.c | 8 +- applications/external/nfc_maker/nfc_maker.h | 4 +- .../external/nfc_maker/nfc_maker_text_input.c | 762 ++++++++++++++++++ .../external/nfc_maker/nfc_maker_text_input.h | 85 ++ .../external/nfc_maker/nfc_maker_validators.c | 57 ++ .../external/nfc_maker/nfc_maker_validators.h | 21 + .../nfc_maker/scenes/nfc_maker_scene_https.c | 8 +- .../nfc_maker/scenes/nfc_maker_scene_mail.c | 8 +- .../nfc_maker/scenes/nfc_maker_scene_menu.c | 2 +- .../nfc_maker/scenes/nfc_maker_scene_name.c | 10 +- .../nfc_maker/scenes/nfc_maker_scene_phone.c | 8 +- .../nfc_maker/scenes/nfc_maker_scene_text.c | 8 +- .../nfc_maker/scenes/nfc_maker_scene_url.c | 8 +- .../nfc_maker/scenes/nfc_maker_scene_wifi.c | 8 +- .../scenes/nfc_maker_scene_wifi_pass.c | 8 +- 22 files changed, 966 insertions(+), 39 deletions(-) create mode 100644 applications/external/nfc_maker/assets/KeyBackspaceSelected_16x9.png create mode 100644 applications/external/nfc_maker/assets/KeyBackspace_16x9.png create mode 100644 applications/external/nfc_maker/assets/KeyKeyboardSelected_10x11.png create mode 100644 applications/external/nfc_maker/assets/KeyKeyboard_10x11.png create mode 100644 applications/external/nfc_maker/assets/KeySaveSelected_24x11.png create mode 100644 applications/external/nfc_maker/assets/KeySave_24x11.png create mode 100644 applications/external/nfc_maker/assets/WarningDolphin_45x42.png create mode 100644 applications/external/nfc_maker/nfc_maker_text_input.c create mode 100644 applications/external/nfc_maker/nfc_maker_text_input.h create mode 100644 applications/external/nfc_maker/nfc_maker_validators.c create mode 100644 applications/external/nfc_maker/nfc_maker_validators.h diff --git a/applications/external/nfc_maker/assets/KeyBackspaceSelected_16x9.png b/applications/external/nfc_maker/assets/KeyBackspaceSelected_16x9.png new file mode 100644 index 0000000000000000000000000000000000000000..7cc0759a8ca6acdb9b9c2e3dc00edde2f0e93a67 GIT binary patch literal 1812 zcmcIl&x_()N{W z-JN@vFI~T+Y1-v}FWiIo6?k5J;aT|qw)dv2CwcE-scA1=t)FMK&%d~)Y0rO}4EC%2 z=aK4R$F?2(hE6fX7H(UFBH{$t4v4EaKLer_Vi@d&Z#A)C)-lFal?RqJo6XEw%T&e4 zBEIiim|Bz~ut4QeR$2KDgeVQ)Gl9#&Q7)}LS*mHl<@TY>s++4|`B+t|9IGdATYvr+ zL&4Vp^Jy_zlt*w&PGkz$CD@V$zdYy`l2xi0C^cC%YIhY;r^KZCtp`aa)U15HX4E*y zkX5o{K-UPu6k#$T&@w-;?b`$g7%xpD(1BnTyO^;O$?)hRrco61v$A3tm;JC~04Xy` zM9{K5&YYn{cI-;z+O~({*abcDHnS;pNXu(4c!7VY__VG>?Z1?*P#iGU)eM+zGa?B_ zvgI?>CU%T`*JH?wZ6SP@IW~7zXzvsW>>M^Zjasu3fJoi8ARJ`vf+uoa+eOUrBobcB zw>cJ!4w<1pj@wleRYXcabz7&```zwtp@zu>K9qa+w)FmX*CD>+AZijr7d#lMB4r@7 zBxNIM<=Lo~JFR)Z8qvz(k!=8Gk?gq@8g zfS#k0rCF(l)r=K#a|A8Qr4h!%R?w1c= z{pq*skHW8}pZxgsP>68$^3NZ9>0PQ& Cw>kg- literal 0 HcmV?d00001 diff --git a/applications/external/nfc_maker/assets/KeyBackspace_16x9.png b/applications/external/nfc_maker/assets/KeyBackspace_16x9.png new file mode 100644 index 0000000000000000000000000000000000000000..9946232d953ef1cbfbf0e6754be6645e5ea2747b GIT binary patch literal 1829 zcmcIl&u`pB6gEEvMG+BPN`-{wNT>+Lo*8@XwOcpZ?1t{5I;81J4Y!WR<6SFjkFlNX zCgRjnK|!3jpo$Y0E}Xb=;LeTzpnm{T)f-4i;dy_hpfu#tmAsxAzxTcGz4y(`m)l!6 zS1w(-q$tWtuiM#y_bNQEzxE>h|J=PM>Pg=HtW=aY-mae)lh*~S0I8^$I!Q-a=}mlXitE9+UN$s!YEtd_TB{DI?graxTNXmKb&NR1RCQdP z*p_AEk5q~&HgLlr6cO9QmPZ_Q{?i~@5yjq4=i_-SnEBeUs&daT#^bR*Hg#DH4C1=3 zfvG_$0t-|gW)+*DtXx|lbVSLEB(D;gsWl=C<$mRBz;u>EnlE9qa$Y7Vm@#3wL3CWF zv@i^U^G(xqX_e|ijf0zqnN0f5E;9~PYWYyXtSU!}MEQj(L+?JpJ#W3Q_ zfcbtgnwBTxh8T$yuuHHdQ+~PEE(EJ&(U)?xXw>#1qDqNQ)vI@tERy5$gPPIYL3CIp zd=0ur5T*!|K7p3G9x*>8*u!{c8h{QWRntB?yEl08lWCYa({L}SbyS-h=I2pl*a_8oT+S_c~#IXiq#zs}!z^4spCcOR_0+*o_q`N6;X{rjK1`QsBs`Gcx|z4v#k QTVG_o&8^N)8~5)21Ktij=l}o! literal 0 HcmV?d00001 diff --git a/applications/external/nfc_maker/assets/KeyKeyboardSelected_10x11.png b/applications/external/nfc_maker/assets/KeyKeyboardSelected_10x11.png new file mode 100644 index 0000000000000000000000000000000000000000..231880386a9d9e83911088c9fcd083f84a7f75bb GIT binary patch literal 7210 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 literal 0 HcmV?d00001 diff --git a/applications/external/nfc_maker/assets/KeyKeyboard_10x11.png b/applications/external/nfc_maker/assets/KeyKeyboard_10x11.png new file mode 100644 index 0000000000000000000000000000000000000000..1f4c03478f68cb15c74ef99888ce4faf50e0a140 GIT binary patch literal 7763 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 GFa8a7)lmol literal 0 HcmV?d00001 diff --git a/applications/external/nfc_maker/assets/KeySave_24x11.png b/applications/external/nfc_maker/assets/KeySave_24x11.png new file mode 100644 index 0000000000000000000000000000000000000000..e7dba987a04dad7dd96001913c55566dbde96c8e GIT binary patch literal 1863 zcmcIlO^6&t6dpxnjjSF75f9pQJZy|LUDdzSZ6$n-*6-2D5s-9_fx~uK( zotfQBVDX?Q(UXLzXF6aX)%U*l z-d9y`w^q+Do_PF3p-@5qOL5h2N9RU z^i-~BIziNECdw*wjUcQeOxncsbnKa>(*%1MPoPck0jC)~9$50g-#!ks+4LGwn$d`f zMy;%ZsA3Rsk2!`#ELuW>2#g3fF>;CFBHMCo-El0(@X1&g%&$qdl~*F4Kd~*B3^?Z1 z^bEmDf}0)Wn??sYC6l9$X;6esLO7#_ZCmDy?ZqU3l|%anS#wn!7%f39-Qp(l9fyJ- z(?=x}n~2%2PcX9#VmYdECvH{tWzv)!s%sn^Z&a(TMEXG=KBQ~smzBm!)h4cOBfSV| zapw6l2`LyY2x(Vnan#Li4>BO#dXPeox2Fr~f_P*4)DM)gJ3Y$sMNw8+?gqit>2PpJ znU9yygm%~yKzf8rCa_fc*^nlp(uJ1%rwg^aiBIX^Xz9mu$p0vPT2|JhQCGkYtEqW1 zTD})enxg%?Uw4c#Ggk#{pLa8zmSLH8=LI=?xR>pc=yYsHAgcQUxz^arx`9fR>e$sw zj@}Uy75!kQXF{tT9e=F+z^*!*3|n>nI6oucWq!(t2og`=40-O!tAE1z^ID@;X)nEd zVK7xqj`wg%Ed2Op{k=a*YZpKHX787$ zzhKuN9(?M5ojmn%xcU1}OP>$^`ZD?cW@lIaTn=x3xBtP#z16o~{qVMZ>hecsD?jQQ ME3387mS5lf8`39Il>h($ literal 0 HcmV?d00001 diff --git a/applications/external/nfc_maker/assets/WarningDolphin_45x42.png b/applications/external/nfc_maker/assets/WarningDolphin_45x42.png new file mode 100644 index 0000000000000000000000000000000000000000..d766ffbb444db1739f2ccd030e506e8bada11ee8 GIT binary patch literal 1139 zcmaJ=TWAz#6rQ4%BpO*okt7zz3Bkm=bK9L=XV}qhW_HceY#KHzP4Z$UGyf(-b}pUy z<8ESW=_MtCk6tj|`k)VrCbo(SMH0n`eW+KIU`wG5O`$IeMg)Vzf7adDhv+af=lqBB zo%5Z`zqhqzdu2s+1%_dji6%LPq#u2o%9fzN?;7Dlq6)^^VVjkKImH23RI|DPo-mXi zkOGP}@Wrnnf?-SQ^>jOIPc{pxWsr*JL*@+|p)oA7EpIDoAAoo_=+RA)c=F3Qf$N$` ze9k55q%DD7y=l+^ZG$aob+Aw6HDcRVJdzhs00Te;&l_3O74jlch$|r7GgAa!aDjay z@rG1;vK5ys2jF3n@vAgV<6)izn!k6Qhd>D(EhD7l zcrhJ1i9|1iwm?z2T#n2INXzM=7@p@Tnx$CQk39VDfC-hn-*jtB5oF-1j&4KUGI1}W z(rxuakw9eMRAJc3%8 zlBq3$QTyJX$a6$&1ldyi4Pe5AEE32Sg@vDzY~7qR?1u@oXh zd9(fBtV<@eK%Tm=yy&p7{=h^#@1W)WFFSe!U5pP~o71uR`FW)7xc*=d5_b}EG@XBZ z@<7MRp-;+|o}SzJvMyfx>37Y4etBizf%0H*zaIF?2ObZt?$D;{o|esJ z*PgAOIO^*8tL%z2)|}k$DP5kN5}8qo*wNa8y?R2=%wO{gCD(`sHt}TzWQuK zMDIJap(nb2=7*ns*oDcRBbC23yy`kvKYV`ovU`a=EmxNv-90;;5r6+l8hQTp^u`Hn XX6*+%8gHC`h)Tl}u@-r>vFqE{F~5F& literal 0 HcmV?d00001 diff --git a/applications/external/nfc_maker/nfc_maker.c b/applications/external/nfc_maker/nfc_maker.c index 578054ade..0f27b145e 100644 --- a/applications/external/nfc_maker/nfc_maker.c +++ b/applications/external/nfc_maker/nfc_maker.c @@ -35,9 +35,11 @@ NfcMaker* nfc_maker_alloc() { view_dispatcher_add_view( app->view_dispatcher, NfcMakerViewSubmenu, submenu_get_view(app->submenu)); - app->text_input = text_input_alloc(); + app->text_input = nfc_maker_text_input_alloc(); view_dispatcher_add_view( - app->view_dispatcher, NfcMakerViewTextInput, text_input_get_view(app->text_input)); + app->view_dispatcher, + NfcMakerViewTextInput, + nfc_maker_text_input_get_view(app->text_input)); app->byte_input = byte_input_alloc(); view_dispatcher_add_view( @@ -56,7 +58,7 @@ void nfc_maker_free(NfcMaker* app) { view_dispatcher_remove_view(app->view_dispatcher, NfcMakerViewSubmenu); submenu_free(app->submenu); view_dispatcher_remove_view(app->view_dispatcher, NfcMakerViewTextInput); - text_input_free(app->text_input); + nfc_maker_text_input_free(app->text_input); view_dispatcher_remove_view(app->view_dispatcher, NfcMakerViewByteInput); byte_input_free(app->byte_input); view_dispatcher_remove_view(app->view_dispatcher, NfcMakerViewPopup); diff --git a/applications/external/nfc_maker/nfc_maker.h b/applications/external/nfc_maker/nfc_maker.h index 76a7a2538..f776d0fa0 100644 --- a/applications/external/nfc_maker/nfc_maker.h +++ b/applications/external/nfc_maker/nfc_maker.h @@ -8,7 +8,7 @@ #include #include "nfc_maker_icons.h" #include -#include +#include "nfc_maker_text_input.h" #include #include #include "scenes/nfc_maker_scene.h" @@ -42,7 +42,7 @@ typedef struct { SceneManager* scene_manager; ViewDispatcher* view_dispatcher; Submenu* submenu; - TextInput* text_input; + NFCMaker_TextInput* text_input; ByteInput* byte_input; Popup* popup; diff --git a/applications/external/nfc_maker/nfc_maker_text_input.c b/applications/external/nfc_maker/nfc_maker_text_input.c new file mode 100644 index 000000000..c5b2b27d6 --- /dev/null +++ b/applications/external/nfc_maker/nfc_maker_text_input.c @@ -0,0 +1,762 @@ +#include "nfc_maker_text_input.h" +#include +#include "nfc_maker.h" +#include + +struct NFCMaker_TextInput { + View* view; + FuriTimer* timer; +}; + +typedef struct { + const char text; + const uint8_t x; + const uint8_t y; +} NFCMaker_TextInputKey; + +typedef struct { + const NFCMaker_TextInputKey* rows[3]; + const uint8_t keyboard_index; +} Keyboard; + +typedef struct { + const char* header; + char* text_buffer; + size_t text_buffer_size; + size_t minimum_length; + bool clear_default_text; + + bool cursor_select; + size_t cursor_pos; + + NFCMaker_TextInputCallback callback; + void* callback_context; + + uint8_t selected_row; + uint8_t selected_column; + uint8_t selected_keyboard; + + NFCMaker_TextInputValidatorCallback validator_callback; + void* validator_callback_context; + FuriString* validator_text; + bool validator_message_visible; +} NFCMaker_TextInputModel; + +static const uint8_t keyboard_origin_x = 1; +static const uint8_t keyboard_origin_y = 29; +static const uint8_t keyboard_row_count = 3; +static const uint8_t keyboard_count = 2; + +#define ENTER_KEY '\r' +#define BACKSPACE_KEY '\b' +#define SWITCH_KEYBOARD_KEY 0xfe + +static const NFCMaker_TextInputKey keyboard_keys_row_1[] = { + {'q', 1, 8}, + {'w', 10, 8}, + {'e', 19, 8}, + {'r', 28, 8}, + {'t', 37, 8}, + {'y', 46, 8}, + {'u', 55, 8}, + {'i', 64, 8}, + {'o', 73, 8}, + {'p', 82, 8}, + {'0', 91, 8}, + {'1', 100, 8}, + {'2', 110, 8}, + {'3', 120, 8}, +}; + +static const NFCMaker_TextInputKey keyboard_keys_row_2[] = { + {'a', 1, 20}, + {'s', 10, 20}, + {'d', 19, 20}, + {'f', 28, 20}, + {'g', 37, 20}, + {'h', 46, 20}, + {'j', 55, 20}, + {'k', 64, 20}, + {'l', 73, 20}, + {BACKSPACE_KEY, 82, 12}, + {'4', 100, 20}, + {'5', 110, 20}, + {'6', 120, 20}, +}; + +static const NFCMaker_TextInputKey keyboard_keys_row_3[] = { + {SWITCH_KEYBOARD_KEY, 1, 23}, + {'z', 13, 32}, + {'x', 21, 32}, + {'c', 28, 32}, + {'v', 36, 32}, + {'b', 44, 32}, + {'n', 52, 32}, + {'m', 59, 32}, + {'_', 67, 32}, + {ENTER_KEY, 74, 23}, + {'7', 100, 32}, + {'8', 110, 32}, + {'9', 120, 32}, +}; + +static const NFCMaker_TextInputKey symbol_keyboard_keys_row_1[] = { + {'!', 2, 8}, + {'@', 12, 8}, + {'#', 22, 8}, + {'$', 32, 8}, + {'%', 42, 8}, + {'^', 52, 8}, + {'&', 62, 8}, + {'(', 71, 8}, + {')', 81, 8}, + {'0', 91, 8}, + {'1', 100, 8}, + {'2', 110, 8}, + {'3', 120, 8}, +}; + +static const NFCMaker_TextInputKey symbol_keyboard_keys_row_2[] = { + {'~', 2, 20}, + {'+', 12, 20}, + {'-', 22, 20}, + {'=', 32, 20}, + {'[', 42, 20}, + {']', 52, 20}, + {'{', 62, 20}, + {'}', 72, 20}, + {BACKSPACE_KEY, 82, 12}, + {'4', 100, 20}, + {'5', 110, 20}, + {'6', 120, 20}, +}; + +static const NFCMaker_TextInputKey symbol_keyboard_keys_row_3[] = { + {SWITCH_KEYBOARD_KEY, 1, 23}, + {'.', 15, 32}, + {',', 29, 32}, + {':', 41, 32}, + {'/', 53, 32}, + {'\\', 65, 32}, + {ENTER_KEY, 74, 23}, + {'7', 100, 32}, + {'8', 110, 32}, + {'9', 120, 32}, +}; + +static const Keyboard keyboard = { + .rows = + { + keyboard_keys_row_1, + keyboard_keys_row_2, + keyboard_keys_row_3, + }, + .keyboard_index = 0, +}; + +static const Keyboard symbol_keyboard = { + .rows = + { + symbol_keyboard_keys_row_1, + symbol_keyboard_keys_row_2, + symbol_keyboard_keys_row_3, + }, + .keyboard_index = 1, +}; + +static const Keyboard* keyboards[] = { + &keyboard, + &symbol_keyboard, +}; + +static void switch_keyboard(NFCMaker_TextInputModel* model) { + model->selected_keyboard = (model->selected_keyboard + 1) % keyboard_count; +} + +static uint8_t get_row_size(const Keyboard* keyboard, uint8_t row_index) { + uint8_t row_size = 0; + if(keyboard == &symbol_keyboard) { + switch(row_index + 1) { + case 1: + row_size = COUNT_OF(symbol_keyboard_keys_row_1); + break; + case 2: + row_size = COUNT_OF(symbol_keyboard_keys_row_2); + break; + case 3: + row_size = COUNT_OF(symbol_keyboard_keys_row_3); + break; + default: + furi_crash(NULL); + } + } else { + switch(row_index + 1) { + case 1: + row_size = COUNT_OF(keyboard_keys_row_1); + break; + case 2: + row_size = COUNT_OF(keyboard_keys_row_2); + break; + case 3: + row_size = COUNT_OF(keyboard_keys_row_3); + break; + default: + furi_crash(NULL); + } + } + + return row_size; +} + +static const NFCMaker_TextInputKey* get_row(const Keyboard* keyboard, uint8_t row_index) { + const NFCMaker_TextInputKey* row = NULL; + if(row_index < 3) { + row = keyboard->rows[row_index]; + } else { + furi_crash(NULL); + } + + return row; +} + +static char get_selected_char(NFCMaker_TextInputModel* model) { + return get_row( + keyboards[model->selected_keyboard], model->selected_row)[model->selected_column] + .text; +} + +static bool char_is_lowercase(char letter) { + return (letter >= 0x61 && letter <= 0x7A); +} + +static char char_to_uppercase(const char letter) { + if(letter == '_') { + return 0x20; + } else if(char_is_lowercase(letter)) { + return (letter - 0x20); + } else { + return letter; + } +} + +static void nfc_maker_text_input_backspace_cb(NFCMaker_TextInputModel* model) { + if(model->clear_default_text) { + model->text_buffer[0] = 0; + model->cursor_pos = 0; + } else if(model->cursor_pos > 0) { + char* move = model->text_buffer + model->cursor_pos; + memmove(move - 1, move, strlen(move) + 1); + model->cursor_pos--; + } +} + +static void nfc_maker_text_input_view_draw_callback(Canvas* canvas, void* _model) { + NFCMaker_TextInputModel* model = _model; + uint8_t text_length = model->text_buffer ? strlen(model->text_buffer) : 0; + uint8_t needed_string_width = canvas_width(canvas) - 8; + uint8_t start_pos = 4; + + model->cursor_pos = model->cursor_pos > text_length ? text_length : model->cursor_pos; + size_t cursor_pos = model->cursor_pos; + + canvas_clear(canvas); + canvas_set_color(canvas, ColorBlack); + + canvas_draw_str(canvas, 2, 8, model->header); + elements_slightly_rounded_frame(canvas, 1, 12, 126, 15); + + char buf[model->text_buffer_size + 1]; + if(model->text_buffer) { + strlcpy(buf, model->text_buffer, sizeof(buf)); + } + char* str = buf; + + if(model->clear_default_text) { + elements_slightly_rounded_box( + canvas, start_pos - 1, 14, canvas_string_width(canvas, str) + 2, 10); + canvas_set_color(canvas, ColorWhite); + } else { + char* move = str + cursor_pos; + memmove(move + 1, move, strlen(move) + 1); + str[cursor_pos] = '|'; + } + + if(cursor_pos > 0 && canvas_string_width(canvas, str) > needed_string_width) { + canvas_draw_str(canvas, start_pos, 22, "..."); + start_pos += 6; + needed_string_width -= 8; + for(uint32_t off = 0; + strlen(str) && canvas_string_width(canvas, str) > needed_string_width && + off < cursor_pos; + off++) { + str++; + } + } + + if(canvas_string_width(canvas, str) > needed_string_width) { + needed_string_width -= 4; + size_t len = strlen(str); + while(len && canvas_string_width(canvas, str) > needed_string_width) { + str[len--] = '\0'; + } + strcat(str, "..."); + } + + canvas_draw_str(canvas, start_pos, 22, str); + + canvas_set_font(canvas, FontKeyboard); + + for(uint8_t row = 0; row < keyboard_row_count; row++) { + const uint8_t column_count = get_row_size(keyboards[model->selected_keyboard], row); + const NFCMaker_TextInputKey* keys = get_row(keyboards[model->selected_keyboard], row); + + for(size_t column = 0; column < column_count; column++) { + bool selected = !model->cursor_select && model->selected_row == row && + model->selected_column == column; + const Icon* icon = NULL; + if(keys[column].text == ENTER_KEY) { + icon = selected ? &I_KeySaveSelected_24x11 : &I_KeySave_24x11; + } else if(keys[column].text == SWITCH_KEYBOARD_KEY) { + icon = selected ? &I_KeyKeyboardSelected_10x11 : &I_KeyKeyboard_10x11; + } else if(keys[column].text == BACKSPACE_KEY) { + icon = selected ? &I_KeyBackspaceSelected_16x9 : &I_KeyBackspace_16x9; + } + canvas_set_color(canvas, ColorBlack); + if(icon != NULL) { + canvas_draw_icon( + canvas, + keyboard_origin_x + keys[column].x, + keyboard_origin_y + keys[column].y, + icon); + } else { + if(selected) { + canvas_draw_box( + canvas, + keyboard_origin_x + keys[column].x - 1, + keyboard_origin_y + keys[column].y - 8, + 7, + 10); + canvas_set_color(canvas, ColorWhite); + } + + if(model->clear_default_text || text_length == 0) { + canvas_draw_glyph( + canvas, + keyboard_origin_x + keys[column].x, + keyboard_origin_y + keys[column].y, + char_to_uppercase(keys[column].text)); + } else { + canvas_draw_glyph( + canvas, + keyboard_origin_x + keys[column].x, + keyboard_origin_y + keys[column].y, + keys[column].text); + } + } + } + } + if(model->validator_message_visible) { + canvas_set_font(canvas, FontSecondary); + canvas_set_color(canvas, ColorWhite); + canvas_draw_box(canvas, 8, 10, 110, 48); + canvas_set_color(canvas, ColorBlack); + canvas_draw_icon(canvas, 10, 14, &I_WarningDolphin_45x42); + canvas_draw_rframe(canvas, 8, 8, 112, 50, 3); + canvas_draw_rframe(canvas, 9, 9, 110, 48, 2); + elements_multiline_text(canvas, 62, 20, furi_string_get_cstr(model->validator_text)); + canvas_set_font(canvas, FontKeyboard); + } +} + +static void nfc_maker_text_input_handle_up( + NFCMaker_TextInput* nfc_maker_text_input, + NFCMaker_TextInputModel* model) { + UNUSED(nfc_maker_text_input); + if(model->selected_row > 0) { + model->selected_row--; + if(model->selected_row == 0 && + model->selected_column > + get_row_size(keyboards[model->selected_keyboard], model->selected_row) - 6) { + model->selected_column = model->selected_column + 1; + } + if(model->selected_row == 1 && + model->selected_keyboard == symbol_keyboard.keyboard_index) { + if(model->selected_column > 5) + model->selected_column += 2; + else if(model->selected_column > 1) + model->selected_column += 1; + } + } else { + model->cursor_select = true; + model->clear_default_text = false; + } +} + +static void nfc_maker_text_input_handle_down( + NFCMaker_TextInput* nfc_maker_text_input, + NFCMaker_TextInputModel* model) { + UNUSED(nfc_maker_text_input); + if(model->cursor_select) { + model->cursor_select = false; + } else if(model->selected_row < keyboard_row_count - 1) { + model->selected_row++; + if(model->selected_row == 1 && + model->selected_column > + get_row_size(keyboards[model->selected_keyboard], model->selected_row) - 4) { + model->selected_column = model->selected_column - 1; + } + if(model->selected_row == 2 && + model->selected_keyboard == symbol_keyboard.keyboard_index) { + if(model->selected_column > 7) + model->selected_column -= 2; + else if(model->selected_column > 1) + model->selected_column -= 1; + } + } +} + +static void nfc_maker_text_input_handle_left( + NFCMaker_TextInput* nfc_maker_text_input, + NFCMaker_TextInputModel* model) { + UNUSED(nfc_maker_text_input); + if(model->cursor_select) { + if(model->cursor_pos > 0) { + model->cursor_pos = CLAMP(model->cursor_pos - 1, strlen(model->text_buffer), 0u); + } + } else if(model->selected_column > 0) { + model->selected_column--; + } else { + model->selected_column = + get_row_size(keyboards[model->selected_keyboard], model->selected_row) - 1; + } +} + +static void nfc_maker_text_input_handle_right( + NFCMaker_TextInput* nfc_maker_text_input, + NFCMaker_TextInputModel* model) { + UNUSED(nfc_maker_text_input); + if(model->cursor_select) { + model->cursor_pos = CLAMP(model->cursor_pos + 1, strlen(model->text_buffer), 0u); + } else if( + model->selected_column < + get_row_size(keyboards[model->selected_keyboard], model->selected_row) - 1) { + model->selected_column++; + } else { + model->selected_column = 0; + } +} + +static void nfc_maker_text_input_handle_ok( + NFCMaker_TextInput* nfc_maker_text_input, + NFCMaker_TextInputModel* model, + InputType type) { + if(model->cursor_select) return; + bool shift = type == InputTypeLong; + bool repeat = type == InputTypeRepeat; + char selected = get_selected_char(model); + size_t text_length = strlen(model->text_buffer); + + if(selected == ENTER_KEY) { + if(model->validator_callback && + (!model->validator_callback( + model->text_buffer, model->validator_text, model->validator_callback_context))) { + model->validator_message_visible = true; + furi_timer_start(nfc_maker_text_input->timer, furi_kernel_get_tick_frequency() * 4); + } else if(model->callback != 0 && text_length >= model->minimum_length) { + model->callback(model->callback_context); + } + } else if(selected == SWITCH_KEYBOARD_KEY) { + switch_keyboard(model); + } else { + if(selected == BACKSPACE_KEY) { + nfc_maker_text_input_backspace_cb(model); + } else if(!repeat) { + if(model->clear_default_text) { + text_length = 0; + } + if(text_length < (model->text_buffer_size - 1)) { + if(shift != (text_length == 0)) { + selected = char_to_uppercase(selected); + } + if(model->clear_default_text) { + model->text_buffer[0] = selected; + model->text_buffer[1] = '\0'; + model->cursor_pos = 1; + } else { + char* move = model->text_buffer + model->cursor_pos; + memmove(move + 1, move, strlen(move) + 1); + model->text_buffer[model->cursor_pos] = selected; + model->cursor_pos++; + } + } + } + model->clear_default_text = false; + } +} + +static bool nfc_maker_text_input_view_input_callback(InputEvent* event, void* context) { + NFCMaker_TextInput* nfc_maker_text_input = context; + furi_assert(nfc_maker_text_input); + + bool consumed = false; + + // Acquire model + NFCMaker_TextInputModel* model = view_get_model(nfc_maker_text_input->view); + + if((!(event->type == InputTypePress) && !(event->type == InputTypeRelease)) && + model->validator_message_visible) { + model->validator_message_visible = false; + consumed = true; + } else if(event->type == InputTypeShort) { + consumed = true; + switch(event->key) { + case InputKeyUp: + nfc_maker_text_input_handle_up(nfc_maker_text_input, model); + break; + case InputKeyDown: + nfc_maker_text_input_handle_down(nfc_maker_text_input, model); + break; + case InputKeyLeft: + nfc_maker_text_input_handle_left(nfc_maker_text_input, model); + break; + case InputKeyRight: + nfc_maker_text_input_handle_right(nfc_maker_text_input, model); + break; + case InputKeyOk: + nfc_maker_text_input_handle_ok(nfc_maker_text_input, model, event->type); + break; + default: + consumed = false; + break; + } + } else if(event->type == InputTypeLong) { + consumed = true; + switch(event->key) { + case InputKeyUp: + nfc_maker_text_input_handle_up(nfc_maker_text_input, model); + break; + case InputKeyDown: + nfc_maker_text_input_handle_down(nfc_maker_text_input, model); + break; + case InputKeyLeft: + nfc_maker_text_input_handle_left(nfc_maker_text_input, model); + break; + case InputKeyRight: + nfc_maker_text_input_handle_right(nfc_maker_text_input, model); + break; + case InputKeyOk: + nfc_maker_text_input_handle_ok(nfc_maker_text_input, model, event->type); + break; + case InputKeyBack: + nfc_maker_text_input_backspace_cb(model); + break; + default: + consumed = false; + break; + } + } else if(event->type == InputTypeRepeat) { + consumed = true; + switch(event->key) { + case InputKeyUp: + nfc_maker_text_input_handle_up(nfc_maker_text_input, model); + break; + case InputKeyDown: + nfc_maker_text_input_handle_down(nfc_maker_text_input, model); + break; + case InputKeyLeft: + nfc_maker_text_input_handle_left(nfc_maker_text_input, model); + break; + case InputKeyRight: + nfc_maker_text_input_handle_right(nfc_maker_text_input, model); + break; + case InputKeyOk: + nfc_maker_text_input_handle_ok(nfc_maker_text_input, model, event->type); + break; + case InputKeyBack: + nfc_maker_text_input_backspace_cb(model); + break; + default: + consumed = false; + break; + } + } + + // Commit model + view_commit_model(nfc_maker_text_input->view, consumed); + + return consumed; +} + +void nfc_maker_text_input_timer_callback(void* context) { + furi_assert(context); + NFCMaker_TextInput* nfc_maker_text_input = context; + + with_view_model( + nfc_maker_text_input->view, + NFCMaker_TextInputModel * model, + { model->validator_message_visible = false; }, + true); +} + +NFCMaker_TextInput* nfc_maker_text_input_alloc() { + NFCMaker_TextInput* nfc_maker_text_input = malloc(sizeof(NFCMaker_TextInput)); + nfc_maker_text_input->view = view_alloc(); + view_set_context(nfc_maker_text_input->view, nfc_maker_text_input); + view_allocate_model( + nfc_maker_text_input->view, ViewModelTypeLocking, sizeof(NFCMaker_TextInputModel)); + view_set_draw_callback(nfc_maker_text_input->view, nfc_maker_text_input_view_draw_callback); + view_set_input_callback(nfc_maker_text_input->view, nfc_maker_text_input_view_input_callback); + + nfc_maker_text_input->timer = furi_timer_alloc( + nfc_maker_text_input_timer_callback, FuriTimerTypeOnce, nfc_maker_text_input); + + with_view_model( + nfc_maker_text_input->view, + NFCMaker_TextInputModel * model, + { + model->validator_text = furi_string_alloc(); + model->minimum_length = 1; + model->cursor_pos = 0; + model->cursor_select = false; + }, + false); + + nfc_maker_text_input_reset(nfc_maker_text_input); + + return nfc_maker_text_input; +} + +void nfc_maker_text_input_free(NFCMaker_TextInput* nfc_maker_text_input) { + furi_assert(nfc_maker_text_input); + with_view_model( + nfc_maker_text_input->view, + NFCMaker_TextInputModel * model, + { furi_string_free(model->validator_text); }, + false); + + // Send stop command + furi_timer_stop(nfc_maker_text_input->timer); + // Release allocated memory + furi_timer_free(nfc_maker_text_input->timer); + + view_free(nfc_maker_text_input->view); + + free(nfc_maker_text_input); +} + +void nfc_maker_text_input_reset(NFCMaker_TextInput* nfc_maker_text_input) { + furi_assert(nfc_maker_text_input); + with_view_model( + nfc_maker_text_input->view, + NFCMaker_TextInputModel * model, + { + model->header = ""; + model->selected_row = 0; + model->selected_column = 0; + model->selected_keyboard = 0; + model->minimum_length = 1; + model->clear_default_text = false; + model->cursor_pos = 0; + model->cursor_select = false; + model->text_buffer = NULL; + model->text_buffer_size = 0; + model->callback = NULL; + model->callback_context = NULL; + model->validator_callback = NULL; + model->validator_callback_context = NULL; + furi_string_reset(model->validator_text); + model->validator_message_visible = false; + }, + true); +} + +View* nfc_maker_text_input_get_view(NFCMaker_TextInput* nfc_maker_text_input) { + furi_assert(nfc_maker_text_input); + return nfc_maker_text_input->view; +} + +void nfc_maker_text_input_set_result_callback( + NFCMaker_TextInput* nfc_maker_text_input, + NFCMaker_TextInputCallback callback, + void* callback_context, + char* text_buffer, + size_t text_buffer_size, + bool clear_default_text) { + with_view_model( + nfc_maker_text_input->view, + NFCMaker_TextInputModel * model, + { + model->callback = callback; + model->callback_context = callback_context; + model->text_buffer = text_buffer; + model->text_buffer_size = text_buffer_size; + model->clear_default_text = clear_default_text; + model->cursor_select = false; + if(text_buffer && text_buffer[0] != '\0') { + model->cursor_pos = strlen(text_buffer); + // Set focus on Save + model->selected_row = 2; + model->selected_column = 9; + model->selected_keyboard = 0; + } else { + model->cursor_pos = 0; + } + }, + true); +} + +void nfc_maker_text_input_set_minimum_length( + NFCMaker_TextInput* nfc_maker_text_input, + size_t minimum_length) { + with_view_model( + nfc_maker_text_input->view, + NFCMaker_TextInputModel * model, + { model->minimum_length = minimum_length; }, + true); +} + +void nfc_maker_text_input_set_validator( + NFCMaker_TextInput* nfc_maker_text_input, + NFCMaker_TextInputValidatorCallback callback, + void* callback_context) { + with_view_model( + nfc_maker_text_input->view, + NFCMaker_TextInputModel * model, + { + model->validator_callback = callback; + model->validator_callback_context = callback_context; + }, + true); +} + +NFCMaker_TextInputValidatorCallback + nfc_maker_text_input_get_validator_callback(NFCMaker_TextInput* nfc_maker_text_input) { + NFCMaker_TextInputValidatorCallback validator_callback = NULL; + with_view_model( + nfc_maker_text_input->view, + NFCMaker_TextInputModel * model, + { validator_callback = model->validator_callback; }, + false); + return validator_callback; +} + +void* nfc_maker_text_input_get_validator_callback_context( + NFCMaker_TextInput* nfc_maker_text_input) { + void* validator_callback_context = NULL; + with_view_model( + nfc_maker_text_input->view, + NFCMaker_TextInputModel * model, + { validator_callback_context = model->validator_callback_context; }, + false); + return validator_callback_context; +} + +void nfc_maker_text_input_set_header_text( + NFCMaker_TextInput* nfc_maker_text_input, + const char* text) { + with_view_model( + nfc_maker_text_input->view, + NFCMaker_TextInputModel * model, + { model->header = text; }, + true); +} diff --git a/applications/external/nfc_maker/nfc_maker_text_input.h b/applications/external/nfc_maker/nfc_maker_text_input.h new file mode 100644 index 000000000..91fa93e9d --- /dev/null +++ b/applications/external/nfc_maker/nfc_maker_text_input.h @@ -0,0 +1,85 @@ +#pragma once + +#include +#include "nfc_maker_validators.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** Text input anonymous structure */ +typedef struct NFCMaker_TextInput NFCMaker_TextInput; +typedef void (*NFCMaker_TextInputCallback)(void* context); +typedef bool ( + *NFCMaker_TextInputValidatorCallback)(const char* text, FuriString* error, void* context); + +/** Allocate and initialize text input + * + * This text input is used to enter string + * + * @return NFCMaker_TextInput instance + */ +NFCMaker_TextInput* nfc_maker_text_input_alloc(); + +/** Deinitialize and free text input + * + * @param text_input NFCMaker_TextInput instance + */ +void nfc_maker_text_input_free(NFCMaker_TextInput* text_input); + +/** Clean text input view Note: this function does not free memory + * + * @param text_input Text input instance + */ +void nfc_maker_text_input_reset(NFCMaker_TextInput* text_input); + +/** Get text input view + * + * @param text_input NFCMaker_TextInput instance + * + * @return View instance that can be used for embedding + */ +View* nfc_maker_text_input_get_view(NFCMaker_TextInput* text_input); + +/** Set text input result callback + * + * @param text_input NFCMaker_TextInput instance + * @param callback callback fn + * @param callback_context callback context + * @param text_buffer pointer to YOUR text buffer, that we going + * to modify + * @param text_buffer_size YOUR text buffer size in bytes. Max string + * length will be text_buffer_size-1. + * @param clear_default_text clear text from text_buffer on first OK + * event + */ +void nfc_maker_text_input_set_result_callback( + NFCMaker_TextInput* text_input, + NFCMaker_TextInputCallback callback, + void* callback_context, + char* text_buffer, + size_t text_buffer_size, + bool clear_default_text); + +void nfc_maker_text_input_set_validator( + NFCMaker_TextInput* text_input, + NFCMaker_TextInputValidatorCallback callback, + void* callback_context); + +void nfc_maker_text_input_set_minimum_length(NFCMaker_TextInput* text_input, size_t minimum_length); + +NFCMaker_TextInputValidatorCallback + nfc_maker_text_input_get_validator_callback(NFCMaker_TextInput* text_input); + +void* nfc_maker_text_input_get_validator_callback_context(NFCMaker_TextInput* text_input); + +/** Set text input header text + * + * @param text_input NFCMaker_TextInput instance + * @param text text to be shown + */ +void nfc_maker_text_input_set_header_text(NFCMaker_TextInput* text_input, const char* text); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/applications/external/nfc_maker/nfc_maker_validators.c b/applications/external/nfc_maker/nfc_maker_validators.c new file mode 100644 index 000000000..77c1b7fb7 --- /dev/null +++ b/applications/external/nfc_maker/nfc_maker_validators.c @@ -0,0 +1,57 @@ +#include +#include "nfc_maker_validators.h" +#include + +struct ValidatorIsFile { + char* app_path_folder; + const char* app_extension; + char* current_name; +}; + +bool validator_is_file_callback(const char* text, FuriString* error, void* context) { + furi_assert(context); + ValidatorIsFile* instance = context; + + if(instance->current_name != NULL) { + if(strcmp(instance->current_name, text) == 0) { + return true; + } + } + + bool ret = true; + FuriString* path = furi_string_alloc_printf( + "%s/%s%s", instance->app_path_folder, text, instance->app_extension); + Storage* storage = furi_record_open(RECORD_STORAGE); + if(storage_common_stat(storage, furi_string_get_cstr(path), NULL) == FSE_OK) { + ret = false; + furi_string_printf(error, "This name\nexists!\nChoose\nanother one."); + } else { + ret = true; + } + furi_string_free(path); + furi_record_close(RECORD_STORAGE); + + return ret; +} + +ValidatorIsFile* validator_is_file_alloc_init( + const char* app_path_folder, + const char* app_extension, + const char* current_name) { + ValidatorIsFile* instance = malloc(sizeof(ValidatorIsFile)); + + instance->app_path_folder = strdup(app_path_folder); + instance->app_extension = app_extension; + if(current_name != NULL) { + instance->current_name = strdup(current_name); + } + + return instance; +} + +void validator_is_file_free(ValidatorIsFile* instance) { + furi_assert(instance); + free(instance->app_path_folder); + free(instance->current_name); + free(instance); +} diff --git a/applications/external/nfc_maker/nfc_maker_validators.h b/applications/external/nfc_maker/nfc_maker_validators.h new file mode 100644 index 000000000..d9200b6db --- /dev/null +++ b/applications/external/nfc_maker/nfc_maker_validators.h @@ -0,0 +1,21 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif +typedef struct ValidatorIsFile ValidatorIsFile; + +ValidatorIsFile* validator_is_file_alloc_init( + const char* app_path_folder, + const char* app_extension, + const char* current_name); + +void validator_is_file_free(ValidatorIsFile* instance); + +bool validator_is_file_callback(const char* text, FuriString* error, void* context); + +#ifdef __cplusplus +} +#endif diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_https.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_https.c index 77b32afe1..8f9704a7e 100644 --- a/applications/external/nfc_maker/scenes/nfc_maker_scene_https.c +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_https.c @@ -12,13 +12,13 @@ static void nfc_maker_scene_https_text_input_callback(void* context) { void nfc_maker_scene_https_on_enter(void* context) { NfcMaker* app = context; - TextInput* text_input = app->text_input; + NFCMaker_TextInput* text_input = app->text_input; - text_input_set_header_text(text_input, "Enter HTTPS Link:"); + nfc_maker_text_input_set_header_text(text_input, "Enter HTTPS Link:"); strlcpy(app->text_buf, "google.com", TEXT_INPUT_LEN); - text_input_set_result_callback( + nfc_maker_text_input_set_result_callback( text_input, nfc_maker_scene_https_text_input_callback, app, @@ -49,5 +49,5 @@ bool nfc_maker_scene_https_on_event(void* context, SceneManagerEvent event) { void nfc_maker_scene_https_on_exit(void* context) { NfcMaker* app = context; - text_input_reset(app->text_input); + nfc_maker_text_input_reset(app->text_input); } diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_mail.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_mail.c index 98c648f6a..2f91c542d 100644 --- a/applications/external/nfc_maker/scenes/nfc_maker_scene_mail.c +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_mail.c @@ -12,13 +12,13 @@ static void nfc_maker_scene_mail_text_input_callback(void* context) { void nfc_maker_scene_mail_on_enter(void* context) { NfcMaker* app = context; - TextInput* text_input = app->text_input; + NFCMaker_TextInput* text_input = app->text_input; - text_input_set_header_text(text_input, "Enter EMail Address:"); + nfc_maker_text_input_set_header_text(text_input, "Enter Email Address:"); strlcpy(app->text_buf, "ben.dover@example.com", TEXT_INPUT_LEN); - text_input_set_result_callback( + nfc_maker_text_input_set_result_callback( text_input, nfc_maker_scene_mail_text_input_callback, app, @@ -49,5 +49,5 @@ bool nfc_maker_scene_mail_on_event(void* context, SceneManagerEvent event) { void nfc_maker_scene_mail_on_exit(void* context) { NfcMaker* app = context; - text_input_reset(app->text_input); + nfc_maker_text_input_reset(app->text_input); } diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_menu.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_menu.c index 4268e7e2f..9f8b3c245 100644 --- a/applications/external/nfc_maker/scenes/nfc_maker_scene_menu.c +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_menu.c @@ -22,7 +22,7 @@ void nfc_maker_scene_menu_on_enter(void* context) { submenu, "HTTPS Link", NfcMakerSceneHttps, nfc_maker_scene_menu_submenu_callback, app); submenu_add_item( - submenu, "Mail Address", NfcMakerSceneMail, nfc_maker_scene_menu_submenu_callback, app); + submenu, "Email Address", NfcMakerSceneMail, nfc_maker_scene_menu_submenu_callback, app); submenu_add_item( submenu, "Phone Number", NfcMakerScenePhone, nfc_maker_scene_menu_submenu_callback, app); diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_name.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_name.c index dd5170d94..7f07a4e50 100644 --- a/applications/external/nfc_maker/scenes/nfc_maker_scene_name.c +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_name.c @@ -12,13 +12,13 @@ static void nfc_maker_scene_name_text_input_callback(void* context) { void nfc_maker_scene_name_on_enter(void* context) { NfcMaker* app = context; - TextInput* text_input = app->text_input; + NFCMaker_TextInput* text_input = app->text_input; - text_input_set_header_text(text_input, "Name the NFC tag:"); + nfc_maker_text_input_set_header_text(text_input, "Name the NFC tag:"); set_random_name(app->name_buf, TEXT_INPUT_LEN); - text_input_set_result_callback( + nfc_maker_text_input_set_result_callback( text_input, nfc_maker_scene_name_text_input_callback, app, @@ -28,7 +28,7 @@ void nfc_maker_scene_name_on_enter(void* context) { ValidatorIsFile* validator_is_file = validator_is_file_alloc_init(NFC_APP_FOLDER, NFC_APP_EXTENSION, NULL); - text_input_set_validator(text_input, validator_is_file_callback, validator_is_file); + nfc_maker_text_input_set_validator(text_input, validator_is_file_callback, validator_is_file); view_dispatcher_switch_to_view(app->view_dispatcher, NfcMakerViewTextInput); } @@ -53,5 +53,5 @@ bool nfc_maker_scene_name_on_event(void* context, SceneManagerEvent event) { void nfc_maker_scene_name_on_exit(void* context) { NfcMaker* app = context; - text_input_reset(app->text_input); + nfc_maker_text_input_reset(app->text_input); } diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_phone.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_phone.c index 4e70bcb09..a399e12e3 100644 --- a/applications/external/nfc_maker/scenes/nfc_maker_scene_phone.c +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_phone.c @@ -12,13 +12,13 @@ static void nfc_maker_scene_phone_text_input_callback(void* context) { void nfc_maker_scene_phone_on_enter(void* context) { NfcMaker* app = context; - TextInput* text_input = app->text_input; + NFCMaker_TextInput* text_input = app->text_input; - text_input_set_header_text(text_input, "Enter Phone Number:"); + nfc_maker_text_input_set_header_text(text_input, "Enter Phone Number:"); strlcpy(app->text_buf, "+", TEXT_INPUT_LEN); - text_input_set_result_callback( + nfc_maker_text_input_set_result_callback( text_input, nfc_maker_scene_phone_text_input_callback, app, @@ -49,5 +49,5 @@ bool nfc_maker_scene_phone_on_event(void* context, SceneManagerEvent event) { void nfc_maker_scene_phone_on_exit(void* context) { NfcMaker* app = context; - text_input_reset(app->text_input); + nfc_maker_text_input_reset(app->text_input); } diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_text.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_text.c index 17d84e0a9..fa8698cf4 100644 --- a/applications/external/nfc_maker/scenes/nfc_maker_scene_text.c +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_text.c @@ -12,13 +12,13 @@ static void nfc_maker_scene_text_text_input_callback(void* context) { void nfc_maker_scene_text_on_enter(void* context) { NfcMaker* app = context; - TextInput* text_input = app->text_input; + NFCMaker_TextInput* text_input = app->text_input; - text_input_set_header_text(text_input, "Enter Text Note:"); + nfc_maker_text_input_set_header_text(text_input, "Enter Text Note:"); strlcpy(app->text_buf, "Lorem ipsum", TEXT_INPUT_LEN); - text_input_set_result_callback( + nfc_maker_text_input_set_result_callback( text_input, nfc_maker_scene_text_text_input_callback, app, @@ -49,5 +49,5 @@ bool nfc_maker_scene_text_on_event(void* context, SceneManagerEvent event) { void nfc_maker_scene_text_on_exit(void* context) { NfcMaker* app = context; - text_input_reset(app->text_input); + nfc_maker_text_input_reset(app->text_input); } diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_url.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_url.c index e5a4996b2..8d6ae9e69 100644 --- a/applications/external/nfc_maker/scenes/nfc_maker_scene_url.c +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_url.c @@ -12,13 +12,13 @@ static void nfc_maker_scene_url_text_input_callback(void* context) { void nfc_maker_scene_url_on_enter(void* context) { NfcMaker* app = context; - TextInput* text_input = app->text_input; + NFCMaker_TextInput* text_input = app->text_input; - text_input_set_header_text(text_input, "Enter Plain URL:"); + nfc_maker_text_input_set_header_text(text_input, "Enter Plain URL:"); strlcpy(app->text_buf, "https://google.com", TEXT_INPUT_LEN); - text_input_set_result_callback( + nfc_maker_text_input_set_result_callback( text_input, nfc_maker_scene_url_text_input_callback, app, @@ -49,5 +49,5 @@ bool nfc_maker_scene_url_on_event(void* context, SceneManagerEvent event) { void nfc_maker_scene_url_on_exit(void* context) { NfcMaker* app = context; - text_input_reset(app->text_input); + nfc_maker_text_input_reset(app->text_input); } diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_wifi.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_wifi.c index 68efaf7f6..eabc79c5b 100644 --- a/applications/external/nfc_maker/scenes/nfc_maker_scene_wifi.c +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_wifi.c @@ -12,13 +12,13 @@ static void nfc_maker_scene_wifi_text_input_callback(void* context) { void nfc_maker_scene_wifi_on_enter(void* context) { NfcMaker* app = context; - TextInput* text_input = app->text_input; + NFCMaker_TextInput* text_input = app->text_input; - text_input_set_header_text(text_input, "Enter WiFi SSID:"); + nfc_maker_text_input_set_header_text(text_input, "Enter WiFi SSID:"); strlcpy(app->text_buf, "Bill Wi the Science Fi", WIFI_INPUT_LEN); - text_input_set_result_callback( + nfc_maker_text_input_set_result_callback( text_input, nfc_maker_scene_wifi_text_input_callback, app, @@ -51,5 +51,5 @@ bool nfc_maker_scene_wifi_on_event(void* context, SceneManagerEvent event) { void nfc_maker_scene_wifi_on_exit(void* context) { NfcMaker* app = context; - text_input_reset(app->text_input); + nfc_maker_text_input_reset(app->text_input); } diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_wifi_pass.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_wifi_pass.c index 3f5b4bd76..c0301d338 100644 --- a/applications/external/nfc_maker/scenes/nfc_maker_scene_wifi_pass.c +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_wifi_pass.c @@ -12,13 +12,13 @@ static void nfc_maker_scene_wifi_pass_text_input_callback(void* context) { void nfc_maker_scene_wifi_pass_on_enter(void* context) { NfcMaker* app = context; - TextInput* text_input = app->text_input; + NFCMaker_TextInput* text_input = app->text_input; - text_input_set_header_text(text_input, "Enter WiFi Password:"); + nfc_maker_text_input_set_header_text(text_input, "Enter WiFi Password:"); strlcpy(app->pass_buf, "244466666", WIFI_INPUT_LEN); - text_input_set_result_callback( + nfc_maker_text_input_set_result_callback( text_input, nfc_maker_scene_wifi_pass_text_input_callback, app, @@ -49,5 +49,5 @@ bool nfc_maker_scene_wifi_pass_on_event(void* context, SceneManagerEvent event) void nfc_maker_scene_wifi_pass_on_exit(void* context) { NfcMaker* app = context; - text_input_reset(app->text_input); + nfc_maker_text_input_reset(app->text_input); } From 8369f72afb524d806fd40d5d77538e005ba82857 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sat, 8 Jul 2023 19:52:25 +0300 Subject: [PATCH 069/364] move app --- applications/external/bad_bt/application.fam | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/external/bad_bt/application.fam b/applications/external/bad_bt/application.fam index a3544b997..981c0c0c0 100644 --- a/applications/external/bad_bt/application.fam +++ b/applications/external/bad_bt/application.fam @@ -10,7 +10,7 @@ App( stack_size=2 * 1024, order=70, fap_libs=["assets"], - fap_category="Tools", + fap_category="Bluetooth", fap_icon="images/badbt_10px.png", fap_icon_assets="images", ) From 27259c0cffb9a9b4c9444ad9ab3dc61b849564d6 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 9 Jul 2023 00:58:46 +0300 Subject: [PATCH 070/364] update changelog --- CHANGELOG.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 74bf3dc9d..820afe918 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,14 @@ ## New changes -* SubGHz: Keeloq: Centurion Nova read and emulate support (+ add manually) +* !!! **Warning! After installing, Desktop settings (Favoutite apps, PIN Code, AutoLock time..) will be resetted to default due to settings changes, Please set your PIN code, Favourite apps again in Settings->Desktop** !!! +* If you have copied any apps manually into `apps` folder - remove `apps` folder or that specific apps you copied on your microSD before installing this release to avoid issues due to OFW API version update! If you using regular builds or extra pack builds (e) without your manually added apps, all included apps will be installed automatically, no extra actions needed! +----- +* SubGHz: **Keeloq: Centurion Nova read and emulate support (+ add manually)** * SubGHz: FAAC SLH - UI Fixes, Fix sending signals with no seed * SubGHz: Code cleanup, proper handling of protocols ignore options (by @gid9798 | PR #516) * SubGHz: Various UI fixes (by @wosk | PR #527) * NFC: Fixed issue #532 (Mifare classic user dict - delete removes more than selected key) * Infrared: Updated universal remote assets (by @amec0e | PR #529) -* Plugins: Use correct categories for all plugins (extra pack too) +* Plugins: **Use correct categories (folders) for all plugins (extra pack too)** * Plugins: Various fixes for uFBT (by @hedger) * Plugins: Added **NFC Maker** plugin (make tags with URLs, Wifi and other things) [(by Willy-JL)](https://github.com/ClaraCrazy/Flipper-Xtreme/tree/dev/applications/external/nfc_maker) * Plugins: Added JetPack Joyride [(by timstrasser)](https://github.com/timstrasser) @@ -14,7 +17,7 @@ * Plugins: Updated i2c Tools [(by NaejEL)](https://github.com/NaejEL/flipperzero-i2ctools) * Settings: Change LED and volume settings by 5% steps (by @cokyrain) * BLE: BadBT fixes and furi_hal_bt cleanup (by @Willy-JL) -* WIP OFW PR 2825: NFC: Improved MFC emulation on some readers (by AloneLiberty) +* WIP OFW PR 2825: **NFC: Improved MFC emulation on some readers (by AloneLiberty)** * OFW PR 2829: Decode only supported Oregon 3 sensor (by @wosk) * OFW PR: Update OFW PR 2782 * OFW: SubGhz: add "SubGhz test" external application and the ability to work "SubGhz" as an external application From b148e396d74ff636c3f90efff43475ba36bf0371 Mon Sep 17 00:00:00 2001 From: Dusan Hlavaty Date: Mon, 10 Jul 2023 08:22:30 +0200 Subject: [PATCH 071/364] feat(infrared): add TCL TV remote (#2853) Adds a TCL TV remote to the universal remotes list. Tested with TCL 55C815 television --- assets/resources/infrared/assets/tv.ir | 36 ++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/assets/resources/infrared/assets/tv.ir b/assets/resources/infrared/assets/tv.ir index ba8807123..c4b6f0c42 100644 --- a/assets/resources/infrared/assets/tv.ir +++ b/assets/resources/infrared/assets/tv.ir @@ -1681,3 +1681,39 @@ type: raw frequency: 38000 duty_cycle: 0.330000 data: 3462 1592 490 332 513 1200 489 331 514 1201 489 355 490 1201 489 356 512 1178 489 356 512 1178 512 334 487 1202 488 1202 488 357 512 1178 512 334 486 1203 487 1202 488 1203 487 1204 486 383 461 1228 488 357 488 357 487 357 487 1203 486 1204 486 359 485 1205 485 360 485 361 484 360 485 360 485 361 484 361 484 361 484 1206 484 360 484 361 484 361 484 1206 484 361 484 361 484 361 484 1206 484 361 484 1206 484 361 484 71543 3434 1620 486 359 485 1205 485 360 485 1206 484 360 485 1206 484 360 485 1206 484 360 485 1206 484 360 485 1205 485 1206 484 360 485 1206 484 360 485 1206 484 1206 484 1206 484 1206 484 361 484 1206 484 360 485 360 485 361 484 1206 484 1206 484 360 484 1206 484 360 485 361 484 361 484 360 485 361 484 361 484 361 484 1206 484 361 484 361 484 361 484 1206 484 361 484 361 484 361 484 1207 483 361 484 1206 484 361 484 71543 3435 1619 486 358 486 1204 486 359 486 1205 485 360 485 1205 485 360 485 1205 485 360 485 1205 485 360 484 1205 485 1205 485 360 485 1205 485 360 485 1205 485 1205 485 1205 485 1206 484 360 485 1205 485 360 485 360 485 360 485 1205 485 1206 484 360 485 1206 484 360 485 360 485 360 485 360 485 360 485 360 485 360 485 1206 484 360 485 360 485 360 485 1206 484 360 485 360 485 360 485 1205 485 360 485 1206 484 360 485 71542 3436 1619 486 358 487 1204 486 359 485 1205 485 360 485 1205 485 360 485 1205 485 360 485 1205 485 360 485 1205 485 1205 485 360 485 1205 485 360 485 1206 484 1206 484 1206 484 1206 484 360 485 1206 484 360 485 360 485 361 484 1206 484 1206 484 361 484 1206 484 361 484 361 484 361 484 361 484 361 484 361 484 360 485 1206 484 361 484 361 484 361 484 1206 484 361 484 361 484 360 485 1206 484 361 484 1206 484 361 484 71542 3437 1618 487 358 486 1204 486 359 486 1205 485 360 485 1205 485 360 485 1205 485 360 485 1205 485 360 485 1205 485 1206 484 360 485 1205 485 360 485 1206 484 1205 485 1206 484 1205 485 360 485 1205 485 360 485 360 485 360 485 1205 485 1205 485 360 485 1205 485 360 485 360 485 360 485 360 485 360 485 360 485 360 485 1205 485 360 485 360 485 360 485 1205 485 360 485 360 485 360 485 1205 485 360 485 1205 485 360 485 +# Model: TCL +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3977 3993 494 1994 495 1995 496 1997 494 1996 495 1004 496 1004 496 1995 496 1004 496 1997 494 1005 495 1995 495 1007 493 1006 494 1006 494 1005 495 1007 493 1997 494 1995 496 1004 496 1995 496 1005 495 1995 496 1003 497 1995 496 8467 3980 3993 494 1994 495 1996 495 1997 494 1995 496 1004 496 1006 494 1995 496 1004 496 1996 495 1004 496 1996 495 1005 495 1005 495 1004 496 1005 495 1005 495 1996 495 1995 496 1005 495 1996 495 1004 496 1995 496 1006 569 1920 571 8393 3980 3993 571 1918 572 1922 569 1920 571 1920 571 929 572 929 571 1920 571 929 571 1920 571 929 571 1921 570 930 570 929 571 929 571 929 571 928 572 1920 571 1921 570 930 571 1920 572 930 571 1921 571 929 571 1923 569 8396 3980 3994 570 1921 569 1923 569 1921 571 1920 572 929 572 930 571 1921 570 930 571 1922 570 930 571 1921 570 930 570 930 571 929 571 929 571 929 572 1922 570 1921 570 931 570 1922 570 930 571 1922 569 931 570 1921 570 +# +name: Vol_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3979 3995 493 1994 496 1997 495 1999 492 1999 492 1006 495 1008 493 1998 493 1006 494 1997 495 1999 493 1997 494 1998 493 1006 495 1007 493 1005 495 1006 495 2025 467 1998 494 1006 494 1996 495 1006 494 1005 495 1006 494 1007 493 8468 3979 3995 492 1995 495 1999 492 1997 494 1997 494 1007 493 1006 494 1997 494 1006 494 1997 494 1996 571 1923 569 1921 570 930 570 929 571 931 569 931 575 1916 570 1920 571 930 570 1922 570 930 571 930 570 930 576 924 576 +# +name: Vol_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3951 3994 494 1997 493 1998 494 1998 493 1998 493 1005 496 1005 496 1996 495 1005 495 1997 495 1996 495 1996 495 1006 494 1005 495 1006 494 1005 495 1006 494 1996 495 1998 493 1005 495 1997 494 1008 492 1006 494 1006 494 1997 494 8471 3977 3996 493 1996 493 1997 494 1998 493 1997 494 1006 494 1007 493 1997 494 1009 492 1996 495 1996 495 1997 494 1006 494 1006 494 1006 494 1006 494 1006 494 1997 493 1997 494 1006 494 1996 494 1005 495 1004 495 1006 494 1996 494 +# +name: Ch_next +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3978 3994 494 1995 495 1996 495 1996 495 1996 495 1006 494 1004 497 1997 494 1005 495 1997 520 1970 577 923 578 1914 577 924 576 924 576 925 575 924 576 1914 577 1915 576 924 576 1914 577 924 576 923 577 1915 576 926 574 8388 3978 3993 576 1913 576 1915 576 1915 576 1917 574 923 577 923 577 1943 548 925 575 1916 576 1915 575 924 576 1915 576 925 575 927 573 925 575 926 574 1916 574 1918 573 927 573 1918 573 928 572 927 573 1918 573 926 574 8389 4006 3966 572 1918 571 1919 572 1918 573 1920 570 929 571 929 571 1922 569 928 571 1920 571 1921 570 928 572 1919 572 929 571 929 571 929 571 929 571 1921 570 1921 521 980 569 1921 521 981 519 979 521 1971 520 979 521 +# +name: Ch_prev +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3979 3994 494 1995 495 1997 494 1996 495 1998 493 1005 495 1006 494 1997 494 1005 495 1996 495 1995 496 1005 495 1005 495 1006 494 1005 495 1004 496 1005 495 1997 494 1997 494 1004 496 1996 495 1005 495 1005 495 1997 494 1996 495 8467 3976 3991 496 1995 495 1996 494 1994 496 1996 494 1005 495 1005 495 1996 495 1005 495 1995 495 1995 496 1006 494 1005 495 1006 494 1005 495 1004 496 1006 494 1994 496 1996 494 1005 495 1995 495 1004 496 1004 496 1995 495 1996 494 +# +name: Mute +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3981 3992 495 1994 495 1995 496 1996 494 1996 495 1005 495 1006 494 1995 495 1997 494 1996 495 1996 494 1997 494 1996 495 1006 494 1005 495 1004 496 1005 495 1995 496 1994 496 1005 495 1004 496 1005 495 1006 494 1004 496 1006 494 8466 3978 3991 495 1994 495 1997 493 1994 496 1995 495 1004 496 1004 496 1996 494 1997 493 1996 494 1995 495 1995 495 1997 493 1004 495 1004 495 1006 494 1005 494 1998 491 1996 494 1006 494 1004 496 1006 494 1006 493 1005 495 1005 571 From 0195f8bf0044ec913e07b8ad34e449fd3e0f73cb Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Mon, 10 Jul 2023 09:48:00 +0300 Subject: [PATCH 072/364] [FL-3350] Device Info update (#2840) * Update F18 version info * Certification info for F18 Co-authored-by: Aleksandr Kutuzov --- applications/settings/about/about.c | 4 +++- assets/icons/About/Certification2_46x33.png | Bin 0 -> 229 bytes assets/icons/About/Certification2_98x33.png | Bin 2495 -> 0 bytes firmware/targets/f18/api_symbols.csv | 3 ++- .../f18/furi_hal/furi_hal_version_device.c | 12 ++++++++---- firmware/targets/f7/api_symbols.csv | 3 ++- .../f7/furi_hal/furi_hal_version_device.c | 4 ++++ .../targets/furi_hal_include/furi_hal_version.h | 6 ++++++ 8 files changed, 25 insertions(+), 7 deletions(-) create mode 100644 assets/icons/About/Certification2_46x33.png delete mode 100644 assets/icons/About/Certification2_98x33.png diff --git a/applications/settings/about/about.c b/applications/settings/about/about.c index 688103306..55bd43e5c 100644 --- a/applications/settings/about/about.c +++ b/applications/settings/about/about.c @@ -82,7 +82,9 @@ static DialogMessageButton icon1_screen(DialogsApp* dialogs, DialogMessage* mess static DialogMessageButton icon2_screen(DialogsApp* dialogs, DialogMessage* message) { DialogMessageButton result; - dialog_message_set_icon(message, &I_Certification2_98x33, 15, 10); + dialog_message_set_icon(message, &I_Certification2_46x33, 15, 10); + dialog_message_set_text( + message, furi_hal_version_get_mic_id(), 63, 27, AlignLeft, AlignCenter); result = dialog_message_show(dialogs, message); dialog_message_set_icon(message, NULL, 0, 0); diff --git a/assets/icons/About/Certification2_46x33.png b/assets/icons/About/Certification2_46x33.png new file mode 100644 index 0000000000000000000000000000000000000000..d421b829149518d4d379b03d76881f88706c3e87 GIT binary patch literal 229 zcmeAS@N?(olHy`uVBq!ia0vp^dO)nm!3-oP8fIPqQfvV}A+G=b{|7Qd4_&SUQnNf= z978H@t(g+YcR+!|`Qg9maYoZ4y+u8qcz^6{V@k`gJEQe1Lp5T}_AiC!OOBS>-1;=T zdEV~dE9_2v2<2Pid?=n_o3MCO-A5)qmHYo@Jp3jwB`^7eym+fxoxz8QtS6Qam1Y|! zr_NbyeOvU-CA$l&z7_nH6v>?!`zVuf^AS(s0M|6%%_#=?UdNv8|9>xd*oX c{KsLyP+HH}pmOhIG0>F^p00i_>zopr08YJL$p8QV literal 0 HcmV?d00001 diff --git a/assets/icons/About/Certification2_98x33.png b/assets/icons/About/Certification2_98x33.png deleted file mode 100644 index 49c5581c7523f2956fda218cfa9c048cf1998ef5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2495 zcma)7eN+=y77u<<3c9DF1uNPeM(e@FB$G)pm^lQ^7bZ%SFD0_A?JzSTQ=2gmAE?2J5Xf$#Jm7}N>SV;K-w?O!$Zhp2HA`rt! z^CZW31cr4(VoV~3%@edR3~*>DxWL$h(cSz|`k)YU5s@oohAdtmS;=X1IVKB& zqBJTjL8>BEv@}7fQb}`&1X4@KnY}J8eb=iatwkMFfl-d12T>@5bno-1ON*3OAxXc45=JzXPf}z(--R@i^+f~G#HRartn{9 zC=nD?EHTY7`81Wxv96&@EbdZc6dq01V>(oA9H|_tQ5Z~mjmfCeAc)cEPZtaU(VrQk z@Qh6jz1=tLu zJZl%c1V`&~K{tW+w%ZwSX$k@z4k=^`M5cb+!|R|yv?n)pfGm_K7suc*wM2O!{ZZrt z2BYRC$RV%?<}n!T@{!3779-yV+6_Jk9GLu$M_VRIBq4#Ys@tw&4`C)Q2QeoB`% z;R2c+T{rKuis}uK1x3ovo!8E3&XtEOMfUFLuh*G#YqJ|~=g)sLYSxvk8MnTAcT-_a zRrQuXHEce3zkkuD=YD?Cez?2yQQb{PO4jauX+C{g-JxHO)v(&?9iPYl;K zQ+uu=1A`}ytX)t2)%N4fq6g7U|GpJ+`*$6DQQzy%>HnF!|MjBk6wMp`VP}#|=eMdm zW3QfQ`>Fnr>3$m*gYUXj6D2d({q%uv=0DHxeqn;^#P`QCdsZlt|Kz*M(FaSqs1W=`??1& zmu);-rqAdrXl!qP@Amc04|ly*cW>N{o)ezkOw!CMw0~c7JgrCTE_TGn_kGyfyFIkh zN#D5^8MP#9>7H`slqp|$)3@RG|5#JS__|645+%zr&Nt17(2jq9#}(&|`Q+8^`n?~0 z>@Me84b^>LJiulR#vGSU&zMFYh&<@t&o@UsZ*7VWU0r$M>~p*=35~jvbf>iXyS?9? zI`B}p9$wS&A^Ob53ER%Rx_JE!y0K+qVT9V(d#|-6tWBNFpAzLv$@yYa_ZoN#+Nq#Kx~p6 zI1|4u?Z2nG3f~<#*7fmn Date: Mon, 10 Jul 2023 10:04:53 +0300 Subject: [PATCH 073/364] Add Hitachi RAK-50PEB universal ac remote (#2826) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: ã‚ã --- assets/resources/infrared/assets/ac.ir | 37 ++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/assets/resources/infrared/assets/ac.ir b/assets/resources/infrared/assets/ac.ir index c7b1aaf7e..cb3c2539c 100644 --- a/assets/resources/infrared/assets/ac.ir +++ b/assets/resources/infrared/assets/ac.ir @@ -507,3 +507,40 @@ type: raw frequency: 38000 duty_cycle: 0.330000 data: 3539 1637 533 1225 502 1193 534 375 501 376 501 376 500 1226 501 376 501 376 500 1226 500 1227 501 376 529 1197 558 320 557 319 555 1169 531 1195 531 346 529 1196 530 1198 528 349 526 352 524 1229 497 379 497 379 497 1230 497 380 497 380 497 379 498 380 497 380 497 380 497 380 497 380 497 380 497 380 497 380 497 380 497 380 497 380 497 380 497 9042 3535 1674 496 1230 497 1230 497 380 497 380 497 380 497 1230 497 380 497 380 496 1230 497 1230 497 380 497 1230 497 380 497 380 497 1231 496 1230 497 380 497 1231 496 1231 496 380 496 380 497 1231 496 380 497 380 496 1231 496 380 497 380 497 380 497 380 497 380 497 380 497 380 497 380 497 380 497 380 496 380 497 380 497 380 497 380 497 380 496 381 496 380 497 380 497 380 497 381 496 1231 496 381 496 380 497 380 497 381 496 381 496 1231 496 381 496 381 496 380 497 381 495 1231 496 1231 496 1231 496 380 497 381 496 381 496 380 496 381 496 381 496 381 496 381 496 381 496 1231 496 1231 496 381 496 381 496 381 496 381 496 381 496 381 496 381 496 381 496 1231 496 381 496 381 496 1232 495 381 495 1232 495 381 496 1231 496 1231 496 381 496 381 496 381 496 381 496 381 496 381 496 381 496 381 496 381 496 381 496 381 496 381 496 381 496 381 496 381 496 381 496 381 496 381 496 381 496 381 496 381 495 381 496 381 496 381 496 381 496 1232 495 381 496 381 496 381 496 381 496 381 495 381 496 381 495 382 495 381 496 382 495 381 495 382 495 381 495 382 495 382 495 382 495 382 495 382 495 382 495 382 495 382 495 382 495 381 495 1232 495 1232 495 1232 495 1232 495 1232 495 382 495 382 495 382 495 +# +# Model: Hitachi RAK-50PEB +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 30683 50966 3411 1600 493 1186 493 347 492 348 491 348 491 349 490 349 490 350 489 351 488 351 488 352 487 352 488 351 488 1192 487 352 487 351 488 352 487 352 487 352 488 352 488 351 488 1192 487 1191 488 352 487 352 487 352 487 352 487 352 487 352 487 352 487 352 487 1192 487 352 487 1192 487 1192 487 1192 487 1192 488 1192 487 1192 488 352 487 1192 487 1192 487 352 487 352 487 352 487 352 488 352 487 352 488 352 487 352 487 1192 487 1192 487 1192 487 1192 487 1192 487 1192 487 1192 487 1192 487 352 487 352 488 352 487 1192 487 352 487 352 487 352 487 352 487 1192 487 352 487 353 486 1192 487 353 486 353 486 353 486 353 486 1193 486 353 487 353 486 353 486 353 486 353 486 353 486 1193 486 1193 486 353 487 353 486 353 486 353 486 353 487 353 486 353 486 353 486 1193 486 353 486 353 486 1193 486 353 487 353 486 353 487 353 486 353 486 353 486 353 486 353 487 353 486 353 486 1193 486 1193 486 353 487 353 486 353 486 353 486 354 485 353 486 354 485 353 486 354 486 353 486 353 486 354 486 353 486 353 487 1193 486 1194 485 353 487 353 486 354 485 354 485 354 486 354 485 354 485 354 485 354 485 354 485 354 485 354 485 354 485 354 485 354 486 354 485 354 485 354 485 354 485 354 485 354 485 354 485 354 485 354 485 354 486 354 485 354 485 354 485 354 485 354 485 354 485 354 485 354 485 354 485 354 485 354 485 354 485 355 484 355 484 355 484 354 485 354 485 355 484 355 484 355 484 355 484 354 486 355 484 378 461 356 484 378 461 355 485 355 484 355 484 355 485 355 484 378 461 378 461 355 484 356 483 355 484 378 461 378 462 355 485 378 461 378 462 378 461 379 461 356 483 378 461 1195 484 378 461 379 461 356 484 378 461 379 460 379 461 378 461 378 462 378 461 379 461 378 461 378 461 379 460 379 460 379 461 378 461 378 461 378 461 378 461 378 461 379 460 379 460 379 460 379 461 379 460 1219 460 1219 460 379 461 1219 460 379 461 1219 460 +# +name: Dh +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 30684 50965 3412 1599 494 1185 494 346 493 346 493 347 492 348 491 349 490 349 490 350 489 351 489 350 489 350 489 351 489 1191 488 351 488 351 488 351 489 351 488 351 488 351 488 351 488 1191 488 1191 488 351 488 351 488 351 488 351 488 351 488 351 489 351 488 351 488 1191 488 351 489 1191 488 1191 488 1191 488 1191 488 1191 488 1191 488 351 488 1191 488 1192 487 351 488 352 487 351 488 351 488 352 487 352 487 352 487 352 488 1192 487 1192 487 1216 463 1192 487 1192 488 1192 487 1193 486 1192 488 352 487 352 487 352 487 1193 486 376 463 376 463 376 464 352 487 1216 463 376 463 376 463 1216 464 376 463 376 463 376 463 1216 463 376 463 376 463 353 486 353 487 376 463 376 463 376 463 1216 463 376 463 1216 463 376 464 376 463 376 463 376 464 376 463 376 463 376 463 376 463 1216 463 376 463 1216 463 376 463 376 463 376 463 376 463 376 463 376 463 376 463 376 463 376 464 376 463 1216 463 1217 463 376 463 377 462 377 462 377 463 376 463 376 463 377 462 376 463 377 462 377 462 377 463 376 463 377 462 377 462 1217 462 1216 463 377 463 377 462 377 462 377 462 377 463 376 463 377 462 377 463 377 462 377 462 377 462 377 462 377 462 377 462 377 462 377 462 377 463 377 462 377 463 377 462 377 462 1217 462 377 462 377 462 377 463 377 462 377 463 377 462 377 462 377 462 377 462 377 463 377 462 377 462 377 462 377 462 377 462 377 463 377 462 377 462 377 462 377 462 377 462 377 462 377 463 377 462 377 462 377 462 377 462 377 462 377 462 377 462 377 462 377 462 377 462 377 462 377 463 377 462 377 463 377 462 377 462 377 462 377 462 377 462 377 462 377 462 377 462 377 462 377 462 377 462 1217 462 377 462 377 462 377 462 377 462 378 461 377 462 377 462 378 462 377 462 377 462 377 463 377 462 378 461 378 462 377 462 378 461 377 462 378 461 378 461 378 461 378 462 377 462 378 462 1217 462 1218 461 1218 461 378 461 378 461 1218 462 377 462 378 462 +# +name: Cool_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 30747 50897 3484 1554 543 1137 542 310 529 309 530 309 530 309 530 310 529 309 530 310 529 309 530 309 530 309 530 309 530 1138 541 309 531 309 530 309 530 309 530 309 530 309 530 309 530 1138 541 1138 541 310 529 309 530 309 531 309 530 309 530 309 530 309 530 310 529 1139 541 309 530 1138 541 1138 541 1138 541 1138 542 1138 541 1138 541 310 529 1138 541 1138 541 309 530 309 530 309 531 310 529 309 530 309 530 309 530 309 530 1139 541 1139 540 1139 541 1138 541 1139 540 1139 540 1138 541 1139 540 310 529 310 529 309 530 1139 541 310 529 309 530 309 530 309 530 1139 540 309 530 309 530 1139 541 309 530 309 530 309 530 1139 540 309 531 309 530 1139 541 309 530 309 530 309 530 309 530 309 530 309 530 1139 540 309 531 309 530 309 530 309 530 309 530 309 530 309 531 309 530 309 531 309 530 1139 540 310 529 309 530 309 530 309 530 309 530 309 531 310 529 310 529 309 531 310 529 1140 540 309 530 309 531 309 530 309 530 309 530 309 531 309 530 309 530 309 530 309 530 309 531 309 530 309 531 309 530 310 529 1140 539 1140 539 309 530 309 530 309 530 309 530 310 529 309 530 309 530 309 530 309 530 309 531 309 530 309 530 309 530 309 530 309 530 309 531 309 530 309 530 309 530 309 530 309 530 1140 540 309 530 309 530 309 530 309 530 309 531 309 530 309 531 309 530 309 530 310 530 309 530 309 531 309 530 309 530 309 530 309 530 309 530 309 531 309 530 309 530 309 531 309 530 309 530 309 531 309 531 309 530 309 530 309 530 309 530 309 531 309 531 309 530 309 530 309 530 309 530 309 530 309 531 309 530 309 531 309 530 309 531 309 530 309 530 309 531 309 530 309 530 309 530 309 530 1141 538 309 531 309 530 309 530 309 530 309 531 309 530 309 530 309 530 309 531 309 530 309 530 309 530 309 530 309 530 309 530 309 530 310 530 309 531 309 530 309 530 309 530 309 530 309 530 309 531 1142 537 309 530 1142 538 309 530 1141 538 309 530 309 530 +# +name: Cool_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 30694 50951 3483 1555 542 1137 542 308 531 309 530 308 531 308 531 308 531 308 531 308 531 308 531 308 532 308 531 308 532 1139 541 308 531 308 531 308 531 308 531 308 531 309 531 308 531 1139 541 1139 540 308 531 308 531 308 531 308 531 309 530 308 531 308 531 308 532 1139 541 308 532 1139 540 1139 541 1139 540 1139 540 1139 540 1139 541 309 530 1139 540 1139 540 308 531 308 531 308 531 308 532 308 531 308 531 308 531 308 531 1140 540 1139 541 1139 540 1139 540 1139 540 1139 541 1139 540 1139 540 308 531 308 531 308 531 1140 540 309 530 308 531 308 532 308 531 1140 540 308 531 308 532 1140 539 308 531 308 531 308 531 308 532 308 531 308 531 1140 540 308 531 308 531 308 531 308 531 308 532 308 531 1140 540 308 531 308 531 308 531 308 531 308 531 308 531 1140 540 1140 540 1140 539 308 531 1140 539 308 531 308 531 308 532 308 531 306 533 308 531 306 533 308 531 307 533 308 531 1141 539 308 532 308 531 308 531 306 534 306 533 306 534 306 533 306 533 306 533 306 533 306 534 307 532 307 533 306 533 308 532 1141 539 1141 539 308 531 308 532 308 531 307 533 307 481 352 538 307 533 307 532 307 533 307 481 352 539 307 532 307 533 306 482 352 487 352 537 306 534 307 482 352 538 307 482 352 487 1192 539 309 530 307 531 306 483 352 487 352 538 307 482 352 538 306 483 352 487 352 487 352 487 353 486 352 488 353 486 352 487 352 487 353 487 353 486 353 486 353 487 352 487 353 486 353 486 353 486 353 486 353 487 353 487 353 486 353 486 353 487 353 486 353 486 353 486 353 487 353 486 353 487 352 487 353 486 353 486 353 487 353 486 353 486 353 487 353 486 353 487 353 486 353 487 353 486 1193 537 306 483 353 486 353 487 353 486 353 487 353 486 353 486 353 487 353 486 353 486 353 487 353 486 353 486 353 486 353 486 353 487 353 486 353 486 353 486 353 486 353 486 353 486 353 486 1194 485 353 487 1193 538 1142 486 1193 486 353 486 353 486 353 568 +# +name: Heat_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 30703 50953 3432 1606 490 1189 490 349 490 349 490 349 490 350 489 350 489 350 489 350 490 350 489 350 489 350 490 350 489 1190 490 350 489 350 489 350 489 350 489 350 490 350 489 350 490 1190 490 1190 489 350 489 350 489 350 489 350 490 350 490 350 489 350 490 350 490 1190 489 350 490 1190 489 1190 490 1190 489 1190 490 1190 489 1190 489 350 490 1190 489 1190 489 350 490 350 489 350 490 350 489 350 489 350 490 350 489 350 490 1190 489 1190 489 1190 490 1190 489 1190 490 1190 489 1190 489 1191 489 350 489 350 489 350 490 1190 490 350 489 350 490 350 489 350 490 1190 489 350 490 350 489 1190 490 350 489 350 490 350 490 350 489 350 489 350 489 1191 489 350 489 350 490 350 489 350 489 1191 489 1190 489 350 489 350 490 350 489 350 490 350 489 351 489 350 489 350 489 350 489 350 490 350 489 350 489 1191 489 350 489 351 489 351 488 351 489 351 488 351 489 350 489 351 489 351 489 1191 488 351 488 351 488 351 489 350 489 351 488 350 490 350 489 351 488 351 488 351 489 350 489 350 489 351 489 351 489 350 489 1191 488 1191 489 350 489 351 488 351 489 351 488 351 489 351 488 351 488 351 489 351 489 351 489 350 489 351 489 351 488 351 488 351 488 351 489 351 488 351 488 351 488 351 489 351 489 1191 488 351 489 351 488 351 489 351 488 351 489 351 489 351 488 351 488 351 489 351 489 351 488 351 488 351 488 351 489 351 488 351 488 351 489 351 488 351 488 351 489 351 489 351 488 351 488 351 489 351 489 351 488 351 489 351 488 351 489 351 488 351 489 351 488 351 489 351 488 351 489 351 488 351 488 351 489 351 488 351 489 351 489 351 489 351 488 351 489 351 488 351 488 351 489 351 488 1192 488 351 488 351 488 351 489 351 488 351 488 351 489 351 489 351 489 351 488 351 488 351 489 351 488 351 489 351 488 351 489 351 488 351 488 351 489 351 488 351 489 351 488 351 489 351 488 351 489 351 489 1191 488 1191 488 352 488 351 488 352 488 351 489 +# +name: Heat_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 30675 50953 3432 1606 490 1190 489 350 489 350 489 350 489 350 489 351 488 350 489 351 489 351 488 351 489 351 488 351 489 1191 488 351 488 351 489 351 488 351 488 351 488 351 489 351 488 1191 489 1191 489 351 488 351 488 351 488 351 488 351 489 351 489 351 488 351 489 1191 488 351 488 1191 489 1191 488 1191 488 1191 488 1191 488 1191 488 351 489 1191 488 1191 489 351 488 351 489 351 488 351 489 351 488 351 488 351 488 351 488 1191 488 1191 488 1191 488 1191 489 1191 488 1191 488 1191 489 1191 488 351 488 351 489 351 488 1191 488 351 489 351 488 351 488 351 489 1191 488 351 489 351 488 1191 488 351 488 351 488 351 489 351 489 351 488 351 489 1191 488 351 488 351 489 351 488 351 488 1191 488 1191 488 351 488 351 489 351 488 351 489 351 488 351 489 351 489 1191 488 1192 488 1191 488 351 489 1191 489 351 488 351 488 351 489 351 489 351 488 351 488 351 489 351 488 351 488 351 488 1192 488 351 489 351 488 351 488 351 489 351 489 351 488 351 488 351 488 352 487 352 488 351 488 352 488 351 488 351 488 351 488 1192 488 1192 487 352 488 352 487 352 487 352 488 352 487 352 488 351 488 352 488 352 488 352 487 352 488 351 488 351 488 352 488 352 487 352 488 352 487 352 488 352 488 352 488 352 487 1192 487 352 487 352 488 352 488 352 488 352 487 352 487 352 488 352 487 352 487 352 487 352 488 352 488 352 487 352 487 352 488 352 487 352 488 352 487 352 487 352 487 352 487 352 487 352 488 352 487 352 487 352 487 352 488 352 487 352 488 352 487 352 488 352 487 352 487 352 488 352 487 352 488 352 487 352 488 352 488 352 487 352 487 352 487 352 487 352 487 352 488 352 487 352 487 352 487 1193 486 352 487 352 488 352 487 352 487 352 488 352 487 352 488 352 488 352 487 352 488 352 487 353 486 353 487 352 487 352 488 352 488 352 487 352 487 352 487 352 487 353 487 352 488 352 488 352 487 1193 486 1193 486 1193 486 1193 487 353 487 352 487 353 486 From 9b2d80d6b7459e7e074b59948341046f3f7ee7f2 Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Mon, 10 Jul 2023 11:03:41 +0300 Subject: [PATCH 074/364] [FL-3400] External menu apps (#2849) * FBT, applications: add MENUEXTERNAL app type * FBT, uFBT: build MENUEXTERNAL as EXTERNAL app * Loader menu: show external menu apps * LFRFID: move to sd card * FBT: always build External Applications list * Archive: look for external apps path * Infrared: move to sd card * Apps: add "start" apps * iButton: move to sd card * BadUSB: move to sd card * External apps: update icons * GPIO: move to sd card * Loader: look for external apps path * U2F: move to sd * SubGHz: move to sd * Apps: "on_start" metapackage * NFC: move to sd * Sync f7 and f18 Co-authored-by: Aleksandr Kutuzov --- applications/main/application.fam | 16 +++- .../archive/scenes/archive_scene_browser.c | 3 +- applications/main/bad_usb/application.fam | 9 +-- applications/main/bad_usb/icon.png | Bin 0 -> 576 bytes applications/main/gpio/application.fam | 6 +- applications/main/gpio/icon.png | Bin 0 -> 1760 bytes applications/main/ibutton/application.fam | 12 +-- applications/main/ibutton/icon.png | Bin 0 -> 304 bytes applications/main/infrared/application.fam | 12 +-- applications/main/infrared/icon.png | Bin 0 -> 305 bytes applications/main/lfrfid/application.fam | 14 +--- applications/main/lfrfid/icon.png | Bin 0 -> 308 bytes applications/main/nfc/application.fam | 13 ++- applications/main/nfc/icon.png | Bin 0 -> 304 bytes applications/main/onewire/application.fam | 8 -- applications/main/subghz/application.fam | 14 ++-- applications/main/subghz/icon.png | Bin 0 -> 299 bytes applications/main/u2f/application.fam | 9 +-- applications/main/u2f/icon.png | Bin 0 -> 583 bytes applications/services/applications.h | 12 +++ applications/services/loader/loader.c | 18 +++++ applications/services/loader/loader_menu.c | 21 ++++- firmware/targets/f18/api_symbols.csv | 2 +- firmware/targets/f7/api_symbols.csv | 75 ++++++++++++------ lib/nfc/SConscript | 5 ++ lib/nfc/helpers/mf_classic_dict.h | 8 ++ lib/nfc/helpers/mfkey32.h | 8 ++ lib/nfc/nfc_types.h | 8 ++ lib/nfc/nfc_worker.h | 10 ++- lib/nfc/parsers/nfc_supported_card.h | 8 ++ lib/nfc/protocols/crypto1.h | 8 ++ lib/nfc/protocols/mifare_classic.h | 8 ++ lib/nfc/protocols/mifare_desfire.h | 8 ++ lib/nfc/protocols/mifare_ultralight.h | 8 ++ scripts/fbt/appmanifest.py | 26 +++++- scripts/fbt_tools/fbt_extapps.py | 5 +- scripts/ufbt/SConstruct | 1 + site_scons/extapps.scons | 1 + 38 files changed, 258 insertions(+), 98 deletions(-) create mode 100644 applications/main/bad_usb/icon.png create mode 100644 applications/main/gpio/icon.png create mode 100644 applications/main/ibutton/icon.png create mode 100644 applications/main/infrared/icon.png create mode 100644 applications/main/lfrfid/icon.png create mode 100644 applications/main/nfc/icon.png create mode 100644 applications/main/subghz/icon.png create mode 100644 applications/main/u2f/icon.png diff --git a/applications/main/application.fam b/applications/main/application.fam index 75d55af93..0a90ee224 100644 --- a/applications/main/application.fam +++ b/applications/main/application.fam @@ -4,7 +4,6 @@ App( apptype=FlipperAppType.METAPACKAGE, provides=[ "gpio", - "onewire", "ibutton", "infrared", "lfrfid", @@ -13,5 +12,20 @@ App( "bad_usb", "u2f", "archive", + "main_apps_on_start", + ], +) + +App( + appid="main_apps_on_start", + name="On start hooks", + apptype=FlipperAppType.METAPACKAGE, + provides=[ + "ibutton_start", + "onewire_start", + "subghz_start", + "infrared_start", + "lfrfid_start", + "nfc_start", ], ) diff --git a/applications/main/archive/scenes/archive_scene_browser.c b/applications/main/archive/scenes/archive_scene_browser.c index e02f7622a..370830a00 100644 --- a/applications/main/archive/scenes/archive_scene_browser.c +++ b/applications/main/archive/scenes/archive_scene_browser.c @@ -5,13 +5,14 @@ #include "../helpers/archive_browser.h" #include "../views/archive_browser_view.h" #include "archive/scenes/archive_scene.h" +#include #define TAG "ArchiveSceneBrowser" #define SCENE_STATE_DEFAULT (0) #define SCENE_STATE_NEED_REFRESH (1) -const char* archive_get_flipper_app_name(ArchiveFileTypeEnum file_type) { +static const char* archive_get_flipper_app_name(ArchiveFileTypeEnum file_type) { switch(file_type) { case ArchiveFileTypeIButton: return "iButton"; diff --git a/applications/main/bad_usb/application.fam b/applications/main/bad_usb/application.fam index 2442dd3aa..5c42c9fa3 100644 --- a/applications/main/bad_usb/application.fam +++ b/applications/main/bad_usb/application.fam @@ -1,15 +1,12 @@ App( appid="bad_usb", name="Bad USB", - apptype=FlipperAppType.APP, + apptype=FlipperAppType.MENUEXTERNAL, entry_point="bad_usb_app", - cdefines=["APP_BAD_USB"], - requires=[ - "gui", - "dialogs", - ], stack_size=2 * 1024, icon="A_BadUsb_14", order=70, fap_libs=["assets"], + fap_icon="icon.png", + fap_category="USB", ) diff --git a/applications/main/bad_usb/icon.png b/applications/main/bad_usb/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..037474aa3bc9c2e1aca79a68483e69980432bcf5 GIT binary patch literal 576 zcmV-G0>AxEX>4Tx04R}tkv&MmKpe$i(`rSk4t5Z6$WWau6cusQDionYs1;guFuC*#nlvOW zE{=k0!NHHks)LKOt`4q(Aou~|=;Wm6A|?JWDYS_3;J6>}?mh0_0Yan9G%FATG`(u3 z5^*t;T@{0`5D-8=V(6BcWz0!Z5}xDh9zMR_MR}I@xj#prnzI<-6NzV;VOEJZh^IHJ z2Iqa^Fe}O`@j3ChNf#u3C`-Nm{=@yu+qV-Xlle$#1U1~DPPFA zta9Gstd(o5bx;1nP)=W2<~q$0B(R7jND!f*h7!uCB1)@HiiH&I$36VRj$a~|Laq`R zITlcX2HEk0|H1EWt^DMKn-q!zT`#u%F$x5Cfo9#dzmILZc>?&Kfh)c3uQY&}Ptxmc zEph}5Yy%h9ZB5w&E_Z;TCqp)6NAlAY@_FF>jJ_!g4Bi60Yi@6?eVjf3Y3eF@0~{Oz zV+G1y_jq?tXK(+WY4!I5C=YUpXXIhH00006VoOIv0RI600RN!9r;`8x010qNS#tmY z3ljhU3ljkVnw%H_000McNliru<^lu`HWXkp{t5s906j@WK~xyijZi@f05Awj>HlAL zr$MwDdI>{Qf+U53tOUR#xOeyy)jcQo#JNRv)7r6DVVK|+*(cmT+R+EbO(O#X#REG4 O00005OriujUt-p2+p8kXl9tpG9lUcLq-^x45JRS1+%E^do6+pY)OCI_kBL^ z^L^j<-uGRTsW5+e-$PrRKtwBnona~L!R4zM%2+9EyT(WuJ-NWa6x8ydq_(isQtPy6tyorO zq|Q%50XGn7)bDn&0_mr)pe_lYB{PnpL5k?4FtgEw=5jnhH42S_z%nCI9dEUf#rij< zo#BeY9HQtUaop$gDSYV)j<@4VtyYT@DqN+KLxx-kup;f3v%*?QBBY@Qf`w;1BEzw$ zq)AtDUXj8uh@;cuB4e9XXNBqG!$jZ`f-4mS{yZJ{nMLRlGLP$o z&vS(7TiC@9&vd3g)Ss{yRIHkb)1 zFQmau+rd`A+C>M2DTx<=?TqzByCmfDN|o5gGH`3vtc!UTqp%DWuAGI+7KEf!lP1Ow zTxLDv2CM*8XQG$|%N7B1ITy#5z_tbywn?K&*97;QsRbFtjhq$2=`TQr+*}jS*%%kZ z@(r+YE4_?MlrtVIH2ddM&^j z3>C_SP=T|FKAH#Fc35Q!%eL7VSfl`IlG+zlp(+KTP|tPoIRKPf{BZbmXt;Fmp2eoa z=S8mz5}v!L&@W_z0{~7Ed}fru#mq1QESx|*95vss`9+B!VY?YvnE3@kkPZ9m_EQDD zo0G0r^jGDbj;@KRzF|7DF+QPsAT_=%=TyR5UgFYU%UaaQa>d>TXHU<*>!%xcU+9SL zXh0u@jf{;RAH&u?#ZxZsoEYwU?ZJKO{!m!Xmp9dEG2!a&suQu*%1_G^8qdYVY|g42 zJJbwr8j53&{&sgw=9Qtmz@f=YS^39WE+h`eHQAf#H=8ncp3FG2Tcy_2=e|;`v)W?T)HzCD&GN>rbh;(bdimjkF(hwtI`7 zerqbMDEpoKVP*39o$Cr>+P?TWw(k_SdfV;JtNYxS1F}cQ>eIUKom1~?75P%ENV#B?PR&Lb*-5QGoBgi((fy> zb5lo|zbC^ttl*oL9*K&DZ;zKf1!V$)EQ^!AVMt4BA~b3Z`s~uggI|53j7Es1vYx4_ z=I9e!lEzKzS2UPnkpqF_t8X7v6MqhT(E8rj<6Re5{q*aBxZXxZxlTB_{+~gr?QQAB NWXLPjcjUa=@GmQdSu_9u literal 0 HcmV?d00001 diff --git a/applications/main/ibutton/application.fam b/applications/main/ibutton/application.fam index 06968bba4..a8faa629c 100644 --- a/applications/main/ibutton/application.fam +++ b/applications/main/ibutton/application.fam @@ -1,25 +1,21 @@ App( appid="ibutton", name="iButton", - apptype=FlipperAppType.APP, + apptype=FlipperAppType.MENUEXTERNAL, targets=["f7"], entry_point="ibutton_app", - cdefines=["APP_IBUTTON"], - requires=[ - "gui", - "dialogs", - ], - provides=["ibutton_start"], icon="A_iButton_14", stack_size=2 * 1024, order=60, fap_libs=["assets"], + fap_icon="icon.png", + fap_category="iButton", ) App( appid="ibutton_start", apptype=FlipperAppType.STARTUP, + targets=["f7"], entry_point="ibutton_on_system_start", - requires=["ibutton"], order=60, ) diff --git a/applications/main/ibutton/icon.png b/applications/main/ibutton/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..2fdaf123a657c00c9c84632ca3c151674e451ae1 GIT binary patch literal 304 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2xkYHHq`AGmsv7|ftIx;Y9?C1WI$O_~uBzpw; zGB8xBF)%c=FfjZA3N^f7U???UV0e|lz+g3lfkC`r&aOZkpafHrx4R1i<>&pI=m5)bW_|craZ$KesPZ!4!j_b)kjvx52z42i= z^Wk##wtdVzTiGS{99;2>!TC2M!yZeXz}?LkD}l;YOI#yLQW8s2t&)pUffR$0fsvuE zfvK*cNr<75m9c@9v4ysQft7*5`ikN&C>nC}Q!>*kp&E>VdO{3LtqcsU49p-Jly36? Qy~)7f>FVdQ&MBb@0C$~I0{{R3 literal 0 HcmV?d00001 diff --git a/applications/main/infrared/application.fam b/applications/main/infrared/application.fam index e5483e9ff..b78b088a7 100644 --- a/applications/main/infrared/application.fam +++ b/applications/main/infrared/application.fam @@ -1,25 +1,21 @@ App( appid="infrared", name="Infrared", - apptype=FlipperAppType.APP, + apptype=FlipperAppType.MENUEXTERNAL, entry_point="infrared_app", targets=["f7"], - cdefines=["APP_INFRARED"], - requires=[ - "gui", - "dialogs", - ], - provides=["infrared_start"], icon="A_Infrared_14", stack_size=3 * 1024, order=40, fap_libs=["assets"], + fap_icon="icon.png", + fap_category="Infrared", ) App( appid="infrared_start", apptype=FlipperAppType.STARTUP, + targets=["f7"], entry_point="infrared_on_system_start", - requires=["infrared"], order=20, ) diff --git a/applications/main/infrared/icon.png b/applications/main/infrared/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..22c986180a2bed76dbe4ff439df1cf9177533c32 GIT binary patch literal 305 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2xkYHHq`AGmsv7|ftIx;Y9?C1WI$O_~uBzpw; zGB8xBF)%c=FfjZA3N^f7U???UV0e|lz+g3lfkC`r&aOZkpafHrx4R1i<>&pI=m5)bWC*+s&X`qmbr;B3<$Ms~3f`-KX%+5*7 zhxan`%;z`w!#>%c-@wM^z<~n{3>VXQv+hhNs0FH5Epd$~Nl7e8wMs5Z1yT$~21bUu z2Bx}(CLxAKR>lTa#unNJ237_J>nn=CplHa=PsvQHglaGb>IpG01*)?$HiBr_e$xf$ PHwFezS3j3^P6<>&pI=m5)bWZjP>yH&963)5S4_<9hOs!iI<>&pI=m5)b(dHL6nbwD9yPZ!4!j_b)k${QZ;XFmLn zjqNsD+YPq1J8W%_*aXBie!OR3*tC!PwU_7Q9H4U564!{5l*E!$tK_0oAjM#0U}UIk zV5)0q5@Kj%Wo%$&Y@uynU}a#izM}XGiiX_$l+3hBs0L%8o)7~QD^p9LQiz6svOM}g O4Gf;HelF{r5}E+GUQp8j literal 0 HcmV?d00001 diff --git a/applications/main/onewire/application.fam b/applications/main/onewire/application.fam index 68d4f6716..3d35abce9 100644 --- a/applications/main/onewire/application.fam +++ b/applications/main/onewire/application.fam @@ -1,14 +1,6 @@ -App( - appid="onewire", - name="1-Wire", - apptype=FlipperAppType.METAPACKAGE, - provides=["onewire_start"], -) - App( appid="onewire_start", apptype=FlipperAppType.STARTUP, entry_point="onewire_on_system_start", - requires=["onewire"], order=60, ) diff --git a/applications/main/subghz/application.fam b/applications/main/subghz/application.fam index f0dc66e89..4f21cb6c4 100644 --- a/applications/main/subghz/application.fam +++ b/applications/main/subghz/application.fam @@ -1,25 +1,21 @@ App( appid="subghz", name="Sub-GHz", - apptype=FlipperAppType.APP, + apptype=FlipperAppType.MENUEXTERNAL, targets=["f7"], entry_point="subghz_app", - cdefines=["APP_SUBGHZ"], - requires=[ - "gui", - "cli", - "dialogs", - ], - provides=["subghz_start"], icon="A_Sub1ghz_14", stack_size=3 * 1024, order=10, + fap_libs=["assets", "hwdrivers"], + fap_icon="icon.png", + fap_category="Sub-GHz", ) App( appid="subghz_start", + targets=["f7"], apptype=FlipperAppType.STARTUP, entry_point="subghz_on_system_start", - requires=["subghz"], order=40, ) diff --git a/applications/main/subghz/icon.png b/applications/main/subghz/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..5a25fdf4ef1c6cf53634aa74675001a3e8c85b7b GIT binary patch literal 299 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2xkYHHq`AGmsv7|ftIx;Y9?C1WI$O_~uBzpw; zGB8xBF)%c=FfjZA3N^f7U???UV0e|lz+g3lfkC`r&aOZkpafHrx4R1i<>&pI=m5)cB{fFDGZlI8yr;B3<$MxhJ?+;A4eL&#) z0Ra}bue?07WhLz78x$BO|L3mq-MMxdP^D^#YeY#(Vo9o1a#1RfVlXl=GSoFN)ipE; zF*LF=Hn1|b&^9ozGB8+QQTzo(LvDUbW?CgwgE3G~h=HkEX>4Tx04R}tkv&MmKpe$i(`rSk4t5Z6$WWau6cusQDionYs1;guFuC*#nlvOW zE{=k0!NHHks)LKOt`4q(Aou~|=;Wm6A|?JWDYS_3;J6>}?mh0_0Yan9G%FATG`(u3 z5^*t;T@{0`5D-8=V(6BcWz0!Z5}xDh9zMR_MR}I@xj#prnzI<-6NzV;VOEJZh^IHJ z2Iqa^Fe}O`@j3ChNf#u3C`-Nm{=@yu+qV-Xlle$#1U1~DPPFA zta9Gstd(o5bx;1nP)=W2<~q$0B(R7jND!f*h7!uCB1)@HiiH&I$36VRj$a~|Laq`R zITlcX2HEk0|H1EWt^DMKn-q!zT`#u%F$x5Cfo9#dzmILZc>?&Kfh)c3uQY&}Ptxmc zEph}5Yy%h9ZB5w&E_Z;TCqp)6NAlAY@_FF>jJ_!g4Bi60Yi@6?eVjf3Y3eF@0~{Oz zV+G1y_jq?tXK(+WY4!I5C=YUpXXIhH00006VoOIv00000008+zyMF)x010qNS#tmY z3ljhU3ljkVnw%H_000McNliru<^lu{1uVUoNs|Bo07OYdK~xyijgYww05Avx?TGzX zz7!EC4G1?heldU+2uZR%k^uSL+0?e2taR-}6`h2x#_2kxune}*>oEbW-V;;Yj|primary_menu, + FLIPPER_EXTERNAL_APPS[i].name, + FLIPPER_EXTERNAL_APPS[i].icon, + i, + loader_menu_external_apps_callback, + (void*)menu); + } + for(i = 0; i < FLIPPER_APPS_COUNT; i++) { menu_add_item( app->primary_menu, FLIPPER_APPS[i].name, FLIPPER_APPS[i].icon, i, - loader_menu_callback, + loader_menu_apps_callback, (void*)menu); } menu_add_item( diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index 5704870c9..014970113 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,33.2,, +Version,+,34.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 29739abb5..2e02608a3 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,33.2,, +Version,+,34.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -147,7 +147,12 @@ Header,+,lib/mlib/m-rbtree.h,, Header,+,lib/mlib/m-tuple.h,, Header,+,lib/mlib/m-variant.h,, Header,+,lib/music_worker/music_worker.h,, +Header,+,lib/nfc/helpers/mfkey32.h,, +Header,+,lib/nfc/helpers/nfc_generators.h,, Header,+,lib/nfc/nfc_device.h,, +Header,+,lib/nfc/nfc_types.h,, +Header,+,lib/nfc/nfc_worker.h,, +Header,+,lib/nfc/parsers/nfc_supported_card.h,, Header,+,lib/nfc/protocols/nfc_util.h,, Header,+,lib/one_wire/maxim_crc.h,, Header,+,lib/one_wire/one_wire_host.h,, @@ -1935,40 +1940,40 @@ Function,-,mf_classic_authenticate,_Bool,"FuriHalNfcTxRxContext*, uint8_t, uint6 Function,-,mf_classic_authenticate_skip_activate,_Bool,"FuriHalNfcTxRxContext*, uint8_t, uint64_t, MfClassicKey, _Bool, uint32_t" Function,-,mf_classic_block_to_value,_Bool,"const uint8_t*, int32_t*, uint8_t*" Function,-,mf_classic_check_card_type,_Bool,"uint8_t, uint8_t, uint8_t" -Function,-,mf_classic_dict_add_key,_Bool,"MfClassicDict*, uint8_t*" +Function,+,mf_classic_dict_add_key,_Bool,"MfClassicDict*, uint8_t*" Function,-,mf_classic_dict_add_key_str,_Bool,"MfClassicDict*, FuriString*" -Function,-,mf_classic_dict_alloc,MfClassicDict*,MfClassicDictType -Function,-,mf_classic_dict_check_presence,_Bool,MfClassicDictType -Function,-,mf_classic_dict_delete_index,_Bool,"MfClassicDict*, uint32_t" +Function,+,mf_classic_dict_alloc,MfClassicDict*,MfClassicDictType +Function,+,mf_classic_dict_check_presence,_Bool,MfClassicDictType +Function,+,mf_classic_dict_delete_index,_Bool,"MfClassicDict*, uint32_t" Function,-,mf_classic_dict_find_index,_Bool,"MfClassicDict*, uint8_t*, uint32_t*" Function,-,mf_classic_dict_find_index_str,_Bool,"MfClassicDict*, FuriString*, uint32_t*" -Function,-,mf_classic_dict_free,void,MfClassicDict* +Function,+,mf_classic_dict_free,void,MfClassicDict* Function,-,mf_classic_dict_get_key_at_index,_Bool,"MfClassicDict*, uint64_t*, uint32_t" -Function,-,mf_classic_dict_get_key_at_index_str,_Bool,"MfClassicDict*, FuriString*, uint32_t" +Function,+,mf_classic_dict_get_key_at_index_str,_Bool,"MfClassicDict*, FuriString*, uint32_t" Function,-,mf_classic_dict_get_next_key,_Bool,"MfClassicDict*, uint64_t*" -Function,-,mf_classic_dict_get_next_key_str,_Bool,"MfClassicDict*, FuriString*" -Function,-,mf_classic_dict_get_total_keys,uint32_t,MfClassicDict* -Function,-,mf_classic_dict_is_key_present,_Bool,"MfClassicDict*, uint8_t*" +Function,+,mf_classic_dict_get_next_key_str,_Bool,"MfClassicDict*, FuriString*" +Function,+,mf_classic_dict_get_total_keys,uint32_t,MfClassicDict* +Function,+,mf_classic_dict_is_key_present,_Bool,"MfClassicDict*, uint8_t*" Function,-,mf_classic_dict_is_key_present_str,_Bool,"MfClassicDict*, FuriString*" Function,-,mf_classic_dict_rewind,_Bool,MfClassicDict* Function,-,mf_classic_emulator,_Bool,"MfClassicEmulator*, FuriHalNfcTxRxContext*, _Bool" Function,-,mf_classic_get_classic_type,MfClassicType,"uint8_t, uint8_t, uint8_t" -Function,-,mf_classic_get_read_sectors_and_keys,void,"MfClassicData*, uint8_t*, uint8_t*" -Function,-,mf_classic_get_sector_by_block,uint8_t,uint8_t +Function,+,mf_classic_get_read_sectors_and_keys,void,"MfClassicData*, uint8_t*, uint8_t*" +Function,+,mf_classic_get_sector_by_block,uint8_t,uint8_t Function,-,mf_classic_get_sector_trailer_block_num_by_sector,uint8_t,uint8_t -Function,-,mf_classic_get_sector_trailer_by_sector,MfClassicSectorTrailer*,"MfClassicData*, uint8_t" +Function,+,mf_classic_get_sector_trailer_by_sector,MfClassicSectorTrailer*,"MfClassicData*, uint8_t" Function,-,mf_classic_get_total_block_num,uint16_t,MfClassicType -Function,-,mf_classic_get_total_sectors_num,uint8_t,MfClassicType +Function,+,mf_classic_get_total_sectors_num,uint8_t,MfClassicType Function,-,mf_classic_get_type_str,const char*,MfClassicType Function,-,mf_classic_halt,void,"FuriHalNfcTxRxContext*, Crypto1*" Function,-,mf_classic_is_allowed_access_data_block,_Bool,"MfClassicData*, uint8_t, MfClassicKey, MfClassicAction" Function,-,mf_classic_is_allowed_access_sector_trailer,_Bool,"MfClassicData*, uint8_t, MfClassicKey, MfClassicAction" -Function,-,mf_classic_is_block_read,_Bool,"MfClassicData*, uint8_t" -Function,-,mf_classic_is_card_read,_Bool,MfClassicData* -Function,-,mf_classic_is_key_found,_Bool,"MfClassicData*, uint8_t, MfClassicKey" +Function,+,mf_classic_is_block_read,_Bool,"MfClassicData*, uint8_t" +Function,+,mf_classic_is_card_read,_Bool,MfClassicData* +Function,+,mf_classic_is_key_found,_Bool,"MfClassicData*, uint8_t, MfClassicKey" Function,-,mf_classic_is_sector_data_read,_Bool,"MfClassicData*, uint8_t" Function,-,mf_classic_is_sector_read,_Bool,"MfClassicData*, uint8_t" -Function,-,mf_classic_is_sector_trailer,_Bool,uint8_t +Function,+,mf_classic_is_sector_trailer,_Bool,uint8_t Function,-,mf_classic_is_value_block,_Bool,"MfClassicData*, uint8_t" Function,-,mf_classic_read_block,_Bool,"FuriHalNfcTxRxContext*, Crypto1*, uint8_t, MfClassicBlock*" Function,-,mf_classic_read_card,uint8_t,"FuriHalNfcTxRxContext*, MfClassicReader*, MfClassicData*" @@ -1986,10 +1991,10 @@ Function,-,mf_classic_value_to_block,void,"int32_t, uint8_t, uint8_t*" Function,-,mf_classic_write_block,_Bool,"FuriHalNfcTxRxContext*, Crypto1*, uint8_t, MfClassicBlock*" Function,-,mf_classic_write_sector,_Bool,"FuriHalNfcTxRxContext*, MfClassicData*, MfClassicData*, uint8_t" Function,-,mf_df_cat_application,void,"MifareDesfireApplication*, FuriString*" -Function,-,mf_df_cat_application_info,void,"MifareDesfireApplication*, FuriString*" -Function,-,mf_df_cat_card_info,void,"MifareDesfireData*, FuriString*" +Function,+,mf_df_cat_application_info,void,"MifareDesfireApplication*, FuriString*" +Function,+,mf_df_cat_card_info,void,"MifareDesfireData*, FuriString*" Function,-,mf_df_cat_data,void,"MifareDesfireData*, FuriString*" -Function,-,mf_df_cat_file,void,"MifareDesfireFile*, FuriString*" +Function,+,mf_df_cat_file,void,"MifareDesfireFile*, FuriString*" Function,-,mf_df_cat_free_mem,void,"MifareDesfireFreeMemory*, FuriString*" Function,-,mf_df_cat_key_settings,void,"MifareDesfireKeySettings*, FuriString*" Function,-,mf_df_cat_version,void,"MifareDesfireVersion*, FuriString*" @@ -2019,8 +2024,8 @@ Function,-,mf_df_prepare_read_records,uint16_t,"uint8_t*, uint8_t, uint32_t, uin Function,-,mf_df_prepare_select_application,uint16_t,"uint8_t*, uint8_t[3]" Function,-,mf_df_read_card,_Bool,"FuriHalNfcTxRxContext*, MifareDesfireData*" Function,-,mf_ul_check_card_type,_Bool,"uint8_t, uint8_t, uint8_t" -Function,-,mf_ul_emulation_supported,_Bool,MfUltralightData* -Function,-,mf_ul_is_full_capture,_Bool,MfUltralightData* +Function,+,mf_ul_emulation_supported,_Bool,MfUltralightData* +Function,+,mf_ul_is_full_capture,_Bool,MfUltralightData* Function,-,mf_ul_prepare_emulation,void,"MfUltralightEmulator*, MfUltralightData*" Function,-,mf_ul_prepare_emulation_response,_Bool,"uint8_t*, uint16_t, uint8_t*, uint16_t*, uint32_t*, void*" Function,-,mf_ul_pwdgen_amiibo,uint32_t,FuriHalNfcDevData* @@ -2030,13 +2035,18 @@ Function,-,mf_ul_reset,void,MfUltralightData* Function,-,mf_ul_reset_emulation,void,"MfUltralightEmulator*, _Bool" Function,-,mf_ultralight_authenticate,_Bool,"FuriHalNfcTxRxContext*, uint32_t, uint16_t*" Function,-,mf_ultralight_fast_read_pages,_Bool,"FuriHalNfcTxRxContext*, MfUltralightReader*, MfUltralightData*" -Function,-,mf_ultralight_get_config_pages,MfUltralightConfigPages*,MfUltralightData* +Function,+,mf_ultralight_get_config_pages,MfUltralightConfigPages*,MfUltralightData* Function,-,mf_ultralight_read_counters,_Bool,"FuriHalNfcTxRxContext*, MfUltralightData*" Function,-,mf_ultralight_read_pages,_Bool,"FuriHalNfcTxRxContext*, MfUltralightReader*, MfUltralightData*" Function,-,mf_ultralight_read_pages_direct,_Bool,"FuriHalNfcTxRxContext*, uint8_t, uint8_t*" Function,-,mf_ultralight_read_signature,_Bool,"FuriHalNfcTxRxContext*, MfUltralightData*" Function,-,mf_ultralight_read_tearing_flags,_Bool,"FuriHalNfcTxRxContext*, MfUltralightData*" Function,-,mf_ultralight_read_version,_Bool,"FuriHalNfcTxRxContext*, MfUltralightReader*, MfUltralightData*" +Function,-,mfkey32_alloc,Mfkey32*,uint32_t +Function,-,mfkey32_free,void,Mfkey32* +Function,+,mfkey32_get_auth_sectors,uint16_t,FuriString* +Function,-,mfkey32_process_data,void,"Mfkey32*, uint8_t*, uint16_t, _Bool, _Bool" +Function,-,mfkey32_set_callback,void,"Mfkey32*, Mfkey32ParseDataCallback, void*" Function,-,mkdtemp,char*,char* Function,-,mkostemp,int,"char*, int" Function,-,mkostemps,int,"char*, int, int" @@ -2085,11 +2095,25 @@ Function,+,nfc_device_save_shadow,_Bool,"NfcDevice*, const char*" Function,+,nfc_device_set_loading_callback,void,"NfcDevice*, NfcLoadingCallback, void*" Function,+,nfc_device_set_name,void,"NfcDevice*, const char*" Function,+,nfc_file_select,_Bool,NfcDevice* +Function,-,nfc_generate_mf_classic,void,"NfcDeviceData*, uint8_t, MfClassicType" +Function,+,nfc_get_dev_type,const char*,FuriHalNfcType +Function,-,nfc_guess_protocol,const char*,NfcProtocol +Function,+,nfc_mf_classic_type,const char*,MfClassicType +Function,+,nfc_mf_ul_type,const char*,"MfUltralightType, _Bool" +Function,+,nfc_supported_card_verify_and_parse,_Bool,NfcDeviceData* Function,+,nfc_util_bytes2num,uint64_t,"const uint8_t*, uint8_t" Function,+,nfc_util_even_parity32,uint8_t,uint32_t Function,+,nfc_util_num2bytes,void,"uint64_t, uint8_t, uint8_t*" Function,+,nfc_util_odd_parity,void,"const uint8_t*, uint8_t*, uint8_t" Function,+,nfc_util_odd_parity8,uint8_t,uint8_t +Function,+,nfc_worker_alloc,NfcWorker*, +Function,+,nfc_worker_free,void,NfcWorker* +Function,+,nfc_worker_get_state,NfcWorkerState,NfcWorker* +Function,-,nfc_worker_nfcv_emulate,void,NfcWorker* +Function,-,nfc_worker_nfcv_sniff,void,NfcWorker* +Function,-,nfc_worker_nfcv_unlock,void,NfcWorker* +Function,+,nfc_worker_start,void,"NfcWorker*, NfcWorkerState, NfcDeviceData*, NfcWorkerCallback, void*" +Function,+,nfc_worker_stop,void,NfcWorker* Function,-,nfca_append_crc16,void,"uint8_t*, uint16_t" Function,-,nfca_emulation_handler,_Bool,"uint8_t*, uint16_t, uint8_t*, uint16_t*" Function,-,nfca_get_crc16,uint16_t,"uint8_t*, uint16_t" @@ -2666,6 +2690,7 @@ Function,-,strupr,char*,char* Function,-,strverscmp,int,"const char*, const char*" Function,-,strxfrm,size_t,"char*, const char*, size_t" Function,-,strxfrm_l,size_t,"char*, const char*, size_t, locale_t" +Function,-,stub_parser_verify_read,_Bool,"NfcWorker*, FuriHalNfcTxRxContext*" Function,+,subghz_block_generic_deserialize,SubGhzProtocolStatus,"SubGhzBlockGeneric*, FlipperFormat*" Function,+,subghz_block_generic_deserialize_check_count_bit,SubGhzProtocolStatus,"SubGhzBlockGeneric*, FlipperFormat*, uint16_t" Function,+,subghz_block_generic_get_preset_name,void,"const char*, FuriString*" @@ -3358,6 +3383,8 @@ Variable,+,message_red_255,const NotificationMessage, Variable,+,message_sound_off,const NotificationMessage, Variable,+,message_vibro_off,const NotificationMessage, Variable,+,message_vibro_on,const NotificationMessage, +Variable,+,nfc_generators,const NfcGenerator*[], +Variable,-,nfc_supported_card,NfcSupportedCard[NfcSupportedCardTypeEnd], Variable,+,sequence_audiovisual_alert,const NotificationSequence, Variable,+,sequence_blink_blue_10,const NotificationSequence, Variable,+,sequence_blink_blue_100,const NotificationSequence, diff --git a/lib/nfc/SConscript b/lib/nfc/SConscript index b8551db84..7a0859ee4 100644 --- a/lib/nfc/SConscript +++ b/lib/nfc/SConscript @@ -6,6 +6,11 @@ env.Append( ], SDK_HEADERS=[ File("nfc_device.h"), + File("nfc_worker.h"), + File("nfc_types.h"), + File("helpers/mfkey32.h"), + File("parsers/nfc_supported_card.h"), + File("helpers/nfc_generators.h"), File("protocols/nfc_util.h"), ], ) diff --git a/lib/nfc/helpers/mf_classic_dict.h b/lib/nfc/helpers/mf_classic_dict.h index 3b2d560ad..b798b1c92 100644 --- a/lib/nfc/helpers/mf_classic_dict.h +++ b/lib/nfc/helpers/mf_classic_dict.h @@ -6,6 +6,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + typedef enum { MfClassicDictTypeUser, MfClassicDictTypeSystem, @@ -97,3 +101,7 @@ bool mf_classic_dict_find_index_str(MfClassicDict* dict, FuriString* key, uint32 * @return true on success */ bool mf_classic_dict_delete_index(MfClassicDict* dict, uint32_t target); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/nfc/helpers/mfkey32.h b/lib/nfc/helpers/mfkey32.h index e1f472e50..e29043224 100644 --- a/lib/nfc/helpers/mfkey32.h +++ b/lib/nfc/helpers/mfkey32.h @@ -2,6 +2,10 @@ #include +#ifdef __cplusplus +extern "C" { +#endif + typedef struct Mfkey32 Mfkey32; typedef enum { @@ -24,3 +28,7 @@ void mfkey32_process_data( void mfkey32_set_callback(Mfkey32* instance, Mfkey32ParseDataCallback callback, void* context); uint16_t mfkey32_get_auth_sectors(FuriString* string); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/nfc/nfc_types.h b/lib/nfc/nfc_types.h index fb53ce7c2..5ebb2d27b 100644 --- a/lib/nfc/nfc_types.h +++ b/lib/nfc/nfc_types.h @@ -2,6 +2,10 @@ #include "nfc_device.h" +#ifdef __cplusplus +extern "C" { +#endif + const char* nfc_get_dev_type(FuriHalNfcType type); const char* nfc_guess_protocol(NfcProtocol protocol); @@ -9,3 +13,7 @@ const char* nfc_guess_protocol(NfcProtocol protocol); const char* nfc_mf_ul_type(MfUltralightType type, bool full_name); const char* nfc_mf_classic_type(MfClassicType type); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/nfc/nfc_worker.h b/lib/nfc/nfc_worker.h index 722f14857..f9f5900bb 100644 --- a/lib/nfc/nfc_worker.h +++ b/lib/nfc/nfc_worker.h @@ -2,6 +2,10 @@ #include "nfc_device.h" +#ifdef __cplusplus +extern "C" { +#endif + typedef struct NfcWorker NfcWorker; typedef enum { @@ -97,4 +101,8 @@ void nfc_worker_start( void nfc_worker_stop(NfcWorker* nfc_worker); void nfc_worker_nfcv_unlock(NfcWorker* nfc_worker); void nfc_worker_nfcv_emulate(NfcWorker* nfc_worker); -void nfc_worker_nfcv_sniff(NfcWorker* nfc_worker); \ No newline at end of file +void nfc_worker_nfcv_sniff(NfcWorker* nfc_worker); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/nfc/parsers/nfc_supported_card.h b/lib/nfc/parsers/nfc_supported_card.h index 877bda737..2e8c48a87 100644 --- a/lib/nfc/parsers/nfc_supported_card.h +++ b/lib/nfc/parsers/nfc_supported_card.h @@ -4,6 +4,10 @@ #include "../nfc_worker.h" #include "../nfc_device.h" +#ifdef __cplusplus +extern "C" { +#endif + typedef enum { NfcSupportedCardTypePlantain, NfcSupportedCardTypeTroika, @@ -37,3 +41,7 @@ bool nfc_supported_card_verify_and_parse(NfcDeviceData* dev_data); // support the card. This is needed for DESFire card parsers which can't // provide keys, and only use NfcSupportedCard->parse. bool stub_parser_verify_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/nfc/protocols/crypto1.h b/lib/nfc/protocols/crypto1.h index 450d1534e..bbf6dc239 100644 --- a/lib/nfc/protocols/crypto1.h +++ b/lib/nfc/protocols/crypto1.h @@ -3,6 +3,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + typedef struct { uint32_t odd; uint32_t even; @@ -35,3 +39,7 @@ void crypto1_encrypt( uint16_t plain_data_bits, uint8_t* encrypted_data, uint8_t* encrypted_parity); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/nfc/protocols/mifare_classic.h b/lib/nfc/protocols/mifare_classic.h index b3e3cbdf8..7efe81b8c 100644 --- a/lib/nfc/protocols/mifare_classic.h +++ b/lib/nfc/protocols/mifare_classic.h @@ -4,6 +4,10 @@ #include "crypto1.h" +#ifdef __cplusplus +extern "C" { +#endif + #define MF_CLASSIC_BLOCK_SIZE (16) #define MF_CLASSIC_TOTAL_BLOCKS_MAX (256) #define MF_MINI_TOTAL_SECTORS_NUM (5) @@ -241,3 +245,7 @@ bool mf_classic_write_sector( MfClassicData* dest_data, MfClassicData* src_data, uint8_t sec_num); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/nfc/protocols/mifare_desfire.h b/lib/nfc/protocols/mifare_desfire.h index 3dc7c4c2a..8faa98ec2 100644 --- a/lib/nfc/protocols/mifare_desfire.h +++ b/lib/nfc/protocols/mifare_desfire.h @@ -5,6 +5,10 @@ #include +#ifdef __cplusplus +extern "C" { +#endif + #define MF_DF_GET_VERSION (0x60) #define MF_DF_GET_FREE_MEMORY (0x6E) #define MF_DF_GET_KEY_SETTINGS (0x45) @@ -169,3 +173,7 @@ uint16_t mf_df_prepare_read_records(uint8_t* dest, uint8_t file_id, uint32_t off bool mf_df_parse_read_data_response(uint8_t* buf, uint16_t len, MifareDesfireFile* out); bool mf_df_read_card(FuriHalNfcTxRxContext* tx_rx, MifareDesfireData* data); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/nfc/protocols/mifare_ultralight.h b/lib/nfc/protocols/mifare_ultralight.h index d444fa798..9cb7ca535 100644 --- a/lib/nfc/protocols/mifare_ultralight.h +++ b/lib/nfc/protocols/mifare_ultralight.h @@ -2,6 +2,10 @@ #include +#ifdef __cplusplus +extern "C" { +#endif + // Largest tag is NTAG I2C Plus 2K, both data sectors plus SRAM #define MF_UL_MAX_DUMP_SIZE ((238 + 256 + 16) * 4) @@ -259,3 +263,7 @@ uint32_t mf_ul_pwdgen_amiibo(FuriHalNfcDevData* data); uint32_t mf_ul_pwdgen_xiaomi(FuriHalNfcDevData* data); bool mf_ul_is_full_capture(MfUltralightData* data); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/scripts/fbt/appmanifest.py b/scripts/fbt/appmanifest.py index 73e5c7770..5b830dda9 100644 --- a/scripts/fbt/appmanifest.py +++ b/scripts/fbt/appmanifest.py @@ -18,6 +18,7 @@ class FlipperAppType(Enum): SETTINGS = "Settings" STARTUP = "StartupHook" EXTERNAL = "External" + MENUEXTERNAL = "MenuExternal" METAPACKAGE = "Package" PLUGIN = "Plugin" @@ -213,7 +214,7 @@ class AppBuildset: appmgr: AppManager, appnames: List[str], hw_target: str, - message_writer: Callable = None, + message_writer: Callable | None = None, ): self.appmgr = appmgr self.appnames = set(appnames) @@ -367,6 +368,11 @@ class ApplicationsCGenerator: ), } + APP_EXTERNAL_TYPE = ( + "FlipperExternalApplication", + "FLIPPER_EXTERNAL_APPS", + ) + def __init__(self, buildset: AppBuildset, autorun_app: str = ""): self.buildset = buildset self.autorun = autorun_app @@ -387,6 +393,17 @@ class ApplicationsCGenerator: .icon = {f"&{app.icon}" if app.icon else "NULL"}, .flags = {'|'.join(f"FlipperInternalApplicationFlag{flag}" for flag in app.flags)} }}""" + def get_external_app_descr(self, app: FlipperApplication): + app_path = "/ext/apps" + if app.fap_category: + app_path += f"/{app.fap_category}" + app_path += f"/{app.appid}.fap" + return f""" + {{ + .name = "{app.name}", + .icon = {f"&{app.icon}" if app.icon else "NULL"}, + .path = "{app_path}" }}""" + def generate(self): contents = [ '#include "applications.h"', @@ -418,4 +435,11 @@ class ApplicationsCGenerator: ] ) + entry_type, entry_block = self.APP_EXTERNAL_TYPE + external_apps = self.buildset.get_apps_of_type(FlipperAppType.MENUEXTERNAL) + contents.append(f"const {entry_type} {entry_block}[] = {{") + contents.append(",\n".join(map(self.get_external_app_descr, external_apps))) + contents.append("};") + contents.append(f"const size_t {entry_block}_COUNT = COUNT_OF({entry_block});") + return "\n".join(contents) diff --git a/scripts/fbt_tools/fbt_extapps.py b/scripts/fbt_tools/fbt_extapps.py index 69d700214..a6cd831d4 100644 --- a/scripts/fbt_tools/fbt_extapps.py +++ b/scripts/fbt_tools/fbt_extapps.py @@ -423,7 +423,10 @@ def AddAppLaunchTarget(env, appname, launch_target_name): host_app = env["APPMGR"].get(artifacts_app_to_run.app.requires[0]) if host_app: - if host_app.apptype == FlipperAppType.EXTERNAL: + if host_app.apptype in [ + FlipperAppType.EXTERNAL, + FlipperAppType.MENUEXTERNAL, + ]: _add_host_app_to_targets(host_app) else: # host app is a built-in app diff --git a/scripts/ufbt/SConstruct b/scripts/ufbt/SConstruct index 8812a4e55..703a9187a 100644 --- a/scripts/ufbt/SConstruct +++ b/scripts/ufbt/SConstruct @@ -262,6 +262,7 @@ apps_artifacts = appenv["EXT_APPS"] apps_to_build_as_faps = [ FlipperAppType.PLUGIN, FlipperAppType.EXTERNAL, + FlipperAppType.MENUEXTERNAL, ] known_extapps = [ diff --git a/site_scons/extapps.scons b/site_scons/extapps.scons index 6db0e538d..0893c4556 100644 --- a/site_scons/extapps.scons +++ b/site_scons/extapps.scons @@ -67,6 +67,7 @@ class FlipperExtAppBuildArtifacts: apps_to_build_as_faps = [ FlipperAppType.PLUGIN, FlipperAppType.EXTERNAL, + FlipperAppType.MENUEXTERNAL, FlipperAppType.DEBUG, ] From e56b970eb42a203e6905644ee15f66df99775df8 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 10 Jul 2023 16:48:18 +0300 Subject: [PATCH 075/364] update mifare nested --- applications/external/mifare_nested/application.fam | 2 +- applications/external/mifare_nested/mifare_nested_i.h | 2 +- .../external/mifare_nested/mifare_nested_worker.c | 8 ++++++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/applications/external/mifare_nested/application.fam b/applications/external/mifare_nested/application.fam index 5b2c0b466..36dfb2673 100644 --- a/applications/external/mifare_nested/application.fam +++ b/applications/external/mifare_nested/application.fam @@ -21,5 +21,5 @@ App( fap_author="AloneLiberty", fap_description="Recover Mifare Classic keys", fap_weburl="https://github.com/AloneLiberty/FlipperNested", - fap_version="1.5.0" + fap_version="1.5.1" ) diff --git a/applications/external/mifare_nested/mifare_nested_i.h b/applications/external/mifare_nested/mifare_nested_i.h index 69907dcd0..b99f785c6 100644 --- a/applications/external/mifare_nested/mifare_nested_i.h +++ b/applications/external/mifare_nested/mifare_nested_i.h @@ -21,7 +21,7 @@ #include #include "mifare_nested_icons.h" -#define NESTED_VERSION_APP "1.5.0" +#define NESTED_VERSION_APP "1.5.1" #define NESTED_GITHUB_LINK "https://github.com/AloneLiberty/FlipperNested" #define NESTED_RECOVER_KEYS_GITHUB_LINK "https://github.com/AloneLiberty/FlipperNestedRecovery" #define NESTED_NONCE_FORMAT_VERSION "3" diff --git a/applications/external/mifare_nested/mifare_nested_worker.c b/applications/external/mifare_nested/mifare_nested_worker.c index 7598d28c9..360b38f4a 100644 --- a/applications/external/mifare_nested/mifare_nested_worker.c +++ b/applications/external/mifare_nested/mifare_nested_worker.c @@ -478,9 +478,13 @@ SaveNoncesResult_t* mifare_nested_worker_write_nonces( for(uint8_t sector = 0; sector < sector_count; sector++) { for(uint8_t key_type = 0; key_type < 2; key_type++) { if(nonces->nonces[sector][key_type][tries]->invalid) { - result->invalid++; + if (tries == 0) { + result->invalid++; + } } else if(nonces->nonces[sector][key_type][tries]->skipped) { - result->skipped++; + if (tries == 0) { + result->skipped++; + } } else if(nonces->nonces[sector][key_type][tries]->collected) { if(nonces->nonces[sector][key_type][tries]->hardnested) { FuriString* hardnested_path = furi_string_alloc(); From b451fa91def8a4020986e58da4675ad8c51f8774 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 10 Jul 2023 16:52:12 +0300 Subject: [PATCH 076/364] Update wifi marauder app --- .../wifi_marauder_scene_console_output.c | 5 + .../scenes/wifi_marauder_scene_flasher.c | 172 ++++++++++++++++-- .../scenes/wifi_marauder_scene_start.c | 1 - .../wifi_marauder_app.c | 1 + .../wifi_marauder_app.h | 2 +- .../wifi_marauder_app_i.h | 17 ++ .../wifi_marauder_custom_event.h | 3 +- .../wifi_marauder_flasher.c | 113 ++++++++---- .../wifi_marauder_flasher.h | 7 + 9 files changed, 264 insertions(+), 57 deletions(-) diff --git a/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_console_output.c b/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_console_output.c index 236fe4eff..9e1719d08 100644 --- a/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_console_output.c +++ b/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_console_output.c @@ -182,6 +182,11 @@ bool wifi_marauder_scene_console_output_on_event(void* context, SceneManagerEven consumed = true; } else if(event.type == SceneManagerEventTypeTick) { consumed = true; + } else { + if(app->flash_worker_busy) { + // ignore button presses while flashing + consumed = true; + } } return consumed; diff --git a/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_flasher.c b/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_flasher.c index 79682879d..8fe91bbba 100644 --- a/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_flasher.c +++ b/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_flasher.c @@ -1,9 +1,14 @@ #include "../wifi_marauder_app_i.h" +#include "../wifi_marauder_flasher.h" enum SubmenuIndex { + SubmenuIndexS3Mode, SubmenuIndexBoot, SubmenuIndexPart, + SubmenuIndexNvs, + SubmenuIndexBootApp0, SubmenuIndexApp, + SubmenuIndexCustom, SubmenuIndexFlash, }; @@ -20,16 +25,30 @@ static void wifi_marauder_scene_flasher_callback(void* context, uint32_t index) // TODO refactor switch(index) { + case SubmenuIndexS3Mode: + // toggle S3 mode + app->selected_flash_options[SelectedFlashS3Mode] = !app->selected_flash_options[SelectedFlashS3Mode]; + view_dispatcher_send_custom_event(app->view_dispatcher, WifiMarauderEventRefreshSubmenu); + break; case SubmenuIndexBoot: - if(dialog_file_browser_show( - app->dialogs, selected_filepath, predefined_filepath, &browser_options)) { - strncpy( - app->bin_file_path_boot, - furi_string_get_cstr(selected_filepath), - sizeof(app->bin_file_path_boot)); + app->selected_flash_options[SelectedFlashBoot] = !app->selected_flash_options[SelectedFlashBoot]; + if (app->selected_flash_options[SelectedFlashBoot]) { + if(dialog_file_browser_show( + app->dialogs, selected_filepath, predefined_filepath, &browser_options)) { + strncpy( + app->bin_file_path_boot, + furi_string_get_cstr(selected_filepath), + sizeof(app->bin_file_path_boot)); + } } + if (app->bin_file_path_boot[0] == '\0') { + // if user didn't select a file, leave unselected + app->selected_flash_options[SelectedFlashBoot] = false; + } + view_dispatcher_send_custom_event(app->view_dispatcher, WifiMarauderEventRefreshSubmenu); break; case SubmenuIndexPart: + app->selected_flash_options[SelectedFlashPart] = !app->selected_flash_options[SelectedFlashPart]; if(dialog_file_browser_show( app->dialogs, selected_filepath, predefined_filepath, &browser_options)) { strncpy( @@ -37,8 +56,44 @@ static void wifi_marauder_scene_flasher_callback(void* context, uint32_t index) furi_string_get_cstr(selected_filepath), sizeof(app->bin_file_path_part)); } + if (app->bin_file_path_part[0] == '\0') { + // if user didn't select a file, leave unselected + app->selected_flash_options[SelectedFlashPart] = false; + } + view_dispatcher_send_custom_event(app->view_dispatcher, WifiMarauderEventRefreshSubmenu); + break; + case SubmenuIndexNvs: + app->selected_flash_options[SelectedFlashNvs] = !app->selected_flash_options[SelectedFlashNvs]; + if(dialog_file_browser_show( + app->dialogs, selected_filepath, predefined_filepath, &browser_options)) { + strncpy( + app->bin_file_path_nvs, + furi_string_get_cstr(selected_filepath), + sizeof(app->bin_file_path_nvs)); + } + if (app->bin_file_path_nvs[0] == '\0') { + // if user didn't select a file, leave unselected + app->selected_flash_options[SelectedFlashNvs] = false; + } + view_dispatcher_send_custom_event(app->view_dispatcher, WifiMarauderEventRefreshSubmenu); + break; + case SubmenuIndexBootApp0: + app->selected_flash_options[SelectedFlashBootApp0] = !app->selected_flash_options[SelectedFlashBootApp0]; + if(dialog_file_browser_show( + app->dialogs, selected_filepath, predefined_filepath, &browser_options)) { + strncpy( + app->bin_file_path_boot_app0, + furi_string_get_cstr(selected_filepath), + sizeof(app->bin_file_path_boot_app0)); + } + if (app->bin_file_path_boot_app0[0] == '\0') { + // if user didn't select a file, leave unselected + app->selected_flash_options[SelectedFlashBootApp0] = false; + } + view_dispatcher_send_custom_event(app->view_dispatcher, WifiMarauderEventRefreshSubmenu); break; case SubmenuIndexApp: + app->selected_flash_options[SelectedFlashApp] = !app->selected_flash_options[SelectedFlashApp]; if(dialog_file_browser_show( app->dialogs, selected_filepath, predefined_filepath, &browser_options)) { strncpy( @@ -46,10 +101,39 @@ static void wifi_marauder_scene_flasher_callback(void* context, uint32_t index) furi_string_get_cstr(selected_filepath), sizeof(app->bin_file_path_app)); } + if (app->bin_file_path_app[0] == '\0') { + // if user didn't select a file, leave unselected + app->selected_flash_options[SelectedFlashApp] = false; + } + view_dispatcher_send_custom_event(app->view_dispatcher, WifiMarauderEventRefreshSubmenu); + break; + case SubmenuIndexCustom: + app->selected_flash_options[SelectedFlashCustom] = !app->selected_flash_options[SelectedFlashCustom]; + if(dialog_file_browser_show( + app->dialogs, selected_filepath, predefined_filepath, &browser_options)) { + strncpy( + app->bin_file_path_custom, + furi_string_get_cstr(selected_filepath), + sizeof(app->bin_file_path_custom)); + } + if (app->bin_file_path_custom[0] == '\0') { + // if user didn't select a file, leave unselected + app->selected_flash_options[SelectedFlashCustom] = false; + } + view_dispatcher_send_custom_event(app->view_dispatcher, WifiMarauderEventRefreshSubmenu); break; case SubmenuIndexFlash: - // TODO error checking - scene_manager_next_scene(app->scene_manager, WifiMarauderSceneConsoleOutput); + // count how many options are selected + app->num_selected_flash_options = 0; + for (bool* option = &app->selected_flash_options[SelectedFlashBoot]; option < &app->selected_flash_options[NUM_FLASH_OPTIONS]; ++option) { + if (*option) { + ++app->num_selected_flash_options; + } + } + if (app->num_selected_flash_options) { + // only start next scene if at least one option is selected + scene_manager_next_scene(app->scene_manager, WifiMarauderSceneConsoleOutput); + } break; } @@ -57,31 +141,83 @@ static void wifi_marauder_scene_flasher_callback(void* context, uint32_t index) furi_string_free(predefined_filepath); } -void wifi_marauder_scene_flasher_on_enter(void* context) { - WifiMarauderApp* app = context; - +#define STR_SELECT "[x]" +#define STR_UNSELECT "[ ]" +#define STR_BOOT "Bootloader (" TOSTRING(ESP_ADDR_BOOT) ")" +#define STR_BOOT_S3 "Bootloader (" TOSTRING(ESP_ADDR_BOOT_S3) ")" +#define STR_PART "Part Table (" TOSTRING(ESP_ADDR_PART) ")" +#define STR_NVS "NVS (" TOSTRING(ESP_ADDR_NVS) ")" +#define STR_BOOT_APP0 "boot_app0 (" TOSTRING(ESP_ADDR_BOOT_APP0) ")" +#define STR_APP "Firmware (" TOSTRING(ESP_ADDR_APP) ")" +#define STR_CUSTOM "Custom" +#define STR_FLASH_S3 "[>] FLASH (ESP32-S3)" +#define STR_FLASH "[>] FLASH" +static void _refresh_submenu(WifiMarauderApp* app) { Submenu* submenu = app->submenu; + submenu_reset(app->submenu); + submenu_set_header(submenu, "Browse for files to flash"); submenu_add_item( - submenu, "Bootloader", SubmenuIndexBoot, wifi_marauder_scene_flasher_callback, app); + submenu, app->selected_flash_options[SelectedFlashS3Mode] ? "[x] Using ESP32-S3" : "[ ] Check if using S3", SubmenuIndexS3Mode, wifi_marauder_scene_flasher_callback, app); + const char* strSelectBootloader = STR_UNSELECT " " STR_BOOT; + if (app->selected_flash_options[SelectedFlashS3Mode]) { + if (app->selected_flash_options[SelectedFlashBoot]) { + strSelectBootloader = STR_SELECT " " STR_BOOT_S3; + } else { + strSelectBootloader = STR_UNSELECT " " STR_BOOT_S3; + } + } else { + if (app->selected_flash_options[SelectedFlashBoot]) { + strSelectBootloader = STR_SELECT " " STR_BOOT; + } else { + strSelectBootloader = STR_UNSELECT " " STR_BOOT; + } + } submenu_add_item( - submenu, "Partition Table", SubmenuIndexPart, wifi_marauder_scene_flasher_callback, app); + submenu, strSelectBootloader, SubmenuIndexBoot, wifi_marauder_scene_flasher_callback, app); submenu_add_item( - submenu, "Application", SubmenuIndexApp, wifi_marauder_scene_flasher_callback, app); + submenu, app->selected_flash_options[SelectedFlashPart] ? STR_SELECT " " STR_PART : STR_UNSELECT " " STR_PART, SubmenuIndexPart, wifi_marauder_scene_flasher_callback, app); submenu_add_item( - submenu, "[>] FLASH", SubmenuIndexFlash, wifi_marauder_scene_flasher_callback, app); + submenu, app->selected_flash_options[SelectedFlashNvs] ? STR_SELECT " " STR_NVS : STR_UNSELECT " " STR_NVS, SubmenuIndexNvs, wifi_marauder_scene_flasher_callback, app); + submenu_add_item( + submenu, app->selected_flash_options[SelectedFlashBootApp0] ? STR_SELECT " " STR_BOOT_APP0 : STR_UNSELECT " " STR_BOOT_APP0, SubmenuIndexBootApp0, wifi_marauder_scene_flasher_callback, app); + submenu_add_item( + submenu, app->selected_flash_options[SelectedFlashApp] ? STR_SELECT " " STR_APP : STR_UNSELECT " " STR_APP, SubmenuIndexApp, wifi_marauder_scene_flasher_callback, app); + // TODO: custom addr + //submenu_add_item( + // submenu, app->selected_flash_options[SelectedFlashCustom] ? STR_SELECT " " STR_CUSTOM : STR_UNSELECT " " STR_CUSTOM, SubmenuIndexCustom, wifi_marauder_scene_flasher_callback, app); + submenu_add_item( + submenu, app->selected_flash_options[SelectedFlashS3Mode] ? STR_FLASH_S3 : STR_FLASH, SubmenuIndexFlash, wifi_marauder_scene_flasher_callback, app); submenu_set_selected_item( submenu, scene_manager_get_scene_state(app->scene_manager, WifiMarauderSceneFlasher)); view_dispatcher_switch_to_view(app->view_dispatcher, WifiMarauderAppViewSubmenu); } +void wifi_marauder_scene_flasher_on_enter(void* context) { + WifiMarauderApp* app = context; + + memset(app->selected_flash_options, 0, sizeof(app->selected_flash_options)); + app->bin_file_path_boot[0] = '\0'; + app->bin_file_path_part[0] = '\0'; + app->bin_file_path_nvs[0] = '\0'; + app->bin_file_path_boot_app0[0] = '\0'; + app->bin_file_path_app[0] = '\0'; + app->bin_file_path_custom[0] = '\0'; + + _refresh_submenu(app); +} + bool wifi_marauder_scene_flasher_on_event(void* context, SceneManagerEvent event) { - //WifiMarauderApp* app = context; - UNUSED(context); - UNUSED(event); + WifiMarauderApp* app = context; bool consumed = false; + if (event.type == SceneManagerEventTypeCustom) { + if (event.event == WifiMarauderEventRefreshSubmenu) { + _refresh_submenu(app); + consumed = true; + } + } return consumed; } diff --git a/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_start.c b/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_start.c index 06dcd7fd7..97b26fc7f 100644 --- a/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_start.c +++ b/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_start.c @@ -243,7 +243,6 @@ void wifi_marauder_scene_start_on_enter(void* context) { } bool wifi_marauder_scene_start_on_event(void* context, SceneManagerEvent event) { - UNUSED(context); WifiMarauderApp* app = context; bool consumed = false; diff --git a/applications/external/wifi_marauder_companion/wifi_marauder_app.c b/applications/external/wifi_marauder_companion/wifi_marauder_app.c index 91fcb2372..c27a941ad 100644 --- a/applications/external/wifi_marauder_companion/wifi_marauder_app.c +++ b/applications/external/wifi_marauder_companion/wifi_marauder_app.c @@ -87,6 +87,7 @@ WifiMarauderApp* wifi_marauder_app_alloc() { app->view_dispatcher, WifiMarauderAppViewSubmenu, submenu_get_view(app->submenu)); app->flash_mode = false; + app->flash_worker_busy = false; scene_manager_next_scene(app->scene_manager, WifiMarauderSceneStart); diff --git a/applications/external/wifi_marauder_companion/wifi_marauder_app.h b/applications/external/wifi_marauder_companion/wifi_marauder_app.h index b6664fdab..187a0aaaa 100644 --- a/applications/external/wifi_marauder_companion/wifi_marauder_app.h +++ b/applications/external/wifi_marauder_companion/wifi_marauder_app.h @@ -4,7 +4,7 @@ extern "C" { #endif -#define WIFI_MARAUDER_APP_VERSION "v0.5.0" +#define WIFI_MARAUDER_APP_VERSION "v0.5.1" typedef struct WifiMarauderApp WifiMarauderApp; diff --git a/applications/external/wifi_marauder_companion/wifi_marauder_app_i.h b/applications/external/wifi_marauder_companion/wifi_marauder_app_i.h index cd248648a..a5c4818f0 100644 --- a/applications/external/wifi_marauder_companion/wifi_marauder_app_i.h +++ b/applications/external/wifi_marauder_companion/wifi_marauder_app_i.h @@ -48,6 +48,17 @@ typedef enum WifiMarauderUserInputType { WifiMarauderUserInputTypeFileName } WifiMarauderUserInputType; +typedef enum SelectedFlashOptions { + SelectedFlashS3Mode, + SelectedFlashBoot, + SelectedFlashPart, + SelectedFlashNvs, + SelectedFlashBootApp0, + SelectedFlashApp, + SelectedFlashCustom, + NUM_FLASH_OPTIONS +} SelectedFlashOptions; + struct WifiMarauderApp { Gui* gui; ViewDispatcher* view_dispatcher; @@ -115,10 +126,16 @@ struct WifiMarauderApp { char special_case_input_dst_addr[20]; // For flashing - TODO: put into its own struct? + bool selected_flash_options[NUM_FLASH_OPTIONS]; + int num_selected_flash_options; char bin_file_path_boot[100]; char bin_file_path_part[100]; + char bin_file_path_nvs[100]; + char bin_file_path_boot_app0[100]; char bin_file_path_app[100]; + char bin_file_path_custom[100]; FuriThread* flash_worker; + bool flash_worker_busy; bool flash_mode; }; diff --git a/applications/external/wifi_marauder_companion/wifi_marauder_custom_event.h b/applications/external/wifi_marauder_companion/wifi_marauder_custom_event.h index 8f020b754..ff03f31dd 100644 --- a/applications/external/wifi_marauder_companion/wifi_marauder_custom_event.h +++ b/applications/external/wifi_marauder_companion/wifi_marauder_custom_event.h @@ -10,5 +10,6 @@ typedef enum { WifiMarauderEventStartLogViewer, WifiMarauderEventStartScriptSelect, WifiMarauderEventStartSniffPmkidOptions, - WifiMarauderEventStartFlasher + WifiMarauderEventStartFlasher, + WifiMarauderEventRefreshSubmenu } WifiMarauderCustomEvent; diff --git a/applications/external/wifi_marauder_companion/wifi_marauder_flasher.c b/applications/external/wifi_marauder_companion/wifi_marauder_flasher.c index 8d30c1539..63820be0b 100644 --- a/applications/external/wifi_marauder_companion/wifi_marauder_flasher.c +++ b/applications/external/wifi_marauder_companion/wifi_marauder_flasher.c @@ -18,6 +18,8 @@ static esp_loader_error_t _flash_file(WifiMarauderApp* app, char* filepath, uint static uint8_t payload[1024]; File* bin_file = storage_file_alloc(app->storage); + char user_msg[256]; + // open file if(!storage_file_open(bin_file, filepath, FSAM_READ, FSOM_OPEN_EXISTING)) { storage_file_close(bin_file); @@ -28,48 +30,34 @@ static esp_loader_error_t _flash_file(WifiMarauderApp* app, char* filepath, uint uint64_t size = storage_file_size(bin_file); - /* - // TODO packet drops with higher BR? - err = esp_loader_change_transmission_rate(230400); - if (err != ESP_LOADER_SUCCESS) { - char err_msg[256]; - snprintf( - err_msg, - sizeof(err_msg), - "Cannot change transmission rate. Error: %u\n", - err); - storage_file_close(bin_file); - storage_file_free(bin_file); - loader_port_debug_print(err_msg); - return; - } - - furi_hal_uart_set_br(FuriHalUartIdUSART1, 230400); - // TODO remember to change BR back! - */ - loader_port_debug_print("Erasing flash...this may take a while\n"); err = esp_loader_flash_start(addr, size, sizeof(payload)); if(err != ESP_LOADER_SUCCESS) { storage_file_close(bin_file); storage_file_free(bin_file); - char err_msg[256]; - snprintf(err_msg, sizeof(err_msg), "Erasing flash failed with error %d\n", err); - loader_port_debug_print(err_msg); + snprintf(user_msg, sizeof(user_msg), "Erasing flash failed with error %d\n", err); + loader_port_debug_print(user_msg); return err; } loader_port_debug_print("Start programming\n"); + uint64_t last_updated = size; while(size > 0) { + if ((last_updated - size) > 50000) { + // inform user every 50k bytes + // TODO: draw a progress bar next update + snprintf(user_msg, sizeof(user_msg), "%llu bytes left.\n", size); + loader_port_debug_print(user_msg); + last_updated = size; + } size_t to_read = MIN(size, sizeof(payload)); uint16_t num_bytes = storage_file_read(bin_file, payload, to_read); err = esp_loader_flash_write(payload, num_bytes); if(err != ESP_LOADER_SUCCESS) { - char err_msg[256]; - snprintf(err_msg, sizeof(err_msg), "Packet could not be written! Error: %u\n", err); + snprintf(user_msg, sizeof(user_msg), "Packet could not be written! Error: %u\n", err); storage_file_close(bin_file); storage_file_free(bin_file); - loader_port_debug_print(err_msg); + loader_port_debug_print(user_msg); return err; } @@ -86,10 +74,49 @@ static esp_loader_error_t _flash_file(WifiMarauderApp* app, char* filepath, uint return ESP_LOADER_SUCCESS; } +typedef struct { + SelectedFlashOptions selected; + const char* description; + char* path; + uint32_t addr; +} FlashItem; + +static void _flash_all_files(WifiMarauderApp* app) { + esp_loader_error_t err; + const int num_steps = app->num_selected_flash_options; + + #define NUM_FLASH_ITEMS 6 + FlashItem items[NUM_FLASH_ITEMS] = { + { SelectedFlashBoot, "bootloader", app->bin_file_path_boot, app->selected_flash_options[SelectedFlashS3Mode] ? ESP_ADDR_BOOT_S3 : ESP_ADDR_BOOT }, + { SelectedFlashPart, "partition table", app->bin_file_path_part, ESP_ADDR_PART }, + { SelectedFlashNvs, "NVS", app->bin_file_path_nvs, ESP_ADDR_NVS }, + { SelectedFlashBootApp0, "boot_app0", app->bin_file_path_boot_app0, ESP_ADDR_BOOT_APP0 }, + { SelectedFlashApp, "firmware", app->bin_file_path_app, ESP_ADDR_APP }, + { SelectedFlashCustom, "custom data", app->bin_file_path_custom, 0x0 }, + /* if you add more entries, update NUM_FLASH_ITEMS above! */ + }; + + char user_msg[256]; + + int current_step = 1; + for (FlashItem* item = &items[0]; item < &items[NUM_FLASH_ITEMS]; ++item) { + if(app->selected_flash_options[item->selected]) { + snprintf(user_msg, sizeof(user_msg), "Flashing %s (%d/%d) to address 0x%lx\n", item->description, current_step++, num_steps, item->addr); + loader_port_debug_print(user_msg); + err = _flash_file(app, item->path, item->addr); + if(err) { + break; + } + } + } +} + static int32_t wifi_marauder_flash_bin(void* context) { WifiMarauderApp* app = (void*)context; esp_loader_error_t err; + app->flash_worker_busy = true; + // alloc global objects flash_rx_stream = furi_stream_buffer_alloc(RX_BUF_SIZE, 1); timer = furi_timer_alloc(_timer_callback, FuriTimerTypePeriodic, app); @@ -103,22 +130,36 @@ static int32_t wifi_marauder_flash_bin(void* context) { loader_port_debug_print(err_msg); } + #if 0 // still getting packet drops with this + // higher BR + if(!err) { + loader_port_debug_print("Increasing speed for faster flash\n"); + err = esp_loader_change_transmission_rate(230400); + if (err != ESP_LOADER_SUCCESS) { + char err_msg[256]; + snprintf( + err_msg, + sizeof(err_msg), + "Cannot change transmission rate. Error: %u\n", + err); + loader_port_debug_print(err_msg); + } + furi_hal_uart_set_br(FuriHalUartIdUSART1, 230400); + } + #endif + if(!err) { loader_port_debug_print("Connected\n"); - loader_port_debug_print("Flashing bootloader (1/3)\n"); - err = _flash_file(app, app->bin_file_path_boot, 0x1000); - } - if(!err) { - loader_port_debug_print("Flashing partition table (2/3)\n"); - err = _flash_file(app, app->bin_file_path_part, 0x8000); - } - if(!err) { - loader_port_debug_print("Flashing app (3/3)\n"); - err = _flash_file(app, app->bin_file_path_app, 0x10000); + _flash_all_files(app); + #if 0 + loader_port_debug_print("Restoring transmission rate\n"); + furi_hal_uart_set_br(FuriHalUartIdUSART1, 115200); + #endif loader_port_debug_print("Done flashing. Please reset the board manually.\n"); } // done + app->flash_worker_busy = false; // cleanup furi_stream_buffer_free(flash_rx_stream); diff --git a/applications/external/wifi_marauder_companion/wifi_marauder_flasher.h b/applications/external/wifi_marauder_companion/wifi_marauder_flasher.h index 796e258e5..3688ed09f 100644 --- a/applications/external/wifi_marauder_companion/wifi_marauder_flasher.h +++ b/applications/external/wifi_marauder_companion/wifi_marauder_flasher.h @@ -5,6 +5,13 @@ #define SERIAL_FLASHER_INTERFACE_UART /* TODO why is application.fam not passing this via cdefines */ #include "esp_loader_io.h" +#define ESP_ADDR_BOOT_S3 0x0 +#define ESP_ADDR_BOOT 0x1000 +#define ESP_ADDR_PART 0x8000 +#define ESP_ADDR_NVS 0x9000 +#define ESP_ADDR_BOOT_APP0 0xE000 +#define ESP_ADDR_APP 0x10000 + void wifi_marauder_flash_start_thread(WifiMarauderApp* app); void wifi_marauder_flash_stop_thread(WifiMarauderApp* app); void wifi_marauder_flash_handle_rx_data_cb(uint8_t* buf, size_t len, void* context); \ No newline at end of file From 9c6d0e7f2101158031f8d49ca74c07a5fb1b1365 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 10 Jul 2023 16:52:58 +0300 Subject: [PATCH 077/364] fbt format --- .../mifare_nested/mifare_nested_worker.c | 4 +- .../scenes/wifi_marauder_scene_flasher.c | 118 ++++++++++++------ .../wifi_marauder_flasher.c | 38 +++--- .../wifi_marauder_flasher.h | 10 +- 4 files changed, 109 insertions(+), 61 deletions(-) diff --git a/applications/external/mifare_nested/mifare_nested_worker.c b/applications/external/mifare_nested/mifare_nested_worker.c index 360b38f4a..60540c74b 100644 --- a/applications/external/mifare_nested/mifare_nested_worker.c +++ b/applications/external/mifare_nested/mifare_nested_worker.c @@ -478,11 +478,11 @@ SaveNoncesResult_t* mifare_nested_worker_write_nonces( for(uint8_t sector = 0; sector < sector_count; sector++) { for(uint8_t key_type = 0; key_type < 2; key_type++) { if(nonces->nonces[sector][key_type][tries]->invalid) { - if (tries == 0) { + if(tries == 0) { result->invalid++; } } else if(nonces->nonces[sector][key_type][tries]->skipped) { - if (tries == 0) { + if(tries == 0) { result->skipped++; } } else if(nonces->nonces[sector][key_type][tries]->collected) { diff --git a/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_flasher.c b/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_flasher.c index 8fe91bbba..2d6b8ea50 100644 --- a/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_flasher.c +++ b/applications/external/wifi_marauder_companion/scenes/wifi_marauder_scene_flasher.c @@ -27,28 +27,31 @@ static void wifi_marauder_scene_flasher_callback(void* context, uint32_t index) switch(index) { case SubmenuIndexS3Mode: // toggle S3 mode - app->selected_flash_options[SelectedFlashS3Mode] = !app->selected_flash_options[SelectedFlashS3Mode]; + app->selected_flash_options[SelectedFlashS3Mode] = + !app->selected_flash_options[SelectedFlashS3Mode]; view_dispatcher_send_custom_event(app->view_dispatcher, WifiMarauderEventRefreshSubmenu); break; case SubmenuIndexBoot: - app->selected_flash_options[SelectedFlashBoot] = !app->selected_flash_options[SelectedFlashBoot]; - if (app->selected_flash_options[SelectedFlashBoot]) { + app->selected_flash_options[SelectedFlashBoot] = + !app->selected_flash_options[SelectedFlashBoot]; + if(app->selected_flash_options[SelectedFlashBoot]) { if(dialog_file_browser_show( - app->dialogs, selected_filepath, predefined_filepath, &browser_options)) { + app->dialogs, selected_filepath, predefined_filepath, &browser_options)) { strncpy( app->bin_file_path_boot, furi_string_get_cstr(selected_filepath), sizeof(app->bin_file_path_boot)); } } - if (app->bin_file_path_boot[0] == '\0') { + if(app->bin_file_path_boot[0] == '\0') { // if user didn't select a file, leave unselected app->selected_flash_options[SelectedFlashBoot] = false; } view_dispatcher_send_custom_event(app->view_dispatcher, WifiMarauderEventRefreshSubmenu); break; case SubmenuIndexPart: - app->selected_flash_options[SelectedFlashPart] = !app->selected_flash_options[SelectedFlashPart]; + app->selected_flash_options[SelectedFlashPart] = + !app->selected_flash_options[SelectedFlashPart]; if(dialog_file_browser_show( app->dialogs, selected_filepath, predefined_filepath, &browser_options)) { strncpy( @@ -56,14 +59,15 @@ static void wifi_marauder_scene_flasher_callback(void* context, uint32_t index) furi_string_get_cstr(selected_filepath), sizeof(app->bin_file_path_part)); } - if (app->bin_file_path_part[0] == '\0') { + if(app->bin_file_path_part[0] == '\0') { // if user didn't select a file, leave unselected app->selected_flash_options[SelectedFlashPart] = false; } view_dispatcher_send_custom_event(app->view_dispatcher, WifiMarauderEventRefreshSubmenu); break; case SubmenuIndexNvs: - app->selected_flash_options[SelectedFlashNvs] = !app->selected_flash_options[SelectedFlashNvs]; + app->selected_flash_options[SelectedFlashNvs] = + !app->selected_flash_options[SelectedFlashNvs]; if(dialog_file_browser_show( app->dialogs, selected_filepath, predefined_filepath, &browser_options)) { strncpy( @@ -71,14 +75,15 @@ static void wifi_marauder_scene_flasher_callback(void* context, uint32_t index) furi_string_get_cstr(selected_filepath), sizeof(app->bin_file_path_nvs)); } - if (app->bin_file_path_nvs[0] == '\0') { + if(app->bin_file_path_nvs[0] == '\0') { // if user didn't select a file, leave unselected app->selected_flash_options[SelectedFlashNvs] = false; } view_dispatcher_send_custom_event(app->view_dispatcher, WifiMarauderEventRefreshSubmenu); break; case SubmenuIndexBootApp0: - app->selected_flash_options[SelectedFlashBootApp0] = !app->selected_flash_options[SelectedFlashBootApp0]; + app->selected_flash_options[SelectedFlashBootApp0] = + !app->selected_flash_options[SelectedFlashBootApp0]; if(dialog_file_browser_show( app->dialogs, selected_filepath, predefined_filepath, &browser_options)) { strncpy( @@ -86,14 +91,15 @@ static void wifi_marauder_scene_flasher_callback(void* context, uint32_t index) furi_string_get_cstr(selected_filepath), sizeof(app->bin_file_path_boot_app0)); } - if (app->bin_file_path_boot_app0[0] == '\0') { + if(app->bin_file_path_boot_app0[0] == '\0') { // if user didn't select a file, leave unselected app->selected_flash_options[SelectedFlashBootApp0] = false; } view_dispatcher_send_custom_event(app->view_dispatcher, WifiMarauderEventRefreshSubmenu); break; case SubmenuIndexApp: - app->selected_flash_options[SelectedFlashApp] = !app->selected_flash_options[SelectedFlashApp]; + app->selected_flash_options[SelectedFlashApp] = + !app->selected_flash_options[SelectedFlashApp]; if(dialog_file_browser_show( app->dialogs, selected_filepath, predefined_filepath, &browser_options)) { strncpy( @@ -101,14 +107,15 @@ static void wifi_marauder_scene_flasher_callback(void* context, uint32_t index) furi_string_get_cstr(selected_filepath), sizeof(app->bin_file_path_app)); } - if (app->bin_file_path_app[0] == '\0') { + if(app->bin_file_path_app[0] == '\0') { // if user didn't select a file, leave unselected app->selected_flash_options[SelectedFlashApp] = false; } view_dispatcher_send_custom_event(app->view_dispatcher, WifiMarauderEventRefreshSubmenu); break; case SubmenuIndexCustom: - app->selected_flash_options[SelectedFlashCustom] = !app->selected_flash_options[SelectedFlashCustom]; + app->selected_flash_options[SelectedFlashCustom] = + !app->selected_flash_options[SelectedFlashCustom]; if(dialog_file_browser_show( app->dialogs, selected_filepath, predefined_filepath, &browser_options)) { strncpy( @@ -116,7 +123,7 @@ static void wifi_marauder_scene_flasher_callback(void* context, uint32_t index) furi_string_get_cstr(selected_filepath), sizeof(app->bin_file_path_custom)); } - if (app->bin_file_path_custom[0] == '\0') { + if(app->bin_file_path_custom[0] == '\0') { // if user didn't select a file, leave unselected app->selected_flash_options[SelectedFlashCustom] = false; } @@ -125,12 +132,14 @@ static void wifi_marauder_scene_flasher_callback(void* context, uint32_t index) case SubmenuIndexFlash: // count how many options are selected app->num_selected_flash_options = 0; - for (bool* option = &app->selected_flash_options[SelectedFlashBoot]; option < &app->selected_flash_options[NUM_FLASH_OPTIONS]; ++option) { - if (*option) { + for(bool* option = &app->selected_flash_options[SelectedFlashBoot]; + option < &app->selected_flash_options[NUM_FLASH_OPTIONS]; + ++option) { + if(*option) { ++app->num_selected_flash_options; } } - if (app->num_selected_flash_options) { + if(app->num_selected_flash_options) { // only start next scene if at least one option is selected scene_manager_next_scene(app->scene_manager, WifiMarauderSceneConsoleOutput); } @@ -141,17 +150,17 @@ static void wifi_marauder_scene_flasher_callback(void* context, uint32_t index) furi_string_free(predefined_filepath); } -#define STR_SELECT "[x]" -#define STR_UNSELECT "[ ]" -#define STR_BOOT "Bootloader (" TOSTRING(ESP_ADDR_BOOT) ")" -#define STR_BOOT_S3 "Bootloader (" TOSTRING(ESP_ADDR_BOOT_S3) ")" -#define STR_PART "Part Table (" TOSTRING(ESP_ADDR_PART) ")" -#define STR_NVS "NVS (" TOSTRING(ESP_ADDR_NVS) ")" -#define STR_BOOT_APP0 "boot_app0 (" TOSTRING(ESP_ADDR_BOOT_APP0) ")" -#define STR_APP "Firmware (" TOSTRING(ESP_ADDR_APP) ")" -#define STR_CUSTOM "Custom" -#define STR_FLASH_S3 "[>] FLASH (ESP32-S3)" -#define STR_FLASH "[>] FLASH" +#define STR_SELECT "[x]" +#define STR_UNSELECT "[ ]" +#define STR_BOOT "Bootloader (" TOSTRING(ESP_ADDR_BOOT) ")" +#define STR_BOOT_S3 "Bootloader (" TOSTRING(ESP_ADDR_BOOT_S3) ")" +#define STR_PART "Part Table (" TOSTRING(ESP_ADDR_PART) ")" +#define STR_NVS "NVS (" TOSTRING(ESP_ADDR_NVS) ")" +#define STR_BOOT_APP0 "boot_app0 (" TOSTRING(ESP_ADDR_BOOT_APP0) ")" +#define STR_APP "Firmware (" TOSTRING(ESP_ADDR_APP) ")" +#define STR_CUSTOM "Custom" +#define STR_FLASH_S3 "[>] FLASH (ESP32-S3)" +#define STR_FLASH "[>] FLASH" static void _refresh_submenu(WifiMarauderApp* app) { Submenu* submenu = app->submenu; @@ -159,16 +168,21 @@ static void _refresh_submenu(WifiMarauderApp* app) { submenu_set_header(submenu, "Browse for files to flash"); submenu_add_item( - submenu, app->selected_flash_options[SelectedFlashS3Mode] ? "[x] Using ESP32-S3" : "[ ] Check if using S3", SubmenuIndexS3Mode, wifi_marauder_scene_flasher_callback, app); + submenu, + app->selected_flash_options[SelectedFlashS3Mode] ? "[x] Using ESP32-S3" : + "[ ] Check if using S3", + SubmenuIndexS3Mode, + wifi_marauder_scene_flasher_callback, + app); const char* strSelectBootloader = STR_UNSELECT " " STR_BOOT; - if (app->selected_flash_options[SelectedFlashS3Mode]) { - if (app->selected_flash_options[SelectedFlashBoot]) { + if(app->selected_flash_options[SelectedFlashS3Mode]) { + if(app->selected_flash_options[SelectedFlashBoot]) { strSelectBootloader = STR_SELECT " " STR_BOOT_S3; } else { strSelectBootloader = STR_UNSELECT " " STR_BOOT_S3; } } else { - if (app->selected_flash_options[SelectedFlashBoot]) { + if(app->selected_flash_options[SelectedFlashBoot]) { strSelectBootloader = STR_SELECT " " STR_BOOT; } else { strSelectBootloader = STR_UNSELECT " " STR_BOOT; @@ -177,18 +191,42 @@ static void _refresh_submenu(WifiMarauderApp* app) { submenu_add_item( submenu, strSelectBootloader, SubmenuIndexBoot, wifi_marauder_scene_flasher_callback, app); submenu_add_item( - submenu, app->selected_flash_options[SelectedFlashPart] ? STR_SELECT " " STR_PART : STR_UNSELECT " " STR_PART, SubmenuIndexPart, wifi_marauder_scene_flasher_callback, app); + submenu, + app->selected_flash_options[SelectedFlashPart] ? STR_SELECT " " STR_PART : + STR_UNSELECT " " STR_PART, + SubmenuIndexPart, + wifi_marauder_scene_flasher_callback, + app); submenu_add_item( - submenu, app->selected_flash_options[SelectedFlashNvs] ? STR_SELECT " " STR_NVS : STR_UNSELECT " " STR_NVS, SubmenuIndexNvs, wifi_marauder_scene_flasher_callback, app); + submenu, + app->selected_flash_options[SelectedFlashNvs] ? STR_SELECT " " STR_NVS : + STR_UNSELECT " " STR_NVS, + SubmenuIndexNvs, + wifi_marauder_scene_flasher_callback, + app); submenu_add_item( - submenu, app->selected_flash_options[SelectedFlashBootApp0] ? STR_SELECT " " STR_BOOT_APP0 : STR_UNSELECT " " STR_BOOT_APP0, SubmenuIndexBootApp0, wifi_marauder_scene_flasher_callback, app); + submenu, + app->selected_flash_options[SelectedFlashBootApp0] ? STR_SELECT " " STR_BOOT_APP0 : + STR_UNSELECT " " STR_BOOT_APP0, + SubmenuIndexBootApp0, + wifi_marauder_scene_flasher_callback, + app); submenu_add_item( - submenu, app->selected_flash_options[SelectedFlashApp] ? STR_SELECT " " STR_APP : STR_UNSELECT " " STR_APP, SubmenuIndexApp, wifi_marauder_scene_flasher_callback, app); + submenu, + app->selected_flash_options[SelectedFlashApp] ? STR_SELECT " " STR_APP : + STR_UNSELECT " " STR_APP, + SubmenuIndexApp, + wifi_marauder_scene_flasher_callback, + app); // TODO: custom addr //submenu_add_item( // submenu, app->selected_flash_options[SelectedFlashCustom] ? STR_SELECT " " STR_CUSTOM : STR_UNSELECT " " STR_CUSTOM, SubmenuIndexCustom, wifi_marauder_scene_flasher_callback, app); submenu_add_item( - submenu, app->selected_flash_options[SelectedFlashS3Mode] ? STR_FLASH_S3 : STR_FLASH, SubmenuIndexFlash, wifi_marauder_scene_flasher_callback, app); + submenu, + app->selected_flash_options[SelectedFlashS3Mode] ? STR_FLASH_S3 : STR_FLASH, + SubmenuIndexFlash, + wifi_marauder_scene_flasher_callback, + app); submenu_set_selected_item( submenu, scene_manager_get_scene_state(app->scene_manager, WifiMarauderSceneFlasher)); @@ -212,8 +250,8 @@ void wifi_marauder_scene_flasher_on_enter(void* context) { bool wifi_marauder_scene_flasher_on_event(void* context, SceneManagerEvent event) { WifiMarauderApp* app = context; bool consumed = false; - if (event.type == SceneManagerEventTypeCustom) { - if (event.event == WifiMarauderEventRefreshSubmenu) { + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == WifiMarauderEventRefreshSubmenu) { _refresh_submenu(app); consumed = true; } diff --git a/applications/external/wifi_marauder_companion/wifi_marauder_flasher.c b/applications/external/wifi_marauder_companion/wifi_marauder_flasher.c index 63820be0b..ffc1acb78 100644 --- a/applications/external/wifi_marauder_companion/wifi_marauder_flasher.c +++ b/applications/external/wifi_marauder_companion/wifi_marauder_flasher.c @@ -43,7 +43,7 @@ static esp_loader_error_t _flash_file(WifiMarauderApp* app, char* filepath, uint loader_port_debug_print("Start programming\n"); uint64_t last_updated = size; while(size > 0) { - if ((last_updated - size) > 50000) { + if((last_updated - size) > 50000) { // inform user every 50k bytes // TODO: draw a progress bar next update snprintf(user_msg, sizeof(user_msg), "%llu bytes left.\n", size); @@ -85,23 +85,33 @@ static void _flash_all_files(WifiMarauderApp* app) { esp_loader_error_t err; const int num_steps = app->num_selected_flash_options; - #define NUM_FLASH_ITEMS 6 +#define NUM_FLASH_ITEMS 6 FlashItem items[NUM_FLASH_ITEMS] = { - { SelectedFlashBoot, "bootloader", app->bin_file_path_boot, app->selected_flash_options[SelectedFlashS3Mode] ? ESP_ADDR_BOOT_S3 : ESP_ADDR_BOOT }, - { SelectedFlashPart, "partition table", app->bin_file_path_part, ESP_ADDR_PART }, - { SelectedFlashNvs, "NVS", app->bin_file_path_nvs, ESP_ADDR_NVS }, - { SelectedFlashBootApp0, "boot_app0", app->bin_file_path_boot_app0, ESP_ADDR_BOOT_APP0 }, - { SelectedFlashApp, "firmware", app->bin_file_path_app, ESP_ADDR_APP }, - { SelectedFlashCustom, "custom data", app->bin_file_path_custom, 0x0 }, + {SelectedFlashBoot, + "bootloader", + app->bin_file_path_boot, + app->selected_flash_options[SelectedFlashS3Mode] ? ESP_ADDR_BOOT_S3 : ESP_ADDR_BOOT}, + {SelectedFlashPart, "partition table", app->bin_file_path_part, ESP_ADDR_PART}, + {SelectedFlashNvs, "NVS", app->bin_file_path_nvs, ESP_ADDR_NVS}, + {SelectedFlashBootApp0, "boot_app0", app->bin_file_path_boot_app0, ESP_ADDR_BOOT_APP0}, + {SelectedFlashApp, "firmware", app->bin_file_path_app, ESP_ADDR_APP}, + {SelectedFlashCustom, "custom data", app->bin_file_path_custom, 0x0}, /* if you add more entries, update NUM_FLASH_ITEMS above! */ }; char user_msg[256]; int current_step = 1; - for (FlashItem* item = &items[0]; item < &items[NUM_FLASH_ITEMS]; ++item) { + for(FlashItem* item = &items[0]; item < &items[NUM_FLASH_ITEMS]; ++item) { if(app->selected_flash_options[item->selected]) { - snprintf(user_msg, sizeof(user_msg), "Flashing %s (%d/%d) to address 0x%lx\n", item->description, current_step++, num_steps, item->addr); + snprintf( + user_msg, + sizeof(user_msg), + "Flashing %s (%d/%d) to address 0x%lx\n", + item->description, + current_step++, + num_steps, + item->addr); loader_port_debug_print(user_msg); err = _flash_file(app, item->path, item->addr); if(err) { @@ -130,7 +140,7 @@ static int32_t wifi_marauder_flash_bin(void* context) { loader_port_debug_print(err_msg); } - #if 0 // still getting packet drops with this +#if 0 // still getting packet drops with this // higher BR if(!err) { loader_port_debug_print("Increasing speed for faster flash\n"); @@ -146,15 +156,15 @@ static int32_t wifi_marauder_flash_bin(void* context) { } furi_hal_uart_set_br(FuriHalUartIdUSART1, 230400); } - #endif +#endif if(!err) { loader_port_debug_print("Connected\n"); _flash_all_files(app); - #if 0 +#if 0 loader_port_debug_print("Restoring transmission rate\n"); furi_hal_uart_set_br(FuriHalUartIdUSART1, 115200); - #endif +#endif loader_port_debug_print("Done flashing. Please reset the board manually.\n"); } diff --git a/applications/external/wifi_marauder_companion/wifi_marauder_flasher.h b/applications/external/wifi_marauder_companion/wifi_marauder_flasher.h index 3688ed09f..d875c2dbe 100644 --- a/applications/external/wifi_marauder_companion/wifi_marauder_flasher.h +++ b/applications/external/wifi_marauder_companion/wifi_marauder_flasher.h @@ -5,12 +5,12 @@ #define SERIAL_FLASHER_INTERFACE_UART /* TODO why is application.fam not passing this via cdefines */ #include "esp_loader_io.h" -#define ESP_ADDR_BOOT_S3 0x0 -#define ESP_ADDR_BOOT 0x1000 -#define ESP_ADDR_PART 0x8000 -#define ESP_ADDR_NVS 0x9000 +#define ESP_ADDR_BOOT_S3 0x0 +#define ESP_ADDR_BOOT 0x1000 +#define ESP_ADDR_PART 0x8000 +#define ESP_ADDR_NVS 0x9000 #define ESP_ADDR_BOOT_APP0 0xE000 -#define ESP_ADDR_APP 0x10000 +#define ESP_ADDR_APP 0x10000 void wifi_marauder_flash_start_thread(WifiMarauderApp* app); void wifi_marauder_flash_stop_thread(WifiMarauderApp* app); From dab2e6e39c3ace355581471f03215c45cb63d4cb Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 10 Jul 2023 17:10:43 +0300 Subject: [PATCH 078/364] remove debug pack for now, fw should fit in debug --- .vscode/example/tasks.json | 16 ++++++++-------- documentation/HowToBuild.md | 8 ++------ fbt_options.py | 13 ------------- 3 files changed, 10 insertions(+), 27 deletions(-) diff --git a/.vscode/example/tasks.json b/.vscode/example/tasks.json index 16437cb10..28e67d456 100644 --- a/.vscode/example/tasks.json +++ b/.vscode/example/tasks.json @@ -13,7 +13,7 @@ "label": "[Debug] Build", "group": "build", "type": "shell", - "command": "./fbt FIRMWARE_APP_SET=debug_pack" + "command": "./fbt" }, { "label": "[Release] Flash (ST-Link)", @@ -25,7 +25,7 @@ "label": "[Debug] Flash (ST-Link)", "group": "build", "type": "shell", - "command": "./fbt FIRMWARE_APP_SET=debug_pack FORCE=1 flash" + "command": "./fbt FORCE=1 flash" }, { "label": "[Release] Flash (blackmagic)", @@ -37,7 +37,7 @@ "label": "[Debug] Flash (blackmagic)", "group": "build", "type": "shell", - "command": "./fbt FIRMWARE_APP_SET=debug_pack FORCE=1 flash_blackmagic" + "command": "./fbt FORCE=1 flash_blackmagic" }, { "label": "[Release] Flash (JLink)", @@ -49,7 +49,7 @@ "label": "[Debug] Flash (JLink)", "group": "build", "type": "shell", - "command": "./fbt FIRMWARE_APP_SET=debug_pack FORCE=1 jflash" + "command": "./fbt FORCE=1 jflash" }, { "label": "[Release] Build update bundle", @@ -61,7 +61,7 @@ "label": "[Debug] Build update bundle", "group": "build", "type": "shell", - "command": "./fbt FIRMWARE_APP_SET=debug_pack updater_package" + "command": "./fbt updater_package" }, { "label": "[Release] Build updater", @@ -73,13 +73,13 @@ "label": "[Debug] Build updater", "group": "build", "type": "shell", - "command": "./fbt FIRMWARE_APP_SET=debug_pack updater_all" + "command": "./fbt updater_all" }, { "label": "[Debug] Flash (USB, w/o resources)", "group": "build", "type": "shell", - "command": "./fbt FIRMWARE_APP_SET=debug_pack FORCE=1 flash_usb" + "command": "./fbt FORCE=1 flash_usb" }, { "label": "[Release] Flash (USB, w/o resources)", @@ -97,7 +97,7 @@ "label": "[Debug] Flash (USB, with resources)", "group": "build", "type": "shell", - "command": "./fbt FIRMWARE_APP_SET=debug_pack FORCE=1 flash_usb_full" + "command": "./fbt FORCE=1 flash_usb_full" }, { "label": "[Release] Flash (USB, with resources)", diff --git a/documentation/HowToBuild.md b/documentation/HowToBuild.md index 76830063f..ddf759f1b 100644 --- a/documentation/HowToBuild.md +++ b/documentation/HowToBuild.md @@ -31,11 +31,9 @@ Check out `documentation/fbt.md` for details on building and flashing firmware. ### Compile everything for development -Edit this file to enable/disable Main apps that you need in DEBUG mode, flash space doesn't allows us to fit them all in DEBUG currently -- `applications/main/application.fam` ```sh -./fbt FIRMWARE_APP_SET=debug_pack updater_package +./fbt updater_package ``` ### Compile everything for release + get updater package to update from microSD card @@ -55,11 +53,9 @@ Check out `documentation/fbt.md` for details on building and flashing firmware. ### Compile everything for development -Edit this file to enable/disable Main apps that you need in DEBUG mode, flash space doesn't allows us to fit them all in DEBUG currently -- `applications/main/application.fam` ```sh -./fbt.cmd FIRMWARE_APP_SET=debug_pack updater_package +./fbt.cmd updater_package ``` ### Compile everything for release + get updater package to update from microSD card diff --git a/fbt_options.py b/fbt_options.py index 561b818d4..4286e08e8 100644 --- a/fbt_options.py +++ b/fbt_options.py @@ -74,19 +74,6 @@ FIRMWARE_APPS = { "updater_app", "unit_tests", ], - "debug_pack": [ - # Svc - "basic_services", - # Apps - "main_apps_default", - "system_apps", - # Settings - "settings_apps", - # Plugins - # "basic_plugins", - # Debug - # "debug_apps", - ], } FIRMWARE_APP_SET = "default" From 5398fb806ed763a8d69a00ea8998e9225d868f49 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 10 Jul 2023 17:21:49 +0300 Subject: [PATCH 079/364] move last apps into microsd --- applications/main/clock_app/application.fam | 7 +++---- applications/main/clock_app/icon.png | Bin 0 -> 7920 bytes applications/main/subghz_remote/application.fam | 14 ++++---------- applications/main/subghz_remote/icon.png | Bin 0 -> 5000 bytes .../main/subghz_remote/subghz_remote_app_i.h | 3 +++ 5 files changed, 10 insertions(+), 14 deletions(-) create mode 100644 applications/main/clock_app/icon.png create mode 100644 applications/main/subghz_remote/icon.png diff --git a/applications/main/clock_app/application.fam b/applications/main/clock_app/application.fam index 9016973c5..47f152b1d 100644 --- a/applications/main/clock_app/application.fam +++ b/applications/main/clock_app/application.fam @@ -1,12 +1,11 @@ App( appid="clock", name="Clock", - apptype=FlipperAppType.APP, + apptype=FlipperAppType.MENUEXTERNAL, entry_point="clock_app", - cdefines=["APP_CLOCK"], - requires=["gui"], icon="A_Clock_14", stack_size=2 * 1024, order=81, + fap_icon="icon.png", + fap_category="Tools", ) - diff --git a/applications/main/clock_app/icon.png b/applications/main/clock_app/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..8a5406e6ce5bf26a43d916c16d14bd43fb0d7cdf GIT binary patch literal 7920 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 Date: Mon, 10 Jul 2023 19:59:25 +0300 Subject: [PATCH 080/364] Fix SubGhz Apps & LF RFID --- .../main/subghz_remote/application.fam | 1 + .../subghz_remote/helpers/txrx/subghz_txrx.c | 571 ++++++++++++++++++ .../subghz_remote/helpers/txrx/subghz_txrx.h | 319 +++++++++- .../helpers/txrx/subghz_txrx_i.h | 29 + firmware/targets/f7/api_symbols.csv | 30 +- .../f7/platform_specific/intrinsic_export.h | 1 + lib/subghz/SConscript | 2 + lib/subghz/blocks/custom_btn.h | 10 +- lib/subghz/subghz_file_encoder_worker.h | 8 + lib/subghz/subghz_protocol_registry.h | 52 ++ 10 files changed, 1017 insertions(+), 6 deletions(-) create mode 100644 applications/main/subghz_remote/helpers/txrx/subghz_txrx.c create mode 100644 applications/main/subghz_remote/helpers/txrx/subghz_txrx_i.h diff --git a/applications/main/subghz_remote/application.fam b/applications/main/subghz_remote/application.fam index f8980c0a7..8e916289b 100644 --- a/applications/main/subghz_remote/application.fam +++ b/applications/main/subghz_remote/application.fam @@ -6,6 +6,7 @@ App( icon="A_SubGHzRemote_14", stack_size=2 * 1024, order=11, + fap_libs=["assets",], fap_icon="icon.png", fap_category="Sub-Ghz", ) diff --git a/applications/main/subghz_remote/helpers/txrx/subghz_txrx.c b/applications/main/subghz_remote/helpers/txrx/subghz_txrx.c new file mode 100644 index 000000000..3275b7288 --- /dev/null +++ b/applications/main/subghz_remote/helpers/txrx/subghz_txrx.c @@ -0,0 +1,571 @@ +#include "subghz_txrx_i.h" +#include +#include + +#define TAG "SubGhz" + +SubGhzTxRx* subghz_txrx_alloc() { + SubGhzTxRx* instance = malloc(sizeof(SubGhzTxRx)); + instance->setting = subghz_setting_alloc(); + subghz_setting_load(instance->setting, EXT_PATH("subghz/assets/setting_user")); + + instance->preset = malloc(sizeof(SubGhzRadioPreset)); + instance->preset->name = furi_string_alloc(); + subghz_txrx_set_preset( + instance, "AM650", subghz_setting_get_default_frequency(instance->setting), NULL, 0); + + instance->txrx_state = SubGhzTxRxStateSleep; + + subghz_txrx_hopper_set_state(instance, SubGhzHopperStateOFF); + subghz_txrx_speaker_set_state(instance, SubGhzSpeakerStateDisable); + subghz_txrx_set_debug_pin_state(instance, false); + + instance->worker = subghz_worker_alloc(); + instance->fff_data = flipper_format_string_alloc(); + + instance->environment = subghz_environment_alloc(); + instance->is_database_loaded = subghz_environment_load_keystore( + instance->environment, EXT_PATH("subghz/assets/keeloq_mfcodes")); + subghz_environment_load_keystore( + instance->environment, EXT_PATH("subghz/assets/keeloq_mfcodes_user")); + subghz_environment_set_came_atomo_rainbow_table_file_name( + instance->environment, EXT_PATH("subghz/assets/came_atomo")); + subghz_environment_set_alutech_at_4n_rainbow_table_file_name( + instance->environment, EXT_PATH("subghz/assets/alutech_at_4n")); + subghz_environment_set_nice_flor_s_rainbow_table_file_name( + instance->environment, EXT_PATH("subghz/assets/nice_flor_s")); + subghz_environment_set_protocol_registry( + instance->environment, (void*)&subghz_protocol_registry); + instance->receiver = subghz_receiver_alloc_init(instance->environment); + + subghz_worker_set_overrun_callback( + instance->worker, (SubGhzWorkerOverrunCallback)subghz_receiver_reset); + subghz_worker_set_pair_callback( + instance->worker, (SubGhzWorkerPairCallback)subghz_receiver_decode); + subghz_worker_set_context(instance->worker, instance->receiver); + + return instance; +} + +void subghz_txrx_free(SubGhzTxRx* instance) { + furi_assert(instance); + + subghz_worker_free(instance->worker); + subghz_receiver_free(instance->receiver); + subghz_environment_free(instance->environment); + flipper_format_free(instance->fff_data); + furi_string_free(instance->preset->name); + subghz_setting_free(instance->setting); + free(instance->preset); + free(instance); +} + +bool subghz_txrx_is_database_loaded(SubGhzTxRx* instance) { + furi_assert(instance); + return instance->is_database_loaded; +} + +void subghz_txrx_set_preset( + SubGhzTxRx* instance, + const char* preset_name, + uint32_t frequency, + uint8_t* preset_data, + size_t preset_data_size) { + furi_assert(instance); + furi_string_set(instance->preset->name, preset_name); + SubGhzRadioPreset* preset = instance->preset; + preset->frequency = frequency; + preset->data = preset_data; + preset->data_size = preset_data_size; +} + +const char* subghz_txrx_get_preset_name(SubGhzTxRx* instance, const char* preset) { + UNUSED(instance); + const char* preset_name = ""; + if(!strcmp(preset, "FuriHalSubGhzPresetOok270Async")) { + preset_name = "AM270"; + } else if(!strcmp(preset, "FuriHalSubGhzPresetOok650Async")) { + preset_name = "AM650"; + } else if(!strcmp(preset, "FuriHalSubGhzPreset2FSKDev238Async")) { + preset_name = "FM238"; + } else if(!strcmp(preset, "FuriHalSubGhzPreset2FSKDev476Async")) { + preset_name = "FM476"; + } else if(!strcmp(preset, "FuriHalSubGhzPresetCustom")) { + preset_name = "CUSTOM"; + } else { + FURI_LOG_E(TAG, "Unknown preset"); + } + return preset_name; +} + +SubGhzRadioPreset subghz_txrx_get_preset(SubGhzTxRx* instance) { + furi_assert(instance); + return *instance->preset; +} + +void subghz_txrx_get_frequency_and_modulation( + SubGhzTxRx* instance, + FuriString* frequency, + FuriString* modulation, + bool long_name) { + furi_assert(instance); + SubGhzRadioPreset* preset = instance->preset; + if(frequency != NULL) { + furi_string_printf( + frequency, + "%03ld.%02ld", + preset->frequency / 1000000 % 1000, + preset->frequency / 10000 % 100); + } + if(modulation != NULL) { + if(long_name) { + furi_string_printf(modulation, "%s", furi_string_get_cstr(preset->name)); + } else { + furi_string_printf(modulation, "%.2s", furi_string_get_cstr(preset->name)); + } + } +} + +static void subghz_txrx_begin(SubGhzTxRx* instance, uint8_t* preset_data) { + furi_assert(instance); + furi_hal_subghz_reset(); + furi_hal_subghz_idle(); + furi_hal_subghz_load_custom_preset(preset_data); + furi_hal_gpio_init(furi_hal_subghz.cc1101_g0_pin, GpioModeInput, GpioPullNo, GpioSpeedLow); + instance->txrx_state = SubGhzTxRxStateIDLE; +} + +static uint32_t subghz_txrx_rx(SubGhzTxRx* instance, uint32_t frequency) { + furi_assert(instance); + if(!furi_hal_subghz_is_frequency_valid(frequency)) { + furi_crash("SubGhz: Incorrect RX frequency."); + } + furi_assert( + instance->txrx_state != SubGhzTxRxStateRx && instance->txrx_state != SubGhzTxRxStateSleep); + + furi_hal_subghz_idle(); + uint32_t value = furi_hal_subghz_set_frequency_and_path(frequency); + furi_hal_gpio_init(furi_hal_subghz.cc1101_g0_pin, GpioModeInput, GpioPullNo, GpioSpeedLow); + furi_hal_subghz_flush_rx(); + subghz_txrx_speaker_on(instance); + furi_hal_subghz_rx(); + + furi_hal_subghz_start_async_rx(subghz_worker_rx_callback, instance->worker); + subghz_worker_start(instance->worker); + instance->txrx_state = SubGhzTxRxStateRx; + return value; +} + +static void subghz_txrx_idle(SubGhzTxRx* instance) { + furi_assert(instance); + furi_assert(instance->txrx_state != SubGhzTxRxStateSleep); + furi_hal_subghz_idle(); + subghz_txrx_speaker_off(instance); + instance->txrx_state = SubGhzTxRxStateIDLE; +} + +static void subghz_txrx_rx_end(SubGhzTxRx* instance) { + furi_assert(instance); + furi_assert(instance->txrx_state == SubGhzTxRxStateRx); + + if(subghz_worker_is_running(instance->worker)) { + subghz_worker_stop(instance->worker); + furi_hal_subghz_stop_async_rx(); + } + furi_hal_subghz_idle(); + subghz_txrx_speaker_off(instance); + instance->txrx_state = SubGhzTxRxStateIDLE; +} + +void subghz_txrx_sleep(SubGhzTxRx* instance) { + furi_assert(instance); + furi_hal_subghz_sleep(); + instance->txrx_state = SubGhzTxRxStateSleep; +} + +static bool subghz_txrx_tx(SubGhzTxRx* instance, uint32_t frequency) { + furi_assert(instance); + if(!furi_hal_subghz_is_frequency_valid(frequency)) { + furi_crash("SubGhz: Incorrect TX frequency."); + } + furi_assert(instance->txrx_state != SubGhzTxRxStateSleep); + furi_hal_subghz_idle(); + furi_hal_subghz_set_frequency_and_path(frequency); + furi_hal_gpio_write(furi_hal_subghz.cc1101_g0_pin, false); + furi_hal_gpio_init( + furi_hal_subghz.cc1101_g0_pin, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); + bool ret = furi_hal_subghz_tx(); + if(ret) { + subghz_txrx_speaker_on(instance); + instance->txrx_state = SubGhzTxRxStateTx; + } + + return ret; +} + +SubGhzTxRxStartTxState subghz_txrx_tx_start(SubGhzTxRx* instance, FlipperFormat* flipper_format) { + furi_assert(instance); + furi_assert(flipper_format); + + subghz_txrx_stop(instance); + + SubGhzTxRxStartTxState ret = SubGhzTxRxStartTxStateErrorParserOthers; + FuriString* temp_str = furi_string_alloc(); + uint32_t repeat = 200; + do { + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + if(!flipper_format_read_string(flipper_format, "Protocol", temp_str)) { + FURI_LOG_E(TAG, "Missing Protocol"); + break; + } + if(!flipper_format_insert_or_update_uint32(flipper_format, "Repeat", &repeat, 1)) { + FURI_LOG_E(TAG, "Unable Repeat"); + break; + } + ret = SubGhzTxRxStartTxStateOk; + + SubGhzRadioPreset* preset = instance->preset; + instance->transmitter = + subghz_transmitter_alloc_init(instance->environment, furi_string_get_cstr(temp_str)); + + if(instance->transmitter) { + if(subghz_transmitter_deserialize(instance->transmitter, flipper_format) == + SubGhzProtocolStatusOk) { + if(strcmp(furi_string_get_cstr(preset->name), "") != 0) { + subghz_txrx_begin( + instance, + subghz_setting_get_preset_data_by_name( + instance->setting, furi_string_get_cstr(preset->name))); + if(preset->frequency) { + if(!subghz_txrx_tx(instance, preset->frequency)) { + FURI_LOG_E(TAG, "Only Rx"); + ret = SubGhzTxRxStartTxStateErrorOnlyRx; + } + } else { + ret = SubGhzTxRxStartTxStateErrorParserOthers; + } + + } else { + FURI_LOG_E( + TAG, "Unknown name preset \" %s \"", furi_string_get_cstr(preset->name)); + ret = SubGhzTxRxStartTxStateErrorParserOthers; + } + + if(ret == SubGhzTxRxStartTxStateOk) { + //Start TX + furi_hal_subghz_start_async_tx( + subghz_transmitter_yield, instance->transmitter); + } + } else { + ret = SubGhzTxRxStartTxStateErrorParserOthers; + } + } else { + ret = SubGhzTxRxStartTxStateErrorParserOthers; + } + if(ret != SubGhzTxRxStartTxStateOk) { + subghz_transmitter_free(instance->transmitter); + if(instance->txrx_state != SubGhzTxRxStateIDLE) { + subghz_txrx_idle(instance); + } + } + + } while(false); + furi_string_free(temp_str); + return ret; +} + +void subghz_txrx_rx_start(SubGhzTxRx* instance) { + furi_assert(instance); + subghz_txrx_stop(instance); + subghz_txrx_begin( + instance, + subghz_setting_get_preset_data_by_name( + subghz_txrx_get_setting(instance), furi_string_get_cstr(instance->preset->name))); + subghz_txrx_rx(instance, instance->preset->frequency); +} + +void subghz_txrx_set_need_save_callback( + SubGhzTxRx* instance, + SubGhzTxRxNeedSaveCallback callback, + void* context) { + furi_assert(instance); + instance->need_save_callback = callback; + instance->need_save_context = context; +} + +static void subghz_txrx_tx_stop(SubGhzTxRx* instance) { + furi_assert(instance); + furi_assert(instance->txrx_state == SubGhzTxRxStateTx); + //Stop TX + furi_hal_subghz_stop_async_tx(); + subghz_transmitter_stop(instance->transmitter); + subghz_transmitter_free(instance->transmitter); + + //if protocol dynamic then we save the last upload + if(instance->decoder_result->protocol->type == SubGhzProtocolTypeDynamic) { + if(instance->need_save_callback) { + instance->need_save_callback(instance->need_save_context); + } + } + subghz_txrx_idle(instance); + subghz_txrx_speaker_off(instance); + //Todo: Show message + // notification_message(notifications, &sequence_reset_red); +} + +FlipperFormat* subghz_txrx_get_fff_data(SubGhzTxRx* instance) { + furi_assert(instance); + return instance->fff_data; +} + +SubGhzSetting* subghz_txrx_get_setting(SubGhzTxRx* instance) { + furi_assert(instance); + return instance->setting; +} + +void subghz_txrx_stop(SubGhzTxRx* instance) { + furi_assert(instance); + + switch(instance->txrx_state) { + case SubGhzTxRxStateTx: + subghz_txrx_tx_stop(instance); + subghz_txrx_speaker_unmute(instance); + break; + case SubGhzTxRxStateRx: + subghz_txrx_rx_end(instance); + subghz_txrx_speaker_mute(instance); + break; + + default: + break; + } +} + +void subghz_txrx_hopper_update(SubGhzTxRx* instance) { + furi_assert(instance); + + switch(instance->hopper_state) { + case SubGhzHopperStateOFF: + case SubGhzHopperStatePause: + return; + case SubGhzHopperStateRSSITimeOut: + if(instance->hopper_timeout != 0) { + instance->hopper_timeout--; + return; + } + break; + default: + break; + } + float rssi = -127.0f; + if(instance->hopper_state != SubGhzHopperStateRSSITimeOut) { + // See RSSI Calculation timings in CC1101 17.3 RSSI + rssi = furi_hal_subghz_get_rssi(); + + // Stay if RSSI is high enough + if(rssi > -90.0f) { + instance->hopper_timeout = 10; + instance->hopper_state = SubGhzHopperStateRSSITimeOut; + return; + } + } else { + instance->hopper_state = SubGhzHopperStateRunning; + } + // Select next frequency + if(instance->hopper_idx_frequency < + subghz_setting_get_hopper_frequency_count(instance->setting) - 1) { + instance->hopper_idx_frequency++; + } else { + instance->hopper_idx_frequency = 0; + } + + if(instance->txrx_state == SubGhzTxRxStateRx) { + subghz_txrx_rx_end(instance); + }; + if(instance->txrx_state == SubGhzTxRxStateIDLE) { + subghz_receiver_reset(instance->receiver); + instance->preset->frequency = + subghz_setting_get_hopper_frequency(instance->setting, instance->hopper_idx_frequency); + subghz_txrx_rx(instance, instance->preset->frequency); + } +} + +SubGhzHopperState subghz_txrx_hopper_get_state(SubGhzTxRx* instance) { + furi_assert(instance); + return instance->hopper_state; +} + +void subghz_txrx_hopper_set_state(SubGhzTxRx* instance, SubGhzHopperState state) { + furi_assert(instance); + instance->hopper_state = state; +} + +void subghz_txrx_hopper_unpause(SubGhzTxRx* instance) { + furi_assert(instance); + if(instance->hopper_state == SubGhzHopperStatePause) { + instance->hopper_state = SubGhzHopperStateRunning; + } +} + +void subghz_txrx_hopper_pause(SubGhzTxRx* instance) { + furi_assert(instance); + if(instance->hopper_state == SubGhzHopperStateRunning) { + instance->hopper_state = SubGhzHopperStatePause; + } +} + +void subghz_txrx_speaker_on(SubGhzTxRx* instance) { + furi_assert(instance); + if(instance->debug_pin_state) { + furi_hal_subghz_set_async_mirror_pin(&gpio_ibutton); + } + + if(instance->speaker_state == SubGhzSpeakerStateEnable) { + if(furi_hal_speaker_acquire(30)) { + if(!instance->debug_pin_state) { + furi_hal_subghz_set_async_mirror_pin(&gpio_speaker); + } + } else { + instance->speaker_state = SubGhzSpeakerStateDisable; + } + } +} + +void subghz_txrx_speaker_off(SubGhzTxRx* instance) { + furi_assert(instance); + if(instance->debug_pin_state) { + furi_hal_subghz_set_async_mirror_pin(NULL); + } + if(instance->speaker_state != SubGhzSpeakerStateDisable) { + if(furi_hal_speaker_is_mine()) { + if(!instance->debug_pin_state) { + furi_hal_subghz_set_async_mirror_pin(NULL); + } + furi_hal_speaker_release(); + if(instance->speaker_state == SubGhzSpeakerStateShutdown) + instance->speaker_state = SubGhzSpeakerStateDisable; + } + } +} + +void subghz_txrx_speaker_mute(SubGhzTxRx* instance) { + furi_assert(instance); + if(instance->debug_pin_state) { + furi_hal_subghz_set_async_mirror_pin(NULL); + } + if(instance->speaker_state == SubGhzSpeakerStateEnable) { + if(furi_hal_speaker_is_mine()) { + if(!instance->debug_pin_state) { + furi_hal_subghz_set_async_mirror_pin(NULL); + } + } + } +} + +void subghz_txrx_speaker_unmute(SubGhzTxRx* instance) { + furi_assert(instance); + if(instance->debug_pin_state) { + furi_hal_subghz_set_async_mirror_pin(&gpio_ibutton); + } + if(instance->speaker_state == SubGhzSpeakerStateEnable) { + if(furi_hal_speaker_is_mine()) { + if(!instance->debug_pin_state) { + furi_hal_subghz_set_async_mirror_pin(&gpio_speaker); + } + } + } +} + +void subghz_txrx_speaker_set_state(SubGhzTxRx* instance, SubGhzSpeakerState state) { + furi_assert(instance); + instance->speaker_state = state; +} + +SubGhzSpeakerState subghz_txrx_speaker_get_state(SubGhzTxRx* instance) { + furi_assert(instance); + return instance->speaker_state; +} + +bool subghz_txrx_load_decoder_by_name_protocol(SubGhzTxRx* instance, const char* name_protocol) { + furi_assert(instance); + furi_assert(name_protocol); + bool res = false; + instance->decoder_result = + subghz_receiver_search_decoder_base_by_name(instance->receiver, name_protocol); + if(instance->decoder_result) { + res = true; + } + return res; +} + +SubGhzProtocolDecoderBase* subghz_txrx_get_decoder(SubGhzTxRx* instance) { + furi_assert(instance); + return instance->decoder_result; +} + +bool subghz_txrx_protocol_is_serializable(SubGhzTxRx* instance) { + furi_assert(instance); + return ( + (instance->decoder_result->protocol->flag & SubGhzProtocolFlag_Save) == + SubGhzProtocolFlag_Save); +} + +bool subghz_txrx_protocol_is_transmittable(SubGhzTxRx* instance, bool check_type) { + furi_assert(instance); + const SubGhzProtocol* protocol = instance->decoder_result->protocol; + if(check_type) { + return ( + ((protocol->flag & SubGhzProtocolFlag_Send) == SubGhzProtocolFlag_Send) && + protocol->encoder->deserialize && protocol->type == SubGhzProtocolTypeStatic); + } + return ( + ((protocol->flag & SubGhzProtocolFlag_Send) == SubGhzProtocolFlag_Send) && + protocol->encoder->deserialize); +} + +void subghz_txrx_receiver_set_filter(SubGhzTxRx* instance, SubGhzProtocolFlag filter) { + furi_assert(instance); + subghz_receiver_set_filter(instance->receiver, filter); +} + +void subghz_txrx_set_rx_calback( + SubGhzTxRx* instance, + SubGhzReceiverCallback callback, + void* context) { + subghz_receiver_set_rx_callback(instance->receiver, callback, context); +} + +void subghz_txrx_set_raw_file_encoder_worker_callback_end( + SubGhzTxRx* instance, + SubGhzProtocolEncoderRAWCallbackEnd callback, + void* context) { + subghz_protocol_raw_file_encoder_worker_set_callback_end( + (SubGhzProtocolEncoderRAW*)subghz_transmitter_get_protocol_instance(instance->transmitter), + callback, + context); +} + +void subghz_txrx_set_debug_pin_state(SubGhzTxRx* instance, bool state) { + furi_assert(instance); + instance->debug_pin_state = state; +} + +bool subghz_txrx_get_debug_pin_state(SubGhzTxRx* instance) { + furi_assert(instance); + return instance->debug_pin_state; +} + +void subghz_txrx_reset_dynamic_and_custom_btns(SubGhzTxRx* instance) { + furi_assert(instance); + subghz_environment_reset_keeloq(instance->environment); + + subghz_custom_btns_reset(); +} + +SubGhzReceiver* subghz_txrx_get_receiver(SubGhzTxRx* instance) { + furi_assert(instance); + return instance->receiver; +} \ No newline at end of file diff --git a/applications/main/subghz_remote/helpers/txrx/subghz_txrx.h b/applications/main/subghz_remote/helpers/txrx/subghz_txrx.h index 5241f402f..6e7641734 100644 --- a/applications/main/subghz_remote/helpers/txrx/subghz_txrx.h +++ b/applications/main/subghz_remote/helpers/txrx/subghz_txrx.h @@ -1,3 +1,320 @@ #pragma once -#include "../../../subghz/helpers/subghz_txrx.h" \ No newline at end of file +#include +#include +#include +#include +#include + +typedef struct SubGhzTxRx SubGhzTxRx; + +typedef void (*SubGhzTxRxNeedSaveCallback)(void* context); + +typedef enum { + SubGhzTxRxStartTxStateOk, + SubGhzTxRxStartTxStateErrorOnlyRx, + SubGhzTxRxStartTxStateErrorParserOthers, +} SubGhzTxRxStartTxState; + +// Type from subghz_types.h need for txrx working +/** SubGhzTxRx state */ +typedef enum { + SubGhzTxRxStateIDLE, + SubGhzTxRxStateRx, + SubGhzTxRxStateTx, + SubGhzTxRxStateSleep, +} SubGhzTxRxState; + +/** SubGhzHopperState state */ +typedef enum { + SubGhzHopperStateOFF, + SubGhzHopperStateRunning, + SubGhzHopperStatePause, + SubGhzHopperStateRSSITimeOut, +} SubGhzHopperState; + +/** SubGhzSpeakerState state */ +typedef enum { + SubGhzSpeakerStateDisable, + SubGhzSpeakerStateShutdown, + SubGhzSpeakerStateEnable, +} SubGhzSpeakerState; + +/** + * Allocate SubGhzTxRx + * + * @return SubGhzTxRx* pointer to SubGhzTxRx + */ +SubGhzTxRx* subghz_txrx_alloc(); + +/** + * Free SubGhzTxRx + * + * @param instance Pointer to a SubGhzTxRx + */ +void subghz_txrx_free(SubGhzTxRx* instance); + +/** + * Check if the database is loaded + * + * @param instance Pointer to a SubGhzTxRx + * @return bool True if the database is loaded + */ +bool subghz_txrx_is_database_loaded(SubGhzTxRx* instance); + +/** + * Set preset + * + * @param instance Pointer to a SubGhzTxRx + * @param preset_name Name of preset + * @param frequency Frequency in Hz + * @param preset_data Data of preset + * @param preset_data_size Size of preset data + */ +void subghz_txrx_set_preset( + SubGhzTxRx* instance, + const char* preset_name, + uint32_t frequency, + uint8_t* preset_data, + size_t preset_data_size); + +/** + * Get name of preset + * + * @param instance Pointer to a SubGhzTxRx + * @param preset String of preset + * @return const char* Name of preset + */ +const char* subghz_txrx_get_preset_name(SubGhzTxRx* instance, const char* preset); + +/** + * Get of preset + * + * @param instance Pointer to a SubGhzTxRx + * @return SubGhzRadioPreset Preset + */ +SubGhzRadioPreset subghz_txrx_get_preset(SubGhzTxRx* instance); + +/** + * Get string frequency and modulation + * + * @param instance Pointer to a SubGhzTxRx + * @param frequency Pointer to a string frequency + * @param modulation Pointer to a string modulation + */ +void subghz_txrx_get_frequency_and_modulation( + SubGhzTxRx* instance, + FuriString* frequency, + FuriString* modulation, + bool long_name); + +/** + * Start TX CC1101 + * + * @param instance Pointer to a SubGhzTxRx + * @param flipper_format Pointer to a FlipperFormat + * @return SubGhzTxRxStartTxState + */ +SubGhzTxRxStartTxState subghz_txrx_tx_start(SubGhzTxRx* instance, FlipperFormat* flipper_format); + +/** + * Start RX CC1101 + * + * @param instance Pointer to a SubGhzTxRx + */ +void subghz_txrx_rx_start(SubGhzTxRx* instance); + +/** + * Stop TX/RX CC1101 + * + * @param instance Pointer to a SubGhzTxRx + */ +void subghz_txrx_stop(SubGhzTxRx* instance); + +/** + * Set sleep mode CC1101 + * + * @param instance Pointer to a SubGhzTxRx + */ +void subghz_txrx_sleep(SubGhzTxRx* instance); + +/** + * Update frequency CC1101 in automatic mode (hopper) + * + * @param instance Pointer to a SubGhzTxRx + */ +void subghz_txrx_hopper_update(SubGhzTxRx* instance); + +/** + * Get state hopper + * + * @param instance Pointer to a SubGhzTxRx + * @return SubGhzHopperState + */ +SubGhzHopperState subghz_txrx_hopper_get_state(SubGhzTxRx* instance); + +/** + * Set state hopper + * + * @param instance Pointer to a SubGhzTxRx + * @param state State hopper + */ +void subghz_txrx_hopper_set_state(SubGhzTxRx* instance, SubGhzHopperState state); + +/** + * Unpause hopper + * + * @param instance Pointer to a SubGhzTxRx + */ +void subghz_txrx_hopper_unpause(SubGhzTxRx* instance); + +/** + * Set pause hopper + * + * @param instance Pointer to a SubGhzTxRx + */ +void subghz_txrx_hopper_pause(SubGhzTxRx* instance); + +/** + * Speaker on + * + * @param instance Pointer to a SubGhzTxRx + */ +void subghz_txrx_speaker_on(SubGhzTxRx* instance); + +/** + * Speaker off + * + * @param instance Pointer to a SubGhzTxRx + */ +void subghz_txrx_speaker_off(SubGhzTxRx* instance); + +/** + * Speaker mute + * + * @param instance Pointer to a SubGhzTxRx + */ +void subghz_txrx_speaker_mute(SubGhzTxRx* instance); + +/** + * Speaker unmute + * + * @param instance Pointer to a SubGhzTxRx + */ +void subghz_txrx_speaker_unmute(SubGhzTxRx* instance); + +/** + * Set state speaker + * + * @param instance Pointer to a SubGhzTxRx + * @param state State speaker + */ +void subghz_txrx_speaker_set_state(SubGhzTxRx* instance, SubGhzSpeakerState state); + +/** + * Get state speaker + * + * @param instance Pointer to a SubGhzTxRx + * @return SubGhzSpeakerState + */ +SubGhzSpeakerState subghz_txrx_speaker_get_state(SubGhzTxRx* instance); + +/** + * load decoder by name protocol + * + * @param instance Pointer to a SubGhzTxRx + * @param name_protocol Name protocol + * @return bool True if the decoder is loaded + */ +bool subghz_txrx_load_decoder_by_name_protocol(SubGhzTxRx* instance, const char* name_protocol); + +/** + * Get decoder + * + * @param instance Pointer to a SubGhzTxRx + * @return SubGhzProtocolDecoderBase* Pointer to a SubGhzProtocolDecoderBase + */ +SubGhzProtocolDecoderBase* subghz_txrx_get_decoder(SubGhzTxRx* instance); + +/** + * Set callback for save data + * + * @param instance Pointer to a SubGhzTxRx + * @param callback Callback for save data + * @param context Context for callback + */ +void subghz_txrx_set_need_save_callback( + SubGhzTxRx* instance, + SubGhzTxRxNeedSaveCallback callback, + void* context); + +/** + * Get pointer to a load data key + * + * @param instance Pointer to a SubGhzTxRx + * @return FlipperFormat* + */ +FlipperFormat* subghz_txrx_get_fff_data(SubGhzTxRx* instance); + +/** + * Get pointer to a SugGhzSetting + * + * @param instance Pointer to a SubGhzTxRx + * @return SubGhzSetting* + */ +SubGhzSetting* subghz_txrx_get_setting(SubGhzTxRx* instance); + +/** + * Is it possible to save this protocol + * + * @param instance Pointer to a SubGhzTxRx + * @return bool True if it is possible to save this protocol + */ +bool subghz_txrx_protocol_is_serializable(SubGhzTxRx* instance); + +/** + * Is it possible to send this protocol + * + * @param instance Pointer to a SubGhzTxRx + * @return bool True if it is possible to send this protocol + */ +bool subghz_txrx_protocol_is_transmittable(SubGhzTxRx* instance, bool check_type); + +/** + * Set filter, what types of decoder to use + * + * @param instance Pointer to a SubGhzTxRx + * @param filter Filter + */ +void subghz_txrx_receiver_set_filter(SubGhzTxRx* instance, SubGhzProtocolFlag filter); + +/** + * Set callback for receive data + * + * @param instance Pointer to a SubGhzTxRx + * @param callback Callback for receive data + * @param context Context for callback + */ +void subghz_txrx_set_rx_calback( + SubGhzTxRx* instance, + SubGhzReceiverCallback callback, + void* context); + +/** + * Set callback for Raw decoder, end of data transfer + * + * @param instance Pointer to a SubGhzTxRx + * @param callback Callback for Raw decoder, end of data transfer + * @param context Context for callback + */ +void subghz_txrx_set_raw_file_encoder_worker_callback_end( + SubGhzTxRx* instance, + SubGhzProtocolEncoderRAWCallbackEnd callback, + void* context); + +void subghz_txrx_set_debug_pin_state(SubGhzTxRx* instance, bool state); +bool subghz_txrx_get_debug_pin_state(SubGhzTxRx* instance); + +void subghz_txrx_reset_dynamic_and_custom_btns(SubGhzTxRx* instance); + +SubGhzReceiver* subghz_txrx_get_receiver(SubGhzTxRx* instance); // TODO use only in DecodeRaw diff --git a/applications/main/subghz_remote/helpers/txrx/subghz_txrx_i.h b/applications/main/subghz_remote/helpers/txrx/subghz_txrx_i.h new file mode 100644 index 000000000..680d27158 --- /dev/null +++ b/applications/main/subghz_remote/helpers/txrx/subghz_txrx_i.h @@ -0,0 +1,29 @@ + +#pragma once +#include "subghz_txrx.h" + +struct SubGhzTxRx { + SubGhzWorker* worker; + + SubGhzEnvironment* environment; + SubGhzReceiver* receiver; + SubGhzTransmitter* transmitter; + SubGhzProtocolDecoderBase* decoder_result; + FlipperFormat* fff_data; + + SubGhzRadioPreset* preset; + SubGhzSetting* setting; + + uint8_t hopper_timeout; + uint8_t hopper_idx_frequency; + bool is_database_loaded; + SubGhzHopperState hopper_state; + + SubGhzTxRxState txrx_state; + SubGhzSpeakerState speaker_state; + + SubGhzTxRxNeedSaveCallback need_save_callback; + void* need_save_context; + + bool debug_pin_state; +}; \ No newline at end of file diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index c18f3bf24..a20ac6fbb 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -188,6 +188,7 @@ Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_usart.h,, Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_utils.h,, Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_wwdg.h,, Header,+,lib/subghz/blocks/const.h,, +Header,+,lib/subghz/blocks/custom_btn.h,, Header,+,lib/subghz/blocks/decoder.h,, Header,+,lib/subghz/blocks/encoder.h,, Header,+,lib/subghz/blocks/generic.h,, @@ -196,6 +197,7 @@ Header,+,lib/subghz/environment.h,, Header,+,lib/subghz/protocols/raw.h,, Header,+,lib/subghz/receiver.h,, Header,+,lib/subghz/registry.h,, +Header,+,lib/subghz/subghz_file_encoder_worker.h,, Header,+,lib/subghz/subghz_protocol_registry.h,, Header,+,lib/subghz/subghz_setting.h,, Header,+,lib/subghz/subghz_tx_rx_worker.h,, @@ -323,6 +325,7 @@ Function,-,LL_mDelay,void,uint32_t Function,-,SystemCoreClockUpdate,void, Function,-,SystemInit,void, Function,-,_Exit,void,int +Function,+,__aeabi_uldivmod,void*,"uint64_t, uint64_t" Function,-,__assert,void,"const char*, int, const char*" Function,+,__assert_func,void,"const char*, int, const char*, const char*" Function,+,__clear_cache,void,"void*, void*" @@ -1443,9 +1446,9 @@ Function,+,furi_hal_subghz_set_async_mirror_pin,void,const GpioPin* Function,+,furi_hal_subghz_set_external_power_disable,void,_Bool Function,+,furi_hal_subghz_set_frequency,uint32_t,uint32_t Function,+,furi_hal_subghz_set_frequency_and_path,uint32_t,uint32_t -Function,-,furi_hal_subghz_set_rolling_counter_mult,void,uint8_t -Function,+,furi_hal_subghz_shutdown,void, Function,+,furi_hal_subghz_set_path,void,FuriHalSubGhzPath +Function,+,furi_hal_subghz_set_rolling_counter_mult,void,uint8_t +Function,+,furi_hal_subghz_shutdown,void, Function,+,furi_hal_subghz_sleep,void, Function,+,furi_hal_subghz_start_async_rx,void,"FuriHalSubGhzCaptureCallback, void*" Function,+,furi_hal_subghz_start_async_tx,_Bool,"FuriHalSubGhzAsyncTxCallback, void*" @@ -2757,6 +2760,11 @@ Function,+,subghz_block_generic_deserialize,SubGhzProtocolStatus,"SubGhzBlockGen Function,+,subghz_block_generic_deserialize_check_count_bit,SubGhzProtocolStatus,"SubGhzBlockGeneric*, FlipperFormat*, uint16_t" Function,+,subghz_block_generic_get_preset_name,void,"const char*, FuriString*" Function,+,subghz_block_generic_serialize,SubGhzProtocolStatus,"SubGhzBlockGeneric*, FlipperFormat*, SubGhzRadioPreset*" +Function,+,subghz_custom_btn_get,uint8_t, +Function,+,subghz_custom_btn_get_original,uint8_t, +Function,+,subghz_custom_btn_is_allowed,_Bool, +Function,+,subghz_custom_btn_set,_Bool,uint8_t +Function,+,subghz_custom_btns_reset,void, Function,+,subghz_environment_alloc,SubGhzEnvironment*, Function,+,subghz_environment_free,void,SubGhzEnvironment* Function,+,subghz_environment_get_alutech_at_4n_rainbow_table_file_name,const char*,SubGhzEnvironment* @@ -2771,6 +2779,14 @@ Function,+,subghz_environment_set_alutech_at_4n_rainbow_table_file_name,void,"Su Function,+,subghz_environment_set_came_atomo_rainbow_table_file_name,void,"SubGhzEnvironment*, const char*" Function,+,subghz_environment_set_nice_flor_s_rainbow_table_file_name,void,"SubGhzEnvironment*, const char*" Function,+,subghz_environment_set_protocol_registry,void,"SubGhzEnvironment*, const SubGhzProtocolRegistry*" +Function,+,subghz_file_encoder_worker_alloc,SubGhzFileEncoderWorker*, +Function,+,subghz_file_encoder_worker_callback_end,void,"SubGhzFileEncoderWorker*, SubGhzFileEncoderWorkerCallbackEnd, void*" +Function,+,subghz_file_encoder_worker_free,void,SubGhzFileEncoderWorker* +Function,+,subghz_file_encoder_worker_get_level_duration,LevelDuration,void* +Function,+,subghz_file_encoder_worker_get_text_progress,void,"SubGhzFileEncoderWorker*, FuriString*" +Function,+,subghz_file_encoder_worker_is_running,_Bool,SubGhzFileEncoderWorker* +Function,+,subghz_file_encoder_worker_start,_Bool,"SubGhzFileEncoderWorker*, const char*" +Function,+,subghz_file_encoder_worker_stop,void,SubGhzFileEncoderWorker* Function,-,subghz_keystore_alloc,SubGhzKeystore*, Function,-,subghz_keystore_free,void,SubGhzKeystore* Function,-,subghz_keystore_get_data,SubGhzKeyArray_t*,SubGhzKeystore* @@ -2779,6 +2795,7 @@ Function,-,subghz_keystore_raw_encrypted_save,_Bool,"const char*, const char*, u Function,-,subghz_keystore_raw_get_data,_Bool,"const char*, size_t, uint8_t*, size_t" Function,-,subghz_keystore_reset_kl,void,SubGhzKeystore* Function,-,subghz_keystore_save,_Bool,"SubGhzKeystore*, const char*, uint8_t*" +Function,+,subghz_protocol_alutech_at_4n_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint8_t, uint16_t, SubGhzRadioPreset*" Function,+,subghz_protocol_blocks_add_bit,void,"SubGhzBlockDecoder*, uint8_t" Function,+,subghz_protocol_blocks_add_bytes,uint8_t,"const uint8_t[], size_t" Function,+,subghz_protocol_blocks_add_to_128_bit,void,"SubGhzBlockDecoder*, uint8_t, uint64_t*" @@ -2800,6 +2817,7 @@ Function,+,subghz_protocol_blocks_parity_bytes,uint8_t,"const uint8_t[], size_t" Function,+,subghz_protocol_blocks_reverse_key,uint64_t,"uint64_t, uint8_t" Function,+,subghz_protocol_blocks_set_bit_array,void,"_Bool, uint8_t[], size_t, size_t" Function,+,subghz_protocol_blocks_xor_bytes,uint8_t,"const uint8_t[], size_t" +Function,+,subghz_protocol_came_atomo_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint16_t, SubGhzRadioPreset*" Function,+,subghz_protocol_decoder_base_deserialize,SubGhzProtocolStatus,"SubGhzProtocolDecoderBase*, FlipperFormat*" Function,+,subghz_protocol_decoder_base_get_hash_data,uint8_t,SubGhzProtocolDecoderBase* Function,+,subghz_protocol_decoder_base_get_string,_Bool,"SubGhzProtocolDecoderBase*, FuriString*" @@ -2817,7 +2835,10 @@ Function,+,subghz_protocol_encoder_raw_deserialize,SubGhzProtocolStatus,"void*, Function,+,subghz_protocol_encoder_raw_free,void,void* Function,+,subghz_protocol_encoder_raw_stop,void,void* Function,+,subghz_protocol_encoder_raw_yield,LevelDuration,void* +Function,+,subghz_protocol_faac_slh_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint8_t, uint32_t, uint32_t, const char*, SubGhzRadioPreset*" +Function,+,subghz_protocol_keeloq_bft_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint8_t, uint16_t, uint32_t, const char*, SubGhzRadioPreset*" Function,+,subghz_protocol_keeloq_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint8_t, uint16_t, const char*, SubGhzRadioPreset*" +Function,+,subghz_protocol_nice_flor_s_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint8_t, uint16_t, SubGhzRadioPreset*, _Bool" Function,+,subghz_protocol_raw_file_encoder_worker_set_callback_end,void,"SubGhzProtocolEncoderRAW*, SubGhzProtocolEncoderRAWCallbackEnd, void*" Function,+,subghz_protocol_raw_gen_fff_data,void,"FlipperFormat*, const char*" Function,+,subghz_protocol_raw_get_sample_write,size_t,SubGhzProtocolDecoderRAW* @@ -2829,6 +2850,7 @@ Function,+,subghz_protocol_registry_get_by_index,const SubGhzProtocol*,"const Su Function,+,subghz_protocol_registry_get_by_name,const SubGhzProtocol*,"const SubGhzProtocolRegistry*, const char*" Function,+,subghz_protocol_secplus_v1_check_fixed,_Bool,uint32_t Function,+,subghz_protocol_secplus_v2_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint8_t, uint32_t, SubGhzRadioPreset*" +Function,+,subghz_protocol_somfy_telis_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint8_t, uint16_t, SubGhzRadioPreset*" Function,+,subghz_receiver_alloc_init,SubGhzReceiver*,SubGhzEnvironment* Function,+,subghz_receiver_decode,void,"SubGhzReceiver*, _Bool, uint32_t" Function,+,subghz_receiver_free,void,SubGhzReceiver* @@ -2837,7 +2859,7 @@ Function,+,subghz_receiver_search_decoder_base_by_name,SubGhzProtocolDecoderBase Function,+,subghz_receiver_set_filter,void,"SubGhzReceiver*, SubGhzProtocolFlag" Function,+,subghz_receiver_set_rx_callback,void,"SubGhzReceiver*, SubGhzReceiverCallback, void*" Function,+,subghz_setting_alloc,SubGhzSetting*, -Function,-,subghz_setting_customs_presets_to_log,uint8_t,SubGhzSetting* +Function,+,subghz_setting_customs_presets_to_log,uint8_t,SubGhzSetting* Function,+,subghz_setting_delete_custom_preset,_Bool,"SubGhzSetting*, const char*" Function,+,subghz_setting_free,void,SubGhzSetting* Function,+,subghz_setting_get_default_frequency,uint32_t,SubGhzSetting* @@ -2890,7 +2912,7 @@ Function,+,submenu_set_header,void,"Submenu*, const char*" Function,+,submenu_set_selected_item,void,"Submenu*, uint32_t" Function,-,system,int,const char* Function,+,t5577_write,void,LFRFIDT5577* -Function,-,t5577_write_with_pass,void,"LFRFIDT5577*, uint32_t" +Function,+,t5577_write_with_pass,void,"LFRFIDT5577*, uint32_t" Function,-,tan,double,double Function,-,tanf,float,float Function,-,tanh,double,double diff --git a/firmware/targets/f7/platform_specific/intrinsic_export.h b/firmware/targets/f7/platform_specific/intrinsic_export.h index c0d76fb89..9180ae11d 100644 --- a/firmware/targets/f7/platform_specific/intrinsic_export.h +++ b/firmware/targets/f7/platform_specific/intrinsic_export.h @@ -6,6 +6,7 @@ extern "C" { #endif void __clear_cache(void*, void*); +void* __aeabi_uldivmod(uint64_t, uint64_t); #ifdef __cplusplus } diff --git a/lib/subghz/SConscript b/lib/subghz/SConscript index 3a0325b71..58afc76a8 100644 --- a/lib/subghz/SConscript +++ b/lib/subghz/SConscript @@ -10,6 +10,7 @@ env.Append( File("registry.h"), File("subghz_worker.h"), File("subghz_tx_rx_worker.h"), + File("subghz_file_encoder_worker.h"), File("transmitter.h"), File("protocols/raw.h"), File("blocks/const.h"), @@ -17,6 +18,7 @@ env.Append( File("blocks/encoder.h"), File("blocks/generic.h"), File("blocks/math.h"), + File("blocks/custom_btn.h"), File("subghz_setting.h"), File("subghz_protocol_registry.h"), ], diff --git a/lib/subghz/blocks/custom_btn.h b/lib/subghz/blocks/custom_btn.h index e06457ccd..45cd839d4 100644 --- a/lib/subghz/blocks/custom_btn.h +++ b/lib/subghz/blocks/custom_btn.h @@ -4,6 +4,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + // Default btn ID #define SUBGHZ_CUSTOM_BTN_OK (0U) #define SUBGHZ_CUSTOM_BTN_UP (1U) @@ -19,4 +23,8 @@ uint8_t subghz_custom_btn_get_original(); void subghz_custom_btns_reset(); -bool subghz_custom_btn_is_allowed(); \ No newline at end of file +bool subghz_custom_btn_is_allowed(); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/subghz/subghz_file_encoder_worker.h b/lib/subghz/subghz_file_encoder_worker.h index 19a46f1e6..f90875c98 100644 --- a/lib/subghz/subghz_file_encoder_worker.h +++ b/lib/subghz/subghz_file_encoder_worker.h @@ -2,6 +2,10 @@ #include +#ifdef __cplusplus +extern "C" { +#endif + typedef void (*SubGhzFileEncoderWorkerCallbackEnd)(void* context); typedef struct SubGhzFileEncoderWorker SubGhzFileEncoderWorker; @@ -63,3 +67,7 @@ void subghz_file_encoder_worker_stop(SubGhzFileEncoderWorker* instance); * @return bool - true if running */ bool subghz_file_encoder_worker_is_running(SubGhzFileEncoderWorker* instance); + +#ifdef __cplusplus +} +#endif diff --git a/lib/subghz/subghz_protocol_registry.h b/lib/subghz/subghz_protocol_registry.h index 8e80071b5..e5cf75cda 100644 --- a/lib/subghz/subghz_protocol_registry.h +++ b/lib/subghz/subghz_protocol_registry.h @@ -27,12 +27,64 @@ bool subghz_protocol_keeloq_create_data( const char* manufacture_name, SubGhzRadioPreset* preset); +bool subghz_protocol_keeloq_bft_create_data( + void* context, + FlipperFormat* flipper_format, + uint32_t serial, + uint8_t btn, + uint16_t cnt, + uint32_t seed, + const char* manufacture_name, + SubGhzRadioPreset* preset); + void subghz_protocol_decoder_bin_raw_data_input_rssi( SubGhzProtocolDecoderBinRAW* instance, float rssi); bool subghz_protocol_secplus_v1_check_fixed(uint32_t fixed); +bool subghz_protocol_faac_slh_create_data( + void* context, + FlipperFormat* flipper_format, + uint32_t serial, + uint8_t btn, + uint32_t cnt, + uint32_t seed, + const char* manufacture_name, + SubGhzRadioPreset* preset); + +bool subghz_protocol_came_atomo_create_data( + void* context, + FlipperFormat* flipper_format, + uint32_t serial, + uint16_t cnt, + SubGhzRadioPreset* preset); + +bool subghz_protocol_somfy_telis_create_data( + void* context, + FlipperFormat* flipper_format, + uint32_t serial, + uint8_t btn, + uint16_t cnt, + SubGhzRadioPreset* preset); + +bool subghz_protocol_nice_flor_s_create_data( + void* context, + FlipperFormat* flipper_format, + uint32_t serial, + uint8_t btn, + uint16_t cnt, + SubGhzRadioPreset* preset, + bool nice_one); + +bool subghz_protocol_alutech_at_4n_create_data( + void* context, + FlipperFormat* flipper_format, + uint32_t serial, + uint8_t btn, + uint16_t cnt, + SubGhzRadioPreset* preset); + #ifdef __cplusplus } #endif From 4a50466f8cc5c890439b184d80c3ad5505051b9d Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Mon, 10 Jul 2023 19:56:44 +0200 Subject: [PATCH 081/364] Revert "Free file handle after elf load" This reverts commit d325a1a43e5e43dc248f2aae5a21cf8a841a85a9. --- lib/flipper_application/elf/elf_file.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/lib/flipper_application/elf/elf_file.c b/lib/flipper_application/elf/elf_file.c index 9f37fc4e0..fc9dd06ba 100644 --- a/lib/flipper_application/elf/elf_file.c +++ b/lib/flipper_application/elf/elf_file.c @@ -764,9 +764,7 @@ void elf_file_free(ELFFile* elf) { free(elf->debug_link_info.debug_link); } - if(elf->fd != NULL) { - storage_file_free(elf->fd); - } + storage_file_free(elf->fd); free(elf); } @@ -897,9 +895,6 @@ ELFFileLoadStatus elf_file_load_sections(ELFFile* elf) { FURI_LOG_I(TAG, "Total size of loaded sections: %u", total_size); //-V576 } - storage_file_free(elf->fd); - elf->fd = NULL; - return status; } From 5897b8278b6f10fe2100088d43a1a3bf018f631e Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 10 Jul 2023 22:46:57 +0300 Subject: [PATCH 082/364] api --- firmware/targets/f7/api_symbols.csv | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 7924d1052..bfadb79a8 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -2731,6 +2731,11 @@ Function,+,subghz_block_generic_deserialize,SubGhzProtocolStatus,"SubGhzBlockGen Function,+,subghz_block_generic_deserialize_check_count_bit,SubGhzProtocolStatus,"SubGhzBlockGeneric*, FlipperFormat*, uint16_t" Function,+,subghz_block_generic_get_preset_name,void,"const char*, FuriString*" Function,+,subghz_block_generic_serialize,SubGhzProtocolStatus,"SubGhzBlockGeneric*, FlipperFormat*, SubGhzRadioPreset*" +Function,+,subghz_custom_btn_get,uint8_t, +Function,+,subghz_custom_btn_get_original,uint8_t, +Function,+,subghz_custom_btn_is_allowed,_Bool, +Function,+,subghz_custom_btn_set,_Bool,uint8_t +Function,+,subghz_custom_btns_reset,void, Function,+,subghz_devices_begin,_Bool,const SubGhzDevice* Function,+,subghz_devices_deinit,void, Function,+,subghz_devices_end,void,const SubGhzDevice* @@ -2761,11 +2766,6 @@ Function,+,subghz_devices_start_async_tx,_Bool,"const SubGhzDevice*, void*, void Function,+,subghz_devices_stop_async_rx,void,const SubGhzDevice* Function,+,subghz_devices_stop_async_tx,void,const SubGhzDevice* Function,+,subghz_devices_write_packet,void,"const SubGhzDevice*, const uint8_t*, uint8_t" -Function,+,subghz_custom_btn_get,uint8_t, -Function,+,subghz_custom_btn_get_original,uint8_t, -Function,+,subghz_custom_btn_is_allowed,_Bool, -Function,+,subghz_custom_btn_set,_Bool,uint8_t -Function,+,subghz_custom_btns_reset,void, Function,+,subghz_environment_alloc,SubGhzEnvironment*, Function,+,subghz_environment_free,void,SubGhzEnvironment* Function,+,subghz_environment_get_alutech_at_4n_rainbow_table_file_name,const char*,SubGhzEnvironment* @@ -2786,7 +2786,7 @@ Function,+,subghz_file_encoder_worker_free,void,SubGhzFileEncoderWorker* Function,+,subghz_file_encoder_worker_get_level_duration,LevelDuration,void* Function,+,subghz_file_encoder_worker_get_text_progress,void,"SubGhzFileEncoderWorker*, FuriString*" Function,+,subghz_file_encoder_worker_is_running,_Bool,SubGhzFileEncoderWorker* -Function,+,subghz_file_encoder_worker_start,_Bool,"SubGhzFileEncoderWorker*, const char*" +Function,+,subghz_file_encoder_worker_start,_Bool,"SubGhzFileEncoderWorker*, const char*, const char*" Function,+,subghz_file_encoder_worker_stop,void,SubGhzFileEncoderWorker* Function,-,subghz_keystore_alloc,SubGhzKeystore*, Function,-,subghz_keystore_free,void,SubGhzKeystore* From 24bf657bbaaf2a40fa7d4b0748eef385c28ec0d9 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Mon, 10 Jul 2023 22:11:25 +0200 Subject: [PATCH 083/364] BadKB fix using only modifier keys by themselves --- applications/main/bad_kb/helpers/ducky_script.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/applications/main/bad_kb/helpers/ducky_script.c b/applications/main/bad_kb/helpers/ducky_script.c index 8f45b5e1f..45eb35c01 100644 --- a/applications/main/bad_kb/helpers/ducky_script.c +++ b/applications/main/bad_kb/helpers/ducky_script.c @@ -294,8 +294,12 @@ static int32_t ducky_parse_line(BadKbScript* bad_kb, FuriString* line) { } if((key & 0xFF00) != 0) { // It's a modifier key - line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; - key |= ducky_get_keycode(bad_kb, line_tmp, true); + uint32_t offset = ducky_get_command_len(line_tmp) + 1; + // ducky_get_command_len() returns 0 without space, so check for != 1 + if(offset != 1 && line_len > offset) { + // It's also a key combination + key |= ducky_get_keycode(bad_kb, line_tmp + offset, true); + } } if(bad_kb->bt) { furi_hal_bt_hid_kb_press(key); From 342c7dc5b5b4676eab200a41f1fa3774cb31c304 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 10 Jul 2023 23:31:38 +0300 Subject: [PATCH 084/364] upd subbrute --- applications/external/subbrute | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/external/subbrute b/applications/external/subbrute index d02418362..63a8845ef 160000 --- a/applications/external/subbrute +++ b/applications/external/subbrute @@ -1 +1 @@ -Subproject commit d02418362472a6f4b2c3c1c80bbdc12e52b69396 +Subproject commit 63a8845efd43d716b733f31c0bd517a6cc234018 From 73737fb94a0015c3d78c80f7b897f1f0db31ec6d Mon Sep 17 00:00:00 2001 From: gid9798 <30450294+gid9798@users.noreply.github.com> Date: Mon, 10 Jul 2023 23:50:56 +0300 Subject: [PATCH 085/364] SubRem app: UPD txrx --- .../subghz_remote/helpers/txrx/subghz_txrx.c | 193 +++++++++++++----- .../subghz_remote/helpers/txrx/subghz_txrx.h | 55 +++++ .../helpers/txrx/subghz_txrx_i.h | 6 +- 3 files changed, 205 insertions(+), 49 deletions(-) diff --git a/applications/main/subghz_remote/helpers/txrx/subghz_txrx.c b/applications/main/subghz_remote/helpers/txrx/subghz_txrx.c index 3275b7288..db485a2aa 100644 --- a/applications/main/subghz_remote/helpers/txrx/subghz_txrx.c +++ b/applications/main/subghz_remote/helpers/txrx/subghz_txrx.c @@ -1,9 +1,28 @@ #include "subghz_txrx_i.h" + #include +#include +#include + #include #define TAG "SubGhz" +static void subghz_txrx_radio_device_power_on(SubGhzTxRx* instance) { + UNUSED(instance); + uint8_t attempts = 0; + while(!furi_hal_power_is_otg_enabled() && attempts++ < 5) { + furi_hal_power_enable_otg(); + //CC1101 power-up time + furi_delay_ms(10); + } +} + +static void subghz_txrx_radio_device_power_off(SubGhzTxRx* instance) { + UNUSED(instance); + if(furi_hal_power_is_otg_enabled()) furi_hal_power_disable_otg(); +} + SubGhzTxRx* subghz_txrx_alloc() { SubGhzTxRx* instance = malloc(sizeof(SubGhzTxRx)); instance->setting = subghz_setting_alloc(); @@ -24,16 +43,15 @@ SubGhzTxRx* subghz_txrx_alloc() { instance->fff_data = flipper_format_string_alloc(); instance->environment = subghz_environment_alloc(); - instance->is_database_loaded = subghz_environment_load_keystore( - instance->environment, EXT_PATH("subghz/assets/keeloq_mfcodes")); - subghz_environment_load_keystore( - instance->environment, EXT_PATH("subghz/assets/keeloq_mfcodes_user")); + instance->is_database_loaded = + subghz_environment_load_keystore(instance->environment, SUBGHZ_KEYSTORE_DIR_NAME); + subghz_environment_load_keystore(instance->environment, SUBGHZ_KEYSTORE_DIR_USER_NAME); subghz_environment_set_came_atomo_rainbow_table_file_name( - instance->environment, EXT_PATH("subghz/assets/came_atomo")); + instance->environment, SUBGHZ_CAME_ATOMO_DIR_NAME); subghz_environment_set_alutech_at_4n_rainbow_table_file_name( - instance->environment, EXT_PATH("subghz/assets/alutech_at_4n")); + instance->environment, SUBGHZ_ALUTECH_AT_4N_DIR_NAME); subghz_environment_set_nice_flor_s_rainbow_table_file_name( - instance->environment, EXT_PATH("subghz/assets/nice_flor_s")); + instance->environment, SUBGHZ_NICE_FLOR_S_DIR_NAME); subghz_environment_set_protocol_registry( instance->environment, (void*)&subghz_protocol_registry); instance->receiver = subghz_receiver_alloc_init(instance->environment); @@ -44,18 +62,32 @@ SubGhzTxRx* subghz_txrx_alloc() { instance->worker, (SubGhzWorkerPairCallback)subghz_receiver_decode); subghz_worker_set_context(instance->worker, instance->receiver); + //set default device Internal + subghz_devices_init(); + instance->radio_device_type = SubGhzRadioDeviceTypeInternal; + instance->radio_device_type = + subghz_txrx_radio_device_set(instance, SubGhzRadioDeviceTypeInternal); + return instance; } void subghz_txrx_free(SubGhzTxRx* instance) { furi_assert(instance); + if(instance->radio_device_type != SubGhzRadioDeviceTypeInternal) { + subghz_txrx_radio_device_power_off(instance); + subghz_devices_end(instance->radio_device); + } + + subghz_devices_deinit(); + subghz_worker_free(instance->worker); subghz_receiver_free(instance->receiver); subghz_environment_free(instance->environment); flipper_format_free(instance->fff_data); furi_string_free(instance->preset->name); subghz_setting_free(instance->setting); + free(instance->preset); free(instance); } @@ -128,29 +160,25 @@ void subghz_txrx_get_frequency_and_modulation( static void subghz_txrx_begin(SubGhzTxRx* instance, uint8_t* preset_data) { furi_assert(instance); - furi_hal_subghz_reset(); - furi_hal_subghz_idle(); - furi_hal_subghz_load_custom_preset(preset_data); - furi_hal_gpio_init(furi_hal_subghz.cc1101_g0_pin, GpioModeInput, GpioPullNo, GpioSpeedLow); + subghz_devices_reset(instance->radio_device); + subghz_devices_idle(instance->radio_device); + subghz_devices_load_preset(instance->radio_device, FuriHalSubGhzPresetCustom, preset_data); instance->txrx_state = SubGhzTxRxStateIDLE; } static uint32_t subghz_txrx_rx(SubGhzTxRx* instance, uint32_t frequency) { furi_assert(instance); - if(!furi_hal_subghz_is_frequency_valid(frequency)) { - furi_crash("SubGhz: Incorrect RX frequency."); - } furi_assert( instance->txrx_state != SubGhzTxRxStateRx && instance->txrx_state != SubGhzTxRxStateSleep); - furi_hal_subghz_idle(); - uint32_t value = furi_hal_subghz_set_frequency_and_path(frequency); - furi_hal_gpio_init(furi_hal_subghz.cc1101_g0_pin, GpioModeInput, GpioPullNo, GpioSpeedLow); - furi_hal_subghz_flush_rx(); - subghz_txrx_speaker_on(instance); - furi_hal_subghz_rx(); + subghz_devices_idle(instance->radio_device); - furi_hal_subghz_start_async_rx(subghz_worker_rx_callback, instance->worker); + uint32_t value = subghz_devices_set_frequency(instance->radio_device, frequency); + subghz_devices_flush_rx(instance->radio_device); + subghz_txrx_speaker_on(instance); + + subghz_devices_start_async_rx( + instance->radio_device, subghz_worker_rx_callback, instance->worker); subghz_worker_start(instance->worker); instance->txrx_state = SubGhzTxRxStateRx; return value; @@ -159,7 +187,7 @@ static uint32_t subghz_txrx_rx(SubGhzTxRx* instance, uint32_t frequency) { static void subghz_txrx_idle(SubGhzTxRx* instance) { furi_assert(instance); furi_assert(instance->txrx_state != SubGhzTxRxStateSleep); - furi_hal_subghz_idle(); + subghz_devices_idle(instance->radio_device); subghz_txrx_speaker_off(instance); instance->txrx_state = SubGhzTxRxStateIDLE; } @@ -170,31 +198,27 @@ static void subghz_txrx_rx_end(SubGhzTxRx* instance) { if(subghz_worker_is_running(instance->worker)) { subghz_worker_stop(instance->worker); - furi_hal_subghz_stop_async_rx(); + subghz_devices_stop_async_rx(instance->radio_device); } - furi_hal_subghz_idle(); + subghz_devices_idle(instance->radio_device); subghz_txrx_speaker_off(instance); instance->txrx_state = SubGhzTxRxStateIDLE; } void subghz_txrx_sleep(SubGhzTxRx* instance) { furi_assert(instance); - furi_hal_subghz_sleep(); + subghz_devices_sleep(instance->radio_device); instance->txrx_state = SubGhzTxRxStateSleep; } static bool subghz_txrx_tx(SubGhzTxRx* instance, uint32_t frequency) { furi_assert(instance); - if(!furi_hal_subghz_is_frequency_valid(frequency)) { - furi_crash("SubGhz: Incorrect TX frequency."); - } furi_assert(instance->txrx_state != SubGhzTxRxStateSleep); - furi_hal_subghz_idle(); - furi_hal_subghz_set_frequency_and_path(frequency); - furi_hal_gpio_write(furi_hal_subghz.cc1101_g0_pin, false); - furi_hal_gpio_init( - furi_hal_subghz.cc1101_g0_pin, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); - bool ret = furi_hal_subghz_tx(); + + subghz_devices_idle(instance->radio_device); + subghz_devices_set_frequency(instance->radio_device, frequency); + + bool ret = subghz_devices_set_tx(instance->radio_device); if(ret) { subghz_txrx_speaker_on(instance); instance->txrx_state = SubGhzTxRxStateTx; @@ -256,8 +280,8 @@ SubGhzTxRxStartTxState subghz_txrx_tx_start(SubGhzTxRx* instance, FlipperFormat* if(ret == SubGhzTxRxStartTxStateOk) { //Start TX - furi_hal_subghz_start_async_tx( - subghz_transmitter_yield, instance->transmitter); + subghz_devices_start_async_tx( + instance->radio_device, subghz_transmitter_yield, instance->transmitter); } } else { ret = SubGhzTxRxStartTxStateErrorParserOthers; @@ -300,7 +324,7 @@ static void subghz_txrx_tx_stop(SubGhzTxRx* instance) { furi_assert(instance); furi_assert(instance->txrx_state == SubGhzTxRxStateTx); //Stop TX - furi_hal_subghz_stop_async_tx(); + subghz_devices_stop_async_tx(instance->radio_device); subghz_transmitter_stop(instance->transmitter); subghz_transmitter_free(instance->transmitter); @@ -313,7 +337,6 @@ static void subghz_txrx_tx_stop(SubGhzTxRx* instance) { subghz_txrx_idle(instance); subghz_txrx_speaker_off(instance); //Todo: Show message - // notification_message(notifications, &sequence_reset_red); } FlipperFormat* subghz_txrx_get_fff_data(SubGhzTxRx* instance) { @@ -363,7 +386,7 @@ void subghz_txrx_hopper_update(SubGhzTxRx* instance) { float rssi = -127.0f; if(instance->hopper_state != SubGhzHopperStateRSSITimeOut) { // See RSSI Calculation timings in CC1101 17.3 RSSI - rssi = furi_hal_subghz_get_rssi(); + rssi = subghz_devices_get_rssi(instance->radio_device); // Stay if RSSI is high enough if(rssi > -90.0f) { @@ -420,13 +443,13 @@ void subghz_txrx_hopper_pause(SubGhzTxRx* instance) { void subghz_txrx_speaker_on(SubGhzTxRx* instance) { furi_assert(instance); if(instance->debug_pin_state) { - furi_hal_subghz_set_async_mirror_pin(&gpio_ibutton); + subghz_devices_set_async_mirror_pin(instance->radio_device, &gpio_ibutton); } if(instance->speaker_state == SubGhzSpeakerStateEnable) { if(furi_hal_speaker_acquire(30)) { if(!instance->debug_pin_state) { - furi_hal_subghz_set_async_mirror_pin(&gpio_speaker); + subghz_devices_set_async_mirror_pin(instance->radio_device, &gpio_speaker); } } else { instance->speaker_state = SubGhzSpeakerStateDisable; @@ -437,12 +460,12 @@ void subghz_txrx_speaker_on(SubGhzTxRx* instance) { void subghz_txrx_speaker_off(SubGhzTxRx* instance) { furi_assert(instance); if(instance->debug_pin_state) { - furi_hal_subghz_set_async_mirror_pin(NULL); + subghz_devices_set_async_mirror_pin(instance->radio_device, NULL); } if(instance->speaker_state != SubGhzSpeakerStateDisable) { if(furi_hal_speaker_is_mine()) { if(!instance->debug_pin_state) { - furi_hal_subghz_set_async_mirror_pin(NULL); + subghz_devices_set_async_mirror_pin(instance->radio_device, NULL); } furi_hal_speaker_release(); if(instance->speaker_state == SubGhzSpeakerStateShutdown) @@ -454,12 +477,12 @@ void subghz_txrx_speaker_off(SubGhzTxRx* instance) { void subghz_txrx_speaker_mute(SubGhzTxRx* instance) { furi_assert(instance); if(instance->debug_pin_state) { - furi_hal_subghz_set_async_mirror_pin(NULL); + subghz_devices_set_async_mirror_pin(instance->radio_device, NULL); } if(instance->speaker_state == SubGhzSpeakerStateEnable) { if(furi_hal_speaker_is_mine()) { if(!instance->debug_pin_state) { - furi_hal_subghz_set_async_mirror_pin(NULL); + subghz_devices_set_async_mirror_pin(instance->radio_device, NULL); } } } @@ -468,12 +491,12 @@ void subghz_txrx_speaker_mute(SubGhzTxRx* instance) { void subghz_txrx_speaker_unmute(SubGhzTxRx* instance) { furi_assert(instance); if(instance->debug_pin_state) { - furi_hal_subghz_set_async_mirror_pin(&gpio_ibutton); + subghz_devices_set_async_mirror_pin(instance->radio_device, &gpio_ibutton); } if(instance->speaker_state == SubGhzSpeakerStateEnable) { if(furi_hal_speaker_is_mine()) { if(!instance->debug_pin_state) { - furi_hal_subghz_set_async_mirror_pin(&gpio_speaker); + subghz_devices_set_async_mirror_pin(instance->radio_device, &gpio_speaker); } } } @@ -548,6 +571,82 @@ void subghz_txrx_set_raw_file_encoder_worker_callback_end( context); } +bool subghz_txrx_radio_device_is_external_connected(SubGhzTxRx* instance, const char* name) { + furi_assert(instance); + + bool is_connect = false; + bool is_otg_enabled = furi_hal_power_is_otg_enabled(); + + if(!is_otg_enabled) { + subghz_txrx_radio_device_power_on(instance); + } + + const SubGhzDevice* device = subghz_devices_get_by_name(name); + if(device) { + is_connect = subghz_devices_is_connect(device); + } + + if(!is_otg_enabled) { + subghz_txrx_radio_device_power_off(instance); + } + return is_connect; +} + +SubGhzRadioDeviceType + subghz_txrx_radio_device_set(SubGhzTxRx* instance, SubGhzRadioDeviceType radio_device_type) { + furi_assert(instance); + + if(radio_device_type == SubGhzRadioDeviceTypeExternalCC1101 && + subghz_txrx_radio_device_is_external_connected(instance, SUBGHZ_DEVICE_CC1101_EXT_NAME)) { + subghz_txrx_radio_device_power_on(instance); + instance->radio_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_EXT_NAME); + subghz_devices_begin(instance->radio_device); + instance->radio_device_type = SubGhzRadioDeviceTypeExternalCC1101; + } else { + subghz_txrx_radio_device_power_off(instance); + if(instance->radio_device_type != SubGhzRadioDeviceTypeInternal) { + subghz_devices_end(instance->radio_device); + } + instance->radio_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME); + instance->radio_device_type = SubGhzRadioDeviceTypeInternal; + } + + return instance->radio_device_type; +} + +SubGhzRadioDeviceType subghz_txrx_radio_device_get(SubGhzTxRx* instance) { + furi_assert(instance); + return instance->radio_device_type; +} + +float subghz_txrx_radio_device_get_rssi(SubGhzTxRx* instance) { + furi_assert(instance); + return subghz_devices_get_rssi(instance->radio_device); +} + +const char* subghz_txrx_radio_device_get_name(SubGhzTxRx* instance) { + furi_assert(instance); + return subghz_devices_get_name(instance->radio_device); +} + +bool subghz_txrx_radio_device_is_frequecy_valid(SubGhzTxRx* instance, uint32_t frequency) { + furi_assert(instance); + return subghz_devices_is_frequency_valid(instance->radio_device, frequency); +} + +bool subghz_txrx_radio_device_is_tx_alowed(SubGhzTxRx* instance, uint32_t frequency) { + furi_assert(instance); + furi_assert(instance->txrx_state != SubGhzTxRxStateSleep); + + subghz_devices_idle(instance->radio_device); + subghz_devices_set_frequency(instance->radio_device, frequency); + + bool ret = subghz_devices_set_tx(instance->radio_device); + subghz_devices_idle(instance->radio_device); + + return ret; +} + void subghz_txrx_set_debug_pin_state(SubGhzTxRx* instance, bool state) { furi_assert(instance); instance->debug_pin_state = state; diff --git a/applications/main/subghz_remote/helpers/txrx/subghz_txrx.h b/applications/main/subghz_remote/helpers/txrx/subghz_txrx.h index 6e7641734..8bb7f2aee 100644 --- a/applications/main/subghz_remote/helpers/txrx/subghz_txrx.h +++ b/applications/main/subghz_remote/helpers/txrx/subghz_txrx.h @@ -5,6 +5,7 @@ #include #include #include +#include typedef struct SubGhzTxRx SubGhzTxRx; @@ -40,6 +41,13 @@ typedef enum { SubGhzSpeakerStateEnable, } SubGhzSpeakerState; +/** SubGhzRadioDeviceType */ +typedef enum { + SubGhzRadioDeviceTypeAuto, + SubGhzRadioDeviceTypeInternal, + SubGhzRadioDeviceTypeExternalCC1101, +} SubGhzRadioDeviceType; + /** * Allocate SubGhzTxRx * @@ -312,6 +320,53 @@ void subghz_txrx_set_raw_file_encoder_worker_callback_end( SubGhzProtocolEncoderRAWCallbackEnd callback, void* context); +/* Checking if an external radio device is connected +* +* @param instance Pointer to a SubGhzTxRx +* @param name Name of external radio device +* @return bool True if is connected to the external radio device +*/ +bool subghz_txrx_radio_device_is_external_connected(SubGhzTxRx* instance, const char* name); + +/* Set the selected radio device to use +* +* @param instance Pointer to a SubGhzTxRx +* @param radio_device_type Radio device type +* @return SubGhzRadioDeviceType Type of installed radio device +*/ +SubGhzRadioDeviceType + subghz_txrx_radio_device_set(SubGhzTxRx* instance, SubGhzRadioDeviceType radio_device_type); + +/* Get the selected radio device to use +* +* @param instance Pointer to a SubGhzTxRx +* @return SubGhzRadioDeviceType Type of installed radio device +*/ +SubGhzRadioDeviceType subghz_txrx_radio_device_get(SubGhzTxRx* instance); + +/* Get RSSI the selected radio device to use +* +* @param instance Pointer to a SubGhzTxRx +* @return float RSSI +*/ +float subghz_txrx_radio_device_get_rssi(SubGhzTxRx* instance); + +/* Get name the selected radio device to use +* +* @param instance Pointer to a SubGhzTxRx +* @return const char* Name of installed radio device +*/ +const char* subghz_txrx_radio_device_get_name(SubGhzTxRx* instance); + +/* Get get intelligence whether frequency the selected radio device to use +* +* @param instance Pointer to a SubGhzTxRx +* @return bool True if the frequency is valid +*/ +bool subghz_txrx_radio_device_is_frequecy_valid(SubGhzTxRx* instance, uint32_t frequency); + +bool subghz_txrx_radio_device_is_tx_alowed(SubGhzTxRx* instance, uint32_t frequency); + void subghz_txrx_set_debug_pin_state(SubGhzTxRx* instance, bool state); bool subghz_txrx_get_debug_pin_state(SubGhzTxRx* instance); diff --git a/applications/main/subghz_remote/helpers/txrx/subghz_txrx_i.h b/applications/main/subghz_remote/helpers/txrx/subghz_txrx_i.h index 680d27158..f058c2282 100644 --- a/applications/main/subghz_remote/helpers/txrx/subghz_txrx_i.h +++ b/applications/main/subghz_remote/helpers/txrx/subghz_txrx_i.h @@ -1,5 +1,5 @@ - #pragma once + #include "subghz_txrx.h" struct SubGhzTxRx { @@ -21,9 +21,11 @@ struct SubGhzTxRx { SubGhzTxRxState txrx_state; SubGhzSpeakerState speaker_state; + const SubGhzDevice* radio_device; + SubGhzRadioDeviceType radio_device_type; SubGhzTxRxNeedSaveCallback need_save_callback; void* need_save_context; bool debug_pin_state; -}; \ No newline at end of file +}; From 5a6c168b689f916011eff145359e671dd8a457a5 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 11 Jul 2023 00:09:10 +0300 Subject: [PATCH 086/364] ext module fix --- applications/main/subghz_remote/helpers/txrx/subghz_txrx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/main/subghz_remote/helpers/txrx/subghz_txrx.c b/applications/main/subghz_remote/helpers/txrx/subghz_txrx.c index db485a2aa..8def9cf01 100644 --- a/applications/main/subghz_remote/helpers/txrx/subghz_txrx.c +++ b/applications/main/subghz_remote/helpers/txrx/subghz_txrx.c @@ -66,7 +66,7 @@ SubGhzTxRx* subghz_txrx_alloc() { subghz_devices_init(); instance->radio_device_type = SubGhzRadioDeviceTypeInternal; instance->radio_device_type = - subghz_txrx_radio_device_set(instance, SubGhzRadioDeviceTypeInternal); + subghz_txrx_radio_device_set(instance, SubGhzRadioDeviceTypeExternalCC1101); return instance; } From 695e3af3fb492ec4cbd3177f600cae7bce879f01 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Tue, 11 Jul 2023 02:12:29 +0200 Subject: [PATCH 087/364] Use offset for nfc maker buffer --- .../nfc_maker/scenes/nfc_maker_scene_result.c | 335 +++++++++--------- 1 file changed, 166 insertions(+), 169 deletions(-) diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_result.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_result.c index 912bf3c9f..5da9d2eba 100644 --- a/applications/external/nfc_maker/scenes/nfc_maker_scene_result.c +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_result.c @@ -29,8 +29,10 @@ void nfc_maker_scene_result_on_enter(void* context) { if(!flipper_format_write_string_cstr(file, "Device type", "NTAG203")) break; // Serial number - buf[0] = 0x04; - furi_hal_random_fill_buf(&buf[1], 8); + size_t i = 0; + buf[i++] = 0x04; + furi_hal_random_fill_buf(&buf[i], 8); + i += 8; uint8_t uid[7]; memcpy(&uid[0], &buf[0], 3); memcpy(&uid[3], &buf[4], 4); @@ -56,233 +58,229 @@ void nfc_maker_scene_result_on_enter(void* context) { if(!flipper_format_write_uint32(file, "Pages total", &pages, 1)) break; // Static data - buf[9] = 0x48; // Internal - buf[10] = 0x00; // Lock bytes - buf[11] = 0x00; // ... - buf[12] = 0xE1; // Capability container - buf[13] = 0x10; // ... - buf[14] = 0x12; // ... - buf[15] = 0x00; // ... - buf[16] = 0x01; // ... - buf[17] = 0x03; // ... - buf[18] = 0xA0; // ... - buf[19] = 0x10; // ... - buf[20] = 0x44; // ... - buf[21] = 0x03; // Message flags + buf[i++] = 0x48; // Internal + buf[i++] = 0x00; // Lock bytes + buf[i++] = 0x00; // ... + buf[i++] = 0xE1; // Capability container + buf[i++] = 0x10; // ... + buf[i++] = 0x12; // ... + buf[i++] = 0x00; // ... + buf[i++] = 0x01; // ... + buf[i++] = 0x03; // ... + buf[i++] = 0xA0; // ... + buf[i++] = 0x10; // ... + buf[i++] = 0x44; // ... + buf[i++] = 0x03; // Message flags + size_t start = i++; - size_t msg_len = 0; switch(scene_manager_get_scene_state(app->scene_manager, NfcMakerSceneMenu)) { case NfcMakerSceneBluetooth: { - msg_len = 0x2B; + buf[i++] = 0xD2; + buf[i++] = 0x20; + buf[i++] = 0x08; + buf[i++] = 0x61; + buf[i++] = 0x70; - buf[23] = 0xD2; - buf[24] = 0x20; - buf[25] = 0x08; - buf[26] = 0x61; - buf[27] = 0x70; + buf[i++] = 0x70; + buf[i++] = 0x6C; + buf[i++] = 0x69; + buf[i++] = 0x63; - buf[28] = 0x70; - buf[29] = 0x6C; - buf[30] = 0x69; - buf[31] = 0x63; + buf[i++] = 0x61; + buf[i++] = 0x74; + buf[i++] = 0x69; + buf[i++] = 0x6F; - buf[32] = 0x61; - buf[33] = 0x74; - buf[34] = 0x69; - buf[35] = 0x6F; + buf[i++] = 0x6E; + buf[i++] = 0x2F; + buf[i++] = 0x76; + buf[i++] = 0x6E; - buf[36] = 0x6E; - buf[37] = 0x2F; - buf[38] = 0x76; - buf[39] = 0x6E; + buf[i++] = 0x64; + buf[i++] = 0x2E; + buf[i++] = 0x62; + buf[i++] = 0x6C; - buf[40] = 0x64; - buf[41] = 0x2E; - buf[42] = 0x62; - buf[43] = 0x6C; + buf[i++] = 0x75; + buf[i++] = 0x65; + buf[i++] = 0x74; + buf[i++] = 0x6F; - buf[44] = 0x75; - buf[45] = 0x65; - buf[46] = 0x74; - buf[47] = 0x6F; + buf[i++] = 0x6F; + buf[i++] = 0x74; + buf[i++] = 0x68; + buf[i++] = 0x2E; - buf[48] = 0x6F; - buf[49] = 0x74; - buf[50] = 0x68; - buf[51] = 0x2E; + buf[i++] = 0x65; + buf[i++] = 0x70; + buf[i++] = 0x2E; + buf[i++] = 0x6F; - buf[52] = 0x65; - buf[53] = 0x70; - buf[54] = 0x2E; - buf[55] = 0x6F; + buf[i++] = 0x6F; + buf[i++] = 0x62; + buf[i++] = 0x08; + buf[i++] = 0x00; - buf[56] = 0x6F; - buf[57] = 0x62; - buf[58] = 0x08; - buf[59] = 0x00; - - memcpy(&buf[60], app->mac_buf, GAP_MAC_ADDR_SIZE); + memcpy(&buf[i], app->mac_buf, GAP_MAC_ADDR_SIZE); + i += GAP_MAC_ADDR_SIZE; break; } case NfcMakerSceneHttps: { uint8_t data_len = strnlen(app->text_buf, TEXT_INPUT_LEN); - msg_len = data_len + 5; - buf[23] = 0xD1; - buf[24] = 0x01; - buf[25] = data_len + 1; - buf[26] = 0x55; + buf[i++] = 0xD1; + buf[i++] = 0x01; + buf[i++] = data_len + 1; + buf[i++] = 0x55; - buf[27] = 0x04; // Prepend "https://" - memcpy(&buf[28], app->text_buf, data_len); + buf[i++] = 0x04; // Prepend "https://" + memcpy(&buf[i], app->text_buf, data_len); + i += data_len; break; } case NfcMakerSceneMail: { uint8_t data_len = strnlen(app->text_buf, TEXT_INPUT_LEN); - msg_len = data_len + 5; - buf[23] = 0xD1; - buf[24] = 0x01; - buf[25] = data_len + 1; - buf[26] = 0x55; + buf[i++] = 0xD1; + buf[i++] = 0x01; + buf[i++] = data_len + 1; + buf[i++] = 0x55; - buf[27] = 0x06; // Prepend "mailto:" - memcpy(&buf[28], app->text_buf, data_len); + buf[i++] = 0x06; // Prepend "mailto:" + memcpy(&buf[i], app->text_buf, data_len); + i += data_len; break; } case NfcMakerScenePhone: { uint8_t data_len = strnlen(app->text_buf, TEXT_INPUT_LEN); - msg_len = data_len + 5; - buf[23] = 0xD1; - buf[24] = 0x01; - buf[25] = data_len + 1; - buf[26] = 0x55; + buf[i++] = 0xD1; + buf[i++] = 0x01; + buf[i++] = data_len + 1; + buf[i++] = 0x55; - buf[27] = 0x05; // Prepend "tel:" - memcpy(&buf[28], app->text_buf, data_len); + buf[i++] = 0x05; // Prepend "tel:" + memcpy(&buf[i], app->text_buf, data_len); + i += data_len; break; } case NfcMakerSceneText: { uint8_t data_len = strnlen(app->text_buf, TEXT_INPUT_LEN); - msg_len = data_len + 7; - buf[23] = 0xD1; - buf[24] = 0x01; - buf[25] = data_len + 3; - buf[26] = 0x54; + buf[i++] = 0xD1; + buf[i++] = 0x01; + buf[i++] = data_len + 3; + buf[i++] = 0x54; - buf[27] = 0x02; - buf[28] = 0x65; // e - buf[29] = 0x6E; // n - memcpy(&buf[30], app->text_buf, data_len); + buf[i++] = 0x02; + buf[i++] = 0x65; // e + buf[i++] = 0x6E; // n + memcpy(&buf[i], app->text_buf, data_len); + i += data_len; break; } case NfcMakerSceneUrl: { uint8_t data_len = strnlen(app->text_buf, TEXT_INPUT_LEN); - msg_len = data_len + 5; - buf[23] = 0xD1; - buf[24] = 0x01; - buf[25] = data_len + 1; - buf[26] = 0x55; + buf[i++] = 0xD1; + buf[i++] = 0x01; + buf[i++] = data_len + 1; + buf[i++] = 0x55; - buf[27] = 0x00; // No prepend - memcpy(&buf[28], app->text_buf, data_len); + buf[i++] = 0x00; // No prepend + memcpy(&buf[i], app->text_buf, data_len); + i += data_len; break; } case NfcMakerSceneWifi: { uint8_t ssid_len = strnlen(app->text_buf, WIFI_INPUT_LEN); uint8_t pass_len = strnlen(app->pass_buf, WIFI_INPUT_LEN); uint8_t data_len = ssid_len + pass_len; - msg_len = data_len + 73; - buf[23] = 0xD2; - buf[24] = 0x17; - buf[25] = data_len + 47; - buf[26] = 0x61; - buf[27] = 0x70; + buf[i++] = 0xD2; + buf[i++] = 0x17; + buf[i++] = data_len + 47; + buf[i++] = 0x61; + buf[i++] = 0x70; - buf[28] = 0x70; - buf[29] = 0x6C; - buf[30] = 0x69; - buf[31] = 0x63; + buf[i++] = 0x70; + buf[i++] = 0x6C; + buf[i++] = 0x69; + buf[i++] = 0x63; - buf[32] = 0x61; - buf[33] = 0x74; - buf[34] = 0x69; - buf[35] = 0x6F; + buf[i++] = 0x61; + buf[i++] = 0x74; + buf[i++] = 0x69; + buf[i++] = 0x6F; - buf[36] = 0x6E; - buf[37] = 0x2F; - buf[38] = 0x76; - buf[39] = 0x6E; + buf[i++] = 0x6E; + buf[i++] = 0x2F; + buf[i++] = 0x76; + buf[i++] = 0x6E; - buf[40] = 0x64; - buf[41] = 0x2E; - buf[42] = 0x77; - buf[43] = 0x66; + buf[i++] = 0x64; + buf[i++] = 0x2E; + buf[i++] = 0x77; + buf[i++] = 0x66; - buf[44] = 0x61; - buf[45] = 0x2E; - buf[46] = 0x77; - buf[47] = 0x73; + buf[i++] = 0x61; + buf[i++] = 0x2E; + buf[i++] = 0x77; + buf[i++] = 0x73; - buf[48] = 0x63; - buf[49] = 0x10; - buf[50] = 0x0E; - buf[51] = 0x00; + buf[i++] = 0x63; + buf[i++] = 0x10; + buf[i++] = 0x0E; + buf[i++] = 0x00; - buf[52] = data_len + 43; - buf[53] = 0x10; - buf[54] = 0x26; - buf[55] = 0x00; + buf[i++] = data_len + 43; + buf[i++] = 0x10; + buf[i++] = 0x26; + buf[i++] = 0x00; - buf[56] = 0x01; - buf[57] = 0x01; - buf[58] = 0x10; - buf[59] = 0x45; + buf[i++] = 0x01; + buf[i++] = 0x01; + buf[i++] = 0x10; + buf[i++] = 0x45; - buf[60] = 0x00; - buf[61] = ssid_len; - memcpy(&buf[62], app->text_buf, ssid_len); - size_t ssid = 62 + ssid_len; - buf[ssid + 0] = 0x10; - buf[ssid + 1] = 0x03; + buf[i++] = 0x00; + buf[i++] = ssid_len; + memcpy(&buf[i], app->text_buf, ssid_len); + i += ssid_len; + buf[i++] = 0x10; + buf[i++] = 0x03; - buf[ssid + 2] = 0x00; - buf[ssid + 3] = 0x02; - buf[ssid + 4] = 0x00; - buf[ssid + 5] = - scene_manager_get_scene_state(app->scene_manager, NfcMakerSceneWifiAuth); + buf[i++] = 0x00; + buf[i++] = 0x02; + buf[i++] = 0x00; + buf[i++] = scene_manager_get_scene_state(app->scene_manager, NfcMakerSceneWifiAuth); - buf[ssid + 6] = 0x10; - buf[ssid + 7] = 0x0F; - buf[ssid + 8] = 0x00; - buf[ssid + 9] = 0x02; + buf[i++] = 0x10; + buf[i++] = 0x0F; + buf[i++] = 0x00; + buf[i++] = 0x02; - buf[ssid + 10] = 0x00; - buf[ssid + 11] = - scene_manager_get_scene_state(app->scene_manager, NfcMakerSceneWifiEncr); - buf[ssid + 12] = 0x10; - buf[ssid + 13] = 0x27; + buf[i++] = 0x00; + buf[i++] = scene_manager_get_scene_state(app->scene_manager, NfcMakerSceneWifiEncr); + buf[i++] = 0x10; + buf[i++] = 0x27; - buf[ssid + 14] = 0x00; - buf[ssid + 15] = pass_len; - memcpy(&buf[ssid + 16], app->pass_buf, pass_len); - size_t pass = ssid + 16 + pass_len; - buf[pass + 0] = 0x10; - buf[pass + 1] = 0x20; + buf[i++] = 0x00; + buf[i++] = pass_len; + memcpy(&buf[i], app->pass_buf, pass_len); + i += pass_len; + buf[i++] = 0x10; + buf[i++] = 0x20; - buf[pass + 2] = 0x00; - buf[pass + 3] = 0x06; - buf[pass + 4] = 0xFF; - buf[pass + 5] = 0xFF; + buf[i++] = 0x00; + buf[i++] = 0x06; + buf[i++] = 0xFF; + buf[i++] = 0xFF; - buf[pass + 6] = 0xFF; - buf[pass + 7] = 0xFF; - buf[pass + 8] = 0xFF; - buf[pass + 9] = 0xFF; + buf[i++] = 0xFF; + buf[i++] = 0xFF; + buf[i++] = 0xFF; + buf[i++] = 0xFF; break; } @@ -291,12 +289,11 @@ void nfc_maker_scene_result_on_enter(void* context) { } // Message length and terminator - buf[22] = msg_len; - size_t msg_end = 23 + msg_len; - buf[msg_end] = 0xFE; + buf[start] = i - start - 1; + buf[i++] = 0xFE; // Padding - for(size_t i = msg_end + 1; i < size; i++) { + for(; i < size; i++) { buf[i] = 0x00; } From e7439272e6a861b94c933ff608ba82ee79364351 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 11 Jul 2023 03:46:12 +0300 Subject: [PATCH 088/364] Update mifare nested --- applications/external/mifare_nested/application.fam | 2 +- applications/external/mifare_nested/mifare_nested_i.h | 2 +- .../external/mifare_nested/mifare_nested_worker.c | 3 +++ .../external/mifare_nested/mifare_nested_worker.h | 1 + .../scenes/mifare_nested_scene_collecting.c | 9 ++++++--- 5 files changed, 12 insertions(+), 5 deletions(-) diff --git a/applications/external/mifare_nested/application.fam b/applications/external/mifare_nested/application.fam index 36dfb2673..b06c4fa3e 100644 --- a/applications/external/mifare_nested/application.fam +++ b/applications/external/mifare_nested/application.fam @@ -21,5 +21,5 @@ App( fap_author="AloneLiberty", fap_description="Recover Mifare Classic keys", fap_weburl="https://github.com/AloneLiberty/FlipperNested", - fap_version="1.5.1" + fap_version="1.5.2" ) diff --git a/applications/external/mifare_nested/mifare_nested_i.h b/applications/external/mifare_nested/mifare_nested_i.h index b99f785c6..b13a6c098 100644 --- a/applications/external/mifare_nested/mifare_nested_i.h +++ b/applications/external/mifare_nested/mifare_nested_i.h @@ -21,7 +21,7 @@ #include #include "mifare_nested_icons.h" -#define NESTED_VERSION_APP "1.5.1" +#define NESTED_VERSION_APP "1.5.2" #define NESTED_GITHUB_LINK "https://github.com/AloneLiberty/FlipperNested" #define NESTED_RECOVER_KEYS_GITHUB_LINK "https://github.com/AloneLiberty/FlipperNestedRecovery" #define NESTED_NONCE_FORMAT_VERSION "3" diff --git a/applications/external/mifare_nested/mifare_nested_worker.c b/applications/external/mifare_nested/mifare_nested_worker.c index 60540c74b..0b8097653 100644 --- a/applications/external/mifare_nested/mifare_nested_worker.c +++ b/applications/external/mifare_nested/mifare_nested_worker.c @@ -578,6 +578,7 @@ bool mifare_nested_worker_check_initial_keys( info->block = mifare_nested_worker_get_block_by_sector(sector); info->collected = false; info->skipped = true; + info->from_start = false; nonces->nonces[sector][key_type][tries] = info; } @@ -595,6 +596,7 @@ bool mifare_nested_worker_check_initial_keys( Nonces* info = nonces->nonces[sector][0][tries]; info->collected = true; info->skipped = true; + info->from_start = true; nonces->nonces[sector][0][tries] = info; } @@ -616,6 +618,7 @@ bool mifare_nested_worker_check_initial_keys( Nonces* info = nonces->nonces[sector][1][tries]; info->collected = true; info->skipped = true; + info->from_start = true; nonces->nonces[sector][1][tries] = info; } diff --git a/applications/external/mifare_nested/mifare_nested_worker.h b/applications/external/mifare_nested/mifare_nested_worker.h index 2421b35eb..99c691749 100644 --- a/applications/external/mifare_nested/mifare_nested_worker.h +++ b/applications/external/mifare_nested/mifare_nested_worker.h @@ -66,6 +66,7 @@ typedef struct { uint32_t target_ks[2]; uint8_t parity[2][4]; bool skipped; + bool from_start; bool invalid; bool collected; bool hardnested; diff --git a/applications/external/mifare_nested/scenes/mifare_nested_scene_collecting.c b/applications/external/mifare_nested/scenes/mifare_nested_scene_collecting.c index 281210107..0e319bbcf 100644 --- a/applications/external/mifare_nested/scenes/mifare_nested_scene_collecting.c +++ b/applications/external/mifare_nested/scenes/mifare_nested_scene_collecting.c @@ -18,12 +18,15 @@ bool mifare_nested_collecting_worker_callback(MifareNestedWorkerEvent event, voi mifare_nested_blink_nonce_collection_start(mifare_nested); uint8_t collected = 0; + uint8_t skip = 0; NonceList_t* nonces = mifare_nested->nonces; for(uint8_t tries = 0; tries < nonces->tries; tries++) { for(uint8_t sector = 0; sector < nonces->sector_count; sector++) { for(uint8_t keyType = 0; keyType < 2; keyType++) { Nonces* info = nonces->nonces[sector][keyType][tries]; - if(info->collected) { + if(info->from_start) { + skip++; + } else if(info->collected) { collected++; } } @@ -37,7 +40,7 @@ bool mifare_nested_collecting_worker_callback(MifareNestedWorkerEvent event, voi model->calibrating = false; model->lost_tag = false; model->nonces_collected = collected; - model->keys_count = nonces->sector_count * nonces->tries * 2; + model->keys_count = (nonces->sector_count * nonces->tries * 2) - skip; }, true); } else if(event == MifareNestedWorkerEventNoTagDetected) { @@ -155,4 +158,4 @@ void mifare_nested_scene_collecting_on_exit(void* context) { mifare_nested_blink_stop(mifare_nested); popup_reset(mifare_nested->popup); widget_reset(mifare_nested->widget); -} +} \ No newline at end of file From 4538471272784a588b8801d70b46d0dc2c15f30f Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Tue, 11 Jul 2023 03:39:46 +0200 Subject: [PATCH 089/364] Port NFC Maker to NTAG215 for more total pages --- .../nfc_maker/scenes/nfc_maker_scene_result.c | 44 ++++++++++++++----- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_result.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_result.c index 5da9d2eba..64aa9db1a 100644 --- a/applications/external/nfc_maker/scenes/nfc_maker_scene_result.c +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_result.c @@ -21,12 +21,12 @@ void nfc_maker_scene_result_on_enter(void* context) { do { if(!flipper_format_file_open_new(file, furi_string_get_cstr(path))) break; - uint32_t pages = 42; + uint32_t pages = 135; size_t size = pages * 4; uint8_t* buf = malloc(size); if(!flipper_format_write_header_cstr(file, "Flipper NFC device", 3)) break; - if(!flipper_format_write_string_cstr(file, "Device type", "NTAG203")) break; + if(!flipper_format_write_string_cstr(file, "Device type", "NTAG215")) break; // Serial number size_t i = 0; @@ -46,7 +46,7 @@ void nfc_maker_scene_result_on_enter(void* context) { "Signature", "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00")) break; - if(!flipper_format_write_string_cstr(file, "Mifare version", "00 00 00 00 00 00 00 00")) + if(!flipper_format_write_string_cstr(file, "Mifare version", "00 04 04 02 01 00 11 03")) break; if(!flipper_format_write_string_cstr(file, "Counter 0", "0")) break; @@ -63,13 +63,8 @@ void nfc_maker_scene_result_on_enter(void* context) { buf[i++] = 0x00; // ... buf[i++] = 0xE1; // Capability container buf[i++] = 0x10; // ... - buf[i++] = 0x12; // ... + buf[i++] = 0x3E; // ... buf[i++] = 0x00; // ... - buf[i++] = 0x01; // ... - buf[i++] = 0x03; // ... - buf[i++] = 0xA0; // ... - buf[i++] = 0x10; // ... - buf[i++] = 0x44; // ... buf[i++] = 0x03; // Message flags size_t start = i++; @@ -292,11 +287,38 @@ void nfc_maker_scene_result_on_enter(void* context) { buf[start] = i - start - 1; buf[i++] = 0xFE; - // Padding - for(; i < size; i++) { + // Padding until last 5 pages + for(; i < size - 20; i++) { buf[i] = 0x00; } + // Last 5 static pages + buf[i++] = 0x00; + buf[i++] = 0x00; + buf[i++] = 0x00; + buf[i++] = 0xBD; + + buf[i++] = 0x04; + buf[i++] = 0x00; + buf[i++] = 0x00; + buf[i++] = 0xFF; + + buf[i++] = 0x00; + buf[i++] = 0x05; + buf[i++] = 0x00; + buf[i++] = 0x00; + + buf[i++] = 0xFF; + buf[i++] = 0xFF; + buf[i++] = 0xFF; + buf[i++] = 0xFF; + + buf[i++] = 0x00; + buf[i++] = 0x00; + buf[i++] = 0x00; + buf[i++] = 0x00; + + // Write pages char str[16]; bool ok = true; for(size_t page = 0; page < pages; page++) { From 0e1b3f2d84c39251519e12a446dbdede3a43b31f Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Tue, 11 Jul 2023 03:49:51 +0200 Subject: [PATCH 090/364] Fix NFC Maker buffer free --- .../nfc_maker/scenes/nfc_maker_scene_result.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_result.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_result.c index 64aa9db1a..38ad1e634 100644 --- a/applications/external/nfc_maker/scenes/nfc_maker_scene_result.c +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_result.c @@ -18,13 +18,13 @@ void nfc_maker_scene_result_on_enter(void* context) { FlipperFormat* file = flipper_format_file_alloc(furi_record_open(RECORD_STORAGE)); FuriString* path = furi_string_alloc(); furi_string_printf(path, NFC_APP_FOLDER "/%s" NFC_APP_EXTENSION, app->name_buf); + + uint32_t pages = 135; + size_t size = pages * 4; + uint8_t* buf = malloc(size); do { if(!flipper_format_file_open_new(file, furi_string_get_cstr(path))) break; - uint32_t pages = 135; - size_t size = pages * 4; - uint8_t* buf = malloc(size); - if(!flipper_format_write_header_cstr(file, "Flipper NFC device", 3)) break; if(!flipper_format_write_string_cstr(file, "Device type", "NTAG215")) break; @@ -330,10 +330,11 @@ void nfc_maker_scene_result_on_enter(void* context) { } if(!ok) break; - free(buf); success = true; } while(false); + free(buf); + furi_string_free(path); flipper_format_free(file); furi_record_close(RECORD_STORAGE); From 978fecf24251f962b38dbcf4b94ca93148ab2394 Mon Sep 17 00:00:00 2001 From: Cody Tolene Date: Tue, 11 Jul 2023 00:06:55 -0500 Subject: [PATCH 091/364] Add Camera Suite GPIO application for the ESP32-CAM module. --- .../external/camera-suite/application.fam | 16 + .../external/camera-suite/camera-suite.c | 139 ++++++ .../external/camera-suite/camera-suite.h | 71 ++++ .../helpers/camera_suite_custom_event.h | 74 ++++ .../helpers/camera_suite_haptic.c | 35 ++ .../helpers/camera_suite_haptic.h | 7 + .../camera-suite/helpers/camera_suite_led.c | 38 ++ .../camera-suite/helpers/camera_suite_led.h | 3 + .../helpers/camera_suite_speaker.c | 26 ++ .../helpers/camera_suite_speaker.h | 5 + .../helpers/camera_suite_storage.c | 113 +++++ .../helpers/camera_suite_storage.h | 20 + .../camera-suite/icons/camera-suite.png | Bin 0 -> 2307 bytes .../camera-suite/scenes/camera_suite_scene.c | 30 ++ .../camera-suite/scenes/camera_suite_scene.h | 29 ++ .../scenes/camera_suite_scene_config.h | 6 + .../scenes/camera_suite_scene_guide.c | 51 +++ .../scenes/camera_suite_scene_menu.c | 87 ++++ .../scenes/camera_suite_scene_settings.c | 137 ++++++ .../scenes/camera_suite_scene_start.c | 55 +++ .../scenes/camera_suite_scene_style_1.c | 52 +++ .../scenes/camera_suite_scene_style_2.c | 54 +++ .../views/camera_suite_view_guide.c | 120 ++++++ .../views/camera_suite_view_guide.h | 19 + .../views/camera_suite_view_start.c | 126 ++++++ .../views/camera_suite_view_start.h | 19 + .../views/camera_suite_view_style_1.c | 401 ++++++++++++++++++ .../views/camera_suite_view_style_1.h | 60 +++ .../views/camera_suite_view_style_2.c | 249 +++++++++++ .../views/camera_suite_view_style_2.h | 19 + 30 files changed, 2061 insertions(+) create mode 100644 applications/external/camera-suite/application.fam create mode 100644 applications/external/camera-suite/camera-suite.c create mode 100644 applications/external/camera-suite/camera-suite.h create mode 100644 applications/external/camera-suite/helpers/camera_suite_custom_event.h create mode 100644 applications/external/camera-suite/helpers/camera_suite_haptic.c create mode 100644 applications/external/camera-suite/helpers/camera_suite_haptic.h create mode 100644 applications/external/camera-suite/helpers/camera_suite_led.c create mode 100644 applications/external/camera-suite/helpers/camera_suite_led.h create mode 100644 applications/external/camera-suite/helpers/camera_suite_speaker.c create mode 100644 applications/external/camera-suite/helpers/camera_suite_speaker.h create mode 100644 applications/external/camera-suite/helpers/camera_suite_storage.c create mode 100644 applications/external/camera-suite/helpers/camera_suite_storage.h create mode 100644 applications/external/camera-suite/icons/camera-suite.png create mode 100644 applications/external/camera-suite/scenes/camera_suite_scene.c create mode 100644 applications/external/camera-suite/scenes/camera_suite_scene.h create mode 100644 applications/external/camera-suite/scenes/camera_suite_scene_config.h create mode 100644 applications/external/camera-suite/scenes/camera_suite_scene_guide.c create mode 100644 applications/external/camera-suite/scenes/camera_suite_scene_menu.c create mode 100644 applications/external/camera-suite/scenes/camera_suite_scene_settings.c create mode 100644 applications/external/camera-suite/scenes/camera_suite_scene_start.c create mode 100644 applications/external/camera-suite/scenes/camera_suite_scene_style_1.c create mode 100644 applications/external/camera-suite/scenes/camera_suite_scene_style_2.c create mode 100644 applications/external/camera-suite/views/camera_suite_view_guide.c create mode 100644 applications/external/camera-suite/views/camera_suite_view_guide.h create mode 100644 applications/external/camera-suite/views/camera_suite_view_start.c create mode 100644 applications/external/camera-suite/views/camera_suite_view_start.h create mode 100644 applications/external/camera-suite/views/camera_suite_view_style_1.c create mode 100644 applications/external/camera-suite/views/camera_suite_view_style_1.h create mode 100644 applications/external/camera-suite/views/camera_suite_view_style_2.c create mode 100644 applications/external/camera-suite/views/camera_suite_view_style_2.h diff --git a/applications/external/camera-suite/application.fam b/applications/external/camera-suite/application.fam new file mode 100644 index 000000000..d2beefcde --- /dev/null +++ b/applications/external/camera-suite/application.fam @@ -0,0 +1,16 @@ +App( + appid="camerasuite", + apptype=FlipperAppType.EXTERNAL, + cdefines=["APP_CAMERA_SUITE"], + entry_point="camera_suite_app", + fap_author="Cody Tolene", + fap_category="GPIO", + fap_description="A camera suite application for the Flipper Zero ESP32-CAM module.", + fap_icon="icons/camera-suite.png", + fap_libs=["assets"], + fap_weburl="https://github.com/CodyTolene/Flipper-Zero-Cam", + name="[ESP32] Camera Suite", + order=1, + requires=["gui", "storage"], + stack_size=8 * 1024 +) \ No newline at end of file diff --git a/applications/external/camera-suite/camera-suite.c b/applications/external/camera-suite/camera-suite.c new file mode 100644 index 000000000..361bfef4e --- /dev/null +++ b/applications/external/camera-suite/camera-suite.c @@ -0,0 +1,139 @@ +#include "camera-suite.h" +#include + +bool camera_suite_custom_event_callback(void* context, uint32_t event) { + furi_assert(context); + CameraSuite* app = context; + return scene_manager_handle_custom_event(app->scene_manager, event); +} + +void camera_suite_tick_event_callback(void* context) { + furi_assert(context); + CameraSuite* app = context; + scene_manager_handle_tick_event(app->scene_manager); +} + +//leave app if back button pressed +bool camera_suite_navigation_event_callback(void* context) { + furi_assert(context); + CameraSuite* app = context; + return scene_manager_handle_back_event(app->scene_manager); +} + +CameraSuite* camera_suite_app_alloc() { + CameraSuite* app = malloc(sizeof(CameraSuite)); + app->gui = furi_record_open(RECORD_GUI); + app->notification = furi_record_open(RECORD_NOTIFICATION); + + //Turn backlight on, believe me this makes testing your app easier + notification_message(app->notification, &sequence_display_backlight_on); + + //Scene additions + app->view_dispatcher = view_dispatcher_alloc(); + view_dispatcher_enable_queue(app->view_dispatcher); + + app->scene_manager = scene_manager_alloc(&camera_suite_scene_handlers, app); + view_dispatcher_set_event_callback_context(app->view_dispatcher, app); + view_dispatcher_set_navigation_event_callback( + app->view_dispatcher, camera_suite_navigation_event_callback); + view_dispatcher_set_tick_event_callback( + app->view_dispatcher, camera_suite_tick_event_callback, 100); + view_dispatcher_set_custom_event_callback( + app->view_dispatcher, camera_suite_custom_event_callback); + app->submenu = submenu_alloc(); + + // Set defaults, in case no config loaded + app->orientation = 0; // Orientation is "portrait", zero degrees by default. + app->haptic = 1; // Haptic is on by default + app->speaker = 1; // Speaker is on by default + app->led = 1; // LED is on by default + + // Load configs + camera_suite_read_settings(app); + + view_dispatcher_add_view( + app->view_dispatcher, CameraSuiteViewIdMenu, submenu_get_view(app->submenu)); + + app->camera_suite_view_start = camera_suite_view_start_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + CameraSuiteViewIdStartscreen, + camera_suite_view_start_get_view(app->camera_suite_view_start)); + + app->camera_suite_view_style_1 = camera_suite_view_style_1_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + CameraSuiteViewIdScene1, + camera_suite_view_style_1_get_view(app->camera_suite_view_style_1)); + + app->camera_suite_view_style_2 = camera_suite_view_style_2_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + CameraSuiteViewIdScene2, + camera_suite_view_style_2_get_view(app->camera_suite_view_style_2)); + + app->camera_suite_view_guide = camera_suite_view_guide_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + CameraSuiteViewIdGuide, + camera_suite_view_guide_get_view(app->camera_suite_view_guide)); + + app->button_menu = button_menu_alloc(); + + app->variable_item_list = variable_item_list_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + CameraSuiteViewIdSettings, + variable_item_list_get_view(app->variable_item_list)); + + //End Scene Additions + + return app; +} + +void camera_suite_app_free(CameraSuite* app) { + furi_assert(app); + + // Scene manager + scene_manager_free(app->scene_manager); + + // View Dispatcher + view_dispatcher_remove_view(app->view_dispatcher, CameraSuiteViewIdMenu); + view_dispatcher_remove_view(app->view_dispatcher, CameraSuiteViewIdScene1); + view_dispatcher_remove_view(app->view_dispatcher, CameraSuiteViewIdScene2); + view_dispatcher_remove_view(app->view_dispatcher, CameraSuiteViewIdGuide); + view_dispatcher_remove_view(app->view_dispatcher, CameraSuiteViewIdSettings); + submenu_free(app->submenu); + + view_dispatcher_free(app->view_dispatcher); + furi_record_close(RECORD_GUI); + + // Free remaining resources + camera_suite_view_start_free(app->camera_suite_view_start); + camera_suite_view_style_1_free(app->camera_suite_view_style_1); + camera_suite_view_style_2_free(app->camera_suite_view_style_2); + camera_suite_view_guide_free(app->camera_suite_view_guide); + button_menu_free(app->button_menu); + variable_item_list_free(app->variable_item_list); + + app->gui = NULL; + app->notification = NULL; + + //Remove whatever is left + free(app); +} + +/** Main entry point for initialization. */ +int32_t camera_suite_app(void* p) { + UNUSED(p); + CameraSuite* app = camera_suite_app_alloc(); + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + // Init with start scene. + scene_manager_next_scene(app->scene_manager, CameraSuiteSceneStart); + furi_hal_power_suppress_charge_enter(); + view_dispatcher_run(app->view_dispatcher); + camera_suite_save_settings(app); + furi_hal_power_suppress_charge_exit(); + camera_suite_app_free(app); + return 0; +} diff --git a/applications/external/camera-suite/camera-suite.h b/applications/external/camera-suite/camera-suite.h new file mode 100644 index 000000000..2b6a7748b --- /dev/null +++ b/applications/external/camera-suite/camera-suite.h @@ -0,0 +1,71 @@ +#pragma once + +#include "helpers/camera_suite_storage.h" +#include "scenes/camera_suite_scene.h" +#include "views/camera_suite_view_guide.h" +#include "views/camera_suite_view_start.h" +#include "views/camera_suite_view_style_1.h" +#include "views/camera_suite_view_style_2.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TAG "Camera Suite" + +typedef struct { + Gui* gui; + NotificationApp* notification; + ViewDispatcher* view_dispatcher; + Submenu* submenu; + SceneManager* scene_manager; + VariableItemList* variable_item_list; + CameraSuiteViewStart* camera_suite_view_start; + CameraSuiteViewStyle1* camera_suite_view_style_1; + CameraSuiteViewStyle2* camera_suite_view_style_2; + CameraSuiteViewGuide* camera_suite_view_guide; + uint32_t orientation; + uint32_t haptic; + uint32_t speaker; + uint32_t led; + ButtonMenu* button_menu; +} CameraSuite; + +typedef enum { + CameraSuiteViewIdStartscreen, + CameraSuiteViewIdMenu, + CameraSuiteViewIdScene1, + CameraSuiteViewIdScene2, + CameraSuiteViewIdGuide, + CameraSuiteViewIdSettings, +} CameraSuiteViewId; + +typedef enum { + CameraSuiteOrientation0, + CameraSuiteOrientation90, + CameraSuiteOrientation180, + CameraSuiteOrientation270, +} CameraSuiteOrientationState; + +typedef enum { + CameraSuiteHapticOff, + CameraSuiteHapticOn, +} CameraSuiteHapticState; + +typedef enum { + CameraSuiteSpeakerOff, + CameraSuiteSpeakerOn, +} CameraSuiteSpeakerState; + +typedef enum { + CameraSuiteLedOff, + CameraSuiteLedOn, +} CameraSuiteLedState; diff --git a/applications/external/camera-suite/helpers/camera_suite_custom_event.h b/applications/external/camera-suite/helpers/camera_suite_custom_event.h new file mode 100644 index 000000000..7727a0de3 --- /dev/null +++ b/applications/external/camera-suite/helpers/camera_suite_custom_event.h @@ -0,0 +1,74 @@ +#pragma once + +typedef enum { + // Scene events: Start menu + CameraSuiteCustomEventStartUp, + CameraSuiteCustomEventStartDown, + CameraSuiteCustomEventStartLeft, + CameraSuiteCustomEventStartRight, + CameraSuiteCustomEventStartOk, + CameraSuiteCustomEventStartBack, + // Scene events: Camera style 1 + CameraSuiteCustomEventSceneStyle1Up, + CameraSuiteCustomEventSceneStyle1Down, + CameraSuiteCustomEventSceneStyle1Left, + CameraSuiteCustomEventSceneStyle1Right, + CameraSuiteCustomEventSceneStyle1Ok, + CameraSuiteCustomEventSceneStyle1Back, + // Scene events: Camera style 2 + CameraSuiteCustomEventSceneStyle2Up, + CameraSuiteCustomEventSceneStyle2Down, + CameraSuiteCustomEventSceneStyle2Left, + CameraSuiteCustomEventSceneStyle2Right, + CameraSuiteCustomEventSceneStyle2Ok, + CameraSuiteCustomEventSceneStyle2Back, + // Scene events: Guide + CameraSuiteCustomEventSceneGuideUp, + CameraSuiteCustomEventSceneGuideDown, + CameraSuiteCustomEventSceneGuideLeft, + CameraSuiteCustomEventSceneGuideRight, + CameraSuiteCustomEventSceneGuideOk, + CameraSuiteCustomEventSceneGuideBack, +} CameraSuiteCustomEvent; + +enum CameraSuiteCustomEventType { + // Reserve first 100 events for button types and indexes, starting from 0. + CameraSuiteCustomEventMenuVoid, + CameraSuiteCustomEventMenuSelected, +}; + +#pragma pack(push, 1) + +typedef union { + uint32_t packed_value; + struct { + uint16_t type; + int16_t value; + } content; +} CameraSuiteCustomEventMenu; + +#pragma pack(pop) + +static inline uint32_t camera_suite_custom_menu_event_pack(uint16_t type, int16_t value) { + CameraSuiteCustomEventMenu event = {.content = {.type = type, .value = value}}; + return event.packed_value; +} + +static inline void + camera_suite_custom_menu_event_unpack(uint32_t packed_value, uint16_t* type, int16_t* value) { + CameraSuiteCustomEventMenu event = {.packed_value = packed_value}; + if(type) *type = event.content.type; + if(value) *value = event.content.value; +} + +static inline uint16_t camera_suite_custom_menu_event_get_type(uint32_t packed_value) { + uint16_t type; + camera_suite_custom_menu_event_unpack(packed_value, &type, NULL); + return type; +} + +static inline int16_t camera_suite_custom_menu_event_get_value(uint32_t packed_value) { + int16_t value; + camera_suite_custom_menu_event_unpack(packed_value, NULL, &value); + return value; +} diff --git a/applications/external/camera-suite/helpers/camera_suite_haptic.c b/applications/external/camera-suite/helpers/camera_suite_haptic.c new file mode 100644 index 000000000..27ea81868 --- /dev/null +++ b/applications/external/camera-suite/helpers/camera_suite_haptic.c @@ -0,0 +1,35 @@ +#include "camera_suite_haptic.h" +#include "../camera-suite.h" + +void camera_suite_play_happy_bump(void* context) { + CameraSuite* app = context; + if(app->haptic != 1) { + return; + } + notification_message(app->notification, &sequence_set_vibro_on); + furi_thread_flags_wait(0, FuriFlagWaitAny, 20); + notification_message(app->notification, &sequence_reset_vibro); +} + +void camera_suite_play_bad_bump(void* context) { + CameraSuite* app = context; + if(app->haptic != 1) { + return; + } + notification_message(app->notification, &sequence_set_vibro_on); + furi_thread_flags_wait(0, FuriFlagWaitAny, 100); + notification_message(app->notification, &sequence_reset_vibro); +} + +void camera_suite_play_long_bump(void* context) { + CameraSuite* app = context; + if(app->haptic != 1) { + return; + } + for(int i = 0; i < 4; i++) { + notification_message(app->notification, &sequence_set_vibro_on); + furi_thread_flags_wait(0, FuriFlagWaitAny, 50); + notification_message(app->notification, &sequence_reset_vibro); + furi_thread_flags_wait(0, FuriFlagWaitAny, 100); + } +} diff --git a/applications/external/camera-suite/helpers/camera_suite_haptic.h b/applications/external/camera-suite/helpers/camera_suite_haptic.h new file mode 100644 index 000000000..9b7651f97 --- /dev/null +++ b/applications/external/camera-suite/helpers/camera_suite_haptic.h @@ -0,0 +1,7 @@ +#include + +void camera_suite_play_happy_bump(void* context); + +void camera_suite_play_bad_bump(void* context); + +void camera_suite_play_long_bump(void* context); diff --git a/applications/external/camera-suite/helpers/camera_suite_led.c b/applications/external/camera-suite/helpers/camera_suite_led.c new file mode 100644 index 000000000..c7211a996 --- /dev/null +++ b/applications/external/camera-suite/helpers/camera_suite_led.c @@ -0,0 +1,38 @@ +#include "camera_suite_led.h" +#include "../camera-suite.h" + +void camera_suite_led_set_rgb(void* context, int red, int green, int blue) { + CameraSuite* app = context; + if(app->led != 1) { + return; + } + NotificationMessage notification_led_message_1; + notification_led_message_1.type = NotificationMessageTypeLedRed; + NotificationMessage notification_led_message_2; + notification_led_message_2.type = NotificationMessageTypeLedGreen; + NotificationMessage notification_led_message_3; + notification_led_message_3.type = NotificationMessageTypeLedBlue; + + notification_led_message_1.data.led.value = red; + notification_led_message_2.data.led.value = green; + notification_led_message_3.data.led.value = blue; + const NotificationSequence notification_sequence = { + ¬ification_led_message_1, + ¬ification_led_message_2, + ¬ification_led_message_3, + &message_do_not_reset, + NULL, + }; + notification_message(app->notification, ¬ification_sequence); + //Delay, prevent removal from RAM before LED value set. + furi_thread_flags_wait(0, FuriFlagWaitAny, 10); +} + +void camera_suite_led_reset(void* context) { + CameraSuite* app = context; + notification_message(app->notification, &sequence_reset_red); + notification_message(app->notification, &sequence_reset_green); + notification_message(app->notification, &sequence_reset_blue); + //Delay, prevent removal from RAM before LED value set. + furi_thread_flags_wait(0, FuriFlagWaitAny, 300); +} diff --git a/applications/external/camera-suite/helpers/camera_suite_led.h b/applications/external/camera-suite/helpers/camera_suite_led.h new file mode 100644 index 000000000..074947da1 --- /dev/null +++ b/applications/external/camera-suite/helpers/camera_suite_led.h @@ -0,0 +1,3 @@ +void camera_suite_led_set_rgb(void* context, int red, int green, int blue); + +void camera_suite_led_reset(void* context); diff --git a/applications/external/camera-suite/helpers/camera_suite_speaker.c b/applications/external/camera-suite/helpers/camera_suite_speaker.c new file mode 100644 index 000000000..35d712109 --- /dev/null +++ b/applications/external/camera-suite/helpers/camera_suite_speaker.c @@ -0,0 +1,26 @@ +#include "camera_suite_speaker.h" +#include "../camera-suite.h" + +#define NOTE_INPUT 587.33f + +void camera_suite_play_input_sound(void* context) { + CameraSuite* app = context; + if(app->speaker != 1) { + return; + } + float volume = 1.0f; + if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(30)) { + furi_hal_speaker_start(NOTE_INPUT, volume); + } +} + +void camera_suite_stop_all_sound(void* context) { + CameraSuite* app = context; + if(app->speaker != 1) { + return; + } + if(furi_hal_speaker_is_mine()) { + furi_hal_speaker_stop(); + furi_hal_speaker_release(); + } +} diff --git a/applications/external/camera-suite/helpers/camera_suite_speaker.h b/applications/external/camera-suite/helpers/camera_suite_speaker.h new file mode 100644 index 000000000..2119bbec5 --- /dev/null +++ b/applications/external/camera-suite/helpers/camera_suite_speaker.h @@ -0,0 +1,5 @@ +#define NOTE_INPUT 587.33f + +void camera_suite_play_input_sound(void* context); + +void camera_suite_stop_all_sound(void* context); diff --git a/applications/external/camera-suite/helpers/camera_suite_storage.c b/applications/external/camera-suite/helpers/camera_suite_storage.c new file mode 100644 index 000000000..38a5f0813 --- /dev/null +++ b/applications/external/camera-suite/helpers/camera_suite_storage.c @@ -0,0 +1,113 @@ +#include "camera_suite_storage.h" + +static Storage* camera_suite_open_storage() { + return furi_record_open(RECORD_STORAGE); +} + +static void camera_suite_close_storage() { + furi_record_close(RECORD_STORAGE); +} + +static void camera_suite_close_config_file(FlipperFormat* file) { + if(file == NULL) return; + flipper_format_file_close(file); + flipper_format_free(file); +} + +void camera_suite_save_settings(void* context) { + CameraSuite* app = context; + + FURI_LOG_D(TAG, "Saving Settings"); + Storage* storage = camera_suite_open_storage(); + FlipperFormat* fff_file = flipper_format_file_alloc(storage); + + // Overwrite wont work, so delete first + if(storage_file_exists(storage, BOILERPLATE_SETTINGS_SAVE_PATH)) { + storage_simply_remove(storage, BOILERPLATE_SETTINGS_SAVE_PATH); + } + + // Open File, create if not exists + if(!storage_common_stat(storage, BOILERPLATE_SETTINGS_SAVE_PATH, NULL) == FSE_OK) { + FURI_LOG_D( + TAG, "Config file %s is not found. Will create new.", BOILERPLATE_SETTINGS_SAVE_PATH); + if(storage_common_stat(storage, CONFIG_FILE_DIRECTORY_PATH, NULL) == FSE_NOT_EXIST) { + FURI_LOG_D( + TAG, "Directory %s doesn't exist. Will create new.", CONFIG_FILE_DIRECTORY_PATH); + if(!storage_simply_mkdir(storage, CONFIG_FILE_DIRECTORY_PATH)) { + FURI_LOG_E(TAG, "Error creating directory %s", CONFIG_FILE_DIRECTORY_PATH); + } + } + } + + if(!flipper_format_file_open_new(fff_file, BOILERPLATE_SETTINGS_SAVE_PATH)) { + //totp_close_config_file(fff_file); + FURI_LOG_E(TAG, "Error creating new file %s", BOILERPLATE_SETTINGS_SAVE_PATH); + camera_suite_close_storage(); + return; + } + + // Store Settings + flipper_format_write_header_cstr( + fff_file, BOILERPLATE_SETTINGS_HEADER, BOILERPLATE_SETTINGS_FILE_VERSION); + flipper_format_write_uint32( + fff_file, BOILERPLATE_SETTINGS_KEY_ORIENTATION, &app->orientation, 1); + flipper_format_write_uint32(fff_file, BOILERPLATE_SETTINGS_KEY_HAPTIC, &app->haptic, 1); + flipper_format_write_uint32(fff_file, BOILERPLATE_SETTINGS_KEY_SPEAKER, &app->speaker, 1); + flipper_format_write_uint32(fff_file, BOILERPLATE_SETTINGS_KEY_LED, &app->led, 1); + + if(!flipper_format_rewind(fff_file)) { + camera_suite_close_config_file(fff_file); + FURI_LOG_E(TAG, "Rewind error"); + camera_suite_close_storage(); + return; + } + + camera_suite_close_config_file(fff_file); + camera_suite_close_storage(); +} + +void camera_suite_read_settings(void* context) { + CameraSuite* app = context; + Storage* storage = camera_suite_open_storage(); + FlipperFormat* fff_file = flipper_format_file_alloc(storage); + + if(storage_common_stat(storage, BOILERPLATE_SETTINGS_SAVE_PATH, NULL) != FSE_OK) { + camera_suite_close_config_file(fff_file); + camera_suite_close_storage(); + return; + } + uint32_t file_version; + FuriString* temp_str = furi_string_alloc(); + + if(!flipper_format_file_open_existing(fff_file, BOILERPLATE_SETTINGS_SAVE_PATH)) { + FURI_LOG_E(TAG, "Cannot open file %s", BOILERPLATE_SETTINGS_SAVE_PATH); + camera_suite_close_config_file(fff_file); + camera_suite_close_storage(); + return; + } + + if(!flipper_format_read_header(fff_file, temp_str, &file_version)) { + FURI_LOG_E(TAG, "Missing Header Data"); + camera_suite_close_config_file(fff_file); + camera_suite_close_storage(); + return; + } + + if(file_version < BOILERPLATE_SETTINGS_FILE_VERSION) { + FURI_LOG_I(TAG, "old config version, will be removed."); + camera_suite_close_config_file(fff_file); + camera_suite_close_storage(); + return; + } + + flipper_format_read_uint32( + fff_file, BOILERPLATE_SETTINGS_KEY_ORIENTATION, &app->orientation, 1); + flipper_format_read_uint32(fff_file, BOILERPLATE_SETTINGS_KEY_HAPTIC, &app->haptic, 1); + flipper_format_read_uint32(fff_file, BOILERPLATE_SETTINGS_KEY_SPEAKER, &app->speaker, 1); + flipper_format_read_uint32(fff_file, BOILERPLATE_SETTINGS_KEY_LED, &app->led, 1); + + flipper_format_rewind(fff_file); + + camera_suite_close_config_file(fff_file); + camera_suite_close_storage(); +} \ No newline at end of file diff --git a/applications/external/camera-suite/helpers/camera_suite_storage.h b/applications/external/camera-suite/helpers/camera_suite_storage.h new file mode 100644 index 000000000..e56a4e5ac --- /dev/null +++ b/applications/external/camera-suite/helpers/camera_suite_storage.h @@ -0,0 +1,20 @@ +#include +#include +#include +#include +#include "../camera-suite.h" + +#define BOILERPLATE_SETTINGS_FILE_VERSION 1 +#define CONFIG_FILE_DIRECTORY_PATH EXT_PATH("apps_data/camera-suite") +#define BOILERPLATE_SETTINGS_SAVE_PATH CONFIG_FILE_DIRECTORY_PATH "/camera-suite.conf" +#define BOILERPLATE_SETTINGS_SAVE_PATH_TMP BOILERPLATE_SETTINGS_SAVE_PATH ".tmp" +#define BOILERPLATE_SETTINGS_HEADER "Camera Suite Config File" +#define BOILERPLATE_SETTINGS_KEY_ORIENTATION "Orientation" +#define BOILERPLATE_SETTINGS_KEY_HAPTIC "Haptic" +#define BOILERPLATE_SETTINGS_KEY_LED "Led" +#define BOILERPLATE_SETTINGS_KEY_SPEAKER "Speaker" +#define BOILERPLATE_SETTINGS_KEY_SAVE_SETTINGS "SaveSettings" + +void camera_suite_save_settings(void* context); + +void camera_suite_read_settings(void* context); diff --git a/applications/external/camera-suite/icons/camera-suite.png b/applications/external/camera-suite/icons/camera-suite.png new file mode 100644 index 0000000000000000000000000000000000000000..cee545bb999eddfc8fe17d1070a27be46f9f299c GIT binary patch literal 2307 zcma)732YQq7~a~JV!28mK<*_pTP(A}M7X4>7N zfT@6`1l)2ZXpn@%08L5^i71DyLP@lhA_jzT8cdp0+a@R!h!x-LWq}&mWM|&I_xeN(|$u!1MlwStl#yWLmB7C0sJ^Le*DWO5m%PU>zC@w`@HL^#?WFMlH zEzC8U<~6OkV6Q4qn3#OK|Ma{`^872K(HS?>_GC6R>N}jH>o;sGEZXn%RDB{^l?t^5F_1#T} z=k!h$R-LV8zFjVDyNt}dmA+za^BV7j^vsU0TsJlrU3#s2(XGbMz9?Gqc1dmF-$}c? zQx2_^8w>EOO^2#hmS^tV-+Hq5QrDgt-jBP~2Oa(gcV=q=+m&f!*JaNfJMPoaqt530 zvs;gK^(-lScLMfDL;JN;LHmzK27+}T-r39V^d%oiyZqaG>(1qFI(U5g=>C=N|=gM<~#ivKb;=@neOW!xACfawCMi$yO5e!N*6l|;5#`vIVB}!)TmK3 zO@~6EGiT0pcX#*p_V)Gl-Me@1;lqc6gM*JBKW@zkErsY8{Y7tRCKH}v{1bu)vujPJ z5yxa_gp-!F_9$A2y4RyPO`8JorzE+MH3`VP&7tzJY(f)7K#2)Xik;fd|7^B)ENA- zqgA@@=LsSZ2;c!4SJX=8Omme!`5Cgx6~X7Dj7&K`j)Hx-l$TbJqC*Q3f=nMioIp zE%50XF>y(Z%ld0nPdETcB!CBaAxwkT%yCmjW5&@&z?HJsAC`b5#8Ge^2My~^%(O1M z|IObMNaG|NXAJ7EQglVDQvO48{5UQIGsx#FVvP}mBj1i1p7N3+$`T)5vKG?9(vk(U zxET?%m?;snQ38j#NZ@v{lFhj79=Lb(fIQ1yp%M^&UuAgL{p_wm5}#A*u%i@CqDc6&)FbEy6gHFWlazoK z-FM1fP_7FGVP-3988c_0%rVKOK-FNG8p_NhPDOIU=cz!@71gOI9y=Ny*T!); z-pCoq7jyuHU^ihTOcLv)NSR zDN=$&k|Io8L|#+edO%P?P9+Q!x(v%3F*rcxMH!wSLk6mVL0^~&3NzabCP*`HW>Dg} zd>_u!=d1pRr$g<-N_dvuVFQZH>e5)-Ba{WVJ{#MRyc)p}#xRX=LOe}Z_-x4FjltnG zH + +// Generate scene id and total number +#define ADD_SCENE(prefix, name, id) CameraSuiteScene##id, +typedef enum { +#include "camera_suite_scene_config.h" + CameraSuiteSceneNum, +} CameraSuiteScene; +#undef ADD_SCENE + +extern const SceneManagerHandlers camera_suite_scene_handlers; + +// Generate scene on_enter handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); +#include "camera_suite_scene_config.h" +#undef ADD_SCENE + +// Generate scene on_event handlers declaration +#define ADD_SCENE(prefix, name, id) \ + bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event); +#include "camera_suite_scene_config.h" +#undef ADD_SCENE + +// Generate scene on_exit handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context); +#include "camera_suite_scene_config.h" +#undef ADD_SCENE diff --git a/applications/external/camera-suite/scenes/camera_suite_scene_config.h b/applications/external/camera-suite/scenes/camera_suite_scene_config.h new file mode 100644 index 000000000..fca73ae16 --- /dev/null +++ b/applications/external/camera-suite/scenes/camera_suite_scene_config.h @@ -0,0 +1,6 @@ +ADD_SCENE(camera_suite, start, Start) +ADD_SCENE(camera_suite, menu, Menu) +ADD_SCENE(camera_suite, style_1, Style_1) +ADD_SCENE(camera_suite, style_2, Style_2) +ADD_SCENE(camera_suite, guide, Guide) +ADD_SCENE(camera_suite, settings, Settings) \ No newline at end of file diff --git a/applications/external/camera-suite/scenes/camera_suite_scene_guide.c b/applications/external/camera-suite/scenes/camera_suite_scene_guide.c new file mode 100644 index 000000000..12682fb24 --- /dev/null +++ b/applications/external/camera-suite/scenes/camera_suite_scene_guide.c @@ -0,0 +1,51 @@ +#include "../camera-suite.h" +#include "../helpers/camera_suite_custom_event.h" +#include "../views/camera_suite_view_guide.h" + +void camera_suite_view_guide_callback(CameraSuiteCustomEvent event, void* context) { + furi_assert(context); + CameraSuite* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, event); +} + +void camera_suite_scene_guide_on_enter(void* context) { + furi_assert(context); + CameraSuite* app = context; + camera_suite_view_guide_set_callback( + app->camera_suite_view_guide, camera_suite_view_guide_callback, app); + view_dispatcher_switch_to_view(app->view_dispatcher, CameraSuiteViewIdGuide); +} + +bool camera_suite_scene_guide_on_event(void* context, SceneManagerEvent event) { + CameraSuite* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + switch(event.event) { + case CameraSuiteCustomEventSceneGuideLeft: + case CameraSuiteCustomEventSceneGuideRight: + case CameraSuiteCustomEventSceneGuideUp: + case CameraSuiteCustomEventSceneGuideDown: + // Do nothing. + break; + case CameraSuiteCustomEventSceneGuideBack: + notification_message(app->notification, &sequence_reset_red); + notification_message(app->notification, &sequence_reset_green); + notification_message(app->notification, &sequence_reset_blue); + if(!scene_manager_search_and_switch_to_previous_scene( + app->scene_manager, CameraSuiteSceneMenu)) { + scene_manager_stop(app->scene_manager); + view_dispatcher_stop(app->view_dispatcher); + } + consumed = true; + break; + } + } + + return consumed; +} + +void camera_suite_scene_guide_on_exit(void* context) { + CameraSuite* app = context; + UNUSED(app); +} \ No newline at end of file diff --git a/applications/external/camera-suite/scenes/camera_suite_scene_menu.c b/applications/external/camera-suite/scenes/camera_suite_scene_menu.c new file mode 100644 index 000000000..538e64252 --- /dev/null +++ b/applications/external/camera-suite/scenes/camera_suite_scene_menu.c @@ -0,0 +1,87 @@ +#include "../camera-suite.h" + +enum SubmenuIndex { + /** Atkinson Dithering Algorithm. */ + SubmenuIndexSceneStyle1 = 10, + /** Floyd-Steinberg Dithering Algorithm. */ + SubmenuIndexSceneStyle2, + /** Guide/how-to. */ + SubmenuIndexGuide, + /** Settings menu. */ + SubmenuIndexSettings, +}; + +void camera_suite_scene_menu_submenu_callback(void* context, uint32_t index) { + CameraSuite* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, index); +} + +void camera_suite_scene_menu_on_enter(void* context) { + CameraSuite* app = context; + + submenu_add_item( + app->submenu, + "Open Camera", + SubmenuIndexSceneStyle1, + camera_suite_scene_menu_submenu_callback, + app); + // Staged view for the future. + // submenu_add_item( + // app->submenu, + // "Test", + // SubmenuIndexSceneStyle2, + // camera_suite_scene_menu_submenu_callback, + // app); + submenu_add_item( + app->submenu, "Guide", SubmenuIndexGuide, camera_suite_scene_menu_submenu_callback, app); + submenu_add_item( + app->submenu, + "Settings", + SubmenuIndexSettings, + camera_suite_scene_menu_submenu_callback, + app); + + submenu_set_selected_item( + app->submenu, scene_manager_get_scene_state(app->scene_manager, CameraSuiteSceneMenu)); + + view_dispatcher_switch_to_view(app->view_dispatcher, CameraSuiteViewIdMenu); +} + +bool camera_suite_scene_menu_on_event(void* context, SceneManagerEvent event) { + CameraSuite* app = context; + UNUSED(app); + if(event.type == SceneManagerEventTypeBack) { + // Exit application. + scene_manager_stop(app->scene_manager); + view_dispatcher_stop(app->view_dispatcher); + return true; + } else if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubmenuIndexSceneStyle1) { + scene_manager_set_scene_state( + app->scene_manager, CameraSuiteSceneMenu, SubmenuIndexSceneStyle1); + scene_manager_next_scene(app->scene_manager, CameraSuiteSceneStyle_1); + return true; + } else if(event.event == SubmenuIndexSceneStyle2) { + scene_manager_set_scene_state( + app->scene_manager, CameraSuiteSceneMenu, SubmenuIndexSceneStyle2); + scene_manager_next_scene(app->scene_manager, CameraSuiteSceneStyle_2); + return true; + } else if(event.event == SubmenuIndexGuide) { + scene_manager_set_scene_state( + app->scene_manager, CameraSuiteSceneMenu, SubmenuIndexGuide); + scene_manager_next_scene(app->scene_manager, CameraSuiteSceneGuide); + return true; + } else if(event.event == SubmenuIndexSettings) { + scene_manager_set_scene_state( + app->scene_manager, CameraSuiteSceneMenu, SubmenuIndexSettings); + scene_manager_next_scene(app->scene_manager, CameraSuiteSceneSettings); + return true; + } + } + return false; +} + +void camera_suite_scene_menu_on_exit(void* context) { + CameraSuite* app = context; + submenu_reset(app->submenu); +} \ No newline at end of file diff --git a/applications/external/camera-suite/scenes/camera_suite_scene_settings.c b/applications/external/camera-suite/scenes/camera_suite_scene_settings.c new file mode 100644 index 000000000..aba80d08c --- /dev/null +++ b/applications/external/camera-suite/scenes/camera_suite_scene_settings.c @@ -0,0 +1,137 @@ +#include "../camera-suite.h" +#include + +// Camera orientation, in degrees. +const char* const orientation_text[4] = { + "0", + "90", + "180", + "270", +}; + +const uint32_t orientation_value[4] = { + CameraSuiteOrientation0, + CameraSuiteOrientation90, + CameraSuiteOrientation180, + CameraSuiteOrientation270, +}; + +const char* const haptic_text[2] = { + "OFF", + "ON", +}; + +const uint32_t haptic_value[2] = { + CameraSuiteHapticOff, + CameraSuiteHapticOn, +}; + +const char* const speaker_text[2] = { + "OFF", + "ON", +}; + +const uint32_t speaker_value[2] = { + CameraSuiteSpeakerOff, + CameraSuiteSpeakerOn, +}; + +const char* const led_text[2] = { + "OFF", + "ON", +}; + +const uint32_t led_value[2] = { + CameraSuiteLedOff, + CameraSuiteLedOn, +}; + +static void camera_suite_scene_settings_set_camera_orientation(VariableItem* item) { + CameraSuite* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + variable_item_set_current_value_text(item, orientation_text[index]); + app->orientation = orientation_value[index]; +} + +static void camera_suite_scene_settings_set_haptic(VariableItem* item) { + CameraSuite* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + variable_item_set_current_value_text(item, haptic_text[index]); + app->haptic = haptic_value[index]; +} + +static void camera_suite_scene_settings_set_speaker(VariableItem* item) { + CameraSuite* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + variable_item_set_current_value_text(item, speaker_text[index]); + app->speaker = speaker_value[index]; +} + +static void camera_suite_scene_settings_set_led(VariableItem* item) { + CameraSuite* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + variable_item_set_current_value_text(item, led_text[index]); + app->led = led_value[index]; +} + +void camera_suite_scene_settings_submenu_callback(void* context, uint32_t index) { + CameraSuite* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, index); +} + +void camera_suite_scene_settings_on_enter(void* context) { + CameraSuite* app = context; + VariableItem* item; + uint8_t value_index; + + // Camera Orientation + item = variable_item_list_add( + app->variable_item_list, + "Orientation:", + 4, + camera_suite_scene_settings_set_camera_orientation, + app); + value_index = value_index_uint32(app->orientation, orientation_value, 4); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, orientation_text[value_index]); + + // Haptic FX ON/OFF + item = variable_item_list_add( + app->variable_item_list, "Haptic FX:", 2, camera_suite_scene_settings_set_haptic, app); + value_index = value_index_uint32(app->haptic, haptic_value, 2); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, haptic_text[value_index]); + + // Sound FX ON/OFF + item = variable_item_list_add( + app->variable_item_list, "Sound FX:", 2, camera_suite_scene_settings_set_speaker, app); + value_index = value_index_uint32(app->speaker, speaker_value, 2); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, speaker_text[value_index]); + + // LED FX ON/OFF + item = variable_item_list_add( + app->variable_item_list, "LED FX:", 2, camera_suite_scene_settings_set_led, app); + value_index = value_index_uint32(app->led, led_value, 2); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, led_text[value_index]); + + view_dispatcher_switch_to_view(app->view_dispatcher, CameraSuiteViewIdSettings); +} + +bool camera_suite_scene_settings_on_event(void* context, SceneManagerEvent event) { + CameraSuite* app = context; + UNUSED(app); + bool consumed = false; + if(event.type == SceneManagerEventTypeCustom) { + } + return consumed; +} + +void camera_suite_scene_settings_on_exit(void* context) { + CameraSuite* app = context; + variable_item_list_set_selected_item(app->variable_item_list, 0); + variable_item_list_reset(app->variable_item_list); +} \ No newline at end of file diff --git a/applications/external/camera-suite/scenes/camera_suite_scene_start.c b/applications/external/camera-suite/scenes/camera_suite_scene_start.c new file mode 100644 index 000000000..9f128b761 --- /dev/null +++ b/applications/external/camera-suite/scenes/camera_suite_scene_start.c @@ -0,0 +1,55 @@ +#include "../camera-suite.h" +#include "../helpers/camera_suite_custom_event.h" +#include "../views/camera_suite_view_start.h" + +void camera_suite_scene_start_callback(CameraSuiteCustomEvent event, void* context) { + furi_assert(context); + CameraSuite* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, event); +} + +void camera_suite_scene_start_on_enter(void* context) { + furi_assert(context); + CameraSuite* app = context; + camera_suite_view_start_set_callback( + app->camera_suite_view_start, camera_suite_scene_start_callback, app); + view_dispatcher_switch_to_view(app->view_dispatcher, CameraSuiteViewIdStartscreen); +} + +bool camera_suite_scene_start_on_event(void* context, SceneManagerEvent event) { + CameraSuite* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + switch(event.event) { + case CameraSuiteCustomEventStartLeft: + case CameraSuiteCustomEventStartRight: + break; + case CameraSuiteCustomEventStartUp: + case CameraSuiteCustomEventStartDown: + break; + case CameraSuiteCustomEventStartOk: + scene_manager_next_scene(app->scene_manager, CameraSuiteSceneMenu); + consumed = true; + break; + case CameraSuiteCustomEventStartBack: + notification_message(app->notification, &sequence_reset_red); + notification_message(app->notification, &sequence_reset_green); + notification_message(app->notification, &sequence_reset_blue); + if(!scene_manager_search_and_switch_to_previous_scene( + app->scene_manager, CameraSuiteSceneStart)) { + scene_manager_stop(app->scene_manager); + view_dispatcher_stop(app->view_dispatcher); + } + consumed = true; + break; + } + } + + return consumed; +} + +void camera_suite_scene_start_on_exit(void* context) { + CameraSuite* app = context; + UNUSED(app); +} \ No newline at end of file diff --git a/applications/external/camera-suite/scenes/camera_suite_scene_style_1.c b/applications/external/camera-suite/scenes/camera_suite_scene_style_1.c new file mode 100644 index 000000000..1341c6173 --- /dev/null +++ b/applications/external/camera-suite/scenes/camera_suite_scene_style_1.c @@ -0,0 +1,52 @@ +#include "../camera-suite.h" +#include "../helpers/camera_suite_custom_event.h" +#include "../views/camera_suite_view_style_1.h" + +static void camera_suite_view_style_1_callback(CameraSuiteCustomEvent event, void* context) { + furi_assert(context); + CameraSuite* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, event); +} + +void camera_suite_scene_style_1_on_enter(void* context) { + furi_assert(context); + CameraSuite* app = context; + camera_suite_view_style_1_set_callback( + app->camera_suite_view_style_1, camera_suite_view_style_1_callback, app); + view_dispatcher_switch_to_view(app->view_dispatcher, CameraSuiteViewIdScene1); +} + +bool camera_suite_scene_style_1_on_event(void* context, SceneManagerEvent event) { + CameraSuite* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + switch(event.event) { + case CameraSuiteCustomEventSceneStyle1Left: + case CameraSuiteCustomEventSceneStyle1Right: + case CameraSuiteCustomEventSceneStyle1Up: + case CameraSuiteCustomEventSceneStyle1Down: + case CameraSuiteCustomEventSceneStyle1Ok: + // Do nothing. + break; + case CameraSuiteCustomEventSceneStyle1Back: + notification_message(app->notification, &sequence_reset_red); + notification_message(app->notification, &sequence_reset_green); + notification_message(app->notification, &sequence_reset_blue); + if(!scene_manager_search_and_switch_to_previous_scene( + app->scene_manager, CameraSuiteSceneMenu)) { + scene_manager_stop(app->scene_manager); + view_dispatcher_stop(app->view_dispatcher); + } + consumed = true; + break; + } + } + + return consumed; +} + +void camera_suite_scene_style_1_on_exit(void* context) { + CameraSuite* app = context; + UNUSED(app); +} diff --git a/applications/external/camera-suite/scenes/camera_suite_scene_style_2.c b/applications/external/camera-suite/scenes/camera_suite_scene_style_2.c new file mode 100644 index 000000000..df8c875ba --- /dev/null +++ b/applications/external/camera-suite/scenes/camera_suite_scene_style_2.c @@ -0,0 +1,54 @@ +#include "../camera-suite.h" +#include "../helpers/camera_suite_custom_event.h" +#include "../helpers/camera_suite_haptic.h" +#include "../helpers/camera_suite_led.h" +#include "../views/camera_suite_view_style_2.h" + +void camera_suite_view_style_2_callback(CameraSuiteCustomEvent event, void* context) { + furi_assert(context); + CameraSuite* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, event); +} + +void camera_suite_scene_style_2_on_enter(void* context) { + furi_assert(context); + CameraSuite* app = context; + camera_suite_view_style_2_set_callback( + app->camera_suite_view_style_2, camera_suite_view_style_2_callback, app); + view_dispatcher_switch_to_view(app->view_dispatcher, CameraSuiteViewIdScene2); +} + +bool camera_suite_scene_style_2_on_event(void* context, SceneManagerEvent event) { + CameraSuite* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + switch(event.event) { + case CameraSuiteCustomEventSceneStyle2Left: + case CameraSuiteCustomEventSceneStyle2Right: + case CameraSuiteCustomEventSceneStyle2Up: + case CameraSuiteCustomEventSceneStyle2Down: + case CameraSuiteCustomEventSceneStyle2Ok: + // Do nothing. + break; + case CameraSuiteCustomEventSceneStyle2Back: + notification_message(app->notification, &sequence_reset_red); + notification_message(app->notification, &sequence_reset_green); + notification_message(app->notification, &sequence_reset_blue); + if(!scene_manager_search_and_switch_to_previous_scene( + app->scene_manager, CameraSuiteSceneMenu)) { + scene_manager_stop(app->scene_manager); + view_dispatcher_stop(app->view_dispatcher); + } + consumed = true; + break; + } + } + + return consumed; +} + +void camera_suite_scene_style_2_on_exit(void* context) { + CameraSuite* app = context; + UNUSED(app); +} diff --git a/applications/external/camera-suite/views/camera_suite_view_guide.c b/applications/external/camera-suite/views/camera_suite_view_guide.c new file mode 100644 index 000000000..f27047d47 --- /dev/null +++ b/applications/external/camera-suite/views/camera_suite_view_guide.c @@ -0,0 +1,120 @@ +#include "../camera-suite.h" +#include +#include +#include +#include +#include + +struct CameraSuiteViewGuide { + View* view; + CameraSuiteViewGuideCallback callback; + void* context; +}; + +typedef struct { + int some_value; +} CameraSuiteViewGuideModel; + +void camera_suite_view_guide_set_callback( + CameraSuiteViewGuide* instance, + CameraSuiteViewGuideCallback callback, + void* context) { + furi_assert(instance); + furi_assert(callback); + instance->callback = callback; + instance->context = context; +} + +void camera_suite_view_guide_draw(Canvas* canvas, CameraSuiteViewGuideModel* model) { + UNUSED(model); + canvas_clear(canvas); + canvas_set_color(canvas, ColorBlack); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 0, 0, AlignLeft, AlignTop, "Guide"); + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned(canvas, 0, 12, AlignLeft, AlignTop, "Left = Toggle Invert"); + canvas_draw_str_aligned(canvas, 0, 22, AlignLeft, AlignTop, "Right = Toggle Dithering"); + canvas_draw_str_aligned(canvas, 0, 32, AlignLeft, AlignTop, "Up = Contrast Up"); + canvas_draw_str_aligned(canvas, 0, 42, AlignLeft, AlignTop, "Down = Contrast Down"); + // TODO: Possibly update to take picture instead. + canvas_draw_str_aligned(canvas, 0, 52, AlignLeft, AlignTop, "Center = Toggle Dither Type"); +} + +static void camera_suite_view_guide_model_init(CameraSuiteViewGuideModel* const model) { + model->some_value = 1; +} + +bool camera_suite_view_guide_input(InputEvent* event, void* context) { + furi_assert(context); + CameraSuiteViewGuide* instance = context; + if(event->type == InputTypeRelease) { + switch(event->key) { + case InputKeyBack: + with_view_model( + instance->view, + CameraSuiteViewGuideModel * model, + { + UNUSED(model); + instance->callback(CameraSuiteCustomEventSceneGuideBack, instance->context); + }, + true); + break; + case InputKeyLeft: + case InputKeyRight: + case InputKeyUp: + case InputKeyDown: + case InputKeyOk: + case InputKeyMAX: + // Do nothing. + break; + } + } + return true; +} + +void camera_suite_view_guide_exit(void* context) { + furi_assert(context); +} + +void camera_suite_view_guide_enter(void* context) { + furi_assert(context); + CameraSuiteViewGuide* instance = (CameraSuiteViewGuide*)context; + with_view_model( + instance->view, + CameraSuiteViewGuideModel * model, + { camera_suite_view_guide_model_init(model); }, + true); +} + +CameraSuiteViewGuide* camera_suite_view_guide_alloc() { + CameraSuiteViewGuide* instance = malloc(sizeof(CameraSuiteViewGuide)); + instance->view = view_alloc(); + view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(CameraSuiteViewGuideModel)); + view_set_context(instance->view, instance); // furi_assert crashes in events without this + view_set_draw_callback(instance->view, (ViewDrawCallback)camera_suite_view_guide_draw); + view_set_input_callback(instance->view, camera_suite_view_guide_input); + view_set_enter_callback(instance->view, camera_suite_view_guide_enter); + view_set_exit_callback(instance->view, camera_suite_view_guide_exit); + + with_view_model( + instance->view, + CameraSuiteViewGuideModel * model, + { camera_suite_view_guide_model_init(model); }, + true); + + return instance; +} + +void camera_suite_view_guide_free(CameraSuiteViewGuide* instance) { + furi_assert(instance); + + with_view_model( + instance->view, CameraSuiteViewGuideModel * model, { UNUSED(model); }, true); + view_free(instance->view); + free(instance); +} + +View* camera_suite_view_guide_get_view(CameraSuiteViewGuide* instance) { + furi_assert(instance); + return instance->view; +} diff --git a/applications/external/camera-suite/views/camera_suite_view_guide.h b/applications/external/camera-suite/views/camera_suite_view_guide.h new file mode 100644 index 000000000..cd78d4b01 --- /dev/null +++ b/applications/external/camera-suite/views/camera_suite_view_guide.h @@ -0,0 +1,19 @@ +#pragma once + +#include +#include "../helpers/camera_suite_custom_event.h" + +typedef struct CameraSuiteViewGuide CameraSuiteViewGuide; + +typedef void (*CameraSuiteViewGuideCallback)(CameraSuiteCustomEvent event, void* context); + +void camera_suite_view_guide_set_callback( + CameraSuiteViewGuide* camera_suite_view_guide, + CameraSuiteViewGuideCallback callback, + void* context); + +View* camera_suite_view_guide_get_view(CameraSuiteViewGuide* camera_suite_static); + +CameraSuiteViewGuide* camera_suite_view_guide_alloc(); + +void camera_suite_view_guide_free(CameraSuiteViewGuide* camera_suite_static); \ No newline at end of file diff --git a/applications/external/camera-suite/views/camera_suite_view_start.c b/applications/external/camera-suite/views/camera_suite_view_start.c new file mode 100644 index 000000000..dbb374cbf --- /dev/null +++ b/applications/external/camera-suite/views/camera_suite_view_start.c @@ -0,0 +1,126 @@ +#include "../camera-suite.h" +#include +#include +#include +#include + +struct CameraSuiteViewStart { + View* view; + CameraSuiteViewStartCallback callback; + void* context; +}; + +typedef struct { + int some_value; +} CameraSuiteViewStartModel; + +void camera_suite_view_start_set_callback( + CameraSuiteViewStart* instance, + CameraSuiteViewStartCallback callback, + void* context) { + furi_assert(instance); + furi_assert(callback); + instance->callback = callback; + instance->context = context; +} + +void camera_suite_view_start_draw(Canvas* canvas, CameraSuiteViewStartModel* model) { + UNUSED(model); + canvas_clear(canvas); + canvas_set_color(canvas, ColorBlack); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 64, 10, AlignCenter, AlignTop, "Camera Suite"); + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned(canvas, 64, 22, AlignCenter, AlignTop, "Flipper Zero"); + canvas_draw_str_aligned(canvas, 64, 32, AlignCenter, AlignTop, "ESP32 CAM"); + elements_button_center(canvas, "Start"); +} + +static void camera_suite_view_start_model_init(CameraSuiteViewStartModel* const model) { + model->some_value = 1; +} + +bool camera_suite_view_start_input(InputEvent* event, void* context) { + furi_assert(context); + CameraSuiteViewStart* instance = context; + if(event->type == InputTypeRelease) { + switch(event->key) { + case InputKeyBack: + // Exit application. + with_view_model( + instance->view, + CameraSuiteViewStartModel * model, + { + UNUSED(model); + instance->callback(CameraSuiteCustomEventStartBack, instance->context); + }, + true); + break; + case InputKeyOk: + // Start the application. + with_view_model( + instance->view, + CameraSuiteViewStartModel * model, + { + UNUSED(model); + instance->callback(CameraSuiteCustomEventStartOk, instance->context); + }, + true); + break; + case InputKeyMAX: + case InputKeyLeft: + case InputKeyRight: + case InputKeyUp: + case InputKeyDown: + // Do nothing. + break; + } + } + return true; +} + +void camera_suite_view_start_exit(void* context) { + furi_assert(context); +} + +void camera_suite_view_start_enter(void* context) { + furi_assert(context); + CameraSuiteViewStart* instance = (CameraSuiteViewStart*)context; + with_view_model( + instance->view, + CameraSuiteViewStartModel * model, + { camera_suite_view_start_model_init(model); }, + true); +} + +CameraSuiteViewStart* camera_suite_view_start_alloc() { + CameraSuiteViewStart* instance = malloc(sizeof(CameraSuiteViewStart)); + instance->view = view_alloc(); + view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(CameraSuiteViewStartModel)); + // furi_assert crashes in events without this + view_set_context(instance->view, instance); + view_set_draw_callback(instance->view, (ViewDrawCallback)camera_suite_view_start_draw); + view_set_input_callback(instance->view, camera_suite_view_start_input); + + with_view_model( + instance->view, + CameraSuiteViewStartModel * model, + { camera_suite_view_start_model_init(model); }, + true); + + return instance; +} + +void camera_suite_view_start_free(CameraSuiteViewStart* instance) { + furi_assert(instance); + + with_view_model( + instance->view, CameraSuiteViewStartModel * model, { UNUSED(model); }, true); + view_free(instance->view); + free(instance); +} + +View* camera_suite_view_start_get_view(CameraSuiteViewStart* instance) { + furi_assert(instance); + return instance->view; +} diff --git a/applications/external/camera-suite/views/camera_suite_view_start.h b/applications/external/camera-suite/views/camera_suite_view_start.h new file mode 100644 index 000000000..e991cce92 --- /dev/null +++ b/applications/external/camera-suite/views/camera_suite_view_start.h @@ -0,0 +1,19 @@ +#pragma once + +#include +#include "../helpers/camera_suite_custom_event.h" + +typedef struct CameraSuiteViewStart CameraSuiteViewStart; + +typedef void (*CameraSuiteViewStartCallback)(CameraSuiteCustomEvent event, void* context); + +void camera_suite_view_start_set_callback( + CameraSuiteViewStart* camera_suite_view_start, + CameraSuiteViewStartCallback callback, + void* context); + +View* camera_suite_view_start_get_view(CameraSuiteViewStart* camera_suite_static); + +CameraSuiteViewStart* camera_suite_view_start_alloc(); + +void camera_suite_view_start_free(CameraSuiteViewStart* camera_suite_static); \ No newline at end of file diff --git a/applications/external/camera-suite/views/camera_suite_view_style_1.c b/applications/external/camera-suite/views/camera_suite_view_style_1.c new file mode 100644 index 000000000..34aeaf272 --- /dev/null +++ b/applications/external/camera-suite/views/camera_suite_view_style_1.c @@ -0,0 +1,401 @@ +#include "../camera-suite.h" +#include +#include +#include +#include +#include +#include "../helpers/camera_suite_haptic.h" +#include "../helpers/camera_suite_speaker.h" +#include "../helpers/camera_suite_led.h" + +static CameraSuiteViewStyle1* current_instance = NULL; +// Dithering type: +// 0 = Floyd Steinberg (default) +// 1 = Atkinson +static int current_dithering = 0; + +struct CameraSuiteViewStyle1 { + CameraSuiteViewStyle1Callback callback; + FuriStreamBuffer* rx_stream; + FuriThread* worker_thread; + View* view; + void* context; +}; + +void camera_suite_view_style_1_set_callback( + CameraSuiteViewStyle1* instance, + CameraSuiteViewStyle1Callback callback, + void* context) { + furi_assert(instance); + furi_assert(callback); + instance->callback = callback; + instance->context = context; +} + +static void camera_suite_view_style_1_draw(Canvas* canvas, UartDumpModel* model) { + // Clear the screen. + canvas_set_color(canvas, ColorBlack); + + // Draw the frame. + canvas_draw_frame(canvas, 0, 0, FRAME_WIDTH, FRAME_HEIGHT); + + CameraSuite* app = current_instance->context; + + // Draw the pixels with rotation. + for(size_t p = 0; p < FRAME_BUFFER_LENGTH; ++p) { + uint8_t x = p % ROW_BUFFER_LENGTH; // 0 .. 15 + uint8_t y = p / ROW_BUFFER_LENGTH; // 0 .. 63 + + // Apply rotation + int16_t rotated_x, rotated_y; + switch(app->orientation) { + case 1: // 90 degrees + rotated_x = y; + rotated_y = FRAME_WIDTH - 1 - x; + break; + case 2: // 180 degrees + rotated_x = FRAME_WIDTH - 1 - x; + rotated_y = FRAME_HEIGHT - 1 - y; + break; + case 3: // 270 degrees + rotated_x = FRAME_HEIGHT - 1 - y; + rotated_y = x; + break; + case 0: // 0 degrees + default: + rotated_x = x; + rotated_y = y; + break; + } + + for(uint8_t i = 0; i < 8; ++i) { + if((model->pixels[p] & (1 << i)) != 0) { + // Adjust the coordinates based on the new screen dimensions + uint16_t screen_x, screen_y; + switch(app->orientation) { + case 1: // 90 degrees + screen_x = rotated_x; + screen_y = FRAME_HEIGHT - 8 + (rotated_y * 8) + i; + break; + case 2: // 180 degrees + screen_x = FRAME_WIDTH - 8 + (rotated_x * 8) + i; + screen_y = FRAME_HEIGHT - 1 - rotated_y; + break; + case 3: // 270 degrees + screen_x = FRAME_WIDTH - 1 - rotated_x; + screen_y = rotated_y * 8 + i; + break; + case 0: // 0 degrees + default: + screen_x = rotated_x * 8 + i; + screen_y = rotated_y; + break; + } + canvas_draw_dot(canvas, screen_x, screen_y); + } + } + } + // Draw the guide if the camera is not initialized. + if(!model->initialized) { + canvas_draw_icon(canvas, 74, 16, &I_DolphinCommon_56x48); + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 8, 12, "Connect the ESP32-CAM"); + canvas_draw_str(canvas, 20, 24, "VCC - 3V3"); + canvas_draw_str(canvas, 20, 34, "GND - GND"); + canvas_draw_str(canvas, 20, 44, "U0R - TX"); + canvas_draw_str(canvas, 20, 54, "U0T - RX"); + } +} + +static void camera_suite_view_style_1_model_init(UartDumpModel* const model) { + for(size_t i = 0; i < FRAME_BUFFER_LENGTH; i++) { + model->pixels[i] = 0; + } +} + +static bool camera_suite_view_style_1_input(InputEvent* event, void* context) { + furi_assert(context); + CameraSuiteViewStyle1* instance = context; + if(event->type == InputTypeRelease) { + switch(event->key) { + default: // Stop all sounds, reset the LED. + with_view_model( + instance->view, + UartDumpModel * model, + { + UNUSED(model); + camera_suite_play_bad_bump(instance->context); + camera_suite_stop_all_sound(instance->context); + camera_suite_led_set_rgb(instance->context, 0, 0, 0); + }, + true); + break; + } + // Send `data` to the ESP32-CAM + } else if(event->type == InputTypePress) { + uint8_t data[1]; + switch(event->key) { + case InputKeyBack: + // Stop the camera stream. + data[0] = 's'; + // Go back to the main menu. + with_view_model( + instance->view, + UartDumpModel * model, + { + UNUSED(model); + instance->callback(CameraSuiteCustomEventSceneStyle1Back, instance->context); + }, + true); + break; + case InputKeyLeft: + // Camera: Invert. + data[0] = '<'; + with_view_model( + instance->view, + UartDumpModel * model, + { + UNUSED(model); + camera_suite_play_happy_bump(instance->context); + camera_suite_play_input_sound(instance->context); + camera_suite_led_set_rgb(instance->context, 0, 0, 255); + instance->callback(CameraSuiteCustomEventSceneStyle1Left, instance->context); + }, + true); + break; + case InputKeyRight: + // Camera: Enable/disable dithering. + data[0] = '>'; + with_view_model( + instance->view, + UartDumpModel * model, + { + UNUSED(model); + camera_suite_play_happy_bump(instance->context); + camera_suite_play_input_sound(instance->context); + camera_suite_led_set_rgb(instance->context, 0, 0, 255); + instance->callback(CameraSuiteCustomEventSceneStyle1Right, instance->context); + }, + true); + break; + case InputKeyUp: + // Camera: Increase contrast. + data[0] = 'C'; + with_view_model( + instance->view, + UartDumpModel * model, + { + UNUSED(model); + camera_suite_play_happy_bump(instance->context); + camera_suite_play_input_sound(instance->context); + camera_suite_led_set_rgb(instance->context, 0, 0, 255); + instance->callback(CameraSuiteCustomEventSceneStyle1Up, instance->context); + }, + true); + break; + case InputKeyDown: + // Camera: Reduce contrast. + data[0] = 'c'; + with_view_model( + instance->view, + UartDumpModel * model, + { + UNUSED(model); + camera_suite_play_happy_bump(instance->context); + camera_suite_play_input_sound(instance->context); + camera_suite_led_set_rgb(instance->context, 0, 0, 255); + instance->callback(CameraSuiteCustomEventSceneStyle1Down, instance->context); + }, + true); + break; + case InputKeyOk: + if(current_dithering == 0) { + data[0] = 'd'; // Update to Floyd Steinberg dithering. + current_dithering = 1; + } else { + data[0] = 'D'; // Update to Atkinson dithering. + current_dithering = 0; + } + with_view_model( + instance->view, + UartDumpModel * model, + { + UNUSED(model); + camera_suite_play_happy_bump(instance->context); + camera_suite_play_input_sound(instance->context); + camera_suite_led_set_rgb(instance->context, 0, 0, 255); + instance->callback(CameraSuiteCustomEventSceneStyle1Ok, instance->context); + }, + true); + break; + case InputKeyMAX: + break; + } + // Send `data` to the ESP32-CAM + furi_hal_uart_tx(FuriHalUartIdUSART1, data, 1); + } + return true; +} + +static void camera_suite_view_style_1_exit(void* context) { + furi_assert(context); +} + +static void camera_suite_view_style_1_enter(void* context) { + // Check `context` for null. If it is null, abort program, else continue. + furi_assert(context); + + // Cast `context` to `CameraSuiteViewStyle1*` and store it in `instance`. + CameraSuiteViewStyle1* instance = (CameraSuiteViewStyle1*)context; + + // Assign the current instance to the global variable + current_instance = instance; + + uint8_t data[1]; + data[0] = 'S'; // Uppercase `S` to start the camera + // Send `data` to the ESP32-CAM + furi_hal_uart_tx(FuriHalUartIdUSART1, data, 1); + + with_view_model( + instance->view, + UartDumpModel * model, + { camera_suite_view_style_1_model_init(model); }, + true); +} + +static void camera_on_irq_cb(UartIrqEvent uartIrqEvent, uint8_t data, void* context) { + // Check `context` for null. If it is null, abort program, else continue. + furi_assert(context); + + // Cast `context` to `CameraSuiteViewStyle1*` and store it in `instance`. + CameraSuiteViewStyle1* instance = context; + + // If `uartIrqEvent` is `UartIrqEventRXNE`, send the data to the + // `rx_stream` and set the `WorkerEventRx` flag. + if(uartIrqEvent == UartIrqEventRXNE) { + furi_stream_buffer_send(instance->rx_stream, &data, 1, 0); + furi_thread_flags_set(furi_thread_get_id(instance->worker_thread), WorkerEventRx); + } +} + +static void process_ringbuffer(UartDumpModel* model, uint8_t byte) { + // First char has to be 'Y' in the buffer. + if(model->ringbuffer_index == 0 && byte != 'Y') { + return; + } + + // Second char has to be ':' in the buffer or reset. + if(model->ringbuffer_index == 1 && byte != ':') { + model->ringbuffer_index = 0; + process_ringbuffer(model, byte); + return; + } + + // Assign current byte to the ringbuffer. + model->row_ringbuffer[model->ringbuffer_index] = byte; + // Increment the ringbuffer index. + ++model->ringbuffer_index; + + // Let's wait 'till the buffer fills. + if(model->ringbuffer_index < RING_BUFFER_LENGTH) { + return; + } + + // Flush the ringbuffer to the framebuffer. + model->ringbuffer_index = 0; // Reset the ringbuffer + model->initialized = true; // Established the connection successfully. + size_t row_start_index = + model->row_ringbuffer[2] * ROW_BUFFER_LENGTH; // Third char will determine the row number + + if(row_start_index > LAST_ROW_INDEX) { // Failsafe + row_start_index = 0; + } + + for(size_t i = 0; i < ROW_BUFFER_LENGTH; ++i) { + model->pixels[row_start_index + i] = + model->row_ringbuffer[i + 3]; // Writing the remaining 16 bytes into the frame buffer + } +} + +static int32_t camera_worker(void* context) { + furi_assert(context); + CameraSuiteViewStyle1* instance = context; + + while(1) { + uint32_t events = + furi_thread_flags_wait(WORKER_EVENTS_MASK, FuriFlagWaitAny, FuriWaitForever); + furi_check((events & FuriFlagError) == 0); + + if(events & WorkerEventStop) { + break; + } else if(events & WorkerEventRx) { + size_t length = 0; + do { + size_t intended_data_size = 64; + uint8_t data[intended_data_size]; + length = + furi_stream_buffer_receive(instance->rx_stream, data, intended_data_size, 0); + + if(length > 0) { + with_view_model( + instance->view, + UartDumpModel * model, + { + for(size_t i = 0; i < length; i++) { + process_ringbuffer(model, data[i]); + } + }, + false); + } + } while(length > 0); + } + } + + return 0; +} + +CameraSuiteViewStyle1* camera_suite_view_style_1_alloc() { + CameraSuiteViewStyle1* instance = malloc(sizeof(CameraSuiteViewStyle1)); + + instance->view = view_alloc(); + + instance->rx_stream = furi_stream_buffer_alloc(2048, 1); + + // Set up views + view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(UartDumpModel)); + view_set_context(instance->view, instance); // furi_assert crashes in events without this + view_set_draw_callback(instance->view, (ViewDrawCallback)camera_suite_view_style_1_draw); + view_set_input_callback(instance->view, camera_suite_view_style_1_input); + view_set_enter_callback(instance->view, camera_suite_view_style_1_enter); + view_set_exit_callback(instance->view, camera_suite_view_style_1_exit); + + with_view_model( + instance->view, + UartDumpModel * model, + { camera_suite_view_style_1_model_init(model); }, + true); + + instance->worker_thread = furi_thread_alloc_ex("UsbUartWorker", 2048, camera_worker, instance); + furi_thread_start(instance->worker_thread); + + // Enable uart listener + furi_hal_console_disable(); + furi_hal_uart_set_br(FuriHalUartIdUSART1, 230400); + furi_hal_uart_set_irq_cb(FuriHalUartIdUSART1, camera_on_irq_cb, instance); + + return instance; +} + +void camera_suite_view_style_1_free(CameraSuiteViewStyle1* instance) { + furi_assert(instance); + + with_view_model( + instance->view, UartDumpModel * model, { UNUSED(model); }, true); + view_free(instance->view); + free(instance); +} + +View* camera_suite_view_style_1_get_view(CameraSuiteViewStyle1* instance) { + furi_assert(instance); + return instance->view; +} diff --git a/applications/external/camera-suite/views/camera_suite_view_style_1.h b/applications/external/camera-suite/views/camera_suite_view_style_1.h new file mode 100644 index 000000000..c460c94ba --- /dev/null +++ b/applications/external/camera-suite/views/camera_suite_view_style_1.h @@ -0,0 +1,60 @@ +#include "../helpers/camera_suite_custom_event.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#pragma once + +#define FRAME_WIDTH 128 +#define FRAME_HEIGHT 64 +#define FRAME_BIT_DEPTH 1 +#define FRAME_BUFFER_LENGTH \ + (FRAME_WIDTH * FRAME_HEIGHT * FRAME_BIT_DEPTH / 8) // 128*64*1 / 8 = 1024 +#define ROW_BUFFER_LENGTH (FRAME_WIDTH / 8) // 128/8 = 16 +#define RING_BUFFER_LENGTH (ROW_BUFFER_LENGTH + 3) // ROW_BUFFER_LENGTH + Header => 16 + 3 = 19 +#define LAST_ROW_INDEX (FRAME_BUFFER_LENGTH - ROW_BUFFER_LENGTH) // 1024 - 16 = 1008 + +typedef struct UartDumpModel UartDumpModel; + +struct UartDumpModel { + bool initialized; + int rotation_angle; + uint8_t pixels[FRAME_BUFFER_LENGTH]; + uint8_t ringbuffer_index; + uint8_t row_ringbuffer[RING_BUFFER_LENGTH]; +}; + +typedef struct CameraSuiteViewStyle1 CameraSuiteViewStyle1; + +typedef void (*CameraSuiteViewStyle1Callback)(CameraSuiteCustomEvent event, void* context); + +void camera_suite_view_style_1_set_callback( + CameraSuiteViewStyle1* camera_suite_view_style_1, + CameraSuiteViewStyle1Callback callback, + void* context); + +CameraSuiteViewStyle1* camera_suite_view_style_1_alloc(); + +void camera_suite_view_style_1_free(CameraSuiteViewStyle1* camera_suite_static); + +View* camera_suite_view_style_1_get_view(CameraSuiteViewStyle1* camera_suite_static); + +typedef enum { + // Reserved for StreamBuffer internal event + WorkerEventReserved = (1 << 0), + WorkerEventStop = (1 << 1), + WorkerEventRx = (1 << 2), +} WorkerEventFlags; + +#define WORKER_EVENTS_MASK (WorkerEventStop | WorkerEventRx) diff --git a/applications/external/camera-suite/views/camera_suite_view_style_2.c b/applications/external/camera-suite/views/camera_suite_view_style_2.c new file mode 100644 index 000000000..eefa4d637 --- /dev/null +++ b/applications/external/camera-suite/views/camera_suite_view_style_2.c @@ -0,0 +1,249 @@ +#include "../camera-suite.h" +#include +#include +#include +#include +#include +#include "../helpers/camera_suite_haptic.h" +#include "../helpers/camera_suite_speaker.h" +#include "../helpers/camera_suite_led.h" + +struct CameraSuiteViewStyle2 { + View* view; + CameraSuiteViewStyle2Callback callback; + void* context; +}; + +typedef struct { + int screen_text; +} CameraSuiteViewStyle2Model; + +char buttonText[11][14] = { + "", + "Press Up", + "Press Down", + "Press Left", + "Press Right", + "Press Ok", + "Release Up", + "Release Down", + "Release Left", + "Release Right", + "Release Ok", +}; + +void camera_suite_view_style_2_set_callback( + CameraSuiteViewStyle2* instance, + CameraSuiteViewStyle2Callback callback, + void* context) { + furi_assert(instance); + furi_assert(callback); + instance->callback = callback; + instance->context = context; +} + +void camera_suite_view_style_2_draw(Canvas* canvas, CameraSuiteViewStyle2Model* model) { + canvas_clear(canvas); + canvas_set_color(canvas, ColorBlack); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 0, 10, AlignLeft, AlignTop, "Scene 2: Input Examples"); + canvas_set_font(canvas, FontSecondary); + char* strInput = malloc(15); + strcpy(strInput, buttonText[model->screen_text]); + canvas_draw_str_aligned(canvas, 0, 22, AlignLeft, AlignTop, strInput); + free(strInput); +} + +static void camera_suite_view_style_2_model_init(CameraSuiteViewStyle2Model* const model) { + model->screen_text = 0; +} + +bool camera_suite_view_style_2_input(InputEvent* event, void* context) { + furi_assert(context); + CameraSuiteViewStyle2* instance = context; + if(event->type == InputTypeRelease) { + switch(event->key) { + case InputKeyBack: + with_view_model( + instance->view, + CameraSuiteViewStyle2Model * model, + { + UNUSED(model); + camera_suite_stop_all_sound(instance->context); + instance->callback(CameraSuiteCustomEventSceneStyle2Back, instance->context); + camera_suite_play_long_bump(instance->context); + }, + true); + break; + case InputKeyUp: + with_view_model( + instance->view, + CameraSuiteViewStyle2Model * model, + { + model->screen_text = 6; + camera_suite_play_bad_bump(instance->context); + camera_suite_stop_all_sound(instance->context); + camera_suite_led_set_rgb(instance->context, 255, 0, 255); + }, + true); + break; + case InputKeyDown: + with_view_model( + instance->view, + CameraSuiteViewStyle2Model * model, + { + model->screen_text = 7; + camera_suite_play_bad_bump(instance->context); + camera_suite_stop_all_sound(instance->context); + camera_suite_led_set_rgb(instance->context, 255, 255, 0); + }, + true); + break; + case InputKeyLeft: + with_view_model( + instance->view, + CameraSuiteViewStyle2Model * model, + { + model->screen_text = 8; + camera_suite_play_bad_bump(instance->context); + camera_suite_stop_all_sound(instance->context); + camera_suite_led_set_rgb(instance->context, 0, 255, 255); + }, + true); + break; + case InputKeyRight: + with_view_model( + instance->view, + CameraSuiteViewStyle2Model * model, + { + model->screen_text = 9; + camera_suite_play_bad_bump(instance->context); + camera_suite_stop_all_sound(instance->context); + camera_suite_led_set_rgb(instance->context, 255, 0, 0); + }, + true); + break; + case InputKeyOk: + with_view_model( + instance->view, + CameraSuiteViewStyle2Model * model, + { + model->screen_text = 10; + camera_suite_play_bad_bump(instance->context); + camera_suite_stop_all_sound(instance->context); + camera_suite_led_set_rgb(instance->context, 255, 255, 255); + }, + true); + break; + case InputKeyMAX: + break; + } + } else if(event->type == InputTypePress) { + switch(event->key) { + case InputKeyUp: + with_view_model( + instance->view, + CameraSuiteViewStyle2Model * model, + { + model->screen_text = 1; + camera_suite_play_happy_bump(instance->context); + camera_suite_play_input_sound(instance->context); + }, + true); + break; + case InputKeyDown: + with_view_model( + instance->view, + CameraSuiteViewStyle2Model * model, + { + model->screen_text = 2; + camera_suite_play_happy_bump(instance->context); + camera_suite_play_input_sound(instance->context); + }, + true); + break; + case InputKeyLeft: + with_view_model( + instance->view, + CameraSuiteViewStyle2Model * model, + { + model->screen_text = 3; + camera_suite_play_happy_bump(instance->context); + camera_suite_play_input_sound(instance->context); + }, + true); + break; + case InputKeyRight: + with_view_model( + instance->view, + CameraSuiteViewStyle2Model * model, + { + model->screen_text = 4; + camera_suite_play_happy_bump(instance->context); + camera_suite_play_input_sound(instance->context); + }, + true); + break; + case InputKeyOk: + with_view_model( + instance->view, + CameraSuiteViewStyle2Model * model, + { + model->screen_text = 5; + camera_suite_play_happy_bump(instance->context); + camera_suite_play_input_sound(instance->context); + }, + true); + break; + case InputKeyBack: + case InputKeyMAX: + break; + } + } + + return true; +} + +void camera_suite_view_style_2_exit(void* context) { + furi_assert(context); + CameraSuite* app = context; + camera_suite_stop_all_sound(app); + //camera_suite_led_reset(app); +} + +void camera_suite_view_style_2_enter(void* context) { + furi_assert(context); + dolphin_deed(DolphinDeedPluginStart); +} + +CameraSuiteViewStyle2* camera_suite_view_style_2_alloc() { + CameraSuiteViewStyle2* instance = malloc(sizeof(CameraSuiteViewStyle2)); + instance->view = view_alloc(); + view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(CameraSuiteViewStyle2Model)); + view_set_context(instance->view, instance); + view_set_draw_callback(instance->view, (ViewDrawCallback)camera_suite_view_style_2_draw); + view_set_input_callback(instance->view, camera_suite_view_style_2_input); + //view_set_enter_callback(instance->view, camera_suite_view_style_2_enter); + view_set_exit_callback(instance->view, camera_suite_view_style_2_exit); + + with_view_model( + instance->view, + CameraSuiteViewStyle2Model * model, + { camera_suite_view_style_2_model_init(model); }, + true); + + return instance; +} + +void camera_suite_view_style_2_free(CameraSuiteViewStyle2* instance) { + furi_assert(instance); + + view_free(instance->view); + free(instance); +} + +View* camera_suite_view_style_2_get_view(CameraSuiteViewStyle2* instance) { + furi_assert(instance); + + return instance->view; +} diff --git a/applications/external/camera-suite/views/camera_suite_view_style_2.h b/applications/external/camera-suite/views/camera_suite_view_style_2.h new file mode 100644 index 000000000..d24b895f8 --- /dev/null +++ b/applications/external/camera-suite/views/camera_suite_view_style_2.h @@ -0,0 +1,19 @@ +#pragma once + +#include +#include "../helpers/camera_suite_custom_event.h" + +typedef struct CameraSuiteViewStyle2 CameraSuiteViewStyle2; + +typedef void (*CameraSuiteViewStyle2Callback)(CameraSuiteCustomEvent event, void* context); + +void camera_suite_view_style_2_set_callback( + CameraSuiteViewStyle2* instance, + CameraSuiteViewStyle2Callback callback, + void* context); + +CameraSuiteViewStyle2* camera_suite_view_style_2_alloc(); + +void camera_suite_view_style_2_free(CameraSuiteViewStyle2* camera_suite_static); + +View* camera_suite_view_style_2_get_view(CameraSuiteViewStyle2* boilerpate_static); From 136114890f24f6418c3b1672d8e378902ed4db02 Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Tue, 11 Jul 2023 10:29:45 +0300 Subject: [PATCH 092/364] [FL-3420] Storage: directory sort (#2850) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Storage: sort_data holder in file structure * Storage: sort directory if it possible * make pvs happy * Storage: fail sorting if there is no more space for realloc * Storage: case insensitive sorting. Co-authored-by: ã‚ã --- .../storage/filesystem_api_internal.h | 3 +- .../services/storage/storage_processing.c | 180 +++++++++++++++++- .../services/storage/storage_sorting.h | 22 +++ 3 files changed, 194 insertions(+), 11 deletions(-) create mode 100644 applications/services/storage/storage_sorting.h diff --git a/applications/services/storage/filesystem_api_internal.h b/applications/services/storage/filesystem_api_internal.h index 967d3bb41..52eb6ef13 100644 --- a/applications/services/storage/filesystem_api_internal.h +++ b/applications/services/storage/filesystem_api_internal.h @@ -19,7 +19,8 @@ struct File { FileType type; FS_Error error_id; /**< Standard API error from FS_Error enum */ int32_t internal_error_id; /**< Internal API error value */ - void* storage; + void* storage; /**< Storage API pointer */ + void* sort_data; /**< Sorted file list for directory */ }; /** File api structure diff --git a/applications/services/storage/storage_processing.c b/applications/services/storage/storage_processing.c index e6b426961..eb745cac4 100644 --- a/applications/services/storage/storage_processing.c +++ b/applications/services/storage/storage_processing.c @@ -1,4 +1,5 @@ #include "storage_processing.h" +#include "storage_sorting.h" #include #include @@ -100,7 +101,7 @@ static FS_Error storage_get_data(Storage* app, FuriString* path, StorageData** s /******************* File Functions *******************/ -bool storage_process_file_open( +static bool storage_process_file_open( Storage* app, File* file, FuriString* path, @@ -127,7 +128,7 @@ bool storage_process_file_open( return ret; } -bool storage_process_file_close(Storage* app, File* file) { +static bool storage_process_file_close(Storage* app, File* file) { bool ret = false; StorageData* storage = get_storage_by_file(file, app->storage); @@ -260,9 +261,149 @@ static bool storage_process_file_eof(Storage* app, File* file) { return ret; } +/*************** Sorting Dir Functions ***************/ + +static bool storage_process_dir_rewind_internal(StorageData* storage, File* file); +static bool storage_process_dir_read_internal( + StorageData* storage, + File* file, + FileInfo* fileinfo, + char* name, + const uint16_t name_length); + +static int storage_sorted_file_record_compare(const void* sorted_a, const void* sorted_b) { + SortedFileRecord* a = (SortedFileRecord*)sorted_a; + SortedFileRecord* b = (SortedFileRecord*)sorted_b; + + if(a->info.flags & FSF_DIRECTORY && !(b->info.flags & FSF_DIRECTORY)) + return -1; + else if(!(a->info.flags & FSF_DIRECTORY) && b->info.flags & FSF_DIRECTORY) + return 1; + else + return furi_string_cmpi(a->name, b->name); +} + +static bool storage_sorted_dir_read_next( + SortedDir* dir, + FileInfo* fileinfo, + char* name, + const uint16_t name_length) { + bool ret = false; + + if(dir->index < dir->count) { + SortedFileRecord* sorted = &dir->sorted[dir->index]; + if(fileinfo) { + *fileinfo = sorted->info; + } + if(name) { + strncpy(name, furi_string_get_cstr(sorted->name), name_length); + } + dir->index++; + ret = true; + } + + return ret; +} + +static void storage_sorted_dir_rewind(SortedDir* dir) { + dir->index = 0; +} + +static bool storage_sorted_dir_prepare(SortedDir* dir, StorageData* storage, File* file) { + bool ret = true; + dir->count = 0; + dir->index = 0; + FileInfo info; + char name[SORTING_MAX_NAME_LENGTH + 1] = {0}; + + furi_check(!dir->sorted); + + while(storage_process_dir_read_internal(storage, file, &info, name, SORTING_MAX_NAME_LENGTH)) { + if(memmgr_get_free_heap() < SORTING_MIN_FREE_MEMORY) { + ret = false; + break; + } + + if(dir->count == 0) { //-V547 + dir->sorted = malloc(sizeof(SortedFileRecord)); + } else { + // Our realloc actually mallocs a new block and copies the data over, + // so we need to check if we have enough memory for the new block + size_t size = sizeof(SortedFileRecord) * (dir->count + 1); + if(memmgr_heap_get_max_free_block() >= size) { + dir->sorted = + realloc(dir->sorted, sizeof(SortedFileRecord) * (dir->count + 1)); //-V701 + } else { + ret = false; + break; + } + } + + dir->sorted[dir->count].name = furi_string_alloc_set(name); + dir->sorted[dir->count].info = info; + dir->count++; + } + + return ret; +} + +static void storage_sorted_dir_sort(SortedDir* dir) { + qsort(dir->sorted, dir->count, sizeof(SortedFileRecord), storage_sorted_file_record_compare); +} + +static void storage_sorted_dir_clear_data(SortedDir* dir) { + if(dir->sorted != NULL) { + for(size_t i = 0; i < dir->count; i++) { + furi_string_free(dir->sorted[i].name); + } + + free(dir->sorted); + dir->sorted = NULL; + } +} + +static void storage_file_remove_sort_data(File* file) { + if(file->sort_data != NULL) { + storage_sorted_dir_clear_data(file->sort_data); + free(file->sort_data); + file->sort_data = NULL; + } +} + +static void storage_file_add_sort_data(File* file, StorageData* storage) { + file->sort_data = malloc(sizeof(SortedDir)); + if(storage_sorted_dir_prepare(file->sort_data, storage, file)) { + storage_sorted_dir_sort(file->sort_data); + } else { + storage_file_remove_sort_data(file); + storage_process_dir_rewind_internal(storage, file); + } +} + +static bool storage_file_has_sort_data(File* file) { + return file->sort_data != NULL; +} + /******************* Dir Functions *******************/ -bool storage_process_dir_open(Storage* app, File* file, FuriString* path) { +static bool storage_process_dir_read_internal( + StorageData* storage, + File* file, + FileInfo* fileinfo, + char* name, + const uint16_t name_length) { + bool ret = false; + FS_CALL(storage, dir.read(storage, file, fileinfo, name, name_length)); + return ret; +} + +static bool storage_process_dir_rewind_internal(StorageData* storage, File* file) { + bool ret = false; + FS_CALL(storage, dir.rewind(storage, file)); + return ret; +} + +static bool storage_process_dir_open(Storage* app, File* file, FuriString* path) { bool ret = false; StorageData* storage; file->error_id = storage_get_data(app, path, &storage); @@ -273,13 +414,17 @@ bool storage_process_dir_open(Storage* app, File* file, FuriString* path) { } else { storage_push_storage_file(file, path, storage); FS_CALL(storage, dir.open(storage, file, cstr_path_without_vfs_prefix(path))); + + if(file->error_id == FSE_OK) { + storage_file_add_sort_data(file, storage); + } } } return ret; } -bool storage_process_dir_close(Storage* app, File* file) { +static bool storage_process_dir_close(Storage* app, File* file) { bool ret = false; StorageData* storage = get_storage_by_file(file, app->storage); @@ -287,6 +432,7 @@ bool storage_process_dir_close(Storage* app, File* file) { file->error_id = FSE_INVALID_PARAMETER; } else { FS_CALL(storage, dir.close(storage, file)); + storage_file_remove_sort_data(file); storage_pop_storage_file(file, storage); StorageEvent event = {.type = StorageEventTypeDirClose}; @@ -296,7 +442,7 @@ bool storage_process_dir_close(Storage* app, File* file) { return ret; } -bool storage_process_dir_read( +static bool storage_process_dir_read( Storage* app, File* file, FileInfo* fileinfo, @@ -308,20 +454,34 @@ bool storage_process_dir_read( if(storage == NULL) { file->error_id = FSE_INVALID_PARAMETER; } else { - FS_CALL(storage, dir.read(storage, file, fileinfo, name, name_length)); + if(storage_file_has_sort_data(file)) { + ret = storage_sorted_dir_read_next(file->sort_data, fileinfo, name, name_length); + if(ret) { + file->error_id = FSE_OK; + } else { + file->error_id = FSE_NOT_EXIST; + } + } else { + ret = storage_process_dir_read_internal(storage, file, fileinfo, name, name_length); + } } return ret; } -bool storage_process_dir_rewind(Storage* app, File* file) { +static bool storage_process_dir_rewind(Storage* app, File* file) { bool ret = false; StorageData* storage = get_storage_by_file(file, app->storage); if(storage == NULL) { file->error_id = FSE_INVALID_PARAMETER; } else { - FS_CALL(storage, dir.rewind(storage, file)); + if(storage_file_has_sort_data(file)) { + storage_sorted_dir_rewind(file->sort_data); + ret = true; + } else { + ret = storage_process_dir_rewind_internal(storage, file); + } } return ret; @@ -461,7 +621,7 @@ static FS_Error storage_process_sd_status(Storage* app) { /******************** Aliases processing *******************/ -void storage_process_alias( +static void storage_process_alias( Storage* app, FuriString* path, FuriThreadId thread_id, @@ -505,7 +665,7 @@ void storage_process_alias( /****************** API calls processing ******************/ -void storage_process_message_internal(Storage* app, StorageMessage* message) { +static void storage_process_message_internal(Storage* app, StorageMessage* message) { FuriString* path = NULL; switch(message->command) { diff --git a/applications/services/storage/storage_sorting.h b/applications/services/storage/storage_sorting.h new file mode 100644 index 000000000..9db9d58bf --- /dev/null +++ b/applications/services/storage/storage_sorting.h @@ -0,0 +1,22 @@ +#pragma once +#include + +#define SORTING_MAX_NAME_LENGTH 255 +#define SORTING_MIN_FREE_MEMORY (1024 * 40) + +/** + * @brief Sorted file record, holds file name and info + */ +typedef struct { + FuriString* name; + FileInfo info; +} SortedFileRecord; + +/** + * @brief Sorted directory, holds sorted file records, count and current index + */ +typedef struct { + SortedFileRecord* sorted; + size_t count; + size_t index; +} SortedDir; \ No newline at end of file From 14fc960246a4dccb0a17cdc5fe1254ba5180de29 Mon Sep 17 00:00:00 2001 From: MMX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 11 Jul 2023 11:39:07 +0300 Subject: [PATCH 093/364] Infrared: RCA protocol support (#2823) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * RCA protocol support * Add unit test Co-authored-by: ã‚ã --- .../debug/unit_tests/infrared/infrared_test.c | 12 ++ assets/unit_tests/infrared/test_rca.irtest | 105 ++++++++++++++++++ .../file_formats/InfraredFileFormats.md | 17 +++ lib/infrared/encoder_decoder/infrared.c | 15 +++ lib/infrared/encoder_decoder/infrared.h | 1 + .../rca/infrared_decoder_rca.c | 45 ++++++++ .../rca/infrared_encoder_rca.c | 37 ++++++ .../rca/infrared_protocol_rca.c | 40 +++++++ .../rca/infrared_protocol_rca.h | 30 +++++ .../rca/infrared_protocol_rca_i.h | 30 +++++ 10 files changed, 332 insertions(+) create mode 100644 assets/unit_tests/infrared/test_rca.irtest create mode 100644 lib/infrared/encoder_decoder/rca/infrared_decoder_rca.c create mode 100644 lib/infrared/encoder_decoder/rca/infrared_encoder_rca.c create mode 100644 lib/infrared/encoder_decoder/rca/infrared_protocol_rca.c create mode 100644 lib/infrared/encoder_decoder/rca/infrared_protocol_rca.h create mode 100644 lib/infrared/encoder_decoder/rca/infrared_protocol_rca_i.h diff --git a/applications/debug/unit_tests/infrared/infrared_test.c b/applications/debug/unit_tests/infrared/infrared_test.c index 2bcb95da8..b2acad470 100644 --- a/applications/debug/unit_tests/infrared/infrared_test.c +++ b/applications/debug/unit_tests/infrared/infrared_test.c @@ -425,6 +425,7 @@ MU_TEST(infrared_test_decoder_mixed) { infrared_test_run_decoder(InfraredProtocolSamsung32, 1); infrared_test_run_decoder(InfraredProtocolSIRC, 3); infrared_test_run_decoder(InfraredProtocolKaseikyo, 1); + infrared_test_run_decoder(InfraredProtocolRCA, 1); } MU_TEST(infrared_test_decoder_nec) { @@ -499,6 +500,15 @@ MU_TEST(infrared_test_decoder_kaseikyo) { infrared_test_run_decoder(InfraredProtocolKaseikyo, 6); } +MU_TEST(infrared_test_decoder_rca) { + infrared_test_run_decoder(InfraredProtocolRCA, 1); + infrared_test_run_decoder(InfraredProtocolRCA, 2); + infrared_test_run_decoder(InfraredProtocolRCA, 3); + infrared_test_run_decoder(InfraredProtocolRCA, 4); + infrared_test_run_decoder(InfraredProtocolRCA, 5); + infrared_test_run_decoder(InfraredProtocolRCA, 6); +} + MU_TEST(infrared_test_encoder_decoder_all) { infrared_test_run_encoder_decoder(InfraredProtocolNEC, 1); infrared_test_run_encoder_decoder(InfraredProtocolNECext, 1); @@ -509,6 +519,7 @@ MU_TEST(infrared_test_encoder_decoder_all) { infrared_test_run_encoder_decoder(InfraredProtocolRC5, 1); infrared_test_run_encoder_decoder(InfraredProtocolSIRC, 1); infrared_test_run_encoder_decoder(InfraredProtocolKaseikyo, 1); + infrared_test_run_encoder_decoder(InfraredProtocolRCA, 1); } MU_TEST_SUITE(infrared_test) { @@ -527,6 +538,7 @@ MU_TEST_SUITE(infrared_test) { MU_RUN_TEST(infrared_test_decoder_samsung32); MU_RUN_TEST(infrared_test_decoder_necext1); MU_RUN_TEST(infrared_test_decoder_kaseikyo); + MU_RUN_TEST(infrared_test_decoder_rca); MU_RUN_TEST(infrared_test_decoder_mixed); MU_RUN_TEST(infrared_test_encoder_decoder_all); } diff --git a/assets/unit_tests/infrared/test_rca.irtest b/assets/unit_tests/infrared/test_rca.irtest new file mode 100644 index 000000000..bfe34e8e4 --- /dev/null +++ b/assets/unit_tests/infrared/test_rca.irtest @@ -0,0 +1,105 @@ +Filetype: IR tests file +Version: 1 +# +name: decoder_input1 +type: raw +data: 1000000 3994 3969 552 1945 551 1945 552 1945 551 1945 552 946 551 947 550 1947 548 951 546 1953 542 979 518 1979 517 981 492 1006 491 1006 492 1006 492 1006 492 2005 492 2005 492 1006 492 2005 492 1006 492 2005 492 1006 492 2006 491 +# +name: decoder_expected1 +type: parsed_array +count: 1 +# +protocol: RCA +address: 0F 00 00 00 +command: 54 00 00 00 +repeat: false +# +name: decoder_input2 +type: raw +data: 1000000 4055 3941 605 1891 551 1947 550 1946 551 1946 551 947 551 947 550 1947 549 949 548 1951 545 1977 519 1978 519 1979 518 980 518 980 518 980 518 980 518 1979 518 1979 518 981 517 1979 518 980 518 980 518 980 518 980 518 +# +name: decoder_expected2 +type: parsed_array +count: 1 +# +protocol: RCA +address: 0F 00 00 00 +command: F4 00 00 00 +repeat: false +# +name: decoder_input3 +type: raw +data: 1000000 4027 3970 551 1946 550 1946 551 1946 551 1946 551 946 551 947 550 1947 549 949 547 1951 545 1978 518 1979 492 1006 492 1007 491 1006 492 1006 492 1006 492 2006 491 2006 491 1006 492 2006 491 1007 491 1007 491 1006 492 2006 491 +# +name: decoder_expected3 +type: parsed_array +count: 1 +# +protocol: RCA +address: 0F 00 00 00 +command: 74 00 00 00 +repeat: false +# +name: decoder_input4 +type: raw +data: 1000000 4021 3941 551 1946 550 1946 551 1946 551 1945 552 946 551 947 550 1947 549 950 547 1952 544 1977 519 979 519 1979 518 980 518 980 518 980 518 980 518 1979 518 1979 518 980 518 1979 518 980 518 980 518 1979 518 980 518 +# +name: decoder_expected4 +type: parsed_array +count: 1 +# +protocol: RCA +address: 0F 00 00 00 +command: B4 00 00 00 +repeat: false +# +name: decoder_input5 +type: raw +data: 1000000 4022 3941 551 1946 551 1946 577 1919 578 1919 578 920 552 946 551 1946 550 947 550 1949 547 1952 544 978 520 979 519 980 518 980 518 980 518 980 518 1979 518 1979 518 980 518 1979 518 980 518 980 518 1979 518 1980 517 +# +name: decoder_expected5 +type: parsed_array +count: 1 +# +protocol: RCA +address: 0F 00 00 00 +command: 34 00 00 00 +repeat: false +# +name: decoder_input6 +type: raw +data: 1000000 3995 3968 552 1944 552 1946 550 1946 550 1946 551 947 550 948 549 1947 549 1949 547 1952 544 1978 518 1979 492 2005 492 1006 492 1006 492 1006 492 1006 492 2005 492 2005 492 1006 492 1006 492 1006 492 1006 492 1006 492 1006 492 +# +name: decoder_expected6 +type: parsed_array +count: 1 +# +protocol: RCA +address: 0F 00 00 00 +command: FC 00 00 00 +repeat: false +# +name: encoder_decoder_input1 +type: parsed_array +count: 4 +# +protocol: RCA +address: 0F 00 00 00 +command: 74 00 00 00 +repeat: false +# +protocol: RCA +address: 0F 00 00 00 +command: B4 00 00 00 +repeat: false +# +protocol: RCA +address: 0F 00 00 00 +command: 34 00 00 00 +repeat: false +# +protocol: RCA +address: 0F 00 00 00 +command: FC 00 00 00 +repeat: false +# diff --git a/documentation/file_formats/InfraredFileFormats.md b/documentation/file_formats/InfraredFileFormats.md index 3c0acdcb7..c9b6a9536 100644 --- a/documentation/file_formats/InfraredFileFormats.md +++ b/documentation/file_formats/InfraredFileFormats.md @@ -1,5 +1,22 @@ # Infrared Flipper File Formats + +## Supported protocols list for `type: parsed` +``` + NEC + NECext + NEC42 + NEC42ext + Samsung32 + RC6 + RC5 + RC5X + SIRC + SIRC15 + SIRC20 + Kaseikyo + RCA +``` ## Infrared Remote File Format ### Example diff --git a/lib/infrared/encoder_decoder/infrared.c b/lib/infrared/encoder_decoder/infrared.c index fcfc5da2b..56f2c3f9e 100644 --- a/lib/infrared/encoder_decoder/infrared.c +++ b/lib/infrared/encoder_decoder/infrared.c @@ -11,6 +11,7 @@ #include "rc6/infrared_protocol_rc6.h" #include "sirc/infrared_protocol_sirc.h" #include "kaseikyo/infrared_protocol_kaseikyo.h" +#include "rca/infrared_protocol_rca.h" typedef struct { InfraredAlloc alloc; @@ -127,6 +128,20 @@ static const InfraredEncoderDecoder infrared_encoder_decoder[] = { .free = infrared_encoder_kaseikyo_free}, .get_protocol_variant = infrared_protocol_kaseikyo_get_variant, }, + { + .decoder = + {.alloc = infrared_decoder_rca_alloc, + .decode = infrared_decoder_rca_decode, + .reset = infrared_decoder_rca_reset, + .check_ready = infrared_decoder_rca_check_ready, + .free = infrared_decoder_rca_free}, + .encoder = + {.alloc = infrared_encoder_rca_alloc, + .encode = infrared_encoder_rca_encode, + .reset = infrared_encoder_rca_reset, + .free = infrared_encoder_rca_free}, + .get_protocol_variant = infrared_protocol_rca_get_variant, + }, }; static int infrared_find_index_by_protocol(InfraredProtocol protocol); diff --git a/lib/infrared/encoder_decoder/infrared.h b/lib/infrared/encoder_decoder/infrared.h index 3ab46cbbf..ada449b98 100644 --- a/lib/infrared/encoder_decoder/infrared.h +++ b/lib/infrared/encoder_decoder/infrared.h @@ -33,6 +33,7 @@ typedef enum { InfraredProtocolSIRC15, InfraredProtocolSIRC20, InfraredProtocolKaseikyo, + InfraredProtocolRCA, InfraredProtocolMAX, } InfraredProtocol; diff --git a/lib/infrared/encoder_decoder/rca/infrared_decoder_rca.c b/lib/infrared/encoder_decoder/rca/infrared_decoder_rca.c new file mode 100644 index 000000000..b6d02a38c --- /dev/null +++ b/lib/infrared/encoder_decoder/rca/infrared_decoder_rca.c @@ -0,0 +1,45 @@ +#include "infrared_protocol_rca_i.h" +#include + +InfraredMessage* infrared_decoder_rca_check_ready(void* ctx) { + return infrared_common_decoder_check_ready(ctx); +} + +bool infrared_decoder_rca_interpret(InfraredCommonDecoder* decoder) { + furi_assert(decoder); + + uint32_t* data = (void*)&decoder->data; + + uint8_t address = (*data & 0xF); + uint8_t command = (*data >> 4) & 0xFF; + uint8_t address_inverse = (*data >> 12) & 0xF; + uint8_t command_inverse = (*data >> 16) & 0xFF; + uint8_t inverse_address_inverse = (uint8_t)~address_inverse & 0xF; + uint8_t inverse_command_inverse = (uint8_t)~command_inverse; + + if((command == inverse_command_inverse) && (address == inverse_address_inverse)) { + decoder->message.protocol = InfraredProtocolRCA; + decoder->message.address = address; + decoder->message.command = command; + decoder->message.repeat = false; + return true; + } + + return false; +} + +void* infrared_decoder_rca_alloc(void) { + return infrared_common_decoder_alloc(&infrared_protocol_rca); +} + +InfraredMessage* infrared_decoder_rca_decode(void* decoder, bool level, uint32_t duration) { + return infrared_common_decode(decoder, level, duration); +} + +void infrared_decoder_rca_free(void* decoder) { + infrared_common_decoder_free(decoder); +} + +void infrared_decoder_rca_reset(void* decoder) { + infrared_common_decoder_reset(decoder); +} diff --git a/lib/infrared/encoder_decoder/rca/infrared_encoder_rca.c b/lib/infrared/encoder_decoder/rca/infrared_encoder_rca.c new file mode 100644 index 000000000..f0be4a6a9 --- /dev/null +++ b/lib/infrared/encoder_decoder/rca/infrared_encoder_rca.c @@ -0,0 +1,37 @@ +#include "infrared_protocol_rca_i.h" + +#include + +void infrared_encoder_rca_reset(void* encoder_ptr, const InfraredMessage* message) { + furi_assert(encoder_ptr); + furi_assert(message); + + InfraredCommonEncoder* encoder = encoder_ptr; + infrared_common_encoder_reset(encoder); + + uint32_t* data = (void*)encoder->data; + + uint8_t address = message->address; + uint8_t address_inverse = ~address; + uint8_t command = message->command; + uint8_t command_inverse = ~command; + + *data = address & 0xF; + *data |= command << 4; + *data |= (address_inverse & 0xF) << 12; + *data |= command_inverse << 16; + + encoder->bits_to_encode = encoder->protocol->databit_len[0]; +} + +void* infrared_encoder_rca_alloc(void) { + return infrared_common_encoder_alloc(&infrared_protocol_rca); +} + +void infrared_encoder_rca_free(void* encoder_ptr) { + infrared_common_encoder_free(encoder_ptr); +} + +InfraredStatus infrared_encoder_rca_encode(void* encoder_ptr, uint32_t* duration, bool* level) { + return infrared_common_encode(encoder_ptr, duration, level); +} diff --git a/lib/infrared/encoder_decoder/rca/infrared_protocol_rca.c b/lib/infrared/encoder_decoder/rca/infrared_protocol_rca.c new file mode 100644 index 000000000..8e1e76dbd --- /dev/null +++ b/lib/infrared/encoder_decoder/rca/infrared_protocol_rca.c @@ -0,0 +1,40 @@ +#include "infrared_protocol_rca_i.h" + +const InfraredCommonProtocolSpec infrared_protocol_rca = { + .timings = + { + .preamble_mark = INFRARED_RCA_PREAMBLE_MARK, + .preamble_space = INFRARED_RCA_PREAMBLE_SPACE, + .bit1_mark = INFRARED_RCA_BIT1_MARK, + .bit1_space = INFRARED_RCA_BIT1_SPACE, + .bit0_mark = INFRARED_RCA_BIT0_MARK, + .bit0_space = INFRARED_RCA_BIT0_SPACE, + .preamble_tolerance = INFRARED_RCA_PREAMBLE_TOLERANCE, + .bit_tolerance = INFRARED_RCA_BIT_TOLERANCE, + .silence_time = INFRARED_RCA_SILENCE, + .min_split_time = INFRARED_RCA_MIN_SPLIT_TIME, + }, + .databit_len[0] = 24, + .no_stop_bit = false, + .decode = infrared_common_decode_pdwm, + .encode = infrared_common_encode_pdwm, + .interpret = infrared_decoder_rca_interpret, + .decode_repeat = NULL, + .encode_repeat = NULL, +}; + +static const InfraredProtocolVariant infrared_protocol_variant_rca = { + .name = "RCA", + .address_length = 4, + .command_length = 8, + .frequency = INFRARED_COMMON_CARRIER_FREQUENCY, + .duty_cycle = INFRARED_COMMON_DUTY_CYCLE, + .repeat_count = INFRARED_RCA_REPEAT_COUNT_MIN, +}; + +const InfraredProtocolVariant* infrared_protocol_rca_get_variant(InfraredProtocol protocol) { + if(protocol == InfraredProtocolRCA) + return &infrared_protocol_variant_rca; + else + return NULL; +} diff --git a/lib/infrared/encoder_decoder/rca/infrared_protocol_rca.h b/lib/infrared/encoder_decoder/rca/infrared_protocol_rca.h new file mode 100644 index 000000000..d9cae48e4 --- /dev/null +++ b/lib/infrared/encoder_decoder/rca/infrared_protocol_rca.h @@ -0,0 +1,30 @@ +#pragma once + +#include "../infrared_i.h" + +/*************************************************************************************************** +* RCA protocol description +* https://www.sbprojects.net/knowledge/ir/rca.php +**************************************************************************************************** +* Preamble Preamble Pulse Distance/Width Pause Preamble Preamble +* mark space Modulation up to period repeat repeat +* mark space +* +* 4000 4000 24 bit ...8000 4000 4000 +* __________ _ _ _ _ _ _ _ _ _ _ _ _ _ ___________ +* ____ __________ _ _ _ __ __ __ _ _ __ __ _ _ ________________ ___________ +* +***************************************************************************************************/ + +void* infrared_decoder_rca_alloc(void); +void infrared_decoder_rca_reset(void* decoder); +void infrared_decoder_rca_free(void* decoder); +InfraredMessage* infrared_decoder_rca_check_ready(void* decoder); +InfraredMessage* infrared_decoder_rca_decode(void* decoder, bool level, uint32_t duration); + +void* infrared_encoder_rca_alloc(void); +InfraredStatus infrared_encoder_rca_encode(void* encoder_ptr, uint32_t* duration, bool* level); +void infrared_encoder_rca_reset(void* encoder_ptr, const InfraredMessage* message); +void infrared_encoder_rca_free(void* encoder_ptr); + +const InfraredProtocolVariant* infrared_protocol_rca_get_variant(InfraredProtocol protocol); diff --git a/lib/infrared/encoder_decoder/rca/infrared_protocol_rca_i.h b/lib/infrared/encoder_decoder/rca/infrared_protocol_rca_i.h new file mode 100644 index 000000000..9ec4fe3b1 --- /dev/null +++ b/lib/infrared/encoder_decoder/rca/infrared_protocol_rca_i.h @@ -0,0 +1,30 @@ +#pragma once + +#include "../common/infrared_common_i.h" + +#define INFRARED_RCA_PREAMBLE_MARK 4000 +#define INFRARED_RCA_PREAMBLE_SPACE 4000 +#define INFRARED_RCA_BIT1_MARK 500 +#define INFRARED_RCA_BIT1_SPACE 2000 +#define INFRARED_RCA_BIT0_MARK 500 +#define INFRARED_RCA_BIT0_SPACE 1000 +#define INFRARED_RCA_REPEAT_PERIOD 8000 +#define INFRARED_RCA_SILENCE INFRARED_RCA_REPEAT_PERIOD + +#define INFRARED_RCA_MIN_SPLIT_TIME INFRARED_RCA_REPEAT_PAUSE_MIN +#define INFRARED_RCA_REPEAT_PAUSE_MIN 4000 +#define INFRARED_RCA_REPEAT_PAUSE_MAX 150000 +#define INFRARED_RCA_REPEAT_COUNT_MIN 1 +#define INFRARED_RCA_REPEAT_MARK INFRARED_RCA_PREAMBLE_MARK +#define INFRARED_RCA_REPEAT_SPACE INFRARED_RCA_PREAMBLE_SPACE +#define INFRARED_RCA_PREAMBLE_TOLERANCE 200 // us +#define INFRARED_RCA_BIT_TOLERANCE 120 // us + +extern const InfraredCommonProtocolSpec infrared_protocol_rca; + +bool infrared_decoder_rca_interpret(InfraredCommonDecoder* decoder); +InfraredStatus infrared_decoder_rca_decode_repeat(InfraredCommonDecoder* decoder); +InfraredStatus infrared_encoder_rca_encode_repeat( + InfraredCommonEncoder* encoder, + uint32_t* duration, + bool* level); From bc0722fe259f6d99777aeea60dea55d2a329485a Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 11 Jul 2023 13:40:46 +0300 Subject: [PATCH 094/364] upd nfc maker / badusb fixes by @Willy-JL --- .../external/bad_bt/helpers/ducky_script.c | 8 +- .../nfc_maker/scenes/nfc_maker_scene_result.c | 374 +++++++++--------- .../main/bad_usb/helpers/ducky_script.c | 8 +- 3 files changed, 209 insertions(+), 181 deletions(-) diff --git a/applications/external/bad_bt/helpers/ducky_script.c b/applications/external/bad_bt/helpers/ducky_script.c index 96807f44d..e66031baa 100644 --- a/applications/external/bad_bt/helpers/ducky_script.c +++ b/applications/external/bad_bt/helpers/ducky_script.c @@ -257,8 +257,12 @@ static int32_t ducky_parse_line(BadBtScript* bad_bt, FuriString* line) { } if((key & 0xFF00) != 0) { // It's a modifier key - line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; - key |= ducky_get_keycode(bad_bt, line_tmp, true); + uint32_t offset = ducky_get_command_len(line_tmp) + 1; + // ducky_get_command_len() returns 0 without space, so check for != 1 + if(offset != 1 && line_len > offset) { + // It's also a key combination + key |= ducky_get_keycode(bad_bt, line_tmp + offset, true); + } } furi_hal_bt_hid_kb_press(key); furi_delay_ms(bt_timeout); diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_result.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_result.c index 912bf3c9f..38ad1e634 100644 --- a/applications/external/nfc_maker/scenes/nfc_maker_scene_result.c +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_result.c @@ -18,19 +18,21 @@ void nfc_maker_scene_result_on_enter(void* context) { FlipperFormat* file = flipper_format_file_alloc(furi_record_open(RECORD_STORAGE)); FuriString* path = furi_string_alloc(); furi_string_printf(path, NFC_APP_FOLDER "/%s" NFC_APP_EXTENSION, app->name_buf); + + uint32_t pages = 135; + size_t size = pages * 4; + uint8_t* buf = malloc(size); do { if(!flipper_format_file_open_new(file, furi_string_get_cstr(path))) break; - uint32_t pages = 42; - size_t size = pages * 4; - uint8_t* buf = malloc(size); - if(!flipper_format_write_header_cstr(file, "Flipper NFC device", 3)) break; - if(!flipper_format_write_string_cstr(file, "Device type", "NTAG203")) break; + if(!flipper_format_write_string_cstr(file, "Device type", "NTAG215")) break; // Serial number - buf[0] = 0x04; - furi_hal_random_fill_buf(&buf[1], 8); + size_t i = 0; + buf[i++] = 0x04; + furi_hal_random_fill_buf(&buf[i], 8); + i += 8; uint8_t uid[7]; memcpy(&uid[0], &buf[0], 3); memcpy(&uid[3], &buf[4], 4); @@ -44,7 +46,7 @@ void nfc_maker_scene_result_on_enter(void* context) { "Signature", "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00")) break; - if(!flipper_format_write_string_cstr(file, "Mifare version", "00 00 00 00 00 00 00 00")) + if(!flipper_format_write_string_cstr(file, "Mifare version", "00 04 04 02 01 00 11 03")) break; if(!flipper_format_write_string_cstr(file, "Counter 0", "0")) break; @@ -56,233 +58,224 @@ void nfc_maker_scene_result_on_enter(void* context) { if(!flipper_format_write_uint32(file, "Pages total", &pages, 1)) break; // Static data - buf[9] = 0x48; // Internal - buf[10] = 0x00; // Lock bytes - buf[11] = 0x00; // ... - buf[12] = 0xE1; // Capability container - buf[13] = 0x10; // ... - buf[14] = 0x12; // ... - buf[15] = 0x00; // ... - buf[16] = 0x01; // ... - buf[17] = 0x03; // ... - buf[18] = 0xA0; // ... - buf[19] = 0x10; // ... - buf[20] = 0x44; // ... - buf[21] = 0x03; // Message flags + buf[i++] = 0x48; // Internal + buf[i++] = 0x00; // Lock bytes + buf[i++] = 0x00; // ... + buf[i++] = 0xE1; // Capability container + buf[i++] = 0x10; // ... + buf[i++] = 0x3E; // ... + buf[i++] = 0x00; // ... + buf[i++] = 0x03; // Message flags + size_t start = i++; - size_t msg_len = 0; switch(scene_manager_get_scene_state(app->scene_manager, NfcMakerSceneMenu)) { case NfcMakerSceneBluetooth: { - msg_len = 0x2B; + buf[i++] = 0xD2; + buf[i++] = 0x20; + buf[i++] = 0x08; + buf[i++] = 0x61; + buf[i++] = 0x70; - buf[23] = 0xD2; - buf[24] = 0x20; - buf[25] = 0x08; - buf[26] = 0x61; - buf[27] = 0x70; + buf[i++] = 0x70; + buf[i++] = 0x6C; + buf[i++] = 0x69; + buf[i++] = 0x63; - buf[28] = 0x70; - buf[29] = 0x6C; - buf[30] = 0x69; - buf[31] = 0x63; + buf[i++] = 0x61; + buf[i++] = 0x74; + buf[i++] = 0x69; + buf[i++] = 0x6F; - buf[32] = 0x61; - buf[33] = 0x74; - buf[34] = 0x69; - buf[35] = 0x6F; + buf[i++] = 0x6E; + buf[i++] = 0x2F; + buf[i++] = 0x76; + buf[i++] = 0x6E; - buf[36] = 0x6E; - buf[37] = 0x2F; - buf[38] = 0x76; - buf[39] = 0x6E; + buf[i++] = 0x64; + buf[i++] = 0x2E; + buf[i++] = 0x62; + buf[i++] = 0x6C; - buf[40] = 0x64; - buf[41] = 0x2E; - buf[42] = 0x62; - buf[43] = 0x6C; + buf[i++] = 0x75; + buf[i++] = 0x65; + buf[i++] = 0x74; + buf[i++] = 0x6F; - buf[44] = 0x75; - buf[45] = 0x65; - buf[46] = 0x74; - buf[47] = 0x6F; + buf[i++] = 0x6F; + buf[i++] = 0x74; + buf[i++] = 0x68; + buf[i++] = 0x2E; - buf[48] = 0x6F; - buf[49] = 0x74; - buf[50] = 0x68; - buf[51] = 0x2E; + buf[i++] = 0x65; + buf[i++] = 0x70; + buf[i++] = 0x2E; + buf[i++] = 0x6F; - buf[52] = 0x65; - buf[53] = 0x70; - buf[54] = 0x2E; - buf[55] = 0x6F; + buf[i++] = 0x6F; + buf[i++] = 0x62; + buf[i++] = 0x08; + buf[i++] = 0x00; - buf[56] = 0x6F; - buf[57] = 0x62; - buf[58] = 0x08; - buf[59] = 0x00; - - memcpy(&buf[60], app->mac_buf, GAP_MAC_ADDR_SIZE); + memcpy(&buf[i], app->mac_buf, GAP_MAC_ADDR_SIZE); + i += GAP_MAC_ADDR_SIZE; break; } case NfcMakerSceneHttps: { uint8_t data_len = strnlen(app->text_buf, TEXT_INPUT_LEN); - msg_len = data_len + 5; - buf[23] = 0xD1; - buf[24] = 0x01; - buf[25] = data_len + 1; - buf[26] = 0x55; + buf[i++] = 0xD1; + buf[i++] = 0x01; + buf[i++] = data_len + 1; + buf[i++] = 0x55; - buf[27] = 0x04; // Prepend "https://" - memcpy(&buf[28], app->text_buf, data_len); + buf[i++] = 0x04; // Prepend "https://" + memcpy(&buf[i], app->text_buf, data_len); + i += data_len; break; } case NfcMakerSceneMail: { uint8_t data_len = strnlen(app->text_buf, TEXT_INPUT_LEN); - msg_len = data_len + 5; - buf[23] = 0xD1; - buf[24] = 0x01; - buf[25] = data_len + 1; - buf[26] = 0x55; + buf[i++] = 0xD1; + buf[i++] = 0x01; + buf[i++] = data_len + 1; + buf[i++] = 0x55; - buf[27] = 0x06; // Prepend "mailto:" - memcpy(&buf[28], app->text_buf, data_len); + buf[i++] = 0x06; // Prepend "mailto:" + memcpy(&buf[i], app->text_buf, data_len); + i += data_len; break; } case NfcMakerScenePhone: { uint8_t data_len = strnlen(app->text_buf, TEXT_INPUT_LEN); - msg_len = data_len + 5; - buf[23] = 0xD1; - buf[24] = 0x01; - buf[25] = data_len + 1; - buf[26] = 0x55; + buf[i++] = 0xD1; + buf[i++] = 0x01; + buf[i++] = data_len + 1; + buf[i++] = 0x55; - buf[27] = 0x05; // Prepend "tel:" - memcpy(&buf[28], app->text_buf, data_len); + buf[i++] = 0x05; // Prepend "tel:" + memcpy(&buf[i], app->text_buf, data_len); + i += data_len; break; } case NfcMakerSceneText: { uint8_t data_len = strnlen(app->text_buf, TEXT_INPUT_LEN); - msg_len = data_len + 7; - buf[23] = 0xD1; - buf[24] = 0x01; - buf[25] = data_len + 3; - buf[26] = 0x54; + buf[i++] = 0xD1; + buf[i++] = 0x01; + buf[i++] = data_len + 3; + buf[i++] = 0x54; - buf[27] = 0x02; - buf[28] = 0x65; // e - buf[29] = 0x6E; // n - memcpy(&buf[30], app->text_buf, data_len); + buf[i++] = 0x02; + buf[i++] = 0x65; // e + buf[i++] = 0x6E; // n + memcpy(&buf[i], app->text_buf, data_len); + i += data_len; break; } case NfcMakerSceneUrl: { uint8_t data_len = strnlen(app->text_buf, TEXT_INPUT_LEN); - msg_len = data_len + 5; - buf[23] = 0xD1; - buf[24] = 0x01; - buf[25] = data_len + 1; - buf[26] = 0x55; + buf[i++] = 0xD1; + buf[i++] = 0x01; + buf[i++] = data_len + 1; + buf[i++] = 0x55; - buf[27] = 0x00; // No prepend - memcpy(&buf[28], app->text_buf, data_len); + buf[i++] = 0x00; // No prepend + memcpy(&buf[i], app->text_buf, data_len); + i += data_len; break; } case NfcMakerSceneWifi: { uint8_t ssid_len = strnlen(app->text_buf, WIFI_INPUT_LEN); uint8_t pass_len = strnlen(app->pass_buf, WIFI_INPUT_LEN); uint8_t data_len = ssid_len + pass_len; - msg_len = data_len + 73; - buf[23] = 0xD2; - buf[24] = 0x17; - buf[25] = data_len + 47; - buf[26] = 0x61; - buf[27] = 0x70; + buf[i++] = 0xD2; + buf[i++] = 0x17; + buf[i++] = data_len + 47; + buf[i++] = 0x61; + buf[i++] = 0x70; - buf[28] = 0x70; - buf[29] = 0x6C; - buf[30] = 0x69; - buf[31] = 0x63; + buf[i++] = 0x70; + buf[i++] = 0x6C; + buf[i++] = 0x69; + buf[i++] = 0x63; - buf[32] = 0x61; - buf[33] = 0x74; - buf[34] = 0x69; - buf[35] = 0x6F; + buf[i++] = 0x61; + buf[i++] = 0x74; + buf[i++] = 0x69; + buf[i++] = 0x6F; - buf[36] = 0x6E; - buf[37] = 0x2F; - buf[38] = 0x76; - buf[39] = 0x6E; + buf[i++] = 0x6E; + buf[i++] = 0x2F; + buf[i++] = 0x76; + buf[i++] = 0x6E; - buf[40] = 0x64; - buf[41] = 0x2E; - buf[42] = 0x77; - buf[43] = 0x66; + buf[i++] = 0x64; + buf[i++] = 0x2E; + buf[i++] = 0x77; + buf[i++] = 0x66; - buf[44] = 0x61; - buf[45] = 0x2E; - buf[46] = 0x77; - buf[47] = 0x73; + buf[i++] = 0x61; + buf[i++] = 0x2E; + buf[i++] = 0x77; + buf[i++] = 0x73; - buf[48] = 0x63; - buf[49] = 0x10; - buf[50] = 0x0E; - buf[51] = 0x00; + buf[i++] = 0x63; + buf[i++] = 0x10; + buf[i++] = 0x0E; + buf[i++] = 0x00; - buf[52] = data_len + 43; - buf[53] = 0x10; - buf[54] = 0x26; - buf[55] = 0x00; + buf[i++] = data_len + 43; + buf[i++] = 0x10; + buf[i++] = 0x26; + buf[i++] = 0x00; - buf[56] = 0x01; - buf[57] = 0x01; - buf[58] = 0x10; - buf[59] = 0x45; + buf[i++] = 0x01; + buf[i++] = 0x01; + buf[i++] = 0x10; + buf[i++] = 0x45; - buf[60] = 0x00; - buf[61] = ssid_len; - memcpy(&buf[62], app->text_buf, ssid_len); - size_t ssid = 62 + ssid_len; - buf[ssid + 0] = 0x10; - buf[ssid + 1] = 0x03; + buf[i++] = 0x00; + buf[i++] = ssid_len; + memcpy(&buf[i], app->text_buf, ssid_len); + i += ssid_len; + buf[i++] = 0x10; + buf[i++] = 0x03; - buf[ssid + 2] = 0x00; - buf[ssid + 3] = 0x02; - buf[ssid + 4] = 0x00; - buf[ssid + 5] = - scene_manager_get_scene_state(app->scene_manager, NfcMakerSceneWifiAuth); + buf[i++] = 0x00; + buf[i++] = 0x02; + buf[i++] = 0x00; + buf[i++] = scene_manager_get_scene_state(app->scene_manager, NfcMakerSceneWifiAuth); - buf[ssid + 6] = 0x10; - buf[ssid + 7] = 0x0F; - buf[ssid + 8] = 0x00; - buf[ssid + 9] = 0x02; + buf[i++] = 0x10; + buf[i++] = 0x0F; + buf[i++] = 0x00; + buf[i++] = 0x02; - buf[ssid + 10] = 0x00; - buf[ssid + 11] = - scene_manager_get_scene_state(app->scene_manager, NfcMakerSceneWifiEncr); - buf[ssid + 12] = 0x10; - buf[ssid + 13] = 0x27; + buf[i++] = 0x00; + buf[i++] = scene_manager_get_scene_state(app->scene_manager, NfcMakerSceneWifiEncr); + buf[i++] = 0x10; + buf[i++] = 0x27; - buf[ssid + 14] = 0x00; - buf[ssid + 15] = pass_len; - memcpy(&buf[ssid + 16], app->pass_buf, pass_len); - size_t pass = ssid + 16 + pass_len; - buf[pass + 0] = 0x10; - buf[pass + 1] = 0x20; + buf[i++] = 0x00; + buf[i++] = pass_len; + memcpy(&buf[i], app->pass_buf, pass_len); + i += pass_len; + buf[i++] = 0x10; + buf[i++] = 0x20; - buf[pass + 2] = 0x00; - buf[pass + 3] = 0x06; - buf[pass + 4] = 0xFF; - buf[pass + 5] = 0xFF; + buf[i++] = 0x00; + buf[i++] = 0x06; + buf[i++] = 0xFF; + buf[i++] = 0xFF; - buf[pass + 6] = 0xFF; - buf[pass + 7] = 0xFF; - buf[pass + 8] = 0xFF; - buf[pass + 9] = 0xFF; + buf[i++] = 0xFF; + buf[i++] = 0xFF; + buf[i++] = 0xFF; + buf[i++] = 0xFF; break; } @@ -291,15 +284,41 @@ void nfc_maker_scene_result_on_enter(void* context) { } // Message length and terminator - buf[22] = msg_len; - size_t msg_end = 23 + msg_len; - buf[msg_end] = 0xFE; + buf[start] = i - start - 1; + buf[i++] = 0xFE; - // Padding - for(size_t i = msg_end + 1; i < size; i++) { + // Padding until last 5 pages + for(; i < size - 20; i++) { buf[i] = 0x00; } + // Last 5 static pages + buf[i++] = 0x00; + buf[i++] = 0x00; + buf[i++] = 0x00; + buf[i++] = 0xBD; + + buf[i++] = 0x04; + buf[i++] = 0x00; + buf[i++] = 0x00; + buf[i++] = 0xFF; + + buf[i++] = 0x00; + buf[i++] = 0x05; + buf[i++] = 0x00; + buf[i++] = 0x00; + + buf[i++] = 0xFF; + buf[i++] = 0xFF; + buf[i++] = 0xFF; + buf[i++] = 0xFF; + + buf[i++] = 0x00; + buf[i++] = 0x00; + buf[i++] = 0x00; + buf[i++] = 0x00; + + // Write pages char str[16]; bool ok = true; for(size_t page = 0; page < pages; page++) { @@ -311,10 +330,11 @@ void nfc_maker_scene_result_on_enter(void* context) { } if(!ok) break; - free(buf); success = true; } while(false); + free(buf); + furi_string_free(path); flipper_format_free(file); furi_record_close(RECORD_STORAGE); diff --git a/applications/main/bad_usb/helpers/ducky_script.c b/applications/main/bad_usb/helpers/ducky_script.c index f194178a0..6ba55ab25 100644 --- a/applications/main/bad_usb/helpers/ducky_script.c +++ b/applications/main/bad_usb/helpers/ducky_script.c @@ -198,8 +198,12 @@ static int32_t ducky_parse_line(BadUsbScript* bad_usb, FuriString* line) { } if((key & 0xFF00) != 0) { // It's a modifier key - line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; - key |= ducky_get_keycode(bad_usb, line_tmp, true); + uint32_t offset = ducky_get_command_len(line_tmp) + 1; + // ducky_get_command_len() returns 0 without space, so check for != 1 + if(offset != 1 && line_len > offset) { + // It's also a key combination + key |= ducky_get_keycode(bad_usb, line_tmp + offset, true); + } } furi_hal_hid_kb_press(key); furi_hal_hid_kb_release(key); From 8bccfd6fd8010699a80208121392cc4ccf5ad0d2 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Tue, 11 Jul 2023 14:41:16 +0300 Subject: [PATCH 095/364] [FL-3363] More descriptive error messages for the log command (#2835) * More descriptive error messages for the log command * Log level description improvements * Log help changes Co-authored-by: Aleksandr Kutuzov --- applications/services/cli/cli_commands.c | 41 ++++++++++++++---------- firmware/targets/f18/api_symbols.csv | 4 ++- firmware/targets/f7/api_symbols.csv | 4 ++- furi/core/log.c | 35 ++++++++++++++++++++ furi/core/log.h | 18 +++++++++++ 5 files changed, 83 insertions(+), 19 deletions(-) diff --git a/applications/services/cli/cli_commands.c b/applications/services/cli/cli_commands.c index 3f94deebc..7009e7531 100644 --- a/applications/services/cli/cli_commands.c +++ b/applications/services/cli/cli_commands.c @@ -165,24 +165,23 @@ void cli_command_log_tx_callback(const uint8_t* buffer, size_t size, void* conte furi_stream_buffer_send(context, buffer, size, 0); } -void cli_command_log_level_set_from_string(FuriString* level) { - if(furi_string_cmpi_str(level, "default") == 0) { - furi_log_set_level(FuriLogLevelDefault); - } else if(furi_string_cmpi_str(level, "none") == 0) { - furi_log_set_level(FuriLogLevelNone); - } else if(furi_string_cmpi_str(level, "error") == 0) { - furi_log_set_level(FuriLogLevelError); - } else if(furi_string_cmpi_str(level, "warn") == 0) { - furi_log_set_level(FuriLogLevelWarn); - } else if(furi_string_cmpi_str(level, "info") == 0) { - furi_log_set_level(FuriLogLevelInfo); - } else if(furi_string_cmpi_str(level, "debug") == 0) { - furi_log_set_level(FuriLogLevelDebug); - } else if(furi_string_cmpi_str(level, "trace") == 0) { - furi_log_set_level(FuriLogLevelTrace); +bool cli_command_log_level_set_from_string(FuriString* level) { + FuriLogLevel log_level; + if(furi_log_level_from_string(furi_string_get_cstr(level), &log_level)) { + furi_log_set_level(log_level); + return true; } else { - printf("Unknown log level\r\n"); + printf(" — start logging using the current level from the system settings\r\n"); + printf(" — only critical errors and other important messages\r\n"); + printf(" — non-critical errors and warnings including \r\n"); + printf(" — non-critical information including \r\n"); + printf(" — the default system log level (equivalent to )\r\n"); + printf( + " — debug information including (may impact system performance)\r\n"); + printf( + " — system traces including (may impact system performance)\r\n"); } + return false; } void cli_command_log(Cli* cli, FuriString* args, void* context) { @@ -193,12 +192,20 @@ void cli_command_log(Cli* cli, FuriString* args, void* context) { bool restore_log_level = false; if(furi_string_size(args) > 0) { - cli_command_log_level_set_from_string(args); + if(!cli_command_log_level_set_from_string(args)) { + furi_stream_buffer_free(ring); + return; + } restore_log_level = true; } + const char* current_level; + furi_log_level_to_string(furi_log_get_level(), ¤t_level); + printf("Current log level: %s\r\n", current_level); + furi_hal_console_set_tx_callback(cli_command_log_tx_callback, ring); + printf("Use to list available log levels\r\n"); printf("Press CTRL+C to stop...\r\n"); while(!cli_cmd_interrupt_received(cli)) { size_t ret = furi_stream_buffer_receive(ring, buffer, CLI_COMMAND_LOG_BUFFER_SIZE, 50); diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index 014970113..bbeaa3b1a 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,34.0,, +Version,+,34.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -1281,6 +1281,8 @@ Function,+,furi_kernel_restore_lock,int32_t,int32_t Function,+,furi_kernel_unlock,int32_t, Function,+,furi_log_get_level,FuriLogLevel, Function,-,furi_log_init,void, +Function,+,furi_log_level_from_string,_Bool,"const char*, FuriLogLevel*" +Function,+,furi_log_level_to_string,_Bool,"FuriLogLevel, const char**" Function,+,furi_log_print_format,void,"FuriLogLevel, const char*, const char*, ..." Function,+,furi_log_print_raw_format,void,"FuriLogLevel, const char*, ..." Function,+,furi_log_set_level,void,FuriLogLevel diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 2e02608a3..0d33f70f6 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,34.0,, +Version,+,34.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -1450,6 +1450,8 @@ Function,+,furi_kernel_restore_lock,int32_t,int32_t Function,+,furi_kernel_unlock,int32_t, Function,+,furi_log_get_level,FuriLogLevel, Function,-,furi_log_init,void, +Function,+,furi_log_level_from_string,_Bool,"const char*, FuriLogLevel*" +Function,+,furi_log_level_to_string,_Bool,"FuriLogLevel, const char**" Function,+,furi_log_print_format,void,"FuriLogLevel, const char*, const char*, ..." Function,+,furi_log_print_raw_format,void,"FuriLogLevel, const char*, ..." Function,+,furi_log_set_level,void,FuriLogLevel diff --git a/furi/core/log.c b/furi/core/log.c index d910ecf21..53467ecdb 100644 --- a/furi/core/log.c +++ b/furi/core/log.c @@ -14,6 +14,21 @@ typedef struct { static FuriLogParams furi_log; +typedef struct { + const char* str; + FuriLogLevel level; +} FuriLogLevelDescription; + +static const FuriLogLevelDescription FURI_LOG_LEVEL_DESCRIPTIONS[] = { + {"default", FuriLogLevelDefault}, + {"none", FuriLogLevelNone}, + {"error", FuriLogLevelError}, + {"warn", FuriLogLevelWarn}, + {"info", FuriLogLevelInfo}, + {"debug", FuriLogLevelDebug}, + {"trace", FuriLogLevelTrace}, +}; + void furi_log_init() { // Set default logging parameters furi_log.log_level = FURI_LOG_LEVEL_DEFAULT; @@ -117,3 +132,23 @@ void furi_log_set_timestamp(FuriLogTimestamp timestamp) { furi_assert(timestamp); furi_log.timestamp = timestamp; } + +bool furi_log_level_to_string(FuriLogLevel level, const char** str) { + for(size_t i = 0; i < COUNT_OF(FURI_LOG_LEVEL_DESCRIPTIONS); i++) { + if(level == FURI_LOG_LEVEL_DESCRIPTIONS[i].level) { + *str = FURI_LOG_LEVEL_DESCRIPTIONS[i].str; + return true; + } + } + return false; +} + +bool furi_log_level_from_string(const char* str, FuriLogLevel* level) { + for(size_t i = 0; i < COUNT_OF(FURI_LOG_LEVEL_DESCRIPTIONS); i++) { + if(strcmp(str, FURI_LOG_LEVEL_DESCRIPTIONS[i].str) == 0) { + *level = FURI_LOG_LEVEL_DESCRIPTIONS[i].level; + return true; + } + } + return false; +} \ No newline at end of file diff --git a/furi/core/log.h b/furi/core/log.h index 46ae7f007..5d11add9b 100644 --- a/furi/core/log.h +++ b/furi/core/log.h @@ -7,6 +7,7 @@ #include #include #include +#include #ifdef __cplusplus extern "C" { @@ -87,6 +88,23 @@ void furi_log_set_puts(FuriLogPuts puts); */ void furi_log_set_timestamp(FuriLogTimestamp timestamp); +/** Log level to string + * + * @param[in] level The level + * + * @return The string + */ +bool furi_log_level_to_string(FuriLogLevel level, const char** str); + +/** Log level from string + * + * @param[in] str The string + * @param level The level + * + * @return True if success, False otherwise + */ +bool furi_log_level_from_string(const char* str, FuriLogLevel* level); + /** Log methods * * @param tag The application tag From b41ffd887da30f1234bd26c9c1293646c89d4296 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 11 Jul 2023 14:53:54 +0300 Subject: [PATCH 096/364] Fix naming, run fbt format --- .../{camera-suite => camera_suite}/application.fam | 12 ++++++------ .../camera-suite.c => camera_suite/camera_suite.c} | 2 +- .../camera-suite.h => camera_suite/camera_suite.h} | 0 .../helpers/camera_suite_custom_event.h | 0 .../helpers/camera_suite_haptic.c | 2 +- .../helpers/camera_suite_haptic.h | 0 .../helpers/camera_suite_led.c | 4 ++-- .../helpers/camera_suite_led.h | 0 .../helpers/camera_suite_speaker.c | 2 +- .../helpers/camera_suite_speaker.h | 0 .../helpers/camera_suite_storage.c | 0 .../helpers/camera_suite_storage.h | 6 +++--- .../icons/camera_suite.png} | Bin .../scenes/camera_suite_scene.c | 0 .../scenes/camera_suite_scene.h | 0 .../scenes/camera_suite_scene_config.h | 0 .../scenes/camera_suite_scene_guide.c | 2 +- .../scenes/camera_suite_scene_menu.c | 2 +- .../scenes/camera_suite_scene_settings.c | 2 +- .../scenes/camera_suite_scene_start.c | 2 +- .../scenes/camera_suite_scene_style_1.c | 2 +- .../scenes/camera_suite_scene_style_2.c | 2 +- .../views/camera_suite_view_guide.c | 2 +- .../views/camera_suite_view_guide.h | 0 .../views/camera_suite_view_start.c | 2 +- .../views/camera_suite_view_start.h | 0 .../views/camera_suite_view_style_1.c | 2 +- .../views/camera_suite_view_style_1.h | 0 .../views/camera_suite_view_style_2.c | 2 +- .../views/camera_suite_view_style_2.h | 0 30 files changed, 24 insertions(+), 24 deletions(-) rename applications/external/{camera-suite => camera_suite}/application.fam (51%) rename applications/external/{camera-suite/camera-suite.c => camera_suite/camera_suite.c} (99%) rename applications/external/{camera-suite/camera-suite.h => camera_suite/camera_suite.h} (100%) rename applications/external/{camera-suite => camera_suite}/helpers/camera_suite_custom_event.h (100%) rename applications/external/{camera-suite => camera_suite}/helpers/camera_suite_haptic.c (97%) rename applications/external/{camera-suite => camera_suite}/helpers/camera_suite_haptic.h (100%) rename applications/external/{camera-suite => camera_suite}/helpers/camera_suite_led.c (94%) rename applications/external/{camera-suite => camera_suite}/helpers/camera_suite_led.h (100%) rename applications/external/{camera-suite => camera_suite}/helpers/camera_suite_speaker.c (95%) rename applications/external/{camera-suite => camera_suite}/helpers/camera_suite_speaker.h (100%) rename applications/external/{camera-suite => camera_suite}/helpers/camera_suite_storage.c (100%) rename applications/external/{camera-suite => camera_suite}/helpers/camera_suite_storage.h (86%) rename applications/external/{camera-suite/icons/camera-suite.png => camera_suite/icons/camera_suite.png} (100%) rename applications/external/{camera-suite => camera_suite}/scenes/camera_suite_scene.c (100%) rename applications/external/{camera-suite => camera_suite}/scenes/camera_suite_scene.h (100%) rename applications/external/{camera-suite => camera_suite}/scenes/camera_suite_scene_config.h (100%) rename applications/external/{camera-suite => camera_suite}/scenes/camera_suite_scene_guide.c (98%) rename applications/external/{camera-suite => camera_suite}/scenes/camera_suite_scene_menu.c (99%) rename applications/external/{camera-suite => camera_suite}/scenes/camera_suite_scene_settings.c (99%) rename applications/external/{camera-suite => camera_suite}/scenes/camera_suite_scene_start.c (98%) rename applications/external/{camera-suite => camera_suite}/scenes/camera_suite_scene_style_1.c (98%) rename applications/external/{camera-suite => camera_suite}/scenes/camera_suite_scene_style_2.c (98%) rename applications/external/{camera-suite => camera_suite}/views/camera_suite_view_guide.c (99%) rename applications/external/{camera-suite => camera_suite}/views/camera_suite_view_guide.h (100%) rename applications/external/{camera-suite => camera_suite}/views/camera_suite_view_start.c (99%) rename applications/external/{camera-suite => camera_suite}/views/camera_suite_view_start.h (100%) rename applications/external/{camera-suite => camera_suite}/views/camera_suite_view_style_1.c (99%) rename applications/external/{camera-suite => camera_suite}/views/camera_suite_view_style_1.h (100%) rename applications/external/{camera-suite => camera_suite}/views/camera_suite_view_style_2.c (99%) rename applications/external/{camera-suite => camera_suite}/views/camera_suite_view_style_2.h (100%) diff --git a/applications/external/camera-suite/application.fam b/applications/external/camera_suite/application.fam similarity index 51% rename from applications/external/camera-suite/application.fam rename to applications/external/camera_suite/application.fam index d2beefcde..40131ae9a 100644 --- a/applications/external/camera-suite/application.fam +++ b/applications/external/camera_suite/application.fam @@ -3,14 +3,14 @@ App( apptype=FlipperAppType.EXTERNAL, cdefines=["APP_CAMERA_SUITE"], entry_point="camera_suite_app", - fap_author="Cody Tolene", + fap_author="Cody Tolene", fap_category="GPIO", - fap_description="A camera suite application for the Flipper Zero ESP32-CAM module.", - fap_icon="icons/camera-suite.png", + fap_description="A camera suite application for the Flipper Zero ESP32-CAM module.", + fap_icon="icons/camera_suite.png", fap_libs=["assets"], - fap_weburl="https://github.com/CodyTolene/Flipper-Zero-Cam", + fap_weburl="https://github.com/CodyTolene/Flipper-Zero-Cam", name="[ESP32] Camera Suite", order=1, requires=["gui", "storage"], - stack_size=8 * 1024 -) \ No newline at end of file + stack_size=8 * 1024, +) diff --git a/applications/external/camera-suite/camera-suite.c b/applications/external/camera_suite/camera_suite.c similarity index 99% rename from applications/external/camera-suite/camera-suite.c rename to applications/external/camera_suite/camera_suite.c index 361bfef4e..13ee09c22 100644 --- a/applications/external/camera-suite/camera-suite.c +++ b/applications/external/camera_suite/camera_suite.c @@ -1,4 +1,4 @@ -#include "camera-suite.h" +#include "camera_suite.h" #include bool camera_suite_custom_event_callback(void* context, uint32_t event) { diff --git a/applications/external/camera-suite/camera-suite.h b/applications/external/camera_suite/camera_suite.h similarity index 100% rename from applications/external/camera-suite/camera-suite.h rename to applications/external/camera_suite/camera_suite.h diff --git a/applications/external/camera-suite/helpers/camera_suite_custom_event.h b/applications/external/camera_suite/helpers/camera_suite_custom_event.h similarity index 100% rename from applications/external/camera-suite/helpers/camera_suite_custom_event.h rename to applications/external/camera_suite/helpers/camera_suite_custom_event.h diff --git a/applications/external/camera-suite/helpers/camera_suite_haptic.c b/applications/external/camera_suite/helpers/camera_suite_haptic.c similarity index 97% rename from applications/external/camera-suite/helpers/camera_suite_haptic.c rename to applications/external/camera_suite/helpers/camera_suite_haptic.c index 27ea81868..237a96004 100644 --- a/applications/external/camera-suite/helpers/camera_suite_haptic.c +++ b/applications/external/camera_suite/helpers/camera_suite_haptic.c @@ -1,5 +1,5 @@ #include "camera_suite_haptic.h" -#include "../camera-suite.h" +#include "../camera_suite.h" void camera_suite_play_happy_bump(void* context) { CameraSuite* app = context; diff --git a/applications/external/camera-suite/helpers/camera_suite_haptic.h b/applications/external/camera_suite/helpers/camera_suite_haptic.h similarity index 100% rename from applications/external/camera-suite/helpers/camera_suite_haptic.h rename to applications/external/camera_suite/helpers/camera_suite_haptic.h diff --git a/applications/external/camera-suite/helpers/camera_suite_led.c b/applications/external/camera_suite/helpers/camera_suite_led.c similarity index 94% rename from applications/external/camera-suite/helpers/camera_suite_led.c rename to applications/external/camera_suite/helpers/camera_suite_led.c index c7211a996..c4f1a85d7 100644 --- a/applications/external/camera-suite/helpers/camera_suite_led.c +++ b/applications/external/camera_suite/helpers/camera_suite_led.c @@ -1,5 +1,5 @@ #include "camera_suite_led.h" -#include "../camera-suite.h" +#include "../camera_suite.h" void camera_suite_led_set_rgb(void* context, int red, int green, int blue) { CameraSuite* app = context; @@ -34,5 +34,5 @@ void camera_suite_led_reset(void* context) { notification_message(app->notification, &sequence_reset_green); notification_message(app->notification, &sequence_reset_blue); //Delay, prevent removal from RAM before LED value set. - furi_thread_flags_wait(0, FuriFlagWaitAny, 300); + furi_thread_flags_wait(0, FuriFlagWaitAny, 300); } diff --git a/applications/external/camera-suite/helpers/camera_suite_led.h b/applications/external/camera_suite/helpers/camera_suite_led.h similarity index 100% rename from applications/external/camera-suite/helpers/camera_suite_led.h rename to applications/external/camera_suite/helpers/camera_suite_led.h diff --git a/applications/external/camera-suite/helpers/camera_suite_speaker.c b/applications/external/camera_suite/helpers/camera_suite_speaker.c similarity index 95% rename from applications/external/camera-suite/helpers/camera_suite_speaker.c rename to applications/external/camera_suite/helpers/camera_suite_speaker.c index 35d712109..c2a5a7dd0 100644 --- a/applications/external/camera-suite/helpers/camera_suite_speaker.c +++ b/applications/external/camera_suite/helpers/camera_suite_speaker.c @@ -1,5 +1,5 @@ #include "camera_suite_speaker.h" -#include "../camera-suite.h" +#include "../camera_suite.h" #define NOTE_INPUT 587.33f diff --git a/applications/external/camera-suite/helpers/camera_suite_speaker.h b/applications/external/camera_suite/helpers/camera_suite_speaker.h similarity index 100% rename from applications/external/camera-suite/helpers/camera_suite_speaker.h rename to applications/external/camera_suite/helpers/camera_suite_speaker.h diff --git a/applications/external/camera-suite/helpers/camera_suite_storage.c b/applications/external/camera_suite/helpers/camera_suite_storage.c similarity index 100% rename from applications/external/camera-suite/helpers/camera_suite_storage.c rename to applications/external/camera_suite/helpers/camera_suite_storage.c diff --git a/applications/external/camera-suite/helpers/camera_suite_storage.h b/applications/external/camera_suite/helpers/camera_suite_storage.h similarity index 86% rename from applications/external/camera-suite/helpers/camera_suite_storage.h rename to applications/external/camera_suite/helpers/camera_suite_storage.h index e56a4e5ac..37e82d722 100644 --- a/applications/external/camera-suite/helpers/camera_suite_storage.h +++ b/applications/external/camera_suite/helpers/camera_suite_storage.h @@ -2,11 +2,11 @@ #include #include #include -#include "../camera-suite.h" +#include "../camera_suite.h" #define BOILERPLATE_SETTINGS_FILE_VERSION 1 -#define CONFIG_FILE_DIRECTORY_PATH EXT_PATH("apps_data/camera-suite") -#define BOILERPLATE_SETTINGS_SAVE_PATH CONFIG_FILE_DIRECTORY_PATH "/camera-suite.conf" +#define CONFIG_FILE_DIRECTORY_PATH EXT_PATH("apps_data/camera_suite") +#define BOILERPLATE_SETTINGS_SAVE_PATH CONFIG_FILE_DIRECTORY_PATH "/camera_suite.conf" #define BOILERPLATE_SETTINGS_SAVE_PATH_TMP BOILERPLATE_SETTINGS_SAVE_PATH ".tmp" #define BOILERPLATE_SETTINGS_HEADER "Camera Suite Config File" #define BOILERPLATE_SETTINGS_KEY_ORIENTATION "Orientation" diff --git a/applications/external/camera-suite/icons/camera-suite.png b/applications/external/camera_suite/icons/camera_suite.png similarity index 100% rename from applications/external/camera-suite/icons/camera-suite.png rename to applications/external/camera_suite/icons/camera_suite.png diff --git a/applications/external/camera-suite/scenes/camera_suite_scene.c b/applications/external/camera_suite/scenes/camera_suite_scene.c similarity index 100% rename from applications/external/camera-suite/scenes/camera_suite_scene.c rename to applications/external/camera_suite/scenes/camera_suite_scene.c diff --git a/applications/external/camera-suite/scenes/camera_suite_scene.h b/applications/external/camera_suite/scenes/camera_suite_scene.h similarity index 100% rename from applications/external/camera-suite/scenes/camera_suite_scene.h rename to applications/external/camera_suite/scenes/camera_suite_scene.h diff --git a/applications/external/camera-suite/scenes/camera_suite_scene_config.h b/applications/external/camera_suite/scenes/camera_suite_scene_config.h similarity index 100% rename from applications/external/camera-suite/scenes/camera_suite_scene_config.h rename to applications/external/camera_suite/scenes/camera_suite_scene_config.h diff --git a/applications/external/camera-suite/scenes/camera_suite_scene_guide.c b/applications/external/camera_suite/scenes/camera_suite_scene_guide.c similarity index 98% rename from applications/external/camera-suite/scenes/camera_suite_scene_guide.c rename to applications/external/camera_suite/scenes/camera_suite_scene_guide.c index 12682fb24..6599058ef 100644 --- a/applications/external/camera-suite/scenes/camera_suite_scene_guide.c +++ b/applications/external/camera_suite/scenes/camera_suite_scene_guide.c @@ -1,4 +1,4 @@ -#include "../camera-suite.h" +#include "../camera_suite.h" #include "../helpers/camera_suite_custom_event.h" #include "../views/camera_suite_view_guide.h" diff --git a/applications/external/camera-suite/scenes/camera_suite_scene_menu.c b/applications/external/camera_suite/scenes/camera_suite_scene_menu.c similarity index 99% rename from applications/external/camera-suite/scenes/camera_suite_scene_menu.c rename to applications/external/camera_suite/scenes/camera_suite_scene_menu.c index 538e64252..09c4dade7 100644 --- a/applications/external/camera-suite/scenes/camera_suite_scene_menu.c +++ b/applications/external/camera_suite/scenes/camera_suite_scene_menu.c @@ -1,4 +1,4 @@ -#include "../camera-suite.h" +#include "../camera_suite.h" enum SubmenuIndex { /** Atkinson Dithering Algorithm. */ diff --git a/applications/external/camera-suite/scenes/camera_suite_scene_settings.c b/applications/external/camera_suite/scenes/camera_suite_scene_settings.c similarity index 99% rename from applications/external/camera-suite/scenes/camera_suite_scene_settings.c rename to applications/external/camera_suite/scenes/camera_suite_scene_settings.c index aba80d08c..a06b45fe9 100644 --- a/applications/external/camera-suite/scenes/camera_suite_scene_settings.c +++ b/applications/external/camera_suite/scenes/camera_suite_scene_settings.c @@ -1,4 +1,4 @@ -#include "../camera-suite.h" +#include "../camera_suite.h" #include // Camera orientation, in degrees. diff --git a/applications/external/camera-suite/scenes/camera_suite_scene_start.c b/applications/external/camera_suite/scenes/camera_suite_scene_start.c similarity index 98% rename from applications/external/camera-suite/scenes/camera_suite_scene_start.c rename to applications/external/camera_suite/scenes/camera_suite_scene_start.c index 9f128b761..12591760c 100644 --- a/applications/external/camera-suite/scenes/camera_suite_scene_start.c +++ b/applications/external/camera_suite/scenes/camera_suite_scene_start.c @@ -1,4 +1,4 @@ -#include "../camera-suite.h" +#include "../camera_suite.h" #include "../helpers/camera_suite_custom_event.h" #include "../views/camera_suite_view_start.h" diff --git a/applications/external/camera-suite/scenes/camera_suite_scene_style_1.c b/applications/external/camera_suite/scenes/camera_suite_scene_style_1.c similarity index 98% rename from applications/external/camera-suite/scenes/camera_suite_scene_style_1.c rename to applications/external/camera_suite/scenes/camera_suite_scene_style_1.c index 1341c6173..1aeecec7a 100644 --- a/applications/external/camera-suite/scenes/camera_suite_scene_style_1.c +++ b/applications/external/camera_suite/scenes/camera_suite_scene_style_1.c @@ -1,4 +1,4 @@ -#include "../camera-suite.h" +#include "../camera_suite.h" #include "../helpers/camera_suite_custom_event.h" #include "../views/camera_suite_view_style_1.h" diff --git a/applications/external/camera-suite/scenes/camera_suite_scene_style_2.c b/applications/external/camera_suite/scenes/camera_suite_scene_style_2.c similarity index 98% rename from applications/external/camera-suite/scenes/camera_suite_scene_style_2.c rename to applications/external/camera_suite/scenes/camera_suite_scene_style_2.c index df8c875ba..b74d33784 100644 --- a/applications/external/camera-suite/scenes/camera_suite_scene_style_2.c +++ b/applications/external/camera_suite/scenes/camera_suite_scene_style_2.c @@ -1,4 +1,4 @@ -#include "../camera-suite.h" +#include "../camera_suite.h" #include "../helpers/camera_suite_custom_event.h" #include "../helpers/camera_suite_haptic.h" #include "../helpers/camera_suite_led.h" diff --git a/applications/external/camera-suite/views/camera_suite_view_guide.c b/applications/external/camera_suite/views/camera_suite_view_guide.c similarity index 99% rename from applications/external/camera-suite/views/camera_suite_view_guide.c rename to applications/external/camera_suite/views/camera_suite_view_guide.c index f27047d47..479f8d4d1 100644 --- a/applications/external/camera-suite/views/camera_suite_view_guide.c +++ b/applications/external/camera_suite/views/camera_suite_view_guide.c @@ -1,4 +1,4 @@ -#include "../camera-suite.h" +#include "../camera_suite.h" #include #include #include diff --git a/applications/external/camera-suite/views/camera_suite_view_guide.h b/applications/external/camera_suite/views/camera_suite_view_guide.h similarity index 100% rename from applications/external/camera-suite/views/camera_suite_view_guide.h rename to applications/external/camera_suite/views/camera_suite_view_guide.h diff --git a/applications/external/camera-suite/views/camera_suite_view_start.c b/applications/external/camera_suite/views/camera_suite_view_start.c similarity index 99% rename from applications/external/camera-suite/views/camera_suite_view_start.c rename to applications/external/camera_suite/views/camera_suite_view_start.c index dbb374cbf..a84ee50c2 100644 --- a/applications/external/camera-suite/views/camera_suite_view_start.c +++ b/applications/external/camera_suite/views/camera_suite_view_start.c @@ -1,4 +1,4 @@ -#include "../camera-suite.h" +#include "../camera_suite.h" #include #include #include diff --git a/applications/external/camera-suite/views/camera_suite_view_start.h b/applications/external/camera_suite/views/camera_suite_view_start.h similarity index 100% rename from applications/external/camera-suite/views/camera_suite_view_start.h rename to applications/external/camera_suite/views/camera_suite_view_start.h diff --git a/applications/external/camera-suite/views/camera_suite_view_style_1.c b/applications/external/camera_suite/views/camera_suite_view_style_1.c similarity index 99% rename from applications/external/camera-suite/views/camera_suite_view_style_1.c rename to applications/external/camera_suite/views/camera_suite_view_style_1.c index 34aeaf272..6d16d0231 100644 --- a/applications/external/camera-suite/views/camera_suite_view_style_1.c +++ b/applications/external/camera_suite/views/camera_suite_view_style_1.c @@ -1,4 +1,4 @@ -#include "../camera-suite.h" +#include "../camera_suite.h" #include #include #include diff --git a/applications/external/camera-suite/views/camera_suite_view_style_1.h b/applications/external/camera_suite/views/camera_suite_view_style_1.h similarity index 100% rename from applications/external/camera-suite/views/camera_suite_view_style_1.h rename to applications/external/camera_suite/views/camera_suite_view_style_1.h diff --git a/applications/external/camera-suite/views/camera_suite_view_style_2.c b/applications/external/camera_suite/views/camera_suite_view_style_2.c similarity index 99% rename from applications/external/camera-suite/views/camera_suite_view_style_2.c rename to applications/external/camera_suite/views/camera_suite_view_style_2.c index eefa4d637..0a7ac0b83 100644 --- a/applications/external/camera-suite/views/camera_suite_view_style_2.c +++ b/applications/external/camera_suite/views/camera_suite_view_style_2.c @@ -1,4 +1,4 @@ -#include "../camera-suite.h" +#include "../camera_suite.h" #include #include #include diff --git a/applications/external/camera-suite/views/camera_suite_view_style_2.h b/applications/external/camera_suite/views/camera_suite_view_style_2.h similarity index 100% rename from applications/external/camera-suite/views/camera_suite_view_style_2.h rename to applications/external/camera_suite/views/camera_suite_view_style_2.h From 8ab33085533a5c8fb1c9b5e00d9be0901014bced Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 11 Jul 2023 14:54:30 +0300 Subject: [PATCH 097/364] fbt format for subbrute --- applications/external/subbrute | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/external/subbrute b/applications/external/subbrute index 63a8845ef..0ba59ffb0 160000 --- a/applications/external/subbrute +++ b/applications/external/subbrute @@ -1 +1 @@ -Subproject commit 63a8845efd43d716b733f31c0bd517a6cc234018 +Subproject commit 0ba59ffb0e9bc56083013bebde3986ee186c7b6e From 4ccfd5ba63fcdd6756f5425f30195a266e2000af Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 11 Jul 2023 14:56:05 +0300 Subject: [PATCH 098/364] add app in readme --- ReadMe.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ReadMe.md b/ReadMe.md index f57256713..1bc96e7e7 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -171,6 +171,7 @@ You can support us by using links or addresses below: - **BadBT** plugin (BT version of BadKB) [(by Willy-JL, ClaraCrazy, XFW contributors)](https://github.com/ClaraCrazy/Flipper-Xtreme/tree/dev/applications/main/bad_kb) (See in Applications->Tools) - (aka BadUSB via Bluetooth) - **Mifare Nested** [(by AloneLiberty)](https://github.com/AloneLiberty/FlipperNested) - Works with PC and python app `FlipperNested` - **NFC Maker** plugin (make tags with URLs, Wifi and other things) [(by Willy-JL)](https://github.com/ClaraCrazy/Flipper-Xtreme/tree/dev/applications/external/nfc_maker) +- ESP32-CAM -> Camera Suite [(by CodyTolene)](https://github.com/CodyTolene/Flipper-Zero-Camera-Suite) Games: - DOOM (fixed) [(by p4nic4ttack)](https://github.com/p4nic4ttack/doom-flipper-zero/) @@ -244,6 +245,8 @@ Games: ## [- How to use: [ESP32] WiFi Marauder](https://github.com/UberGuidoZ/Flipper/tree/main/Wifi_DevBoard) +## [- How to use: [ESP32-CAM] Camera Suite](https://github.com/CodyTolene/Flipper-Zero-Camera-Suite) + ## [- [WiFi] Scanner - Web Flasher for module firmware](https://sequoiasan.github.io/FlipperZero-WiFi-Scanner_Module/) ## [- [ESP8266] Deauther - Web Flasher for module firmware](https://sequoiasan.github.io/FlipperZero-Wifi-ESP8266-Deauther-Module/) From b7d2fe769c59b086436217c0a1e2b39a23b87f34 Mon Sep 17 00:00:00 2001 From: Nikita Vostokov Date: Tue, 11 Jul 2023 17:26:18 +0300 Subject: [PATCH 099/364] Decode only supported Oregon 3 sensor (#2829) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: wosk Co-authored-by: ã‚ã --- applications/external/weather_station/protocols/oregon3.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/applications/external/weather_station/protocols/oregon3.c b/applications/external/weather_station/protocols/oregon3.c index a211c5ad3..bd35c2fd5 100644 --- a/applications/external/weather_station/protocols/oregon3.c +++ b/applications/external/weather_station/protocols/oregon3.c @@ -116,9 +116,11 @@ static ManchesterEvent level_and_duration_to_event(bool level, uint32_t duration static uint8_t oregon3_sensor_id_var_bits(uint16_t sensor_id) { switch(sensor_id) { case ID_THGR221: - default: // nibbles: temp + hum + '0' return (4 + 2 + 1) * 4; + default: + FURI_LOG_D(TAG, "Unsupported sensor id 0x%x", sensor_id); + return 0; } } @@ -198,10 +200,8 @@ void ws_protocol_decoder_oregon3_feed(void* context, bool level, uint32_t durati oregon3_sensor_id_var_bits(OREGON3_SENSOR_ID(instance->generic.data)); if(!instance->var_bits) { - // sensor is not supported, stop decoding, but showing the decoded fixed part + // sensor is not supported, stop decoding instance->decoder.parser_step = Oregon3DecoderStepReset; - if(instance->base.callback) - instance->base.callback(&instance->base, instance->base.context); } else { instance->decoder.parser_step = Oregon3DecoderStepVarData; } From a319a6fdf230560101ab3e4814e3a2c7f5a5d1ad Mon Sep 17 00:00:00 2001 From: Max Andreev Date: Tue, 11 Jul 2023 19:36:15 +0400 Subject: [PATCH 100/364] Update toolchain to v23 (#2824) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: ã‚ã --- scripts/toolchain/fbtenv.cmd | 2 +- scripts/toolchain/fbtenv.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/toolchain/fbtenv.cmd b/scripts/toolchain/fbtenv.cmd index 4ae04e2a2..51708b8c4 100644 --- a/scripts/toolchain/fbtenv.cmd +++ b/scripts/toolchain/fbtenv.cmd @@ -13,7 +13,7 @@ if not ["%FBT_NOENV%"] == [""] ( exit /b 0 ) -set "FLIPPER_TOOLCHAIN_VERSION=22" +set "FLIPPER_TOOLCHAIN_VERSION=23" if ["%FBT_TOOLCHAIN_PATH%"] == [""] ( set "FBT_TOOLCHAIN_PATH=%FBT_ROOT%" diff --git a/scripts/toolchain/fbtenv.sh b/scripts/toolchain/fbtenv.sh index e5548f488..85d139040 100755 --- a/scripts/toolchain/fbtenv.sh +++ b/scripts/toolchain/fbtenv.sh @@ -4,7 +4,7 @@ # public variables DEFAULT_SCRIPT_PATH="$(pwd -P)"; -FBT_TOOLCHAIN_VERSION="${FBT_TOOLCHAIN_VERSION:-"22"}"; +FBT_TOOLCHAIN_VERSION="${FBT_TOOLCHAIN_VERSION:-"23"}"; if [ -z ${FBT_TOOLCHAIN_PATH+x} ] ; then FBT_TOOLCHAIN_PATH_WAS_SET=0; From 6e9e6262c65ad64c87bc67700af3522a3f47cfcb Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Wed, 12 Jul 2023 01:53:27 +0200 Subject: [PATCH 101/364] Refactor NFC Maker with proper NDEF logic Also fixes wifi not working in some cases --- .../nfc_maker/scenes/nfc_maker_scene_result.c | 324 +++++++++--------- 1 file changed, 157 insertions(+), 167 deletions(-) diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_result.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_result.c index 38ad1e634..70cb664ba 100644 --- a/applications/external/nfc_maker/scenes/nfc_maker_scene_result.c +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_result.c @@ -61,221 +61,168 @@ void nfc_maker_scene_result_on_enter(void* context) { buf[i++] = 0x48; // Internal buf[i++] = 0x00; // Lock bytes buf[i++] = 0x00; // ... + buf[i++] = 0xE1; // Capability container buf[i++] = 0x10; // ... buf[i++] = 0x3E; // ... buf[i++] = 0x00; // ... - buf[i++] = 0x03; // Message flags - size_t start = i++; + buf[i++] = 0x03; // Container flags + + // NDEF Docs: https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/nrf/protocols/nfc/index.html#nfc-data-exchange-format-ndef + uint8_t tnf = 0x00; + const char* type = ""; + uint8_t* payload = NULL; + size_t payload_len = 0; + + size_t data_len = 0; + size_t j = 0; switch(scene_manager_get_scene_state(app->scene_manager, NfcMakerSceneMenu)) { case NfcMakerSceneBluetooth: { - buf[i++] = 0xD2; - buf[i++] = 0x20; - buf[i++] = 0x08; - buf[i++] = 0x61; - buf[i++] = 0x70; + tnf = 0x02; // Media-type [RFC 2046] + type = "application/vnd.bluetooth.ep.oob"; - buf[i++] = 0x70; - buf[i++] = 0x6C; - buf[i++] = 0x69; - buf[i++] = 0x63; + data_len = GAP_MAC_ADDR_SIZE; + payload_len = data_len + 2; + payload = malloc(payload_len); - buf[i++] = 0x61; - buf[i++] = 0x74; - buf[i++] = 0x69; - buf[i++] = 0x6F; - - buf[i++] = 0x6E; - buf[i++] = 0x2F; - buf[i++] = 0x76; - buf[i++] = 0x6E; - - buf[i++] = 0x64; - buf[i++] = 0x2E; - buf[i++] = 0x62; - buf[i++] = 0x6C; - - buf[i++] = 0x75; - buf[i++] = 0x65; - buf[i++] = 0x74; - buf[i++] = 0x6F; - - buf[i++] = 0x6F; - buf[i++] = 0x74; - buf[i++] = 0x68; - buf[i++] = 0x2E; - - buf[i++] = 0x65; - buf[i++] = 0x70; - buf[i++] = 0x2E; - buf[i++] = 0x6F; - - buf[i++] = 0x6F; - buf[i++] = 0x62; - buf[i++] = 0x08; - buf[i++] = 0x00; - - memcpy(&buf[i], app->mac_buf, GAP_MAC_ADDR_SIZE); - i += GAP_MAC_ADDR_SIZE; + payload[j++] = 0x08; + payload[j++] = 0x00; + memcpy(&payload[j], app->mac_buf, data_len); + j += data_len; break; } case NfcMakerSceneHttps: { - uint8_t data_len = strnlen(app->text_buf, TEXT_INPUT_LEN); + tnf = 0x01; // NFC Forum well-known type [NFC RTD] + type = "\x55"; - buf[i++] = 0xD1; - buf[i++] = 0x01; - buf[i++] = data_len + 1; - buf[i++] = 0x55; + data_len = strnlen(app->text_buf, TEXT_INPUT_LEN); + payload_len = data_len + 1; + payload = malloc(payload_len); - buf[i++] = 0x04; // Prepend "https://" - memcpy(&buf[i], app->text_buf, data_len); - i += data_len; + payload[j++] = 0x04; // Prepend "https://" + memcpy(&payload[j], app->text_buf, data_len); + j += data_len; break; } case NfcMakerSceneMail: { - uint8_t data_len = strnlen(app->text_buf, TEXT_INPUT_LEN); + tnf = 0x01; // NFC Forum well-known type [NFC RTD] + type = "\x55"; - buf[i++] = 0xD1; - buf[i++] = 0x01; - buf[i++] = data_len + 1; - buf[i++] = 0x55; + data_len = strnlen(app->text_buf, TEXT_INPUT_LEN); + payload_len = data_len + 1; + payload = malloc(payload_len); - buf[i++] = 0x06; // Prepend "mailto:" - memcpy(&buf[i], app->text_buf, data_len); - i += data_len; + payload[j++] = 0x06; // Prepend "mailto:" + memcpy(&payload[j], app->text_buf, data_len); + j += data_len; break; } case NfcMakerScenePhone: { - uint8_t data_len = strnlen(app->text_buf, TEXT_INPUT_LEN); + tnf = 0x01; // NFC Forum well-known type [NFC RTD] + type = "\x55"; - buf[i++] = 0xD1; - buf[i++] = 0x01; - buf[i++] = data_len + 1; - buf[i++] = 0x55; + data_len = strnlen(app->text_buf, TEXT_INPUT_LEN); + payload_len = data_len + 1; + payload = malloc(payload_len); - buf[i++] = 0x05; // Prepend "tel:" - memcpy(&buf[i], app->text_buf, data_len); - i += data_len; + payload[j++] = 0x05; // Prepend "tel:" + memcpy(&payload[j], app->text_buf, data_len); + j += data_len; break; } case NfcMakerSceneText: { - uint8_t data_len = strnlen(app->text_buf, TEXT_INPUT_LEN); + tnf = 0x01; // NFC Forum well-known type [NFC RTD] + type = "\x54"; - buf[i++] = 0xD1; - buf[i++] = 0x01; - buf[i++] = data_len + 3; - buf[i++] = 0x54; + data_len = strnlen(app->text_buf, TEXT_INPUT_LEN); + payload_len = data_len + 3; + payload = malloc(payload_len); - buf[i++] = 0x02; - buf[i++] = 0x65; // e - buf[i++] = 0x6E; // n - memcpy(&buf[i], app->text_buf, data_len); - i += data_len; + payload[j++] = 0x02; + payload[j++] = 0x65; // e + payload[j++] = 0x6E; // n + memcpy(&payload[j], app->text_buf, data_len); + j += data_len; break; } case NfcMakerSceneUrl: { - uint8_t data_len = strnlen(app->text_buf, TEXT_INPUT_LEN); + tnf = 0x01; // NFC Forum well-known type [NFC RTD] + type = "\x55"; - buf[i++] = 0xD1; - buf[i++] = 0x01; - buf[i++] = data_len + 1; - buf[i++] = 0x55; + data_len = strnlen(app->text_buf, TEXT_INPUT_LEN); + payload_len = data_len + 1; + payload = malloc(payload_len); - buf[i++] = 0x00; // No prepend - memcpy(&buf[i], app->text_buf, data_len); - i += data_len; + payload[j++] = 0x00; // No prepend + memcpy(&payload[j], app->text_buf, data_len); + j += data_len; break; } case NfcMakerSceneWifi: { + tnf = 0x02; // Media-type [RFC 2046] + type = "application/vnd.wfa.wsc"; + uint8_t ssid_len = strnlen(app->text_buf, WIFI_INPUT_LEN); uint8_t pass_len = strnlen(app->pass_buf, WIFI_INPUT_LEN); uint8_t data_len = ssid_len + pass_len; + payload_len = data_len + 39; + payload = malloc(payload_len); - buf[i++] = 0xD2; - buf[i++] = 0x17; - buf[i++] = data_len + 47; - buf[i++] = 0x61; - buf[i++] = 0x70; + payload[j++] = 0x10; + payload[j++] = 0x0E; + payload[j++] = 0x00; - buf[i++] = 0x70; - buf[i++] = 0x6C; - buf[i++] = 0x69; - buf[i++] = 0x63; + payload[j++] = data_len + 43; + payload[j++] = 0x10; + payload[j++] = 0x26; + payload[j++] = 0x00; - buf[i++] = 0x61; - buf[i++] = 0x74; - buf[i++] = 0x69; - buf[i++] = 0x6F; + payload[j++] = 0x01; + payload[j++] = 0x01; + payload[j++] = 0x10; + payload[j++] = 0x45; - buf[i++] = 0x6E; - buf[i++] = 0x2F; - buf[i++] = 0x76; - buf[i++] = 0x6E; + payload[j++] = 0x00; + payload[j++] = ssid_len; + memcpy(&payload[j], app->text_buf, ssid_len); + j += ssid_len; + payload[j++] = 0x10; + payload[j++] = 0x03; - buf[i++] = 0x64; - buf[i++] = 0x2E; - buf[i++] = 0x77; - buf[i++] = 0x66; + payload[j++] = 0x00; + payload[j++] = 0x02; + payload[j++] = 0x00; + payload[j++] = + scene_manager_get_scene_state(app->scene_manager, NfcMakerSceneWifiAuth); - buf[i++] = 0x61; - buf[i++] = 0x2E; - buf[i++] = 0x77; - buf[i++] = 0x73; + payload[j++] = 0x10; + payload[j++] = 0x0F; + payload[j++] = 0x00; + payload[j++] = 0x02; - buf[i++] = 0x63; - buf[i++] = 0x10; - buf[i++] = 0x0E; - buf[i++] = 0x00; + payload[j++] = 0x00; + payload[j++] = + scene_manager_get_scene_state(app->scene_manager, NfcMakerSceneWifiEncr); + payload[j++] = 0x10; + payload[j++] = 0x27; - buf[i++] = data_len + 43; - buf[i++] = 0x10; - buf[i++] = 0x26; - buf[i++] = 0x00; + payload[j++] = 0x00; + payload[j++] = pass_len; + memcpy(&payload[j], app->pass_buf, pass_len); + j += pass_len; + payload[j++] = 0x10; + payload[j++] = 0x20; - buf[i++] = 0x01; - buf[i++] = 0x01; - buf[i++] = 0x10; - buf[i++] = 0x45; + payload[j++] = 0x00; + payload[j++] = 0x06; + payload[j++] = 0xFF; + payload[j++] = 0xFF; - buf[i++] = 0x00; - buf[i++] = ssid_len; - memcpy(&buf[i], app->text_buf, ssid_len); - i += ssid_len; - buf[i++] = 0x10; - buf[i++] = 0x03; - - buf[i++] = 0x00; - buf[i++] = 0x02; - buf[i++] = 0x00; - buf[i++] = scene_manager_get_scene_state(app->scene_manager, NfcMakerSceneWifiAuth); - - buf[i++] = 0x10; - buf[i++] = 0x0F; - buf[i++] = 0x00; - buf[i++] = 0x02; - - buf[i++] = 0x00; - buf[i++] = scene_manager_get_scene_state(app->scene_manager, NfcMakerSceneWifiEncr); - buf[i++] = 0x10; - buf[i++] = 0x27; - - buf[i++] = 0x00; - buf[i++] = pass_len; - memcpy(&buf[i], app->pass_buf, pass_len); - i += pass_len; - buf[i++] = 0x10; - buf[i++] = 0x20; - - buf[i++] = 0x00; - buf[i++] = 0x06; - buf[i++] = 0xFF; - buf[i++] = 0xFF; - - buf[i++] = 0xFF; - buf[i++] = 0xFF; - buf[i++] = 0xFF; - buf[i++] = 0xFF; + payload[j++] = 0xFF; + payload[j++] = 0xFF; + payload[j++] = 0xFF; + payload[j++] = 0xFF; break; } @@ -283,8 +230,51 @@ void nfc_maker_scene_result_on_enter(void* context) { break; } - // Message length and terminator - buf[start] = i - start - 1; + // Record header + uint8_t flags = 0; + flags |= 1 << 7; // MB (Message Begin) + flags |= 1 << 6; // ME (Message End) + flags |= tnf; // TNF (Type Name Format) + size_t type_len = strlen(type); + + size_t header_len = 0; + header_len += 1; // Flags and TNF + header_len += 1; // Type length + if(payload_len < 0xFF) { + flags |= 1 << 4; // SR (Short Record) + header_len += 1; // Payload length + } else { + header_len += 4; // Payload length + } + header_len += type_len; // Payload type + + size_t record_len = header_len + payload_len; + if(record_len < 0xFF) { + buf[i++] = record_len; // Record length + } else { + buf[i++] = 0xFF; // Record length + buf[i++] = record_len >> 8; // ... + buf[i++] = record_len & 0xFF; // ... + } + buf[i++] = flags; // Flags and TNF + buf[i++] = type_len; // Type length + if(flags & 1 << 4) { // SR (Short Record) + buf[i++] = payload_len; // Payload length + } else { + buf[i++] = 0x00; // Payload length + buf[i++] = 0x00; // ... + buf[i++] = payload_len >> 8; // ... + buf[i++] = payload_len & 0xFF; // ... + } + memcpy(&buf[i], type, type_len); // Payload type + i += type_len; + + // Record payload + memcpy(&buf[i], payload, payload_len); + i += payload_len; + free(payload); + + // Record terminator buf[i++] = 0xFE; // Padding until last 5 pages From 99b6a6af86f461eb57e71f957af64291a3282305 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Wed, 12 Jul 2023 02:55:07 +0200 Subject: [PATCH 102/364] NFC Maker tidy up buffers and rename scenes --- applications/external/nfc_maker/nfc_maker.c | 3 +- applications/external/nfc_maker/nfc_maker.h | 20 +++++++---- .../scenes/nfc_maker_scene_bluetooth.c | 6 ++-- .../nfc_maker/scenes/nfc_maker_scene_config.h | 4 +-- .../nfc_maker/scenes/nfc_maker_scene_https.c | 8 ++--- .../nfc_maker/scenes/nfc_maker_scene_mail.c | 8 ++--- .../nfc_maker/scenes/nfc_maker_scene_phone.c | 8 ++--- .../nfc_maker/scenes/nfc_maker_scene_result.c | 36 +++++++++---------- ...er_scene_name.c => nfc_maker_scene_save.c} | 18 +++++----- ...r_scene_menu.c => nfc_maker_scene_start.c} | 26 +++++++------- .../nfc_maker/scenes/nfc_maker_scene_text.c | 8 ++--- .../nfc_maker/scenes/nfc_maker_scene_url.c | 8 ++--- .../nfc_maker/scenes/nfc_maker_scene_wifi.c | 6 ++-- .../scenes/nfc_maker_scene_wifi_auth.c | 4 +-- .../scenes/nfc_maker_scene_wifi_pass.c | 8 ++--- 15 files changed, 90 insertions(+), 81 deletions(-) rename applications/external/nfc_maker/scenes/{nfc_maker_scene_name.c => nfc_maker_scene_save.c} (71%) rename applications/external/nfc_maker/scenes/{nfc_maker_scene_menu.c => nfc_maker_scene_start.c} (71%) diff --git a/applications/external/nfc_maker/nfc_maker.c b/applications/external/nfc_maker/nfc_maker.c index 578054ade..6e61f1a16 100644 --- a/applications/external/nfc_maker/nfc_maker.c +++ b/applications/external/nfc_maker/nfc_maker.c @@ -74,7 +74,8 @@ void nfc_maker_free(NfcMaker* app) { extern int32_t nfc_maker(void* p) { UNUSED(p); NfcMaker* app = nfc_maker_alloc(); - scene_manager_next_scene(app->scene_manager, NfcMakerSceneMenu); + scene_manager_set_scene_state(app->scene_manager, NfcMakerSceneStart, NfcMakerSceneHttps); + scene_manager_next_scene(app->scene_manager, NfcMakerSceneStart); view_dispatcher_run(app->view_dispatcher); nfc_maker_free(app); return 0; diff --git a/applications/external/nfc_maker/nfc_maker.h b/applications/external/nfc_maker/nfc_maker.h index 388dc7196..4028cfb1e 100644 --- a/applications/external/nfc_maker/nfc_maker.h +++ b/applications/external/nfc_maker/nfc_maker.h @@ -17,8 +17,12 @@ #include #include -#define TEXT_INPUT_LEN 248 -#define WIFI_INPUT_LEN 90 +#define MAC_INPUT_LEN GAP_MAC_ADDR_SIZE +#define MAIL_INPUT_LEN 128 +#define PHONE_INPUT_LEN 17 + +#define BIG_INPUT_LEN 248 +#define SMALL_INPUT_LEN 90 typedef enum { WifiAuthenticationOpen = 0x01, @@ -45,10 +49,14 @@ typedef struct { ByteInput* byte_input; Popup* popup; - uint8_t mac_buf[GAP_MAC_ADDR_SIZE]; - char text_buf[TEXT_INPUT_LEN]; - char pass_buf[WIFI_INPUT_LEN]; - char name_buf[TEXT_INPUT_LEN]; + uint8_t mac_buf[MAC_INPUT_LEN]; + char mail_buf[MAIL_INPUT_LEN]; + char phone_buf[PHONE_INPUT_LEN]; + + char big_buf[BIG_INPUT_LEN]; + char small_buf1[SMALL_INPUT_LEN]; + char small_buf2[SMALL_INPUT_LEN]; + char save_buf[BIG_INPUT_LEN]; } NfcMaker; typedef enum { diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_bluetooth.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_bluetooth.c index 4e70a184d..e392ea096 100644 --- a/applications/external/nfc_maker/scenes/nfc_maker_scene_bluetooth.c +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_bluetooth.c @@ -16,7 +16,7 @@ void nfc_maker_scene_bluetooth_on_enter(void* context) { byte_input_set_header_text(byte_input, "Enter Bluetooth MAC:"); - for(size_t i = 0; i < GAP_MAC_ADDR_SIZE; i++) { + for(size_t i = 0; i < MAC_INPUT_LEN; i++) { app->mac_buf[i] = 0x69; } @@ -26,7 +26,7 @@ void nfc_maker_scene_bluetooth_on_enter(void* context) { NULL, app, app->mac_buf, - GAP_MAC_ADDR_SIZE); + MAC_INPUT_LEN); view_dispatcher_switch_to_view(app->view_dispatcher, NfcMakerViewByteInput); } @@ -40,7 +40,7 @@ bool nfc_maker_scene_bluetooth_on_event(void* context, SceneManagerEvent event) switch(event.event) { case ByteInputResultOk: furi_hal_bt_reverse_mac_addr(app->mac_buf); - scene_manager_next_scene(app->scene_manager, NfcMakerSceneName); + scene_manager_next_scene(app->scene_manager, NfcMakerSceneSave); break; default: break; diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_config.h b/applications/external/nfc_maker/scenes/nfc_maker_scene_config.h index a89b4198c..4a6da04d6 100644 --- a/applications/external/nfc_maker/scenes/nfc_maker_scene_config.h +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_config.h @@ -1,4 +1,4 @@ -ADD_SCENE(nfc_maker, menu, Menu) +ADD_SCENE(nfc_maker, start, Start) ADD_SCENE(nfc_maker, bluetooth, Bluetooth) ADD_SCENE(nfc_maker, https, Https) ADD_SCENE(nfc_maker, mail, Mail) @@ -9,5 +9,5 @@ ADD_SCENE(nfc_maker, wifi, Wifi) ADD_SCENE(nfc_maker, wifi_auth, WifiAuth) ADD_SCENE(nfc_maker, wifi_encr, WifiEncr) ADD_SCENE(nfc_maker, wifi_pass, WifiPass) -ADD_SCENE(nfc_maker, name, Name) +ADD_SCENE(nfc_maker, save, Save) ADD_SCENE(nfc_maker, result, Result) diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_https.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_https.c index 9697c0d1c..a814f8e22 100644 --- a/applications/external/nfc_maker/scenes/nfc_maker_scene_https.c +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_https.c @@ -16,14 +16,14 @@ void nfc_maker_scene_https_on_enter(void* context) { text_input_set_header_text(text_input, "Enter Https Link:"); - strlcpy(app->text_buf, "flipper-xtre.me", TEXT_INPUT_LEN); + strlcpy(app->big_buf, "flipper-xtre.me", BIG_INPUT_LEN); text_input_set_result_callback( text_input, nfc_maker_scene_https_text_input_callback, app, - app->text_buf, - TEXT_INPUT_LEN, + app->big_buf, + BIG_INPUT_LEN, true); view_dispatcher_switch_to_view(app->view_dispatcher, NfcMakerViewTextInput); @@ -37,7 +37,7 @@ bool nfc_maker_scene_https_on_event(void* context, SceneManagerEvent event) { consumed = true; switch(event.event) { case TextInputResultOk: - scene_manager_next_scene(app->scene_manager, NfcMakerSceneName); + scene_manager_next_scene(app->scene_manager, NfcMakerSceneSave); break; default: break; diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_mail.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_mail.c index 5b76f56a2..eba7319d4 100644 --- a/applications/external/nfc_maker/scenes/nfc_maker_scene_mail.c +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_mail.c @@ -16,14 +16,14 @@ void nfc_maker_scene_mail_on_enter(void* context) { text_input_set_header_text(text_input, "Enter Mail Address:"); - strlcpy(app->text_buf, "ben.dover@example.com", TEXT_INPUT_LEN); + strlcpy(app->mail_buf, "ben.dover@yourmom.zip", MAIL_INPUT_LEN); text_input_set_result_callback( text_input, nfc_maker_scene_mail_text_input_callback, app, - app->text_buf, - TEXT_INPUT_LEN, + app->mail_buf, + MAIL_INPUT_LEN, true); view_dispatcher_switch_to_view(app->view_dispatcher, NfcMakerViewTextInput); @@ -37,7 +37,7 @@ bool nfc_maker_scene_mail_on_event(void* context, SceneManagerEvent event) { consumed = true; switch(event.event) { case TextInputResultOk: - scene_manager_next_scene(app->scene_manager, NfcMakerSceneName); + scene_manager_next_scene(app->scene_manager, NfcMakerSceneSave); break; default: break; diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_phone.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_phone.c index 4e70bcb09..27ad9e671 100644 --- a/applications/external/nfc_maker/scenes/nfc_maker_scene_phone.c +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_phone.c @@ -16,14 +16,14 @@ void nfc_maker_scene_phone_on_enter(void* context) { text_input_set_header_text(text_input, "Enter Phone Number:"); - strlcpy(app->text_buf, "+", TEXT_INPUT_LEN); + strlcpy(app->phone_buf, "+", PHONE_INPUT_LEN); text_input_set_result_callback( text_input, nfc_maker_scene_phone_text_input_callback, app, - app->text_buf, - TEXT_INPUT_LEN, + app->phone_buf, + PHONE_INPUT_LEN, false); view_dispatcher_switch_to_view(app->view_dispatcher, NfcMakerViewTextInput); @@ -37,7 +37,7 @@ bool nfc_maker_scene_phone_on_event(void* context, SceneManagerEvent event) { consumed = true; switch(event.event) { case TextInputResultOk: - scene_manager_next_scene(app->scene_manager, NfcMakerSceneName); + scene_manager_next_scene(app->scene_manager, NfcMakerSceneSave); break; default: break; diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_result.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_result.c index 70cb664ba..18867dc44 100644 --- a/applications/external/nfc_maker/scenes/nfc_maker_scene_result.c +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_result.c @@ -17,7 +17,7 @@ void nfc_maker_scene_result_on_enter(void* context) { FlipperFormat* file = flipper_format_file_alloc(furi_record_open(RECORD_STORAGE)); FuriString* path = furi_string_alloc(); - furi_string_printf(path, NFC_APP_FOLDER "/%s" NFC_APP_EXTENSION, app->name_buf); + furi_string_printf(path, NFC_APP_FOLDER "/%s" NFC_APP_EXTENSION, app->save_buf); uint32_t pages = 135; size_t size = pages * 4; @@ -77,12 +77,12 @@ void nfc_maker_scene_result_on_enter(void* context) { size_t data_len = 0; size_t j = 0; - switch(scene_manager_get_scene_state(app->scene_manager, NfcMakerSceneMenu)) { + switch(scene_manager_get_scene_state(app->scene_manager, NfcMakerSceneStart)) { case NfcMakerSceneBluetooth: { tnf = 0x02; // Media-type [RFC 2046] type = "application/vnd.bluetooth.ep.oob"; - data_len = GAP_MAC_ADDR_SIZE; + data_len = MAC_INPUT_LEN; payload_len = data_len + 2; payload = malloc(payload_len); @@ -96,12 +96,12 @@ void nfc_maker_scene_result_on_enter(void* context) { tnf = 0x01; // NFC Forum well-known type [NFC RTD] type = "\x55"; - data_len = strnlen(app->text_buf, TEXT_INPUT_LEN); + data_len = strnlen(app->big_buf, BIG_INPUT_LEN); payload_len = data_len + 1; payload = malloc(payload_len); payload[j++] = 0x04; // Prepend "https://" - memcpy(&payload[j], app->text_buf, data_len); + memcpy(&payload[j], app->big_buf, data_len); j += data_len; break; } @@ -109,12 +109,12 @@ void nfc_maker_scene_result_on_enter(void* context) { tnf = 0x01; // NFC Forum well-known type [NFC RTD] type = "\x55"; - data_len = strnlen(app->text_buf, TEXT_INPUT_LEN); + data_len = strnlen(app->mail_buf, MAIL_INPUT_LEN); payload_len = data_len + 1; payload = malloc(payload_len); payload[j++] = 0x06; // Prepend "mailto:" - memcpy(&payload[j], app->text_buf, data_len); + memcpy(&payload[j], app->mail_buf, data_len); j += data_len; break; } @@ -122,12 +122,12 @@ void nfc_maker_scene_result_on_enter(void* context) { tnf = 0x01; // NFC Forum well-known type [NFC RTD] type = "\x55"; - data_len = strnlen(app->text_buf, TEXT_INPUT_LEN); + data_len = strnlen(app->phone_buf, PHONE_INPUT_LEN); payload_len = data_len + 1; payload = malloc(payload_len); payload[j++] = 0x05; // Prepend "tel:" - memcpy(&payload[j], app->text_buf, data_len); + memcpy(&payload[j], app->phone_buf, data_len); j += data_len; break; } @@ -135,14 +135,14 @@ void nfc_maker_scene_result_on_enter(void* context) { tnf = 0x01; // NFC Forum well-known type [NFC RTD] type = "\x54"; - data_len = strnlen(app->text_buf, TEXT_INPUT_LEN); + data_len = strnlen(app->big_buf, BIG_INPUT_LEN); payload_len = data_len + 3; payload = malloc(payload_len); payload[j++] = 0x02; payload[j++] = 0x65; // e payload[j++] = 0x6E; // n - memcpy(&payload[j], app->text_buf, data_len); + memcpy(&payload[j], app->big_buf, data_len); j += data_len; break; } @@ -150,12 +150,12 @@ void nfc_maker_scene_result_on_enter(void* context) { tnf = 0x01; // NFC Forum well-known type [NFC RTD] type = "\x55"; - data_len = strnlen(app->text_buf, TEXT_INPUT_LEN); + data_len = strnlen(app->big_buf, BIG_INPUT_LEN); payload_len = data_len + 1; payload = malloc(payload_len); payload[j++] = 0x00; // No prepend - memcpy(&payload[j], app->text_buf, data_len); + memcpy(&payload[j], app->big_buf, data_len); j += data_len; break; } @@ -163,8 +163,8 @@ void nfc_maker_scene_result_on_enter(void* context) { tnf = 0x02; // Media-type [RFC 2046] type = "application/vnd.wfa.wsc"; - uint8_t ssid_len = strnlen(app->text_buf, WIFI_INPUT_LEN); - uint8_t pass_len = strnlen(app->pass_buf, WIFI_INPUT_LEN); + uint8_t ssid_len = strnlen(app->small_buf1, SMALL_INPUT_LEN); + uint8_t pass_len = strnlen(app->small_buf2, SMALL_INPUT_LEN); uint8_t data_len = ssid_len + pass_len; payload_len = data_len + 39; payload = malloc(payload_len); @@ -185,7 +185,7 @@ void nfc_maker_scene_result_on_enter(void* context) { payload[j++] = 0x00; payload[j++] = ssid_len; - memcpy(&payload[j], app->text_buf, ssid_len); + memcpy(&payload[j], app->small_buf1, ssid_len); j += ssid_len; payload[j++] = 0x10; payload[j++] = 0x03; @@ -209,7 +209,7 @@ void nfc_maker_scene_result_on_enter(void* context) { payload[j++] = 0x00; payload[j++] = pass_len; - memcpy(&payload[j], app->pass_buf, pass_len); + memcpy(&payload[j], app->small_buf2, pass_len); j += pass_len; payload[j++] = 0x10; payload[j++] = 0x20; @@ -353,7 +353,7 @@ bool nfc_maker_scene_result_on_event(void* context, SceneManagerEvent event) { switch(event.event) { case PopupEventExit: scene_manager_search_and_switch_to_previous_scene( - app->scene_manager, NfcMakerSceneMenu); + app->scene_manager, NfcMakerSceneStart); break; default: break; diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_name.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_save.c similarity index 71% rename from applications/external/nfc_maker/scenes/nfc_maker_scene_name.c rename to applications/external/nfc_maker/scenes/nfc_maker_scene_save.c index dd5170d94..e307c92dc 100644 --- a/applications/external/nfc_maker/scenes/nfc_maker_scene_name.c +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_save.c @@ -4,26 +4,26 @@ enum TextInputResult { TextInputResultOk, }; -static void nfc_maker_scene_name_text_input_callback(void* context) { +static void nfc_maker_scene_save_text_input_callback(void* context) { NfcMaker* app = context; view_dispatcher_send_custom_event(app->view_dispatcher, TextInputResultOk); } -void nfc_maker_scene_name_on_enter(void* context) { +void nfc_maker_scene_save_on_enter(void* context) { NfcMaker* app = context; TextInput* text_input = app->text_input; - text_input_set_header_text(text_input, "Name the NFC tag:"); + text_input_set_header_text(text_input, "Save the NFC tag:"); - set_random_name(app->name_buf, TEXT_INPUT_LEN); + set_random_name(app->save_buf, BIG_INPUT_LEN); text_input_set_result_callback( text_input, - nfc_maker_scene_name_text_input_callback, + nfc_maker_scene_save_text_input_callback, app, - app->name_buf, - TEXT_INPUT_LEN, + app->save_buf, + BIG_INPUT_LEN, true); ValidatorIsFile* validator_is_file = @@ -33,7 +33,7 @@ void nfc_maker_scene_name_on_enter(void* context) { view_dispatcher_switch_to_view(app->view_dispatcher, NfcMakerViewTextInput); } -bool nfc_maker_scene_name_on_event(void* context, SceneManagerEvent event) { +bool nfc_maker_scene_save_on_event(void* context, SceneManagerEvent event) { NfcMaker* app = context; bool consumed = false; @@ -51,7 +51,7 @@ bool nfc_maker_scene_name_on_event(void* context, SceneManagerEvent event) { return consumed; } -void nfc_maker_scene_name_on_exit(void* context) { +void nfc_maker_scene_save_on_exit(void* context) { NfcMaker* app = context; text_input_reset(app->text_input); } diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_menu.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_start.c similarity index 71% rename from applications/external/nfc_maker/scenes/nfc_maker_scene_menu.c rename to applications/external/nfc_maker/scenes/nfc_maker_scene_start.c index 4268e7e2f..77b705630 100644 --- a/applications/external/nfc_maker/scenes/nfc_maker_scene_menu.c +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_start.c @@ -1,11 +1,11 @@ #include "../nfc_maker.h" -void nfc_maker_scene_menu_submenu_callback(void* context, uint32_t index) { +void nfc_maker_scene_start_submenu_callback(void* context, uint32_t index) { NfcMaker* app = context; view_dispatcher_send_custom_event(app->view_dispatcher, index); } -void nfc_maker_scene_menu_on_enter(void* context) { +void nfc_maker_scene_start_on_enter(void* context) { NfcMaker* app = context; Submenu* submenu = app->submenu; @@ -15,39 +15,39 @@ void nfc_maker_scene_menu_on_enter(void* context) { submenu, "Bluetooth MAC", NfcMakerSceneBluetooth, - nfc_maker_scene_menu_submenu_callback, + nfc_maker_scene_start_submenu_callback, app); submenu_add_item( - submenu, "HTTPS Link", NfcMakerSceneHttps, nfc_maker_scene_menu_submenu_callback, app); + submenu, "HTTPS Link", NfcMakerSceneHttps, nfc_maker_scene_start_submenu_callback, app); submenu_add_item( - submenu, "Mail Address", NfcMakerSceneMail, nfc_maker_scene_menu_submenu_callback, app); + submenu, "Mail Address", NfcMakerSceneMail, nfc_maker_scene_start_submenu_callback, app); submenu_add_item( - submenu, "Phone Number", NfcMakerScenePhone, nfc_maker_scene_menu_submenu_callback, app); + submenu, "Phone Number", NfcMakerScenePhone, nfc_maker_scene_start_submenu_callback, app); submenu_add_item( - submenu, "Text Note", NfcMakerSceneText, nfc_maker_scene_menu_submenu_callback, app); + submenu, "Text Note", NfcMakerSceneText, nfc_maker_scene_start_submenu_callback, app); submenu_add_item( - submenu, "Plain URL", NfcMakerSceneUrl, nfc_maker_scene_menu_submenu_callback, app); + submenu, "Plain URL", NfcMakerSceneUrl, nfc_maker_scene_start_submenu_callback, app); submenu_add_item( - submenu, "WiFi Login", NfcMakerSceneWifi, nfc_maker_scene_menu_submenu_callback, app); + submenu, "WiFi Login", NfcMakerSceneWifi, nfc_maker_scene_start_submenu_callback, app); submenu_set_selected_item( - submenu, scene_manager_get_scene_state(app->scene_manager, NfcMakerSceneMenu)); + submenu, scene_manager_get_scene_state(app->scene_manager, NfcMakerSceneStart)); view_dispatcher_switch_to_view(app->view_dispatcher, NfcMakerViewSubmenu); } -bool nfc_maker_scene_menu_on_event(void* context, SceneManagerEvent event) { +bool nfc_maker_scene_start_on_event(void* context, SceneManagerEvent event) { NfcMaker* app = context; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { - scene_manager_set_scene_state(app->scene_manager, NfcMakerSceneMenu, event.event); + scene_manager_set_scene_state(app->scene_manager, NfcMakerSceneStart, event.event); consumed = true; scene_manager_next_scene(app->scene_manager, event.event); } @@ -55,7 +55,7 @@ bool nfc_maker_scene_menu_on_event(void* context, SceneManagerEvent event) { return consumed; } -void nfc_maker_scene_menu_on_exit(void* context) { +void nfc_maker_scene_start_on_exit(void* context) { NfcMaker* app = context; submenu_reset(app->submenu); } diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_text.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_text.c index 17d84e0a9..4c86f3b52 100644 --- a/applications/external/nfc_maker/scenes/nfc_maker_scene_text.c +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_text.c @@ -16,14 +16,14 @@ void nfc_maker_scene_text_on_enter(void* context) { text_input_set_header_text(text_input, "Enter Text Note:"); - strlcpy(app->text_buf, "Lorem ipsum", TEXT_INPUT_LEN); + strlcpy(app->big_buf, "Lorem ipsum", BIG_INPUT_LEN); text_input_set_result_callback( text_input, nfc_maker_scene_text_text_input_callback, app, - app->text_buf, - TEXT_INPUT_LEN, + app->big_buf, + BIG_INPUT_LEN, true); view_dispatcher_switch_to_view(app->view_dispatcher, NfcMakerViewTextInput); @@ -37,7 +37,7 @@ bool nfc_maker_scene_text_on_event(void* context, SceneManagerEvent event) { consumed = true; switch(event.event) { case TextInputResultOk: - scene_manager_next_scene(app->scene_manager, NfcMakerSceneName); + scene_manager_next_scene(app->scene_manager, NfcMakerSceneSave); break; default: break; diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_url.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_url.c index 1250b46af..f5d2eddaa 100644 --- a/applications/external/nfc_maker/scenes/nfc_maker_scene_url.c +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_url.c @@ -16,14 +16,14 @@ void nfc_maker_scene_url_on_enter(void* context) { text_input_set_header_text(text_input, "Enter Plain URL:"); - strlcpy(app->text_buf, "https://flipper-xtre.me", TEXT_INPUT_LEN); + strlcpy(app->big_buf, "https://flipper-xtre.me", BIG_INPUT_LEN); text_input_set_result_callback( text_input, nfc_maker_scene_url_text_input_callback, app, - app->text_buf, - TEXT_INPUT_LEN, + app->big_buf, + BIG_INPUT_LEN, true); view_dispatcher_switch_to_view(app->view_dispatcher, NfcMakerViewTextInput); @@ -37,7 +37,7 @@ bool nfc_maker_scene_url_on_event(void* context, SceneManagerEvent event) { consumed = true; switch(event.event) { case TextInputResultOk: - scene_manager_next_scene(app->scene_manager, NfcMakerSceneName); + scene_manager_next_scene(app->scene_manager, NfcMakerSceneSave); break; default: break; diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_wifi.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_wifi.c index 68efaf7f6..cfbc51ace 100644 --- a/applications/external/nfc_maker/scenes/nfc_maker_scene_wifi.c +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_wifi.c @@ -16,14 +16,14 @@ void nfc_maker_scene_wifi_on_enter(void* context) { text_input_set_header_text(text_input, "Enter WiFi SSID:"); - strlcpy(app->text_buf, "Bill Wi the Science Fi", WIFI_INPUT_LEN); + strlcpy(app->small_buf1, "Bill Wi the Science Fi", SMALL_INPUT_LEN); text_input_set_result_callback( text_input, nfc_maker_scene_wifi_text_input_callback, app, - app->text_buf, - WIFI_INPUT_LEN, + app->small_buf1, + SMALL_INPUT_LEN, true); view_dispatcher_switch_to_view(app->view_dispatcher, NfcMakerViewTextInput); diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_wifi_auth.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_wifi_auth.c index 4cb90dabe..6e14c4e97 100644 --- a/applications/external/nfc_maker/scenes/nfc_maker_scene_wifi_auth.c +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_wifi_auth.c @@ -65,8 +65,8 @@ bool nfc_maker_scene_wifi_auth_on_event(void* context, SceneManagerEvent event) if(event.event == WifiAuthenticationOpen) { scene_manager_set_scene_state( app->scene_manager, NfcMakerSceneWifiEncr, WifiEncryptionNone); - strcpy(app->pass_buf, ""); - scene_manager_next_scene(app->scene_manager, NfcMakerSceneName); + strcpy(app->small_buf2, ""); + scene_manager_next_scene(app->scene_manager, NfcMakerSceneSave); } else { scene_manager_set_scene_state( app->scene_manager, NfcMakerSceneWifiEncr, WifiEncryptionAes); diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_wifi_pass.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_wifi_pass.c index 3f5b4bd76..acf1d7447 100644 --- a/applications/external/nfc_maker/scenes/nfc_maker_scene_wifi_pass.c +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_wifi_pass.c @@ -16,14 +16,14 @@ void nfc_maker_scene_wifi_pass_on_enter(void* context) { text_input_set_header_text(text_input, "Enter WiFi Password:"); - strlcpy(app->pass_buf, "244466666", WIFI_INPUT_LEN); + strlcpy(app->small_buf2, "244466666", SMALL_INPUT_LEN); text_input_set_result_callback( text_input, nfc_maker_scene_wifi_pass_text_input_callback, app, - app->pass_buf, - WIFI_INPUT_LEN, + app->small_buf2, + SMALL_INPUT_LEN, true); view_dispatcher_switch_to_view(app->view_dispatcher, NfcMakerViewTextInput); @@ -37,7 +37,7 @@ bool nfc_maker_scene_wifi_pass_on_event(void* context, SceneManagerEvent event) consumed = true; switch(event.event) { case TextInputResultOk: - scene_manager_next_scene(app->scene_manager, NfcMakerSceneName); + scene_manager_next_scene(app->scene_manager, NfcMakerSceneSave); break; default: break; From e448f5a315019ba5c6efac68b8456978f44a896a Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Wed, 12 Jul 2023 03:08:10 +0200 Subject: [PATCH 103/364] NFC Maker add contact vcard support --- .../nfc_maker/scenes/nfc_maker_scene_config.h | 5 ++ .../scenes/nfc_maker_scene_contact.c | 53 ++++++++++++++++++ .../scenes/nfc_maker_scene_contact_last.c | 55 +++++++++++++++++++ .../scenes/nfc_maker_scene_contact_mail.c | 55 +++++++++++++++++++ .../scenes/nfc_maker_scene_contact_phone.c | 55 +++++++++++++++++++ .../scenes/nfc_maker_scene_contact_url.c | 55 +++++++++++++++++++ .../nfc_maker/scenes/nfc_maker_scene_result.c | 31 +++++++++++ .../nfc_maker/scenes/nfc_maker_scene_start.c | 7 +++ 8 files changed, 316 insertions(+) create mode 100644 applications/external/nfc_maker/scenes/nfc_maker_scene_contact.c create mode 100644 applications/external/nfc_maker/scenes/nfc_maker_scene_contact_last.c create mode 100644 applications/external/nfc_maker/scenes/nfc_maker_scene_contact_mail.c create mode 100644 applications/external/nfc_maker/scenes/nfc_maker_scene_contact_phone.c create mode 100644 applications/external/nfc_maker/scenes/nfc_maker_scene_contact_url.c diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_config.h b/applications/external/nfc_maker/scenes/nfc_maker_scene_config.h index 4a6da04d6..0ef4f021f 100644 --- a/applications/external/nfc_maker/scenes/nfc_maker_scene_config.h +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_config.h @@ -1,5 +1,10 @@ ADD_SCENE(nfc_maker, start, Start) ADD_SCENE(nfc_maker, bluetooth, Bluetooth) +ADD_SCENE(nfc_maker, contact, Contact) +ADD_SCENE(nfc_maker, contact_last, ContactLast) +ADD_SCENE(nfc_maker, contact_mail, ContactMail) +ADD_SCENE(nfc_maker, contact_phone, ContactPhone) +ADD_SCENE(nfc_maker, contact_url, ContactUrl) ADD_SCENE(nfc_maker, https, Https) ADD_SCENE(nfc_maker, mail, Mail) ADD_SCENE(nfc_maker, phone, Phone) diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_contact.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_contact.c new file mode 100644 index 000000000..ecc054617 --- /dev/null +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_contact.c @@ -0,0 +1,53 @@ +#include "../nfc_maker.h" + +enum TextInputResult { + TextInputResultOk, +}; + +static void nfc_maker_scene_contact_text_input_callback(void* context) { + NfcMaker* app = context; + + view_dispatcher_send_custom_event(app->view_dispatcher, TextInputResultOk); +} + +void nfc_maker_scene_contact_on_enter(void* context) { + NfcMaker* app = context; + TextInput* text_input = app->text_input; + + text_input_set_header_text(text_input, "Enter First Name:"); + + strlcpy(app->small_buf1, "Ben", SMALL_INPUT_LEN); + + text_input_set_result_callback( + text_input, + nfc_maker_scene_contact_text_input_callback, + app, + app->small_buf1, + SMALL_INPUT_LEN, + true); + + view_dispatcher_switch_to_view(app->view_dispatcher, NfcMakerViewTextInput); +} + +bool nfc_maker_scene_contact_on_event(void* context, SceneManagerEvent event) { + NfcMaker* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + switch(event.event) { + case TextInputResultOk: + scene_manager_next_scene(app->scene_manager, NfcMakerSceneContactLast); + break; + default: + break; + } + } + + return consumed; +} + +void nfc_maker_scene_contact_on_exit(void* context) { + NfcMaker* app = context; + text_input_reset(app->text_input); +} diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_contact_last.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_contact_last.c new file mode 100644 index 000000000..78f6ba712 --- /dev/null +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_contact_last.c @@ -0,0 +1,55 @@ +#include "../nfc_maker.h" + +enum TextInputResult { + TextInputResultOk, +}; + +static void nfc_maker_scene_contact_last_text_input_callback(void* context) { + NfcMaker* app = context; + + view_dispatcher_send_custom_event(app->view_dispatcher, TextInputResultOk); +} + +void nfc_maker_scene_contact_last_on_enter(void* context) { + NfcMaker* app = context; + TextInput* text_input = app->text_input; + + text_input_set_header_text(text_input, "Enter Last Name:"); + + strlcpy(app->small_buf2, "Dover", SMALL_INPUT_LEN); + + text_input_set_result_callback( + text_input, + nfc_maker_scene_contact_last_text_input_callback, + app, + app->small_buf2, + SMALL_INPUT_LEN, + true); + + text_input_set_minimum_length(text_input, 0); + + view_dispatcher_switch_to_view(app->view_dispatcher, NfcMakerViewTextInput); +} + +bool nfc_maker_scene_contact_last_on_event(void* context, SceneManagerEvent event) { + NfcMaker* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + switch(event.event) { + case TextInputResultOk: + scene_manager_next_scene(app->scene_manager, NfcMakerSceneContactMail); + break; + default: + break; + } + } + + return consumed; +} + +void nfc_maker_scene_contact_last_on_exit(void* context) { + NfcMaker* app = context; + text_input_reset(app->text_input); +} diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_contact_mail.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_contact_mail.c new file mode 100644 index 000000000..07f9c3cb7 --- /dev/null +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_contact_mail.c @@ -0,0 +1,55 @@ +#include "../nfc_maker.h" + +enum TextInputResult { + TextInputResultOk, +}; + +static void nfc_maker_scene_contact_mail_text_input_callback(void* context) { + NfcMaker* app = context; + + view_dispatcher_send_custom_event(app->view_dispatcher, TextInputResultOk); +} + +void nfc_maker_scene_contact_mail_on_enter(void* context) { + NfcMaker* app = context; + TextInput* text_input = app->text_input; + + text_input_set_header_text(text_input, "Enter Mail Address:"); + + strlcpy(app->mail_buf, "ben.dover@yourmom.zip", MAIL_INPUT_LEN); + + text_input_set_result_callback( + text_input, + nfc_maker_scene_contact_mail_text_input_callback, + app, + app->mail_buf, + MAIL_INPUT_LEN, + true); + + text_input_set_minimum_length(text_input, 0); + + view_dispatcher_switch_to_view(app->view_dispatcher, NfcMakerViewTextInput); +} + +bool nfc_maker_scene_contact_mail_on_event(void* context, SceneManagerEvent event) { + NfcMaker* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + switch(event.event) { + case TextInputResultOk: + scene_manager_next_scene(app->scene_manager, NfcMakerSceneContactPhone); + break; + default: + break; + } + } + + return consumed; +} + +void nfc_maker_scene_contact_mail_on_exit(void* context) { + NfcMaker* app = context; + text_input_reset(app->text_input); +} diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_contact_phone.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_contact_phone.c new file mode 100644 index 000000000..e9d16a217 --- /dev/null +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_contact_phone.c @@ -0,0 +1,55 @@ +#include "../nfc_maker.h" + +enum TextInputResult { + TextInputResultOk, +}; + +static void nfc_maker_scene_contact_phone_text_input_callback(void* context) { + NfcMaker* app = context; + + view_dispatcher_send_custom_event(app->view_dispatcher, TextInputResultOk); +} + +void nfc_maker_scene_contact_phone_on_enter(void* context) { + NfcMaker* app = context; + TextInput* text_input = app->text_input; + + text_input_set_header_text(text_input, "Enter Phone Number:"); + + strlcpy(app->phone_buf, "+", PHONE_INPUT_LEN); + + text_input_set_result_callback( + text_input, + nfc_maker_scene_contact_phone_text_input_callback, + app, + app->phone_buf, + PHONE_INPUT_LEN, + false); + + text_input_set_minimum_length(text_input, 0); + + view_dispatcher_switch_to_view(app->view_dispatcher, NfcMakerViewTextInput); +} + +bool nfc_maker_scene_contact_phone_on_event(void* context, SceneManagerEvent event) { + NfcMaker* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + switch(event.event) { + case TextInputResultOk: + scene_manager_next_scene(app->scene_manager, NfcMakerSceneContactUrl); + break; + default: + break; + } + } + + return consumed; +} + +void nfc_maker_scene_contact_phone_on_exit(void* context) { + NfcMaker* app = context; + text_input_reset(app->text_input); +} diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_contact_url.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_contact_url.c new file mode 100644 index 000000000..1d2f61d28 --- /dev/null +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_contact_url.c @@ -0,0 +1,55 @@ +#include "../nfc_maker.h" + +enum TextInputResult { + TextInputResultOk, +}; + +static void nfc_maker_scene_contact_url_text_input_callback(void* context) { + NfcMaker* app = context; + + view_dispatcher_send_custom_event(app->view_dispatcher, TextInputResultOk); +} + +void nfc_maker_scene_contact_url_on_enter(void* context) { + NfcMaker* app = context; + TextInput* text_input = app->text_input; + + text_input_set_header_text(text_input, "Enter URL Link:"); + + strlcpy(app->big_buf, "flipper-xtre.me", BIG_INPUT_LEN); + + text_input_set_result_callback( + text_input, + nfc_maker_scene_contact_url_text_input_callback, + app, + app->big_buf, + BIG_INPUT_LEN, + true); + + text_input_set_minimum_length(text_input, 0); + + view_dispatcher_switch_to_view(app->view_dispatcher, NfcMakerViewTextInput); +} + +bool nfc_maker_scene_contact_url_on_event(void* context, SceneManagerEvent event) { + NfcMaker* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + switch(event.event) { + case TextInputResultOk: + scene_manager_next_scene(app->scene_manager, NfcMakerSceneSave); + break; + default: + break; + } + } + + return consumed; +} + +void nfc_maker_scene_contact_url_on_exit(void* context) { + NfcMaker* app = context; + text_input_reset(app->text_input); +} diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_result.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_result.c index 18867dc44..ad7371b5c 100644 --- a/applications/external/nfc_maker/scenes/nfc_maker_scene_result.c +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_result.c @@ -92,6 +92,37 @@ void nfc_maker_scene_result_on_enter(void* context) { j += data_len; break; } + case NfcMakerSceneContact: { + tnf = 0x02; // Media-type [RFC 2046] + type = "text/vcard"; + + FuriString* vcard = furi_string_alloc_set("BEGIN:VCARD\r\nVERSION:3.0\r\n"); + furi_string_cat_printf( + vcard, "PRODID:-//Flipper Xtreme//%s//EN\r\n", version_get_version(NULL)); + furi_string_cat_printf(vcard, "N:%s;%s;;;\r\n", app->small_buf2, app->small_buf1); + furi_string_cat_printf( + vcard, + "FN:%s%s%s\r\n", + app->small_buf1, + strnlen(app->small_buf2, SMALL_INPUT_LEN) ? " " : "", + app->small_buf2); + if(strnlen(app->mail_buf, MAIL_INPUT_LEN)) { + furi_string_cat_printf(vcard, "EMAIL:%s\r\n", app->mail_buf); + } + if(strnlen(app->phone_buf, PHONE_INPUT_LEN)) { + furi_string_cat_printf(vcard, "TEL:%s\r\n", app->phone_buf); + } + if(strnlen(app->big_buf, BIG_INPUT_LEN)) { + furi_string_cat_printf(vcard, "URL:%s\r\n", app->big_buf); + } + furi_string_cat_printf(vcard, "END:VCARD\r\n"); + + payload_len = furi_string_size(vcard); + payload = malloc(payload_len); + memcpy(payload, furi_string_get_cstr(vcard), payload_len); + furi_string_free(vcard); + break; + } case NfcMakerSceneHttps: { tnf = 0x01; // NFC Forum well-known type [NFC RTD] type = "\x55"; diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_start.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_start.c index 77b705630..e791b1284 100644 --- a/applications/external/nfc_maker/scenes/nfc_maker_scene_start.c +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_start.c @@ -18,6 +18,13 @@ void nfc_maker_scene_start_on_enter(void* context) { nfc_maker_scene_start_submenu_callback, app); + submenu_add_item( + submenu, + "Contact Vcard", + NfcMakerSceneContact, + nfc_maker_scene_start_submenu_callback, + app); + submenu_add_item( submenu, "HTTPS Link", NfcMakerSceneHttps, nfc_maker_scene_start_submenu_callback, app); From b1e13d44b8b4c9d0423cfe52de2566a6177e6527 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Wed, 12 Jul 2023 12:28:51 +0400 Subject: [PATCH 104/364] Dolphin: add new animation (#2865) --- .../external/L1_My_dude_128x64/frame_0.png | Bin 0 -> 1615 bytes .../external/L1_My_dude_128x64/frame_1.png | Bin 0 -> 1637 bytes .../external/L1_My_dude_128x64/frame_10.png | Bin 0 -> 1044 bytes .../external/L1_My_dude_128x64/frame_11.png | Bin 0 -> 990 bytes .../external/L1_My_dude_128x64/frame_12.png | Bin 0 -> 1100 bytes .../external/L1_My_dude_128x64/frame_13.png | Bin 0 -> 1494 bytes .../external/L1_My_dude_128x64/frame_14.png | Bin 0 -> 1460 bytes .../external/L1_My_dude_128x64/frame_15.png | Bin 0 -> 1440 bytes .../external/L1_My_dude_128x64/frame_16.png | Bin 0 -> 1210 bytes .../external/L1_My_dude_128x64/frame_17.png | Bin 0 -> 1399 bytes .../external/L1_My_dude_128x64/frame_18.png | Bin 0 -> 1454 bytes .../external/L1_My_dude_128x64/frame_19.png | Bin 0 -> 1648 bytes .../external/L1_My_dude_128x64/frame_2.png | Bin 0 -> 1629 bytes .../external/L1_My_dude_128x64/frame_20.png | Bin 0 -> 1433 bytes .../external/L1_My_dude_128x64/frame_21.png | Bin 0 -> 1032 bytes .../external/L1_My_dude_128x64/frame_22.png | Bin 0 -> 1054 bytes .../external/L1_My_dude_128x64/frame_23.png | Bin 0 -> 1050 bytes .../external/L1_My_dude_128x64/frame_24.png | Bin 0 -> 939 bytes .../external/L1_My_dude_128x64/frame_25.png | Bin 0 -> 1447 bytes .../external/L1_My_dude_128x64/frame_26.png | Bin 0 -> 1509 bytes .../external/L1_My_dude_128x64/frame_27.png | Bin 0 -> 1504 bytes .../external/L1_My_dude_128x64/frame_28.png | Bin 0 -> 1529 bytes .../external/L1_My_dude_128x64/frame_29.png | Bin 0 -> 1625 bytes .../external/L1_My_dude_128x64/frame_3.png | Bin 0 -> 1599 bytes .../external/L1_My_dude_128x64/frame_30.png | Bin 0 -> 1575 bytes .../external/L1_My_dude_128x64/frame_31.png | Bin 0 -> 1609 bytes .../external/L1_My_dude_128x64/frame_32.png | Bin 0 -> 1635 bytes .../external/L1_My_dude_128x64/frame_33.png | Bin 0 -> 1668 bytes .../external/L1_My_dude_128x64/frame_34.png | Bin 0 -> 1588 bytes .../external/L1_My_dude_128x64/frame_35.png | Bin 0 -> 1551 bytes .../external/L1_My_dude_128x64/frame_36.png | Bin 0 -> 1656 bytes .../external/L1_My_dude_128x64/frame_37.png | Bin 0 -> 1545 bytes .../external/L1_My_dude_128x64/frame_38.png | Bin 0 -> 1650 bytes .../external/L1_My_dude_128x64/frame_39.png | Bin 0 -> 1028 bytes .../external/L1_My_dude_128x64/frame_4.png | Bin 0 -> 1623 bytes .../external/L1_My_dude_128x64/frame_40.png | Bin 0 -> 1225 bytes .../external/L1_My_dude_128x64/frame_41.png | Bin 0 -> 1256 bytes .../external/L1_My_dude_128x64/frame_42.png | Bin 0 -> 1055 bytes .../external/L1_My_dude_128x64/frame_43.png | Bin 0 -> 831 bytes .../external/L1_My_dude_128x64/frame_44.png | Bin 0 -> 623 bytes .../external/L1_My_dude_128x64/frame_45.png | Bin 0 -> 556 bytes .../external/L1_My_dude_128x64/frame_46.png | Bin 0 -> 928 bytes .../external/L1_My_dude_128x64/frame_47.png | Bin 0 -> 1206 bytes .../external/L1_My_dude_128x64/frame_48.png | Bin 0 -> 1019 bytes .../external/L1_My_dude_128x64/frame_5.png | Bin 0 -> 1648 bytes .../external/L1_My_dude_128x64/frame_6.png | Bin 0 -> 1570 bytes .../external/L1_My_dude_128x64/frame_7.png | Bin 0 -> 1063 bytes .../external/L1_My_dude_128x64/frame_8.png | Bin 0 -> 1024 bytes .../external/L1_My_dude_128x64/frame_9.png | Bin 0 -> 1078 bytes .../external/L1_My_dude_128x64/meta.txt | 32 ++++++++++++++++++ assets/dolphin/external/manifest.txt | 7 ++++ 51 files changed, 39 insertions(+) create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_0.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_1.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_10.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_11.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_12.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_13.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_14.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_15.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_16.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_17.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_18.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_19.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_2.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_20.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_21.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_22.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_23.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_24.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_25.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_26.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_27.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_28.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_29.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_3.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_30.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_31.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_32.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_33.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_34.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_35.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_36.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_37.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_38.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_39.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_4.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_40.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_41.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_42.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_43.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_44.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_45.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_46.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_47.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_48.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_5.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_6.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_7.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_8.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_9.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/meta.txt diff --git a/assets/dolphin/external/L1_My_dude_128x64/frame_0.png b/assets/dolphin/external/L1_My_dude_128x64/frame_0.png new file mode 100644 index 0000000000000000000000000000000000000000..bf07d03d6e633a1cd9725d181b4970b96d216058 GIT binary patch literal 1615 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 M07*qoM6N<$f?iSKcK`qY literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_My_dude_128x64/frame_12.png b/assets/dolphin/external/L1_My_dude_128x64/frame_12.png new file mode 100644 index 0000000000000000000000000000000000000000..19fc985ac866535e616f20dacc7956deb9bf1f9c GIT binary patch literal 1100 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_My_dude_128x64/frame_14.png b/assets/dolphin/external/L1_My_dude_128x64/frame_14.png new file mode 100644 index 0000000000000000000000000000000000000000..9a3a84fff837373f3e503913b6095071d044c33f GIT binary patch literal 1460 zcmV;l1xxygP)AxL1WuIkKPX1RU|H3IM(9i+z9&O$8Zz9^wF+NT%vb zZB|9*Q&fP8a_ODi4sdt!p8_fBsd}Z9a(O%DIywy>qtpAm}OE zuNClo%NW&lF99AE=|2^2skHPywQ% zIMWgCw9Kp^zm(u5AX3WzvLaCA_fj(#7H0|10ixG)>P1Fo z&rOOJcaZ-P`P9#_PJd?Y=VfU)gN%vSeD$(ow}WtNK6O|I%b795HYo=|+@P}^f$Kv{ z?k*>=Bz)0_XG>24vmF8UG1JeElgZ3O9t}Pmz&ikKmRd^A(!)7}G@jmWr4v{qKeDg) z{TBSDc+xe5teR{No@K)`9xeH!IdzZY$6ky<%ddM;I4w)hbONjNM~|i3yR=@%Z9OPR z1Lb~Y>9l=**6%Guulplzq+knM0=(-wHPBNOAu{@A$h~&zo_wm2#^B6gDCzIZ#0f{i5lg|$9Kk~$4M*JlVPYP!1Y~R-cVxsPM7-lD`lVe! zS~6NDb^&le-smr7yj9dXf>b{Ov+o-9X_wv0PpV66?Y{zy2AS=h8hN!kg4AvXo{65d z;q6mYp4lZp^2%A85@sDiYA+#z{24o$=L5CYa^bH_T#0|qI5OqV2k@Y|oI$&t(k7Fz z?h5j$Jv_HOO3SB)*Y^?-y(UE=;%vZ^y=NwUGd8&A9{%1*AT}l?9IN-RQD*`xb`d+I zU&|LRFH9z`)QwJ^ad%v)MiZ|-{Pyr;_ zxC^N5p5-01&hC|dynXBKB$BPXQKjl6kTP~B^a;`IZf0ypC4HWJf#XNIzOWI^_1(}j z|IzrPoO(o}MNzb+Rsp!{kB>nkkGW&?Q;3RC={j;u*1omZ*OMck!|Xdnl0WKy6zS2| zA0LCF*T|D0$+agU;e8BE&R})0+3~(}0C zGfANryU1^?{r-)h)8(H-1ZnVHcPL9FsnE7mpZpql0^t?~!iiVEwkD6e6i_15>I9|e zmzCE^q(fj5ESWW>T$oTPQsDh8>|Fcb0aCy&fSa_P&M_iR+m>pBor{vs*?wonmnrkA z9DsXCO%!A&PG0+*l5kG2A{a8(ezyYPmy^hsCM9yxMr-nnD)e^R#5XcA9wnUV5zi^W zZ1h`SIKmDGt9@#cx#*{y#M&peeN+xGnxF{GPTI)M2wLT$NQLb+lc>dRvdOY6a)53R zPDX^CSy;&&O(Gmus$3_O?B58=Kx^{fyS3Vp>TzWx`L%u?OP@flru+w0?-z}=lz5x~ O0000zkvSgZur`#u4^_xmY;$ErOv@jajJ z4KR;yqW~7`j0oHz>z zC3-U|h2L6|0%QmfVi|aYFY1NgTA~NYvf@fbcv6_*gb%HSFV?+on0OSKuKbfdf+T!; z0+8;tR09D&KsXE8NgcQ0r&#d}G~sja@fheEp}Z7ak=aX3vEm8tUyAu$y#%+BeU+aA zD%^KPcv!6R+u zct$Z}`7@C2?SK37HteyYYDUj~`Q$1k@XSm38eQMlF~N!E$4tC+3LEscZg z)j0u$wkAzB(mlucO!N0x9EUqGytP_SFynP9){|NY0*n?ZWv_QD`cyrwTorQge1NSr z`LAwApK<-3jesI>^WQ}gvXvkb_bq)E+5MV7D+FX~ady%ZLDt?lI=I;5NmR42&1g55 z%taQ*tpR%n%UQn?SA>E{ogyN`O?tQ>)tXzYvBuFR=!1|ug$WBX;=Z*$pvjJg>gf<* zbzL5MfQU6z7qVvPM}g#6{tu&fo|1t)+&5!c5mgs$MV@6faU@;##9wVG(c>>O5w7h3E8?QO%76Q z$K$zvXg5o&Ut#%`u5+6oMpoL9gGS1X(e?FT^+t(U zuAhS5`63g?+$)4{;UA6tqt@yHr0dgH!ZeL}s`bN$qaMtf%&nMX_Ii7xod)hHLpYE6~EyI}QtG&i4N@ff3AAgld(itt-IyX*>PTTrnEZLVyg z|7!RB{50?h?mv5NngUQ`xQZXMLiOA)dVsqF)>x^ecQJrU5AgFOp>sI0*`z&O>E5lV z3xLgT>9K<^VuYTF45_S|hg~Esk@{U2P0_50xI^R0p7o zn8}PWqD+h=V44EaKJ65AwIXoB$9-j|0B#|p&5shLj6^D`T#_*9+N9IZRo=j&lw}@3 zT2R);ql%!#_J~)I+&`sT?GaW8f8Ue;ZJY+0qVO<6LCAC1reeC|!S)C|$2Ga(c*iidap z5-oZLq#EFBn$I=a!cI6X3N!$pkBdH5MZl2k zJTHWKF-OVa&DUGE`tw7nwH6KHsI`8a>#?%F1;YGNuHO71A4whN`QOs1JG@{}oF&Hz zo{*EauHALOdU|qMO{e(bTC$jLQ6D@HYt&$Et+mm3_Z~+pubt8TkSDxs;KBIbO z<9~)6Kgsj|J_B$MabkFxt`URc3&mayO>^Sdsq1JHK=-|Nyi}wWv%SN5a#=QkXae*G zIKAUnMA$K<=ZK7%4!h&u0-sKV>ZDzAa2;6i%yf-$R33OuGM-NbRWH6~q4N$tr^Y=e z!m8=mY_8exbCHcu3SA%O6g~lVN#LS(Oy|s&m_Axl+YA&B4kdx>x1PKjT>hItZpU+` zk3`ny;l#Dx?b9Jy?xK1EaAde5wB~y>!zirt zt-PBxy1P#Rt1+e(D|>kBXFdTW$*E#@$ZJK+8WuaY$LR?G^E^0-&d1#^9lLNX?GD7(CHXb_Y1UIsn%2(z7I2~oxSfxm zm)t9vB%{e=@j8nzALo5m$CYGE@4ODEG0O$KZ}v>T-rD?&p%s%lYIdSzT?^B(Uk^@h zk<5}bi;BbD#tyMP%f>3!05Rz-t|W45cQCZKi*CH^SC zg&C(e5w*!5>ZEoNPsAo!Gyz-}H^!(bFk04m(tNC&*}n&fLTONwWHmC>&DA5-t=9VE z{$FUNs-|Z#?QHVQKyRb67@C#Kjczfs~)$5}HsN!d0jP8f$EsORJOVdy2p!EaB YADMSfJkRd}+5i9m07*qoM6N<$f@&T_hX4Qo literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_My_dude_128x64/frame_17.png b/assets/dolphin/external/L1_My_dude_128x64/frame_17.png new file mode 100644 index 0000000000000000000000000000000000000000..fd910ce5a61c659c7caf0486f2eef29eed874edf GIT binary patch literal 1399 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 IM6N<$f>4IGLjV8( literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_My_dude_128x64/frame_19.png b/assets/dolphin/external/L1_My_dude_128x64/frame_19.png new file mode 100644 index 0000000000000000000000000000000000000000..d602a01d5b14b4134e49fdd08ca5441ee263c2db GIT binary patch literal 1648 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 N002ovPDHLkV1kz(xQ_q; literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_My_dude_128x64/frame_25.png b/assets/dolphin/external/L1_My_dude_128x64/frame_25.png new file mode 100644 index 0000000000000000000000000000000000000000..832c2bde9f9cda0876517a95f8d7b6f9a7350b11 GIT binary patch literal 1447 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_My_dude_128x64/frame_28.png b/assets/dolphin/external/L1_My_dude_128x64/frame_28.png new file mode 100644 index 0000000000000000000000000000000000000000..94e05f94d9d191742662fcbf308a2eafd80f54df GIT binary patch literal 1529 zcmVoO27YwfGe>VrP3^|=~UF70==pIvy(=oBY5qI`fKMWfR3p0};W&ar2p z+yGWs^=>1m@cvUUg#je=F3@geQ=B44B}xror5Efnj3;?Nz(NMNJMOGx3uOjiEzs!V zY2Lnz1)k#l0KW_n!rhf@;i~B5$qU4RjB`p&VieOEpvpLQg#k%^sW^34{Dy!8&={WL z{Qw09@MQXb^4t(5IRFQz#Y&poMY%NgKV=61P?!W#tX`DXTDdZ`*pFl3{oOHS0H-Vj z^+P6o3UmCB;Y)yoJ^_kMtY0q{leuzHaqvhlBqG-aNuWY4grXQJ~k4S-j^ zEbLymsGQP%UVCo$oIE+WVMpJic=3*S2X|0@1!f*+_ad?zk-oi*^ow|5N74iTi}E>6 z@%|B5&P(SuR0#4GDI!26GADyPexBz2bbNq$YbF{%#S!bOMYZCfhC9Z7?I>J&FM}@w zpx^ZO8bQUjan?AQWa$)R0EjDFo1Zq9F9V33$c2X-4^)T>OOBibO1R6`4QEc1DlnQSTRx30iqHVGt!*_o9;L|Dq1`u&sy2t@koTB9!ChF+eq{_qblI+md5-@^?y@p-z9% z8|zA6?imoF^;Nwvk=jF5?VuMk4Z=Z)hjEvnRv@+D*G^}_7)QAUi&CDsAxK+^rJc)Kgc_IQ3( zNSIw~0kFU6_^1SOfwZE~>^yq_;t3SN{WdQoJ!~jzJEL72Y4>C1| z=sIYLLZzcXxqqKq~|?`k)YNAGB|0+5M8jS5=V?6s(kDJk-VWt z@d)*cpe$&1x!VXRGW0*H+}>O&%e)r-XV$;9_MHQafLifrGLwNdmX!T_iCT}6y?#7+ ztl(X>Fs_Y_&tB3?Bajwk6(ZhJMdzcrfhIrK!{~UL5n%tN48S|b3Cx6N;r#ch%BQGO fJ~bx(hyDKnt&=3EFH=)P00000NkvXXu0mjf(FWoE literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_My_dude_128x64/frame_29.png b/assets/dolphin/external/L1_My_dude_128x64/frame_29.png new file mode 100644 index 0000000000000000000000000000000000000000..1ce384b165cb3b83bcffcc872254ad52ea846971 GIT binary patch literal 1625 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_My_dude_128x64/frame_30.png b/assets/dolphin/external/L1_My_dude_128x64/frame_30.png new file mode 100644 index 0000000000000000000000000000000000000000..8d42b8b482bade89f498e5ed1cae3a82bf89ee26 GIT binary patch literal 1575 zcmV+?2H5$DP)ZS7q4@pXh&b}8Xmfz6By2Q`mvWB6 z$=rd{R(}+ChtR2ri18ub_x*F;-?J z*?^iuOBV2L;G7Tsz8Y15aESw)5~Quboa;$7i42xh0xOiXYa6JRjkk{pYai8Y^FIi5 z02(io|9dk4?%fJ!NISqc)4#+xku5;d)h->jFaY+4 z?A4OIXkQf-K+Hawxu+c9lt>sK9aoR1qUNvFNj%`TrO5a~4seR$F{&J)qvqeiCY%h< z{9O*<5xior)&i;uNA0NjcfvbovjbF8SQWI`BjN}ZMqAC0nSV{09Dw_0jt8BE8tOaM zx7RS)BkWE`;Qngo#WP>D9OPI!SWS7Bv0-IN$FWrhTD#8*B*3{mWAH}9b#Do~148RK zt|*6pzsaG$5;`)S8WS( zobUU7J|5Yr?-6yL(lK?zViPPp7D$^#-o)ePfKB7zO`Q42v_x)CV{?_ibP z=K&Tu-T#`BaL)l)gnQ|edREOSkWL{ix1~9%onR1S$iZ49N*BN?TC|w!oW`YJsn`_q!QD$_`Ej zr5s9`*8Q|<-kr>4i7!M3z)V0nM@8APG9u0@RH|EW?J7~pJW~Eo+4;JzpF$9K)dX9B zi#6Kw4sgLAE}?exwObC9g|Clfe90+1;NGE}L91R_Xmhs9|mXhC3SO<+YVh37=s^@a_pK!LjNTzN2knIA6V)yBcaE>I@yBLa^hy=^P$ zAIgOH54Y0seKiAUbZ(ivrX;HZGnDNylEiY=u-f&;9>LvL(-xPGdD60qj3i>fiaxbi zC)<}4@RBjWykbwm&#Rq)lc)6)UA0!_b~so^&}Z$FWq{cTH2F7L1!}sm>Plk)&sKmxT-KIifNwDR1UD~k_2Mhno_mjbKF1&hb{g>NryQ`H0e%ZO z14}_4Gz3bH`IIIp7BcPN<(dK;axng-*Ox2|?&)!cm zz!c(L{gBdjLfcy@XU8iBKDS;q`IbfI1?AA*akDW%bUmQu+qQPh|cNX=F_f93$n7)VLx#NHGBC z2;M>Masmv>DIiN#(Yoh~$HVEIa$4$lh;{}z)d{54PxYTQV&;m+gP8?BgYEGLJ92bh Ze*l(B5h$^b7i0hc002ovPDHLkV1m%s`g;HX literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_My_dude_128x64/frame_31.png b/assets/dolphin/external/L1_My_dude_128x64/frame_31.png new file mode 100644 index 0000000000000000000000000000000000000000..ac926d7be3120cd6322739b62a89f67b326563c0 GIT binary patch literal 1609 zcmV-P2DbT$P)dSg`=RNpK1QE70NrC`5^|+t?gFVfT+Q#-mc$$_1FjSiRs?WTfQmAWta)ehRX? zpSxEgR~I?T2p}y$cBgW?eYF?p9!rvq$x)@puLlTXb?nbpR2)i@z;{f3yf|%+RmpLC z6;QQs+=O>j33I&bx{f1;7r!CE04e|Au1m7u5z6yCUxhRQ-hBsnG3N_g!Cs?yD**1< z3No~K0IXOgYj?Q&DuPS0c5R{(0(6xqp7B?BBFg3O_JG;(Qv^8W?Hn`){GEjq`=eu3 zbO5C{kzRxdaEjqEx`;4kQe?x`M?8v5ljM)H+dnzd@w_Tin6h59BUJ+WA}5r)v6b~-#ZpJrxyai zn<_m4^@gxBrea9fSI7QL-OfSV_1yV~wdHAjcnHl+&q4W8C zj@w$6%&>48q(n2TZv`1FGO9L6Bnf7}3G}*>li<-Y+UNJm=B`(fEQ&iT;8W3A213JE zS3@`?O81WOj{ugK9VH2+L|2CGMR%K&z(K77qL2(?t;H?RMEeb^8b6s9Zxq&-V&AF&s!sO^#DekPgvXOm&r7daPfbu^ zC|C(8l)qE~(qeiRw!3^S<$$Kkn#J&@y-yUJ{(@9VQ~g z^So@M?D`b~sDyz^AWmDvidHqm>R4?>WRBS5KzqWj>5W^c)Xx7TNbY|Zya_3l8{ucT zbI{%<4^RzQ4_AQm0;dwV+G{~rDlfMOkg5ZZVz>!{n0uLJTN0T zrr@JjG3`88+EJ6xKlM3#*)t{DboX zoPBS}_>|k2gLHuk4_w0I(Su9=q&Zd8+T8UOIf}Nquj~5%RWP^X@$V|dHFv&i2YGeq zLbdy!M{L)x*TK?`?{NQ=i}ECemIcz}K<^S=@MuyzMlZo|f(y^V-`8I~__|oi4XPB4 zz*9F%wZF$06?3-|H#3fB4Q}hs?}}TIiFPDTE5_{(S$GVKUB7)0Ld6K`d^NZvkGr=D z(wr*Dp7Czc&;sXUsp`Ic5=JqFI^P9L_G|@L+Hpqr^ccE&2a;SVH^SF65ANy%tTO%t zc3=3sS5C+Mp8UfKZGE|Q;qJBCRqX?;G(MgL{96S)SI^vc*8i5oo$LdgX#6T@**|5l zNIxPA&P7X*u9tcMZLpsL%`;d%hqkFap#cA3a$AnKb`u`>UBK=Bs=kBwH@*f97u5%b z$B6OGIe>Lyr^8wSP*sq0Klhw;o-!UhFA;;g&Xsy49w3w9o&qZVQ=S1E#<~f#lB;|D zq;X9iK$_e;!Rr309W&2+j?_3(PF0auOY;fm08_{(>~;(;oKcy#cM;+9nw)u_(kWYY zdG%&<0L$Xx>nA{qoAVwiVZ!%B7FBY?oSyhxgbZm95RFwZt33fx_ir_#w6nGASid<2 z)IGY6X%CPA+*PN+>i#oE*5u6V@W|`n1I&RUApqYC7kL%9aVeJ%|6hH? zI!n4r;PQxV9snz5nM4OT0^{F1jE_A4Ha6af(jS~hW?p{)K=@bv8-Shm00000NkvXX Hu0mjfz{LRy literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_My_dude_128x64/frame_32.png b/assets/dolphin/external/L1_My_dude_128x64/frame_32.png new file mode 100644 index 0000000000000000000000000000000000000000..35070eb6b4a23c5e6493389e66a563f9171ae144 GIT binary patch literal 1635 zcmV-p2AuhcP)<$Zr!=JTD$MedqQ_3dNi%@o2Cw&94$|7YAy8wfGLjSn_|)*{!OR@3Z%0F- zV;TH4l;;3hf=fx?j%2X&yRIvBVS1l~qgOT0|4ASKDgPLse=4R8;5UT-5q-Wp+R~kG zroojYfmFaufp>)aod(V&gU1=bs*F|Rzha@rz295^BN0Q&ak#Op`A!+YDW`KZi_Vfq z>ph^nq!UdJUb!>o8!~`X7M`NY5IRjQ5kxU7lhrfuMh2Gwcoe&0ua@MbwAvjV?y?XKCDb5-mxt?7T$dg+OftjZF`WxYWX21-55%u_}M_Nj!kzhypC_!ul{dRbaK{5g)8_&#zMxN-Q)uVr82^AI8lRwNLLUU)Q zFSDJ?L9345p4|Zn5UZYf?vaB-#z9X+My=wZ2R7A)2loN0tZ??&6Fo(X_8V35;l5=ttI5X>$b z+L^0@w2o#3;6APrur=NgtR~8g0%`t}Ri;^VyvC10iUO_(V0r$EIvEL>9)pyUzj02K z)@<0XdLIR{p08g8OCC?}vm$^M2EBWD1$OM!Rs1-<_kI664(39kbe}0y$5>Y6WmUv3f=`$;WyZQjD7Qcfl1IKYMor?E8`3FkR>P1L;(dZ%9$4QG% zCo%r50@n*bfmS7m$9sV!?%efN1$Y+!5LF zBg6B@1*U)A#lJ4{djQP}Kg|dv?~f{<&fj}Kj%DbQZjX`0hjRdHVxI!E0Zs`Okc^-?0hNqc4#-2Tr+2I^I)6IdlzW(3gqb9|u)P$}F>py8kMm_F{ga(uL$ zDkJ;X<`d2VI+1Vib_y%hM#3lJ=~@Kf@~qheF(n5;lt-VR0Bhgu?`R1VeJ7$+EeuO? z(y@p!Oh*9C2sHHXPBDaN_(vB}^K9!mm9O&{WsmA{Is(jK^s3WMNC5lU0M1M{J7#R4`-`6_5;=-tSm^8Ug5H hQ%BVP#(B)l>klc^gV)~j;w%6F002ovPDHLkV1mQPe z&+|NO0ebJPwbpR>-uuTjZ4COXH0BAQEL7IW8mncB&imdeNL0Y*d49cfdV>*JtT51406?MumB=4_~ z{Uu9|1p2NU7V^%nVH9%YgFYM zS-fTk_*Qroqj{ag64mj4jEQr4*j;S|S=T(^gjm5MnK5_t-)TjK#z3!CJE8YGM1K-H zU~Sy&{fHbKA|;hX|4|ut7*ho9KENu$GbM@kz9%P%cu{rZr=m?<1fOObjdYZPNJfNS_v(be^de5W>#dzRm#bBDbb)Mx}Wvp9Y!#dGvK;kV<78@ zf6q=ni|9u0WCu@=paPK`h|}v&2*3{s+)*0%B&xlwGKORJzZ=MuRG+j<`KBk_@!K$) z?METySllzwF(pXtcuv1>ivg-ZH=<$=$yjeQV9n^Hq&5nxbv=&M2wwQnLdyZTbFX07 z{gRp^Rq&RY-sEp3jaPL9HRi_H&oTfqffgR3f?3x+Uf}HwXzz1O;lWL8l7Xa0R}L_v z{D^?9@rEE7M+J2v`&B6VWcaZ{A|78JPsj7sNfU+#i6SF%NXehJrixnoBrNCdz5n+w zVVcL|c@+a#bRLc2E3j(p-Bp&R*PqY9n#Zr8e~zLeNug&0t#K&3WEB$Glb!tlOmOKn zdfr=Wk5!_q>R67-}$5Yk)`j;@yQn>4@U@146^^c<#;_!LTT$Q~8Zoj2DrTci# zV(?HOV3+a-kiPJGbet8>NAeHvcvde$+81p+&(Z7y>{LE3g8p^^*BV*7w<_^?d=#F< zojd+i0TJcjoD5+Sq-FoNtRqrsX;llizG()KCi~Z5F@uLWDmw+u)`D86{iejQjK`$%bQj*OXy^U!B0QK+j|n(3*Ea znwO(|`uFZ5%B;z%LCPbJF#rnYS;PuR28`z?l#dL6%0@@jesmo(>-q!Mx7!J;WBAzs O0000 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 B=oSC~ literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_My_dude_128x64/frame_36.png b/assets/dolphin/external/L1_My_dude_128x64/frame_36.png new file mode 100644 index 0000000000000000000000000000000000000000..d2cd0c970c61dea42452df834901ad2a0501a55c GIT binary patch literal 1656 zcmV-;28a2HP)uPiWtMMVeVe+b?pOUj<{4EYp z83`w{KsNfNyhrP^8q1QviEpIIiEiB`+1NR%^eA_Ll?L9)0;|`4EWfJHS6YGtQKey} zunVU#SJj*>b$|}+aE{sE7~?p{@S=1H`1Z-*ASIz(yCe%J4`Eh8TKlCl z4oxOTR7QmpvJMa#Aa$_wTfjL(=6%@aK~+L}U5o={Jc>KmS%J#kGHnjLS$l;n2~=`u zk=s@UU`JR1YPXDQ^PqaqzZ2fTBORbJ;j)x1S>tdrqCY6fj(s)zWgLA-KM7kkkPJ&i zbkoMgMSNt#C#qvae)&(rC>rk=%xapHCTu11oq+qgdmvo9Wxr;1y;53q_YvPoB0^mC3k+pM}p`r^jaejF@q&PeyT_pR^3?NO;+i098DVscmIwK(7KLV6p;*7an|lZNAzQv8TnPZO`Yzpb(xz;@1csv0lYhy8cS4V z{;LvB&-mvv%l|JRVkB!n)^d;QqdNLEc~zx!M(-C?WEHf?vmkpsJ7sv}S7H91;CY@> zO24l6-z!)uw0pjUABdJIFtPdube5*vJgg{(r4kqZ&7KBY?s1^Xj}C{Zl-U{a-3F(a z5bq0ccy$=YQu=lD6?IZin-@FS(9++0=EZI4N*g%c+oT?7gFRt6g6r~1NP8~Ren0;i zBqda*7`qDHhdvZ$!dJ29k#QzG?w=(vSmxpuTfT;15%-DC8w4H zz~^4rBREg7Dyw;56(Z#wM~$4bkeg>UM)y^ccn*Qe% z+x6!-Sc>=x`kx)bq;N9Bt^^AvtWg~z??su^+XoIj%C5I3@++x}tK&VHxOD=h+8_2C z$tNPqVWf`t0#`(wU0+h9cNw2ja)M$6jwR;(%E_vDj>C$*32Wqk#DDuq809IMfmk5z8&Dspmjr6y@P1S;YyX-IuUsit!fhIZ zzcGNNRPaoE1Rjr{&vr+zrbBhV+T40%4xn8tjZevn^us+HF#=~ik3L$ztCmo! z@m&m1sW=z$Rxh_cr)B@yrJxENMMw4WFu=Xq_z+MoPF_|bQkr?cPiV`X#rZj=#Lj&z z1HgfB1NRz4YtQBd99vnxs}qo?fzmozJ@FBF7Iv_9DafcK*y{{w2H+-HbBGSsn2z9C9^ktZR%iAYQHpG>9qTvyfVxNLGtB^= z38OmRxqNuQ1 zd7kG0<2a5XiPf%WWmV*$aU91OWBk>F>a!z{=>~9T;zz{bMX-fVbdHvwsX=09euAof+C}-^BuNnf+t@XMhx*>SPO$ z0C=NeJiKA{kFkIOP{!H43_RJ%7QVX`cp|QV)}2Pq*(<0Nlm#9Oyu>*8aQNAd6H0WCp!rt=9CI!3bI~ROHZORu;Q;VT zy*uC;u@eX$y>?66Qh(XD2)l;@fE%OI@$7Y(|Cai$G6HJNx7zMnO56b8gZHp{AQ9c@ zy-4zOm&`wcuC8VP@I9}E-3E*5nJwO+Gk!z`(E1!L3pD_qDRg;K(9S1S=*0 z?aTW{L45^%21gpDnar&07+HE9D+OAG?q|L1K?Z_X+=}Y`jXszu2CW1-(VU&Qt;!3$ zg4cELliiy>VcUnCv9s21{#QkJ2WKXM{{mLO+gI1(MBkkDBjs%kA1ZJLB&B8;87*^0 zFd&LS2wzbdAE|SxlT3j{O2p_NI?KNF%Nl28q2|(WM-j|QjMYL=GODv*8k4l#RmLi{i7YYE zzP#roStq@YA(^eeZ7WBl~%M@tm=`OE}ROY*d5XY{|TZ!|PN zX@33jL5Rcv()F3jckXB5(IrB@2g*WdXiMA=2B3;c3?N;f3DlNP+RjSn{P{?K+1i0; zJHBN@!(#R?Jz1Fmdg#vfZ2QdfUXoP>l4-qM?pF?QC+F{kR11ir8yHA?Yc&sb7w|^s zTlB9vMJk}I0Lso*!K=goV>~1Koehr}I?RDW*7+d@xbU5T$=*$o0`|?*)O>uAsa literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_My_dude_128x64/frame_38.png b/assets/dolphin/external/L1_My_dude_128x64/frame_38.png new file mode 100644 index 0000000000000000000000000000000000000000..70e56b168bf780745f329c7eabd85a4f48c5ae39 GIT binary patch literal 1650 zcmV-&295cNP)c;^d8YrnSe7xyUx8+fKgNH`1x3R% zt=owcj6aKV2Us!T-A-_d16H7g0XSqGW4Ey-c%mQLb;}Q9&~ZHDK9z#3q&t}OYI;4< z8OQkPkhqS^n{b;_2l!Xujs6|r7-)=7#{iTq0h$74l_SyU>gC~Emodx&RQ~@NAc+&B zpMxb^2?Lz0fGTvcSSLI(x9=eT|qT9Uxbfkd|NZge|1cU+$R7QT_lpl?d*{%uXoe z01?HmSgR#DDjdOzrc;bRt7o?ZSVz4(VC5cFjBw(qAKisyS-|B0(l2u&=u#S%MUpG( z2&WjCL-q1zIso;43#%39k&U||&HOBFr(;z7#EygN01;~09Wh=xxvd<1zk4niTEtUX zGdqEY>j>9J$6;Wrt-^EVAMvQ*??5~fZ9J;_w&yDTD^@HZxc zbUCP4#@O4yiiv{|+^ft}0??;+M{(Qg6E z9AUx_2cTJH6}oQ-Em<{3IVt)jXm-(-0t=7#$kTh{w@P8(Au2?od8Ir=|9x&Y$x z1pG|#cnujj3_1aodCU%QP9VJpXctzJBZ#{I?V{y$Cx{sBl#@OVSl3)47(L?VY$qNB zRifKzI{H7w8R*-i>oM0(oRZ}oUE$4Bz2Dv!hUW5k$5>L1>7 z8Sm){Y6RLP-R$>J|2_zkwh!Dp4W3AyfwV2MAZ5_>dG}Jlp;9I~@0;HINhSS%cJNt5 zo!~U1a?qSK(-+neSH51aanqztD(U|x;I}1$TDFhdWE>xt*g0;?Qz{U-sq4n7iz?jz zYH-^=8u>qxK#c(^ARQxDj3I41cSkeGkp0fu`*TS(cMrhX7uP?ukyOr3iV92nsy0^U zfTZ&5f--9c)*c?(aPPf#eI#RBA6G#0UJkPiA~A&r8RKW=k?JHnN&R~4c}vFCKJEaf zHaZ5>K^xk3hCQm-4fo+?6))pxc97P1+#c7{@|HY9`q&bD7Sy=d@LXFjq~uSVQ`8}{ z&d3>0d+#Z|?ccu&mLi@$S22J^=UED^LuGZWd=Uefy?KM3L02(G>wQj#aNt%5eEh_lb?#)^2& zE}r*VfA1UsH`3OcPFPBeCS_1+R7l%<#-fo`<&>O}aJ?rN@V-%YJEi_-r5b&(mV%PX?!|){I?2puCsFo{c-(BiC>hFrJRk9!Enn#_^!tCxX~93TrERj0vA|1-2}^#ku|8U4P}_}4{pfLTYtPO#b$D(HU&?P7VV wjk%QpsIo1ibb*@(m1C#Uu{^tte_hx318OBM5yM-Wwg3PC07*qoM6N<$g62{mX#fBK literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_My_dude_128x64/frame_39.png b/assets/dolphin/external/L1_My_dude_128x64/frame_39.png new file mode 100644 index 0000000000000000000000000000000000000000..450b4d4f63dbc666d19228fd3223d0ca0c621835 GIT binary patch literal 1028 zcmV+f1pE7mP){-C}4AW%O z6H`hl#u!pcVK<^e+CRn^LI{w4An=3ObTzr9um}!K%1->CHlOJNl=Rlg)~@G)oYy+Q zEGNioXoSS?kpze)

9$$O&zcY9YR~w&q?BeL2bjN@6GxES+45_>u$E0!_+J{6U}m z8bSQud&y5JndW#PWhefildzJx>i*jNwQmYJ0yK;SOLC11GlQr@!RMp< zuoI!*2F?@#b&+23PZWWh=Kvfv1m&NzIC@Avnf#Peh$-RzDtJ@`R^qvP)C5q9mGvR} z41W$Fp%hl)^SXIx)~2|sK6szKI)IjF$t@(8sLi9-J@jGL;dz_`(4?g(-NR43CGvXc z`e*e|BERVXt=Lfn-KUicB~g>NW<8AvrSKpI2}K8>jVNJBX&ig)E1yFCT52@KT4=?c z@6pydv;Lhp{2U-RsfIJ`ixVP|HgZ~}H9gq*iuK(-2WW{-I>l53^giwB=j))!=Q+7@ z08~?2PLM~8@~@6opzB>pevFPk1&oS-i=kHp=sI)WtJZ&{16W0nh0qfo>RgGBb$}ib zSnfsFwF|d70B!yX5ok}dMg(Y5Ug-dofED7hL~UM-&wm1F<|X~HlHP!t2qApUKq65&zJ^>{Wc*xHqR=*9*!C&*>^F~k`J#PqS z=J_TJXa!ybql+Xi!Z3J9;^K9HW3e?h?2ju>JJ2MdaMhsubpKWG>agbH0b8;2vA1@; z?(YKfCaVxlW>1|OQby~W-M>fwJ;1Kd$u%4aFXI2-Gu_|02jLgb=wvR!#eh%ZA`o39 zF;~$p^6-vitZ7;faB;JS@ yDz9ztS}*C(#=>ZSglUmpyZ?%1?}@Knm&PybUz7epZzaC~0000{{JuMJW)BWFJmx-?CD5Vwh3T^Zv(0J zIM4GW1IKX?5s}v9IF8SKuiX!ODaAzL<(WB#xwUgvqz zd7g9}$LAwcyLD~Qj4sENeSX*ja0#eL@75SU4sjn|M0!NG2)4fWT=^L7ej8gp`(X{f zGC-E-^$~k1ADuu3*Q>X-M;t->E|svjhh==CE06GYFxL3mIDZ{-efXz417w7I2G!bm z_p)tK{WND-l_5rWN_6fLtxI0M18Yws{l1HMX2cEN7#1q*9XJlo$fwy~Ve}82FE7H++ zd=b3+qxIFRl_&>@u2Px9?T*qNwlT5J2J@oODlL_z6m9p#0kp}^zFNwm7+uK7 zIb~`_N*|v?M@G$}D(zc(ppHEf5YgI6IhMZe7{q=Qig;0^ zSk~xhsZ?KLCxPH+~}zpD(*lAEy^k-`C1VAKgvZX(-OFS>SSlEDty=OqWp zZoe|9nlN_+^e}0cn!kcGW%yi}0e0Hza5P8o&Lnu-RXSMY>TRJ zhw-0^a2tpK-Sf^y4=Ei*hB&X@^IL5>-pE$SI!BI>IUpI`l4(bwE7LBQzjMl$cv}xN zgLn(;m68<)Mbh{?oH9!6SdL5w&@h|z_c)AY{5v9028+xJcj{x|Ya1xxEh^uNX`f!D z(PLfBS)vzOv#wG%si(F!JCusce+coEvQn(OZ>#Rw0!E zkO^Ck;Eme@#DP#J8!^_5F}HPqPp2U5%J!}To!hpkUuF@j8TyC;S35vz8`$7wgI)rZ zaRjfF+<6Yb} VUEO9qCnW#?002ovPDHLkV1gCk1=0Wj literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_My_dude_128x64/frame_40.png b/assets/dolphin/external/L1_My_dude_128x64/frame_40.png new file mode 100644 index 0000000000000000000000000000000000000000..369200345db3985d5b105b40ed3ba37e3a0ea311 GIT binary patch literal 1225 zcmV;)1UCDLP)WJa;M*fY6M zDWwomx%fMgGLX1<5L1x2cnm26iHlbOE*?V4K;q(6fQyHaGLX1<72x6_qzoi3UIn;# z2q^=Ji&p_I9zx1M;^I|+i-(Xhkhpji;Nl^q3?vQ&{dSz@a}-mM*jQx#o}W9-_h6pR zlURw>wDT+xDfn*36eJcGzn+u3pI`f}_>~HP6uEc(*I)_~8x)|P*REyYdjg-oZ|Hd* z%8b>J@$?fENwcnb{Lk{sgs%)F(B#(QdB)4RJ=6RlO?Ns^BlyZd0!=QGF3#f~?K(F$$n1A2Dkzq5_uBUbD95 zA@jdN0eBL$9gHGR-b~@5%%fEGy+kD5)lCrgO`vA2B1QV=>uBQ;AEWbE7Drk-1*oKW zGAh=Clb;D<##X8Yq5v!M8`T6;&j6AXM^1hu=;$U`X_7ZlyfW~0EdQJbgr)Sq3A7gF zNr()#wO-G9<7Tdp+(uU39<2O()>jpvI&C?t?^}yU1$9aRp6CDD7iznUZ`G&r8Bzc% z2$%EVAd%vEcPT$Fi|F-@@>{6{)zD`A_bI>{%$@Ek>g+su4oT36@7`Fe;db5@G<@+% zyNfSxCoLYW>!?*JFQxo5-VgLExJs`DjR8*-W*g_(B|K! z0FjGYL83*^ypt_%E#>R^=qW;U(YfDO>*TCcD=4JsS~3trcc7f(HH4dN2CKPuHUak= z@@rr(2yf9_Nn2rcJx`-OPydS|elePnHdHwCa3kM3x1aHD43e$er~IVg`TP4MPF zS^iqVG5AB_z_L8!y#C;ZFfpuo`*G4>~WSS^Ty0NPKAC zXX^J5QSp+DQ~<9cw62}BUS70uClo;XC88e%kSBL4KyQb0elqbD>w#Kpr^!REp{;G@ zg^!OY{;UE>zeE)K3@|Eth!R)@UhG`Vg|UOjQQ{H`v( z^w<19Kx7j{72jHK))2*M<%btf&PR*jDOQA{O25#jfZp|Ns1;_`Rx8{t|2!TlKdY5* zK*ac*nIl8I5lF3sXz#TIo(@9ewd*(?FIqo^uP>NvL3@+)t;HYB!`m}}v{JBVfY$h* n_4fbE6}k6CS(bQ^dT90sAA)%(Wh!_*00000NkvXXu0mjfk>5o{ literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_My_dude_128x64/frame_41.png b/assets/dolphin/external/L1_My_dude_128x64/frame_41.png new file mode 100644 index 0000000000000000000000000000000000000000..e0f882268275af59257326019b77b30c145070ac GIT binary patch literal 1256 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 JPDHLkV1iSbhV}pe literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_My_dude_128x64/frame_44.png b/assets/dolphin/external/L1_My_dude_128x64/frame_44.png new file mode 100644 index 0000000000000000000000000000000000000000..f425bcc179405dedfcbf095ce131b75707a636a7 GIT binary patch literal 623 zcmV-#0+9WQP)Hos!R)?YJ15L?jey!PUnW3d2QCjBTVZ%cj}7jHkcMWFgO>x-qa_c<7c zrrmkzL6SWeZc)<$ADB(l^IXO@gnbR96lhmg{`}KNnz9z4klwz4@^2SG{M{bRYfbZY9C-C?eZ? z08<2WutT!*$IDl8>F+Qm(~%xmRsIh!J7u>rGVe1Vb~B$ULNL8q%3tpNVpU*LEk6r) zW}XL4(eJ{&pJm*fOIiTAVkjkjZ~jxolH2d9A||KyVl3rjtzYe{s%B<4KE?n5002ov JPDHLkV1h`J6Q2M8 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_My_dude_128x64/frame_45.png b/assets/dolphin/external/L1_My_dude_128x64/frame_45.png new file mode 100644 index 0000000000000000000000000000000000000000..b0ea1a7e78c0b590c493451bc16d27dfbd421928 GIT binary patch literal 556 zcmeAS@N?(olHy`uVBq!ia0vp^4M6O`!3HERU8}DLQk(@Ik;M!Q+`=Ht$S`Y;1Oo%( zJx>?Mkcv5PXGZrm8wjwp-}!&;-uwtJfn#Pj-kl3l?)6d-s*)=yo9v;vw8P`WOdfW@ z01s}#fMW#$jD-vuNfsRq77QYWGA<4>3|$g@N(y`ou0S;c>oUFo?wu76 zg|E)ay31R7w|a7vg$Sp<6~mVVK5zDh?6&#L>=g>d4;X+}Sun_)3^l*K-1VbDYct0W z2KI(|U^l!-(!8>(ZsKyj3BkHTTrvsve}ei%E=zq{_E#C`xQrJJA0)x*Ds-}UKKQrm zQB<(4ifH=N=e66W1l`)E8{~EI@Xd>>k2h|WD4c)sZ}nZxGPSFR_XMS%4tRIXd{@1G zyPCDJQr8|~z0?2qr9U$M?)Ycc<|v*i`pt1HBA1+w-00nQ;o-UdIlmA0?bvZR#614) z^!1;8#baga_}U*O7HU4*Q_0t^^zfu}U4>AY`Q|CwU!T0Fy!*{=!;ed;V(CXuSw$`0 zt-rLl>~2KG`IUUupj%ioWi#iK@kh`xU5^^EHL^RJYD@<);T3K0RYV)<>vqZ literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_My_dude_128x64/frame_46.png b/assets/dolphin/external/L1_My_dude_128x64/frame_46.png new file mode 100644 index 0000000000000000000000000000000000000000..3113ff2e623dbc8f684d790420b396e3e1fd811c GIT binary patch literal 928 zcmV;R17G}!P)}cXvuQxv{iDYrc2Fmz3-sv83^Kk}L(s zc{R{tAn-^9&@$DGkSl=}04;`_3SfadNuCjJ#{_6GTvPz7WbBMP z8oMSyi{YXISS4d;+?kL}&>P2t7zn(+=I(<`%RIA2XqiW}VKoJh!)g!{17^ur#_q8nk~s zPGCO&A3;>SS@CJVmJveqDLwEc8su1!Ie|m;n^b^N?1_)&_Y_QMaYOv503kjQq3;4p zaQigqZvf~=EiX74-El0OCB;hrSouyWz-;XPf>p*S`{)&S_mzIE0!VGOzm@X?zw@mx zI(kxu$r@^Co>S<8L?8^42NDxx@4M2xq6-oe;-h&y4U-2F10-GWcqFLmLP%iW9IGLTJ9^)ozbq8^7tsZY zKp3WK#|5cCbJ0KhXflCtGvG-Cez3%``+Ask;7{>O7_ar67yzPur^Oeri<$h7|7w4UhF^2sml2^GJDY4u zt+kd?>cPK>B|{Mh|6{u!wr_@}4;}|NcpTv14lEgpICvc3;3g~?ia2;2;NT`K8H&h; z_!hZ8tRZCIXmX#1JBA>*zx_pILwp(|5!c|3JDSXlPze0oH)8Ra;N49FtWKP;La1>BDmjs(>qAm(L6zhN&gx66l01a z5)1(p2R;{B=V)J#I?7YUwazEa>$Puc2guem4l4(M`KXiVu`kX);{Zl|=MZ&O{1#gA zcZ#qTQxuWF0bs73{YCG8{X8hwbrnw>#cDo_`pTPxPreqB{li|Y`jX(wTXZZIhrRWj zXx8Gxu;ydbKQ{;?9#-^#&zm~{H`ShbxR~7!4n))moZa7wUpEK9ldZ`k6%(J784;B8=iPR1E%&q@wUcR}x zPm_f3+)zY|4{O=M&sA%!8Q{=l~I- zdp3{r?;ZkNSL2~Q7tKFaSFP_?T@Z)oe)zuC&4Ukb-lcf^6flZ*odj`2zRg&j{*}IG z9U+Pr3cLRcpfHo&_lB<4O4hMk{88TUp}-Dc)m*grjksP7)d@Kkgb|l~S=`9xC1Oa{^>U5YP_=nGpf)e8{Sgi@>j;!EvmqH4 zZ}Gz&AiMY+`O=du8HyF*+q$QT|40YO5U>_c(Q$b2_-YTdV$80iP(#j+h!}E>J{<3j z$69}W4*;8)-E4)7c0IBQtxuwbUCrjf>mYiBl`V7mv^RSq`UHXihxElf^zgU9a-~-CH;5 z>ZX#D_f6ur$bKkbowrW`J)2s#83tQkP5x}+xBjrb;}Z_Ai^f*Z#{syLjvre(!#CQ0 z;{wL6lP!Jx>n#Ajm|65uXvLT8!R?nFZ?zXR0GNmsC*bnz913mphH1B$vwFbz2SXzB Uk+^RXRR91007*qoM6N<$g2xy?&Hw-a literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_My_dude_128x64/frame_48.png b/assets/dolphin/external/L1_My_dude_128x64/frame_48.png new file mode 100644 index 0000000000000000000000000000000000000000..2734e2fcd57ef6b102da9c2a977264694246b63e GIT binary patch literal 1019 zcmVWDt>!^@==SY6v+uI=P?5h$0A%&%c7vK{+G^7KhfbovmUKNs2OGzjjAg382i^-IqbvR!$)z5w{8p+}aJUitM9m7ED2;&qY5^${xQ96Rp!zUljtpe~oNABIa4=K9YB%wMZ zf@Q23^h6Y4L5s@3wE}od@r53ikZaxX81u`S081vcR)#pH*_yNzLD)LrR~!g4wU*%c z@g|_SR>)5>pU5}?B9b$Lk2e9^1b7iWL`?v`J`*4~c!}L+>!r^|#s8=Ypb->gwZnUb z@feOcL?gJutH#&vM;Ad&Pc*;&UzFGDRlZ*P0$;DPhIPLcW@tU~UJpC8&Jt7Q#p;*38*u{_Ldao!k5luxh?n(OCB3fP03H zV5QTwex&(BOhlBq^R1PCRSZ3>n(xv1qp;??`W@iY%$Lt|Qh4z_UZckA{Z`|!ynlpU zC%{Vb(dO%pX_+AR9sxR@C|>@PzzpJBMbI*I1ik(4kSEsk9nW1Rz(^6Sz!C>KpQHIA z3BJAqTi43D8X5A?<5Ux1r;b^y=+(#+e=8qvzL1PGUy9>ocoo$Q&+OxA{!Sm{zO4UU z3no($>?j6H@$`_XiF!pV@4Nkrpok!55OEG0p26f9e;7pc^IO2L7lX~+T?|&#CXz;m zysR|mTivInx0He3d#~=J7`4!G zd*zLYa{uq(OvQkXZ)4utoqiR3*5usLWX6migV%FO$et|gLhF~Tu_He(u7Vnf#=aP~ pp1*pZH+vyUZ;cr<@y_}o@ef@Xp~TjZ6$$_V002ovPDHLkV1g9->bC#@ literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_My_dude_128x64/frame_5.png b/assets/dolphin/external/L1_My_dude_128x64/frame_5.png new file mode 100644 index 0000000000000000000000000000000000000000..df225594910480d7e00543e706d7286736ca814a GIT binary patch literal 1648 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_My_dude_128x64/frame_7.png b/assets/dolphin/external/L1_My_dude_128x64/frame_7.png new file mode 100644 index 0000000000000000000000000000000000000000..9703809405f7a9cb8e800272d21c3cdb499ede15 GIT binary patch literal 1063 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_My_dude_128x64/meta.txt b/assets/dolphin/external/L1_My_dude_128x64/meta.txt new file mode 100644 index 000000000..8c326cf42 --- /dev/null +++ b/assets/dolphin/external/L1_My_dude_128x64/meta.txt @@ -0,0 +1,32 @@ +Filetype: Flipper Animation +Version: 1 + +Width: 128 +Height: 64 +Passive frames: 19 +Active frames: 51 +Frames order: 0 1 2 3 4 5 6 0 1 2 7 8 9 10 11 12 7 8 9 13 14 15 14 13 14 15 7 8 9 16 17 18 13 14 19 20 21 22 23 24 21 25 26 27 28 29 30 31 32 33 32 34 35 36 35 34 37 38 39 40 41 42 43 44 45 46 17 47 48 7 +Active cycles: 1 +Frame rate: 2 +Duration: 3600 +Active cooldown: 7 + +Bubble slots: 1 + +Slot: 0 +X: 41 +Y: 43 +Text: My dude +AlignH: Right +AlignV: Top +StartFrame: 50 +EndFrame: 50 + +Slot: 0 +X: 59 +Y: 43 +Text: My dude +AlignH: Left +AlignV: Top +StartFrame: 54 +EndFrame: 54 \ No newline at end of file diff --git a/assets/dolphin/external/manifest.txt b/assets/dolphin/external/manifest.txt index 55abe0ce8..4e3dbbf11 100644 --- a/assets/dolphin/external/manifest.txt +++ b/assets/dolphin/external/manifest.txt @@ -97,6 +97,13 @@ Min butthurt: 0 Max butthurt: 10 Min level: 1 Max level: 3 +Weight: 3 + +Name: L1_My_dude_128x64 +Min butthurt: 0 +Max butthurt: 8 +Min level: 1 +Max level: 3 Weight: 4 Name: L2_Wake_up_128x64 From bf15d3ce7450490ad294145c318772caa0c06990 Mon Sep 17 00:00:00 2001 From: AloneLiberty <111039319+AloneLiberty@users.noreply.github.com> Date: Wed, 12 Jul 2023 09:14:11 +0000 Subject: [PATCH 105/364] NFC: Improved MFC emulation on some readers (#2825) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * NFC: Improved MFC emulation on some readers * NFC: Improved emulation on some readers (part 2): Some Android devices don't like this * NFC: Improved emulation on some readers (part 3): I knew that during the emulation timings are critical, but one log breaks all... * NFC: Improved emulation on some readers (part 4): Add fixes to Detect reader and refactor code * NFC: Improved emulation on some readers (part 5) * NFC: Improved emulation on some readers (part 6): GUI doesn't update without delay * NFC: Improved emulation on some readers (part 7): Reworked emulation flow, some bug fixes and improvements Co-authored-by: ã‚ã Co-authored-by: gornekich --- lib/nfc/nfc_worker.c | 11 +- lib/nfc/protocols/mifare_classic.c | 165 ++++++++++++++++++++--------- lib/nfc/protocols/nfca.c | 12 +-- lib/nfc/protocols/nfca.h | 3 + 4 files changed, 127 insertions(+), 64 deletions(-) diff --git a/lib/nfc/nfc_worker.c b/lib/nfc/nfc_worker.c index d2834fa46..145007bd3 100644 --- a/lib/nfc/nfc_worker.c +++ b/lib/nfc/nfc_worker.c @@ -1022,7 +1022,9 @@ void nfc_worker_emulate_mf_classic(NfcWorker* nfc_worker) { furi_hal_nfc_listen_start(nfc_data); while(nfc_worker->state == NfcWorkerStateMfClassicEmulate) { //-V1044 if(furi_hal_nfc_listen_rx(&tx_rx, 300)) { - mf_classic_emulator(&emulator, &tx_rx, false); + if(!mf_classic_emulator(&emulator, &tx_rx, false)) { + furi_hal_nfc_listen_start(nfc_data); + } } } if(emulator.data_changed) { @@ -1297,8 +1299,6 @@ void nfc_worker_analyze_reader(NfcWorker* nfc_worker) { bool reader_no_data_notified = true; while(nfc_worker->state == NfcWorkerStateAnalyzeReader) { - furi_hal_nfc_stop_cmd(); - furi_delay_ms(5); furi_hal_nfc_listen_start(nfc_data); if(furi_hal_nfc_listen_rx(&tx_rx, 300)) { if(reader_no_data_notified) { @@ -1309,7 +1309,9 @@ void nfc_worker_analyze_reader(NfcWorker* nfc_worker) { NfcProtocol protocol = reader_analyzer_guess_protocol(reader_analyzer, tx_rx.rx_data, tx_rx.rx_bits / 8); if(protocol == NfcDeviceProtocolMifareClassic) { - mf_classic_emulator(&emulator, &tx_rx, true); + if(!mf_classic_emulator(&emulator, &tx_rx, true)) { + furi_hal_nfc_listen_start(nfc_data); + } } } else { reader_no_data_received_cnt++; @@ -1321,6 +1323,7 @@ void nfc_worker_analyze_reader(NfcWorker* nfc_worker) { FURI_LOG_D(TAG, "No data from reader"); continue; } + furi_delay_ms(1); } rfal_platform_spi_release(); diff --git a/lib/nfc/protocols/mifare_classic.c b/lib/nfc/protocols/mifare_classic.c index ebe49a4a0..011747d59 100644 --- a/lib/nfc/protocols/mifare_classic.c +++ b/lib/nfc/protocols/mifare_classic.c @@ -851,16 +851,20 @@ bool mf_classic_emulator( bool is_reader_analyzer) { furi_assert(emulator); furi_assert(tx_rx); - bool command_processed = false; - bool is_encrypted = false; uint8_t plain_data[MF_CLASSIC_MAX_DATA_SIZE]; MfClassicKey access_key = MfClassicKeyA; + bool need_reset = false; + bool need_nack = false; + bool is_encrypted = false; + uint8_t sector = 0; + // Used for decrement and increment - copy to block on transfer - uint8_t transfer_buf[MF_CLASSIC_BLOCK_SIZE] = {}; + uint8_t transfer_buf[MF_CLASSIC_BLOCK_SIZE]; bool transfer_buf_valid = false; - // Read command - while(!command_processed) { //-V654 + // Process commands + while(!need_reset && !need_nack) { //-V654 + memset(plain_data, 0, MF_CLASSIC_MAX_DATA_SIZE); if(!is_encrypted) { crypto1_reset(&emulator->crypto); memcpy(plain_data, tx_rx->rx_data, tx_rx->rx_bits / 8); @@ -868,9 +872,10 @@ bool mf_classic_emulator( if(!furi_hal_nfc_tx_rx(tx_rx, 300)) { FURI_LOG_D( TAG, - "Error in tx rx. Tx :%d bits, Rx: %d bits", + "Error in tx rx. Tx: %d bits, Rx: %d bits", tx_rx->tx_bits, tx_rx->rx_bits); + need_reset = true; break; } crypto1_decrypt(&emulator->crypto, tx_rx->rx_data, tx_rx->rx_bits, plain_data); @@ -879,19 +884,28 @@ bool mf_classic_emulator( // After increment, decrement or restore the only allowed command is transfer uint8_t cmd = plain_data[0]; if(transfer_buf_valid && cmd != MF_CLASSIC_TRANSFER_CMD) { + need_nack = true; break; } - if(cmd == 0x50 && plain_data[1] == 0x00) { + if(cmd == NFCA_CMD_HALT && plain_data[1] == 0x00) { FURI_LOG_T(TAG, "Halt received"); - furi_hal_nfc_listen_sleep(); - command_processed = true; + need_reset = true; break; } + + if(cmd == NFCA_CMD_RATS) { + // Mifare Classic doesn't support ATS, NACK it and start listening again + FURI_LOG_T(TAG, "RATS received"); + need_nack = true; + break; + } + if(cmd == MF_CLASSIC_AUTH_KEY_A_CMD || cmd == MF_CLASSIC_AUTH_KEY_B_CMD) { uint8_t block = plain_data[1]; uint64_t key = 0; uint8_t sector_trailer_block = mf_classic_get_sector_trailer_num_by_block(block); + sector = mf_classic_get_sector_by_block(block); MfClassicSectorTrailer* sector_trailer = (MfClassicSectorTrailer*)emulator->data.block[sector_trailer_block].value; if(cmd == MF_CLASSIC_AUTH_KEY_A_CMD) { @@ -902,7 +916,7 @@ bool mf_classic_emulator( access_key = MfClassicKeyA; } else { FURI_LOG_D(TAG, "Key not known"); - command_processed = true; + need_nack = true; break; } } else { @@ -913,7 +927,7 @@ bool mf_classic_emulator( access_key = MfClassicKeyB; } else { FURI_LOG_D(TAG, "Key not known"); - command_processed = true; + need_nack = true; break; } } @@ -942,15 +956,15 @@ bool mf_classic_emulator( tx_rx->tx_bits = sizeof(nt) * 8; tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; } + if(!furi_hal_nfc_tx_rx(tx_rx, 500)) { FURI_LOG_E(TAG, "Error in NT exchange"); - command_processed = true; + need_reset = true; break; } if(tx_rx->rx_bits != 64) { - FURI_LOG_W(TAG, "Incorrect nr + ar length: %d", tx_rx->rx_bits); - command_processed = true; + need_reset = true; break; } @@ -960,9 +974,14 @@ bool mf_classic_emulator( crypto1_word(&emulator->crypto, nr, 1); uint32_t cardRr = ar ^ crypto1_word(&emulator->crypto, 0, 0); if(cardRr != prng_successor(nonce, 64)) { - FURI_LOG_T(TAG, "Wrong AUTH! %08lX != %08lX", cardRr, prng_successor(nonce, 64)); + FURI_LOG_T( + TAG, + "Wrong AUTH on block %u! %08lX != %08lX", + block, + cardRr, + prng_successor(nonce, 64)); // Don't send NACK, as the tag doesn't send it - command_processed = true; + need_reset = true; break; } @@ -985,11 +1004,25 @@ bool mf_classic_emulator( if(!is_encrypted) { FURI_LOG_T(TAG, "Invalid command before auth session established: %02X", cmd); + need_nack = true; break; } - if(cmd == MF_CLASSIC_READ_BLOCK_CMD) { - uint8_t block = plain_data[1]; + // Mifare Classic commands always have block number after command + uint8_t block = plain_data[1]; + if(mf_classic_get_sector_by_block(block) != sector) { + // Don't allow access to sectors other than authorized + FURI_LOG_T( + TAG, + "Trying to access block %u from not authorized sector (command: %02X)", + block, + cmd); + need_nack = true; + break; + } + + switch(cmd) { + case MF_CLASSIC_READ_BLOCK_CMD: { uint8_t block_data[MF_CLASSIC_BLOCK_SIZE + 2] = {}; memcpy(block_data, emulator->data.block[block].value, MF_CLASSIC_BLOCK_SIZE); if(mf_classic_is_sector_trailer(block)) { @@ -1005,17 +1038,14 @@ bool mf_classic_emulator( emulator, block, access_key, MfClassicActionACRead)) { memset(&block_data[6], 0, 4); } - } else if(!mf_classic_is_allowed_access( - emulator, block, access_key, MfClassicActionDataRead)) { - // Send NACK - uint8_t nack = 0x04; - crypto1_encrypt( - &emulator->crypto, NULL, &nack, 4, tx_rx->tx_data, tx_rx->tx_parity); - tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; - tx_rx->tx_bits = 4; - furi_hal_nfc_tx_rx(tx_rx, 300); + } else if( + !mf_classic_is_allowed_access( + emulator, block, access_key, MfClassicActionDataRead) || + !mf_classic_is_block_read(&emulator->data, block)) { + need_nack = true; break; } + nfca_append_crc16(block_data, 16); crypto1_encrypt( @@ -1027,23 +1057,36 @@ bool mf_classic_emulator( tx_rx->tx_parity); tx_rx->tx_bits = (MF_CLASSIC_BLOCK_SIZE + 2) * 8; tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; - } else if(cmd == MF_CLASSIC_WRITE_BLOCK_CMD) { - uint8_t block = plain_data[1]; - if(block > mf_classic_get_total_block_num(emulator->data.type)) { - break; - } + break; + } + + case MF_CLASSIC_WRITE_BLOCK_CMD: { // Send ACK uint8_t ack = MF_CLASSIC_ACK_CMD; crypto1_encrypt(&emulator->crypto, NULL, &ack, 4, tx_rx->tx_data, tx_rx->tx_parity); tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; tx_rx->tx_bits = 4; - if(!furi_hal_nfc_tx_rx(tx_rx, 300)) break; - if(tx_rx->rx_bits != (MF_CLASSIC_BLOCK_SIZE + 2) * 8) break; + if(!furi_hal_nfc_tx_rx(tx_rx, 300)) { + need_reset = true; + break; + } + + if(tx_rx->rx_bits != (MF_CLASSIC_BLOCK_SIZE + 2) * 8) { + need_reset = true; + break; + } crypto1_decrypt(&emulator->crypto, tx_rx->rx_data, tx_rx->rx_bits, plain_data); uint8_t block_data[MF_CLASSIC_BLOCK_SIZE] = {}; memcpy(block_data, emulator->data.block[block].value, MF_CLASSIC_BLOCK_SIZE); + + if(!mf_classic_is_block_read(&emulator->data, block)) { + // Don't allow writing to the block for which we haven't read data yet + need_nack = true; + break; + } + if(mf_classic_is_sector_trailer(block)) { if(mf_classic_is_allowed_access( emulator, block, access_key, MfClassicActionKeyAWrite)) { @@ -1062,38 +1105,39 @@ bool mf_classic_emulator( emulator, block, access_key, MfClassicActionDataWrite)) { memcpy(block_data, plain_data, MF_CLASSIC_BLOCK_SIZE); } else { + need_nack = true; break; } } + if(memcmp(block_data, emulator->data.block[block].value, MF_CLASSIC_BLOCK_SIZE) != 0) { memcpy(emulator->data.block[block].value, block_data, MF_CLASSIC_BLOCK_SIZE); emulator->data_changed = true; } + // Send ACK ack = MF_CLASSIC_ACK_CMD; crypto1_encrypt(&emulator->crypto, NULL, &ack, 4, tx_rx->tx_data, tx_rx->tx_parity); tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; tx_rx->tx_bits = 4; - } else if( - cmd == MF_CLASSIC_DECREMENT_CMD || cmd == MF_CLASSIC_INCREMENT_CMD || - cmd == MF_CLASSIC_RESTORE_CMD) { - uint8_t block = plain_data[1]; + break; + } - if(block > mf_classic_get_total_block_num(emulator->data.type)) { - break; - } + case MF_CLASSIC_DECREMENT_CMD: + case MF_CLASSIC_INCREMENT_CMD: + case MF_CLASSIC_RESTORE_CMD: { + MfClassicAction action = (cmd == MF_CLASSIC_INCREMENT_CMD) ? MfClassicActionDataInc : + MfClassicActionDataDec; - MfClassicAction action = MfClassicActionDataDec; - if(cmd == MF_CLASSIC_INCREMENT_CMD) { - action = MfClassicActionDataInc; - } if(!mf_classic_is_allowed_access(emulator, block, access_key, action)) { + need_nack = true; break; } int32_t prev_value; uint8_t addr; if(!mf_classic_block_to_value(emulator->data.block[block].value, &prev_value, &addr)) { + need_nack = true; break; } @@ -1103,8 +1147,15 @@ bool mf_classic_emulator( tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; tx_rx->tx_bits = 4; - if(!furi_hal_nfc_tx_rx(tx_rx, 300)) break; - if(tx_rx->rx_bits != (sizeof(int32_t) + 2) * 8) break; + if(!furi_hal_nfc_tx_rx(tx_rx, 300)) { + need_reset = true; + break; + } + + if(tx_rx->rx_bits != (sizeof(int32_t) + 2) * 8) { + need_reset = true; + break; + } crypto1_decrypt(&emulator->crypto, tx_rx->rx_data, tx_rx->rx_bits, plain_data); int32_t value = *(int32_t*)&plain_data[0]; @@ -1121,9 +1172,12 @@ bool mf_classic_emulator( transfer_buf_valid = true; // Commands do not ACK tx_rx->tx_bits = 0; - } else if(cmd == MF_CLASSIC_TRANSFER_CMD) { - uint8_t block = plain_data[1]; + break; + } + + case MF_CLASSIC_TRANSFER_CMD: { if(!mf_classic_is_allowed_access(emulator, block, access_key, MfClassicActionDataDec)) { + need_nack = true; break; } if(memcmp(transfer_buf, emulator->data.block[block].value, MF_CLASSIC_BLOCK_SIZE) != @@ -1137,13 +1191,17 @@ bool mf_classic_emulator( crypto1_encrypt(&emulator->crypto, NULL, &ack, 4, tx_rx->tx_data, tx_rx->tx_parity); tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; tx_rx->tx_bits = 4; - } else { + break; + } + + default: FURI_LOG_T(TAG, "Unknown command: %02X", cmd); + need_nack = true; break; } } - if(!command_processed) { + if(need_nack && !need_reset) { // Send NACK uint8_t nack = transfer_buf_valid ? MF_CLASSIC_NACK_BUF_VALID_CMD : MF_CLASSIC_NACK_BUF_INVALID_CMD; @@ -1155,15 +1213,16 @@ bool mf_classic_emulator( tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; tx_rx->tx_bits = 4; furi_hal_nfc_tx_rx(tx_rx, 300); + need_reset = true; } - return true; + return !need_reset; } void mf_classic_halt(FuriHalNfcTxRxContext* tx_rx, Crypto1* crypto) { furi_assert(tx_rx); - uint8_t plain_data[4] = {0x50, 0x00, 0x00, 0x00}; + uint8_t plain_data[4] = {NFCA_CMD_HALT, 0x00, 0x00, 0x00}; nfca_append_crc16(plain_data, 2); if(crypto) { diff --git a/lib/nfc/protocols/nfca.c b/lib/nfc/protocols/nfca.c index c401f8cc5..ab4f3f23c 100644 --- a/lib/nfc/protocols/nfca.c +++ b/lib/nfc/protocols/nfca.c @@ -3,8 +3,6 @@ #include #include -#define NFCA_CMD_RATS (0xE0U) - #define NFCA_CRC_INIT (0x6363) #define NFCA_F_SIG (13560000.0) @@ -22,7 +20,7 @@ typedef struct { static uint8_t nfca_default_ats[] = {0x05, 0x78, 0x80, 0x80, 0x00}; -static uint8_t nfca_sleep_req[] = {0x50, 0x00}; +static uint8_t nfca_halt_req[] = {NFCA_CMD_HALT, 0x00}; uint16_t nfca_get_crc16(uint8_t* buff, uint16_t len) { uint16_t crc = NFCA_CRC_INIT; @@ -50,17 +48,17 @@ bool nfca_emulation_handler( uint16_t buff_rx_len, uint8_t* buff_tx, uint16_t* buff_tx_len) { - bool sleep = false; + bool halt = false; uint8_t rx_bytes = buff_rx_len / 8; - if(rx_bytes == sizeof(nfca_sleep_req) && !memcmp(buff_rx, nfca_sleep_req, rx_bytes)) { - sleep = true; + if(rx_bytes == sizeof(nfca_halt_req) && !memcmp(buff_rx, nfca_halt_req, rx_bytes)) { + halt = true; } else if(rx_bytes == sizeof(nfca_cmd_rats) && buff_rx[0] == NFCA_CMD_RATS) { memcpy(buff_tx, nfca_default_ats, sizeof(nfca_default_ats)); *buff_tx_len = sizeof(nfca_default_ats) * 8; } - return sleep; + return halt; } static void nfca_add_bit(DigitalSignal* signal, bool bit) { diff --git a/lib/nfc/protocols/nfca.h b/lib/nfc/protocols/nfca.h index 498ef2843..e4978a3e0 100644 --- a/lib/nfc/protocols/nfca.h +++ b/lib/nfc/protocols/nfca.h @@ -5,6 +5,9 @@ #include +#define NFCA_CMD_RATS (0xE0U) +#define NFCA_CMD_HALT (0x50U) + typedef struct { DigitalSignal* one; DigitalSignal* zero; From 25ec09c7eb3682473c19047b992c3ad05cd0b7c0 Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Wed, 12 Jul 2023 13:41:46 +0400 Subject: [PATCH 106/364] SubGhz: fix check connect cc1101_ext (#2857) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * SubGhz: fix check connect cc1101_ext * SubGhz: fix syntax * SubGhz: enable interface pin pullups * SubGhz: fix syntax * SubGhz: fix CLI check connect CC1101_ext * SubGhz: fix CLI display of the selected device Co-authored-by: ã‚ã --- .../drivers/subghz/cc1101_ext/cc1101_ext.c | 55 +++++++++--- applications/main/subghz/subghz_cli.c | 34 +++++--- .../targets/f7/furi_hal/furi_hal_spi_config.c | 49 ++++++++++- lib/drivers/cc1101.c | 83 ++++++++++--------- lib/drivers/cc1101.h | 28 +++++-- lib/drivers/cc1101_regs.h | 2 +- 6 files changed, 178 insertions(+), 73 deletions(-) diff --git a/applications/drivers/subghz/cc1101_ext/cc1101_ext.c b/applications/drivers/subghz/cc1101_ext/cc1101_ext.c index 896b9bd2f..594a74a01 100644 --- a/applications/drivers/subghz/cc1101_ext/cc1101_ext.c +++ b/applications/drivers/subghz/cc1101_ext/cc1101_ext.c @@ -87,6 +87,7 @@ static bool subghz_device_cc1101_ext_check_init() { subghz_device_cc1101_ext->state = SubGhzDeviceCC1101ExtStateIdle; bool ret = false; + CC1101Status cc1101_status = {0}; furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle); FuriHalCortexTimer timer = furi_hal_cortex_timer_get(100 * 1000); @@ -94,16 +95,34 @@ static bool subghz_device_cc1101_ext_check_init() { // Reset furi_hal_gpio_init( subghz_device_cc1101_ext->g0_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); - cc1101_reset(subghz_device_cc1101_ext->spi_bus_handle); - cc1101_write_reg( - subghz_device_cc1101_ext->spi_bus_handle, CC1101_IOCFG0, CC1101IocfgHighImpedance); + furi_hal_gpio_init( + subghz_device_cc1101_ext->spi_bus_handle->miso, + GpioModeInput, + GpioPullUp, + GpioSpeedLow); + cc1101_status = cc1101_reset(subghz_device_cc1101_ext->spi_bus_handle); + if(cc1101_status.CHIP_RDYn != 0) { + //timeout or error + break; + } + cc1101_status = cc1101_write_reg( + subghz_device_cc1101_ext->spi_bus_handle, CC1101_IOCFG0, CC1101IocfgHighImpedance); + if(cc1101_status.CHIP_RDYn != 0) { + //timeout or error + break; + } // Prepare GD0 for power on self test furi_hal_gpio_init( - subghz_device_cc1101_ext->g0_pin, GpioModeInput, GpioPullNo, GpioSpeedLow); + subghz_device_cc1101_ext->g0_pin, GpioModeInput, GpioPullUp, GpioSpeedLow); // GD0 low - cc1101_write_reg(subghz_device_cc1101_ext->spi_bus_handle, CC1101_IOCFG0, CC1101IocfgHW); + cc1101_status = cc1101_write_reg( + subghz_device_cc1101_ext->spi_bus_handle, CC1101_IOCFG0, CC1101IocfgHW); + if(cc1101_status.CHIP_RDYn != 0) { + //timeout or error + break; + } while(furi_hal_gpio_read(subghz_device_cc1101_ext->g0_pin) != false) { if(furi_hal_cortex_timer_is_expired(timer)) { //timeout @@ -116,10 +135,16 @@ static bool subghz_device_cc1101_ext_check_init() { } // GD0 high - cc1101_write_reg( + furi_hal_gpio_init( + subghz_device_cc1101_ext->g0_pin, GpioModeInput, GpioPullDown, GpioSpeedLow); + cc1101_status = cc1101_write_reg( subghz_device_cc1101_ext->spi_bus_handle, CC1101_IOCFG0, CC1101IocfgHW | CC1101_IOCFG_INV); + if(cc1101_status.CHIP_RDYn != 0) { + //timeout or error + break; + } while(furi_hal_gpio_read(subghz_device_cc1101_ext->g0_pin) != true) { if(furi_hal_cortex_timer_is_expired(timer)) { //timeout @@ -132,17 +157,21 @@ static bool subghz_device_cc1101_ext_check_init() { } // Reset GD0 to floating state - cc1101_write_reg( + cc1101_status = cc1101_write_reg( subghz_device_cc1101_ext->spi_bus_handle, CC1101_IOCFG0, CC1101IocfgHighImpedance); + if(cc1101_status.CHIP_RDYn != 0) { + //timeout or error + break; + } furi_hal_gpio_init( subghz_device_cc1101_ext->g0_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); - // RF switches - furi_hal_gpio_init(&gpio_rf_sw_0, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); - cc1101_write_reg(subghz_device_cc1101_ext->spi_bus_handle, CC1101_IOCFG2, CC1101IocfgHW); - // Go to sleep - cc1101_shutdown(subghz_device_cc1101_ext->spi_bus_handle); + cc1101_status = cc1101_shutdown(subghz_device_cc1101_ext->spi_bus_handle); + if(cc1101_status.CHIP_RDYn != 0) { + //timeout or error + break; + } ret = true; } while(false); @@ -152,6 +181,8 @@ static bool subghz_device_cc1101_ext_check_init() { FURI_LOG_I(TAG, "Init OK"); } else { FURI_LOG_E(TAG, "Init failed"); + furi_hal_gpio_init( + subghz_device_cc1101_ext->g0_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); } return ret; } diff --git a/applications/main/subghz/subghz_cli.c b/applications/main/subghz/subghz_cli.c index bc7be507e..fe97c8a06 100644 --- a/applications/main/subghz/subghz_cli.c +++ b/applications/main/subghz/subghz_cli.c @@ -28,12 +28,20 @@ #define SUBGHZ_REGION_FILENAME "/int/.region_data" +#define TAG "SubGhz CLI" + static void subghz_cli_radio_device_power_on() { - uint8_t attempts = 0; - while(!furi_hal_power_is_otg_enabled() && attempts++ < 5) { - furi_hal_power_enable_otg(); - //CC1101 power-up time - furi_delay_ms(10); + uint8_t attempts = 5; + while(--attempts > 0) { + if(furi_hal_power_enable_otg()) break; + } + if(attempts == 0) { + if(furi_hal_power_get_usb_voltage() < 4.5f) { + FURI_LOG_E( + "TAG", + "Error power otg enable. BQ2589 check otg fault = %d", + furi_hal_power_check_otg_fault() ? 1 : 0); + } } } @@ -126,9 +134,9 @@ void subghz_cli_command_rx_carrier(Cli* cli, FuriString* args, void* context) { furi_hal_subghz_sleep(); } -static const SubGhzDevice* subghz_cli_command_get_device(uint32_t device_ind) { +static const SubGhzDevice* subghz_cli_command_get_device(uint32_t* device_ind) { const SubGhzDevice* device = NULL; - switch(device_ind) { + switch(*device_ind) { case 1: subghz_cli_radio_device_power_on(); device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_EXT_NAME); @@ -138,6 +146,12 @@ static const SubGhzDevice* subghz_cli_command_get_device(uint32_t device_ind) { device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME); break; } + //check if the device is connected + if(!subghz_devices_is_connect(device)) { + subghz_cli_radio_device_power_off(); + device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME); + *device_ind = 0; + } return device; } @@ -175,7 +189,7 @@ void subghz_cli_command_tx(Cli* cli, FuriString* args, void* context) { } } subghz_devices_init(); - const SubGhzDevice* device = subghz_cli_command_get_device(device_ind); + const SubGhzDevice* device = subghz_cli_command_get_device(&device_ind); if(!subghz_devices_is_frequency_valid(device, frequency)) { printf( "Frequency must be in " SUBGHZ_FREQUENCY_RANGE_STR " range, not %lu\r\n", frequency); @@ -295,7 +309,7 @@ void subghz_cli_command_rx(Cli* cli, FuriString* args, void* context) { } } subghz_devices_init(); - const SubGhzDevice* device = subghz_cli_command_get_device(device_ind); + const SubGhzDevice* device = subghz_cli_command_get_device(&device_ind); if(!subghz_devices_is_frequency_valid(device, frequency)) { printf( "Frequency must be in " SUBGHZ_FREQUENCY_RANGE_STR " range, not %lu\r\n", frequency); @@ -688,7 +702,7 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) { } } subghz_devices_init(); - const SubGhzDevice* device = subghz_cli_command_get_device(device_ind); + const SubGhzDevice* device = subghz_cli_command_get_device(&device_ind); if(!subghz_devices_is_frequency_valid(device, frequency)) { printf( "Frequency must be in " SUBGHZ_FREQUENCY_RANGE_STR " range, not %lu\r\n", frequency); diff --git a/firmware/targets/f7/furi_hal/furi_hal_spi_config.c b/firmware/targets/f7/furi_hal/furi_hal_spi_config.c index 09ac79d2a..757ac2366 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_spi_config.c +++ b/firmware/targets/f7/furi_hal/furi_hal_spi_config.c @@ -192,6 +192,52 @@ inline static void furi_hal_spi_bus_r_handle_event_callback( } } +inline static void furi_hal_spi_bus_external_handle_event_callback( + FuriHalSpiBusHandle* handle, + FuriHalSpiBusHandleEvent event, + const LL_SPI_InitTypeDef* preset) { + if(event == FuriHalSpiBusHandleEventInit) { + furi_hal_gpio_write(handle->cs, true); + furi_hal_gpio_init(handle->cs, GpioModeOutputPushPull, GpioPullUp, GpioSpeedVeryHigh); + } else if(event == FuriHalSpiBusHandleEventDeinit) { + furi_hal_gpio_write(handle->cs, true); + furi_hal_gpio_init(handle->cs, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + } else if(event == FuriHalSpiBusHandleEventActivate) { + LL_SPI_Init(handle->bus->spi, (LL_SPI_InitTypeDef*)preset); + LL_SPI_SetRxFIFOThreshold(handle->bus->spi, LL_SPI_RX_FIFO_TH_QUARTER); + LL_SPI_Enable(handle->bus->spi); + + furi_hal_gpio_init_ex( + handle->miso, + GpioModeAltFunctionPushPull, + GpioPullDown, + GpioSpeedVeryHigh, + GpioAltFn5SPI1); + furi_hal_gpio_init_ex( + handle->mosi, + GpioModeAltFunctionPushPull, + GpioPullDown, + GpioSpeedVeryHigh, + GpioAltFn5SPI1); + furi_hal_gpio_init_ex( + handle->sck, + GpioModeAltFunctionPushPull, + GpioPullDown, + GpioSpeedVeryHigh, + GpioAltFn5SPI1); + + furi_hal_gpio_write(handle->cs, false); + } else if(event == FuriHalSpiBusHandleEventDeactivate) { + furi_hal_gpio_write(handle->cs, true); + + furi_hal_gpio_init(handle->miso, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_init(handle->mosi, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_init(handle->sck, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + + LL_SPI_Disable(handle->bus->spi); + } +} + inline static void furi_hal_spi_bus_nfc_handle_event_callback( FuriHalSpiBusHandle* handle, FuriHalSpiBusHandleEvent event, @@ -291,7 +337,8 @@ FuriHalSpiBusHandle furi_hal_spi_bus_handle_nfc = { static void furi_hal_spi_bus_handle_external_event_callback( FuriHalSpiBusHandle* handle, FuriHalSpiBusHandleEvent event) { - furi_hal_spi_bus_r_handle_event_callback(handle, event, &furi_hal_spi_preset_1edge_low_2m); + furi_hal_spi_bus_external_handle_event_callback( + handle, event, &furi_hal_spi_preset_1edge_low_2m); } FuriHalSpiBusHandle furi_hal_spi_bus_handle_external = { diff --git a/lib/drivers/cc1101.c b/lib/drivers/cc1101.c index d0feb0218..85d915acd 100644 --- a/lib/drivers/cc1101.c +++ b/lib/drivers/cc1101.c @@ -1,14 +1,27 @@ #include "cc1101.h" #include #include +#include + +static bool cc1101_spi_trx(FuriHalSpiBusHandle* handle, uint8_t* tx, uint8_t* rx, uint8_t size) { + FuriHalCortexTimer timer = furi_hal_cortex_timer_get(CC1101_TIMEOUT * 1000); + + while(furi_hal_gpio_read(handle->miso)) { + if(furi_hal_cortex_timer_is_expired(timer)) { + //timeout + return false; + } + } + if(!furi_hal_spi_bus_trx(handle, tx, rx, size, CC1101_TIMEOUT)) return false; + return true; +} CC1101Status cc1101_strobe(FuriHalSpiBusHandle* handle, uint8_t strobe) { uint8_t tx[1] = {strobe}; CC1101Status rx[1] = {0}; + rx[0].CHIP_RDYn = 1; - while(furi_hal_gpio_read(handle->miso)) - ; - furi_hal_spi_bus_trx(handle, tx, (uint8_t*)rx, 1, CC1101_TIMEOUT); + cc1101_spi_trx(handle, tx, (uint8_t*)rx, 1); assert(rx[0].CHIP_RDYn == 0); return rx[0]; @@ -17,10 +30,10 @@ CC1101Status cc1101_strobe(FuriHalSpiBusHandle* handle, uint8_t strobe) { CC1101Status cc1101_write_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t data) { uint8_t tx[2] = {reg, data}; CC1101Status rx[2] = {0}; + rx[0].CHIP_RDYn = 1; + rx[1].CHIP_RDYn = 1; - while(furi_hal_gpio_read(handle->miso)) - ; - furi_hal_spi_bus_trx(handle, tx, (uint8_t*)rx, 2, CC1101_TIMEOUT); + cc1101_spi_trx(handle, tx, (uint8_t*)rx, 2); assert((rx[0].CHIP_RDYn | rx[1].CHIP_RDYn) == 0); return rx[1]; @@ -30,10 +43,9 @@ CC1101Status cc1101_read_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* assert(sizeof(CC1101Status) == 1); uint8_t tx[2] = {reg | CC1101_READ, 0}; CC1101Status rx[2] = {0}; + rx[0].CHIP_RDYn = 1; - while(furi_hal_gpio_read(handle->miso)) - ; - furi_hal_spi_bus_trx(handle, tx, (uint8_t*)rx, 2, CC1101_TIMEOUT); + cc1101_spi_trx(handle, tx, (uint8_t*)rx, 2); assert((rx[0].CHIP_RDYn) == 0); *data = *(uint8_t*)&rx[1]; @@ -58,40 +70,40 @@ uint8_t cc1101_get_rssi(FuriHalSpiBusHandle* handle) { return rssi; } -void cc1101_reset(FuriHalSpiBusHandle* handle) { - cc1101_strobe(handle, CC1101_STROBE_SRES); +CC1101Status cc1101_reset(FuriHalSpiBusHandle* handle) { + return cc1101_strobe(handle, CC1101_STROBE_SRES); } CC1101Status cc1101_get_status(FuriHalSpiBusHandle* handle) { return cc1101_strobe(handle, CC1101_STROBE_SNOP); } -void cc1101_shutdown(FuriHalSpiBusHandle* handle) { - cc1101_strobe(handle, CC1101_STROBE_SPWD); +CC1101Status cc1101_shutdown(FuriHalSpiBusHandle* handle) { + return cc1101_strobe(handle, CC1101_STROBE_SPWD); } -void cc1101_calibrate(FuriHalSpiBusHandle* handle) { - cc1101_strobe(handle, CC1101_STROBE_SCAL); +CC1101Status cc1101_calibrate(FuriHalSpiBusHandle* handle) { + return cc1101_strobe(handle, CC1101_STROBE_SCAL); } -void cc1101_switch_to_idle(FuriHalSpiBusHandle* handle) { - cc1101_strobe(handle, CC1101_STROBE_SIDLE); +CC1101Status cc1101_switch_to_idle(FuriHalSpiBusHandle* handle) { + return cc1101_strobe(handle, CC1101_STROBE_SIDLE); } -void cc1101_switch_to_rx(FuriHalSpiBusHandle* handle) { - cc1101_strobe(handle, CC1101_STROBE_SRX); +CC1101Status cc1101_switch_to_rx(FuriHalSpiBusHandle* handle) { + return cc1101_strobe(handle, CC1101_STROBE_SRX); } -void cc1101_switch_to_tx(FuriHalSpiBusHandle* handle) { - cc1101_strobe(handle, CC1101_STROBE_STX); +CC1101Status cc1101_switch_to_tx(FuriHalSpiBusHandle* handle) { + return cc1101_strobe(handle, CC1101_STROBE_STX); } -void cc1101_flush_rx(FuriHalSpiBusHandle* handle) { - cc1101_strobe(handle, CC1101_STROBE_SFRX); +CC1101Status cc1101_flush_rx(FuriHalSpiBusHandle* handle) { + return cc1101_strobe(handle, CC1101_STROBE_SFRX); } -void cc1101_flush_tx(FuriHalSpiBusHandle* handle) { - cc1101_strobe(handle, CC1101_STROBE_SFTX); +CC1101Status cc1101_flush_tx(FuriHalSpiBusHandle* handle) { + return cc1101_strobe(handle, CC1101_STROBE_SFTX); } uint32_t cc1101_set_frequency(FuriHalSpiBusHandle* handle, uint32_t value) { @@ -123,12 +135,12 @@ uint32_t cc1101_set_intermediate_frequency(FuriHalSpiBusHandle* handle, uint32_t void cc1101_set_pa_table(FuriHalSpiBusHandle* handle, const uint8_t value[8]) { uint8_t tx[9] = {CC1101_PATABLE | CC1101_BURST}; //-V1009 CC1101Status rx[9] = {0}; + rx[0].CHIP_RDYn = 1; + rx[8].CHIP_RDYn = 1; memcpy(&tx[1], &value[0], 8); - while(furi_hal_gpio_read(handle->miso)) - ; - furi_hal_spi_bus_trx(handle, tx, (uint8_t*)rx, sizeof(rx), CC1101_TIMEOUT); + cc1101_spi_trx(handle, tx, (uint8_t*)rx, sizeof(rx)); assert((rx[0].CHIP_RDYn | rx[8].CHIP_RDYn) == 0); } @@ -139,12 +151,7 @@ uint8_t cc1101_write_fifo(FuriHalSpiBusHandle* handle, const uint8_t* data, uint buff_tx[0] = CC1101_FIFO | CC1101_BURST; memcpy(&buff_tx[1], data, size); - // Start transaction - // Wait IC to become ready - while(furi_hal_gpio_read(handle->miso)) - ; - // Tell IC what we want - furi_hal_spi_bus_trx(handle, buff_tx, (uint8_t*)buff_rx, size + 1, CC1101_TIMEOUT); + cc1101_spi_trx(handle, buff_tx, (uint8_t*)buff_rx, size + 1); return size; } @@ -153,13 +160,7 @@ uint8_t cc1101_read_fifo(FuriHalSpiBusHandle* handle, uint8_t* data, uint8_t* si uint8_t buff_trx[2]; buff_trx[0] = CC1101_FIFO | CC1101_READ | CC1101_BURST; - // Start transaction - // Wait IC to become ready - while(furi_hal_gpio_read(handle->miso)) - ; - - // First byte - packet length - furi_hal_spi_bus_trx(handle, buff_trx, buff_trx, 2, CC1101_TIMEOUT); + cc1101_spi_trx(handle, buff_trx, buff_trx, 2); // Check that the packet is placed in the receive buffer if(buff_trx[1] > 64) { diff --git a/lib/drivers/cc1101.h b/lib/drivers/cc1101.h index af1f15569..d8ee05d52 100644 --- a/lib/drivers/cc1101.h +++ b/lib/drivers/cc1101.h @@ -46,8 +46,10 @@ CC1101Status cc1101_read_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* /** Reset * * @param handle - pointer to FuriHalSpiHandle + * + * @return CC1101Status structure */ -void cc1101_reset(FuriHalSpiBusHandle* handle); +CC1101Status cc1101_reset(FuriHalSpiBusHandle* handle); /** Get status * @@ -60,8 +62,10 @@ CC1101Status cc1101_get_status(FuriHalSpiBusHandle* handle); /** Enable shutdown mode * * @param handle - pointer to FuriHalSpiHandle + * + * @return CC1101Status structure */ -void cc1101_shutdown(FuriHalSpiBusHandle* handle); +CC1101Status cc1101_shutdown(FuriHalSpiBusHandle* handle); /** Get Partnumber * @@ -90,38 +94,46 @@ uint8_t cc1101_get_rssi(FuriHalSpiBusHandle* handle); /** Calibrate oscillator * * @param handle - pointer to FuriHalSpiHandle + * + * @return CC1101Status structure */ -void cc1101_calibrate(FuriHalSpiBusHandle* handle); +CC1101Status cc1101_calibrate(FuriHalSpiBusHandle* handle); /** Switch to idle * * @param handle - pointer to FuriHalSpiHandle */ -void cc1101_switch_to_idle(FuriHalSpiBusHandle* handle); +CC1101Status cc1101_switch_to_idle(FuriHalSpiBusHandle* handle); /** Switch to RX * * @param handle - pointer to FuriHalSpiHandle + * + * @return CC1101Status structure */ -void cc1101_switch_to_rx(FuriHalSpiBusHandle* handle); +CC1101Status cc1101_switch_to_rx(FuriHalSpiBusHandle* handle); /** Switch to TX * * @param handle - pointer to FuriHalSpiHandle + * + * @return CC1101Status structure */ -void cc1101_switch_to_tx(FuriHalSpiBusHandle* handle); +CC1101Status cc1101_switch_to_tx(FuriHalSpiBusHandle* handle); /** Flush RX FIFO * * @param handle - pointer to FuriHalSpiHandle + * + * @return CC1101Status structure */ -void cc1101_flush_rx(FuriHalSpiBusHandle* handle); +CC1101Status cc1101_flush_rx(FuriHalSpiBusHandle* handle); /** Flush TX FIFO * * @param handle - pointer to FuriHalSpiHandle */ -void cc1101_flush_tx(FuriHalSpiBusHandle* handle); +CC1101Status cc1101_flush_tx(FuriHalSpiBusHandle* handle); /** Set Frequency * diff --git a/lib/drivers/cc1101_regs.h b/lib/drivers/cc1101_regs.h index a326dc92c..e0aed6bd9 100644 --- a/lib/drivers/cc1101_regs.h +++ b/lib/drivers/cc1101_regs.h @@ -14,7 +14,7 @@ extern "C" { #define CC1101_IFDIV 0x400 /* IO Bus constants */ -#define CC1101_TIMEOUT 500 +#define CC1101_TIMEOUT 250 /* Bits and pieces */ #define CC1101_READ (1 << 7) /** Read Bit */ From dcb49c540f50b0f7ab501e1662272a21d52b10b3 Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Wed, 12 Jul 2023 12:49:17 +0300 Subject: [PATCH 107/364] [FL-3422] Loader: exit animation fix (#2860) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: ã‚ã --- .../services/loader/loader_applications.c | 67 +++++++++++-------- 1 file changed, 39 insertions(+), 28 deletions(-) diff --git a/applications/services/loader/loader_applications.c b/applications/services/loader/loader_applications.c index 1801edef9..7bf189e55 100644 --- a/applications/services/loader/loader_applications.c +++ b/applications/services/loader/loader_applications.c @@ -38,6 +38,11 @@ typedef struct { FuriString* fap_path; DialogsApp* dialogs; Storage* storage; + Loader* loader; + + Gui* gui; + ViewHolder* view_holder; + Loading* loading; } LoaderApplicationsApp; static LoaderApplicationsApp* loader_applications_app_alloc() { @@ -45,15 +50,30 @@ static LoaderApplicationsApp* loader_applications_app_alloc() { app->fap_path = furi_string_alloc_set(EXT_PATH("apps")); app->dialogs = furi_record_open(RECORD_DIALOGS); app->storage = furi_record_open(RECORD_STORAGE); + app->loader = furi_record_open(RECORD_LOADER); + + app->gui = furi_record_open(RECORD_GUI); + app->view_holder = view_holder_alloc(); + app->loading = loading_alloc(); + + view_holder_attach_to_gui(app->view_holder, app->gui); + view_holder_set_view(app->view_holder, loading_get_view(app->loading)); + return app; } //-V773 -static void loader_applications_app_free(LoaderApplicationsApp* loader_applications_app) { - furi_assert(loader_applications_app); +static void loader_applications_app_free(LoaderApplicationsApp* app) { + furi_assert(app); + + view_holder_free(app->view_holder); + loading_free(app->loading); + furi_record_close(RECORD_GUI); + + furi_record_close(RECORD_LOADER); furi_record_close(RECORD_DIALOGS); furi_record_close(RECORD_STORAGE); - furi_string_free(loader_applications_app->fap_path); - free(loader_applications_app); + furi_string_free(app->fap_path); + free(app); } static bool loader_applications_item_callback( @@ -96,47 +116,38 @@ static void loader_pubsub_callback(const void* message, void* context) { } } -static void loader_applications_start_app(const char* name) { - // start loading animation - Gui* gui = furi_record_open(RECORD_GUI); - ViewHolder* view_holder = view_holder_alloc(); - Loading* loading = loading_alloc(); - - view_holder_attach_to_gui(view_holder, gui); - view_holder_set_view(view_holder, loading_get_view(loading)); - view_holder_start(view_holder); +static void loader_applications_start_app(LoaderApplicationsApp* app) { + const char* name = furi_string_get_cstr(app->fap_path); // load app FuriThreadId thread_id = furi_thread_get_current_id(); - Loader* loader = furi_record_open(RECORD_LOADER); FuriPubSubSubscription* subscription = - furi_pubsub_subscribe(loader_get_pubsub(loader), loader_pubsub_callback, thread_id); + furi_pubsub_subscribe(loader_get_pubsub(app->loader), loader_pubsub_callback, thread_id); - LoaderStatus status = loader_start_with_gui_error(loader, name, NULL); + LoaderStatus status = loader_start_with_gui_error(app->loader, name, NULL); if(status == LoaderStatusOk) { furi_thread_flags_wait(APPLICATION_STOP_EVENT, FuriFlagWaitAny, FuriWaitForever); } - furi_pubsub_unsubscribe(loader_get_pubsub(loader), subscription); - furi_record_close(RECORD_LOADER); - - // stop loading animation - view_holder_stop(view_holder); - view_holder_free(view_holder); - loading_free(loading); - furi_record_close(RECORD_GUI); + furi_pubsub_unsubscribe(loader_get_pubsub(app->loader), subscription); } static int32_t loader_applications_thread(void* p) { LoaderApplications* loader_applications = p; - LoaderApplicationsApp* loader_applications_app = loader_applications_app_alloc(); + LoaderApplicationsApp* app = loader_applications_app_alloc(); - while(loader_applications_select_app(loader_applications_app)) { - loader_applications_start_app(furi_string_get_cstr(loader_applications_app->fap_path)); + // start loading animation + view_holder_start(app->view_holder); + + while(loader_applications_select_app(app)) { + loader_applications_start_app(app); } - loader_applications_app_free(loader_applications_app); + // stop loading animation + view_holder_stop(app->view_holder); + + loader_applications_app_free(app); if(loader_applications->closed_cb) { loader_applications->closed_cb(loader_applications->context); From a4b48028976613bbefd523e1493cd1a6edc342b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Wed, 12 Jul 2023 15:02:52 +0400 Subject: [PATCH 108/364] Revert "[FL-3420] Storage: directory sort (#2850)" (#2868) This reverts commit 136114890f24f6418c3b1672d8e378902ed4db02. --- .../storage/filesystem_api_internal.h | 3 +- .../services/storage/storage_processing.c | 180 +----------------- .../services/storage/storage_sorting.h | 22 --- 3 files changed, 11 insertions(+), 194 deletions(-) delete mode 100644 applications/services/storage/storage_sorting.h diff --git a/applications/services/storage/filesystem_api_internal.h b/applications/services/storage/filesystem_api_internal.h index 52eb6ef13..967d3bb41 100644 --- a/applications/services/storage/filesystem_api_internal.h +++ b/applications/services/storage/filesystem_api_internal.h @@ -19,8 +19,7 @@ struct File { FileType type; FS_Error error_id; /**< Standard API error from FS_Error enum */ int32_t internal_error_id; /**< Internal API error value */ - void* storage; /**< Storage API pointer */ - void* sort_data; /**< Sorted file list for directory */ + void* storage; }; /** File api structure diff --git a/applications/services/storage/storage_processing.c b/applications/services/storage/storage_processing.c index eb745cac4..e6b426961 100644 --- a/applications/services/storage/storage_processing.c +++ b/applications/services/storage/storage_processing.c @@ -1,5 +1,4 @@ #include "storage_processing.h" -#include "storage_sorting.h" #include #include @@ -101,7 +100,7 @@ static FS_Error storage_get_data(Storage* app, FuriString* path, StorageData** s /******************* File Functions *******************/ -static bool storage_process_file_open( +bool storage_process_file_open( Storage* app, File* file, FuriString* path, @@ -128,7 +127,7 @@ static bool storage_process_file_open( return ret; } -static bool storage_process_file_close(Storage* app, File* file) { +bool storage_process_file_close(Storage* app, File* file) { bool ret = false; StorageData* storage = get_storage_by_file(file, app->storage); @@ -261,149 +260,9 @@ static bool storage_process_file_eof(Storage* app, File* file) { return ret; } -/*************** Sorting Dir Functions ***************/ - -static bool storage_process_dir_rewind_internal(StorageData* storage, File* file); -static bool storage_process_dir_read_internal( - StorageData* storage, - File* file, - FileInfo* fileinfo, - char* name, - const uint16_t name_length); - -static int storage_sorted_file_record_compare(const void* sorted_a, const void* sorted_b) { - SortedFileRecord* a = (SortedFileRecord*)sorted_a; - SortedFileRecord* b = (SortedFileRecord*)sorted_b; - - if(a->info.flags & FSF_DIRECTORY && !(b->info.flags & FSF_DIRECTORY)) - return -1; - else if(!(a->info.flags & FSF_DIRECTORY) && b->info.flags & FSF_DIRECTORY) - return 1; - else - return furi_string_cmpi(a->name, b->name); -} - -static bool storage_sorted_dir_read_next( - SortedDir* dir, - FileInfo* fileinfo, - char* name, - const uint16_t name_length) { - bool ret = false; - - if(dir->index < dir->count) { - SortedFileRecord* sorted = &dir->sorted[dir->index]; - if(fileinfo) { - *fileinfo = sorted->info; - } - if(name) { - strncpy(name, furi_string_get_cstr(sorted->name), name_length); - } - dir->index++; - ret = true; - } - - return ret; -} - -static void storage_sorted_dir_rewind(SortedDir* dir) { - dir->index = 0; -} - -static bool storage_sorted_dir_prepare(SortedDir* dir, StorageData* storage, File* file) { - bool ret = true; - dir->count = 0; - dir->index = 0; - FileInfo info; - char name[SORTING_MAX_NAME_LENGTH + 1] = {0}; - - furi_check(!dir->sorted); - - while(storage_process_dir_read_internal(storage, file, &info, name, SORTING_MAX_NAME_LENGTH)) { - if(memmgr_get_free_heap() < SORTING_MIN_FREE_MEMORY) { - ret = false; - break; - } - - if(dir->count == 0) { //-V547 - dir->sorted = malloc(sizeof(SortedFileRecord)); - } else { - // Our realloc actually mallocs a new block and copies the data over, - // so we need to check if we have enough memory for the new block - size_t size = sizeof(SortedFileRecord) * (dir->count + 1); - if(memmgr_heap_get_max_free_block() >= size) { - dir->sorted = - realloc(dir->sorted, sizeof(SortedFileRecord) * (dir->count + 1)); //-V701 - } else { - ret = false; - break; - } - } - - dir->sorted[dir->count].name = furi_string_alloc_set(name); - dir->sorted[dir->count].info = info; - dir->count++; - } - - return ret; -} - -static void storage_sorted_dir_sort(SortedDir* dir) { - qsort(dir->sorted, dir->count, sizeof(SortedFileRecord), storage_sorted_file_record_compare); -} - -static void storage_sorted_dir_clear_data(SortedDir* dir) { - if(dir->sorted != NULL) { - for(size_t i = 0; i < dir->count; i++) { - furi_string_free(dir->sorted[i].name); - } - - free(dir->sorted); - dir->sorted = NULL; - } -} - -static void storage_file_remove_sort_data(File* file) { - if(file->sort_data != NULL) { - storage_sorted_dir_clear_data(file->sort_data); - free(file->sort_data); - file->sort_data = NULL; - } -} - -static void storage_file_add_sort_data(File* file, StorageData* storage) { - file->sort_data = malloc(sizeof(SortedDir)); - if(storage_sorted_dir_prepare(file->sort_data, storage, file)) { - storage_sorted_dir_sort(file->sort_data); - } else { - storage_file_remove_sort_data(file); - storage_process_dir_rewind_internal(storage, file); - } -} - -static bool storage_file_has_sort_data(File* file) { - return file->sort_data != NULL; -} - /******************* Dir Functions *******************/ -static bool storage_process_dir_read_internal( - StorageData* storage, - File* file, - FileInfo* fileinfo, - char* name, - const uint16_t name_length) { - bool ret = false; - FS_CALL(storage, dir.read(storage, file, fileinfo, name, name_length)); - return ret; -} - -static bool storage_process_dir_rewind_internal(StorageData* storage, File* file) { - bool ret = false; - FS_CALL(storage, dir.rewind(storage, file)); - return ret; -} - -static bool storage_process_dir_open(Storage* app, File* file, FuriString* path) { +bool storage_process_dir_open(Storage* app, File* file, FuriString* path) { bool ret = false; StorageData* storage; file->error_id = storage_get_data(app, path, &storage); @@ -414,17 +273,13 @@ static bool storage_process_dir_open(Storage* app, File* file, FuriString* path) } else { storage_push_storage_file(file, path, storage); FS_CALL(storage, dir.open(storage, file, cstr_path_without_vfs_prefix(path))); - - if(file->error_id == FSE_OK) { - storage_file_add_sort_data(file, storage); - } } } return ret; } -static bool storage_process_dir_close(Storage* app, File* file) { +bool storage_process_dir_close(Storage* app, File* file) { bool ret = false; StorageData* storage = get_storage_by_file(file, app->storage); @@ -432,7 +287,6 @@ static bool storage_process_dir_close(Storage* app, File* file) { file->error_id = FSE_INVALID_PARAMETER; } else { FS_CALL(storage, dir.close(storage, file)); - storage_file_remove_sort_data(file); storage_pop_storage_file(file, storage); StorageEvent event = {.type = StorageEventTypeDirClose}; @@ -442,7 +296,7 @@ static bool storage_process_dir_close(Storage* app, File* file) { return ret; } -static bool storage_process_dir_read( +bool storage_process_dir_read( Storage* app, File* file, FileInfo* fileinfo, @@ -454,34 +308,20 @@ static bool storage_process_dir_read( if(storage == NULL) { file->error_id = FSE_INVALID_PARAMETER; } else { - if(storage_file_has_sort_data(file)) { - ret = storage_sorted_dir_read_next(file->sort_data, fileinfo, name, name_length); - if(ret) { - file->error_id = FSE_OK; - } else { - file->error_id = FSE_NOT_EXIST; - } - } else { - ret = storage_process_dir_read_internal(storage, file, fileinfo, name, name_length); - } + FS_CALL(storage, dir.read(storage, file, fileinfo, name, name_length)); } return ret; } -static bool storage_process_dir_rewind(Storage* app, File* file) { +bool storage_process_dir_rewind(Storage* app, File* file) { bool ret = false; StorageData* storage = get_storage_by_file(file, app->storage); if(storage == NULL) { file->error_id = FSE_INVALID_PARAMETER; } else { - if(storage_file_has_sort_data(file)) { - storage_sorted_dir_rewind(file->sort_data); - ret = true; - } else { - ret = storage_process_dir_rewind_internal(storage, file); - } + FS_CALL(storage, dir.rewind(storage, file)); } return ret; @@ -621,7 +461,7 @@ static FS_Error storage_process_sd_status(Storage* app) { /******************** Aliases processing *******************/ -static void storage_process_alias( +void storage_process_alias( Storage* app, FuriString* path, FuriThreadId thread_id, @@ -665,7 +505,7 @@ static void storage_process_alias( /****************** API calls processing ******************/ -static void storage_process_message_internal(Storage* app, StorageMessage* message) { +void storage_process_message_internal(Storage* app, StorageMessage* message) { FuriString* path = NULL; switch(message->command) { diff --git a/applications/services/storage/storage_sorting.h b/applications/services/storage/storage_sorting.h deleted file mode 100644 index 9db9d58bf..000000000 --- a/applications/services/storage/storage_sorting.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once -#include - -#define SORTING_MAX_NAME_LENGTH 255 -#define SORTING_MIN_FREE_MEMORY (1024 * 40) - -/** - * @brief Sorted file record, holds file name and info - */ -typedef struct { - FuriString* name; - FileInfo info; -} SortedFileRecord; - -/** - * @brief Sorted directory, holds sorted file records, count and current index - */ -typedef struct { - SortedFileRecord* sorted; - size_t count; - size_t index; -} SortedDir; \ No newline at end of file From 92c0baa46192b29f0dc331f1fc4704a246b43024 Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Wed, 12 Jul 2023 19:35:11 +0300 Subject: [PATCH 109/364] [FL-3383, FL-3413] Archive and file browser fixes (#2862) * File browser: flickering and reload fixes * The same for archive browser --- .../main/archive/helpers/archive_browser.c | 34 +++++++++++++++- .../main/archive/helpers/archive_browser.h | 1 + .../main/archive/views/archive_browser_view.c | 26 ++++--------- .../services/gui/modules/file_browser.c | 39 ++++++++++++++----- 4 files changed, 71 insertions(+), 29 deletions(-) diff --git a/applications/main/archive/helpers/archive_browser.c b/applications/main/archive/helpers/archive_browser.c index 70137d694..51457fe81 100644 --- a/applications/main/archive/helpers/archive_browser.c +++ b/applications/main/archive/helpers/archive_browser.c @@ -64,8 +64,20 @@ static void if(!is_last) { archive_add_file_item(browser, is_folder, furi_string_get_cstr(item_path)); } else { + bool load_again = false; with_view_model( - browser->view, ArchiveBrowserViewModel * model, { model->list_loading = false; }, true); + browser->view, + ArchiveBrowserViewModel * model, + { + model->list_loading = false; + if(archive_is_file_list_load_required(model)) { + load_again = true; + } + }, + true); + if(load_again) { + archive_file_array_load(browser, 0); + } } } @@ -111,6 +123,26 @@ bool archive_is_item_in_array(ArchiveBrowserViewModel* model, uint32_t idx) { return true; } +bool archive_is_file_list_load_required(ArchiveBrowserViewModel* model) { + size_t array_size = files_array_size(model->files); + + if((model->list_loading) || (array_size >= model->item_cnt)) { + return false; + } + + if((model->array_offset > 0) && + (model->item_idx < (model->array_offset + FILE_LIST_BUF_LEN / 4))) { + return true; + } + + if(((model->array_offset + array_size) < model->item_cnt) && + (model->item_idx > (int32_t)(model->array_offset + array_size - FILE_LIST_BUF_LEN / 4))) { + return true; + } + + return false; +} + void archive_update_offset(ArchiveBrowserView* browser) { furi_assert(browser); diff --git a/applications/main/archive/helpers/archive_browser.h b/applications/main/archive/helpers/archive_browser.h index 09ffea1f9..5e66a3dbb 100644 --- a/applications/main/archive/helpers/archive_browser.h +++ b/applications/main/archive/helpers/archive_browser.h @@ -64,6 +64,7 @@ inline bool archive_is_known_app(ArchiveFileTypeEnum type) { } bool archive_is_item_in_array(ArchiveBrowserViewModel* model, uint32_t idx); +bool archive_is_file_list_load_required(ArchiveBrowserViewModel* model); void archive_update_offset(ArchiveBrowserView* browser); void archive_update_focus(ArchiveBrowserView* browser, const char* target); diff --git a/applications/main/archive/views/archive_browser_view.c b/applications/main/archive/views/archive_browser_view.c index 3c2f13215..ba147f74c 100644 --- a/applications/main/archive/views/archive_browser_view.c +++ b/applications/main/archive/views/archive_browser_view.c @@ -248,24 +248,10 @@ View* archive_browser_get_view(ArchiveBrowserView* browser) { return browser->view; } -static bool is_file_list_load_required(ArchiveBrowserViewModel* model) { - size_t array_size = files_array_size(model->files); - - if((model->list_loading) || (array_size >= model->item_cnt)) { - return false; +static void file_list_rollover(ArchiveBrowserViewModel* model) { + if(!model->list_loading && files_array_size(model->files) < model->item_cnt) { + files_array_reset(model->files); } - - if((model->array_offset > 0) && - (model->item_idx < (model->array_offset + FILE_LIST_BUF_LEN / 4))) { - return true; - } - - if(((model->array_offset + array_size) < model->item_cnt) && - (model->item_idx > (int32_t)(model->array_offset + array_size - FILE_LIST_BUF_LEN / 4))) { - return true; - } - - return false; } static bool archive_view_input(InputEvent* event, void* context) { @@ -347,12 +333,13 @@ static bool archive_view_input(InputEvent* event, void* context) { if(model->item_idx < scroll_speed) { model->button_held_for_ticks = 0; model->item_idx = model->item_cnt - 1; + file_list_rollover(model); } else { model->item_idx = ((model->item_idx - scroll_speed) + model->item_cnt) % model->item_cnt; } - if(is_file_list_load_required(model)) { + if(archive_is_file_list_load_required(model)) { model->list_loading = true; browser->callback(ArchiveBrowserEventLoadPrevItems, browser->context); } @@ -366,10 +353,11 @@ static bool archive_view_input(InputEvent* event, void* context) { if(model->item_idx + scroll_speed >= count) { model->button_held_for_ticks = 0; model->item_idx = 0; + file_list_rollover(model); } else { model->item_idx = (model->item_idx + scroll_speed) % model->item_cnt; } - if(is_file_list_load_required(model)) { + if(archive_is_file_list_load_required(model)) { model->list_loading = true; browser->callback(ArchiveBrowserEventLoadNextItems, browser->context); } diff --git a/applications/services/gui/modules/file_browser.c b/applications/services/gui/modules/file_browser.c index c764a1cf7..91b03ec8a 100644 --- a/applications/services/gui/modules/file_browser.c +++ b/applications/services/gui/modules/file_browser.c @@ -303,6 +303,12 @@ static bool browser_is_list_load_required(FileBrowserModel* model) { return false; } +static void browser_list_rollover(FileBrowserModel* model) { + if(!model->list_loading && items_array_size(model->items) < model->item_cnt) { + items_array_reset(model->items); + } +} + static void browser_update_offset(FileBrowser* browser) { furi_assert(browser); @@ -385,7 +391,7 @@ static void browser_list_load_cb(void* context, uint32_t list_load_offset) { } } }, - true); + false); BrowserItem_t_clear(&back_item); } @@ -425,14 +431,15 @@ static void (browser->hide_ext) && (item.type == BrowserItemTypeFile)); } + // We shouldn't update screen on each item if custom callback is not set + // Otherwise it will cause screen flickering + bool instant_update = (browser->item_callback != NULL); with_view_model( browser->view, FileBrowserModel * model, - { - items_array_push_back(model->items, item); - // TODO: calculate if element is visible - }, - true); + { items_array_push_back(model->items, item); }, + instant_update); + furi_string_free(item.display_name); furi_string_free(item.path); if(item.custom_icon_data) { @@ -440,7 +447,18 @@ static void } } else { with_view_model( - browser->view, FileBrowserModel * model, { model->list_loading = false; }, true); + browser->view, + FileBrowserModel * model, + { + model->list_loading = false; + if(browser_is_list_load_required(model)) { + model->list_loading = true; + int32_t load_offset = CLAMP( + model->item_idx - ITEM_LIST_LEN_MAX / 2, (int32_t)model->item_cnt, 0); + file_browser_worker_load(browser->worker, load_offset, ITEM_LIST_LEN_MAX); + } + }, + true); } } @@ -604,11 +622,13 @@ static bool file_browser_view_input_callback(InputEvent* event, void* context) { if(model->item_idx < scroll_speed) { model->button_held_for_ticks = 0; model->item_idx = model->item_cnt - 1; + browser_list_rollover(model); } else { model->item_idx = ((model->item_idx - scroll_speed) + model->item_cnt) % model->item_cnt; } + if(browser_is_list_load_required(model)) { model->list_loading = true; int32_t load_offset = CLAMP( @@ -622,13 +642,14 @@ static bool file_browser_view_input_callback(InputEvent* event, void* context) { model->button_held_for_ticks += 1; } else if(event->key == InputKeyDown) { - int32_t count = model->item_cnt; - if(model->item_idx + scroll_speed >= count) { + if(model->item_idx + scroll_speed >= (int32_t)model->item_cnt) { model->button_held_for_ticks = 0; model->item_idx = 0; + browser_list_rollover(model); } else { model->item_idx = (model->item_idx + scroll_speed) % model->item_cnt; } + if(browser_is_list_load_required(model)) { model->list_loading = true; int32_t load_offset = CLAMP( From f998948623d8cb8bb1ead7efe839682f96f34d3c Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 12 Jul 2023 21:23:49 +0300 Subject: [PATCH 110/364] merge changes --- .../services/gui/modules/file_browser.c | 34 ++++++++++++------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/applications/services/gui/modules/file_browser.c b/applications/services/gui/modules/file_browser.c index 8bb4ddf31..db44b3874 100644 --- a/applications/services/gui/modules/file_browser.c +++ b/applications/services/gui/modules/file_browser.c @@ -487,25 +487,33 @@ static void browser_list_item_cb( browser->view, FileBrowserModel * model, { - if(model->item_cnt <= BROWSER_SORT_THRESHOLD) { - FuriString* selected = NULL; - if(model->item_idx > 0) { - selected = furi_string_alloc_set( - items_array_get(model->items, model->item_idx)->path); - } + model->list_loading = false; + if(browser_is_list_load_required(model)) { + model->list_loading = true; + int32_t load_offset = CLAMP( + model->item_idx - ITEM_LIST_LEN_MAX / 2, (int32_t)model->item_cnt, 0); + file_browser_worker_load(browser->worker, load_offset, ITEM_LIST_LEN_MAX); - items_array_sort(model->items); + if(model->item_cnt <= BROWSER_SORT_THRESHOLD) { + FuriString* selected = NULL; + if(model->item_idx > 0) { + selected = furi_string_alloc_set( + items_array_get(model->items, model->item_idx)->path); + } - if(selected != NULL) { - for(uint32_t i = 0; i < model->item_cnt; i++) { - if(!furi_string_cmp(items_array_get(model->items, i)->path, selected)) { - model->item_idx = i; - break; + items_array_sort(model->items); + + if(selected != NULL) { + for(uint32_t i = 0; i < model->item_cnt; i++) { + if(!furi_string_cmp( + items_array_get(model->items, i)->path, selected)) { + model->item_idx = i; + break; + } } } } } - model->list_loading = false; }, false); browser_update_offset(browser); From ea357b8eafe91c3a07cb89c3b90d03b78e2633ff Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 13 Jul 2023 12:48:26 +0300 Subject: [PATCH 111/364] upd ofw anims list --- .ci_files/anims_ofw.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.ci_files/anims_ofw.txt b/.ci_files/anims_ofw.txt index e3d3314c0..2fc22ec8c 100644 --- a/.ci_files/anims_ofw.txt +++ b/.ci_files/anims_ofw.txt @@ -92,6 +92,13 @@ Min level: 1 Max level: 3 Weight: 4 +Name: L1_My_dude_128x64 +Min butthurt: 0 +Max butthurt: 8 +Min level: 1 +Max level: 3 +Weight: 4 + Name: L2_Wake_up_128x64 Min butthurt: 0 Max butthurt: 12 From d3c0fbef3b37982d579840ef351e8c9229b598a8 Mon Sep 17 00:00:00 2001 From: gid9798 <30450294+gid9798@users.noreply.github.com> Date: Thu, 13 Jul 2023 13:29:41 +0300 Subject: [PATCH 112/364] GUI module: SubMenu fix vertical orientation --- applications/services/gui/modules/submenu.c | 120 ++++++++++++++++---- applications/services/gui/modules/submenu.h | 7 ++ firmware/targets/f7/api_symbols.csv | 1 + 3 files changed, 103 insertions(+), 25 deletions(-) diff --git a/applications/services/gui/modules/submenu.c b/applications/services/gui/modules/submenu.c index 5f47ac179..368f5be28 100644 --- a/applications/services/gui/modules/submenu.c +++ b/applications/services/gui/modules/submenu.c @@ -1,5 +1,4 @@ #include "submenu.h" -#include #include #include @@ -66,22 +65,23 @@ typedef struct { size_t position; size_t window_position; bool locked_message_visible; + bool is_vertical; } SubmenuModel; static void submenu_process_up(Submenu* submenu); static void submenu_process_down(Submenu* submenu); static void submenu_process_ok(Submenu* submenu); +static size_t submenu_items_on_screen(bool header, bool vertical) { + size_t res = (vertical) ? 8 : 4; + return (header) ? res - 1 : res; +} + static void submenu_view_draw_callback(Canvas* canvas, void* _model) { SubmenuModel* model = _model; const uint8_t item_height = 16; - uint8_t item_width = 123; - - if(canvas->orientation == CanvasOrientationVertical || - canvas->orientation == CanvasOrientationVerticalFlip) { - item_width = 60; - } + uint8_t item_width = canvas_width(canvas) - 5; canvas_clear(canvas); @@ -97,7 +97,8 @@ static void submenu_view_draw_callback(Canvas* canvas, void* _model) { for(SubmenuItemArray_it(it, model->items); !SubmenuItemArray_end_p(it); SubmenuItemArray_next(it)) { const size_t item_position = position - model->window_position; - const size_t items_on_screen = furi_string_empty(model->header) ? 4 : 3; + const size_t items_on_screen = + submenu_items_on_screen(!furi_string_empty(model->header), model->is_vertical); uint8_t y_offset = furi_string_empty(model->header) ? 0 : 16; if(item_position < items_on_screen) { @@ -117,7 +118,7 @@ static void submenu_view_draw_callback(Canvas* canvas, void* _model) { if(SubmenuItemArray_cref(it)->locked) { canvas_draw_icon( canvas, - 110, + item_width - 10, y_offset + (item_position * item_height) + item_height - 12, &I_Lock_7x8); } @@ -125,7 +126,7 @@ static void submenu_view_draw_callback(Canvas* canvas, void* _model) { FuriString* disp_str; disp_str = furi_string_alloc_set(SubmenuItemArray_cref(it)->label); elements_string_fit_width( - canvas, disp_str, item_width - (SubmenuItemArray_cref(it)->locked ? 25 : 11)); + canvas, disp_str, item_width - (SubmenuItemArray_cref(it)->locked ? 21 : 11)); canvas_draw_str( canvas, @@ -142,20 +143,39 @@ static void submenu_view_draw_callback(Canvas* canvas, void* _model) { elements_scrollbar(canvas, model->position, SubmenuItemArray_size(model->items)); if(model->locked_message_visible) { + const uint8_t frame_x = 7; + const uint8_t frame_width = canvas_width(canvas) - frame_x * 2; + const uint8_t frame_y = 7; + const uint8_t frame_height = canvas_height(canvas) - frame_y * 2; + canvas_set_color(canvas, ColorWhite); - canvas_draw_box(canvas, 8, 10, 110, 48); + canvas_draw_box(canvas, frame_x + 2, frame_y + 2, frame_width - 4, frame_height - 4); + canvas_set_color(canvas, ColorBlack); - canvas_draw_icon(canvas, 10, 14, &I_WarningDolphin_45x42); - canvas_draw_rframe(canvas, 8, 8, 112, 50, 3); - canvas_draw_rframe(canvas, 9, 9, 110, 48, 2); - elements_multiline_text_aligned( - canvas, - 84, - 32, - AlignCenter, - AlignCenter, - furi_string_get_cstr( - SubmenuItemArray_get(model->items, model->position)->locked_message)); + canvas_draw_icon( + canvas, frame_x + 2, canvas_height(canvas) - frame_y - 2 - 42, &I_WarningDolphin_45x42); + + canvas_draw_rframe(canvas, frame_x, frame_y, frame_width, frame_height, 3); + canvas_draw_rframe(canvas, frame_x + 1, frame_y + 1, frame_width - 2, frame_height - 2, 2); + if(model->is_vertical) { + elements_multiline_text_aligned( + canvas, + 32, + 42, + AlignCenter, + AlignCenter, + furi_string_get_cstr( + SubmenuItemArray_get(model->items, model->position)->locked_message)); + } else { + elements_multiline_text_aligned( + canvas, + 84, + 32, + AlignCenter, + AlignCenter, + furi_string_get_cstr( + SubmenuItemArray_get(model->items, model->position)->locked_message)); + } } } @@ -303,6 +323,7 @@ void submenu_add_lockable_item( void submenu_reset(Submenu* submenu) { furi_assert(submenu); + view_set_orientation(submenu->view, ViewOrientationHorizontal); with_view_model( submenu->view, @@ -311,6 +332,7 @@ void submenu_reset(Submenu* submenu) { SubmenuItemArray_reset(model->items); model->position = 0; model->window_position = 0; + model->is_vertical = false; furi_string_reset(model->header); }, true); @@ -344,7 +366,8 @@ void submenu_set_selected_item(Submenu* submenu, uint32_t index) { model->window_position -= 1; } - const size_t items_on_screen = furi_string_empty(model->header) ? 4 : 3; + const size_t items_on_screen = + submenu_items_on_screen(!furi_string_empty(model->header), model->is_vertical); if(items_size <= items_on_screen) { model->window_position = 0; @@ -363,7 +386,8 @@ void submenu_process_up(Submenu* submenu) { submenu->view, SubmenuModel * model, { - const size_t items_on_screen = furi_string_empty(model->header) ? 4 : 3; + const size_t items_on_screen = + submenu_items_on_screen(!furi_string_empty(model->header), model->is_vertical); const size_t items_size = SubmenuItemArray_size(model->items); if(model->position > 0) { @@ -386,7 +410,8 @@ void submenu_process_down(Submenu* submenu) { submenu->view, SubmenuModel * model, { - const size_t items_on_screen = furi_string_empty(model->header) ? 4 : 3; + const size_t items_on_screen = + submenu_items_on_screen(!furi_string_empty(model->header), model->is_vertical); const size_t items_size = SubmenuItemArray_size(model->items); if(model->position < items_size - 1) { @@ -441,3 +466,48 @@ void submenu_set_header(Submenu* submenu, const char* header) { }, true); } + +void submenu_set_orientation(Submenu* submenu, ViewOrientation orientation) { + furi_assert(submenu); + const bool is_vertical = + (orientation == ViewOrientationVertical || orientation == ViewOrientationVerticalFlip) ? + true : + false; + + view_set_orientation(submenu->view, orientation); + + with_view_model( + submenu->view, + SubmenuModel * model, + { + model->is_vertical = is_vertical; + + // Recalculating the position + // Need if _set_orientation is called after _set_selected_item + size_t position = model->position; + const size_t items_size = SubmenuItemArray_size(model->items); + const size_t items_on_screen = + submenu_items_on_screen(!furi_string_empty(model->header), model->is_vertical); + + if(position >= items_size) { + position = 0; + } + + model->position = position; + model->window_position = position; + + if(model->window_position > 0) { + model->window_position -= 1; + } + + if(items_size <= items_on_screen) { + model->window_position = 0; + } else { + const size_t pos = items_size - items_on_screen; + if(model->window_position > pos) { + model->window_position = pos; + } + } + }, + true); +} diff --git a/applications/services/gui/modules/submenu.h b/applications/services/gui/modules/submenu.h index e7252eb33..03337d777 100644 --- a/applications/services/gui/modules/submenu.h +++ b/applications/services/gui/modules/submenu.h @@ -93,6 +93,13 @@ void submenu_set_selected_item(Submenu* submenu, uint32_t index); */ void submenu_set_header(Submenu* submenu, const char* header); +/** Set Orientation + * + * @param submenu Submenu instance + * @param orientation either vertical or horizontal + */ +void submenu_set_orientation(Submenu* submenu, ViewOrientation orientation); + #ifdef __cplusplus } #endif diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 6b7f7389f..c1a8dc63a 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -2912,6 +2912,7 @@ Function,+,submenu_free,void,Submenu* Function,+,submenu_get_view,View*,Submenu* Function,+,submenu_reset,void,Submenu* Function,+,submenu_set_header,void,"Submenu*, const char*" +Function,+,submenu_set_orientation,void,"Submenu*, ViewOrientation" Function,+,submenu_set_selected_item,void,"Submenu*, uint32_t" Function,-,system,int,const char* Function,+,t5577_write,void,LFRFIDT5577* From b55d97f82796e2be84a0baaee70f5ce8b82b57b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Thu, 13 Jul 2023 15:02:08 +0400 Subject: [PATCH 113/364] Desktop,Cli: add uptime info (#2874) --- applications/services/cli/cli_commands.c | 9 + .../desktop/scenes/desktop_scene_debug.c | 23 +- .../desktop/views/desktop_view_debug.c | 211 ++++++------------ .../desktop/views/desktop_view_debug.h | 19 +- 4 files changed, 86 insertions(+), 176 deletions(-) diff --git a/applications/services/cli/cli_commands.c b/applications/services/cli/cli_commands.c index 7009e7531..467e7c530 100644 --- a/applications/services/cli/cli_commands.c +++ b/applications/services/cli/cli_commands.c @@ -89,6 +89,14 @@ void cli_command_help(Cli* cli, FuriString* args, void* context) { } } +void cli_command_uptime(Cli* cli, FuriString* args, void* context) { + UNUSED(cli); + UNUSED(args); + UNUSED(context); + uint32_t uptime = furi_get_tick() / furi_kernel_get_tick_frequency(); + printf("Uptime: %luh%lum%lus", uptime / 60 / 60, uptime / 60 % 60, uptime % 60); +} + void cli_command_date(Cli* cli, FuriString* args, void* context) { UNUSED(cli); UNUSED(context); @@ -451,6 +459,7 @@ void cli_commands_init(Cli* cli) { cli_add_command(cli, "?", CliCommandFlagParallelSafe, cli_command_help, NULL); cli_add_command(cli, "help", CliCommandFlagParallelSafe, cli_command_help, NULL); + cli_add_command(cli, "uptime", CliCommandFlagDefault, cli_command_uptime, NULL); cli_add_command(cli, "date", CliCommandFlagParallelSafe, cli_command_date, NULL); cli_add_command(cli, "log", CliCommandFlagParallelSafe, cli_command_log, NULL); cli_add_command(cli, "sysctl", CliCommandFlagDefault, cli_command_sysctl, NULL); diff --git a/applications/services/desktop/scenes/desktop_scene_debug.c b/applications/services/desktop/scenes/desktop_scene_debug.c index a5bd3a6b1..866c736ab 100644 --- a/applications/services/desktop/scenes/desktop_scene_debug.c +++ b/applications/services/desktop/scenes/desktop_scene_debug.c @@ -14,8 +14,6 @@ void desktop_scene_debug_callback(DesktopEvent event, void* context) { void desktop_scene_debug_on_enter(void* context) { Desktop* desktop = (Desktop*)context; - desktop_debug_get_dolphin_data(desktop->debug_view); - desktop_debug_set_callback(desktop->debug_view, desktop_scene_debug_callback, desktop); view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewIdDebug); } @@ -32,24 +30,6 @@ bool desktop_scene_debug_on_event(void* context, SceneManagerEvent event) { dolphin_flush(dolphin); consumed = true; break; - - case DesktopDebugEventDeed: - dolphin_deed(DolphinDeedTestRight); - desktop_debug_get_dolphin_data(desktop->debug_view); - consumed = true; - break; - - case DesktopDebugEventWrongDeed: - dolphin_deed(DolphinDeedTestLeft); - desktop_debug_get_dolphin_data(desktop->debug_view); - consumed = true; - break; - - case DesktopDebugEventSaveState: - dolphin_flush(dolphin); - consumed = true; - break; - default: break; } @@ -60,6 +40,5 @@ bool desktop_scene_debug_on_event(void* context, SceneManagerEvent event) { } void desktop_scene_debug_on_exit(void* context) { - Desktop* desktop = (Desktop*)context; - desktop_debug_reset_screen_idx(desktop->debug_view); + UNUSED(context); } diff --git a/applications/services/desktop/views/desktop_view_debug.c b/applications/services/desktop/views/desktop_view_debug.c index 7a16c0847..35c7dc038 100644 --- a/applications/services/desktop/views/desktop_view_debug.c +++ b/applications/services/desktop/views/desktop_view_debug.c @@ -18,96 +18,71 @@ void desktop_debug_set_callback( } void desktop_debug_render(Canvas* canvas, void* model) { + UNUSED(model); canvas_clear(canvas); - DesktopDebugViewModel* m = model; const Version* ver; char buffer[64]; - static const char* headers[] = {"Device Info:", "Dolphin Info:"}; - canvas_set_color(canvas, ColorBlack); canvas_set_font(canvas, FontPrimary); - canvas_draw_str_aligned( - canvas, 64, 1 + STATUS_BAR_Y_SHIFT, AlignCenter, AlignTop, headers[m->screen]); + + uint32_t uptime = furi_get_tick() / furi_kernel_get_tick_frequency(); + snprintf( + buffer, + sizeof(buffer), + "Uptime: %luh%lum%lus", + uptime / 60 / 60, + uptime / 60 % 60, + uptime % 60); + canvas_draw_str_aligned(canvas, 64, 1 + STATUS_BAR_Y_SHIFT, AlignCenter, AlignTop, buffer); + canvas_set_font(canvas, FontSecondary); - if(m->screen != DesktopViewStatsMeta) { - // Hardware version - const char* my_name = furi_hal_version_get_name_ptr(); - snprintf( - buffer, - sizeof(buffer), - "%d.F%dB%dC%d %s:%s %s", - furi_hal_version_get_hw_version(), - furi_hal_version_get_hw_target(), - furi_hal_version_get_hw_body(), - furi_hal_version_get_hw_connect(), - furi_hal_version_get_hw_region_name(), - furi_hal_region_get_name(), - my_name ? my_name : "Unknown"); - canvas_draw_str(canvas, 0, 19 + STATUS_BAR_Y_SHIFT, buffer); + // Hardware version + const char* my_name = furi_hal_version_get_name_ptr(); + snprintf( + buffer, + sizeof(buffer), + "%d.F%dB%dC%d %s:%s %s", + furi_hal_version_get_hw_version(), + furi_hal_version_get_hw_target(), + furi_hal_version_get_hw_body(), + furi_hal_version_get_hw_connect(), + furi_hal_version_get_hw_region_name(), + furi_hal_region_get_name(), + my_name ? my_name : "Unknown"); + canvas_draw_str(canvas, 0, 19 + STATUS_BAR_Y_SHIFT, buffer); - ver = furi_hal_version_get_firmware_version(); - const BleGlueC2Info* c2_ver = NULL; + ver = furi_hal_version_get_firmware_version(); + const BleGlueC2Info* c2_ver = NULL; #ifdef SRV_BT - c2_ver = ble_glue_get_c2_info(); + c2_ver = ble_glue_get_c2_info(); #endif - if(!ver) { //-V1051 - canvas_draw_str(canvas, 0, 30 + STATUS_BAR_Y_SHIFT, "No info"); - return; - } - - snprintf( - buffer, - sizeof(buffer), - "%s [%s]", - version_get_version(ver), - version_get_builddate(ver)); - canvas_draw_str(canvas, 0, 30 + STATUS_BAR_Y_SHIFT, buffer); - - uint16_t api_major, api_minor; - furi_hal_info_get_api_version(&api_major, &api_minor); - snprintf( - buffer, - sizeof(buffer), - "%s%s [%d.%d] %s", - version_get_dirty_flag(ver) ? "[!] " : "", - version_get_githash(ver), - api_major, - api_minor, - c2_ver ? c2_ver->StackTypeString : ""); - canvas_draw_str(canvas, 0, 40 + STATUS_BAR_Y_SHIFT, buffer); - - snprintf( - buffer, sizeof(buffer), "[%d] %s", version_get_target(ver), version_get_gitbranch(ver)); - canvas_draw_str(canvas, 0, 50 + STATUS_BAR_Y_SHIFT, buffer); - - } else { - Dolphin* dolphin = furi_record_open(RECORD_DOLPHIN); - DolphinStats stats = dolphin_stats(dolphin); - furi_record_close(RECORD_DOLPHIN); - - uint32_t current_lvl = stats.level; - uint32_t remaining = dolphin_state_xp_to_levelup(m->icounter); - - canvas_set_font(canvas, FontSecondary); - snprintf(buffer, sizeof(buffer), "Icounter: %lu Butthurt %lu", m->icounter, m->butthurt); - canvas_draw_str(canvas, 5, 19 + STATUS_BAR_Y_SHIFT, buffer); - - snprintf( - buffer, - sizeof(buffer), - "Level: %lu To level up: %lu", - current_lvl, - (remaining == (uint32_t)(-1) ? remaining : 0)); - canvas_draw_str(canvas, 5, 29 + STATUS_BAR_Y_SHIFT, buffer); - - // even if timestamp is uint64_t, it's safe to cast it to uint32_t, because furi_hal_rtc_datetime_to_timestamp only returns uint32_t - snprintf(buffer, sizeof(buffer), "%lu", (uint32_t)m->timestamp); - - canvas_draw_str(canvas, 5, 39 + STATUS_BAR_Y_SHIFT, buffer); - canvas_draw_str(canvas, 0, 49 + STATUS_BAR_Y_SHIFT, "[< >] icounter value [ok] save"); + if(!ver) { //-V1051 + canvas_draw_str(canvas, 0, 30 + STATUS_BAR_Y_SHIFT, "No info"); + return; } + + snprintf( + buffer, sizeof(buffer), "%s [%s]", version_get_version(ver), version_get_builddate(ver)); + canvas_draw_str(canvas, 0, 30 + STATUS_BAR_Y_SHIFT, buffer); + + uint16_t api_major, api_minor; + furi_hal_info_get_api_version(&api_major, &api_minor); + snprintf( + buffer, + sizeof(buffer), + "%s%s [%d.%d] %s", + version_get_dirty_flag(ver) ? "[!] " : "", + version_get_githash(ver), + api_major, + api_minor, + c2_ver ? c2_ver->StackTypeString : ""); + canvas_draw_str(canvas, 0, 40 + STATUS_BAR_Y_SHIFT, buffer); + + snprintf( + buffer, sizeof(buffer), "[%d] %s", version_get_target(ver), version_get_gitbranch(ver)); + canvas_draw_str(canvas, 0, 50 + STATUS_BAR_Y_SHIFT, buffer); } View* desktop_debug_get_view(DesktopDebugView* debug_view) { @@ -115,61 +90,43 @@ View* desktop_debug_get_view(DesktopDebugView* debug_view) { return debug_view->view; } -bool desktop_debug_input(InputEvent* event, void* context) { +static bool desktop_debug_input(InputEvent* event, void* context) { furi_assert(event); furi_assert(context); DesktopDebugView* debug_view = context; - if(event->type != InputTypeShort && event->type != InputTypeRepeat) { - return false; - } - - DesktopViewStatsScreens current = 0; - with_view_model( - debug_view->view, - DesktopDebugViewModel * model, - { -#ifdef SRV_DOLPHIN_STATE_DEBUG - if((event->key == InputKeyDown) || (event->key == InputKeyUp)) { - model->screen = !model->screen; - } -#endif - current = model->screen; - }, - true); - - size_t count = (event->type == InputTypeRepeat) ? 10 : 1; - if(current == DesktopViewStatsMeta) { - if(event->key == InputKeyLeft) { - while(count-- > 0) { - debug_view->callback(DesktopDebugEventWrongDeed, debug_view->context); - } - } else if(event->key == InputKeyRight) { - while(count-- > 0) { - debug_view->callback(DesktopDebugEventDeed, debug_view->context); - } - } else if(event->key == InputKeyOk) { - debug_view->callback(DesktopDebugEventSaveState, debug_view->context); - } else { - return false; - } - } - - if(event->key == InputKeyBack) { + if(event->key == InputKeyBack && event->type == InputTypeShort) { debug_view->callback(DesktopDebugEventExit, debug_view->context); } return true; } +static void desktop_debug_enter(void* context) { + DesktopDebugView* debug_view = context; + furi_timer_start(debug_view->timer, furi_ms_to_ticks(1000)); +} + +static void desktop_debug_exit(void* context) { + DesktopDebugView* debug_view = context; + furi_timer_stop(debug_view->timer); +} +void desktop_debug_timer(void* context) { + DesktopDebugView* debug_view = context; + view_get_model(debug_view->view); + view_commit_model(debug_view->view, true); +} + DesktopDebugView* desktop_debug_alloc() { DesktopDebugView* debug_view = malloc(sizeof(DesktopDebugView)); debug_view->view = view_alloc(); - view_allocate_model(debug_view->view, ViewModelTypeLocking, sizeof(DesktopDebugViewModel)); + debug_view->timer = furi_timer_alloc(desktop_debug_timer, FuriTimerTypePeriodic, debug_view); view_set_context(debug_view->view, debug_view); view_set_draw_callback(debug_view->view, (ViewDrawCallback)desktop_debug_render); view_set_input_callback(debug_view->view, desktop_debug_input); + view_set_enter_callback(debug_view->view, desktop_debug_enter); + view_set_exit_callback(debug_view->view, desktop_debug_exit); return debug_view; } @@ -177,27 +134,7 @@ DesktopDebugView* desktop_debug_alloc() { void desktop_debug_free(DesktopDebugView* debug_view) { furi_assert(debug_view); + furi_timer_free(debug_view->timer); view_free(debug_view->view); free(debug_view); } - -void desktop_debug_get_dolphin_data(DesktopDebugView* debug_view) { - Dolphin* dolphin = furi_record_open(RECORD_DOLPHIN); - DolphinStats stats = dolphin_stats(dolphin); - with_view_model( - debug_view->view, - DesktopDebugViewModel * model, - { - model->icounter = stats.icounter; - model->butthurt = stats.butthurt; - model->timestamp = stats.timestamp; - }, - true); - - furi_record_close(RECORD_DOLPHIN); -} - -void desktop_debug_reset_screen_idx(DesktopDebugView* debug_view) { - with_view_model( - debug_view->view, DesktopDebugViewModel * model, { model->screen = 0; }, true); -} diff --git a/applications/services/desktop/views/desktop_view_debug.h b/applications/services/desktop/views/desktop_view_debug.h index f6af16b2e..fea0c22a0 100644 --- a/applications/services/desktop/views/desktop_view_debug.h +++ b/applications/services/desktop/views/desktop_view_debug.h @@ -8,26 +8,13 @@ typedef struct DesktopDebugView DesktopDebugView; typedef void (*DesktopDebugViewCallback)(DesktopEvent event, void* context); -// Debug info -typedef enum { - DesktopViewStatsFw, - DesktopViewStatsMeta, - DesktopViewStatsTotalCount, -} DesktopViewStatsScreens; - struct DesktopDebugView { View* view; + FuriTimer* timer; DesktopDebugViewCallback callback; void* context; }; -typedef struct { - uint32_t icounter; - uint32_t butthurt; - uint64_t timestamp; - DesktopViewStatsScreens screen; -} DesktopDebugViewModel; - void desktop_debug_set_callback( DesktopDebugView* debug_view, DesktopDebugViewCallback callback, @@ -36,7 +23,5 @@ void desktop_debug_set_callback( View* desktop_debug_get_view(DesktopDebugView* debug_view); DesktopDebugView* desktop_debug_alloc(); -void desktop_debug_free(DesktopDebugView* debug_view); -void desktop_debug_get_dolphin_data(DesktopDebugView* debug_view); -void desktop_debug_reset_screen_idx(DesktopDebugView* debug_view); +void desktop_debug_free(DesktopDebugView* debug_view); From 8dc1edac18139aed83ee2f389c33e338403c085f Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Thu, 13 Jul 2023 15:02:59 +0300 Subject: [PATCH 114/364] Loader: good looking error messages (#2873) * Loader: special error for unknown external app * Loader: update special error * Loader: beautify GUI errors, remove redundant logs * Loader: fix gui error vertical position * Desktop settings: add external menu apps * Desktop: smaller settings struct and fix incorrect behavior with ext apps Co-authored-by: Aleksandr Kutuzov --- .../services/desktop/desktop_settings.h | 3 +- applications/services/loader/loader.c | 60 ++++++++++++------- .../services/loader/loader_applications.c | 2 +- applications/services/loader/loader_menu.c | 2 +- .../scenes/desktop_settings_scene_favorite.c | 44 ++++++++------ 5 files changed, 68 insertions(+), 43 deletions(-) diff --git a/applications/services/desktop/desktop_settings.h b/applications/services/desktop/desktop_settings.h index 7ab39094d..9b88868a8 100644 --- a/applications/services/desktop/desktop_settings.h +++ b/applications/services/desktop/desktop_settings.h @@ -8,7 +8,7 @@ #include #include -#define DESKTOP_SETTINGS_VER (7) +#define DESKTOP_SETTINGS_VER (8) #define DESKTOP_SETTINGS_PATH INT_PATH(DESKTOP_SETTINGS_FILE_NAME) #define DESKTOP_SETTINGS_MAGIC (0x17) @@ -42,7 +42,6 @@ typedef struct { } PinCode; typedef struct { - bool is_external; char name_or_path[MAX_APP_LENGTH]; } FavoriteApp; diff --git a/applications/services/loader/loader.c b/applications/services/loader/loader.c index e7fa38596..41c0f95d4 100644 --- a/applications/services/loader/loader.c +++ b/applications/services/loader/loader.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -11,7 +12,20 @@ #define TAG "Loader" #define LOADER_MAGIC_THREAD_VALUE 0xDEADBEEF -// api + +// helpers + +static const char* loader_find_external_application_by_name(const char* app_name) { + for(size_t i = 0; i < FLIPPER_EXTERNAL_APPS_COUNT; i++) { + if(strcmp(FLIPPER_EXTERNAL_APPS[i].name, app_name) == 0) { + return FLIPPER_EXTERNAL_APPS[i].path; + } + } + + return NULL; +} + +// API LoaderStatus loader_start(Loader* loader, const char* name, const char* args, FuriString* error_message) { @@ -33,17 +47,33 @@ LoaderStatus loader_start_with_gui_error(Loader* loader, const char* name, const FuriString* error_message = furi_string_alloc(); LoaderStatus status = loader_start(loader, name, args, error_message); - // TODO: we have many places where we can emit a double start, ex: desktop, menu - // so i prefer to not show LoaderStatusErrorAppStarted error message for now - if(status == LoaderStatusErrorUnknownApp || status == LoaderStatusErrorInternal) { + if(status == LoaderStatusErrorUnknownApp && + loader_find_external_application_by_name(name) != NULL) { + // Special case for external apps + DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); + DialogMessage* message = dialog_message_alloc(); + dialog_message_set_header(message, "Update needed", 64, 3, AlignCenter, AlignTop); + dialog_message_set_buttons(message, NULL, NULL, NULL); + dialog_message_set_icon(message, &I_DolphinCommon_56x48, 72, 17); + dialog_message_set_text( + message, "Update firmware\nto run this app", 3, 26, AlignLeft, AlignTop); + dialog_message_show(dialogs, message); + dialog_message_free(message); + furi_record_close(RECORD_DIALOGS); + } else if(status == LoaderStatusErrorUnknownApp || status == LoaderStatusErrorInternal) { + // TODO: we have many places where we can emit a double start, ex: desktop, menu + // so i prefer to not show LoaderStatusErrorAppStarted error message for now DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); DialogMessage* message = dialog_message_alloc(); dialog_message_set_header(message, "Error", 64, 0, AlignCenter, AlignTop); dialog_message_set_buttons(message, NULL, NULL, NULL); - furi_string_replace(error_message, ":", "\n"); + furi_string_replace(error_message, "/ext/apps/", ""); + furi_string_replace(error_message, ", ", "\n"); + furi_string_replace(error_message, ": ", "\n"); + dialog_message_set_text( - message, furi_string_get_cstr(error_message), 64, 32, AlignCenter, AlignCenter); + message, furi_string_get_cstr(error_message), 64, 35, AlignCenter, AlignCenter); dialog_message_show(dialogs, message); dialog_message_free(message); @@ -170,16 +200,6 @@ static const FlipperInternalApplication* loader_find_application_by_name(const c return application; } -static const char* loader_find_external_application_by_name(const char* app_name) { - for(size_t i = 0; i < FLIPPER_EXTERNAL_APPS_COUNT; i++) { - if(strcmp(FLIPPER_EXTERNAL_APPS[i].name, app_name) == 0) { - return FLIPPER_EXTERNAL_APPS[i].path; - } - } - - return NULL; -} - static void loader_start_app_thread(Loader* loader, FlipperInternalApplicationFlag flags) { // setup heap trace FuriHalRtcHeapTrackMode mode = furi_hal_rtc_get_heap_track_mode(); @@ -278,22 +298,20 @@ static LoaderStatus loader_start_external_app( if(preload_res != FlipperApplicationPreloadStatusSuccess) { const char* err_msg = flipper_application_preload_status_to_string(preload_res); status = loader_make_status_error( - LoaderStatusErrorInternal, error_message, "Preload failed %s: %s", path, err_msg); + LoaderStatusErrorInternal, error_message, "Preload failed, %s: %s", path, err_msg); break; } - FURI_LOG_I(TAG, "Mapping"); FlipperApplicationLoadStatus load_status = flipper_application_map_to_memory(loader->app.fap); if(load_status != FlipperApplicationLoadStatusSuccess) { const char* err_msg = flipper_application_load_status_to_string(load_status); status = loader_make_status_error( - LoaderStatusErrorInternal, error_message, "Load failed %s: %s", path, err_msg); + LoaderStatusErrorInternal, error_message, "Load failed, %s: %s", path, err_msg); break; } FURI_LOG_I(TAG, "Loaded in %zums", (size_t)(furi_get_tick() - start)); - FURI_LOG_I(TAG, "Starting app"); loader->app.thread = flipper_application_alloc_thread(loader->app.fap, args); FuriString* app_name = furi_string_alloc(); @@ -397,7 +415,7 @@ static LoaderStatus loader_do_start_by_name( } } - // check external apps + // check Faps { Storage* storage = furi_record_open(RECORD_STORAGE); if(storage_file_exists(storage, name)) { diff --git a/applications/services/loader/loader_applications.c b/applications/services/loader/loader_applications.c index 7bf189e55..8ae91d764 100644 --- a/applications/services/loader/loader_applications.c +++ b/applications/services/loader/loader_applications.c @@ -20,7 +20,7 @@ static int32_t loader_applications_thread(void* p); LoaderApplications* loader_applications_alloc(void (*closed_cb)(void*), void* context) { LoaderApplications* loader_applications = malloc(sizeof(LoaderApplications)); loader_applications->thread = - furi_thread_alloc_ex(TAG, 512, loader_applications_thread, (void*)loader_applications); + furi_thread_alloc_ex(TAG, 768, loader_applications_thread, (void*)loader_applications); loader_applications->closed_cb = closed_cb; loader_applications->context = context; furi_thread_start(loader_applications->thread); diff --git a/applications/services/loader/loader_menu.c b/applications/services/loader/loader_menu.c index 2c48b0923..149fea72c 100644 --- a/applications/services/loader/loader_menu.c +++ b/applications/services/loader/loader_menu.c @@ -60,7 +60,7 @@ static void loader_menu_apps_callback(void* context, uint32_t index) { static void loader_menu_external_apps_callback(void* context, uint32_t index) { UNUSED(context); - const char* path = FLIPPER_EXTERNAL_APPS[index].path; + const char* path = FLIPPER_EXTERNAL_APPS[index].name; loader_menu_start(path); } diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c index c35a10568..26e7bc587 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c @@ -5,11 +5,26 @@ #include #include +#define APPS_COUNT (FLIPPER_APPS_COUNT + FLIPPER_EXTERNAL_APPS_COUNT) + #define EXTERNAL_BROWSER_NAME ("Applications") -#define EXTERNAL_BROWSER_INDEX (FLIPPER_APPS_COUNT + 1) +#define EXTERNAL_BROWSER_INDEX (APPS_COUNT + 1) #define EXTERNAL_APPLICATION_NAME ("[External Application]") -#define EXTERNAL_APPLICATION_INDEX (FLIPPER_APPS_COUNT + 2) +#define EXTERNAL_APPLICATION_INDEX (APPS_COUNT + 2) + +#define PRESELECTED_SPECIAL 0xffffffff + +static const char* favorite_fap_get_app_name(size_t i) { + const char* name; + if(i < FLIPPER_APPS_COUNT) { + name = FLIPPER_APPS[i].name; + } else { + name = FLIPPER_EXTERNAL_APPS[i - FLIPPER_APPS_COUNT].name; + } + + return name; +} static bool favorite_fap_selector_item_callback( FuriString* file_path, @@ -42,21 +57,17 @@ void desktop_settings_scene_favorite_on_enter(void* context) { uint32_t primary_favorite = scene_manager_get_scene_state(app->scene_manager, DesktopSettingsAppSceneFavorite); - uint32_t pre_select_item = 0; + uint32_t pre_select_item = PRESELECTED_SPECIAL; FavoriteApp* curr_favorite_app = primary_favorite ? &app->settings.favorite_primary : &app->settings.favorite_secondary; - for(size_t i = 0; i < FLIPPER_APPS_COUNT; i++) { - submenu_add_item( - submenu, - FLIPPER_APPS[i].name, - i, - desktop_settings_scene_favorite_submenu_callback, - app); + for(size_t i = 0; i < APPS_COUNT; i++) { + const char* name = favorite_fap_get_app_name(i); + + submenu_add_item(submenu, name, i, desktop_settings_scene_favorite_submenu_callback, app); // Select favorite item in submenu - if(!curr_favorite_app->is_external && - !strcmp(FLIPPER_APPS[i].name, curr_favorite_app->name_or_path)) { + if(!strcmp(name, curr_favorite_app->name_or_path)) { pre_select_item = i; } } @@ -77,7 +88,7 @@ void desktop_settings_scene_favorite_on_enter(void* context) { desktop_settings_scene_favorite_submenu_callback, app); - if(curr_favorite_app->is_external) { + if(pre_select_item == PRESELECTED_SPECIAL) { if(curr_favorite_app->name_or_path[0] == '\0') { pre_select_item = EXTERNAL_BROWSER_INDEX; } else { @@ -104,7 +115,6 @@ bool desktop_settings_scene_favorite_on_event(void* context, SceneManagerEvent e if(event.type == SceneManagerEventTypeCustom) { if(event.event == EXTERNAL_BROWSER_INDEX) { - curr_favorite_app->is_external = true; curr_favorite_app->name_or_path[0] = '\0'; consumed = true; } else if(event.event == EXTERNAL_APPLICATION_INDEX) { @@ -125,7 +135,6 @@ bool desktop_settings_scene_favorite_on_event(void* context, SceneManagerEvent e if(dialog_file_browser_show(app->dialogs, temp_path, temp_path, &browser_options)) { submenu_reset(app->submenu); // Prevent menu from being shown when we exiting scene - curr_favorite_app->is_external = true; strncpy( curr_favorite_app->name_or_path, furi_string_get_cstr(temp_path), @@ -133,9 +142,8 @@ bool desktop_settings_scene_favorite_on_event(void* context, SceneManagerEvent e consumed = true; } } else { - curr_favorite_app->is_external = false; - strncpy( - curr_favorite_app->name_or_path, FLIPPER_APPS[event.event].name, MAX_APP_LENGTH); + const char* name = favorite_fap_get_app_name(event.event); + if(name) strncpy(curr_favorite_app->name_or_path, name, MAX_APP_LENGTH); consumed = true; } if(consumed) { From 6605740ce9e1e9440d577e7cce0273d269676d74 Mon Sep 17 00:00:00 2001 From: Andrey Zakharov Date: Thu, 13 Jul 2023 15:09:04 +0300 Subject: [PATCH 115/364] Add LG A/C IR signals to universal remote (#2871) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Andrey Zakharov Co-authored-by: ã‚ã --- assets/resources/infrared/assets/ac.ir | 37 ++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/assets/resources/infrared/assets/ac.ir b/assets/resources/infrared/assets/ac.ir index cb3c2539c..6ec6fc829 100644 --- a/assets/resources/infrared/assets/ac.ir +++ b/assets/resources/infrared/assets/ac.ir @@ -544,3 +544,40 @@ type: raw frequency: 38000 duty_cycle: 0.330000 data: 30675 50953 3432 1606 490 1190 489 350 489 350 489 350 489 350 489 351 488 350 489 351 489 351 488 351 489 351 488 351 489 1191 488 351 488 351 489 351 488 351 488 351 488 351 489 351 488 1191 489 1191 489 351 488 351 488 351 488 351 488 351 489 351 489 351 488 351 489 1191 488 351 488 1191 489 1191 488 1191 488 1191 488 1191 488 1191 488 351 489 1191 488 1191 489 351 488 351 489 351 488 351 489 351 488 351 488 351 488 351 488 1191 488 1191 488 1191 488 1191 489 1191 488 1191 488 1191 489 1191 488 351 488 351 489 351 488 1191 488 351 489 351 488 351 488 351 489 1191 488 351 489 351 488 1191 488 351 488 351 488 351 489 351 489 351 488 351 489 1191 488 351 488 351 489 351 488 351 488 1191 488 1191 488 351 488 351 489 351 488 351 489 351 488 351 489 351 489 1191 488 1192 488 1191 488 351 489 1191 489 351 488 351 488 351 489 351 489 351 488 351 488 351 489 351 488 351 488 351 488 1192 488 351 489 351 488 351 488 351 489 351 489 351 488 351 488 351 488 352 487 352 488 351 488 352 488 351 488 351 488 351 488 1192 488 1192 487 352 488 352 487 352 487 352 488 352 487 352 488 351 488 352 488 352 488 352 487 352 488 351 488 351 488 352 488 352 487 352 488 352 487 352 488 352 488 352 488 352 487 1192 487 352 487 352 488 352 488 352 488 352 487 352 487 352 488 352 487 352 487 352 487 352 488 352 488 352 487 352 487 352 488 352 487 352 488 352 487 352 487 352 487 352 487 352 487 352 488 352 487 352 487 352 487 352 488 352 487 352 488 352 487 352 488 352 487 352 487 352 488 352 487 352 488 352 487 352 488 352 488 352 487 352 487 352 487 352 487 352 487 352 488 352 487 352 487 352 487 1193 486 352 487 352 488 352 487 352 487 352 488 352 487 352 488 352 488 352 487 352 488 352 487 353 486 353 487 352 487 352 488 352 488 352 487 352 487 352 487 352 487 353 487 352 488 352 488 352 487 1193 486 1193 486 1193 486 1193 487 353 487 352 487 353 486 +# +# Model: LG PC07SQR +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3169 9836 535 1553 511 551 491 543 491 544 490 1586 490 532 510 530 511 543 491 1579 489 1577 490 543 490 543 491 543 491 544 490 544 489 544 490 543 491 544 490 550 492 544 490 543 491 1586 489 542 492 1583 492 552 490 532 510 537 512 1585 491 +# +name: Dh +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3385 9888 512 1544 512 544 489 544 489 544 490 1586 489 545 489 544 489 545 489 531 511 541 508 533 508 542 507 544 489 546 487 544 490 1587 489 1573 510 543 490 543 491 1578 490 545 489 1574 509 545 488 1587 489 1573 510 1586 490 1579 489 1568 507 +# +name: Cool_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3288 9816 598 1504 574 472 569 463 571 463 571 1515 568 472 569 461 573 471 571 459 590 462 571 463 571 447 594 462 572 462 571 464 570 459 590 463 570 464 570 1496 571 1497 570 463 571 1514 569 464 570 1504 572 1514 569 471 571 473 568 462 572 +# +name: Cool_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3205 9865 616 1473 536 518 516 517 517 517 517 1575 517 527 568 464 515 517 571 463 570 462 572 462 572 469 573 461 573 463 570 462 572 469 573 1504 572 451 590 451 591 472 569 463 571 1494 573 461 573 1497 570 1505 570 1503 572 471 571 1491 592 +# +name: Heat_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3363 9846 596 1495 514 518 515 517 517 515 518 1552 516 502 539 517 517 516 518 516 518 517 517 517 571 462 517 518 516 1554 568 461 589 462 572 1505 571 1507 568 1514 514 1568 570 461 573 1504 572 472 570 1507 592 1490 593 460 574 462 572 447 594 +# +name: Heat_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3204 9889 537 1587 491 529 512 544 489 545 489 1573 510 530 511 543 491 552 490 538 511 543 491 543 491 532 509 543 491 1587 489 537 512 543 491 1577 490 543 491 543 491 544 489 543 491 1586 489 544 490 1587 489 539 510 543 491 543 491 1586 490 From a7b3f9df4cd0046a3c8a9076412121abd614ff0e Mon Sep 17 00:00:00 2001 From: amec0e <88857687+amec0e@users.noreply.github.com> Date: Thu, 13 Jul 2023 18:32:54 +0100 Subject: [PATCH 116/364] Update audio.ir New additions --- assets/resources/infrared/assets/audio.ir | 52 ++++++++++++++++++++++- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/assets/resources/infrared/assets/audio.ir b/assets/resources/infrared/assets/audio.ir index 74538cad1..a5bfa1da1 100644 --- a/assets/resources/infrared/assets/audio.ir +++ b/assets/resources/infrared/assets/audio.ir @@ -1,7 +1,7 @@ Filetype: IR library file Version: 1 -# Last Updated 1st Jul, 2023 -# Last Checked 1st Jul, 2023 +# Last Updated 13th Jul, 2023 +# Last Checked 13th Jul, 2023 # name: Power type: parsed @@ -3680,3 +3680,51 @@ type: parsed protocol: NEC address: 80 00 00 00 command: 02 00 00 00 +# +name: Power +type: parsed +protocol: RC5 +address: 15 00 00 00 +command: 0C 00 00 00 +# +name: Mute +type: parsed +protocol: RC5 +address: 15 00 00 00 +command: 0D 00 00 00 +# +name: Vol_up +type: parsed +protocol: RC5 +address: 15 00 00 00 +command: 10 00 00 00 +# +name: Vol_dn +type: parsed +protocol: RC5 +address: 15 00 00 00 +command: 11 00 00 00 +# +name: Prev +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 40 00 00 00 +# +name: Next +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 43 00 00 00 +# +name: Play +type: parsed +protocol: RC5 +address: 15 00 00 00 +command: 35 00 00 00 +# +name: Pause +type: parsed +protocol: RC5 +address: 15 00 00 00 +command: 35 00 00 00 From 34abfcb45441942b55967b16270dc3e266f71c48 Mon Sep 17 00:00:00 2001 From: amec0e <88857687+amec0e@users.noreply.github.com> Date: Thu, 13 Jul 2023 18:33:26 +0100 Subject: [PATCH 117/364] Update tv.ir Updated last checked --- assets/resources/infrared/assets/tv.ir | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/resources/infrared/assets/tv.ir b/assets/resources/infrared/assets/tv.ir index 32ddfded2..b4e0cf66a 100755 --- a/assets/resources/infrared/assets/tv.ir +++ b/assets/resources/infrared/assets/tv.ir @@ -1,7 +1,7 @@ Filetype: IR library file Version: 1 # Last Updated 1st Jul, 2023 -# Last Checked 1st Jul, 2023 +# Last Checked 13th Jul, 2023 # name: Power type: parsed From 59d56186aa1ee7714ad573ed25d4a237ecffe66b Mon Sep 17 00:00:00 2001 From: amec0e <88857687+amec0e@users.noreply.github.com> Date: Thu, 13 Jul 2023 18:33:54 +0100 Subject: [PATCH 118/364] Update projectors.ir Updated last checked --- assets/resources/infrared/assets/projectors.ir | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/resources/infrared/assets/projectors.ir b/assets/resources/infrared/assets/projectors.ir index 7df0fe127..7815b333d 100644 --- a/assets/resources/infrared/assets/projectors.ir +++ b/assets/resources/infrared/assets/projectors.ir @@ -1,7 +1,7 @@ Filetype: IR library file Version: 1 # Last Updated 16th Jun, 2023 -# Last Checked 1st Jul, 2023 +# Last Checked 13th Jul, 2023 # # ON name: Power From d64d797bcc6f92e06644061bfb5c5740947e7d78 Mon Sep 17 00:00:00 2001 From: amec0e <88857687+amec0e@users.noreply.github.com> Date: Thu, 13 Jul 2023 18:35:12 +0100 Subject: [PATCH 119/364] Update fans.ir New additions --- assets/resources/infrared/assets/fans.ir | 52 +++++++++++++++++++++++- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/assets/resources/infrared/assets/fans.ir b/assets/resources/infrared/assets/fans.ir index 185573ece..1201fe696 100644 --- a/assets/resources/infrared/assets/fans.ir +++ b/assets/resources/infrared/assets/fans.ir @@ -1,7 +1,7 @@ Filetype: IR library file Version: 1 -# Last Updated 1st Jul, 2023 -# Last Checked 1st Jul, 2023 +# Last Updated 13th Jul, 2023 +# Last Checked 13th Jul, 2023 # name: Power type: raw @@ -1833,3 +1833,51 @@ type: raw frequency: 38000 duty_cycle: 0.33 data: 1286 396 1287 396 444 1240 1286 396 1286 397 443 1239 444 1240 1285 396 444 1239 444 1240 443 1241 442 7977 1286 396 1286 396 500 1183 1287 396 1286 395 501 1183 501 1184 1286 395 500 1184 499 1183 500 1183 501 7922 1340 341 1341 341 499 1184 1342 341 1341 340 500 1184 499 1183 1343 340 500 1184 499 1183 500 1184 499 7922 1342 340 1342 340 500 1184 1342 340 1342 340 500 1185 498 1184 1342 340 500 1184 499 1184 499 1186 498 7922 1341 341 1341 341 499 1184 1342 341 1341 340 500 1183 500 1184 1342 341 499 1184 500 1184 499 1184 499 +# +name: Power +type: parsed +protocol: NECext +address: 82 21 00 00 +command: 1F E0 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 82 21 00 00 +command: 1B E4 00 00 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 1278 421 1279 422 508 1210 1250 422 1278 421 509 1182 509 1183 508 1183 508 1183 508 1182 509 1184 1276 7327 1278 421 1279 421 509 1184 1276 422 1277 422 508 1183 508 1183 508 1182 509 1184 507 1184 507 1182 1278 7329 1351 345 1355 346 508 1183 1353 346 1353 346 509 1182 509 1182 509 1184 507 1183 508 1182 508 1183 1353 7251 1354 345 1354 346 508 1184 1352 346 1353 346 508 1184 507 1182 509 1185 506 1182 509 1183 508 1186 1350 7253 1352 345 1354 346 508 1183 1353 346 1353 345 509 1184 507 1183 508 1211 479 1184 507 1183 508 1185 1351 7254 1351 347 1352 348 506 1183 1353 347 1352 346 508 1183 508 1184 507 1187 504 1183 508 1185 506 1184 1352 +# +name: Rotate +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 1278 421 1279 421 509 1183 1277 421 1278 422 508 1183 508 1182 509 1182 509 1182 509 1184 1276 421 509 8086 1279 421 1278 421 509 1183 1277 422 1277 422 508 1182 509 1183 508 1182 509 1184 507 1183 1352 347 508 8088 1353 346 1353 346 508 1184 1351 346 1353 347 507 1184 507 1184 507 1183 508 1187 504 1183 1353 346 508 8088 1352 345 1354 346 508 1183 1353 347 1352 346 508 1184 507 1185 506 1185 506 1184 507 1183 1349 350 508 8092 1345 352 1348 351 506 1185 1347 351 1348 352 505 1185 505 1185 506 1187 503 1185 505 1185 1347 354 504 8094 1250 446 1253 447 497 1193 1253 447 1252 446 497 1194 496 1195 495 1193 497 1195 495 1196 1252 446 495 +# +name: Timer +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 1253 446 1253 447 495 1194 1254 446 1253 447 422 1268 422 1268 1253 448 421 1270 421 1269 422 1269 422 8176 1251 448 1251 447 421 1269 1253 445 1254 447 421 1267 423 1269 1253 446 422 1270 421 1269 422 1269 422 8174 1253 447 1252 446 423 1269 1252 447 1252 445 424 1268 423 1269 1252 447 422 1268 422 1269 422 1268 423 8174 1252 446 1253 448 493 1195 1254 446 1253 447 495 1196 495 1195 1252 447 496 1193 497 1194 498 1194 496 8098 1253 445 1254 446 499 1194 1252 446 1253 446 498 1192 498 1191 1254 447 503 1187 504 1187 504 1186 505 8091 1346 352 1347 353 505 1186 1346 352 1347 352 505 1185 506 1187 1345 351 507 1185 505 1185 505 1184 506 +# +name: Power +type: parsed +protocol: NEC +address: 30 00 00 00 +command: 88 00 00 00 +# +name: Speed_up +type: parsed +protocol: NEC +address: 30 00 00 00 +command: 8C 00 00 00 +# +name: Speed_dn +type: parsed +protocol: NEC +address: 30 00 00 00 +command: 87 00 00 00 From 705d6265d17d598eb563a7581199edd5b924aecb Mon Sep 17 00:00:00 2001 From: amec0e <88857687+amec0e@users.noreply.github.com> Date: Thu, 13 Jul 2023 18:37:04 +0100 Subject: [PATCH 120/364] Update ac.ir New captures for Tosot and Chigo (fixed swing on) and Added LG PC07SQR --- assets/resources/infrared/assets/ac.ir | 116 +++++++++++++++++-------- 1 file changed, 78 insertions(+), 38 deletions(-) diff --git a/assets/resources/infrared/assets/ac.ir b/assets/resources/infrared/assets/ac.ir index 4edb9eb66..0e4ced07d 100644 --- a/assets/resources/infrared/assets/ac.ir +++ b/assets/resources/infrared/assets/ac.ir @@ -1,5 +1,7 @@ Filetype: IR library file Version: 1 +# Last Updated 13th Jul, 2023 +# Last Checked 13th Jul, 2023 # # Model: Electrolux EACM-16 HP/N3 name: Off @@ -476,74 +478,75 @@ name: Off type: raw frequency: 38000 duty_cycle: 0.330000 -data: 6058 7359 592 1635 591 1634 592 1634 592 1634 592 1634 592 1633 592 1634 591 1634 592 515 591 516 590 516 590 516 590 517 589 518 588 518 589 518 589 1637 589 1637 589 1637 589 1637 589 1637 589 1637 589 1638 588 1638 588 518 589 518 588 518 589 518 588 518 589 518 589 518 589 518 588 1637 589 1638 588 1638 588 1638 588 1638 588 1638 588 1638 588 1638 588 518 588 519 588 518 588 519 588 518 589 519 587 519 588 519 587 1638 588 1638 588 519 588 1638 588 519 587 519 588 1638 588 1638 588 519 588 519 587 1639 587 519 587 1638 588 1639 587 519 588 519 587 1639 587 1639 587 1639 587 1639 587 1639 587 520 587 1639 587 1639 587 519 587 520 586 520 586 520 587 520 587 1640 586 521 586 521 586 522 585 1664 562 544 562 1664 562 544 562 1664 562 544 562 544 563 1664 562 544 562 1664 562 545 562 1664 562 545 561 1664 562 1664 562 7386 562 -# +data: 6060 7357 592 1633 593 1633 593 1633 593 1633 593 1633 593 1633 592 1634 592 1634 592 515 591 515 591 515 591 516 590 517 590 516 590 517 590 517 589 1636 590 1636 590 1636 590 1636 590 1636 590 1636 590 1636 590 1636 590 517 590 517 590 517 590 517 590 517 590 517 589 517 590 517 590 1636 590 1637 589 1637 589 1637 589 1636 615 1612 589 1637 589 1636 590 517 590 517 589 517 590 517 590 517 590 517 589 517 590 517 590 1637 589 1637 589 1637 589 517 589 517 589 517 590 1637 589 1637 589 517 590 517 590 517 589 1637 589 1637 589 1637 590 517 589 517 589 517 589 518 589 517 589 1637 589 1637 589 517 590 1637 589 1637 589 1637 589 1637 589 1637 590 517 589 517 589 1637 589 517 589 517 590 517 589 1638 589 517 590 1637 589 518 589 1637 589 518 588 518 589 1637 589 518 588 1637 589 518 588 1637 589 518 589 1637 589 1637 589 7359 589 +# name: Cool_hi type: raw frequency: 38000 duty_cycle: 0.330000 -data: 6033 7358 591 1634 593 1634 592 1634 592 1634 592 1634 592 1634 592 1634 591 1634 592 515 617 490 616 491 590 517 590 517 590 517 590 517 590 517 590 1637 589 1637 589 1637 589 1637 589 1637 589 1637 589 1637 589 1637 589 518 589 517 590 517 589 518 589 517 616 492 589 517 615 492 590 517 589 518 613 1613 589 1637 614 1612 589 1637 589 1637 589 1637 589 1637 589 1638 588 518 613 493 613 494 596 512 589 518 612 494 589 1637 614 492 589 518 588 1637 614 1612 614 492 615 1612 613 1613 613 493 590 1637 589 1637 589 518 614 492 614 1612 614 492 614 493 613 1613 613 1612 614 1612 614 1612 614 1612 614 492 614 1612 614 1612 614 493 614 493 614 493 614 493 614 492 614 1612 614 493 614 492 615 492 614 1612 614 492 614 1612 614 492 614 1612 614 493 613 493 614 1612 614 492 614 1612 614 493 614 1612 614 493 614 1612 614 1612 614 7334 614 -# -name: Heat_hi -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 6090 7300 672 1579 646 1579 622 1604 622 1604 622 1605 620 1605 620 1606 619 1607 618 488 618 489 617 490 616 490 616 491 616 491 616 490 617 491 616 1610 616 1610 616 1610 616 1610 616 1610 616 1611 616 1610 616 1610 616 491 616 491 615 491 616 491 615 491 615 491 615 491 616 491 616 1611 615 491 616 1611 615 1611 615 1611 615 1611 615 1611 615 1611 615 491 615 1611 615 491 615 491 616 491 615 492 615 491 615 491 616 1611 615 491 615 492 614 1611 615 1611 615 492 615 1611 615 1611 615 492 614 1611 615 1611 615 492 615 492 615 1611 615 492 615 492 614 1611 615 492 614 492 615 493 615 1611 615 1611 615 1612 614 492 615 492 614 1611 615 1611 615 1612 614 492 615 492 614 492 614 1612 614 492 614 1612 614 492 615 1612 614 492 614 1612 614 492 614 492 615 1612 614 493 614 1612 614 493 614 1612 614 493 614 1612 614 1612 614 7334 614 -# -name: Heat_lo -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 6091 7301 646 1604 647 1579 647 1579 622 1604 621 1605 620 1605 620 1606 619 1607 618 488 618 488 618 489 617 490 616 491 615 491 615 491 616 491 616 1610 616 1611 615 1611 615 1611 616 1611 615 1610 616 1611 615 1611 615 491 615 491 616 491 616 491 615 491 615 491 616 491 615 491 615 1611 615 491 615 1611 615 1611 615 1611 615 1611 615 1611 615 1611 615 491 615 1611 615 491 615 492 615 491 615 492 615 492 615 491 616 1611 615 492 614 492 615 1611 615 1611 615 492 614 1611 615 1611 615 492 615 1611 615 1612 614 492 614 492 615 1611 615 492 615 492 614 492 615 492 615 492 614 1611 615 1612 614 1612 614 1612 614 492 615 1612 614 1612 614 1612 614 492 615 492 615 492 614 493 614 1612 614 492 615 1612 614 492 614 1612 614 492 614 1612 614 492 615 492 614 1612 614 493 613 1612 614 493 613 1612 614 493 613 1612 614 1612 614 7334 614 -# +data: 6032 7356 592 1633 593 1633 592 1633 592 1633 592 1633 592 1633 618 1608 593 1634 617 490 590 516 614 492 616 491 591 517 589 517 590 517 589 517 590 1636 590 1636 590 1636 590 1636 615 1612 589 1636 590 1636 590 1636 589 517 590 517 589 517 589 517 590 517 589 517 589 517 590 517 589 517 589 517 590 1636 590 1636 590 1636 589 1637 589 1637 589 1636 589 1637 589 1637 589 517 589 517 589 517 589 517 589 517 590 517 589 1637 589 517 590 1636 589 517 589 517 589 1637 589 1637 588 1637 589 517 589 1637 589 518 588 1637 588 1637 589 518 588 518 588 518 588 1637 589 1637 589 1637 589 1637 589 1637 589 518 588 1637 589 1637 589 518 588 518 588 518 589 518 588 518 589 1637 589 517 589 518 589 518 588 1637 589 518 588 1637 589 518 588 1637 589 518 588 518 589 1637 588 518 589 1637 588 518 588 1638 588 518 588 1637 589 1637 589 7358 588 +# name: Cool_lo type: raw frequency: 38000 duty_cycle: 0.330000 -data: 6114 7274 647 1604 621 1605 620 1604 622 1604 621 1604 621 1605 620 1605 619 1606 619 488 618 488 618 489 617 490 616 490 616 491 616 490 616 490 616 1610 616 1610 616 1610 615 1610 615 1610 616 1610 616 1610 616 1610 616 491 615 491 616 491 615 491 615 491 615 491 616 491 615 491 615 1610 616 491 615 1610 616 1610 616 1610 616 1610 616 1610 615 1610 616 491 615 1610 616 491 615 491 615 491 616 491 615 491 615 491 616 1610 616 491 615 491 615 1610 616 1610 615 491 615 1610 617 1611 615 491 616 1610 616 1610 616 491 615 491 616 1610 616 491 615 491 615 491 615 491 615 491 615 1611 615 1611 614 491 615 1611 615 1611 615 1611 615 1611 615 1611 614 492 614 492 614 1611 614 492 615 492 615 492 614 1611 615 492 615 1611 615 492 614 1611 616 492 615 492 614 1611 614 492 615 1611 615 492 614 1611 615 492 614 1611 615 1611 615 7332 614 -# +data: 6057 7329 647 1604 621 1579 646 1604 621 1604 621 1605 620 1605 620 1606 619 1607 618 488 618 489 617 490 616 490 616 491 616 491 616 490 616 490 616 1610 616 1610 616 1610 616 1610 616 1609 616 1610 616 1610 616 1610 616 490 616 490 617 490 616 491 615 490 616 490 616 490 616 490 616 1610 616 491 616 1610 616 1610 616 1610 616 1610 616 1610 616 1610 615 490 616 1610 615 491 615 491 615 491 616 491 616 490 616 491 616 1610 616 490 616 1610 615 491 616 490 616 1610 616 1610 616 1610 616 491 615 1610 616 491 615 1610 615 1610 616 491 615 491 615 491 616 491 615 491 616 491 615 1610 615 1611 615 491 615 1610 615 1610 616 1610 616 1610 615 1611 615 491 615 491 616 1611 614 491 615 491 616 492 615 1611 614 492 615 1611 614 492 615 1611 614 491 615 492 615 1611 615 492 614 1611 614 492 615 1611 615 492 614 1611 614 1611 615 7332 614 +# +name: Heat_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 6034 7357 592 1633 593 1634 592 1633 593 1633 593 1633 593 1633 618 1608 618 1608 618 489 617 490 616 490 618 491 614 492 615 492 589 517 614 493 614 1611 590 1637 614 1611 590 1636 614 1612 590 1637 589 1637 589 1637 590 517 589 517 590 517 589 517 590 517 589 517 590 517 589 517 589 1636 590 517 589 1637 589 1637 589 1637 589 1637 589 1637 589 1637 589 517 590 1637 589 517 590 517 589 517 589 518 588 517 590 517 589 1637 589 517 589 1637 589 517 589 517 590 1637 589 1637 589 1637 589 518 589 1637 589 517 589 1637 589 1637 590 517 589 518 589 518 588 518 589 518 589 517 589 1637 589 1637 589 1637 589 1637 590 518 589 1637 589 1637 589 1637 589 518 588 518 589 518 588 518 588 1638 588 518 589 1637 589 518 588 1637 589 518 588 1638 588 518 589 518 589 1637 589 518 589 1637 589 518 589 1637 589 518 589 1638 588 1638 588 7360 588 +# +name: Heat_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 6034 7358 592 1634 592 1634 592 1634 592 1633 593 1633 593 1634 592 1634 592 1634 592 515 616 490 591 516 590 517 613 493 590 517 589 517 590 517 589 1637 589 1637 589 1637 589 1637 589 1637 589 1637 589 1637 589 1637 590 517 589 517 590 517 589 517 590 517 589 517 590 517 589 517 590 1637 589 517 590 1637 589 1637 589 1637 589 1637 589 1637 589 1637 589 517 590 1637 589 517 590 517 590 517 589 517 590 517 590 517 589 1637 589 517 589 1637 589 518 588 518 589 1637 589 1637 589 1637 589 518 589 1637 589 517 589 1637 589 1637 589 518 589 517 589 517 590 1637 589 517 589 518 588 518 589 1637 589 1637 589 1637 589 518 588 518 589 1637 589 1637 589 1637 589 518 589 518 589 518 589 1637 589 518 588 1637 589 518 589 1637 589 518 588 1637 589 518 589 518 588 1637 589 518 588 1637 589 517 589 1637 589 518 589 1637 589 1637 589 7359 589 +# name: Dh type: raw frequency: 38000 duty_cycle: 0.330000 -data: 6089 7302 644 1582 644 1582 644 1583 643 1584 642 1586 640 1607 618 1608 617 1609 617 490 616 491 615 491 615 492 615 492 614 492 615 492 615 492 614 1612 614 1612 614 1611 615 1611 615 1612 614 1612 614 1611 615 1611 615 492 615 492 615 492 614 492 614 492 614 492 615 492 615 492 615 492 615 1612 614 1612 614 1612 614 1612 614 1612 614 1612 614 1612 614 1612 614 492 614 492 614 492 614 492 615 492 615 492 614 492 615 1612 614 493 614 492 615 1612 614 1612 614 492 614 493 614 1612 614 492 615 1612 614 1612 614 493 614 493 613 1612 614 1612 614 493 614 493 613 1612 614 1612 614 493 613 1612 614 1612 614 493 614 1612 614 1612 614 493 613 493 614 1612 614 493 613 493 614 1613 613 494 613 493 614 1613 613 493 613 1613 613 493 613 1613 613 493 613 493 614 1613 613 494 613 1613 613 493 613 1613 613 494 613 1613 613 1613 613 7335 613 +data: 6059 7355 592 1633 592 1633 592 1633 592 1633 592 1633 592 1633 592 1633 618 1608 617 489 617 490 616 490 616 491 590 517 589 517 614 492 590 517 590 1636 589 1636 590 1636 590 1636 589 1636 590 1636 590 1636 590 1636 590 517 589 517 589 517 589 517 590 517 589 517 589 517 589 517 589 517 589 1636 614 492 590 1636 590 1636 590 1636 589 1636 590 1636 589 1637 589 517 589 1636 590 517 589 517 589 517 614 492 590 517 589 1636 590 517 589 1636 590 517 589 517 589 517 589 517 590 1636 590 517 589 1636 590 517 589 1636 589 1636 590 1636 590 1637 589 517 589 517 589 1637 588 1637 589 517 589 1637 589 1636 590 517 589 1636 590 1636 590 517 589 517 589 1637 589 517 589 517 589 1637 589 517 589 518 588 1637 589 518 588 1637 589 517 590 1637 588 518 588 518 588 1637 589 518 588 1637 589 518 588 1637 589 518 588 1637 589 1637 589 7357 589 # # Model: Tosot T24H-ILF/I/T24H-ILU/O +# Compatible Brands: Gree name: Off type: raw frequency: 38000 duty_cycle: 0.330000 -data: 9067 4428 600 1590 599 481 597 481 598 482 596 484 595 487 592 511 568 511 568 511 568 1621 569 511 568 1622 568 511 568 511 568 511 569 511 568 511 569 511 568 511 568 511 568 511 568 1622 568 511 568 511 568 511 568 511 569 511 568 511 568 1622 568 511 568 1622 568 511 568 511 568 1622 568 511 648 20161 569 511 568 511 568 511 568 511 568 511 569 511 568 511 568 511 568 511 568 511 568 511 568 511 568 511 568 1622 568 511 568 511 568 511 568 511 568 511 568 511 569 511 568 511 568 511 569 511 568 512 568 511 568 511 568 511 568 1622 568 1622 568 1622 568 511 648 40402 9171 4434 594 1621 569 510 569 511 568 511 568 510 569 511 568 511 568 511 568 511 568 1621 569 511 568 1621 569 511 568 511 568 511 568 511 568 511 568 511 568 511 568 511 568 511 568 1622 568 511 568 511 568 511 569 511 568 511 568 511 568 1622 568 1622 568 1622 568 512 568 512 567 1622 568 512 673 20162 568 511 568 511 568 510 569 510 569 511 568 511 568 511 568 511 569 511 568 511 569 511 568 511 568 511 568 511 568 511 568 511 569 511 568 511 569 511 568 511 568 511 568 511 568 511 568 511 568 511 568 511 568 511 569 511 568 1622 568 511 568 1622 568 512 567 -# +data: 9072 4445 602 1586 603 477 601 478 600 479 599 480 598 481 598 482 597 482 572 1617 597 1593 572 1617 597 483 572 507 572 507 572 507 572 507 572 507 597 482 597 482 572 507 597 482 572 1618 571 507 572 507 572 507 572 507 572 507 572 507 572 1618 571 507 572 1618 572 507 572 507 572 1618 572 507 652 20153 572 507 597 482 572 507 572 507 597 482 597 482 572 507 596 483 572 507 572 507 572 507 572 507 572 507 572 1618 596 483 572 507 596 483 595 484 572 507 597 482 595 484 597 482 597 482 572 507 572 507 597 482 572 507 597 482 572 507 597 483 571 1618 597 482 675 40391 9179 4421 599 1590 599 481 597 482 597 482 597 481 598 482 597 482 597 482 597 1592 597 1592 598 1592 597 482 597 482 597 482 597 482 597 482 597 482 597 482 597 482 597 482 572 507 597 1593 597 482 572 507 572 507 572 507 572 507 572 508 571 1618 597 1593 596 1593 572 507 572 507 572 1618 597 482 678 20152 597 483 596 482 597 482 597 482 597 482 597 482 572 507 597 482 597 482 597 482 572 507 597 482 597 482 597 482 597 482 597 482 572 507 597 482 597 482 572 507 597 482 572 507 572 507 572 507 572 507 572 507 572 507 572 507 572 507 572 1618 596 482 572 507 572 +# name: Cool_hi type: raw frequency: 38000 duty_cycle: 0.330000 -data: 9087 4428 598 1591 598 482 596 482 596 1594 595 1595 594 1596 593 486 593 486 593 486 593 486 593 486 593 486 593 486 593 486 593 486 593 486 593 486 593 486 593 486 593 486 593 486 593 1597 593 486 593 486 593 486 593 486 593 487 592 486 593 1597 592 487 592 1597 592 487 592 487 592 1597 593 487 671 20132 593 1596 593 486 593 486 593 486 593 1596 593 486 593 486 593 486 593 486 593 486 593 486 593 486 593 486 593 1597 592 487 592 486 593 486 593 486 593 487 592 486 593 486 593 487 592 487 592 487 592 487 592 487 592 487 592 486 593 487 592 1597 593 1597 592 487 672 40393 9168 4432 594 1596 593 486 593 486 593 1597 593 1596 593 1597 592 486 593 486 593 486 593 486 593 486 593 486 593 486 593 486 593 486 593 486 593 486 593 487 592 487 592 486 593 486 593 1597 592 487 592 486 593 487 592 487 592 487 592 487 592 1597 593 1597 593 1597 592 487 592 487 592 1597 592 487 698 20131 593 486 593 486 593 486 593 486 593 486 593 486 593 486 593 486 593 486 593 486 593 486 593 486 593 486 593 486 593 486 593 486 593 486 593 486 593 486 593 486 593 1597 593 486 593 1597 592 487 592 486 593 487 592 486 593 487 592 487 592 487 592 487 592 1597 592 -# -name: Heat_hi -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 9065 4429 598 482 597 482 597 1593 596 1594 595 1596 594 1597 593 487 592 487 592 487 593 1598 592 1598 592 1598 592 488 592 487 592 488 591 511 568 487 592 488 591 511 568 511 568 488 591 1599 591 511 568 511 568 511 568 511 568 511 569 511 568 1622 568 511 568 1622 568 511 568 511 568 1622 568 511 648 20137 593 1598 592 487 593 487 592 488 591 1599 591 488 591 487 592 488 591 511 568 488 591 511 568 489 590 511 568 1599 591 511 568 511 568 511 568 511 568 511 568 511 568 511 568 511 568 511 569 511 568 511 568 511 569 511 568 511 568 1622 568 1622 568 1622 568 511 648 40403 9170 4434 594 486 593 487 592 1597 593 1598 592 1598 592 1597 593 487 592 487 592 487 593 1598 592 1598 592 1598 592 488 591 488 591 488 591 511 568 488 591 488 591 511 568 511 569 511 568 1622 568 511 568 511 568 511 568 511 569 511 568 511 568 1622 568 1622 568 1622 568 511 569 511 568 1622 568 511 674 20138 592 488 591 488 591 511 568 488 591 511 568 511 568 511 568 511 568 511 568 511 568 489 590 511 568 511 568 511 568 511 568 511 568 511 568 511 568 511 568 511 568 1622 568 511 568 1622 568 511 568 511 568 511 569 511 568 511 568 1622 568 511 569 511 568 1622 568 -# -name: Heat_lo -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 9104 4416 604 474 604 475 603 1587 602 1588 601 1590 599 1590 599 480 600 481 598 1591 599 1591 599 1591 599 481 598 481 598 481 599 481 598 481 598 481 598 481 598 481 598 481 599 481 598 1592 598 481 598 481 599 481 598 481 598 481 598 481 598 1592 598 481 598 1592 598 481 598 481 598 1592 598 481 678 20131 599 1591 599 481 598 481 598 481 598 1591 599 481 599 481 598 481 598 481 599 481 598 481 598 481 598 481 598 1592 598 481 598 481 599 481 598 481 598 481 598 481 598 481 599 481 598 481 598 481 598 481 598 481 599 481 598 481 598 481 598 481 598 481 598 481 678 40398 9182 4421 599 479 600 480 599 1591 599 1591 599 1591 599 1591 599 480 599 481 598 1591 599 1591 599 1591 599 481 598 481 598 481 598 481 598 481 599 481 598 481 599 480 599 481 599 481 598 1592 598 481 598 481 598 481 599 481 598 481 598 481 598 1592 598 1592 598 1592 598 481 598 481 598 1592 598 481 704 20131 599 481 598 481 598 481 599 480 599 481 598 481 598 481 598 481 599 481 598 481 598 481 598 481 598 481 598 481 598 481 598 481 598 481 598 481 598 481 598 481 598 1592 598 481 598 1592 598 481 599 481 598 481 598 481 598 481 598 481 598 1592 598 481 598 482 597 -# +data: 9049 4446 575 1613 577 503 601 478 601 1590 599 480 598 482 572 507 572 507 572 508 571 507 572 507 572 507 572 508 571 508 571 507 573 507 572 508 571 508 572 507 572 508 571 507 572 1618 572 508 572 507 572 508 571 508 571 508 571 508 571 1618 572 508 571 1618 572 508 571 508 571 1619 571 508 651 20157 572 507 572 507 572 507 572 507 572 507 572 507 573 507 572 507 572 507 572 507 572 507 572 507 572 507 572 1618 572 507 572 507 572 507 572 507 573 507 572 508 571 507 572 508 571 507 572 507 573 507 572 507 572 507 572 508 571 1619 571 507 572 1618 572 508 651 40424 9180 4422 599 1591 598 482 597 483 572 1618 572 507 598 482 597 482 572 507 572 507 598 482 572 507 598 482 572 507 598 482 597 482 572 507 572 507 572 507 573 507 572 507 573 507 572 1618 572 507 598 482 572 507 572 507 597 483 596 483 597 1593 572 1618 572 1618 572 508 572 507 572 1618 572 508 678 20157 572 507 597 482 597 482 597 481 598 482 572 507 572 507 572 507 572 507 597 482 572 507 597 482 572 507 573 507 572 507 572 507 572 507 572 507 572 507 572 507 572 508 572 507 572 507 572 507 572 507 572 507 572 508 571 508 571 1619 571 1618 572 508 571 507 572 +# name: Cool_lo type: raw frequency: 38000 duty_cycle: 0.330000 -data: 9045 4446 576 1613 576 503 575 503 601 1589 575 1615 574 1617 572 506 573 507 572 1618 571 1618 572 1618 571 507 572 507 572 507 572 507 572 508 571 507 572 507 572 507 572 508 571 508 571 1618 571 508 571 508 571 508 571 508 571 508 571 508 571 1619 571 508 571 1618 571 508 571 508 571 1619 571 508 651 20153 571 1618 571 508 571 508 571 507 572 1618 571 508 571 508 571 508 571 508 571 508 571 508 571 508 571 508 571 1619 571 508 571 508 571 508 571 508 571 508 571 508 571 508 571 508 571 508 571 508 571 508 571 508 571 508 571 508 571 1619 571 508 571 1619 570 1619 650 40414 9150 4450 573 1617 572 507 572 508 571 1618 572 1618 571 1618 571 508 571 508 571 1619 570 1618 572 1618 571 508 571 508 571 508 571 508 571 508 571 508 571 508 571 508 571 509 570 508 571 1619 571 509 570 509 570 509 570 508 571 509 570 509 570 1619 570 1620 569 1620 570 509 570 509 570 1620 570 509 676 20154 570 508 571 508 571 508 571 508 571 508 571 508 571 508 571 508 571 508 571 508 571 508 571 509 570 508 571 508 571 508 571 508 571 509 570 509 570 509 570 509 570 1619 570 509 570 1620 569 509 570 509 570 509 570 509 570 509 570 1620 569 1621 569 1620 569 1620 570 -# +data: 9075 4419 603 1586 603 476 602 478 600 1589 600 480 599 481 598 482 597 482 597 1593 597 1593 597 1593 597 482 597 482 572 507 572 507 596 483 573 507 572 507 573 507 598 482 572 507 573 1618 572 508 572 507 572 508 572 507 596 483 572 508 571 1618 596 484 572 1618 572 507 572 507 572 1618 572 508 651 20158 597 482 598 482 597 482 597 482 597 482 572 507 572 507 572 507 597 482 597 483 597 482 572 507 573 507 597 1594 572 507 597 482 572 507 597 482 598 482 572 508 571 508 571 508 572 507 572 507 597 482 597 483 572 508 571 508 571 508 596 483 597 1593 596 1593 652 40424 9181 4422 599 1591 598 481 598 482 572 1618 598 482 597 481 598 482 598 481 598 1593 597 1593 597 1592 598 482 598 482 597 482 597 482 598 482 597 482 597 482 598 482 597 482 597 482 598 1593 597 482 597 482 597 482 597 483 597 482 572 508 597 1593 596 1594 597 1593 597 483 571 508 571 1618 572 508 678 20157 572 507 572 507 573 507 572 507 572 507 572 507 572 507 572 507 572 507 572 507 572 507 572 507 572 507 572 507 572 507 573 507 572 507 573 507 572 507 573 507 572 507 572 507 572 507 572 508 571 507 572 508 571 508 571 508 571 508 572 1618 572 508 571 1618 572 +# +name: Heat_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9048 4447 576 503 576 503 601 1589 600 1590 599 481 573 507 572 507 572 507 572 1618 572 1618 572 1618 572 507 572 507 573 507 572 508 572 507 572 507 572 507 572 507 572 507 572 507 572 1618 572 507 573 507 572 508 571 507 572 507 572 508 571 1618 572 507 572 1618 572 508 571 507 572 1618 572 508 651 20157 572 507 572 507 572 507 572 507 572 508 572 507 572 507 572 507 572 508 571 508 572 508 571 508 571 508 571 1619 571 507 572 508 571 508 571 508 571 508 571 508 571 508 571 508 571 508 571 508 571 508 571 508 571 509 570 509 570 1620 570 1620 570 1620 570 1620 649 40424 9153 4450 573 506 573 507 572 1619 571 1618 572 508 571 508 571 508 572 508 571 1619 571 1619 571 1619 571 508 571 508 571 508 571 508 571 508 571 508 571 508 571 508 571 509 570 509 571 1619 571 509 571 509 570 509 571 509 570 509 570 509 570 1620 570 1621 569 1644 546 509 570 509 570 1620 570 510 676 20158 571 508 572 507 572 508 571 508 571 508 571 508 571 508 571 508 572 508 571 508 571 508 571 508 572 508 571 508 571 508 571 508 571 509 570 509 570 509 571 509 570 509 570 534 545 511 568 533 546 534 546 533 546 534 545 534 545 1645 545 534 545 1645 545 1644 546 +# +name: Heat_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9072 4445 602 476 602 476 602 1588 600 1589 600 480 599 481 597 481 598 481 598 481 598 1592 598 1592 598 1592 598 481 598 481 598 481 598 481 598 481 598 481 598 481 598 482 597 482 597 1592 598 481 598 481 598 481 598 481 598 481 598 481 598 1592 598 481 598 1592 598 481 598 481 598 1592 598 481 678 20126 598 481 598 481 598 481 598 481 598 481 598 481 598 481 598 481 598 481 598 481 598 481 598 481 598 481 598 1592 597 481 598 481 598 481 599 481 598 481 598 481 598 481 598 481 598 481 598 481 598 481 598 481 598 482 597 481 598 481 598 1592 597 1592 597 481 677 40388 9180 4420 600 479 599 480 599 1591 599 1591 598 481 598 481 598 481 598 481 598 481 598 1591 598 1591 598 1591 599 480 599 481 598 481 598 481 598 481 598 481 598 481 598 481 598 481 598 1591 598 481 598 481 598 481 598 481 598 481 598 481 598 1591 598 1592 598 1591 598 481 598 481 598 1592 598 481 704 20126 598 480 599 480 599 480 599 481 598 480 599 480 599 480 599 481 598 480 599 480 599 480 599 481 598 481 598 480 599 481 598 480 599 481 598 480 599 480 599 481 598 481 598 481 598 480 599 481 598 481 598 481 598 481 598 481 598 481 598 481 598 1591 599 481 598 +# name: Dh type: raw frequency: 38000 duty_cycle: 0.330000 -data: 9102 4396 624 476 602 1587 602 478 600 1590 600 1591 599 481 598 481 598 481 598 1592 598 1592 598 1592 598 481 598 481 599 481 598 481 598 481 598 481 598 481 598 481 598 481 599 481 598 1592 598 481 598 481 598 481 599 481 599 481 598 481 599 1592 598 481 598 1592 598 482 597 481 598 1592 598 481 678 20131 599 1592 598 481 598 481 598 481 598 1592 598 481 598 481 598 481 599 481 598 481 598 481 598 481 599 481 598 1592 598 481 599 481 598 481 598 481 598 481 598 481 598 481 598 482 597 481 598 481 598 481 598 481 598 481 598 481 598 482 597 1592 598 1592 598 1592 677 40398 9182 4421 599 480 599 1591 599 481 599 1592 598 1592 598 481 598 481 598 481 598 1592 598 1592 598 1592 598 481 598 481 598 481 598 481 598 481 599 481 598 481 598 481 598 481 598 481 598 1592 598 481 599 481 598 482 597 481 598 481 598 481 598 1592 598 1592 598 1592 598 481 598 482 597 1592 598 482 703 20131 598 481 598 481 598 481 598 481 598 481 599 481 598 481 598 481 599 481 598 481 598 481 598 481 598 481 598 481 599 481 598 481 598 481 598 481 599 481 598 481 598 1592 598 481 598 481 598 481 598 481 598 481 598 481 598 481 598 481 598 481 598 1592 598 1592 598 +data: 9076 4445 577 503 602 1587 603 477 601 1589 600 1590 599 481 573 507 572 507 572 1618 572 1618 572 1618 572 507 572 507 573 507 572 507 572 507 572 507 572 507 572 507 572 507 572 507 572 1618 572 507 573 507 572 507 572 507 573 507 572 507 572 1618 572 507 573 1618 572 507 572 507 572 1618 572 507 652 20157 597 482 573 507 597 483 572 507 572 507 573 507 572 507 572 507 596 483 597 482 572 507 572 507 572 507 597 1593 572 507 572 507 573 507 572 507 572 507 572 507 572 507 572 507 572 507 572 507 596 484 572 507 572 507 572 507 597 1593 596 483 572 1618 572 1618 652 40424 9181 4422 599 480 598 1591 599 481 598 1592 598 1592 598 481 598 481 598 481 598 1592 598 1592 598 1592 598 481 598 481 598 481 598 482 597 481 598 481 599 481 598 482 598 482 597 482 598 1592 598 482 597 482 597 482 597 482 597 482 598 482 597 1593 597 1593 597 1592 598 482 598 482 596 1593 597 482 703 20132 598 481 598 481 599 481 598 481 598 481 598 481 598 481 598 481 598 482 598 481 598 482 597 482 597 482 597 482 598 482 598 481 598 481 598 482 597 482 598 482 598 1592 598 482 597 482 597 482 598 482 597 482 597 482 572 507 572 507 598 482 597 1593 597 1593 572 # # Model: LG Generic name: Off @@ -729,3 +732,40 @@ type: raw frequency: 38000 duty_cycle: 0.330000 data: 30675 50953 3432 1606 490 1190 489 350 489 350 489 350 489 350 489 351 488 350 489 351 489 351 488 351 489 351 488 351 489 1191 488 351 488 351 489 351 488 351 488 351 488 351 489 351 488 1191 489 1191 489 351 488 351 488 351 488 351 488 351 489 351 489 351 488 351 489 1191 488 351 488 1191 489 1191 488 1191 488 1191 488 1191 488 1191 488 351 489 1191 488 1191 489 351 488 351 489 351 488 351 489 351 488 351 488 351 488 351 488 1191 488 1191 488 1191 488 1191 489 1191 488 1191 488 1191 489 1191 488 351 488 351 489 351 488 1191 488 351 489 351 488 351 488 351 489 1191 488 351 489 351 488 1191 488 351 488 351 488 351 489 351 489 351 488 351 489 1191 488 351 488 351 489 351 488 351 488 1191 488 1191 488 351 488 351 489 351 488 351 489 351 488 351 489 351 489 1191 488 1192 488 1191 488 351 489 1191 489 351 488 351 488 351 489 351 489 351 488 351 488 351 489 351 488 351 488 351 488 1192 488 351 489 351 488 351 488 351 489 351 489 351 488 351 488 351 488 352 487 352 488 351 488 352 488 351 488 351 488 351 488 1192 488 1192 487 352 488 352 487 352 487 352 488 352 487 352 488 351 488 352 488 352 488 352 487 352 488 351 488 351 488 352 488 352 487 352 488 352 487 352 488 352 488 352 488 352 487 1192 487 352 487 352 488 352 488 352 488 352 487 352 487 352 488 352 487 352 487 352 487 352 488 352 488 352 487 352 487 352 488 352 487 352 488 352 487 352 487 352 487 352 487 352 487 352 488 352 487 352 487 352 487 352 488 352 487 352 488 352 487 352 488 352 487 352 487 352 488 352 487 352 488 352 487 352 488 352 488 352 487 352 487 352 487 352 487 352 487 352 488 352 487 352 487 352 487 1193 486 352 487 352 488 352 487 352 487 352 488 352 487 352 488 352 488 352 487 352 488 352 487 353 486 353 487 352 487 352 488 352 488 352 487 352 487 352 487 352 487 353 487 352 488 352 488 352 487 1193 486 1193 486 1193 486 1193 487 353 487 352 487 353 486 +# +# Model: LG PC07SQR +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3169 9836 535 1553 511 551 491 543 491 544 490 1586 490 532 510 530 511 543 491 1579 489 1577 490 543 490 543 491 543 491 544 490 544 489 544 490 543 491 544 490 550 492 544 490 543 491 1586 489 542 492 1583 492 552 490 532 510 537 512 1585 491 +# +name: Dh +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3385 9888 512 1544 512 544 489 544 489 544 490 1586 489 545 489 544 489 545 489 531 511 541 508 533 508 542 507 544 489 546 487 544 490 1587 489 1573 510 543 490 543 491 1578 490 545 489 1574 509 545 488 1587 489 1573 510 1586 490 1579 489 1568 507 +# +name: Cool_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3288 9816 598 1504 574 472 569 463 571 463 571 1515 568 472 569 461 573 471 571 459 590 462 571 463 571 447 594 462 572 462 571 464 570 459 590 463 570 464 570 1496 571 1497 570 463 571 1514 569 464 570 1504 572 1514 569 471 571 473 568 462 572 +# +name: Cool_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3205 9865 616 1473 536 518 516 517 517 517 517 1575 517 527 568 464 515 517 571 463 570 462 572 462 572 469 573 461 573 463 570 462 572 469 573 1504 572 451 590 451 591 472 569 463 571 1494 573 461 573 1497 570 1505 570 1503 572 471 571 1491 592 +# +name: Heat_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3363 9846 596 1495 514 518 515 517 517 515 518 1552 516 502 539 517 517 516 518 516 518 517 517 517 571 462 517 518 516 1554 568 461 589 462 572 1505 571 1507 568 1514 514 1568 570 461 573 1504 572 472 570 1507 592 1490 593 460 574 462 572 447 594 +# +name: Heat_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3204 9889 537 1587 491 529 512 544 489 545 489 1573 510 530 511 543 491 552 490 538 511 543 491 543 491 532 509 543 491 1587 489 537 512 543 491 1577 490 543 491 543 491 544 489 543 491 1586 489 544 490 1587 489 539 510 543 491 543 491 1586 490 From ef907152b4d6513fcfbe4f89c58842f8e974fd32 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Thu, 13 Jul 2023 20:42:01 +0200 Subject: [PATCH 121/364] Remove FAPP and fix main menu --- applications/main/xtreme_app/application.fam | 10 +++---- applications/services/loader/loader.c | 11 ++------ applications/services/loader/loader_menu.c | 28 +++++++++++++------- scripts/fbt/appmanifest.py | 19 ++++--------- scripts/fbt_tools/fbt_extapps.py | 2 +- site_scons/extapps.scons | 1 - 6 files changed, 31 insertions(+), 40 deletions(-) diff --git a/applications/main/xtreme_app/application.fam b/applications/main/xtreme_app/application.fam index 854e72199..c08a3d1d7 100644 --- a/applications/main/xtreme_app/application.fam +++ b/applications/main/xtreme_app/application.fam @@ -1,14 +1,12 @@ App( appid="xtreme_app", name="Xtreme", - apptype=FlipperAppType.FAPP, + apptype=FlipperAppType.MENUEXTERNAL, entry_point="xtreme_app", - cdefines=["APP_XTREME"], - requires=[ - "gui", - "dolphin", - ], stack_size=2 * 1024, icon="A_Xtreme_14", order=90, + fap_libs=["assets"], + # fap_icon="", + # fap_category="", ) diff --git a/applications/services/loader/loader.c b/applications/services/loader/loader.c index 74da6728c..1ff52f037 100644 --- a/applications/services/loader/loader.c +++ b/applications/services/loader/loader.c @@ -483,15 +483,8 @@ static LoaderStatus loader_do_start_by_name( { const FlipperInternalApplication* app = loader_find_application_by_name(name); if(app) { - if(app->app == NULL) { - // FAPP support - status = loader_start_external_app( - loader, furi_record_open(RECORD_STORAGE), app->appid, args, error_message); - furi_record_close(RECORD_STORAGE); - } else { - loader_start_internal_app(loader, app, args); - status = loader_make_success_status(error_message); - } + loader_start_internal_app(loader, app, args); + status = loader_make_success_status(error_message); break; } } diff --git a/applications/services/loader/loader_menu.c b/applications/services/loader/loader_menu.c index 279efe904..136504cc5 100644 --- a/applications/services/loader/loader_menu.c +++ b/applications/services/loader/loader_menu.c @@ -62,12 +62,6 @@ static void loader_menu_apps_callback(void* context, uint32_t index) { loader_menu_start(name_or_path); } -static void loader_menu_external_apps_callback(void* context, uint32_t index) { - UNUSED(context); - const char* path = FLIPPER_EXTERNAL_APPS[index].path; - loader_menu_start(path); -} - static void loader_menu_applications_callback(void* context, uint32_t index) { UNUSED(index); UNUSED(context); @@ -98,13 +92,29 @@ static uint32_t loader_menu_exit(void* context) { } static void loader_menu_build_menu(LoaderMenuApp* app, LoaderMenu* menu) { - size_t i; - for(i = 0; i < FLIPPER_APPS_COUNT; i++) { + menu_add_item( + app->primary_menu, + LOADER_APPLICATIONS_NAME, + &A_Plugins_14, + 0, + loader_menu_applications_callback, + (void*)menu); + for(size_t i = 0; i < FLIPPER_EXTERNAL_APPS_COUNT; i++) { + menu_add_item( + app->primary_menu, + FLIPPER_EXTERNAL_APPS[i].name, + FLIPPER_EXTERNAL_APPS[i].icon, + (uint32_t)FLIPPER_EXTERNAL_APPS[i].path, + loader_menu_apps_callback, + (void*)menu); + } + + for(size_t i = 0; i < FLIPPER_APPS_COUNT; i++) { menu_add_item( app->primary_menu, FLIPPER_APPS[i].name, FLIPPER_APPS[i].icon, - i, + (uint32_t)FLIPPER_APPS[i].name, loader_menu_apps_callback, (void*)menu); } diff --git a/scripts/fbt/appmanifest.py b/scripts/fbt/appmanifest.py index a4b83f3c9..98f794453 100644 --- a/scripts/fbt/appmanifest.py +++ b/scripts/fbt/appmanifest.py @@ -13,7 +13,6 @@ class FlipperAppType(Enum): SERVICE = "Service" SYSTEM = "System" APP = "App" - FAPP = "Fapp" DEBUG = "Debug" ARCHIVE = "Archive" SETTINGS = "Settings" @@ -382,14 +381,6 @@ class ApplicationsCGenerator: def get_app_descr(self, app: FlipperApplication): if app.apptype == FlipperAppType.STARTUP: return app.entry_point - if app.apptype == FlipperAppType.FAPP: - return f""" - {{.app = NULL, - .name = "{app.name}", - .appid = "/ext/apps/assets/{app.appid}.fap", - .stack_size = 0, - .icon = {f"&{app.icon}" if app.icon else "NULL"}, - .flags = {'|'.join(f"FlipperInternalApplicationFlag{flag}" for flag in app.flags)}}}""" return f""" {{.app = {app.entry_point}, .name = "{app.name}", @@ -421,11 +412,11 @@ class ApplicationsCGenerator: ) entry_type, entry_block = self.APP_TYPE_MAP[apptype] contents.append(f"const {entry_type} {entry_block}[] = {{") - apps = self.buildset.get_apps_of_type(apptype) - if apptype is FlipperAppType.APP: - apps += self.buildset.get_apps_of_type(FlipperAppType.FAPP) - apps.sort(key=lambda app: app.order) - contents.append(",\n".join(map(self.get_app_descr, apps))) + contents.append( + ",\n".join( + map(self.get_app_descr, self.buildset.get_apps_of_type(apptype)) + ) + ) contents.append("};") contents.append( f"const size_t {entry_block}_COUNT = COUNT_OF({entry_block});" diff --git a/scripts/fbt_tools/fbt_extapps.py b/scripts/fbt_tools/fbt_extapps.py index 951da0560..a0beb1402 100644 --- a/scripts/fbt_tools/fbt_extapps.py +++ b/scripts/fbt_tools/fbt_extapps.py @@ -179,7 +179,7 @@ class AppBuilder: deployable = False app_artifacts.dist_entries.append((deployable, fal_path)) else: - fap_path = f"apps/{'assets' if self.app.apptype == FlipperAppType.FAPP else self.app.fap_category}/{app_artifacts.compact.name}" + fap_path = f"apps/{self.app.fap_category}/{app_artifacts.compact.name}" app_artifacts.dist_entries.append( (self.app.is_default_deployable, fap_path) ) diff --git a/site_scons/extapps.scons b/site_scons/extapps.scons index 195d75c81..0893c4556 100644 --- a/site_scons/extapps.scons +++ b/site_scons/extapps.scons @@ -65,7 +65,6 @@ class FlipperExtAppBuildArtifacts: apps_to_build_as_faps = [ - FlipperAppType.FAPP, FlipperAppType.PLUGIN, FlipperAppType.EXTERNAL, FlipperAppType.MENUEXTERNAL, From 158b727cb8cc19319e604c49c241e42b3001d4db Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Thu, 13 Jul 2023 20:44:06 +0200 Subject: [PATCH 122/364] Asset packs in external apps again --- applications/external/camera_suite/application.fam | 1 - applications/main/bad_kb/application.fam | 1 - applications/main/gpio/application.fam | 1 - applications/main/ibutton/application.fam | 1 - applications/main/infrared/application.fam | 1 - applications/main/lfrfid/application.fam | 1 - applications/main/nfc/application.fam | 1 - applications/main/subghz/application.fam | 2 +- applications/main/u2f/application.fam | 1 - applications/main/xtreme_app/application.fam | 1 - 10 files changed, 1 insertion(+), 10 deletions(-) diff --git a/applications/external/camera_suite/application.fam b/applications/external/camera_suite/application.fam index 40131ae9a..2e26a3a27 100644 --- a/applications/external/camera_suite/application.fam +++ b/applications/external/camera_suite/application.fam @@ -7,7 +7,6 @@ App( fap_category="GPIO", fap_description="A camera suite application for the Flipper Zero ESP32-CAM module.", fap_icon="icons/camera_suite.png", - fap_libs=["assets"], fap_weburl="https://github.com/CodyTolene/Flipper-Zero-Cam", name="[ESP32] Camera Suite", order=1, diff --git a/applications/main/bad_kb/application.fam b/applications/main/bad_kb/application.fam index 00897a505..873767b66 100644 --- a/applications/main/bad_kb/application.fam +++ b/applications/main/bad_kb/application.fam @@ -6,7 +6,6 @@ App( stack_size=2 * 1024, icon="A_BadKb_14", order=70, - fap_libs=["assets"], fap_icon="icon.png", fap_category="USB", ) diff --git a/applications/main/gpio/application.fam b/applications/main/gpio/application.fam index 763919921..6eef678cf 100644 --- a/applications/main/gpio/application.fam +++ b/applications/main/gpio/application.fam @@ -6,7 +6,6 @@ App( stack_size=1 * 1024, icon="A_GPIO_14", order=50, - fap_libs=["assets"], fap_icon="icon.png", fap_category="GPIO", ) diff --git a/applications/main/ibutton/application.fam b/applications/main/ibutton/application.fam index a8faa629c..5df50fcf2 100644 --- a/applications/main/ibutton/application.fam +++ b/applications/main/ibutton/application.fam @@ -7,7 +7,6 @@ App( icon="A_iButton_14", stack_size=2 * 1024, order=60, - fap_libs=["assets"], fap_icon="icon.png", fap_category="iButton", ) diff --git a/applications/main/infrared/application.fam b/applications/main/infrared/application.fam index b78b088a7..d669e4298 100644 --- a/applications/main/infrared/application.fam +++ b/applications/main/infrared/application.fam @@ -7,7 +7,6 @@ App( icon="A_Infrared_14", stack_size=3 * 1024, order=40, - fap_libs=["assets"], fap_icon="icon.png", fap_category="Infrared", ) diff --git a/applications/main/lfrfid/application.fam b/applications/main/lfrfid/application.fam index cad07fbf7..88af98631 100644 --- a/applications/main/lfrfid/application.fam +++ b/applications/main/lfrfid/application.fam @@ -7,7 +7,6 @@ App( icon="A_125khz_14", stack_size=2 * 1024, order=20, - fap_libs=["assets"], fap_icon="icon.png", fap_category="RFID", ) diff --git a/applications/main/nfc/application.fam b/applications/main/nfc/application.fam index bc34d4e65..0e7f74379 100644 --- a/applications/main/nfc/application.fam +++ b/applications/main/nfc/application.fam @@ -7,7 +7,6 @@ App( icon="A_NFC_14", stack_size=5 * 1024, order=30, - fap_libs=["assets"], fap_icon="icon.png", fap_category="NFC", ) diff --git a/applications/main/subghz/application.fam b/applications/main/subghz/application.fam index 76553d695..6492690a3 100644 --- a/applications/main/subghz/application.fam +++ b/applications/main/subghz/application.fam @@ -19,7 +19,7 @@ App( icon="A_Sub1ghz_14", stack_size=3 * 1024, order=10, - fap_libs=["assets", "hwdrivers"], + fap_libs=["hwdrivers"], fap_icon="icon.png", fap_category="Sub-GHz", ) diff --git a/applications/main/u2f/application.fam b/applications/main/u2f/application.fam index 8167e6277..042634c6a 100644 --- a/applications/main/u2f/application.fam +++ b/applications/main/u2f/application.fam @@ -6,7 +6,6 @@ App( stack_size=2 * 1024, icon="A_U2F_14", order=80, - fap_libs=["assets"], fap_category="USB", fap_icon="icon.png", ) diff --git a/applications/main/xtreme_app/application.fam b/applications/main/xtreme_app/application.fam index c08a3d1d7..bd2ec5149 100644 --- a/applications/main/xtreme_app/application.fam +++ b/applications/main/xtreme_app/application.fam @@ -6,7 +6,6 @@ App( stack_size=2 * 1024, icon="A_Xtreme_14", order=90, - fap_libs=["assets"], # fap_icon="", # fap_category="", ) From 22d72dc990a161aff6af3dd89a9b7bf319780b94 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Thu, 13 Jul 2023 22:08:45 +0200 Subject: [PATCH 123/364] Fix main apps paths and manifests --- applications/main/bad_kb/application.fam | 1 - applications/main/gpio/application.fam | 1 - applications/main/ibutton/application.fam | 1 - applications/main/infrared/application.fam | 1 - applications/main/lfrfid/application.fam | 1 - applications/main/nfc/application.fam | 1 - applications/main/subghz/application.fam | 1 - applications/main/u2f/application.fam | 1 - applications/main/xtreme_app/application.fam | 3 +-- applications/main/xtreme_app/icon.png | Bin 0 -> 620 bytes scripts/fbt_tools/fbt_extapps.py | 2 +- 11 files changed, 2 insertions(+), 11 deletions(-) create mode 100644 applications/main/xtreme_app/icon.png diff --git a/applications/main/bad_kb/application.fam b/applications/main/bad_kb/application.fam index 873767b66..0665c5661 100644 --- a/applications/main/bad_kb/application.fam +++ b/applications/main/bad_kb/application.fam @@ -7,5 +7,4 @@ App( icon="A_BadKb_14", order=70, fap_icon="icon.png", - fap_category="USB", ) diff --git a/applications/main/gpio/application.fam b/applications/main/gpio/application.fam index 6eef678cf..c87fb5dbd 100644 --- a/applications/main/gpio/application.fam +++ b/applications/main/gpio/application.fam @@ -7,5 +7,4 @@ App( icon="A_GPIO_14", order=50, fap_icon="icon.png", - fap_category="GPIO", ) diff --git a/applications/main/ibutton/application.fam b/applications/main/ibutton/application.fam index 5df50fcf2..9ac40b46a 100644 --- a/applications/main/ibutton/application.fam +++ b/applications/main/ibutton/application.fam @@ -8,7 +8,6 @@ App( stack_size=2 * 1024, order=60, fap_icon="icon.png", - fap_category="iButton", ) App( diff --git a/applications/main/infrared/application.fam b/applications/main/infrared/application.fam index d669e4298..6c770f9e9 100644 --- a/applications/main/infrared/application.fam +++ b/applications/main/infrared/application.fam @@ -8,7 +8,6 @@ App( stack_size=3 * 1024, order=40, fap_icon="icon.png", - fap_category="Infrared", ) App( diff --git a/applications/main/lfrfid/application.fam b/applications/main/lfrfid/application.fam index 88af98631..285badba7 100644 --- a/applications/main/lfrfid/application.fam +++ b/applications/main/lfrfid/application.fam @@ -8,7 +8,6 @@ App( stack_size=2 * 1024, order=20, fap_icon="icon.png", - fap_category="RFID", ) App( diff --git a/applications/main/nfc/application.fam b/applications/main/nfc/application.fam index 0e7f74379..a22d64af5 100644 --- a/applications/main/nfc/application.fam +++ b/applications/main/nfc/application.fam @@ -8,7 +8,6 @@ App( stack_size=5 * 1024, order=30, fap_icon="icon.png", - fap_category="NFC", ) App( diff --git a/applications/main/subghz/application.fam b/applications/main/subghz/application.fam index 6492690a3..7da1b9081 100644 --- a/applications/main/subghz/application.fam +++ b/applications/main/subghz/application.fam @@ -21,7 +21,6 @@ App( order=10, fap_libs=["hwdrivers"], fap_icon="icon.png", - fap_category="Sub-GHz", ) App( diff --git a/applications/main/u2f/application.fam b/applications/main/u2f/application.fam index 042634c6a..3bab607cd 100644 --- a/applications/main/u2f/application.fam +++ b/applications/main/u2f/application.fam @@ -6,6 +6,5 @@ App( stack_size=2 * 1024, icon="A_U2F_14", order=80, - fap_category="USB", fap_icon="icon.png", ) diff --git a/applications/main/xtreme_app/application.fam b/applications/main/xtreme_app/application.fam index bd2ec5149..0fd447773 100644 --- a/applications/main/xtreme_app/application.fam +++ b/applications/main/xtreme_app/application.fam @@ -6,6 +6,5 @@ App( stack_size=2 * 1024, icon="A_Xtreme_14", order=90, - # fap_icon="", - # fap_category="", + fap_icon="icon.png", ) diff --git a/applications/main/xtreme_app/icon.png b/applications/main/xtreme_app/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..606a6d13c38162d11a36444f6788d853825a31ec GIT binary patch literal 620 zcmV-y0+aoTP)EX>4Tx04R}tkv&MmKpe$iQ$>-Ag9Sw$f>bBFsEA{&LJ=y2TA@`3lS{v#Nkfw2 z;wZQl9Q;_UI=DFN>fkB}f*&BRE>4OrQsV!TLW>v=j{EWM-sA2aAk@oDvpQmcrrTyJ z5f?MrRk7<8K@4L6QH;vWGUg;H3E%N`j{slqqCCt0+@GUY%~=cxh{Q2wm^SeS@yw=e zaNZ{lv!bjLpA%1*bV1@rt}7nDaW1$l@XWB8PR$dCiN!)2D{ah*rbawX98oo$@`a4Y zD(5ZETDi)a_v9}O<@A+huG1Vr0*hFJ1Q80VD4`6S`YJ&u#X^eq<39c&*DsMvAy)~E z91EyGgY5dj|KNAGR(@*IOA5t-&KJk|7z4U?fkw@7zKjJ_!g^xpzKYhG{7eVjf3Y3eF@0~{Oz z;|0oI_jz}BdvE`qY4-O6eSad^gZEa<4bO1wgWnpw> zWFU8GbZ8()Nlj2!fese{002ZuL_t&-)18h%3IH$&!=nHHv%5^_bPSmnX(??iA_Bn7 z0N9f1Im2tgY{}n(w@LMnMh#c}$5^>k`-@BcHl3l$J7!#N4<8UgVk+MN0000 Date: Thu, 13 Jul 2023 23:22:55 +0300 Subject: [PATCH 124/364] fix --- applications/services/desktop/scenes/desktop_scene_main.c | 3 ++- .../scenes/desktop_settings_scene_favorite.c | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/applications/services/desktop/scenes/desktop_scene_main.c b/applications/services/desktop/scenes/desktop_scene_main.c index d92948d0c..1de8a3cd8 100644 --- a/applications/services/desktop/scenes/desktop_scene_main.c +++ b/applications/services/desktop/scenes/desktop_scene_main.c @@ -72,7 +72,8 @@ static void desktop_scene_main_open_app_or_profile(Desktop* desktop, const char* static void desktop_scene_main_start_favorite(Desktop* desktop, FavoriteApp* application) { if(strlen(application->name_or_path) > 2) { loader_start_with_gui_error(desktop->loader, application->name_or_path, NULL); - } else if(strlen(application->name_or_path) < 2) { + } else if( + (strlen(application->name_or_path) < 2) && (strcmp(application->name_or_path, "d") != 0)) { loader_start(desktop->loader, LOADER_APPLICATIONS_NAME, NULL, NULL); } } diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c index 33dd305ae..90b5d6cfe 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c @@ -13,7 +13,7 @@ #define EXTERNAL_APPLICATION_NAME ("[External Application]") #define EXTERNAL_APPLICATION_INDEX (APPS_COUNT + 2) -#define NONE_APPLICATION_INDEX (FLIPPER_APPS_COUNT + 3) +#define NONE_APPLICATION_INDEX (APPS_COUNT + 3) #define PRESELECTED_SPECIAL 0xffffffff @@ -181,7 +181,9 @@ bool desktop_settings_scene_favorite_on_event(void* context, SceneManagerEvent e consumed = true; } } else if(event.event == NONE_APPLICATION_INDEX) { - strncpy(curr_favorite_app->name_or_path, "n", MAX_APP_LENGTH); + // Clear favorite app path and set it to "d" (disabled) + memset(curr_favorite_app->name_or_path, 0, strlen(curr_favorite_app->name_or_path)); + strncpy(curr_favorite_app->name_or_path, "d", MAX_APP_LENGTH); consumed = true; } else { const char* name = favorite_fap_get_app_name(event.event); From b670631aa3b17e9202c7fa1bf310bf05e4b7107e Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Thu, 13 Jul 2023 22:23:55 +0200 Subject: [PATCH 125/364] More menuexternal fixes --- applications/services/loader/loader_menu.c | 4 +-- .../desktop_settings_scene_keybinds_action.c | 29 ++++++++++++------- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/applications/services/loader/loader_menu.c b/applications/services/loader/loader_menu.c index 136504cc5..d6b0bde09 100644 --- a/applications/services/loader/loader_menu.c +++ b/applications/services/loader/loader_menu.c @@ -104,7 +104,7 @@ static void loader_menu_build_menu(LoaderMenuApp* app, LoaderMenu* menu) { app->primary_menu, FLIPPER_EXTERNAL_APPS[i].name, FLIPPER_EXTERNAL_APPS[i].icon, - (uint32_t)FLIPPER_EXTERNAL_APPS[i].path, + (uint32_t)FLIPPER_EXTERNAL_APPS[i].name, loader_menu_apps_callback, (void*)menu); } @@ -129,7 +129,7 @@ static void loader_menu_build_menu(LoaderMenuApp* app, LoaderMenu* menu) { ext_main_app->name, ext_main_app->icon, (uint32_t)ext_main_app->path, - loader_menu_callback, + loader_menu_apps_callback, (void*)menu); } furi_record_close(RECORD_LOADER); diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_keybinds_action.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_keybinds_action.c index 4d2f01fca..dc2d7eb6c 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_keybinds_action.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_keybinds_action.c @@ -5,15 +5,9 @@ static void desktop_settings_scene_keybinds_action_submenu_callback(void* context, uint32_t index) { DesktopSettingsApp* app = context; - DesktopSettingsAppKeybindActionType action_type = scene_manager_get_scene_state( - app->scene_manager, DesktopSettingsAppSceneKeybindsActionType); char* keybind = desktop_settings_app_get_keybind(app); - if(action_type == DesktopSettingsAppKeybindActionTypeMainApp) { - strncpy(keybind, FLIPPER_APPS[index].name, MAX_KEYBIND_LENGTH); - } else if(action_type == DesktopSettingsAppKeybindActionTypeMoreActions) { - strncpy(keybind, EXTRA_KEYBINDS[index], MAX_KEYBIND_LENGTH); - } + strncpy(keybind, (const char*)index, MAX_KEYBIND_LENGTH); DESKTOP_KEYBINDS_SAVE(&app->desktop->keybinds, sizeof(app->desktop->keybinds)); scene_manager_search_and_switch_to_previous_scene( @@ -31,17 +25,30 @@ void desktop_settings_scene_keybinds_action_on_enter(void* context) { app->scene_manager, DesktopSettingsAppSceneKeybindsActionType); if(action_type == DesktopSettingsAppKeybindActionTypeMainApp) { + for(size_t i = 0; i < FLIPPER_EXTERNAL_APPS_COUNT; i++) { + submenu_add_item( + submenu, + FLIPPER_EXTERNAL_APPS[i].name, + (uint32_t)FLIPPER_EXTERNAL_APPS[i].name, + desktop_settings_scene_keybinds_action_submenu_callback, + app); + + // Select keybind item in submenu + if(!strncmp(FLIPPER_EXTERNAL_APPS[i].name, keybind, MAX_KEYBIND_LENGTH)) { + pre_select_item = FLIPPER_EXTERNAL_APPS[i].name; + } + } for(size_t i = 0; i < FLIPPER_APPS_COUNT; i++) { submenu_add_item( submenu, FLIPPER_APPS[i].name, - i, + (uint32_t)FLIPPER_APPS[i].name, desktop_settings_scene_keybinds_action_submenu_callback, app); // Select keybind item in submenu if(!strncmp(FLIPPER_APPS[i].name, keybind, MAX_KEYBIND_LENGTH)) { - pre_select_item = i; + pre_select_item = FLIPPER_APPS[i].name; } } } else if(action_type == DesktopSettingsAppKeybindActionTypeMoreActions) { @@ -49,13 +56,13 @@ void desktop_settings_scene_keybinds_action_on_enter(void* context) { submenu_add_item( submenu, EXTRA_KEYBINDS[i], - i, + (uint32_t)EXTRA_KEYBINDS[i], desktop_settings_scene_keybinds_action_submenu_callback, app); // Select keybind item in submenu if(!strncmp(EXTRA_KEYBINDS[i], keybind, MAX_KEYBIND_LENGTH)) { - pre_select_item = i; + pre_select_item = EXTRA_KEYBINDS[i]; } } } From 3f7337cd138b82c53d663f26db5500a45252e1dd Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 13 Jul 2023 23:24:38 +0300 Subject: [PATCH 126/364] cleanup to automerge --- assets/resources/infrared/assets/ac.ir | 37 -------------------------- 1 file changed, 37 deletions(-) diff --git a/assets/resources/infrared/assets/ac.ir b/assets/resources/infrared/assets/ac.ir index 5110ef073..4edb9eb66 100644 --- a/assets/resources/infrared/assets/ac.ir +++ b/assets/resources/infrared/assets/ac.ir @@ -729,40 +729,3 @@ type: raw frequency: 38000 duty_cycle: 0.330000 data: 30675 50953 3432 1606 490 1190 489 350 489 350 489 350 489 350 489 351 488 350 489 351 489 351 488 351 489 351 488 351 489 1191 488 351 488 351 489 351 488 351 488 351 488 351 489 351 488 1191 489 1191 489 351 488 351 488 351 488 351 488 351 489 351 489 351 488 351 489 1191 488 351 488 1191 489 1191 488 1191 488 1191 488 1191 488 1191 488 351 489 1191 488 1191 489 351 488 351 489 351 488 351 489 351 488 351 488 351 488 351 488 1191 488 1191 488 1191 488 1191 489 1191 488 1191 488 1191 489 1191 488 351 488 351 489 351 488 1191 488 351 489 351 488 351 488 351 489 1191 488 351 489 351 488 1191 488 351 488 351 488 351 489 351 489 351 488 351 489 1191 488 351 488 351 489 351 488 351 488 1191 488 1191 488 351 488 351 489 351 488 351 489 351 488 351 489 351 489 1191 488 1192 488 1191 488 351 489 1191 489 351 488 351 488 351 489 351 489 351 488 351 488 351 489 351 488 351 488 351 488 1192 488 351 489 351 488 351 488 351 489 351 489 351 488 351 488 351 488 352 487 352 488 351 488 352 488 351 488 351 488 351 488 1192 488 1192 487 352 488 352 487 352 487 352 488 352 487 352 488 351 488 352 488 352 488 352 487 352 488 351 488 351 488 352 488 352 487 352 488 352 487 352 488 352 488 352 488 352 487 1192 487 352 487 352 488 352 488 352 488 352 487 352 487 352 488 352 487 352 487 352 487 352 488 352 488 352 487 352 487 352 488 352 487 352 488 352 487 352 487 352 487 352 487 352 487 352 488 352 487 352 487 352 487 352 488 352 487 352 488 352 487 352 488 352 487 352 487 352 488 352 487 352 488 352 487 352 488 352 488 352 487 352 487 352 487 352 487 352 487 352 488 352 487 352 487 352 487 1193 486 352 487 352 488 352 487 352 487 352 488 352 487 352 488 352 488 352 487 352 488 352 487 353 486 353 487 352 487 352 488 352 488 352 487 352 487 352 487 352 487 353 487 352 488 352 488 352 487 1193 486 1193 486 1193 486 1193 487 353 487 352 487 353 486 -# -# Model: LG PC07SQR -name: Off -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 3169 9836 535 1553 511 551 491 543 491 544 490 1586 490 532 510 530 511 543 491 1579 489 1577 490 543 490 543 491 543 491 544 490 544 489 544 490 543 491 544 490 550 492 544 490 543 491 1586 489 542 492 1583 492 552 490 532 510 537 512 1585 491 -# -name: Dh -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 3385 9888 512 1544 512 544 489 544 489 544 490 1586 489 545 489 544 489 545 489 531 511 541 508 533 508 542 507 544 489 546 487 544 490 1587 489 1573 510 543 490 543 491 1578 490 545 489 1574 509 545 488 1587 489 1573 510 1586 490 1579 489 1568 507 -# -name: Cool_hi -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 3288 9816 598 1504 574 472 569 463 571 463 571 1515 568 472 569 461 573 471 571 459 590 462 571 463 571 447 594 462 572 462 571 464 570 459 590 463 570 464 570 1496 571 1497 570 463 571 1514 569 464 570 1504 572 1514 569 471 571 473 568 462 572 -# -name: Cool_lo -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 3205 9865 616 1473 536 518 516 517 517 517 517 1575 517 527 568 464 515 517 571 463 570 462 572 462 572 469 573 461 573 463 570 462 572 469 573 1504 572 451 590 451 591 472 569 463 571 1494 573 461 573 1497 570 1505 570 1503 572 471 571 1491 592 -# -name: Heat_hi -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 3363 9846 596 1495 514 518 515 517 517 515 518 1552 516 502 539 517 517 516 518 516 518 517 517 517 571 462 517 518 516 1554 568 461 589 462 572 1505 571 1507 568 1514 514 1568 570 461 573 1504 572 472 570 1507 592 1490 593 460 574 462 572 447 594 -# -name: Heat_lo -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 3204 9889 537 1587 491 529 512 544 489 545 489 1573 510 530 511 543 491 552 490 538 511 543 491 543 491 532 509 543 491 1587 489 537 512 543 491 1577 490 543 491 543 491 544 489 543 491 1586 489 544 490 1587 489 539 510 543 491 543 491 1586 490 From fd778be03c6ca9cb2b5f3d7ef020567651d4b8ea Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 13 Jul 2023 23:32:41 +0300 Subject: [PATCH 127/364] Update lightmeter --- applications/external/lightmeter/README.md | 26 ++++----- .../external/lightmeter/application.fam | 16 ++++-- .../gui/scenes/lightmeter_scene_config.c | 22 +++++++ .../gui/scenes/lightmeter_scene_help.c | 3 +- .../gui/scenes/lightmeter_scene_main.c | 3 +- .../lightmeter/lib/MAX44009/MAX44009.c | 27 +++++++++ .../lightmeter/lib/MAX44009/MAX44009.h | 26 +++++++++ applications/external/lightmeter/lightmeter.c | 57 ++++++++++++++----- applications/external/lightmeter/lightmeter.h | 4 ++ .../external/lightmeter/lightmeter_config.h | 7 ++- 10 files changed, 154 insertions(+), 37 deletions(-) create mode 100644 applications/external/lightmeter/lib/MAX44009/MAX44009.c create mode 100644 applications/external/lightmeter/lib/MAX44009/MAX44009.h diff --git a/applications/external/lightmeter/README.md b/applications/external/lightmeter/README.md index d9c071e67..e90630c03 100644 --- a/applications/external/lightmeter/README.md +++ b/applications/external/lightmeter/README.md @@ -1,21 +1,17 @@ -# flipperzero-lightmeter +## Lightmeter app for photography -[Original link](https://github.com/oleksiikutuzov/flipperzero-lightmeter) +An application that suggests settings for your manual camera based on the reading of the ambient light sensor. Can also be used in a pure lux meter mode. +## Supported sensors - +- BH1750 +- MAX44009 ## Wiring -``` -VCC -> 3.3V -GND -> GND -SCL -> C0 -SDA -> C1 -``` - -## TODO -- [ ] Save settings to sd card - -## References -App inspired by [lightmeter](https://github.com/vpominchuk/lightmeter) project for Arduino by [vpominchuk](https://github.com/vpominchuk). +| Sensor | Flipper Zero | +| ------ | ------------ | +| VCC | 3.3V | +| GND | GND | +| SCL | C0 | +| SDA | C1 | diff --git a/applications/external/lightmeter/application.fam b/applications/external/lightmeter/application.fam index fc46550fe..dcdf0d9f9 100644 --- a/applications/external/lightmeter/application.fam +++ b/applications/external/lightmeter/application.fam @@ -1,6 +1,6 @@ App( appid="lightmeter", - name="[BH1750] Lightmeter", + name="Lightmeter", apptype=FlipperAppType.EXTERNAL, entry_point="lightmeter_app", requires=[ @@ -8,6 +8,7 @@ App( ], stack_size=1 * 1024, order=90, + fap_version=(1, 1), fap_icon="lightmeter.png", fap_category="GPIO", fap_private_libs=[ @@ -18,9 +19,16 @@ App( "BH1750.c", ], ), + Lib( + name="MAX44009", + cincludes=["."], + sources=[ + "MAX44009.c", + ], + ), ], + fap_description="Lightmeter app for photography", + fap_author="Oleksii Kutuzov", + fap_weburl="https://github.com/oleksiikutuzov/flipperzero-lightmeter", fap_icon_assets="icons", - fap_author="@oleksiikutuzov", - fap_version="1.0", - fap_description="Lightmeter app for photography based on BH1750 sensor", ) diff --git a/applications/external/lightmeter/gui/scenes/lightmeter_scene_config.c b/applications/external/lightmeter/gui/scenes/lightmeter_scene_config.c index 3d6bd5803..aafa1f843 100644 --- a/applications/external/lightmeter/gui/scenes/lightmeter_scene_config.c +++ b/applications/external/lightmeter/gui/scenes/lightmeter_scene_config.c @@ -51,12 +51,18 @@ static const char* lux_only[] = { [LUX_ONLY_ON] = "On", }; +static const char* sensor_type[] = { + [SENSOR_BH1750] = "BH1750", + [SENSOR_MAX44009] = "MAX44009", +}; + enum LightMeterSubmenuIndex { LightMeterSubmenuIndexISO, LightMeterSubmenuIndexND, LightMeterSubmenuIndexDome, LightMeterSubmenuIndexBacklight, LightMeterSubmenuIndexLuxMeter, + LightMeterSubmenuIndexSensorType, LightMeterSubmenuIndexHelp, LightMeterSubmenuIndexAbout, }; @@ -127,6 +133,17 @@ static void lux_only_cb(VariableItem* item) { lightmeter_app_set_config(app, config); } +static void sensor_type_cb(VariableItem* item) { + LightMeterApp* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + variable_item_set_current_value_text(item, sensor_type[index]); + + LightMeterConfig* config = app->config; + config->sensor_type = index; + lightmeter_app_set_config(app, config); +} + static void ok_cb(void* context, uint32_t index) { LightMeterApp* app = context; UNUSED(app); @@ -173,6 +190,11 @@ void lightmeter_scene_config_on_enter(void* context) { variable_item_set_current_value_index(item, config->lux_only); variable_item_set_current_value_text(item, lux_only[config->lux_only]); + item = variable_item_list_add( + var_item_list, "Sensor", COUNT_OF(sensor_type), sensor_type_cb, app); + variable_item_set_current_value_index(item, config->sensor_type); + variable_item_set_current_value_text(item, sensor_type[config->sensor_type]); + item = variable_item_list_add(var_item_list, "Help and Pinout", 0, NULL, NULL); item = variable_item_list_add(var_item_list, "About", 0, NULL, NULL); diff --git a/applications/external/lightmeter/gui/scenes/lightmeter_scene_help.c b/applications/external/lightmeter/gui/scenes/lightmeter_scene_help.c index 0441f0925..a5688ee2f 100644 --- a/applications/external/lightmeter/gui/scenes/lightmeter_scene_help.c +++ b/applications/external/lightmeter/gui/scenes/lightmeter_scene_help.c @@ -6,7 +6,8 @@ void lightmeter_scene_help_on_enter(void* context) { FuriString* temp_str; temp_str = furi_string_alloc(); furi_string_printf( - temp_str, "App works with BH1750\nambient light sensor\nconnected via I2C interface\n\n"); + temp_str, + "App works with BH1750 and MAX44409 ambient light sensor connected via I2C interface\n\n"); furi_string_cat(temp_str, "\e#Pinout:\r\n"); furi_string_cat( temp_str, diff --git a/applications/external/lightmeter/gui/scenes/lightmeter_scene_main.c b/applications/external/lightmeter/gui/scenes/lightmeter_scene_main.c index 8ba474461..3b4789e99 100644 --- a/applications/external/lightmeter/gui/scenes/lightmeter_scene_main.c +++ b/applications/external/lightmeter/gui/scenes/lightmeter_scene_main.c @@ -9,6 +9,7 @@ static void lightmeter_scene_main_on_left(void* context) { void lightmeter_scene_main_on_enter(void* context) { LightMeterApp* app = context; + lightmeter_app_i2c_init_sensor(context); lightmeter_main_view_set_left_callback(app->main_view, lightmeter_scene_main_on_left, app); view_dispatcher_switch_to_view(app->view_dispatcher, LightMeterAppViewMainView); } @@ -39,5 +40,5 @@ bool lightmeter_scene_main_on_event(void* context, SceneManagerEvent event) { } void lightmeter_scene_main_on_exit(void* context) { - UNUSED(context); + lightmeter_app_i2c_deinit_sensor(context); } diff --git a/applications/external/lightmeter/lib/MAX44009/MAX44009.c b/applications/external/lightmeter/lib/MAX44009/MAX44009.c new file mode 100644 index 000000000..211f6a9ba --- /dev/null +++ b/applications/external/lightmeter/lib/MAX44009/MAX44009.c @@ -0,0 +1,27 @@ +#include +#include +#include + +void max44009_init() { + furi_hal_i2c_acquire(I2C_BUS); + furi_hal_i2c_write_reg_8(I2C_BUS, MAX44009_ADDR, + MAX44009_REG_CONFIG, MAX44009_REG_CONFIG_CONT_MODE, I2C_TIMEOUT); + furi_hal_i2c_release(I2C_BUS); +} + +int max44009_read_light(float* result) { + uint8_t data_one = 0; + uint8_t exp, mantissa; + int status; + + furi_hal_i2c_acquire(I2C_BUS); + furi_hal_i2c_read_reg_8(I2C_BUS, MAX44009_ADDR, MAX44009_REG_LUX_HI, &data_one, I2C_TIMEOUT); + exp = (data_one & MAX44009_REG_LUX_HI_EXP_MASK) >> 4; + mantissa = (data_one & MAX44009_REG_LUX_HI_MANT_HI_MASK) << 4; + status = furi_hal_i2c_read_reg_8(I2C_BUS, MAX44009_ADDR, MAX44009_REG_LUX_LO, &data_one, I2C_TIMEOUT); + mantissa |= (data_one & MAX44009_REG_LUX_LO_MANT_LO_MASK); + furi_hal_i2c_release(I2C_BUS); + *result = (float)pow(2, exp) * mantissa * 0.045; + FURI_LOG_D("MAX44009", "exp %d, mant %d, lux %f", exp, mantissa, (double)*result); + return status; +} \ No newline at end of file diff --git a/applications/external/lightmeter/lib/MAX44009/MAX44009.h b/applications/external/lightmeter/lib/MAX44009/MAX44009.h new file mode 100644 index 000000000..fb350653d --- /dev/null +++ b/applications/external/lightmeter/lib/MAX44009/MAX44009.h @@ -0,0 +1,26 @@ +#include +#include + +#pragma once + +// I2C BUS +#define I2C_BUS &furi_hal_i2c_handle_external +#define I2C_TIMEOUT 10 + +#define MAX44009_ADDR (0x4A << 1) + +#define MAX44009_REG_INT_STATUS 0x00 +#define MAX44009_REG_INT_EN 0x01 +#define MAX44009_REG_CONFIG 0x02 +#define MAX44009_REG_CONFIG_CONT_MODE (1 << 7) +#define MAX44009_REG_LUX_HI 0x03 +#define MAX44009_REG_LUX_HI_EXP_MASK 0xF0 +#define MAX44009_REG_LUX_HI_MANT_HI_MASK 0x0F +#define MAX44009_REG_LUX_LO 0x04 +#define MAX44009_REG_LUX_LO_MANT_LO_MASK 0x0F +#define MAX44009_REG_THRESH_HI 0x05 +#define MAX44009_REG_THRESH_LO 0x06 +#define MAX44009_REG_INT_TIME 0x07 + +void max44009_init(); +int max44009_read_light(float* result); diff --git a/applications/external/lightmeter/lightmeter.c b/applications/external/lightmeter/lightmeter.c index 07661d2d4..b128e334f 100644 --- a/applications/external/lightmeter/lightmeter.c +++ b/applications/external/lightmeter/lightmeter.c @@ -27,11 +27,6 @@ static void lightmeter_tick_event_callback(void* context) { LightMeterApp* lightmeter_app_alloc(uint32_t first_scene) { LightMeterApp* app = malloc(sizeof(LightMeterApp)); - // Sensor - bh1750_set_power_state(1); - bh1750_init(); - bh1750_set_mode(ONETIME_HIGH_RES_MODE); - // Set default values to config app->config = malloc(sizeof(LightMeterConfig)); app->config->iso = DEFAULT_ISO; @@ -117,8 +112,6 @@ void lightmeter_app_free(LightMeterApp* app) { } furi_record_close(RECORD_NOTIFICATION); - bh1750_set_power_state(0); - free(app->config); free(app); } @@ -138,6 +131,38 @@ void lightmeter_app_set_config(LightMeterApp* context, LightMeterConfig* config) app->config = config; } +void lightmeter_app_i2c_init_sensor(LightMeterApp* context) { + LightMeterApp* app = context; + switch(app->config->sensor_type) { + case SENSOR_BH1750: + bh1750_set_power_state(1); + bh1750_init(); + bh1750_set_mode(ONETIME_HIGH_RES_MODE); + break; + case SENSOR_MAX44009: + max44009_init(); + break; + default: + FURI_LOG_E(TAG, "Invalid sensor type %d", app->config->sensor_type); + return; + } +} + +void lightmeter_app_i2c_deinit_sensor(LightMeterApp* context) { + LightMeterApp* app = context; + switch(app->config->sensor_type) { + case SENSOR_BH1750: + bh1750_set_power_state(0); + break; + case SENSOR_MAX44009: + // nothing + break; + default: + FURI_LOG_E(TAG, "Invalid sensor type %d", app->config->sensor_type); + return; + } +} + void lightmeter_app_i2c_callback(LightMeterApp* context) { LightMeterApp* app = context; @@ -145,16 +170,18 @@ void lightmeter_app_i2c_callback(LightMeterApp* context) { float lux = 0; bool response = 0; - if(bh1750_trigger_manual_conversion() == BH1750_OK) response = 1; - - if(response) { - bh1750_read_light(&lux); - - if(main_view_get_dome(app->main_view)) lux *= DOME_COEFFICIENT; - - EV = lux2ev(lux); + if(app->config->sensor_type == SENSOR_BH1750) { + if(bh1750_trigger_manual_conversion() == BH1750_OK) { + bh1750_read_light(&lux); + response = 1; + } + } else if(app->config->sensor_type == SENSOR_MAX44009) { + if(max44009_read_light(&lux)) response = 1; } + if(main_view_get_dome(app->main_view)) lux *= DOME_COEFFICIENT; + EV = lux2ev(lux); + main_view_set_lux(app->main_view, lux); main_view_set_EV(app->main_view, EV); main_view_set_response(app->main_view, response); diff --git a/applications/external/lightmeter/lightmeter.h b/applications/external/lightmeter/lightmeter.h index 2558be3c5..75bc09b02 100644 --- a/applications/external/lightmeter/lightmeter.h +++ b/applications/external/lightmeter/lightmeter.h @@ -18,6 +18,7 @@ #include "lightmeter_config.h" #include +#include typedef struct { int iso; @@ -26,6 +27,7 @@ typedef struct { int dome; int backlight; int lux_only; + int sensor_type; } LightMeterConfig; typedef struct { @@ -55,4 +57,6 @@ typedef enum { void lightmeter_app_set_config(LightMeterApp* context, LightMeterConfig* config); +void lightmeter_app_i2c_init_sensor(LightMeterApp* context); +void lightmeter_app_i2c_deinit_sensor(LightMeterApp* context); void lightmeter_app_i2c_callback(LightMeterApp* context); diff --git a/applications/external/lightmeter/lightmeter_config.h b/applications/external/lightmeter/lightmeter_config.h index 5edbdce0a..d46f0cd38 100644 --- a/applications/external/lightmeter/lightmeter_config.h +++ b/applications/external/lightmeter/lightmeter_config.h @@ -1,6 +1,6 @@ #pragma once -#define LM_VERSION_APP "0.7" +#define LM_VERSION_APP "1.1" #define LM_DEVELOPED "Oleksii Kutuzov" #define LM_GITHUB "https://github.com/oleksiikutuzov/flipperzero-lightmeter" @@ -105,4 +105,9 @@ typedef enum { LUX_ONLY_ON, } LightMeterLuxOnlyMode; +typedef enum { + SENSOR_BH1750, + SENSOR_MAX44009, +} LightMeterSensorType; + typedef enum { BACKLIGHT_AUTO, BACKLIGHT_ON } LightMeterBacklight; From ec588935b58a6108acca3471e2b7cceae635c88f Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 13 Jul 2023 23:33:40 +0300 Subject: [PATCH 128/364] fbt format --- .../desktop/views/desktop_view_debug.c | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/applications/services/desktop/views/desktop_view_debug.c b/applications/services/desktop/views/desktop_view_debug.c index 36f1394bf..fef2356ba 100644 --- a/applications/services/desktop/views/desktop_view_debug.c +++ b/applications/services/desktop/views/desktop_view_debug.c @@ -38,19 +38,19 @@ void desktop_debug_render(Canvas* canvas, void* model) { canvas_set_font(canvas, FontSecondary); - // Hardware version - const char* my_name = furi_hal_version_get_name_ptr(); - snprintf( - buffer, - sizeof(buffer), - "%d.F%dB%dC%d %s %s", - furi_hal_version_get_hw_version(), - furi_hal_version_get_hw_target(), - furi_hal_version_get_hw_body(), - furi_hal_version_get_hw_connect(), - furi_hal_version_get_hw_region_name_otp(), - my_name ? my_name : "Unknown"); - canvas_draw_str(canvas, 0, 19 + STATUS_BAR_Y_SHIFT, buffer); + // Hardware version + const char* my_name = furi_hal_version_get_name_ptr(); + snprintf( + buffer, + sizeof(buffer), + "%d.F%dB%dC%d %s %s", + furi_hal_version_get_hw_version(), + furi_hal_version_get_hw_target(), + furi_hal_version_get_hw_body(), + furi_hal_version_get_hw_connect(), + furi_hal_version_get_hw_region_name_otp(), + my_name ? my_name : "Unknown"); + canvas_draw_str(canvas, 0, 19 + STATUS_BAR_Y_SHIFT, buffer); ver = furi_hal_version_get_firmware_version(); const BleGlueC2Info* c2_ver = NULL; From 090a912f9301e63263f196e1b81850847348ef80 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 13 Jul 2023 23:53:46 +0300 Subject: [PATCH 129/364] remove --- .../external/multi_fuzzer/application.fam | 49 -- applications/external/multi_fuzzer/fuzzer.c | 160 ------ applications/external/multi_fuzzer/fuzzer_i.h | 55 -- .../helpers/fuzzer_custom_event.h | 17 - .../multi_fuzzer/helpers/fuzzer_types.h | 30 -- .../external/multi_fuzzer/icons/125_10px.png | Bin 308 -> 0 bytes .../multi_fuzzer/icons/ButtonLeft_4x7.png | Bin 1415 -> 0 bytes .../multi_fuzzer/icons/ButtonRight_4x7.png | Bin 1839 -> 0 bytes .../multi_fuzzer/icons/Ok_btn_9x9.png | Bin 3605 -> 0 bytes .../multi_fuzzer/icons/Pin_arrow_up_7x9.png | Bin 3603 -> 0 bytes .../icons/Pin_back_arrow_10x8.png | Bin 3606 -> 0 bytes .../multi_fuzzer/icons/ibutt_10px.png | Bin 304 -> 0 bytes .../external/multi_fuzzer/icons/rfid_10px.png | Bin 2389 -> 0 bytes .../multi_fuzzer/lib/worker/fake_worker.c | 485 ------------------ .../multi_fuzzer/lib/worker/fake_worker.h | 138 ----- .../multi_fuzzer/lib/worker/protocol.c | 291 ----------- .../multi_fuzzer/lib/worker/protocol.h | 89 ---- .../multi_fuzzer/lib/worker/protocol_i.h | 43 -- .../multi_fuzzer/scenes/fuzzer_scene.c | 30 -- .../multi_fuzzer/scenes/fuzzer_scene.h | 29 -- .../multi_fuzzer/scenes/fuzzer_scene_attack.c | 162 ------ .../multi_fuzzer/scenes/fuzzer_scene_config.h | 3 - .../scenes/fuzzer_scene_field_editor.c | 66 --- .../multi_fuzzer/scenes/fuzzer_scene_main.c | 189 ------- applications/external/multi_fuzzer/todo.md | 44 -- .../external/multi_fuzzer/views/attack.c | 384 -------------- .../external/multi_fuzzer/views/attack.h | 42 -- .../multi_fuzzer/views/field_editor.c | 358 ------------- .../multi_fuzzer/views/field_editor.h | 29 -- .../external/multi_fuzzer/views/main_menu.c | 235 --------- .../external/multi_fuzzer/views/main_menu.h | 23 - 31 files changed, 2951 deletions(-) delete mode 100644 applications/external/multi_fuzzer/application.fam delete mode 100644 applications/external/multi_fuzzer/fuzzer.c delete mode 100644 applications/external/multi_fuzzer/fuzzer_i.h delete mode 100644 applications/external/multi_fuzzer/helpers/fuzzer_custom_event.h delete mode 100644 applications/external/multi_fuzzer/helpers/fuzzer_types.h delete mode 100644 applications/external/multi_fuzzer/icons/125_10px.png delete mode 100644 applications/external/multi_fuzzer/icons/ButtonLeft_4x7.png delete mode 100644 applications/external/multi_fuzzer/icons/ButtonRight_4x7.png delete mode 100644 applications/external/multi_fuzzer/icons/Ok_btn_9x9.png delete mode 100644 applications/external/multi_fuzzer/icons/Pin_arrow_up_7x9.png delete mode 100644 applications/external/multi_fuzzer/icons/Pin_back_arrow_10x8.png delete mode 100644 applications/external/multi_fuzzer/icons/ibutt_10px.png delete mode 100644 applications/external/multi_fuzzer/icons/rfid_10px.png delete mode 100644 applications/external/multi_fuzzer/lib/worker/fake_worker.c delete mode 100644 applications/external/multi_fuzzer/lib/worker/fake_worker.h delete mode 100644 applications/external/multi_fuzzer/lib/worker/protocol.c delete mode 100644 applications/external/multi_fuzzer/lib/worker/protocol.h delete mode 100644 applications/external/multi_fuzzer/lib/worker/protocol_i.h delete mode 100644 applications/external/multi_fuzzer/scenes/fuzzer_scene.c delete mode 100644 applications/external/multi_fuzzer/scenes/fuzzer_scene.h delete mode 100644 applications/external/multi_fuzzer/scenes/fuzzer_scene_attack.c delete mode 100644 applications/external/multi_fuzzer/scenes/fuzzer_scene_config.h delete mode 100644 applications/external/multi_fuzzer/scenes/fuzzer_scene_field_editor.c delete mode 100644 applications/external/multi_fuzzer/scenes/fuzzer_scene_main.c delete mode 100644 applications/external/multi_fuzzer/todo.md delete mode 100644 applications/external/multi_fuzzer/views/attack.c delete mode 100644 applications/external/multi_fuzzer/views/attack.h delete mode 100644 applications/external/multi_fuzzer/views/field_editor.c delete mode 100644 applications/external/multi_fuzzer/views/field_editor.h delete mode 100644 applications/external/multi_fuzzer/views/main_menu.c delete mode 100644 applications/external/multi_fuzzer/views/main_menu.h diff --git a/applications/external/multi_fuzzer/application.fam b/applications/external/multi_fuzzer/application.fam deleted file mode 100644 index b5205803d..000000000 --- a/applications/external/multi_fuzzer/application.fam +++ /dev/null @@ -1,49 +0,0 @@ -App( - appid="fuzzer_ibtn", - name="iButton Fuzzer", - apptype=FlipperAppType.EXTERNAL, - entry_point="fuzzer_start_ibtn", - requires=[ - "gui", - "storage", - "dialogs", - "input", - "notification", - ], - stack_size=2 * 1024, - fap_icon="icons/ibutt_10px.png", - fap_category="iButton", - fap_private_libs=[ - Lib( - name="worker", - cdefines=["IBUTTON_PROTOCOL"], - ), - ], - fap_icon_assets="icons", - fap_icon_assets_symbol="fuzzer", -) - -App( - appid="fuzzer_rfid", - name="RFID Fuzzer", - apptype=FlipperAppType.EXTERNAL, - entry_point="fuzzer_start_rfid", - requires=[ - "gui", - "storage", - "dialogs", - "input", - "notification", - ], - stack_size=2 * 1024, - fap_icon="icons/rfid_10px.png", - fap_category="RFID", - fap_private_libs=[ - Lib( - name="worker", - cdefines=["RFID_125_PROTOCOL"], - ), - ], - fap_icon_assets="icons", - fap_icon_assets_symbol="fuzzer", -) diff --git a/applications/external/multi_fuzzer/fuzzer.c b/applications/external/multi_fuzzer/fuzzer.c deleted file mode 100644 index 113291d0c..000000000 --- a/applications/external/multi_fuzzer/fuzzer.c +++ /dev/null @@ -1,160 +0,0 @@ -#include "fuzzer_i.h" -#include "helpers/fuzzer_types.h" - -static bool fuzzer_app_custom_event_callback(void* context, uint32_t event) { - furi_assert(context); - PacsFuzzerApp* app = context; - return scene_manager_handle_custom_event(app->scene_manager, event); -} - -static bool fuzzer_app_back_event_callback(void* context) { - furi_assert(context); - PacsFuzzerApp* app = context; - return scene_manager_handle_back_event(app->scene_manager); -} - -static void fuzzer_app_tick_event_callback(void* context) { - furi_assert(context); - PacsFuzzerApp* app = context; - scene_manager_handle_tick_event(app->scene_manager); -} - -PacsFuzzerApp* fuzzer_app_alloc() { - PacsFuzzerApp* app = malloc(sizeof(PacsFuzzerApp)); - - app->fuzzer_state.menu_index = 0; - app->fuzzer_state.proto_index = 0; - - app->worker = fuzzer_worker_alloc(); - app->payload = fuzzer_payload_alloc(); - - app->file_path = furi_string_alloc(); - - // GUI - app->gui = furi_record_open(RECORD_GUI); - - // Dialog - app->dialogs = furi_record_open(RECORD_DIALOGS); - - // Open Notification record - app->notifications = furi_record_open(RECORD_NOTIFICATION); - - // View Dispatcher - app->view_dispatcher = view_dispatcher_alloc(); - - // Popup - app->popup = popup_alloc(); - view_dispatcher_add_view(app->view_dispatcher, FuzzerViewIDPopup, popup_get_view(app->popup)); - - // Main view - app->main_view = fuzzer_view_main_alloc(); - view_dispatcher_add_view( - app->view_dispatcher, FuzzerViewIDMain, fuzzer_view_main_get_view(app->main_view)); - - // Attack view - app->attack_view = fuzzer_view_attack_alloc(); - view_dispatcher_add_view( - app->view_dispatcher, FuzzerViewIDAttack, fuzzer_view_attack_get_view(app->attack_view)); - - // FieldEditor view - app->field_editor_view = fuzzer_view_field_editor_alloc(); - view_dispatcher_add_view( - app->view_dispatcher, - FuzzerViewIDFieldEditor, - fuzzer_view_field_editor_get_view(app->field_editor_view)); - - app->scene_manager = scene_manager_alloc(&fuzzer_scene_handlers, app); - view_dispatcher_enable_queue(app->view_dispatcher); - - view_dispatcher_set_event_callback_context(app->view_dispatcher, app); - view_dispatcher_set_custom_event_callback( - app->view_dispatcher, fuzzer_app_custom_event_callback); - view_dispatcher_set_navigation_event_callback( - app->view_dispatcher, fuzzer_app_back_event_callback); - view_dispatcher_set_tick_event_callback( - app->view_dispatcher, fuzzer_app_tick_event_callback, 100); - - view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); - - scene_manager_next_scene(app->scene_manager, FuzzerSceneMain); - - return app; -} - -void fuzzer_app_free(PacsFuzzerApp* app) { - furi_assert(app); - - // Remote view - view_dispatcher_remove_view(app->view_dispatcher, FuzzerViewIDMain); - fuzzer_view_main_free(app->main_view); - - // Attack view - view_dispatcher_remove_view(app->view_dispatcher, FuzzerViewIDAttack); - fuzzer_view_attack_free(app->attack_view); - - // FieldEditor view - view_dispatcher_remove_view(app->view_dispatcher, FuzzerViewIDFieldEditor); - fuzzer_view_field_editor_free(app->field_editor_view); - - // Popup - view_dispatcher_remove_view(app->view_dispatcher, FuzzerViewIDPopup); - popup_free(app->popup); - - scene_manager_free(app->scene_manager); - view_dispatcher_free(app->view_dispatcher); - - // Dialog - furi_record_close(RECORD_DIALOGS); - - // Close records - furi_record_close(RECORD_GUI); - - // Notifications - furi_record_close(RECORD_NOTIFICATION); - app->notifications = NULL; - - furi_string_free(app->file_path); - - fuzzer_payload_free(app->payload); - fuzzer_worker_free(app->worker); - - free(app); -} - -int32_t fuzzer_start_ibtn(void* p) { - UNUSED(p); - PacsFuzzerApp* fuzzer_app = fuzzer_app_alloc(); - - FuzzerConsts app_const = { - .custom_dict_folder = "/ext/ibtnfuzzer", - .custom_dict_extension = ".txt", - .key_extension = ".ibtn", - .path_key_folder = "/ext/ibutton", - .key_icon = &I_ibutt_10px, - }; - fuzzer_app->fuzzer_const = &app_const; - - view_dispatcher_run(fuzzer_app->view_dispatcher); - - fuzzer_app_free(fuzzer_app); - return 0; -} - -int32_t fuzzer_start_rfid(void* p) { - UNUSED(p); - PacsFuzzerApp* fuzzer_app = fuzzer_app_alloc(); - - FuzzerConsts app_const = { - .custom_dict_folder = "/ext/rfidfuzzer", - .custom_dict_extension = ".txt", - .key_extension = ".rfid", - .path_key_folder = "/ext/lfrfid", - .key_icon = &I_125_10px, - }; - fuzzer_app->fuzzer_const = &app_const; - - view_dispatcher_run(fuzzer_app->view_dispatcher); - - fuzzer_app_free(fuzzer_app); - return 0; -} \ No newline at end of file diff --git a/applications/external/multi_fuzzer/fuzzer_i.h b/applications/external/multi_fuzzer/fuzzer_i.h deleted file mode 100644 index 5b58e59d8..000000000 --- a/applications/external/multi_fuzzer/fuzzer_i.h +++ /dev/null @@ -1,55 +0,0 @@ -#pragma once - -#include -#include - -#include -#include -#include -#include - -#include -#include - -#include "scenes/fuzzer_scene.h" -#include "views/main_menu.h" -#include "views/attack.h" -#include "views/field_editor.h" - -#include "helpers/fuzzer_types.h" -#include "lib/worker/fake_worker.h" - -#include -#include "fuzzer_icons.h" - -#define FUZZ_TIME_DELAY_MAX (80) - -typedef struct { - const char* custom_dict_extension; - const char* custom_dict_folder; - const char* key_extension; - const char* path_key_folder; - const Icon* key_icon; -} FuzzerConsts; - -typedef struct { - Gui* gui; - NotificationApp* notifications; - - ViewDispatcher* view_dispatcher; - SceneManager* scene_manager; - - Popup* popup; - DialogsApp* dialogs; - FuzzerViewMain* main_view; - FuzzerViewAttack* attack_view; - FuzzerViewFieldEditor* field_editor_view; - - FuriString* file_path; - - FuzzerState fuzzer_state; - FuzzerConsts* fuzzer_const; - - FuzzerWorker* worker; - FuzzerPayload* payload; -} PacsFuzzerApp; \ No newline at end of file diff --git a/applications/external/multi_fuzzer/helpers/fuzzer_custom_event.h b/applications/external/multi_fuzzer/helpers/fuzzer_custom_event.h deleted file mode 100644 index 321187722..000000000 --- a/applications/external/multi_fuzzer/helpers/fuzzer_custom_event.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -typedef enum { - - // FuzzerCustomEvent - FuzzerCustomEventViewMainBack = 100, - FuzzerCustomEventViewMainOk, - FuzzerCustomEventViewMainPopupErr, - - FuzzerCustomEventViewAttackBack, - FuzzerCustomEventViewAttackOk, - // FuzzerCustomEventViewAttackTick, // now not use - FuzzerCustomEventViewAttackEnd, - - FuzzerCustomEventViewFieldEditorBack, - FuzzerCustomEventViewFieldEditorOk, -} FuzzerCustomEvent; \ No newline at end of file diff --git a/applications/external/multi_fuzzer/helpers/fuzzer_types.h b/applications/external/multi_fuzzer/helpers/fuzzer_types.h deleted file mode 100644 index bb608a5f1..000000000 --- a/applications/external/multi_fuzzer/helpers/fuzzer_types.h +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -#include - -typedef struct { - uint8_t menu_index; - uint8_t proto_index; -} FuzzerState; - -typedef enum { - FuzzerAttackStateOff = 0, - FuzzerAttackStateIdle, - FuzzerAttackStateRunning, - FuzzerAttackStateEnd, - -} FuzzerAttackState; - -typedef enum { - FuzzerFieldEditorStateEditingOn = 0, - FuzzerFieldEditorStateEditingOff, - -} FuzzerFieldEditorState; - -typedef enum { - FuzzerViewIDPopup, - - FuzzerViewIDMain, - FuzzerViewIDAttack, - FuzzerViewIDFieldEditor, -} FuzzerViewID; \ No newline at end of file diff --git a/applications/external/multi_fuzzer/icons/125_10px.png b/applications/external/multi_fuzzer/icons/125_10px.png deleted file mode 100644 index ce01284a2c1f3eb413f581b84f1fb3f3a2a7223b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 308 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2xkYHHq`AGmsv7|ftIx;Y9?C1WI$O_~uBzpw; zGB8xBF)%c=FfjZA3N^f7U???UV0e|lz+g3lfkC`r&aOZkpafHrx4R1i<>&pI=m5)bWZjP>yH&963)5S4_<9hOs!iIaU2M7$VM1Vxif;vTv~W2u`S7ED{V3s&&L*<`XiG|9wd+THd> z5CnY!sdyuJtrvQyAo>KpiLcV|{Tkc)riAbluXfwSZCApL`ztB&p zx6LGKvks4K_4~)qD&oGa-YdJlW)hAKMNJd7<=t?6c^RI1>c$ifyjaM>^|&8!ey zB4!nh9u>5uen6Ve@<H5rru6h<2Ef#GQdQ*CmZOlQi~N!?9H`Rp;C% zU}CB21#?;r`&0|6C0}b-=jODa5|nEJ#ntxQ&{~jpgtwDta4hftr~G=#p@V36e4Zjh zq%J~{y26Jjn=1Nw-l*3%QW5YFE*v4z3gt0$&(*xf2en34c?JpH8+FYldo+Alvg8af-pG4(=!fyUi-Wsg z`g#n9VUcf(DFr{poMSNzw-lz>w+HV+n1ELr&SLA#LHUb0p(xWQ(1*vJ-i+1!`swxZ Z!O7;c$;lT_->m1Ovaz)0yuI`A$q$F8u*d)a diff --git a/applications/external/multi_fuzzer/icons/ButtonRight_4x7.png b/applications/external/multi_fuzzer/icons/ButtonRight_4x7.png deleted file mode 100644 index 8e1c74c1c0038ea55172f19ac875003fc80c2d06..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1839 zcmcIlO>f*p7#Yw)M6zw!O+@VZ{?d|D~WYi~8rHRY?X-&T}Yen`g$^+EJ;z+|RV zE@PoDvZ9%#+_}3bC_5Cj8jDGq541mi{7F+&KF}W65sr$Xn5H|YrMQ2(J7%Yc%;(zO z57ax000=TsQ+1Ke@+w#iw3au3cGGQWY740k2ijH>P(6tD)S)be>gX6Tj7`<`b>di- zgWp$8Y+?i31~CzF0&E4uRlA=C(Mp~K`{74jEchB|)4DDK!ZVhSwdFyw0YIZ1cDh0S{OvfO-U_~ zvmRF*m9sWDXNH)GOyqS1Skhxbr6}s*7t&@~kFM(NW5}qh?Lu@lJ}HE;FDiLdGO>LO z5pS*%E2grR)l^;|?O5b_?u0me&c1U}%jrk8*%=Wk%i)8yp2P|kuxmKg<=(u_`oQRI_0 zS`-DNysBx=#3&qSkgA@hJP>~D+ZM(s5jI6Owp`?yE=3e`YGUqkVOp#Cp=3wR3O4hX zX6BLsN3UBzV(vI5;|SZHgOb=HD0VFjpTyfFW}GnQuh>2*Q`k>*cAmA#iUT7EXSpo# zkPm5~#I-o^cpgfe#P$=4-Pi*SpT!-@nJgp8L347xe>5EKl`=_ZFc8XGy+_j=_R_7! z@vZZMowS1GJ?Zw)eetks%~G{BTR>T}9|jt0j3Btyb*C3-`C?fwY3EY`q*oYZ39DpM z&uJ;PCZPLs4QO1Jd_|A1PF)azZJ)RZ`^-VMWr6e#XUOA%3eLG_Ch@BDOHzMk*MF0G zCo7xMd?Mg*HMIXw%nNz?%60fZiZPlqb?GqUpXO`F&Yi!okZl(n>P@r1P2i)yk3DgRwbHeNn6e|;J^SK4TM LH~i+q&mR8;k>NTA diff --git a/applications/external/multi_fuzzer/icons/Ok_btn_9x9.png b/applications/external/multi_fuzzer/icons/Ok_btn_9x9.png deleted file mode 100644 index 9a1539da2049f12f7b25f96b11a9c40cd8227302..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3605 zcmaJ@c{r5q+kR|?vSeS9G2*Q(Gqz$f_GQ#q8r!JE7=ytqjlqnNNGaK}Wlbolp-q`& zs|bxHiiEP0&{#s&zVZIv-rx7f*Y_O9^W67+-RF5;*L_{ra~$^-2RmyaK{-JH0EBE1 z7AVdru>JD$aK0bym%#uaXpT2Gcd#)x2azcxAABGV0BC)Aj-lw(6)B^^6`Y8RS?}DV z%)ko(See1!Eb3M$dL6)A6csaRjExg?k&xVzi*Rm;?iNJk#f=mkVEUR~jXN3dd|Lmz z;y}sMh%ol-?E1&`>dD;6jdps6NYoxN)s%@sf4~40YY6LAOtMEbwA4g#OCpANL823^ zSH66W05Hcxr$tg98gFntAOYL}xm$C;Skv&Ym?{TVR{)d(41vWacX1`7fM!jnW(lBK z26*WB#9I(Z1Ast!xEUC@Cj`v=urcBTdP`FWq=DYTy`}s>0vC{VzHdNRvxNFy}ir1|g=xDsrFP&l1P<-Sv zXLqYVYz{b^ZIV@1Ulg->7DEgvM*Min&Y8{8QW! z$_pA434?^wCTq$4%^>Zo8&|8XwbCv;KEd;WJJ{s;T}8R8Zwi7ssk$QWQ5l5+opKfX z;8D*COFEB#4W^*FIrRU%PDSc?B(}+9ZV?N9(yH>0uSnM?xg!>+>;e z{{7tXQQ|ZFXD*7q3XD!pwnih-=66+Qlqtl9;N-D|PHoI&B5d8>^V#i{mE>V0gQgu3+(DG%B z|8W!pl$lbQERt-0eZA%NSfvE4F>VAYP`DpeoF;Zm4`)2id;6xgSysWl6K$pWANcRZ z!ETRXKIU9G=@9lEB?<{ivj7!8FE9WN;qoo2Lr0#c@DmcF=JzU<73PmM3 zbe!-gs`c26Uc(AKz7%U!a0yZ5gsprdo1i51MjJPeHtV6d@Jy=*+_3dJ^>}p#8N#kPK_4t?hltq>u=?m+t z?em(Y%u3Bp_pyV?c_w-4c}p+?Y$aHr>TuPGs@SUj;Er!b@3GVLDS@T8OTts1JFS-p zKZ=&5zp;DRor*`Gy8MTeWdpVJv2(4-*slRM@XXG+i^F&Ku>7i08vKenZHoS4s(!!h zJE}*MHu7PR_IfdNzu*P}3^87K?f&A1;>NMsgKcR6**;aB74NC7tR(NB?{dHT-9QhXa*KoG!kGU1}$l2D>ypo)fSBuG$ zkTW4?+|I1m?6ZH8tD4^fB{cUpoEoZOo%4hl!EtNtQ#?j*jJR)x-Mn0TrxrX2uT_rh ziOh=Jxsktqbd9x{^s{c5z92Pk$LGoQl53o+=7QXXCp-Z>io998w|DCCCGfr20oiRN zX|`KH$W4)wN~)J$kYB~>4EU;NcS^qH&yzeUzXokpMegg_lX$6ve^4}%bY~Sg)%uJ- zZpb$p4x^GS5d{XJP=STbfpHV`58UBH& zKFg&BgS6bV+#-|^KBGeIBee2B zrM-`uTB^_(eS+{-KK1h3l`-Yjpv8X4z*uBwQ3a~pL0Ae2xvNGyC3A|#MARToe$W~8 z+4{DsyenENye9df1M}gNUM9_Leh6G=`9exL-cdSKQ_CGyEdZ3W5uoR!Lb^D)9!bd=7h@R=M%=|JqX9XP;Z6# zFD15Bw7qTP(ZlG?o@#x@=wG;XxM(>n@4P$9WwY#lW$h=`zMi_zq30HbV-zHheqpE0 zR6kXtxdzl&Ml2D#zDIvflJkb*e zIAI?GMjp?JBK76WW`{l{pFAY|%5?nYUxRnT&y6~Kz19AD;C0(z*7?dM{%HhVtqWEc z%+M$z6u@uQu)kg_%2PO_U|n1JE0V1>iVbekOLEOG$U6X^Umc519WC)L$t%`#Di0$ zY1|5H*440_`onhmXeayq`8EIg?x2r9KWe()q}QayqCMEC?c4meb4}#i`HHPaxO&3SPtSVKj@ND?Y+-@R`CDnf-d`T>vTn8RR<=@3 zNXk=Gloyh#S@3R89WHrXBHr;f(&ZO@I_Uo7;O5Bs@ecGx@7%7{_>Q`Adg&sCeZTYp ztVy{^vAUfOpTDzF*4`h%X0odWn`#uZ4s4igIV^UrVVg?c*{>K)hHq^^RxU2CM;WN> z;oK@^sg`J}BguyvilN{DQ*V+N4rD{X_~KAFj5qyk3(gP#cvSIDXe!zk3B!^InwV{j zCXGPmumQl(m`28618`K37tR+?goD{H>cAkpHyrG$XA89@o8$cOh%gGyG0e^h8y0{y z@CF+jfedLdjsO8i#eispKw=P#1_%GG3**eU%@8o?ZwNI24*pM2Xj=!6If;S;9nsX% zz(S!=&=CVoZ;TfP>*b{m(uQhlL7=)2EnN*L6sBVU)71t2^ME<-DBeCWl!etl&NwSL z*pEsj!yu5*&``}#9ZeF&7oufgU;u$?L$tLuI0%g(I+2Q@X%K^ye=Atvg0K`knTjV7 zLEDNLFH$fS4(5dVpED51|H=}B{>c+3V-OmK4AIhrZlCEl(AM_T0=zuK- zizjYd4*pHCwT0ObgQyrH7H4At2XjO;@px~TsgAA%R9|05PuEIcOUu&SOwUTs^00xK zshI`T;)sF%Z>|Li8%)3vslU12|K;lbk-Oav1Tx371&)Fb!FgLzNCeQ|r-tGG9E;W; z_5R^{|2Y=zKXM_QU?AJI{a>~IZQ?Z0_VnM@jcrt7jKN@*#$ZMzB}>VcEo(xFhBigA zRfKF&B$OpfLSqS8d&l#8dVcR8Z}0is_kGT}&h`CX>-l`{D|W}MM1o0W+qqCz&a@8xmO|M3uh;cln|6OUI z@X7fQ&dki(hqbDStcmq@R)<*FE(x{7@jPF^02^V5=v9ihMb|f1hw)0IhxkF_<1H_} z1sVWgmXE~@Wjrum=ebV>cmZ0s_CATm;a}mEc52Q5C=nO}OHAzGNx%Y4+73-pK+|sE zf&F7oVIUa*{8{JBz(BDGF#W^YNC4<9N*a&_dh_-a2?DV^K)SlsK3W zOCXnR0@miQE9D7uc?!4U4XYLag5q!qVkYiDSh|^JD*)2x1yFk>+xS2jzFcTm?NE^$ zEusR=1Jt#ow51*G(vhl2c`F}0KRYy{Jo3{2p&4FwzqpssC^#!EQ$-Rz!G~$z2>|jd zoi8@^jT0uuM~BC~Cj2=+8uB*%W~pE!<+;Jls%yObfcUWvPM_P@SPvhqk>^2RtzXee zpw9{L8C-GI=@-g9A^bLEC5ENHZn8J$mR*yf;vV50J7!cpZdF6S#2Ee38Kw@!gf4MU zH~T|ofioE<=_Pgf;Tvc0l%P^<+(Zk%8H}<#p|aT+abY8Ff9Htq!&92lSLbk7D(t{E zjjU(bM04fllo5%^3-CFm)D5AeU=e^FXGmfr{&k_>d3a+)aa}=xN$7&sHTfNh zfVj6VoV5%9Nwq8SCK^0ITUx;v0I2%9`_$cJSLF_4$)r9^g5d7-;)ha7k^2JBT`QGyenmoI!B!BgFZa^nPSIjjmHP5e8zHBct z>}g(M=h3f$4B-6LI6_z_Ow{YzNBpU4Q5No3aPn%6GK4Xlo>ROYK@oQ-NLryT2hS1Q z#~TwSIW2hlviM8?O9=^9I1CPTS9MyYOrlcISt$H6?B!qJq`S6dsv#09^-K@M!vvfq zTkX5@UgaFs(|?Idx+S6ai8fy!JtnNIngF-nVeN7Z`Pkld>>sQwike&!d8m z!q}j+#PS5O1l#Lt&96qwr4S9#BN(B)eb|Czi6eSM<1zl*H{oXKxy8rZigMly7Dpp) zp0Fn82H8REqlzST12a_HGG$OL1zP#tZ!<{Vq-7t-B%@O3Q}|wsw6|$peqXmwPE3aX z2;M0YDH7g@_E4AelRGO{xVu~ql8(6}@GdRA$pQKSu8{71L+l3C5qDtez&Yu}Hxem` z6sMHXl!;;o#{fs;ZdUOQhkK4<_f9*Vzhmk6*zQY_(0iGC-9?Iy&x;P0wqt{_@pc`@ z-STVPHZH9aL>@&(Sms8e^BoA~ujOKuWnROHb2zgex)a}&rr!-4kCTs9rZGVRYYIV- zvlx3+K(QCwE72=^{7f5<=%`? zl>Nr(;dCk;g6aw$Opx=3=@VvK69`}ZZjdTEXD<)m-PPh#nON_W-)WuySB2X5DDN+N zOj#o@Hg%5&TlX_@z|RoxL4x-e)E6|2*6eRf_RH|9>@0i7Xl-rM9ANjdo2TOpy0iRp z@HHQ+`qyJ4Zd+tE9Emv?)0oNb81R+irnMuZ>Qj# zxib@y+4A&mNoGlXP$qd$YD6l2f7kv+drBW{dVN}WI%9gX}>;*m9J4X{*B+`P?WbMg?R|_dOLt0YC zJHiM_Ty3A^GkR^rdo$!_RLz|l@F22ACA23r zJ#_ne&f4MCmW}wIwZp7=nYm*E?mRDe#(1hP%3plU=f|hSpU!`KyPiO-!1Ha8okr4T zJB37Cl;}y+I@x)J6@t!yw`NAC^c%r!=@Sa8&{j3f-kx1?ksX4A;-S<#E11dFr-IQ# zR{qfyN+h{-*_HEB`wzg2wZ9!NvuB)PENk|#M_tyutK;V4i>^I8-0%C89^}pT^~d@X zrZX$TDvB#EGNXQ4%%w>%B=-r;Tp6wJtw&z@62Lp*pP`dAn&FVjAe4>`?UC_VILOQnvfFm7kYb}KIe$4b!q%cDFE;P^!}5wFhS$flol=(c zKOH`gTJ?#vwG4c%BV>!!U?s|3f2Oiv<7D3Rncea6%ttMQ=SEEn7*BSKM z{I;U9VyY&6%QWwRxn-WhQPHJ&t+6%>}7+sVXoLpPbO)$>wJq(%cIl{yAd4L zao(3TFdv5v@49^(rE$qwH>D`KxrI{ti`zebVW|0ofEcHjRC^^ydT1 zit!QWV{YB&7Fp!JzRyR>-^@&*rwXPh>}8kQ`$wvMO}pPl&We;M%*Bo=xRH;1X50$# zU5slhYkSkir-#>@IobM@-9LZpVE$4__664#r;U<(Fif+aek4~_5ISPczF+n%G&YJPZd_dwhcM)XK$a~zGT6f@?}u{2kzI_J`y5h z5613ABWPopVbs3NnT+5kv=awJUz(1+_-pXaxwBvFzTRqoHSnr!F#SULqTm#orO}0` z4PcuJ1W{iBF zKEPVWtf%|A9(S$wMs?&E%QC)W%H5Wm7d}tKyUte8et?%f`c=!1mLN-!R-v?wVf6iz z)G6X}%Z#&ODdUID)ZtFfy9=wnb=?6Uetyt)y~(QPyq;Dlr>K3}Q=wY9_%mo}MmAXZ zJ7&N&B%XPHy{2#D+xAtlZx_lo9}?@xLqFZ?+&f;mh;c-PqH;Eqf4z$u?y_pN>Q=E- ziH*-zQc@6+ub%g8PZ}Rf89BiysN>^Vu*|b~eTqQIXzO`L8nmD()4q3juuoh;Z zx{Lc)DaWwDG3=>cj9@&S2$*_OJ%}J{GTxhrCE`61Z>_G%gwd42_vIJi(910C^C-NfacQ^Sl-eB6%Xg&U!Xb8ybq}LqdnpiS{AK90(zP z1Ord7u@T6SiQp2Di3~i5N%p4%Aecz--@FL!dP@uegZ@@w_#wgnaSCT+2SQQlM9?8^ zm=*yFg@O(lXcIm0a1R|XJV6r#hr(eH8234(1v`X*>mXnTpnnFKYmn~gg}|Cy{$q~2 zLxO!63>pFg2@Vd{4%X48(!C)t0|NsH6b^yIwYVBu0W1mw&(xv>sQhLyCk7DcBpQQ6 zrGT~=@gCGb1`^D5_CHaOY5&qv0{+PqH)jwgo(6$wL${*(t!QKO|ErS8|7r&?u*CoR z`+pJ#IIw6$2$mQ?4WtvewewQhGDSn6=tMk&N_U`A{eLIY&WFmN2KZ2EAh?b;45V&@ zCy*#xlKp=}Y-|wLlmG^vLLge3Bf(q}Z4${7VPJ`Z>caJO59#RW!C)3BeO)*VWoc## zg<9yK4D<|sW6i0AKr)fS_>J}aFIMl5*sX>j)3}z+iF8sB(bJMnC4>Hs8bSKAFYrI| z{e$)VvoAV-#6q~vK(=c8ziRzk#BHFh<-g6#-Td4BL<+a(>D=bN76lY@FUB@IjDy9m z(5*YN-4s*8oj}&+rVh+L4|neH1o$j1E!71)pl~xe=$Un0lQ15DzW@MOBBhHB}+m>LbCLY=Y4wK?~kwVKJMeb&hxs?-|t+n40QpA+b4G8*k_>A)gsvzul2%)`{+ zGXO-B3u=_{$d$PU5YEZSn%Bo%6nB$X*pi8HtvlN(j>)<>oU^ms-{SJc!?CVM_kGpq zD|mb=fG|Jac@dmEE>EYKyFP!dPw~V2q0~L3V4zJ7VgZs-lDyFoU9CnK9lA z{|)s3FeAcdMKT|ltq9$x0m1;iQ-6nS!_cqj3MXxM0Gt2}LS)A!gg7{$QQxIe9%xhs z9ymYp6$g?4Aeep95(3@bioPky5s{%vM(c>C~+;D?q3rCl<9Vk3~u)C^5I%(w`)RT2PH zm)f7N?K9(ykBtnC`Hctjzt`uk1dC{xK3DmG+T--QM)Dliz9M@cHh&jC)x2t{F@ZnKih0C+}OXW@w z`v&$?T!Pj1rsQGSiPMN#jg(cf#BeEqd)~3u;mM}Qyx`i%uR_AH()f-rz&vtJ?~1BK z0wCjWh+r=QKw`~Oyt$4L(2|<}2>>cTD<8d+q=bD10syO=GrJ#HY?6E~&#jfte6C(u zt0YX=Xk{+Bqt-;ma^pzUR`Hw4DHbX&wa9MK#}7nQbGD=p$&@~a?~@uIls$T8lCHGT zTRHoMa^-n3QHw^99AP{1;ufE{Zb&OgDJ@PELckbai^>O2T$Dcqsc&TD3l~}jCU{~r zzv(gLjjtXx|H*H&$^=ebjw433!=?SMd>|aXa>3gB5?)oiL6JC$H*$+NBC6x}hAF7kW)t|J z9m26ua#NsV=VV?4pXG3D@mM_ij@FcBscZ$vT`c+>{Ka38#5<0qS`o5Kbu1s`Lk`}C ztNnHRw(Z$k$NrL*^Gd|*kZ!s*;vl|Vi-WL}unWTUV)XKz^G!Qs$eCE}Ne-py;|QoE ziVIFnDC2DAI9^+BdO1=ikF38qj1|k>fy+;lJzzvK8x_5E17Vq#bN5h7VfH)F-HXT@ zhwUgiVNOuz3x#rqq3K#J8H#9LzFuDEn{={2c`*Pw!K@JLkKSgT`X;p_=<}wD@rmf~ z;gVA4rJ@@!K08%{R8FWAD3_@~)3CQUyiHAObb-A`sHOQ|-+Z0sir>Ak`=mm`YuRLE zvRiUw^7vgB*AQ2;PWD|1mwT?8?;UeHb=$`Ek<+I_v3H91It$fZpB3&YZpDS;;+@(K zdF54mt)Bf!lqxwNW0P|pljlM#d!=%9yW%SZX%=tU#c&gu)D60B?{lPNX$l**VOcE< zdIIZ=4!P^c^-J)}8av)1B>n2);EeHy%mc04Tcui0=!xi=={@WUEb=RgEZW->(No>y zGtHP*oSy9AhtjjmvvjlOkrd=&s943GibEAK6}_QtUrgT;C)pEX^RMTnC;HoM=PBRw z=9RwiyZG%Idtrv4Jsg!__&(xHGl%#&=sLN)edgTIoh`h8iiEm=ymq_1zsj}0Uhw~9 z#8NW#s4ujm8iU4JvG{?xr?d;JWxCeN2BzQy;MMf~vb=1*A#83ixqIOEV` zVaGg#~3WwEx!kV?Q+q$;Ioo@pT$VAd^FJUK|pMWk7 z+6G@N*C4B;DJ`9n-?bZYSO3eQQfKCI=Av#Fcf@1azbbAvzVOP^{k?%t7-9b0z+hZ3 zaVn!cs{C&G8PM z+2JN0Mjo7#`(m!krk0qEMuRP#pvsP;1yp-=xo_t(VjQijbFbzedRSI|z~tIkmRs_| zzW)8E&_4stJKBW4G7xjb>97-2u07S9vv;%V`p9kjaQuUwaZ+YdW*$z8oKmXu9#*!q z%+XIrCsAsIJw|!0mU!Xy;)v!_$Xu^Na16FRuM}78B&~>r-qB$lQ9i;d$5deszcU!{ zTl=!4DREZuWEJOuQ~85O-Q_Hg*+EE+^)p4ySZAeheYhvC!k0y!={Us;;FYATIt}A- zuHORLec$46(H*yLp>@u>8zvVfHSws$-w!_}DiD%=UHO5jok!eG?^a6o;?lWyihn$? zDIXhlckt>wInSo_^n5%}_Ii2}Gnqe0E+&@qiXwmuR{ESqQ+U(U)H80A6kIb79 zf%9=Kr7f>pM2rYV(?^=0aC^Vq+>^Huk#*XW=eAmOudMomc28GLfB11cI@{U7;B zQ-8QzAye z?YX)QgQSmUMA3ROrqjb8(+}^Keqk~C{I7xACr^BG`h2tXW#7w|fwa?Q^Pou#Tc-nA z6Ux=gqvW7&R`EYy$;(ndrfyqZ_A8PP|3nOJFp782&dJ(|nq3+>oA{}~w;(&q!3^~- zt&hEkT}cb_JmgvBk8aC0Q(}I_mU%5U&3zn?_nfJue}^pk^lFtIEJ78dY$NHbLzw$V zXp^Kx-n6?(G4s3qJ66M%C`$TCPDSu}Lmjrwww;{p%X+9*d9fjae!jTBR?Bh)&695p|Np`_A@%C6Gkw(!c ztlQ|bD0BfD08GqSbOJGm#02}0{K-@lg#WAt0w(*SAnr!?Fncs1cZ-)AAzU~M!*noC|vOF)r0RvA`FmlWAHx@MBtF&>xaZy+5F>9 zprIfEOeP%(g@%WR>xUcY(-{6xxUsP@6o!Bz5PAX&y%08)Nnq(wLo|OgSdl`A3^JWb zrcuG`j07KAC=&${1pA*XDD;16sUiPVN>DQ>i$I6M^|Nl)Xlz**5m^jjZ zpQ#thS=L9?WiG40+mRzvqC`xB>H5sFVffs4KqX-!S)&$7{TGz=zWF=INHY2 z0tT}-KpPtw|HfL;h@lh`mH8X%`(G^lkJ$BrpwI=Ltw;=V7|GX$L8E~G&KgPnV=RW& zf8_fI>-)!83~m01g$ja!uJ`tT_4@agV1U-ee}`9~{5$?6s$k|Bg5ln!QST+V7#p3i zF4n&y*YC(C3v7{K(X_L&aAEcMczb*MMhV&2h)M`^tW<_XOB8+kL0OWLfY3%j)E-d2 TFC+3}9cE|kU{!4CefEC<&8td2 diff --git a/applications/external/multi_fuzzer/icons/ibutt_10px.png b/applications/external/multi_fuzzer/icons/ibutt_10px.png deleted file mode 100644 index 2fdaf123a657c00c9c84632ca3c151674e451ae1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 304 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2xkYHHq`AGmsv7|ftIx;Y9?C1WI$O_~uBzpw; zGB8xBF)%c=FfjZA3N^f7U???UV0e|lz+g3lfkC`r&aOZkpafHrx4R1i<>&pI=m5)bW_|craZ$KesPZ!4!j_b)kjvx52z42i= z^Wk##wtdVzTiGS{99;2>!TC2M!yZeXz}?LkD}l;YOI#yLQW8s2t&)pUffR$0fsvuE zfvK*cNr<75m9c@9v4ysQft7*5`ikN&C>nC}Q!>*kp&E>VdO{3LtqcsU49p-Jly36? Qy~)7f>FVdQ&MBb@0C$~I0{{R3 diff --git a/applications/external/multi_fuzzer/icons/rfid_10px.png b/applications/external/multi_fuzzer/icons/rfid_10px.png deleted file mode 100644 index 8097f477552333f695733baf98f72e52ae840206..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2389 zcmcIleQXnD7{6^!#>U44<0gnZixCj6_tCqycdm@C?Y2%j%BZlR1g>}Qb?4f<$dYAbBp?=L>s9H44zi+G=z@ z$+GSR2r_^Bu5APL?}u;SJTN7D;oJYQXJbzy83Gd~}8wyVLJ?VRDO5w9Zzw8@g?;6Z|IeLEIrsbQiG#zUHZ2RGh zXD*aUd-8i4_PpADs0}HdxBi3jq2Z#Q^P&Dr^r?|WO%j;%cM#(P)0|Mc8; z{t8g`*A^{V)i?clE}f@2H&CjT}F< z?2jues!qd7PS)z04FoBfX?^mLy)Tp_$Rt&cG?`7IrJSH9?7UT9dorQHXauRON@~2& z3QRN#VzT0~4fhY&P+9cYRxu$Wr1?OLT-T+86?9@-1c8#I)9z+Pr-LUJp%g)pI7#A^ z8>2{$U^#~a&AepaJ-|V!`|Vrt9lHFc42XX!YK-a5tz}b zn0yjbjJa6^KQIJc)=XJdPz#Zds%@sn2DzpWkAYyf`V1Rfhy zjlu{PBk2f5aSnoGTnh;YM-b`I5OjjboBR#IOoSjf8osX&Rz+FroJeRW#03?@@6WtU}<<5`k-GmIOPTuus$(zJDPkQf=2B!Xdi7eP5vyx@MnDzsUZu=b~oE2;v- z$W@bzGC*KNw}3fBr-TVK?Z%=6L1S(pkqisLNim1EOqXHr@bS^87Ap}ViVmI>S)RcJ zh9WV*(*TPy(I`d}G$F90;3Qy6q1W>I)VQjLR1sDe;)?<&sd|Ek{*e=W4B(m)v)l~P z;VJ5514`GK>5mm)eP$Jx(Uj>pUa-9Gu?d#Q0Om>GmdB{x#CWFnceDTqI*$11FhiBh z4qgY|7_9WanhU=fd4q2spSnP~C5On1n8Y&u^S%9C@(-&evej?~Ro2+Su!z(GxDJJ~1-%>yfuE?__2!EG(Q-w?lmFd+!g0U&StM zdu5hu`a91J8fzAOad#fF`0WbM%BH;qRp;*CJ^0}3t1p(6l^kTQe)IdHQfq1l0}cMe I)$5-48`f?Vpa1{> diff --git a/applications/external/multi_fuzzer/lib/worker/fake_worker.c b/applications/external/multi_fuzzer/lib/worker/fake_worker.c deleted file mode 100644 index 07b0479b4..000000000 --- a/applications/external/multi_fuzzer/lib/worker/fake_worker.c +++ /dev/null @@ -1,485 +0,0 @@ -#include "fake_worker.h" -#include "protocol_i.h" - -#include - -#include -#include -#include - -#define TAG "Fuzzer worker" - -#if defined(RFID_125_PROTOCOL) - -#include -#include -#include - -#else - -#include -#include - -#endif - -#include - -struct FuzzerWorker { -#if defined(RFID_125_PROTOCOL) - LFRFIDWorker* proto_worker; - ProtocolId protocol_id; - ProtocolDict* protocols_items; -#else - iButtonWorker* proto_worker; - iButtonProtocolId protocol_id; // TODO - iButtonProtocols* protocols_items; - iButtonKey* key; -#endif - - const FuzzerProtocol* protocol; - FuzzerWorkerAttackType attack_type; - uint16_t timer_idle_time_ms; - uint16_t timer_emu_time_ms; - - uint8_t payload[MAX_PAYLOAD_SIZE]; - Stream* uids_stream; - uint16_t index; - uint8_t chusen_byte; - - bool treead_running; - bool in_emu_phase; - FuriTimer* timer; - - FuzzerWorkerUidChagedCallback tick_callback; - void* tick_context; - - FuzzerWorkerEndCallback end_callback; - void* end_context; -}; - -static bool fuzzer_worker_load_key(FuzzerWorker* instance, bool next) { - furi_assert(instance); - furi_assert(instance->protocol); - bool res = false; - - const FuzzerProtocol* protocol = instance->protocol; - - switch(instance->attack_type) { - case FuzzerWorkerAttackTypeDefaultDict: - if(next) { - instance->index++; - } - if(instance->index < protocol->dict.len) { - memcpy( - instance->payload, - &protocol->dict.val[instance->index * protocol->data_size], - protocol->data_size); - res = true; - } - break; - - case FuzzerWorkerAttackTypeLoadFileCustomUids: { - if(next) { - instance->index++; - } - uint8_t str_len = protocol->data_size * 2 + 1; - FuriString* data_str = furi_string_alloc(); - while(true) { - furi_string_reset(data_str); - if(!stream_read_line(instance->uids_stream, data_str)) { - stream_rewind(instance->uids_stream); - // TODO Check empty file & close stream and storage - break; - } else if(furi_string_get_char(data_str, 0) == '#') { - // Skip comment string - continue; - } else if(furi_string_size(data_str) != str_len) { - // Ignore strin with bad length - FURI_LOG_W(TAG, "Bad string length"); - continue; - } else { - FURI_LOG_D(TAG, "Uid candidate: \"%s\"", furi_string_get_cstr(data_str)); - bool parse_ok = true; - for(uint8_t i = 0; i < protocol->data_size; i++) { - if(!hex_char_to_uint8( - furi_string_get_cstr(data_str)[i * 2], - furi_string_get_cstr(data_str)[i * 2 + 1], - &instance->payload[i])) { - parse_ok = false; - break; - } - } - res = parse_ok; - } - break; - } - } - - break; - - case FuzzerWorkerAttackTypeLoadFile: - if(instance->payload[instance->index] != 0xFF) { - instance->payload[instance->index]++; - res = true; - } - - break; - - default: - break; - } -#if defined(RFID_125_PROTOCOL) - protocol_dict_set_data( - instance->protocols_items, instance->protocol_id, instance->payload, MAX_PAYLOAD_SIZE); -#else - ibutton_key_set_protocol_id(instance->key, instance->protocol_id); - iButtonEditableData data; - ibutton_protocols_get_editable_data(instance->protocols_items, instance->key, &data); - - // TODO check data.size logic - data.size = MAX_PAYLOAD_SIZE; - memcpy(data.ptr, instance->payload, MAX_PAYLOAD_SIZE); // data.size); -#endif - return res; -} - -static void fuzzer_worker_on_tick_callback(void* context) { - furi_assert(context); - - FuzzerWorker* instance = context; - - if(instance->in_emu_phase) { - if(instance->treead_running) { -#if defined(RFID_125_PROTOCOL) - lfrfid_worker_stop(instance->proto_worker); -#else - ibutton_worker_stop(instance->proto_worker); -#endif - } - instance->in_emu_phase = false; - furi_timer_start(instance->timer, furi_ms_to_ticks(instance->timer_idle_time_ms)); - } else { - if(!fuzzer_worker_load_key(instance, true)) { - fuzzer_worker_pause(instance); // XXX - if(instance->end_callback) { - instance->end_callback(instance->end_context); - } - } else { - if(instance->treead_running) { -#if defined(RFID_125_PROTOCOL) - lfrfid_worker_emulate_start(instance->proto_worker, instance->protocol_id); -#else - ibutton_worker_emulate_start(instance->proto_worker, instance->key); -#endif - } - instance->in_emu_phase = true; - furi_timer_start(instance->timer, furi_ms_to_ticks(instance->timer_emu_time_ms)); - if(instance->tick_callback) { - instance->tick_callback(instance->tick_context); - } - } - } -} - -void fuzzer_worker_get_current_key(FuzzerWorker* instance, FuzzerPayload* output_key) { - furi_assert(instance); - furi_assert(output_key); - furi_assert(instance->protocol); - - output_key->data_size = instance->protocol->data_size; - memcpy(output_key->data, instance->payload, instance->protocol->data_size); -} - -static void fuzzer_worker_set_protocol(FuzzerWorker* instance, FuzzerProtocolsID protocol_index) { - instance->protocol = &fuzzer_proto_items[protocol_index]; - -#if defined(RFID_125_PROTOCOL) - instance->protocol_id = - protocol_dict_get_protocol_by_name(instance->protocols_items, instance->protocol->name); -#else - // TODO iButtonProtocolIdInvalid check - instance->protocol_id = - ibutton_protocols_get_id_by_name(instance->protocols_items, instance->protocol->name); -#endif -} - -bool fuzzer_worker_init_attack_dict(FuzzerWorker* instance, FuzzerProtocolsID protocol_index) { - furi_assert(instance); - - bool res = false; - fuzzer_worker_set_protocol(instance, protocol_index); - - instance->attack_type = FuzzerWorkerAttackTypeDefaultDict; - instance->index = 0; - - if(!fuzzer_worker_load_key(instance, false)) { - instance->attack_type = FuzzerWorkerAttackTypeMax; - } else { - res = true; - } - - return res; -} - -bool fuzzer_worker_init_attack_file_dict( - FuzzerWorker* instance, - FuzzerProtocolsID protocol_index, - FuriString* file_path) { - furi_assert(instance); - furi_assert(file_path); - - bool res = false; - fuzzer_worker_set_protocol(instance, protocol_index); - - Storage* storage = furi_record_open(RECORD_STORAGE); - instance->uids_stream = buffered_file_stream_alloc(storage); - - if(!buffered_file_stream_open( - instance->uids_stream, furi_string_get_cstr(file_path), FSAM_READ, FSOM_OPEN_EXISTING)) { - buffered_file_stream_close(instance->uids_stream); - return res; - } - - instance->attack_type = FuzzerWorkerAttackTypeLoadFileCustomUids; - instance->index = 0; - - if(!fuzzer_worker_load_key(instance, false)) { - instance->attack_type = FuzzerWorkerAttackTypeMax; - buffered_file_stream_close(instance->uids_stream); - furi_record_close(RECORD_STORAGE); - } else { - res = true; - } - - return res; -} - -bool fuzzer_worker_init_attack_bf_byte( - FuzzerWorker* instance, - FuzzerProtocolsID protocol_index, - const FuzzerPayload* new_uid, - uint8_t chusen) { - furi_assert(instance); - - bool res = false; - fuzzer_worker_set_protocol(instance, protocol_index); - - instance->attack_type = FuzzerWorkerAttackTypeLoadFile; - instance->index = chusen; - - memcpy(instance->payload, new_uid->data, instance->protocol->data_size); - - res = true; - - return res; -} - -// TODO make it protocol independent -bool fuzzer_worker_load_key_from_file( - FuzzerWorker* instance, - FuzzerProtocolsID protocol_index, - const char* filename) { - furi_assert(instance); - - bool res = false; - fuzzer_worker_set_protocol(instance, protocol_index); - -#if defined(RFID_125_PROTOCOL) - ProtocolId loaded_proto_id = lfrfid_dict_file_load(instance->protocols_items, filename); - if(loaded_proto_id == PROTOCOL_NO) { - // Err Cant load file - FURI_LOG_W(TAG, "Cant load file"); - } else if(instance->protocol_id != loaded_proto_id) { // Err wrong protocol - FURI_LOG_W(TAG, "Wrong protocol"); - FURI_LOG_W( - TAG, - "Selected: %s Loaded: %s", - instance->protocol->name, - protocol_dict_get_name(instance->protocols_items, loaded_proto_id)); - } else { - protocol_dict_get_data( - instance->protocols_items, instance->protocol_id, instance->payload, MAX_PAYLOAD_SIZE); - res = true; - } -#else - if(!ibutton_protocols_load(instance->protocols_items, instance->key, filename)) { - // Err Cant load file - FURI_LOG_W(TAG, "Cant load file"); - } else { - if(instance->protocol_id != ibutton_key_get_protocol_id(instance->key)) { - // Err wrong protocol - FURI_LOG_W(TAG, "Wrong protocol"); - FURI_LOG_W( - TAG, - "Selected: %s Loaded: %s", - instance->protocol->name, - ibutton_protocols_get_name( - instance->protocols_items, ibutton_key_get_protocol_id(instance->key))); - } else { - iButtonEditableData data; - ibutton_protocols_get_editable_data(instance->protocols_items, instance->key, &data); - memcpy(instance->payload, data.ptr, data.size); - res = true; - } - } -#endif - - return res; -} - -FuzzerWorker* fuzzer_worker_alloc() { - FuzzerWorker* instance = malloc(sizeof(FuzzerWorker)); - -#if defined(RFID_125_PROTOCOL) - instance->protocols_items = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax); - - instance->proto_worker = lfrfid_worker_alloc(instance->protocols_items); -#else - instance->protocols_items = ibutton_protocols_alloc(); - instance->key = - ibutton_key_alloc(ibutton_protocols_get_max_data_size(instance->protocols_items)); - - instance->proto_worker = ibutton_worker_alloc(instance->protocols_items); -#endif - instance->attack_type = FuzzerWorkerAttackTypeMax; - instance->index = 0; - instance->treead_running = false; - instance->in_emu_phase = false; - - memset(instance->payload, 0x00, sizeof(instance->payload)); - - instance->timer_idle_time_ms = PROTOCOL_DEF_IDLE_TIME * 100; - instance->timer_emu_time_ms = PROTOCOL_DEF_EMU_TIME * 100; - - instance->timer = - furi_timer_alloc(fuzzer_worker_on_tick_callback, FuriTimerTypeOnce, instance); - - return instance; -} - -void fuzzer_worker_free(FuzzerWorker* instance) { - furi_assert(instance); - - fuzzer_worker_stop(instance); - - furi_timer_free(instance->timer); - -#if defined(RFID_125_PROTOCOL) - lfrfid_worker_free(instance->proto_worker); - - protocol_dict_free(instance->protocols_items); -#else - ibutton_worker_free(instance->proto_worker); - - ibutton_key_free(instance->key); - ibutton_protocols_free(instance->protocols_items); -#endif - - free(instance); -} - -bool fuzzer_worker_start(FuzzerWorker* instance, uint8_t idle_time, uint8_t emu_time) { - furi_assert(instance); - - if(instance->attack_type < FuzzerWorkerAttackTypeMax) { - if(idle_time == 0) { - instance->timer_idle_time_ms = 10; - } else { - instance->timer_idle_time_ms = idle_time * 100; - } - if(emu_time == 0) { - instance->timer_emu_time_ms = 10; - } else { - instance->timer_emu_time_ms = emu_time * 100; - } - - FURI_LOG_D( - TAG, - "Emu_time %u ms Idle_time %u ms", - instance->timer_emu_time_ms, - instance->timer_idle_time_ms); - - if(!instance->treead_running) { -#if defined(RFID_125_PROTOCOL) - lfrfid_worker_start_thread(instance->proto_worker); -#else - ibutton_worker_start_thread(instance->proto_worker); -#endif - FURI_LOG_D(TAG, "Worker Starting"); - instance->treead_running = true; - } else { - FURI_LOG_D(TAG, "Worker UnPaused"); - } - -#if defined(RFID_125_PROTOCOL) - // lfrfid_worker_start_thread(instance->proto_worker); - lfrfid_worker_emulate_start(instance->proto_worker, instance->protocol_id); -#else - // ibutton_worker_start_thread(instance->proto_worker); - ibutton_worker_emulate_start(instance->proto_worker, instance->key); -#endif - instance->in_emu_phase = true; - furi_timer_start(instance->timer, furi_ms_to_ticks(instance->timer_emu_time_ms)); - return true; - } - return false; -} - -void fuzzer_worker_pause(FuzzerWorker* instance) { - furi_assert(instance); - - furi_timer_stop(instance->timer); - - if(instance->treead_running) { -#if defined(RFID_125_PROTOCOL) - lfrfid_worker_stop(instance->proto_worker); -#else - ibutton_worker_stop(instance->proto_worker); -#endif - FURI_LOG_D(TAG, "Worker Paused"); - } -} - -void fuzzer_worker_stop(FuzzerWorker* instance) { - furi_assert(instance); - - furi_timer_stop(instance->timer); - - if(instance->treead_running) { -#if defined(RFID_125_PROTOCOL) - lfrfid_worker_stop(instance->proto_worker); - lfrfid_worker_stop_thread(instance->proto_worker); -#else - ibutton_worker_stop(instance->proto_worker); - ibutton_worker_stop_thread(instance->proto_worker); -#endif - FURI_LOG_D(TAG, "Worker Stopping"); - instance->treead_running = false; - } - - if(instance->attack_type == FuzzerWorkerAttackTypeLoadFileCustomUids) { - buffered_file_stream_close(instance->uids_stream); - furi_record_close(RECORD_STORAGE); - instance->attack_type = FuzzerWorkerAttackTypeMax; - } - - // TODO anything else -} - -void fuzzer_worker_set_uid_chaged_callback( - FuzzerWorker* instance, - FuzzerWorkerUidChagedCallback callback, - void* context) { - furi_assert(instance); - instance->tick_callback = callback; - instance->tick_context = context; -} - -void fuzzer_worker_set_end_callback( - FuzzerWorker* instance, - FuzzerWorkerEndCallback callback, - void* context) { - furi_assert(instance); - instance->end_callback = callback; - instance->end_context = context; -} diff --git a/applications/external/multi_fuzzer/lib/worker/fake_worker.h b/applications/external/multi_fuzzer/lib/worker/fake_worker.h deleted file mode 100644 index 8b934f300..000000000 --- a/applications/external/multi_fuzzer/lib/worker/fake_worker.h +++ /dev/null @@ -1,138 +0,0 @@ -#pragma once - -#include - -#include "protocol.h" - -typedef enum { - FuzzerWorkerAttackTypeDefaultDict = 0, - FuzzerWorkerAttackTypeLoadFile, - FuzzerWorkerAttackTypeLoadFileCustomUids, - - FuzzerWorkerAttackTypeMax, -} FuzzerWorkerAttackType; - -typedef void (*FuzzerWorkerUidChagedCallback)(void* context); -typedef void (*FuzzerWorkerEndCallback)(void* context); - -typedef struct FuzzerWorker FuzzerWorker; - -/** - * Allocate FuzzerWorker - * - * @return FuzzerWorker* pointer to FuzzerWorker - */ -FuzzerWorker* fuzzer_worker_alloc(); - -/** - * Free FuzzerWorker - * - * @param instance Pointer to a FuzzerWorker - */ -void fuzzer_worker_free(FuzzerWorker* instance); - -/** - * Start or continue emulation - * - * @param instance Pointer to a FuzzerWorker - * @param idle_time Delay between emulations in tenths of a second - * @param emu_time Emulation time of one UID in tenths of a second - * @return bool True if emulation has started - */ -bool fuzzer_worker_start(FuzzerWorker* instance, uint8_t idle_time, uint8_t emu_time); - -/** - * Stop emulation and deinit worker - * - * @param instance Pointer to a FuzzerWorker - */ -void fuzzer_worker_stop(FuzzerWorker* instance); - -/** - * Suspend emulation - * - * @param instance Pointer to a FuzzerWorker - */ -void fuzzer_worker_pause(FuzzerWorker* instance); - -/** - * Init attack by default dictionary - * - * @param instance Pointer to a FuzzerWorker - * @param protocol_index index of the selected protocol - * @return bool True if initialization is successful - */ -bool fuzzer_worker_init_attack_dict(FuzzerWorker* instance, FuzzerProtocolsID protocol_index); - -/** - * Init attack by custom dictionary - * - * @param instance Pointer to a FuzzerWorker - * @param protocol_index index of the selected protocol - * @param file_path file path to the dictionary - * @return bool True if initialization is successful - */ -bool fuzzer_worker_init_attack_file_dict( - FuzzerWorker* instance, - FuzzerProtocolsID protocol_index, - FuriString* file_path); - -/** - * Init attack brute force one of byte - * - * @param instance Pointer to a FuzzerWorker - * @param protocol_index index of the selected protocol - * @param new_uid Pointer to a FuzzerPayload with UID for brute force - * @param chosen index of chusen byte - * @return bool True if initialization is successful - */ -bool fuzzer_worker_init_attack_bf_byte( - FuzzerWorker* instance, - FuzzerProtocolsID protocol_index, - const FuzzerPayload* new_uid, - uint8_t chusen); - -/** - * Get current UID - * - * @param instance Pointer to a FuzzerWorker - * @param output_key Pointer to a FuzzerPayload - */ -void fuzzer_worker_get_current_key(FuzzerWorker* instance, FuzzerPayload* output_key); - -/** - * Load UID from Flipper Format Key file - * - * @param instance Pointer to a FuzzerWorker - * @param protocol_index index of the selected protocol - * @param filename file path to the key file - * @return bool True if loading is successful - */ -bool fuzzer_worker_load_key_from_file( - FuzzerWorker* instance, - FuzzerProtocolsID protocol_index, - const char* filename); - -/** - * Set callback for uid changed - * - * @param instance Pointer to a FuzzerWorker - * @param callback Callback for uid changed - * @param context Context for callback - */ -void fuzzer_worker_set_uid_chaged_callback( - FuzzerWorker* instance, - FuzzerWorkerUidChagedCallback callback, - void* context); - -/** - * Set callback for end of emulation - * - * @param instance Pointer to a FuzzerWorker - * @param callback Callback for end of emulation - * @param context Context for callback - */ -void fuzzer_worker_set_end_callback( - FuzzerWorker* instance, - FuzzerWorkerEndCallback callback, - void* context); \ No newline at end of file diff --git a/applications/external/multi_fuzzer/lib/worker/protocol.c b/applications/external/multi_fuzzer/lib/worker/protocol.c deleted file mode 100644 index a64fe8767..000000000 --- a/applications/external/multi_fuzzer/lib/worker/protocol.c +++ /dev/null @@ -1,291 +0,0 @@ -#include "protocol_i.h" -#include "furi.h" - -// ####################### -// ## Ibutton Protocols ## -// ####################### -#define DS1990_DATA_SIZE (8) -#define Metakom_DATA_SIZE (4) -#define Cyfral_DATA_SIZE (2) - -const uint8_t uid_list_ds1990[][DS1990_DATA_SIZE] = { - {0x01, 0xBE, 0x40, 0x11, 0x5A, 0x36, 0x00, 0xE1}, //– код универÑального ключа, Ð´Ð»Ñ Vizit - {0x01, 0xBE, 0x40, 0x11, 0x5A, 0x56, 0x00, 0xBB}, //- проверен работает - {0x01, 0xBE, 0x40, 0x11, 0x00, 0x00, 0x00, 0x77}, //- проверен работает - {0x01, 0xBE, 0x40, 0x11, 0x0A, 0x00, 0x00, 0x1D}, //- проверен работает Визит иногда КЕЙМÐÐЫ - {0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x2F}, //- проверен(метаком, цифрал, ВИЗИТ). - {0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x9B}, //- проверен Визит, Метакомы, КОÐДОР - {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x14}, //???-Открываает 98% Метаком и некоторые Цифрал - {0x01, 0x00, 0x00, 0x00, 0x00, 0x90, 0x19, 0xFF}, //???-Отлично работает на Ñтарых домофонах - {0x01, 0x6F, 0x2E, 0x88, 0x8A, 0x00, 0x00, 0x4D}, //???-Открывать что-то должен - {0x01, 0x53, 0xD4, 0xFE, 0x00, 0x00, 0x7E, 0x88}, //???-Cyfral, Metakom - {0x01, 0x53, 0xD4, 0xFE, 0x00, 0x00, 0x00, 0x6F}, //???-домофоны Визит (Vizit) - до 99% - {0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3D}, //???-домофоны Cyfral CCD-20 - до 70% - {0x01, 0x00, 0xBE, 0x11, 0xAA, 0x00, 0x00, 0xFB}, //???-домофоны Кейман (KEYMAN) - {0x01, 0x76, 0xB8, 0x2E, 0x0F, 0x00, 0x00, 0x5C}, //???-домофоны Форвард - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // Null bytes - {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x14}, // Only FF - {0x01, 0x78, 0x00, 0x48, 0xFD, 0xFF, 0xFF, 0xD1}, // StarNew Uni5 - {0x01, 0xA9, 0xE4, 0x3C, 0x09, 0x00, 0x00, 0xE6}, // Eltis Uni -}; - -const uint8_t uid_list_metakom[][Metakom_DATA_SIZE] = { - {0x00, 0x00, 0x00, 0x00}, // Null bytes - {0xFF, 0xFF, 0xFF, 0xFF}, // Only FF - {0x11, 0x11, 0x11, 0x11}, // Only 11 - {0x22, 0x22, 0x22, 0x22}, // Only 22 - {0x33, 0x33, 0x33, 0x33}, // Only 33 - {0x44, 0x44, 0x44, 0x44}, // Only 44 - {0x55, 0x55, 0x55, 0x55}, // Only 55 - {0x66, 0x66, 0x66, 0x66}, // Only 66 - {0x77, 0x77, 0x77, 0x77}, // Only 77 - {0x88, 0x88, 0x88, 0x88}, // Only 88 - {0x99, 0x99, 0x99, 0x99}, // Only 99 - {0x12, 0x34, 0x56, 0x78}, // Incremental UID - {0x9A, 0x78, 0x56, 0x34}, // Decremental UID - {0x04, 0xd0, 0x9b, 0x0d}, // ?? - {0x34, 0x00, 0x29, 0x3d}, // ?? - {0x04, 0xdf, 0x00, 0x00}, // ?? - {0xCA, 0xCA, 0xCA, 0xCA}, // ?? -}; - -const uint8_t uid_list_cyfral[][Cyfral_DATA_SIZE] = { - {0x00, 0x00}, // Null bytes - {0xFF, 0xFF}, // Only FF - {0x11, 0x11}, // Only 11 - {0x22, 0x22}, // Only 22 - {0x33, 0x33}, // Only 33 - {0x44, 0x44}, // Only 44 - {0x55, 0x55}, // Only 55 - {0x66, 0x66}, // Only 66 - {0x77, 0x77}, // Only 77 - {0x88, 0x88}, // Only 88 - {0x99, 0x99}, // Only 99 - {0x12, 0x34}, // Incremental UID - {0x56, 0x34}, // Decremental UID - {0xCA, 0xCA}, // ?? - {0x8E, 0xC9}, // Elevator code - {0x6A, 0x50}, // VERY fresh code from smartkey -}; - -// ########################### -// ## Rfid_125khz Protocols ## -// ########################### -#define EM4100_DATA_SIZE (5) -#define HIDProx_DATA_SIZE (6) -#define PAC_DATA_SIZE (4) -#define H10301_DATA_SIZE (3) - -const uint8_t uid_list_em4100[][EM4100_DATA_SIZE] = { - {0x00, 0x00, 0x00, 0x00, 0x00}, // Null bytes - {0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, // Only FF - {0x11, 0x11, 0x11, 0x11, 0x11}, // Only 11 - {0x22, 0x22, 0x22, 0x22, 0x22}, // Only 22 - {0x33, 0x33, 0x33, 0x33, 0x33}, // Only 33 - {0x44, 0x44, 0x44, 0x44, 0x44}, // Only 44 - {0x55, 0x55, 0x55, 0x55, 0x55}, // Only 55 - {0x66, 0x66, 0x66, 0x66, 0x66}, // Only 66 - {0x77, 0x77, 0x77, 0x77, 0x77}, // Only 77 - {0x88, 0x88, 0x88, 0x88, 0x88}, // Only 88 - {0x99, 0x99, 0x99, 0x99, 0x99}, // Only 99 - {0x12, 0x34, 0x56, 0x78, 0x9A}, // Incremental UID - {0x9A, 0x78, 0x56, 0x34, 0x12}, // Decremental UID - {0x04, 0xd0, 0x9b, 0x0d, 0x6a}, // From arha - {0x34, 0x00, 0x29, 0x3d, 0x9e}, // From arha - {0x04, 0xdf, 0x00, 0x00, 0x01}, // From arha - {0xCA, 0xCA, 0xCA, 0xCA, 0xCA}, // From arha -}; - -const uint8_t uid_list_hid[][HIDProx_DATA_SIZE] = { - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // Null bytes - {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, // Only FF - {0x11, 0x11, 0x11, 0x11, 0x11, 0x11}, // Only 11 - {0x22, 0x22, 0x22, 0x22, 0x22, 0x22}, // Only 22 - {0x33, 0x33, 0x33, 0x33, 0x33, 0x33}, // Only 33 - {0x44, 0x44, 0x44, 0x44, 0x44, 0x44}, // Only 44 - {0x55, 0x55, 0x55, 0x55, 0x55, 0x55}, // Only 55 - {0x66, 0x66, 0x66, 0x66, 0x66, 0x66}, // Only 66 - {0x77, 0x77, 0x77, 0x77, 0x77, 0x77}, // Only 77 - {0x88, 0x88, 0x88, 0x88, 0x88, 0x88}, // Only 88 - {0x99, 0x99, 0x99, 0x99, 0x99, 0x99}, // Only 99 - {0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC}, // Incremental UID - {0xBC, 0x9A, 0x78, 0x56, 0x34, 0x12}, // Decremental UID - {0xCA, 0xCA, 0xCA, 0xCA, 0xCA, 0xCA}, // From arha -}; - -const uint8_t uid_list_pac[][PAC_DATA_SIZE] = { - {0x00, 0x00, 0x00, 0x00}, // Null bytes - {0xFF, 0xFF, 0xFF, 0xFF}, // Only FF - {0x11, 0x11, 0x11, 0x11}, // Only 11 - {0x22, 0x22, 0x22, 0x22}, // Only 22 - {0x33, 0x33, 0x33, 0x33}, // Only 33 - {0x44, 0x44, 0x44, 0x44}, // Only 44 - {0x55, 0x55, 0x55, 0x55}, // Only 55 - {0x66, 0x66, 0x66, 0x66}, // Only 66 - {0x77, 0x77, 0x77, 0x77}, // Only 77 - {0x88, 0x88, 0x88, 0x88}, // Only 88 - {0x99, 0x99, 0x99, 0x99}, // Only 99 - {0x12, 0x34, 0x56, 0x78}, // Incremental UID - {0x9A, 0x78, 0x56, 0x34}, // Decremental UID - {0x04, 0xd0, 0x9b, 0x0d}, // From arha - {0x34, 0x00, 0x29, 0x3d}, // From arha - {0x04, 0xdf, 0x00, 0x00}, // From arha - {0xCA, 0xCA, 0xCA, 0xCA}, // From arha -}; - -const uint8_t uid_list_h10301[][H10301_DATA_SIZE] = { - {0x00, 0x00, 0x00}, // Null bytes - {0xFF, 0xFF, 0xFF}, // Only FF - {0x11, 0x11, 0x11}, // Only 11 - {0x22, 0x22, 0x22}, // Only 22 - {0x33, 0x33, 0x33}, // Only 33 - {0x44, 0x44, 0x44}, // Only 44 - {0x55, 0x55, 0x55}, // Only 55 - {0x66, 0x66, 0x66}, // Only 66 - {0x77, 0x77, 0x77}, // Only 77 - {0x88, 0x88, 0x88}, // Only 88 - {0x99, 0x99, 0x99}, // Only 99 - {0x12, 0x34, 0x56}, // Incremental UID - {0x56, 0x34, 0x12}, // Decremental UID - {0xCA, 0xCA, 0xCA}, // From arha -}; - -#if defined(RFID_125_PROTOCOL) -const FuzzerProtocol fuzzer_proto_items[] = { - [EM4100] = - { - .name = "EM4100", - .data_size = EM4100_DATA_SIZE, - .dict = - { - .val = (const uint8_t*)&uid_list_em4100, - .len = COUNT_OF(uid_list_em4100), - }, - }, - [HIDProx] = - { - .name = "HIDProx", - .data_size = HIDProx_DATA_SIZE, - .dict = - { - .val = (const uint8_t*)&uid_list_hid, - .len = COUNT_OF(uid_list_hid), - }, - }, - [PAC] = - { - .name = "PAC/Stanley", - .data_size = PAC_DATA_SIZE, - .dict = - { - .val = (const uint8_t*)&uid_list_pac, - .len = COUNT_OF(uid_list_pac), - }, - }, - [H10301] = - { - .name = "H10301", - .data_size = H10301_DATA_SIZE, - .dict = - { - .val = (const uint8_t*)&uid_list_h10301, - .len = COUNT_OF(uid_list_h10301), - }, - }, -}; -#else -const FuzzerProtocol fuzzer_proto_items[] = { - [DS1990] = - { - .name = "DS1990", - .data_size = DS1990_DATA_SIZE, - .dict = - { - .val = (const uint8_t*)&uid_list_ds1990, - .len = COUNT_OF(uid_list_ds1990), - }, - }, - [Metakom] = - { - .name = "Metakom", - .data_size = Metakom_DATA_SIZE, - .dict = - { - .val = (const uint8_t*)&uid_list_metakom, - .len = COUNT_OF(uid_list_metakom), - }, - }, - [Cyfral] = - { - .name = "Cyfral", - .data_size = Cyfral_DATA_SIZE, - .dict = - { - .val = (const uint8_t*)&uid_list_cyfral, - .len = COUNT_OF(uid_list_cyfral), - }, - }, -}; -#endif - -typedef struct { - const char* menu_label; - FuzzerAttackId attack_id; -} FuzzerMenuItems; - -const FuzzerMenuItems fuzzer_menu_items[] = { - {"Default Values", FuzzerAttackIdDefaultValues}, -#ifdef RFID_125_PROTOCOL - {"BF Customer ID", FuzzerAttackIdBFCustomerID}, -#endif - {"Load File", FuzzerAttackIdLoadFile}, - {"Load UIDs from file", FuzzerAttackIdLoadFileCustomUids}, -}; - -FuzzerPayload* fuzzer_payload_alloc() { - FuzzerPayload* payload = malloc(sizeof(FuzzerPayload)); - payload->data = malloc(sizeof(payload->data[0]) * MAX_PAYLOAD_SIZE); - - return payload; -} - -void fuzzer_payload_free(FuzzerPayload* payload) { - furi_assert(payload); - - if(payload->data) { - free(payload->data); - } - free(payload); -} - -const char* fuzzer_proto_get_name(FuzzerProtocolsID index) { - return fuzzer_proto_items[index].name; -} - -uint8_t fuzzer_proto_get_count_of_protocols() { - return COUNT_OF(fuzzer_proto_items); -} - -uint8_t fuzzer_proto_get_max_data_size() { - return MAX_PAYLOAD_SIZE; -} - -uint8_t fuzzer_proto_get_def_emu_time() { - return PROTOCOL_DEF_EMU_TIME; -} - -uint8_t fuzzer_proto_get_def_idle_time() { - return PROTOCOL_DEF_IDLE_TIME; -} - -const char* fuzzer_proto_get_menu_label(uint8_t index) { - return fuzzer_menu_items[index].menu_label; -} - -FuzzerAttackId fuzzer_proto_get_attack_id_by_index(uint8_t index) { - return fuzzer_menu_items[index].attack_id; -} - -uint8_t fuzzer_proto_get_count_of_menu_items() { - return COUNT_OF(fuzzer_menu_items); -} diff --git a/applications/external/multi_fuzzer/lib/worker/protocol.h b/applications/external/multi_fuzzer/lib/worker/protocol.h deleted file mode 100644 index 9c5315d00..000000000 --- a/applications/external/multi_fuzzer/lib/worker/protocol.h +++ /dev/null @@ -1,89 +0,0 @@ -#pragma once - -#include - -// #define RFID_125_PROTOCOL - -typedef struct FuzzerPayload FuzzerPayload; - -typedef enum { -#if defined(RFID_125_PROTOCOL) - EM4100, - HIDProx, - PAC, - H10301, -#else - DS1990, - Metakom, - Cyfral, -#endif -} FuzzerProtocolsID; - -typedef enum { - FuzzerAttackIdDefaultValues = 0, - FuzzerAttackIdLoadFile, - FuzzerAttackIdLoadFileCustomUids, - FuzzerAttackIdBFCustomerID, -} FuzzerAttackId; - -struct FuzzerPayload { - uint8_t* data; - uint8_t data_size; -}; - -/** - * Allocate FuzzerPayload - * - * @return FuzzerPayload* pointer to FuzzerPayload - */ -FuzzerPayload* fuzzer_payload_alloc(); - -/** - * Free FuzzerPayload - * - * @param instance Pointer to a FuzzerPayload - */ -void fuzzer_payload_free(FuzzerPayload*); - -/** - * Get maximum length of UID among all supported protocols - * @return Maximum length of UID - */ -uint8_t fuzzer_proto_get_max_data_size(); - -// TODO add description -uint8_t fuzzer_proto_get_def_emu_time(); -uint8_t fuzzer_proto_get_def_idle_time(); - -/** - * Get protocol name based on its index - * @param index protocol index - * @return pointer to a string containing the name - */ -const char* fuzzer_proto_get_name(FuzzerProtocolsID index); - -/** - * Get number of protocols - * @return number of protocols - */ -uint8_t fuzzer_proto_get_count_of_protocols(); - -/** - * Get menu label based on its index - * @param index menu index - * @return pointer to a string containing the menu label - */ -const char* fuzzer_proto_get_menu_label(uint8_t index); - -/** - * Get FuzzerAttackId based on its index - * @param index menu index - * @return FuzzerAttackId - */ -FuzzerAttackId fuzzer_proto_get_attack_id_by_index(uint8_t index); - -/** - * Get number of menu items - * @return number of menu items - */ -uint8_t fuzzer_proto_get_count_of_menu_items(); \ No newline at end of file diff --git a/applications/external/multi_fuzzer/lib/worker/protocol_i.h b/applications/external/multi_fuzzer/lib/worker/protocol_i.h deleted file mode 100644 index 2f1c65fd7..000000000 --- a/applications/external/multi_fuzzer/lib/worker/protocol_i.h +++ /dev/null @@ -1,43 +0,0 @@ -#pragma once - -#include "protocol.h" - -#if defined(RFID_125_PROTOCOL) -#define MAX_PAYLOAD_SIZE (6) -#define PROTOCOL_DEF_IDLE_TIME (4) -#define PROTOCOL_DEF_EMU_TIME (5) -#define PROTOCOL_TIME_DELAY_MIN PROTOCOL_DEF_IDLE_TIME + PROTOCOL_DEF_EMU_TIME -#else -#define MAX_PAYLOAD_SIZE (8) -#define PROTOCOL_DEF_IDLE_TIME (2) -#define PROTOCOL_DEF_EMU_TIME (2) -#define PROTOCOL_TIME_DELAY_MIN PROTOCOL_DEF_IDLE_TIME + PROTOCOL_DEF_EMU_TIME -#endif - -typedef struct ProtoDict ProtoDict; -typedef struct FuzzerProtocol FuzzerProtocol; - -struct ProtoDict { - const uint8_t* val; - const uint8_t len; -}; - -struct FuzzerProtocol { - const char* name; - const uint8_t data_size; - const ProtoDict dict; -}; - -// #define MAX_PAYLOAD_SIZE 6 - -// #define FUZZ_TIME_DELAY_MIN (5) -// #define FUZZ_TIME_DELAY_DEFAULT (10) -// #define FUZZ_TIME_DELAY_MAX (70) - -// #define MAX_PAYLOAD_SIZE 8 - -// #define FUZZ_TIME_DELAY_MIN (4) -// #define FUZZ_TIME_DELAY_DEFAULT (8) -// #define FUZZ_TIME_DELAY_MAX (80) - -extern const FuzzerProtocol fuzzer_proto_items[]; \ No newline at end of file diff --git a/applications/external/multi_fuzzer/scenes/fuzzer_scene.c b/applications/external/multi_fuzzer/scenes/fuzzer_scene.c deleted file mode 100644 index 0fe0f558d..000000000 --- a/applications/external/multi_fuzzer/scenes/fuzzer_scene.c +++ /dev/null @@ -1,30 +0,0 @@ -#include "fuzzer_scene.h" - -// Generate scene on_enter handlers array -#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, -void (*const fuzzer_scene_on_enter_handlers[])(void*) = { -#include "fuzzer_scene_config.h" -}; -#undef ADD_SCENE - -// Generate scene on_event handlers array -#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event, -bool (*const fuzzer_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = { -#include "fuzzer_scene_config.h" -}; -#undef ADD_SCENE - -// Generate scene on_exit handlers array -#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit, -void (*const fuzzer_scene_on_exit_handlers[])(void* context) = { -#include "fuzzer_scene_config.h" -}; -#undef ADD_SCENE - -// Initialize scene handlers configuration structure -const SceneManagerHandlers fuzzer_scene_handlers = { - .on_enter_handlers = fuzzer_scene_on_enter_handlers, - .on_event_handlers = fuzzer_scene_on_event_handlers, - .on_exit_handlers = fuzzer_scene_on_exit_handlers, - .scene_num = FuzzerSceneNum, -}; diff --git a/applications/external/multi_fuzzer/scenes/fuzzer_scene.h b/applications/external/multi_fuzzer/scenes/fuzzer_scene.h deleted file mode 100644 index 280654510..000000000 --- a/applications/external/multi_fuzzer/scenes/fuzzer_scene.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -#include - -// Generate scene id and total number -#define ADD_SCENE(prefix, name, id) FuzzerScene##id, -typedef enum { -#include "fuzzer_scene_config.h" - FuzzerSceneNum, -} FuzzerScene; -#undef ADD_SCENE - -extern const SceneManagerHandlers fuzzer_scene_handlers; - -// Generate scene on_enter handlers declaration -#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); -#include "fuzzer_scene_config.h" -#undef ADD_SCENE - -// Generate scene on_event handlers declaration -#define ADD_SCENE(prefix, name, id) \ - bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event); -#include "fuzzer_scene_config.h" -#undef ADD_SCENE - -// Generate scene on_exit handlers declaration -#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context); -#include "fuzzer_scene_config.h" -#undef ADD_SCENE diff --git a/applications/external/multi_fuzzer/scenes/fuzzer_scene_attack.c b/applications/external/multi_fuzzer/scenes/fuzzer_scene_attack.c deleted file mode 100644 index 6424e62b5..000000000 --- a/applications/external/multi_fuzzer/scenes/fuzzer_scene_attack.c +++ /dev/null @@ -1,162 +0,0 @@ -#include "../fuzzer_i.h" -#include "../helpers/fuzzer_custom_event.h" - -const NotificationSequence sequence_one_green_50_on_blink_blue = { - &message_red_255, - &message_delay_50, - &message_red_0, - &message_blink_start_10, - &message_blink_set_color_blue, - &message_do_not_reset, - NULL, -}; - -static void fuzzer_scene_attack_update_uid(PacsFuzzerApp* app) { - furi_assert(app); - furi_assert(app->worker); - furi_assert(app->attack_view); - - fuzzer_worker_get_current_key(app->worker, app->payload); - - fuzzer_view_attack_set_uid(app->attack_view, app->payload); -} - -static void fuzzer_scene_attack_set_state(PacsFuzzerApp* app, FuzzerAttackState state) { - furi_assert(app); - - scene_manager_set_scene_state(app->scene_manager, FuzzerSceneAttack, state); - switch(state) { - case FuzzerAttackStateIdle: - notification_message(app->notifications, &sequence_blink_stop); - fuzzer_view_attack_pause(app->attack_view); - break; - - case FuzzerAttackStateRunning: - notification_message(app->notifications, &sequence_blink_start_blue); - fuzzer_view_attack_start(app->attack_view); - break; - - case FuzzerAttackStateEnd: - notification_message(app->notifications, &sequence_blink_stop); - notification_message(app->notifications, &sequence_single_vibro); - fuzzer_view_attack_end(app->attack_view); - break; - - case FuzzerAttackStateOff: - notification_message(app->notifications, &sequence_blink_stop); - fuzzer_view_attack_stop(app->attack_view); - break; - } -} - -void fuzzer_scene_attack_worker_tick_callback(void* context) { - furi_assert(context); - PacsFuzzerApp* app = context; - - notification_message(app->notifications, &sequence_one_green_50_on_blink_blue); - fuzzer_scene_attack_update_uid(app); - - // view_dispatcher_send_custom_event(app->view_dispatcher, FuzzerCustomEventViewAttackTick); -} - -void fuzzer_scene_attack_worker_end_callback(void* context) { - furi_assert(context); - PacsFuzzerApp* app = context; - view_dispatcher_send_custom_event(app->view_dispatcher, FuzzerCustomEventViewAttackEnd); -} - -void fuzzer_scene_attack_callback(FuzzerCustomEvent event, void* context) { - furi_assert(context); - PacsFuzzerApp* app = context; - view_dispatcher_send_custom_event(app->view_dispatcher, event); -} - -void fuzzer_scene_attack_on_enter(void* context) { - furi_assert(context); - PacsFuzzerApp* app = context; - - fuzzer_view_attack_set_callback(app->attack_view, fuzzer_scene_attack_callback, app); - - fuzzer_worker_set_uid_chaged_callback( - app->worker, fuzzer_scene_attack_worker_tick_callback, app); - - fuzzer_worker_set_end_callback(app->worker, fuzzer_scene_attack_worker_end_callback, app); - - fuzzer_view_attack_reset_data( - app->attack_view, - fuzzer_proto_get_menu_label(app->fuzzer_state.menu_index), - fuzzer_proto_get_name(app->fuzzer_state.proto_index)); - - fuzzer_scene_attack_update_uid(app); - - scene_manager_set_scene_state(app->scene_manager, FuzzerSceneAttack, FuzzerAttackStateIdle); - - view_dispatcher_switch_to_view(app->view_dispatcher, FuzzerViewIDAttack); -} - -bool fuzzer_scene_attack_on_event(void* context, SceneManagerEvent event) { - furi_assert(context); - PacsFuzzerApp* app = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == FuzzerCustomEventViewAttackBack) { - if(scene_manager_get_scene_state(app->scene_manager, FuzzerSceneAttack) == - FuzzerAttackStateRunning) { - // Pause if attack running - fuzzer_worker_pause(app->worker); - - fuzzer_scene_attack_set_state(app, FuzzerAttackStateIdle); - } else { - // Exit - fuzzer_worker_stop(app->worker); - - fuzzer_scene_attack_set_state(app, FuzzerAttackStateOff); - if(!scene_manager_previous_scene(app->scene_manager)) { - scene_manager_stop(app->scene_manager); - view_dispatcher_stop(app->view_dispatcher); - } - } - consumed = true; - } else if(event.event == FuzzerCustomEventViewAttackOk) { - if(scene_manager_get_scene_state(app->scene_manager, FuzzerSceneAttack) == - FuzzerAttackStateIdle) { - // Start or Continue Attack - if(fuzzer_worker_start( - app->worker, - fuzzer_view_attack_get_time_delay(app->attack_view), - fuzzer_view_attack_get_emu_time(app->attack_view))) { - fuzzer_scene_attack_set_state(app, FuzzerAttackStateRunning); - } else { - // Error? - } - } else if( - scene_manager_get_scene_state(app->scene_manager, FuzzerSceneAttack) == - FuzzerAttackStateRunning) { - // Pause if attack running - fuzzer_worker_pause(app->worker); - - fuzzer_scene_attack_set_state(app, FuzzerAttackStateIdle); - } - consumed = true; - // } else if(event.event == FuzzerCustomEventViewAttackTick) { - // consumed = true; - } else if(event.event == FuzzerCustomEventViewAttackEnd) { - fuzzer_scene_attack_set_state(app, FuzzerAttackStateEnd); - consumed = true; - } - } - - return consumed; -} - -void fuzzer_scene_attack_on_exit(void* context) { - furi_assert(context); - PacsFuzzerApp* app = context; - - // XXX the scene has no descendants, and the return will be processed in on_event - // fuzzer_worker_stop(); - - fuzzer_worker_set_uid_chaged_callback(app->worker, NULL, NULL); - fuzzer_worker_set_end_callback(app->worker, NULL, NULL); -} diff --git a/applications/external/multi_fuzzer/scenes/fuzzer_scene_config.h b/applications/external/multi_fuzzer/scenes/fuzzer_scene_config.h deleted file mode 100644 index 711ebe1c4..000000000 --- a/applications/external/multi_fuzzer/scenes/fuzzer_scene_config.h +++ /dev/null @@ -1,3 +0,0 @@ -ADD_SCENE(fuzzer, main, Main) -ADD_SCENE(fuzzer, attack, Attack) -ADD_SCENE(fuzzer, field_editor, FieldEditor) \ No newline at end of file diff --git a/applications/external/multi_fuzzer/scenes/fuzzer_scene_field_editor.c b/applications/external/multi_fuzzer/scenes/fuzzer_scene_field_editor.c deleted file mode 100644 index ccea123dc..000000000 --- a/applications/external/multi_fuzzer/scenes/fuzzer_scene_field_editor.c +++ /dev/null @@ -1,66 +0,0 @@ -#include "../fuzzer_i.h" -#include "../helpers/fuzzer_custom_event.h" - -void fuzzer_scene_field_editor_callback(FuzzerCustomEvent event, void* context) { - furi_assert(context); - PacsFuzzerApp* app = context; - view_dispatcher_send_custom_event(app->view_dispatcher, event); -} - -void fuzzer_scene_field_editor_on_enter(void* context) { - furi_assert(context); - PacsFuzzerApp* app = context; - - fuzzer_view_field_editor_set_callback( - app->field_editor_view, fuzzer_scene_field_editor_callback, app); - - fuzzer_worker_get_current_key(app->worker, app->payload); - - switch(scene_manager_get_scene_state(app->scene_manager, FuzzerSceneFieldEditor)) { - case FuzzerFieldEditorStateEditingOn: - fuzzer_view_field_editor_reset_data(app->field_editor_view, app->payload, true); - break; - - case FuzzerFieldEditorStateEditingOff: - fuzzer_view_field_editor_reset_data(app->field_editor_view, app->payload, false); - break; - - default: - break; - } - - view_dispatcher_switch_to_view(app->view_dispatcher, FuzzerViewIDFieldEditor); -} - -bool fuzzer_scene_field_editor_on_event(void* context, SceneManagerEvent event) { - furi_assert(context); - PacsFuzzerApp* app = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == FuzzerCustomEventViewFieldEditorBack) { - if(!scene_manager_previous_scene(app->scene_manager)) { - scene_manager_stop(app->scene_manager); - view_dispatcher_stop(app->view_dispatcher); - } - consumed = true; - } else if(event.event == FuzzerCustomEventViewFieldEditorOk) { - fuzzer_view_field_editor_get_uid(app->field_editor_view, app->payload); - if(fuzzer_worker_init_attack_bf_byte( - app->worker, - app->fuzzer_state.proto_index, - app->payload, - fuzzer_view_field_editor_get_index(app->field_editor_view))) { - scene_manager_next_scene(app->scene_manager, FuzzerSceneAttack); - } - } - } - - return consumed; -} - -void fuzzer_scene_field_editor_on_exit(void* context) { - // furi_assert(context); - // PacsFuzzerApp* app = context; - UNUSED(context); -} diff --git a/applications/external/multi_fuzzer/scenes/fuzzer_scene_main.c b/applications/external/multi_fuzzer/scenes/fuzzer_scene_main.c deleted file mode 100644 index 0d074a121..000000000 --- a/applications/external/multi_fuzzer/scenes/fuzzer_scene_main.c +++ /dev/null @@ -1,189 +0,0 @@ -#include "../fuzzer_i.h" -#include "../helpers/fuzzer_custom_event.h" - -#include "../lib/worker/protocol.h" - -void fuzzer_scene_main_callback(FuzzerCustomEvent event, void* context) { - furi_assert(context); - PacsFuzzerApp* app = context; - view_dispatcher_send_custom_event(app->view_dispatcher, event); -} - -void fuzzer_scene_main_error_popup_callback(void* context) { - PacsFuzzerApp* app = context; - notification_message(app->notifications, &sequence_reset_rgb); - view_dispatcher_send_custom_event(app->view_dispatcher, FuzzerCustomEventViewMainPopupErr); -} - -static bool fuzzer_scene_main_load_custom_dict(void* context) { - furi_assert(context); - PacsFuzzerApp* app = context; - - FuzzerConsts* consts = app->fuzzer_const; - - furi_string_set_str(app->file_path, consts->custom_dict_folder); - - DialogsFileBrowserOptions browser_options; - dialog_file_browser_set_basic_options( - &browser_options, consts->custom_dict_extension, &I_rfid_10px); - browser_options.base_path = consts->custom_dict_folder; - browser_options.hide_ext = false; - - bool res = - dialog_file_browser_show(app->dialogs, app->file_path, app->file_path, &browser_options); - - return res; -} - -static bool fuzzer_scene_main_load_key(void* context) { - furi_assert(context); - PacsFuzzerApp* app = context; - - FuzzerConsts* consts = app->fuzzer_const; - - furi_string_set_str(app->file_path, consts->path_key_folder); - - DialogsFileBrowserOptions browser_options; - dialog_file_browser_set_basic_options( - &browser_options, consts->key_extension, consts->key_icon); - browser_options.base_path = consts->path_key_folder; - browser_options.hide_ext = true; - - bool res = - dialog_file_browser_show(app->dialogs, app->file_path, app->file_path, &browser_options); - - return res; -} - -static void fuzzer_scene_main_show_error(void* context, const char* erre_str) { - furi_assert(context); - PacsFuzzerApp* app = context; - popup_set_header(app->popup, erre_str, 64, 20, AlignCenter, AlignTop); - notification_message(app->notifications, &sequence_set_red_255); - notification_message(app->notifications, &sequence_double_vibro); - view_dispatcher_switch_to_view(app->view_dispatcher, FuzzerViewIDPopup); -} - -void fuzzer_scene_main_on_enter(void* context) { - furi_assert(context); - PacsFuzzerApp* app = context; - - fuzzer_view_main_set_callback(app->main_view, fuzzer_scene_main_callback, app); - - fuzzer_view_main_update_data(app->main_view, app->fuzzer_state); - - // Setup view - Popup* popup = app->popup; - // popup_set_icon(popup, 72, 17, &I_DolphinCommon_56x48); - popup_set_timeout(popup, 2500); - popup_set_context(popup, app); - popup_set_callback(popup, fuzzer_scene_main_error_popup_callback); - popup_enable_timeout(popup); - - view_dispatcher_switch_to_view(app->view_dispatcher, FuzzerViewIDMain); -} - -bool fuzzer_scene_main_on_event(void* context, SceneManagerEvent event) { - furi_assert(context); - PacsFuzzerApp* app = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == FuzzerCustomEventViewMainBack) { - if(!scene_manager_previous_scene(app->scene_manager)) { - scene_manager_stop(app->scene_manager); - view_dispatcher_stop(app->view_dispatcher); - } - consumed = true; - } else if(event.event == FuzzerCustomEventViewMainPopupErr) { - view_dispatcher_switch_to_view(app->view_dispatcher, FuzzerViewIDMain); - consumed = true; - } else if(event.event == FuzzerCustomEventViewMainOk) { - fuzzer_view_main_get_state(app->main_view, &app->fuzzer_state); - - // TODO error logic - bool loading_ok = false; - - switch(fuzzer_proto_get_attack_id_by_index(app->fuzzer_state.menu_index)) { - case FuzzerAttackIdDefaultValues: - loading_ok = - fuzzer_worker_init_attack_dict(app->worker, app->fuzzer_state.proto_index); - - if(!loading_ok) { - // error - fuzzer_scene_main_show_error(app, "Default dictionary\nis empty"); - } - break; - - case FuzzerAttackIdBFCustomerID: - // TODO - app->payload->data_size = fuzzer_proto_get_max_data_size(); - memset(app->payload->data, 0x00, app->payload->data_size); - - if(fuzzer_worker_init_attack_bf_byte( - app->worker, app->fuzzer_state.proto_index, app->payload, 0)) { - scene_manager_set_scene_state( - app->scene_manager, - FuzzerSceneFieldEditor, - FuzzerFieldEditorStateEditingOff); - scene_manager_next_scene(app->scene_manager, FuzzerSceneFieldEditor); - - } else { - // error - } - break; - - case FuzzerAttackIdLoadFile: - if(!fuzzer_scene_main_load_key(app)) { - break; - } else { - if(fuzzer_worker_load_key_from_file( - app->worker, - app->fuzzer_state.proto_index, - furi_string_get_cstr(app->file_path))) { - scene_manager_set_scene_state( - app->scene_manager, - FuzzerSceneFieldEditor, - FuzzerFieldEditorStateEditingOn); - scene_manager_next_scene(app->scene_manager, FuzzerSceneFieldEditor); - FURI_LOG_I("Scene", "Load ok"); - } else { - fuzzer_scene_main_show_error(app, "Unsupported protocol\nor broken file"); - FURI_LOG_W("Scene", "Load err"); - } - } - break; - - case FuzzerAttackIdLoadFileCustomUids: - if(!fuzzer_scene_main_load_custom_dict(app)) { - break; - } else { - loading_ok = fuzzer_worker_init_attack_file_dict( - app->worker, app->fuzzer_state.proto_index, app->file_path); - if(!loading_ok) { - fuzzer_scene_main_show_error(app, "Incorrect key format\nor length"); - // error - } - } - break; - - default: - fuzzer_scene_main_show_error(app, "Unsuported attack"); - break; - } - - if(loading_ok) { - scene_manager_next_scene(app->scene_manager, FuzzerSceneAttack); - } - consumed = true; - } - } - - return consumed; -} - -void fuzzer_scene_main_on_exit(void* context) { - // furi_assert(context); - // PacsFuzzerApp* app = context; - UNUSED(context); -} diff --git a/applications/external/multi_fuzzer/todo.md b/applications/external/multi_fuzzer/todo.md deleted file mode 100644 index 4e3b0e17d..000000000 --- a/applications/external/multi_fuzzer/todo.md +++ /dev/null @@ -1,44 +0,0 @@ -## Working Improvement - -#### Quality of life - -- [ ] Make the "Load File" independent of the current protocol -- [x] Add pause - - [ ] Switching UIDs if possible -- [x] Led and sound Notification - - [x] Led - - [x] Vibro - - [ ] Sound? -- [x] Error Notification - - [x] Custom UIDs dict loading - - [x] Key file loading - - [ ] Anything else - -#### App functionality - -- [x] Add `BFCustomerID` attack - - [x] Add the ability to select index -- [ ] Save key logic - -## Code Improvement - -- [ ] GUI - - [x] Rewrite `gui_const` logic - - [x] Icon in dialog - - [x] Description and buttons in `field_editor` view - - [ ] Protocol carousel in `main_menu` - - [x] prototype - - [x] Add the ability to edit emulation time and downtime separately - - [x] Decide on the display -- [x] UID - - [x] Simplify the storage and exchange of `uids.data` `uid.data_size` in `views` - - [x] Using `FuzzerPayload` to store the uid - - [x] `UID_MAX_SIZE` -- [x] Add pause - - [x] Fix `Custom dict` attack when ended -- [ ] Pause V2 - - [ ] Save logic - - [ ] Switching UIDs if possible -- [ ] Worker - - [ ] Use `prtocol_id` instead of protocol name - - [x] this can be simplified `fuzzer_proto_items` \ No newline at end of file diff --git a/applications/external/multi_fuzzer/views/attack.c b/applications/external/multi_fuzzer/views/attack.c deleted file mode 100644 index 87aa9f659..000000000 --- a/applications/external/multi_fuzzer/views/attack.c +++ /dev/null @@ -1,384 +0,0 @@ -#include "attack.h" -#include "../fuzzer_i.h" - -#include -#include - -#define ATTACK_SCENE_MAX_UID_LENGTH 25 -#define UID_MAX_DISPLAYED_LEN (8U) -#define LIFT_RIGHT_OFFSET (3) - -struct FuzzerViewAttack { - View* view; - FuzzerViewAttackCallback callback; - void* context; -}; - -typedef struct { - uint8_t time_delay; // 1 = 100ms - uint8_t time_delay_min; // 1 = 100ms - uint8_t emu_time; // 1 = 100ms - uint8_t emu_time_min; // 1 = 100ms - bool td_emt_cursor; // false - time_delay, true - emu_time - const char* attack_name; - const char* protocol_name; - FuzzerAttackState attack_state; - FuriString* uid_str; -} FuzzerViewAttackModel; - -void fuzzer_view_attack_reset_data( - FuzzerViewAttack* view, - const char* attack_name, - const char* protocol_name) { - furi_assert(view); - - with_view_model( - view->view, - FuzzerViewAttackModel * model, - { - model->attack_name = attack_name; - model->protocol_name = protocol_name; - model->attack_state = FuzzerAttackStateIdle; - furi_string_set_str(model->uid_str, "Not_set"); - }, - true); -} - -void fuzzer_view_attack_set_uid(FuzzerViewAttack* view, const FuzzerPayload* uid) { - furi_assert(view); - furi_assert(uid->data); - - with_view_model( - view->view, - FuzzerViewAttackModel * model, - { - furi_string_printf(model->uid_str, "%02X", uid->data[0]); - for(uint8_t i = 1; i < uid->data_size; i++) { - furi_string_cat_printf(model->uid_str, ":%02X", uid->data[i]); - } - }, - true); -} - -void fuzzer_view_attack_start(FuzzerViewAttack* view) { - furi_assert(view); - - with_view_model( - view->view, - FuzzerViewAttackModel * model, - { model->attack_state = FuzzerAttackStateRunning; }, - true); -} - -void fuzzer_view_attack_stop(FuzzerViewAttack* view) { - furi_assert(view); - - with_view_model( - view->view, - FuzzerViewAttackModel * model, - { model->attack_state = FuzzerAttackStateOff; }, - true); -} - -void fuzzer_view_attack_pause(FuzzerViewAttack* view) { - furi_assert(view); - - with_view_model( - view->view, - FuzzerViewAttackModel * model, - { model->attack_state = FuzzerAttackStateIdle; }, - true); -} - -void fuzzer_view_attack_end(FuzzerViewAttack* view) { - furi_assert(view); - - with_view_model( - view->view, - FuzzerViewAttackModel * model, - { model->attack_state = FuzzerAttackStateEnd; }, - true); -} - -void fuzzer_view_attack_set_callback( - FuzzerViewAttack* view_attack, - FuzzerViewAttackCallback callback, - void* context) { - furi_assert(view_attack); - - view_attack->callback = callback; - view_attack->context = context; -} - -void fuzzer_view_attack_draw(Canvas* canvas, FuzzerViewAttackModel* model) { - char temp_str[50]; - - canvas_clear(canvas); - canvas_set_color(canvas, ColorBlack); - - canvas_set_font(canvas, FontPrimary); - canvas_draw_str_aligned(canvas, 64, 2, AlignCenter, AlignTop, model->attack_name); - - uint16_t crt; - canvas_set_font(canvas, FontPrimary); - - if(!model->td_emt_cursor) { - canvas_set_font(canvas, FontSecondary); - snprintf(temp_str, sizeof(temp_str), "Time delay:"); - canvas_draw_str_aligned(canvas, LIFT_RIGHT_OFFSET, 21, AlignLeft, AlignBottom, temp_str); - crt = canvas_string_width(canvas, temp_str); - - canvas_set_font(canvas, FontPrimary); - snprintf( - temp_str, sizeof(temp_str), "%d.%d", model->time_delay / 10, model->time_delay % 10); - canvas_draw_str_aligned( - canvas, crt + LIFT_RIGHT_OFFSET + 3, 21, AlignLeft, AlignBottom, temp_str); - - canvas_set_font(canvas, FontSecondary); - snprintf( - temp_str, sizeof(temp_str), "EmT: %d.%d", model->emu_time / 10, model->emu_time % 10); - canvas_draw_str_aligned( - canvas, 128 - LIFT_RIGHT_OFFSET, 21, AlignRight, AlignBottom, temp_str); - } else { - canvas_set_font(canvas, FontSecondary); - snprintf( - temp_str, - sizeof(temp_str), - "TD: %d.%d", - model->time_delay / 10, - model->time_delay % 10); - - canvas_draw_str_aligned(canvas, LIFT_RIGHT_OFFSET, 21, AlignLeft, AlignBottom, temp_str); - - canvas_set_font(canvas, FontPrimary); - snprintf(temp_str, sizeof(temp_str), "%d.%d", model->emu_time / 10, model->emu_time % 10); - canvas_draw_str_aligned( - canvas, 128 - LIFT_RIGHT_OFFSET, 21, AlignRight, AlignBottom, temp_str); - crt = canvas_string_width(canvas, temp_str); - - canvas_set_font(canvas, FontSecondary); - snprintf(temp_str, sizeof(temp_str), "Emulation time:"); - canvas_draw_str_aligned( - canvas, 128 - LIFT_RIGHT_OFFSET - crt - 3, 21, AlignRight, AlignBottom, temp_str); - } - - canvas_set_font(canvas, FontSecondary); - canvas_draw_str_aligned(canvas, 64, 26, AlignCenter, AlignTop, model->protocol_name); - - canvas_set_font(canvas, FontPrimary); - if(128 < canvas_string_width(canvas, furi_string_get_cstr(model->uid_str))) { - canvas_set_font(canvas, FontSecondary); - } - canvas_draw_str_aligned( - canvas, 64, 38, AlignCenter, AlignTop, furi_string_get_cstr(model->uid_str)); - - canvas_set_font(canvas, FontSecondary); - if(model->attack_state == FuzzerAttackStateRunning) { - elements_button_center(canvas, "Stop"); - } else if(model->attack_state == FuzzerAttackStateIdle) { - if(model->td_emt_cursor) { - elements_button_center(canvas, "Start"); - elements_button_left(canvas, "EmT -"); - elements_button_right(canvas, "+ EmT"); - } else { - elements_button_center(canvas, "Start"); - elements_button_left(canvas, "TD -"); - elements_button_right(canvas, "+ TD"); - } - - } else if(model->attack_state == FuzzerAttackStateEnd) { - // elements_button_center(canvas, "Restart"); // Reset - elements_button_left(canvas, "Exit"); - } -} - -bool fuzzer_view_attack_input(InputEvent* event, void* context) { - furi_assert(context); - FuzzerViewAttack* view_attack = context; - - if(event->key == InputKeyBack && event->type == InputTypeShort) { - view_attack->callback(FuzzerCustomEventViewAttackBack, view_attack->context); - return true; - } else if(event->key == InputKeyOk && event->type == InputTypeShort) { - view_attack->callback(FuzzerCustomEventViewAttackOk, view_attack->context); - return true; - } else if(event->key == InputKeyLeft) { - with_view_model( - view_attack->view, - FuzzerViewAttackModel * model, - { - if(model->attack_state == FuzzerAttackStateIdle) { - if(!model->td_emt_cursor) { - // TimeDelay -- - if(event->type == InputTypeShort) { - if(model->time_delay > model->time_delay_min) { - model->time_delay--; - } - } else if(event->type == InputTypeLong) { - if((model->time_delay - 10) >= model->time_delay_min) { - model->time_delay -= 10; - } else { - model->time_delay = model->time_delay_min; - } - } - } else { - // EmuTime -- - if(event->type == InputTypeShort) { - if(model->emu_time > model->emu_time_min) { - model->emu_time--; - } - } else if(event->type == InputTypeLong) { - if((model->emu_time - 10) >= model->emu_time_min) { - model->emu_time -= 10; - } else { - model->emu_time = model->emu_time_min; - } - } - } - } else if( - (model->attack_state == FuzzerAttackStateEnd) && - (event->type == InputTypeShort)) { - // Exit if Ended - view_attack->callback(FuzzerCustomEventViewAttackBack, view_attack->context); - } - }, - true); - return true; - } else if(event->key == InputKeyRight) { - with_view_model( - view_attack->view, - FuzzerViewAttackModel * model, - { - if(model->attack_state == FuzzerAttackStateIdle) { - if(!model->td_emt_cursor) { - // TimeDelay ++ - if(event->type == InputTypeShort) { - if(model->time_delay < FUZZ_TIME_DELAY_MAX) { - model->time_delay++; - } - } else if(event->type == InputTypeLong) { - model->time_delay += 10; - if(model->time_delay > FUZZ_TIME_DELAY_MAX) { - model->time_delay = FUZZ_TIME_DELAY_MAX; - } - } - } else { - // EmuTime ++ - if(event->type == InputTypeShort) { - if(model->emu_time < FUZZ_TIME_DELAY_MAX) { - model->emu_time++; - } - } else if(event->type == InputTypeLong) { - model->emu_time += 10; - if(model->emu_time > FUZZ_TIME_DELAY_MAX) { - model->emu_time = FUZZ_TIME_DELAY_MAX; - } - } - } - } else { - // Nothing - } - }, - true); - return true; - } else if( - (event->key == InputKeyUp || event->key == InputKeyDown) && - event->type == InputTypeShort) { - with_view_model( - view_attack->view, - FuzzerViewAttackModel * model, - { model->td_emt_cursor = !model->td_emt_cursor; }, - true); - return true; - } - - return true; -} - -void fuzzer_view_attack_enter(void* context) { - furi_assert(context); -} - -void fuzzer_view_attack_exit(void* context) { - furi_assert(context); - FuzzerViewAttack* view_attack = context; - with_view_model( - view_attack->view, FuzzerViewAttackModel * model, { model->td_emt_cursor = false; }, true); -} - -FuzzerViewAttack* fuzzer_view_attack_alloc() { - if(fuzzer_proto_get_max_data_size() > UID_MAX_DISPLAYED_LEN) { - furi_crash("Maximum of displayed bytes exceeded"); - } - - FuzzerViewAttack* view_attack = malloc(sizeof(FuzzerViewAttack)); - - // View allocation and configuration - view_attack->view = view_alloc(); - view_allocate_model(view_attack->view, ViewModelTypeLocking, sizeof(FuzzerViewAttackModel)); - view_set_context(view_attack->view, view_attack); - view_set_draw_callback(view_attack->view, (ViewDrawCallback)fuzzer_view_attack_draw); - view_set_input_callback(view_attack->view, fuzzer_view_attack_input); - view_set_enter_callback(view_attack->view, fuzzer_view_attack_enter); - view_set_exit_callback(view_attack->view, fuzzer_view_attack_exit); - - with_view_model( - view_attack->view, - FuzzerViewAttackModel * model, - { - model->time_delay = fuzzer_proto_get_def_idle_time(); - model->time_delay_min = 0; // model->time_delay; - - model->emu_time = fuzzer_proto_get_def_emu_time(); - - model->emu_time_min = 2; // model->emu_time; - - model->uid_str = furi_string_alloc_set_str("Not_set"); - // malloc(ATTACK_SCENE_MAX_UID_LENGTH + 1); - model->attack_state = FuzzerAttackStateOff; - model->td_emt_cursor = false; - - // strcpy(model->uid_str, "Not_set"); - model->attack_name = "Not_set"; - model->protocol_name = "Not_set"; - }, - true); - return view_attack; -} - -void fuzzer_view_attack_free(FuzzerViewAttack* view_attack) { - furi_assert(view_attack); - - with_view_model( - view_attack->view, - FuzzerViewAttackModel * model, - { furi_string_free(model->uid_str); }, - true); - view_free(view_attack->view); - free(view_attack); -} - -View* fuzzer_view_attack_get_view(FuzzerViewAttack* view_attack) { - furi_assert(view_attack); - return view_attack->view; -} - -uint8_t fuzzer_view_attack_get_time_delay(FuzzerViewAttack* view) { - furi_assert(view); - uint8_t time_delay; - - with_view_model( - view->view, FuzzerViewAttackModel * model, { time_delay = model->time_delay; }, false); - - return time_delay; -} - -uint8_t fuzzer_view_attack_get_emu_time(FuzzerViewAttack* view) { - furi_assert(view); - uint8_t emu_time; - - with_view_model( - view->view, FuzzerViewAttackModel * model, { emu_time = model->emu_time; }, false); - - return emu_time; -} \ No newline at end of file diff --git a/applications/external/multi_fuzzer/views/attack.h b/applications/external/multi_fuzzer/views/attack.h deleted file mode 100644 index 66e96d7d6..000000000 --- a/applications/external/multi_fuzzer/views/attack.h +++ /dev/null @@ -1,42 +0,0 @@ -#pragma once - -#include - -#include "../helpers/fuzzer_custom_event.h" -#include "../helpers/fuzzer_types.h" - -#include "../lib/worker/protocol.h" - -typedef struct FuzzerViewAttack FuzzerViewAttack; - -typedef void (*FuzzerViewAttackCallback)(FuzzerCustomEvent event, void* context); - -void fuzzer_view_attack_set_callback( - FuzzerViewAttack* view_attack, - FuzzerViewAttackCallback callback, - void* context); - -FuzzerViewAttack* fuzzer_view_attack_alloc(); - -void fuzzer_view_attack_free(FuzzerViewAttack* view_attack); - -View* fuzzer_view_attack_get_view(FuzzerViewAttack* view_attack); - -void fuzzer_view_attack_reset_data( - FuzzerViewAttack* view, - const char* attack_name, - const char* protocol_name); - -void fuzzer_view_attack_set_uid(FuzzerViewAttack* view, const FuzzerPayload* uid); - -void fuzzer_view_attack_start(FuzzerViewAttack* view); - -void fuzzer_view_attack_stop(FuzzerViewAttack* view); - -void fuzzer_view_attack_pause(FuzzerViewAttack* view); - -void fuzzer_view_attack_end(FuzzerViewAttack* view); - -uint8_t fuzzer_view_attack_get_time_delay(FuzzerViewAttack* view); - -uint8_t fuzzer_view_attack_get_emu_time(FuzzerViewAttack* view); \ No newline at end of file diff --git a/applications/external/multi_fuzzer/views/field_editor.c b/applications/external/multi_fuzzer/views/field_editor.c deleted file mode 100644 index bdce0a516..000000000 --- a/applications/external/multi_fuzzer/views/field_editor.c +++ /dev/null @@ -1,358 +0,0 @@ -#include "field_editor.h" -#include "../fuzzer_i.h" - -#include -#include -#include - -#define FIELD_EDITOR_V2 - -#define GUI_DISPLAY_WIDTH 128 -#define GUI_DISPLAY_HEIGHT 64 - -#define GUI_DISPLAY_HORIZONTAL_CENTER 64 -#define GUI_DISPLAY_VERTICAL_CENTER 32 - -#define UID_STR_LENGTH 25 - -#ifdef FIELD_EDITOR_V2 -#define EDITOR_STRING_Y 38 -#else -#define EDITOR_STRING_Y 50 -#endif - -struct FuzzerViewFieldEditor { - View* view; - FuzzerViewFieldEditorCallback callback; - void* context; -}; - -typedef struct { - uint8_t* uid; - uint8_t uid_size; - - FuriString* uid_str; - - uint8_t index; - bool lo; - bool allow_edit; -} FuzzerViewFieldEditorModel; - -void fuzzer_view_field_editor_set_callback( - FuzzerViewFieldEditor* view_edit, - FuzzerViewFieldEditorCallback callback, - void* context) { - furi_assert(view_edit); - - view_edit->callback = callback; - view_edit->context = context; -} - -void fuzzer_view_field_editor_reset_data( - FuzzerViewFieldEditor* view_edit, - const FuzzerPayload* new_uid, - bool allow_edit) { - furi_assert(view_edit); - furi_assert(new_uid->data); - - with_view_model( - view_edit->view, - FuzzerViewFieldEditorModel * model, - { - memcpy(model->uid, new_uid->data, new_uid->data_size); - model->index = 0; - model->lo = false; - model->uid_size = new_uid->data_size; - model->allow_edit = allow_edit; - }, - true); -} - -void fuzzer_view_field_editor_get_uid(FuzzerViewFieldEditor* view_edit, FuzzerPayload* output_uid) { - furi_assert(view_edit); - furi_assert(output_uid); - with_view_model( - view_edit->view, - FuzzerViewFieldEditorModel * model, - { - output_uid->data_size = model->uid_size; - memcpy(output_uid->data, model->uid, model->uid_size); - }, - true); -} - -uint8_t fuzzer_view_field_editor_get_index(FuzzerViewFieldEditor* view_edit) { - furi_assert(view_edit); - uint8_t index; - with_view_model( - view_edit->view, FuzzerViewFieldEditorModel * model, { index = model->index; }, true); - return index; -} - -void fuzzer_view_field_editor_draw(Canvas* canvas, FuzzerViewFieldEditorModel* model) { - canvas_clear(canvas); - canvas_set_color(canvas, ColorBlack); - -#ifdef FIELD_EDITOR_V2 - - canvas_set_font(canvas, FontSecondary); - if(model->allow_edit) { - canvas_draw_icon(canvas, 2, 4, &I_ButtonLeft_4x7); - canvas_draw_icon(canvas, 8, 4, &I_ButtonRight_4x7); - - canvas_draw_icon_ex(canvas, 62, 3, &I_Pin_arrow_up_7x9, IconRotation180); - canvas_draw_icon(canvas, 69, 3, &I_Pin_arrow_up_7x9); - - canvas_draw_str(canvas, 14, 10, "select byte"); - canvas_draw_str(canvas, 79, 10, "adjust byte"); - } else { - canvas_draw_icon(canvas, 35, 4, &I_ButtonLeft_4x7); - canvas_draw_icon(canvas, 41, 4, &I_ButtonRight_4x7); - canvas_draw_str(canvas, 49, 10, "select byte"); - } - - char msg_index[18]; - canvas_set_font(canvas, FontPrimary); - snprintf(msg_index, sizeof(msg_index), "Field index : %d", model->index); - - canvas_draw_str_aligned( - canvas, GUI_DISPLAY_HORIZONTAL_CENTER, 24, AlignCenter, AlignBottom, msg_index); - - canvas_set_font(canvas, FontSecondary); - canvas_draw_icon(canvas, 4, 52, &I_Pin_back_arrow_10x8); - canvas_draw_icon(canvas, 85, 52, &I_Ok_btn_9x9); - - canvas_draw_str(canvas, 16, 60, "Back"); - canvas_draw_str(canvas, 96, 60, "Attack"); -#else - canvas_set_font(canvas, FontSecondary); - canvas_draw_str_aligned( - canvas, - GUI_DISPLAY_HORIZONTAL_CENTER, - 5, - AlignCenter, - AlignTop, - "Left and right: select byte"); - canvas_draw_str_aligned( - canvas, - GUI_DISPLAY_HORIZONTAL_CENTER, - 15, - AlignCenter, - AlignTop, - "Up and down: adjust byte"); - - char msg_index[18]; - canvas_set_font(canvas, FontPrimary); - snprintf(msg_index, sizeof(msg_index), "Field index : %d", model->index); - canvas_draw_str_aligned( - canvas, GUI_DISPLAY_HORIZONTAL_CENTER, 28, AlignCenter, AlignTop, msg_index); -#endif - // ####### Editor ####### - FuriString* temp_s = model->uid_str; - canvas_set_font(canvas, FontSecondary); - - furi_string_reset(temp_s); - for(int i = -3; i != 0; i++) { - if(0 <= (model->index + i)) { - furi_string_cat_printf(temp_s, "%02X ", model->uid[model->index + i]); - } - } - canvas_draw_str_aligned( - canvas, 52, EDITOR_STRING_Y, AlignRight, AlignBottom, furi_string_get_cstr(temp_s)); - - furi_string_reset(temp_s); - for(int i = 1; i != 4; i++) { - if((model->index + i) < model->uid_size) { - furi_string_cat_printf(temp_s, " %02X", model->uid[model->index + i]); - } - } - canvas_draw_str_aligned( - canvas, 77, EDITOR_STRING_Y, AlignLeft, AlignBottom, furi_string_get_cstr(temp_s)); - - canvas_set_font(canvas, FontPrimary); - - furi_string_reset(temp_s); - furi_string_cat_printf(temp_s, "<%02X>", model->uid[model->index]); - canvas_draw_str_aligned( - canvas, - GUI_DISPLAY_HORIZONTAL_CENTER, - EDITOR_STRING_Y, - AlignCenter, - AlignBottom, - furi_string_get_cstr(temp_s)); - - uint16_t w = canvas_string_width(canvas, furi_string_get_cstr(temp_s)); - w -= 11; // '<' & '>' - w /= 2; - - if(model->allow_edit) { - if(model->lo) { - canvas_draw_line( - canvas, - GUI_DISPLAY_HORIZONTAL_CENTER + 1, - EDITOR_STRING_Y + 2, - GUI_DISPLAY_HORIZONTAL_CENTER + w, - EDITOR_STRING_Y + 2); - } else { - canvas_draw_line( - canvas, - GUI_DISPLAY_HORIZONTAL_CENTER - w, - EDITOR_STRING_Y + 2, - GUI_DISPLAY_HORIZONTAL_CENTER - 1, - EDITOR_STRING_Y + 2); - } - } else { - // canvas_draw_line( - // canvas, - // GUI_DISPLAY_HORIZONTAL_CENTER - w, - // EDITOR_STRING_Y + 2, - // GUI_DISPLAY_HORIZONTAL_CENTER + w, - // EDITOR_STRING_Y + 2); - } - // ####### Editor ####### -} - -bool fuzzer_view_field_editor_input(InputEvent* event, void* context) { - furi_assert(context); - FuzzerViewFieldEditor* view_edit = context; - - if(event->key == InputKeyBack && event->type == InputTypeShort) { - view_edit->callback(FuzzerCustomEventViewFieldEditorBack, view_edit->context); - return true; - } else if(event->key == InputKeyOk && event->type == InputTypeShort) { - view_edit->callback(FuzzerCustomEventViewFieldEditorOk, view_edit->context); - return true; - } else if(event->key == InputKeyLeft) { - with_view_model( - view_edit->view, - FuzzerViewFieldEditorModel * model, - { - if(event->type == InputTypeShort) { - if(!model->allow_edit) { - model->lo = false; - } - if(model->index > 0 || model->lo) { - if(!model->lo) { - model->index--; - } - model->lo = !model->lo; - } - } else if(event->type == InputTypeLong) { - model->index = 0; - model->lo = false; - } - }, - true); - return true; - } else if(event->key == InputKeyRight) { - with_view_model( - view_edit->view, - FuzzerViewFieldEditorModel * model, - { - if(event->type == InputTypeShort) { - if(!model->allow_edit) { - model->lo = true; - } - if(model->index < (model->uid_size - 1) || !model->lo) { - if(model->lo) { - model->index++; - } - model->lo = !model->lo; - } - } else if(event->type == InputTypeLong) { - model->index = model->uid_size - 1; - model->lo = true; - } - }, - true); - return true; - } else if(event->key == InputKeyUp) { - with_view_model( - view_edit->view, - FuzzerViewFieldEditorModel * model, - { - if(event->type == InputTypeShort && model->allow_edit) { - if(model->lo) { - model->uid[model->index] = (model->uid[model->index] & 0xF0) | - ((model->uid[model->index] + 1) & 0x0F); - } else { - model->uid[model->index] = ((model->uid[model->index] + 0x10) & 0xF0) | - (model->uid[model->index] & 0x0F); - } - } - }, - true); - return true; - } else if(event->key == InputKeyDown) { - with_view_model( - view_edit->view, - FuzzerViewFieldEditorModel * model, - { - if(event->type == InputTypeShort && model->allow_edit) { - if(model->lo) { - model->uid[model->index] = (model->uid[model->index] & 0xF0) | - ((model->uid[model->index] - 1) & 0x0F); - } else { - model->uid[model->index] = ((model->uid[model->index] - 0x10) & 0xF0) | - (model->uid[model->index] & 0x0F); - } - } - }, - true); - return true; - } - - return true; -} - -void fuzzer_view_field_editor_enter(void* context) { - furi_assert(context); -} - -void fuzzer_view_field_editor_exit(void* context) { - furi_assert(context); -} - -FuzzerViewFieldEditor* fuzzer_view_field_editor_alloc() { - FuzzerViewFieldEditor* view_edit = malloc(sizeof(FuzzerViewFieldEditor)); - - // View allocation and configuration - view_edit->view = view_alloc(); - view_allocate_model(view_edit->view, ViewModelTypeLocking, sizeof(FuzzerViewFieldEditorModel)); - view_set_context(view_edit->view, view_edit); - view_set_draw_callback(view_edit->view, (ViewDrawCallback)fuzzer_view_field_editor_draw); - view_set_input_callback(view_edit->view, fuzzer_view_field_editor_input); - view_set_enter_callback(view_edit->view, fuzzer_view_field_editor_enter); - view_set_exit_callback(view_edit->view, fuzzer_view_field_editor_exit); - - with_view_model( - view_edit->view, - FuzzerViewFieldEditorModel * model, - { - model->uid_str = furi_string_alloc(); - model->uid = malloc(fuzzer_proto_get_max_data_size()); - }, - true); - - return view_edit; -} - -void fuzzer_view_field_editor_free(FuzzerViewFieldEditor* view_edit) { - furi_assert(view_edit); - - with_view_model( - view_edit->view, - FuzzerViewFieldEditorModel * model, - { - furi_string_free(model->uid_str); - free(model->uid); - }, - true); - view_free(view_edit->view); - free(view_edit); -} - -View* fuzzer_view_field_editor_get_view(FuzzerViewFieldEditor* view_edit) { - furi_assert(view_edit); - return view_edit->view; -} \ No newline at end of file diff --git a/applications/external/multi_fuzzer/views/field_editor.h b/applications/external/multi_fuzzer/views/field_editor.h deleted file mode 100644 index d81538bf8..000000000 --- a/applications/external/multi_fuzzer/views/field_editor.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -#include -#include "../helpers/fuzzer_custom_event.h" -#include "../lib/worker/protocol.h" - -typedef struct FuzzerViewFieldEditor FuzzerViewFieldEditor; - -typedef void (*FuzzerViewFieldEditorCallback)(FuzzerCustomEvent event, void* context); - -void fuzzer_view_field_editor_set_callback( - FuzzerViewFieldEditor* view_attack, - FuzzerViewFieldEditorCallback callback, - void* context); - -FuzzerViewFieldEditor* fuzzer_view_field_editor_alloc(); - -void fuzzer_view_field_editor_free(FuzzerViewFieldEditor* view_attack); - -View* fuzzer_view_field_editor_get_view(FuzzerViewFieldEditor* view_attack); - -void fuzzer_view_field_editor_reset_data( - FuzzerViewFieldEditor* view_edit, - const FuzzerPayload* new_uid, - bool allow_edit); - -void fuzzer_view_field_editor_get_uid(FuzzerViewFieldEditor* view_edit, FuzzerPayload* output_uid); - -uint8_t fuzzer_view_field_editor_get_index(FuzzerViewFieldEditor* view_edit); \ No newline at end of file diff --git a/applications/external/multi_fuzzer/views/main_menu.c b/applications/external/multi_fuzzer/views/main_menu.c deleted file mode 100644 index 14422145b..000000000 --- a/applications/external/multi_fuzzer/views/main_menu.c +++ /dev/null @@ -1,235 +0,0 @@ -#include "main_menu.h" -#include "../fuzzer_i.h" - -#include - -#include "../lib/worker/protocol.h" - -#define PROTOCOL_NAME_Y 12 -// #define PROTOCOL_CAROUSEL - -struct FuzzerViewMain { - View* view; - FuzzerViewMainCallback callback; - void* context; -}; - -typedef struct { - uint8_t proto_index; - uint8_t menu_index; - uint8_t proto_max; - uint8_t menu_max; -} FuzzerViewMainModel; - -void fuzzer_view_main_update_data(FuzzerViewMain* view, FuzzerState state) { - furi_assert(view); - with_view_model( - view->view, - FuzzerViewMainModel * model, - { - model->proto_index = state.proto_index; - model->menu_index = state.menu_index; - }, - true); -} - -void fuzzer_view_main_get_state(FuzzerViewMain* view, FuzzerState* state) { - furi_assert(view); - with_view_model( - view->view, - FuzzerViewMainModel * model, - { - state->proto_index = model->proto_index; - state->menu_index = model->menu_index; - }, - true); -} - -void fuzzer_view_main_set_callback( - FuzzerViewMain* view, - FuzzerViewMainCallback callback, - void* context) { - furi_assert(view); - - view->callback = callback; - view->context = context; -} - -void fuzzer_view_main_draw(Canvas* canvas, FuzzerViewMainModel* model) { - canvas_clear(canvas); - canvas_set_color(canvas, ColorBlack); - - if(model->menu_index > 0) { - canvas_set_font(canvas, FontSecondary); - canvas_draw_str_aligned( - canvas, - 64, - 24, - AlignCenter, - AlignTop, - fuzzer_proto_get_menu_label(model->menu_index - 1)); - } - - canvas_set_font(canvas, FontPrimary); - canvas_draw_str_aligned( - canvas, 64, 36, AlignCenter, AlignTop, fuzzer_proto_get_menu_label(model->menu_index)); - - if(model->menu_index < (model->menu_max - 1)) { - canvas_set_font(canvas, FontSecondary); - canvas_draw_str_aligned( - canvas, - 64, - 48, - AlignCenter, - AlignTop, - fuzzer_proto_get_menu_label(model->menu_index + 1)); - } - - canvas_set_font(canvas, FontPrimary); - canvas_draw_str_aligned(canvas, 27, PROTOCOL_NAME_Y, AlignCenter, AlignBottom, "<"); - canvas_draw_str_aligned( - canvas, - 64, - PROTOCOL_NAME_Y, - AlignCenter, - AlignBottom, - fuzzer_proto_get_name(model->proto_index)); - canvas_draw_str_aligned(canvas, 101, PROTOCOL_NAME_Y, AlignCenter, AlignBottom, ">"); - -#ifdef PROTOCOL_CAROUSEL - canvas_set_font(canvas, FontSecondary); - canvas_draw_str_aligned( - canvas, - 20, - PROTOCOL_NAME_Y, - AlignRight, - AlignBottom, - (model->proto_index > 0) ? fuzzer_proto_get_name(model->proto_index - 1) : - fuzzer_proto_get_name((model->proto_max - 1))); - canvas_draw_str_aligned( - canvas, - 108, - PROTOCOL_NAME_Y, - AlignLeft, - AlignBottom, - (model->proto_index < (model->proto_max - 1)) ? - fuzzer_proto_get_name(model->proto_index + 1) : - fuzzer_proto_get_name(0)); -#endif -} - -bool fuzzer_view_main_input(InputEvent* event, void* context) { - furi_assert(context); - FuzzerViewMain* view = context; - - if(event->key == InputKeyBack && - (event->type == InputTypeLong || event->type == InputTypeShort)) { - view->callback(FuzzerCustomEventViewMainBack, view->context); - return true; - } else if(event->key == InputKeyOk && event->type == InputTypeShort) { - view->callback(FuzzerCustomEventViewMainOk, view->context); - return true; - } else if(event->key == InputKeyDown && event->type == InputTypeShort) { - with_view_model( - view->view, - FuzzerViewMainModel * model, - { - if(model->menu_index < (model->menu_max - 1)) { - model->menu_index++; - } - }, - true); - return true; - } else if(event->key == InputKeyUp && event->type == InputTypeShort) { - with_view_model( - view->view, - FuzzerViewMainModel * model, - { - if(model->menu_index != 0) { - model->menu_index--; - } - }, - true); - return true; - } else if(event->key == InputKeyLeft && event->type == InputTypeShort) { - with_view_model( - view->view, - FuzzerViewMainModel * model, - { - if(model->proto_index != 0) { - model->proto_index--; - } else { - model->proto_index = (model->proto_max - 1); - } - }, - true); - return true; - } else if(event->key == InputKeyRight && event->type == InputTypeShort) { - with_view_model( - view->view, - FuzzerViewMainModel * model, - { - if(model->proto_index == (model->proto_max - 1)) { - model->proto_index = 0; - } else { - model->proto_index++; - } - }, - true); - return true; - } - - return true; -} - -void fuzzer_view_main_enter(void* context) { - furi_assert(context); -} - -void fuzzer_view_main_exit(void* context) { - furi_assert(context); -} - -FuzzerViewMain* fuzzer_view_main_alloc() { - FuzzerViewMain* view = malloc(sizeof(FuzzerViewMain)); - - // View allocation and configuration - view->view = view_alloc(); - view_allocate_model(view->view, ViewModelTypeLocking, sizeof(FuzzerViewMainModel)); - view_set_context(view->view, view); - view_set_draw_callback(view->view, (ViewDrawCallback)fuzzer_view_main_draw); - view_set_input_callback(view->view, fuzzer_view_main_input); - view_set_enter_callback(view->view, fuzzer_view_main_enter); - view_set_exit_callback(view->view, fuzzer_view_main_exit); - - with_view_model( - view->view, - FuzzerViewMainModel * model, - { - model->proto_index = 0; - model->proto_max = fuzzer_proto_get_count_of_protocols(); - model->menu_index = 0; - model->menu_max = fuzzer_proto_get_count_of_menu_items(); - }, - true); - return view; -} - -void fuzzer_view_main_free(FuzzerViewMain* view) { - furi_assert(view); - - // with_view_model( - // view->view, - // FuzzerViewMainModel * model, - // { - - // }, - // true); - view_free(view->view); - free(view); -} - -View* fuzzer_view_main_get_view(FuzzerViewMain* view) { - furi_assert(view); - return view->view; -} \ No newline at end of file diff --git a/applications/external/multi_fuzzer/views/main_menu.h b/applications/external/multi_fuzzer/views/main_menu.h deleted file mode 100644 index 262d54405..000000000 --- a/applications/external/multi_fuzzer/views/main_menu.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -#include -#include "../helpers/fuzzer_custom_event.h" -#include "../helpers/fuzzer_types.h" - -typedef struct FuzzerViewMain FuzzerViewMain; - -typedef void (*FuzzerViewMainCallback)(FuzzerCustomEvent event, void* context); - -void fuzzer_view_main_set_callback( - FuzzerViewMain* fuzzer_view_main, - FuzzerViewMainCallback callback, - void* context); - -FuzzerViewMain* fuzzer_view_main_alloc(); - -void fuzzer_view_main_free(FuzzerViewMain* view); - -View* fuzzer_view_main_get_view(FuzzerViewMain* view); - -void fuzzer_view_main_update_data(FuzzerViewMain* view, FuzzerState state); -void fuzzer_view_main_get_state(FuzzerViewMain* view, FuzzerState* state); \ No newline at end of file From af23870d3be1d124bbad674768307e1f3ea05c7f Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 13 Jul 2023 23:54:50 +0300 Subject: [PATCH 130/364] make multi_fuzzer submodule --- .gitmodules | 3 +++ applications/external/multi_fuzzer | 1 + 2 files changed, 4 insertions(+) create mode 160000 applications/external/multi_fuzzer diff --git a/.gitmodules b/.gitmodules index 396aced1c..331177e26 100644 --- a/.gitmodules +++ b/.gitmodules @@ -43,3 +43,6 @@ [submodule "lib/stm32wb_copro"] path = lib/stm32wb_copro url = https://github.com/flipperdevices/stm32wb_copro.git +[submodule "applications/external/multi_fuzzer"] + path = applications/external/multi_fuzzer + url = https://github.com/DarkFlippers/Multi_Fuzzer.git diff --git a/applications/external/multi_fuzzer b/applications/external/multi_fuzzer new file mode 160000 index 000000000..64a4d768b --- /dev/null +++ b/applications/external/multi_fuzzer @@ -0,0 +1 @@ +Subproject commit 64a4d768b26ac2a21eb8423fa5e6552a9ef7465b From f03cac5bdb6d5fc8b3b88253e401e8b515eccb94 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 14 Jul 2023 00:03:56 +0200 Subject: [PATCH 131/364] Fix subghz merge --- applications/drivers/application.fam | 6 + applications/drivers/subghz/application.fam | 8 + .../drivers/subghz/cc1101_ext/cc1101_ext.h | 206 ++++++++++ .../cc1101_ext/cc1101_ext_interconnect.c | 110 +++++ .../cc1101_ext/cc1101_ext_interconnect.h | 8 + .../{ => helpers}/radio_device_loader.c | 0 .../{ => helpers}/radio_device_loader.h | 0 .../external/subghz_playlist/playlist.c | 2 +- .../external/subghz_remote/application.fam | 19 - .../subghz_remote/helpers/txrx/subghz_txrx.h | 375 ++++++++++++++++++ .../application.fam | 2 +- applications/main/subghz/application.fam | 9 +- .../main/subghz/helpers/subghz_chat.c | 7 +- .../main/subghz/helpers/subghz_chat.h | 6 +- .../subghz/helpers/subghz_threshold_rssi.c | 3 +- .../subghz/helpers/subghz_threshold_rssi.h | 3 +- .../main/subghz/helpers/subghz_txrx.c | 71 +++- .../main/subghz/helpers/subghz_txrx.h | 5 +- .../main/subghz/helpers/subghz_txrx_i.h | 2 + .../main/subghz/helpers/subghz_types.h | 7 + .../subghz/scenes/subghz_scene_read_raw.c | 31 +- .../subghz/scenes/subghz_scene_receiver.c | 3 + .../main/subghz/scenes/subghz_scene_rpc.c | 1 + .../subghz/scenes/subghz_scene_save_name.c | 3 +- .../subghz/scenes/subghz_scene_transmitter.c | 16 +- applications/main/subghz/subghz_cli.c | 133 ++++--- .../main/subghz/subghz_dangerous_freq.c | 23 ++ .../main/subghz/subghz_extended_freq.c | 19 - applications/main/subghz/subghz_i.c | 3 +- .../main/subghz/views/subghz_read_raw.c | 8 + .../main/subghz/views/subghz_read_raw.h | 29 +- applications/main/subghz/views/transmitter.c | 11 + applications/main/subghz/views/transmitter.h | 20 +- firmware/targets/f7/api_symbols.csv | 37 -- lib/subghz/SConscript | 2 +- .../cc1101_int/cc1101_int_interconnect.c | 96 +++++ .../cc1101_int/cc1101_int_interconnect.h | 8 + lib/subghz/devices/device_registry.h | 13 + lib/subghz/devices/devices.h | 52 +++ lib/subghz/devices/preset.h | 13 + lib/subghz/devices/registry.c | 76 ++++ lib/subghz/devices/registry.h | 40 ++ lib/subghz/devices/types.h | 91 +++++ lib/subghz/protocols/alutech_at_4n.h | 8 - lib/subghz/protocols/keeloq.h | 8 - lib/subghz/protocols/nice_flor_s.h | 8 - lib/subghz/protocols/raw.c | 25 +- lib/subghz/protocols/raw.h | 6 +- lib/subghz/protocols/secplus_v2.h | 8 - lib/subghz/protocols/somfy_telis.h | 8 - lib/subghz/protocols/star_line.h | 8 - lib/subghz/subghz_file_encoder_worker.c | 13 +- lib/subghz/subghz_file_encoder_worker.h | 7 +- lib/subghz/subghz_setting.c | 32 +- lib/subghz/subghz_tx_rx_worker.c | 47 ++- lib/subghz/subghz_tx_rx_worker.h | 7 +- lib/subghz/types.h | 6 + 57 files changed, 1452 insertions(+), 316 deletions(-) create mode 100644 applications/drivers/application.fam create mode 100644 applications/drivers/subghz/application.fam create mode 100644 applications/drivers/subghz/cc1101_ext/cc1101_ext.h create mode 100644 applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.c create mode 100644 applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h rename applications/external/subghz_playlist/{ => helpers}/radio_device_loader.c (100%) rename applications/external/subghz_playlist/{ => helpers}/radio_device_loader.h (100%) create mode 100644 applications/external/subghz_remote/helpers/txrx/subghz_txrx.h create mode 100644 applications/main/subghz/subghz_dangerous_freq.c delete mode 100644 applications/main/subghz/subghz_extended_freq.c create mode 100644 lib/subghz/devices/cc1101_int/cc1101_int_interconnect.c create mode 100644 lib/subghz/devices/cc1101_int/cc1101_int_interconnect.h create mode 100644 lib/subghz/devices/device_registry.h create mode 100644 lib/subghz/devices/devices.h create mode 100644 lib/subghz/devices/preset.h create mode 100644 lib/subghz/devices/registry.c create mode 100644 lib/subghz/devices/registry.h create mode 100644 lib/subghz/devices/types.h diff --git a/applications/drivers/application.fam b/applications/drivers/application.fam new file mode 100644 index 000000000..dc70e630c --- /dev/null +++ b/applications/drivers/application.fam @@ -0,0 +1,6 @@ +# Placeholder +App( + appid="drivers", + name="Drivers device", + apptype=FlipperAppType.METAPACKAGE, +) diff --git a/applications/drivers/subghz/application.fam b/applications/drivers/subghz/application.fam new file mode 100644 index 000000000..aaf0e1bd9 --- /dev/null +++ b/applications/drivers/subghz/application.fam @@ -0,0 +1,8 @@ +App( + appid="radio_device_cc1101_ext", + apptype=FlipperAppType.PLUGIN, + targets=["f7"], + entry_point="subghz_device_cc1101_ext_ep", + requires=["subghz"], + fap_libs=["hwdrivers"], +) diff --git a/applications/drivers/subghz/cc1101_ext/cc1101_ext.h b/applications/drivers/subghz/cc1101_ext/cc1101_ext.h new file mode 100644 index 000000000..d972fcb66 --- /dev/null +++ b/applications/drivers/subghz/cc1101_ext/cc1101_ext.h @@ -0,0 +1,206 @@ +/** + * @file furi_hal_subghz.h + * SubGhz HAL API + */ + +#pragma once +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Mirror RX/TX async modulation signal to specified pin + * + * @warning Configures pin to output mode. Make sure it is not connected + * directly to power or ground. + * + * @param[in] pin pointer to the gpio pin structure or NULL to disable + */ +void subghz_device_cc1101_ext_set_async_mirror_pin(const GpioPin* pin); + +/** Get data GPIO + * + * @return pointer to the gpio pin structure + */ +const GpioPin* subghz_device_cc1101_ext_get_data_gpio(); + +/** Initialize device + * + * @return true if success + */ +bool subghz_device_cc1101_ext_alloc(); + +/** Deinitialize device + */ +void subghz_device_cc1101_ext_free(); + +/** Check and switch to power save mode Used by internal API-HAL + * initialization routine Can be used to reinitialize device to safe state and + * send it to sleep + */ +bool subghz_device_cc1101_ext_is_connect(); + +/** Send device to sleep mode + */ +void subghz_device_cc1101_ext_sleep(); + +/** Dump info to stdout + */ +void subghz_device_cc1101_ext_dump_state(); + +/** Load custom registers from preset + * + * @param preset_data registers to load + */ +void subghz_device_cc1101_ext_load_custom_preset(const uint8_t* preset_data); + +/** Load registers + * + * @param data Registers data + */ +void subghz_device_cc1101_ext_load_registers(const uint8_t* data); + +/** Load PATABLE + * + * @param data 8 uint8_t values + */ +void subghz_device_cc1101_ext_load_patable(const uint8_t data[8]); + +/** Write packet to FIFO + * + * @param data bytes array + * @param size size + */ +void subghz_device_cc1101_ext_write_packet(const uint8_t* data, uint8_t size); + +/** Check if receive pipe is not empty + * + * @return true if not empty + */ +bool subghz_device_cc1101_ext_rx_pipe_not_empty(); + +/** Check if received data crc is valid + * + * @return true if valid + */ +bool subghz_device_cc1101_ext_is_rx_data_crc_valid(); + +/** Read packet from FIFO + * + * @param data pointer + * @param size size + */ +void subghz_device_cc1101_ext_read_packet(uint8_t* data, uint8_t* size); + +/** Flush rx FIFO buffer + */ +void subghz_device_cc1101_ext_flush_rx(); + +/** Flush tx FIFO buffer + */ +void subghz_device_cc1101_ext_flush_tx(); + +/** Shutdown Issue SPWD command + * @warning registers content will be lost + */ +void subghz_device_cc1101_ext_shutdown(); + +/** Reset Issue reset command + * @warning registers content will be lost + */ +void subghz_device_cc1101_ext_reset(); + +/** Switch to Idle + */ +void subghz_device_cc1101_ext_idle(); + +/** Switch to Receive + */ +void subghz_device_cc1101_ext_rx(); + +/** Switch to Transmit + * + * @return true if the transfer is allowed by belonging to the region + */ +bool subghz_device_cc1101_ext_tx(); + +/** Get RSSI value in dBm + * + * @return RSSI value + */ +float subghz_device_cc1101_ext_get_rssi(); + +/** Get LQI + * + * @return LQI value + */ +uint8_t subghz_device_cc1101_ext_get_lqi(); + +/** Check if frequency is in valid range + * + * @param value frequency in Hz + * + * @return true if frequency is valid, otherwise false + */ +bool subghz_device_cc1101_ext_is_frequency_valid(uint32_t value); + +/** Set frequency + * + * @param value frequency in Hz + * + * @return real frequency in Hz + */ +uint32_t subghz_device_cc1101_ext_set_frequency(uint32_t value); + +/* High Level API */ + +/** Signal Timings Capture callback */ +typedef void (*SubGhzDeviceCC1101ExtCaptureCallback)(bool level, uint32_t duration, void* context); + +/** Enable signal timings capture Initializes GPIO and TIM2 for timings capture + * + * @param callback SubGhzDeviceCC1101ExtCaptureCallback + * @param context callback context + */ +void subghz_device_cc1101_ext_start_async_rx( + SubGhzDeviceCC1101ExtCaptureCallback callback, + void* context); + +/** Disable signal timings capture Resets GPIO and TIM2 + */ +void subghz_device_cc1101_ext_stop_async_rx(); + +/** Async TX callback type + * @param context callback context + * @return LevelDuration + */ +typedef LevelDuration (*SubGhzDeviceCC1101ExtCallback)(void* context); + +/** Start async TX Initializes GPIO, TIM2 and DMA1 for signal output + * + * @param callback SubGhzDeviceCC1101ExtCallback + * @param context callback context + * + * @return true if the transfer is allowed by belonging to the region + */ +bool subghz_device_cc1101_ext_start_async_tx(SubGhzDeviceCC1101ExtCallback callback, void* context); + +/** Wait for async transmission to complete + * + * @return true if TX complete + */ +bool subghz_device_cc1101_ext_is_async_tx_complete(); + +/** Stop async transmission and cleanup resources Resets GPIO, TIM2, and DMA1 + */ +void subghz_device_cc1101_ext_stop_async_tx(); + +#ifdef __cplusplus +} +#endif diff --git a/applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.c b/applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.c new file mode 100644 index 000000000..51f5a0d1d --- /dev/null +++ b/applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.c @@ -0,0 +1,110 @@ +#include "cc1101_ext_interconnect.h" +#include "cc1101_ext.h" +#include + +#define TAG "SubGhzDeviceCC1101Ext" + +static bool subghz_device_cc1101_ext_interconnect_is_frequency_valid(uint32_t frequency) { + bool ret = subghz_device_cc1101_ext_is_frequency_valid(frequency); + if(!ret) { + furi_crash("SubGhz: Incorrect frequency."); + } + return ret; +} + +static uint32_t subghz_device_cc1101_ext_interconnect_set_frequency(uint32_t frequency) { + subghz_device_cc1101_ext_interconnect_is_frequency_valid(frequency); + return subghz_device_cc1101_ext_set_frequency(frequency); +} + +static bool subghz_device_cc1101_ext_interconnect_start_async_tx(void* callback, void* context) { + return subghz_device_cc1101_ext_start_async_tx( + (SubGhzDeviceCC1101ExtCallback)callback, context); +} + +static void subghz_device_cc1101_ext_interconnect_start_async_rx(void* callback, void* context) { + subghz_device_cc1101_ext_start_async_rx( + (SubGhzDeviceCC1101ExtCaptureCallback)callback, context); +} + +static void subghz_device_cc1101_ext_interconnect_load_preset( + FuriHalSubGhzPreset preset, + uint8_t* preset_data) { + switch(preset) { + case FuriHalSubGhzPresetOok650Async: + subghz_device_cc1101_ext_load_custom_preset( + subghz_device_cc1101_preset_ook_650khz_async_regs); + break; + case FuriHalSubGhzPresetOok270Async: + subghz_device_cc1101_ext_load_custom_preset( + subghz_device_cc1101_preset_ook_270khz_async_regs); + break; + case FuriHalSubGhzPreset2FSKDev238Async: + subghz_device_cc1101_ext_load_custom_preset( + subghz_device_cc1101_preset_2fsk_dev2_38khz_async_regs); + break; + case FuriHalSubGhzPreset2FSKDev476Async: + subghz_device_cc1101_ext_load_custom_preset( + subghz_device_cc1101_preset_2fsk_dev47_6khz_async_regs); + break; + case FuriHalSubGhzPresetMSK99_97KbAsync: + subghz_device_cc1101_ext_load_custom_preset( + subghz_device_cc1101_preset_msk_99_97kb_async_regs); + break; + case FuriHalSubGhzPresetGFSK9_99KbAsync: + subghz_device_cc1101_ext_load_custom_preset( + subghz_device_cc1101_preset_gfsk_9_99kb_async_regs); + break; + + default: + subghz_device_cc1101_ext_load_custom_preset(preset_data); + } +} + +const SubGhzDeviceInterconnect subghz_device_cc1101_ext_interconnect = { + .begin = subghz_device_cc1101_ext_alloc, + .end = subghz_device_cc1101_ext_free, + .is_connect = subghz_device_cc1101_ext_is_connect, + .reset = subghz_device_cc1101_ext_reset, + .sleep = subghz_device_cc1101_ext_sleep, + .idle = subghz_device_cc1101_ext_idle, + .load_preset = subghz_device_cc1101_ext_interconnect_load_preset, + .set_frequency = subghz_device_cc1101_ext_interconnect_set_frequency, + .is_frequency_valid = subghz_device_cc1101_ext_is_frequency_valid, + .set_async_mirror_pin = subghz_device_cc1101_ext_set_async_mirror_pin, + .get_data_gpio = subghz_device_cc1101_ext_get_data_gpio, + + .set_tx = subghz_device_cc1101_ext_tx, + .flush_tx = subghz_device_cc1101_ext_flush_tx, + .start_async_tx = subghz_device_cc1101_ext_interconnect_start_async_tx, + .is_async_complete_tx = subghz_device_cc1101_ext_is_async_tx_complete, + .stop_async_tx = subghz_device_cc1101_ext_stop_async_tx, + + .set_rx = subghz_device_cc1101_ext_rx, + .flush_rx = subghz_device_cc1101_ext_flush_rx, + .start_async_rx = subghz_device_cc1101_ext_interconnect_start_async_rx, + .stop_async_rx = subghz_device_cc1101_ext_stop_async_rx, + + .get_rssi = subghz_device_cc1101_ext_get_rssi, + .get_lqi = subghz_device_cc1101_ext_get_lqi, + + .rx_pipe_not_empty = subghz_device_cc1101_ext_rx_pipe_not_empty, + .is_rx_data_crc_valid = subghz_device_cc1101_ext_is_rx_data_crc_valid, + .read_packet = subghz_device_cc1101_ext_read_packet, + .write_packet = subghz_device_cc1101_ext_write_packet, +}; + +const SubGhzDevice subghz_device_cc1101_ext = { + .name = SUBGHZ_DEVICE_CC1101_EXT_NAME, + .interconnect = &subghz_device_cc1101_ext_interconnect, +}; + +static const FlipperAppPluginDescriptor subghz_device_cc1101_ext_descriptor = { + .appid = SUBGHZ_RADIO_DEVICE_PLUGIN_APP_ID, + .ep_api_version = SUBGHZ_RADIO_DEVICE_PLUGIN_API_VERSION, + .entry_point = &subghz_device_cc1101_ext, +}; + +const FlipperAppPluginDescriptor* subghz_device_cc1101_ext_ep() { + return &subghz_device_cc1101_ext_descriptor; +} \ No newline at end of file diff --git a/applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h b/applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h new file mode 100644 index 000000000..cf1ff3ee0 --- /dev/null +++ b/applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h @@ -0,0 +1,8 @@ +#pragma once +#include + +#define SUBGHZ_DEVICE_CC1101_EXT_NAME "cc1101_ext" + +typedef struct SubGhzDeviceCC1101Ext SubGhzDeviceCC1101Ext; + +const FlipperAppPluginDescriptor* subghz_device_cc1101_ext_ep(); diff --git a/applications/external/subghz_playlist/radio_device_loader.c b/applications/external/subghz_playlist/helpers/radio_device_loader.c similarity index 100% rename from applications/external/subghz_playlist/radio_device_loader.c rename to applications/external/subghz_playlist/helpers/radio_device_loader.c diff --git a/applications/external/subghz_playlist/radio_device_loader.h b/applications/external/subghz_playlist/helpers/radio_device_loader.h similarity index 100% rename from applications/external/subghz_playlist/radio_device_loader.h rename to applications/external/subghz_playlist/helpers/radio_device_loader.h diff --git a/applications/external/subghz_playlist/playlist.c b/applications/external/subghz_playlist/playlist.c index dc7b7a45b..5ed896780 100644 --- a/applications/external/subghz_playlist/playlist.c +++ b/applications/external/subghz_playlist/playlist.c @@ -720,7 +720,7 @@ void playlist_free(Playlist* app) { free(app); } -int32_t playlist_app(char* p) { +int32_t playlist_app(void* p) { UNUSED(p); dolphin_deed(DolphinDeedPluginStart); diff --git a/applications/external/subghz_remote/application.fam b/applications/external/subghz_remote/application.fam index 4af6a1275..030ed030f 100644 --- a/applications/external/subghz_remote/application.fam +++ b/applications/external/subghz_remote/application.fam @@ -1,29 +1,10 @@ App( appid="subghz_remote", name="Sub-GHz Remote", -<<<<<<< HEAD:applications/external/subghz_remote/application.fam apptype=FlipperAppType.EXTERNAL, entry_point="subghz_remote_app", - cdefines=[ - "APP_SUBGHZREMOTE", - "SUBREM_LIGHT", - ], - requires=[ - "gui", - "dialogs", - ], stack_size=2 * 1024, order=11, fap_icon="subrem_10px.png", fap_category="Sub-GHz", -======= - apptype=FlipperAppType.MENUEXTERNAL, - entry_point="subghz_remote_app", - icon="A_SubGHzRemote_14", - stack_size=2 * 1024, - order=11, - fap_libs=["assets",], - fap_icon="icon.png", - fap_category="Sub-Ghz", ->>>>>>> ea357b8eafe91c3a07cb89c3b90d03b78e2633ff:applications/main/subghz_remote/application.fam ) diff --git a/applications/external/subghz_remote/helpers/txrx/subghz_txrx.h b/applications/external/subghz_remote/helpers/txrx/subghz_txrx.h new file mode 100644 index 000000000..8bb7f2aee --- /dev/null +++ b/applications/external/subghz_remote/helpers/txrx/subghz_txrx.h @@ -0,0 +1,375 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +typedef struct SubGhzTxRx SubGhzTxRx; + +typedef void (*SubGhzTxRxNeedSaveCallback)(void* context); + +typedef enum { + SubGhzTxRxStartTxStateOk, + SubGhzTxRxStartTxStateErrorOnlyRx, + SubGhzTxRxStartTxStateErrorParserOthers, +} SubGhzTxRxStartTxState; + +// Type from subghz_types.h need for txrx working +/** SubGhzTxRx state */ +typedef enum { + SubGhzTxRxStateIDLE, + SubGhzTxRxStateRx, + SubGhzTxRxStateTx, + SubGhzTxRxStateSleep, +} SubGhzTxRxState; + +/** SubGhzHopperState state */ +typedef enum { + SubGhzHopperStateOFF, + SubGhzHopperStateRunning, + SubGhzHopperStatePause, + SubGhzHopperStateRSSITimeOut, +} SubGhzHopperState; + +/** SubGhzSpeakerState state */ +typedef enum { + SubGhzSpeakerStateDisable, + SubGhzSpeakerStateShutdown, + SubGhzSpeakerStateEnable, +} SubGhzSpeakerState; + +/** SubGhzRadioDeviceType */ +typedef enum { + SubGhzRadioDeviceTypeAuto, + SubGhzRadioDeviceTypeInternal, + SubGhzRadioDeviceTypeExternalCC1101, +} SubGhzRadioDeviceType; + +/** + * Allocate SubGhzTxRx + * + * @return SubGhzTxRx* pointer to SubGhzTxRx + */ +SubGhzTxRx* subghz_txrx_alloc(); + +/** + * Free SubGhzTxRx + * + * @param instance Pointer to a SubGhzTxRx + */ +void subghz_txrx_free(SubGhzTxRx* instance); + +/** + * Check if the database is loaded + * + * @param instance Pointer to a SubGhzTxRx + * @return bool True if the database is loaded + */ +bool subghz_txrx_is_database_loaded(SubGhzTxRx* instance); + +/** + * Set preset + * + * @param instance Pointer to a SubGhzTxRx + * @param preset_name Name of preset + * @param frequency Frequency in Hz + * @param preset_data Data of preset + * @param preset_data_size Size of preset data + */ +void subghz_txrx_set_preset( + SubGhzTxRx* instance, + const char* preset_name, + uint32_t frequency, + uint8_t* preset_data, + size_t preset_data_size); + +/** + * Get name of preset + * + * @param instance Pointer to a SubGhzTxRx + * @param preset String of preset + * @return const char* Name of preset + */ +const char* subghz_txrx_get_preset_name(SubGhzTxRx* instance, const char* preset); + +/** + * Get of preset + * + * @param instance Pointer to a SubGhzTxRx + * @return SubGhzRadioPreset Preset + */ +SubGhzRadioPreset subghz_txrx_get_preset(SubGhzTxRx* instance); + +/** + * Get string frequency and modulation + * + * @param instance Pointer to a SubGhzTxRx + * @param frequency Pointer to a string frequency + * @param modulation Pointer to a string modulation + */ +void subghz_txrx_get_frequency_and_modulation( + SubGhzTxRx* instance, + FuriString* frequency, + FuriString* modulation, + bool long_name); + +/** + * Start TX CC1101 + * + * @param instance Pointer to a SubGhzTxRx + * @param flipper_format Pointer to a FlipperFormat + * @return SubGhzTxRxStartTxState + */ +SubGhzTxRxStartTxState subghz_txrx_tx_start(SubGhzTxRx* instance, FlipperFormat* flipper_format); + +/** + * Start RX CC1101 + * + * @param instance Pointer to a SubGhzTxRx + */ +void subghz_txrx_rx_start(SubGhzTxRx* instance); + +/** + * Stop TX/RX CC1101 + * + * @param instance Pointer to a SubGhzTxRx + */ +void subghz_txrx_stop(SubGhzTxRx* instance); + +/** + * Set sleep mode CC1101 + * + * @param instance Pointer to a SubGhzTxRx + */ +void subghz_txrx_sleep(SubGhzTxRx* instance); + +/** + * Update frequency CC1101 in automatic mode (hopper) + * + * @param instance Pointer to a SubGhzTxRx + */ +void subghz_txrx_hopper_update(SubGhzTxRx* instance); + +/** + * Get state hopper + * + * @param instance Pointer to a SubGhzTxRx + * @return SubGhzHopperState + */ +SubGhzHopperState subghz_txrx_hopper_get_state(SubGhzTxRx* instance); + +/** + * Set state hopper + * + * @param instance Pointer to a SubGhzTxRx + * @param state State hopper + */ +void subghz_txrx_hopper_set_state(SubGhzTxRx* instance, SubGhzHopperState state); + +/** + * Unpause hopper + * + * @param instance Pointer to a SubGhzTxRx + */ +void subghz_txrx_hopper_unpause(SubGhzTxRx* instance); + +/** + * Set pause hopper + * + * @param instance Pointer to a SubGhzTxRx + */ +void subghz_txrx_hopper_pause(SubGhzTxRx* instance); + +/** + * Speaker on + * + * @param instance Pointer to a SubGhzTxRx + */ +void subghz_txrx_speaker_on(SubGhzTxRx* instance); + +/** + * Speaker off + * + * @param instance Pointer to a SubGhzTxRx + */ +void subghz_txrx_speaker_off(SubGhzTxRx* instance); + +/** + * Speaker mute + * + * @param instance Pointer to a SubGhzTxRx + */ +void subghz_txrx_speaker_mute(SubGhzTxRx* instance); + +/** + * Speaker unmute + * + * @param instance Pointer to a SubGhzTxRx + */ +void subghz_txrx_speaker_unmute(SubGhzTxRx* instance); + +/** + * Set state speaker + * + * @param instance Pointer to a SubGhzTxRx + * @param state State speaker + */ +void subghz_txrx_speaker_set_state(SubGhzTxRx* instance, SubGhzSpeakerState state); + +/** + * Get state speaker + * + * @param instance Pointer to a SubGhzTxRx + * @return SubGhzSpeakerState + */ +SubGhzSpeakerState subghz_txrx_speaker_get_state(SubGhzTxRx* instance); + +/** + * load decoder by name protocol + * + * @param instance Pointer to a SubGhzTxRx + * @param name_protocol Name protocol + * @return bool True if the decoder is loaded + */ +bool subghz_txrx_load_decoder_by_name_protocol(SubGhzTxRx* instance, const char* name_protocol); + +/** + * Get decoder + * + * @param instance Pointer to a SubGhzTxRx + * @return SubGhzProtocolDecoderBase* Pointer to a SubGhzProtocolDecoderBase + */ +SubGhzProtocolDecoderBase* subghz_txrx_get_decoder(SubGhzTxRx* instance); + +/** + * Set callback for save data + * + * @param instance Pointer to a SubGhzTxRx + * @param callback Callback for save data + * @param context Context for callback + */ +void subghz_txrx_set_need_save_callback( + SubGhzTxRx* instance, + SubGhzTxRxNeedSaveCallback callback, + void* context); + +/** + * Get pointer to a load data key + * + * @param instance Pointer to a SubGhzTxRx + * @return FlipperFormat* + */ +FlipperFormat* subghz_txrx_get_fff_data(SubGhzTxRx* instance); + +/** + * Get pointer to a SugGhzSetting + * + * @param instance Pointer to a SubGhzTxRx + * @return SubGhzSetting* + */ +SubGhzSetting* subghz_txrx_get_setting(SubGhzTxRx* instance); + +/** + * Is it possible to save this protocol + * + * @param instance Pointer to a SubGhzTxRx + * @return bool True if it is possible to save this protocol + */ +bool subghz_txrx_protocol_is_serializable(SubGhzTxRx* instance); + +/** + * Is it possible to send this protocol + * + * @param instance Pointer to a SubGhzTxRx + * @return bool True if it is possible to send this protocol + */ +bool subghz_txrx_protocol_is_transmittable(SubGhzTxRx* instance, bool check_type); + +/** + * Set filter, what types of decoder to use + * + * @param instance Pointer to a SubGhzTxRx + * @param filter Filter + */ +void subghz_txrx_receiver_set_filter(SubGhzTxRx* instance, SubGhzProtocolFlag filter); + +/** + * Set callback for receive data + * + * @param instance Pointer to a SubGhzTxRx + * @param callback Callback for receive data + * @param context Context for callback + */ +void subghz_txrx_set_rx_calback( + SubGhzTxRx* instance, + SubGhzReceiverCallback callback, + void* context); + +/** + * Set callback for Raw decoder, end of data transfer + * + * @param instance Pointer to a SubGhzTxRx + * @param callback Callback for Raw decoder, end of data transfer + * @param context Context for callback + */ +void subghz_txrx_set_raw_file_encoder_worker_callback_end( + SubGhzTxRx* instance, + SubGhzProtocolEncoderRAWCallbackEnd callback, + void* context); + +/* Checking if an external radio device is connected +* +* @param instance Pointer to a SubGhzTxRx +* @param name Name of external radio device +* @return bool True if is connected to the external radio device +*/ +bool subghz_txrx_radio_device_is_external_connected(SubGhzTxRx* instance, const char* name); + +/* Set the selected radio device to use +* +* @param instance Pointer to a SubGhzTxRx +* @param radio_device_type Radio device type +* @return SubGhzRadioDeviceType Type of installed radio device +*/ +SubGhzRadioDeviceType + subghz_txrx_radio_device_set(SubGhzTxRx* instance, SubGhzRadioDeviceType radio_device_type); + +/* Get the selected radio device to use +* +* @param instance Pointer to a SubGhzTxRx +* @return SubGhzRadioDeviceType Type of installed radio device +*/ +SubGhzRadioDeviceType subghz_txrx_radio_device_get(SubGhzTxRx* instance); + +/* Get RSSI the selected radio device to use +* +* @param instance Pointer to a SubGhzTxRx +* @return float RSSI +*/ +float subghz_txrx_radio_device_get_rssi(SubGhzTxRx* instance); + +/* Get name the selected radio device to use +* +* @param instance Pointer to a SubGhzTxRx +* @return const char* Name of installed radio device +*/ +const char* subghz_txrx_radio_device_get_name(SubGhzTxRx* instance); + +/* Get get intelligence whether frequency the selected radio device to use +* +* @param instance Pointer to a SubGhzTxRx +* @return bool True if the frequency is valid +*/ +bool subghz_txrx_radio_device_is_frequecy_valid(SubGhzTxRx* instance, uint32_t frequency); + +bool subghz_txrx_radio_device_is_tx_alowed(SubGhzTxRx* instance, uint32_t frequency); + +void subghz_txrx_set_debug_pin_state(SubGhzTxRx* instance, bool state); +bool subghz_txrx_get_debug_pin_state(SubGhzTxRx* instance); + +void subghz_txrx_reset_dynamic_and_custom_btns(SubGhzTxRx* instance); + +SubGhzReceiver* subghz_txrx_get_receiver(SubGhzTxRx* instance); // TODO use only in DecodeRaw diff --git a/applications/external/subghz_remote_configurator/application.fam b/applications/external/subghz_remote_configurator/application.fam index 4604a509b..29b7ed082 100644 --- a/applications/external/subghz_remote_configurator/application.fam +++ b/applications/external/subghz_remote_configurator/application.fam @@ -10,6 +10,6 @@ App( stack_size=2 * 1024, order=50, fap_description="File Editor for the SubGhz Remote app", - fap_category="Sub-Ghz", + fap_category="Sub-GHz", fap_icon="subrem_10px.png", ) diff --git a/applications/main/subghz/application.fam b/applications/main/subghz/application.fam index 7da1b9081..37e131cc4 100644 --- a/applications/main/subghz/application.fam +++ b/applications/main/subghz/application.fam @@ -11,10 +11,7 @@ App( ], provides=[ "subghz_start", - "subghz_load_extended_settings", - ], - sdk_headers=[ - "helpers/subghz_txrx.h", + "subghz_load_dangerous_settings", ], icon="A_Sub1ghz_14", stack_size=3 * 1024, @@ -32,9 +29,9 @@ App( ) App( - appid="subghz_load_extended_settings", + appid="subghz_load_dangerous_settings", apptype=FlipperAppType.STARTUP, - entry_point="subghz_extended_freq", + entry_point="subghz_dangerous_freq", requires=["storage", "subghz"], order=650, ) diff --git a/applications/main/subghz/helpers/subghz_chat.c b/applications/main/subghz/helpers/subghz_chat.c index b589ba5d5..6e2ac7388 100644 --- a/applications/main/subghz/helpers/subghz_chat.c +++ b/applications/main/subghz/helpers/subghz_chat.c @@ -76,12 +76,15 @@ void subghz_chat_worker_free(SubGhzChatWorker* instance) { free(instance); } -bool subghz_chat_worker_start(SubGhzChatWorker* instance, uint32_t frequency) { +bool subghz_chat_worker_start( + SubGhzChatWorker* instance, + const SubGhzDevice* device, + uint32_t frequency) { furi_assert(instance); furi_assert(!instance->worker_running); bool res = false; - if(subghz_tx_rx_worker_start(instance->subghz_txrx, frequency)) { + if(subghz_tx_rx_worker_start(instance->subghz_txrx, device, frequency)) { furi_message_queue_reset(instance->event_queue); subghz_tx_rx_worker_set_callback_have_read( instance->subghz_txrx, subghz_chat_worker_update_rx_event_chat, instance); diff --git a/applications/main/subghz/helpers/subghz_chat.h b/applications/main/subghz/helpers/subghz_chat.h index b418bbdbf..2c454b75d 100644 --- a/applications/main/subghz/helpers/subghz_chat.h +++ b/applications/main/subghz/helpers/subghz_chat.h @@ -1,5 +1,6 @@ #pragma once #include "../subghz_i.h" +#include #include typedef struct SubGhzChatWorker SubGhzChatWorker; @@ -20,7 +21,10 @@ typedef struct { SubGhzChatWorker* subghz_chat_worker_alloc(Cli* cli); void subghz_chat_worker_free(SubGhzChatWorker* instance); -bool subghz_chat_worker_start(SubGhzChatWorker* instance, uint32_t frequency); +bool subghz_chat_worker_start( + SubGhzChatWorker* instance, + const SubGhzDevice* device, + uint32_t frequency); void subghz_chat_worker_stop(SubGhzChatWorker* instance); bool subghz_chat_worker_is_running(SubGhzChatWorker* instance); SubGhzChatEvent subghz_chat_worker_get_event_chat(SubGhzChatWorker* instance); diff --git a/applications/main/subghz/helpers/subghz_threshold_rssi.c b/applications/main/subghz/helpers/subghz_threshold_rssi.c index 04a06bc17..07d7bccf9 100644 --- a/applications/main/subghz/helpers/subghz_threshold_rssi.c +++ b/applications/main/subghz/helpers/subghz_threshold_rssi.c @@ -32,9 +32,8 @@ float subghz_threshold_rssi_get(SubGhzThresholdRssi* instance) { return instance->threshold_rssi; } -SubGhzThresholdRssiData subghz_threshold_get_rssi_data(SubGhzThresholdRssi* instance) { +SubGhzThresholdRssiData subghz_threshold_get_rssi_data(SubGhzThresholdRssi* instance, float rssi) { furi_assert(instance); - float rssi = furi_hal_subghz_get_rssi(); SubGhzThresholdRssiData ret = {.rssi = rssi, .is_above = false}; if(float_is_equal(instance->threshold_rssi, SUBGHZ_RAW_THRESHOLD_MIN)) { diff --git a/applications/main/subghz/helpers/subghz_threshold_rssi.h b/applications/main/subghz/helpers/subghz_threshold_rssi.h index e28092acb..1d588e271 100644 --- a/applications/main/subghz/helpers/subghz_threshold_rssi.h +++ b/applications/main/subghz/helpers/subghz_threshold_rssi.h @@ -38,6 +38,7 @@ float subghz_threshold_rssi_get(SubGhzThresholdRssi* instance); /** Check threshold * * @param instance Pointer to a SubGhzThresholdRssi + * @param rssi Current RSSI * @return SubGhzThresholdRssiData */ -SubGhzThresholdRssiData subghz_threshold_get_rssi_data(SubGhzThresholdRssi* instance); +SubGhzThresholdRssiData subghz_threshold_get_rssi_data(SubGhzThresholdRssi* instance, float rssi); diff --git a/applications/main/subghz/helpers/subghz_txrx.c b/applications/main/subghz/helpers/subghz_txrx.c index db833d272..d878c0e04 100644 --- a/applications/main/subghz/helpers/subghz_txrx.c +++ b/applications/main/subghz/helpers/subghz_txrx.c @@ -8,6 +8,21 @@ #define TAG "SubGhz" +static void subghz_txrx_radio_device_power_on(SubGhzTxRx* instance) { + UNUSED(instance); + uint8_t attempts = 0; + while(!furi_hal_power_is_otg_enabled() && attempts++ < 5) { + furi_hal_power_enable_otg(); + //CC1101 power-up time + furi_delay_ms(10); + } +} + +static void subghz_txrx_radio_device_power_off(SubGhzTxRx* instance) { + UNUSED(instance); + if(furi_hal_power_is_otg_enabled()) furi_hal_power_disable_otg(); +} + SubGhzTxRx* subghz_txrx_alloc() { SubGhzTxRx* instance = malloc(sizeof(SubGhzTxRx)); instance->setting = subghz_setting_alloc(); @@ -28,16 +43,15 @@ SubGhzTxRx* subghz_txrx_alloc() { instance->fff_data = flipper_format_string_alloc(); instance->environment = subghz_environment_alloc(); - instance->is_database_loaded = subghz_environment_load_keystore( - instance->environment, EXT_PATH("subghz/assets/keeloq_mfcodes")); - subghz_environment_load_keystore( - instance->environment, EXT_PATH("subghz/assets/keeloq_mfcodes_user")); + instance->is_database_loaded = + subghz_environment_load_keystore(instance->environment, SUBGHZ_KEYSTORE_DIR_NAME); + subghz_environment_load_keystore(instance->environment, SUBGHZ_KEYSTORE_DIR_USER_NAME); subghz_environment_set_came_atomo_rainbow_table_file_name( - instance->environment, EXT_PATH("subghz/assets/came_atomo")); + instance->environment, SUBGHZ_CAME_ATOMO_DIR_NAME); subghz_environment_set_alutech_at_4n_rainbow_table_file_name( - instance->environment, EXT_PATH("subghz/assets/alutech_at_4n")); + instance->environment, SUBGHZ_ALUTECH_AT_4N_DIR_NAME); subghz_environment_set_nice_flor_s_rainbow_table_file_name( - instance->environment, EXT_PATH("subghz/assets/nice_flor_s")); + instance->environment, SUBGHZ_NICE_FLOR_S_DIR_NAME); subghz_environment_set_protocol_registry( instance->environment, (void*)&subghz_protocol_registry); instance->receiver = subghz_receiver_alloc_init(instance->environment); @@ -48,18 +62,32 @@ SubGhzTxRx* subghz_txrx_alloc() { instance->worker, (SubGhzWorkerPairCallback)subghz_receiver_decode); subghz_worker_set_context(instance->worker, instance->receiver); + //set default device External + subghz_devices_init(); + instance->radio_device_type = SubGhzRadioDeviceTypeInternal; + instance->radio_device_type = + subghz_txrx_radio_device_set(instance, SubGhzRadioDeviceTypeExternalCC1101); + return instance; } void subghz_txrx_free(SubGhzTxRx* instance) { furi_assert(instance); + if(instance->radio_device_type != SubGhzRadioDeviceTypeInternal) { + subghz_txrx_radio_device_power_off(instance); + subghz_devices_end(instance->radio_device); + } + + subghz_devices_deinit(); + subghz_worker_free(instance->worker); subghz_receiver_free(instance->receiver); subghz_environment_free(instance->environment); flipper_format_free(instance->fff_data); furi_string_free(instance->preset->name); subghz_setting_free(instance->setting); + free(instance->preset); free(instance); } @@ -145,7 +173,12 @@ static uint32_t subghz_txrx_rx(SubGhzTxRx* instance, uint32_t frequency) { subghz_devices_idle(instance->radio_device); - furi_hal_subghz_start_async_rx(subghz_worker_rx_callback, instance->worker); + uint32_t value = subghz_devices_set_frequency(instance->radio_device, frequency); + subghz_devices_flush_rx(instance->radio_device); + subghz_txrx_speaker_on(instance); + + subghz_devices_start_async_rx( + instance->radio_device, subghz_worker_rx_callback, instance->worker); subghz_worker_start(instance->worker); instance->txrx_state = SubGhzTxRxStateRx; return value; @@ -154,7 +187,7 @@ static uint32_t subghz_txrx_rx(SubGhzTxRx* instance, uint32_t frequency) { static void subghz_txrx_idle(SubGhzTxRx* instance) { furi_assert(instance); furi_assert(instance->txrx_state != SubGhzTxRxStateSleep); - furi_hal_subghz_idle(); + subghz_devices_idle(instance->radio_device); subghz_txrx_speaker_off(instance); instance->txrx_state = SubGhzTxRxStateIDLE; } @@ -165,24 +198,21 @@ static void subghz_txrx_rx_end(SubGhzTxRx* instance) { if(subghz_worker_is_running(instance->worker)) { subghz_worker_stop(instance->worker); - furi_hal_subghz_stop_async_rx(); + subghz_devices_stop_async_rx(instance->radio_device); } - furi_hal_subghz_idle(); + subghz_devices_idle(instance->radio_device); subghz_txrx_speaker_off(instance); instance->txrx_state = SubGhzTxRxStateIDLE; } void subghz_txrx_sleep(SubGhzTxRx* instance) { furi_assert(instance); - furi_hal_subghz_sleep(); + subghz_devices_sleep(instance->radio_device); instance->txrx_state = SubGhzTxRxStateSleep; } static bool subghz_txrx_tx(SubGhzTxRx* instance, uint32_t frequency) { furi_assert(instance); - if(!furi_hal_subghz_is_frequency_valid(frequency)) { - furi_crash("SubGhz: Incorrect TX frequency."); - } furi_assert(instance->txrx_state != SubGhzTxRxStateSleep); subghz_devices_idle(instance->radio_device); @@ -250,8 +280,8 @@ SubGhzTxRxStartTxState subghz_txrx_tx_start(SubGhzTxRx* instance, FlipperFormat* if(ret == SubGhzTxRxStartTxStateOk) { //Start TX - furi_hal_subghz_start_async_tx( - subghz_transmitter_yield, instance->transmitter); + subghz_devices_start_async_tx( + instance->radio_device, subghz_transmitter_yield, instance->transmitter); } } else { ret = SubGhzTxRxStartTxStateErrorParserOthers; @@ -294,7 +324,7 @@ static void subghz_txrx_tx_stop(SubGhzTxRx* instance) { furi_assert(instance); furi_assert(instance->txrx_state == SubGhzTxRxStateTx); //Stop TX - furi_hal_subghz_stop_async_tx(); + subghz_devices_stop_async_tx(instance->radio_device); subghz_transmitter_stop(instance->transmitter); subghz_transmitter_free(instance->transmitter); @@ -307,7 +337,6 @@ static void subghz_txrx_tx_stop(SubGhzTxRx* instance) { subghz_txrx_idle(instance); subghz_txrx_speaker_off(instance); //Todo: Show message - // notification_message(notifications, &sequence_reset_red); } FlipperFormat* subghz_txrx_get_fff_data(SubGhzTxRx* instance) { @@ -357,7 +386,7 @@ void subghz_txrx_hopper_update(SubGhzTxRx* instance) { float rssi = -127.0f; if(instance->hopper_state != SubGhzHopperStateRSSITimeOut) { // See RSSI Calculation timings in CC1101 17.3 RSSI - rssi = furi_hal_subghz_get_rssi(); + rssi = subghz_devices_get_rssi(instance->radio_device); // Stay if RSSI is high enough if(rssi > -90.0f) { @@ -638,4 +667,4 @@ void subghz_txrx_reset_dynamic_and_custom_btns(SubGhzTxRx* instance) { SubGhzReceiver* subghz_txrx_get_receiver(SubGhzTxRx* instance) { furi_assert(instance); return instance->receiver; -} +} \ No newline at end of file diff --git a/applications/main/subghz/helpers/subghz_txrx.h b/applications/main/subghz/helpers/subghz_txrx.h index d6d420352..76c7c8ead 100644 --- a/applications/main/subghz/helpers/subghz_txrx.h +++ b/applications/main/subghz/helpers/subghz_txrx.h @@ -7,10 +7,7 @@ #include #include #include - -#ifdef __cplusplus -extern "C" { -#endif +#include typedef struct SubGhzTxRx SubGhzTxRx; diff --git a/applications/main/subghz/helpers/subghz_txrx_i.h b/applications/main/subghz/helpers/subghz_txrx_i.h index 5240884ff..f058c2282 100644 --- a/applications/main/subghz/helpers/subghz_txrx_i.h +++ b/applications/main/subghz/helpers/subghz_txrx_i.h @@ -21,6 +21,8 @@ struct SubGhzTxRx { SubGhzTxRxState txrx_state; SubGhzSpeakerState speaker_state; + const SubGhzDevice* radio_device; + SubGhzRadioDeviceType radio_device_type; SubGhzTxRxNeedSaveCallback need_save_callback; void* need_save_context; diff --git a/applications/main/subghz/helpers/subghz_types.h b/applications/main/subghz/helpers/subghz_types.h index 2e7ca468d..0d748a80b 100644 --- a/applications/main/subghz/helpers/subghz_types.h +++ b/applications/main/subghz/helpers/subghz_types.h @@ -35,6 +35,13 @@ typedef enum { SubGhzSpeakerStateEnable, } SubGhzSpeakerState; +/** SubGhzRadioDeviceType */ +typedef enum { + SubGhzRadioDeviceTypeAuto, + SubGhzRadioDeviceTypeInternal, + SubGhzRadioDeviceTypeExternalCC1101, +} SubGhzRadioDeviceType; + /** SubGhzRxKeyState state */ typedef enum { SubGhzRxKeyStateIDLE, diff --git a/applications/main/subghz/scenes/subghz_scene_read_raw.c b/applications/main/subghz/scenes/subghz_scene_read_raw.c index 7394ace90..0be51244d 100644 --- a/applications/main/subghz/scenes/subghz_scene_read_raw.c +++ b/applications/main/subghz/scenes/subghz_scene_read_raw.c @@ -52,6 +52,9 @@ static void subghz_scene_read_raw_update_statusbar(void* context) { furi_string_free(frequency_str); furi_string_free(modulation_str); + + subghz_read_raw_set_radio_device_type( + subghz->subghz_read_raw, subghz_txrx_radio_device_get(subghz->txrx)); } void subghz_scene_read_raw_callback(SubGhzCustomEvent event, void* context) { @@ -116,15 +119,17 @@ void subghz_scene_read_raw_on_enter(void* context) { // Start sending immediately with favorites if(subghz->fav_timeout) { - with_view_model( - subghz->subghz_read_raw->view, - SubGhzReadRAWModel * model, - { - scene_manager_handle_custom_event( - subghz->scene_manager, SubGhzCustomEventViewReadRAWSendStart); - model->status = SubGhzReadRAWStatusTXRepeat; - }, - true); + scene_manager_handle_custom_event( + subghz->scene_manager, SubGhzCustomEventViewReadRAWSendStart); + // with_view_model( + // subghz->subghz_read_raw->view, + // SubGhzReadRAWModel * model, + // { + // scene_manager_handle_custom_event( + // subghz->scene_manager, SubGhzCustomEventViewReadRAWSendStart); + // model->status = SubGhzReadRAWStatusTXRepeat; + // }, + // true); } } @@ -267,7 +272,9 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { furi_string_printf( temp_str, "%s/%s%s", SUBGHZ_RAW_FOLDER, RAW_FILE_NAME, SUBGHZ_APP_EXTENSION); subghz_protocol_raw_gen_fff_data( - subghz_txrx_get_fff_data(subghz->txrx), furi_string_get_cstr(temp_str)); + subghz_txrx_get_fff_data(subghz->txrx), + furi_string_get_cstr(temp_str), + subghz_txrx_radio_device_get_name(subghz->txrx)); furi_string_free(temp_str); if(spl_count > 0) { @@ -327,8 +334,8 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { subghz_read_raw_update_sample_write( subghz->subghz_read_raw, subghz_protocol_raw_get_sample_write(decoder_raw)); - SubGhzThresholdRssiData ret_rssi = - subghz_threshold_get_rssi_data(subghz->threshold_rssi); + SubGhzThresholdRssiData ret_rssi = subghz_threshold_get_rssi_data( + subghz->threshold_rssi, subghz_txrx_radio_device_get_rssi(subghz->txrx)); subghz_read_raw_add_data_rssi( subghz->subghz_read_raw, ret_rssi.rssi, ret_rssi.is_above); subghz_protocol_raw_save_to_file_pause(decoder_raw, !ret_rssi.is_above); diff --git a/applications/main/subghz/scenes/subghz_scene_receiver.c b/applications/main/subghz/scenes/subghz_scene_receiver.c index 427e5e3de..ac09ebcea 100644 --- a/applications/main/subghz/scenes/subghz_scene_receiver.c +++ b/applications/main/subghz/scenes/subghz_scene_receiver.c @@ -80,6 +80,9 @@ static void subghz_scene_receiver_update_statusbar(void* context) { subghz->state_notifications = SubGhzNotificationStateIDLE; } furi_string_free(history_stat_str); + + subghz_view_receiver_set_radio_device_type( + subghz->subghz_receiver, subghz_txrx_radio_device_get(subghz->txrx)); } void subghz_scene_receiver_callback(SubGhzCustomEvent event, void* context) { diff --git a/applications/main/subghz/scenes/subghz_scene_rpc.c b/applications/main/subghz/scenes/subghz_scene_rpc.c index b07ad4a98..724abaf3e 100644 --- a/applications/main/subghz/scenes/subghz_scene_rpc.c +++ b/applications/main/subghz/scenes/subghz_scene_rpc.c @@ -14,6 +14,7 @@ void subghz_scene_rpc_on_enter(void* context) { popup_set_header(popup, "Sub-GHz", 89, 42, AlignCenter, AlignBottom); popup_set_text(popup, "RPC mode", 89, 44, AlignCenter, AlignTop); + popup_set_icon(popup, 0, 12, &I_RFIDDolphinSend_97x61); view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdPopup); diff --git a/applications/main/subghz/scenes/subghz_scene_save_name.c b/applications/main/subghz/scenes/subghz_scene_save_name.c index 845b40f41..07618d7ae 100644 --- a/applications/main/subghz/scenes/subghz_scene_save_name.c +++ b/applications/main/subghz/scenes/subghz_scene_save_name.c @@ -163,7 +163,8 @@ bool subghz_scene_save_name_on_event(void* context, SceneManagerEvent event) { SubGhzCustomEventManagerNoSet) { subghz_protocol_raw_gen_fff_data( subghz_txrx_get_fff_data(subghz->txrx), - furi_string_get_cstr(subghz->file_path)); + furi_string_get_cstr(subghz->file_path), + subghz_txrx_radio_device_get_name(subghz->txrx)); scene_manager_set_scene_state( subghz->scene_manager, SubGhzSceneReadRAW, SubGhzCustomEventManagerNoSet); } else { diff --git a/applications/main/subghz/scenes/subghz_scene_transmitter.c b/applications/main/subghz/scenes/subghz_scene_transmitter.c index c7c95f457..48dc69ea0 100644 --- a/applications/main/subghz/scenes/subghz_scene_transmitter.c +++ b/applications/main/subghz/scenes/subghz_scene_transmitter.c @@ -39,6 +39,8 @@ bool subghz_scene_transmitter_update_data_show(void* context) { furi_string_free(modulation_str); furi_string_free(key_str); } + subghz_view_transmitter_set_radio_device_type( + subghz->subghz_transmitter, subghz_txrx_radio_device_get(subghz->txrx)); return ret; } @@ -66,19 +68,19 @@ void subghz_scene_transmitter_on_enter(void* context) { // Auto send and exit with favorites if(subghz->fav_timeout) { - subghz_custom_btn_set(0); + // subghz_custom_btn_set(0); scene_manager_handle_custom_event( subghz->scene_manager, SubGhzCustomEventViewTransmitterSendStart); - with_view_model( - subghz->subghz_transmitter->view, - SubGhzViewTransmitterModel * model, - { model->show_button = false; }, - true); + // with_view_model( + // subghz->subghz_transmitter->view, + // SubGhzViewTransmitterModel * model, + // { model->show_button = false; }, + // true); subghz->fav_timer = furi_timer_alloc(fav_timer_callback, FuriTimerTypeOnce, subghz); furi_timer_start( subghz->fav_timer, XTREME_SETTINGS()->favorite_timeout * furi_kernel_get_tick_frequency()); - subghz->state_notifications = SubGhzNotificationStateTx; + // subghz->state_notifications = SubGhzNotificationStateTx; } } diff --git a/applications/main/subghz/subghz_cli.c b/applications/main/subghz/subghz_cli.c index 1d49038eb..d58c66917 100644 --- a/applications/main/subghz/subghz_cli.c +++ b/applications/main/subghz/subghz_cli.c @@ -10,6 +10,10 @@ #include #include #include +#include +#include +#include +#include #include "helpers/subghz_chat.h" @@ -65,7 +69,7 @@ void subghz_cli_command_tx_carrier(Cli* cli, FuriString* args, void* context) { } furi_hal_subghz_reset(); - furi_hal_subghz_load_preset(FuriHalSubGhzPresetOok650Async); + furi_hal_subghz_load_custom_preset(subghz_device_cc1101_preset_ook_650khz_async_regs); frequency = furi_hal_subghz_set_frequency_and_path(frequency); furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); @@ -109,7 +113,7 @@ void subghz_cli_command_rx_carrier(Cli* cli, FuriString* args, void* context) { } furi_hal_subghz_reset(); - furi_hal_subghz_load_preset(FuriHalSubGhzPresetOok650Async); + furi_hal_subghz_load_custom_preset(subghz_device_cc1101_preset_ook_650khz_async_regs); frequency = furi_hal_subghz_set_frequency_and_path(frequency); printf("Receiving at frequency %lu Hz\r\n", frequency); printf("Press CTRL+C to stop\r\n"); @@ -157,21 +161,29 @@ void subghz_cli_command_tx(Cli* cli, FuriString* args, void* context) { uint32_t key = 0x0074BADE; uint32_t repeat = 10; uint32_t te = 403; + uint32_t device_ind = 0; // 0 - CC1101_INT, 1 - CC1101_EXT if(furi_string_size(args)) { - int ret = - sscanf(furi_string_get_cstr(args), "%lx %lu %lu %lu", &key, &frequency, &te, &repeat); - if(ret != 4) { + int ret = sscanf( + furi_string_get_cstr(args), + "%lx %lu %lu %lu %lu", + &key, + &frequency, + &te, + &repeat, + &device_ind); + if(ret != 5) { printf( - "sscanf returned %d, key: %lx, frequency: %lu, te:%lu, repeat: %lu\r\n", + "sscanf returned %d, key: %lx, frequency: %lu, te: %lu, repeat: %lu, device: %lu\r\n ", ret, key, frequency, te, - repeat); + repeat, + device_ind); cli_print_usage( "subghz tx", - "<3 Byte Key: in hex> ", + "<3 Byte Key: in hex> ", furi_string_get_cstr(args)); return; } @@ -186,11 +198,12 @@ void subghz_cli_command_tx(Cli* cli, FuriString* args, void* context) { return; } printf( - "Transmitting at %lu, key %lx, te %lu, repeat %lu. Press CTRL+C to stop\r\n", + "Transmitting at %lu, key %lx, te %lu, repeat %lu device %lu. Press CTRL+C to stop\r\n", frequency, key, te, - repeat); + repeat, + device_ind); FuriString* flipper_format_string = furi_string_alloc_printf( "Protocol: Princeton\n" @@ -214,25 +227,29 @@ void subghz_cli_command_tx(Cli* cli, FuriString* args, void* context) { SubGhzTransmitter* transmitter = subghz_transmitter_alloc_init(environment, "Princeton"); subghz_transmitter_deserialize(transmitter, flipper_format); - furi_hal_subghz_reset(); - furi_hal_subghz_load_preset(FuriHalSubGhzPresetOok650Async); - frequency = furi_hal_subghz_set_frequency_and_path(frequency); + subghz_devices_begin(device); + subghz_devices_reset(device); + subghz_devices_load_preset(device, FuriHalSubGhzPresetOok650Async, NULL); + frequency = subghz_devices_set_frequency(device, frequency); furi_hal_power_suppress_charge_enter(); - - if(furi_hal_subghz_start_async_tx(subghz_transmitter_yield, transmitter)) { - while(!(furi_hal_subghz_is_async_tx_complete() || cli_cmd_interrupt_received(cli))) { + if(subghz_devices_start_async_tx(device, subghz_transmitter_yield, transmitter)) { + while(!(subghz_devices_is_async_complete_tx(device) || cli_cmd_interrupt_received(cli))) { printf("."); fflush(stdout); furi_delay_ms(333); } - furi_hal_subghz_stop_async_tx(); + subghz_devices_stop_async_tx(device); } else { printf("Frequency is outside of default range. Check docs.\r\n"); } - furi_hal_subghz_sleep(); + subghz_devices_sleep(device); + subghz_devices_end(device); + subghz_devices_deinit(); + subghz_cli_radio_device_power_off(); + furi_hal_power_suppress_charge_exit(); flipper_format_free(flipper_format); @@ -276,12 +293,17 @@ static void subghz_cli_command_rx_callback( void subghz_cli_command_rx(Cli* cli, FuriString* args, void* context) { UNUSED(context); uint32_t frequency = 433920000; + uint32_t device_ind = 0; // 0 - CC1101_INT, 1 - CC1101_EXT if(furi_string_size(args)) { - int ret = sscanf(furi_string_get_cstr(args), "%lu", &frequency); - if(ret != 1) { - printf("sscanf returned %d, frequency: %lu\r\n", ret, frequency); - cli_print_usage("subghz rx", "", furi_string_get_cstr(args)); + int ret = sscanf(furi_string_get_cstr(args), "%lu %lu", &frequency, &device_ind); + if(ret != 2) { + printf( + "sscanf returned %d, frequency: %lu device: %lu\r\n", ret, frequency, device_ind); + cli_print_usage( + "subghz rx", + " ", + furi_string_get_cstr(args)); return; } } @@ -302,14 +324,14 @@ void subghz_cli_command_rx(Cli* cli, FuriString* args, void* context) { furi_check(instance->stream); SubGhzEnvironment* environment = subghz_environment_alloc(); - subghz_environment_load_keystore(environment, EXT_PATH("subghz/assets/keeloq_mfcodes")); - subghz_environment_load_keystore(environment, EXT_PATH("subghz/assets/keeloq_mfcodes_user")); + subghz_environment_load_keystore(environment, SUBGHZ_KEYSTORE_DIR_NAME); + subghz_environment_load_keystore(environment, SUBGHZ_KEYSTORE_DIR_USER_NAME); subghz_environment_set_came_atomo_rainbow_table_file_name( - environment, EXT_PATH("subghz/assets/came_atomo")); + environment, SUBGHZ_CAME_ATOMO_DIR_NAME); subghz_environment_set_alutech_at_4n_rainbow_table_file_name( - environment, EXT_PATH("subghz/assets/alutech_at_4n")); + environment, SUBGHZ_ALUTECH_AT_4N_DIR_NAME); subghz_environment_set_nice_flor_s_rainbow_table_file_name( - environment, EXT_PATH("subghz/assets/nice_flor_s")); + environment, SUBGHZ_NICE_FLOR_S_DIR_NAME); subghz_environment_set_protocol_registry(environment, (void*)&subghz_protocol_registry); SubGhzReceiver* receiver = subghz_receiver_alloc_init(environment); @@ -325,10 +347,13 @@ void subghz_cli_command_rx(Cli* cli, FuriString* args, void* context) { furi_hal_power_suppress_charge_enter(); // Prepare and start RX - furi_hal_subghz_start_async_rx(subghz_cli_command_rx_capture_callback, instance); + subghz_devices_start_async_rx(device, subghz_cli_command_rx_capture_callback, instance); // Wait for packets to arrive - printf("Listening at %lu. Press CTRL+C to stop\r\n", frequency); + printf( + "Listening at frequency: %lu device: %lu. Press CTRL+C to stop\r\n", + frequency, + device_ind); LevelDuration level_duration; while(!cli_cmd_interrupt_received(cli)) { int ret = furi_stream_buffer_receive( @@ -346,8 +371,11 @@ void subghz_cli_command_rx(Cli* cli, FuriString* args, void* context) { } // Shutdown radio - furi_hal_subghz_stop_async_rx(); - furi_hal_subghz_sleep(); + subghz_devices_stop_async_rx(device); + subghz_devices_sleep(device); + subghz_devices_end(device); + subghz_devices_deinit(); + subghz_cli_radio_device_power_off(); furi_hal_power_suppress_charge_exit(); @@ -435,6 +463,7 @@ void subghz_cli_command_rx_raw(Cli* cli, FuriString* args, void* context) { furi_stream_buffer_free(instance->stream); free(instance); } + void subghz_cli_command_decode_raw(Cli* cli, FuriString* args, void* context) { UNUSED(context); FuriString* file_name = furi_string_alloc(); @@ -486,25 +515,23 @@ void subghz_cli_command_decode_raw(Cli* cli, FuriString* args, void* context) { SubGhzCliCommandRx* instance = malloc(sizeof(SubGhzCliCommandRx)); SubGhzEnvironment* environment = subghz_environment_alloc(); - if(subghz_environment_load_keystore( - environment, EXT_PATH("subghz/assets/keeloq_mfcodes"))) { + if(subghz_environment_load_keystore(environment, SUBGHZ_KEYSTORE_DIR_NAME)) { printf("SubGhz decode_raw: Load_keystore keeloq_mfcodes \033[0;32mOK\033[0m\r\n"); } else { printf("SubGhz decode_raw: Load_keystore keeloq_mfcodes \033[0;31mERROR\033[0m\r\n"); } - if(subghz_environment_load_keystore( - environment, EXT_PATH("subghz/assets/keeloq_mfcodes_user"))) { + if(subghz_environment_load_keystore(environment, SUBGHZ_KEYSTORE_DIR_USER_NAME)) { printf("SubGhz decode_raw: Load_keystore keeloq_mfcodes_user \033[0;32mOK\033[0m\r\n"); } else { printf( "SubGhz decode_raw: Load_keystore keeloq_mfcodes_user \033[0;31mERROR\033[0m\r\n"); } subghz_environment_set_came_atomo_rainbow_table_file_name( - environment, EXT_PATH("subghz/assets/came_atomo")); + environment, SUBGHZ_CAME_ATOMO_DIR_NAME); subghz_environment_set_alutech_at_4n_rainbow_table_file_name( - environment, EXT_PATH("subghz/assets/alutech_at_4n")); + environment, SUBGHZ_ALUTECH_AT_4N_DIR_NAME); subghz_environment_set_nice_flor_s_rainbow_table_file_name( - environment, EXT_PATH("subghz/assets/nice_flor_s")); + environment, SUBGHZ_NICE_FLOR_S_DIR_NAME); subghz_environment_set_protocol_registry(environment, (void*)&subghz_protocol_registry); SubGhzReceiver* receiver = subghz_receiver_alloc_init(environment); @@ -512,7 +539,8 @@ void subghz_cli_command_decode_raw(Cli* cli, FuriString* args, void* context) { subghz_receiver_set_rx_callback(receiver, subghz_cli_command_rx_callback, instance); SubGhzFileEncoderWorker* file_worker_encoder = subghz_file_encoder_worker_alloc(); - if(subghz_file_encoder_worker_start(file_worker_encoder, furi_string_get_cstr(file_name))) { + if(subghz_file_encoder_worker_start( + file_worker_encoder, furi_string_get_cstr(file_name), NULL)) { //the worker needs a file in order to open and read part of the file furi_delay_ms(100); } @@ -554,10 +582,11 @@ static void subghz_cli_command_print_usage() { printf("subghz \r\n"); printf("Cmd list:\r\n"); - printf("\tchat \t - Chat with other Flippers\r\n"); printf( - "\ttx <3 byte Key: in hex> \t - Transmitting key\r\n"); - printf("\trx \t - Receive\r\n"); + "\tchat \t - Chat with other Flippers\r\n"); + printf( + "\ttx <3 byte Key: in hex> \t - Transmitting key\r\n"); + printf("\trx \t - Receive\r\n"); printf("\trx_raw \t - Receive RAW\r\n"); printf("\tdecode_raw \t - Testing\r\n"); @@ -652,12 +681,17 @@ static void subghz_cli_command_encrypt_raw(Cli* cli, FuriString* args) { static void subghz_cli_command_chat(Cli* cli, FuriString* args, void* context) { UNUSED(context); uint32_t frequency = 433920000; + uint32_t device_ind = 0; // 0 - CC1101_INT, 1 - CC1101_EXT if(furi_string_size(args)) { - int ret = sscanf(furi_string_get_cstr(args), "%lu", &frequency); - if(ret != 1) { - printf("sscanf returned %d, frequency: %lu\r\n", ret, frequency); - cli_print_usage("subghz chat", "", furi_string_get_cstr(args)); + int ret = sscanf(furi_string_get_cstr(args), "%lu %lu", &frequency, &device_ind); + if(ret != 2) { + printf("sscanf returned %d, Frequency: %lu\r\n", ret, frequency); + printf("sscanf returned %d, Device: %lu\r\n", ret, device_ind); + cli_print_usage( + "subghz chat", + " ", + furi_string_get_cstr(args)); return; } } @@ -681,7 +715,8 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args, void* context) { } SubGhzChatWorker* subghz_chat = subghz_chat_worker_alloc(cli); - if(!subghz_chat_worker_start(subghz_chat, frequency)) { + + if(!subghz_chat_worker_start(subghz_chat, device, frequency)) { printf("Startup error SubGhzChatWorker\r\n"); if(subghz_chat_worker_is_running(subghz_chat)) { @@ -823,6 +858,10 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args, void* context) { furi_string_free(name); furi_string_free(output); furi_string_free(sysmsg); + + subghz_devices_deinit(); + subghz_cli_radio_device_power_off(); + furi_hal_power_suppress_charge_exit(); furi_record_close(RECORD_NOTIFICATION); diff --git a/applications/main/subghz/subghz_dangerous_freq.c b/applications/main/subghz/subghz_dangerous_freq.c new file mode 100644 index 000000000..69a54f04b --- /dev/null +++ b/applications/main/subghz/subghz_dangerous_freq.c @@ -0,0 +1,23 @@ +#include +#include + +#include + +#include + +void subghz_dangerous_freq() { + bool is_extended_i = false; + + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* fff_data_file = flipper_format_file_alloc(storage); + + if(flipper_format_file_open_existing(fff_data_file, "/ext/subghz/assets/dangerous_settings")) { + flipper_format_read_bool( + fff_data_file, "yes_i_want_to_destroy_my_flipper", &is_extended_i, 1); + } + + furi_hal_subghz_set_dangerous_frequency(is_extended_i); + + flipper_format_free(fff_data_file); + furi_record_close(RECORD_STORAGE); +} diff --git a/applications/main/subghz/subghz_extended_freq.c b/applications/main/subghz/subghz_extended_freq.c deleted file mode 100644 index bbd44f639..000000000 --- a/applications/main/subghz/subghz_extended_freq.c +++ /dev/null @@ -1,19 +0,0 @@ -#include -#include -#include -#include - -void subghz_extended_freq() { - bool is_extended_i = false; - Storage* storage = furi_record_open(RECORD_STORAGE); - FlipperFormat* file = flipper_format_file_alloc(storage); - - if(flipper_format_file_open_existing(file, "/ext/subghz/assets/extend_range.txt")) { - flipper_format_read_bool(file, "use_ext_range_at_own_risk", &is_extended_i, 1); - } - - furi_hal_subghz_set_extended_frequency(is_extended_i); - - flipper_format_free(file); - furi_record_close(RECORD_STORAGE); -} diff --git a/applications/main/subghz/subghz_i.c b/applications/main/subghz/subghz_i.c index b16900177..f91f128cf 100644 --- a/applications/main/subghz/subghz_i.c +++ b/applications/main/subghz/subghz_i.c @@ -170,7 +170,8 @@ bool subghz_key_load(SubGhz* subghz, const char* file_path, bool show_dialog) { if(!strcmp(furi_string_get_cstr(temp_str), "RAW")) { //if RAW subghz->load_type_file = SubGhzLoadTypeFileRaw; - subghz_protocol_raw_gen_fff_data(fff_data, file_path); + subghz_protocol_raw_gen_fff_data( + fff_data, file_path, subghz_txrx_radio_device_get_name(subghz->txrx)); } else { subghz->load_type_file = SubGhzLoadTypeFileKey; stream_copy_full( diff --git a/applications/main/subghz/views/subghz_read_raw.c b/applications/main/subghz/views/subghz_read_raw.c index 2a10e9d2e..45668a6fc 100644 --- a/applications/main/subghz/views/subghz_read_raw.c +++ b/applications/main/subghz/views/subghz_read_raw.c @@ -59,6 +59,14 @@ void subghz_read_raw_add_data_statusbar( true); } +void subghz_read_raw_set_radio_device_type( + SubGhzReadRAW* instance, + SubGhzRadioDeviceType device_type) { + furi_assert(instance); + with_view_model( + instance->view, SubGhzReadRAWModel * model, { model->device_type = device_type; }, true); +} + void subghz_read_raw_add_data_rssi(SubGhzReadRAW* instance, float rssi, bool trace) { furi_assert(instance); uint8_t u_rssi = 0; diff --git a/applications/main/subghz/views/subghz_read_raw.h b/applications/main/subghz/views/subghz_read_raw.h index f9a56ca04..c7d87f2d5 100644 --- a/applications/main/subghz/views/subghz_read_raw.h +++ b/applications/main/subghz/views/subghz_read_raw.h @@ -1,17 +1,14 @@ #pragma once #include +#include "../helpers/subghz_types.h" #include "../helpers/subghz_custom_event.h" #define SUBGHZ_RAW_THRESHOLD_MIN -90.0f -typedef void (*SubGhzReadRAWCallback)(SubGhzCustomEvent event, void* context); +typedef struct SubGhzReadRAW SubGhzReadRAW; -typedef struct { - View* view; - SubGhzReadRAWCallback callback; - void* context; -} SubGhzReadRAW; +typedef void (*SubGhzReadRAWCallback)(SubGhzCustomEvent event, void* context); typedef enum { SubGhzReadRAWStatusStart, @@ -26,22 +23,6 @@ typedef enum { SubGhzReadRAWStatusSaveKey, } SubGhzReadRAWStatus; -typedef struct { - FuriString* frequency_str; - FuriString* preset_str; - FuriString* sample_write; - FuriString* file_name; - uint8_t* rssi_history; - uint8_t rssi_current; - bool rssi_history_end; - uint8_t ind_write; - uint8_t ind_sin; - SubGhzReadRAWStatus status; - bool raw_send_only; - float raw_threshold_rssi; - bool not_showing_samples; -} SubGhzReadRAWModel; - void subghz_read_raw_set_callback( SubGhzReadRAW* subghz_read_raw, SubGhzReadRAWCallback callback, @@ -56,6 +37,10 @@ void subghz_read_raw_add_data_statusbar( const char* frequency_str, const char* preset_str); +void subghz_read_raw_set_radio_device_type( + SubGhzReadRAW* instance, + SubGhzRadioDeviceType device_type); + void subghz_read_raw_update_sample_write(SubGhzReadRAW* instance, size_t sample); void subghz_read_raw_stop_send(SubGhzReadRAW* instance); diff --git a/applications/main/subghz/views/transmitter.c b/applications/main/subghz/views/transmitter.c index d555b2244..16a2ea110 100644 --- a/applications/main/subghz/views/transmitter.c +++ b/applications/main/subghz/views/transmitter.c @@ -51,6 +51,17 @@ void subghz_view_transmitter_add_data_to_show( true); } +void subghz_view_transmitter_set_radio_device_type( + SubGhzViewTransmitter* subghz_transmitter, + SubGhzRadioDeviceType device_type) { + furi_assert(subghz_transmitter); + with_view_model( + subghz_transmitter->view, + SubGhzViewTransmitterModel * model, + { model->device_type = device_type; }, + true); +} + static void subghz_view_transmitter_button_right(Canvas* canvas, const char* str) { const uint8_t button_height = 12; const uint8_t vertical_offset = 3; diff --git a/applications/main/subghz/views/transmitter.h b/applications/main/subghz/views/transmitter.h index e121c8746..19da3145c 100644 --- a/applications/main/subghz/views/transmitter.h +++ b/applications/main/subghz/views/transmitter.h @@ -1,30 +1,22 @@ #pragma once #include +#include "../helpers/subghz_types.h" #include "../helpers/subghz_custom_event.h" -typedef struct { - FuriString* frequency_str; - FuriString* preset_str; - FuriString* key_str; - bool show_button; - FuriString* temp_button_id; - bool draw_temp_button; -} SubGhzViewTransmitterModel; +typedef struct SubGhzViewTransmitter SubGhzViewTransmitter; typedef void (*SubGhzViewTransmitterCallback)(SubGhzCustomEvent event, void* context); -typedef struct { - View* view; - SubGhzViewTransmitterCallback callback; - void* context; -} SubGhzViewTransmitter; - void subghz_view_transmitter_set_callback( SubGhzViewTransmitter* subghz_transmitter, SubGhzViewTransmitterCallback callback, void* context); +void subghz_view_transmitter_set_radio_device_type( + SubGhzViewTransmitter* subghz_transmitter, + SubGhzRadioDeviceType device_type); + SubGhzViewTransmitter* subghz_view_transmitter_alloc(); void subghz_view_transmitter_free(SubGhzViewTransmitter* subghz_transmitter); diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 2e372506f..535afe8c7 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,7 +1,6 @@ entry,status,name,type,params Version,+,34.1,, Header,+,applications/main/archive/helpers/favorite_timeout.h,, -Header,+,applications/main/subghz/helpers/subghz_txrx.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -2983,42 +2982,6 @@ Function,+,subghz_tx_rx_worker_set_callback_have_read,void,"SubGhzTxRxWorker*, S Function,+,subghz_tx_rx_worker_start,_Bool,"SubGhzTxRxWorker*, uint32_t" Function,+,subghz_tx_rx_worker_stop,void,SubGhzTxRxWorker* Function,+,subghz_tx_rx_worker_write,_Bool,"SubGhzTxRxWorker*, uint8_t*, size_t" -Function,+,subghz_txrx_alloc,SubGhzTxRx*, -Function,+,subghz_txrx_free,void,SubGhzTxRx* -Function,-,subghz_txrx_get_debug_pin_state,_Bool,SubGhzTxRx* -Function,+,subghz_txrx_get_decoder,SubGhzProtocolDecoderBase*,SubGhzTxRx* -Function,-,subghz_txrx_get_fff_data,FlipperFormat*,SubGhzTxRx* -Function,-,subghz_txrx_get_frequency_and_modulation,void,"SubGhzTxRx*, FuriString*, FuriString*, _Bool" -Function,-,subghz_txrx_get_preset,SubGhzRadioPreset,SubGhzTxRx* -Function,+,subghz_txrx_get_preset_name,const char*,"SubGhzTxRx*, const char*" -Function,-,subghz_txrx_get_receiver,SubGhzReceiver*,SubGhzTxRx* -Function,+,subghz_txrx_get_setting,SubGhzSetting*,SubGhzTxRx* -Function,-,subghz_txrx_hopper_get_state,SubGhzHopperState,SubGhzTxRx* -Function,-,subghz_txrx_hopper_pause,void,SubGhzTxRx* -Function,-,subghz_txrx_hopper_set_state,void,"SubGhzTxRx*, SubGhzHopperState" -Function,-,subghz_txrx_hopper_unpause,void,SubGhzTxRx* -Function,-,subghz_txrx_hopper_update,void,SubGhzTxRx* -Function,-,subghz_txrx_is_database_loaded,_Bool,SubGhzTxRx* -Function,+,subghz_txrx_load_decoder_by_name_protocol,_Bool,"SubGhzTxRx*, const char*" -Function,-,subghz_txrx_protocol_is_serializable,_Bool,SubGhzTxRx* -Function,-,subghz_txrx_protocol_is_transmittable,_Bool,"SubGhzTxRx*, _Bool" -Function,-,subghz_txrx_receiver_set_filter,void,"SubGhzTxRx*, SubGhzProtocolFlag" -Function,+,subghz_txrx_reset_dynamic_and_custom_btns,void,SubGhzTxRx* -Function,-,subghz_txrx_rx_start,void,SubGhzTxRx* -Function,-,subghz_txrx_set_debug_pin_state,void,"SubGhzTxRx*, _Bool" -Function,+,subghz_txrx_set_need_save_callback,void,"SubGhzTxRx*, SubGhzTxRxNeedSaveCallback, void*" -Function,+,subghz_txrx_set_preset,void,"SubGhzTxRx*, const char*, uint32_t, uint8_t*, size_t" -Function,+,subghz_txrx_set_raw_file_encoder_worker_callback_end,void,"SubGhzTxRx*, SubGhzProtocolEncoderRAWCallbackEnd, void*" -Function,-,subghz_txrx_set_rx_calback,void,"SubGhzTxRx*, SubGhzReceiverCallback, void*" -Function,-,subghz_txrx_sleep,void,SubGhzTxRx* -Function,-,subghz_txrx_speaker_get_state,SubGhzSpeakerState,SubGhzTxRx* -Function,-,subghz_txrx_speaker_mute,void,SubGhzTxRx* -Function,-,subghz_txrx_speaker_off,void,SubGhzTxRx* -Function,-,subghz_txrx_speaker_on,void,SubGhzTxRx* -Function,-,subghz_txrx_speaker_set_state,void,"SubGhzTxRx*, SubGhzSpeakerState" -Function,-,subghz_txrx_speaker_unmute,void,SubGhzTxRx* -Function,+,subghz_txrx_stop,void,SubGhzTxRx* -Function,+,subghz_txrx_tx_start,SubGhzTxRxStartTxState,"SubGhzTxRx*, FlipperFormat*" Function,+,subghz_worker_alloc,SubGhzWorker*, Function,+,subghz_worker_free,void,SubGhzWorker* Function,+,subghz_worker_is_running,_Bool,SubGhzWorker* diff --git a/lib/subghz/SConscript b/lib/subghz/SConscript index ae0816582..575d27b2a 100644 --- a/lib/subghz/SConscript +++ b/lib/subghz/SConscript @@ -14,7 +14,6 @@ env.Append( File("transmitter.h"), File("protocols/raw.h"), File("blocks/const.h"), - File("blocks/custom_btn.h"), File("blocks/decoder.h"), File("blocks/encoder.h"), File("blocks/generic.h"), @@ -22,6 +21,7 @@ env.Append( File("blocks/custom_btn.h"), File("subghz_setting.h"), File("subghz_protocol_registry.h"), + File("devices/cc1101_configs.h"), ], ) diff --git a/lib/subghz/devices/cc1101_int/cc1101_int_interconnect.c b/lib/subghz/devices/cc1101_int/cc1101_int_interconnect.c new file mode 100644 index 000000000..41a0609df --- /dev/null +++ b/lib/subghz/devices/cc1101_int/cc1101_int_interconnect.c @@ -0,0 +1,96 @@ +#include "cc1101_int_interconnect.h" +#include +#include "../cc1101_configs.h" + +#define TAG "SubGhzDeviceCC1101Int" + +static bool subghz_device_cc1101_int_interconnect_is_frequency_valid(uint32_t frequency) { + bool ret = furi_hal_subghz_is_frequency_valid(frequency); + if(!ret) { + furi_crash("SubGhz: Incorrect frequency."); + } + return ret; +} + +static uint32_t subghz_device_cc1101_int_interconnect_set_frequency(uint32_t frequency) { + subghz_device_cc1101_int_interconnect_is_frequency_valid(frequency); + return furi_hal_subghz_set_frequency_and_path(frequency); +} + +static bool subghz_device_cc1101_int_interconnect_start_async_tx(void* callback, void* context) { + return furi_hal_subghz_start_async_tx((FuriHalSubGhzAsyncTxCallback)callback, context); +} + +static void subghz_device_cc1101_int_interconnect_start_async_rx(void* callback, void* context) { + furi_hal_subghz_start_async_rx((FuriHalSubGhzCaptureCallback)callback, context); +} + +static void subghz_device_cc1101_int_interconnect_load_preset( + FuriHalSubGhzPreset preset, + uint8_t* preset_data) { + switch(preset) { + case FuriHalSubGhzPresetOok650Async: + furi_hal_subghz_load_custom_preset(subghz_device_cc1101_preset_ook_650khz_async_regs); + break; + case FuriHalSubGhzPresetOok270Async: + furi_hal_subghz_load_custom_preset(subghz_device_cc1101_preset_ook_270khz_async_regs); + break; + case FuriHalSubGhzPreset2FSKDev238Async: + furi_hal_subghz_load_custom_preset(subghz_device_cc1101_preset_2fsk_dev2_38khz_async_regs); + break; + case FuriHalSubGhzPreset2FSKDev476Async: + furi_hal_subghz_load_custom_preset(subghz_device_cc1101_preset_2fsk_dev47_6khz_async_regs); + break; + case FuriHalSubGhzPresetMSK99_97KbAsync: + furi_hal_subghz_load_custom_preset(subghz_device_cc1101_preset_msk_99_97kb_async_regs); + break; + case FuriHalSubGhzPresetGFSK9_99KbAsync: + furi_hal_subghz_load_custom_preset(subghz_device_cc1101_preset_gfsk_9_99kb_async_regs); + break; + + default: + furi_hal_subghz_load_custom_preset(preset_data); + } +} + +static bool subghz_device_cc1101_int_interconnect_is_connect(void) { + return true; +} + +const SubGhzDeviceInterconnect subghz_device_cc1101_int_interconnect = { + .begin = NULL, + .end = furi_hal_subghz_shutdown, + .is_connect = subghz_device_cc1101_int_interconnect_is_connect, + .reset = furi_hal_subghz_reset, + .sleep = furi_hal_subghz_sleep, + .idle = furi_hal_subghz_idle, + .load_preset = subghz_device_cc1101_int_interconnect_load_preset, + .set_frequency = subghz_device_cc1101_int_interconnect_set_frequency, + .is_frequency_valid = furi_hal_subghz_is_frequency_valid, + .set_async_mirror_pin = furi_hal_subghz_set_async_mirror_pin, + .get_data_gpio = furi_hal_subghz_get_data_gpio, + + .set_tx = furi_hal_subghz_tx, + .flush_tx = furi_hal_subghz_flush_tx, + .start_async_tx = subghz_device_cc1101_int_interconnect_start_async_tx, + .is_async_complete_tx = furi_hal_subghz_is_async_tx_complete, + .stop_async_tx = furi_hal_subghz_stop_async_tx, + + .set_rx = furi_hal_subghz_rx, + .flush_rx = furi_hal_subghz_flush_rx, + .start_async_rx = subghz_device_cc1101_int_interconnect_start_async_rx, + .stop_async_rx = furi_hal_subghz_stop_async_rx, + + .get_rssi = furi_hal_subghz_get_rssi, + .get_lqi = furi_hal_subghz_get_lqi, + + .rx_pipe_not_empty = furi_hal_subghz_rx_pipe_not_empty, + .is_rx_data_crc_valid = furi_hal_subghz_is_rx_data_crc_valid, + .read_packet = furi_hal_subghz_read_packet, + .write_packet = furi_hal_subghz_write_packet, +}; + +const SubGhzDevice subghz_device_cc1101_int = { + .name = SUBGHZ_DEVICE_CC1101_INT_NAME, + .interconnect = &subghz_device_cc1101_int_interconnect, +}; diff --git a/lib/subghz/devices/cc1101_int/cc1101_int_interconnect.h b/lib/subghz/devices/cc1101_int/cc1101_int_interconnect.h new file mode 100644 index 000000000..629d1264c --- /dev/null +++ b/lib/subghz/devices/cc1101_int/cc1101_int_interconnect.h @@ -0,0 +1,8 @@ +#pragma once +#include "../types.h" + +#define SUBGHZ_DEVICE_CC1101_INT_NAME "cc1101_int" + +typedef struct SubGhzDeviceCC1101Int SubGhzDeviceCC1101Int; + +extern const SubGhzDevice subghz_device_cc1101_int; diff --git a/lib/subghz/devices/device_registry.h b/lib/subghz/devices/device_registry.h new file mode 100644 index 000000000..70a0db4b2 --- /dev/null +++ b/lib/subghz/devices/device_registry.h @@ -0,0 +1,13 @@ +#pragma once + +#include "registry.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern const SubGhzDeviceRegistry subghz_device_registry; + +#ifdef __cplusplus +} +#endif diff --git a/lib/subghz/devices/devices.h b/lib/subghz/devices/devices.h new file mode 100644 index 000000000..dad3c9aeb --- /dev/null +++ b/lib/subghz/devices/devices.h @@ -0,0 +1,52 @@ +#pragma once + +#include "types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct SubGhzDevice SubGhzDevice; + +void subghz_devices_init(); +void subghz_devices_deinit(void); + +const SubGhzDevice* subghz_devices_get_by_name(const char* device_name); +const char* subghz_devices_get_name(const SubGhzDevice* device); +bool subghz_devices_begin(const SubGhzDevice* device); +void subghz_devices_end(const SubGhzDevice* device); +bool subghz_devices_is_connect(const SubGhzDevice* device); +void subghz_devices_reset(const SubGhzDevice* device); +void subghz_devices_sleep(const SubGhzDevice* device); +void subghz_devices_idle(const SubGhzDevice* device); +void subghz_devices_load_preset( + const SubGhzDevice* device, + FuriHalSubGhzPreset preset, + uint8_t* preset_data); +uint32_t subghz_devices_set_frequency(const SubGhzDevice* device, uint32_t frequency); +bool subghz_devices_is_frequency_valid(const SubGhzDevice* device, uint32_t frequency); +void subghz_devices_set_async_mirror_pin(const SubGhzDevice* device, const GpioPin* gpio); +const GpioPin* subghz_devices_get_data_gpio(const SubGhzDevice* device); + +bool subghz_devices_set_tx(const SubGhzDevice* device); +void subghz_devices_flush_tx(const SubGhzDevice* device); +bool subghz_devices_start_async_tx(const SubGhzDevice* device, void* callback, void* context); +bool subghz_devices_is_async_complete_tx(const SubGhzDevice* device); +void subghz_devices_stop_async_tx(const SubGhzDevice* device); + +void subghz_devices_set_rx(const SubGhzDevice* device); +void subghz_devices_flush_rx(const SubGhzDevice* device); +void subghz_devices_start_async_rx(const SubGhzDevice* device, void* callback, void* context); +void subghz_devices_stop_async_rx(const SubGhzDevice* device); + +float subghz_devices_get_rssi(const SubGhzDevice* device); +uint8_t subghz_devices_get_lqi(const SubGhzDevice* device); + +bool subghz_devices_rx_pipe_not_empty(const SubGhzDevice* device); +bool subghz_devices_is_rx_data_crc_valid(const SubGhzDevice* device); +void subghz_devices_read_packet(const SubGhzDevice* device, uint8_t* data, uint8_t* size); +void subghz_devices_write_packet(const SubGhzDevice* device, const uint8_t* data, uint8_t size); + +#ifdef __cplusplus +} +#endif diff --git a/lib/subghz/devices/preset.h b/lib/subghz/devices/preset.h new file mode 100644 index 000000000..8716f2e23 --- /dev/null +++ b/lib/subghz/devices/preset.h @@ -0,0 +1,13 @@ +#pragma once + +/** Radio Presets */ +typedef enum { + FuriHalSubGhzPresetIDLE, /**< default configuration */ + FuriHalSubGhzPresetOok270Async, /**< OOK, bandwidth 270kHz, asynchronous */ + FuriHalSubGhzPresetOok650Async, /**< OOK, bandwidth 650kHz, asynchronous */ + FuriHalSubGhzPreset2FSKDev238Async, /**< FM, deviation 2.380371 kHz, asynchronous */ + FuriHalSubGhzPreset2FSKDev476Async, /**< FM, deviation 47.60742 kHz, asynchronous */ + FuriHalSubGhzPresetMSK99_97KbAsync, /**< MSK, deviation 47.60742 kHz, 99.97Kb/s, asynchronous */ + FuriHalSubGhzPresetGFSK9_99KbAsync, /**< GFSK, deviation 19.042969 kHz, 9.996Kb/s, asynchronous */ + FuriHalSubGhzPresetCustom, /**Custom Preset*/ +} FuriHalSubGhzPreset; diff --git a/lib/subghz/devices/registry.c b/lib/subghz/devices/registry.c new file mode 100644 index 000000000..c0d5bb292 --- /dev/null +++ b/lib/subghz/devices/registry.c @@ -0,0 +1,76 @@ +#include "registry.h" + +#include "cc1101_int/cc1101_int_interconnect.h" +#include +#include + +#define TAG "SubGhzDeviceRegistry" + +struct SubGhzDeviceRegistry { + const SubGhzDevice** items; + size_t size; + PluginManager* manager; +}; + +static SubGhzDeviceRegistry* subghz_device_registry = NULL; + +void subghz_device_registry_init(void) { + SubGhzDeviceRegistry* subghz_device = + (SubGhzDeviceRegistry*)malloc(sizeof(SubGhzDeviceRegistry)); + subghz_device->manager = plugin_manager_alloc( + SUBGHZ_RADIO_DEVICE_PLUGIN_APP_ID, + SUBGHZ_RADIO_DEVICE_PLUGIN_API_VERSION, + firmware_api_interface); + + //ToDo: fix path to plugins + if(plugin_manager_load_all(subghz_device->manager, "/any/apps_data/subghz/plugins") != + //if(plugin_manager_load_all(subghz_device->manager, APP_DATA_PATH("plugins")) != + PluginManagerErrorNone) { + FURI_LOG_E(TAG, "Failed to load all libs"); + } + + subghz_device->size = plugin_manager_get_count(subghz_device->manager) + 1; + subghz_device->items = + (const SubGhzDevice**)malloc(sizeof(SubGhzDevice*) * subghz_device->size); + subghz_device->items[0] = &subghz_device_cc1101_int; + for(uint32_t i = 1; i < subghz_device->size; i++) { + const SubGhzDevice* plugin = plugin_manager_get_ep(subghz_device->manager, i - 1); + subghz_device->items[i] = plugin; + } + + FURI_LOG_I(TAG, "Loaded %zu radio device", subghz_device->size); + subghz_device_registry = subghz_device; +} + +void subghz_device_registry_deinit(void) { + plugin_manager_free(subghz_device_registry->manager); + free(subghz_device_registry->items); + free(subghz_device_registry); + subghz_device_registry = NULL; +} + +bool subghz_device_registry_is_valid(void) { + return subghz_device_registry != NULL; +} + +const SubGhzDevice* subghz_device_registry_get_by_name(const char* name) { + furi_assert(subghz_device_registry); + + if(name != NULL) { + for(size_t i = 0; i < subghz_device_registry->size; i++) { + if(strcmp(name, subghz_device_registry->items[i]->name) == 0) { + return subghz_device_registry->items[i]; + } + } + } + return NULL; +} + +const SubGhzDevice* subghz_device_registry_get_by_index(size_t index) { + furi_assert(subghz_device_registry); + if(index < subghz_device_registry->size) { + return subghz_device_registry->items[index]; + } else { + return NULL; + } +} diff --git a/lib/subghz/devices/registry.h b/lib/subghz/devices/registry.h new file mode 100644 index 000000000..520058920 --- /dev/null +++ b/lib/subghz/devices/registry.h @@ -0,0 +1,40 @@ +#pragma once + +#include "types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct SubGhzDevice SubGhzDevice; + +void subghz_device_registry_init(void); + +void subghz_device_registry_deinit(void); + +bool subghz_device_registry_is_valid(void); + +/** + * Registration by name SubGhzDevice. + * @param name SubGhzDevice name + * @return SubGhzDevice* pointer to a SubGhzDevice instance + */ +const SubGhzDevice* subghz_device_registry_get_by_name(const char* name); + +/** + * Registration subghzdevice by index in array SubGhzDevice. + * @param index SubGhzDevice by index in array + * @return SubGhzDevice* pointer to a SubGhzDevice instance + */ +const SubGhzDevice* subghz_device_registry_get_by_index(size_t index); + +/** + * Getting the number of registered subghzdevices. + * @param subghz_device SubGhzDeviceRegistry + * @return Number of subghzdevices + */ +size_t subghz_device_registry_count(void); + +#ifdef __cplusplus +} +#endif diff --git a/lib/subghz/devices/types.h b/lib/subghz/devices/types.h new file mode 100644 index 000000000..8a4198426 --- /dev/null +++ b/lib/subghz/devices/types.h @@ -0,0 +1,91 @@ +#pragma once + +#include +#include +#include + +#include +#include + +#include "preset.h" + +#include + +#define SUBGHZ_RADIO_DEVICE_PLUGIN_APP_ID "subghz_radio_device" +#define SUBGHZ_RADIO_DEVICE_PLUGIN_API_VERSION 1 + +typedef struct SubGhzDeviceRegistry SubGhzDeviceRegistry; +typedef struct SubGhzDevice SubGhzDevice; + +typedef bool (*SubGhzBegin)(void); +typedef void (*SubGhzEnd)(void); +typedef bool (*SubGhzIsConnect)(void); +typedef void (*SubGhzReset)(void); +typedef void (*SubGhzSleep)(void); +typedef void (*SubGhzIdle)(void); +typedef void (*SubGhzLoadPreset)(FuriHalSubGhzPreset preset, uint8_t* preset_data); +typedef uint32_t (*SubGhzSetFrequency)(uint32_t frequency); +typedef bool (*SubGhzIsFrequencyValid)(uint32_t frequency); + +typedef void (*SubGhzSetAsyncMirrorPin)(const GpioPin* gpio); +typedef const GpioPin* (*SubGhzGetDataGpio)(void); + +typedef bool (*SubGhzSetTx)(void); +typedef void (*SubGhzFlushTx)(void); +typedef bool (*SubGhzStartAsyncTx)(void* callback, void* context); +typedef bool (*SubGhzIsAsyncCompleteTx)(void); +typedef void (*SubGhzStopAsyncTx)(void); + +typedef void (*SubGhzSetRx)(void); +typedef void (*SubGhzFlushRx)(void); +typedef void (*SubGhzStartAsyncRx)(void* callback, void* context); +typedef void (*SubGhzStopAsyncRx)(void); + +typedef float (*SubGhzGetRSSI)(void); +typedef uint8_t (*SubGhzGetLQI)(void); + +typedef bool (*SubGhzRxPipeNotEmpty)(void); +typedef bool (*SubGhzRxIsDataCrcValid)(void); +typedef void (*SubGhzReadPacket)(uint8_t* data, uint8_t* size); +typedef void (*SubGhzWritePacket)(const uint8_t* data, uint8_t size); + +typedef struct { + SubGhzBegin begin; + SubGhzEnd end; + + SubGhzIsConnect is_connect; + SubGhzReset reset; + SubGhzSleep sleep; + SubGhzIdle idle; + + SubGhzLoadPreset load_preset; + SubGhzSetFrequency set_frequency; + SubGhzIsFrequencyValid is_frequency_valid; + SubGhzSetAsyncMirrorPin set_async_mirror_pin; + SubGhzGetDataGpio get_data_gpio; + + SubGhzSetTx set_tx; + SubGhzFlushTx flush_tx; + SubGhzStartAsyncTx start_async_tx; + SubGhzIsAsyncCompleteTx is_async_complete_tx; + SubGhzStopAsyncTx stop_async_tx; + + SubGhzSetRx set_rx; + SubGhzFlushRx flush_rx; + SubGhzStartAsyncRx start_async_rx; + SubGhzStopAsyncRx stop_async_rx; + + SubGhzGetRSSI get_rssi; + SubGhzGetLQI get_lqi; + + SubGhzRxPipeNotEmpty rx_pipe_not_empty; + SubGhzRxIsDataCrcValid is_rx_data_crc_valid; + SubGhzReadPacket read_packet; + SubGhzWritePacket write_packet; + +} SubGhzDeviceInterconnect; + +struct SubGhzDevice { + const char* name; + const SubGhzDeviceInterconnect* interconnect; +}; diff --git a/lib/subghz/protocols/alutech_at_4n.h b/lib/subghz/protocols/alutech_at_4n.h index 68d6580ea..b7b5b9ede 100644 --- a/lib/subghz/protocols/alutech_at_4n.h +++ b/lib/subghz/protocols/alutech_at_4n.h @@ -1,10 +1,6 @@ #pragma once #include "base.h" -#ifdef __cplusplus -extern "C" { -#endif - #define SUBGHZ_PROTOCOL_ALUTECH_AT_4N_NAME "Alutech AT-4N" typedef struct SubGhzProtocolDecoderAlutech_at_4n SubGhzProtocolDecoderAlutech_at_4n; @@ -128,7 +124,3 @@ SubGhzProtocolStatus * @param output Resulting text */ void subghz_protocol_decoder_alutech_at_4n_get_string(void* context, FuriString* output); - -#ifdef __cplusplus -} -#endif diff --git a/lib/subghz/protocols/keeloq.h b/lib/subghz/protocols/keeloq.h index 988b37c24..a757e82d5 100644 --- a/lib/subghz/protocols/keeloq.h +++ b/lib/subghz/protocols/keeloq.h @@ -2,10 +2,6 @@ #include "base.h" -#ifdef __cplusplus -extern "C" { -#endif - #define SUBGHZ_PROTOCOL_KEELOQ_NAME "KeeLoq" typedef struct SubGhzProtocolDecoderKeeloq SubGhzProtocolDecoderKeeloq; @@ -153,7 +149,3 @@ SubGhzProtocolStatus * @param output Resulting text */ void subghz_protocol_decoder_keeloq_get_string(void* context, FuriString* output); - -#ifdef __cplusplus -} -#endif diff --git a/lib/subghz/protocols/nice_flor_s.h b/lib/subghz/protocols/nice_flor_s.h index 56ef15aa3..5f8b30644 100644 --- a/lib/subghz/protocols/nice_flor_s.h +++ b/lib/subghz/protocols/nice_flor_s.h @@ -2,10 +2,6 @@ #include "base.h" -#ifdef __cplusplus -extern "C" { -#endif - #define SUBGHZ_PROTOCOL_NICE_FLOR_S_NAME "Nice FloR-S" typedef struct SubGhzProtocolDecoderNiceFlorS SubGhzProtocolDecoderNiceFlorS; @@ -133,7 +129,3 @@ SubGhzProtocolStatus * @param output Resulting text */ void subghz_protocol_decoder_nice_flor_s_get_string(void* context, FuriString* output); - -#ifdef __cplusplus -} -#endif diff --git a/lib/subghz/protocols/raw.c b/lib/subghz/protocols/raw.c index a82c9cf83..d945d19a1 100644 --- a/lib/subghz/protocols/raw.c +++ b/lib/subghz/protocols/raw.c @@ -40,6 +40,7 @@ struct SubGhzProtocolEncoderRAW { bool is_running; FuriString* file_name; + FuriString* radio_device_name; SubGhzFileEncoderWorker* file_worker_encoder; }; @@ -282,6 +283,7 @@ void* subghz_protocol_encoder_raw_alloc(SubGhzEnvironment* environment) { instance->base.protocol = &subghz_protocol_raw; instance->file_name = furi_string_alloc(); + instance->radio_device_name = furi_string_alloc(); instance->is_running = false; return instance; } @@ -300,6 +302,7 @@ void subghz_protocol_encoder_raw_free(void* context) { SubGhzProtocolEncoderRAW* instance = context; subghz_protocol_encoder_raw_stop(instance); furi_string_free(instance->file_name); + furi_string_free(instance->radio_device_name); free(instance); } @@ -318,7 +321,9 @@ static bool subghz_protocol_encoder_raw_worker_init(SubGhzProtocolEncoderRAW* in instance->file_worker_encoder = subghz_file_encoder_worker_alloc(); if(subghz_file_encoder_worker_start( - instance->file_worker_encoder, furi_string_get_cstr(instance->file_name))) { + instance->file_worker_encoder, + furi_string_get_cstr(instance->file_name), + furi_string_get_cstr(instance->radio_device_name))) { //the worker needs a file in order to open and read part of the file furi_delay_ms(100); instance->is_running = true; @@ -328,7 +333,10 @@ static bool subghz_protocol_encoder_raw_worker_init(SubGhzProtocolEncoderRAW* in return instance->is_running; } -void subghz_protocol_raw_gen_fff_data(FlipperFormat* flipper_format, const char* file_path) { +void subghz_protocol_raw_gen_fff_data( + FlipperFormat* flipper_format, + const char* file_path, + const char* radio_device_name) { do { stream_clean(flipper_format_get_raw_stream(flipper_format)); if(!flipper_format_write_string_cstr(flipper_format, "Protocol", "RAW")) { @@ -340,6 +348,12 @@ void subghz_protocol_raw_gen_fff_data(FlipperFormat* flipper_format, const char* FURI_LOG_E(TAG, "Unable to add File_name"); break; } + + if(!flipper_format_write_string_cstr( + flipper_format, "Radio_device_name", radio_device_name)) { + FURI_LOG_E(TAG, "Unable to add Radio_device_name"); + break; + } } while(false); } @@ -364,6 +378,13 @@ SubGhzProtocolStatus } furi_string_set(instance->file_name, temp_str); + if(!flipper_format_read_string(flipper_format, "Radio_device_name", temp_str)) { + FURI_LOG_E(TAG, "Missing Radio_device_name"); + res = SubGhzProtocolStatusErrorParserOthers; + break; + } + furi_string_set(instance->radio_device_name, temp_str); + if(!subghz_protocol_encoder_raw_worker_init(instance)) { res = SubGhzProtocolStatusErrorEncoderGetUpload; break; diff --git a/lib/subghz/protocols/raw.h b/lib/subghz/protocols/raw.h index 4f67a4e2f..6d791bb36 100644 --- a/lib/subghz/protocols/raw.h +++ b/lib/subghz/protocols/raw.h @@ -126,8 +126,12 @@ void subghz_protocol_raw_file_encoder_worker_set_callback_end( * File generation for RAW work. * @param flipper_format Pointer to a FlipperFormat instance * @param file_path File path + * @param radio_dev_name Radio device name */ -void subghz_protocol_raw_gen_fff_data(FlipperFormat* flipper_format, const char* file_path); +void subghz_protocol_raw_gen_fff_data( + FlipperFormat* flipper_format, + const char* file_path, + const char* radio_dev_name); /** * Deserialize and generating an upload to send. diff --git a/lib/subghz/protocols/secplus_v2.h b/lib/subghz/protocols/secplus_v2.h index c71038bcb..0eea732af 100644 --- a/lib/subghz/protocols/secplus_v2.h +++ b/lib/subghz/protocols/secplus_v2.h @@ -1,10 +1,6 @@ #pragma once #include "base.h" -#ifdef __cplusplus -extern "C" { -#endif - #define SUBGHZ_PROTOCOL_SECPLUS_V2_NAME "Security+ 2.0" typedef struct SubGhzProtocolDecoderSecPlus_v2 SubGhzProtocolDecoderSecPlus_v2; @@ -129,7 +125,3 @@ SubGhzProtocolStatus * @param output Resulting text */ void subghz_protocol_decoder_secplus_v2_get_string(void* context, FuriString* output); - -#ifdef __cplusplus -} -#endif diff --git a/lib/subghz/protocols/somfy_telis.h b/lib/subghz/protocols/somfy_telis.h index 053940304..cb5cf8e96 100644 --- a/lib/subghz/protocols/somfy_telis.h +++ b/lib/subghz/protocols/somfy_telis.h @@ -2,10 +2,6 @@ #include "base.h" -#ifdef __cplusplus -extern "C" { -#endif - #define SUBGHZ_PROTOCOL_SOMFY_TELIS_NAME "Somfy Telis" typedef struct SubGhzProtocolDecoderSomfyTelis SubGhzProtocolDecoderSomfyTelis; @@ -129,7 +125,3 @@ SubGhzProtocolStatus * @param output Resulting text */ void subghz_protocol_decoder_somfy_telis_get_string(void* context, FuriString* output); - -#ifdef __cplusplus -} -#endif diff --git a/lib/subghz/protocols/star_line.h b/lib/subghz/protocols/star_line.h index 7400e1e01..851819ec7 100644 --- a/lib/subghz/protocols/star_line.h +++ b/lib/subghz/protocols/star_line.h @@ -2,10 +2,6 @@ #include "base.h" -#ifdef __cplusplus -extern "C" { -#endif - #define SUBGHZ_PROTOCOL_STAR_LINE_NAME "Star Line" typedef struct SubGhzProtocolDecoderStarLine SubGhzProtocolDecoderStarLine; @@ -131,7 +127,3 @@ SubGhzProtocolStatus * @param output Resulting text */ void subghz_protocol_decoder_star_line_get_string(void* context, FuriString* output); - -#ifdef __cplusplus -} -#endif diff --git a/lib/subghz/subghz_file_encoder_worker.c b/lib/subghz/subghz_file_encoder_worker.c index fce4e8592..3534745b7 100644 --- a/lib/subghz/subghz_file_encoder_worker.c +++ b/lib/subghz/subghz_file_encoder_worker.c @@ -3,6 +3,7 @@ #include #include #include +#include #define TAG "SubGhzFileEncoderWorker" @@ -21,6 +22,7 @@ struct SubGhzFileEncoderWorker { bool is_storage_slow; FuriString* str_data; FuriString* file_path; + const SubGhzDevice* device; SubGhzFileEncoderWorkerCallbackEnd callback_end; void* context_end; @@ -180,10 +182,13 @@ static int32_t subghz_file_encoder_worker_thread(void* context) { if(instance->is_storage_slow) { FURI_LOG_E(TAG, "Storage is slow"); } + FURI_LOG_I(TAG, "End read file"); - while(!furi_hal_subghz_is_async_tx_complete() && instance->worker_running) { + while(instance->device && !subghz_devices_is_async_complete_tx(instance->device) && + instance->worker_running) { furi_delay_ms(5); } + FURI_LOG_I(TAG, "End transmission"); while(instance->worker_running) { if(instance->worker_stopping) { @@ -230,12 +235,16 @@ void subghz_file_encoder_worker_free(SubGhzFileEncoderWorker* instance) { free(instance); } -bool subghz_file_encoder_worker_start(SubGhzFileEncoderWorker* instance, const char* file_path) { +bool subghz_file_encoder_worker_start( + SubGhzFileEncoderWorker* instance, + const char* file_path, + const char* radio_device_name) { furi_assert(instance); furi_assert(!instance->worker_running); furi_stream_buffer_reset(instance->stream); furi_string_set(instance->file_path, file_path); + instance->device = subghz_devices_get_by_name(radio_device_name); instance->worker_running = true; furi_thread_start(instance->thread); diff --git a/lib/subghz/subghz_file_encoder_worker.h b/lib/subghz/subghz_file_encoder_worker.h index f90875c98..eace03348 100644 --- a/lib/subghz/subghz_file_encoder_worker.h +++ b/lib/subghz/subghz_file_encoder_worker.h @@ -51,9 +51,14 @@ LevelDuration subghz_file_encoder_worker_get_level_duration(void* context); /** * Start SubGhzFileEncoderWorker. * @param instance Pointer to a SubGhzFileEncoderWorker instance + * @param file_path File path + * @param radio_device_name Radio device name * @return bool - true if ok */ -bool subghz_file_encoder_worker_start(SubGhzFileEncoderWorker* instance, const char* file_path); +bool subghz_file_encoder_worker_start( + SubGhzFileEncoderWorker* instance, + const char* file_path, + const char* radio_device_name); /** * Stop SubGhzFileEncoderWorker diff --git a/lib/subghz/subghz_setting.c b/lib/subghz/subghz_setting.c index 5e936b33b..8efe85f7b 100644 --- a/lib/subghz/subghz_setting.c +++ b/lib/subghz/subghz_setting.c @@ -3,7 +3,7 @@ //#include "subghz_i.h" #include -#include +#include #define TAG "SubGhzSetting" @@ -141,8 +141,7 @@ void subghz_setting_free(SubGhzSetting* instance) { static void subghz_setting_load_default_preset( SubGhzSetting* instance, const char* preset_name, - const uint8_t* preset_data, - const uint8_t preset_pa_table[8]) { + const uint8_t* preset_data) { furi_assert(instance); furi_assert(preset_data); uint32_t preset_data_count = 0; @@ -158,10 +157,8 @@ static void subghz_setting_load_default_preset( preset_data_count += 2; item->custom_preset_data_size = sizeof(uint8_t) * preset_data_count + sizeof(uint8_t) * 8; item->custom_preset_data = malloc(item->custom_preset_data_size); - //load preset register - memcpy(&item->custom_preset_data[0], &preset_data[0], preset_data_count); - //load pa table - memcpy(&item->custom_preset_data[preset_data_count], &preset_pa_table[0], 8); + //load preset register + pa table + memcpy(&item->custom_preset_data[0], &preset_data[0], item->custom_preset_data_size); } static void subghz_setting_load_default_region( @@ -185,25 +182,13 @@ static void subghz_setting_load_default_region( } subghz_setting_load_default_preset( - instance, - "AM270", - (uint8_t*)furi_hal_subghz_preset_ook_270khz_async_regs, - furi_hal_subghz_preset_ook_async_patable); + instance, "AM270", subghz_device_cc1101_preset_ook_270khz_async_regs); subghz_setting_load_default_preset( - instance, - "AM650", - (uint8_t*)furi_hal_subghz_preset_ook_650khz_async_regs, - furi_hal_subghz_preset_ook_async_patable); + instance, "AM650", subghz_device_cc1101_preset_ook_650khz_async_regs); subghz_setting_load_default_preset( - instance, - "FM238", - (uint8_t*)furi_hal_subghz_preset_2fsk_dev2_38khz_async_regs, - furi_hal_subghz_preset_2fsk_async_patable); + instance, "FM238", subghz_device_cc1101_preset_2fsk_dev2_38khz_async_regs); subghz_setting_load_default_preset( - instance, - "FM476", - (uint8_t*)furi_hal_subghz_preset_2fsk_dev47_6khz_async_regs, - furi_hal_subghz_preset_2fsk_async_patable); + instance, "FM476", subghz_device_cc1101_preset_2fsk_dev47_6khz_async_regs); } // Region check removed @@ -262,6 +247,7 @@ void subghz_setting_load(SubGhzSetting* instance, const char* file_path) { } while(flipper_format_read_uint32( fff_data_file, "Frequency", (uint32_t*)&temp_data32, 1)) { + //Todo: add a frequency support check depending on the selected radio device if(furi_hal_subghz_is_frequency_valid(temp_data32)) { FURI_LOG_I(TAG, "Frequency loaded %lu", temp_data32); FrequencyList_push_back(instance->frequencies, temp_data32); diff --git a/lib/subghz/subghz_tx_rx_worker.c b/lib/subghz/subghz_tx_rx_worker.c index b9d2f3c61..4eca17419 100644 --- a/lib/subghz/subghz_tx_rx_worker.c +++ b/lib/subghz/subghz_tx_rx_worker.c @@ -21,6 +21,8 @@ struct SubGhzTxRxWorker { SubGhzTxRxWorkerStatus status; uint32_t frequency; + const SubGhzDevice* device; + const GpioPin* device_data_gpio; SubGhzTxRxWorkerCallbackHaveRead callback_have_read; void* context_have_read; @@ -65,7 +67,7 @@ bool subghz_tx_rx_worker_rx(SubGhzTxRxWorker* instance, uint8_t* data, uint8_t* uint8_t timeout = 100; bool ret = false; if(instance->status != SubGhzTxRxWorkerStatusRx) { - furi_hal_subghz_rx(); + subghz_devices_set_rx(instance->device); instance->status = SubGhzTxRxWorkerStatusRx; furi_delay_tick(1); } @@ -74,24 +76,24 @@ bool subghz_tx_rx_worker_rx(SubGhzTxRxWorker* instance, uint8_t* data, uint8_t* furi_delay_tick(1); if(!--timeout) { FURI_LOG_W(TAG, "RX cc1101_g0 timeout"); - furi_hal_subghz_flush_rx(); - furi_hal_subghz_rx(); + subghz_devices_flush_rx(instance->device); + subghz_devices_set_rx(instance->device); break; } } - if(furi_hal_subghz_rx_pipe_not_empty()) { + if(subghz_devices_rx_pipe_not_empty(instance->device)) { FURI_LOG_I( TAG, "RSSI: %03.1fdbm LQI: %d", - (double)furi_hal_subghz_get_rssi(), - furi_hal_subghz_get_lqi()); - if(furi_hal_subghz_is_rx_data_crc_valid()) { - furi_hal_subghz_read_packet(data, size); + (double)subghz_devices_get_rssi(instance->device), + subghz_devices_get_lqi(instance->device)); + if(subghz_devices_is_rx_data_crc_valid(instance->device)) { + subghz_devices_read_packet(instance->device, data, size); ret = true; } - furi_hal_subghz_flush_rx(); - furi_hal_subghz_rx(); + subghz_devices_flush_rx(instance->device); + subghz_devices_set_rx(instance->device); } return ret; } @@ -99,10 +101,10 @@ bool subghz_tx_rx_worker_rx(SubGhzTxRxWorker* instance, uint8_t* data, uint8_t* void subghz_tx_rx_worker_tx(SubGhzTxRxWorker* instance, uint8_t* data, size_t size) { uint8_t timeout = 200; if(instance->status != SubGhzTxRxWorkerStatusIDLE) { - furi_hal_subghz_idle(); + subghz_devices_idle(instance->device); } - furi_hal_subghz_write_packet(data, size); - furi_hal_subghz_tx(); //start send + subghz_devices_write_packet(instance->device, data, size); + subghz_devices_set_tx(instance->device); //start send instance->status = SubGhzTxRxWorkerStatusTx; while(!furi_hal_gpio_read( instance->device_data_gpio)) { // Wait for GDO0 to be set -> sync transmitted @@ -120,7 +122,7 @@ void subghz_tx_rx_worker_tx(SubGhzTxRxWorker* instance, uint8_t* data, size_t si break; } } - furi_hal_subghz_idle(); + subghz_devices_idle(instance->device); instance->status = SubGhzTxRxWorkerStatusIDLE; } /** Worker thread @@ -130,6 +132,7 @@ void subghz_tx_rx_worker_tx(SubGhzTxRxWorker* instance, uint8_t* data, size_t si */ static int32_t subghz_tx_rx_worker_thread(void* context) { SubGhzTxRxWorker* instance = context; + furi_assert(instance->device); FURI_LOG_I(TAG, "Worker start"); subghz_devices_begin(instance->device); @@ -138,8 +141,10 @@ static int32_t subghz_tx_rx_worker_thread(void* context) { subghz_devices_idle(instance->device); subghz_devices_load_preset(instance->device, FuriHalSubGhzPresetGFSK9_99KbAsync, NULL); - furi_hal_subghz_set_frequency_and_path(instance->frequency); - furi_hal_subghz_flush_rx(); + furi_hal_gpio_init(instance->device_data_gpio, GpioModeInput, GpioPullNo, GpioSpeedLow); + + subghz_devices_set_frequency(instance->device, instance->frequency); + subghz_devices_flush_rx(instance->device); uint8_t data[SUBGHZ_TXRX_WORKER_MAX_TXRX_SIZE + 1] = {0}; size_t size_tx = 0; @@ -193,8 +198,8 @@ static int32_t subghz_tx_rx_worker_thread(void* context) { furi_delay_tick(1); } - furi_hal_subghz_set_path(FuriHalSubGhzPathIsolate); - furi_hal_subghz_sleep(); + subghz_devices_sleep(instance->device); + subghz_devices_end(instance->device); FURI_LOG_I(TAG, "Worker stop"); return 0; @@ -226,7 +231,10 @@ void subghz_tx_rx_worker_free(SubGhzTxRxWorker* instance) { free(instance); } -bool subghz_tx_rx_worker_start(SubGhzTxRxWorker* instance, uint32_t frequency) { +bool subghz_tx_rx_worker_start( + SubGhzTxRxWorker* instance, + const SubGhzDevice* device, + uint32_t frequency) { furi_assert(instance); furi_assert(!instance->worker_running); bool res = false; @@ -237,6 +245,7 @@ bool subghz_tx_rx_worker_start(SubGhzTxRxWorker* instance, uint32_t frequency) { if(furi_hal_subghz_is_tx_allowed(frequency)) { instance->frequency = frequency; + instance->device = device; res = true; } diff --git a/lib/subghz/subghz_tx_rx_worker.h b/lib/subghz/subghz_tx_rx_worker.h index ddc02e749..56bdb0a1f 100644 --- a/lib/subghz/subghz_tx_rx_worker.h +++ b/lib/subghz/subghz_tx_rx_worker.h @@ -1,6 +1,7 @@ #pragma once #include +#include #ifdef __cplusplus extern "C" { @@ -67,9 +68,13 @@ void subghz_tx_rx_worker_free(SubGhzTxRxWorker* instance); /** * Start SubGhzTxRxWorker * @param instance Pointer to a SubGhzTxRxWorker instance + * @param device Pointer to a SubGhzDevice instance * @return bool - true if ok */ -bool subghz_tx_rx_worker_start(SubGhzTxRxWorker* instance, uint32_t frequency); +bool subghz_tx_rx_worker_start( + SubGhzTxRxWorker* instance, + const SubGhzDevice* device, + uint32_t frequency); /** * Stop SubGhzTxRxWorker diff --git a/lib/subghz/types.h b/lib/subghz/types.h index 70a57d3f9..d0b500a85 100644 --- a/lib/subghz/types.h +++ b/lib/subghz/types.h @@ -21,6 +21,12 @@ #define SUBGHZ_RAW_FILE_VERSION 1 #define SUBGHZ_RAW_FILE_TYPE "Flipper SubGhz RAW File" +#define SUBGHZ_KEYSTORE_DIR_NAME EXT_PATH("subghz/assets/keeloq_mfcodes") +#define SUBGHZ_KEYSTORE_DIR_USER_NAME EXT_PATH("subghz/assets/keeloq_mfcodes_user") +#define SUBGHZ_CAME_ATOMO_DIR_NAME EXT_PATH("subghz/assets/came_atomo") +#define SUBGHZ_NICE_FLOR_S_DIR_NAME EXT_PATH("subghz/assets/nice_flor_s") +#define SUBGHZ_ALUTECH_AT_4N_DIR_NAME EXT_PATH("subghz/assets/alutech_at_4n") + typedef struct SubGhzProtocolRegistry SubGhzProtocolRegistry; typedef struct SubGhzEnvironment SubGhzEnvironment; From 0a45cba2bfa3c77a99c711921348e670e74446b8 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Fri, 14 Jul 2023 01:18:35 +0300 Subject: [PATCH 132/364] Update NFC Maker by Willy-JL --- applications/external/nfc_maker/nfc_maker.c | 3 +- applications/external/nfc_maker/nfc_maker.h | 20 +- .../scenes/nfc_maker_scene_bluetooth.c | 6 +- .../nfc_maker/scenes/nfc_maker_scene_config.h | 9 +- .../scenes/nfc_maker_scene_contact.c | 53 +++ .../scenes/nfc_maker_scene_contact_last.c | 55 +++ .../scenes/nfc_maker_scene_contact_mail.c | 55 +++ .../scenes/nfc_maker_scene_contact_phone.c | 55 +++ .../scenes/nfc_maker_scene_contact_url.c | 55 +++ .../nfc_maker/scenes/nfc_maker_scene_https.c | 8 +- .../nfc_maker/scenes/nfc_maker_scene_mail.c | 8 +- .../nfc_maker/scenes/nfc_maker_scene_phone.c | 8 +- .../nfc_maker/scenes/nfc_maker_scene_result.c | 361 +++++++++--------- ...er_scene_name.c => nfc_maker_scene_save.c} | 18 +- ...r_scene_menu.c => nfc_maker_scene_start.c} | 33 +- .../nfc_maker/scenes/nfc_maker_scene_text.c | 8 +- .../nfc_maker/scenes/nfc_maker_scene_url.c | 8 +- .../nfc_maker/scenes/nfc_maker_scene_wifi.c | 6 +- .../scenes/nfc_maker_scene_wifi_auth.c | 4 +- .../scenes/nfc_maker_scene_wifi_pass.c | 8 +- 20 files changed, 548 insertions(+), 233 deletions(-) create mode 100644 applications/external/nfc_maker/scenes/nfc_maker_scene_contact.c create mode 100644 applications/external/nfc_maker/scenes/nfc_maker_scene_contact_last.c create mode 100644 applications/external/nfc_maker/scenes/nfc_maker_scene_contact_mail.c create mode 100644 applications/external/nfc_maker/scenes/nfc_maker_scene_contact_phone.c create mode 100644 applications/external/nfc_maker/scenes/nfc_maker_scene_contact_url.c rename applications/external/nfc_maker/scenes/{nfc_maker_scene_name.c => nfc_maker_scene_save.c} (72%) rename applications/external/nfc_maker/scenes/{nfc_maker_scene_menu.c => nfc_maker_scene_start.c} (63%) diff --git a/applications/external/nfc_maker/nfc_maker.c b/applications/external/nfc_maker/nfc_maker.c index 0f27b145e..332614248 100644 --- a/applications/external/nfc_maker/nfc_maker.c +++ b/applications/external/nfc_maker/nfc_maker.c @@ -76,7 +76,8 @@ void nfc_maker_free(NfcMaker* app) { extern int32_t nfc_maker(void* p) { UNUSED(p); NfcMaker* app = nfc_maker_alloc(); - scene_manager_next_scene(app->scene_manager, NfcMakerSceneMenu); + scene_manager_set_scene_state(app->scene_manager, NfcMakerSceneStart, NfcMakerSceneHttps); + scene_manager_next_scene(app->scene_manager, NfcMakerSceneStart); view_dispatcher_run(app->view_dispatcher); nfc_maker_free(app); return 0; diff --git a/applications/external/nfc_maker/nfc_maker.h b/applications/external/nfc_maker/nfc_maker.h index f776d0fa0..261b7d2c3 100644 --- a/applications/external/nfc_maker/nfc_maker.h +++ b/applications/external/nfc_maker/nfc_maker.h @@ -18,8 +18,12 @@ #include #include "strnlen.h" -#define TEXT_INPUT_LEN 248 -#define WIFI_INPUT_LEN 90 +#define MAC_INPUT_LEN GAP_MAC_ADDR_SIZE +#define MAIL_INPUT_LEN 128 +#define PHONE_INPUT_LEN 17 + +#define BIG_INPUT_LEN 248 +#define SMALL_INPUT_LEN 90 typedef enum { WifiAuthenticationOpen = 0x01, @@ -46,10 +50,14 @@ typedef struct { ByteInput* byte_input; Popup* popup; - uint8_t mac_buf[GAP_MAC_ADDR_SIZE]; - char text_buf[TEXT_INPUT_LEN]; - char pass_buf[WIFI_INPUT_LEN]; - char name_buf[TEXT_INPUT_LEN]; + uint8_t mac_buf[MAC_INPUT_LEN]; + char mail_buf[MAIL_INPUT_LEN]; + char phone_buf[PHONE_INPUT_LEN]; + + char big_buf[BIG_INPUT_LEN]; + char small_buf1[SMALL_INPUT_LEN]; + char small_buf2[SMALL_INPUT_LEN]; + char save_buf[BIG_INPUT_LEN]; } NfcMaker; typedef enum { diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_bluetooth.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_bluetooth.c index 4e70a184d..e392ea096 100644 --- a/applications/external/nfc_maker/scenes/nfc_maker_scene_bluetooth.c +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_bluetooth.c @@ -16,7 +16,7 @@ void nfc_maker_scene_bluetooth_on_enter(void* context) { byte_input_set_header_text(byte_input, "Enter Bluetooth MAC:"); - for(size_t i = 0; i < GAP_MAC_ADDR_SIZE; i++) { + for(size_t i = 0; i < MAC_INPUT_LEN; i++) { app->mac_buf[i] = 0x69; } @@ -26,7 +26,7 @@ void nfc_maker_scene_bluetooth_on_enter(void* context) { NULL, app, app->mac_buf, - GAP_MAC_ADDR_SIZE); + MAC_INPUT_LEN); view_dispatcher_switch_to_view(app->view_dispatcher, NfcMakerViewByteInput); } @@ -40,7 +40,7 @@ bool nfc_maker_scene_bluetooth_on_event(void* context, SceneManagerEvent event) switch(event.event) { case ByteInputResultOk: furi_hal_bt_reverse_mac_addr(app->mac_buf); - scene_manager_next_scene(app->scene_manager, NfcMakerSceneName); + scene_manager_next_scene(app->scene_manager, NfcMakerSceneSave); break; default: break; diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_config.h b/applications/external/nfc_maker/scenes/nfc_maker_scene_config.h index a89b4198c..0ef4f021f 100644 --- a/applications/external/nfc_maker/scenes/nfc_maker_scene_config.h +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_config.h @@ -1,5 +1,10 @@ -ADD_SCENE(nfc_maker, menu, Menu) +ADD_SCENE(nfc_maker, start, Start) ADD_SCENE(nfc_maker, bluetooth, Bluetooth) +ADD_SCENE(nfc_maker, contact, Contact) +ADD_SCENE(nfc_maker, contact_last, ContactLast) +ADD_SCENE(nfc_maker, contact_mail, ContactMail) +ADD_SCENE(nfc_maker, contact_phone, ContactPhone) +ADD_SCENE(nfc_maker, contact_url, ContactUrl) ADD_SCENE(nfc_maker, https, Https) ADD_SCENE(nfc_maker, mail, Mail) ADD_SCENE(nfc_maker, phone, Phone) @@ -9,5 +14,5 @@ ADD_SCENE(nfc_maker, wifi, Wifi) ADD_SCENE(nfc_maker, wifi_auth, WifiAuth) ADD_SCENE(nfc_maker, wifi_encr, WifiEncr) ADD_SCENE(nfc_maker, wifi_pass, WifiPass) -ADD_SCENE(nfc_maker, name, Name) +ADD_SCENE(nfc_maker, save, Save) ADD_SCENE(nfc_maker, result, Result) diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_contact.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_contact.c new file mode 100644 index 000000000..91df6bfe7 --- /dev/null +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_contact.c @@ -0,0 +1,53 @@ +#include "../nfc_maker.h" + +enum TextInputResult { + TextInputResultOk, +}; + +static void nfc_maker_scene_contact_text_input_callback(void* context) { + NfcMaker* app = context; + + view_dispatcher_send_custom_event(app->view_dispatcher, TextInputResultOk); +} + +void nfc_maker_scene_contact_on_enter(void* context) { + NfcMaker* app = context; + NFCMaker_TextInput* text_input = app->text_input; + + nfc_maker_text_input_set_header_text(text_input, "Enter First Name:"); + + strlcpy(app->small_buf1, "Ben", SMALL_INPUT_LEN); + + nfc_maker_text_input_set_result_callback( + text_input, + nfc_maker_scene_contact_text_input_callback, + app, + app->small_buf1, + SMALL_INPUT_LEN, + true); + + view_dispatcher_switch_to_view(app->view_dispatcher, NfcMakerViewTextInput); +} + +bool nfc_maker_scene_contact_on_event(void* context, SceneManagerEvent event) { + NfcMaker* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + switch(event.event) { + case TextInputResultOk: + scene_manager_next_scene(app->scene_manager, NfcMakerSceneContactLast); + break; + default: + break; + } + } + + return consumed; +} + +void nfc_maker_scene_contact_on_exit(void* context) { + NfcMaker* app = context; + nfc_maker_text_input_reset(app->text_input); +} diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_contact_last.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_contact_last.c new file mode 100644 index 000000000..a22607de4 --- /dev/null +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_contact_last.c @@ -0,0 +1,55 @@ +#include "../nfc_maker.h" + +enum TextInputResult { + TextInputResultOk, +}; + +static void nfc_maker_scene_contact_last_text_input_callback(void* context) { + NfcMaker* app = context; + + view_dispatcher_send_custom_event(app->view_dispatcher, TextInputResultOk); +} + +void nfc_maker_scene_contact_last_on_enter(void* context) { + NfcMaker* app = context; + NFCMaker_TextInput* text_input = app->text_input; + + nfc_maker_text_input_set_header_text(text_input, "Enter Last Name:"); + + strlcpy(app->small_buf2, "Dover", SMALL_INPUT_LEN); + + nfc_maker_text_input_set_result_callback( + text_input, + nfc_maker_scene_contact_last_text_input_callback, + app, + app->small_buf2, + SMALL_INPUT_LEN, + true); + + nfc_maker_text_input_set_minimum_length(text_input, 0); + + view_dispatcher_switch_to_view(app->view_dispatcher, NfcMakerViewTextInput); +} + +bool nfc_maker_scene_contact_last_on_event(void* context, SceneManagerEvent event) { + NfcMaker* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + switch(event.event) { + case TextInputResultOk: + scene_manager_next_scene(app->scene_manager, NfcMakerSceneContactMail); + break; + default: + break; + } + } + + return consumed; +} + +void nfc_maker_scene_contact_last_on_exit(void* context) { + NfcMaker* app = context; + nfc_maker_text_input_reset(app->text_input); +} diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_contact_mail.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_contact_mail.c new file mode 100644 index 000000000..0f11c8ebf --- /dev/null +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_contact_mail.c @@ -0,0 +1,55 @@ +#include "../nfc_maker.h" + +enum TextInputResult { + TextInputResultOk, +}; + +static void nfc_maker_scene_contact_mail_text_input_callback(void* context) { + NfcMaker* app = context; + + view_dispatcher_send_custom_event(app->view_dispatcher, TextInputResultOk); +} + +void nfc_maker_scene_contact_mail_on_enter(void* context) { + NfcMaker* app = context; + NFCMaker_TextInput* text_input = app->text_input; + + nfc_maker_text_input_set_header_text(text_input, "Enter Email Address:"); + + strlcpy(app->mail_buf, "ben.dover@example.com", MAIL_INPUT_LEN); + + nfc_maker_text_input_set_result_callback( + text_input, + nfc_maker_scene_contact_mail_text_input_callback, + app, + app->mail_buf, + MAIL_INPUT_LEN, + true); + + nfc_maker_text_input_set_minimum_length(text_input, 0); + + view_dispatcher_switch_to_view(app->view_dispatcher, NfcMakerViewTextInput); +} + +bool nfc_maker_scene_contact_mail_on_event(void* context, SceneManagerEvent event) { + NfcMaker* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + switch(event.event) { + case TextInputResultOk: + scene_manager_next_scene(app->scene_manager, NfcMakerSceneContactPhone); + break; + default: + break; + } + } + + return consumed; +} + +void nfc_maker_scene_contact_mail_on_exit(void* context) { + NfcMaker* app = context; + nfc_maker_text_input_reset(app->text_input); +} diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_contact_phone.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_contact_phone.c new file mode 100644 index 000000000..cbe39c5a7 --- /dev/null +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_contact_phone.c @@ -0,0 +1,55 @@ +#include "../nfc_maker.h" + +enum TextInputResult { + TextInputResultOk, +}; + +static void nfc_maker_scene_contact_phone_text_input_callback(void* context) { + NfcMaker* app = context; + + view_dispatcher_send_custom_event(app->view_dispatcher, TextInputResultOk); +} + +void nfc_maker_scene_contact_phone_on_enter(void* context) { + NfcMaker* app = context; + NFCMaker_TextInput* text_input = app->text_input; + + nfc_maker_text_input_set_header_text(text_input, "Enter Phone Number:"); + + strlcpy(app->phone_buf, "+", PHONE_INPUT_LEN); + + nfc_maker_text_input_set_result_callback( + text_input, + nfc_maker_scene_contact_phone_text_input_callback, + app, + app->phone_buf, + PHONE_INPUT_LEN, + false); + + nfc_maker_text_input_set_minimum_length(text_input, 0); + + view_dispatcher_switch_to_view(app->view_dispatcher, NfcMakerViewTextInput); +} + +bool nfc_maker_scene_contact_phone_on_event(void* context, SceneManagerEvent event) { + NfcMaker* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + switch(event.event) { + case TextInputResultOk: + scene_manager_next_scene(app->scene_manager, NfcMakerSceneContactUrl); + break; + default: + break; + } + } + + return consumed; +} + +void nfc_maker_scene_contact_phone_on_exit(void* context) { + NfcMaker* app = context; + nfc_maker_text_input_reset(app->text_input); +} diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_contact_url.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_contact_url.c new file mode 100644 index 000000000..97ffd18c1 --- /dev/null +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_contact_url.c @@ -0,0 +1,55 @@ +#include "../nfc_maker.h" + +enum TextInputResult { + TextInputResultOk, +}; + +static void nfc_maker_scene_contact_url_text_input_callback(void* context) { + NfcMaker* app = context; + + view_dispatcher_send_custom_event(app->view_dispatcher, TextInputResultOk); +} + +void nfc_maker_scene_contact_url_on_enter(void* context) { + NfcMaker* app = context; + NFCMaker_TextInput* text_input = app->text_input; + + nfc_maker_text_input_set_header_text(text_input, "Enter URL Link:"); + + strlcpy(app->big_buf, "google.com", BIG_INPUT_LEN); + + nfc_maker_text_input_set_result_callback( + text_input, + nfc_maker_scene_contact_url_text_input_callback, + app, + app->big_buf, + BIG_INPUT_LEN, + true); + + nfc_maker_text_input_set_minimum_length(text_input, 0); + + view_dispatcher_switch_to_view(app->view_dispatcher, NfcMakerViewTextInput); +} + +bool nfc_maker_scene_contact_url_on_event(void* context, SceneManagerEvent event) { + NfcMaker* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + switch(event.event) { + case TextInputResultOk: + scene_manager_next_scene(app->scene_manager, NfcMakerSceneSave); + break; + default: + break; + } + } + + return consumed; +} + +void nfc_maker_scene_contact_url_on_exit(void* context) { + NfcMaker* app = context; + nfc_maker_text_input_reset(app->text_input); +} diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_https.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_https.c index 8f9704a7e..98411f73f 100644 --- a/applications/external/nfc_maker/scenes/nfc_maker_scene_https.c +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_https.c @@ -16,14 +16,14 @@ void nfc_maker_scene_https_on_enter(void* context) { nfc_maker_text_input_set_header_text(text_input, "Enter HTTPS Link:"); - strlcpy(app->text_buf, "google.com", TEXT_INPUT_LEN); + strlcpy(app->big_buf, "google.com", BIG_INPUT_LEN); nfc_maker_text_input_set_result_callback( text_input, nfc_maker_scene_https_text_input_callback, app, - app->text_buf, - TEXT_INPUT_LEN, + app->big_buf, + BIG_INPUT_LEN, true); view_dispatcher_switch_to_view(app->view_dispatcher, NfcMakerViewTextInput); @@ -37,7 +37,7 @@ bool nfc_maker_scene_https_on_event(void* context, SceneManagerEvent event) { consumed = true; switch(event.event) { case TextInputResultOk: - scene_manager_next_scene(app->scene_manager, NfcMakerSceneName); + scene_manager_next_scene(app->scene_manager, NfcMakerSceneSave); break; default: break; diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_mail.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_mail.c index 2f91c542d..0cb4c3d7e 100644 --- a/applications/external/nfc_maker/scenes/nfc_maker_scene_mail.c +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_mail.c @@ -16,14 +16,14 @@ void nfc_maker_scene_mail_on_enter(void* context) { nfc_maker_text_input_set_header_text(text_input, "Enter Email Address:"); - strlcpy(app->text_buf, "ben.dover@example.com", TEXT_INPUT_LEN); + strlcpy(app->mail_buf, "ben.dover@example.com", MAIL_INPUT_LEN); nfc_maker_text_input_set_result_callback( text_input, nfc_maker_scene_mail_text_input_callback, app, - app->text_buf, - TEXT_INPUT_LEN, + app->mail_buf, + MAIL_INPUT_LEN, true); view_dispatcher_switch_to_view(app->view_dispatcher, NfcMakerViewTextInput); @@ -37,7 +37,7 @@ bool nfc_maker_scene_mail_on_event(void* context, SceneManagerEvent event) { consumed = true; switch(event.event) { case TextInputResultOk: - scene_manager_next_scene(app->scene_manager, NfcMakerSceneName); + scene_manager_next_scene(app->scene_manager, NfcMakerSceneSave); break; default: break; diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_phone.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_phone.c index a399e12e3..68cbeab84 100644 --- a/applications/external/nfc_maker/scenes/nfc_maker_scene_phone.c +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_phone.c @@ -16,14 +16,14 @@ void nfc_maker_scene_phone_on_enter(void* context) { nfc_maker_text_input_set_header_text(text_input, "Enter Phone Number:"); - strlcpy(app->text_buf, "+", TEXT_INPUT_LEN); + strlcpy(app->phone_buf, "+", PHONE_INPUT_LEN); nfc_maker_text_input_set_result_callback( text_input, nfc_maker_scene_phone_text_input_callback, app, - app->text_buf, - TEXT_INPUT_LEN, + app->phone_buf, + PHONE_INPUT_LEN, false); view_dispatcher_switch_to_view(app->view_dispatcher, NfcMakerViewTextInput); @@ -37,7 +37,7 @@ bool nfc_maker_scene_phone_on_event(void* context, SceneManagerEvent event) { consumed = true; switch(event.event) { case TextInputResultOk: - scene_manager_next_scene(app->scene_manager, NfcMakerSceneName); + scene_manager_next_scene(app->scene_manager, NfcMakerSceneSave); break; default: break; diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_result.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_result.c index 38ad1e634..ad7371b5c 100644 --- a/applications/external/nfc_maker/scenes/nfc_maker_scene_result.c +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_result.c @@ -17,7 +17,7 @@ void nfc_maker_scene_result_on_enter(void* context) { FlipperFormat* file = flipper_format_file_alloc(furi_record_open(RECORD_STORAGE)); FuriString* path = furi_string_alloc(); - furi_string_printf(path, NFC_APP_FOLDER "/%s" NFC_APP_EXTENSION, app->name_buf); + furi_string_printf(path, NFC_APP_FOLDER "/%s" NFC_APP_EXTENSION, app->save_buf); uint32_t pages = 135; size_t size = pages * 4; @@ -61,221 +61,199 @@ void nfc_maker_scene_result_on_enter(void* context) { buf[i++] = 0x48; // Internal buf[i++] = 0x00; // Lock bytes buf[i++] = 0x00; // ... + buf[i++] = 0xE1; // Capability container buf[i++] = 0x10; // ... buf[i++] = 0x3E; // ... buf[i++] = 0x00; // ... - buf[i++] = 0x03; // Message flags - size_t start = i++; - switch(scene_manager_get_scene_state(app->scene_manager, NfcMakerSceneMenu)) { + buf[i++] = 0x03; // Container flags + + // NDEF Docs: https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/nrf/protocols/nfc/index.html#nfc-data-exchange-format-ndef + uint8_t tnf = 0x00; + const char* type = ""; + uint8_t* payload = NULL; + size_t payload_len = 0; + + size_t data_len = 0; + size_t j = 0; + switch(scene_manager_get_scene_state(app->scene_manager, NfcMakerSceneStart)) { case NfcMakerSceneBluetooth: { - buf[i++] = 0xD2; - buf[i++] = 0x20; - buf[i++] = 0x08; - buf[i++] = 0x61; - buf[i++] = 0x70; + tnf = 0x02; // Media-type [RFC 2046] + type = "application/vnd.bluetooth.ep.oob"; - buf[i++] = 0x70; - buf[i++] = 0x6C; - buf[i++] = 0x69; - buf[i++] = 0x63; + data_len = MAC_INPUT_LEN; + payload_len = data_len + 2; + payload = malloc(payload_len); - buf[i++] = 0x61; - buf[i++] = 0x74; - buf[i++] = 0x69; - buf[i++] = 0x6F; + payload[j++] = 0x08; + payload[j++] = 0x00; + memcpy(&payload[j], app->mac_buf, data_len); + j += data_len; + break; + } + case NfcMakerSceneContact: { + tnf = 0x02; // Media-type [RFC 2046] + type = "text/vcard"; - buf[i++] = 0x6E; - buf[i++] = 0x2F; - buf[i++] = 0x76; - buf[i++] = 0x6E; + FuriString* vcard = furi_string_alloc_set("BEGIN:VCARD\r\nVERSION:3.0\r\n"); + furi_string_cat_printf( + vcard, "PRODID:-//Flipper Xtreme//%s//EN\r\n", version_get_version(NULL)); + furi_string_cat_printf(vcard, "N:%s;%s;;;\r\n", app->small_buf2, app->small_buf1); + furi_string_cat_printf( + vcard, + "FN:%s%s%s\r\n", + app->small_buf1, + strnlen(app->small_buf2, SMALL_INPUT_LEN) ? " " : "", + app->small_buf2); + if(strnlen(app->mail_buf, MAIL_INPUT_LEN)) { + furi_string_cat_printf(vcard, "EMAIL:%s\r\n", app->mail_buf); + } + if(strnlen(app->phone_buf, PHONE_INPUT_LEN)) { + furi_string_cat_printf(vcard, "TEL:%s\r\n", app->phone_buf); + } + if(strnlen(app->big_buf, BIG_INPUT_LEN)) { + furi_string_cat_printf(vcard, "URL:%s\r\n", app->big_buf); + } + furi_string_cat_printf(vcard, "END:VCARD\r\n"); - buf[i++] = 0x64; - buf[i++] = 0x2E; - buf[i++] = 0x62; - buf[i++] = 0x6C; - - buf[i++] = 0x75; - buf[i++] = 0x65; - buf[i++] = 0x74; - buf[i++] = 0x6F; - - buf[i++] = 0x6F; - buf[i++] = 0x74; - buf[i++] = 0x68; - buf[i++] = 0x2E; - - buf[i++] = 0x65; - buf[i++] = 0x70; - buf[i++] = 0x2E; - buf[i++] = 0x6F; - - buf[i++] = 0x6F; - buf[i++] = 0x62; - buf[i++] = 0x08; - buf[i++] = 0x00; - - memcpy(&buf[i], app->mac_buf, GAP_MAC_ADDR_SIZE); - i += GAP_MAC_ADDR_SIZE; + payload_len = furi_string_size(vcard); + payload = malloc(payload_len); + memcpy(payload, furi_string_get_cstr(vcard), payload_len); + furi_string_free(vcard); break; } case NfcMakerSceneHttps: { - uint8_t data_len = strnlen(app->text_buf, TEXT_INPUT_LEN); + tnf = 0x01; // NFC Forum well-known type [NFC RTD] + type = "\x55"; - buf[i++] = 0xD1; - buf[i++] = 0x01; - buf[i++] = data_len + 1; - buf[i++] = 0x55; + data_len = strnlen(app->big_buf, BIG_INPUT_LEN); + payload_len = data_len + 1; + payload = malloc(payload_len); - buf[i++] = 0x04; // Prepend "https://" - memcpy(&buf[i], app->text_buf, data_len); - i += data_len; + payload[j++] = 0x04; // Prepend "https://" + memcpy(&payload[j], app->big_buf, data_len); + j += data_len; break; } case NfcMakerSceneMail: { - uint8_t data_len = strnlen(app->text_buf, TEXT_INPUT_LEN); + tnf = 0x01; // NFC Forum well-known type [NFC RTD] + type = "\x55"; - buf[i++] = 0xD1; - buf[i++] = 0x01; - buf[i++] = data_len + 1; - buf[i++] = 0x55; + data_len = strnlen(app->mail_buf, MAIL_INPUT_LEN); + payload_len = data_len + 1; + payload = malloc(payload_len); - buf[i++] = 0x06; // Prepend "mailto:" - memcpy(&buf[i], app->text_buf, data_len); - i += data_len; + payload[j++] = 0x06; // Prepend "mailto:" + memcpy(&payload[j], app->mail_buf, data_len); + j += data_len; break; } case NfcMakerScenePhone: { - uint8_t data_len = strnlen(app->text_buf, TEXT_INPUT_LEN); + tnf = 0x01; // NFC Forum well-known type [NFC RTD] + type = "\x55"; - buf[i++] = 0xD1; - buf[i++] = 0x01; - buf[i++] = data_len + 1; - buf[i++] = 0x55; + data_len = strnlen(app->phone_buf, PHONE_INPUT_LEN); + payload_len = data_len + 1; + payload = malloc(payload_len); - buf[i++] = 0x05; // Prepend "tel:" - memcpy(&buf[i], app->text_buf, data_len); - i += data_len; + payload[j++] = 0x05; // Prepend "tel:" + memcpy(&payload[j], app->phone_buf, data_len); + j += data_len; break; } case NfcMakerSceneText: { - uint8_t data_len = strnlen(app->text_buf, TEXT_INPUT_LEN); + tnf = 0x01; // NFC Forum well-known type [NFC RTD] + type = "\x54"; - buf[i++] = 0xD1; - buf[i++] = 0x01; - buf[i++] = data_len + 3; - buf[i++] = 0x54; + data_len = strnlen(app->big_buf, BIG_INPUT_LEN); + payload_len = data_len + 3; + payload = malloc(payload_len); - buf[i++] = 0x02; - buf[i++] = 0x65; // e - buf[i++] = 0x6E; // n - memcpy(&buf[i], app->text_buf, data_len); - i += data_len; + payload[j++] = 0x02; + payload[j++] = 0x65; // e + payload[j++] = 0x6E; // n + memcpy(&payload[j], app->big_buf, data_len); + j += data_len; break; } case NfcMakerSceneUrl: { - uint8_t data_len = strnlen(app->text_buf, TEXT_INPUT_LEN); + tnf = 0x01; // NFC Forum well-known type [NFC RTD] + type = "\x55"; - buf[i++] = 0xD1; - buf[i++] = 0x01; - buf[i++] = data_len + 1; - buf[i++] = 0x55; + data_len = strnlen(app->big_buf, BIG_INPUT_LEN); + payload_len = data_len + 1; + payload = malloc(payload_len); - buf[i++] = 0x00; // No prepend - memcpy(&buf[i], app->text_buf, data_len); - i += data_len; + payload[j++] = 0x00; // No prepend + memcpy(&payload[j], app->big_buf, data_len); + j += data_len; break; } case NfcMakerSceneWifi: { - uint8_t ssid_len = strnlen(app->text_buf, WIFI_INPUT_LEN); - uint8_t pass_len = strnlen(app->pass_buf, WIFI_INPUT_LEN); + tnf = 0x02; // Media-type [RFC 2046] + type = "application/vnd.wfa.wsc"; + + uint8_t ssid_len = strnlen(app->small_buf1, SMALL_INPUT_LEN); + uint8_t pass_len = strnlen(app->small_buf2, SMALL_INPUT_LEN); uint8_t data_len = ssid_len + pass_len; + payload_len = data_len + 39; + payload = malloc(payload_len); - buf[i++] = 0xD2; - buf[i++] = 0x17; - buf[i++] = data_len + 47; - buf[i++] = 0x61; - buf[i++] = 0x70; + payload[j++] = 0x10; + payload[j++] = 0x0E; + payload[j++] = 0x00; - buf[i++] = 0x70; - buf[i++] = 0x6C; - buf[i++] = 0x69; - buf[i++] = 0x63; + payload[j++] = data_len + 43; + payload[j++] = 0x10; + payload[j++] = 0x26; + payload[j++] = 0x00; - buf[i++] = 0x61; - buf[i++] = 0x74; - buf[i++] = 0x69; - buf[i++] = 0x6F; + payload[j++] = 0x01; + payload[j++] = 0x01; + payload[j++] = 0x10; + payload[j++] = 0x45; - buf[i++] = 0x6E; - buf[i++] = 0x2F; - buf[i++] = 0x76; - buf[i++] = 0x6E; + payload[j++] = 0x00; + payload[j++] = ssid_len; + memcpy(&payload[j], app->small_buf1, ssid_len); + j += ssid_len; + payload[j++] = 0x10; + payload[j++] = 0x03; - buf[i++] = 0x64; - buf[i++] = 0x2E; - buf[i++] = 0x77; - buf[i++] = 0x66; + payload[j++] = 0x00; + payload[j++] = 0x02; + payload[j++] = 0x00; + payload[j++] = + scene_manager_get_scene_state(app->scene_manager, NfcMakerSceneWifiAuth); - buf[i++] = 0x61; - buf[i++] = 0x2E; - buf[i++] = 0x77; - buf[i++] = 0x73; + payload[j++] = 0x10; + payload[j++] = 0x0F; + payload[j++] = 0x00; + payload[j++] = 0x02; - buf[i++] = 0x63; - buf[i++] = 0x10; - buf[i++] = 0x0E; - buf[i++] = 0x00; + payload[j++] = 0x00; + payload[j++] = + scene_manager_get_scene_state(app->scene_manager, NfcMakerSceneWifiEncr); + payload[j++] = 0x10; + payload[j++] = 0x27; - buf[i++] = data_len + 43; - buf[i++] = 0x10; - buf[i++] = 0x26; - buf[i++] = 0x00; + payload[j++] = 0x00; + payload[j++] = pass_len; + memcpy(&payload[j], app->small_buf2, pass_len); + j += pass_len; + payload[j++] = 0x10; + payload[j++] = 0x20; - buf[i++] = 0x01; - buf[i++] = 0x01; - buf[i++] = 0x10; - buf[i++] = 0x45; + payload[j++] = 0x00; + payload[j++] = 0x06; + payload[j++] = 0xFF; + payload[j++] = 0xFF; - buf[i++] = 0x00; - buf[i++] = ssid_len; - memcpy(&buf[i], app->text_buf, ssid_len); - i += ssid_len; - buf[i++] = 0x10; - buf[i++] = 0x03; - - buf[i++] = 0x00; - buf[i++] = 0x02; - buf[i++] = 0x00; - buf[i++] = scene_manager_get_scene_state(app->scene_manager, NfcMakerSceneWifiAuth); - - buf[i++] = 0x10; - buf[i++] = 0x0F; - buf[i++] = 0x00; - buf[i++] = 0x02; - - buf[i++] = 0x00; - buf[i++] = scene_manager_get_scene_state(app->scene_manager, NfcMakerSceneWifiEncr); - buf[i++] = 0x10; - buf[i++] = 0x27; - - buf[i++] = 0x00; - buf[i++] = pass_len; - memcpy(&buf[i], app->pass_buf, pass_len); - i += pass_len; - buf[i++] = 0x10; - buf[i++] = 0x20; - - buf[i++] = 0x00; - buf[i++] = 0x06; - buf[i++] = 0xFF; - buf[i++] = 0xFF; - - buf[i++] = 0xFF; - buf[i++] = 0xFF; - buf[i++] = 0xFF; - buf[i++] = 0xFF; + payload[j++] = 0xFF; + payload[j++] = 0xFF; + payload[j++] = 0xFF; + payload[j++] = 0xFF; break; } @@ -283,8 +261,51 @@ void nfc_maker_scene_result_on_enter(void* context) { break; } - // Message length and terminator - buf[start] = i - start - 1; + // Record header + uint8_t flags = 0; + flags |= 1 << 7; // MB (Message Begin) + flags |= 1 << 6; // ME (Message End) + flags |= tnf; // TNF (Type Name Format) + size_t type_len = strlen(type); + + size_t header_len = 0; + header_len += 1; // Flags and TNF + header_len += 1; // Type length + if(payload_len < 0xFF) { + flags |= 1 << 4; // SR (Short Record) + header_len += 1; // Payload length + } else { + header_len += 4; // Payload length + } + header_len += type_len; // Payload type + + size_t record_len = header_len + payload_len; + if(record_len < 0xFF) { + buf[i++] = record_len; // Record length + } else { + buf[i++] = 0xFF; // Record length + buf[i++] = record_len >> 8; // ... + buf[i++] = record_len & 0xFF; // ... + } + buf[i++] = flags; // Flags and TNF + buf[i++] = type_len; // Type length + if(flags & 1 << 4) { // SR (Short Record) + buf[i++] = payload_len; // Payload length + } else { + buf[i++] = 0x00; // Payload length + buf[i++] = 0x00; // ... + buf[i++] = payload_len >> 8; // ... + buf[i++] = payload_len & 0xFF; // ... + } + memcpy(&buf[i], type, type_len); // Payload type + i += type_len; + + // Record payload + memcpy(&buf[i], payload, payload_len); + i += payload_len; + free(payload); + + // Record terminator buf[i++] = 0xFE; // Padding until last 5 pages @@ -363,7 +384,7 @@ bool nfc_maker_scene_result_on_event(void* context, SceneManagerEvent event) { switch(event.event) { case PopupEventExit: scene_manager_search_and_switch_to_previous_scene( - app->scene_manager, NfcMakerSceneMenu); + app->scene_manager, NfcMakerSceneStart); break; default: break; diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_name.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_save.c similarity index 72% rename from applications/external/nfc_maker/scenes/nfc_maker_scene_name.c rename to applications/external/nfc_maker/scenes/nfc_maker_scene_save.c index 7f07a4e50..ee960cf3d 100644 --- a/applications/external/nfc_maker/scenes/nfc_maker_scene_name.c +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_save.c @@ -4,26 +4,26 @@ enum TextInputResult { TextInputResultOk, }; -static void nfc_maker_scene_name_text_input_callback(void* context) { +static void nfc_maker_scene_save_text_input_callback(void* context) { NfcMaker* app = context; view_dispatcher_send_custom_event(app->view_dispatcher, TextInputResultOk); } -void nfc_maker_scene_name_on_enter(void* context) { +void nfc_maker_scene_save_on_enter(void* context) { NfcMaker* app = context; NFCMaker_TextInput* text_input = app->text_input; - nfc_maker_text_input_set_header_text(text_input, "Name the NFC tag:"); + nfc_maker_text_input_set_header_text(text_input, "Save the NFC tag:"); - set_random_name(app->name_buf, TEXT_INPUT_LEN); + set_random_name(app->save_buf, BIG_INPUT_LEN); nfc_maker_text_input_set_result_callback( text_input, - nfc_maker_scene_name_text_input_callback, + nfc_maker_scene_save_text_input_callback, app, - app->name_buf, - TEXT_INPUT_LEN, + app->save_buf, + BIG_INPUT_LEN, true); ValidatorIsFile* validator_is_file = @@ -33,7 +33,7 @@ void nfc_maker_scene_name_on_enter(void* context) { view_dispatcher_switch_to_view(app->view_dispatcher, NfcMakerViewTextInput); } -bool nfc_maker_scene_name_on_event(void* context, SceneManagerEvent event) { +bool nfc_maker_scene_save_on_event(void* context, SceneManagerEvent event) { NfcMaker* app = context; bool consumed = false; @@ -51,7 +51,7 @@ bool nfc_maker_scene_name_on_event(void* context, SceneManagerEvent event) { return consumed; } -void nfc_maker_scene_name_on_exit(void* context) { +void nfc_maker_scene_save_on_exit(void* context) { NfcMaker* app = context; nfc_maker_text_input_reset(app->text_input); } diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_menu.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_start.c similarity index 63% rename from applications/external/nfc_maker/scenes/nfc_maker_scene_menu.c rename to applications/external/nfc_maker/scenes/nfc_maker_scene_start.c index 9f8b3c245..e791b1284 100644 --- a/applications/external/nfc_maker/scenes/nfc_maker_scene_menu.c +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_start.c @@ -1,11 +1,11 @@ #include "../nfc_maker.h" -void nfc_maker_scene_menu_submenu_callback(void* context, uint32_t index) { +void nfc_maker_scene_start_submenu_callback(void* context, uint32_t index) { NfcMaker* app = context; view_dispatcher_send_custom_event(app->view_dispatcher, index); } -void nfc_maker_scene_menu_on_enter(void* context) { +void nfc_maker_scene_start_on_enter(void* context) { NfcMaker* app = context; Submenu* submenu = app->submenu; @@ -15,39 +15,46 @@ void nfc_maker_scene_menu_on_enter(void* context) { submenu, "Bluetooth MAC", NfcMakerSceneBluetooth, - nfc_maker_scene_menu_submenu_callback, + nfc_maker_scene_start_submenu_callback, app); submenu_add_item( - submenu, "HTTPS Link", NfcMakerSceneHttps, nfc_maker_scene_menu_submenu_callback, app); + submenu, + "Contact Vcard", + NfcMakerSceneContact, + nfc_maker_scene_start_submenu_callback, + app); submenu_add_item( - submenu, "Email Address", NfcMakerSceneMail, nfc_maker_scene_menu_submenu_callback, app); + submenu, "HTTPS Link", NfcMakerSceneHttps, nfc_maker_scene_start_submenu_callback, app); submenu_add_item( - submenu, "Phone Number", NfcMakerScenePhone, nfc_maker_scene_menu_submenu_callback, app); + submenu, "Mail Address", NfcMakerSceneMail, nfc_maker_scene_start_submenu_callback, app); submenu_add_item( - submenu, "Text Note", NfcMakerSceneText, nfc_maker_scene_menu_submenu_callback, app); + submenu, "Phone Number", NfcMakerScenePhone, nfc_maker_scene_start_submenu_callback, app); submenu_add_item( - submenu, "Plain URL", NfcMakerSceneUrl, nfc_maker_scene_menu_submenu_callback, app); + submenu, "Text Note", NfcMakerSceneText, nfc_maker_scene_start_submenu_callback, app); submenu_add_item( - submenu, "WiFi Login", NfcMakerSceneWifi, nfc_maker_scene_menu_submenu_callback, app); + submenu, "Plain URL", NfcMakerSceneUrl, nfc_maker_scene_start_submenu_callback, app); + + submenu_add_item( + submenu, "WiFi Login", NfcMakerSceneWifi, nfc_maker_scene_start_submenu_callback, app); submenu_set_selected_item( - submenu, scene_manager_get_scene_state(app->scene_manager, NfcMakerSceneMenu)); + submenu, scene_manager_get_scene_state(app->scene_manager, NfcMakerSceneStart)); view_dispatcher_switch_to_view(app->view_dispatcher, NfcMakerViewSubmenu); } -bool nfc_maker_scene_menu_on_event(void* context, SceneManagerEvent event) { +bool nfc_maker_scene_start_on_event(void* context, SceneManagerEvent event) { NfcMaker* app = context; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { - scene_manager_set_scene_state(app->scene_manager, NfcMakerSceneMenu, event.event); + scene_manager_set_scene_state(app->scene_manager, NfcMakerSceneStart, event.event); consumed = true; scene_manager_next_scene(app->scene_manager, event.event); } @@ -55,7 +62,7 @@ bool nfc_maker_scene_menu_on_event(void* context, SceneManagerEvent event) { return consumed; } -void nfc_maker_scene_menu_on_exit(void* context) { +void nfc_maker_scene_start_on_exit(void* context) { NfcMaker* app = context; submenu_reset(app->submenu); } diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_text.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_text.c index fa8698cf4..3c1f7d3ca 100644 --- a/applications/external/nfc_maker/scenes/nfc_maker_scene_text.c +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_text.c @@ -16,14 +16,14 @@ void nfc_maker_scene_text_on_enter(void* context) { nfc_maker_text_input_set_header_text(text_input, "Enter Text Note:"); - strlcpy(app->text_buf, "Lorem ipsum", TEXT_INPUT_LEN); + strlcpy(app->big_buf, "Lorem ipsum", BIG_INPUT_LEN); nfc_maker_text_input_set_result_callback( text_input, nfc_maker_scene_text_text_input_callback, app, - app->text_buf, - TEXT_INPUT_LEN, + app->big_buf, + BIG_INPUT_LEN, true); view_dispatcher_switch_to_view(app->view_dispatcher, NfcMakerViewTextInput); @@ -37,7 +37,7 @@ bool nfc_maker_scene_text_on_event(void* context, SceneManagerEvent event) { consumed = true; switch(event.event) { case TextInputResultOk: - scene_manager_next_scene(app->scene_manager, NfcMakerSceneName); + scene_manager_next_scene(app->scene_manager, NfcMakerSceneSave); break; default: break; diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_url.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_url.c index 8d6ae9e69..03c620308 100644 --- a/applications/external/nfc_maker/scenes/nfc_maker_scene_url.c +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_url.c @@ -16,14 +16,14 @@ void nfc_maker_scene_url_on_enter(void* context) { nfc_maker_text_input_set_header_text(text_input, "Enter Plain URL:"); - strlcpy(app->text_buf, "https://google.com", TEXT_INPUT_LEN); + strlcpy(app->big_buf, "https://google.com", BIG_INPUT_LEN); nfc_maker_text_input_set_result_callback( text_input, nfc_maker_scene_url_text_input_callback, app, - app->text_buf, - TEXT_INPUT_LEN, + app->big_buf, + BIG_INPUT_LEN, true); view_dispatcher_switch_to_view(app->view_dispatcher, NfcMakerViewTextInput); @@ -37,7 +37,7 @@ bool nfc_maker_scene_url_on_event(void* context, SceneManagerEvent event) { consumed = true; switch(event.event) { case TextInputResultOk: - scene_manager_next_scene(app->scene_manager, NfcMakerSceneName); + scene_manager_next_scene(app->scene_manager, NfcMakerSceneSave); break; default: break; diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_wifi.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_wifi.c index eabc79c5b..e7cdc6640 100644 --- a/applications/external/nfc_maker/scenes/nfc_maker_scene_wifi.c +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_wifi.c @@ -16,14 +16,14 @@ void nfc_maker_scene_wifi_on_enter(void* context) { nfc_maker_text_input_set_header_text(text_input, "Enter WiFi SSID:"); - strlcpy(app->text_buf, "Bill Wi the Science Fi", WIFI_INPUT_LEN); + strlcpy(app->small_buf1, "Bill Wi the Science Fi", SMALL_INPUT_LEN); nfc_maker_text_input_set_result_callback( text_input, nfc_maker_scene_wifi_text_input_callback, app, - app->text_buf, - WIFI_INPUT_LEN, + app->small_buf1, + SMALL_INPUT_LEN, true); view_dispatcher_switch_to_view(app->view_dispatcher, NfcMakerViewTextInput); diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_wifi_auth.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_wifi_auth.c index 4cb90dabe..6e14c4e97 100644 --- a/applications/external/nfc_maker/scenes/nfc_maker_scene_wifi_auth.c +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_wifi_auth.c @@ -65,8 +65,8 @@ bool nfc_maker_scene_wifi_auth_on_event(void* context, SceneManagerEvent event) if(event.event == WifiAuthenticationOpen) { scene_manager_set_scene_state( app->scene_manager, NfcMakerSceneWifiEncr, WifiEncryptionNone); - strcpy(app->pass_buf, ""); - scene_manager_next_scene(app->scene_manager, NfcMakerSceneName); + strcpy(app->small_buf2, ""); + scene_manager_next_scene(app->scene_manager, NfcMakerSceneSave); } else { scene_manager_set_scene_state( app->scene_manager, NfcMakerSceneWifiEncr, WifiEncryptionAes); diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_wifi_pass.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_wifi_pass.c index c0301d338..228cd95da 100644 --- a/applications/external/nfc_maker/scenes/nfc_maker_scene_wifi_pass.c +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_wifi_pass.c @@ -16,14 +16,14 @@ void nfc_maker_scene_wifi_pass_on_enter(void* context) { nfc_maker_text_input_set_header_text(text_input, "Enter WiFi Password:"); - strlcpy(app->pass_buf, "244466666", WIFI_INPUT_LEN); + strlcpy(app->small_buf2, "244466666", SMALL_INPUT_LEN); nfc_maker_text_input_set_result_callback( text_input, nfc_maker_scene_wifi_pass_text_input_callback, app, - app->pass_buf, - WIFI_INPUT_LEN, + app->small_buf2, + SMALL_INPUT_LEN, true); view_dispatcher_switch_to_view(app->view_dispatcher, NfcMakerViewTextInput); @@ -37,7 +37,7 @@ bool nfc_maker_scene_wifi_pass_on_event(void* context, SceneManagerEvent event) consumed = true; switch(event.event) { case TextInputResultOk: - scene_manager_next_scene(app->scene_manager, NfcMakerSceneName); + scene_manager_next_scene(app->scene_manager, NfcMakerSceneSave); break; default: break; From 913af0baf3dae4930c2276d7525a7f06f45eb7dc Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 14 Jul 2023 00:28:17 +0200 Subject: [PATCH 133/364] Even more subghz merge --- .../debug/unit_tests/subghz/subghz_test.c | 11 ++-- .../drivers/subghz/cc1101_ext/cc1101_ext.c | 8 +-- applications/main/subghz/application.fam | 6 +-- .../main/subghz/subghz_dangerous_freq.c | 23 -------- .../main/subghz/subghz_extended_freq.c | 19 +++++++ .../targets/f7/furi_hal/furi_hal_subghz.c | 53 +++---------------- .../targets/f7/furi_hal/furi_hal_subghz.h | 30 ++++------- site_scons/commandline.scons | 1 + 8 files changed, 52 insertions(+), 99 deletions(-) delete mode 100644 applications/main/subghz/subghz_dangerous_freq.c create mode 100644 applications/main/subghz/subghz_extended_freq.c diff --git a/applications/debug/unit_tests/subghz/subghz_test.c b/applications/debug/unit_tests/subghz/subghz_test.c index f1ab92653..6bdaa641e 100644 --- a/applications/debug/unit_tests/subghz/subghz_test.c +++ b/applications/debug/unit_tests/subghz/subghz_test.c @@ -7,6 +7,8 @@ #include #include #include +#include +#include #define TAG "SubGhz TEST" #define KEYSTORE_DIR_NAME EXT_PATH("subghz/assets/keeloq_mfcodes") @@ -49,12 +51,15 @@ static void subghz_test_init(void) { subghz_environment_set_protocol_registry( environment_handler, (void*)&subghz_protocol_registry); + subghz_devices_init(); + receiver_handler = subghz_receiver_alloc_init(environment_handler); subghz_receiver_set_filter(receiver_handler, SubGhzProtocolFlag_Decodable); subghz_receiver_set_rx_callback(receiver_handler, subghz_test_rx_callback, NULL); } static void subghz_test_deinit(void) { + subghz_devices_deinit(); subghz_receiver_free(receiver_handler); subghz_environment_free(environment_handler); } @@ -68,7 +73,7 @@ static bool subghz_decoder_test(const char* path, const char* name_decoder) { if(decoder) { file_worker_encoder_handler = subghz_file_encoder_worker_alloc(); - if(subghz_file_encoder_worker_start(file_worker_encoder_handler, path)) { + if(subghz_file_encoder_worker_start(file_worker_encoder_handler, path, NULL)) { // the worker needs a file in order to open and read part of the file furi_delay_ms(100); @@ -108,7 +113,7 @@ static bool subghz_decode_random_test(const char* path) { uint32_t test_start = furi_get_tick(); file_worker_encoder_handler = subghz_file_encoder_worker_alloc(); - if(subghz_file_encoder_worker_start(file_worker_encoder_handler, path)) { + if(subghz_file_encoder_worker_start(file_worker_encoder_handler, path, NULL)) { // the worker needs a file in order to open and read part of the file furi_delay_ms(100); @@ -318,7 +323,7 @@ bool subghz_hal_async_tx_test_run(SubGhzHalAsyncTxTestType type) { SubGhzHalAsyncTxTest test = {0}; test.type = type; furi_hal_subghz_reset(); - furi_hal_subghz_load_preset(FuriHalSubGhzPresetOok650Async); + furi_hal_subghz_load_custom_preset(subghz_device_cc1101_preset_ook_650khz_async_regs); furi_hal_subghz_set_frequency_and_path(433920000); if(!furi_hal_subghz_start_async_tx(subghz_hal_async_tx_test_yield, &test)) { diff --git a/applications/drivers/subghz/cc1101_ext/cc1101_ext.c b/applications/drivers/subghz/cc1101_ext/cc1101_ext.c index 964317f9b..a95d1d0e8 100644 --- a/applications/drivers/subghz/cc1101_ext/cc1101_ext.c +++ b/applications/drivers/subghz/cc1101_ext/cc1101_ext.c @@ -18,7 +18,7 @@ #define TAG "SubGhz_Device_CC1101_Ext" #define SUBGHZ_DEVICE_CC1101_EXT_TX_GPIO &gpio_ext_pb2 -#define SUBGHZ_DEVICE_CC1101_EXT_DANGEROUS_RANGE false +#define SUBGHZ_DEVICE_CC1101_EXT_EXTENDED_RANGE false /* DMA Channels definition */ #define SUBGHZ_DEVICE_CC1101_EXT_DMA DMA2 @@ -432,16 +432,16 @@ bool subghz_device_cc1101_ext_is_frequency_valid(uint32_t value) { } bool subghz_device_cc1101_ext_is_tx_allowed(uint32_t value) { - if(!(SUBGHZ_DEVICE_CC1101_EXT_DANGEROUS_RANGE) && + if(!(SUBGHZ_DEVICE_CC1101_EXT_EXTENDED_RANGE) && !(value >= 299999755 && value <= 350000335) && // was increased from 348 to 350 !(value >= 386999938 && value <= 467750000) && // was increased from 464 to 467.75 !(value >= 778999847 && value <= 928000000)) { FURI_LOG_I(TAG, "Frequency blocked - outside default range"); return false; } else if( - (SUBGHZ_DEVICE_CC1101_EXT_DANGEROUS_RANGE) && + (SUBGHZ_DEVICE_CC1101_EXT_EXTENDED_RANGE) && !subghz_device_cc1101_ext_is_frequency_valid(value)) { - FURI_LOG_I(TAG, "Frequency blocked - outside dangerous range"); + FURI_LOG_I(TAG, "Frequency blocked - outside extended range"); return false; } diff --git a/applications/main/subghz/application.fam b/applications/main/subghz/application.fam index 37e131cc4..f4a181c1f 100644 --- a/applications/main/subghz/application.fam +++ b/applications/main/subghz/application.fam @@ -11,7 +11,7 @@ App( ], provides=[ "subghz_start", - "subghz_load_dangerous_settings", + "subghz_load_extended_settings", ], icon="A_Sub1ghz_14", stack_size=3 * 1024, @@ -29,9 +29,9 @@ App( ) App( - appid="subghz_load_dangerous_settings", + appid="subghz_load_extended_settings", apptype=FlipperAppType.STARTUP, - entry_point="subghz_dangerous_freq", + entry_point="subghz_extended_freq", requires=["storage", "subghz"], order=650, ) diff --git a/applications/main/subghz/subghz_dangerous_freq.c b/applications/main/subghz/subghz_dangerous_freq.c deleted file mode 100644 index 69a54f04b..000000000 --- a/applications/main/subghz/subghz_dangerous_freq.c +++ /dev/null @@ -1,23 +0,0 @@ -#include -#include - -#include - -#include - -void subghz_dangerous_freq() { - bool is_extended_i = false; - - Storage* storage = furi_record_open(RECORD_STORAGE); - FlipperFormat* fff_data_file = flipper_format_file_alloc(storage); - - if(flipper_format_file_open_existing(fff_data_file, "/ext/subghz/assets/dangerous_settings")) { - flipper_format_read_bool( - fff_data_file, "yes_i_want_to_destroy_my_flipper", &is_extended_i, 1); - } - - furi_hal_subghz_set_dangerous_frequency(is_extended_i); - - flipper_format_free(fff_data_file); - furi_record_close(RECORD_STORAGE); -} diff --git a/applications/main/subghz/subghz_extended_freq.c b/applications/main/subghz/subghz_extended_freq.c new file mode 100644 index 000000000..bbd44f639 --- /dev/null +++ b/applications/main/subghz/subghz_extended_freq.c @@ -0,0 +1,19 @@ +#include +#include +#include +#include + +void subghz_extended_freq() { + bool is_extended_i = false; + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* file = flipper_format_file_alloc(storage); + + if(flipper_format_file_open_existing(file, "/ext/subghz/assets/extend_range.txt")) { + flipper_format_read_bool(file, "use_ext_range_at_own_risk", &is_extended_i, 1); + } + + furi_hal_subghz_set_extended_frequency(is_extended_i); + + flipper_format_free(file); + furi_record_close(RECORD_STORAGE); +} diff --git a/firmware/targets/f7/furi_hal/furi_hal_subghz.c b/firmware/targets/f7/furi_hal/furi_hal_subghz.c index 98e1af326..cef6bb2fb 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_subghz.c +++ b/firmware/targets/f7/furi_hal/furi_hal_subghz.c @@ -54,16 +54,15 @@ typedef struct { uint8_t rolling_counter_mult; bool timestamp_file_names : 1; - bool dangerous_frequency_i : 1; + bool extended_frequency_i : 1; } FuriHalSubGhz; volatile FuriHalSubGhz furi_hal_subghz = { .state = SubGhzStateInit, .regulation = SubGhzRegulationTxRx, - .preset = FuriHalSubGhzPresetIDLE, .async_mirror_pin = NULL, .rolling_counter_mult = 1, - .dangerous_frequency_i = false, + .extended_frequency_i = false, }; uint8_t furi_hal_subghz_get_rolling_counter_mult(void) { @@ -74,16 +73,8 @@ void furi_hal_subghz_set_rolling_counter_mult(uint8_t mult) { furi_hal_subghz.rolling_counter_mult = mult; } -void furi_hal_subghz_set_external_power_disable(bool state) { - furi_hal_subghz.ext_module_power_disabled = state; -} - -bool furi_hal_subghz_get_external_power_disable(void) { - return furi_hal_subghz.ext_module_power_disabled; -} - -void furi_hal_subghz_set_dangerous_frequency(bool state_i) { - furi_hal_subghz.dangerous_frequency_i = state_i; +void furi_hal_subghz_set_extended_frequency(bool state_i) { + furi_hal_subghz.extended_frequency_i = state_i; } void furi_hal_subghz_set_async_mirror_pin(const GpioPin* pin) { @@ -97,7 +88,6 @@ const GpioPin* furi_hal_subghz_get_data_gpio() { void furi_hal_subghz_init() { furi_assert(furi_hal_subghz.state == SubGhzStateInit); furi_hal_subghz.state = SubGhzStateIdle; - furi_hal_subghz.preset = FuriHalSubGhzPresetIDLE; furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); @@ -162,34 +152,7 @@ void furi_hal_subghz_dump_state() { furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); } -void furi_hal_subghz_load_preset(FuriHalSubGhzPreset preset) { - if(preset == FuriHalSubGhzPresetOok650Async) { - furi_hal_subghz_load_registers((uint8_t*)furi_hal_subghz_preset_ook_650khz_async_regs); - furi_hal_subghz_load_patable(furi_hal_subghz_preset_ook_async_patable); - } else if(preset == FuriHalSubGhzPresetOok270Async) { - furi_hal_subghz_load_registers((uint8_t*)furi_hal_subghz_preset_ook_270khz_async_regs); - furi_hal_subghz_load_patable(furi_hal_subghz_preset_ook_async_patable); - } else if(preset == FuriHalSubGhzPreset2FSKDev238Async) { - furi_hal_subghz_load_registers( - (uint8_t*)furi_hal_subghz_preset_2fsk_dev2_38khz_async_regs); - furi_hal_subghz_load_patable(furi_hal_subghz_preset_2fsk_async_patable); - } else if(preset == FuriHalSubGhzPreset2FSKDev476Async) { - furi_hal_subghz_load_registers( - (uint8_t*)furi_hal_subghz_preset_2fsk_dev47_6khz_async_regs); - furi_hal_subghz_load_patable(furi_hal_subghz_preset_2fsk_async_patable); - } else if(preset == FuriHalSubGhzPresetMSK99_97KbAsync) { - furi_hal_subghz_load_registers((uint8_t*)furi_hal_subghz_preset_msk_99_97kb_async_regs); - furi_hal_subghz_load_patable(furi_hal_subghz_preset_msk_async_patable); - } else if(preset == FuriHalSubGhzPresetGFSK9_99KbAsync) { - furi_hal_subghz_load_registers((uint8_t*)furi_hal_subghz_preset_gfsk_9_99kb_async_regs); - furi_hal_subghz_load_patable(furi_hal_subghz_preset_gfsk_async_patable); - } else { - furi_crash("SubGhz: Missing config."); - } - furi_hal_subghz.preset = preset; -} - -void furi_hal_subghz_load_custom_preset(uint8_t* preset_data) { +void furi_hal_subghz_load_custom_preset(const uint8_t* preset_data) { //load config furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); cc1101_reset(&furi_hal_spi_bus_handle_subghz); @@ -204,7 +167,6 @@ void furi_hal_subghz_load_custom_preset(uint8_t* preset_data) { //load pa table memcpy(&pa[0], &preset_data[i + 2], 8); furi_hal_subghz_load_patable(pa); - furi_hal_subghz.preset = FuriHalSubGhzPresetCustom; //show debug if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { @@ -350,8 +312,7 @@ uint8_t furi_hal_subghz_get_lqi() { /* Modified by @tkerby & MX to the full YARD Stick One extended range of 281-361 MHz, 378-481 MHz, and 749-962 MHz. - These changes are at your own risk. The PLL may not lock and FZ devs have warned of possible damage - Set flag use_ext_range_at_own_risk in extend_range.txt to use + These changes are at your own risk. The PLL may not lock and FZ devs have warned of possible damage! */ bool furi_hal_subghz_is_frequency_valid(uint32_t value) { @@ -391,7 +352,7 @@ bool furi_hal_subghz_is_tx_allowed(uint32_t value) { } else if( (allow_extended_for_int) && // !furi_hal_subghz_is_frequency_valid(value)) { - FURI_LOG_I(TAG, "Frequency blocked - outside dangerous range"); + FURI_LOG_I(TAG, "Frequency blocked - outside extended range"); return false; } diff --git a/firmware/targets/f7/furi_hal/furi_hal_subghz.h b/firmware/targets/f7/furi_hal/furi_hal_subghz.h index 5098eec1c..c7249e1a6 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_subghz.h +++ b/firmware/targets/f7/furi_hal/furi_hal_subghz.h @@ -5,6 +5,8 @@ #pragma once +#include + #include #include #include @@ -21,18 +23,6 @@ extern "C" { #define API_HAL_SUBGHZ_ASYNC_TX_BUFFER_HALF (API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL / 2) #define API_HAL_SUBGHZ_ASYNC_TX_GUARD_TIME 999 -/** Radio Presets */ -typedef enum { - FuriHalSubGhzPresetIDLE, /**< default configuration */ - FuriHalSubGhzPresetOok270Async, /**< OOK, bandwidth 270kHz, asynchronous */ - FuriHalSubGhzPresetOok650Async, /**< OOK, bandwidth 650kHz, asynchronous */ - FuriHalSubGhzPreset2FSKDev238Async, /**< FM, deviation 2.380371 kHz, asynchronous */ - FuriHalSubGhzPreset2FSKDev476Async, /**< FM, deviation 47.60742 kHz, asynchronous */ - FuriHalSubGhzPresetMSK99_97KbAsync, /**< MSK, deviation 47.60742 kHz, 99.97Kb/s, asynchronous */ - FuriHalSubGhzPresetGFSK9_99KbAsync, /**< GFSK, deviation 19.042969 kHz, 9.996Kb/s, asynchronous */ - FuriHalSubGhzPresetCustom, /**Custom Preset*/ -} FuriHalSubGhzPreset; - /** Switchable Radio Paths */ typedef enum { FuriHalSubGhzPathIsolate, /**< Isolate Radio from antenna */ @@ -50,6 +40,12 @@ typedef enum { */ void furi_hal_subghz_set_async_mirror_pin(const GpioPin* pin); +/** Get data GPIO + * + * @return pointer to the gpio pin structure + */ +const GpioPin* furi_hal_subghz_get_data_gpio(); + /** Initialize and switch to power save mode Used by internal API-HAL * initialization routine Can be used to reinitialize device to safe state and * send it to sleep @@ -64,23 +60,17 @@ void furi_hal_subghz_sleep(); */ void furi_hal_subghz_dump_state(); -/** Load registers from preset by preset name - * - * @param preset to load - */ -void furi_hal_subghz_load_preset(FuriHalSubGhzPreset preset); - /** Load custom registers from preset * * @param preset_data registers to load */ -void furi_hal_subghz_load_custom_preset(uint8_t* preset_data); +void furi_hal_subghz_load_custom_preset(const uint8_t* preset_data); /** Load registers * * @param data Registers data */ -void furi_hal_subghz_load_registers(uint8_t* data); +void furi_hal_subghz_load_registers(const uint8_t* data); /** Load PATABLE * diff --git a/site_scons/commandline.scons b/site_scons/commandline.scons index 8ea43ca71..0e75cd908 100644 --- a/site_scons/commandline.scons +++ b/site_scons/commandline.scons @@ -233,6 +233,7 @@ vars.AddVariables( ("applications/debug", False), ("applications/external", False), ("applications/examples", False), + ("applications/drivers", False), ("applications_user", False), ], ), From cf182594749fa766c3ebcea31d418d93f87271f1 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 14 Jul 2023 00:30:39 +0200 Subject: [PATCH 134/364] Oops i forgor --- firmware/targets/f7/platform_specific/intrinsic_export.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/firmware/targets/f7/platform_specific/intrinsic_export.h b/firmware/targets/f7/platform_specific/intrinsic_export.h index c0d76fb89..d3c7be5e0 100644 --- a/firmware/targets/f7/platform_specific/intrinsic_export.h +++ b/firmware/targets/f7/platform_specific/intrinsic_export.h @@ -1,11 +1,13 @@ #pragma once #include +#include #ifdef __cplusplus extern "C" { #endif void __clear_cache(void*, void*); +void* __aeabi_uldivmod(uint64_t, uint64_t); #ifdef __cplusplus } From d8ccd03308043b5bfbfc4ed3a9aa62b3931628c1 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 14 Jul 2023 00:52:09 +0200 Subject: [PATCH 135/364] Fix main apps manifest merge --- applications/main/application.fam | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/applications/main/application.fam b/applications/main/application.fam index bf7843ff1..1248838e8 100644 --- a/applications/main/application.fam +++ b/applications/main/application.fam @@ -8,9 +8,7 @@ App( "infrared", "lfrfid", "nfc", - "infrared", - "gpio", - "ibutton", + "subghz", "bad_kb", "u2f", "archive", From 27dca6920f303cafad5eeed35dc004412ca146ff Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 14 Jul 2023 01:17:57 +0200 Subject: [PATCH 136/364] Update subbrute --- .../helpers/radio_device_loader.c | 64 +++++++++++++++++++ .../helpers/radio_device_loader.h | 15 +++++ .../helpers/subbrute_worker.c | 51 ++++++++++----- .../helpers/subbrute_worker.h | 5 +- .../helpers/subbrute_worker_private.h | 1 + .../external/subghz_bruteforcer/subbrute.c | 37 +++++------ .../subghz_bruteforcer/subbrute_device.c | 23 +++++-- .../subghz_bruteforcer/subbrute_device.h | 4 +- .../external/subghz_bruteforcer/subbrute_i.h | 1 + 9 files changed, 159 insertions(+), 42 deletions(-) create mode 100644 applications/external/subghz_bruteforcer/helpers/radio_device_loader.c create mode 100644 applications/external/subghz_bruteforcer/helpers/radio_device_loader.h diff --git a/applications/external/subghz_bruteforcer/helpers/radio_device_loader.c b/applications/external/subghz_bruteforcer/helpers/radio_device_loader.c new file mode 100644 index 000000000..d2cffde58 --- /dev/null +++ b/applications/external/subghz_bruteforcer/helpers/radio_device_loader.c @@ -0,0 +1,64 @@ +#include "radio_device_loader.h" + +#include +#include + +static void radio_device_loader_power_on() { + uint8_t attempts = 0; + while(!furi_hal_power_is_otg_enabled() && attempts++ < 5) { + furi_hal_power_enable_otg(); + //CC1101 power-up time + furi_delay_ms(10); + } +} + +static void radio_device_loader_power_off() { + if(furi_hal_power_is_otg_enabled()) furi_hal_power_disable_otg(); +} + +bool radio_device_loader_is_connect_external(const char* name) { + bool is_connect = false; + bool is_otg_enabled = furi_hal_power_is_otg_enabled(); + + if(!is_otg_enabled) { + radio_device_loader_power_on(); + } + + const SubGhzDevice* device = subghz_devices_get_by_name(name); + if(device) { + is_connect = subghz_devices_is_connect(device); + } + + if(!is_otg_enabled) { + radio_device_loader_power_off(); + } + return is_connect; +} + +const SubGhzDevice* radio_device_loader_set( + const SubGhzDevice* current_radio_device, + SubGhzRadioDeviceType radio_device_type) { + const SubGhzDevice* radio_device; + + if(radio_device_type == SubGhzRadioDeviceTypeExternalCC1101 && + radio_device_loader_is_connect_external(SUBGHZ_DEVICE_CC1101_EXT_NAME)) { + radio_device_loader_power_on(); + radio_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_EXT_NAME); + subghz_devices_begin(radio_device); + } else if(current_radio_device == NULL) { + radio_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME); + } else { + radio_device_loader_end(current_radio_device); + radio_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME); + } + + return radio_device; +} + +void radio_device_loader_end(const SubGhzDevice* radio_device) { + furi_assert(radio_device); + radio_device_loader_power_off(); + if(radio_device != subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME)) { + subghz_devices_end(radio_device); + } +} \ No newline at end of file diff --git a/applications/external/subghz_bruteforcer/helpers/radio_device_loader.h b/applications/external/subghz_bruteforcer/helpers/radio_device_loader.h new file mode 100644 index 000000000..bee4e2c36 --- /dev/null +++ b/applications/external/subghz_bruteforcer/helpers/radio_device_loader.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +/** SubGhzRadioDeviceType */ +typedef enum { + SubGhzRadioDeviceTypeInternal, + SubGhzRadioDeviceTypeExternalCC1101, +} SubGhzRadioDeviceType; + +const SubGhzDevice* radio_device_loader_set( + const SubGhzDevice* current_radio_device, + SubGhzRadioDeviceType radio_device_type); + +void radio_device_loader_end(const SubGhzDevice* radio_device); \ No newline at end of file diff --git a/applications/external/subghz_bruteforcer/helpers/subbrute_worker.c b/applications/external/subghz_bruteforcer/helpers/subbrute_worker.c index ef622482f..fb17e15a6 100644 --- a/applications/external/subghz_bruteforcer/helpers/subbrute_worker.c +++ b/applications/external/subghz_bruteforcer/helpers/subbrute_worker.c @@ -9,7 +9,7 @@ #define SUBBRUTE_TX_TIMEOUT 6 #define SUBBRUTE_MANUAL_TRANSMIT_INTERVAL 250 -SubBruteWorker* subbrute_worker_alloc() { +SubBruteWorker* subbrute_worker_alloc(const SubGhzDevice* radio_device) { SubBruteWorker* instance = malloc(sizeof(SubBruteWorker)); instance->state = SubBruteWorkerStateIDLE; @@ -37,6 +37,8 @@ SubBruteWorker* subbrute_worker_alloc() { instance->transmit_mode = false; + instance->radio_device = radio_device; + return instance; } @@ -56,6 +58,9 @@ void subbrute_worker_free(SubBruteWorker* instance) { furi_thread_free(instance->thread); + subghz_devices_sleep(instance->radio_device); + radio_device_loader_end(instance->radio_device); + free(instance); } @@ -206,9 +211,7 @@ void subbrute_worker_stop(SubBruteWorker* instance) { instance->worker_running = false; furi_thread_join(instance->thread); - furi_hal_subghz_set_path(FuriHalSubGhzPathIsolate); - furi_hal_subghz_idle(); - furi_hal_subghz_sleep(); + subghz_devices_idle(instance->radio_device); } bool subbrute_worker_transmit_current_key(SubBruteWorker* instance, uint64_t step) { @@ -320,20 +323,24 @@ void subbrute_worker_subghz_transmit(SubBruteWorker* instance, FlipperFormat* fl instance->transmitter = subghz_transmitter_alloc_init(instance->environment, instance->protocol_name); subghz_transmitter_deserialize(instance->transmitter, flipper_format); - furi_hal_subghz_reset(); - furi_hal_subghz_idle(); - furi_hal_subghz_load_preset(instance->preset); - furi_hal_subghz_set_frequency_and_path(instance->frequency); - furi_hal_subghz_start_async_tx(subghz_transmitter_yield, instance->transmitter); - while(!furi_hal_subghz_is_async_tx_complete()) { - furi_delay_ms(timeout); + subghz_devices_reset(instance->radio_device); + subghz_devices_idle(instance->radio_device); + subghz_devices_load_preset(instance->radio_device, instance->preset, NULL); + subghz_devices_set_frequency( + instance->radio_device, instance->frequency); // TODO is freq valid check + + if(subghz_devices_set_tx(instance->radio_device)) { + subghz_devices_start_async_tx( + instance->radio_device, subghz_transmitter_yield, instance->transmitter); + while(!subghz_devices_is_async_complete_tx(instance->radio_device)) { + furi_delay_ms(timeout); + } + subghz_devices_stop_async_tx(instance->radio_device); } - furi_hal_subghz_stop_async_tx(); - //furi_hal_subghz_set_path(FuriHalSubGhzPathIsolate); - furi_hal_subghz_idle(); - //furi_hal_subghz_sleep(); + subghz_devices_idle(instance->radio_device); + subghz_transmitter_stop(instance->transmitter); subghz_transmitter_free(instance->transmitter); instance->transmitter = NULL; @@ -456,4 +463,18 @@ void subbrute_worker_timeout_dec(SubBruteWorker* instance) { if(instance->tx_timeout_ms > 0) { instance->tx_timeout_ms--; } +} + +bool subbrute_worker_is_tx_allowed(SubBruteWorker* instance, uint32_t value) { + furi_assert(instance); + bool res = false; + + if(!subghz_devices_is_frequency_valid(instance->radio_device, value)) { + } else { + subghz_devices_set_frequency(instance->radio_device, value); + res = subghz_devices_set_tx(instance->radio_device); + subghz_devices_idle(instance->radio_device); + } + + return res; } \ No newline at end of file diff --git a/applications/external/subghz_bruteforcer/helpers/subbrute_worker.h b/applications/external/subghz_bruteforcer/helpers/subbrute_worker.h index f7c32dd4b..ef3a5eba8 100644 --- a/applications/external/subghz_bruteforcer/helpers/subbrute_worker.h +++ b/applications/external/subghz_bruteforcer/helpers/subbrute_worker.h @@ -1,6 +1,7 @@ #pragma once #include "../subbrute_protocols.h" +#include "radio_device_loader.h" typedef enum { SubBruteWorkerStateIDLE, @@ -13,7 +14,7 @@ typedef void (*SubBruteWorkerCallback)(void* context, SubBruteWorkerState state) typedef struct SubBruteWorker SubBruteWorker; -SubBruteWorker* subbrute_worker_alloc(); +SubBruteWorker* subbrute_worker_alloc(const SubGhzDevice* radio_device); void subbrute_worker_free(SubBruteWorker* instance); uint64_t subbrute_worker_get_step(SubBruteWorker* instance); bool subbrute_worker_set_step(SubBruteWorker* instance, uint64_t step); @@ -46,3 +47,5 @@ uint8_t subbrute_worker_get_timeout(SubBruteWorker* instance); void subbrute_worker_timeout_inc(SubBruteWorker* instance); void subbrute_worker_timeout_dec(SubBruteWorker* instance); + +bool subbrute_worker_is_tx_allowed(SubBruteWorker* instance, uint32_t value); \ No newline at end of file diff --git a/applications/external/subghz_bruteforcer/helpers/subbrute_worker_private.h b/applications/external/subghz_bruteforcer/helpers/subbrute_worker_private.h index a660ca731..7268389db 100644 --- a/applications/external/subghz_bruteforcer/helpers/subbrute_worker_private.h +++ b/applications/external/subghz_bruteforcer/helpers/subbrute_worker_private.h @@ -22,6 +22,7 @@ struct SubBruteWorker { SubGhzTransmitter* transmitter; const char* protocol_name; uint8_t tx_timeout_ms; + const SubGhzDevice* radio_device; // Initiated values SubBruteAttacks attack; // Attack state diff --git a/applications/external/subghz_bruteforcer/subbrute.c b/applications/external/subghz_bruteforcer/subbrute.c index f47c75943..75506f2c2 100644 --- a/applications/external/subghz_bruteforcer/subbrute.c +++ b/applications/external/subghz_bruteforcer/subbrute.c @@ -49,11 +49,20 @@ SubBruteState* subbrute_alloc() { // Notifications instance->notifications = furi_record_open(RECORD_NOTIFICATION); + subghz_devices_init(); + + // init radio device + instance->radio_device = + radio_device_loader_set(instance->radio_device, SubGhzRadioDeviceTypeExternalCC1101); + + subghz_devices_reset(instance->radio_device); + subghz_devices_idle(instance->radio_device); + // Devices - instance->device = subbrute_device_alloc(); + instance->device = subbrute_device_alloc(instance->radio_device); // SubBruteWorker - instance->worker = subbrute_worker_alloc(); + instance->worker = subbrute_worker_alloc(instance->radio_device); // TextInput instance->text_input = text_input_alloc(); @@ -95,7 +104,7 @@ SubBruteState* subbrute_alloc() { //instance->environment = subghz_environment_alloc(); // Uncomment to enable Debug pin output on PIN 17(1W) - //furi_hal_subghz_set_async_mirror_pin(&gpio_ibutton); + // subghz_devices_set_async_mirror_pin(instance->radio_device, &gpio_ibutton); return instance; } @@ -104,7 +113,7 @@ void subbrute_free(SubBruteState* instance) { furi_assert(instance); // Uncomment to enable Debug pin output on PIN 17(1W) - //furi_hal_subghz_set_async_mirror_pin(NULL); + // subghz_devices_set_async_mirror_pin(instance->radio_device, NULL); // SubBruteWorker subbrute_worker_stop(instance->worker); @@ -113,6 +122,8 @@ void subbrute_free(SubBruteState* instance) { // SubBruteDevice subbrute_device_free(instance->device); + subghz_devices_deinit(); + // Notifications notification_message(instance->notifications, &sequence_blink_stop); furi_record_close(RECORD_NOTIFICATION); @@ -179,6 +190,7 @@ void subbrute_popup_closed_callback(void* context) { // ENTRYPOINT int32_t subbrute_app(void* p) { UNUSED(p); + furi_hal_power_suppress_charge_enter(); dolphin_deed(DolphinDeedPluginStart); SubBruteState* instance = subbrute_alloc(); @@ -186,26 +198,11 @@ int32_t subbrute_app(void* p) { instance->view_dispatcher, instance->gui, ViewDispatcherTypeFullscreen); scene_manager_next_scene(instance->scene_manager, SubBruteSceneStart); - // Enable power for External CC1101 if it is connected - furi_hal_subghz_enable_ext_power(); - // Auto switch to internal radio if external radio is not available - furi_delay_ms(15); - if(!furi_hal_subghz_check_radio()) { - furi_hal_subghz_select_radio_type(SubGhzRadioInternal); - furi_hal_subghz_init_radio_type(SubGhzRadioInternal); - } - - furi_hal_power_suppress_charge_enter(); - notification_message(instance->notifications, &sequence_display_backlight_on); view_dispatcher_run(instance->view_dispatcher); - furi_hal_power_suppress_charge_exit(); - // Disable power for External CC1101 if it was enabled and module is connected - furi_hal_subghz_disable_ext_power(); - // Reinit SPI handles for internal radio / nfc - furi_hal_subghz_init_radio_type(SubGhzRadioInternal); subbrute_free(instance); + furi_hal_power_suppress_charge_exit(); return 0; } diff --git a/applications/external/subghz_bruteforcer/subbrute_device.c b/applications/external/subghz_bruteforcer/subbrute_device.c index 192b8bfa0..01ed74b36 100644 --- a/applications/external/subghz_bruteforcer/subbrute_device.c +++ b/applications/external/subghz_bruteforcer/subbrute_device.c @@ -9,7 +9,7 @@ #define TAG "SubBruteDevice" -SubBruteDevice* subbrute_device_alloc() { +SubBruteDevice* subbrute_device_alloc(const SubGhzDevice* radio_device) { SubBruteDevice* instance = malloc(sizeof(SubBruteDevice)); instance->current_step = 0; @@ -22,6 +22,8 @@ SubBruteDevice* subbrute_device_alloc() { subghz_environment_set_protocol_registry( instance->environment, (void*)&subghz_protocol_registry); + instance->radio_device = radio_device; + #ifdef FURI_DEBUG subbrute_device_attack_set_default_values(instance, SubBruteAttackLoadFile); #else @@ -152,7 +154,7 @@ SubBruteFileResult subbrute_device_attack_set( // For non-file types we didn't set SubGhzProtocolDecoderBase instance->receiver = subghz_receiver_alloc_init(instance->environment); subghz_receiver_set_filter(instance->receiver, SubGhzProtocolFlag_Decodable); - furi_hal_subghz_reset(); + // furi_hal_subghz_reset(); // TODO Is this necessary? uint8_t protocol_check_result = SubBruteFileResultProtocolNotFound; #ifdef FURI_DEBUG @@ -241,7 +243,7 @@ uint8_t subbrute_device_load_from_file(SubBruteDevice* instance, const char* fil instance->receiver = subghz_receiver_alloc_init(instance->environment); subghz_receiver_set_filter(instance->receiver, SubGhzProtocolFlag_Decodable); - furi_hal_subghz_reset(); + // furi_hal_subghz_reset(); // TODO Is this necessary? do { if(!flipper_format_file_open_existing(fff_data_file, file_path)) { @@ -261,11 +263,22 @@ uint8_t subbrute_device_load_from_file(SubBruteDevice* instance, const char* fil result = SubBruteFileResultMissingOrIncorrectFrequency; break; } - instance->file_protocol_info->frequency = temp_data32; - if(!furi_hal_subghz_is_tx_allowed(instance->file_protocol_info->frequency)) { + + if(!subghz_devices_is_frequency_valid(instance->radio_device, temp_data32)) { + FURI_LOG_E(TAG, "Unsupported radio device frequency"); + result = SubBruteFileResultMissingOrIncorrectFrequency; + break; + } + + instance->file_protocol_info->frequency = + subghz_devices_set_frequency(instance->radio_device, temp_data32); + + if(!subghz_devices_set_tx(instance->radio_device)) { + subghz_devices_idle(instance->radio_device); result = SubBruteFileResultFrequencyNotAllowed; break; } + subghz_devices_idle(instance->radio_device); // Preset if(!flipper_format_read_string(fff_data_file, "Preset", temp_str)) { diff --git a/applications/external/subghz_bruteforcer/subbrute_device.h b/applications/external/subghz_bruteforcer/subbrute_device.h index 7ff650e93..8b91222ee 100644 --- a/applications/external/subghz_bruteforcer/subbrute_device.h +++ b/applications/external/subghz_bruteforcer/subbrute_device.h @@ -5,6 +5,7 @@ #include #include #include +#include "helpers/radio_device_loader.h" #define SUBBRUTE_TEXT_STORE_SIZE 256 @@ -42,6 +43,7 @@ typedef struct { SubGhzReceiver* receiver; SubGhzProtocolDecoderBase* decoder_result; SubGhzEnvironment* environment; + const SubGhzDevice* radio_device; // Attack state SubBruteAttacks attack; @@ -56,7 +58,7 @@ typedef struct { uint8_t bit_index; } SubBruteDevice; -SubBruteDevice* subbrute_device_alloc(); +SubBruteDevice* subbrute_device_alloc(const SubGhzDevice* radio_device;); void subbrute_device_free(SubBruteDevice* instance); bool subbrute_device_save_file(SubBruteDevice* instance, const char* key_name); diff --git a/applications/external/subghz_bruteforcer/subbrute_i.h b/applications/external/subghz_bruteforcer/subbrute_i.h index 9f1bd57d4..6f8f3ac09 100644 --- a/applications/external/subghz_bruteforcer/subbrute_i.h +++ b/applications/external/subghz_bruteforcer/subbrute_i.h @@ -56,6 +56,7 @@ struct SubBruteState { Popup* popup; Widget* widget; DialogsApp* dialogs; + const SubGhzDevice* radio_device; // Text store char text_store[SUBBRUTE_MAX_LEN_NAME]; From 1243b192be3f0fcc5d2b88b0528c5ead2d290ba5 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 14 Jul 2023 01:18:17 +0200 Subject: [PATCH 137/364] Update api symbols --- firmware/targets/f7/api_symbols.csv | 39 ++++++++++------------------- 1 file changed, 13 insertions(+), 26 deletions(-) diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 535afe8c7..0bc40cfb7 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -59,7 +59,6 @@ Header,+,firmware/targets/f7/furi_hal/furi_hal_rfid.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_spi_config.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_spi_types.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_subghz.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_subghz_configs.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_target_hw.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_uart.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_usb_cdc.h,, @@ -197,6 +196,7 @@ Header,+,lib/subghz/blocks/decoder.h,, Header,+,lib/subghz/blocks/encoder.h,, Header,+,lib/subghz/blocks/generic.h,, Header,+,lib/subghz/blocks/math.h,, +Header,+,lib/subghz/devices/cc1101_configs.h,, Header,+,lib/subghz/environment.h,, Header,+,lib/subghz/protocols/raw.h,, Header,+,lib/subghz/receiver.h,, @@ -339,6 +339,7 @@ Function,+,XTREME_SETTINGS,XtremeSettings*, Function,-,XTREME_SETTINGS_LOAD,void, Function,+,XTREME_SETTINGS_SAVE,void, Function,-,_Exit,void,int +Function,+,__aeabi_uldivmod,void*,"uint64_t, uint64_t" Function,-,__assert,void,"const char*, int, const char*" Function,+,__assert_func,void,"const char*, int, const char*, const char*" Function,+,__clear_cache,void,"void*, void*" @@ -691,26 +692,6 @@ Function,+,canvas_width,uint8_t,const Canvas* Function,-,cbrt,double,double Function,-,cbrtf,float,float Function,-,cbrtl,long double,long double -Function,+,cc1101_calibrate,void,FuriHalSpiBusHandle* -Function,+,cc1101_flush_rx,void,FuriHalSpiBusHandle* -Function,+,cc1101_flush_tx,void,FuriHalSpiBusHandle* -Function,-,cc1101_get_partnumber,uint8_t,FuriHalSpiBusHandle* -Function,+,cc1101_get_rssi,uint8_t,FuriHalSpiBusHandle* -Function,+,cc1101_get_status,CC1101Status,FuriHalSpiBusHandle* -Function,-,cc1101_get_version,uint8_t,FuriHalSpiBusHandle* -Function,+,cc1101_read_fifo,uint8_t,"FuriHalSpiBusHandle*, uint8_t*, uint8_t*" -Function,+,cc1101_read_reg,CC1101Status,"FuriHalSpiBusHandle*, uint8_t, uint8_t*" -Function,+,cc1101_reset,void,FuriHalSpiBusHandle* -Function,+,cc1101_set_frequency,uint32_t,"FuriHalSpiBusHandle*, uint32_t" -Function,-,cc1101_set_intermediate_frequency,uint32_t,"FuriHalSpiBusHandle*, uint32_t" -Function,+,cc1101_set_pa_table,void,"FuriHalSpiBusHandle*, const uint8_t[8]" -Function,+,cc1101_shutdown,void,FuriHalSpiBusHandle* -Function,+,cc1101_strobe,CC1101Status,"FuriHalSpiBusHandle*, uint8_t" -Function,+,cc1101_switch_to_idle,void,FuriHalSpiBusHandle* -Function,+,cc1101_switch_to_rx,void,FuriHalSpiBusHandle* -Function,+,cc1101_switch_to_tx,void,FuriHalSpiBusHandle* -Function,+,cc1101_write_fifo,uint8_t,"FuriHalSpiBusHandle*, const uint8_t*, uint8_t" -Function,+,cc1101_write_reg,CC1101Status,"FuriHalSpiBusHandle*, uint8_t, uint8_t" Function,-,ceil,double,double Function,-,ceilf,float,float Function,-,ceill,long double,long double @@ -1480,8 +1461,7 @@ Function,+,furi_hal_subghz_is_rx_data_crc_valid,_Bool, Function,+,furi_hal_subghz_is_tx_allowed,_Bool,uint32_t Function,+,furi_hal_subghz_load_custom_preset,void,const uint8_t* Function,+,furi_hal_subghz_load_patable,void,const uint8_t[8] -Function,+,furi_hal_subghz_load_preset,void,FuriHalSubGhzPreset -Function,+,furi_hal_subghz_load_registers,void,uint8_t* +Function,+,furi_hal_subghz_load_registers,void,const uint8_t* Function,+,furi_hal_subghz_read_packet,void,"uint8_t*, uint8_t*" Function,+,furi_hal_subghz_reset,void, Function,+,furi_hal_subghz_rx,void, @@ -2207,6 +2187,7 @@ Function,+,nfc_device_save,_Bool,"NfcDevice*, const char*" Function,+,nfc_device_save_shadow,_Bool,"NfcDevice*, const char*" Function,+,nfc_device_set_loading_callback,void,"NfcDevice*, NfcLoadingCallback, void*" Function,+,nfc_device_set_name,void,"NfcDevice*, const char*" +Function,+,nfc_felica_type,const char*,FelicaICType Function,+,nfc_file_select,_Bool,NfcDevice* Function,-,nfc_generate_mf_classic,void,"NfcDeviceData*, uint8_t, MfClassicType" Function,+,nfc_get_dev_type,const char*,FuriHalNfcType @@ -2930,7 +2911,7 @@ Function,+,subghz_protocol_keeloq_bft_create_data,_Bool,"void*, FlipperFormat*, Function,+,subghz_protocol_keeloq_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint8_t, uint16_t, const char*, SubGhzRadioPreset*" Function,+,subghz_protocol_nice_flor_s_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint8_t, uint16_t, SubGhzRadioPreset*, _Bool" Function,+,subghz_protocol_raw_file_encoder_worker_set_callback_end,void,"SubGhzProtocolEncoderRAW*, SubGhzProtocolEncoderRAWCallbackEnd, void*" -Function,+,subghz_protocol_raw_gen_fff_data,void,"FlipperFormat*, const char*" +Function,+,subghz_protocol_raw_gen_fff_data,void,"FlipperFormat*, const char*, const char*" Function,+,subghz_protocol_raw_get_sample_write,size_t,SubGhzProtocolDecoderRAW* Function,+,subghz_protocol_raw_save_to_file_init,_Bool,"SubGhzProtocolDecoderRAW*, const char*, SubGhzRadioPreset*" Function,+,subghz_protocol_raw_save_to_file_pause,void,"SubGhzProtocolDecoderRAW*, _Bool" @@ -2979,7 +2960,7 @@ Function,+,subghz_tx_rx_worker_free,void,SubGhzTxRxWorker* Function,+,subghz_tx_rx_worker_is_running,_Bool,SubGhzTxRxWorker* Function,+,subghz_tx_rx_worker_read,size_t,"SubGhzTxRxWorker*, uint8_t*, size_t" Function,+,subghz_tx_rx_worker_set_callback_have_read,void,"SubGhzTxRxWorker*, SubGhzTxRxWorkerCallbackHaveRead, void*" -Function,+,subghz_tx_rx_worker_start,_Bool,"SubGhzTxRxWorker*, uint32_t" +Function,+,subghz_tx_rx_worker_start,_Bool,"SubGhzTxRxWorker*, const SubGhzDevice*, uint32_t" Function,+,subghz_tx_rx_worker_stop,void,SubGhzTxRxWorker* Function,+,subghz_tx_rx_worker_write,_Bool,"SubGhzTxRxWorker*, uint8_t*, size_t" Function,+,subghz_worker_alloc,SubGhzWorker*, @@ -3354,7 +3335,7 @@ Variable,+,I_CC_Lock_16x16,Icon, Variable,+,I_CC_Settings_16x16,Icon, Variable,+,I_CC_Xtreme_16x16,Icon, Variable,+,I_Certification1_103x56,Icon, -Variable,+,I_Certification2_98x33,Icon, +Variable,+,I_Certification2_46x33,Icon, Variable,+,I_Charging_lightning_9x10,Icon, Variable,+,I_Charging_lightning_mask_9x10,Icon, Variable,+,I_Circles_47x47,Icon, @@ -3791,6 +3772,12 @@ Variable,+,sequence_set_vibro_on,const NotificationSequence, Variable,+,sequence_single_vibro,const NotificationSequence, Variable,+,sequence_solid_yellow,const NotificationSequence, Variable,+,sequence_success,const NotificationSequence, +Variable,+,subghz_device_cc1101_preset_2fsk_dev2_38khz_async_regs,const uint8_t[], +Variable,+,subghz_device_cc1101_preset_2fsk_dev47_6khz_async_regs,const uint8_t[], +Variable,+,subghz_device_cc1101_preset_gfsk_9_99kb_async_regs,const uint8_t[], +Variable,+,subghz_device_cc1101_preset_msk_99_97kb_async_regs,const uint8_t[], +Variable,+,subghz_device_cc1101_preset_ook_270khz_async_regs,const uint8_t[], +Variable,+,subghz_device_cc1101_preset_ook_650khz_async_regs,const uint8_t[], Variable,+,subghz_protocol_raw,const SubGhzProtocol, Variable,+,subghz_protocol_raw_decoder,const SubGhzProtocolDecoder, Variable,+,subghz_protocol_raw_encoder,const SubGhzProtocolEncoder, From 52c30f50ec37da37fd91c4da103e396179f7c003 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Fri, 14 Jul 2023 03:07:35 +0300 Subject: [PATCH 138/364] update changelog and readme --- CHANGELOG.md | 60 +++++++++++++++++++--------------------------------- ReadMe.md | 6 +++--- 2 files changed, 25 insertions(+), 41 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 820afe918..93f9e6455 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,45 +1,29 @@ ## New changes * !!! **Warning! After installing, Desktop settings (Favoutite apps, PIN Code, AutoLock time..) will be resetted to default due to settings changes, Please set your PIN code, Favourite apps again in Settings->Desktop** !!! -* If you have copied any apps manually into `apps` folder - remove `apps` folder or that specific apps you copied on your microSD before installing this release to avoid issues due to OFW API version update! If you using regular builds or extra pack builds (e) without your manually added apps, all included apps will be installed automatically, no extra actions needed! +* This next text applies to you only **If you have copied any apps manually** into `apps` folder and don't know that you need to update those apps manually in same way you added them - remove `apps` folder or that specific apps you copied on your microSD before installing this release to avoid issues due to OFW API version update! **If you using regular builds or extra pack builds (e) without your manually added apps, all included apps will be installed automatically, no extra actions needed!** ----- -* SubGHz: **Keeloq: Centurion Nova read and emulate support (+ add manually)** -* SubGHz: FAAC SLH - UI Fixes, Fix sending signals with no seed -* SubGHz: Code cleanup, proper handling of protocols ignore options (by @gid9798 | PR #516) -* SubGHz: Various UI fixes (by @wosk | PR #527) -* NFC: Fixed issue #532 (Mifare classic user dict - delete removes more than selected key) -* Infrared: Updated universal remote assets (by @amec0e | PR #529) -* Plugins: **Use correct categories (folders) for all plugins (extra pack too)** -* Plugins: Various fixes for uFBT (by @hedger) -* Plugins: Added **NFC Maker** plugin (make tags with URLs, Wifi and other things) [(by Willy-JL)](https://github.com/ClaraCrazy/Flipper-Xtreme/tree/dev/applications/external/nfc_maker) -* Plugins: Added JetPack Joyride [(by timstrasser)](https://github.com/timstrasser) -* Plugins: Moved Barcode Generator [(by Kingal1337)](https://github.com/Kingal1337/flipper-barcode-generator) from extra pack into base fw, old barcode generator was removed +* SubGHz: Port latest OFW external radio driver, fix issues (now you can make drivers for other radio chips) (by @gid9798 | PR #539 #536 #535 #534) +* GUI module: SubMenu fix vertical orientation (by @gid9798 | PR #543) +* Apps: After merge fixes (by @gid9798 | PR #537) +* Docs: Update docs for debug build and update vscode example, please remove `debug_pack` target if you had it in your workflow +* Infrared: Updated universal remote assets (by @amec0e | PR #544) +* Plugins: Added Camera Suite GPIO application for the ESP32-CAM module. [(by CodyTolene)](https://github.com/CodyTolene/Flipper-Zero-Camera-Suite) (PR #541) * Plugins: Updated ESP32: WiFi Marauder companion plugin [(by 0xchocolate)](https://github.com/0xchocolate/flipperzero-wifi-marauder) -* Plugins: Updated i2c Tools [(by NaejEL)](https://github.com/NaejEL/flipperzero-i2ctools) -* Settings: Change LED and volume settings by 5% steps (by @cokyrain) -* BLE: BadBT fixes and furi_hal_bt cleanup (by @Willy-JL) -* WIP OFW PR 2825: **NFC: Improved MFC emulation on some readers (by AloneLiberty)** -* OFW PR 2829: Decode only supported Oregon 3 sensor (by @wosk) -* OFW PR: Update OFW PR 2782 -* OFW: SubGhz: add "SubGhz test" external application and the ability to work "SubGhz" as an external application -* OFW: API: explicitly add math.h -* OFW: NFC: Mf Ultralight emulation optimization -* OFW: Furi_Power: fix furi_hal_power_enable_otg -* OFW: FuriHal: allow nulling null isr -* OFW: FuriHal, Infrared, Protobuf: various fixes and improvements -* OFW: Picopass fix ice -* OFW: Desktop settings: show icon and name for external applications -* OFW: Furi,FuriHal: various improvements -* OFW: Debug apps: speaker, uart_echo with baudrate -* OFW: Add Mitsubishi MSZ-AP25VGK universal ac remote -* OFW: Fix roll-over in file browser and archive -* OFW: Fix fr-FR-mac keylayout -* OFW: NFC/RFID detector app -* OFW: Fast FAP Loader -* OFW: LF-RFID debug: make it work -* OFW: Fix M*LIB usage -* OFW: fix: make `dialog_file_browser_set_basic_options` initialize all fields -* OFW: Scroll acceleration -* OFW: Loader refaptoring: second encounter +* Plugins: Updated **NFC Maker** plugin (make tags with URLs, Wifi and other things) [(by Willy-JL)](https://github.com/ClaraCrazy/Flipper-Xtreme/tree/dev/applications/external/nfc_maker) +* Plugins: Updated **Mifare Nested** [(by AloneLiberty)](https://github.com/AloneLiberty/FlipperNested) +* Plugins: Updated Lightmeter [(by oleksiikutuzov)](https://github.com/oleksiikutuzov/flipperzero-lightmeter) +* Plugins: Updated **Multi (RFID/iButton) Fuzzer** [(by @gid9798)](https://github.com/DarkFlippers/Multi_Fuzzer) +* OFW: Loader: good looking error messages +* OFW: Desktop,Cli: add uptime info +* OFW: Archive and file browser fixes +* OFW: Loader: exit animation fix +* OFW: SubGhz: fix check connect cc1101_ext +* OFW: NFC: Improved MFC emulation on some readers +* OFW: Dolphin: add new animation +* OFW: Update toolchain to v23 +* OFW: More descriptive error messages for the log command +* OFW: **External menu apps** -> now we have ton of free internal space +* OFW: Device Info update ---- diff --git a/ReadMe.md b/ReadMe.md index 1bc96e7e7..a73fa5221 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -137,8 +137,8 @@ You can support us by using links or addresses below: ### Community apps included: -- **RFID Fuzzer** [(by @gid9798)](https://github.com/DarkFlippers/unleashed-firmware/pull/507) (original by Ganapati & xMasterX) -- **iButton Fuzzer** [(by @gid9798)](https://github.com/DarkFlippers/unleashed-firmware/pull/507) (original by xMasterX) +- **RFID Fuzzer** [(by @gid9798)](https://github.com/DarkFlippers/Multi_Fuzzer) (original by Ganapati & xMasterX) +- **iButton Fuzzer** [(by @gid9798)](https://github.com/DarkFlippers/Multi_Fuzzer) (original by xMasterX) - **Sub-GHz bruteforcer** [(by @derskythe & xMasterX)](https://github.com/derskythe/flipperzero-subbrute) [(original by Ganapati & xMasterX)](https://github.com/DarkFlippers/unleashed-firmware/pull/57) - **Sub-GHz playlist** [(by darmiel)](https://github.com/DarkFlippers/unleashed-firmware/pull/62) - ESP8266 Deauther plugin [(by SequoiaSan)](https://github.com/SequoiaSan/FlipperZero-Wifi-ESP8266-Deauther-Module) @@ -160,7 +160,7 @@ You can support us by using links or addresses below: - HC-SR04 Distance sensor - Ported and modified by @xMasterX [(original by Sanqui)](https://github.com/Sanqui/flipperzero-firmware/tree/hc_sr04) - How to connect -> (5V -> VCC) / (GND -> GND) / (13|TX -> Trig) / (14|RX -> Echo) - Morse Code [(by wh00hw)](https://github.com/wh00hw/MorseCodeFAP) - **Unitemp - Temperature sensors reader** (DHT11/22, DS18B20, BMP280, HTU21x and more) [(by quen0n)](https://github.com/quen0n/unitemp-flipperzero) -- BH1750 - Lightmeter [(by oleksiikutuzov)](https://github.com/oleksiikutuzov/flipperzero-lightmeter) +- Lightmeter [(by oleksiikutuzov)](https://github.com/oleksiikutuzov/flipperzero-lightmeter) - HEX Viewer [(by QtRoS)](https://github.com/QtRoS/flipper-zero-hex-viewer) - POCSAG Pager [(by xMasterX & Shmuma)](https://github.com/xMasterX/flipper-pager) - Text Viewer [(by kowalski7cc & kyhwana)](https://github.com/kowalski7cc/flipper-zero-text-viewer/tree/refactor-text-app) From a33a4610742ebf68d0d231f9bb12b488933cc47a Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 14 Jul 2023 02:43:32 +0200 Subject: [PATCH 139/364] Update apps --- applications/external/4inrow/4inrow.c | 1 + applications/external/blackjack/blackjack.c | 3 +- .../external/brainfuck/application.fam | 2 +- applications/external/brainfuck/brainfuck_i.h | 1 - .../external/brainfuck/views/bf_dev_env.c | 12 +- .../external/caesarcipher/application.fam | 2 +- .../external/calculator/application.fam | 2 +- .../external/cntdown_timer/application.fam | 4 +- .../cntdown_timer/views/countdown_view.c | 3 + applications/external/counter/application.fam | 4 +- .../external/esp8266_deauth/application.fam | 2 +- applications/external/flipbip/application.fam | 2 +- applications/external/flipbip/flipbip.h | 2 +- .../external/flipbip/helpers/flipbip_file.c | 1 - .../external/flipbip/lib/crypto/rand.c | 74 ++++----- .../flipbip/scenes/flipbip_scene_scene_1.c | 2 +- .../external/flipbip/views/flipbip_scene_1.c | 114 ++++++++----- .../flizzer_tracker/flizzer_tracker.c | 7 +- .../flizzer_tracker/flizzer_tracker.h | 7 +- .../flizzer_tracker/flizzer_tracker_hal.c | 2 +- .../external/flizzer_tracker/init_deinit.c | 1 + .../sound_engine/sound_engine.c | 4 +- .../sound_engine/sound_engine_adsr.c | 3 +- applications/external/geiger/application.fam | 2 +- applications/external/geiger/flipper_geiger.c | 58 +++++++ applications/external/ifttt/application.fam | 2 +- .../external/ifttt/ifttt_virtual_button.c | 10 +- applications/external/ifttt/views/send_view.c | 2 +- .../external/ir_remote/application.fam | 2 +- .../external/mifare_fuzzer/mifare_fuzzer.c | 1 + applications/external/nightstand/clock_app.c | 2 +- applications/external/nrf24batch/nrf24batch.c | 23 ++- applications/external/passgen/application.fam | 2 +- applications/external/passgen/passgen.c | 22 +-- .../external/pomodoro/application.fam | 4 +- .../external/pomodoro/flipp_pomodoro_app.c | 27 +++- .../external/pomodoro/flipp_pomodoro_app.h | 6 + .../external/pomodoro/flipp_pomodoro_app_i.h | 3 +- .../images/flipp_pomodoro_learn_50x128.png | Bin 0 -> 1234 bytes .../pomodoro/modules/flipp_pomodoro.h | 1 - .../modules/flipp_pomodoro_statistics.c | 26 +++ .../modules/flipp_pomodoro_statistics.h | 45 ++++++ .../config/flipp_pomodoro_scene_config.h | 1 + .../pomodoro/scenes/flipp_pomodoro_scene.h | 1 + .../scenes/flipp_pomodoro_scene_info.c | 59 +++++++ .../scenes/flipp_pomodoro_scene_timer.c | 12 +- .../pomodoro/views/flipp_pomodoro_info_view.c | 152 ++++++++++++++++++ .../pomodoro/views/flipp_pomodoro_info_view.h | 71 ++++++++ .../views/flipp_pomodoro_timer_view.c | 5 +- applications/external/pong/application.fam | 2 +- applications/external/pong/pong.png | Bin 6459 -> 7315 bytes applications/external/qrcode/application.fam | 2 +- .../rubiks_cube_scrambler/application.fam | 2 +- .../external/scorched_tanks/application.fam | 4 +- .../scorched_tanks/scorchedTanks_10px.png | Bin 614 -> 536 bytes applications/external/snake_2/snake_20.c | 3 +- applications/external/solitaire/solitaire.c | 3 +- applications/external/text2sam/stm32_sam.cpp | 9 +- .../external/timelapse/application.fam | 2 +- applications/external/timelapse/zeitraffer.c | 9 +- .../external/tuning_fork/tuning_fork.c | 1 - applications/external/unitemp/application.fam | 1 + applications/external/videopoker/poker.c | 1 - .../wifi_marauder_companion/application.fam | 30 ++-- .../external/wifi_scanner/application.fam | 2 +- 65 files changed, 696 insertions(+), 169 deletions(-) create mode 100644 applications/external/pomodoro/images/flipp_pomodoro_learn_50x128.png create mode 100644 applications/external/pomodoro/modules/flipp_pomodoro_statistics.c create mode 100644 applications/external/pomodoro/modules/flipp_pomodoro_statistics.h create mode 100644 applications/external/pomodoro/scenes/flipp_pomodoro_scene_info.c create mode 100644 applications/external/pomodoro/views/flipp_pomodoro_info_view.c create mode 100644 applications/external/pomodoro/views/flipp_pomodoro_info_view.h diff --git a/applications/external/4inrow/4inrow.c b/applications/external/4inrow/4inrow.c index e9b7b6c69..1f112a81a 100644 --- a/applications/external/4inrow/4inrow.c +++ b/applications/external/4inrow/4inrow.c @@ -4,6 +4,7 @@ #include #include #include +#include static int matrix[6][7] = {0}; static int cursorx = 3; diff --git a/applications/external/blackjack/blackjack.c b/applications/external/blackjack/blackjack.c index 91a493228..50d8e30f6 100644 --- a/applications/external/blackjack/blackjack.c +++ b/applications/external/blackjack/blackjack.c @@ -1,6 +1,7 @@ #include #include +#include #include #include @@ -629,4 +630,4 @@ free_and_exit: furi_message_queue_free(event_queue); return return_code; -} +} \ No newline at end of file diff --git a/applications/external/brainfuck/application.fam b/applications/external/brainfuck/application.fam index 9ee518fbe..e7da3af73 100644 --- a/applications/external/brainfuck/application.fam +++ b/applications/external/brainfuck/application.fam @@ -9,7 +9,7 @@ App( ], stack_size=8 * 1024, fap_icon="bfico.png", - fap_category="Misc", + fap_category="Tools", fap_icon_assets="icons", fap_author="@nymda", fap_weburl="https://github.com/nymda/FlipperZeroBrainfuck", diff --git a/applications/external/brainfuck/brainfuck_i.h b/applications/external/brainfuck/brainfuck_i.h index 293654c3e..1258c5d3c 100644 --- a/applications/external/brainfuck/brainfuck_i.h +++ b/applications/external/brainfuck/brainfuck_i.h @@ -38,7 +38,6 @@ typedef unsigned char byte; #include #include -#include #define BF_INST_BUFFER_SIZE 2048 #define BF_OUTPUT_SIZE 512 diff --git a/applications/external/brainfuck/views/bf_dev_env.c b/applications/external/brainfuck/views/bf_dev_env.c index 241a4ebc3..4167414c3 100644 --- a/applications/external/brainfuck/views/bf_dev_env.c +++ b/applications/external/brainfuck/views/bf_dev_env.c @@ -20,6 +20,12 @@ typedef struct { int right; } bMapping; +#ifdef FW_ORIGIN_Official +#define FONT_NAME FontSecondary +#else +#define FONT_NAME FontBatteryPercent +#endif + static bool bf_dev_process_up(BFDevEnv* devEnv); static bool bf_dev_process_down(BFDevEnv* devEnv); static bool bf_dev_process_left(BFDevEnv* devEnv); @@ -63,7 +69,7 @@ static void bf_dev_draw_button(Canvas* canvas, int x, int y, bool selected, cons if(selected) { canvas_draw_rbox(canvas, x, y, BT_X, BT_Y, 3); canvas_invert_color(canvas); - canvas_set_font(canvas, FontBatteryPercent); + canvas_set_font(canvas, FONT_NAME); canvas_draw_str_aligned( canvas, x + (BT_X / 2), y + (BT_Y / 2) - 1, AlignCenter, AlignCenter, lbl); canvas_invert_color(canvas); @@ -73,7 +79,7 @@ static void bf_dev_draw_button(Canvas* canvas, int x, int y, bool selected, cons canvas_draw_rbox(canvas, x + 2, y - 1, BT_X - 2, BT_Y - 1, 3); canvas_invert_color(canvas); canvas_draw_rframe(canvas, x, y, BT_X, BT_Y, 3); - canvas_set_font(canvas, FontBatteryPercent); + canvas_set_font(canvas, FONT_NAME); canvas_draw_str_aligned( canvas, x + (BT_X / 2), y + (BT_Y / 2) - 1, AlignCenter, AlignCenter, lbl); } @@ -131,7 +137,7 @@ static void bf_dev_draw_callback(Canvas* canvas, void* _model) { //textbox //grossly overcomplicated. not fixing it. canvas_draw_rframe(canvas, 1, 1, 126, 33, 2); - canvas_set_font(canvas, FontBatteryPercent); + canvas_set_font(canvas, FONT_NAME); int dbOffset = 0; if(appDev->dataSize > 72) { diff --git a/applications/external/caesarcipher/application.fam b/applications/external/caesarcipher/application.fam index 7f973d54a..fdb1052b0 100644 --- a/applications/external/caesarcipher/application.fam +++ b/applications/external/caesarcipher/application.fam @@ -9,7 +9,7 @@ App( ], stack_size=2 * 1024, fap_icon="caesar_cipher_icon.png", - fap_category="Misc", + fap_category="Tools", order=20, fap_author="@panki27", fap_weburl="https://github.com/panki27/caesar-cipher", diff --git a/applications/external/calculator/application.fam b/applications/external/calculator/application.fam index 1913e5503..cba45e49b 100644 --- a/applications/external/calculator/application.fam +++ b/applications/external/calculator/application.fam @@ -8,7 +8,7 @@ App( stack_size=1 * 1024, order=45, fap_icon="calcIcon.png", - fap_category="Misc", + fap_category="Tools", fap_author="@n-o-T-I-n-s-a-n-e", fap_weburl="https://github.com/n-o-T-I-n-s-a-n-e", fap_version="1.0", diff --git a/applications/external/cntdown_timer/application.fam b/applications/external/cntdown_timer/application.fam index 10d610d0f..054833eeb 100644 --- a/applications/external/cntdown_timer/application.fam +++ b/applications/external/cntdown_timer/application.fam @@ -12,9 +12,9 @@ App( stack_size=2 * 1024, order=20, fap_icon="cntdown_timer.png", - fap_category="Misc", + fap_category="Tools", fap_author="@0w0mewo", fap_weburl="https://github.com/0w0mewo/fpz_cntdown_timer", - fap_version="1.0", + fap_version="1.1", fap_description="Simple count down timer", ) diff --git a/applications/external/cntdown_timer/views/countdown_view.c b/applications/external/cntdown_timer/views/countdown_view.c index 97e8cb248..94e53ac55 100644 --- a/applications/external/cntdown_timer/views/countdown_view.c +++ b/applications/external/cntdown_timer/views/countdown_view.c @@ -129,6 +129,9 @@ static bool countdown_timer_view_on_input(InputEvent* event, void* ctx) { handle_misc_cmd(hw, CountDownTimerToggleCounting); } break; + case InputKeyBack: + return false; + break; default: break; diff --git a/applications/external/counter/application.fam b/applications/external/counter/application.fam index 3678c644d..6f2703307 100644 --- a/applications/external/counter/application.fam +++ b/applications/external/counter/application.fam @@ -6,10 +6,10 @@ App( requires=[ "gui", ], - fap_category="Misc", + fap_category="Tools", fap_icon="counter_icon.png", fap_author="@Krulknul", fap_weburl="https://github.com/Krulknul/dolphin-counter", - fap_version="1.0", + fap_version="1.1", fap_description="Simple counter", ) diff --git a/applications/external/esp8266_deauth/application.fam b/applications/external/esp8266_deauth/application.fam index 23ae94dce..9b176a3dd 100644 --- a/applications/external/esp8266_deauth/application.fam +++ b/applications/external/esp8266_deauth/application.fam @@ -5,7 +5,7 @@ App( entry_point="esp8266_deauth_app", requires=["gui"], stack_size=2 * 1024, - order=20, + order=100, fap_icon="wifi_10px.png", fap_category="WiFi", fap_author="@SequoiaSan & @xMasterX", diff --git a/applications/external/flipbip/application.fam b/applications/external/flipbip/application.fam index e8c660a89..0c17d5787 100644 --- a/applications/external/flipbip/application.fam +++ b/applications/external/flipbip/application.fam @@ -15,7 +15,7 @@ App( name="crypto", ), ], - fap_category="Misc", + fap_category="Tools", fap_description="Crypto toolkit for Flipper", fap_author="Struan Clark (xtruan)", fap_weburl="https://github.com/xtruan/FlipBIP", diff --git a/applications/external/flipbip/flipbip.h b/applications/external/flipbip/flipbip.h index 6f84a1736..527807011 100644 --- a/applications/external/flipbip/flipbip.h +++ b/applications/external/flipbip/flipbip.h @@ -15,7 +15,7 @@ #include "views/flipbip_startscreen.h" #include "views/flipbip_scene_1.h" -#define FLIPBIP_VERSION "v0.0.9" +#define FLIPBIP_VERSION "v1.0.0" #define COIN_BTC 0 #define COIN_DOGE 3 diff --git a/applications/external/flipbip/helpers/flipbip_file.c b/applications/external/flipbip/helpers/flipbip_file.c index 3b61b6b95..fd389e48e 100644 --- a/applications/external/flipbip/helpers/flipbip_file.c +++ b/applications/external/flipbip/helpers/flipbip_file.c @@ -1,6 +1,5 @@ #include "flipbip_file.h" #include -#include #include #include "../helpers/flipbip_string.h" // From: lib/crypto diff --git a/applications/external/flipbip/lib/crypto/rand.c b/applications/external/flipbip/lib/crypto/rand.c index b35214285..67a97876d 100644 --- a/applications/external/flipbip/lib/crypto/rand.c +++ b/applications/external/flipbip/lib/crypto/rand.c @@ -21,43 +21,25 @@ * OTHER DEALINGS IN THE SOFTWARE. */ -// NOTE: -// random32() and random_buffer() have been replaced in this implementation -// with Flipper Zero specific code. The original code is commented out below. +#define FLIPPER_HAL_RANDOM #include "rand.h" +#ifdef FLIPPER_HAL_RANDOM + +// NOTE: +// random32() and random_buffer() have been replaced in this implementation +// with Flipper Zero specific code. The original code is disabled by #define. + // Flipper Zero RNG code: #include -#ifndef RAND_PLATFORM_INDEPENDENT - -// Original code: -// #pragma message("NOT SUITABLE FOR PRODUCTION USE! Replace random32() function with your own secure code.") - -// The following code is not supposed to be used in a production environment. -// It's included only to make the library testable. -// The message above tries to prevent any accidental use outside of the test -// environment. -// -// You are supposed to replace the random8() and random32() function with your -// own secure code. There is also a possibility to replace the random_buffer() -// function as it is defined as a weak symbol. - static uint32_t seed = 0; void random_reseed(const uint32_t value) { seed = value; } -// Original code: -// uint32_t random32(void) { -// // Linear congruential generator from Numerical Recipes -// // https://en.wikipedia.org/wiki/Linear_congruential_generator -// seed = 1664525 * seed + 1013904223; -// return seed; -// } - // Flipper Zero RNG code: uint32_t random32(void) { return furi_hal_random_get(); @@ -68,22 +50,42 @@ void random_buffer(uint8_t* buf, size_t len) { furi_hal_random_fill_buf(buf, len); } -#endif /* RAND_PLATFORM_INDEPENDENT */ +#else /* PLATFORM INDEPENDENT */ + +#pragma message( \ + "NOT SUITABLE FOR PRODUCTION USE! Replace random32() function with your own secure code.") + +// The following code is not supposed to be used in a production environment. +// It's included only to make the library testable. +// The message above tries to prevent any accidental use outside of the test +// environment. +// +// You are supposed to replace the random8() and random32() function with your +// own secure code. There is also a possibility to replace the random_buffer() +// function as it is defined as a weak symbol. // // The following code is platform independent // -// Original code: -// void __attribute__((weak)) random_buffer(uint8_t *buf, size_t len) { -// uint32_t r = 0; -// for (size_t i = 0; i < len; i++) { -// if (i % 4 == 0) { -// r = random32(); -// } -// buf[i] = (r >> ((i % 4) * 8)) & 0xFF; -// } -// } +uint32_t random32(void) { + // Linear congruential generator from Numerical Recipes + // https://en.wikipedia.org/wiki/Linear_congruential_generator + seed = 1664525 * seed + 1013904223; + return seed; +} + +void __attribute__((weak)) random_buffer(uint8_t* buf, size_t len) { + uint32_t r = 0; + for(size_t i = 0; i < len; i++) { + if(i % 4 == 0) { + r = random32(); + } + buf[i] = (r >> ((i % 4) * 8)) & 0xFF; + } +} + +#endif /* FLIPPER_HAL_RANDOM */ uint32_t random_uniform(uint32_t n) { uint32_t x = 0, max = 0xFFFFFFFF - (0xFFFFFFFF % n); diff --git a/applications/external/flipbip/scenes/flipbip_scene_scene_1.c b/applications/external/flipbip/scenes/flipbip_scene_scene_1.c index 6f4064cd4..2f0201443 100644 --- a/applications/external/flipbip/scenes/flipbip_scene_scene_1.c +++ b/applications/external/flipbip/scenes/flipbip_scene_scene_1.c @@ -47,4 +47,4 @@ bool flipbip_scene_scene_1_on_event(void* context, SceneManagerEvent event) { void flipbip_scene_scene_1_on_exit(void* context) { FlipBip* app = context; UNUSED(app); -} \ No newline at end of file +} diff --git a/applications/external/flipbip/views/flipbip_scene_1.c b/applications/external/flipbip/views/flipbip_scene_1.c index 3437a1149..f57fe4830 100644 --- a/applications/external/flipbip/views/flipbip_scene_1.c +++ b/applications/external/flipbip/views/flipbip_scene_1.c @@ -23,7 +23,9 @@ #define DERIV_ACCOUNT 0 #define DERIV_CHANGE 0 -#define MAX_ADDR_LEN 42 + 1 // 42 = max length of address + null terminator +#define MAX_TEXT_LEN 30 // 30 = max length of text +#define MAX_TEXT_BUF (MAX_TEXT_LEN + 1) // max length of text + null terminator +#define MAX_ADDR_BUF (42 + 1) // 42 = max length of address + null terminator #define NUM_ADDRS 6 #define PAGE_LOADING 0 @@ -44,10 +46,10 @@ #define TEXT_RECEIVE_ADDRESS "receive address:" #define TEXT_DEFAULT_DERIV "m/44'/X'/0'/0" const char* TEXT_INFO = "-Scroll pages with up/down-" - "p1,2) Mnemonic/Seed " - "p3) xprv Root Key " - "p4,5) xprv/xpub Accnt Keys" - "p6,7) xprv/xpub Extnd Keys" + "p1,2) BIP39 Mnemonic/Seed" + "p3) BIP32 Root Key " + "p4,5) Prv/Pub Account Keys" + "p6,7) Prv/Pub BIP32 Keys " "p8+) Receive Addresses "; // #define TEXT_SAVE_QR "Save QR" @@ -98,6 +100,10 @@ static CONFIDENTIAL char* s_disp_text5 = NULL; static CONFIDENTIAL char* s_disp_text6 = NULL; // Derivation path text static const char* s_derivation_text = TEXT_DEFAULT_DERIV; +// Warning text +static bool s_warn_insecure = false; +#define WARN_INSECURE_TEXT_1 "Recommendation:" +#define WARN_INSECURE_TEXT_2 "Set BIP39 Passphrase" //static bool s_busy = false; void flipbip_scene_1_set_callback( @@ -117,13 +123,15 @@ static void flipbip_scene_1_init_address( uint32_t addr_index) { //s_busy = true; - // Buffer for address serialization - const size_t buflen = 40; - char buf[40 + 1] = {0}; + // buffer for address serialization + // subtract 2 for "0x", 1 for null terminator + const size_t buflen = MAX_ADDR_BUF - (2 + 1); + // subtract 2 for "0x" + char buf[MAX_ADDR_BUF - 2] = {0}; // Use static node for address generation memcpy(s_addr_node, node, sizeof(HDNode)); - memzero(addr_text, MAX_ADDR_LEN); + memzero(addr_text, MAX_ADDR_BUF); hdnode_private_ckd(s_addr_node, addr_index); hdnode_fill_public_key(s_addr_node); @@ -158,8 +166,13 @@ static void flipbip_scene_1_init_address( //s_busy = false; } -static void flipbip_scene_1_draw_generic(const char* text, size_t line_len) { +static void + flipbip_scene_1_draw_generic(const char* text, const size_t line_len, const bool chunk) { // Split the text into parts + size_t len = line_len; + if(len > MAX_TEXT_LEN) { + len = MAX_TEXT_LEN; + } for(size_t si = 1; si <= 6; si++) { char* ptr = NULL; @@ -176,11 +189,18 @@ static void flipbip_scene_1_draw_generic(const char* text, size_t line_len) { else if(si == 6) ptr = s_disp_text6; - memzero(ptr, 30 + 1); - if(line_len > 30) { - strncpy(ptr, text + ((si - 1) * 30), 30); - } else { - strncpy(ptr, text + ((si - 1) * line_len), line_len); + memzero(ptr, MAX_TEXT_BUF); + strncpy(ptr, text + ((si - 1) * len), len); + // add a space every 4 characters and shift the text + if(len < 23 && chunk) { + for(size_t i = 0; i < strlen(ptr); i++) { + if(i % 5 == 0) { + for(size_t j = strlen(ptr); j > i; j--) { + ptr[j] = ptr[j - 1]; + } + ptr[i] = ' '; + } + } } } } @@ -220,9 +240,9 @@ static void flipbip_scene_1_draw_mnemonic(const char* mnemonic) { else if(mi == 6) ptr = s_disp_text6; - memzero(ptr, 30 + 1); - if(strlen(mnemonic_part) > 30) { - strncpy(ptr, mnemonic_part, 30); + memzero(ptr, MAX_TEXT_BUF); + if(strlen(mnemonic_part) > MAX_TEXT_LEN) { + strncpy(ptr, mnemonic_part, MAX_TEXT_LEN); } else { strncpy(ptr, mnemonic_part, strlen(mnemonic_part)); } @@ -241,7 +261,7 @@ static void flipbip_scene_1_draw_seed(FlipBipScene1Model* const model) { // Convert the seed to a hex string flipbip_btox(model->seed, 64, seed_working); - flipbip_scene_1_draw_generic(seed_working, 22); + flipbip_scene_1_draw_generic(seed_working, 22, false); // Free the working seed memory memzero(seed_working, seed_working_len); @@ -249,12 +269,12 @@ static void flipbip_scene_1_draw_seed(FlipBipScene1Model* const model) { } static void flipbip_scene_1_clear_text() { - memzero((void*)s_disp_text1, 30 + 1); - memzero((void*)s_disp_text2, 30 + 1); - memzero((void*)s_disp_text3, 30 + 1); - memzero((void*)s_disp_text4, 30 + 1); - memzero((void*)s_disp_text5, 30 + 1); - memzero((void*)s_disp_text6, 30 + 1); + memzero((void*)s_disp_text1, MAX_TEXT_BUF); + memzero((void*)s_disp_text2, MAX_TEXT_BUF); + memzero((void*)s_disp_text3, MAX_TEXT_BUF); + memzero((void*)s_disp_text4, MAX_TEXT_BUF); + memzero((void*)s_disp_text5, MAX_TEXT_BUF); + memzero((void*)s_disp_text6, MAX_TEXT_BUF); } void flipbip_scene_1_draw(Canvas* canvas, FlipBipScene1Model* model) { @@ -264,35 +284,40 @@ void flipbip_scene_1_draw(Canvas* canvas, FlipBipScene1Model* model) { flipbip_scene_1_clear_text(); if(model->page == PAGE_INFO) { - flipbip_scene_1_draw_generic(TEXT_INFO, 27); + flipbip_scene_1_draw_generic(TEXT_INFO, 27, false); } else if(model->page == PAGE_MNEMONIC) { flipbip_scene_1_draw_mnemonic(model->mnemonic); } else if(model->page == PAGE_SEED) { flipbip_scene_1_draw_seed(model); } else if(model->page == PAGE_XPRV_ROOT) { - flipbip_scene_1_draw_generic(model->xprv_root, 20); + flipbip_scene_1_draw_generic(model->xprv_root, 20, false); } else if(model->page == PAGE_XPRV_ACCT) { - flipbip_scene_1_draw_generic(model->xprv_account, 20); + flipbip_scene_1_draw_generic(model->xprv_account, 20, false); } else if(model->page == PAGE_XPUB_ACCT) { - flipbip_scene_1_draw_generic(model->xpub_account, 20); + flipbip_scene_1_draw_generic(model->xpub_account, 20, false); } else if(model->page == PAGE_XPRV_EXTD) { - flipbip_scene_1_draw_generic(model->xprv_extended, 20); + flipbip_scene_1_draw_generic(model->xprv_extended, 20, false); } else if(model->page == PAGE_XPUB_EXTD) { - flipbip_scene_1_draw_generic(model->xpub_extended, 20); + flipbip_scene_1_draw_generic(model->xpub_extended, 20, false); } else if(model->page >= PAGE_ADDR_BEGIN && model->page <= PAGE_ADDR_END) { - uint32_t line_len = 12; + size_t line_len = 12; if(model->coin == FlipBipCoinETH60) { line_len = 14; } flipbip_scene_1_draw_generic( - model->recv_addresses[model->page - PAGE_ADDR_BEGIN], line_len); + model->recv_addresses[model->page - PAGE_ADDR_BEGIN], line_len, true); } if(model->page == PAGE_LOADING) { canvas_set_font(canvas, FontPrimary); canvas_draw_str(canvas, 2, 10, TEXT_LOADING); canvas_draw_str(canvas, 7, 30, s_derivation_text); - canvas_draw_icon(canvas, 86, 25, &I_Keychain_39x36); + canvas_draw_icon(canvas, 86, 22, &I_Keychain_39x36); + if(s_warn_insecure) { + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 2, 50, WARN_INSECURE_TEXT_1); + canvas_draw_str(canvas, 2, 60, WARN_INSECURE_TEXT_2); + } } else if(model->page >= PAGE_ADDR_BEGIN && model->page <= PAGE_ADDR_END) { // draw address header canvas_set_font(canvas, FontSecondary); @@ -461,8 +486,8 @@ static int flipbip_scene_1_model_init( // Initialize addresses for(uint8_t a = 0; a < NUM_ADDRS; a++) { - model->recv_addresses[a] = malloc(MAX_ADDR_LEN); - memzero(model->recv_addresses[a], MAX_ADDR_LEN); + model->recv_addresses[a] = malloc(MAX_ADDR_BUF); + memzero(model->recv_addresses[a], MAX_ADDR_BUF); flipbip_scene_1_init_address(model->recv_addresses[a], node, coin, a); // Save QR code file @@ -586,7 +611,7 @@ void flipbip_scene_1_exit(void* context) { free((void*)model->xprv_extended); free((void*)model->xpub_extended); for(int a = 0; a < NUM_ADDRS; a++) { - memzero((void*)model->recv_addresses[a], MAX_ADDR_LEN); + memzero((void*)model->recv_addresses[a], MAX_ADDR_BUF); free((void*)model->recv_addresses[a]); } } @@ -614,6 +639,9 @@ void flipbip_scene_1_enter(void* context) { const char* passphrase_text = ""; if(app->passphrase == FlipBipPassphraseOn && strlen(app->passphrase_text) > 0) { passphrase_text = app->passphrase_text; + s_warn_insecure = false; + } else { + s_warn_insecure = true; } // BIP44 Coin setting @@ -685,12 +713,12 @@ FlipBipScene1* flipbip_scene_1_alloc() { s_addr_node = (HDNode*)malloc(sizeof(HDNode)); // allocate the display text - s_disp_text1 = (char*)malloc(30 + 1); - s_disp_text2 = (char*)malloc(30 + 1); - s_disp_text3 = (char*)malloc(30 + 1); - s_disp_text4 = (char*)malloc(30 + 1); - s_disp_text5 = (char*)malloc(30 + 1); - s_disp_text6 = (char*)malloc(30 + 1); + s_disp_text1 = (char*)malloc(MAX_TEXT_BUF); + s_disp_text2 = (char*)malloc(MAX_TEXT_BUF); + s_disp_text3 = (char*)malloc(MAX_TEXT_BUF); + s_disp_text4 = (char*)malloc(MAX_TEXT_BUF); + s_disp_text5 = (char*)malloc(MAX_TEXT_BUF); + s_disp_text6 = (char*)malloc(MAX_TEXT_BUF); return instance; } diff --git a/applications/external/flizzer_tracker/flizzer_tracker.c b/applications/external/flizzer_tracker/flizzer_tracker.c index ed696cdc6..2c0729a43 100644 --- a/applications/external/flizzer_tracker/flizzer_tracker.c +++ b/applications/external/flizzer_tracker/flizzer_tracker.c @@ -99,7 +99,10 @@ int32_t flizzer_tracker_app(void* p) { UNUSED(p); Storage* storage = furi_record_open(RECORD_STORAGE); - storage_simply_mkdir(storage, FLIZZER_TRACKER_INSTRUMENTS_FOLDER); + bool st = storage_simply_mkdir(storage, APPSDATA_FOLDER); + st = storage_simply_mkdir(storage, FLIZZER_TRACKER_FOLDER); + st = storage_simply_mkdir(storage, FLIZZER_TRACKER_INSTRUMENTS_FOLDER); + UNUSED(st); furi_record_close(RECORD_STORAGE); FlizzerTrackerApp* tracker = init_tracker(44100, 50, true, 1024); @@ -212,4 +215,4 @@ int32_t flizzer_tracker_app(void* p) { deinit_tracker(tracker); return 0; -} +} \ No newline at end of file diff --git a/applications/external/flizzer_tracker/flizzer_tracker.h b/applications/external/flizzer_tracker/flizzer_tracker.h index 6ece14e20..97269a98e 100644 --- a/applications/external/flizzer_tracker/flizzer_tracker.h +++ b/applications/external/flizzer_tracker/flizzer_tracker.h @@ -22,8 +22,9 @@ #include "sound_engine/sound_engine_filter.h" #include "tracker_engine/tracker_engine_defs.h" -#define FLIZZER_TRACKER_FOLDER STORAGE_APP_DATA_PATH_PREFIX -#define FLIZZER_TRACKER_INSTRUMENTS_FOLDER APP_DATA_PATH("instruments") +#define APPSDATA_FOLDER "/ext/apps_data" +#define FLIZZER_TRACKER_FOLDER "/ext/apps_data/flizzer_tracker" +#define FLIZZER_TRACKER_INSTRUMENTS_FOLDER "/ext/apps_data/flizzer_tracker/instruments" #define FILE_NAME_LEN 64 typedef enum { @@ -222,4 +223,4 @@ typedef struct { } TrackerViewModel; void draw_callback(Canvas* canvas, void* ctx); -bool input_callback(InputEvent* input_event, void* ctx); +bool input_callback(InputEvent* input_event, void* ctx); \ No newline at end of file diff --git a/applications/external/flizzer_tracker/flizzer_tracker_hal.c b/applications/external/flizzer_tracker/flizzer_tracker_hal.c index a2f483f08..d229598bf 100644 --- a/applications/external/flizzer_tracker/flizzer_tracker_hal.c +++ b/applications/external/flizzer_tracker/flizzer_tracker_hal.c @@ -310,4 +310,4 @@ void play() { void stop() { sound_engine_stop(); tracker_engine_stop(); -} +} \ No newline at end of file diff --git a/applications/external/flizzer_tracker/init_deinit.c b/applications/external/flizzer_tracker/init_deinit.c index 33e42fe36..0887f5506 100644 --- a/applications/external/flizzer_tracker/init_deinit.c +++ b/applications/external/flizzer_tracker/init_deinit.c @@ -262,6 +262,7 @@ void deinit_tracker(FlizzerTrackerApp* tracker) { view_dispatcher_remove_view(tracker->view_dispatcher, VIEW_SETTINGS); view_dispatcher_remove_view(tracker->view_dispatcher, VIEW_FILE_OVERWRITE); view_dispatcher_remove_view(tracker->view_dispatcher, VIEW_SUBMENU_INSTRUMENT); + view_dispatcher_remove_view(tracker->view_dispatcher, VIEW_INSTRUMENT_FILE_OVERWRITE); view_dispatcher_remove_view(tracker->view_dispatcher, VIEW_SUBMENU_PATTERN_COPYPASTE); view_dispatcher_remove_view(tracker->view_dispatcher, VIEW_SUBMENU_PATTERN); view_dispatcher_remove_view(tracker->view_dispatcher, VIEW_KEYBOARD); diff --git a/applications/external/flizzer_tracker/sound_engine/sound_engine.c b/applications/external/flizzer_tracker/sound_engine/sound_engine.c index 4908c9226..05ccaf2bb 100644 --- a/applications/external/flizzer_tracker/sound_engine/sound_engine.c +++ b/applications/external/flizzer_tracker/sound_engine/sound_engine.c @@ -34,6 +34,8 @@ void sound_engine_init( sound_engine->sine_lut[i] = (uint8_t)((sinf(i / 64.0 * PI) + 1.0) * 127.0); } + furi_hal_interrupt_set_isr(FuriHalInterruptIdDma1Ch1, NULL, NULL); + furi_hal_interrupt_set_isr_ex( FuriHalInterruptIdDma1Ch1, 15, sound_engine_dma_isr, sound_engine); @@ -54,7 +56,7 @@ void sound_engine_deinit(SoundEngine* sound_engine) { furi_hal_gpio_init(&gpio_ext_pa6, GpioModeAnalog, GpioPullNo, GpioSpeedLow); } - furi_hal_interrupt_set_isr_ex(FuriHalInterruptIdDma1Ch1, 13, NULL, NULL); + furi_hal_interrupt_set_isr(FuriHalInterruptIdDma1Ch1, NULL, NULL); sound_engine_stop(); sound_engine_deinit_timer(); } diff --git a/applications/external/flizzer_tracker/sound_engine/sound_engine_adsr.c b/applications/external/flizzer_tracker/sound_engine/sound_engine_adsr.c index 21eb8f76b..07d405631 100644 --- a/applications/external/flizzer_tracker/sound_engine/sound_engine_adsr.c +++ b/applications/external/flizzer_tracker/sound_engine/sound_engine_adsr.c @@ -54,5 +54,6 @@ int32_t sound_engine_cycle_and_output_adsr( } } - return (int32_t)((int32_t)input * (int32_t)(adsr->envelope >> 10) / (int32_t)(MAX_ADSR >> 10) * (int32_t)adsr->volume / (int32_t)MAX_ADSR_VOLUME); + return (int32_t)((int32_t)input * (int32_t)(adsr->envelope >> 10) / (int32_t)(MAX_ADSR >> 10) * + (int32_t)adsr->volume / (int32_t)MAX_ADSR_VOLUME); } \ No newline at end of file diff --git a/applications/external/geiger/application.fam b/applications/external/geiger/application.fam index eca3167e4..4721ff778 100644 --- a/applications/external/geiger/application.fam +++ b/applications/external/geiger/application.fam @@ -7,7 +7,7 @@ App( requires=[ "gui", ], - stack_size=1 * 1024, + stack_size=2 * 1024, fap_icon="geiger.png", fap_category="GPIO", fap_author="@nmrr", diff --git a/applications/external/geiger/flipper_geiger.c b/applications/external/geiger/flipper_geiger.c index 17f2b7058..a9101a277 100644 --- a/applications/external/geiger/flipper_geiger.c +++ b/applications/external/geiger/flipper_geiger.c @@ -10,6 +10,10 @@ #include #include #include +#include +#include + +#include #define SCREEN_SIZE_X 128 #define SCREEN_SIZE_Y 64 @@ -147,6 +151,14 @@ int32_t flipper_geiger_app(void* p) { furi_delay_ms(10); } + Storage* storage = furi_record_open(RECORD_STORAGE); + Stream* file_stream = buffered_file_stream_alloc(storage); + FuriString* dataString = furi_string_alloc(); + uint32_t epoch = 0; + uint8_t recordData = 0; + + NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); + while(1) { FuriStatus event_status = furi_message_queue_get(event_queue, &event, FuriWaitForever); @@ -166,6 +178,36 @@ int32_t flipper_geiger_app(void* p) { screenRefresh = 1; furi_mutex_release(mutexVal.mutex); + } else if(event.input.key == InputKeyUp && event.input.type == InputTypeLong) { + if(recordData == 0) { + notification_message(notification, &sequence_set_only_red_255); + + FuriHalRtcDateTime datetime; + furi_hal_rtc_get_datetime(&datetime); + + char path[64]; + snprintf( + path, + sizeof(path), + EXT_PATH("/geiger-%.4d-%.2d-%.2d--%.2d-%.2d-%.2d.csv"), + datetime.year, + datetime.month, + datetime.day, + datetime.hour, + datetime.minute, + datetime.second); + + buffered_file_stream_open( + file_stream, path, FSAM_WRITE, FSOM_CREATE_ALWAYS); + furi_string_printf(dataString, "epoch,cps\n"); + stream_write_string(file_stream, dataString); + epoch = 0; + recordData = 1; + } else { + buffered_file_stream_close(file_stream); + notification_message(notification, &sequence_reset_red); + recordData = 0; + } } else if((event.input.key == InputKeyLeft && event.input.type == InputTypeShort)) { furi_mutex_acquire(mutexVal.mutex, FuriWaitForever); @@ -192,6 +234,11 @@ int32_t flipper_geiger_app(void* p) { } else if(event.type == ClockEventTypeTick) { furi_mutex_acquire(mutexVal.mutex, FuriWaitForever); + if(recordData == 1) { + furi_string_printf(dataString, "%lu,%lu\n", epoch++, counter); + stream_write_string(file_stream, dataString); + } + for(int i = 0; i < SCREEN_SIZE_X / 2 - 1; i++) mutexVal.line[SCREEN_SIZE_X / 2 - 1 - i] = mutexVal.line[SCREEN_SIZE_X / 2 - 2 - i]; @@ -222,6 +269,16 @@ int32_t flipper_geiger_app(void* p) { if(screenRefresh == 1) view_port_update(view_port); } + if(recordData == 1) { + buffered_file_stream_close(file_stream); + notification_message(notification, &sequence_reset_red); + } + + furi_string_free(dataString); + furi_record_close(RECORD_NOTIFICATION); + stream_free(file_stream); + furi_record_close(RECORD_STORAGE); + // Disable 5v power if(furi_hal_power_is_otg_enabled()) { furi_hal_power_disable_otg(); @@ -230,6 +287,7 @@ int32_t flipper_geiger_app(void* p) { furi_hal_gpio_disable_int_callback(&gpio_ext_pa7); furi_hal_gpio_remove_int_callback(&gpio_ext_pa7); furi_hal_pwm_stop(FuriHalPwmOutputIdLptim2PA4); + furi_hal_gpio_init(&gpio_ext_pa7, GpioModeAnalog, GpioPullNo, GpioSpeedLow); furi_message_queue_free(event_queue); furi_mutex_free(mutexVal.mutex); diff --git a/applications/external/ifttt/application.fam b/applications/external/ifttt/application.fam index 195c8a9eb..1a86000a4 100644 --- a/applications/external/ifttt/application.fam +++ b/applications/external/ifttt/application.fam @@ -1,5 +1,5 @@ App( - appid="ifttt", + appid="esp8266_ifttt_virtual_button", name="[ESP8266] IFTTT Button", apptype=FlipperAppType.EXTERNAL, entry_point="ifttt_virtual_button_app", diff --git a/applications/external/ifttt/ifttt_virtual_button.c b/applications/external/ifttt/ifttt_virtual_button.c index 01a02b0f3..ba1684daf 100644 --- a/applications/external/ifttt/ifttt_virtual_button.c +++ b/applications/external/ifttt/ifttt_virtual_button.c @@ -1,7 +1,8 @@ #include "ifttt_virtual_button.h" -#define IFTTT_CONFIG_FOLDER APP_DATA_PATH("config") -const char* CONFIG_FILE_PATH = APP_DATA_PATH("config/config.settings"); +#define IFTTT_FOLDER "/ext/apps_data/ifttt" +#define IFTTT_CONFIG_FOLDER "/ext/apps_data/ifttt/config" +const char* CONFIG_FILE_PATH = "/ext/apps_data/ifttt/config/config.settings"; #define FLIPPERZERO_SERIAL_BAUD 115200 typedef enum ESerialCommand { ESerialCommand_Config } ESerialCommand; @@ -218,7 +219,10 @@ int32_t ifttt_virtual_button_app(void* p) { UNUSED(p); Storage* storage = furi_record_open(RECORD_STORAGE); - storage_simply_mkdir(storage, IFTTT_CONFIG_FOLDER); + if(!storage_simply_mkdir(storage, IFTTT_FOLDER)) { + } + if(!storage_simply_mkdir(storage, IFTTT_CONFIG_FOLDER)) { + } furi_record_close(RECORD_STORAGE); uint32_t first_scene = VirtualButtonAppSceneStart; diff --git a/applications/external/ifttt/views/send_view.c b/applications/external/ifttt/views/send_view.c index e1638e7a7..7debf650b 100644 --- a/applications/external/ifttt/views/send_view.c +++ b/applications/external/ifttt/views/send_view.c @@ -134,4 +134,4 @@ void send_view_set_data(SendView* send_view, bool connected) { furi_assert(send_view); with_view_model( send_view->view, SendViewModel * model, { model->connected = connected; }, true); -} \ No newline at end of file +} diff --git a/applications/external/ir_remote/application.fam b/applications/external/ir_remote/application.fam index 5a98bd904..d1ec0da80 100644 --- a/applications/external/ir_remote/application.fam +++ b/applications/external/ir_remote/application.fam @@ -8,7 +8,7 @@ App( "gui", "dialogs", ], - fap_category="Tools", + fap_category="Infrared", fap_icon="ir_10px.png", fap_author="@Hong5489 & @friebel & @d4ve10", fap_weburl="https://github.com/Hong5489/ir_remote", diff --git a/applications/external/mifare_fuzzer/mifare_fuzzer.c b/applications/external/mifare_fuzzer/mifare_fuzzer.c index 8f839cf35..76fec98ed 100644 --- a/applications/external/mifare_fuzzer/mifare_fuzzer.c +++ b/applications/external/mifare_fuzzer/mifare_fuzzer.c @@ -95,6 +95,7 @@ void mifare_fuzzer_free(MifareFuzzerApp* app) { //FURI_LOG_D(TAG, "mifare_fuzzer_free() :: Views"); view_dispatcher_remove_view(app->view_dispatcher, MifareFuzzerViewSelectCard); view_dispatcher_remove_view(app->view_dispatcher, MifareFuzzerViewSelectAttack); + view_dispatcher_remove_view(app->view_dispatcher, MifareFuzzerViewEmulator); // Submenus //FURI_LOG_D(TAG, "mifare_fuzzer_free() :: Submenus"); diff --git a/applications/external/nightstand/clock_app.c b/applications/external/nightstand/clock_app.c index eddc2b080..787352e93 100644 --- a/applications/external/nightstand/clock_app.c +++ b/applications/external/nightstand/clock_app.c @@ -365,4 +365,4 @@ int32_t clock_app(void* p) { notification_message(notif, &led_reset); return 0; -} +} \ No newline at end of file diff --git a/applications/external/nrf24batch/nrf24batch.c b/applications/external/nrf24batch/nrf24batch.c index a41de8210..1eb90c3c2 100644 --- a/applications/external/nrf24batch/nrf24batch.c +++ b/applications/external/nrf24batch/nrf24batch.c @@ -218,6 +218,21 @@ static void add_to_str_hex_bytes(char* out, uint8_t* arr, int bytes) { } while(--bytes); } +// skip leading zeros +static void add_to_str_hex_variable(char* out, uint8_t* arr, int size) { + if(size <= 0) return; + out += strlen(out); + arr += size - 1; + while(*arr == 0 && size > 1) { + arr--; + size--; + } + do { + snprintf(out, 3, "%02X", *arr--); + out += 2; + } while(--size); +} + void Edit_insert_digit(char new) { if(*Edit_pos == '-') return; if(what_doing <= 1) { @@ -506,7 +521,7 @@ bool nrf24_read_newpacket() { else { char hex[9]; hex[0] = '\0'; - add_to_str_hex_bytes(hex, (uint8_t*)&var, size); + add_to_str_hex_variable(hex, (uint8_t*)&var, size); if((cmd_array && cmd_array_hex) || furi_string_end_with_str(str, "0x")) furi_string_cat_str(str, hex); else { @@ -883,7 +898,7 @@ static uint8_t load_settings_file() { NRF_INITED = false; while(stream_read_line(file_stream, str)) { char* p = (char*)furi_string_get_cstr(str); - if(*p <= ' ') continue; + if(*p <= '!' || *p == ';') continue; //char* delim_eq = strchr(p, '='); char* delim_col = strchr(p, ':'); if(delim_col == NULL) { // Constant found - no ':' @@ -1288,12 +1303,12 @@ static void render_callback(Canvas* const canvas, void* ctx) { int32_t n = get_payload_receive_field(pld, len); if(hex) { strcat(screen_buf, "0x"); - add_to_str_hex_bytes(screen_buf, pld, len); + add_to_str_hex_variable(screen_buf, pld, len); } else { snprintf(screen_buf + strlen(screen_buf), 20, "%ld", n); if(n > 9) { strcat(screen_buf, " ("); - add_to_str_hex_bytes(screen_buf, pld, len); + add_to_str_hex_variable(screen_buf, pld, len); strcat(screen_buf, ")"); } } diff --git a/applications/external/passgen/application.fam b/applications/external/passgen/application.fam index ededadaec..6a9652dc1 100644 --- a/applications/external/passgen/application.fam +++ b/applications/external/passgen/application.fam @@ -6,7 +6,7 @@ App( requires=[ "gui", ], - fap_category="Misc", + fap_category="Tools", fap_icon="icons/passgen_icon.png", fap_icon_assets="icons", ) diff --git a/applications/external/passgen/passgen.c b/applications/external/passgen/passgen.c index be6656f7a..07a97a3c0 100644 --- a/applications/external/passgen/passgen.c +++ b/applications/external/passgen/passgen.c @@ -6,14 +6,14 @@ #include #include #include +#include #define PASSGEN_MAX_LENGTH 16 -#define PASSGEN_CHARACTERS_LENGTH (26 * 4) #define PASSGEN_DIGITS "0123456789" #define PASSGEN_LETTERS_LOW "abcdefghijklmnopqrstuvwxyz" #define PASSGEN_LETTERS_UP "ABCDEFGHIJKLMNOPQRSTUVWXYZ" -#define PASSGEN_SPECIAL "!#$%^&*.-_" +#define PASSGEN_SPECIAL "!#$%%^&*.-_" typedef enum PassGen_Alphabet { Digits = 1, @@ -46,7 +46,8 @@ typedef struct { FuriMutex** mutex; NotificationApp* notify; char password[PASSGEN_MAX_LENGTH + 1]; - char alphabet[PASSGEN_CHARACTERS_LENGTH + 1]; + // char alphabet[PASSGEN_CHARACTERS_LENGTH + 1]; + FuriString* alphabet; int length; int level; } PassGen; @@ -58,6 +59,7 @@ void state_free(PassGen* app) { furi_message_queue_free(app->input_queue); furi_mutex_free(app->mutex); furi_record_close(RECORD_NOTIFICATION); + furi_string_free(app->alphabet); free(app); } @@ -99,17 +101,17 @@ static void render_callback(Canvas* canvas, void* ctx) { void build_alphabet(PassGen* app) { PassGen_Alphabet mode = AlphabetLevels[app->level]; - app->alphabet[0] = '\0'; - if((mode & Digits) != 0) strcat(app->alphabet, PASSGEN_DIGITS); - if((mode & Lowercase) != 0) strcat(app->alphabet, PASSGEN_LETTERS_LOW); - if((mode & Uppercase) != 0) strcat(app->alphabet, PASSGEN_LETTERS_UP); - if((mode & Special) != 0) strcat(app->alphabet, PASSGEN_SPECIAL); + if((mode & Digits) != 0) furi_string_cat(app->alphabet, PASSGEN_DIGITS); + if((mode & Lowercase) != 0) furi_string_cat(app->alphabet, PASSGEN_LETTERS_LOW); + if((mode & Uppercase) != 0) furi_string_cat(app->alphabet, PASSGEN_LETTERS_UP); + if((mode & Special) != 0) furi_string_cat(app->alphabet, PASSGEN_SPECIAL); } PassGen* state_init() { PassGen* app = malloc(sizeof(PassGen)); app->length = 8; app->level = 2; + app->alphabet = furi_string_alloc(); build_alphabet(app); app->input_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); app->view_port = view_port_alloc(); @@ -125,10 +127,10 @@ PassGen* state_init() { } void generate(PassGen* app) { - int hi = strlen(app->alphabet); + int hi = furi_string_size(app->alphabet); for(int i = 0; i < app->length; i++) { int x = rand() % hi; - app->password[i] = app->alphabet[x]; + app->password[i] = furi_string_get_char(app->alphabet, x); } app->password[app->length] = '\0'; } diff --git a/applications/external/pomodoro/application.fam b/applications/external/pomodoro/application.fam index e0d4e9ec0..a96870e09 100644 --- a/applications/external/pomodoro/application.fam +++ b/applications/external/pomodoro/application.fam @@ -5,11 +5,11 @@ App( entry_point="flipp_pomodoro_app", requires=["gui", "notification", "dolphin"], stack_size=1 * 1024, - fap_category="Misc", + fap_category="Tools", fap_icon_assets="images", fap_icon="flipp_pomodoro_10.png", fap_author="@Th3Un1q3", fap_weburl="https://github.com/Th3Un1q3/flipp_pomodoro", - fap_version="1.0", + fap_version="1.1", fap_description="Boost Your Productivity with the Pomodoro Timer", ) diff --git a/applications/external/pomodoro/flipp_pomodoro_app.c b/applications/external/pomodoro/flipp_pomodoro_app.c index 3ee3b0277..70f065e8b 100644 --- a/applications/external/pomodoro/flipp_pomodoro_app.c +++ b/applications/external/pomodoro/flipp_pomodoro_app.c @@ -1,5 +1,7 @@ #include "flipp_pomodoro_app_i.h" +#define TAG "FlippPomodoro" + enum { CustomEventConsumed = true, CustomEventNotConsumed = false, @@ -29,6 +31,14 @@ static bool flipp_pomodoro_app_custom_event_callback(void* ctx, uint32_t event) app->view_dispatcher, FlippPomodoroAppCustomEventStateUpdated); return CustomEventConsumed; case FlippPomodoroAppCustomEventStageComplete: + if(flipp_pomodoro__get_stage(app->state) == FlippPomodoroStageFocus) { + // REGISTER a deed on work stage complete to get an acheivement + // dolphin_deed(DolphinDeedPluginGameWin); + FURI_LOG_I(TAG, "Focus stage reward added"); + + flipp_pomodoro_statistics__increase_focus_stages_completed(app->statistics); + }; + flipp_pomodoro__toggle_stage(app->state); notification_message( app->notification_app, @@ -51,6 +61,8 @@ FlippPomodoroApp* flipp_pomodoro_app_alloc() { app->notification_app = furi_record_open(RECORD_NOTIFICATION); app->view_dispatcher = view_dispatcher_alloc(); + app->statistics = flipp_pomodoro_statistics__new(); + view_dispatcher_enable_queue(app->view_dispatcher); view_dispatcher_set_event_callback_context(app->view_dispatcher, app); view_dispatcher_set_custom_event_callback( @@ -62,22 +74,31 @@ FlippPomodoroApp* flipp_pomodoro_app_alloc() { app->view_dispatcher, flipp_pomodoro_app_back_event_callback); app->timer_view = flipp_pomodoro_view_timer_alloc(); + app->info_view = flipp_pomodoro_info_view_alloc(); view_dispatcher_add_view( app->view_dispatcher, FlippPomodoroAppViewTimer, flipp_pomodoro_view_timer_get_view(app->timer_view)); - scene_manager_next_scene(app->scene_manager, FlippPomodoroSceneTimer); + view_dispatcher_add_view( + app->view_dispatcher, + FlippPomodoroAppViewInfo, + flipp_pomodoro_info_view_get_view(app->info_view)); + scene_manager_next_scene(app->scene_manager, FlippPomodoroSceneTimer); + FURI_LOG_I(TAG, "Alloc complete"); return app; } void flipp_pomodoro_app_free(FlippPomodoroApp* app) { view_dispatcher_remove_view(app->view_dispatcher, FlippPomodoroAppViewTimer); + view_dispatcher_remove_view(app->view_dispatcher, FlippPomodoroAppViewInfo); view_dispatcher_free(app->view_dispatcher); scene_manager_free(app->scene_manager); flipp_pomodoro_view_timer_free(app->timer_view); + flipp_pomodoro_info_view_free(app->info_view); + flipp_pomodoro_statistics__destroy(app->statistics); flipp_pomodoro__destroy(app->state); free(app); furi_record_close(RECORD_GUI); @@ -86,8 +107,12 @@ void flipp_pomodoro_app_free(FlippPomodoroApp* app) { int32_t flipp_pomodoro_app(void* p) { UNUSED(p); + FURI_LOG_I(TAG, "Initial"); FlippPomodoroApp* app = flipp_pomodoro_app_alloc(); + FURI_LOG_I(TAG, "Run deed added"); + // dolphin_deed(DolphinDeedPluginGameStart); + view_dispatcher_run(app->view_dispatcher); flipp_pomodoro_app_free(app); diff --git a/applications/external/pomodoro/flipp_pomodoro_app.h b/applications/external/pomodoro/flipp_pomodoro_app.h index 54102e1f3..fab9b0f9c 100644 --- a/applications/external/pomodoro/flipp_pomodoro_app.h +++ b/applications/external/pomodoro/flipp_pomodoro_app.h @@ -7,8 +7,10 @@ #include #include #include "views/flipp_pomodoro_timer_view.h" +#include "views/flipp_pomodoro_info_view.h" #include "modules/flipp_pomodoro.h" +#include "modules/flipp_pomodoro_statistics.h" typedef enum { // Reserve first 100 events for button types and indexes, starting from 0 @@ -16,6 +18,7 @@ typedef enum { FlippPomodoroAppCustomEventStageComplete, // By Expiration FlippPomodoroAppCustomEventTimerTick, FlippPomodoroAppCustomEventStateUpdated, + FlippPomodoroAppCustomEventResumeTimer, } FlippPomodoroAppCustomEvent; typedef struct { @@ -24,9 +27,12 @@ typedef struct { Gui* gui; NotificationApp* notification_app; FlippPomodoroTimerView* timer_view; + FlippPomodoroInfoView* info_view; FlippPomodoroState* state; + FlippPomodoroStatistics* statistics; } FlippPomodoroApp; typedef enum { FlippPomodoroAppViewTimer, + FlippPomodoroAppViewInfo, } FlippPomodoroAppView; \ No newline at end of file diff --git a/applications/external/pomodoro/flipp_pomodoro_app_i.h b/applications/external/pomodoro/flipp_pomodoro_app_i.h index 03c3dc894..c07968952 100644 --- a/applications/external/pomodoro/flipp_pomodoro_app_i.h +++ b/applications/external/pomodoro/flipp_pomodoro_app_i.h @@ -1,6 +1,6 @@ #pragma once -#define FURI_DEBUG 1 +// #define FURI_DEBUG 1 /** * Index of dependencies for the main app @@ -15,6 +15,7 @@ #include #include #include +#include #include // App resource imports diff --git a/applications/external/pomodoro/images/flipp_pomodoro_learn_50x128.png b/applications/external/pomodoro/images/flipp_pomodoro_learn_50x128.png new file mode 100644 index 0000000000000000000000000000000000000000..d32f62c1d051b97b6fadffe9bb5bf9c4a0d11268 GIT binary patch literal 1234 zcmV;@1TFiCP)Px(j7da6RCr$PT+x!_APj5&|D(H;yP-oJ%Mu3K3}Ih(HU$H=EDJ*S`FK2DkHpXG z^}pNm`TY00{EbYNcI9)_-?mey=8a$5isozAaQt1juWe`6!F;2xaYnl%dP@SJ4q|bf z7g+SHfZE#Wu{$Gxy*A$fP*Lh-OJ)*iJe5)Epdto}GTZjAnW+TXn+swU%NSm}+E0;f zH5M7a+dfMJz-E3614Jdj1}L)YF-9q!sBHZd0H_ADnxE}=LZs0YS-U6$m3q%KK%_T3 z;2;oqrD;S~x7au_zzB!e6JWrZ1%MVO{IYFbZoZlMZ}1fnXai-P!c;q}>B!zp0I1n+ z>p$A5c+W=!jWNVbfOazg6K#x9W&uD|>$V;Mnzo z7Ej5e+RmOwdZGsacK5=PX$AMH|x$DwAvm$pLu- zAm_JbdQkw_Z5!K%tLFqLv)Oa?^JOA5c_AABlO23@^QUc~ciG`sgt~8M?U4P^y8?Ei zcg(|kFQH!2u*bvE%O`KAgxc4Buyd^E7QHix+MO8O-U(a@0P5uocGl0{EFMmc0HO4+ z1H;}~c%Q4!AOKo3w^ik%_UgFoya8#Gn!UV0p=T%{_PM8#JTTaH2y_aNP_Uyh;CSdl z$f&bK{c zialU3&7f!iHHgugTeJXTkFq|b6%fl%FEvXkX^NALwF;I9pIqMM0;jcQ)Jz|?*rsK zw*ipz+cLd|-wCwLz|?(fjYmwf86bzr4S<~AmgzO@S~$vl!Iwg$*SYy%+YH`oikhGzEz22Vpa*L*3o^#st)M7`6C5XbHzYza^Ui`tdIme0NNX#r+! zZ2)wKRX5-Ppze*%3L>B=LH7jc8#J^3T|C<*AoqE+8M*pu>e`#@Xa-WR=ASiAA07WKR+Ei@cY5)KL07*qoM6N<$f-LDlVE_OC literal 0 HcmV?d00001 diff --git a/applications/external/pomodoro/modules/flipp_pomodoro.h b/applications/external/pomodoro/modules/flipp_pomodoro.h index 251a77469..a1e50cb6e 100644 --- a/applications/external/pomodoro/modules/flipp_pomodoro.h +++ b/applications/external/pomodoro/modules/flipp_pomodoro.h @@ -12,7 +12,6 @@ typedef enum { /// @brief State of the pomodoro timer typedef struct { - PomodoroStage stage; uint8_t current_stage_index; uint32_t started_at_timestamp; } FlippPomodoroState; diff --git a/applications/external/pomodoro/modules/flipp_pomodoro_statistics.c b/applications/external/pomodoro/modules/flipp_pomodoro_statistics.c new file mode 100644 index 000000000..4bc5bf56f --- /dev/null +++ b/applications/external/pomodoro/modules/flipp_pomodoro_statistics.c @@ -0,0 +1,26 @@ +#include "flipp_pomodoro_statistics.h" + +FlippPomodoroStatistics* flipp_pomodoro_statistics__new() { + FlippPomodoroStatistics* statistics = malloc(sizeof(FlippPomodoroStatistics)); + + statistics->focus_stages_completed = 0; + + return statistics; +} + +// Return the number of completed focus stages +uint8_t + flipp_pomodoro_statistics__get_focus_stages_completed(FlippPomodoroStatistics* statistics) { + return statistics->focus_stages_completed; +} + +// Increase the number of completed focus stages by one +void flipp_pomodoro_statistics__increase_focus_stages_completed( + FlippPomodoroStatistics* statistics) { + statistics->focus_stages_completed++; +} + +void flipp_pomodoro_statistics__destroy(FlippPomodoroStatistics* statistics) { + furi_assert(statistics); + free(statistics); +}; diff --git a/applications/external/pomodoro/modules/flipp_pomodoro_statistics.h b/applications/external/pomodoro/modules/flipp_pomodoro_statistics.h new file mode 100644 index 000000000..cfc843147 --- /dev/null +++ b/applications/external/pomodoro/modules/flipp_pomodoro_statistics.h @@ -0,0 +1,45 @@ +#pragma once +#include + +/** @brief FlippPomodoroStatistics structure + * + * This structure is used to keep track of completed focus stages. + */ +typedef struct { + uint8_t focus_stages_completed; +} FlippPomodoroStatistics; + +/** @brief Allocate and initialize a new FlippPomodoroStatistics + * + * This function allocates a new FlippPomodoroStatistics structure, initializes its members + * and returns a pointer to it. + * + * @return A pointer to a new FlippPomodoroStatistics structure + */ +FlippPomodoroStatistics* flipp_pomodoro_statistics__new(); + +/** @brief Get the number of completed focus stages + * + * This function retrieves the number of completed focus stages in a FlippPomodoroStatistics structure. + * + * @param statistics A pointer to a FlippPomodoroStatistics structure + * @return The number of completed focus stages + */ +uint8_t flipp_pomodoro_statistics__get_focus_stages_completed(FlippPomodoroStatistics* statistics); + +/** @brief Increase the number of completed focus stages + * + * This function increases the count of the completed focus stages by one in a FlippPomodoroStatistics structure. + * + * @param statistics A pointer to a FlippPomodoroStatistics structure + */ +void flipp_pomodoro_statistics__increase_focus_stages_completed( + FlippPomodoroStatistics* statistics); + +/** @brief Free a FlippPomodoroStatistics structure + * + * This function frees the memory used by a FlippPomodoroStatistics structure. + * + * @param statistics A pointer to a FlippPomodoroStatistics structure + */ +void flipp_pomodoro_statistics__destroy(FlippPomodoroStatistics* state); \ No newline at end of file diff --git a/applications/external/pomodoro/scenes/config/flipp_pomodoro_scene_config.h b/applications/external/pomodoro/scenes/config/flipp_pomodoro_scene_config.h index f95daeb30..5a83fb021 100644 --- a/applications/external/pomodoro/scenes/config/flipp_pomodoro_scene_config.h +++ b/applications/external/pomodoro/scenes/config/flipp_pomodoro_scene_config.h @@ -1 +1,2 @@ +ADD_SCENE(flipp_pomodoro, info, Info) ADD_SCENE(flipp_pomodoro, timer, Timer) \ No newline at end of file diff --git a/applications/external/pomodoro/scenes/flipp_pomodoro_scene.h b/applications/external/pomodoro/scenes/flipp_pomodoro_scene.h index 3b8a9052a..708d8f8e4 100644 --- a/applications/external/pomodoro/scenes/flipp_pomodoro_scene.h +++ b/applications/external/pomodoro/scenes/flipp_pomodoro_scene.h @@ -1,3 +1,4 @@ +#pragma once #include // Generate scene id and total number diff --git a/applications/external/pomodoro/scenes/flipp_pomodoro_scene_info.c b/applications/external/pomodoro/scenes/flipp_pomodoro_scene_info.c new file mode 100644 index 000000000..9d73108f8 --- /dev/null +++ b/applications/external/pomodoro/scenes/flipp_pomodoro_scene_info.c @@ -0,0 +1,59 @@ +#include +#include +#include +#include "flipp_pomodoro_scene.h" +#include "../flipp_pomodoro_app.h" +#include "../views/flipp_pomodoro_info_view.h" + +enum { SceneEventConusmed = true, SceneEventNotConusmed = false }; + +void flipp_pomodoro_scene_info_on_back_to_timer(void* ctx) { + furi_assert(ctx); + FlippPomodoroApp* app = ctx; + + view_dispatcher_send_custom_event( + app->view_dispatcher, FlippPomodoroAppCustomEventResumeTimer); +}; + +void flipp_pomodoro_scene_info_on_enter(void* ctx) { + furi_assert(ctx); + FlippPomodoroApp* app = ctx; + + view_dispatcher_switch_to_view(app->view_dispatcher, FlippPomodoroAppViewInfo); + flipp_pomodoro_info_view_set_pomodoros_completed( + flipp_pomodoro_info_view_get_view(app->info_view), + flipp_pomodoro_statistics__get_focus_stages_completed(app->statistics)); + flipp_pomodoro_info_view_set_mode( + flipp_pomodoro_info_view_get_view(app->info_view), FlippPomodoroInfoViewModeStats); + flipp_pomodoro_info_view_set_resume_timer_cb( + app->info_view, flipp_pomodoro_scene_info_on_back_to_timer, app); +}; + +void flipp_pomodoro_scene_info_handle_custom_event( + FlippPomodoroApp* app, + FlippPomodoroAppCustomEvent custom_event) { + if(custom_event == FlippPomodoroAppCustomEventResumeTimer) { + scene_manager_next_scene(app->scene_manager, FlippPomodoroSceneTimer); + } +}; + +bool flipp_pomodoro_scene_info_on_event(void* ctx, SceneManagerEvent event) { + furi_assert(ctx); + FlippPomodoroApp* app = ctx; + + switch(event.type) { + case SceneManagerEventTypeBack: + view_dispatcher_stop(app->view_dispatcher); + return SceneEventConusmed; + case SceneManagerEventTypeCustom: + flipp_pomodoro_scene_info_handle_custom_event(app, event.event); + return SceneEventConusmed; + default: + break; + }; + return SceneEventNotConusmed; +}; + +void flipp_pomodoro_scene_info_on_exit(void* ctx) { + UNUSED(ctx); +}; \ No newline at end of file diff --git a/applications/external/pomodoro/scenes/flipp_pomodoro_scene_timer.c b/applications/external/pomodoro/scenes/flipp_pomodoro_scene_timer.c index 8ed5dd5e7..20fb3c449 100644 --- a/applications/external/pomodoro/scenes/flipp_pomodoro_scene_timer.c +++ b/applications/external/pomodoro/scenes/flipp_pomodoro_scene_timer.c @@ -1,13 +1,13 @@ #include #include #include +#include +#include "flipp_pomodoro_scene.h" #include "../flipp_pomodoro_app.h" #include "../views/flipp_pomodoro_timer_view.h" enum { SceneEventConusmed = true, SceneEventNotConusmed = false }; -uint8_t ExitSignal = 0; - void flipp_pomodoro_scene_timer_sync_view_state(void* ctx) { furi_assert(ctx); @@ -30,6 +30,11 @@ void flipp_pomodoro_scene_timer_on_enter(void* ctx) { FlippPomodoroApp* app = ctx; + if(flipp_pomodoro__is_stage_expired(app->state)) { + flipp_pomodoro__destroy(app->state); + app->state = flipp_pomodoro__new(); + } + view_dispatcher_switch_to_view(app->view_dispatcher, FlippPomodoroAppViewTimer); flipp_pomodoro_scene_timer_sync_view_state(app); flipp_pomodoro_view_timer_set_on_right_cb( @@ -59,7 +64,8 @@ bool flipp_pomodoro_scene_timer_on_event(void* ctx, SceneManagerEvent event) { flipp_pomodoro_scene_timer_handle_custom_event(app, event.event); return SceneEventConusmed; case SceneManagerEventTypeBack: - return ExitSignal; + scene_manager_next_scene(app->scene_manager, FlippPomodoroSceneInfo); + return SceneEventConusmed; default: break; } diff --git a/applications/external/pomodoro/views/flipp_pomodoro_info_view.c b/applications/external/pomodoro/views/flipp_pomodoro_info_view.c new file mode 100644 index 000000000..dab23ab9e --- /dev/null +++ b/applications/external/pomodoro/views/flipp_pomodoro_info_view.c @@ -0,0 +1,152 @@ + +#include +#include +#include +#include +#include "flipp_pomodoro_info_view.h" +// Auto-compiled icons +#include "flipp_pomodoro_icons.h" + +enum { + ViewInputConsumed = true, + ViewInputNotConusmed = false, +}; + +struct FlippPomodoroInfoView { + View* view; + FlippPomodoroInfoViewUserActionCb resume_timer_cb; + void* user_action_cb_ctx; +}; + +typedef struct { + uint8_t pomodoros_completed; + FlippPomodoroInfoViewMode mode; +} FlippPomodoroInfoViewModel; + +static void + flipp_pomodoro_info_view_draw_statistics(Canvas* canvas, FlippPomodoroInfoViewModel* model) { + FuriString* stats_string = furi_string_alloc(); + + furi_string_printf( + stats_string, + "So Long,\nand Thanks for All the Focus...\nand for completing\n%i pomodoro(s)", + model->pomodoros_completed); + const char* stats_string_formatted = furi_string_get_cstr(stats_string); + + elements_text_box( + canvas, + 0, + 0, + canvas_width(canvas), + canvas_height(canvas) - 10, + AlignCenter, + AlignCenter, + stats_string_formatted, + true); + + furi_string_free(stats_string); + + elements_button_left(canvas, "Guide"); +} + +static void + flipp_pomodoro_info_view_draw_about(Canvas* canvas, FlippPomodoroInfoViewModel* model) { + UNUSED(model); + canvas_draw_icon(canvas, 0, 0, &I_flipp_pomodoro_learn_50x128); + elements_button_left(canvas, "Stats"); +} + +static void flipp_pomodoro_info_view_draw_callback(Canvas* canvas, void* _model) { + if(!_model) { + return; + }; + + FlippPomodoroInfoViewModel* model = _model; + + canvas_clear(canvas); + + if(model->mode == FlippPomodoroInfoViewModeStats) { + flipp_pomodoro_info_view_draw_statistics(canvas, model); + } else { + flipp_pomodoro_info_view_draw_about(canvas, model); + } + + elements_button_right(canvas, "Resume"); +} + +void flipp_pomodoro_info_view_set_mode(View* view, FlippPomodoroInfoViewMode desired_mode) { + with_view_model( + view, FlippPomodoroInfoViewModel * model, { model->mode = desired_mode; }, false); +} + +void flipp_pomodoro_info_view_toggle_mode(FlippPomodoroInfoView* info_view) { + with_view_model( + flipp_pomodoro_info_view_get_view(info_view), + FlippPomodoroInfoViewModel * model, + { + flipp_pomodoro_info_view_set_mode( + flipp_pomodoro_info_view_get_view(info_view), + (model->mode == FlippPomodoroInfoViewModeStats) ? FlippPomodoroInfoViewModeAbout : + FlippPomodoroInfoViewModeStats); + }, + true); +} + +bool flipp_pomodoro_info_view_input_callback(InputEvent* event, void* ctx) { + FlippPomodoroInfoView* info_view = ctx; + + if(event->type == InputTypePress) { + if(event->key == InputKeyRight && info_view->resume_timer_cb != NULL) { + info_view->resume_timer_cb(info_view->user_action_cb_ctx); + return ViewInputConsumed; + } else if(event->key == InputKeyLeft) { + flipp_pomodoro_info_view_toggle_mode(info_view); + return ViewInputConsumed; + } + } + + return ViewInputNotConusmed; +} + +FlippPomodoroInfoView* flipp_pomodoro_info_view_alloc() { + FlippPomodoroInfoView* info_view = malloc(sizeof(FlippPomodoroInfoView)); + info_view->view = view_alloc(); + + view_allocate_model( + flipp_pomodoro_info_view_get_view(info_view), + ViewModelTypeLockFree, + sizeof(FlippPomodoroInfoViewModel)); + view_set_context(flipp_pomodoro_info_view_get_view(info_view), info_view); + view_set_draw_callback( + flipp_pomodoro_info_view_get_view(info_view), flipp_pomodoro_info_view_draw_callback); + view_set_input_callback( + flipp_pomodoro_info_view_get_view(info_view), flipp_pomodoro_info_view_input_callback); + + return info_view; +} + +View* flipp_pomodoro_info_view_get_view(FlippPomodoroInfoView* info_view) { + return info_view->view; +} + +void flipp_pomodoro_info_view_free(FlippPomodoroInfoView* info_view) { + furi_assert(info_view); + view_free(info_view->view); + free(info_view); +} + +void flipp_pomodoro_info_view_set_pomodoros_completed(View* view, uint8_t pomodoros_completed) { + with_view_model( + view, + FlippPomodoroInfoViewModel * model, + { model->pomodoros_completed = pomodoros_completed; }, + false); +} + +void flipp_pomodoro_info_view_set_resume_timer_cb( + FlippPomodoroInfoView* info_view, + FlippPomodoroInfoViewUserActionCb user_action_cb, + void* user_action_cb_ctx) { + info_view->resume_timer_cb = user_action_cb; + info_view->user_action_cb_ctx = user_action_cb_ctx; +} diff --git a/applications/external/pomodoro/views/flipp_pomodoro_info_view.h b/applications/external/pomodoro/views/flipp_pomodoro_info_view.h new file mode 100644 index 000000000..dd289a4d8 --- /dev/null +++ b/applications/external/pomodoro/views/flipp_pomodoro_info_view.h @@ -0,0 +1,71 @@ +#pragma once + +#include + +/** @brief Mode types for FlippPomodoroInfoView + * + * These are the modes that can be used in the FlippPomodoroInfoView + */ +typedef enum { + FlippPomodoroInfoViewModeStats, + FlippPomodoroInfoViewModeAbout, +} FlippPomodoroInfoViewMode; + +/** @brief Forward declaration of the FlippPomodoroInfoView struct */ +typedef struct FlippPomodoroInfoView FlippPomodoroInfoView; + +/** @brief User action callback function type + * + * Callback functions of this type are called when a user action is performed. + */ +typedef void (*FlippPomodoroInfoViewUserActionCb)(void* ctx); + +/** @brief Allocate a new FlippPomodoroInfoView + * + * Allocates a new FlippPomodoroInfoView and returns a pointer to it. + * @return A pointer to a new FlippPomodoroInfoView + */ +FlippPomodoroInfoView* flipp_pomodoro_info_view_alloc(); + +/** @brief Get the view from a FlippPomodoroInfoView + * + * Returns a pointer to the view associated with a FlippPomodoroInfoView. + * @param info_view A pointer to a FlippPomodoroInfoView + * @return A pointer to the view of the FlippPomodoroInfoView + */ +View* flipp_pomodoro_info_view_get_view(FlippPomodoroInfoView* info_view); + +/** @brief Free a FlippPomodoroInfoView + * + * Frees the memory used by a FlippPomodoroInfoView. + * @param info_view A pointer to a FlippPomodoroInfoView + */ +void flipp_pomodoro_info_view_free(FlippPomodoroInfoView* info_view); + +/** @brief Set the number of completed pomodoros in the view + * + * Sets the number of completed pomodoros that should be displayed in the view. + * @param info_view A pointer to the view + * @param pomodoros_completed The number of completed pomodoros + */ +void flipp_pomodoro_info_view_set_pomodoros_completed(View* info_view, uint8_t pomodoros_completed); + +/** @brief Set the callback function to be called when the timer should be resumed + * + * Sets the callback function that will be called when the timer should be resumed. + * @param info_view A pointer to the FlippPomodoroInfoView + * @param user_action_cb The callback function + * @param user_action_cb_ctx The context to be passed to the callback function + */ +void flipp_pomodoro_info_view_set_resume_timer_cb( + FlippPomodoroInfoView* info_view, + FlippPomodoroInfoViewUserActionCb user_action_cb, + void* user_action_cb_ctx); + +/** @brief Set the mode of the view + * + * Sets the mode that should be used in the view. + * @param view A pointer to the view + * @param desired_mode The desired mode + */ +void flipp_pomodoro_info_view_set_mode(View* view, FlippPomodoroInfoViewMode desired_mode); diff --git a/applications/external/pomodoro/views/flipp_pomodoro_timer_view.c b/applications/external/pomodoro/views/flipp_pomodoro_timer_view.c index 302380ddd..eef96fcd1 100644 --- a/applications/external/pomodoro/views/flipp_pomodoro_timer_view.c +++ b/applications/external/pomodoro/views/flipp_pomodoro_timer_view.c @@ -156,7 +156,10 @@ FlippPomodoroTimerView* flipp_pomodoro_view_timer_alloc() { FlippPomodoroTimerView* timer = malloc(sizeof(FlippPomodoroTimerView)); timer->view = view_alloc(); - view_allocate_model(timer->view, ViewModelTypeLockFree, sizeof(FlippPomodoroTimerViewModel)); + view_allocate_model( + flipp_pomodoro_view_timer_get_view(timer), + ViewModelTypeLockFree, + sizeof(FlippPomodoroTimerViewModel)); view_set_context(flipp_pomodoro_view_timer_get_view(timer), timer); view_set_draw_callback(timer->view, flipp_pomodoro_view_timer_draw_callback); view_set_input_callback(timer->view, flipp_pomodoro_view_timer_input_callback); diff --git a/applications/external/pong/application.fam b/applications/external/pong/application.fam index 02dcfd675..97292e6c5 100644 --- a/applications/external/pong/application.fam +++ b/applications/external/pong/application.fam @@ -12,6 +12,6 @@ App( fap_category="Games", fap_author="@nmrr & @SimplyMinimal", fap_weburl="https://github.com/nmrr/flipperzero-pong", - fap_version="1.0", + fap_version="1.1", fap_description="Simple pong game", ) diff --git a/applications/external/pong/pong.png b/applications/external/pong/pong.png index 507ce711c0f97ee36d4c2d76800d0ba5463974da..b86745f289ba0a2c653ff6d13e68a8a515461252 100644 GIT binary patch literal 7315 zcmd5>Yitx%6rPImxJ@-mt7w{75R5fOXYZZ4Go#`Is8U0K0455ko!y-=jTjp!u>^dC z`T%QG@Rto54Zcu=(IO~HlvjxHN$@Qw5hFDQQ8c2W;CJrKcIUR+c3V_B&CcG}`Of#9 zbLLF4T{?C0m1XPvPR5 zZL3dQy}0W4l5g6-eX@Pjn8O!TuUYc*?pM#;|Kr#nzbYBGa?^~qn~s}mzcX=bCN0;U z``a6%?ku^>Y<_mjq_NAcfA_6HM{HU zzh7~1>G&5$S8v(7WzXR29{PRR4==xS%Zip0OD`7L?6hCDj7-1u*~Ym;+Lk|Y%9P>v zR$RSu(a0ZO-n5{lZRV<%J{kK*Y4d`c$GkVc^{geCVZ%m`9Ct-oFr#kdxaY69Z)y3y zZ5RKw`GHGo&wpfgV9q`7aaOT>&yXX{U+-$YdsVGdJz~)M8|EC!l(&C=LhJl54_~r& z-*>}y-!XXgnze6!VT6~~zPRp{pHBYp&9d3E*VzkBdUa>XJ&!fM{ZQrGQ>LF&e&<2) z=hKyDQ|a-a?EGrxA@??efAsNzvWAAC_g7Re+_h)dhDSI2z3}Oezdosa-`w{1`H)#i z$Ka`xX4D)xas@#AsbH!Z41jX{@=XahiW?Rm0p{!umNu zRpWw&peksp4-H0lRz(+=F0*XMm7bI)F3Yn#?qlH63>S?}Gi6%pt|svst!nG6V}oNu z;4FP`xe6NRbL**S9jDZl@-eb8jOWbF#2aaS!y^{BxQlfH&jH1b1To@RkL{Tk4+MF} zMk+q&$MU4-I!S5i_&hmL7oEe0(M>oC+f2nQI?pW!(aYL6BsdQQeBmwxX6nO*FH#H> zpB8Z_B`jJJj=B?V0=7Kp%QFpWoh}3^DVcR>r*sv{j_77Rg6m+#ly?(%E-a$17@ERU zDxD@GTs~ z(y^FKIWEtVAli?3B$vA6*6s2$;-5SZqXMsKg+)J9w2p9h^~YI-@7RT}TIHZ_pCbR1ekBdj?0$l#LG0 z;<)5u;oF{#xXeR-l@TLggTc7;gd;ZVbimS8qA$%g$)Yvv&oegl?w{ClN(5bA9`Lq;sg&V)Mx4v%?A@l+3OcYV76NESvaF^-3tC#SY@$RRE6oGH~m3wHQCU@rAg zq4G-4BN`C88ADx1n5ja?(ONxJjV&{_3kDQw3V=uyMVywnj`E%orbNC6)2IS-mUuzr zv?6TEOqnCO6m)b;J8G{Go)1ES7Ye;;IC@P-15qu6AnBR+EovSXTC@qzhz4qi+AXRs z*KmjzwLcd1KNz4elbgw&1Vc{*!W9|xh+H31Yx(Ab80Fh(F$Ue8XWe;7dhg6v?RApR zdeX0f6>Pb=R|F3`7)5Uh+EC_Q-eY?FqSPnvaUCU>`ZxZ6fbz-ho#p?-sCORu6P+0K zMNA%qMxB84&A!b3J)r)J(fR&+jC9<-%ycaSfsxL>PmFXiM**ap>a#vE(oOYQFF-ws z=@XECxTje!jCwNF_qqTTFQykDeY^Ns7e>XK>L&rzUol;P^o9P;j!Ifm%TWO(*&i#Q zq?jb10r?iu33i=zFGfFmtb(nxNX`l{DppJqX252xe_{rvw7ixAS<@*OJ3Dqjc`-WI zW5-D6k)IX8s5>!v5E>N$q#JZM`)`4Y6QgtcZyD*nikroZ`XfenFmR;hvTz|8{{e$4OgR7m literal 6459 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 diff --git a/applications/external/qrcode/application.fam b/applications/external/qrcode/application.fam index abf06041b..3aac3f18d 100644 --- a/applications/external/qrcode/application.fam +++ b/applications/external/qrcode/application.fam @@ -13,7 +13,7 @@ App( "gui", "dialogs", ], - fap_category="Misc", + fap_category="Tools", fap_icon="icons/qrcode_10px.png", fap_icon_assets="icons", ) diff --git a/applications/external/rubiks_cube_scrambler/application.fam b/applications/external/rubiks_cube_scrambler/application.fam index 8dee8e952..7193ae302 100644 --- a/applications/external/rubiks_cube_scrambler/application.fam +++ b/applications/external/rubiks_cube_scrambler/application.fam @@ -15,7 +15,7 @@ App( apptype=FlipperAppType.EXTERNAL, entry_point="rubiks_cube_scrambler_main", stack_size=1 * 1024, - fap_category="Misc", + fap_category="Games", fap_icon="cube.png", fap_author="@RaZeSloth", fap_weburl="https://github.com/RaZeSloth/flipperzero-rubiks-cube-scrambler", diff --git a/applications/external/scorched_tanks/application.fam b/applications/external/scorched_tanks/application.fam index 5bccead84..4d74c5ced 100644 --- a/applications/external/scorched_tanks/application.fam +++ b/applications/external/scorched_tanks/application.fam @@ -11,6 +11,6 @@ App( fap_category="Games", fap_author="@jasniec", fap_weburl="https://github.com/jasniec/flipper-scorched-tanks-game", - fap_version="1.0", - fap_description="A flipper zero game inspired by scorched earth.", + fap_version="1.1", + fap_description="A Flipper Zero game inspired by scorched earth", ) diff --git a/applications/external/scorched_tanks/scorchedTanks_10px.png b/applications/external/scorched_tanks/scorchedTanks_10px.png index 6e1ae4c04b973c4015b6c4bb84ee6e806c4cde04..a37b16cd6ab8f2d1b99158d3a51e8b62fd96c0e6 100644 GIT binary patch delta 146 zcmaFHGJ|D;q%ac$0|Qe)#482{#+poL=YY)Qi$Q^dIr_{0{W-#ix#sM9`Oq>k&G~wc1ViUZjdg_vUDvgr vo800l**>{8bJmkjWn3|FTuBu|tKOT3ZppayVYeb22 zer|4RUI~M9QEFmIYKlU6W=V#EyQgnJcq5-UP?4Rdi(`ny<=zRNTnq+0Or~G{$IqL` z)mn1VD)@k74_m+jFNNFdSeRC@Oj=TCbbasl_0m_S)ck#W<5}fWz4I?02)is%VOE&1 Q2V@_Er>mdKI;Vst05Awh-2eap diff --git a/applications/external/snake_2/snake_20.c b/applications/external/snake_2/snake_20.c index ccd623870..e56f38b71 100644 --- a/applications/external/snake_2/snake_20.c +++ b/applications/external/snake_2/snake_20.c @@ -2,6 +2,7 @@ #include #include #include +#include #include #include @@ -524,4 +525,4 @@ int32_t snake_20_app(void* p) { free(snake_state); return 0; -} +} \ No newline at end of file diff --git a/applications/external/solitaire/solitaire.c b/applications/external/solitaire/solitaire.c index f83a53575..1eb82cd6e 100644 --- a/applications/external/solitaire/solitaire.c +++ b/applications/external/solitaire/solitaire.c @@ -1,4 +1,5 @@ #include +#include #include #include #include "defines.h" @@ -571,4 +572,4 @@ free_and_exit: free(game_state); furi_message_queue_free(event_queue); return return_code; -} +} \ No newline at end of file diff --git a/applications/external/text2sam/stm32_sam.cpp b/applications/external/text2sam/stm32_sam.cpp index 16f6fcaab..c77543c03 100644 --- a/applications/external/text2sam/stm32_sam.cpp +++ b/applications/external/text2sam/stm32_sam.cpp @@ -5400,15 +5400,14 @@ void STM32SAM::sam( } } + if(i < 256) { + input[i] = phonetic ? '\x9b' : '['; + } + if(!phonetic) { - strncat(input, "[", 256); if(!TextToPhonemes((unsigned char*)input)) { - // PrintUsage(); return; } - - } else { - strncat(input, "\x9b", 256); } SetInput(input); diff --git a/applications/external/timelapse/application.fam b/applications/external/timelapse/application.fam index a6dc6ad33..cd1e2c408 100644 --- a/applications/external/timelapse/application.fam +++ b/applications/external/timelapse/application.fam @@ -1,5 +1,5 @@ App( - appid="timelapse", + appid="gpio_timelapse", name="[GPIO] Timelapse", apptype=FlipperAppType.EXTERNAL, entry_point="zeitraffer_app", diff --git a/applications/external/timelapse/zeitraffer.c b/applications/external/timelapse/zeitraffer.c index 4ffdba5f2..859d256ee 100644 --- a/applications/external/timelapse/zeitraffer.c +++ b/applications/external/timelapse/zeitraffer.c @@ -5,10 +5,11 @@ #include #include #include "gpio_item.h" -#include "timelapse_icons.h" +#include "gpio_timelapse_icons.h" #include -#define CONFIG_FILE_PATH APP_DATA_PATH("timelapse.conf") +#define CONFIG_FILE_DIRECTORY_PATH "/ext/apps_data/timelapse" +#define CONFIG_FILE_PATH CONFIG_FILE_DIRECTORY_PATH "/timelapse.conf" // ЧаÑть кода покрадена из https://github.com/zmactep/flipperzero-hello-world @@ -153,6 +154,10 @@ int32_t zeitraffer_app(void* p) { FlipperFormat* load = flipper_format_file_alloc(storage); do { + if(!storage_simply_mkdir(storage, CONFIG_FILE_DIRECTORY_PATH)) { + notification_message(notifications, &sequence_error); + break; + } if(!flipper_format_file_open_existing(load, CONFIG_FILE_PATH)) { notification_message(notifications, &sequence_error); break; diff --git a/applications/external/tuning_fork/tuning_fork.c b/applications/external/tuning_fork/tuning_fork.c index 5547fb670..3ff76fa9c 100644 --- a/applications/external/tuning_fork/tuning_fork.c +++ b/applications/external/tuning_fork/tuning_fork.c @@ -1,7 +1,6 @@ #include #include #include -#include #include #include diff --git a/applications/external/unitemp/application.fam b/applications/external/unitemp/application.fam index 3fbdd5185..260069f3e 100644 --- a/applications/external/unitemp/application.fam +++ b/applications/external/unitemp/application.fam @@ -9,6 +9,7 @@ App( stack_size=2 * 1024, order=100, fap_description="Universal temperature sensors reader", + fap_version="1.4", fap_author="@quen0n & (fixes by @xMasterX)", fap_weburl="https://github.com/quen0n/unitemp-flipperzero", fap_category="GPIO", diff --git a/applications/external/videopoker/poker.c b/applications/external/videopoker/poker.c index 0205d1319..20881529c 100644 --- a/applications/external/videopoker/poker.c +++ b/applications/external/videopoker/poker.c @@ -5,7 +5,6 @@ #include #include #include -#include "assets_icons.h" #include /* Core game logic from diff --git a/applications/external/wifi_marauder_companion/application.fam b/applications/external/wifi_marauder_companion/application.fam index 782f858d5..bfd23232c 100644 --- a/applications/external/wifi_marauder_companion/application.fam +++ b/applications/external/wifi_marauder_companion/application.fam @@ -9,21 +9,21 @@ App( fap_icon="wifi_10px.png", fap_category="WiFi", fap_private_libs=[ - Lib( - name="esp-serial-flasher", - fap_include_paths=["include"], - sources=[ - "src/esp_loader.c", - "src/esp_targets.c", - "src/md5_hash.c", - "src/protocol_common.c", - "src/protocol_uart.c", - "src/slip.c" - ], - cincludes=["lib/esp-serial-flasher/private_include"], - cdefines=["SERIAL_FLASHER_INTERFACE_UART=1", "MD5_ENABLED=1"], - ), - ], + Lib( + name="esp-serial-flasher", + fap_include_paths=["include"], + sources=[ + "src/esp_loader.c", + "src/esp_targets.c", + "src/md5_hash.c", + "src/protocol_common.c", + "src/protocol_uart.c", + "src/slip.c", + ], + cincludes=["lib/esp-serial-flasher/private_include"], + cdefines=["SERIAL_FLASHER_INTERFACE_UART=1", "MD5_ENABLED=1"], + ), + ], cdefines=["SERIAL_FLASHER_INTERFACE_UART=1"], fap_icon_assets="assets", ) diff --git a/applications/external/wifi_scanner/application.fam b/applications/external/wifi_scanner/application.fam index b8a441a2e..14f06ff7a 100644 --- a/applications/external/wifi_scanner/application.fam +++ b/applications/external/wifi_scanner/application.fam @@ -5,7 +5,7 @@ App( entry_point="wifi_scanner_app", requires=["gui"], stack_size=2 * 1024, - order=70, + order=110, fap_icon="wifi_10px.png", fap_category="WiFi", fap_author="@SequoiaSan & @xMasterX", From 15639f47458927ef66452a0c6305b4d1465cbc0a Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 14 Jul 2023 02:51:21 +0200 Subject: [PATCH 140/364] More app updates --- applications/external/airmouse/air_mouse.c | 3 ++- applications/external/airmouse/application.fam | 2 ++ applications/external/airmouse/tracking/calibration_data.h | 2 +- applications/external/gpioreader_a/application.fam | 2 +- applications/external/gpioreader_b/application.fam | 3 +-- 5 files changed, 7 insertions(+), 5 deletions(-) diff --git a/applications/external/airmouse/air_mouse.c b/applications/external/airmouse/air_mouse.c index 5ca5df21c..6723ed409 100644 --- a/applications/external/airmouse/air_mouse.c +++ b/applications/external/airmouse/air_mouse.c @@ -54,8 +54,9 @@ AirMouse* air_mouse_app_alloc() { AirMouse* app = malloc(sizeof(AirMouse)); Storage* storage = furi_record_open(RECORD_STORAGE); + storage_simply_mkdir(storage, EXT_PATH("apps_data/air_mouse")); storage_common_migrate( - storage, EXT_PATH(".calibration.data"), APP_DATA_PATH("calibration.data")); + storage, EXT_PATH(".calibration.data"), EXT_PATH("apps_data/air_mouse/calibration.data")); furi_record_close(RECORD_STORAGE); // Gui diff --git a/applications/external/airmouse/application.fam b/applications/external/airmouse/application.fam index 1164b66de..dfb88da1e 100644 --- a/applications/external/airmouse/application.fam +++ b/applications/external/airmouse/application.fam @@ -6,4 +6,6 @@ App( stack_size=10 * 1024, fap_category="GPIO", fap_icon="mouse_10px.png", + fap_version="0.6", + sources=["*.c", "*.cc"], ) diff --git a/applications/external/airmouse/tracking/calibration_data.h b/applications/external/airmouse/tracking/calibration_data.h index 522a09a9a..e373eaa66 100644 --- a/applications/external/airmouse/tracking/calibration_data.h +++ b/applications/external/airmouse/tracking/calibration_data.h @@ -7,7 +7,7 @@ #include "util/vector.h" #define CALIBRATION_DATA_VER (1) -#define CALIBRATION_DATA_PATH APP_DATA_PATH("calibration.data") +#define CALIBRATION_DATA_PATH EXT_PATH("apps_data/air_mouse/calibration.data") #define CALIBRATION_DATA_MAGIC (0x23) #define CALIBRATION_DATA_SAVE(x) \ diff --git a/applications/external/gpioreader_a/application.fam b/applications/external/gpioreader_a/application.fam index c37346096..04bde0f14 100644 --- a/applications/external/gpioreader_a/application.fam +++ b/applications/external/gpioreader_a/application.fam @@ -1,5 +1,5 @@ App( - appid="gpio_reader_a", + appid="gpio_reader", name="[GPIO] Reader (aureli1c)", apptype=FlipperAppType.EXTERNAL, entry_point="GPIO_reader_app", diff --git a/applications/external/gpioreader_b/application.fam b/applications/external/gpioreader_b/application.fam index c3a417774..06d9551a9 100644 --- a/applications/external/gpioreader_b/application.fam +++ b/applications/external/gpioreader_b/application.fam @@ -1,9 +1,8 @@ App( - appid="gpio_reader_b", + appid="gpioreader2", name="[GPIO] Reader (biotinker)", apptype=FlipperAppType.EXTERNAL, entry_point="gpio_app", - cdefines=["APP_GPIOREADER"], requires=["gui"], stack_size=1 * 1024, order=50, From 49fe96247abc6fc29bee9075f7960ff80998962e Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 14 Jul 2023 02:52:58 +0200 Subject: [PATCH 141/364] Fix some build errors --- applications/external/subghz_playlist/playlist.c | 3 +-- applications/main/application.fam | 2 +- .../scenes/desktop_settings_scene_keybinds_action.c | 6 +++--- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/applications/external/subghz_playlist/playlist.c b/applications/external/subghz_playlist/playlist.c index 5ed896780..424bfe678 100644 --- a/applications/external/subghz_playlist/playlist.c +++ b/applications/external/subghz_playlist/playlist.c @@ -720,8 +720,7 @@ void playlist_free(Playlist* app) { free(app); } -int32_t playlist_app(void* p) { - UNUSED(p); +int32_t playlist_app(char* p) { dolphin_deed(DolphinDeedPluginStart); // create playlist folder diff --git a/applications/main/application.fam b/applications/main/application.fam index 1248838e8..277d4b3e2 100644 --- a/applications/main/application.fam +++ b/applications/main/application.fam @@ -12,7 +12,7 @@ App( "bad_kb", "u2f", "archive", - "clock", + "nightstand", "subghz_remote", "main_apps_on_start", ], diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_keybinds_action.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_keybinds_action.c index dc2d7eb6c..1663246db 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_keybinds_action.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_keybinds_action.c @@ -35,7 +35,7 @@ void desktop_settings_scene_keybinds_action_on_enter(void* context) { // Select keybind item in submenu if(!strncmp(FLIPPER_EXTERNAL_APPS[i].name, keybind, MAX_KEYBIND_LENGTH)) { - pre_select_item = FLIPPER_EXTERNAL_APPS[i].name; + pre_select_item = (uint32_t)FLIPPER_EXTERNAL_APPS[i].name; } } for(size_t i = 0; i < FLIPPER_APPS_COUNT; i++) { @@ -48,7 +48,7 @@ void desktop_settings_scene_keybinds_action_on_enter(void* context) { // Select keybind item in submenu if(!strncmp(FLIPPER_APPS[i].name, keybind, MAX_KEYBIND_LENGTH)) { - pre_select_item = FLIPPER_APPS[i].name; + pre_select_item = (uint32_t)FLIPPER_APPS[i].name; } } } else if(action_type == DesktopSettingsAppKeybindActionTypeMoreActions) { @@ -62,7 +62,7 @@ void desktop_settings_scene_keybinds_action_on_enter(void* context) { // Select keybind item in submenu if(!strncmp(EXTRA_KEYBINDS[i], keybind, MAX_KEYBIND_LENGTH)) { - pre_select_item = EXTRA_KEYBINDS[i]; + pre_select_item = (uint32_t)EXTRA_KEYBINDS[i]; } } } From fe95845fa0c1b76f08350f184230b8e833b15945 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Fri, 14 Jul 2023 04:06:15 +0300 Subject: [PATCH 142/364] trying to find where issue is hiding --- .drone.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.drone.yml b/.drone.yml index d1659b756..4058dc362 100644 --- a/.drone.yml +++ b/.drone.yml @@ -21,6 +21,7 @@ steps: commands: - export DIST_SUFFIX=${DRONE_TAG} - export WORKFLOW_BRANCH_OR_TAG=release-cfw + - rm -rf assets/resources/apps/ - ./fbt COMPACT=1 DEBUG=0 updater_package - mkdir artifacts-default - mv dist/f7-C/* artifacts-default/ @@ -316,6 +317,7 @@ steps: commands: - export DIST_SUFFIX=${DRONE_BUILD_NUMBER} - export WORKFLOW_BRANCH_OR_TAG=dev-cfw + - rm -rf assets/resources/apps/ - ./fbt COMPACT=1 DEBUG=0 updater_package - mkdir artifacts-default - mv dist/f7-C/* artifacts-default/ From dd26ec22c3a2724ba9819852288c63e9b29cc388 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Fri, 14 Jul 2023 04:08:20 +0300 Subject: [PATCH 143/364] remove all possible leftovers --- .drone.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.drone.yml b/.drone.yml index 4058dc362..959534e28 100644 --- a/.drone.yml +++ b/.drone.yml @@ -22,6 +22,9 @@ steps: - export DIST_SUFFIX=${DRONE_TAG} - export WORKFLOW_BRANCH_OR_TAG=release-cfw - rm -rf assets/resources/apps/ + - rm -rf build/ + - rm -rf dist/ + - rm -rf .sconsign.dblite - ./fbt COMPACT=1 DEBUG=0 updater_package - mkdir artifacts-default - mv dist/f7-C/* artifacts-default/ @@ -318,6 +321,9 @@ steps: - export DIST_SUFFIX=${DRONE_BUILD_NUMBER} - export WORKFLOW_BRANCH_OR_TAG=dev-cfw - rm -rf assets/resources/apps/ + - rm -rf build/ + - rm -rf dist/ + - rm -rf .sconsign.dblite - ./fbt COMPACT=1 DEBUG=0 updater_package - mkdir artifacts-default - mv dist/f7-C/* artifacts-default/ From 00771df0c37cdf14db64a85d31b741514951e954 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 14 Jul 2023 03:18:52 +0200 Subject: [PATCH 144/364] Update barcode gen --- .../external/barcode_gen/barcode_app.h | 5 +- .../external/barcode_gen/barcode_utils.c | 13 +- .../external/barcode_gen/barcode_utils.h | 5 +- .../external/barcode_gen/barcode_validator.c | 131 +++++++++++++++++- .../external/barcode_gen/barcode_validator.h | 3 +- .../external/barcode_gen/views/barcode_view.c | 3 +- 6 files changed, 153 insertions(+), 7 deletions(-) diff --git a/applications/external/barcode_gen/barcode_app.h b/applications/external/barcode_gen/barcode_app.h index bbc787f1e..3150bff1f 100644 --- a/applications/external/barcode_gen/barcode_app.h +++ b/applications/external/barcode_gen/barcode_app.h @@ -35,6 +35,9 @@ //the folder where the code 128 encoding table is located #define CODE128_DICT_FILE_PATH BARCODE_DATA_FILE_DIR_PATH "/code128_encodings.txt" +//the folder where the code 128 C encoding table is located +#define CODE128C_DICT_FILE_PATH BARCODE_DATA_FILE_DIR_PATH "/code128c_encodings.txt" + //the folder where the user stores their barcodes #define DEFAULT_USER_BARCODES EXT_PATH("apps_data/barcodes") @@ -85,4 +88,4 @@ uint32_t main_menu_callback(void* context); uint32_t exit_callback(void* context); -int32_t barcode_main(void* p); \ No newline at end of file +int32_t barcode_main(void* p); diff --git a/applications/external/barcode_gen/barcode_utils.c b/applications/external/barcode_gen/barcode_utils.c index d7bb12689..31274c1fe 100644 --- a/applications/external/barcode_gen/barcode_utils.c +++ b/applications/external/barcode_gen/barcode_utils.c @@ -43,6 +43,14 @@ void init_types() { code_128->start_pos = 0; barcode_type_objs[CODE128] = code_128; + BarcodeTypeObj* code_128c = malloc(sizeof(BarcodeTypeObj)); + code_128c->name = "CODE-128C"; + code_128c->type = CODE128C; + code_128c->min_digits = 2; + code_128c->max_digits = -1; + code_128c->start_pos = 0; + barcode_type_objs[CODE128C] = code_128c; + BarcodeTypeObj* codabar = malloc(sizeof(BarcodeTypeObj)); codabar->name = "Codabar"; codabar->type = CODABAR; @@ -82,6 +90,9 @@ BarcodeTypeObj* get_type(FuriString* type_string) { if(furi_string_cmp_str(type_string, "CODE-128") == 0) { return barcode_type_objs[CODE128]; } + if(furi_string_cmp_str(type_string, "CODE-128C") == 0) { + return barcode_type_objs[CODE128C]; + } if(furi_string_cmp_str(type_string, "Codabar") == 0) { return barcode_type_objs[CODABAR]; } @@ -133,4 +144,4 @@ const char* get_error_code_message(ErrorCode error_code) { default: return "Could not read barcode data"; }; -} \ No newline at end of file +} diff --git a/applications/external/barcode_gen/barcode_utils.h b/applications/external/barcode_gen/barcode_utils.h index 7bc565cee..0a27785cf 100644 --- a/applications/external/barcode_gen/barcode_utils.h +++ b/applications/external/barcode_gen/barcode_utils.h @@ -3,7 +3,7 @@ #include #include -#define NUMBER_OF_BARCODE_TYPES 7 +#define NUMBER_OF_BARCODE_TYPES 8 typedef enum { WrongNumberOfDigits, //There is too many or too few digits in the barcode @@ -22,6 +22,7 @@ typedef enum { EAN13, CODE39, CODE128, + CODE128C, CODABAR, UNKNOWN @@ -51,4 +52,4 @@ void init_types(); void free_types(); BarcodeTypeObj* get_type(FuriString* type_string); const char* get_error_code_name(ErrorCode error_code); -const char* get_error_code_message(ErrorCode error_code); \ No newline at end of file +const char* get_error_code_message(ErrorCode error_code); diff --git a/applications/external/barcode_gen/barcode_validator.c b/applications/external/barcode_gen/barcode_validator.c index 51e71ae2e..cb493f3e9 100644 --- a/applications/external/barcode_gen/barcode_validator.c +++ b/applications/external/barcode_gen/barcode_validator.c @@ -13,6 +13,9 @@ void barcode_loader(BarcodeData* barcode_data) { case CODE128: code_128_loader(barcode_data); break; + case CODE128C: + code_128c_loader(barcode_data); + break; case CODABAR: codabar_loader(barcode_data); break; @@ -39,6 +42,7 @@ int calculate_check_digit(BarcodeData* barcode_data) { break; case CODE39: case CODE128: + case CODE128C: case CODABAR: case UNKNOWN: default: @@ -345,6 +349,131 @@ void code_128_loader(BarcodeData* barcode_data) { furi_string_free(barcode_bits); } +/** + * Loads a code 128 C barcode +*/ +void code_128c_loader(BarcodeData* barcode_data) { + int barcode_length = furi_string_size(barcode_data->raw_data); + + //the start code for character set C + int start_code_value = 105; + + //The bits for the start code + const char* start_code_bits = "11010011100"; + + //The bits for the stop code + const char* stop_code_bits = "1100011101011"; + + int min_digits = barcode_data->type_obj->min_digits; + + int checksum_adder = start_code_value; + int checksum_digits = 0; + + //the calculated check digit + int final_check_digit = 0; + + // check the length of the barcode, must contain atleast 2 character, + // this can have as many characters as it wants, it might not fit on the screen + // code 128 C: the length must be even + if((barcode_length < min_digits) || (barcode_length & 1)) { + barcode_data->reason = WrongNumberOfDigits; + barcode_data->valid = false; + return; + } + //Open Storage + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* ff = flipper_format_file_alloc(storage); + + FuriString* barcode_bits = furi_string_alloc(); + + //add the start code + furi_string_cat(barcode_bits, start_code_bits); + + if(!flipper_format_file_open_existing(ff, CODE128C_DICT_FILE_PATH)) { + FURI_LOG_E(TAG, "c128c Could not open file %s", CODE128C_DICT_FILE_PATH); + barcode_data->reason = MissingEncodingTable; + barcode_data->valid = false; + } else { + FuriString* value = furi_string_alloc(); + FuriString* char_bits = furi_string_alloc(); + for(int i = 0; i < barcode_length; i += 2) { + char barcode_char1 = furi_string_get_char(barcode_data->raw_data, i); + char barcode_char2 = furi_string_get_char(barcode_data->raw_data, i + 1); + FURI_LOG_I(TAG, "c128c bc1='%c' bc2='%c'", barcode_char1, barcode_char2); + + char current_chars[4]; + snprintf(current_chars, 3, "%c%c", barcode_char1, barcode_char2); + FURI_LOG_I(TAG, "c128c current_chars='%s'", current_chars); + + //using the value of the characters, get the characters bits + if(!flipper_format_read_string(ff, current_chars, char_bits)) { + FURI_LOG_E(TAG, "c128c Could not read \"%s\" string", current_chars); + barcode_data->reason = EncodingTableError; + barcode_data->valid = false; + break; + } else { + //add the bits to the full barcode + furi_string_cat(barcode_bits, char_bits); + + // calculate the checksum + checksum_digits += 1; + checksum_adder += (atoi(current_chars) * checksum_digits); + + FURI_LOG_I( + TAG, + "c128c \"%s\" string: %s : %s : %d : %d : %d", + current_chars, + furi_string_get_cstr(char_bits), + furi_string_get_cstr(value), + checksum_digits, + (atoi(furi_string_get_cstr(value)) * checksum_digits), + checksum_adder); + } + //bring the file pointer back to the begining + flipper_format_rewind(ff); + } + //calculate the check digit and convert it into a c string for lookup in the encoding table + final_check_digit = checksum_adder % 103; + FURI_LOG_I(TAG, "c128c finale_check_digit=%d", final_check_digit); + + int length = snprintf(NULL, 0, "%d", final_check_digit); + if(final_check_digit < 100) length = 2; + char* final_check_digit_string = malloc(length + 1); + snprintf(final_check_digit_string, length + 1, "%02d", final_check_digit); + + //after the checksum has been calculated, add the bits to the full barcode + if(!flipper_format_read_string(ff, final_check_digit_string, char_bits)) { + FURI_LOG_E(TAG, "c128c cksum Could not read \"%s\" string", final_check_digit_string); + barcode_data->reason = EncodingTableError; + barcode_data->valid = false; + } else { + //add the check digit bits to the full barcode + furi_string_cat(barcode_bits, char_bits); + + FURI_LOG_I( + TAG, + "check digit \"%s\" string: %s", + final_check_digit_string, + furi_string_get_cstr(char_bits)); + } + + free(final_check_digit_string); + furi_string_free(value); + furi_string_free(char_bits); + } + + //add the stop code + furi_string_cat(barcode_bits, stop_code_bits); + + //Close Storage + flipper_format_free(ff); + furi_record_close(RECORD_STORAGE); + + FURI_LOG_I(TAG, "c128c %s", furi_string_get_cstr(barcode_bits)); + furi_string_cat(barcode_data->correct_data, barcode_bits); + furi_string_free(barcode_bits); +} + void codabar_loader(BarcodeData* barcode_data) { int barcode_length = furi_string_size(barcode_data->raw_data); @@ -400,4 +529,4 @@ void codabar_loader(BarcodeData* barcode_data) { furi_string_cat(barcode_data->correct_data, barcode_bits); furi_string_free(barcode_bits); -} \ No newline at end of file +} diff --git a/applications/external/barcode_gen/barcode_validator.h b/applications/external/barcode_gen/barcode_validator.h index 739c80ddf..2138124dd 100644 --- a/applications/external/barcode_gen/barcode_validator.h +++ b/applications/external/barcode_gen/barcode_validator.h @@ -10,5 +10,6 @@ void ean_8_loader(BarcodeData* barcode_data); void ean_13_loader(BarcodeData* barcode_data); void code_39_loader(BarcodeData* barcode_data); void code_128_loader(BarcodeData* barcode_data); +void code_128c_loader(BarcodeData* barcode_data); void codabar_loader(BarcodeData* barcode_data); -void barcode_loader(BarcodeData* barcode_data); \ No newline at end of file +void barcode_loader(BarcodeData* barcode_data); diff --git a/applications/external/barcode_gen/views/barcode_view.c b/applications/external/barcode_gen/views/barcode_view.c index 994053801..55ab52046 100644 --- a/applications/external/barcode_gen/views/barcode_view.c +++ b/applications/external/barcode_gen/views/barcode_view.c @@ -406,6 +406,7 @@ static void barcode_draw_callback(Canvas* canvas, void* ctx) { draw_code_39(canvas, data); break; case CODE128: + case CODE128C: draw_code_128(canvas, data); break; case CODABAR: @@ -506,4 +507,4 @@ void barcode_free(Barcode* barcode) { View* barcode_get_view(Barcode* barcode) { furi_assert(barcode); return barcode->view; -} \ No newline at end of file +} From 0ddb50de7255552cc9f247aacc43eafe5e3dcef3 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 14 Jul 2023 03:20:36 +0200 Subject: [PATCH 145/364] Move misc category --- applications/external/.mass_storage/application.fam | 2 +- applications/external/barcode_gen/application.fam | 2 +- applications/external/morse_code/application.fam | 2 +- applications/external/orgasmotron/application.fam | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/applications/external/.mass_storage/application.fam b/applications/external/.mass_storage/application.fam index 5cc57b2b5..3a09d5f62 100644 --- a/applications/external/.mass_storage/application.fam +++ b/applications/external/.mass_storage/application.fam @@ -8,5 +8,5 @@ App( stack_size=1 * 1024, order=2, # fap_icon="", - fap_category="Misc", + fap_category="USB", ) diff --git a/applications/external/barcode_gen/application.fam b/applications/external/barcode_gen/application.fam index 9c0299617..19bb84122 100644 --- a/applications/external/barcode_gen/application.fam +++ b/applications/external/barcode_gen/application.fam @@ -5,7 +5,7 @@ App( entry_point="barcode_main", requires=["gui", "storage"], stack_size=2 * 1024, - fap_category="Misc", + fap_category="Tools", fap_icon="images/barcode_10.png", fap_icon_assets="images", fap_icon_assets_symbol="barcode_app", diff --git a/applications/external/morse_code/application.fam b/applications/external/morse_code/application.fam index 1a221844e..d46b34628 100644 --- a/applications/external/morse_code/application.fam +++ b/applications/external/morse_code/application.fam @@ -9,7 +9,7 @@ App( stack_size=1 * 1024, order=20, fap_icon="morse_code_10px.png", - fap_category="Misc", + fap_category="Media", fap_author="@wh00hw & @xMasterX", fap_version="1.0", fap_description="Simple Morse Code parser", diff --git a/applications/external/orgasmotron/application.fam b/applications/external/orgasmotron/application.fam index 29d968fb5..b4c191a8a 100644 --- a/applications/external/orgasmotron/application.fam +++ b/applications/external/orgasmotron/application.fam @@ -8,5 +8,5 @@ App( stack_size=1 * 1024, order=20, fap_icon="orgasmotron_10px.png", - fap_category="Misc", + fap_category="Tools", ) From 523f522de0061ac373f3bb6107fe378efb0a5dad Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Fri, 14 Jul 2023 04:40:22 +0300 Subject: [PATCH 146/364] revert archive fixes temporarily --- .../main/archive/helpers/archive_browser.c | 29 +------------------ .../main/archive/helpers/archive_browser.h | 1 - .../main/archive/views/archive_browser_view.c | 26 ++++++++++++----- .../services/gui/modules/file_browser.c | 26 +++++------------ 4 files changed, 28 insertions(+), 54 deletions(-) diff --git a/applications/main/archive/helpers/archive_browser.c b/applications/main/archive/helpers/archive_browser.c index 5abb8e212..43888cb4d 100644 --- a/applications/main/archive/helpers/archive_browser.c +++ b/applications/main/archive/helpers/archive_browser.c @@ -70,23 +70,16 @@ static void archive_list_item_cb( if(!is_last) { archive_add_file_item(browser, is_folder, furi_string_get_cstr(item_path)); } else { - bool load_again = false; with_view_model( browser->view, ArchiveBrowserViewModel * model, { - model->list_loading = false; if(model->item_cnt <= BROWSER_SORT_THRESHOLD) { files_array_sort(model->files); } - if(archive_is_file_list_load_required(model)) { - load_again = true; - } + model->list_loading = false; }, true); - if(load_again) { - archive_file_array_load(browser, 0); - } } } @@ -132,26 +125,6 @@ bool archive_is_item_in_array(ArchiveBrowserViewModel* model, uint32_t idx) { return true; } -bool archive_is_file_list_load_required(ArchiveBrowserViewModel* model) { - size_t array_size = files_array_size(model->files); - - if((model->list_loading) || (array_size >= model->item_cnt)) { - return false; - } - - if((model->array_offset > 0) && - (model->item_idx < (model->array_offset + FILE_LIST_BUF_LEN / 4))) { - return true; - } - - if(((model->array_offset + array_size) < model->item_cnt) && - (model->item_idx > (int32_t)(model->array_offset + array_size - FILE_LIST_BUF_LEN / 4))) { - return true; - } - - return false; -} - void archive_update_offset(ArchiveBrowserView* browser) { furi_assert(browser); diff --git a/applications/main/archive/helpers/archive_browser.h b/applications/main/archive/helpers/archive_browser.h index c2699c3f1..43a9a651a 100644 --- a/applications/main/archive/helpers/archive_browser.h +++ b/applications/main/archive/helpers/archive_browser.h @@ -66,7 +66,6 @@ inline bool archive_is_known_app(ArchiveFileTypeEnum type) { } bool archive_is_item_in_array(ArchiveBrowserViewModel* model, uint32_t idx); -bool archive_is_file_list_load_required(ArchiveBrowserViewModel* model); void archive_update_offset(ArchiveBrowserView* browser); void archive_update_focus(ArchiveBrowserView* browser, const char* target); diff --git a/applications/main/archive/views/archive_browser_view.c b/applications/main/archive/views/archive_browser_view.c index 55a5438ca..63fa91c4d 100644 --- a/applications/main/archive/views/archive_browser_view.c +++ b/applications/main/archive/views/archive_browser_view.c @@ -370,10 +370,24 @@ View* archive_browser_get_view(ArchiveBrowserView* browser) { return browser->view; } -static void file_list_rollover(ArchiveBrowserViewModel* model) { - if(!model->list_loading && files_array_size(model->files) < model->item_cnt) { - files_array_reset(model->files); +static bool is_file_list_load_required(ArchiveBrowserViewModel* model) { + size_t array_size = files_array_size(model->files); + + if((model->list_loading) || (array_size >= model->item_cnt)) { + return false; } + + if((model->array_offset > 0) && + (model->item_idx < (model->array_offset + FILE_LIST_BUF_LEN / 4))) { + return true; + } + + if(((model->array_offset + array_size) < model->item_cnt) && + (model->item_idx > (int32_t)(model->array_offset + array_size - FILE_LIST_BUF_LEN / 4))) { + return true; + } + + return false; } static bool archive_view_input(InputEvent* event, void* context) { @@ -463,13 +477,12 @@ static bool archive_view_input(InputEvent* event, void* context) { if(model->item_idx < scroll_speed) { model->button_held_for_ticks = 0; model->item_idx = model->item_cnt - 1; - file_list_rollover(model); } else { model->item_idx = ((model->item_idx - scroll_speed) + model->item_cnt) % model->item_cnt; } - if(archive_is_file_list_load_required(model)) { + if(is_file_list_load_required(model)) { model->list_loading = true; browser->callback(ArchiveBrowserEventLoadPrevItems, browser->context); } @@ -483,11 +496,10 @@ static bool archive_view_input(InputEvent* event, void* context) { if(model->item_idx + scroll_speed >= count) { model->button_held_for_ticks = 0; model->item_idx = 0; - file_list_rollover(model); } else { model->item_idx = (model->item_idx + scroll_speed) % model->item_cnt; } - if(archive_is_file_list_load_required(model)) { + if(is_file_list_load_required(model)) { model->list_loading = true; browser->callback(ArchiveBrowserEventLoadNextItems, browser->context); } diff --git a/applications/services/gui/modules/file_browser.c b/applications/services/gui/modules/file_browser.c index db44b3874..eb1e39bd2 100644 --- a/applications/services/gui/modules/file_browser.c +++ b/applications/services/gui/modules/file_browser.c @@ -335,12 +335,6 @@ static bool browser_is_list_load_required(FileBrowserModel* model) { return false; } -static void browser_list_rollover(FileBrowserModel* model) { - if(!model->list_loading && items_array_size(model->items) < model->item_cnt) { - items_array_reset(model->items); - } -} - static void browser_update_offset(FileBrowser* browser) { furi_assert(browser); @@ -423,7 +417,7 @@ static void browser_list_load_cb(void* context, uint32_t list_load_offset) { } } }, - false); + true); BrowserItem_t_clear(&back_item); } @@ -468,15 +462,14 @@ static void browser_list_item_cb( (browser->hide_ext) && (item.type == BrowserItemTypeFile)); } - // We shouldn't update screen on each item if custom callback is not set - // Otherwise it will cause screen flickering - bool instant_update = (browser->item_callback != NULL); with_view_model( browser->view, FileBrowserModel * model, - { items_array_push_back(model->items, item); }, - instant_update); - + { + items_array_push_back(model->items, item); + // TODO: calculate if element is visible + }, + false); furi_string_free(item.display_name); furi_string_free(item.path); if(item.custom_icon_data) { @@ -689,13 +682,11 @@ static bool file_browser_view_input_callback(InputEvent* event, void* context) { if(model->item_idx < scroll_speed) { model->button_held_for_ticks = 0; model->item_idx = model->item_cnt - 1; - browser_list_rollover(model); } else { model->item_idx = ((model->item_idx - scroll_speed) + model->item_cnt) % model->item_cnt; } - if(browser_is_list_load_required(model)) { model->list_loading = true; int32_t load_offset = CLAMP( @@ -709,14 +700,13 @@ static bool file_browser_view_input_callback(InputEvent* event, void* context) { model->button_held_for_ticks += 1; } else if(event->key == InputKeyDown) { - if(model->item_idx + scroll_speed >= (int32_t)model->item_cnt) { + int32_t count = model->item_cnt; + if(model->item_idx + scroll_speed >= count) { model->button_held_for_ticks = 0; model->item_idx = 0; - browser_list_rollover(model); } else { model->item_idx = (model->item_idx + scroll_speed) % model->item_cnt; } - if(browser_is_list_load_required(model)) { model->list_loading = true; int32_t load_offset = CLAMP( From 0b0d9eba5211443e95f672cfbd2cdd875173a7f2 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 14 Jul 2023 03:42:02 +0200 Subject: [PATCH 147/364] Fix apps bundle again --- applications/main/application.fam | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/main/application.fam b/applications/main/application.fam index 277d4b3e2..0af6617d7 100644 --- a/applications/main/application.fam +++ b/applications/main/application.fam @@ -11,9 +11,9 @@ App( "subghz", "bad_kb", "u2f", + "xtreme_app", "archive", "nightstand", - "subghz_remote", "main_apps_on_start", ], ) From 06c44031e79be9d716b7ed5029abcc4fbc1ae950 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 14 Jul 2023 03:52:23 +0200 Subject: [PATCH 148/364] Merge previous file browser commits --- .../main/archive/helpers/archive_browser.c | 29 +++++- .../main/archive/helpers/archive_browser.h | 1 + .../main/archive/views/archive_browser_view.c | 69 ++++--------- .../main/archive/views/archive_browser_view.h | 2 +- .../services/gui/modules/file_browser.c | 97 +++++++++---------- 5 files changed, 96 insertions(+), 102 deletions(-) diff --git a/applications/main/archive/helpers/archive_browser.c b/applications/main/archive/helpers/archive_browser.c index c0325ff16..4b8c3d584 100644 --- a/applications/main/archive/helpers/archive_browser.c +++ b/applications/main/archive/helpers/archive_browser.c @@ -70,10 +70,12 @@ static void archive_list_item_cb( if(!is_last) { archive_add_file_item(browser, is_folder, furi_string_get_cstr(item_path)); } else { + bool load_again = false; with_view_model( browser->view, ArchiveBrowserViewModel * model, { + model->list_loading = false; if(model->item_cnt <= BROWSER_SORT_THRESHOLD) { FuriString* selected = NULL; if(model->item_idx >= 0) { @@ -96,9 +98,14 @@ static void archive_list_item_cb( model->item_idx = 0; } } - model->list_loading = false; + if(archive_is_file_list_load_required(model)) { + load_again = true; + } }, true); + if(load_again) { + archive_file_array_load(browser, 0); + } archive_update_offset(browser); } } @@ -145,6 +152,26 @@ bool archive_is_item_in_array(ArchiveBrowserViewModel* model, uint32_t idx) { return true; } +bool archive_is_file_list_load_required(ArchiveBrowserViewModel* model) { + size_t array_size = files_array_size(model->files); + + if((model->list_loading) || (array_size >= model->item_cnt)) { + return false; + } + + if((model->array_offset > 0) && + (model->item_idx < (model->array_offset + FILE_LIST_BUF_LEN / 4))) { + return true; + } + + if(((model->array_offset + array_size) < model->item_cnt) && + (model->item_idx > (int32_t)(model->array_offset + array_size - FILE_LIST_BUF_LEN / 4))) { + return true; + } + + return false; +} + void archive_update_offset(ArchiveBrowserView* browser) { furi_assert(browser); diff --git a/applications/main/archive/helpers/archive_browser.h b/applications/main/archive/helpers/archive_browser.h index 8074d2532..d9214bee1 100644 --- a/applications/main/archive/helpers/archive_browser.h +++ b/applications/main/archive/helpers/archive_browser.h @@ -72,6 +72,7 @@ inline bool archive_is_known_app(ArchiveFileTypeEnum type) { } bool archive_is_item_in_array(ArchiveBrowserViewModel* model, uint32_t idx); +bool archive_is_file_list_load_required(ArchiveBrowserViewModel* model); void archive_update_offset(ArchiveBrowserView* browser); void archive_update_focus(ArchiveBrowserView* browser, const char* target); diff --git a/applications/main/archive/views/archive_browser_view.c b/applications/main/archive/views/archive_browser_view.c index 5aba05578..7bc608040 100644 --- a/applications/main/archive/views/archive_browser_view.c +++ b/applications/main/archive/views/archive_browser_view.c @@ -335,24 +335,10 @@ View* archive_browser_get_view(ArchiveBrowserView* browser) { return browser->view; } -static bool is_file_list_load_required(ArchiveBrowserViewModel* model) { - size_t array_size = files_array_size(model->files); - - if((model->list_loading) || (array_size >= model->item_cnt)) { - return false; +static void file_list_rollover(ArchiveBrowserViewModel* model) { + if(!model->list_loading && files_array_size(model->files) < model->item_cnt) { + files_array_reset(model->files); } - - if((model->array_offset > 0) && - (model->item_idx < (model->array_offset + FILE_LIST_BUF_LEN / 4))) { - return true; - } - - if(((model->array_offset + array_size) < model->item_cnt) && - (model->item_idx > (int32_t)(model->array_offset + array_size - FILE_LIST_BUF_LEN / 4))) { - return true; - } - - return false; } static bool archive_view_input(InputEvent* event, void* context) { @@ -438,25 +424,19 @@ static bool archive_view_input(InputEvent* event, void* context) { } else { scroll_speed = model->button_held_for_ticks > 9 ? 4 : 2; } - } else if(model->button_held_for_ticks < 0) { - scroll_speed = 0; } if(event->key == InputKeyUp) { if(model->item_idx < scroll_speed) { - scroll_speed = model->item_idx; - if(scroll_speed == 0) { - if(model->button_held_for_ticks > 0) { - model->button_held_for_ticks = -1; - } else { - scroll_speed = 1; - } - } + model->button_held_for_ticks = 0; + model->item_idx = model->item_cnt - 1; + file_list_rollover(model); + } else { + model->item_idx = + ((model->item_idx - scroll_speed) + model->item_cnt) % + model->item_cnt; } - - model->item_idx = - ((model->item_idx - scroll_speed) + model->item_cnt) % model->item_cnt; - if(is_file_list_load_required(model)) { + if(archive_is_file_list_load_required(model)) { model->list_loading = true; browser->callback(ArchiveBrowserEventLoadPrevItems, browser->context); } @@ -464,26 +444,17 @@ static bool archive_view_input(InputEvent* event, void* context) { browser->callback(ArchiveBrowserEventFavMoveUp, browser->context); } model->scroll_counter = 0; - - if(model->button_held_for_ticks < -1) { - model->button_held_for_ticks = 0; - } model->button_held_for_ticks += 1; } else if(event->key == InputKeyDown) { int32_t count = model->item_cnt; - if(model->item_idx >= (count - scroll_speed)) { - scroll_speed = model->item_cnt - model->item_idx - 1; - if(scroll_speed == 0) { - if(model->button_held_for_ticks > 0) { - model->button_held_for_ticks = -1; - } else { - scroll_speed = 1; - } - } + if(model->item_idx + scroll_speed >= count) { + model->button_held_for_ticks = 0; + model->item_idx = 0; + file_list_rollover(model); + } else { + model->item_idx = (model->item_idx + scroll_speed) % model->item_cnt; } - - model->item_idx = (model->item_idx + scroll_speed) % model->item_cnt; - if(is_file_list_load_required(model)) { + if(archive_is_file_list_load_required(model)) { model->list_loading = true; browser->callback(ArchiveBrowserEventLoadNextItems, browser->context); } @@ -491,10 +462,6 @@ static bool archive_view_input(InputEvent* event, void* context) { browser->callback(ArchiveBrowserEventFavMoveDown, browser->context); } model->scroll_counter = 0; - - if(model->button_held_for_ticks < -1) { - model->button_held_for_ticks = 0; - } model->button_held_for_ticks += 1; } }, diff --git a/applications/main/archive/views/archive_browser_view.h b/applications/main/archive/views/archive_browser_view.h index f87d59a2b..d06e27e8c 100644 --- a/applications/main/archive/views/archive_browser_view.h +++ b/applications/main/archive/views/archive_browser_view.h @@ -115,7 +115,7 @@ typedef struct { int32_t list_offset; size_t scroll_counter; - int32_t button_held_for_ticks; + uint32_t button_held_for_ticks; } ArchiveBrowserViewModel; void archive_browser_set_callback( diff --git a/applications/services/gui/modules/file_browser.c b/applications/services/gui/modules/file_browser.c index b547077f7..4ffecbf66 100644 --- a/applications/services/gui/modules/file_browser.c +++ b/applications/services/gui/modules/file_browser.c @@ -148,7 +148,7 @@ typedef struct { bool hide_ext; size_t scroll_counter; - int32_t button_held_for_ticks; + uint32_t button_held_for_ticks; } FileBrowserModel; static const Icon* BrowserItemIcons[] = { @@ -336,6 +336,12 @@ static bool browser_is_list_load_required(FileBrowserModel* model) { return false; } +static void browser_list_rollover(FileBrowserModel* model) { + if(!model->list_loading && items_array_size(model->items) < model->item_cnt) { + items_array_reset(model->items); + } +} + static void browser_update_offset(FileBrowser* browser) { furi_assert(browser); @@ -418,7 +424,7 @@ static void browser_list_load_cb(void* context, uint32_t list_load_offset) { } } }, - true); + false); BrowserItem_t_clear(&back_item); } @@ -463,14 +469,15 @@ static void browser_list_item_cb( (browser->hide_ext) && (item.type == BrowserItemTypeFile)); } + // We shouldn't update screen on each item if custom callback is not set + // Otherwise it will cause screen flickering + bool instant_update = (browser->item_callback != NULL); with_view_model( browser->view, FileBrowserModel * model, - { - items_array_push_back(model->items, item); - // TODO: calculate if element is visible - }, - false); + { items_array_push_back(model->items, item); }, + instant_update); + furi_string_free(item.display_name); furi_string_free(item.path); if(item.custom_icon_data) { @@ -481,25 +488,33 @@ static void browser_list_item_cb( browser->view, FileBrowserModel * model, { - if(model->item_cnt <= BROWSER_SORT_THRESHOLD) { - FuriString* selected = NULL; - if(model->item_idx > 0) { - selected = furi_string_alloc_set( - items_array_get(model->items, model->item_idx)->path); - } + model->list_loading = false; + if(browser_is_list_load_required(model)) { + model->list_loading = true; + int32_t load_offset = CLAMP( + model->item_idx - ITEM_LIST_LEN_MAX / 2, (int32_t)model->item_cnt, 0); + file_browser_worker_load(browser->worker, load_offset, ITEM_LIST_LEN_MAX); - items_array_sort(model->items); + if(model->item_cnt <= BROWSER_SORT_THRESHOLD) { + FuriString* selected = NULL; + if(model->item_idx > 0) { + selected = furi_string_alloc_set( + items_array_get(model->items, model->item_idx)->path); + } - if(selected != NULL) { - for(uint32_t i = 0; i < model->item_cnt; i++) { - if(!furi_string_cmp(items_array_get(model->items, i)->path, selected)) { - model->item_idx = i; - break; + items_array_sort(model->items); + + if(selected != NULL) { + for(uint32_t i = 0; i < model->item_cnt; i++) { + if(!furi_string_cmp( + items_array_get(model->items, i)->path, selected)) { + model->item_idx = i; + break; + } } } } } - model->list_loading = false; }, false); browser_update_offset(browser); @@ -670,24 +685,19 @@ static bool file_browser_view_input_callback(InputEvent* event, void* context) { } else { scroll_speed = model->button_held_for_ticks > 9 ? 5 : 3; } - } else if(model->button_held_for_ticks < 0) { - scroll_speed = 0; } if(event->key == InputKeyUp) { if(model->item_idx < scroll_speed) { - scroll_speed = model->item_idx; - if(scroll_speed == 0) { - if(model->button_held_for_ticks > 0) { - model->button_held_for_ticks = -1; - } else { - scroll_speed = 1; - } - } + model->button_held_for_ticks = 0; + model->item_idx = model->item_cnt - 1; + browser_list_rollover(model); + } else { + model->item_idx = + ((model->item_idx - scroll_speed) + model->item_cnt) % + model->item_cnt; } - model->item_idx = - ((model->item_idx - scroll_speed) + model->item_cnt) % model->item_cnt; if(browser_is_list_load_required(model)) { model->list_loading = true; int32_t load_offset = CLAMP( @@ -699,24 +709,16 @@ static bool file_browser_view_input_callback(InputEvent* event, void* context) { } model->scroll_counter = 0; - if(model->button_held_for_ticks < -1) { - model->button_held_for_ticks = 0; - } model->button_held_for_ticks += 1; } else if(event->key == InputKeyDown) { - int32_t count = model->item_cnt; - if(model->item_idx + scroll_speed >= count) { - scroll_speed = count - model->item_idx - 1; - if(scroll_speed == 0) { - if(model->button_held_for_ticks > 0) { - model->button_held_for_ticks = -1; - } else { - scroll_speed = 1; - } - } + if(model->item_idx + scroll_speed >= (int32_t)model->item_cnt) { + model->button_held_for_ticks = 0; + model->item_idx = 0; + browser_list_rollover(model); + } else { + model->item_idx = (model->item_idx + scroll_speed) % model->item_cnt; } - model->item_idx = (model->item_idx + scroll_speed) % model->item_cnt; if(browser_is_list_load_required(model)) { model->list_loading = true; int32_t load_offset = CLAMP( @@ -728,9 +730,6 @@ static bool file_browser_view_input_callback(InputEvent* event, void* context) { } model->scroll_counter = 0; - if(model->button_held_for_ticks < -1) { - model->button_held_for_ticks = 0; - } model->button_held_for_ticks += 1; } }, From 46de43b617784b3597ce54ced109739be5b2d630 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 14 Jul 2023 03:53:04 +0200 Subject: [PATCH 149/364] Cdefines for faps arent needed anymore --- scripts/fbt_tools/fbt_extapps.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/scripts/fbt_tools/fbt_extapps.py b/scripts/fbt_tools/fbt_extapps.py index 65143af0b..8c8a07655 100644 --- a/scripts/fbt_tools/fbt_extapps.py +++ b/scripts/fbt_tools/fbt_extapps.py @@ -52,9 +52,6 @@ class AppBuilder: self.app_env = self.fw_env.Clone( FAP_SRC_DIR=self.app._appdir, FAP_WORK_DIR=self.app_work_dir ) - self.app_env.Append( - CPPDEFINES=self.app.cdefines, - ) self.app_env.VariantDir(self.app_work_dir, self.app._appdir, duplicate=False) def _build_external_files(self): From 0443cedc7bfe5e8c3525bfc763dff58147046819 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 14 Jul 2023 03:56:12 +0200 Subject: [PATCH 150/364] Disable tanksgame for now --- .../{tanksgame => .tanksgame}/application.fam | 0 .../external/{tanksgame => .tanksgame}/constants.h | 0 .../images/HappyFlipper_128x64.png | Bin .../images/TanksSplashScreen_128x64.png | Bin .../{tanksgame => .tanksgame}/images/enemy_down.png | Bin .../{tanksgame => .tanksgame}/images/enemy_left.png | Bin .../images/enemy_right.png | Bin .../{tanksgame => .tanksgame}/images/enemy_up.png | Bin .../images/projectile_down.png | Bin .../images/projectile_left.png | Bin .../images/projectile_right.png | Bin .../images/projectile_up.png | Bin .../{tanksgame => .tanksgame}/images/tank_base.png | Bin .../{tanksgame => .tanksgame}/images/tank_down.png | Bin .../images/tank_explosion.png | Bin .../images/tank_hedgehog.png | Bin .../{tanksgame => .tanksgame}/images/tank_left.png | Bin .../{tanksgame => .tanksgame}/images/tank_right.png | Bin .../{tanksgame => .tanksgame}/images/tank_stone.png | Bin .../{tanksgame => .tanksgame}/images/tank_up.png | Bin .../{tanksgame => .tanksgame}/images/tank_wall.png | Bin .../{tanksgame => .tanksgame}/tanksIcon.png | Bin .../external/{tanksgame => .tanksgame}/tanks_game.c | 0 23 files changed, 0 insertions(+), 0 deletions(-) rename applications/external/{tanksgame => .tanksgame}/application.fam (100%) rename applications/external/{tanksgame => .tanksgame}/constants.h (100%) rename applications/external/{tanksgame => .tanksgame}/images/HappyFlipper_128x64.png (100%) rename applications/external/{tanksgame => .tanksgame}/images/TanksSplashScreen_128x64.png (100%) rename applications/external/{tanksgame => .tanksgame}/images/enemy_down.png (100%) rename applications/external/{tanksgame => .tanksgame}/images/enemy_left.png (100%) rename applications/external/{tanksgame => .tanksgame}/images/enemy_right.png (100%) rename applications/external/{tanksgame => .tanksgame}/images/enemy_up.png (100%) rename applications/external/{tanksgame => .tanksgame}/images/projectile_down.png (100%) rename applications/external/{tanksgame => .tanksgame}/images/projectile_left.png (100%) rename applications/external/{tanksgame => .tanksgame}/images/projectile_right.png (100%) rename applications/external/{tanksgame => .tanksgame}/images/projectile_up.png (100%) rename applications/external/{tanksgame => .tanksgame}/images/tank_base.png (100%) rename applications/external/{tanksgame => .tanksgame}/images/tank_down.png (100%) rename applications/external/{tanksgame => .tanksgame}/images/tank_explosion.png (100%) rename applications/external/{tanksgame => .tanksgame}/images/tank_hedgehog.png (100%) rename applications/external/{tanksgame => .tanksgame}/images/tank_left.png (100%) rename applications/external/{tanksgame => .tanksgame}/images/tank_right.png (100%) rename applications/external/{tanksgame => .tanksgame}/images/tank_stone.png (100%) rename applications/external/{tanksgame => .tanksgame}/images/tank_up.png (100%) rename applications/external/{tanksgame => .tanksgame}/images/tank_wall.png (100%) rename applications/external/{tanksgame => .tanksgame}/tanksIcon.png (100%) rename applications/external/{tanksgame => .tanksgame}/tanks_game.c (100%) diff --git a/applications/external/tanksgame/application.fam b/applications/external/.tanksgame/application.fam similarity index 100% rename from applications/external/tanksgame/application.fam rename to applications/external/.tanksgame/application.fam diff --git a/applications/external/tanksgame/constants.h b/applications/external/.tanksgame/constants.h similarity index 100% rename from applications/external/tanksgame/constants.h rename to applications/external/.tanksgame/constants.h diff --git a/applications/external/tanksgame/images/HappyFlipper_128x64.png b/applications/external/.tanksgame/images/HappyFlipper_128x64.png similarity index 100% rename from applications/external/tanksgame/images/HappyFlipper_128x64.png rename to applications/external/.tanksgame/images/HappyFlipper_128x64.png diff --git a/applications/external/tanksgame/images/TanksSplashScreen_128x64.png b/applications/external/.tanksgame/images/TanksSplashScreen_128x64.png similarity index 100% rename from applications/external/tanksgame/images/TanksSplashScreen_128x64.png rename to applications/external/.tanksgame/images/TanksSplashScreen_128x64.png diff --git a/applications/external/tanksgame/images/enemy_down.png b/applications/external/.tanksgame/images/enemy_down.png similarity index 100% rename from applications/external/tanksgame/images/enemy_down.png rename to applications/external/.tanksgame/images/enemy_down.png diff --git a/applications/external/tanksgame/images/enemy_left.png b/applications/external/.tanksgame/images/enemy_left.png similarity index 100% rename from applications/external/tanksgame/images/enemy_left.png rename to applications/external/.tanksgame/images/enemy_left.png diff --git a/applications/external/tanksgame/images/enemy_right.png b/applications/external/.tanksgame/images/enemy_right.png similarity index 100% rename from applications/external/tanksgame/images/enemy_right.png rename to applications/external/.tanksgame/images/enemy_right.png diff --git a/applications/external/tanksgame/images/enemy_up.png b/applications/external/.tanksgame/images/enemy_up.png similarity index 100% rename from applications/external/tanksgame/images/enemy_up.png rename to applications/external/.tanksgame/images/enemy_up.png diff --git a/applications/external/tanksgame/images/projectile_down.png b/applications/external/.tanksgame/images/projectile_down.png similarity index 100% rename from applications/external/tanksgame/images/projectile_down.png rename to applications/external/.tanksgame/images/projectile_down.png diff --git a/applications/external/tanksgame/images/projectile_left.png b/applications/external/.tanksgame/images/projectile_left.png similarity index 100% rename from applications/external/tanksgame/images/projectile_left.png rename to applications/external/.tanksgame/images/projectile_left.png diff --git a/applications/external/tanksgame/images/projectile_right.png b/applications/external/.tanksgame/images/projectile_right.png similarity index 100% rename from applications/external/tanksgame/images/projectile_right.png rename to applications/external/.tanksgame/images/projectile_right.png diff --git a/applications/external/tanksgame/images/projectile_up.png b/applications/external/.tanksgame/images/projectile_up.png similarity index 100% rename from applications/external/tanksgame/images/projectile_up.png rename to applications/external/.tanksgame/images/projectile_up.png diff --git a/applications/external/tanksgame/images/tank_base.png b/applications/external/.tanksgame/images/tank_base.png similarity index 100% rename from applications/external/tanksgame/images/tank_base.png rename to applications/external/.tanksgame/images/tank_base.png diff --git a/applications/external/tanksgame/images/tank_down.png b/applications/external/.tanksgame/images/tank_down.png similarity index 100% rename from applications/external/tanksgame/images/tank_down.png rename to applications/external/.tanksgame/images/tank_down.png diff --git a/applications/external/tanksgame/images/tank_explosion.png b/applications/external/.tanksgame/images/tank_explosion.png similarity index 100% rename from applications/external/tanksgame/images/tank_explosion.png rename to applications/external/.tanksgame/images/tank_explosion.png diff --git a/applications/external/tanksgame/images/tank_hedgehog.png b/applications/external/.tanksgame/images/tank_hedgehog.png similarity index 100% rename from applications/external/tanksgame/images/tank_hedgehog.png rename to applications/external/.tanksgame/images/tank_hedgehog.png diff --git a/applications/external/tanksgame/images/tank_left.png b/applications/external/.tanksgame/images/tank_left.png similarity index 100% rename from applications/external/tanksgame/images/tank_left.png rename to applications/external/.tanksgame/images/tank_left.png diff --git a/applications/external/tanksgame/images/tank_right.png b/applications/external/.tanksgame/images/tank_right.png similarity index 100% rename from applications/external/tanksgame/images/tank_right.png rename to applications/external/.tanksgame/images/tank_right.png diff --git a/applications/external/tanksgame/images/tank_stone.png b/applications/external/.tanksgame/images/tank_stone.png similarity index 100% rename from applications/external/tanksgame/images/tank_stone.png rename to applications/external/.tanksgame/images/tank_stone.png diff --git a/applications/external/tanksgame/images/tank_up.png b/applications/external/.tanksgame/images/tank_up.png similarity index 100% rename from applications/external/tanksgame/images/tank_up.png rename to applications/external/.tanksgame/images/tank_up.png diff --git a/applications/external/tanksgame/images/tank_wall.png b/applications/external/.tanksgame/images/tank_wall.png similarity index 100% rename from applications/external/tanksgame/images/tank_wall.png rename to applications/external/.tanksgame/images/tank_wall.png diff --git a/applications/external/tanksgame/tanksIcon.png b/applications/external/.tanksgame/tanksIcon.png similarity index 100% rename from applications/external/tanksgame/tanksIcon.png rename to applications/external/.tanksgame/tanksIcon.png diff --git a/applications/external/tanksgame/tanks_game.c b/applications/external/.tanksgame/tanks_game.c similarity index 100% rename from applications/external/tanksgame/tanks_game.c rename to applications/external/.tanksgame/tanks_game.c From 49167866eacf3413c39772377efae2b1936c6e03 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 14 Jul 2023 04:12:18 +0200 Subject: [PATCH 151/364] Fix default MENUEXTERNAL category --- scripts/fbt/appmanifest.py | 2 ++ scripts/fbt_tools/fbt_extapps.py | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/scripts/fbt/appmanifest.py b/scripts/fbt/appmanifest.py index 98f794453..5ed8c260b 100644 --- a/scripts/fbt/appmanifest.py +++ b/scripts/fbt/appmanifest.py @@ -393,6 +393,8 @@ class ApplicationsCGenerator: app_path = "/ext/apps" if app.fap_category: app_path += f"/{app.fap_category}" + else: + app_path += "/assets" app_path += f"/{app.appid}.fap" return f""" {{ diff --git a/scripts/fbt_tools/fbt_extapps.py b/scripts/fbt_tools/fbt_extapps.py index 8c8a07655..b48df6095 100644 --- a/scripts/fbt_tools/fbt_extapps.py +++ b/scripts/fbt_tools/fbt_extapps.py @@ -176,7 +176,8 @@ class AppBuilder: deployable = False app_artifacts.dist_entries.append((deployable, fal_path)) else: - fap_path = f"apps/{'assets' if self.app.apptype == FlipperAppType.MENUEXTERNAL else self.app.fap_category}/{app_artifacts.compact.name}" + category = 'assets' if self.app.apptype == FlipperAppType.MENUEXTERNAL and not self.app.fap_category else self.app.fap_category + fap_path = f"apps/{category}/{app_artifacts.compact.name}" app_artifacts.dist_entries.append( (self.app.is_default_deployable, fap_path) ) From e073c603a4bad53cbaca3c67d46c143a80779206 Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Fri, 14 Jul 2023 12:16:22 +0300 Subject: [PATCH 152/364] [FL-3334] Storage: explosive rename fix (#2876) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: ã‚ã --- .../services/storage/storage_external_api.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/applications/services/storage/storage_external_api.c b/applications/services/storage/storage_external_api.c index 5fcaa5921..585ded414 100644 --- a/applications/services/storage/storage_external_api.c +++ b/applications/services/storage/storage_external_api.c @@ -430,6 +430,20 @@ FS_Error storage_common_rename(Storage* storage, const char* old_path, const cha break; } + if(storage_dir_exists(storage, old_path)) { + FuriString* dir_path = furi_string_alloc_set_str(old_path); + if(!furi_string_end_with_str(dir_path, "/")) { + furi_string_cat_str(dir_path, "/"); + } + const char* dir_path_s = furi_string_get_cstr(dir_path); + if(strncmp(new_path, dir_path_s, strlen(dir_path_s)) == 0) { + error = FSE_INVALID_NAME; + furi_string_free(dir_path); + break; + } + furi_string_free(dir_path); + } + if(storage_file_exists(storage, new_path)) { storage_common_remove(storage, new_path); } From 521ef5659f489de6c08bc161d576cbee8d4b3bba Mon Sep 17 00:00:00 2001 From: gid9798 <30450294+gid9798@users.noreply.github.com> Date: Fri, 14 Jul 2023 12:28:09 +0300 Subject: [PATCH 153/364] SubRem Configurator: add TX --- .../application.fam | 6 +- .../helpers/subrem_custom_event.h | 3 +- .../helpers/txrx/subghz_txrx.c | 2 +- .../subghz_remote_configurator/icon.png | Bin 0 -> 5000 bytes .../scenes/subrem_scene_config.h | 1 + .../scenes/subrem_scene_open_map_file.c | 2 + .../scenes/subrem_scene_remote.c | 115 ++++++++++++++++++ .../scenes/subrem_scene_start.c | 29 ++++- .../subghz_remote_app.c | 11 +- .../subghz_remote_app_i.c | 54 +++++++- .../subghz_remote_app_i.h | 7 +- 11 files changed, 213 insertions(+), 17 deletions(-) create mode 100644 applications/external/subghz_remote_configurator/icon.png create mode 100644 applications/external/subghz_remote_configurator/scenes/subrem_scene_remote.c diff --git a/applications/external/subghz_remote_configurator/application.fam b/applications/external/subghz_remote_configurator/application.fam index f43dbcbd8..a39e4af4c 100644 --- a/applications/external/subghz_remote_configurator/application.fam +++ b/applications/external/subghz_remote_configurator/application.fam @@ -9,8 +9,8 @@ App( ], stack_size=2 * 1024, order=50, - fap_description="File Editor for the SubGhz Remote app", + fap_libs=["assets",], + fap_icon="icon.png", + fap_description="SubGhz Remote, uses up to 5 .sub files", fap_category="Sub-Ghz", - fap_icon_assets="icons", - fap_icon="icons/subrem_10px.png", ) \ No newline at end of file diff --git a/applications/external/subghz_remote_configurator/helpers/subrem_custom_event.h b/applications/external/subghz_remote_configurator/helpers/subrem_custom_event.h index da3de2aae..e6b9e8ac6 100644 --- a/applications/external/subghz_remote_configurator/helpers/subrem_custom_event.h +++ b/applications/external/subghz_remote_configurator/helpers/subrem_custom_event.h @@ -10,7 +10,8 @@ typedef enum { typedef enum { // StartSubmenuIndex - SubmenuIndexSubRemEditMapFile = 0, + SubmenuIndexSubRemOpenMapFile = 0, + SubmenuIndexSubRemEditMapFile, SubmenuIndexSubRemNewMapFile, #if FURI_DEBUG SubmenuIndexSubRemRemoteView, diff --git a/applications/external/subghz_remote_configurator/helpers/txrx/subghz_txrx.c b/applications/external/subghz_remote_configurator/helpers/txrx/subghz_txrx.c index db485a2aa..8def9cf01 100644 --- a/applications/external/subghz_remote_configurator/helpers/txrx/subghz_txrx.c +++ b/applications/external/subghz_remote_configurator/helpers/txrx/subghz_txrx.c @@ -66,7 +66,7 @@ SubGhzTxRx* subghz_txrx_alloc() { subghz_devices_init(); instance->radio_device_type = SubGhzRadioDeviceTypeInternal; instance->radio_device_type = - subghz_txrx_radio_device_set(instance, SubGhzRadioDeviceTypeInternal); + subghz_txrx_radio_device_set(instance, SubGhzRadioDeviceTypeExternalCC1101); return instance; } diff --git a/applications/external/subghz_remote_configurator/icon.png b/applications/external/subghz_remote_configurator/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..c6b410f4c598d6b241b851826875a568c74f4d20 GIT binary patch literal 5000 zcmeHLeQXow8NVhhfsmvNbWJE`yEq*mNzeCX`_4BziDT!(3JxJLO+Xvv_1$~UEw<0t zm)N1})-a|4OCbE6;07#-3sX@sAxkg$U?hnu$6%_P+PX_Jv(ud zLf1xG|CwUz?7R1Up5OC4zxVgNce*W&4YheW_vK(1mglK+H=%$1JZE+m`hA@F<1zI2 zyA8fptqH{ONK}=TAjGw<2*hDRkufZBKGgVD-U({Lf_l{mhyV1gGrhn5dOvmG&rb;X|2#O* z`;Gdu`y8=lFC6>o;MdlzEhu;(gUPC_z|AEGi;B+`t?Ze5z3-kKC+@xe!rsi0?9s|K zy?-daKNrL9+N8K#jUJb4ydqS`GmmU@)cv;7aPpz%>TUZsFY*}}-;x*iZ59ty6_jpT zvujoMQ}z8jJ+AG;!%Gj}Yq-_gD;(ypTplW&z40q}pQ&N1scCq0d(~q_cR%sbwf8Sv zdVdlA`l;oI1plLZ-jYiT3faL`zr6A#=Lo=7=AJsu{N?^-b1q)%coKW)>T~u}qi^rn z-7>H`clPEJwEVR7TGqAGdqR;5OY#pr*E?^={1s1Y&f(g=vM=|qHywW9AEyugs9|9K z_qUv^T38l3y>(BG-D_BB`RVoV^}JI09`V|mBd`AW<~wBWyCXky1n0|1Nlg+*V)QGN;EdcVFdq|MubW(V_Tn9{lz<&(!Cf?0&8A zl@E$Ck9Ky~46J|Y$whnDXUy8sU3Tos>?t>Un9|+}sNpj`p?cz$4F;W6I^yu1td=)j zwphHBH{ybAO5KJiY~Ik|6F0PrHpy5~o?}l42p|MCfG0x1a7;)zj7eMpo$JG-5l@?` zHXBJXB*PHMf{1m6HIN{}u@W63h2e%VF{(r~MGfORCh)5rn!{*B^Z0mvp@`R;h7ZTa zSU`M`2@oM^6GetXP{HeN+v@{V%k5_5e+8G zkwg*(VF;PVP*i$K$XbuLG3}vK5Kuyqq!%K4ie;ot)zny<8cCZ^NiaQ~ENpU0nj%lI zJjF+!xy>BKy>oC09YBCl_0@MKq5HSOc8#H zHxrPt@G@uPXxUw}=@F^kKtO1=<+RE`fZLx72 zM^h}%PZ&K2qcJ389h0U^tT{O|!J$hHs!^{hL5Gn|PU-6=pgIxrK<@yAog7DH3a%&w z8g!!r!BMD#C>udrd^9VVErOXZqga7TC7!lcqdrv)I*fX4xSm29%!}Gu0vbreA!k;g z%|4nF%vOQs$|!0w97;_$K_%JJIG$`y z0f?!B#blXMGE;<>npEzfp3l7GX_S~MYjF^T&H&=qVRY(yC*C;TeK^CKEcntEB`m4& z*s`e!#M_|0il0b3`57vUflm0by2LgR4nVX&k8KG5wO*Mw+x#3L%ravoDAo)K9*8VK z`z@R#$`oXol=A*h>SY-g(0){rAj zX|i`eX-Qc`CULv;$ClJi>UW`W?b^xP)oq_>=<%(|iFP_&{;^5&uL6OoA}L2u$;}G@ zRN$B(Zj5Yq}83M;=f=r9w8MiVD2l{4`U z0fy0oX&k*FI5pSMi{36|`Ri-l*r@*9d2H`fXk<>LZgmX9OeOkpSK|4KPBfUUdA!xx z?`7r}mfscene_manager, SubRemSceneEditMenu, SubRemSubKeyNameUp); scene_manager_next_scene(app->scene_manager, SubRemSceneEditMenu); + } else if(start_scene_state == SubmenuIndexSubRemOpenMapFile) { + scene_manager_next_scene(app->scene_manager, SubRemSceneRemote); } } diff --git a/applications/external/subghz_remote_configurator/scenes/subrem_scene_remote.c b/applications/external/subghz_remote_configurator/scenes/subrem_scene_remote.c new file mode 100644 index 000000000..ebc582991 --- /dev/null +++ b/applications/external/subghz_remote_configurator/scenes/subrem_scene_remote.c @@ -0,0 +1,115 @@ +#include "../subghz_remote_app_i.h" +#include "../views/remote.h" + +#include + +#define TAG "SubRemScenRemote" + +void subrem_scene_remote_callback(SubRemCustomEvent event, void* context) { + furi_assert(context); + SubGhzRemoteApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, event); +} + +void subrem_scene_remote_raw_callback_end_tx(void* context) { + furi_assert(context); + SubGhzRemoteApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, SubRemCustomEventViewRemoteForcedStop); +} + +static uint8_t subrem_scene_remote_event_to_index(SubRemCustomEvent event_id) { + uint8_t ret = 0; + + if(event_id == SubRemCustomEventViewRemoteStartUP) { + ret = SubRemSubKeyNameUp; + } else if(event_id == SubRemCustomEventViewRemoteStartDOWN) { + ret = SubRemSubKeyNameDown; + } else if(event_id == SubRemCustomEventViewRemoteStartLEFT) { + ret = SubRemSubKeyNameLeft; + } else if(event_id == SubRemCustomEventViewRemoteStartRIGHT) { + ret = SubRemSubKeyNameRight; + } else if(event_id == SubRemCustomEventViewRemoteStartOK) { + ret = SubRemSubKeyNameOk; + } + + return ret; +} + +void subrem_scene_remote_on_enter(void* context) { + SubGhzRemoteApp* app = context; + + subrem_view_remote_update_data_labels(app->subrem_remote_view, app->map_preset->subs_preset); + + subrem_view_remote_set_callback(app->subrem_remote_view, subrem_scene_remote_callback, app); + + view_dispatcher_switch_to_view(app->view_dispatcher, SubRemViewIDRemote); +} + +bool subrem_scene_remote_on_event(void* context, SceneManagerEvent event) { + SubGhzRemoteApp* app = context; + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubRemCustomEventViewRemoteBack) { + if(!scene_manager_previous_scene(app->scene_manager)) { + scene_manager_stop(app->scene_manager); + view_dispatcher_stop(app->view_dispatcher); + } + return true; + } else if( + event.event == SubRemCustomEventViewRemoteStartUP || + event.event == SubRemCustomEventViewRemoteStartDOWN || + event.event == SubRemCustomEventViewRemoteStartLEFT || + event.event == SubRemCustomEventViewRemoteStartRIGHT || + event.event == SubRemCustomEventViewRemoteStartOK) { + // Start sending sub + subrem_tx_stop_sub(app, true); + + uint8_t chusen_sub = subrem_scene_remote_event_to_index(event.event); + app->chusen_sub = chusen_sub; + + subrem_view_remote_set_state( + app->subrem_remote_view, SubRemViewRemoteStateLoading, chusen_sub); + + if(subrem_tx_start_sub(app, app->map_preset->subs_preset[chusen_sub])) { + if(app->map_preset->subs_preset[chusen_sub]->type == SubGhzProtocolTypeRAW) { + subghz_txrx_set_raw_file_encoder_worker_callback_end( + app->txrx, subrem_scene_remote_raw_callback_end_tx, app); + } + subrem_view_remote_set_state( + app->subrem_remote_view, SubRemViewRemoteStateSending, chusen_sub); + notification_message(app->notifications, &sequence_blink_start_magenta); + } else { + subrem_view_remote_set_state( + app->subrem_remote_view, SubRemViewRemoteStateIdle, 0); + notification_message(app->notifications, &sequence_blink_red_100); + } + return true; + } else if(event.event == SubRemCustomEventViewRemoteForcedStop) { + subrem_tx_stop_sub(app, true); + subrem_view_remote_set_state(app->subrem_remote_view, SubRemViewRemoteStateIdle, 0); + + notification_message(app->notifications, &sequence_blink_stop); + return true; + } else if(event.event == SubRemCustomEventViewRemoteStop) { + if(subrem_tx_stop_sub(app, false)) { + subrem_view_remote_set_state( + app->subrem_remote_view, SubRemViewRemoteStateIdle, 0); + + notification_message(app->notifications, &sequence_blink_stop); + } + return true; + } + } + // } else if(event.type == SceneManagerEventTypeTick) { + // } + return false; +} + +void subrem_scene_remote_on_exit(void* context) { + SubGhzRemoteApp* app = context; + + subrem_tx_stop_sub(app, true); + + subrem_view_remote_set_state(app->subrem_remote_view, SubRemViewRemoteStateIdle, 0); + + notification_message(app->notifications, &sequence_blink_stop); +} diff --git a/applications/external/subghz_remote_configurator/scenes/subrem_scene_start.c b/applications/external/subghz_remote_configurator/scenes/subrem_scene_start.c index e5a254111..0f3399b7c 100644 --- a/applications/external/subghz_remote_configurator/scenes/subrem_scene_start.c +++ b/applications/external/subghz_remote_configurator/scenes/subrem_scene_start.c @@ -13,6 +13,20 @@ void subrem_scene_start_on_enter(void* context) { SubGhzRemoteApp* app = context; Submenu* submenu = app->submenu; + submenu_add_item( + submenu, + "Open Map File", + SubmenuIndexSubRemOpenMapFile, + subrem_scene_start_submenu_callback, + app); +#if FURI_DEBUG + submenu_add_item( + submenu, + "Remote_Debug", + SubmenuIndexSubRemRemoteView, + subrem_scene_start_submenu_callback, + app); +#endif submenu_add_item( submenu, "Edit Map File", @@ -45,7 +59,20 @@ bool subrem_scene_start_on_event(void* context, SceneManagerEvent event) { bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SubmenuIndexSubRemEditMapFile) { + if(event.event == SubmenuIndexSubRemOpenMapFile) { + scene_manager_set_scene_state( + app->scene_manager, SubRemSceneStart, SubmenuIndexSubRemOpenMapFile); + + scene_manager_next_scene(app->scene_manager, SubRemSceneOpenMapFile); + consumed = true; + } +#if FURI_DEBUG + else if(event.event == SubmenuIndexSubRemRemoteView) { + scene_manager_next_scene(app->scene_manager, SubRemSceneRemote); + consumed = true; + } +#endif + else if(event.event == SubmenuIndexSubRemEditMapFile) { scene_manager_set_scene_state( app->scene_manager, SubRemSceneStart, SubmenuIndexSubRemEditMapFile); scene_manager_next_scene(app->scene_manager, SubRemSceneOpenMapFile); diff --git a/applications/external/subghz_remote_configurator/subghz_remote_app.c b/applications/external/subghz_remote_configurator/subghz_remote_app.c index ba71b134b..f268fea78 100644 --- a/applications/external/subghz_remote_configurator/subghz_remote_app.c +++ b/applications/external/subghz_remote_configurator/subghz_remote_app.c @@ -29,7 +29,7 @@ SubGhzRemoteApp* subghz_remote_app_alloc() { } furi_record_close(RECORD_STORAGE); - // furi_hal_power_suppress_charge_enter(); + furi_hal_power_suppress_charge_enter(); app->file_path = furi_string_alloc(); furi_string_set(app->file_path, SUBREM_APP_FOLDER); @@ -103,13 +103,8 @@ SubGhzRemoteApp* subghz_remote_app_alloc() { app->map_not_saved = false; -#ifdef SUBREM_LIGHT - scene_manager_next_scene(app->scene_manager, SubRemSceneOpenMapFile); -#else scene_manager_next_scene(app->scene_manager, SubRemSceneStart); - scene_manager_set_scene_state( - app->scene_manager, SubRemSceneStart, SubmenuIndexSubRemEditMapFile); -#endif + scene_manager_next_scene(app->scene_manager, SubRemSceneOpenMapFile); return app; } @@ -117,7 +112,7 @@ SubGhzRemoteApp* subghz_remote_app_alloc() { void subghz_remote_app_free(SubGhzRemoteApp* app) { furi_assert(app); - // furi_hal_power_suppress_charge_exit(); + furi_hal_power_suppress_charge_exit(); // Submenu view_dispatcher_remove_view(app->view_dispatcher, SubRemViewIDSubmenu); diff --git a/applications/external/subghz_remote_configurator/subghz_remote_app_i.c b/applications/external/subghz_remote_configurator/subghz_remote_app_i.c index 1eb64541f..82e762c2a 100644 --- a/applications/external/subghz_remote_configurator/subghz_remote_app_i.c +++ b/applications/external/subghz_remote_configurator/subghz_remote_app_i.c @@ -7,8 +7,8 @@ // #include // #include -// #include -// #include +#include +#include #define TAG "SubGhzRemote" @@ -204,6 +204,56 @@ void subrem_save_active_sub(void* context) { sub_preset->fff_data, furi_string_get_cstr(sub_preset->file_path)); } +bool subrem_tx_start_sub(SubGhzRemoteApp* app, SubRemSubFilePreset* sub_preset) { + furi_assert(app); + furi_assert(sub_preset); + bool ret = false; + + subrem_tx_stop_sub(app, true); + + if(sub_preset->type == SubGhzProtocolTypeUnknown) { + ret = false; + } else { + FURI_LOG_I(TAG, "Send %s", furi_string_get_cstr(sub_preset->label)); + + subghz_txrx_load_decoder_by_name_protocol( + app->txrx, furi_string_get_cstr(sub_preset->protocaol_name)); + + subghz_txrx_set_preset( + app->txrx, + furi_string_get_cstr(sub_preset->freq_preset.name), + sub_preset->freq_preset.frequency, + NULL, + 0); + + subghz_custom_btns_reset(); + + if(subghz_txrx_tx_start(app->txrx, sub_preset->fff_data) == SubGhzTxRxStartTxStateOk) { + ret = true; + } + } + + return ret; +} + +bool subrem_tx_stop_sub(SubGhzRemoteApp* app, bool forced) { + furi_assert(app); + SubRemSubFilePreset* sub_preset = app->map_preset->subs_preset[app->chusen_sub]; + + if(forced || (sub_preset->type != SubGhzProtocolTypeRAW)) { + subghz_txrx_stop(app->txrx); + + if(sub_preset->type == SubGhzProtocolTypeDynamic) { + subghz_txrx_reset_dynamic_and_custom_btns(app->txrx); + } + subghz_custom_btns_reset(); + + return true; + } + + return false; +} + SubRemLoadMapState subrem_load_from_file(SubGhzRemoteApp* app) { furi_assert(app); diff --git a/applications/external/subghz_remote_configurator/subghz_remote_app_i.h b/applications/external/subghz_remote_configurator/subghz_remote_app_i.h index a84e1ba50..d200bdf96 100644 --- a/applications/external/subghz_remote_configurator/subghz_remote_app_i.h +++ b/applications/external/subghz_remote_configurator/subghz_remote_app_i.h @@ -5,7 +5,8 @@ #include "scenes/subrem_scene.h" #include "helpers/txrx/subghz_txrx.h" -#include + +#include #include "views/remote.h" #include "views/edit_menu.h" @@ -54,6 +55,10 @@ typedef struct { SubRemLoadMapState subrem_load_from_file(SubGhzRemoteApp* app); +bool subrem_tx_start_sub(SubGhzRemoteApp* app, SubRemSubFilePreset* sub_preset); + +bool subrem_tx_stop_sub(SubGhzRemoteApp* app, bool forced); + SubRemLoadMapState subrem_map_file_load(SubGhzRemoteApp* app, const char* file_path); void subrem_map_preset_reset(SubRemMapPreset* map_preset); From f49cad314e629d4e29dc94157b242c65e29a3405 Mon Sep 17 00:00:00 2001 From: gid9798 <30450294+gid9798@users.noreply.github.com> Date: Fri, 14 Jul 2023 12:59:58 +0300 Subject: [PATCH 154/364] SubRem: swap apps --- .../application.fam | 16 - .../helpers/subrem_custom_event.h | 51 -- .../helpers/subrem_presets.c | 181 ----- .../helpers/subrem_presets.h | 39 - .../helpers/subrem_types.h | 48 -- .../helpers/txrx/Readme.md | 4 - .../helpers/txrx/subghz_txrx.c | 670 ------------------ .../helpers/txrx/subghz_txrx.h | 375 ---------- .../helpers/txrx/subghz_txrx_i.h | 31 - .../subghz_remote_configurator/icon.png | Bin 5000 -> 0 bytes .../icons/DolphinNice_96x59.png | Bin 2459 -> 0 bytes .../icons/remote_scene/ButtonDown_7x4.png | Bin 102 -> 0 bytes .../icons/remote_scene/ButtonLeft_4x7.png | Bin 1415 -> 0 bytes .../icons/remote_scene/ButtonRight_4x7.png | Bin 1839 -> 0 bytes .../icons/remote_scene/ButtonUp_7x4.png | Bin 102 -> 0 bytes .../icons/remote_scene/Ok_btn_9x9.png | Bin 3605 -> 0 bytes .../icons/remote_scene/Pin_arrow_up_7x9.png | Bin 3603 -> 0 bytes .../icons/remote_scene/Pin_cell_13x13.png | Bin 3593 -> 0 bytes .../icons/remote_scene/Pin_star_7x7.png | Bin 3600 -> 0 bytes .../icons/remote_scene/back_10px.png | Bin 154 -> 0 bytes .../icons/sub1_10px.png | Bin 299 -> 0 bytes .../icons/subrem_10px.png | Bin 5000 -> 0 bytes .../scenes/subrem_scene.c | 30 - .../scenes/subrem_scene.h | 29 - .../scenes/subrem_scene_config.h | 9 - .../scenes/subrem_scene_open_map_file.c | 31 - .../scenes/subrem_scene_remote.c | 115 --- .../scenes/subrem_scene_start.c | 100 --- .../subghz_remote_app.c | 178 ----- .../subghz_remote_app_i.c | 320 --------- .../subghz_remote_app_i.h | 68 -- .../subghz_remote_configurator/views/remote.c | 294 -------- .../subghz_remote_configurator/views/remote.h | 36 - .../main/subghz_remote/application.fam | 7 +- .../helpers/subrem_custom_event.h | 31 + .../main/subghz_remote/helpers/subrem_types.h | 4 + .../scenes/subrem_scene_config.h | 10 +- .../scenes/subrem_scene_edit_label.c | 0 .../scenes/subrem_scene_edit_menu.c | 0 .../scenes/subrem_scene_edit_preview.c | 0 .../scenes/subrem_scene_edit_submenu.c | 0 .../scenes/subrem_scene_enter_new_name.c | 0 .../scenes/subrem_scene_open_map_file.c | 32 +- .../scenes/subrem_scene_open_sub_file.c | 0 .../subghz_remote/scenes/subrem_scene_start.c | 38 +- .../main/subghz_remote/subghz_remote_app.c | 60 +- .../main/subghz_remote/subghz_remote_app_i.c | 54 +- .../main/subghz_remote/subghz_remote_app_i.h | 21 +- .../subghz_remote}/views/edit_menu.c | 0 .../subghz_remote}/views/edit_menu.h | 0 50 files changed, 189 insertions(+), 2693 deletions(-) delete mode 100644 applications/external/subghz_remote_configurator/application.fam delete mode 100644 applications/external/subghz_remote_configurator/helpers/subrem_custom_event.h delete mode 100644 applications/external/subghz_remote_configurator/helpers/subrem_presets.c delete mode 100644 applications/external/subghz_remote_configurator/helpers/subrem_presets.h delete mode 100644 applications/external/subghz_remote_configurator/helpers/subrem_types.h delete mode 100644 applications/external/subghz_remote_configurator/helpers/txrx/Readme.md delete mode 100644 applications/external/subghz_remote_configurator/helpers/txrx/subghz_txrx.c delete mode 100644 applications/external/subghz_remote_configurator/helpers/txrx/subghz_txrx.h delete mode 100644 applications/external/subghz_remote_configurator/helpers/txrx/subghz_txrx_i.h delete mode 100644 applications/external/subghz_remote_configurator/icon.png delete mode 100644 applications/external/subghz_remote_configurator/icons/DolphinNice_96x59.png delete mode 100644 applications/external/subghz_remote_configurator/icons/remote_scene/ButtonDown_7x4.png delete mode 100644 applications/external/subghz_remote_configurator/icons/remote_scene/ButtonLeft_4x7.png delete mode 100644 applications/external/subghz_remote_configurator/icons/remote_scene/ButtonRight_4x7.png delete mode 100644 applications/external/subghz_remote_configurator/icons/remote_scene/ButtonUp_7x4.png delete mode 100644 applications/external/subghz_remote_configurator/icons/remote_scene/Ok_btn_9x9.png delete mode 100644 applications/external/subghz_remote_configurator/icons/remote_scene/Pin_arrow_up_7x9.png delete mode 100644 applications/external/subghz_remote_configurator/icons/remote_scene/Pin_cell_13x13.png delete mode 100644 applications/external/subghz_remote_configurator/icons/remote_scene/Pin_star_7x7.png delete mode 100644 applications/external/subghz_remote_configurator/icons/remote_scene/back_10px.png delete mode 100644 applications/external/subghz_remote_configurator/icons/sub1_10px.png delete mode 100644 applications/external/subghz_remote_configurator/icons/subrem_10px.png delete mode 100644 applications/external/subghz_remote_configurator/scenes/subrem_scene.c delete mode 100644 applications/external/subghz_remote_configurator/scenes/subrem_scene.h delete mode 100644 applications/external/subghz_remote_configurator/scenes/subrem_scene_config.h delete mode 100644 applications/external/subghz_remote_configurator/scenes/subrem_scene_open_map_file.c delete mode 100644 applications/external/subghz_remote_configurator/scenes/subrem_scene_remote.c delete mode 100644 applications/external/subghz_remote_configurator/scenes/subrem_scene_start.c delete mode 100644 applications/external/subghz_remote_configurator/subghz_remote_app.c delete mode 100644 applications/external/subghz_remote_configurator/subghz_remote_app_i.c delete mode 100644 applications/external/subghz_remote_configurator/subghz_remote_app_i.h delete mode 100644 applications/external/subghz_remote_configurator/views/remote.c delete mode 100644 applications/external/subghz_remote_configurator/views/remote.h rename applications/{external/subghz_remote_configurator => main/subghz_remote}/scenes/subrem_scene_edit_label.c (100%) rename applications/{external/subghz_remote_configurator => main/subghz_remote}/scenes/subrem_scene_edit_menu.c (100%) rename applications/{external/subghz_remote_configurator => main/subghz_remote}/scenes/subrem_scene_edit_preview.c (100%) rename applications/{external/subghz_remote_configurator => main/subghz_remote}/scenes/subrem_scene_edit_submenu.c (100%) rename applications/{external/subghz_remote_configurator => main/subghz_remote}/scenes/subrem_scene_enter_new_name.c (100%) rename applications/{external/subghz_remote_configurator => main/subghz_remote}/scenes/subrem_scene_open_sub_file.c (100%) rename applications/{external/subghz_remote_configurator => main/subghz_remote}/views/edit_menu.c (100%) rename applications/{external/subghz_remote_configurator => main/subghz_remote}/views/edit_menu.h (100%) diff --git a/applications/external/subghz_remote_configurator/application.fam b/applications/external/subghz_remote_configurator/application.fam deleted file mode 100644 index a39e4af4c..000000000 --- a/applications/external/subghz_remote_configurator/application.fam +++ /dev/null @@ -1,16 +0,0 @@ -App( - appid="subrem_configurator", - name="Remote Maker", - apptype=FlipperAppType.EXTERNAL, - entry_point="subghz_remote_config_app", - requires=[ - "gui", - "dialogs", - ], - stack_size=2 * 1024, - order=50, - fap_libs=["assets",], - fap_icon="icon.png", - fap_description="SubGhz Remote, uses up to 5 .sub files", - fap_category="Sub-Ghz", -) \ No newline at end of file diff --git a/applications/external/subghz_remote_configurator/helpers/subrem_custom_event.h b/applications/external/subghz_remote_configurator/helpers/subrem_custom_event.h deleted file mode 100644 index e6b9e8ac6..000000000 --- a/applications/external/subghz_remote_configurator/helpers/subrem_custom_event.h +++ /dev/null @@ -1,51 +0,0 @@ -#pragma once - -typedef enum { - SubRemEditMenuStateUP = 0, - SubRemEditMenuStateDOWN, - SubRemEditMenuStateLEFT, - SubRemEditMenuStateRIGHT, - SubRemEditMenuStateOK, -} SubRemEditMenuState; - -typedef enum { - // StartSubmenuIndex - SubmenuIndexSubRemOpenMapFile = 0, - SubmenuIndexSubRemEditMapFile, - SubmenuIndexSubRemNewMapFile, -#if FURI_DEBUG - SubmenuIndexSubRemRemoteView, -#endif - // SubmenuIndexSubRemAbout, - - // EditSubmenuIndex - EditSubmenuIndexEditLabel, - EditSubmenuIndexEditFile, - - // SubRemCustomEvent - SubRemCustomEventViewRemoteStartUP = 100, - SubRemCustomEventViewRemoteStartDOWN, - SubRemCustomEventViewRemoteStartLEFT, - SubRemCustomEventViewRemoteStartRIGHT, - SubRemCustomEventViewRemoteStartOK, - SubRemCustomEventViewRemoteBack, - SubRemCustomEventViewRemoteStop, - SubRemCustomEventViewRemoteForcedStop, - - SubRemCustomEventViewEditMenuBack, - SubRemCustomEventViewEditMenuUP, - SubRemCustomEventViewEditMenuDOWN, - SubRemCustomEventViewEditMenuEdit, - SubRemCustomEventViewEditMenuSave, - - SubRemCustomEventSceneEditsubmenu, - SubRemCustomEventSceneEditLabelInputDone, - SubRemCustomEventSceneEditLabelWidgetAcces, - SubRemCustomEventSceneEditLabelWidgetBack, - - SubRemCustomEventSceneEditOpenSubErrorPopup, - - SubRemCustomEventSceneEditPreviewSaved, - - SubRemCustomEventSceneNewName, -} SubRemCustomEvent; \ No newline at end of file diff --git a/applications/external/subghz_remote_configurator/helpers/subrem_presets.c b/applications/external/subghz_remote_configurator/helpers/subrem_presets.c deleted file mode 100644 index 75ced8e00..000000000 --- a/applications/external/subghz_remote_configurator/helpers/subrem_presets.c +++ /dev/null @@ -1,181 +0,0 @@ -#include "subrem_presets.h" - -#define TAG "SubRemPresets" - -SubRemSubFilePreset* subrem_sub_file_preset_alloc() { - SubRemSubFilePreset* sub_preset = malloc(sizeof(SubRemSubFilePreset)); - - sub_preset->fff_data = flipper_format_string_alloc(); - sub_preset->file_path = furi_string_alloc(); - sub_preset->protocaol_name = furi_string_alloc(); - sub_preset->label = furi_string_alloc(); - - sub_preset->freq_preset.name = furi_string_alloc(); - - sub_preset->type = SubGhzProtocolTypeUnknown; - sub_preset->load_state = SubRemLoadSubStateNotSet; - - return sub_preset; -} - -void subrem_sub_file_preset_free(SubRemSubFilePreset* sub_preset) { - furi_assert(sub_preset); - - furi_string_free(sub_preset->label); - furi_string_free(sub_preset->protocaol_name); - furi_string_free(sub_preset->file_path); - flipper_format_free(sub_preset->fff_data); - - furi_string_free(sub_preset->freq_preset.name); - - free(sub_preset); -} - -void subrem_sub_file_preset_reset(SubRemSubFilePreset* sub_preset) { - furi_assert(sub_preset); - - furi_string_set_str(sub_preset->label, ""); - furi_string_reset(sub_preset->protocaol_name); - furi_string_reset(sub_preset->file_path); - - Stream* fff_data_stream = flipper_format_get_raw_stream(sub_preset->fff_data); - stream_clean(fff_data_stream); - - sub_preset->type = SubGhzProtocolTypeUnknown; - sub_preset->load_state = SubRemLoadSubStateNotSet; -} - -SubRemLoadSubState subrem_sub_preset_load( - SubRemSubFilePreset* sub_preset, - SubGhzTxRx* txrx, - FlipperFormat* fff_data_file) { - furi_assert(sub_preset); - furi_assert(txrx); - furi_assert(fff_data_file); - - Stream* fff_data_stream = flipper_format_get_raw_stream(sub_preset->fff_data); - - SubRemLoadSubState ret; - FuriString* temp_str = furi_string_alloc(); - uint32_t temp_data32; - uint32_t repeat = 200; - - ret = SubRemLoadSubStateError; - - do { - stream_clean(fff_data_stream); - if(!flipper_format_read_header(fff_data_file, temp_str, &temp_data32)) { - FURI_LOG_E(TAG, "Missing or incorrect header"); - break; - } - - if(((!strcmp(furi_string_get_cstr(temp_str), SUBGHZ_KEY_FILE_TYPE)) || - (!strcmp(furi_string_get_cstr(temp_str), SUBGHZ_RAW_FILE_TYPE))) && - temp_data32 == SUBGHZ_KEY_FILE_VERSION) { - } else { - FURI_LOG_E(TAG, "Type or version mismatch"); - break; - } - - SubGhzSetting* setting = subghz_txrx_get_setting(txrx); - - //Load frequency or using default from settings - ret = SubRemLoadSubStateErrorFreq; - if(!flipper_format_read_uint32(fff_data_file, "Frequency", &temp_data32, 1)) { - FURI_LOG_W(TAG, "Cannot read frequency. Set default frequency"); - sub_preset->freq_preset.frequency = subghz_setting_get_default_frequency(setting); - } else if(!subghz_txrx_radio_device_is_frequecy_valid(txrx, temp_data32)) { - FURI_LOG_E(TAG, "Frequency not supported on chosen radio module"); - break; - } - sub_preset->freq_preset.frequency = temp_data32; - - //Load preset - ret = SubRemLoadSubStateErrorMod; - if(!flipper_format_read_string(fff_data_file, "Preset", temp_str)) { - FURI_LOG_E(TAG, "Missing Preset"); - break; - } - - furi_string_set_str( - temp_str, subghz_txrx_get_preset_name(txrx, furi_string_get_cstr(temp_str))); - if(!strcmp(furi_string_get_cstr(temp_str), "")) { - break; - } - - if(!strcmp(furi_string_get_cstr(temp_str), "CUSTOM")) { - FURI_LOG_E(TAG, "CUSTOM preset is not supported"); - break; - // TODO Custom preset loading logic if need - // sub_preset->freq_preset.preset_index = - // subghz_setting_get_inx_preset_by_name(setting, furi_string_get_cstr(temp_str)); - } - - furi_string_set(sub_preset->freq_preset.name, temp_str); - - // Load protocol - ret = SubRemLoadSubStateErrorProtocol; - if(!flipper_format_read_string(fff_data_file, "Protocol", temp_str)) { - FURI_LOG_E(TAG, "Missing Protocol"); - break; - } - - FlipperFormat* fff_data = sub_preset->fff_data; - if(!strcmp(furi_string_get_cstr(temp_str), "RAW")) { - //if RAW - subghz_protocol_raw_gen_fff_data( - fff_data, - furi_string_get_cstr(sub_preset->file_path), - subghz_txrx_radio_device_get_name(txrx)); - } else { - stream_copy_full( - flipper_format_get_raw_stream(fff_data_file), - flipper_format_get_raw_stream(fff_data)); - } - - if(subghz_txrx_load_decoder_by_name_protocol(txrx, furi_string_get_cstr(temp_str))) { - SubGhzProtocolStatus status = - subghz_protocol_decoder_base_deserialize(subghz_txrx_get_decoder(txrx), fff_data); - if(status != SubGhzProtocolStatusOk) { - break; - } - } else { - FURI_LOG_E(TAG, "Protocol not found"); - break; - } - - const SubGhzProtocol* protocol = subghz_txrx_get_decoder(txrx)->protocol; - - if(protocol->flag & SubGhzProtocolFlag_Send) { - if((protocol->type == SubGhzProtocolTypeStatic) || - (protocol->type == SubGhzProtocolTypeDynamic) || - (protocol->type == SubGhzProtocolTypeBinRAW) || - (protocol->type == SubGhzProtocolTypeRAW)) { - sub_preset->type = protocol->type; - } else { - FURI_LOG_E(TAG, "Unsuported Protocol"); - break; - } - - furi_string_set(sub_preset->protocaol_name, temp_str); - } else { - FURI_LOG_E(TAG, "Protocol does not support transmission"); - break; - } - - if(!flipper_format_insert_or_update_uint32(fff_data, "Repeat", &repeat, 1)) { - FURI_LOG_E(TAG, "Unable Repeat"); - break; - } - - ret = SubRemLoadSubStateOK; - -#if FURI_DEBUG - FURI_LOG_I(TAG, "%-16s - protocol Loaded", furi_string_get_cstr(sub_preset->label)); -#endif - } while(false); - - furi_string_free(temp_str); - sub_preset->load_state = ret; - return ret; -} diff --git a/applications/external/subghz_remote_configurator/helpers/subrem_presets.h b/applications/external/subghz_remote_configurator/helpers/subrem_presets.h deleted file mode 100644 index d66181b90..000000000 --- a/applications/external/subghz_remote_configurator/helpers/subrem_presets.h +++ /dev/null @@ -1,39 +0,0 @@ -#pragma once - -#include "subrem_types.h" -#include "txrx/subghz_txrx.h" - -#include -#include - -typedef struct { - FuriString* name; - uint32_t frequency; - // size_t preset_index; // Need for custom preset -} FreqPreset; - -// Sub File preset -typedef struct { - FlipperFormat* fff_data; - FreqPreset freq_preset; - FuriString* file_path; - FuriString* protocaol_name; - FuriString* label; - SubGhzProtocolType type; - SubRemLoadSubState load_state; -} SubRemSubFilePreset; - -typedef struct { - SubRemSubFilePreset* subs_preset[SubRemSubKeyNameMaxCount]; -} SubRemMapPreset; - -SubRemSubFilePreset* subrem_sub_file_preset_alloc(); - -void subrem_sub_file_preset_free(SubRemSubFilePreset* sub_preset); - -void subrem_sub_file_preset_reset(SubRemSubFilePreset* sub_preset); - -SubRemLoadSubState subrem_sub_preset_load( - SubRemSubFilePreset* sub_preset, - SubGhzTxRx* txrx, - FlipperFormat* fff_data_file); diff --git a/applications/external/subghz_remote_configurator/helpers/subrem_types.h b/applications/external/subghz_remote_configurator/helpers/subrem_types.h deleted file mode 100644 index b43f8499d..000000000 --- a/applications/external/subghz_remote_configurator/helpers/subrem_types.h +++ /dev/null @@ -1,48 +0,0 @@ -#pragma once - -#include -#include - -#define SUBREM_APP_APP_FILE_VERSION 1 -#define SUBREM_APP_APP_FILE_TYPE "Flipper SubRem Map file" -#define SUBREM_APP_EXTENSION ".txt" - -typedef enum { - SubRemSubKeyNameUp = (0U), - SubRemSubKeyNameDown, - SubRemSubKeyNameLeft, - SubRemSubKeyNameRight, - SubRemSubKeyNameOk, - SubRemSubKeyNameMaxCount, -} SubRemSubKeyName; - -typedef enum { - SubRemViewIDSubmenu, - SubRemViewIDWidget, - SubRemViewIDPopup, - SubRemViewIDTextInput, - SubRemViewIDRemote, - SubRemViewIDEditMenu, -} SubRemViewID; - -typedef enum { - SubRemLoadSubStateNotSet = 0, - SubRemLoadSubStatePreloaded, - SubRemLoadSubStateError, - SubRemLoadSubStateErrorIncorectPath, - SubRemLoadSubStateErrorNoFile, - SubRemLoadSubStateErrorFreq, - SubRemLoadSubStateErrorMod, - SubRemLoadSubStateErrorProtocol, - SubRemLoadSubStateOK, -} SubRemLoadSubState; - -typedef enum { - SubRemLoadMapStateBack = 0, - SubRemLoadMapStateError, - SubRemLoadMapStateErrorOpenError, - SubRemLoadMapStateErrorStorage, - SubRemLoadMapStateErrorBrokenFile, - SubRemLoadMapStateNotAllOK, - SubRemLoadMapStateOK, -} SubRemLoadMapState; \ No newline at end of file diff --git a/applications/external/subghz_remote_configurator/helpers/txrx/Readme.md b/applications/external/subghz_remote_configurator/helpers/txrx/Readme.md deleted file mode 100644 index 918160198..000000000 --- a/applications/external/subghz_remote_configurator/helpers/txrx/Readme.md +++ /dev/null @@ -1,4 +0,0 @@ -This is part of the official `SubGhz` app from [flipperzero-firmware](https://github.com/flipperdevices/flipperzero-firmware/tree/3217f286f03da119398586daf94c0723d28b872a/applications/main/subghz) - -With changes from [unleashed-firmware -](https://github.com/DarkFlippers/unleashed-firmware/tree/3eac6ccd48a3851cf5d63bf7899b387a293e5319/applications/main/subghz) \ No newline at end of file diff --git a/applications/external/subghz_remote_configurator/helpers/txrx/subghz_txrx.c b/applications/external/subghz_remote_configurator/helpers/txrx/subghz_txrx.c deleted file mode 100644 index 8def9cf01..000000000 --- a/applications/external/subghz_remote_configurator/helpers/txrx/subghz_txrx.c +++ /dev/null @@ -1,670 +0,0 @@ -#include "subghz_txrx_i.h" - -#include -#include -#include - -#include - -#define TAG "SubGhz" - -static void subghz_txrx_radio_device_power_on(SubGhzTxRx* instance) { - UNUSED(instance); - uint8_t attempts = 0; - while(!furi_hal_power_is_otg_enabled() && attempts++ < 5) { - furi_hal_power_enable_otg(); - //CC1101 power-up time - furi_delay_ms(10); - } -} - -static void subghz_txrx_radio_device_power_off(SubGhzTxRx* instance) { - UNUSED(instance); - if(furi_hal_power_is_otg_enabled()) furi_hal_power_disable_otg(); -} - -SubGhzTxRx* subghz_txrx_alloc() { - SubGhzTxRx* instance = malloc(sizeof(SubGhzTxRx)); - instance->setting = subghz_setting_alloc(); - subghz_setting_load(instance->setting, EXT_PATH("subghz/assets/setting_user")); - - instance->preset = malloc(sizeof(SubGhzRadioPreset)); - instance->preset->name = furi_string_alloc(); - subghz_txrx_set_preset( - instance, "AM650", subghz_setting_get_default_frequency(instance->setting), NULL, 0); - - instance->txrx_state = SubGhzTxRxStateSleep; - - subghz_txrx_hopper_set_state(instance, SubGhzHopperStateOFF); - subghz_txrx_speaker_set_state(instance, SubGhzSpeakerStateDisable); - subghz_txrx_set_debug_pin_state(instance, false); - - instance->worker = subghz_worker_alloc(); - instance->fff_data = flipper_format_string_alloc(); - - instance->environment = subghz_environment_alloc(); - instance->is_database_loaded = - subghz_environment_load_keystore(instance->environment, SUBGHZ_KEYSTORE_DIR_NAME); - subghz_environment_load_keystore(instance->environment, SUBGHZ_KEYSTORE_DIR_USER_NAME); - subghz_environment_set_came_atomo_rainbow_table_file_name( - instance->environment, SUBGHZ_CAME_ATOMO_DIR_NAME); - subghz_environment_set_alutech_at_4n_rainbow_table_file_name( - instance->environment, SUBGHZ_ALUTECH_AT_4N_DIR_NAME); - subghz_environment_set_nice_flor_s_rainbow_table_file_name( - instance->environment, SUBGHZ_NICE_FLOR_S_DIR_NAME); - subghz_environment_set_protocol_registry( - instance->environment, (void*)&subghz_protocol_registry); - instance->receiver = subghz_receiver_alloc_init(instance->environment); - - subghz_worker_set_overrun_callback( - instance->worker, (SubGhzWorkerOverrunCallback)subghz_receiver_reset); - subghz_worker_set_pair_callback( - instance->worker, (SubGhzWorkerPairCallback)subghz_receiver_decode); - subghz_worker_set_context(instance->worker, instance->receiver); - - //set default device Internal - subghz_devices_init(); - instance->radio_device_type = SubGhzRadioDeviceTypeInternal; - instance->radio_device_type = - subghz_txrx_radio_device_set(instance, SubGhzRadioDeviceTypeExternalCC1101); - - return instance; -} - -void subghz_txrx_free(SubGhzTxRx* instance) { - furi_assert(instance); - - if(instance->radio_device_type != SubGhzRadioDeviceTypeInternal) { - subghz_txrx_radio_device_power_off(instance); - subghz_devices_end(instance->radio_device); - } - - subghz_devices_deinit(); - - subghz_worker_free(instance->worker); - subghz_receiver_free(instance->receiver); - subghz_environment_free(instance->environment); - flipper_format_free(instance->fff_data); - furi_string_free(instance->preset->name); - subghz_setting_free(instance->setting); - - free(instance->preset); - free(instance); -} - -bool subghz_txrx_is_database_loaded(SubGhzTxRx* instance) { - furi_assert(instance); - return instance->is_database_loaded; -} - -void subghz_txrx_set_preset( - SubGhzTxRx* instance, - const char* preset_name, - uint32_t frequency, - uint8_t* preset_data, - size_t preset_data_size) { - furi_assert(instance); - furi_string_set(instance->preset->name, preset_name); - SubGhzRadioPreset* preset = instance->preset; - preset->frequency = frequency; - preset->data = preset_data; - preset->data_size = preset_data_size; -} - -const char* subghz_txrx_get_preset_name(SubGhzTxRx* instance, const char* preset) { - UNUSED(instance); - const char* preset_name = ""; - if(!strcmp(preset, "FuriHalSubGhzPresetOok270Async")) { - preset_name = "AM270"; - } else if(!strcmp(preset, "FuriHalSubGhzPresetOok650Async")) { - preset_name = "AM650"; - } else if(!strcmp(preset, "FuriHalSubGhzPreset2FSKDev238Async")) { - preset_name = "FM238"; - } else if(!strcmp(preset, "FuriHalSubGhzPreset2FSKDev476Async")) { - preset_name = "FM476"; - } else if(!strcmp(preset, "FuriHalSubGhzPresetCustom")) { - preset_name = "CUSTOM"; - } else { - FURI_LOG_E(TAG, "Unknown preset"); - } - return preset_name; -} - -SubGhzRadioPreset subghz_txrx_get_preset(SubGhzTxRx* instance) { - furi_assert(instance); - return *instance->preset; -} - -void subghz_txrx_get_frequency_and_modulation( - SubGhzTxRx* instance, - FuriString* frequency, - FuriString* modulation, - bool long_name) { - furi_assert(instance); - SubGhzRadioPreset* preset = instance->preset; - if(frequency != NULL) { - furi_string_printf( - frequency, - "%03ld.%02ld", - preset->frequency / 1000000 % 1000, - preset->frequency / 10000 % 100); - } - if(modulation != NULL) { - if(long_name) { - furi_string_printf(modulation, "%s", furi_string_get_cstr(preset->name)); - } else { - furi_string_printf(modulation, "%.2s", furi_string_get_cstr(preset->name)); - } - } -} - -static void subghz_txrx_begin(SubGhzTxRx* instance, uint8_t* preset_data) { - furi_assert(instance); - subghz_devices_reset(instance->radio_device); - subghz_devices_idle(instance->radio_device); - subghz_devices_load_preset(instance->radio_device, FuriHalSubGhzPresetCustom, preset_data); - instance->txrx_state = SubGhzTxRxStateIDLE; -} - -static uint32_t subghz_txrx_rx(SubGhzTxRx* instance, uint32_t frequency) { - furi_assert(instance); - furi_assert( - instance->txrx_state != SubGhzTxRxStateRx && instance->txrx_state != SubGhzTxRxStateSleep); - - subghz_devices_idle(instance->radio_device); - - uint32_t value = subghz_devices_set_frequency(instance->radio_device, frequency); - subghz_devices_flush_rx(instance->radio_device); - subghz_txrx_speaker_on(instance); - - subghz_devices_start_async_rx( - instance->radio_device, subghz_worker_rx_callback, instance->worker); - subghz_worker_start(instance->worker); - instance->txrx_state = SubGhzTxRxStateRx; - return value; -} - -static void subghz_txrx_idle(SubGhzTxRx* instance) { - furi_assert(instance); - furi_assert(instance->txrx_state != SubGhzTxRxStateSleep); - subghz_devices_idle(instance->radio_device); - subghz_txrx_speaker_off(instance); - instance->txrx_state = SubGhzTxRxStateIDLE; -} - -static void subghz_txrx_rx_end(SubGhzTxRx* instance) { - furi_assert(instance); - furi_assert(instance->txrx_state == SubGhzTxRxStateRx); - - if(subghz_worker_is_running(instance->worker)) { - subghz_worker_stop(instance->worker); - subghz_devices_stop_async_rx(instance->radio_device); - } - subghz_devices_idle(instance->radio_device); - subghz_txrx_speaker_off(instance); - instance->txrx_state = SubGhzTxRxStateIDLE; -} - -void subghz_txrx_sleep(SubGhzTxRx* instance) { - furi_assert(instance); - subghz_devices_sleep(instance->radio_device); - instance->txrx_state = SubGhzTxRxStateSleep; -} - -static bool subghz_txrx_tx(SubGhzTxRx* instance, uint32_t frequency) { - furi_assert(instance); - furi_assert(instance->txrx_state != SubGhzTxRxStateSleep); - - subghz_devices_idle(instance->radio_device); - subghz_devices_set_frequency(instance->radio_device, frequency); - - bool ret = subghz_devices_set_tx(instance->radio_device); - if(ret) { - subghz_txrx_speaker_on(instance); - instance->txrx_state = SubGhzTxRxStateTx; - } - - return ret; -} - -SubGhzTxRxStartTxState subghz_txrx_tx_start(SubGhzTxRx* instance, FlipperFormat* flipper_format) { - furi_assert(instance); - furi_assert(flipper_format); - - subghz_txrx_stop(instance); - - SubGhzTxRxStartTxState ret = SubGhzTxRxStartTxStateErrorParserOthers; - FuriString* temp_str = furi_string_alloc(); - uint32_t repeat = 200; - do { - if(!flipper_format_rewind(flipper_format)) { - FURI_LOG_E(TAG, "Rewind error"); - break; - } - if(!flipper_format_read_string(flipper_format, "Protocol", temp_str)) { - FURI_LOG_E(TAG, "Missing Protocol"); - break; - } - if(!flipper_format_insert_or_update_uint32(flipper_format, "Repeat", &repeat, 1)) { - FURI_LOG_E(TAG, "Unable Repeat"); - break; - } - ret = SubGhzTxRxStartTxStateOk; - - SubGhzRadioPreset* preset = instance->preset; - instance->transmitter = - subghz_transmitter_alloc_init(instance->environment, furi_string_get_cstr(temp_str)); - - if(instance->transmitter) { - if(subghz_transmitter_deserialize(instance->transmitter, flipper_format) == - SubGhzProtocolStatusOk) { - if(strcmp(furi_string_get_cstr(preset->name), "") != 0) { - subghz_txrx_begin( - instance, - subghz_setting_get_preset_data_by_name( - instance->setting, furi_string_get_cstr(preset->name))); - if(preset->frequency) { - if(!subghz_txrx_tx(instance, preset->frequency)) { - FURI_LOG_E(TAG, "Only Rx"); - ret = SubGhzTxRxStartTxStateErrorOnlyRx; - } - } else { - ret = SubGhzTxRxStartTxStateErrorParserOthers; - } - - } else { - FURI_LOG_E( - TAG, "Unknown name preset \" %s \"", furi_string_get_cstr(preset->name)); - ret = SubGhzTxRxStartTxStateErrorParserOthers; - } - - if(ret == SubGhzTxRxStartTxStateOk) { - //Start TX - subghz_devices_start_async_tx( - instance->radio_device, subghz_transmitter_yield, instance->transmitter); - } - } else { - ret = SubGhzTxRxStartTxStateErrorParserOthers; - } - } else { - ret = SubGhzTxRxStartTxStateErrorParserOthers; - } - if(ret != SubGhzTxRxStartTxStateOk) { - subghz_transmitter_free(instance->transmitter); - if(instance->txrx_state != SubGhzTxRxStateIDLE) { - subghz_txrx_idle(instance); - } - } - - } while(false); - furi_string_free(temp_str); - return ret; -} - -void subghz_txrx_rx_start(SubGhzTxRx* instance) { - furi_assert(instance); - subghz_txrx_stop(instance); - subghz_txrx_begin( - instance, - subghz_setting_get_preset_data_by_name( - subghz_txrx_get_setting(instance), furi_string_get_cstr(instance->preset->name))); - subghz_txrx_rx(instance, instance->preset->frequency); -} - -void subghz_txrx_set_need_save_callback( - SubGhzTxRx* instance, - SubGhzTxRxNeedSaveCallback callback, - void* context) { - furi_assert(instance); - instance->need_save_callback = callback; - instance->need_save_context = context; -} - -static void subghz_txrx_tx_stop(SubGhzTxRx* instance) { - furi_assert(instance); - furi_assert(instance->txrx_state == SubGhzTxRxStateTx); - //Stop TX - subghz_devices_stop_async_tx(instance->radio_device); - subghz_transmitter_stop(instance->transmitter); - subghz_transmitter_free(instance->transmitter); - - //if protocol dynamic then we save the last upload - if(instance->decoder_result->protocol->type == SubGhzProtocolTypeDynamic) { - if(instance->need_save_callback) { - instance->need_save_callback(instance->need_save_context); - } - } - subghz_txrx_idle(instance); - subghz_txrx_speaker_off(instance); - //Todo: Show message -} - -FlipperFormat* subghz_txrx_get_fff_data(SubGhzTxRx* instance) { - furi_assert(instance); - return instance->fff_data; -} - -SubGhzSetting* subghz_txrx_get_setting(SubGhzTxRx* instance) { - furi_assert(instance); - return instance->setting; -} - -void subghz_txrx_stop(SubGhzTxRx* instance) { - furi_assert(instance); - - switch(instance->txrx_state) { - case SubGhzTxRxStateTx: - subghz_txrx_tx_stop(instance); - subghz_txrx_speaker_unmute(instance); - break; - case SubGhzTxRxStateRx: - subghz_txrx_rx_end(instance); - subghz_txrx_speaker_mute(instance); - break; - - default: - break; - } -} - -void subghz_txrx_hopper_update(SubGhzTxRx* instance) { - furi_assert(instance); - - switch(instance->hopper_state) { - case SubGhzHopperStateOFF: - case SubGhzHopperStatePause: - return; - case SubGhzHopperStateRSSITimeOut: - if(instance->hopper_timeout != 0) { - instance->hopper_timeout--; - return; - } - break; - default: - break; - } - float rssi = -127.0f; - if(instance->hopper_state != SubGhzHopperStateRSSITimeOut) { - // See RSSI Calculation timings in CC1101 17.3 RSSI - rssi = subghz_devices_get_rssi(instance->radio_device); - - // Stay if RSSI is high enough - if(rssi > -90.0f) { - instance->hopper_timeout = 10; - instance->hopper_state = SubGhzHopperStateRSSITimeOut; - return; - } - } else { - instance->hopper_state = SubGhzHopperStateRunning; - } - // Select next frequency - if(instance->hopper_idx_frequency < - subghz_setting_get_hopper_frequency_count(instance->setting) - 1) { - instance->hopper_idx_frequency++; - } else { - instance->hopper_idx_frequency = 0; - } - - if(instance->txrx_state == SubGhzTxRxStateRx) { - subghz_txrx_rx_end(instance); - }; - if(instance->txrx_state == SubGhzTxRxStateIDLE) { - subghz_receiver_reset(instance->receiver); - instance->preset->frequency = - subghz_setting_get_hopper_frequency(instance->setting, instance->hopper_idx_frequency); - subghz_txrx_rx(instance, instance->preset->frequency); - } -} - -SubGhzHopperState subghz_txrx_hopper_get_state(SubGhzTxRx* instance) { - furi_assert(instance); - return instance->hopper_state; -} - -void subghz_txrx_hopper_set_state(SubGhzTxRx* instance, SubGhzHopperState state) { - furi_assert(instance); - instance->hopper_state = state; -} - -void subghz_txrx_hopper_unpause(SubGhzTxRx* instance) { - furi_assert(instance); - if(instance->hopper_state == SubGhzHopperStatePause) { - instance->hopper_state = SubGhzHopperStateRunning; - } -} - -void subghz_txrx_hopper_pause(SubGhzTxRx* instance) { - furi_assert(instance); - if(instance->hopper_state == SubGhzHopperStateRunning) { - instance->hopper_state = SubGhzHopperStatePause; - } -} - -void subghz_txrx_speaker_on(SubGhzTxRx* instance) { - furi_assert(instance); - if(instance->debug_pin_state) { - subghz_devices_set_async_mirror_pin(instance->radio_device, &gpio_ibutton); - } - - if(instance->speaker_state == SubGhzSpeakerStateEnable) { - if(furi_hal_speaker_acquire(30)) { - if(!instance->debug_pin_state) { - subghz_devices_set_async_mirror_pin(instance->radio_device, &gpio_speaker); - } - } else { - instance->speaker_state = SubGhzSpeakerStateDisable; - } - } -} - -void subghz_txrx_speaker_off(SubGhzTxRx* instance) { - furi_assert(instance); - if(instance->debug_pin_state) { - subghz_devices_set_async_mirror_pin(instance->radio_device, NULL); - } - if(instance->speaker_state != SubGhzSpeakerStateDisable) { - if(furi_hal_speaker_is_mine()) { - if(!instance->debug_pin_state) { - subghz_devices_set_async_mirror_pin(instance->radio_device, NULL); - } - furi_hal_speaker_release(); - if(instance->speaker_state == SubGhzSpeakerStateShutdown) - instance->speaker_state = SubGhzSpeakerStateDisable; - } - } -} - -void subghz_txrx_speaker_mute(SubGhzTxRx* instance) { - furi_assert(instance); - if(instance->debug_pin_state) { - subghz_devices_set_async_mirror_pin(instance->radio_device, NULL); - } - if(instance->speaker_state == SubGhzSpeakerStateEnable) { - if(furi_hal_speaker_is_mine()) { - if(!instance->debug_pin_state) { - subghz_devices_set_async_mirror_pin(instance->radio_device, NULL); - } - } - } -} - -void subghz_txrx_speaker_unmute(SubGhzTxRx* instance) { - furi_assert(instance); - if(instance->debug_pin_state) { - subghz_devices_set_async_mirror_pin(instance->radio_device, &gpio_ibutton); - } - if(instance->speaker_state == SubGhzSpeakerStateEnable) { - if(furi_hal_speaker_is_mine()) { - if(!instance->debug_pin_state) { - subghz_devices_set_async_mirror_pin(instance->radio_device, &gpio_speaker); - } - } - } -} - -void subghz_txrx_speaker_set_state(SubGhzTxRx* instance, SubGhzSpeakerState state) { - furi_assert(instance); - instance->speaker_state = state; -} - -SubGhzSpeakerState subghz_txrx_speaker_get_state(SubGhzTxRx* instance) { - furi_assert(instance); - return instance->speaker_state; -} - -bool subghz_txrx_load_decoder_by_name_protocol(SubGhzTxRx* instance, const char* name_protocol) { - furi_assert(instance); - furi_assert(name_protocol); - bool res = false; - instance->decoder_result = - subghz_receiver_search_decoder_base_by_name(instance->receiver, name_protocol); - if(instance->decoder_result) { - res = true; - } - return res; -} - -SubGhzProtocolDecoderBase* subghz_txrx_get_decoder(SubGhzTxRx* instance) { - furi_assert(instance); - return instance->decoder_result; -} - -bool subghz_txrx_protocol_is_serializable(SubGhzTxRx* instance) { - furi_assert(instance); - return ( - (instance->decoder_result->protocol->flag & SubGhzProtocolFlag_Save) == - SubGhzProtocolFlag_Save); -} - -bool subghz_txrx_protocol_is_transmittable(SubGhzTxRx* instance, bool check_type) { - furi_assert(instance); - const SubGhzProtocol* protocol = instance->decoder_result->protocol; - if(check_type) { - return ( - ((protocol->flag & SubGhzProtocolFlag_Send) == SubGhzProtocolFlag_Send) && - protocol->encoder->deserialize && protocol->type == SubGhzProtocolTypeStatic); - } - return ( - ((protocol->flag & SubGhzProtocolFlag_Send) == SubGhzProtocolFlag_Send) && - protocol->encoder->deserialize); -} - -void subghz_txrx_receiver_set_filter(SubGhzTxRx* instance, SubGhzProtocolFlag filter) { - furi_assert(instance); - subghz_receiver_set_filter(instance->receiver, filter); -} - -void subghz_txrx_set_rx_calback( - SubGhzTxRx* instance, - SubGhzReceiverCallback callback, - void* context) { - subghz_receiver_set_rx_callback(instance->receiver, callback, context); -} - -void subghz_txrx_set_raw_file_encoder_worker_callback_end( - SubGhzTxRx* instance, - SubGhzProtocolEncoderRAWCallbackEnd callback, - void* context) { - subghz_protocol_raw_file_encoder_worker_set_callback_end( - (SubGhzProtocolEncoderRAW*)subghz_transmitter_get_protocol_instance(instance->transmitter), - callback, - context); -} - -bool subghz_txrx_radio_device_is_external_connected(SubGhzTxRx* instance, const char* name) { - furi_assert(instance); - - bool is_connect = false; - bool is_otg_enabled = furi_hal_power_is_otg_enabled(); - - if(!is_otg_enabled) { - subghz_txrx_radio_device_power_on(instance); - } - - const SubGhzDevice* device = subghz_devices_get_by_name(name); - if(device) { - is_connect = subghz_devices_is_connect(device); - } - - if(!is_otg_enabled) { - subghz_txrx_radio_device_power_off(instance); - } - return is_connect; -} - -SubGhzRadioDeviceType - subghz_txrx_radio_device_set(SubGhzTxRx* instance, SubGhzRadioDeviceType radio_device_type) { - furi_assert(instance); - - if(radio_device_type == SubGhzRadioDeviceTypeExternalCC1101 && - subghz_txrx_radio_device_is_external_connected(instance, SUBGHZ_DEVICE_CC1101_EXT_NAME)) { - subghz_txrx_radio_device_power_on(instance); - instance->radio_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_EXT_NAME); - subghz_devices_begin(instance->radio_device); - instance->radio_device_type = SubGhzRadioDeviceTypeExternalCC1101; - } else { - subghz_txrx_radio_device_power_off(instance); - if(instance->radio_device_type != SubGhzRadioDeviceTypeInternal) { - subghz_devices_end(instance->radio_device); - } - instance->radio_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME); - instance->radio_device_type = SubGhzRadioDeviceTypeInternal; - } - - return instance->radio_device_type; -} - -SubGhzRadioDeviceType subghz_txrx_radio_device_get(SubGhzTxRx* instance) { - furi_assert(instance); - return instance->radio_device_type; -} - -float subghz_txrx_radio_device_get_rssi(SubGhzTxRx* instance) { - furi_assert(instance); - return subghz_devices_get_rssi(instance->radio_device); -} - -const char* subghz_txrx_radio_device_get_name(SubGhzTxRx* instance) { - furi_assert(instance); - return subghz_devices_get_name(instance->radio_device); -} - -bool subghz_txrx_radio_device_is_frequecy_valid(SubGhzTxRx* instance, uint32_t frequency) { - furi_assert(instance); - return subghz_devices_is_frequency_valid(instance->radio_device, frequency); -} - -bool subghz_txrx_radio_device_is_tx_alowed(SubGhzTxRx* instance, uint32_t frequency) { - furi_assert(instance); - furi_assert(instance->txrx_state != SubGhzTxRxStateSleep); - - subghz_devices_idle(instance->radio_device); - subghz_devices_set_frequency(instance->radio_device, frequency); - - bool ret = subghz_devices_set_tx(instance->radio_device); - subghz_devices_idle(instance->radio_device); - - return ret; -} - -void subghz_txrx_set_debug_pin_state(SubGhzTxRx* instance, bool state) { - furi_assert(instance); - instance->debug_pin_state = state; -} - -bool subghz_txrx_get_debug_pin_state(SubGhzTxRx* instance) { - furi_assert(instance); - return instance->debug_pin_state; -} - -void subghz_txrx_reset_dynamic_and_custom_btns(SubGhzTxRx* instance) { - furi_assert(instance); - subghz_environment_reset_keeloq(instance->environment); - - subghz_custom_btns_reset(); -} - -SubGhzReceiver* subghz_txrx_get_receiver(SubGhzTxRx* instance) { - furi_assert(instance); - return instance->receiver; -} \ No newline at end of file diff --git a/applications/external/subghz_remote_configurator/helpers/txrx/subghz_txrx.h b/applications/external/subghz_remote_configurator/helpers/txrx/subghz_txrx.h deleted file mode 100644 index 8bb7f2aee..000000000 --- a/applications/external/subghz_remote_configurator/helpers/txrx/subghz_txrx.h +++ /dev/null @@ -1,375 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include - -typedef struct SubGhzTxRx SubGhzTxRx; - -typedef void (*SubGhzTxRxNeedSaveCallback)(void* context); - -typedef enum { - SubGhzTxRxStartTxStateOk, - SubGhzTxRxStartTxStateErrorOnlyRx, - SubGhzTxRxStartTxStateErrorParserOthers, -} SubGhzTxRxStartTxState; - -// Type from subghz_types.h need for txrx working -/** SubGhzTxRx state */ -typedef enum { - SubGhzTxRxStateIDLE, - SubGhzTxRxStateRx, - SubGhzTxRxStateTx, - SubGhzTxRxStateSleep, -} SubGhzTxRxState; - -/** SubGhzHopperState state */ -typedef enum { - SubGhzHopperStateOFF, - SubGhzHopperStateRunning, - SubGhzHopperStatePause, - SubGhzHopperStateRSSITimeOut, -} SubGhzHopperState; - -/** SubGhzSpeakerState state */ -typedef enum { - SubGhzSpeakerStateDisable, - SubGhzSpeakerStateShutdown, - SubGhzSpeakerStateEnable, -} SubGhzSpeakerState; - -/** SubGhzRadioDeviceType */ -typedef enum { - SubGhzRadioDeviceTypeAuto, - SubGhzRadioDeviceTypeInternal, - SubGhzRadioDeviceTypeExternalCC1101, -} SubGhzRadioDeviceType; - -/** - * Allocate SubGhzTxRx - * - * @return SubGhzTxRx* pointer to SubGhzTxRx - */ -SubGhzTxRx* subghz_txrx_alloc(); - -/** - * Free SubGhzTxRx - * - * @param instance Pointer to a SubGhzTxRx - */ -void subghz_txrx_free(SubGhzTxRx* instance); - -/** - * Check if the database is loaded - * - * @param instance Pointer to a SubGhzTxRx - * @return bool True if the database is loaded - */ -bool subghz_txrx_is_database_loaded(SubGhzTxRx* instance); - -/** - * Set preset - * - * @param instance Pointer to a SubGhzTxRx - * @param preset_name Name of preset - * @param frequency Frequency in Hz - * @param preset_data Data of preset - * @param preset_data_size Size of preset data - */ -void subghz_txrx_set_preset( - SubGhzTxRx* instance, - const char* preset_name, - uint32_t frequency, - uint8_t* preset_data, - size_t preset_data_size); - -/** - * Get name of preset - * - * @param instance Pointer to a SubGhzTxRx - * @param preset String of preset - * @return const char* Name of preset - */ -const char* subghz_txrx_get_preset_name(SubGhzTxRx* instance, const char* preset); - -/** - * Get of preset - * - * @param instance Pointer to a SubGhzTxRx - * @return SubGhzRadioPreset Preset - */ -SubGhzRadioPreset subghz_txrx_get_preset(SubGhzTxRx* instance); - -/** - * Get string frequency and modulation - * - * @param instance Pointer to a SubGhzTxRx - * @param frequency Pointer to a string frequency - * @param modulation Pointer to a string modulation - */ -void subghz_txrx_get_frequency_and_modulation( - SubGhzTxRx* instance, - FuriString* frequency, - FuriString* modulation, - bool long_name); - -/** - * Start TX CC1101 - * - * @param instance Pointer to a SubGhzTxRx - * @param flipper_format Pointer to a FlipperFormat - * @return SubGhzTxRxStartTxState - */ -SubGhzTxRxStartTxState subghz_txrx_tx_start(SubGhzTxRx* instance, FlipperFormat* flipper_format); - -/** - * Start RX CC1101 - * - * @param instance Pointer to a SubGhzTxRx - */ -void subghz_txrx_rx_start(SubGhzTxRx* instance); - -/** - * Stop TX/RX CC1101 - * - * @param instance Pointer to a SubGhzTxRx - */ -void subghz_txrx_stop(SubGhzTxRx* instance); - -/** - * Set sleep mode CC1101 - * - * @param instance Pointer to a SubGhzTxRx - */ -void subghz_txrx_sleep(SubGhzTxRx* instance); - -/** - * Update frequency CC1101 in automatic mode (hopper) - * - * @param instance Pointer to a SubGhzTxRx - */ -void subghz_txrx_hopper_update(SubGhzTxRx* instance); - -/** - * Get state hopper - * - * @param instance Pointer to a SubGhzTxRx - * @return SubGhzHopperState - */ -SubGhzHopperState subghz_txrx_hopper_get_state(SubGhzTxRx* instance); - -/** - * Set state hopper - * - * @param instance Pointer to a SubGhzTxRx - * @param state State hopper - */ -void subghz_txrx_hopper_set_state(SubGhzTxRx* instance, SubGhzHopperState state); - -/** - * Unpause hopper - * - * @param instance Pointer to a SubGhzTxRx - */ -void subghz_txrx_hopper_unpause(SubGhzTxRx* instance); - -/** - * Set pause hopper - * - * @param instance Pointer to a SubGhzTxRx - */ -void subghz_txrx_hopper_pause(SubGhzTxRx* instance); - -/** - * Speaker on - * - * @param instance Pointer to a SubGhzTxRx - */ -void subghz_txrx_speaker_on(SubGhzTxRx* instance); - -/** - * Speaker off - * - * @param instance Pointer to a SubGhzTxRx - */ -void subghz_txrx_speaker_off(SubGhzTxRx* instance); - -/** - * Speaker mute - * - * @param instance Pointer to a SubGhzTxRx - */ -void subghz_txrx_speaker_mute(SubGhzTxRx* instance); - -/** - * Speaker unmute - * - * @param instance Pointer to a SubGhzTxRx - */ -void subghz_txrx_speaker_unmute(SubGhzTxRx* instance); - -/** - * Set state speaker - * - * @param instance Pointer to a SubGhzTxRx - * @param state State speaker - */ -void subghz_txrx_speaker_set_state(SubGhzTxRx* instance, SubGhzSpeakerState state); - -/** - * Get state speaker - * - * @param instance Pointer to a SubGhzTxRx - * @return SubGhzSpeakerState - */ -SubGhzSpeakerState subghz_txrx_speaker_get_state(SubGhzTxRx* instance); - -/** - * load decoder by name protocol - * - * @param instance Pointer to a SubGhzTxRx - * @param name_protocol Name protocol - * @return bool True if the decoder is loaded - */ -bool subghz_txrx_load_decoder_by_name_protocol(SubGhzTxRx* instance, const char* name_protocol); - -/** - * Get decoder - * - * @param instance Pointer to a SubGhzTxRx - * @return SubGhzProtocolDecoderBase* Pointer to a SubGhzProtocolDecoderBase - */ -SubGhzProtocolDecoderBase* subghz_txrx_get_decoder(SubGhzTxRx* instance); - -/** - * Set callback for save data - * - * @param instance Pointer to a SubGhzTxRx - * @param callback Callback for save data - * @param context Context for callback - */ -void subghz_txrx_set_need_save_callback( - SubGhzTxRx* instance, - SubGhzTxRxNeedSaveCallback callback, - void* context); - -/** - * Get pointer to a load data key - * - * @param instance Pointer to a SubGhzTxRx - * @return FlipperFormat* - */ -FlipperFormat* subghz_txrx_get_fff_data(SubGhzTxRx* instance); - -/** - * Get pointer to a SugGhzSetting - * - * @param instance Pointer to a SubGhzTxRx - * @return SubGhzSetting* - */ -SubGhzSetting* subghz_txrx_get_setting(SubGhzTxRx* instance); - -/** - * Is it possible to save this protocol - * - * @param instance Pointer to a SubGhzTxRx - * @return bool True if it is possible to save this protocol - */ -bool subghz_txrx_protocol_is_serializable(SubGhzTxRx* instance); - -/** - * Is it possible to send this protocol - * - * @param instance Pointer to a SubGhzTxRx - * @return bool True if it is possible to send this protocol - */ -bool subghz_txrx_protocol_is_transmittable(SubGhzTxRx* instance, bool check_type); - -/** - * Set filter, what types of decoder to use - * - * @param instance Pointer to a SubGhzTxRx - * @param filter Filter - */ -void subghz_txrx_receiver_set_filter(SubGhzTxRx* instance, SubGhzProtocolFlag filter); - -/** - * Set callback for receive data - * - * @param instance Pointer to a SubGhzTxRx - * @param callback Callback for receive data - * @param context Context for callback - */ -void subghz_txrx_set_rx_calback( - SubGhzTxRx* instance, - SubGhzReceiverCallback callback, - void* context); - -/** - * Set callback for Raw decoder, end of data transfer - * - * @param instance Pointer to a SubGhzTxRx - * @param callback Callback for Raw decoder, end of data transfer - * @param context Context for callback - */ -void subghz_txrx_set_raw_file_encoder_worker_callback_end( - SubGhzTxRx* instance, - SubGhzProtocolEncoderRAWCallbackEnd callback, - void* context); - -/* Checking if an external radio device is connected -* -* @param instance Pointer to a SubGhzTxRx -* @param name Name of external radio device -* @return bool True if is connected to the external radio device -*/ -bool subghz_txrx_radio_device_is_external_connected(SubGhzTxRx* instance, const char* name); - -/* Set the selected radio device to use -* -* @param instance Pointer to a SubGhzTxRx -* @param radio_device_type Radio device type -* @return SubGhzRadioDeviceType Type of installed radio device -*/ -SubGhzRadioDeviceType - subghz_txrx_radio_device_set(SubGhzTxRx* instance, SubGhzRadioDeviceType radio_device_type); - -/* Get the selected radio device to use -* -* @param instance Pointer to a SubGhzTxRx -* @return SubGhzRadioDeviceType Type of installed radio device -*/ -SubGhzRadioDeviceType subghz_txrx_radio_device_get(SubGhzTxRx* instance); - -/* Get RSSI the selected radio device to use -* -* @param instance Pointer to a SubGhzTxRx -* @return float RSSI -*/ -float subghz_txrx_radio_device_get_rssi(SubGhzTxRx* instance); - -/* Get name the selected radio device to use -* -* @param instance Pointer to a SubGhzTxRx -* @return const char* Name of installed radio device -*/ -const char* subghz_txrx_radio_device_get_name(SubGhzTxRx* instance); - -/* Get get intelligence whether frequency the selected radio device to use -* -* @param instance Pointer to a SubGhzTxRx -* @return bool True if the frequency is valid -*/ -bool subghz_txrx_radio_device_is_frequecy_valid(SubGhzTxRx* instance, uint32_t frequency); - -bool subghz_txrx_radio_device_is_tx_alowed(SubGhzTxRx* instance, uint32_t frequency); - -void subghz_txrx_set_debug_pin_state(SubGhzTxRx* instance, bool state); -bool subghz_txrx_get_debug_pin_state(SubGhzTxRx* instance); - -void subghz_txrx_reset_dynamic_and_custom_btns(SubGhzTxRx* instance); - -SubGhzReceiver* subghz_txrx_get_receiver(SubGhzTxRx* instance); // TODO use only in DecodeRaw diff --git a/applications/external/subghz_remote_configurator/helpers/txrx/subghz_txrx_i.h b/applications/external/subghz_remote_configurator/helpers/txrx/subghz_txrx_i.h deleted file mode 100644 index f058c2282..000000000 --- a/applications/external/subghz_remote_configurator/helpers/txrx/subghz_txrx_i.h +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once - -#include "subghz_txrx.h" - -struct SubGhzTxRx { - SubGhzWorker* worker; - - SubGhzEnvironment* environment; - SubGhzReceiver* receiver; - SubGhzTransmitter* transmitter; - SubGhzProtocolDecoderBase* decoder_result; - FlipperFormat* fff_data; - - SubGhzRadioPreset* preset; - SubGhzSetting* setting; - - uint8_t hopper_timeout; - uint8_t hopper_idx_frequency; - bool is_database_loaded; - SubGhzHopperState hopper_state; - - SubGhzTxRxState txrx_state; - SubGhzSpeakerState speaker_state; - const SubGhzDevice* radio_device; - SubGhzRadioDeviceType radio_device_type; - - SubGhzTxRxNeedSaveCallback need_save_callback; - void* need_save_context; - - bool debug_pin_state; -}; diff --git a/applications/external/subghz_remote_configurator/icon.png b/applications/external/subghz_remote_configurator/icon.png deleted file mode 100644 index c6b410f4c598d6b241b851826875a568c74f4d20..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5000 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 AV*mgE diff --git a/applications/external/subghz_remote_configurator/icons/remote_scene/ButtonLeft_4x7.png b/applications/external/subghz_remote_configurator/icons/remote_scene/ButtonLeft_4x7.png deleted file mode 100644 index 0b4655d43247083aa705620e9836ac415b42ca46..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1415 zcmbVM+iKK67*5rq)>aU2M7$VM1Vxif;vTv~W2u`S7ED{V3s&&L*<`XiG|9wd+THd> z5CnY!sdyuJtrvQyAo>KpiLcV|{Tkc)riAbluXfwSZCApL`ztB&p zx6LGKvks4K_4~)qD&oGa-YdJlW)hAKMNJd7<=t?6c^RI1>c$ifyjaM>^|&8!ey zB4!nh9u>5uen6Ve@<H5rru6h<2Ef#GQdQ*CmZOlQi~N!?9H`Rp;C% zU}CB21#?;r`&0|6C0}b-=jODa5|nEJ#ntxQ&{~jpgtwDta4hftr~G=#p@V36e4Zjh zq%J~{y26Jjn=1Nw-l*3%QW5YFE*v4z3gt0$&(*xf2en34c?JpH8+FYldo+Alvg8af-pG4(=!fyUi-Wsg z`g#n9VUcf(DFr{poMSNzw-lz>w+HV+n1ELr&SLA#LHUb0p(xWQ(1*vJ-i+1!`swxZ Z!O7;c$;lT_->m1Ovaz)0yuI`A$q$F8u*d)a diff --git a/applications/external/subghz_remote_configurator/icons/remote_scene/ButtonRight_4x7.png b/applications/external/subghz_remote_configurator/icons/remote_scene/ButtonRight_4x7.png deleted file mode 100644 index 8e1c74c1c0038ea55172f19ac875003fc80c2d06..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1839 zcmcIlO>f*p7#Yw)M6zw!O+@VZ{?d|D~WYi~8rHRY?X-&T}Yen`g$^+EJ;z+|RV zE@PoDvZ9%#+_}3bC_5Cj8jDGq541mi{7F+&KF}W65sr$Xn5H|YrMQ2(J7%Yc%;(zO z57ax000=TsQ+1Ke@+w#iw3au3cGGQWY740k2ijH>P(6tD)S)be>gX6Tj7`<`b>di- zgWp$8Y+?i31~CzF0&E4uRlA=C(Mp~K`{74jEchB|)4DDK!ZVhSwdFyw0YIZ1cDh0S{OvfO-U_~ zvmRF*m9sWDXNH)GOyqS1Skhxbr6}s*7t&@~kFM(NW5}qh?Lu@lJ}HE;FDiLdGO>LO z5pS*%E2grR)l^;|?O5b_?u0me&c1U}%jrk8*%=Wk%i)8yp2P|kuxmKg<=(u_`oQRI_0 zS`-DNysBx=#3&qSkgA@hJP>~D+ZM(s5jI6Owp`?yE=3e`YGUqkVOp#Cp=3wR3O4hX zX6BLsN3UBzV(vI5;|SZHgOb=HD0VFjpTyfFW}GnQuh>2*Q`k>*cAmA#iUT7EXSpo# zkPm5~#I-o^cpgfe#P$=4-Pi*SpT!-@nJgp8L347xe>5EKl`=_ZFc8XGy+_j=_R_7! z@vZZMowS1GJ?Zw)eetks%~G{BTR>T}9|jt0j3Btyb*C3-`C?fwY3EY`q*oYZ39DpM z&uJ;PCZPLs4QO1Jd_|A1PF)azZJ)RZ`^-VMWr6e#XUOA%3eLG_Ch@BDOHzMk*MF0G zCo7xMd?Mg*HMIXw%nNz?%60fZiZPlqb?GqUpXO`F&Yi!okZl(n>P@r1P2i)yk3DgRwbHeNn6e|;J^SK4TM LH~i+q&mR8;k>NTA diff --git a/applications/external/subghz_remote_configurator/icons/remote_scene/ButtonUp_7x4.png b/applications/external/subghz_remote_configurator/icons/remote_scene/ButtonUp_7x4.png deleted file mode 100644 index 1be79328b40a93297a5609756328406565c437c0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 102 zcmeAS@N?(olHy`uVBq!ia0vp^>_E)I!3HFqj;YoHDIHH2#}J8d-yTOk1_O>mFaFD) zeWb+ZHz{mGZZ1QpXe09^4tcYT#4oe=UbmGC^A-KE*|F&zP#=S*tDnm{r-UX30HgpM AM*si- diff --git a/applications/external/subghz_remote_configurator/icons/remote_scene/Ok_btn_9x9.png b/applications/external/subghz_remote_configurator/icons/remote_scene/Ok_btn_9x9.png deleted file mode 100644 index 9a1539da2049f12f7b25f96b11a9c40cd8227302..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3605 zcmaJ@c{r5q+kR|?vSeS9G2*Q(Gqz$f_GQ#q8r!JE7=ytqjlqnNNGaK}Wlbolp-q`& zs|bxHiiEP0&{#s&zVZIv-rx7f*Y_O9^W67+-RF5;*L_{ra~$^-2RmyaK{-JH0EBE1 z7AVdru>JD$aK0bym%#uaXpT2Gcd#)x2azcxAABGV0BC)Aj-lw(6)B^^6`Y8RS?}DV z%)ko(See1!Eb3M$dL6)A6csaRjExg?k&xVzi*Rm;?iNJk#f=mkVEUR~jXN3dd|Lmz z;y}sMh%ol-?E1&`>dD;6jdps6NYoxN)s%@sf4~40YY6LAOtMEbwA4g#OCpANL823^ zSH66W05Hcxr$tg98gFntAOYL}xm$C;Skv&Ym?{TVR{)d(41vWacX1`7fM!jnW(lBK z26*WB#9I(Z1Ast!xEUC@Cj`v=urcBTdP`FWq=DYTy`}s>0vC{VzHdNRvxNFy}ir1|g=xDsrFP&l1P<-Sv zXLqYVYz{b^ZIV@1Ulg->7DEgvM*Min&Y8{8QW! z$_pA434?^wCTq$4%^>Zo8&|8XwbCv;KEd;WJJ{s;T}8R8Zwi7ssk$QWQ5l5+opKfX z;8D*COFEB#4W^*FIrRU%PDSc?B(}+9ZV?N9(yH>0uSnM?xg!>+>;e z{{7tXQQ|ZFXD*7q3XD!pwnih-=66+Qlqtl9;N-D|PHoI&B5d8>^V#i{mE>V0gQgu3+(DG%B z|8W!pl$lbQERt-0eZA%NSfvE4F>VAYP`DpeoF;Zm4`)2id;6xgSysWl6K$pWANcRZ z!ETRXKIU9G=@9lEB?<{ivj7!8FE9WN;qoo2Lr0#c@DmcF=JzU<73PmM3 zbe!-gs`c26Uc(AKz7%U!a0yZ5gsprdo1i51MjJPeHtV6d@Jy=*+_3dJ^>}p#8N#kPK_4t?hltq>u=?m+t z?em(Y%u3Bp_pyV?c_w-4c}p+?Y$aHr>TuPGs@SUj;Er!b@3GVLDS@T8OTts1JFS-p zKZ=&5zp;DRor*`Gy8MTeWdpVJv2(4-*slRM@XXG+i^F&Ku>7i08vKenZHoS4s(!!h zJE}*MHu7PR_IfdNzu*P}3^87K?f&A1;>NMsgKcR6**;aB74NC7tR(NB?{dHT-9QhXa*KoG!kGU1}$l2D>ypo)fSBuG$ zkTW4?+|I1m?6ZH8tD4^fB{cUpoEoZOo%4hl!EtNtQ#?j*jJR)x-Mn0TrxrX2uT_rh ziOh=Jxsktqbd9x{^s{c5z92Pk$LGoQl53o+=7QXXCp-Z>io998w|DCCCGfr20oiRN zX|`KH$W4)wN~)J$kYB~>4EU;NcS^qH&yzeUzXokpMegg_lX$6ve^4}%bY~Sg)%uJ- zZpb$p4x^GS5d{XJP=STbfpHV`58UBH& zKFg&BgS6bV+#-|^KBGeIBee2B zrM-`uTB^_(eS+{-KK1h3l`-Yjpv8X4z*uBwQ3a~pL0Ae2xvNGyC3A|#MARToe$W~8 z+4{DsyenENye9df1M}gNUM9_Leh6G=`9exL-cdSKQ_CGyEdZ3W5uoR!Lb^D)9!bd=7h@R=M%=|JqX9XP;Z6# zFD15Bw7qTP(ZlG?o@#x@=wG;XxM(>n@4P$9WwY#lW$h=`zMi_zq30HbV-zHheqpE0 zR6kXtxdzl&Ml2D#zDIvflJkb*e zIAI?GMjp?JBK76WW`{l{pFAY|%5?nYUxRnT&y6~Kz19AD;C0(z*7?dM{%HhVtqWEc z%+M$z6u@uQu)kg_%2PO_U|n1JE0V1>iVbekOLEOG$U6X^Umc519WC)L$t%`#Di0$ zY1|5H*440_`onhmXeayq`8EIg?x2r9KWe()q}QayqCMEC?c4meb4}#i`HHPaxO&3SPtSVKj@ND?Y+-@R`CDnf-d`T>vTn8RR<=@3 zNXk=Gloyh#S@3R89WHrXBHr;f(&ZO@I_Uo7;O5Bs@ecGx@7%7{_>Q`Adg&sCeZTYp ztVy{^vAUfOpTDzF*4`h%X0odWn`#uZ4s4igIV^UrVVg?c*{>K)hHq^^RxU2CM;WN> z;oK@^sg`J}BguyvilN{DQ*V+N4rD{X_~KAFj5qyk3(gP#cvSIDXe!zk3B!^InwV{j zCXGPmumQl(m`28618`K37tR+?goD{H>cAkpHyrG$XA89@o8$cOh%gGyG0e^h8y0{y z@CF+jfedLdjsO8i#eispKw=P#1_%GG3**eU%@8o?ZwNI24*pM2Xj=!6If;S;9nsX% zz(S!=&=CVoZ;TfP>*b{m(uQhlL7=)2EnN*L6sBVU)71t2^ME<-DBeCWl!etl&NwSL z*pEsj!yu5*&``}#9ZeF&7oufgU;u$?L$tLuI0%g(I+2Q@X%K^ye=Atvg0K`knTjV7 zLEDNLFH$fS4(5dVpED51|H=}B{>c+3V-OmK4AIhrZlCEl(AM_T0=zuK- zizjYd4*pHCwT0ObgQyrH7H4At2XjO;@px~TsgAA%R9|05PuEIcOUu&SOwUTs^00xK zshI`T;)sF%Z>|Li8%)3vslU12|K;lbk-Oav1Tx371&)Fb!FgLzNCeQ|r-tGG9E;W; z_5R^{|2Y=zKXM_QU?AJI{a>~IZQ?Z0_VnM@jcrt7jKN@*#$ZMzB}>VcEo(xFhBigA zRfKF&B$OpfLSqS8d&l#8dVcR8Z}0is_kGT}&h`CX>-l`{D|W}MM1o0W+qqCz&a@8xmO|M3uh;cln|6OUI z@X7fQ&dki(hqbDStcmq@R)<*FE(x{7@jPF^02^V5=v9ihMb|f1hw)0IhxkF_<1H_} z1sVWgmXE~@Wjrum=ebV>cmZ0s_CATm;a}mEc52Q5C=nO}OHAzGNx%Y4+73-pK+|sE zf&F7oVIUa*{8{JBz(BDGF#W^YNC4<9N*a&_dh_-a2?DV^K)SlsK3W zOCXnR0@miQE9D7uc?!4U4XYLag5q!qVkYiDSh|^JD*)2x1yFk>+xS2jzFcTm?NE^$ zEusR=1Jt#ow51*G(vhl2c`F}0KRYy{Jo3{2p&4FwzqpssC^#!EQ$-Rz!G~$z2>|jd zoi8@^jT0uuM~BC~Cj2=+8uB*%W~pE!<+;Jls%yObfcUWvPM_P@SPvhqk>^2RtzXee zpw9{L8C-GI=@-g9A^bLEC5ENHZn8J$mR*yf;vV50J7!cpZdF6S#2Ee38Kw@!gf4MU zH~T|ofioE<=_Pgf;Tvc0l%P^<+(Zk%8H}<#p|aT+abY8Ff9Htq!&92lSLbk7D(t{E zjjU(bM04fllo5%^3-CFm)D5AeU=e^FXGmfr{&k_>d3a+)aa}=xN$7&sHTfNh zfVj6VoV5%9Nwq8SCK^0ITUx;v0I2%9`_$cJSLF_4$)r9^g5d7-;)ha7k^2JBT`QGyenmoI!B!BgFZa^nPSIjjmHP5e8zHBct z>}g(M=h3f$4B-6LI6_z_Ow{YzNBpU4Q5No3aPn%6GK4Xlo>ROYK@oQ-NLryT2hS1Q z#~TwSIW2hlviM8?O9=^9I1CPTS9MyYOrlcISt$H6?B!qJq`S6dsv#09^-K@M!vvfq zTkX5@UgaFs(|?Idx+S6ai8fy!JtnNIngF-nVeN7Z`Pkld>>sQwike&!d8m z!q}j+#PS5O1l#Lt&96qwr4S9#BN(B)eb|Czi6eSM<1zl*H{oXKxy8rZigMly7Dpp) zp0Fn82H8REqlzST12a_HGG$OL1zP#tZ!<{Vq-7t-B%@O3Q}|wsw6|$peqXmwPE3aX z2;M0YDH7g@_E4AelRGO{xVu~ql8(6}@GdRA$pQKSu8{71L+l3C5qDtez&Yu}Hxem` z6sMHXl!;;o#{fs;ZdUOQhkK4<_f9*Vzhmk6*zQY_(0iGC-9?Iy&x;P0wqt{_@pc`@ z-STVPHZH9aL>@&(Sms8e^BoA~ujOKuWnROHb2zgex)a}&rr!-4kCTs9rZGVRYYIV- zvlx3+K(QCwE72=^{7f5<=%`? zl>Nr(;dCk;g6aw$Opx=3=@VvK69`}ZZjdTEXD<)m-PPh#nON_W-)WuySB2X5DDN+N zOj#o@Hg%5&TlX_@z|RoxL4x-e)E6|2*6eRf_RH|9>@0i7Xl-rM9ANjdo2TOpy0iRp z@HHQ+`qyJ4Zd+tE9Emv?)0oNb81R+irnMuZ>Qj# zxib@y+4A&mNoGlXP$qd$YD6l2f7kv+drBW{dVN}WI%9gX}>;*m9J4X{*B+`P?WbMg?R|_dOLt0YC zJHiM_Ty3A^GkR^rdo$!_RLz|l@F22ACA23r zJ#_ne&f4MCmW}wIwZp7=nYm*E?mRDe#(1hP%3plU=f|hSpU!`KyPiO-!1Ha8okr4T zJB37Cl;}y+I@x)J6@t!yw`NAC^c%r!=@Sa8&{j3f-kx1?ksX4A;-S<#E11dFr-IQ# zR{qfyN+h{-*_HEB`wzg2wZ9!NvuB)PENk|#M_tyutK;V4i>^I8-0%C89^}pT^~d@X zrZX$TDvB#EGNXQ4%%w>%B=-r;Tp6wJtw&z@62Lp*pP`dAn&FVjAe4>`?UC_VILOQnvfFm7kYb}KIe$4b!q%cDFE;P^!}5wFhS$flol=(c zKOH`gTJ?#vwG4c%BV>!!U?s|3f2Oiv<7D3Rncea6%ttMQ=SEEn7*BSKM z{I;U9VyY&6%QWwRxn-WhQPHJ&t+6%>}7+sVXoLpPbO)$>wJq(%cIl{yAd4L zao(3TFdv5v@49^(rE$qwH>D`KxrI{ti`zebVW|0ofEcHjRC^^ydT1 zit!QWV{YB&7Fp!JzRyR>-^@&*rwXPh>}8kQ`$wvMO}pPl&We;M%*Bo=xRH;1X50$# zU5slhYkSkir-#>@IobM@-9LZpVE$4__664#r;U<(Fif+aek4~_5ISPczF+n%G&YJPZd_dwhcM)XK$a~zGT6f@?}u{2kzI_J`y5h z5613ABWPopVbs3NnT+5kv=awJUz(1+_-pXaxwBvFzTRqoHSnr!F#SULqTm#orO}0` z4PcuJ1W{iBF zKEPVWtf%|A9(S$wMs?&E%QC)W%H5Wm7d}tKyUte8et?%f`c=!1mLN-!R-v?wVf6iz z)G6X}%Z#&ODdUID)ZtFfy9=wnb=?6Uetyt)y~(QPyq;Dlr>K3}Q=wY9_%mo}MmAXZ zJ7&N&B%XPHy{2#D+xAtlZx_lo9}?@xLqFZ?+&f;mh;c-PqH;Eqf4z$u?y_pN>Q=E- ziH*-zQc@6+ub%g8PZ}Rf89BiysN>^Vu*|b~eTqQIXzO`L8nmD()4q3juuoh;Z zx{Lc)DaWwDG3=>cj9@&S2$*_OJ%}J{GTxhrCE`61Z>_G%gwd42_vIJi(910C^C-NfacQ^Sl-eB6%Xg&U!Xb8ybq}LqdnpiS{AK90(zP z1Ord7u@T6SiQp2Di3~i5N%p4%Aecz--@FL!dP@uegZ@@w_#wgnaSCT+2SQQlM9?8^ zm=*yFg@O(lXcIm0a1R|XJV6r#hr(eH8234(1v`X*>mXnTpnnFKYmn~gg}|Cy{$q~2 zLxO!63>pFg2@Vd{4%X48(!C)t0|NsH6b^yIwYVBu0W1mw&(xv>sQhLyCk7DcBpQQ6 zrGT~=@gCGb1`^D5_CHaOY5&qv0{+PqH)jwgo(6$wL${*(t!QKO|ErS8|7r&?u*CoR z`+pJ#IIw6$2$mQ?4WtvewewQhGDSn6=tMk&N_U`A{eLIY&WFmN2KZ2EAh?b;45V&@ zCy*#xlKp=}Y-|wLlmG^vLLge3Bf(q}Z4${7VPJ`Z>caJO59#RW!C)3BeO)*VWoc## zg<9yK4D<|sW6i0AKr)fS_>J}aFIMl5*sX>j)3}z+iF8sB(bJMnC4>Hs8bSKAFYrI| z{e$)VvoAV-#6q~vK(=c8ziRzk#BHFh<-g6#-Td4BL<+a(>D=bN76lY@FUB@IjDy9m z(5*YN-4s*8oj}&+rVh+L4|neH1o$j1E!71)pl~xe=$Un0lQ15DzW@MRrx z!J?<(q3pT2^$+V+Q`u7+9n4PA$lc;2p&F8~jx^B8sR zx>rCR%LJ^+TUW{z>G}+2%^g|I2L#7s6GcrtfXECp^)>*c&kdOGlW6Awp?LDNx@(7v z-Ko(PNG_nRHMKqcShu!hMe19*kj44oQKivW0gudZG6%)H1;)YI=~>DW$SEFFhY$eB zt#!TJ(l<_=nj9aQ^qvY}e{aa&@}H-Gjg%IKwyLgi^8#Xao$P-1iHTkwY7^JPpj!Xp zlR&>S;5)SDrad5#cS7)O=vpjOf5T*7?k#k)p~7ClUAyK~Ja1KNjl~-M(jK7<$40Dh zzHSYK&I4yMO)^UA3Zgd8;K;$HnE0tyUNb0pbxL`wDf--I{K2kKokyqCrLHbuuT-GH zwoT0Em?R6Omef)4>2t6J#k5U<Kzn-O7ywj#*>mb{iVUie9{?=!&L4Vcx>M+-B&$v&`=vrv zoeVc_hlPpI{yIZ3vmN7+dj)UpNi&sotb_OQK7Gg|m$y4}M6B#3R9|>%Sp3xa8LG?< zk3G4s_EcRG;5BXLm%u5(V|IJS_klb3WisMb#kh|E-FUbw5 zyr@BwG>AK8@-uOu83en!aka`CnsWZ}ah~_wK_<`dD#~4L%nR(I>xjBVrsey0$(8Lx zL_W(e>N@r%hz^8bjmJlJK}Ec;eZ-x*cG=S73RX_FNg6+a)pbtL#VcSB2TRG<<>J`< z`?+HyC1&|gUle;4a3L|#8jHf3-&L7aE)%chcM*uX2z~VjIQg!9nM$bmT0O%P{wNV^ z#ZvvIv`;Bl<@6sS67I>!{UR;b$L$1_R1#q}yKMZC14xZRheD%nF=94KbtaM2@_C&9 zaU=_ro>ZPFnrMH0z2)_Ixg@+HW)vlmzaLYWB7RhtU_8Nl`zFjRBk$hv_Tt?4{P$wu zH&57*@`BM2hs(thIzgE#?OD?1t%Vu|J#RCKKEzdD$TYoD;8WB-%k;PD-Tq&8PESoo zeGd^5z9bygg!DWh>o0p&wrEeeEF=SUhwoi_Mzf>V2bg?@&kfNV6esMVl|x}tNpHkc z;i=B45vf!69GwE4jC+{(b~)a661{)gIsA^5(-ZVqvA}!j`#r@9PA`h}N;@zim;`j^ zarc56_st7G@xqTUMO)=vLKZmU%Nu3ml%yMBgaxcwFU^@}M&190t>?+dYqO|ezIFLv z$XS$wdEh;7mUohO&g7YPE|JDZ!}A6ovyXNtbqIHy)!@-E)_BzGSK?g~QF6FHw7;g` zbB;DAJvYm|wtK=twSZHf3V{x^sfUGo=5?(S~&txT%-E$Ff-_@hGg+hw0I zU51R2H;b~@lcn>SFz9cH^CZFs3hN6S#%m6?r}$@jS9X=Xqqns+s}HjJSS_>h20hvS zxwx8-RRbGw(YGzL8;-{6#Wtn&r-ilhrP-#fvTisVIWwJ?oj*D(2*V8UO@;O6;BUNmvJB!T`eNt3~f!F zko#8I{q)^(LDq|`!IF=p_n+Dj4dM6KZ8fvxTijkF*rwm-SFxjK+QxE!oV zwhoA?P$bG`$gG7+9y|oQr}_1GnFIX{eO0}eHSW6ZQyssMP<-wAkpaJFv|t~WUjQZm zKbut%S#hu8Jmc~Y%Y}4ty2O5gxhv!Kef5YdV}aaL0h!v_-oUDw1g{pcIw>5q*kqCjS7$R7KNBC@T5#Nx%QXnV_={J8w%kIE~K8eX5waZX*) z|8ykW{HO0Fd#j*EZ2^0X8Z$}u`g7$aTW5>j&#camXFh5eq-3XL7hr^mX=Q33w8{^Z z+k302B@2%;CrNMQlP|wn9amlpTpExHh(>i4lwnHIBGM?xT{XtZJtr9z$ZF(?_u50= zTVL0dcU_PUt4@4~u6X#QuY%#aFbuA>d?BqI>mU=N33bC%dNGLe-Qlgit&h_-(W6+5 z)1n`9a4{Ye)qVT6x!MI6oz&u#mR54<_Y=?YQn*wvC$?XD&q?QVhh$RSSya~D(jO14 zDkeu=?A&|8mYJmf{?A9t-^|S*X9{P?tX0?A2S=;@Oncs5ninpSUx=HKcPAbFOurTC zw;bPI*8ZlQM;E6%ce3pnYhdw~UcpLe&N;VM=gpG)B&9!VE;HmQ^~52OSEds${}{Rxc6JQ?81X)1 zkhzN5$nbYN?pEz%-kEDGL;r>k0huQ(;>hkkyMz>yZX3 zyE%WAvUE!<-GSmw55dt0fT1NJfC!FKWRcq89?}qHC*VOEo9>5|N=afxKLY%hDXc9TWKN+GK!-J< z8h9-&Ezn^DO@bE==Be$C!>fZ}S}-UC%DE3~Ko7%V+Hj}==hG=V2Xg(0Afq?-;3kHF~G&l&2Kqi@vV`z{Am47Q(5CZWuB9%_0 zkU`suI8RCt9RcQ;{c9E^>OZpNz`s|Dvt|$mjtYTlYHiQzH_+Dh|A&%D|DXfu7{Y)3 z{;P1HBa=#iUSh0fZq#=_NCA%fxZ+f2&SzG1s$-( z;fdt!$iY7;wzhB^av&W?#uIET5MYjoCXwg`*VTsV=^4Ou4x5@;LZO!CW~Mq82B!L! zmXcl{>#<7uV}wy!_2I{hwS2#|&h9Z~xC;{|<2qXuJDQ@p163R|OV+mP%$Mbu7e(xV|@A;f_?)$#(@ArFM*L_{*^EuaSt<44aW%vOA5U@a* zpxNW@orjl;{a(6JI069tNCFaRYk@?9C{(g1!4D4r^!{wSAWYJ#g#OSfUdYk7Z~k$b zUjzVFWb!r(JLd`C1hAKdMGPCGqWK-g#P?;P92ze5@T0P$M{^HVdKq1hJ{{w5R_D9? zVByoyVAkB+#>b87sjR8Z4o0U?_&yQk#K}A#Ko=dQ2k(=Qw?Q?u)P!@2qlURb!jrA9 zym%S`V4jOX52HOY*yMOf1~>sqkNQE8rjcKfRkq4b04Na{28&GX;YdIO&Fc2eVnDML z@W}3o2S1Pu0Dg=RV=z!G0L=cd(B}dAijoE;fxf)`MZ7>P2atZq{2-^{3&71G0q|Mpou9$XIm2ssfWSCRf{>vb5T0(V+6I7hI057V(RMD7C0DLScinK2 zD?A8>kOnE00v^YOJsxbP>@3Apf^02Tc-#9ocEmKhxHN|Dwu@?Yj z*1BG9>lh?VO^%ODdQSPVel+H7`_7ZW`U(p}+toKXxdCD8PFBC`#6&L_rHSKFK%H;V z8KB=0@E%%o(H!8*J5H%h`P41Gq#yx+dBvvQ`q}QMt$y`k-#IvA1To!#fMM8@+6|dK ziGZ+|7L2h907-Rg@rEiKKzmxj7ywj%l{$MrS<>~E)&DO2kZ5OjdzWQ@8`cGm1-nyUk~r&e)@<@CU;-Ph;aE!sE)wYu*lhn8H(gC zH>sRgQq@=ZxQ&{5MX?I-=zZ>Sec%pW$@DmGFczhCGrRya9W8bW+}KPl;4CusNpwLe zE~-(*bYssNt|tsMgJ9P;uUDHxlOxJbaed$nFnoSrUgr9nT>mbbmXJ$$YMyVGO!)ys z__Msiu9IH_Xh7)oI9zxaRM7LrC+yi9S54inVPuq>BybZLZO3?RoE+v@ptx*(4wl7x zkTWJ+be8wrW#LzTml6`pF_swQeWh8&a*--tC%(wb&{uzflkVG;D+PY9W)DA;my+?roODFJ4&$HEsifKn^4E70#2CS+ME&m<6AzKrvh zg)>2Ei4_S#2{t!3T3(M=h`}49M=kmC4x$T^MNVkr4JNqn-i8^c=N6x8FUtAATO19) zecFPU8)yr$yILfw6_BCSo+*KBEl|tvd6z-(BCL8trfF4tpCb>LroBt+_WinhdTKiI zN6=n@D*};CDEC9szS0+@3#BTgA?cR)c;2U_H`{A`gvq9R-4eP*cEB82IT9kC_*NtZ zp5mAimNHdr@8IuX(8DO+WB<4uOsfYFugtYL9z;N<2%#N{;mh_t*Bj z&r##I1#=Yz*lv&>Qq%!)j&Y!H~sgx8OAi<^4n#>>Cau}%fuh~ z%aY$%y{sVeJJsJo_FjVEG`#x$k&r-rohq*|q}GH*HRJ2D)X9X~QHde6?N&JcT@{A^{N zGWTY}Gh3hCFUc%v2+Sl7iH(ZIAMQT9Y)9&c&Th`~&t}Z-n$umut|+Y#S32d|_KV2% z9;Y1-q0$1{0{tk}GX*1BuZtRrUQauD$$H)K&tB4&ymvC8RU|DiP1257c)gHxJGeDv zLgsr__tW>w`I#>=2TMK?KYVUOG=@Iduu{*IZE<;xU>W_GU&V}`ZyU=l%q)DhlrRN3 z7kJM3+(yj-n2aZ{3RjSvSI1lvuFlapQQ&F~Lz2ArtY0%a==@JDvOPZf%}eo)^0yd-cVQ z_wori%Ttrc^^%LSYdFn8FV&1L@wdF$;-_WTHQJOd5A^PfyVA)!BpgP*w`Mur_KY`r z*xWC=Ql224F1Z#ecK8UaSpD0nay#02+Nx?VbKH5ut0rzCzUapD;{!g=sDWNgA3wAo zZZ@+ryt245f`0X<=|Y+aP4pn&+_mwBz6Qj#F@Me}zYNW+@eKP^8m@F=Fz>nK*pdK?zI9eHHo{sWbFSR1NC%2hAbR z?Qd&}doD?Y)FeEzt$g&PuafS(Fbu9UeIcP3V<#D;4s}6SdC&>--Jz}Ct!1fOwxbxd z!=evka4`-Y*?speQst79R!UKFODn1L$LZ%dacqi*1Is6^=ZxdUBa$huObYXU>CZ=I zm6M}R)~-Dv%M4z$6*gRk3%(l1sl^Uk0cD&6q9 z0H#_#F&A;ChV}JEezx2>IrG|zUtuih7%remJKiZLH~SD`VQu_U(paHKVNSNS0pdgY zAY;{XGu_waluL~lvNOj(lJ?!Q!gaM}>C05S%X~HE2YA(eK&j$n38EBX9!A+3K|MS} zp24rS&N=Co(tcRY9PeVizqsyG-{b%B=SOvy+l(64n_1ZklJe*Ml}c61KLc0hB!l?B zTMoJe$I~Bf*7k3G+r2LI?PB@%V|+bv_@`UFTjy(MA(kND)tv3*U+=Gubep%C_b8ev z#>QvM%gYML)GT^*B#ji76^eGg4Rid(nDKuwHMBLlak3M$**CvuEvB=slu@)qWj!c* z2yaqslCSPyAQtXzmUIk+vMO0sLrpdE>4!EAw{4fY)^SaR?`&4}r$V+jA*+{{Ho|q4 z_ObserD>)ZnjP7b7KEkZ0V5BxJ04^~#CqY;c&rEGd<$L=0Jshj>@hTql_eZUCaPn1 zFzR$7h0O*4Jp(!gi}S_PK<;=i0to?Ty{H3&2p$NqleU$H6$Od+CZK|;c)MV0dt9(D zPS*o$pbyfc!`T8vJPiw?6a7g3a5@6~w=SGL-!VhLpuZtBUj+C+L1CCCs^dMdFn3K)EKU^!(||!CQ1*RH4SEa?(}Y8HLH}G}wnM6iCmd~J_K!RE z3IX<}(I{{TBq%6IJxEiXO!b05b#-+i8ZZb9rp897`7=l~EM1M{ulQTR1n-Zd5-2nR znFQKV#JZCMXb3Pn*#Bffr2H#O^8e?g*k=ZzV<`}*y2egczkya(|38#S{1@#{L*xG& z@Bb<6Z_l9MA!ximIe>~|*UnRM#}x&Rq~ftOGS!|;_WOO1w%%kK+25N?0l_rYp`b%n zSR8@0V>$dc#mWk9LGq_zNjSWP2?ER(Q6~^Q;7Bc`rjaR9`S)2BNHb$2 z4GmLGq^`E^Z>|X$7eK_5Xur80|K%S2BX_4Eh!nPG6Fij=i1#p~l8K~IZDKdj&h+2rWiS41e>{oZ^Hg?oM~n|nus@7lwwCs$ z?D1C^P>lR^nmv=VFfp>H_q)5fd2lQ2GLzyiQ{d*V|Ea<2ASCPtaP|Sxo{WhCHW08d LwKgd=cDwXHDN#*w diff --git a/applications/external/subghz_remote_configurator/icons/remote_scene/back_10px.png b/applications/external/subghz_remote_configurator/icons/remote_scene/back_10px.png deleted file mode 100644 index f9c615a99e69c0100b03a9ae7b2df903da4ecd66..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 154 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2xGmzZ=C-xtZVk{1FcVbv~PUa<$!;&U>c zv7h@-A}f&37T^=&`v3obAT#vPO>_%)r1c48n{Iv*t(u1!X;5977~7Co?ed rv9U?Av01aVRT(gMJdt+jXk=uN>R^g!*w%ImsF1<>&pI=m5)cB{fFDGZlI8yr;B3<$MxhJ?+;A4eL&#) z0Ra}bue?07WhLz78x$BO|L3mq-MMxdP^D^#YeY#(Vo9o1a#1RfVlXl=GSoFN)ipE; zF*LF=Hn1|b&^9ozGB8+QQTzo(LvDUbW?CgwgE3G~h=HkCbE6;07#-3sX@sAxkg$U?hnu$6%_P+PX_Jv(ud zLf1xG|CwUz?7R1Up5OC4zxVgNce*W&4YheW_vK(1mglK+H=%$1JZE+m`hA@F<1zI2 zyA8fptqH{ONK}=TAjGw<2*hDRkufZBKGgVD-U({Lf_l{mhyV1gGrhn5dOvmG&rb;X|2#O* z`;Gdu`y8=lFC6>o;MdlzEhu;(gUPC_z|AEGi;B+`t?Ze5z3-kKC+@xe!rsi0?9s|K zy?-daKNrL9+N8K#jUJb4ydqS`GmmU@)cv;7aPpz%>TUZsFY*}}-;x*iZ59ty6_jpT zvujoMQ}z8jJ+AG;!%Gj}Yq-_gD;(ypTplW&z40q}pQ&N1scCq0d(~q_cR%sbwf8Sv zdVdlA`l;oI1plLZ-jYiT3faL`zr6A#=Lo=7=AJsu{N?^-b1q)%coKW)>T~u}qi^rn z-7>H`clPEJwEVR7TGqAGdqR;5OY#pr*E?^={1s1Y&f(g=vM=|qHywW9AEyugs9|9K z_qUv^T38l3y>(BG-D_BB`RVoV^}JI09`V|mBd`AW<~wBWyCXky1n0|1Nlg+*V)QGN;EdcVFdq|MubW(V_Tn9{lz<&(!Cf?0&8A zl@E$Ck9Ky~46J|Y$whnDXUy8sU3Tos>?t>Un9|+}sNpj`p?cz$4F;W6I^yu1td=)j zwphHBH{ybAO5KJiY~Ik|6F0PrHpy5~o?}l42p|MCfG0x1a7;)zj7eMpo$JG-5l@?` zHXBJXB*PHMf{1m6HIN{}u@W63h2e%VF{(r~MGfORCh)5rn!{*B^Z0mvp@`R;h7ZTa zSU`M`2@oM^6GetXP{HeN+v@{V%k5_5e+8G zkwg*(VF;PVP*i$K$XbuLG3}vK5Kuyqq!%K4ie;ot)zny<8cCZ^NiaQ~ENpU0nj%lI zJjF+!xy>BKy>oC09YBCl_0@MKq5HSOc8#H zHxrPt@G@uPXxUw}=@F^kKtO1=<+RE`fZLx72 zM^h}%PZ&K2qcJ389h0U^tT{O|!J$hHs!^{hL5Gn|PU-6=pgIxrK<@yAog7DH3a%&w z8g!!r!BMD#C>udrd^9VVErOXZqga7TC7!lcqdrv)I*fX4xSm29%!}Gu0vbreA!k;g z%|4nF%vOQs$|!0w97;_$K_%JJIG$`y z0f?!B#blXMGE;<>npEzfp3l7GX_S~MYjF^T&H&=qVRY(yC*C;TeK^CKEcntEB`m4& z*s`e!#M_|0il0b3`57vUflm0by2LgR4nVX&k8KG5wO*Mw+x#3L%ravoDAo)K9*8VK z`z@R#$`oXol=A*h>SY-g(0){rAj zX|i`eX-Qc`CULv;$ClJi>UW`W?b^xP)oq_>=<%(|iFP_&{;^5&uL6OoA}L2u$;}G@ zRN$B(Zj5Yq}83M;=f=r9w8MiVD2l{4`U z0fy0oX&k*FI5pSMi{36|`Ri-l*r@*9d2H`fXk<>LZgmX9OeOkpSK|4KPBfUUdA!xx z?`7r}mf - -// Generate scene id and total number -#define ADD_SCENE(prefix, name, id) SubRemScene##id, -typedef enum { -#include "subrem_scene_config.h" - SubRemSceneNum, -} SubRemScene; -#undef ADD_SCENE - -extern const SceneManagerHandlers subrem_scene_handlers; - -// Generate scene on_enter handlers declaration -#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); -#include "subrem_scene_config.h" -#undef ADD_SCENE - -// Generate scene on_event handlers declaration -#define ADD_SCENE(prefix, name, id) \ - bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event); -#include "subrem_scene_config.h" -#undef ADD_SCENE - -// Generate scene on_exit handlers declaration -#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context); -#include "subrem_scene_config.h" -#undef ADD_SCENE diff --git a/applications/external/subghz_remote_configurator/scenes/subrem_scene_config.h b/applications/external/subghz_remote_configurator/scenes/subrem_scene_config.h deleted file mode 100644 index 08486be74..000000000 --- a/applications/external/subghz_remote_configurator/scenes/subrem_scene_config.h +++ /dev/null @@ -1,9 +0,0 @@ -ADD_SCENE(subrem, start, Start) -ADD_SCENE(subrem, open_map_file, OpenMapFile) -ADD_SCENE(subrem, remote, Remote) -ADD_SCENE(subrem, edit_menu, EditMenu) -ADD_SCENE(subrem, edit_submenu, EditSubMenu) -ADD_SCENE(subrem, edit_label, EditLabel) -ADD_SCENE(subrem, open_sub_file, OpenSubFile) -ADD_SCENE(subrem, edit_preview, EditPreview) -ADD_SCENE(subrem, enter_new_name, EnterNewName) \ No newline at end of file diff --git a/applications/external/subghz_remote_configurator/scenes/subrem_scene_open_map_file.c b/applications/external/subghz_remote_configurator/scenes/subrem_scene_open_map_file.c deleted file mode 100644 index 1ed8ea252..000000000 --- a/applications/external/subghz_remote_configurator/scenes/subrem_scene_open_map_file.c +++ /dev/null @@ -1,31 +0,0 @@ -#include "../subghz_remote_app_i.h" - -void subrem_scene_open_map_file_on_enter(void* context) { - furi_assert(context); - SubGhzRemoteApp* app = context; - - SubRemLoadMapState load_state = subrem_load_from_file(app); - uint32_t start_scene_state = - scene_manager_get_scene_state(app->scene_manager, SubRemSceneStart); - - // TODO if optimization - - if(load_state == SubRemLoadMapStateBack) { - scene_manager_previous_scene(app->scene_manager); - } else if(start_scene_state == SubmenuIndexSubRemEditMapFile) { - scene_manager_set_scene_state(app->scene_manager, SubRemSceneEditMenu, SubRemSubKeyNameUp); - scene_manager_next_scene(app->scene_manager, SubRemSceneEditMenu); - } else if(start_scene_state == SubmenuIndexSubRemOpenMapFile) { - scene_manager_next_scene(app->scene_manager, SubRemSceneRemote); - } -} - -bool subrem_scene_open_map_file_on_event(void* context, SceneManagerEvent event) { - UNUSED(context); - UNUSED(event); - return false; -} - -void subrem_scene_open_map_file_on_exit(void* context) { - UNUSED(context); -} diff --git a/applications/external/subghz_remote_configurator/scenes/subrem_scene_remote.c b/applications/external/subghz_remote_configurator/scenes/subrem_scene_remote.c deleted file mode 100644 index ebc582991..000000000 --- a/applications/external/subghz_remote_configurator/scenes/subrem_scene_remote.c +++ /dev/null @@ -1,115 +0,0 @@ -#include "../subghz_remote_app_i.h" -#include "../views/remote.h" - -#include - -#define TAG "SubRemScenRemote" - -void subrem_scene_remote_callback(SubRemCustomEvent event, void* context) { - furi_assert(context); - SubGhzRemoteApp* app = context; - view_dispatcher_send_custom_event(app->view_dispatcher, event); -} - -void subrem_scene_remote_raw_callback_end_tx(void* context) { - furi_assert(context); - SubGhzRemoteApp* app = context; - view_dispatcher_send_custom_event(app->view_dispatcher, SubRemCustomEventViewRemoteForcedStop); -} - -static uint8_t subrem_scene_remote_event_to_index(SubRemCustomEvent event_id) { - uint8_t ret = 0; - - if(event_id == SubRemCustomEventViewRemoteStartUP) { - ret = SubRemSubKeyNameUp; - } else if(event_id == SubRemCustomEventViewRemoteStartDOWN) { - ret = SubRemSubKeyNameDown; - } else if(event_id == SubRemCustomEventViewRemoteStartLEFT) { - ret = SubRemSubKeyNameLeft; - } else if(event_id == SubRemCustomEventViewRemoteStartRIGHT) { - ret = SubRemSubKeyNameRight; - } else if(event_id == SubRemCustomEventViewRemoteStartOK) { - ret = SubRemSubKeyNameOk; - } - - return ret; -} - -void subrem_scene_remote_on_enter(void* context) { - SubGhzRemoteApp* app = context; - - subrem_view_remote_update_data_labels(app->subrem_remote_view, app->map_preset->subs_preset); - - subrem_view_remote_set_callback(app->subrem_remote_view, subrem_scene_remote_callback, app); - - view_dispatcher_switch_to_view(app->view_dispatcher, SubRemViewIDRemote); -} - -bool subrem_scene_remote_on_event(void* context, SceneManagerEvent event) { - SubGhzRemoteApp* app = context; - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SubRemCustomEventViewRemoteBack) { - if(!scene_manager_previous_scene(app->scene_manager)) { - scene_manager_stop(app->scene_manager); - view_dispatcher_stop(app->view_dispatcher); - } - return true; - } else if( - event.event == SubRemCustomEventViewRemoteStartUP || - event.event == SubRemCustomEventViewRemoteStartDOWN || - event.event == SubRemCustomEventViewRemoteStartLEFT || - event.event == SubRemCustomEventViewRemoteStartRIGHT || - event.event == SubRemCustomEventViewRemoteStartOK) { - // Start sending sub - subrem_tx_stop_sub(app, true); - - uint8_t chusen_sub = subrem_scene_remote_event_to_index(event.event); - app->chusen_sub = chusen_sub; - - subrem_view_remote_set_state( - app->subrem_remote_view, SubRemViewRemoteStateLoading, chusen_sub); - - if(subrem_tx_start_sub(app, app->map_preset->subs_preset[chusen_sub])) { - if(app->map_preset->subs_preset[chusen_sub]->type == SubGhzProtocolTypeRAW) { - subghz_txrx_set_raw_file_encoder_worker_callback_end( - app->txrx, subrem_scene_remote_raw_callback_end_tx, app); - } - subrem_view_remote_set_state( - app->subrem_remote_view, SubRemViewRemoteStateSending, chusen_sub); - notification_message(app->notifications, &sequence_blink_start_magenta); - } else { - subrem_view_remote_set_state( - app->subrem_remote_view, SubRemViewRemoteStateIdle, 0); - notification_message(app->notifications, &sequence_blink_red_100); - } - return true; - } else if(event.event == SubRemCustomEventViewRemoteForcedStop) { - subrem_tx_stop_sub(app, true); - subrem_view_remote_set_state(app->subrem_remote_view, SubRemViewRemoteStateIdle, 0); - - notification_message(app->notifications, &sequence_blink_stop); - return true; - } else if(event.event == SubRemCustomEventViewRemoteStop) { - if(subrem_tx_stop_sub(app, false)) { - subrem_view_remote_set_state( - app->subrem_remote_view, SubRemViewRemoteStateIdle, 0); - - notification_message(app->notifications, &sequence_blink_stop); - } - return true; - } - } - // } else if(event.type == SceneManagerEventTypeTick) { - // } - return false; -} - -void subrem_scene_remote_on_exit(void* context) { - SubGhzRemoteApp* app = context; - - subrem_tx_stop_sub(app, true); - - subrem_view_remote_set_state(app->subrem_remote_view, SubRemViewRemoteStateIdle, 0); - - notification_message(app->notifications, &sequence_blink_stop); -} diff --git a/applications/external/subghz_remote_configurator/scenes/subrem_scene_start.c b/applications/external/subghz_remote_configurator/scenes/subrem_scene_start.c deleted file mode 100644 index 0f3399b7c..000000000 --- a/applications/external/subghz_remote_configurator/scenes/subrem_scene_start.c +++ /dev/null @@ -1,100 +0,0 @@ -#include "../subghz_remote_app_i.h" -#include "../helpers/subrem_custom_event.h" - -void subrem_scene_start_submenu_callback(void* context, uint32_t index) { - furi_assert(context); - SubGhzRemoteApp* app = context; - - view_dispatcher_send_custom_event(app->view_dispatcher, index); -} - -void subrem_scene_start_on_enter(void* context) { - furi_assert(context); - - SubGhzRemoteApp* app = context; - Submenu* submenu = app->submenu; - submenu_add_item( - submenu, - "Open Map File", - SubmenuIndexSubRemOpenMapFile, - subrem_scene_start_submenu_callback, - app); -#if FURI_DEBUG - submenu_add_item( - submenu, - "Remote_Debug", - SubmenuIndexSubRemRemoteView, - subrem_scene_start_submenu_callback, - app); -#endif - submenu_add_item( - submenu, - "Edit Map File", - SubmenuIndexSubRemEditMapFile, - subrem_scene_start_submenu_callback, - app); - submenu_add_item( - submenu, - "New Map File", - SubmenuIndexSubRemNewMapFile, - subrem_scene_start_submenu_callback, - app); - // submenu_add_item( - // submenu, - // "About", - // SubmenuIndexSubGhzRemoteAbout, - // subrem_scene_start_submenu_callback, - // app); - - submenu_set_selected_item( - submenu, scene_manager_get_scene_state(app->scene_manager, SubRemSceneStart)); - - view_dispatcher_switch_to_view(app->view_dispatcher, SubRemViewIDSubmenu); -} - -bool subrem_scene_start_on_event(void* context, SceneManagerEvent event) { - furi_assert(context); - - SubGhzRemoteApp* app = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SubmenuIndexSubRemOpenMapFile) { - scene_manager_set_scene_state( - app->scene_manager, SubRemSceneStart, SubmenuIndexSubRemOpenMapFile); - - scene_manager_next_scene(app->scene_manager, SubRemSceneOpenMapFile); - consumed = true; - } -#if FURI_DEBUG - else if(event.event == SubmenuIndexSubRemRemoteView) { - scene_manager_next_scene(app->scene_manager, SubRemSceneRemote); - consumed = true; - } -#endif - else if(event.event == SubmenuIndexSubRemEditMapFile) { - scene_manager_set_scene_state( - app->scene_manager, SubRemSceneStart, SubmenuIndexSubRemEditMapFile); - scene_manager_next_scene(app->scene_manager, SubRemSceneOpenMapFile); - consumed = true; - } else if(event.event == SubmenuIndexSubRemNewMapFile) { - scene_manager_set_scene_state( - app->scene_manager, SubRemSceneStart, SubmenuIndexSubRemNewMapFile); - scene_manager_next_scene(app->scene_manager, SubRemSceneEnterNewName); - consumed = true; - } - // } else if(event.event == SubmenuIndexSubRemAbout) { - // scene_manager_next_scene(app->scene_manager, SubRemSceneAbout); - // consumed = true; - // } - } - - return consumed; -} - -void subrem_scene_start_on_exit(void* context) { - furi_assert(context); - - SubGhzRemoteApp* app = context; - submenu_reset(app->submenu); -} diff --git a/applications/external/subghz_remote_configurator/subghz_remote_app.c b/applications/external/subghz_remote_configurator/subghz_remote_app.c deleted file mode 100644 index f268fea78..000000000 --- a/applications/external/subghz_remote_configurator/subghz_remote_app.c +++ /dev/null @@ -1,178 +0,0 @@ -#include "subghz_remote_app_i.h" - -static bool subghz_remote_app_custom_event_callback(void* context, uint32_t event) { - furi_assert(context); - SubGhzRemoteApp* app = context; - return scene_manager_handle_custom_event(app->scene_manager, event); -} - -static bool subghz_remote_app_back_event_callback(void* context) { - furi_assert(context); - SubGhzRemoteApp* app = context; - return scene_manager_handle_back_event(app->scene_manager); -} - -static void subghz_remote_app_tick_event_callback(void* context) { - furi_assert(context); - SubGhzRemoteApp* app = context; - scene_manager_handle_tick_event(app->scene_manager); -} - -SubGhzRemoteApp* subghz_remote_app_alloc() { - SubGhzRemoteApp* app = malloc(sizeof(SubGhzRemoteApp)); - - Storage* storage = furi_record_open(RECORD_STORAGE); - storage_common_migrate(storage, EXT_PATH("unirf"), SUBREM_APP_FOLDER); - - if(!storage_simply_mkdir(storage, SUBREM_APP_FOLDER)) { - //FURI_LOG_E(TAG, "Could not create folder %s", SUBREM_APP_FOLDER); - } - furi_record_close(RECORD_STORAGE); - - furi_hal_power_suppress_charge_enter(); - - app->file_path = furi_string_alloc(); - furi_string_set(app->file_path, SUBREM_APP_FOLDER); - - // GUI - app->gui = furi_record_open(RECORD_GUI); - - // View Dispatcher - app->view_dispatcher = view_dispatcher_alloc(); - - app->scene_manager = scene_manager_alloc(&subrem_scene_handlers, app); - view_dispatcher_enable_queue(app->view_dispatcher); - - view_dispatcher_set_event_callback_context(app->view_dispatcher, app); - view_dispatcher_set_custom_event_callback( - app->view_dispatcher, subghz_remote_app_custom_event_callback); - view_dispatcher_set_navigation_event_callback( - app->view_dispatcher, subghz_remote_app_back_event_callback); - view_dispatcher_set_tick_event_callback( - app->view_dispatcher, subghz_remote_app_tick_event_callback, 100); - - view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); - - // Open Notification record - app->notifications = furi_record_open(RECORD_NOTIFICATION); - - // SubMenu - app->submenu = submenu_alloc(); - view_dispatcher_add_view( - app->view_dispatcher, SubRemViewIDSubmenu, submenu_get_view(app->submenu)); - - // Dialog - app->dialogs = furi_record_open(RECORD_DIALOGS); - - // TextInput - app->text_input = text_input_alloc(); - view_dispatcher_add_view( - app->view_dispatcher, SubRemViewIDTextInput, text_input_get_view(app->text_input)); - - // Widget - app->widget = widget_alloc(); - view_dispatcher_add_view( - app->view_dispatcher, SubRemViewIDWidget, widget_get_view(app->widget)); - - // Popup - app->popup = popup_alloc(); - view_dispatcher_add_view(app->view_dispatcher, SubRemViewIDPopup, popup_get_view(app->popup)); - - // Remote view - app->subrem_remote_view = subrem_view_remote_alloc(); - view_dispatcher_add_view( - app->view_dispatcher, - SubRemViewIDRemote, - subrem_view_remote_get_view(app->subrem_remote_view)); - - // Edit Menu view - app->subrem_edit_menu = subrem_view_edit_menu_alloc(); - view_dispatcher_add_view( - app->view_dispatcher, - SubRemViewIDEditMenu, - subrem_view_edit_menu_get_view(app->subrem_edit_menu)); - - app->map_preset = malloc(sizeof(SubRemMapPreset)); - for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) { - app->map_preset->subs_preset[i] = subrem_sub_file_preset_alloc(); - } - - app->txrx = subghz_txrx_alloc(); - - subghz_txrx_set_need_save_callback(app->txrx, subrem_save_active_sub, app); - - app->map_not_saved = false; - - scene_manager_next_scene(app->scene_manager, SubRemSceneStart); - scene_manager_next_scene(app->scene_manager, SubRemSceneOpenMapFile); - - return app; -} - -void subghz_remote_app_free(SubGhzRemoteApp* app) { - furi_assert(app); - - furi_hal_power_suppress_charge_exit(); - - // Submenu - view_dispatcher_remove_view(app->view_dispatcher, SubRemViewIDSubmenu); - submenu_free(app->submenu); - - // Dialog - furi_record_close(RECORD_DIALOGS); - - // TextInput - view_dispatcher_remove_view(app->view_dispatcher, SubRemViewIDTextInput); - text_input_free(app->text_input); - - // Widget - view_dispatcher_remove_view(app->view_dispatcher, SubRemViewIDWidget); - widget_free(app->widget); - - // Popup - view_dispatcher_remove_view(app->view_dispatcher, SubRemViewIDPopup); - popup_free(app->popup); - - // Remote view - view_dispatcher_remove_view(app->view_dispatcher, SubRemViewIDRemote); - subrem_view_remote_free(app->subrem_remote_view); - - // Edit view - view_dispatcher_remove_view(app->view_dispatcher, SubRemViewIDEditMenu); - subrem_view_edit_menu_free(app->subrem_edit_menu); - - scene_manager_free(app->scene_manager); - view_dispatcher_free(app->view_dispatcher); - - subghz_txrx_free(app->txrx); - - for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) { - subrem_sub_file_preset_free(app->map_preset->subs_preset[i]); - } - free(app->map_preset); - - // Notifications - furi_record_close(RECORD_NOTIFICATION); - app->notifications = NULL; - - // Close records - furi_record_close(RECORD_GUI); - - // Path strings - furi_string_free(app->file_path); - - free(app); -} - -int32_t subghz_remote_config_app(void* p) { - UNUSED(p); - SubGhzRemoteApp* subghz_remote_app = subghz_remote_app_alloc(); - - furi_string_set(subghz_remote_app->file_path, SUBREM_APP_FOLDER); - - view_dispatcher_run(subghz_remote_app->view_dispatcher); - - subghz_remote_app_free(subghz_remote_app); - - return 0; -} diff --git a/applications/external/subghz_remote_configurator/subghz_remote_app_i.c b/applications/external/subghz_remote_configurator/subghz_remote_app_i.c deleted file mode 100644 index 82e762c2a..000000000 --- a/applications/external/subghz_remote_configurator/subghz_remote_app_i.c +++ /dev/null @@ -1,320 +0,0 @@ -#include "subghz_remote_app_i.h" -#include -#include - -#include "helpers/txrx/subghz_txrx.h" - -// #include -// #include - -#include -#include - -#define TAG "SubGhzRemote" - -static const char* map_file_labels[SubRemSubKeyNameMaxCount][2] = { - [SubRemSubKeyNameUp] = {"UP", "ULABEL"}, - [SubRemSubKeyNameDown] = {"DOWN", "DLABEL"}, - [SubRemSubKeyNameLeft] = {"LEFT", "LLABEL"}, - [SubRemSubKeyNameRight] = {"RIGHT", "RLABEL"}, - [SubRemSubKeyNameOk] = {"OK", "OKLABEL"}, -}; - -void subrem_map_preset_reset(SubRemMapPreset* map_preset) { - furi_assert(map_preset); - - for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) { - subrem_sub_file_preset_reset(map_preset->subs_preset[i]); - } -} - -static SubRemLoadMapState subrem_map_preset_check( - SubRemMapPreset* map_preset, - SubGhzTxRx* txrx, - FlipperFormat* fff_data_file) { - furi_assert(map_preset); - furi_assert(txrx); - - bool all_loaded = true; - SubRemLoadMapState ret = SubRemLoadMapStateErrorBrokenFile; - - SubRemLoadSubState sub_loadig_state; - SubRemSubFilePreset* sub_preset; - - for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) { - sub_preset = map_preset->subs_preset[i]; - - sub_loadig_state = SubRemLoadSubStateErrorNoFile; - - if(furi_string_empty(sub_preset->file_path)) { - // FURI_LOG_I(TAG, "Empty file path"); - } else if(!flipper_format_file_open_existing( - fff_data_file, furi_string_get_cstr(sub_preset->file_path))) { - sub_preset->load_state = SubRemLoadSubStateErrorNoFile; - FURI_LOG_W(TAG, "Error open file %s", furi_string_get_cstr(sub_preset->file_path)); - } else { - sub_loadig_state = subrem_sub_preset_load(sub_preset, txrx, fff_data_file); - } - - if(sub_loadig_state != SubRemLoadSubStateOK) { - all_loaded = false; - } else { - ret = SubRemLoadMapStateNotAllOK; - } - - if(ret != SubRemLoadMapStateErrorBrokenFile && all_loaded) { - ret = SubRemLoadMapStateOK; - } - - flipper_format_file_close(fff_data_file); - } - - return ret; -} - -static bool subrem_map_preset_load(SubRemMapPreset* map_preset, FlipperFormat* fff_data_file) { - furi_assert(map_preset); - bool ret = false; - SubRemSubFilePreset* sub_preset; - for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) { - sub_preset = map_preset->subs_preset[i]; - if(!flipper_format_read_string( - fff_data_file, map_file_labels[i][0], sub_preset->file_path)) { -#if FURI_DEBUG - FURI_LOG_W(TAG, "No file patch for %s", map_file_labels[i][0]); -#endif - sub_preset->type = SubGhzProtocolTypeUnknown; - } else if(!path_contains_only_ascii(furi_string_get_cstr(sub_preset->file_path))) { - FURI_LOG_E(TAG, "Incorrect characters in [%s] file path", map_file_labels[i][0]); - sub_preset->type = SubGhzProtocolTypeUnknown; - } else if(!flipper_format_rewind(fff_data_file)) { - // Rewind error - } else if(!flipper_format_read_string( - fff_data_file, map_file_labels[i][1], sub_preset->label)) { -#if FURI_DEBUG - FURI_LOG_W(TAG, "No Label for %s", map_file_labels[i][0]); -#endif - ret = true; - } else { - ret = true; - } - if(ret) { - // Preload seccesful - FURI_LOG_I( - TAG, - "%-5s: %s %s", - map_file_labels[i][0], - furi_string_get_cstr(sub_preset->label), - furi_string_get_cstr(sub_preset->file_path)); - sub_preset->load_state = SubRemLoadSubStatePreloaded; - } - - flipper_format_rewind(fff_data_file); - } - return ret; -} - -SubRemLoadMapState subrem_map_file_load(SubGhzRemoteApp* app, const char* file_path) { - furi_assert(app); - furi_assert(file_path); -#if FURI_DEBUG - FURI_LOG_I(TAG, "Load Map File Start"); -#endif - Storage* storage = furi_record_open(RECORD_STORAGE); - FlipperFormat* fff_data_file = flipper_format_file_alloc(storage); - SubRemLoadMapState ret = SubRemLoadMapStateErrorOpenError; -#if FURI_DEBUG - FURI_LOG_I(TAG, "Open Map File.."); -#endif - subrem_map_preset_reset(app->map_preset); - - if(!flipper_format_file_open_existing(fff_data_file, file_path)) { - FURI_LOG_E(TAG, "Could not open MAP file %s", file_path); - ret = SubRemLoadMapStateErrorOpenError; - } else { - if(!subrem_map_preset_load(app->map_preset, fff_data_file)) { - FURI_LOG_E(TAG, "Could no Sub file path in MAP file"); - // ret = // error for popup - } else if(!flipper_format_file_close(fff_data_file)) { - ret = SubRemLoadMapStateErrorOpenError; - } else { - ret = subrem_map_preset_check(app->map_preset, app->txrx, fff_data_file); - } - } - - if(ret == SubRemLoadMapStateOK) { - FURI_LOG_I(TAG, "Load Map File Seccesful"); - } else if(ret == SubRemLoadMapStateNotAllOK) { - FURI_LOG_I(TAG, "Load Map File Seccesful [Not all files]"); - } else { - FURI_LOG_E(TAG, "Broken Map File"); - } - - flipper_format_file_close(fff_data_file); - flipper_format_free(fff_data_file); - - furi_record_close(RECORD_STORAGE); - return ret; -} - -bool subrem_save_protocol_to_file(FlipperFormat* flipper_format, const char* sub_file_name) { - furi_assert(flipper_format); - furi_assert(sub_file_name); - - Storage* storage = furi_record_open(RECORD_STORAGE); - Stream* flipper_format_stream = flipper_format_get_raw_stream(flipper_format); - - bool saved = false; - uint32_t repeat = 200; - FuriString* file_dir = furi_string_alloc(); - - path_extract_dirname(sub_file_name, file_dir); - do { - // removing additional fields - flipper_format_delete_key(flipper_format, "Repeat"); - // flipper_format_delete_key(flipper_format, "Manufacture"); - - if(!storage_simply_remove(storage, sub_file_name)) { - break; - } - - //ToDo check Write - stream_seek(flipper_format_stream, 0, StreamOffsetFromStart); - stream_save_to_file(flipper_format_stream, storage, sub_file_name, FSOM_CREATE_ALWAYS); - - if(!flipper_format_insert_or_update_uint32(flipper_format, "Repeat", &repeat, 1)) { - FURI_LOG_E(TAG, "Unable Repeat"); - break; - } - - saved = true; - } while(0); - - furi_string_free(file_dir); - furi_record_close(RECORD_STORAGE); - return saved; -} - -void subrem_save_active_sub(void* context) { - furi_assert(context); - SubGhzRemoteApp* app = context; - - SubRemSubFilePreset* sub_preset = app->map_preset->subs_preset[app->chusen_sub]; - subrem_save_protocol_to_file( - sub_preset->fff_data, furi_string_get_cstr(sub_preset->file_path)); -} - -bool subrem_tx_start_sub(SubGhzRemoteApp* app, SubRemSubFilePreset* sub_preset) { - furi_assert(app); - furi_assert(sub_preset); - bool ret = false; - - subrem_tx_stop_sub(app, true); - - if(sub_preset->type == SubGhzProtocolTypeUnknown) { - ret = false; - } else { - FURI_LOG_I(TAG, "Send %s", furi_string_get_cstr(sub_preset->label)); - - subghz_txrx_load_decoder_by_name_protocol( - app->txrx, furi_string_get_cstr(sub_preset->protocaol_name)); - - subghz_txrx_set_preset( - app->txrx, - furi_string_get_cstr(sub_preset->freq_preset.name), - sub_preset->freq_preset.frequency, - NULL, - 0); - - subghz_custom_btns_reset(); - - if(subghz_txrx_tx_start(app->txrx, sub_preset->fff_data) == SubGhzTxRxStartTxStateOk) { - ret = true; - } - } - - return ret; -} - -bool subrem_tx_stop_sub(SubGhzRemoteApp* app, bool forced) { - furi_assert(app); - SubRemSubFilePreset* sub_preset = app->map_preset->subs_preset[app->chusen_sub]; - - if(forced || (sub_preset->type != SubGhzProtocolTypeRAW)) { - subghz_txrx_stop(app->txrx); - - if(sub_preset->type == SubGhzProtocolTypeDynamic) { - subghz_txrx_reset_dynamic_and_custom_btns(app->txrx); - } - subghz_custom_btns_reset(); - - return true; - } - - return false; -} - -SubRemLoadMapState subrem_load_from_file(SubGhzRemoteApp* app) { - furi_assert(app); - - FuriString* file_path = furi_string_alloc(); - SubRemLoadMapState ret = SubRemLoadMapStateBack; - - DialogsFileBrowserOptions browser_options; - dialog_file_browser_set_basic_options(&browser_options, SUBREM_APP_EXTENSION, &I_subrem_10px); - browser_options.base_path = SUBREM_APP_FOLDER; - - // Input events and views are managed by file_select - if(!dialog_file_browser_show(app->dialogs, app->file_path, app->file_path, &browser_options)) { - } else { - ret = subrem_map_file_load(app, furi_string_get_cstr(app->file_path)); - } - - furi_string_free(file_path); - - return ret; -} - -bool subrem_save_map_to_file(SubGhzRemoteApp* app) { - furi_assert(app); - - const char* file_name = furi_string_get_cstr(app->file_path); - bool saved = false; - FlipperFormat* fff_data = flipper_format_string_alloc(); - - SubRemSubFilePreset* sub_preset; - - flipper_format_write_header_cstr( - fff_data, SUBREM_APP_APP_FILE_TYPE, SUBREM_APP_APP_FILE_VERSION); - for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) { - sub_preset = app->map_preset->subs_preset[i]; - if(!furi_string_empty(sub_preset->file_path)) { - flipper_format_write_string(fff_data, map_file_labels[i][0], sub_preset->file_path); - } - } - for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) { - sub_preset = app->map_preset->subs_preset[i]; - if(!furi_string_empty(sub_preset->file_path)) { - flipper_format_write_string(fff_data, map_file_labels[i][1], sub_preset->label); - } - } - - Storage* storage = furi_record_open(RECORD_STORAGE); - Stream* flipper_format_stream = flipper_format_get_raw_stream(fff_data); - - do { - if(!storage_simply_remove(storage, file_name)) { - break; - } - //ToDo check Write - stream_seek(flipper_format_stream, 0, StreamOffsetFromStart); - stream_save_to_file(flipper_format_stream, storage, file_name, FSOM_CREATE_ALWAYS); - - saved = true; - } while(0); - - furi_record_close(RECORD_STORAGE); - flipper_format_free(fff_data); - - return saved; -} \ No newline at end of file diff --git a/applications/external/subghz_remote_configurator/subghz_remote_app_i.h b/applications/external/subghz_remote_configurator/subghz_remote_app_i.h deleted file mode 100644 index d200bdf96..000000000 --- a/applications/external/subghz_remote_configurator/subghz_remote_app_i.h +++ /dev/null @@ -1,68 +0,0 @@ -#pragma once - -#include "helpers/subrem_types.h" -#include "helpers/subrem_presets.h" -#include "scenes/subrem_scene.h" - -#include "helpers/txrx/subghz_txrx.h" - -#include - -#include "views/remote.h" -#include "views/edit_menu.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#define SUBREM_APP_FOLDER EXT_PATH("subghz_remote") -#define SUBREM_MAX_LEN_NAME 64 - -typedef struct { - Gui* gui; - ViewDispatcher* view_dispatcher; - SceneManager* scene_manager; - NotificationApp* notifications; - DialogsApp* dialogs; - Widget* widget; - Popup* popup; - TextInput* text_input; - Submenu* submenu; - - FuriString* file_path; - char file_name_tmp[SUBREM_MAX_LEN_NAME]; - - SubRemViewRemote* subrem_remote_view; - SubRemViewEditMenu* subrem_edit_menu; - - SubRemMapPreset* map_preset; - - SubGhzTxRx* txrx; - - bool map_not_saved; - - uint8_t chusen_sub; -} SubGhzRemoteApp; - -SubRemLoadMapState subrem_load_from_file(SubGhzRemoteApp* app); - -bool subrem_tx_start_sub(SubGhzRemoteApp* app, SubRemSubFilePreset* sub_preset); - -bool subrem_tx_stop_sub(SubGhzRemoteApp* app, bool forced); - -SubRemLoadMapState subrem_map_file_load(SubGhzRemoteApp* app, const char* file_path); - -void subrem_map_preset_reset(SubRemMapPreset* map_preset); - -bool subrem_save_map_to_file(SubGhzRemoteApp* app); - -void subrem_save_active_sub(void* context); \ No newline at end of file diff --git a/applications/external/subghz_remote_configurator/views/remote.c b/applications/external/subghz_remote_configurator/views/remote.c deleted file mode 100644 index c2b41cfd6..000000000 --- a/applications/external/subghz_remote_configurator/views/remote.c +++ /dev/null @@ -1,294 +0,0 @@ -#include "remote.h" -#include "../subghz_remote_app_i.h" - -#include -#include - -#include - -#define SUBREM_VIEW_REMOTE_MAX_LABEL_LENGTH 30 -#define SUBREM_VIEW_REMOTE_LEFT_OFFSET 10 -#define SUBREM_VIEW_REMOTE_RIGHT_OFFSET 22 - -struct SubRemViewRemote { - View* view; - SubRemViewRemoteCallback callback; - void* context; -}; - -typedef struct { - char* labels[SubRemSubKeyNameMaxCount]; - - SubRemViewRemoteState state; - - uint8_t pressed_btn; -} SubRemViewRemoteModel; - -void subrem_view_remote_set_callback( - SubRemViewRemote* subrem_view_remote, - SubRemViewRemoteCallback callback, - void* context) { - furi_assert(subrem_view_remote); - - subrem_view_remote->callback = callback; - subrem_view_remote->context = context; -} - -void subrem_view_remote_update_data_labels( - SubRemViewRemote* subrem_view_remote, - SubRemSubFilePreset** subs_presets) { - furi_assert(subrem_view_remote); - furi_assert(subs_presets); - - FuriString* labels[SubRemSubKeyNameMaxCount]; - SubRemSubFilePreset* sub_preset; - - for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) { - sub_preset = subs_presets[i]; - switch(sub_preset->load_state) { - case SubRemLoadSubStateOK: - if(!furi_string_empty(sub_preset->label)) { - labels[i] = furi_string_alloc_set(sub_preset->label); - } else if(!furi_string_empty(sub_preset->file_path)) { - labels[i] = furi_string_alloc(); - path_extract_filename(sub_preset->file_path, labels[i], true); - } else { - labels[i] = furi_string_alloc_set("Empty Label"); - } - break; - - case SubRemLoadSubStateErrorNoFile: - labels[i] = furi_string_alloc_set("[X] Can't open file"); - break; - - case SubRemLoadSubStateErrorFreq: - case SubRemLoadSubStateErrorMod: - case SubRemLoadSubStateErrorProtocol: - labels[i] = furi_string_alloc_set("[X] Error in .sub file"); - break; - - default: - labels[i] = furi_string_alloc_set(""); - break; - } - } - - with_view_model( - subrem_view_remote->view, - SubRemViewRemoteModel * model, - { - for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) { - strncpy( - model->labels[i], - furi_string_get_cstr(labels[i]), - SUBREM_VIEW_REMOTE_MAX_LABEL_LENGTH); - } - }, - true); - - for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) { - furi_string_free(labels[i]); - } -} - -void subrem_view_remote_set_state( - SubRemViewRemote* subrem_view_remote, - SubRemViewRemoteState state, - uint8_t presed_btn) { - furi_assert(subrem_view_remote); - with_view_model( - subrem_view_remote->view, - SubRemViewRemoteModel * model, - { - model->state = state; - model->pressed_btn = presed_btn; - }, - true); -} - -void subrem_view_remote_draw(Canvas* canvas, SubRemViewRemoteModel* model) { - canvas_clear(canvas); - canvas_set_color(canvas, ColorBlack); - - canvas_clear(canvas); - - //Icons for Labels - //canvas_draw_icon(canvas, 0, 0, &I_SubGHzRemote_LeftAlignedButtons_9x64); - canvas_draw_icon(canvas, 1, 5, &I_ButtonUp_7x4); - canvas_draw_icon(canvas, 1, 15, &I_ButtonDown_7x4); - canvas_draw_icon(canvas, 2, 23, &I_ButtonLeft_4x7); - canvas_draw_icon(canvas, 2, 33, &I_ButtonRight_4x7); - canvas_draw_icon(canvas, 0, 42, &I_Ok_btn_9x9); - canvas_draw_icon(canvas, 0, 53, &I_back_10px); - - //Labels - canvas_set_font(canvas, FontSecondary); - uint8_t y = 0; - for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) { - elements_text_box( - canvas, - SUBREM_VIEW_REMOTE_LEFT_OFFSET, - y + 2, - 126 - SUBREM_VIEW_REMOTE_LEFT_OFFSET - SUBREM_VIEW_REMOTE_RIGHT_OFFSET, - 12, - AlignLeft, - AlignBottom, - model->labels[i], - false); - y += 10; - } - - if(model->state == SubRemViewRemoteStateOFF) { - elements_button_left(canvas, "Back"); - elements_button_right(canvas, "Save"); - } else { - canvas_draw_str_aligned(canvas, 11, 62, AlignLeft, AlignBottom, "Hold=Exit."); - } - - //Status text and indicator - canvas_draw_icon(canvas, 113, 15, &I_Pin_cell_13x13); - - if(model->state == SubRemViewRemoteStateIdle || model->state == SubRemViewRemoteStateOFF) { - canvas_draw_str_aligned(canvas, 126, 10, AlignRight, AlignBottom, "Idle"); - } else { - switch(model->state) { - case SubRemViewRemoteStateSending: - canvas_draw_str_aligned(canvas, 126, 10, AlignRight, AlignBottom, "Send"); - break; - case SubRemViewRemoteStateLoading: - canvas_draw_str_aligned(canvas, 126, 10, AlignRight, AlignBottom, "Load"); - break; - default: -#if FURI_DEBUG - canvas_draw_str_aligned(canvas, 126, 10, AlignRight, AlignBottom, "Wrong_state"); -#endif - break; - } - - switch(model->pressed_btn) { - case SubRemSubKeyNameUp: - canvas_draw_icon(canvas, 116, 17, &I_Pin_arrow_up_7x9); - break; - case SubRemSubKeyNameDown: - canvas_draw_icon_ex(canvas, 116, 17, &I_Pin_arrow_up_7x9, IconRotation180); - break; - case SubRemSubKeyNameLeft: - canvas_draw_icon_ex(canvas, 115, 18, &I_Pin_arrow_up_7x9, IconRotation270); - break; - case SubRemSubKeyNameRight: - canvas_draw_icon_ex(canvas, 115, 18, &I_Pin_arrow_up_7x9, IconRotation90); - break; - case SubRemSubKeyNameOk: - canvas_draw_icon(canvas, 116, 18, &I_Pin_star_7x7); - break; - } - } -} - -bool subrem_view_remote_input(InputEvent* event, void* context) { - furi_assert(context); - SubRemViewRemote* subrem_view_remote = context; - - if(event->key == InputKeyBack && event->type == InputTypeLong) { - subrem_view_remote->callback(SubRemCustomEventViewRemoteBack, subrem_view_remote->context); - return true; - } else if(event->key == InputKeyBack && event->type == InputTypeShort) { - with_view_model( - subrem_view_remote->view, - SubRemViewRemoteModel * model, - { model->pressed_btn = 0; }, - true); - subrem_view_remote->callback( - SubRemCustomEventViewRemoteForcedStop, subrem_view_remote->context); - return true; - } else if(event->key == InputKeyBack) { - return true; - } - // BACK button processing end - - if(event->key == InputKeyUp && event->type == InputTypePress) { - subrem_view_remote->callback( - SubRemCustomEventViewRemoteStartUP, subrem_view_remote->context); - return true; - } else if(event->key == InputKeyDown && event->type == InputTypePress) { - subrem_view_remote->callback( - SubRemCustomEventViewRemoteStartDOWN, subrem_view_remote->context); - return true; - } else if(event->key == InputKeyLeft && event->type == InputTypePress) { - subrem_view_remote->callback( - SubRemCustomEventViewRemoteStartLEFT, subrem_view_remote->context); - return true; - } else if(event->key == InputKeyRight && event->type == InputTypePress) { - subrem_view_remote->callback( - SubRemCustomEventViewRemoteStartRIGHT, subrem_view_remote->context); - return true; - } else if(event->key == InputKeyOk && event->type == InputTypePress) { - subrem_view_remote->callback( - SubRemCustomEventViewRemoteStartOK, subrem_view_remote->context); - return true; - } else if(event->type == InputTypeRelease) { - subrem_view_remote->callback(SubRemCustomEventViewRemoteStop, subrem_view_remote->context); - return true; - } - - return true; -} - -void subrem_view_remote_enter(void* context) { - furi_assert(context); -} - -void subrem_view_remote_exit(void* context) { - furi_assert(context); -} - -SubRemViewRemote* subrem_view_remote_alloc() { - SubRemViewRemote* subrem_view_remote = malloc(sizeof(SubRemViewRemote)); - - // View allocation and configuration - subrem_view_remote->view = view_alloc(); - view_allocate_model( - subrem_view_remote->view, ViewModelTypeLocking, sizeof(SubRemViewRemoteModel)); - view_set_context(subrem_view_remote->view, subrem_view_remote); - view_set_draw_callback(subrem_view_remote->view, (ViewDrawCallback)subrem_view_remote_draw); - view_set_input_callback(subrem_view_remote->view, subrem_view_remote_input); - view_set_enter_callback(subrem_view_remote->view, subrem_view_remote_enter); - view_set_exit_callback(subrem_view_remote->view, subrem_view_remote_exit); - - with_view_model( - subrem_view_remote->view, - SubRemViewRemoteModel * model, - { - model->state = SubRemViewRemoteStateIdle; - - for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) { - model->labels[i] = malloc(sizeof(char) * SUBREM_VIEW_REMOTE_MAX_LABEL_LENGTH + 1); - strcpy(model->labels[i], ""); - } - - model->pressed_btn = 0; - }, - true); - return subrem_view_remote; -} - -void subrem_view_remote_free(SubRemViewRemote* subghz_remote) { - furi_assert(subghz_remote); - - with_view_model( - subghz_remote->view, - SubRemViewRemoteModel * model, - { - for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) { - free(model->labels[i]); - } - }, - true); - view_free(subghz_remote->view); - free(subghz_remote); -} - -View* subrem_view_remote_get_view(SubRemViewRemote* subrem_view_remote) { - furi_assert(subrem_view_remote); - return subrem_view_remote->view; -} \ No newline at end of file diff --git a/applications/external/subghz_remote_configurator/views/remote.h b/applications/external/subghz_remote_configurator/views/remote.h deleted file mode 100644 index 5b1e8153a..000000000 --- a/applications/external/subghz_remote_configurator/views/remote.h +++ /dev/null @@ -1,36 +0,0 @@ -#pragma once - -#include -#include "../helpers/subrem_custom_event.h" -#include "../helpers/subrem_presets.h" - -typedef enum { - SubRemViewRemoteStateIdle, - SubRemViewRemoteStateLoading, - SubRemViewRemoteStateSending, - SubRemViewRemoteStateOFF, -} SubRemViewRemoteState; - -typedef struct SubRemViewRemote SubRemViewRemote; - -typedef void (*SubRemViewRemoteCallback)(SubRemCustomEvent event, void* context); - -void subrem_view_remote_set_callback( - SubRemViewRemote* subrem_view_remote, - SubRemViewRemoteCallback callback, - void* context); - -SubRemViewRemote* subrem_view_remote_alloc(); - -void subrem_view_remote_free(SubRemViewRemote* subrem_view_remote); - -View* subrem_view_remote_get_view(SubRemViewRemote* subrem_view_remote); - -void subrem_view_remote_update_data_labels( - SubRemViewRemote* subrem_view_remote, - SubRemSubFilePreset** subs_presets); - -void subrem_view_remote_set_state( - SubRemViewRemote* subrem_view_remote, - SubRemViewRemoteState state, - uint8_t presed_btn); \ No newline at end of file diff --git a/applications/main/subghz_remote/application.fam b/applications/main/subghz_remote/application.fam index 8e916289b..b007b73d0 100644 --- a/applications/main/subghz_remote/application.fam +++ b/applications/main/subghz_remote/application.fam @@ -4,9 +4,14 @@ App( apptype=FlipperAppType.MENUEXTERNAL, entry_point="subghz_remote_app", icon="A_SubGHzRemote_14", + requires=[ + "gui", + "dialogs", + ], stack_size=2 * 1024, order=11, fap_libs=["assets",], fap_icon="icon.png", + fap_description="SubGhz Remote, uses up to 5 .sub files", fap_category="Sub-Ghz", -) +) \ No newline at end of file diff --git a/applications/main/subghz_remote/helpers/subrem_custom_event.h b/applications/main/subghz_remote/helpers/subrem_custom_event.h index 8d93ab1fd..e6b9e8ac6 100644 --- a/applications/main/subghz_remote/helpers/subrem_custom_event.h +++ b/applications/main/subghz_remote/helpers/subrem_custom_event.h @@ -1,13 +1,27 @@ #pragma once +typedef enum { + SubRemEditMenuStateUP = 0, + SubRemEditMenuStateDOWN, + SubRemEditMenuStateLEFT, + SubRemEditMenuStateRIGHT, + SubRemEditMenuStateOK, +} SubRemEditMenuState; + typedef enum { // StartSubmenuIndex SubmenuIndexSubRemOpenMapFile = 0, + SubmenuIndexSubRemEditMapFile, + SubmenuIndexSubRemNewMapFile, #if FURI_DEBUG SubmenuIndexSubRemRemoteView, #endif // SubmenuIndexSubRemAbout, + // EditSubmenuIndex + EditSubmenuIndexEditLabel, + EditSubmenuIndexEditFile, + // SubRemCustomEvent SubRemCustomEventViewRemoteStartUP = 100, SubRemCustomEventViewRemoteStartDOWN, @@ -17,4 +31,21 @@ typedef enum { SubRemCustomEventViewRemoteBack, SubRemCustomEventViewRemoteStop, SubRemCustomEventViewRemoteForcedStop, + + SubRemCustomEventViewEditMenuBack, + SubRemCustomEventViewEditMenuUP, + SubRemCustomEventViewEditMenuDOWN, + SubRemCustomEventViewEditMenuEdit, + SubRemCustomEventViewEditMenuSave, + + SubRemCustomEventSceneEditsubmenu, + SubRemCustomEventSceneEditLabelInputDone, + SubRemCustomEventSceneEditLabelWidgetAcces, + SubRemCustomEventSceneEditLabelWidgetBack, + + SubRemCustomEventSceneEditOpenSubErrorPopup, + + SubRemCustomEventSceneEditPreviewSaved, + + SubRemCustomEventSceneNewName, } SubRemCustomEvent; \ No newline at end of file diff --git a/applications/main/subghz_remote/helpers/subrem_types.h b/applications/main/subghz_remote/helpers/subrem_types.h index b392de17e..b43f8499d 100644 --- a/applications/main/subghz_remote/helpers/subrem_types.h +++ b/applications/main/subghz_remote/helpers/subrem_types.h @@ -18,7 +18,11 @@ typedef enum { typedef enum { SubRemViewIDSubmenu, + SubRemViewIDWidget, + SubRemViewIDPopup, + SubRemViewIDTextInput, SubRemViewIDRemote, + SubRemViewIDEditMenu, } SubRemViewID; typedef enum { diff --git a/applications/main/subghz_remote/scenes/subrem_scene_config.h b/applications/main/subghz_remote/scenes/subrem_scene_config.h index 45e55850a..08486be74 100644 --- a/applications/main/subghz_remote/scenes/subrem_scene_config.h +++ b/applications/main/subghz_remote/scenes/subrem_scene_config.h @@ -1,5 +1,9 @@ -#ifndef SUBREM_LIGHT ADD_SCENE(subrem, start, Start) -#endif ADD_SCENE(subrem, open_map_file, OpenMapFile) -ADD_SCENE(subrem, remote, Remote) \ No newline at end of file +ADD_SCENE(subrem, remote, Remote) +ADD_SCENE(subrem, edit_menu, EditMenu) +ADD_SCENE(subrem, edit_submenu, EditSubMenu) +ADD_SCENE(subrem, edit_label, EditLabel) +ADD_SCENE(subrem, open_sub_file, OpenSubFile) +ADD_SCENE(subrem, edit_preview, EditPreview) +ADD_SCENE(subrem, enter_new_name, EnterNewName) \ No newline at end of file diff --git a/applications/external/subghz_remote_configurator/scenes/subrem_scene_edit_label.c b/applications/main/subghz_remote/scenes/subrem_scene_edit_label.c similarity index 100% rename from applications/external/subghz_remote_configurator/scenes/subrem_scene_edit_label.c rename to applications/main/subghz_remote/scenes/subrem_scene_edit_label.c diff --git a/applications/external/subghz_remote_configurator/scenes/subrem_scene_edit_menu.c b/applications/main/subghz_remote/scenes/subrem_scene_edit_menu.c similarity index 100% rename from applications/external/subghz_remote_configurator/scenes/subrem_scene_edit_menu.c rename to applications/main/subghz_remote/scenes/subrem_scene_edit_menu.c diff --git a/applications/external/subghz_remote_configurator/scenes/subrem_scene_edit_preview.c b/applications/main/subghz_remote/scenes/subrem_scene_edit_preview.c similarity index 100% rename from applications/external/subghz_remote_configurator/scenes/subrem_scene_edit_preview.c rename to applications/main/subghz_remote/scenes/subrem_scene_edit_preview.c diff --git a/applications/external/subghz_remote_configurator/scenes/subrem_scene_edit_submenu.c b/applications/main/subghz_remote/scenes/subrem_scene_edit_submenu.c similarity index 100% rename from applications/external/subghz_remote_configurator/scenes/subrem_scene_edit_submenu.c rename to applications/main/subghz_remote/scenes/subrem_scene_edit_submenu.c diff --git a/applications/external/subghz_remote_configurator/scenes/subrem_scene_enter_new_name.c b/applications/main/subghz_remote/scenes/subrem_scene_enter_new_name.c similarity index 100% rename from applications/external/subghz_remote_configurator/scenes/subrem_scene_enter_new_name.c rename to applications/main/subghz_remote/scenes/subrem_scene_enter_new_name.c diff --git a/applications/main/subghz_remote/scenes/subrem_scene_open_map_file.c b/applications/main/subghz_remote/scenes/subrem_scene_open_map_file.c index 1e917580c..1ed8ea252 100644 --- a/applications/main/subghz_remote/scenes/subrem_scene_open_map_file.c +++ b/applications/main/subghz_remote/scenes/subrem_scene_open_map_file.c @@ -5,30 +5,18 @@ void subrem_scene_open_map_file_on_enter(void* context) { SubGhzRemoteApp* app = context; SubRemLoadMapState load_state = subrem_load_from_file(app); + uint32_t start_scene_state = + scene_manager_get_scene_state(app->scene_manager, SubRemSceneStart); - if(load_state == SubRemLoadMapStateOK || load_state == SubRemLoadMapStateNotAllOK) { + // TODO if optimization + + if(load_state == SubRemLoadMapStateBack) { + scene_manager_previous_scene(app->scene_manager); + } else if(start_scene_state == SubmenuIndexSubRemEditMapFile) { + scene_manager_set_scene_state(app->scene_manager, SubRemSceneEditMenu, SubRemSubKeyNameUp); + scene_manager_next_scene(app->scene_manager, SubRemSceneEditMenu); + } else if(start_scene_state == SubmenuIndexSubRemOpenMapFile) { scene_manager_next_scene(app->scene_manager, SubRemSceneRemote); - } else { - if(load_state != SubRemLoadMapStateBack) { -#ifdef SUBREM_LIGHT - dialog_message_show_storage_error(app->dialogs, "Can't load\nMap file"); -#else - DialogMessage* message = dialog_message_alloc(); - - dialog_message_set_header(message, "Map File Error", 64, 8, AlignCenter, AlignCenter); - dialog_message_set_text( - message, "Can't load\nMap file", 64, 32, AlignCenter, AlignCenter); - dialog_message_set_buttons(message, "Back", NULL, NULL); - dialog_message_show(app->dialogs, message); - - dialog_message_free(message); -#endif - } - // TODO: Map Preset Reset - if(!scene_manager_previous_scene(app->scene_manager)) { - scene_manager_stop(app->scene_manager); - view_dispatcher_stop(app->view_dispatcher); - } } } diff --git a/applications/external/subghz_remote_configurator/scenes/subrem_scene_open_sub_file.c b/applications/main/subghz_remote/scenes/subrem_scene_open_sub_file.c similarity index 100% rename from applications/external/subghz_remote_configurator/scenes/subrem_scene_open_sub_file.c rename to applications/main/subghz_remote/scenes/subrem_scene_open_sub_file.c diff --git a/applications/main/subghz_remote/scenes/subrem_scene_start.c b/applications/main/subghz_remote/scenes/subrem_scene_start.c index e780b54ce..0f3399b7c 100644 --- a/applications/main/subghz_remote/scenes/subrem_scene_start.c +++ b/applications/main/subghz_remote/scenes/subrem_scene_start.c @@ -27,16 +27,28 @@ void subrem_scene_start_on_enter(void* context) { subrem_scene_start_submenu_callback, app); #endif + submenu_add_item( + submenu, + "Edit Map File", + SubmenuIndexSubRemEditMapFile, + subrem_scene_start_submenu_callback, + app); + submenu_add_item( + submenu, + "New Map File", + SubmenuIndexSubRemNewMapFile, + subrem_scene_start_submenu_callback, + app); // submenu_add_item( // submenu, // "About", // SubmenuIndexSubGhzRemoteAbout, // subrem_scene_start_submenu_callback, // app); -#ifndef SUBREM_LIGHT + submenu_set_selected_item( submenu, scene_manager_get_scene_state(app->scene_manager, SubRemSceneStart)); -#endif + view_dispatcher_switch_to_view(app->view_dispatcher, SubRemViewIDSubmenu); } @@ -48,23 +60,33 @@ bool subrem_scene_start_on_event(void* context, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { if(event.event == SubmenuIndexSubRemOpenMapFile) { -#ifndef SUBREM_LIGHT scene_manager_set_scene_state( app->scene_manager, SubRemSceneStart, SubmenuIndexSubRemOpenMapFile); -#endif + scene_manager_next_scene(app->scene_manager, SubRemSceneOpenMapFile); consumed = true; } - // } else if(event.event == SubmenuIndexSubRemAbout) { - // scene_manager_next_scene(app->scene_manager, SubRemSceneAbout); - // consumed = true; - // } #if FURI_DEBUG else if(event.event == SubmenuIndexSubRemRemoteView) { scene_manager_next_scene(app->scene_manager, SubRemSceneRemote); consumed = true; } #endif + else if(event.event == SubmenuIndexSubRemEditMapFile) { + scene_manager_set_scene_state( + app->scene_manager, SubRemSceneStart, SubmenuIndexSubRemEditMapFile); + scene_manager_next_scene(app->scene_manager, SubRemSceneOpenMapFile); + consumed = true; + } else if(event.event == SubmenuIndexSubRemNewMapFile) { + scene_manager_set_scene_state( + app->scene_manager, SubRemSceneStart, SubmenuIndexSubRemNewMapFile); + scene_manager_next_scene(app->scene_manager, SubRemSceneEnterNewName); + consumed = true; + } + // } else if(event.event == SubmenuIndexSubRemAbout) { + // scene_manager_next_scene(app->scene_manager, SubRemSceneAbout); + // consumed = true; + // } } return consumed; diff --git a/applications/main/subghz_remote/subghz_remote_app.c b/applications/main/subghz_remote/subghz_remote_app.c index 937025b05..e91d07d32 100644 --- a/applications/main/subghz_remote/subghz_remote_app.c +++ b/applications/main/subghz_remote/subghz_remote_app.c @@ -29,15 +29,6 @@ SubGhzRemoteApp* subghz_remote_app_alloc() { } furi_record_close(RECORD_STORAGE); - // // Enable power for External CC1101 if it is connected - // furi_hal_subghz_enable_ext_power(); - // // Auto switch to internal radio if external radio is not available - // furi_delay_ms(15); - // if(!furi_hal_subghz_check_radio()) { - // furi_hal_subghz_select_radio_type(SubGhzRadioInternal); - // furi_hal_subghz_init_radio_type(SubGhzRadioInternal); - // } - furi_hal_power_suppress_charge_enter(); app->file_path = furi_string_alloc(); @@ -73,6 +64,20 @@ SubGhzRemoteApp* subghz_remote_app_alloc() { // Dialog app->dialogs = furi_record_open(RECORD_DIALOGS); + // TextInput + app->text_input = text_input_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, SubRemViewIDTextInput, text_input_get_view(app->text_input)); + + // Widget + app->widget = widget_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, SubRemViewIDWidget, widget_get_view(app->widget)); + + // Popup + app->popup = popup_alloc(); + view_dispatcher_add_view(app->view_dispatcher, SubRemViewIDPopup, popup_get_view(app->popup)); + // Remote view app->subrem_remote_view = subrem_view_remote_alloc(); view_dispatcher_add_view( @@ -80,6 +85,13 @@ SubGhzRemoteApp* subghz_remote_app_alloc() { SubRemViewIDRemote, subrem_view_remote_get_view(app->subrem_remote_view)); + // Edit Menu view + app->subrem_edit_menu = subrem_view_edit_menu_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + SubRemViewIDEditMenu, + subrem_view_edit_menu_get_view(app->subrem_edit_menu)); + app->map_preset = malloc(sizeof(SubRemMapPreset)); for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) { app->map_preset->subs_preset[i] = subrem_sub_file_preset_alloc(); @@ -89,13 +101,10 @@ SubGhzRemoteApp* subghz_remote_app_alloc() { subghz_txrx_set_need_save_callback(app->txrx, subrem_save_active_sub, app); -#ifdef SUBREM_LIGHT - scene_manager_next_scene(app->scene_manager, SubRemSceneOpenMapFile); -#else + app->map_not_saved = false; + scene_manager_next_scene(app->scene_manager, SubRemSceneStart); - scene_manager_set_scene_state( - app->scene_manager, SubRemSceneStart, SubmenuIndexSubRemOpenMapFile); -#endif + scene_manager_next_scene(app->scene_manager, SubRemSceneOpenMapFile); return app; } @@ -105,11 +114,6 @@ void subghz_remote_app_free(SubGhzRemoteApp* app) { furi_hal_power_suppress_charge_exit(); - // // Disable power for External CC1101 if it was enabled and module is connected - // furi_hal_subghz_disable_ext_power(); - // // Reinit SPI handles for internal radio / nfc - // furi_hal_subghz_init_radio_type(SubGhzRadioInternal); - // Submenu view_dispatcher_remove_view(app->view_dispatcher, SubRemViewIDSubmenu); submenu_free(app->submenu); @@ -117,10 +121,26 @@ void subghz_remote_app_free(SubGhzRemoteApp* app) { // Dialog furi_record_close(RECORD_DIALOGS); + // TextInput + view_dispatcher_remove_view(app->view_dispatcher, SubRemViewIDTextInput); + text_input_free(app->text_input); + + // Widget + view_dispatcher_remove_view(app->view_dispatcher, SubRemViewIDWidget); + widget_free(app->widget); + + // Popup + view_dispatcher_remove_view(app->view_dispatcher, SubRemViewIDPopup); + popup_free(app->popup); + // Remote view view_dispatcher_remove_view(app->view_dispatcher, SubRemViewIDRemote); subrem_view_remote_free(app->subrem_remote_view); + // Edit view + view_dispatcher_remove_view(app->view_dispatcher, SubRemViewIDEditMenu); + subrem_view_edit_menu_free(app->subrem_edit_menu); + scene_manager_free(app->scene_manager); view_dispatcher_free(app->view_dispatcher); diff --git a/applications/main/subghz_remote/subghz_remote_app_i.c b/applications/main/subghz_remote/subghz_remote_app_i.c index 26659ccb1..82e762c2a 100644 --- a/applications/main/subghz_remote/subghz_remote_app_i.c +++ b/applications/main/subghz_remote/subghz_remote_app_i.c @@ -7,10 +7,8 @@ // #include // #include -#ifdef APP_SUBGHZREMOTE #include #include -#endif #define TAG "SubGhzRemote" @@ -22,7 +20,7 @@ static const char* map_file_labels[SubRemSubKeyNameMaxCount][2] = { [SubRemSubKeyNameOk] = {"OK", "OKLABEL"}, }; -static void subrem_map_preset_reset(SubRemMapPreset* map_preset) { +void subrem_map_preset_reset(SubRemMapPreset* map_preset) { furi_assert(map_preset); for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) { @@ -228,9 +226,7 @@ bool subrem_tx_start_sub(SubGhzRemoteApp* app, SubRemSubFilePreset* sub_preset) NULL, 0); -#ifdef APP_SUBGHZREMOTE subghz_custom_btns_reset(); -#endif if(subghz_txrx_tx_start(app->txrx, sub_preset->fff_data) == SubGhzTxRxStartTxStateOk) { ret = true; @@ -246,12 +242,12 @@ bool subrem_tx_stop_sub(SubGhzRemoteApp* app, bool forced) { if(forced || (sub_preset->type != SubGhzProtocolTypeRAW)) { subghz_txrx_stop(app->txrx); -#ifdef APP_SUBGHZREMOTE + if(sub_preset->type == SubGhzProtocolTypeDynamic) { subghz_txrx_reset_dynamic_and_custom_btns(app->txrx); } subghz_custom_btns_reset(); -#endif + return true; } @@ -278,3 +274,47 @@ SubRemLoadMapState subrem_load_from_file(SubGhzRemoteApp* app) { return ret; } + +bool subrem_save_map_to_file(SubGhzRemoteApp* app) { + furi_assert(app); + + const char* file_name = furi_string_get_cstr(app->file_path); + bool saved = false; + FlipperFormat* fff_data = flipper_format_string_alloc(); + + SubRemSubFilePreset* sub_preset; + + flipper_format_write_header_cstr( + fff_data, SUBREM_APP_APP_FILE_TYPE, SUBREM_APP_APP_FILE_VERSION); + for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) { + sub_preset = app->map_preset->subs_preset[i]; + if(!furi_string_empty(sub_preset->file_path)) { + flipper_format_write_string(fff_data, map_file_labels[i][0], sub_preset->file_path); + } + } + for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) { + sub_preset = app->map_preset->subs_preset[i]; + if(!furi_string_empty(sub_preset->file_path)) { + flipper_format_write_string(fff_data, map_file_labels[i][1], sub_preset->label); + } + } + + Storage* storage = furi_record_open(RECORD_STORAGE); + Stream* flipper_format_stream = flipper_format_get_raw_stream(fff_data); + + do { + if(!storage_simply_remove(storage, file_name)) { + break; + } + //ToDo check Write + stream_seek(flipper_format_stream, 0, StreamOffsetFromStart); + stream_save_to_file(flipper_format_stream, storage, file_name, FSOM_CREATE_ALWAYS); + + saved = true; + } while(0); + + furi_record_close(RECORD_STORAGE); + flipper_format_free(fff_data); + + return saved; +} \ No newline at end of file diff --git a/applications/main/subghz_remote/subghz_remote_app_i.h b/applications/main/subghz_remote/subghz_remote_app_i.h index a0f25667c..d200bdf96 100644 --- a/applications/main/subghz_remote/subghz_remote_app_i.h +++ b/applications/main/subghz_remote/subghz_remote_app_i.h @@ -1,21 +1,15 @@ #pragma once -#define SUBREM_LIGHT 1 -#define APP_SUBGHZREMOTE 1 - #include "helpers/subrem_types.h" #include "helpers/subrem_presets.h" #include "scenes/subrem_scene.h" #include "helpers/txrx/subghz_txrx.h" -#ifdef APP_SUBGHZREMOTE #include -#else -#include -#endif #include "views/remote.h" +#include "views/edit_menu.h" #include #include @@ -39,16 +33,23 @@ typedef struct { SceneManager* scene_manager; NotificationApp* notifications; DialogsApp* dialogs; + Widget* widget; + Popup* popup; + TextInput* text_input; Submenu* submenu; FuriString* file_path; + char file_name_tmp[SUBREM_MAX_LEN_NAME]; SubRemViewRemote* subrem_remote_view; + SubRemViewEditMenu* subrem_edit_menu; SubRemMapPreset* map_preset; SubGhzTxRx* txrx; + bool map_not_saved; + uint8_t chusen_sub; } SubGhzRemoteApp; @@ -58,4 +59,10 @@ bool subrem_tx_start_sub(SubGhzRemoteApp* app, SubRemSubFilePreset* sub_preset); bool subrem_tx_stop_sub(SubGhzRemoteApp* app, bool forced); +SubRemLoadMapState subrem_map_file_load(SubGhzRemoteApp* app, const char* file_path); + +void subrem_map_preset_reset(SubRemMapPreset* map_preset); + +bool subrem_save_map_to_file(SubGhzRemoteApp* app); + void subrem_save_active_sub(void* context); \ No newline at end of file diff --git a/applications/external/subghz_remote_configurator/views/edit_menu.c b/applications/main/subghz_remote/views/edit_menu.c similarity index 100% rename from applications/external/subghz_remote_configurator/views/edit_menu.c rename to applications/main/subghz_remote/views/edit_menu.c diff --git a/applications/external/subghz_remote_configurator/views/edit_menu.h b/applications/main/subghz_remote/views/edit_menu.h similarity index 100% rename from applications/external/subghz_remote_configurator/views/edit_menu.h rename to applications/main/subghz_remote/views/edit_menu.h From d0a6022e65ce03e25a20bca97ed79f1a1267d6be Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 14 Jul 2023 12:32:59 +0200 Subject: [PATCH 155/364] Fix #329 (RFID and NFC apps not launching) --- applications/main/lfrfid/application.fam | 2 +- firmware/targets/f7/api_symbols.csv | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/applications/main/lfrfid/application.fam b/applications/main/lfrfid/application.fam index 285badba7..9489b1b1f 100644 --- a/applications/main/lfrfid/application.fam +++ b/applications/main/lfrfid/application.fam @@ -1,6 +1,6 @@ App( appid="lfrfid", - name="125 kHz RFID", + name="RFID", apptype=FlipperAppType.MENUEXTERNAL, targets=["f7"], entry_point="lfrfid_app", diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 0bc40cfb7..91e27443a 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -881,10 +881,10 @@ Function,-,fdopen,FILE*,"int, const char*" Function,-,felica_check_ic_type,_Bool,uint8_t* Function,-,felica_clear,void,FelicaData* Function,-,felica_define_normal_block,void,"FelicaService*, uint16_t, uint8_t*" -Function,-,felica_estimate_timing_us,uint_least32_t,"uint_least8_t, uint_least8_t" +Function,+,felica_estimate_timing_us,uint_least32_t,"uint_least8_t, uint_least8_t" Function,-,felica_get_ic_type,FelicaICType,uint8_t* Function,-,felica_get_service_name,FuriString*,FelicaService* -Function,-,felica_get_system_name,FuriString*,FelicaSystem* +Function,+,felica_get_system_name,FuriString*,FelicaSystem* Function,-,felica_lite_dump_data,_Bool,"FuriHalNfcTxRxContext*, FelicaReader*, FelicaData*, FelicaSystem*" Function,-,felica_lite_is_issued,_Bool,FelicaLiteInfo* Function,-,felica_lite_prepare_unencrypted_read,uint8_t,"uint8_t*, const FelicaReader*, _Bool, const uint8_t*, uint8_t" From cc012e7c5e1ede352b1865839912897b8a55fa24 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 14 Jul 2023 12:37:11 +0200 Subject: [PATCH 156/364] Fix felica api link error (#329) --- lib/nfc/protocols/felica_util.h | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/nfc/protocols/felica_util.h b/lib/nfc/protocols/felica_util.h index de7ff884a..653ba4501 100644 --- a/lib/nfc/protocols/felica_util.h +++ b/lib/nfc/protocols/felica_util.h @@ -1,6 +1,14 @@ #include "./felica.h" +#ifdef __cplusplus +extern "C" { +#endif + uint_least32_t felica_estimate_timing_us(uint_least8_t timing, uint_least8_t units); bool felica_lite_is_issued(FelicaLiteInfo* lite_info); FuriString* felica_get_system_name(FelicaSystem* system); -FuriString* felica_get_service_name(FelicaService* service); \ No newline at end of file +FuriString* felica_get_service_name(FelicaService* service); + +#ifdef __cplusplus +} +#endif From 2fa25e27c694f49920791012520bbb9ab43c1a54 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 14 Jul 2023 12:43:35 +0200 Subject: [PATCH 157/364] Fix nightstand location and keybind handling --- .../external/nightstand/application.fam | 2 +- applications/services/desktop/desktop.c | 15 ++++-------- .../services/desktop/desktop_settings.c | 23 ++++++++++++++++++- .../desktop_settings/desktop_settings_app.c | 1 + 4 files changed, 28 insertions(+), 13 deletions(-) diff --git a/applications/external/nightstand/application.fam b/applications/external/nightstand/application.fam index 28b8b8694..e94e24cf2 100644 --- a/applications/external/nightstand/application.fam +++ b/applications/external/nightstand/application.fam @@ -6,7 +6,7 @@ App( requires=["gui"], stack_size=2 * 1024, fap_icon="clock.png", - fap_category="Misc", + fap_category="Tools", order=81, fap_author="@nymda & @Willy-JL", fap_weburl="https://github.com/nymda/FlipperNightStand", diff --git a/applications/services/desktop/desktop.c b/applications/services/desktop/desktop.c index 8ecf0e936..cb1519adc 100644 --- a/applications/services/desktop/desktop.c +++ b/applications/services/desktop/desktop.c @@ -471,6 +471,9 @@ void desktop_run_keybind(Desktop* instance, InputType _type, InputKey _key) { loader_start_detached_with_gui_error(instance->loader, LOADER_APPLICATIONS_NAME, NULL); } else if(!strncmp(keybind, "Archive", MAX_KEYBIND_LENGTH)) { view_dispatcher_send_custom_event(instance->view_dispatcher, DesktopMainEventOpenArchive); + } else if(!strncmp(keybind, "Clock", MAX_KEYBIND_LENGTH)) { + loader_start_detached_with_gui_error( + instance->loader, EXT_PATH("apps/Tools/nightstand.fap"), ""); } else if(!strncmp(keybind, "Device Info", MAX_KEYBIND_LENGTH)) { loader_start_detached_with_gui_error(instance->loader, "Power", "about_battery"); } else if(!strncmp(keybind, "Lock Menu", MAX_KEYBIND_LENGTH)) { @@ -504,17 +507,7 @@ int32_t desktop_srv(void* p) { furi_hal_rtc_set_pin_fails(0); } - if(!DESKTOP_KEYBINDS_LOAD(&desktop->keybinds, sizeof(desktop->keybinds))) { - memset(&desktop->keybinds, 0, sizeof(desktop->keybinds)); - strcpy(desktop->keybinds[KeybindTypePress][KeybindKeyUp].data, "Lock Menu"); - strcpy(desktop->keybinds[KeybindTypePress][KeybindKeyDown].data, "Archive"); - strcpy(desktop->keybinds[KeybindTypePress][KeybindKeyRight].data, "Passport"); - strcpy( - desktop->keybinds[KeybindTypePress][KeybindKeyLeft].data, - EXT_PATH("apps/Misc/nightstand.fap")); - strcpy(desktop->keybinds[KeybindTypeHold][KeybindKeyRight].data, "Device Info"); - strcpy(desktop->keybinds[KeybindTypeHold][KeybindKeyLeft].data, "Lock with PIN"); - } + DESKTOP_KEYBINDS_LOAD(&desktop->keybinds, sizeof(desktop->keybinds)); desktop_clock_toggle_view(desktop, XTREME_SETTINGS()->statusbar_clock); diff --git a/applications/services/desktop/desktop_settings.c b/applications/services/desktop/desktop_settings.c index 48d12b3ce..1fafaefe7 100644 --- a/applications/services/desktop/desktop_settings.c +++ b/applications/services/desktop/desktop_settings.c @@ -24,6 +24,27 @@ bool DESKTOP_KEYBINDS_SAVE(Keybind (*x)[KeybindTypeCount][KeybindKeyCount], size } bool DESKTOP_KEYBINDS_LOAD(Keybind (*x)[KeybindTypeCount][KeybindKeyCount], size_t size) { - return saved_struct_load( + bool ok = saved_struct_load( DESKTOP_KEYBINDS_PATH, x, size, DESKTOP_KEYBINDS_MAGIC, DESKTOP_KEYBINDS_VER); + if(ok) { + for(KeybindType i = 0; i < KeybindTypeCount; i++) { + for(KeybindKey j = 0; j < KeybindKeyCount; j++) { + char* keybind = (*x)[i][j].data; + if(!strncmp(keybind, EXT_PATH("apps/Misc/nightstand.fap"), MAX_KEYBIND_LENGTH)) { + strcpy(keybind, "Clock"); + } + } + } + } else { + memset(x, 0, size); + strcpy((*x)[KeybindTypePress][KeybindKeyUp].data, "Lock Menu"); + strcpy((*x)[KeybindTypePress][KeybindKeyDown].data, "Archive"); + strcpy((*x)[KeybindTypePress][KeybindKeyRight].data, "Passport"); + strcpy((*x)[KeybindTypePress][KeybindKeyLeft].data, "Clock"); + strcpy((*x)[KeybindTypeHold][KeybindKeyUp].data, ""); + strcpy((*x)[KeybindTypeHold][KeybindKeyDown].data, ""); + strcpy((*x)[KeybindTypeHold][KeybindKeyRight].data, "Device Info"); + strcpy((*x)[KeybindTypeHold][KeybindKeyLeft].data, "Lock with PIN"); + } + return ok; } diff --git a/applications/settings/desktop_settings/desktop_settings_app.c b/applications/settings/desktop_settings/desktop_settings_app.c index d394f389c..aad33c96c 100644 --- a/applications/settings/desktop_settings/desktop_settings_app.c +++ b/applications/settings/desktop_settings/desktop_settings_app.c @@ -9,6 +9,7 @@ const char* EXTRA_KEYBINDS[] = { "Apps Menu", "Archive", + "Clock", "Device Info", "Lock Menu", "Lock Keypad", From b3810ec573c37610612a606d10aa864566034773 Mon Sep 17 00:00:00 2001 From: gid9798 <30450294+gid9798@users.noreply.github.com> Date: Fri, 14 Jul 2023 14:16:37 +0300 Subject: [PATCH 158/364] SubRem: archive browser --- .../main/archive/helpers/archive_browser.h | 3 ++ .../main/archive/helpers/archive_files.c | 6 +++ .../main/archive/helpers/archive_files.h | 1 + .../archive/scenes/archive_scene_browser.c | 2 + .../main/archive/views/archive_browser_view.c | 2 + .../main/archive/views/archive_browser_view.h | 1 + .../scenes/subrem_scene_open_map_file.c | 2 - .../scenes/subrem_scene_remote.c | 3 ++ .../main/subghz_remote/subghz_remote_app.c | 44 +++++++++++++++---- .../main/subghz_remote/views/remote.c | 13 ++++++ .../main/subghz_remote/views/remote.h | 4 +- 11 files changed, 69 insertions(+), 12 deletions(-) diff --git a/applications/main/archive/helpers/archive_browser.h b/applications/main/archive/helpers/archive_browser.h index 43a9a651a..579c51082 100644 --- a/applications/main/archive/helpers/archive_browser.h +++ b/applications/main/archive/helpers/archive_browser.h @@ -12,6 +12,7 @@ static const char* tab_default_paths[] = { [ArchiveTabIButton] = ANY_PATH("ibutton"), [ArchiveTabNFC] = ANY_PATH("nfc"), [ArchiveTabSubGhz] = ANY_PATH("subghz"), + [ArchiveTabSubGhzRemote] = EXT_PATH("subghz_remote"), [ArchiveTabLFRFID] = ANY_PATH("lfrfid"), [ArchiveTabInfrared] = ANY_PATH("infrared"), [ArchiveTabBadUsb] = ANY_PATH("badusb"), @@ -25,6 +26,7 @@ static const char* known_ext[] = { [ArchiveFileTypeIButton] = ".ibtn", [ArchiveFileTypeNFC] = ".nfc", [ArchiveFileTypeSubGhz] = ".sub", + [ArchiveFileTypeSubGhzRemote] = ".txt", [ArchiveFileTypeLFRFID] = ".rfid", [ArchiveFileTypeInfrared] = ".ir", [ArchiveFileTypeBadUsb] = ".txt", @@ -40,6 +42,7 @@ static const ArchiveFileTypeEnum known_type[] = { [ArchiveTabIButton] = ArchiveFileTypeIButton, [ArchiveTabNFC] = ArchiveFileTypeNFC, [ArchiveTabSubGhz] = ArchiveFileTypeSubGhz, + [ArchiveTabSubGhzRemote] = ArchiveFileTypeSubGhzRemote, [ArchiveTabLFRFID] = ArchiveFileTypeLFRFID, [ArchiveTabInfrared] = ArchiveFileTypeInfrared, [ArchiveTabBadUsb] = ArchiveFileTypeBadUsb, diff --git a/applications/main/archive/helpers/archive_files.c b/applications/main/archive/helpers/archive_files.c index 8aef0ef09..e12576cb2 100644 --- a/applications/main/archive/helpers/archive_files.c +++ b/applications/main/archive/helpers/archive_files.c @@ -22,6 +22,12 @@ void archive_set_file_type(ArchiveFile_t* file, const char* path, bool is_folder file->type = i; return; // *.txt file is a BadUSB script only if it is in BadUSB folder } + } else if(i == ArchiveFileTypeSubGhzRemote) { + if(furi_string_search( + file->path, archive_get_default_path(ArchiveTabSubGhzRemote)) == 0) { + file->type = i; + return; // *.txt file is a SubRem map file only if it is in SubRem folder + } } else { file->type = i; return; diff --git a/applications/main/archive/helpers/archive_files.h b/applications/main/archive/helpers/archive_files.h index 6acdb2213..989198fec 100644 --- a/applications/main/archive/helpers/archive_files.h +++ b/applications/main/archive/helpers/archive_files.h @@ -12,6 +12,7 @@ typedef enum { ArchiveFileTypeIButton, ArchiveFileTypeNFC, ArchiveFileTypeSubGhz, + ArchiveFileTypeSubGhzRemote, ArchiveFileTypeLFRFID, ArchiveFileTypeInfrared, ArchiveFileTypeBadUsb, diff --git a/applications/main/archive/scenes/archive_scene_browser.c b/applications/main/archive/scenes/archive_scene_browser.c index a6892239a..e512d31d5 100644 --- a/applications/main/archive/scenes/archive_scene_browser.c +++ b/applications/main/archive/scenes/archive_scene_browser.c @@ -20,6 +20,8 @@ static const char* archive_get_flipper_app_name(ArchiveFileTypeEnum file_type) { return "NFC"; case ArchiveFileTypeSubGhz: return "Sub-GHz"; + case ArchiveFileTypeSubGhzRemote: + return "Sub-GHz Remote"; case ArchiveFileTypeLFRFID: return "125 kHz RFID"; case ArchiveFileTypeInfrared: diff --git a/applications/main/archive/views/archive_browser_view.c b/applications/main/archive/views/archive_browser_view.c index 63fa91c4d..fd7de727d 100644 --- a/applications/main/archive/views/archive_browser_view.c +++ b/applications/main/archive/views/archive_browser_view.c @@ -14,6 +14,7 @@ static const char* ArchiveTabNames[] = { [ArchiveTabIButton] = "iButton", [ArchiveTabNFC] = "NFC", [ArchiveTabSubGhz] = "Sub-GHz", + [ArchiveTabSubGhzRemote] = "SubRem", [ArchiveTabLFRFID] = "RFID LF", [ArchiveTabInfrared] = "Infrared", [ArchiveTabBadUsb] = "Bad USB", @@ -27,6 +28,7 @@ static const Icon* ArchiveItemIcons[] = { [ArchiveFileTypeIButton] = &I_ibutt_10px, [ArchiveFileTypeNFC] = &I_Nfc_10px, [ArchiveFileTypeSubGhz] = &I_sub1_10px, + [ArchiveFileTypeSubGhzRemote] = &I_subrem_10px, [ArchiveFileTypeLFRFID] = &I_125_10px, [ArchiveFileTypeInfrared] = &I_ir_10px, [ArchiveFileTypeBadUsb] = &I_badusb_10px, diff --git a/applications/main/archive/views/archive_browser_view.h b/applications/main/archive/views/archive_browser_view.h index 323e583cc..d3a57f755 100644 --- a/applications/main/archive/views/archive_browser_view.h +++ b/applications/main/archive/views/archive_browser_view.h @@ -24,6 +24,7 @@ typedef enum { ArchiveTabFavorites, ArchiveTabSubGhz, + ArchiveTabSubGhzRemote, ArchiveTabLFRFID, ArchiveTabNFC, ArchiveTabInfrared, diff --git a/applications/main/subghz_remote/scenes/subrem_scene_open_map_file.c b/applications/main/subghz_remote/scenes/subrem_scene_open_map_file.c index 1ed8ea252..b91a35129 100644 --- a/applications/main/subghz_remote/scenes/subrem_scene_open_map_file.c +++ b/applications/main/subghz_remote/scenes/subrem_scene_open_map_file.c @@ -8,8 +8,6 @@ void subrem_scene_open_map_file_on_enter(void* context) { uint32_t start_scene_state = scene_manager_get_scene_state(app->scene_manager, SubRemSceneStart); - // TODO if optimization - if(load_state == SubRemLoadMapStateBack) { scene_manager_previous_scene(app->scene_manager); } else if(start_scene_state == SubmenuIndexSubRemEditMapFile) { diff --git a/applications/main/subghz_remote/scenes/subrem_scene_remote.c b/applications/main/subghz_remote/scenes/subrem_scene_remote.c index ebc582991..692863434 100644 --- a/applications/main/subghz_remote/scenes/subrem_scene_remote.c +++ b/applications/main/subghz_remote/scenes/subrem_scene_remote.c @@ -39,6 +39,9 @@ void subrem_scene_remote_on_enter(void* context) { SubGhzRemoteApp* app = context; subrem_view_remote_update_data_labels(app->subrem_remote_view, app->map_preset->subs_preset); + subrem_view_remote_set_radio( + app->subrem_remote_view, + subghz_txrx_radio_device_get(app->txrx) == SubGhzRadioDeviceTypeExternalCC1101); subrem_view_remote_set_callback(app->subrem_remote_view, subrem_scene_remote_callback, app); diff --git a/applications/main/subghz_remote/subghz_remote_app.c b/applications/main/subghz_remote/subghz_remote_app.c index e91d07d32..1af39f57d 100644 --- a/applications/main/subghz_remote/subghz_remote_app.c +++ b/applications/main/subghz_remote/subghz_remote_app.c @@ -18,16 +18,23 @@ static void subghz_remote_app_tick_event_callback(void* context) { scene_manager_handle_tick_event(app->scene_manager); } -SubGhzRemoteApp* subghz_remote_app_alloc() { - SubGhzRemoteApp* app = malloc(sizeof(SubGhzRemoteApp)); +static void subghz_remote_make_app_folder(SubGhzRemoteApp* app) { + furi_assert(app); Storage* storage = furi_record_open(RECORD_STORAGE); + + // Migrate old users data storage_common_migrate(storage, EXT_PATH("unirf"), SUBREM_APP_FOLDER); if(!storage_simply_mkdir(storage, SUBREM_APP_FOLDER)) { - //FURI_LOG_E(TAG, "Could not create folder %s", SUBREM_APP_FOLDER); + // FURI_LOG_E(TAG, "Could not create folder %s", SUBREM_APP_FOLDER); + dialog_message_show_storage_error(app->dialogs, "Cannot create\napp folder"); } furi_record_close(RECORD_STORAGE); +} + +SubGhzRemoteApp* subghz_remote_app_alloc() { + SubGhzRemoteApp* app = malloc(sizeof(SubGhzRemoteApp)); furi_hal_power_suppress_charge_enter(); @@ -103,9 +110,6 @@ SubGhzRemoteApp* subghz_remote_app_alloc() { app->map_not_saved = false; - scene_manager_next_scene(app->scene_manager, SubRemSceneStart); - scene_manager_next_scene(app->scene_manager, SubRemSceneOpenMapFile); - return app; } @@ -164,11 +168,33 @@ void subghz_remote_app_free(SubGhzRemoteApp* app) { free(app); } -int32_t subghz_remote_app(void* p) { - UNUSED(p); +int32_t subghz_remote_app(void* arg) { SubGhzRemoteApp* subghz_remote_app = subghz_remote_app_alloc(); - furi_string_set(subghz_remote_app->file_path, SUBREM_APP_FOLDER); + subghz_remote_make_app_folder(subghz_remote_app); + + bool map_loaded = false; + + if((arg != NULL) && (strlen(arg) != 0)) { + furi_string_set(subghz_remote_app->file_path, (const char*)arg); + SubRemLoadMapState load_state = subrem_map_file_load( + subghz_remote_app, furi_string_get_cstr(subghz_remote_app->file_path)); + + if(load_state == SubRemLoadMapStateOK || load_state == SubRemLoadMapStateNotAllOK) { + map_loaded = true; + } else { + // TODO Replace + dialog_message_show_storage_error(subghz_remote_app->dialogs, "Cannot load\nmap file"); + } + } + + if(map_loaded) { + scene_manager_next_scene(subghz_remote_app->scene_manager, SubRemSceneRemote); + } else { + furi_string_set(subghz_remote_app->file_path, SUBREM_APP_FOLDER); + scene_manager_next_scene(subghz_remote_app->scene_manager, SubRemSceneStart); + scene_manager_next_scene(subghz_remote_app->scene_manager, SubRemSceneOpenMapFile); + } view_dispatcher_run(subghz_remote_app->view_dispatcher); diff --git a/applications/main/subghz_remote/views/remote.c b/applications/main/subghz_remote/views/remote.c index c2b41cfd6..fc7608624 100644 --- a/applications/main/subghz_remote/views/remote.c +++ b/applications/main/subghz_remote/views/remote.c @@ -22,6 +22,7 @@ typedef struct { SubRemViewRemoteState state; uint8_t pressed_btn; + bool is_external; } SubRemViewRemoteModel; void subrem_view_remote_set_callback( @@ -106,6 +107,15 @@ void subrem_view_remote_set_state( true); } +void subrem_view_remote_set_radio(SubRemViewRemote* subrem_view_remote, bool external) { + furi_assert(subrem_view_remote); + with_view_model( + subrem_view_remote->view, + SubRemViewRemoteModel * model, + { model->is_external = external; }, + true); +} + void subrem_view_remote_draw(Canvas* canvas, SubRemViewRemoteModel* model) { canvas_clear(canvas); canvas_set_color(canvas, ColorBlack); @@ -143,6 +153,8 @@ void subrem_view_remote_draw(Canvas* canvas, SubRemViewRemoteModel* model) { elements_button_right(canvas, "Save"); } else { canvas_draw_str_aligned(canvas, 11, 62, AlignLeft, AlignBottom, "Hold=Exit."); + canvas_draw_str_aligned( + canvas, 126, 62, AlignRight, AlignBottom, ((model->is_external) ? "Ext" : "Int")); } //Status text and indicator @@ -267,6 +279,7 @@ SubRemViewRemote* subrem_view_remote_alloc() { } model->pressed_btn = 0; + model->is_external = false; }, true); return subrem_view_remote; diff --git a/applications/main/subghz_remote/views/remote.h b/applications/main/subghz_remote/views/remote.h index 5b1e8153a..39f9a007d 100644 --- a/applications/main/subghz_remote/views/remote.h +++ b/applications/main/subghz_remote/views/remote.h @@ -33,4 +33,6 @@ void subrem_view_remote_update_data_labels( void subrem_view_remote_set_state( SubRemViewRemote* subrem_view_remote, SubRemViewRemoteState state, - uint8_t presed_btn); \ No newline at end of file + uint8_t presed_btn); + +void subrem_view_remote_set_radio(SubRemViewRemote* subrem_view_remote, bool external); \ No newline at end of file From 8e2371d0c9d91488b49e53a7e42d5bc9e78047b4 Mon Sep 17 00:00:00 2001 From: gid9798 <30450294+gid9798@users.noreply.github.com> Date: Fri, 14 Jul 2023 14:28:46 +0300 Subject: [PATCH 159/364] SubRem: small fix --- applications/main/subghz_remote/scenes/subrem_scene_remote.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/main/subghz_remote/scenes/subrem_scene_remote.c b/applications/main/subghz_remote/scenes/subrem_scene_remote.c index 692863434..e8d57dae7 100644 --- a/applications/main/subghz_remote/scenes/subrem_scene_remote.c +++ b/applications/main/subghz_remote/scenes/subrem_scene_remote.c @@ -41,7 +41,7 @@ void subrem_scene_remote_on_enter(void* context) { subrem_view_remote_update_data_labels(app->subrem_remote_view, app->map_preset->subs_preset); subrem_view_remote_set_radio( app->subrem_remote_view, - subghz_txrx_radio_device_get(app->txrx) == SubGhzRadioDeviceTypeExternalCC1101); + subghz_txrx_radio_device_get(app->txrx) != SubGhzRadioDeviceTypeInternal); subrem_view_remote_set_callback(app->subrem_remote_view, subrem_scene_remote_callback, app); From 2ace7723979a1b3cc6ee9846055e0543b4b64105 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 14 Jul 2023 13:45:01 +0200 Subject: [PATCH 160/364] OFW compatible elements_scrollable_text_line --- .../main/archive/views/archive_browser_view.c | 3 +-- applications/services/gui/elements.c | 12 ++++++++++++ applications/services/gui/elements.h | 8 ++++++++ applications/services/gui/modules/file_browser.c | 3 +-- applications/services/gui/modules/menu.c | 2 +- .../services/gui/modules/variable_item_list.c | 3 +-- firmware/targets/f7/api_symbols.csv | 3 ++- 7 files changed, 26 insertions(+), 8 deletions(-) diff --git a/applications/main/archive/views/archive_browser_view.c b/applications/main/archive/views/archive_browser_view.c index 7bc608040..2f0bd3878 100644 --- a/applications/main/archive/views/archive_browser_view.c +++ b/applications/main/archive/views/archive_browser_view.c @@ -246,8 +246,7 @@ static void draw_list(Canvas* canvas, ArchiveBrowserViewModel* model) { ((scrollbar ? MAX_LEN_PX - 6 : MAX_LEN_PX) - x_offset), str_buf, scroll_counter, - (model->item_idx != idx), - false); + (model->item_idx != idx)); furi_string_free(str_buf); } diff --git a/applications/services/gui/elements.c b/applications/services/gui/elements.c index fc5609a36..f70a9f232 100644 --- a/applications/services/gui/elements.c +++ b/applications/services/gui/elements.c @@ -632,6 +632,18 @@ void elements_scrollable_text_line_str( } void elements_scrollable_text_line( + Canvas* canvas, + uint8_t x, + uint8_t y, + uint8_t width, + FuriString* string, + size_t scroll, + bool ellipsis) { + elements_scrollable_text_line_str( + canvas, x, y, width, furi_string_get_cstr(string), scroll, ellipsis, false); +} + +void elements_scrollable_text_line_centered( Canvas* canvas, uint8_t x, uint8_t y, diff --git a/applications/services/gui/elements.h b/applications/services/gui/elements.h index 7cab3a67f..e8029f75b 100644 --- a/applications/services/gui/elements.h +++ b/applications/services/gui/elements.h @@ -230,6 +230,14 @@ void elements_scrollable_text_line_str( bool ellipsis, bool centered); void elements_scrollable_text_line( + Canvas* canvas, + uint8_t x, + uint8_t y, + uint8_t width, + FuriString* string, + size_t scroll, + bool ellipsis); +void elements_scrollable_text_line_centered( Canvas* canvas, uint8_t x, uint8_t y, diff --git a/applications/services/gui/modules/file_browser.c b/applications/services/gui/modules/file_browser.c index 4ffecbf66..089b0b6b7 100644 --- a/applications/services/gui/modules/file_browser.c +++ b/applications/services/gui/modules/file_browser.c @@ -619,8 +619,7 @@ static void browser_draw_list(Canvas* canvas, FileBrowserModel* model) { (show_scrollbar ? MAX_LEN_PX - 6 : MAX_LEN_PX), filename, scroll_counter, - (model->item_idx != idx), - false); + (model->item_idx != idx)); } if(show_scrollbar) { diff --git a/applications/services/gui/modules/menu.c b/applications/services/gui/modules/menu.c index fecefb3c0..29c5b4131 100644 --- a/applications/services/gui/modules/menu.c +++ b/applications/services/gui/modules/menu.c @@ -91,7 +91,7 @@ static void menu_draw_callback(Canvas* canvas, void* _model) { furi_string_right(name, trim + 2); } } - elements_scrollable_text_line( + elements_scrollable_text_line_centered( canvas, 20 + x_off, 26 + y_off, 36, name, scroll_counter, false, true); if(item_i == position) { canvas_set_color(canvas, ColorBlack); diff --git a/applications/services/gui/modules/variable_item_list.c b/applications/services/gui/modules/variable_item_list.c index 225118586..e37af509a 100644 --- a/applications/services/gui/modules/variable_item_list.c +++ b/applications/services/gui/modules/variable_item_list.c @@ -101,8 +101,7 @@ static void variable_item_list_draw_callback(Canvas* canvas, void* _model) { 37, item->current_value_text, scroll_counter, - false, - true); + false); if(item->current_value_index < (item->values_count - 1)) { canvas_draw_str(canvas, 115, item_text_y, ">"); diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 91e27443a..0671c5211 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -832,7 +832,8 @@ Function,+,elements_multiline_text_aligned,void,"Canvas*, uint8_t, uint8_t, Alig Function,+,elements_multiline_text_framed,void,"Canvas*, uint8_t, uint8_t, const char*" Function,+,elements_progress_bar,void,"Canvas*, uint8_t, uint8_t, uint8_t, float" Function,+,elements_progress_bar_with_text,void,"Canvas*, uint8_t, uint8_t, uint8_t, float, const char*" -Function,+,elements_scrollable_text_line,void,"Canvas*, uint8_t, uint8_t, uint8_t, FuriString*, size_t, _Bool, _Bool" +Function,+,elements_scrollable_text_line,void,"Canvas*, uint8_t, uint8_t, uint8_t, FuriString*, size_t, _Bool" +Function,+,elements_scrollable_text_line_centered,void,"Canvas*, uint8_t, uint8_t, uint8_t, FuriString*, size_t, _Bool, _Bool" Function,+,elements_scrollable_text_line_str,void,"Canvas*, uint8_t, uint8_t, uint8_t, const char*, size_t, _Bool, _Bool" Function,+,elements_scrollbar,void,"Canvas*, uint16_t, uint16_t" Function,+,elements_scrollbar_pos,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint16_t, uint16_t" From 16795085b2f6d0f26e34b3e6622f186a042c4a7d Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 14 Jul 2023 14:24:59 +0200 Subject: [PATCH 161/364] Unsorted idx is not needed anymore --- applications/services/gui/modules/file_browser.c | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/applications/services/gui/modules/file_browser.c b/applications/services/gui/modules/file_browser.c index 089b0b6b7..e260d6080 100644 --- a/applications/services/gui/modules/file_browser.c +++ b/applications/services/gui/modules/file_browser.c @@ -33,7 +33,6 @@ typedef enum { } BrowserItemType; typedef struct { - uint32_t unsorted_idx; FuriString* path; BrowserItemType type; uint8_t* custom_icon_data; @@ -41,7 +40,6 @@ typedef struct { } BrowserItem_t; static void BrowserItem_t_init(BrowserItem_t* obj) { - obj->unsorted_idx = 0; obj->type = BrowserItemTypeLoading; obj->path = furi_string_alloc(); obj->display_name = furi_string_alloc(); @@ -49,7 +47,6 @@ static void BrowserItem_t_init(BrowserItem_t* obj) { } static void BrowserItem_t_init_set(BrowserItem_t* obj, const BrowserItem_t* src) { - obj->unsorted_idx = src->unsorted_idx; obj->type = src->type; obj->path = furi_string_alloc_set(src->path); obj->display_name = furi_string_alloc_set(src->display_name); @@ -62,7 +59,6 @@ static void BrowserItem_t_init_set(BrowserItem_t* obj, const BrowserItem_t* src) } static void BrowserItem_t_set(BrowserItem_t* obj, const BrowserItem_t* src) { - obj->unsorted_idx = src->unsorted_idx; obj->type = src->type; furi_string_set(obj->path, src->path); furi_string_set(obj->display_name, src->display_name); @@ -436,11 +432,11 @@ static void browser_list_item_cb( bool is_folder, bool is_last) { furi_assert(context); + UNUSED(idx); FileBrowser* browser = (FileBrowser*)context; BrowserItem_t item; item.custom_icon_data = NULL; - item.unsorted_idx = idx; if(!is_last) { item.path = furi_string_alloc_set(item_path); @@ -745,7 +741,6 @@ static bool file_browser_view_input_callback(InputEvent* event, void* context) { } else if(event->key == InputKeyOk) { if(event->type == InputTypeShort) { BrowserItem_t* selected_item = NULL; - int32_t select_index = 0; with_view_model( browser->view, FileBrowserModel * model, @@ -753,7 +748,6 @@ static bool file_browser_view_input_callback(InputEvent* event, void* context) { if(browser_is_item_in_array(model, model->item_idx)) { selected_item = items_array_get(model->items, model->item_idx - model->array_offset); - select_index = selected_item->unsorted_idx; } }, false); @@ -762,8 +756,7 @@ static bool file_browser_view_input_callback(InputEvent* event, void* context) { if(selected_item->type == BrowserItemTypeBack) { file_browser_worker_folder_exit(browser->worker); } else if(selected_item->type == BrowserItemTypeFolder) { - file_browser_worker_folder_enter( - browser->worker, selected_item->path, select_index); + file_browser_worker_folder_enter(browser->worker, selected_item->path, 0); } else if(selected_item->type == BrowserItemTypeFile) { furi_string_set(browser->result_path, selected_item->path); if(browser->callback) { From 7ce9e41091900b76fe80ee9afe1940ab2d5e2ff1 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 14 Jul 2023 14:25:26 +0200 Subject: [PATCH 162/364] Revert "Merge previous file browser commits" This reverts commit 06c44031e79be9d716b7ed5029abcc4fbc1ae950. --- .../main/archive/helpers/archive_browser.c | 29 +----- .../main/archive/helpers/archive_browser.h | 1 - .../main/archive/views/archive_browser_view.c | 69 +++++++++---- .../main/archive/views/archive_browser_view.h | 2 +- .../services/gui/modules/file_browser.c | 97 ++++++++++--------- 5 files changed, 102 insertions(+), 96 deletions(-) diff --git a/applications/main/archive/helpers/archive_browser.c b/applications/main/archive/helpers/archive_browser.c index 4b8c3d584..c0325ff16 100644 --- a/applications/main/archive/helpers/archive_browser.c +++ b/applications/main/archive/helpers/archive_browser.c @@ -70,12 +70,10 @@ static void archive_list_item_cb( if(!is_last) { archive_add_file_item(browser, is_folder, furi_string_get_cstr(item_path)); } else { - bool load_again = false; with_view_model( browser->view, ArchiveBrowserViewModel * model, { - model->list_loading = false; if(model->item_cnt <= BROWSER_SORT_THRESHOLD) { FuriString* selected = NULL; if(model->item_idx >= 0) { @@ -98,14 +96,9 @@ static void archive_list_item_cb( model->item_idx = 0; } } - if(archive_is_file_list_load_required(model)) { - load_again = true; - } + model->list_loading = false; }, true); - if(load_again) { - archive_file_array_load(browser, 0); - } archive_update_offset(browser); } } @@ -152,26 +145,6 @@ bool archive_is_item_in_array(ArchiveBrowserViewModel* model, uint32_t idx) { return true; } -bool archive_is_file_list_load_required(ArchiveBrowserViewModel* model) { - size_t array_size = files_array_size(model->files); - - if((model->list_loading) || (array_size >= model->item_cnt)) { - return false; - } - - if((model->array_offset > 0) && - (model->item_idx < (model->array_offset + FILE_LIST_BUF_LEN / 4))) { - return true; - } - - if(((model->array_offset + array_size) < model->item_cnt) && - (model->item_idx > (int32_t)(model->array_offset + array_size - FILE_LIST_BUF_LEN / 4))) { - return true; - } - - return false; -} - void archive_update_offset(ArchiveBrowserView* browser) { furi_assert(browser); diff --git a/applications/main/archive/helpers/archive_browser.h b/applications/main/archive/helpers/archive_browser.h index d9214bee1..8074d2532 100644 --- a/applications/main/archive/helpers/archive_browser.h +++ b/applications/main/archive/helpers/archive_browser.h @@ -72,7 +72,6 @@ inline bool archive_is_known_app(ArchiveFileTypeEnum type) { } bool archive_is_item_in_array(ArchiveBrowserViewModel* model, uint32_t idx); -bool archive_is_file_list_load_required(ArchiveBrowserViewModel* model); void archive_update_offset(ArchiveBrowserView* browser); void archive_update_focus(ArchiveBrowserView* browser, const char* target); diff --git a/applications/main/archive/views/archive_browser_view.c b/applications/main/archive/views/archive_browser_view.c index 2f0bd3878..559040877 100644 --- a/applications/main/archive/views/archive_browser_view.c +++ b/applications/main/archive/views/archive_browser_view.c @@ -334,10 +334,24 @@ View* archive_browser_get_view(ArchiveBrowserView* browser) { return browser->view; } -static void file_list_rollover(ArchiveBrowserViewModel* model) { - if(!model->list_loading && files_array_size(model->files) < model->item_cnt) { - files_array_reset(model->files); +static bool is_file_list_load_required(ArchiveBrowserViewModel* model) { + size_t array_size = files_array_size(model->files); + + if((model->list_loading) || (array_size >= model->item_cnt)) { + return false; } + + if((model->array_offset > 0) && + (model->item_idx < (model->array_offset + FILE_LIST_BUF_LEN / 4))) { + return true; + } + + if(((model->array_offset + array_size) < model->item_cnt) && + (model->item_idx > (int32_t)(model->array_offset + array_size - FILE_LIST_BUF_LEN / 4))) { + return true; + } + + return false; } static bool archive_view_input(InputEvent* event, void* context) { @@ -423,19 +437,25 @@ static bool archive_view_input(InputEvent* event, void* context) { } else { scroll_speed = model->button_held_for_ticks > 9 ? 4 : 2; } + } else if(model->button_held_for_ticks < 0) { + scroll_speed = 0; } if(event->key == InputKeyUp) { if(model->item_idx < scroll_speed) { - model->button_held_for_ticks = 0; - model->item_idx = model->item_cnt - 1; - file_list_rollover(model); - } else { - model->item_idx = - ((model->item_idx - scroll_speed) + model->item_cnt) % - model->item_cnt; + scroll_speed = model->item_idx; + if(scroll_speed == 0) { + if(model->button_held_for_ticks > 0) { + model->button_held_for_ticks = -1; + } else { + scroll_speed = 1; + } + } } - if(archive_is_file_list_load_required(model)) { + + model->item_idx = + ((model->item_idx - scroll_speed) + model->item_cnt) % model->item_cnt; + if(is_file_list_load_required(model)) { model->list_loading = true; browser->callback(ArchiveBrowserEventLoadPrevItems, browser->context); } @@ -443,17 +463,26 @@ static bool archive_view_input(InputEvent* event, void* context) { browser->callback(ArchiveBrowserEventFavMoveUp, browser->context); } model->scroll_counter = 0; + + if(model->button_held_for_ticks < -1) { + model->button_held_for_ticks = 0; + } model->button_held_for_ticks += 1; } else if(event->key == InputKeyDown) { int32_t count = model->item_cnt; - if(model->item_idx + scroll_speed >= count) { - model->button_held_for_ticks = 0; - model->item_idx = 0; - file_list_rollover(model); - } else { - model->item_idx = (model->item_idx + scroll_speed) % model->item_cnt; + if(model->item_idx >= (count - scroll_speed)) { + scroll_speed = model->item_cnt - model->item_idx - 1; + if(scroll_speed == 0) { + if(model->button_held_for_ticks > 0) { + model->button_held_for_ticks = -1; + } else { + scroll_speed = 1; + } + } } - if(archive_is_file_list_load_required(model)) { + + model->item_idx = (model->item_idx + scroll_speed) % model->item_cnt; + if(is_file_list_load_required(model)) { model->list_loading = true; browser->callback(ArchiveBrowserEventLoadNextItems, browser->context); } @@ -461,6 +490,10 @@ static bool archive_view_input(InputEvent* event, void* context) { browser->callback(ArchiveBrowserEventFavMoveDown, browser->context); } model->scroll_counter = 0; + + if(model->button_held_for_ticks < -1) { + model->button_held_for_ticks = 0; + } model->button_held_for_ticks += 1; } }, diff --git a/applications/main/archive/views/archive_browser_view.h b/applications/main/archive/views/archive_browser_view.h index d06e27e8c..f87d59a2b 100644 --- a/applications/main/archive/views/archive_browser_view.h +++ b/applications/main/archive/views/archive_browser_view.h @@ -115,7 +115,7 @@ typedef struct { int32_t list_offset; size_t scroll_counter; - uint32_t button_held_for_ticks; + int32_t button_held_for_ticks; } ArchiveBrowserViewModel; void archive_browser_set_callback( diff --git a/applications/services/gui/modules/file_browser.c b/applications/services/gui/modules/file_browser.c index e260d6080..d934cccc8 100644 --- a/applications/services/gui/modules/file_browser.c +++ b/applications/services/gui/modules/file_browser.c @@ -144,7 +144,7 @@ typedef struct { bool hide_ext; size_t scroll_counter; - uint32_t button_held_for_ticks; + int32_t button_held_for_ticks; } FileBrowserModel; static const Icon* BrowserItemIcons[] = { @@ -332,12 +332,6 @@ static bool browser_is_list_load_required(FileBrowserModel* model) { return false; } -static void browser_list_rollover(FileBrowserModel* model) { - if(!model->list_loading && items_array_size(model->items) < model->item_cnt) { - items_array_reset(model->items); - } -} - static void browser_update_offset(FileBrowser* browser) { furi_assert(browser); @@ -420,7 +414,7 @@ static void browser_list_load_cb(void* context, uint32_t list_load_offset) { } } }, - false); + true); BrowserItem_t_clear(&back_item); } @@ -465,15 +459,14 @@ static void browser_list_item_cb( (browser->hide_ext) && (item.type == BrowserItemTypeFile)); } - // We shouldn't update screen on each item if custom callback is not set - // Otherwise it will cause screen flickering - bool instant_update = (browser->item_callback != NULL); with_view_model( browser->view, FileBrowserModel * model, - { items_array_push_back(model->items, item); }, - instant_update); - + { + items_array_push_back(model->items, item); + // TODO: calculate if element is visible + }, + false); furi_string_free(item.display_name); furi_string_free(item.path); if(item.custom_icon_data) { @@ -484,33 +477,25 @@ static void browser_list_item_cb( browser->view, FileBrowserModel * model, { - model->list_loading = false; - if(browser_is_list_load_required(model)) { - model->list_loading = true; - int32_t load_offset = CLAMP( - model->item_idx - ITEM_LIST_LEN_MAX / 2, (int32_t)model->item_cnt, 0); - file_browser_worker_load(browser->worker, load_offset, ITEM_LIST_LEN_MAX); + if(model->item_cnt <= BROWSER_SORT_THRESHOLD) { + FuriString* selected = NULL; + if(model->item_idx > 0) { + selected = furi_string_alloc_set( + items_array_get(model->items, model->item_idx)->path); + } - if(model->item_cnt <= BROWSER_SORT_THRESHOLD) { - FuriString* selected = NULL; - if(model->item_idx > 0) { - selected = furi_string_alloc_set( - items_array_get(model->items, model->item_idx)->path); - } + items_array_sort(model->items); - items_array_sort(model->items); - - if(selected != NULL) { - for(uint32_t i = 0; i < model->item_cnt; i++) { - if(!furi_string_cmp( - items_array_get(model->items, i)->path, selected)) { - model->item_idx = i; - break; - } + if(selected != NULL) { + for(uint32_t i = 0; i < model->item_cnt; i++) { + if(!furi_string_cmp(items_array_get(model->items, i)->path, selected)) { + model->item_idx = i; + break; } } } } + model->list_loading = false; }, false); browser_update_offset(browser); @@ -680,19 +665,24 @@ static bool file_browser_view_input_callback(InputEvent* event, void* context) { } else { scroll_speed = model->button_held_for_ticks > 9 ? 5 : 3; } + } else if(model->button_held_for_ticks < 0) { + scroll_speed = 0; } if(event->key == InputKeyUp) { if(model->item_idx < scroll_speed) { - model->button_held_for_ticks = 0; - model->item_idx = model->item_cnt - 1; - browser_list_rollover(model); - } else { - model->item_idx = - ((model->item_idx - scroll_speed) + model->item_cnt) % - model->item_cnt; + scroll_speed = model->item_idx; + if(scroll_speed == 0) { + if(model->button_held_for_ticks > 0) { + model->button_held_for_ticks = -1; + } else { + scroll_speed = 1; + } + } } + model->item_idx = + ((model->item_idx - scroll_speed) + model->item_cnt) % model->item_cnt; if(browser_is_list_load_required(model)) { model->list_loading = true; int32_t load_offset = CLAMP( @@ -704,16 +694,24 @@ static bool file_browser_view_input_callback(InputEvent* event, void* context) { } model->scroll_counter = 0; + if(model->button_held_for_ticks < -1) { + model->button_held_for_ticks = 0; + } model->button_held_for_ticks += 1; } else if(event->key == InputKeyDown) { - if(model->item_idx + scroll_speed >= (int32_t)model->item_cnt) { - model->button_held_for_ticks = 0; - model->item_idx = 0; - browser_list_rollover(model); - } else { - model->item_idx = (model->item_idx + scroll_speed) % model->item_cnt; + int32_t count = model->item_cnt; + if(model->item_idx + scroll_speed >= count) { + scroll_speed = count - model->item_idx - 1; + if(scroll_speed == 0) { + if(model->button_held_for_ticks > 0) { + model->button_held_for_ticks = -1; + } else { + scroll_speed = 1; + } + } } + model->item_idx = (model->item_idx + scroll_speed) % model->item_cnt; if(browser_is_list_load_required(model)) { model->list_loading = true; int32_t load_offset = CLAMP( @@ -725,6 +723,9 @@ static bool file_browser_view_input_callback(InputEvent* event, void* context) { } model->scroll_counter = 0; + if(model->button_held_for_ticks < -1) { + model->button_held_for_ticks = 0; + } model->button_held_for_ticks += 1; } }, From af64ae0e4015ed58d102b15f4a1f9140c67e286d Mon Sep 17 00:00:00 2001 From: DEXV <89728480+DXVVAY@users.noreply.github.com> Date: Fri, 14 Jul 2023 14:43:13 +0200 Subject: [PATCH 163/364] Badusb: Ducky script to auto install qFlipper (#2346) * Badusb:Script to auto install/update qFlipper * Update Install_qFlipper_windows.txt --- .../badusb/Install_qFlipper_windows.txt | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 assets/resources/badusb/Install_qFlipper_windows.txt diff --git a/assets/resources/badusb/Install_qFlipper_windows.txt b/assets/resources/badusb/Install_qFlipper_windows.txt new file mode 100644 index 000000000..942823215 --- /dev/null +++ b/assets/resources/badusb/Install_qFlipper_windows.txt @@ -0,0 +1,42 @@ +REM Written by @dexv +DELAY 2000 +GUI r +DELAY 500 +STRING powershell +ENTER +DELAY 1000 +STRING $url = "https://update.flipperzero.one/qFlipper/release/windows-amd64/portable" +ENTER +STRING $output = "$env:USERPROFILE\Documents\qFlipper.zip" +ENTER +STRING $destination = "$env:USERPROFILE\Documents\qFlipper" +ENTER +STRING $shortcutPath = "$env:USERPROFILE\Desktop\qFlipper.lnk" +ENTER +STRING $scriptPath = "$env:USERPROFILE\Documents\qFlipperInstall.ps1" +ENTER +STRING $driverPath = "$destination\STM32 Driver" +ENTER +STRING $installBat = "$driverPath\install.bat" +ENTER +STRING (New-Object System.Net.WebClient).DownloadFile($url, $output) +ENTER +STRING Expand-Archive -Path $output -DestinationPath $destination -Force +ENTER +STRING Set-Location -Path $destination +ENTER +STRING Start-Process -FilePath ".\qFlipper.exe" +ENTER +STRING Start-Process -Wait -FilePath "cmd.exe" -ArgumentList "/c $installBat" +ENTER +STRING $shell = New-Object -ComObject WScript.Shell +ENTER +STRING $shortcut = $shell.CreateShortcut($shortcutPath) +ENTER +STRING $shortcut.TargetPath = "$destination\qFlipper.exe" +ENTER +STRING $shortcut.Save() +ENTER +DELAY 500 +STRING "powershell -ExecutionPolicy Bypass -File $scriptPath" +ENTER From ae8488ecba43b472c42ae5141624b098dd3d8970 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 14 Jul 2023 15:25:02 +0200 Subject: [PATCH 164/364] Re-merge the file browser changes properly --- .../main/archive/helpers/archive_browser.c | 34 +++++++- .../main/archive/helpers/archive_browser.h | 1 + .../main/archive/views/archive_browser_view.c | 70 +++++----------- .../main/archive/views/archive_browser_view.h | 2 +- .../services/gui/modules/file_browser.c | 80 ++++++++++--------- 5 files changed, 94 insertions(+), 93 deletions(-) diff --git a/applications/main/archive/helpers/archive_browser.c b/applications/main/archive/helpers/archive_browser.c index c0325ff16..4dcbc04a8 100644 --- a/applications/main/archive/helpers/archive_browser.c +++ b/applications/main/archive/helpers/archive_browser.c @@ -70,6 +70,7 @@ static void archive_list_item_cb( if(!is_last) { archive_add_file_item(browser, is_folder, furi_string_get_cstr(item_path)); } else { + bool load_again = false; with_view_model( browser->view, ArchiveBrowserViewModel * model, @@ -96,10 +97,19 @@ static void archive_list_item_cb( model->item_idx = 0; } } - model->list_loading = false; + if(archive_is_file_list_load_required(model)) { + model->list_loading = true; + load_again = true; + } else { + model->list_loading = false; + } }, true); - archive_update_offset(browser); + if(load_again) { + archive_file_array_load(browser, 0); + } else { + archive_update_offset(browser); + } } } @@ -145,6 +155,26 @@ bool archive_is_item_in_array(ArchiveBrowserViewModel* model, uint32_t idx) { return true; } +bool archive_is_file_list_load_required(ArchiveBrowserViewModel* model) { + size_t array_size = files_array_size(model->files); + + if((model->list_loading) || (array_size >= model->item_cnt)) { + return false; + } + + if((model->array_offset > 0) && + (model->item_idx < (model->array_offset + FILE_LIST_BUF_LEN / 4))) { + return true; + } + + if(((model->array_offset + array_size) < model->item_cnt) && + (model->item_idx > (int32_t)(model->array_offset + array_size - FILE_LIST_BUF_LEN / 4))) { + return true; + } + + return false; +} + void archive_update_offset(ArchiveBrowserView* browser) { furi_assert(browser); diff --git a/applications/main/archive/helpers/archive_browser.h b/applications/main/archive/helpers/archive_browser.h index 8074d2532..d9214bee1 100644 --- a/applications/main/archive/helpers/archive_browser.h +++ b/applications/main/archive/helpers/archive_browser.h @@ -72,6 +72,7 @@ inline bool archive_is_known_app(ArchiveFileTypeEnum type) { } bool archive_is_item_in_array(ArchiveBrowserViewModel* model, uint32_t idx); +bool archive_is_file_list_load_required(ArchiveBrowserViewModel* model); void archive_update_offset(ArchiveBrowserView* browser); void archive_update_focus(ArchiveBrowserView* browser, const char* target); diff --git a/applications/main/archive/views/archive_browser_view.c b/applications/main/archive/views/archive_browser_view.c index 559040877..5334e8a48 100644 --- a/applications/main/archive/views/archive_browser_view.c +++ b/applications/main/archive/views/archive_browser_view.c @@ -5,7 +5,6 @@ #include "archive_browser_view.h" #include "../helpers/archive_browser.h" -#define TAG "Archive" #define SCROLL_INTERVAL (333) #define SCROLL_DELAY (2) @@ -334,24 +333,10 @@ View* archive_browser_get_view(ArchiveBrowserView* browser) { return browser->view; } -static bool is_file_list_load_required(ArchiveBrowserViewModel* model) { - size_t array_size = files_array_size(model->files); - - if((model->list_loading) || (array_size >= model->item_cnt)) { - return false; +static void file_list_rollover(ArchiveBrowserViewModel* model) { + if(!model->list_loading && files_array_size(model->files) < model->item_cnt) { + files_array_reset(model->files); } - - if((model->array_offset > 0) && - (model->item_idx < (model->array_offset + FILE_LIST_BUF_LEN / 4))) { - return true; - } - - if(((model->array_offset + array_size) < model->item_cnt) && - (model->item_idx > (int32_t)(model->array_offset + array_size - FILE_LIST_BUF_LEN / 4))) { - return true; - } - - return false; } static bool archive_view_input(InputEvent* event, void* context) { @@ -437,25 +422,19 @@ static bool archive_view_input(InputEvent* event, void* context) { } else { scroll_speed = model->button_held_for_ticks > 9 ? 4 : 2; } - } else if(model->button_held_for_ticks < 0) { - scroll_speed = 0; } if(event->key == InputKeyUp) { if(model->item_idx < scroll_speed) { - scroll_speed = model->item_idx; - if(scroll_speed == 0) { - if(model->button_held_for_ticks > 0) { - model->button_held_for_ticks = -1; - } else { - scroll_speed = 1; - } - } + model->button_held_for_ticks = 0; + model->item_idx = model->item_cnt - 1; + file_list_rollover(model); + } else { + model->item_idx = + ((model->item_idx - scroll_speed) + model->item_cnt) % + model->item_cnt; } - - model->item_idx = - ((model->item_idx - scroll_speed) + model->item_cnt) % model->item_cnt; - if(is_file_list_load_required(model)) { + if(archive_is_file_list_load_required(model)) { model->list_loading = true; browser->callback(ArchiveBrowserEventLoadPrevItems, browser->context); } @@ -463,26 +442,17 @@ static bool archive_view_input(InputEvent* event, void* context) { browser->callback(ArchiveBrowserEventFavMoveUp, browser->context); } model->scroll_counter = 0; - - if(model->button_held_for_ticks < -1) { - model->button_held_for_ticks = 0; - } model->button_held_for_ticks += 1; } else if(event->key == InputKeyDown) { int32_t count = model->item_cnt; - if(model->item_idx >= (count - scroll_speed)) { - scroll_speed = model->item_cnt - model->item_idx - 1; - if(scroll_speed == 0) { - if(model->button_held_for_ticks > 0) { - model->button_held_for_ticks = -1; - } else { - scroll_speed = 1; - } - } + if(model->item_idx + scroll_speed >= count) { + model->button_held_for_ticks = 0; + model->item_idx = 0; + file_list_rollover(model); + } else { + model->item_idx = (model->item_idx + scroll_speed) % model->item_cnt; } - - model->item_idx = (model->item_idx + scroll_speed) % model->item_cnt; - if(is_file_list_load_required(model)) { + if(archive_is_file_list_load_required(model)) { model->list_loading = true; browser->callback(ArchiveBrowserEventLoadNextItems, browser->context); } @@ -490,10 +460,6 @@ static bool archive_view_input(InputEvent* event, void* context) { browser->callback(ArchiveBrowserEventFavMoveDown, browser->context); } model->scroll_counter = 0; - - if(model->button_held_for_ticks < -1) { - model->button_held_for_ticks = 0; - } model->button_held_for_ticks += 1; } }, diff --git a/applications/main/archive/views/archive_browser_view.h b/applications/main/archive/views/archive_browser_view.h index f87d59a2b..d06e27e8c 100644 --- a/applications/main/archive/views/archive_browser_view.h +++ b/applications/main/archive/views/archive_browser_view.h @@ -115,7 +115,7 @@ typedef struct { int32_t list_offset; size_t scroll_counter; - int32_t button_held_for_ticks; + uint32_t button_held_for_ticks; } ArchiveBrowserViewModel; void archive_browser_set_callback( diff --git a/applications/services/gui/modules/file_browser.c b/applications/services/gui/modules/file_browser.c index d934cccc8..acbeeeea3 100644 --- a/applications/services/gui/modules/file_browser.c +++ b/applications/services/gui/modules/file_browser.c @@ -144,7 +144,7 @@ typedef struct { bool hide_ext; size_t scroll_counter; - int32_t button_held_for_ticks; + uint32_t button_held_for_ticks; } FileBrowserModel; static const Icon* BrowserItemIcons[] = { @@ -332,6 +332,12 @@ static bool browser_is_list_load_required(FileBrowserModel* model) { return false; } +static void browser_list_rollover(FileBrowserModel* model) { + if(!model->list_loading && items_array_size(model->items) < model->item_cnt) { + items_array_reset(model->items); + } +} + static void browser_update_offset(FileBrowser* browser) { furi_assert(browser); @@ -354,7 +360,7 @@ static void browser_update_offset(FileBrowser* browser) { CLAMP(model->item_idx - 1, (int32_t)model->item_cnt - bounds, 0); } }, - true); + false); } static void @@ -459,20 +465,23 @@ static void browser_list_item_cb( (browser->hide_ext) && (item.type == BrowserItemTypeFile)); } + // We shouldn't update screen on each item if custom callback is not set + // Otherwise it will cause screen flickering + bool instant_update = (browser->item_callback != NULL); with_view_model( browser->view, FileBrowserModel * model, - { - items_array_push_back(model->items, item); - // TODO: calculate if element is visible - }, - false); + { items_array_push_back(model->items, item); }, + instant_update); + furi_string_free(item.display_name); furi_string_free(item.path); if(item.custom_icon_data) { free(item.custom_icon_data); } } else { + bool load_again = false; + int32_t load_offset = 0; with_view_model( browser->view, FileBrowserModel * model, @@ -495,10 +504,21 @@ static void browser_list_item_cb( } } } - model->list_loading = false; + if(browser_is_list_load_required(model)) { + model->list_loading = true; + load_again = true; + load_offset = CLAMP( + model->item_idx - ITEM_LIST_LEN_MAX / 2, (int32_t)model->item_cnt, 0); + } else { + model->list_loading = false; + } }, false); - browser_update_offset(browser); + if(load_again) { + file_browser_worker_load(browser->worker, load_offset, ITEM_LIST_LEN_MAX); + } else { + browser_update_offset(browser); + } } } @@ -665,24 +685,19 @@ static bool file_browser_view_input_callback(InputEvent* event, void* context) { } else { scroll_speed = model->button_held_for_ticks > 9 ? 5 : 3; } - } else if(model->button_held_for_ticks < 0) { - scroll_speed = 0; } if(event->key == InputKeyUp) { if(model->item_idx < scroll_speed) { - scroll_speed = model->item_idx; - if(scroll_speed == 0) { - if(model->button_held_for_ticks > 0) { - model->button_held_for_ticks = -1; - } else { - scroll_speed = 1; - } - } + model->button_held_for_ticks = 0; + model->item_idx = model->item_cnt - 1; + browser_list_rollover(model); + } else { + model->item_idx = + ((model->item_idx - scroll_speed) + model->item_cnt) % + model->item_cnt; } - model->item_idx = - ((model->item_idx - scroll_speed) + model->item_cnt) % model->item_cnt; if(browser_is_list_load_required(model)) { model->list_loading = true; int32_t load_offset = CLAMP( @@ -694,24 +709,16 @@ static bool file_browser_view_input_callback(InputEvent* event, void* context) { } model->scroll_counter = 0; - if(model->button_held_for_ticks < -1) { - model->button_held_for_ticks = 0; - } model->button_held_for_ticks += 1; } else if(event->key == InputKeyDown) { - int32_t count = model->item_cnt; - if(model->item_idx + scroll_speed >= count) { - scroll_speed = count - model->item_idx - 1; - if(scroll_speed == 0) { - if(model->button_held_for_ticks > 0) { - model->button_held_for_ticks = -1; - } else { - scroll_speed = 1; - } - } + if(model->item_idx + scroll_speed >= (int32_t)model->item_cnt) { + model->button_held_for_ticks = 0; + model->item_idx = 0; + browser_list_rollover(model); + } else { + model->item_idx = (model->item_idx + scroll_speed) % model->item_cnt; } - model->item_idx = (model->item_idx + scroll_speed) % model->item_cnt; if(browser_is_list_load_required(model)) { model->list_loading = true; int32_t load_offset = CLAMP( @@ -723,9 +730,6 @@ static bool file_browser_view_input_callback(InputEvent* event, void* context) { } model->scroll_counter = 0; - if(model->button_held_for_ticks < -1) { - model->button_held_for_ticks = 0; - } model->button_held_for_ticks += 1; } }, From a377178a35d35e89708150b5efeb365a51ca7b40 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 14 Jul 2023 15:26:56 +0200 Subject: [PATCH 165/364] Item idx not needed anymore --- .../main/archive/helpers/archive_browser.c | 9 ++------- .../services/gui/modules/file_browser.c | 17 ++++------------- .../services/gui/modules/file_browser_worker.c | 12 ++++-------- .../services/gui/modules/file_browser_worker.h | 1 - 4 files changed, 10 insertions(+), 29 deletions(-) diff --git a/applications/main/archive/helpers/archive_browser.c b/applications/main/archive/helpers/archive_browser.c index 4dcbc04a8..b5be220fc 100644 --- a/applications/main/archive/helpers/archive_browser.c +++ b/applications/main/archive/helpers/archive_browser.c @@ -57,14 +57,9 @@ static void archive_list_load_cb(void* context, uint32_t list_load_offset) { false); } -static void archive_list_item_cb( - void* context, - FuriString* item_path, - uint32_t idx, - bool is_folder, - bool is_last) { +static void + archive_list_item_cb(void* context, FuriString* item_path, bool is_folder, bool is_last) { furi_assert(context); - UNUSED(idx); ArchiveBrowserView* browser = (ArchiveBrowserView*)context; if(!is_last) { diff --git a/applications/services/gui/modules/file_browser.c b/applications/services/gui/modules/file_browser.c index acbeeeea3..a47d16d63 100644 --- a/applications/services/gui/modules/file_browser.c +++ b/applications/services/gui/modules/file_browser.c @@ -160,12 +160,8 @@ static bool file_browser_view_input_callback(InputEvent* event, void* context); static void browser_folder_open_cb(void* context, uint32_t item_cnt, int32_t file_idx, bool is_root); static void browser_list_load_cb(void* context, uint32_t list_load_offset); -static void browser_list_item_cb( - void* context, - FuriString* item_path, - uint32_t idx, - bool is_folder, - bool is_last); +static void + browser_list_item_cb(void* context, FuriString* item_path, bool is_folder, bool is_last); static void browser_long_load_cb(void* context); static void file_browser_scroll_timer_callback(void* context) { @@ -425,14 +421,9 @@ static void browser_list_load_cb(void* context, uint32_t list_load_offset) { BrowserItem_t_clear(&back_item); } -static void browser_list_item_cb( - void* context, - FuriString* item_path, - uint32_t idx, - bool is_folder, - bool is_last) { +static void + browser_list_item_cb(void* context, FuriString* item_path, bool is_folder, bool is_last) { furi_assert(context); - UNUSED(idx); FileBrowser* browser = (FileBrowser*)context; BrowserItem_t item; diff --git a/applications/services/gui/modules/file_browser_worker.c b/applications/services/gui/modules/file_browser_worker.c index d740cc9d4..991e45f24 100644 --- a/applications/services/gui/modules/file_browser_worker.c +++ b/applications/services/gui/modules/file_browser_worker.c @@ -241,11 +241,7 @@ static bool browser_folder_load_chunked( furi_string_printf(name_str, "%s/%s", furi_string_get_cstr(path), name_temp); if(browser->list_item_cb) { browser->list_item_cb( - browser->cb_ctx, - name_str, - items_cnt, - file_info_is_dir(&file_info), - false); + browser->cb_ctx, name_str, file_info_is_dir(&file_info), false); } items_cnt++; } @@ -254,7 +250,7 @@ static bool browser_folder_load_chunked( } } if(browser->list_item_cb) { - browser->list_item_cb(browser->cb_ctx, NULL, 0, false, true); + browser->list_item_cb(browser->cb_ctx, NULL, false, true); } } while(0); @@ -296,13 +292,13 @@ static bool browser_folder_load_full(BrowserWorker* browser, FuriString* path) { furi_string_printf(name_str, "%s/%s", furi_string_get_cstr(path), name_temp); if(browser->list_item_cb) { browser->list_item_cb( - browser->cb_ctx, name_str, items_cnt, file_info_is_dir(&file_info), false); + browser->cb_ctx, name_str, file_info_is_dir(&file_info), false); } items_cnt++; } } if(browser->list_item_cb) { - browser->list_item_cb(browser->cb_ctx, NULL, 0, false, true); + browser->list_item_cb(browser->cb_ctx, NULL, false, true); } ret = true; } while(0); diff --git a/applications/services/gui/modules/file_browser_worker.h b/applications/services/gui/modules/file_browser_worker.h index b4b731092..d73f153d2 100644 --- a/applications/services/gui/modules/file_browser_worker.h +++ b/applications/services/gui/modules/file_browser_worker.h @@ -19,7 +19,6 @@ typedef void (*BrowserWorkerListLoadCallback)(void* context, uint32_t list_load_ typedef void (*BrowserWorkerListItemCallback)( void* context, FuriString* item_path, - uint32_t idx, bool is_folder, bool is_last); typedef void (*BrowserWorkerLongLoadCallback)(void* context); From 61a66c52c61ea0fb862c0a564fd918326952b4cd Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 12 Jul 2023 21:23:49 +0300 Subject: [PATCH 166/364] Revert "merge changes" This reverts commit f998948623d8cb8bb1ead7efe839682f96f34d3c. --- .../services/gui/modules/file_browser.c | 34 +++++++------------ 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/applications/services/gui/modules/file_browser.c b/applications/services/gui/modules/file_browser.c index eb1e39bd2..bfca1f91c 100644 --- a/applications/services/gui/modules/file_browser.c +++ b/applications/services/gui/modules/file_browser.c @@ -480,33 +480,25 @@ static void browser_list_item_cb( browser->view, FileBrowserModel * model, { - model->list_loading = false; - if(browser_is_list_load_required(model)) { - model->list_loading = true; - int32_t load_offset = CLAMP( - model->item_idx - ITEM_LIST_LEN_MAX / 2, (int32_t)model->item_cnt, 0); - file_browser_worker_load(browser->worker, load_offset, ITEM_LIST_LEN_MAX); + if(model->item_cnt <= BROWSER_SORT_THRESHOLD) { + FuriString* selected = NULL; + if(model->item_idx > 0) { + selected = furi_string_alloc_set( + items_array_get(model->items, model->item_idx)->path); + } - if(model->item_cnt <= BROWSER_SORT_THRESHOLD) { - FuriString* selected = NULL; - if(model->item_idx > 0) { - selected = furi_string_alloc_set( - items_array_get(model->items, model->item_idx)->path); - } + items_array_sort(model->items); - items_array_sort(model->items); - - if(selected != NULL) { - for(uint32_t i = 0; i < model->item_cnt; i++) { - if(!furi_string_cmp( - items_array_get(model->items, i)->path, selected)) { - model->item_idx = i; - break; - } + if(selected != NULL) { + for(uint32_t i = 0; i < model->item_cnt; i++) { + if(!furi_string_cmp(items_array_get(model->items, i)->path, selected)) { + model->item_idx = i; + break; } } } } + model->list_loading = false; }, false); browser_update_offset(browser); From f274c5899c0b57cd68b4d915bc38729ee27e586e Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 14 Jul 2023 15:41:49 +0200 Subject: [PATCH 167/364] Improve browser wrap around behavior --- .../main/archive/views/archive_browser_view.c | 44 +++++++++++++++---- .../main/archive/views/archive_browser_view.h | 2 +- .../services/gui/modules/file_browser.c | 43 +++++++++++++++--- 3 files changed, 73 insertions(+), 16 deletions(-) diff --git a/applications/main/archive/views/archive_browser_view.c b/applications/main/archive/views/archive_browser_view.c index 5334e8a48..2215ce338 100644 --- a/applications/main/archive/views/archive_browser_view.c +++ b/applications/main/archive/views/archive_browser_view.c @@ -423,13 +423,29 @@ static bool archive_view_input(InputEvent* event, void* context) { scroll_speed = model->button_held_for_ticks > 9 ? 4 : 2; } } + if(model->button_held_for_ticks < -1) { + model->button_held_for_ticks = 0; + } if(event->key == InputKeyUp) { if(model->item_idx < scroll_speed) { - model->button_held_for_ticks = 0; - model->item_idx = model->item_cnt - 1; - file_list_rollover(model); + // Would wrap around + if(model->item_idx == 0) { + // Is first item + if(model->button_held_for_ticks > 0) { + // Was holding, so wait a second to roll over + model->button_held_for_ticks = -1; + } else { + // Wasn't holding / done waiting, roll over now + model->item_idx = model->item_cnt - 1; + file_list_rollover(model); + } + } else { + // Not first item, jump to first + model->item_idx = 0; + } } else { + // No wrap around model->item_idx = ((model->item_idx - scroll_speed) + model->item_cnt) % model->item_cnt; @@ -444,12 +460,24 @@ static bool archive_view_input(InputEvent* event, void* context) { model->scroll_counter = 0; model->button_held_for_ticks += 1; } else if(event->key == InputKeyDown) { - int32_t count = model->item_cnt; - if(model->item_idx + scroll_speed >= count) { - model->button_held_for_ticks = 0; - model->item_idx = 0; - file_list_rollover(model); + if(model->item_idx + scroll_speed >= (int32_t)model->item_cnt) { + // Would wrap around + if(model->item_idx == (int32_t)model->item_cnt - 1) { + // Is last item + if(model->button_held_for_ticks > 0) { + // Was holding, so wait a second to roll over + model->button_held_for_ticks = -1; + } else { + // Wasn't holding / done waiting, roll over now + model->item_idx = 0; + file_list_rollover(model); + } + } else { + // Not last item, jump to last + model->item_idx = model->item_cnt - 1; + } } else { + // No wrap around model->item_idx = (model->item_idx + scroll_speed) % model->item_cnt; } if(archive_is_file_list_load_required(model)) { diff --git a/applications/main/archive/views/archive_browser_view.h b/applications/main/archive/views/archive_browser_view.h index d06e27e8c..f87d59a2b 100644 --- a/applications/main/archive/views/archive_browser_view.h +++ b/applications/main/archive/views/archive_browser_view.h @@ -115,7 +115,7 @@ typedef struct { int32_t list_offset; size_t scroll_counter; - uint32_t button_held_for_ticks; + int32_t button_held_for_ticks; } ArchiveBrowserViewModel; void archive_browser_set_callback( diff --git a/applications/services/gui/modules/file_browser.c b/applications/services/gui/modules/file_browser.c index a47d16d63..29d55c775 100644 --- a/applications/services/gui/modules/file_browser.c +++ b/applications/services/gui/modules/file_browser.c @@ -144,7 +144,7 @@ typedef struct { bool hide_ext; size_t scroll_counter; - uint32_t button_held_for_ticks; + int32_t button_held_for_ticks; } FileBrowserModel; static const Icon* BrowserItemIcons[] = { @@ -677,13 +677,29 @@ static bool file_browser_view_input_callback(InputEvent* event, void* context) { scroll_speed = model->button_held_for_ticks > 9 ? 5 : 3; } } + if(model->button_held_for_ticks < -1) { + model->button_held_for_ticks = 0; + } if(event->key == InputKeyUp) { if(model->item_idx < scroll_speed) { - model->button_held_for_ticks = 0; - model->item_idx = model->item_cnt - 1; - browser_list_rollover(model); + // Would wrap around + if(model->item_idx == 0) { + // Is first item + if(model->button_held_for_ticks > 0) { + // Was holding, so wait a second to roll over + model->button_held_for_ticks = -1; + } else { + // Wasn't holding / done waiting, roll over now + model->item_idx = model->item_cnt - 1; + file_list_rollover(model); + } + } else { + // Not first item, jump to first + model->item_idx = 0; + } } else { + // No wrap around model->item_idx = ((model->item_idx - scroll_speed) + model->item_cnt) % model->item_cnt; @@ -703,10 +719,23 @@ static bool file_browser_view_input_callback(InputEvent* event, void* context) { model->button_held_for_ticks += 1; } else if(event->key == InputKeyDown) { if(model->item_idx + scroll_speed >= (int32_t)model->item_cnt) { - model->button_held_for_ticks = 0; - model->item_idx = 0; - browser_list_rollover(model); + // Would wrap around + if(model->item_idx == (int32_t)model->item_cnt - 1) { + // Is last item + if(model->button_held_for_ticks > 0) { + // Was holding, so wait a second to roll over + model->button_held_for_ticks = -1; + } else { + // Wasn't holding / done waiting, roll over now + model->item_idx = 0; + file_list_rollover(model); + } + } else { + // Not last item, jump to last + model->item_idx = model->item_cnt - 1; + } } else { + // No wrap around model->item_idx = (model->item_idx + scroll_speed) % model->item_cnt; } From 86ce1456516cbcaf9e5100c8911996d2ab0aa0a7 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 14 Jul 2023 15:44:30 +0200 Subject: [PATCH 168/364] Fix build --- applications/services/gui/modules/file_browser.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/applications/services/gui/modules/file_browser.c b/applications/services/gui/modules/file_browser.c index 29d55c775..8cb0eca2b 100644 --- a/applications/services/gui/modules/file_browser.c +++ b/applications/services/gui/modules/file_browser.c @@ -692,7 +692,7 @@ static bool file_browser_view_input_callback(InputEvent* event, void* context) { } else { // Wasn't holding / done waiting, roll over now model->item_idx = model->item_cnt - 1; - file_list_rollover(model); + browser_list_rollover(model); } } else { // Not first item, jump to first @@ -728,7 +728,7 @@ static bool file_browser_view_input_callback(InputEvent* event, void* context) { } else { // Wasn't holding / done waiting, roll over now model->item_idx = 0; - file_list_rollover(model); + browser_list_rollover(model); } } else { // Not last item, jump to last From f2324e4d1ca57654e66e580c4ea14da145cc7e4c Mon Sep 17 00:00:00 2001 From: hedger Date: Fri, 14 Jul 2023 16:45:16 +0300 Subject: [PATCH 169/364] [FL-3377] Update error code descriptions (#2875) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * updater: added update error code descriptions * updater: separate ram/flash messages * updater: extra pre-update checks * updater: fixed string comparison * updater: Additional logging Co-authored-by: ã‚ã --- .../system/updater/util/update_task.c | 192 +++++++++++++++++- .../system/updater/util/update_task_i.h | 5 + .../updater/util/update_task_worker_backup.c | 5 - .../updater/util/update_task_worker_flasher.c | 26 ++- .../system/updater/views/updater_main.c | 15 +- documentation/OTA.md | 7 +- lib/update_util/update_operation.c | 21 +- lib/update_util/update_operation.h | 3 +- 8 files changed, 238 insertions(+), 36 deletions(-) diff --git a/applications/system/updater/util/update_task.c b/applications/system/updater/util/update_task.c index 708d10ce0..74d752b43 100644 --- a/applications/system/updater/util/update_task.c +++ b/applications/system/updater/util/update_task.c @@ -19,7 +19,7 @@ static const char* update_task_stage_descr[] = { [UpdateTaskStageRadioErase] = "Uninstalling radio FW", [UpdateTaskStageRadioWrite] = "Writing radio FW", [UpdateTaskStageRadioInstall] = "Installing radio FW", - [UpdateTaskStageRadioBusy] = "Radio is updating", + [UpdateTaskStageRadioBusy] = "Core 2 busy", [UpdateTaskStageOBValidation] = "Validating opt. bytes", [UpdateTaskStageLfsBackup] = "Backing up LFS", [UpdateTaskStageLfsRestore] = "Restoring LFS", @@ -30,6 +30,191 @@ static const char* update_task_stage_descr[] = { [UpdateTaskStageOBError] = "OB, report", }; +static const struct { + UpdateTaskStage stage; + uint8_t percent_min, percent_max; + const char* descr; +} update_task_error_detail[] = { + { + .stage = UpdateTaskStageReadManifest, + .percent_min = 0, + .percent_max = 13, + .descr = "Wrong Updater HW", + }, + { + .stage = UpdateTaskStageReadManifest, + .percent_min = 14, + .percent_max = 20, + .descr = "Manifest pointer error", + }, + { + .stage = UpdateTaskStageReadManifest, + .percent_min = 21, + .percent_max = 30, + .descr = "Manifest load error", + }, + { + .stage = UpdateTaskStageReadManifest, + .percent_min = 31, + .percent_max = 40, + .descr = "Wrong package version", + }, + { + .stage = UpdateTaskStageReadManifest, + .percent_min = 41, + .percent_max = 50, + .descr = "HW Target mismatch", + }, + { + .stage = UpdateTaskStageReadManifest, + .percent_min = 51, + .percent_max = 60, + .descr = "No DFU file", + }, + { + .stage = UpdateTaskStageReadManifest, + .percent_min = 61, + .percent_max = 80, + .descr = "No Radio file", + }, +#ifndef FURI_RAM_EXEC + { + .stage = UpdateTaskStageLfsBackup, + .percent_min = 0, + .percent_max = 100, + .descr = "FS R/W error", + }, +#else + { + .stage = UpdateTaskStageRadioImageValidate, + .percent_min = 0, + .percent_max = 98, + .descr = "FS Read error", + }, + { + .stage = UpdateTaskStageRadioImageValidate, + .percent_min = 99, + .percent_max = 100, + .descr = "CRC mismatch", + }, + { + .stage = UpdateTaskStageRadioErase, + .percent_min = 0, + .percent_max = 30, + .descr = "Stack remove: cmd error", + }, + { + .stage = UpdateTaskStageRadioErase, + .percent_min = 31, + .percent_max = 100, + .descr = "Stack remove: wait failed", + }, + { + .stage = UpdateTaskStageRadioWrite, + .percent_min = 0, + .percent_max = 100, + .descr = "Stack write: error", + }, + { + .stage = UpdateTaskStageRadioInstall, + .percent_min = 0, + .percent_max = 10, + .descr = "Stack install: cmd error", + }, + { + .stage = UpdateTaskStageRadioInstall, + .percent_min = 11, + .percent_max = 100, + .descr = "Stack install: wait failed", + }, + { + .stage = UpdateTaskStageRadioBusy, + .percent_min = 0, + .percent_max = 10, + .descr = "Failed to start C2", + }, + { + .stage = UpdateTaskStageRadioBusy, + .percent_min = 11, + .percent_max = 20, + .descr = "C2 FUS swich failed", + }, + { + .stage = UpdateTaskStageRadioBusy, + .percent_min = 21, + .percent_max = 30, + .descr = "FUS operation failed", + }, + { + .stage = UpdateTaskStageRadioBusy, + .percent_min = 31, + .percent_max = 100, + .descr = "C2 Stach switch failed", + }, + { + .stage = UpdateTaskStageOBValidation, + .percent_min = 0, + .percent_max = 100, + .descr = "Uncorr. value mismatch", + }, + { + .stage = UpdateTaskStageValidateDFUImage, + .percent_min = 0, + .percent_max = 1, + .descr = "Failed to open DFU file", + }, + { + .stage = UpdateTaskStageValidateDFUImage, + .percent_min = 1, + .percent_max = 97, + .descr = "DFU file read error", + }, + { + .stage = UpdateTaskStageValidateDFUImage, + .percent_min = 98, + .percent_max = 100, + .descr = "DFU file CRC mismatch", + }, + { + .stage = UpdateTaskStageFlashWrite, + .percent_min = 0, + .percent_max = 100, + .descr = "Flash write error", + }, + { + .stage = UpdateTaskStageFlashValidate, + .percent_min = 0, + .percent_max = 100, + .descr = "Flash compare error", + }, +#endif +#ifndef FURI_RAM_EXEC + { + .stage = UpdateTaskStageLfsRestore, + .percent_min = 0, + .percent_max = 100, + .descr = "LFS I/O error", + }, + { + .stage = UpdateTaskStageResourcesUpdate, + .percent_min = 0, + .percent_max = 100, + .descr = "SD card I/O error", + }, +#endif +}; + +static const char* update_task_get_error_message(UpdateTaskStage stage, uint8_t percent) { + for(size_t i = 0; i < COUNT_OF(update_task_error_detail); i++) { + if(update_task_error_detail[i].stage == stage && + percent >= update_task_error_detail[i].percent_min && + percent <= update_task_error_detail[i].percent_max) { + return update_task_error_detail[i].descr; + } + } + return "Unknown error"; +} + typedef struct { UpdateTaskStageGroup group; uint8_t weight; @@ -111,8 +296,9 @@ void update_task_set_progress(UpdateTask* update_task, UpdateTaskStage stage, ui if(stage >= UpdateTaskStageError) { furi_string_printf( update_task->state.status, - "%s #[%d-%d]", - update_task_stage_descr[stage], + "%s\n#[%d-%d]", + update_task_get_error_message( + update_task->state.stage, update_task->state.stage_progress), update_task->state.stage, update_task->state.stage_progress); } else { diff --git a/applications/system/updater/util/update_task_i.h b/applications/system/updater/util/update_task_i.h index 0dbeca5f4..1b664e57e 100644 --- a/applications/system/updater/util/update_task_i.h +++ b/applications/system/updater/util/update_task_i.h @@ -24,3 +24,8 @@ bool update_task_open_file(UpdateTask* update_task, FuriString* filename); int32_t update_task_worker_flash_writer(void* context); int32_t update_task_worker_backup_restore(void* context); + +#define CHECK_RESULT(x) \ + if(!(x)) { \ + break; \ + } diff --git a/applications/system/updater/util/update_task_worker_backup.c b/applications/system/updater/util/update_task_worker_backup.c index f2c33c2ed..ef4276fac 100644 --- a/applications/system/updater/util/update_task_worker_backup.c +++ b/applications/system/updater/util/update_task_worker_backup.c @@ -15,11 +15,6 @@ #define TAG "UpdWorkerBackup" -#define CHECK_RESULT(x) \ - if(!(x)) { \ - break; \ - } - static bool update_task_pre_update(UpdateTask* update_task) { bool success = false; FuriString* backup_file_path; diff --git a/applications/system/updater/util/update_task_worker_flasher.c b/applications/system/updater/util/update_task_worker_flasher.c index 5d2477464..d6dc13e37 100644 --- a/applications/system/updater/util/update_task_worker_flasher.c +++ b/applications/system/updater/util/update_task_worker_flasher.c @@ -13,11 +13,6 @@ #define TAG "UpdWorkerRAM" -#define CHECK_RESULT(x) \ - if(!(x)) { \ - break; \ - } - #define STM_DFU_VENDOR_ID 0x0483 #define STM_DFU_PRODUCT_ID 0xDF11 /* Written into DFU file by build pipeline */ @@ -137,7 +132,7 @@ static bool update_task_write_stack_data(UpdateTask* update_task) { } static void update_task_wait_for_restart(UpdateTask* update_task) { - update_task_set_progress(update_task, UpdateTaskStageRadioBusy, 10); + update_task_set_progress(update_task, UpdateTaskStageRadioBusy, 70); furi_delay_ms(C2_MODE_SWITCH_TIMEOUT); furi_crash("C2 timeout"); } @@ -153,12 +148,12 @@ static bool update_task_write_stack(UpdateTask* update_task) { manifest->radio_crc); CHECK_RESULT(update_task_write_stack_data(update_task)); - update_task_set_progress(update_task, UpdateTaskStageRadioInstall, 0); + update_task_set_progress(update_task, UpdateTaskStageRadioInstall, 10); CHECK_RESULT( ble_glue_fus_stack_install(manifest->radio_address, 0) != BleGlueCommandResultError); - update_task_set_progress(update_task, UpdateTaskStageRadioInstall, 80); + update_task_set_progress(update_task, UpdateTaskStageProgress, 80); CHECK_RESULT(ble_glue_fus_wait_operation() == BleGlueCommandResultOK); - update_task_set_progress(update_task, UpdateTaskStageRadioInstall, 100); + update_task_set_progress(update_task, UpdateTaskStageProgress, 100); /* ...system will restart here. */ update_task_wait_for_restart(update_task); } while(false); @@ -170,9 +165,9 @@ static bool update_task_remove_stack(UpdateTask* update_task) { FURI_LOG_W(TAG, "Removing stack"); update_task_set_progress(update_task, UpdateTaskStageRadioErase, 30); CHECK_RESULT(ble_glue_fus_stack_delete() != BleGlueCommandResultError); - update_task_set_progress(update_task, UpdateTaskStageRadioErase, 80); + update_task_set_progress(update_task, UpdateTaskStageProgress, 80); CHECK_RESULT(ble_glue_fus_wait_operation() == BleGlueCommandResultOK); - update_task_set_progress(update_task, UpdateTaskStageRadioErase, 100); + update_task_set_progress(update_task, UpdateTaskStageProgress, 100); /* ...system will restart here. */ update_task_wait_for_restart(update_task); } while(false); @@ -180,6 +175,7 @@ static bool update_task_remove_stack(UpdateTask* update_task) { } static bool update_task_manage_radiostack(UpdateTask* update_task) { + update_task_set_progress(update_task, UpdateTaskStageRadioBusy, 10); bool success = false; do { CHECK_RESULT(ble_glue_wait_for_c2_start(FURI_HAL_BT_C2_START_TIMEOUT)); @@ -208,15 +204,17 @@ static bool update_task_manage_radiostack(UpdateTask* update_task) { /* Version or type mismatch. Let's boot to FUS and start updating. */ FURI_LOG_W(TAG, "Restarting to FUS"); furi_hal_rtc_set_flag(FuriHalRtcFlagC2Update); + update_task_set_progress(update_task, UpdateTaskStageProgress, 20); + CHECK_RESULT(furi_hal_bt_ensure_c2_mode(BleGlueC2ModeFUS)); /* ...system will restart here. */ update_task_wait_for_restart(update_task); } } else if(c2_state->mode == BleGlueC2ModeFUS) { /* OK, we're in FUS mode. */ - update_task_set_progress(update_task, UpdateTaskStageRadioBusy, 10); FURI_LOG_W(TAG, "Waiting for FUS to settle"); - ble_glue_fus_wait_operation(); + update_task_set_progress(update_task, UpdateTaskStageProgress, 30); + CHECK_RESULT(ble_glue_fus_wait_operation() == BleGlueCommandResultOK); if(stack_version_match) { /* We can't check StackType with FUS, but partial version matches */ if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagC2Update)) { @@ -230,7 +228,7 @@ static bool update_task_manage_radiostack(UpdateTask* update_task) { /* We might just had the stack installed. * Let's start it up to check its version */ FURI_LOG_W(TAG, "Starting stack to check full version"); - update_task_set_progress(update_task, UpdateTaskStageRadioBusy, 40); + update_task_set_progress(update_task, UpdateTaskStageProgress, 50); CHECK_RESULT(furi_hal_bt_ensure_c2_mode(BleGlueC2ModeStack)); /* ...system will restart here. */ update_task_wait_for_restart(update_task); diff --git a/applications/system/updater/views/updater_main.c b/applications/system/updater/views/updater_main.c index 1199cc882..d32d51b7c 100644 --- a/applications/system/updater/views/updater_main.c +++ b/applications/system/updater/views/updater_main.c @@ -81,16 +81,17 @@ static void updater_main_draw_callback(Canvas* canvas, void* _model) { canvas_set_font(canvas, FontPrimary); if(model->failed) { - canvas_draw_str_aligned(canvas, 42, 16, AlignLeft, AlignTop, "Update Failed!"); + canvas_draw_icon(canvas, 2, 22, &I_Warning_30x23); + canvas_draw_str_aligned(canvas, 40, 9, AlignLeft, AlignTop, "Update Failed!"); canvas_set_font(canvas, FontSecondary); - canvas_draw_str_aligned( - canvas, 42, 32, AlignLeft, AlignTop, furi_string_get_cstr(model->status)); - canvas_draw_icon(canvas, 7, 16, &I_Warning_30x23); + elements_multiline_text_aligned( + canvas, 75, 26, AlignCenter, AlignTop, furi_string_get_cstr(model->status)); + canvas_draw_str_aligned( - canvas, 18, 51, AlignLeft, AlignTop, "to retry, hold to abort"); - canvas_draw_icon(canvas, 7, 50, &I_Ok_btn_9x9); - canvas_draw_icon(canvas, 75, 51, &I_Pin_back_arrow_10x8); + canvas, 18, 55, AlignLeft, AlignTop, "to retry, hold to abort"); + canvas_draw_icon(canvas, 7, 54, &I_Ok_btn_9x9); + canvas_draw_icon(canvas, 75, 55, &I_Pin_back_arrow_10x8); } else { canvas_draw_str_aligned(canvas, 55, 14, AlignLeft, AlignTop, "UPDATING"); canvas_set_font(canvas, FontSecondary); diff --git a/documentation/OTA.md b/documentation/OTA.md index 799548f4d..ed75560cf 100644 --- a/documentation/OTA.md +++ b/documentation/OTA.md @@ -87,9 +87,12 @@ Even if something goes wrong, updater allows you to retry failed operations and | Uninstalling radio FW | **4** | **0** | SHCI Delete command error | | | | **80** | Error awaiting command status | | Writing radio FW | **5** | **0-100** | Block read/write error | -| Installing radio FW | **6** | **0** | SHCI Install command error | +| Installing radio FW | **6** | **10** | SHCI Install command error | | | | **80** | Error awaiting command status | -| Radio is updating | **7** | **10** | Error waiting for operation completion | +| Core2 is busy | **7** | **10** | Couldn't start C2 | +| | | **20** | Failed to switch C2 to FUS mode | +| | | **30** | Error in FUS operation | +| | | **50** | Failed to switch C2 to stack mode | | Validating opt. bytes | **8** | **yy** | Option byte code | | Checking DFU file | **9** | **0** | Error opening DFU file | | | | **1-98** | Error reading DFU file | diff --git a/lib/update_util/update_operation.c b/lib/update_util/update_operation.c index 6e05b0233..0cecfc016 100644 --- a/lib/update_util/update_operation.c +++ b/lib/update_util/update_operation.c @@ -20,7 +20,8 @@ static const char* update_prepare_result_descr[] = { [UpdatePrepareResultManifestInvalid] = "Invalid manifest data", [UpdatePrepareResultStageMissing] = "Missing Stage2 loader", [UpdatePrepareResultStageIntegrityError] = "Corrupted Stage2 loader", - [UpdatePrepareResultManifestPointerError] = "Failed to create update pointer file", + [UpdatePrepareResultManifestPointerCreateError] = "Failed to create update pointer file", + [UpdatePrepareResultManifestPointerCheckError] = "Update pointer file error (corrupted FS?)", [UpdatePrepareResultTargetMismatch] = "Hardware target mismatch", [UpdatePrepareResultOutdatedManifestVersion] = "Update package is too old", [UpdatePrepareResultIntFull] = "Need more free space in internal storage", @@ -142,8 +143,8 @@ UpdatePrepareResult update_operation_prepare(const char* manifest_file_path) { File* file = storage_file_alloc(storage); uint64_t free_int_space; - FuriString* stage_path; - stage_path = furi_string_alloc(); + FuriString* stage_path = furi_string_alloc(); + FuriString* manifest_path_check = furi_string_alloc(); do { if((storage_common_fs_info(storage, STORAGE_INT_PATH_PREFIX, NULL, &free_int_space) != FSE_OK) || @@ -188,7 +189,18 @@ UpdatePrepareResult update_operation_prepare(const char* manifest_file_path) { } if(!update_operation_persist_manifest_path(storage, manifest_file_path)) { - result = UpdatePrepareResultManifestPointerError; + result = UpdatePrepareResultManifestPointerCreateError; + break; + } + + if(!update_operation_get_current_package_manifest_path(storage, manifest_path_check) || + (furi_string_cmpi_str(manifest_path_check, manifest_file_path) != 0)) { + FURI_LOG_E( + "update", + "Manifest pointer check failed: '%s' != '%s'", + furi_string_get_cstr(manifest_path_check), + manifest_file_path); + result = UpdatePrepareResultManifestPointerCheckError; break; } @@ -197,6 +209,7 @@ UpdatePrepareResult update_operation_prepare(const char* manifest_file_path) { } while(false); furi_string_free(stage_path); + furi_string_free(manifest_path_check); storage_file_free(file); update_manifest_free(manifest); diff --git a/lib/update_util/update_operation.h b/lib/update_util/update_operation.h index 65abf8e15..8e36b5a13 100644 --- a/lib/update_util/update_operation.h +++ b/lib/update_util/update_operation.h @@ -28,7 +28,8 @@ typedef enum { UpdatePrepareResultManifestInvalid, UpdatePrepareResultStageMissing, UpdatePrepareResultStageIntegrityError, - UpdatePrepareResultManifestPointerError, + UpdatePrepareResultManifestPointerCreateError, + UpdatePrepareResultManifestPointerCheckError, UpdatePrepareResultTargetMismatch, UpdatePrepareResultOutdatedManifestVersion, UpdatePrepareResultIntFull, From d74dc117759d5e1feaf03a08be9805123555e72e Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Fri, 14 Jul 2023 16:52:51 +0300 Subject: [PATCH 170/364] Add extra symbols for nfc maker and wifi marauder keyboard --- applications/external/nfc_maker/nfc_maker_text_input.c | 8 +++++++- .../wifi_marauder_companion/wifi_marauder_text_input.c | 10 ++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/applications/external/nfc_maker/nfc_maker_text_input.c b/applications/external/nfc_maker/nfc_maker_text_input.c index c5b2b27d6..64691b79c 100644 --- a/applications/external/nfc_maker/nfc_maker_text_input.c +++ b/applications/external/nfc_maker/nfc_maker_text_input.c @@ -137,7 +137,7 @@ static const NFCMaker_TextInputKey symbol_keyboard_keys_row_3[] = { {',', 29, 32}, {':', 41, 32}, {'/', 53, 32}, - {'\\', 65, 32}, + {'\'', 65, 32}, {ENTER_KEY, 74, 23}, {'7', 100, 32}, {'8', 110, 32}, @@ -232,6 +232,12 @@ static bool char_is_lowercase(char letter) { static char char_to_uppercase(const char letter) { if(letter == '_') { return 0x20; + } else if(letter == ':') { + return 0x3B; + } else if(letter == '/') { + return 0x5C; + } else if(letter == '\'') { + return 0x60; } else if(char_is_lowercase(letter)) { return (letter - 0x20); } else { diff --git a/applications/external/wifi_marauder_companion/wifi_marauder_text_input.c b/applications/external/wifi_marauder_companion/wifi_marauder_text_input.c index d087f268d..b5ab1d32e 100644 --- a/applications/external/wifi_marauder_companion/wifi_marauder_text_input.c +++ b/applications/external/wifi_marauder_companion/wifi_marauder_text_input.c @@ -136,8 +136,8 @@ static const WIFI_TextInputKey symbol_keyboard_keys_row_3[] = { {SWITCH_KEYBOARD_KEY, 1, 23}, {'.', 15, 32}, {',', 29, 32}, - {';', 41, 32}, - {'`', 53, 32}, + {':', 41, 32}, + {'/', 53, 32}, {'\'', 65, 32}, {ENTER_KEY, 74, 23}, {'7', 100, 32}, @@ -233,6 +233,12 @@ static bool char_is_lowercase(char letter) { static char char_to_uppercase(const char letter) { if(letter == '_') { return 0x20; + } else if(letter == ':') { + return 0x3B; + } else if(letter == '/') { + return 0x5C; + } else if(letter == '\'') { + return 0x60; } else if(char_is_lowercase(letter)) { return (letter - 0x20); } else { From c5a6e8fa46c317fe381d93ed553b3da14c782e59 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 14 Jul 2023 16:19:50 +0200 Subject: [PATCH 171/364] Archive show loading while sorting --- applications/main/archive/views/archive_browser_view.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/main/archive/views/archive_browser_view.c b/applications/main/archive/views/archive_browser_view.c index 2215ce338..390e84c32 100644 --- a/applications/main/archive/views/archive_browser_view.c +++ b/applications/main/archive/views/archive_browser_view.c @@ -195,7 +195,7 @@ static void draw_list(Canvas* canvas, ArchiveBrowserViewModel* model) { ArchiveFileTypeEnum file_type = ArchiveFileTypeLoading; uint8_t* custom_icon_data = NULL; - if(archive_is_item_in_array(model, idx)) { + if(!model->list_loading && archive_is_item_in_array(model, idx)) { ArchiveFile_t* file = files_array_get( model->files, CLAMP(idx - model->array_offset, (int32_t)(array_size - 1), 0)); file_type = file->type; From f78da36c50e4c3cee8b139eac52a8f1d6e3713f1 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 14 Jul 2023 16:45:52 +0200 Subject: [PATCH 172/364] Hide browser cursor while loading --- applications/main/archive/views/archive_browser_view.c | 2 +- applications/services/gui/modules/file_browser.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/applications/main/archive/views/archive_browser_view.c b/applications/main/archive/views/archive_browser_view.c index 390e84c32..a335890c0 100644 --- a/applications/main/archive/views/archive_browser_view.c +++ b/applications/main/archive/views/archive_browser_view.c @@ -218,7 +218,7 @@ static void draw_list(Canvas* canvas, ArchiveBrowserViewModel* model) { size_t scroll_counter = model->scroll_counter; - if(model->item_idx == idx) { + if(!model->list_loading && model->item_idx == idx) { archive_draw_frame(canvas, i, scrollbar, model->move_fav); if(scroll_counter < SCROLL_DELAY) { scroll_counter = 0; diff --git a/applications/services/gui/modules/file_browser.c b/applications/services/gui/modules/file_browser.c index 8cb0eca2b..23bf1b76a 100644 --- a/applications/services/gui/modules/file_browser.c +++ b/applications/services/gui/modules/file_browser.c @@ -582,7 +582,7 @@ static void browser_draw_list(Canvas* canvas, FileBrowserModel* model) { } size_t scroll_counter = model->scroll_counter; - if(model->item_idx == idx) { + if(!model->list_loading && model->item_idx == idx) { browser_draw_frame(canvas, i, show_scrollbar); if(scroll_counter < SCROLL_DELAY) { scroll_counter = 0; From 6ecb6d93abee1b7693b32c6511b327e05a396c8d Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 14 Jul 2023 16:46:23 +0200 Subject: [PATCH 173/364] Fix browser input ignore while loading --- applications/main/archive/views/archive_browser_view.c | 4 ++-- applications/services/gui/modules/file_browser.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/applications/main/archive/views/archive_browser_view.c b/applications/main/archive/views/archive_browser_view.c index a335890c0..3cb0e88ca 100644 --- a/applications/main/archive/views/archive_browser_view.c +++ b/applications/main/archive/views/archive_browser_view.c @@ -360,8 +360,8 @@ static bool archive_view_input(InputEvent* event, void* context) { }, false); - if(is_loading) { - return false; + if(is_loading && event->key != InputKeyBack) { + return true; // Return without doing anything } if(in_menu) { if(event->type != InputTypeShort) { diff --git a/applications/services/gui/modules/file_browser.c b/applications/services/gui/modules/file_browser.c index 23bf1b76a..ca09d01b2 100644 --- a/applications/services/gui/modules/file_browser.c +++ b/applications/services/gui/modules/file_browser.c @@ -661,8 +661,8 @@ static bool file_browser_view_input_callback(InputEvent* event, void* context) { { is_loading = model->folder_loading || model->list_loading; }, false); - if(is_loading) { - return false; + if(is_loading && event->key != InputKeyBack) { + return true; // Return without doing anything } else if(event->key == InputKeyUp || event->key == InputKeyDown) { if(event->type == InputTypeShort || event->type == InputTypeRepeat) { with_view_model( From 52d4b3f905905beb5f3f5c4bea29dc4a6bc3903e Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 14 Jul 2023 16:47:29 +0200 Subject: [PATCH 174/364] Fix some missing browser screen updates --- applications/services/gui/modules/file_browser.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/applications/services/gui/modules/file_browser.c b/applications/services/gui/modules/file_browser.c index ca09d01b2..edbd76446 100644 --- a/applications/services/gui/modules/file_browser.c +++ b/applications/services/gui/modules/file_browser.c @@ -504,7 +504,7 @@ static void model->list_loading = false; } }, - false); + true); if(load_again) { file_browser_worker_load(browser->worker, load_offset, ITEM_LIST_LEN_MAX); } else { @@ -753,7 +753,7 @@ static bool file_browser_view_input_callback(InputEvent* event, void* context) { model->button_held_for_ticks += 1; } }, - false); + true); browser_update_offset(browser); consumed = true; } else if(event->type == InputTypeRelease) { From bb9ba09b6d9363d2069ccb7361a4078c8e214503 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Fri, 14 Jul 2023 17:53:32 +0300 Subject: [PATCH 175/364] update docs --- documentation/SubGHzRemotePlugin.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/SubGHzRemotePlugin.md b/documentation/SubGHzRemotePlugin.md index cbdc0995b..59d4e0a32 100644 --- a/documentation/SubGHzRemotePlugin.md +++ b/documentation/SubGHzRemotePlugin.md @@ -2,7 +2,7 @@ # UPDATE!!!!!! -## Now you can create and edit map files directly on flipper, go into Applications->Sub-GHz->Remote Maker +## Now you can create and edit map files directly on flipper, go into Sub-GHz Remote and click back button

From b8d740c6d84e3f6ce4c636b71370b3569d41656d Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 14 Jul 2023 17:18:30 +0200 Subject: [PATCH 176/364] Merge loader error message changes --- applications/services/loader/loader.c | 35 ++++++++++++++++----------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/applications/services/loader/loader.c b/applications/services/loader/loader.c index 1ff52f037..6a7e7bb1d 100644 --- a/applications/services/loader/loader.c +++ b/applications/services/loader/loader.c @@ -15,7 +15,20 @@ #define TAG "Loader" #define LOADER_MAGIC_THREAD_VALUE 0xDEADBEEF -// api + +// helpers + +static const char* loader_find_external_application_by_name(const char* app_name) { + for(size_t i = 0; i < FLIPPER_EXTERNAL_APPS_COUNT; i++) { + if(strcmp(FLIPPER_EXTERNAL_APPS[i].name, app_name) == 0) { + return FLIPPER_EXTERNAL_APPS[i].path; + } + } + + return NULL; +} + +// API LoaderStatus loader_start(Loader* loader, const char* name, const char* args, FuriString* error_message) { @@ -43,8 +56,12 @@ static void loader_show_gui_error(LoaderStatus status, FuriString* error_message dialog_message_set_buttons(message, NULL, NULL, NULL); furi_string_replace(error_message, ":", "\n"); + furi_string_replace(error_message, "/ext/apps/", ""); + furi_string_replace(error_message, ", ", "\n"); + furi_string_replace(error_message, ": ", "\n"); + dialog_message_set_text( - message, furi_string_get_cstr(error_message), 64, 32, AlignCenter, AlignCenter); + message, furi_string_get_cstr(error_message), 64, 35, AlignCenter, AlignCenter); dialog_message_show(dialogs, message); dialog_message_free(message); @@ -245,16 +262,6 @@ static const FlipperInternalApplication* loader_find_application_by_name(const c return application; } -static const char* loader_find_external_application_by_name(const char* app_name) { - for(size_t i = 0; i < FLIPPER_EXTERNAL_APPS_COUNT; i++) { - if(strcmp(FLIPPER_EXTERNAL_APPS[i].name, app_name) == 0) { - return FLIPPER_EXTERNAL_APPS[i].path; - } - } - - return NULL; -} - static void loader_start_app_thread(Loader* loader, FlipperInternalApplicationFlag flags) { // setup heap trace FuriHalRtcHeapTrackMode mode = furi_hal_rtc_get_heap_track_mode(); @@ -356,7 +363,7 @@ static LoaderStatus loader_start_external_app( } else if(preload_res != FlipperApplicationPreloadStatusSuccess) { const char* err_msg = flipper_application_preload_status_to_string(preload_res); status = loader_make_status_error( - LoaderStatusErrorInternal, error_message, "Preload failed %s: %s", path, err_msg); + LoaderStatusErrorInternal, error_message, "Preload failed, %s: %s", path, err_msg); break; } @@ -504,7 +511,7 @@ static LoaderStatus loader_do_start_by_name( } } - // check external apps + // check Faps { Storage* storage = furi_record_open(RECORD_STORAGE); if(storage_file_exists(storage, name)) { From 6afbd97d810c3351aa06deab478d9ab42fa5544b Mon Sep 17 00:00:00 2001 From: amec0e <88857687+amec0e@users.noreply.github.com> Date: Fri, 14 Jul 2023 16:23:20 +0100 Subject: [PATCH 177/364] Update ac.ir Renamed a few entries, added compatibility comment and added new Shivaki captures to ensure swing was and is off --- assets/resources/infrared/assets/ac.ir | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/assets/resources/infrared/assets/ac.ir b/assets/resources/infrared/assets/ac.ir index 0e4ced07d..df312151d 100644 --- a/assets/resources/infrared/assets/ac.ir +++ b/assets/resources/infrared/assets/ac.ir @@ -1,7 +1,7 @@ Filetype: IR library file Version: 1 -# Last Updated 13th Jul, 2023 -# Last Checked 13th Jul, 2023 +# Last Updated 14th Jul, 2023 +# Last Checked 14th Jul, 2023 # # Model: Electrolux EACM-16 HP/N3 name: Off @@ -152,6 +152,7 @@ duty_cycle: 0.330000 data: 3013 1709 463 1085 462 1084 463 387 461 355 469 355 444 1084 463 387 461 378 436 1084 462 1084 487 355 469 1059 463 387 460 355 444 1083 463 1098 464 386 437 1083 463 1083 489 361 463 360 463 1082 464 361 462 376 462 1085 461 363 460 364 459 364 459 365 458 365 459 364 459 380 459 365 458 365 458 365 458 365 458 365 458 365 458 365 459 380 458 365 459 365 458 365 459 365 458 365 458 1089 458 365 458 380 459 1088 459 365 458 365 458 365 458 365 459 365 458 365 458 381 458 365 458 365 458 365 458 1089 458 365 458 365 458 365 458 381 458 365 458 365 458 365 458 365 458 365 458 365 458 365 458 381 457 366 457 366 457 366 457 366 457 366 458 365 458 366 457 381 458 366 458 366 457 366 457 366 457 366 457 366 457 366 457 381 458 366 457 366 457 366 457 366 457 366 457 366 457 366 457 382 457 366 457 366 457 366 457 366 457 366 457 367 457 366 457 382 457 367 456 1090 457 1090 456 1090 457 1090 457 1090 456 367 457 372 457 # # Model: Olimpia Splendid OS-SEAMH09EI +# Also compatible with Timberk, Royal Clima, Ballu name: Dh type: raw frequency: 38000 @@ -337,7 +338,7 @@ frequency: 38000 duty_cycle: 0.330000 data: 503 365 500 364 501 366 499 365 500 364 502 25049 3535 1660 504 1228 503 390 474 391 473 393 471 1261 469 397 468 397 469 397 469 397 469 1264 468 398 468 1264 468 1264 468 398 468 1265 467 1265 467 1265 467 1265 467 1265 467 399 467 399 467 1266 466 399 467 400 466 400 466 400 466 423 443 423 443 401 465 423 442 424 442 424 442 1290 442 424 442 1290 442 424 442 424 441 424 442 1290 442 1291 441 425 441 424 442 425 441 425 441 1291 441 425 440 425 441 425 441 425 441 425 441 425 440 425 441 425 441 425 441 425 441 426 440 1292 440 1292 440 1292 440 426 440 426 440 1292 440 1293 439 1293 439 35480 3503 1696 467 1264 468 398 468 398 467 398 468 1265 467 398 467 399 467 399 466 399 467 1265 467 399 467 1266 466 1267 465 400 466 1290 442 1290 442 1290 442 1290 442 1290 442 424 442 424 442 1290 442 424 441 424 442 424 442 424 442 424 442 424 441 424 442 425 441 424 442 425 441 425 441 1291 441 425 441 425 441 425 441 425 440 1292 440 425 441 425 440 426 440 426 440 426 440 426 440 426 440 426 440 426 440 426 439 427 439 426 440 426 440 1293 439 427 439 427 439 427 438 427 439 428 438 1294 438 428 437 428 438 1295 437 1319 413 453 413 35480 3503 1696 468 1265 467 398 468 398 468 398 468 1265 467 398 468 399 466 399 467 399 467 1266 466 399 466 1267 465 1290 442 401 465 1290 442 1290 442 1290 442 1290 442 1290 442 424 442 424 441 1291 441 424 442 424 442 424 442 424 442 424 441 424 442 425 441 424 442 424 441 425 441 425 441 425 440 425 441 425 441 425 441 425 441 425 441 425 441 1292 440 426 440 426 440 1292 440 426 440 426 440 1293 439 426 440 426 440 1293 439 1293 439 1293 439 427 439 1294 438 427 438 427 439 427 438 428 438 428 438 428 438 428 438 453 413 429 437 453 413 1319 413 1320 412 1320 412 1320 412 454 412 1320 412 454 412 1321 411 1321 411 1321 411 1321 411 1321 411 455 410 455 411 455 411 455 410 456 410 456 410 456 410 481 384 481 385 482 383 482 383 483 358 507 359 1374 358 1374 358 508 358 508 358 508 358 509 357 509 357 535 331 535 331 535 330 535 330 536 330 1403 329 1403 329 563 302 564 301 564 302 564 301 565 301 591 274 619 246 593 273 620 245 620 245 621 245 673 189 # -# Model: Mitsubishi SRK63HE. +# Model: Mitsubishi Heavy Industries SRK63HE name: Off type: raw frequency: 38000 @@ -585,44 +586,43 @@ frequency: 38000 duty_cycle: 0.330000 data: 8780 4015 578 1498 553 471 526 494 525 494 524 1520 525 495 523 498 522 499 521 502 522 497 522 499 522 500 521 1519 522 500 521 499 521 1520 546 1496 521 503 523 494 522 500 521 499 522 1520 521 499 522 499 522 502 522 1518 521 501 521 1522 522 # -# Model: Shivaki General +# Model: Shivaki SSA18002 name: Off type: raw frequency: 38000 duty_cycle: 0.330000 -data: 3163 1577 577 1009 579 1008 579 342 485 342 485 342 485 1033 579 339 462 342 485 1033 553 1034 552 342 485 1035 550 342 484 343 483 1039 547 1040 546 341 486 1041 546 1041 545 341 486 341 486 1041 545 341 486 340 487 1041 545 341 486 341 486 340 487 340 488 340 487 340 487 341 486 341 486 340 487 340 487 341 486 340 487 341 487 341 486 341 486 341 486 340 487 341 486 340 487 340 487 1041 545 341 486 341 486 1042 545 1041 545 341 486 340 487 340 487 340 487 340 487 341 486 1042 544 341 486 1042 544 341 486 341 486 341 486 341 486 341 486 341 486 341 486 341 486 1042 544 1042 544 1042 544 1042 544 340 488 341 486 341 486 341 486 341 486 341 486 341 486 341 486 341 486 340 487 340 487 341 487 341 486 341 486 341 487 341 486 341 486 341 486 341 486 341 486 341 486 341 486 341 486 341 486 340 487 341 486 341 487 341 486 341 487 341 486 341 486 340 487 1043 543 1043 544 341 486 1043 543 341 486 1043 543 1044 542 340 487 340 488 -# +data: 3197 1545 581 1033 553 1006 606 338 463 342 485 339 488 1033 553 342 485 342 485 1034 551 1035 550 342 485 1037 548 342 485 340 487 1040 546 1040 546 340 487 1040 546 1040 546 340 487 340 488 1040 546 340 487 340 488 1040 546 340 487 340 487 340 487 342 485 340 487 340 487 340 487 342 485 342 485 342 485 340 487 342 485 340 487 340 487 342 485 342 485 342 485 342 485 340 488 342 485 1041 545 340 487 340 487 1041 545 1041 545 340 488 340 487 340 487 340 487 340 488 342 485 342 485 340 487 340 488 1041 545 340 487 340 487 342 486 342 485 340 487 340 487 340 487 340 487 342 485 341 486 1041 545 340 487 340 487 340 487 340 487 340 488 342 485 340 487 342 485 342 485 340 487 342 485 340 487 340 488 342 485 343 484 340 488 340 487 340 487 340 487 340 487 342 485 340 487 340 487 341 486 342 485 340 488 342 485 342 485 342 485 340 487 340 487 340 488 1042 544 340 487 340 487 340 487 340 487 340 487 340 487 340 488 342 485 name: Cool_hi type: raw frequency: 38000 duty_cycle: 0.330000 -data: 3193 1549 579 1007 580 1007 579 342 485 342 485 342 511 1007 579 338 463 342 485 1034 552 1034 551 342 484 1035 550 342 484 340 487 1039 547 1040 546 341 487 1041 545 1041 545 340 487 341 487 1041 545 341 486 340 487 1041 545 341 486 341 486 340 487 340 487 341 486 340 487 341 486 341 486 340 487 341 486 340 487 340 488 341 486 340 487 340 487 340 487 341 487 1041 545 340 487 340 487 1041 545 340 488 341 486 1042 544 1042 544 341 487 341 486 341 486 340 487 341 487 341 486 1042 544 1042 544 1042 544 1042 545 341 486 341 487 341 486 341 486 1042 545 341 486 1042 544 1042 544 1042 544 1042 544 341 487 341 486 340 487 341 486 341 486 340 487 341 486 340 487 341 486 341 486 341 486 341 486 341 487 341 486 341 486 341 486 340 488 341 486 341 486 341 486 341 486 341 486 340 488 341 486 341 486 341 487 341 486 341 486 341 486 1043 544 341 486 341 486 341 487 1043 543 341 486 341 486 341 486 341 486 1044 542 340 487 341 486 341 486 +data: 3139 1604 522 1063 523 1063 523 340 487 342 485 340 487 1063 523 342 485 340 487 1063 523 1062 523 342 484 1063 522 342 485 342 509 1041 520 1066 520 342 485 1067 519 1067 519 342 485 342 485 1067 519 342 485 342 485 1067 519 342 485 342 485 342 485 342 485 342 485 342 485 342 485 342 485 343 484 342 485 343 484 342 485 342 485 342 485 342 485 342 485 342 485 1068 518 342 485 342 485 1068 518 342 485 342 485 1068 518 1068 518 342 485 342 485 342 485 342 485 342 485 342 485 1068 518 1069 517 1068 518 1069 517 342 485 342 485 342 485 342 485 343 484 343 484 343 484 342 485 343 484 343 484 343 484 342 485 342 485 342 485 342 485 343 484 342 485 343 484 342 485 343 484 343 484 343 484 343 484 343 484 343 484 343 484 343 484 343 484 342 485 343 484 342 485 343 484 343 484 342 485 342 485 343 484 342 485 343 484 342 485 342 485 342 485 342 485 343 484 1093 493 1093 493 1094 492 342 485 1093 493 342 485 342 485 1094 492 1093 493 # name: Heat_hi type: raw frequency: 38000 duty_cycle: 0.330000 -data: 3169 1547 579 1006 580 1006 580 342 485 342 485 342 484 1033 553 342 485 342 485 1033 552 1034 551 342 484 1035 550 342 484 342 485 1039 546 1040 546 342 485 1040 546 1040 546 342 485 343 484 1040 546 342 485 342 485 1040 546 342 486 342 485 343 484 342 485 343 484 342 485 342 485 342 485 343 484 342 485 342 485 342 485 343 484 342 485 342 485 342 485 342 485 1041 545 342 485 343 484 1041 545 342 485 343 484 1041 545 342 485 343 484 342 485 342 485 342 485 342 485 342 485 1041 545 342 485 343 484 342 485 342 485 342 485 343 484 342 485 1041 545 342 485 1041 545 1041 545 1041 545 1041 545 342 485 342 485 343 484 342 485 343 484 342 485 342 485 342 485 342 485 342 485 342 485 342 485 343 484 342 485 342 485 342 485 342 485 342 485 342 485 342 485 342 485 343 484 342 485 342 485 342 485 342 485 342 485 343 484 342 485 1042 544 343 484 343 484 342 485 1042 544 341 486 342 485 342 485 343 484 342 485 342 485 342 485 340 487 +data: 3192 1547 579 1009 577 1008 578 338 489 338 488 338 489 1010 576 338 489 338 489 1035 550 1036 549 339 488 1038 547 339 488 339 488 1040 546 1040 546 339 488 1040 546 1040 546 339 488 339 489 1040 546 339 488 339 489 1040 546 339 488 339 488 339 489 339 488 339 489 339 488 339 488 339 488 339 488 339 488 339 488 339 488 339 488 339 488 339 488 339 488 339 488 1041 545 339 489 339 488 1041 545 339 488 339 488 1041 545 339 488 339 489 339 488 339 488 339 488 339 489 339 488 1041 545 339 488 339 488 339 488 339 488 339 488 339 488 339 488 339 488 339 488 339 488 339 488 339 488 339 489 339 488 339 488 339 488 339 488 339 488 339 488 339 488 339 488 339 489 339 488 339 488 339 488 339 488 339 488 339 488 339 488 339 488 339 488 339 488 339 488 339 488 339 489 339 488 339 488 339 488 339 488 340 487 339 488 339 488 339 489 339 488 339 488 339 488 1042 544 1042 545 1042 544 339 488 1042 544 1042 544 1042 544 339 488 1042 544 # name: Heat_lo type: raw frequency: 38000 duty_cycle: 0.330000 -data: 3162 1605 523 1064 522 1064 523 343 484 343 484 343 484 1063 523 343 484 343 484 1063 523 1063 523 343 484 1063 523 343 484 343 484 1065 521 1066 520 343 484 1068 518 1068 518 343 484 343 484 1068 518 343 484 343 484 1068 518 343 484 343 484 343 484 343 484 343 484 342 485 343 484 343 485 343 484 343 484 343 484 343 484 343 484 343 484 343 484 343 484 343 484 1068 518 343 484 343 484 1069 517 343 484 343 485 1069 517 343 484 343 484 343 485 343 484 343 484 343 484 343 485 343 484 343 484 343 484 1070 516 344 483 344 483 344 484 343 484 1070 517 344 483 1094 493 1070 517 1093 493 1094 492 344 483 343 484 343 484 343 484 343 484 343 484 343 485 343 484 343 484 343 484 344 483 343 484 344 483 343 484 344 483 343 484 344 483 343 484 343 484 344 483 344 483 343 484 343 484 344 483 343 484 343 484 343 484 344 483 343 484 1094 492 343 484 344 484 343 484 1094 492 1094 492 1094 492 1094 492 343 484 344 483 343 484 343 484 344 483 +data: 3194 1546 580 1006 580 1005 581 341 485 341 511 337 465 1032 554 341 486 339 488 1033 552 1033 552 341 485 1035 550 339 487 339 487 1039 546 1039 547 339 488 1040 546 1040 546 339 488 339 488 1040 546 339 488 339 488 1040 546 339 488 340 487 339 488 339 488 339 488 339 488 339 488 340 487 339 488 339 488 339 488 339 488 340 487 339 488 339 488 339 488 339 488 1040 546 339 488 339 488 1040 546 339 488 340 487 1040 546 339 488 340 487 339 488 339 488 340 487 339 488 340 487 340 487 340 487 339 488 1041 545 340 487 340 487 339 488 340 487 340 487 339 488 340 487 340 487 340 487 340 487 339 488 340 487 340 487 339 488 339 488 339 488 339 488 339 488 340 487 340 487 339 488 339 488 340 487 339 488 340 487 340 487 339 488 340 487 340 487 340 487 340 487 340 487 340 487 339 488 339 488 340 487 339 488 340 487 340 487 339 488 339 488 339 488 340 487 1042 544 340 487 1042 544 340 487 339 488 340 487 340 487 1042 544 1042 544 # name: Cool_lo type: raw frequency: 38000 duty_cycle: 0.330000 -data: 3138 1604 523 1063 523 1063 523 343 484 343 484 343 484 1063 523 343 484 343 484 1062 523 1063 523 343 484 1063 522 343 484 343 484 1065 521 1066 519 343 484 1067 519 1067 519 343 484 343 484 1067 519 343 484 343 484 1067 519 343 484 343 484 343 484 343 484 343 484 343 484 343 484 342 485 343 484 343 484 343 484 343 484 343 484 343 484 343 484 343 484 343 484 1068 518 343 484 343 484 1068 518 343 484 343 484 1068 518 1068 518 343 484 343 484 343 484 343 484 343 484 343 484 343 484 343 484 343 484 1067 519 343 484 343 484 343 484 343 484 1067 519 343 484 1067 519 1067 519 1068 518 1067 519 343 484 343 484 343 484 343 484 343 484 343 484 343 484 343 484 343 484 343 484 343 484 343 484 343 484 343 484 343 484 343 484 343 484 343 484 343 484 343 484 343 484 343 484 343 484 343 484 343 484 343 484 343 484 343 484 343 484 1068 518 343 484 343 484 343 484 1068 518 1068 518 343 484 343 484 1068 518 343 484 343 484 343 484 343 484 +data: 3197 1547 580 1005 581 1033 553 341 512 338 463 341 486 1033 553 339 488 339 488 1033 552 1034 552 341 485 1036 549 339 488 339 488 1039 547 1040 547 340 487 1040 546 1040 546 340 487 340 487 1040 546 340 488 340 487 1040 546 340 487 340 487 340 487 340 487 340 487 340 487 339 488 339 488 340 487 340 488 339 488 340 487 340 487 339 489 340 487 340 487 339 488 1041 545 339 489 339 488 1040 546 339 488 339 488 1041 545 1041 546 340 487 340 487 340 487 340 488 340 487 340 487 340 487 340 487 340 487 1041 545 340 487 340 487 339 488 339 488 339 489 339 488 340 487 340 487 340 487 340 487 340 487 340 487 339 488 340 487 340 488 340 487 340 487 340 487 340 487 340 487 340 488 340 487 340 487 340 487 340 487 340 487 340 487 340 487 339 488 340 488 339 488 340 487 340 487 339 488 340 487 340 487 340 487 340 488 340 487 340 487 340 487 339 488 340 487 1042 544 340 487 340 488 1042 544 340 487 340 488 340 487 1043 543 1043 543 # name: Dh type: raw frequency: 38000 duty_cycle: 0.330000 -data: 3165 1578 549 1037 549 1037 550 342 485 343 484 343 513 1034 551 340 487 340 487 1035 551 1035 550 340 486 1037 548 341 485 341 485 1041 545 1041 545 341 486 1042 544 1042 544 340 487 341 486 1042 544 341 486 340 488 1042 544 341 487 341 486 341 486 341 461 343 509 341 487 341 461 343 484 343 510 341 486 341 461 343 484 343 509 341 462 343 509 341 462 343 484 1068 518 343 510 341 461 1068 518 343 484 343 484 343 484 1068 518 343 484 343 484 343 484 343 484 342 485 343 485 1068 518 1068 518 1068 518 343 485 343 484 343 485 343 484 343 484 343 484 343 484 343 484 1068 518 1068 518 1068 518 343 484 343 484 343 485 343 484 343 484 343 484 343 484 343 484 343 484 342 485 343 485 343 484 343 484 343 484 343 484 343 484 343 484 343 485 343 484 343 484 343 484 343 484 343 484 343 485 343 484 343 484 343 484 343 484 343 484 1070 516 343 484 343 484 343 484 1071 515 343 484 1094 492 343 484 343 484 343 485 344 483 343 484 343 484 +data: 3196 1545 581 1005 580 1008 604 337 464 338 488 338 489 1033 553 339 488 338 489 1033 552 1034 551 339 487 1036 549 339 488 339 488 1039 547 1039 547 340 487 1040 546 1040 546 339 488 339 488 1040 546 340 487 339 488 1040 546 339 488 339 488 339 488 339 488 339 488 339 488 340 487 339 488 339 488 339 488 339 488 339 488 339 488 339 488 339 488 340 487 339 488 1040 546 339 488 339 488 1040 546 339 488 339 488 339 488 1040 546 339 488 339 488 339 488 339 488 339 488 339 488 1040 546 1041 545 1041 545 340 487 339 488 339 488 339 488 339 488 339 488 339 488 339 488 339 488 339 488 339 488 339 488 339 488 340 487 339 488 339 488 339 488 339 488 339 488 339 488 339 488 339 488 339 488 339 488 340 487 339 488 339 488 339 488 339 488 339 488 339 488 339 488 339 488 339 488 339 488 339 488 339 488 339 488 339 488 340 487 339 488 339 488 340 487 339 488 1042 544 339 488 1042 544 339 488 339 488 339 488 339 488 1042 544 1042 544 # -# Model: Mitsubishi MSZ-AP25VGK +# Model: Mitsubishi Electric MSZ-AP25VGK name: Off type: raw frequency: 38000 From afba0aad712d1abc93cec5b059da92d098e9af60 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 14 Jul 2023 17:50:15 +0200 Subject: [PATCH 178/364] Update multi fuzzer and subghz remote --- applications/external/multi_fuzzer/LICENSE | 21 + .../external/multi_fuzzer/application.fam | 10 + applications/external/multi_fuzzer/fuzzer.c | 9 + applications/external/multi_fuzzer/fuzzer_i.h | 4 + .../helpers/fuzzer_custom_event.h | 16 +- .../multi_fuzzer/helpers/fuzzer_types.h | 5 +- .../multi_fuzzer/lib/worker/fake_worker.c | 391 +++++----- .../multi_fuzzer/lib/worker/fake_worker.h | 19 +- .../lib/worker/helpers/hardware_worker.c | 198 ++++++ .../lib/worker/helpers/hardware_worker.h | 48 ++ .../multi_fuzzer/lib/worker/protocol.c | 140 ++-- .../multi_fuzzer/lib/worker/protocol.h | 23 +- .../multi_fuzzer/lib/worker/protocol_i.h | 8 + .../multi_fuzzer/scenes/fuzzer_scene_attack.c | 100 +-- .../multi_fuzzer/scenes/fuzzer_scene_config.h | 4 +- .../scenes/fuzzer_scene_field_editor.c | 1 + .../multi_fuzzer/scenes/fuzzer_scene_main.c | 31 +- .../scenes/fuzzer_scene_save_name.c | 67 ++ .../scenes/fuzzer_scene_save_success.c | 44 ++ .../external/multi_fuzzer/views/attack.c | 403 +++++++---- .../external/multi_fuzzer/views/attack.h | 8 +- .../external/subghz_remote/application.fam | 1 + .../helpers/subrem_custom_event.h | 31 + .../subghz_remote/helpers/subrem_types.h | 4 + applications/external/subghz_remote/icon.png | Bin 5000 -> 0 bytes .../scenes/subrem_scene_config.h | 10 +- .../scenes/subrem_scene_edit_label.c | 0 .../scenes/subrem_scene_edit_menu.c | 0 .../scenes/subrem_scene_edit_preview.c | 0 .../scenes/subrem_scene_edit_submenu.c | 0 .../scenes/subrem_scene_enter_new_name.c | 0 .../scenes/subrem_scene_open_map_file.c | 30 +- .../scenes/subrem_scene_open_sub_file.c | 0 .../scenes/subrem_scene_remote.c | 3 + .../subghz_remote/scenes/subrem_scene_start.c | 38 +- .../subghz_remote/subghz_remote_app.c | 109 ++- .../subghz_remote/subghz_remote_app_i.c | 54 +- .../subghz_remote/subghz_remote_app_i.h | 19 +- .../views/edit_menu.c | 0 .../views/edit_menu.h | 0 .../external/subghz_remote/views/remote.c | 13 + .../external/subghz_remote/views/remote.h | 4 +- .../application.fam | 15 - .../helpers/subrem_custom_event.h | 50 -- .../helpers/subrem_presets.c | 181 ----- .../helpers/subrem_presets.h | 39 - .../helpers/subrem_types.h | 48 -- .../helpers/txrx/subghz_txrx.c | 670 ------------------ .../helpers/txrx/subghz_txrx.h | 375 ---------- .../helpers/txrx/subghz_txrx_i.h | 31 - .../scenes/subrem_scene.c | 30 - .../scenes/subrem_scene.h | 29 - .../scenes/subrem_scene_config.h | 8 - .../scenes/subrem_scene_open_map_file.c | 29 - .../scenes/subrem_scene_start.c | 73 -- .../subghz_remote_app.c | 184 ----- .../subghz_remote_app_i.c | 270 ------- .../subghz_remote_app_i.h | 63 -- .../subrem_10px.png | Bin 5000 -> 0 bytes .../subghz_remote_configurator/views/remote.c | 294 -------- .../subghz_remote_configurator/views/remote.h | 36 - 61 files changed, 1302 insertions(+), 2989 deletions(-) create mode 100644 applications/external/multi_fuzzer/LICENSE create mode 100644 applications/external/multi_fuzzer/lib/worker/helpers/hardware_worker.c create mode 100644 applications/external/multi_fuzzer/lib/worker/helpers/hardware_worker.h create mode 100644 applications/external/multi_fuzzer/scenes/fuzzer_scene_save_name.c create mode 100644 applications/external/multi_fuzzer/scenes/fuzzer_scene_save_success.c delete mode 100644 applications/external/subghz_remote/icon.png rename applications/external/{subghz_remote_configurator => subghz_remote}/scenes/subrem_scene_edit_label.c (100%) rename applications/external/{subghz_remote_configurator => subghz_remote}/scenes/subrem_scene_edit_menu.c (100%) rename applications/external/{subghz_remote_configurator => subghz_remote}/scenes/subrem_scene_edit_preview.c (100%) rename applications/external/{subghz_remote_configurator => subghz_remote}/scenes/subrem_scene_edit_submenu.c (100%) rename applications/external/{subghz_remote_configurator => subghz_remote}/scenes/subrem_scene_enter_new_name.c (100%) rename applications/external/{subghz_remote_configurator => subghz_remote}/scenes/subrem_scene_open_sub_file.c (100%) rename applications/external/{subghz_remote_configurator => subghz_remote}/views/edit_menu.c (100%) rename applications/external/{subghz_remote_configurator => subghz_remote}/views/edit_menu.h (100%) delete mode 100644 applications/external/subghz_remote_configurator/application.fam delete mode 100644 applications/external/subghz_remote_configurator/helpers/subrem_custom_event.h delete mode 100644 applications/external/subghz_remote_configurator/helpers/subrem_presets.c delete mode 100644 applications/external/subghz_remote_configurator/helpers/subrem_presets.h delete mode 100644 applications/external/subghz_remote_configurator/helpers/subrem_types.h delete mode 100644 applications/external/subghz_remote_configurator/helpers/txrx/subghz_txrx.c delete mode 100644 applications/external/subghz_remote_configurator/helpers/txrx/subghz_txrx.h delete mode 100644 applications/external/subghz_remote_configurator/helpers/txrx/subghz_txrx_i.h delete mode 100644 applications/external/subghz_remote_configurator/scenes/subrem_scene.c delete mode 100644 applications/external/subghz_remote_configurator/scenes/subrem_scene.h delete mode 100644 applications/external/subghz_remote_configurator/scenes/subrem_scene_config.h delete mode 100644 applications/external/subghz_remote_configurator/scenes/subrem_scene_open_map_file.c delete mode 100644 applications/external/subghz_remote_configurator/scenes/subrem_scene_start.c delete mode 100644 applications/external/subghz_remote_configurator/subghz_remote_app.c delete mode 100644 applications/external/subghz_remote_configurator/subghz_remote_app_i.c delete mode 100644 applications/external/subghz_remote_configurator/subghz_remote_app_i.h delete mode 100644 applications/external/subghz_remote_configurator/subrem_10px.png delete mode 100644 applications/external/subghz_remote_configurator/views/remote.c delete mode 100644 applications/external/subghz_remote_configurator/views/remote.h diff --git a/applications/external/multi_fuzzer/LICENSE b/applications/external/multi_fuzzer/LICENSE new file mode 100644 index 000000000..c24a18ce0 --- /dev/null +++ b/applications/external/multi_fuzzer/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 @gid9798 @xMasterX @G4N4P4T1 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/applications/external/multi_fuzzer/application.fam b/applications/external/multi_fuzzer/application.fam index 4d20930ba..208a8a9ca 100644 --- a/applications/external/multi_fuzzer/application.fam +++ b/applications/external/multi_fuzzer/application.fam @@ -11,6 +11,11 @@ App( "notification", ], stack_size=2 * 1024, + fap_author="gid9798 xMasterX", + fap_weburl="https://github.com/DarkFlippers/Multi_Fuzzer", + fap_version="1.1", + targets=["f7"], + fap_description="Fuzzer for ibutton readers", fap_icon="ibutt_10px.png", fap_category="iButton", fap_private_libs=[ @@ -36,6 +41,11 @@ App( "notification", ], stack_size=2 * 1024, + fap_author="gid9798 xMasterX", + fap_weburl="https://github.com/DarkFlippers/Multi_Fuzzer", + fap_version="1.1", + targets=["f7"], + fap_description="Fuzzer for lfrfid readers", fap_icon="icons/rfid_10px.png", fap_category="RFID", fap_private_libs=[ diff --git a/applications/external/multi_fuzzer/fuzzer.c b/applications/external/multi_fuzzer/fuzzer.c index 01a1d60df..bc9646873 100644 --- a/applications/external/multi_fuzzer/fuzzer.c +++ b/applications/external/multi_fuzzer/fuzzer.c @@ -49,6 +49,11 @@ PacsFuzzerApp* fuzzer_app_alloc() { app->popup = popup_alloc(); view_dispatcher_add_view(app->view_dispatcher, FuzzerViewIDPopup, popup_get_view(app->popup)); + // TextInput + app->text_input = text_input_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, FuzzerViewIDTextInput, text_input_get_view(app->text_input)); + // Main view app->main_view = fuzzer_view_main_alloc(); view_dispatcher_add_view( @@ -103,6 +108,10 @@ void fuzzer_app_free(PacsFuzzerApp* app) { view_dispatcher_remove_view(app->view_dispatcher, FuzzerViewIDPopup); popup_free(app->popup); + // TextInput + view_dispatcher_remove_view(app->view_dispatcher, FuzzerViewIDTextInput); + text_input_free(app->text_input); + scene_manager_free(app->scene_manager); view_dispatcher_free(app->view_dispatcher); diff --git a/applications/external/multi_fuzzer/fuzzer_i.h b/applications/external/multi_fuzzer/fuzzer_i.h index c8308adbd..46d6df13f 100644 --- a/applications/external/multi_fuzzer/fuzzer_i.h +++ b/applications/external/multi_fuzzer/fuzzer_i.h @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -24,6 +25,7 @@ #include #define FUZZ_TIME_DELAY_MAX (80) +#define KEY_NAME_SIZE 22 typedef struct { const char* custom_dict_extension; @@ -42,11 +44,13 @@ typedef struct { Popup* popup; DialogsApp* dialogs; + TextInput* text_input; FuzzerViewMain* main_view; FuzzerViewAttack* attack_view; FuzzerViewFieldEditor* field_editor_view; FuriString* file_path; + char key_name[KEY_NAME_SIZE + 1]; FuzzerState fuzzer_state; FuzzerConsts* fuzzer_const; diff --git a/applications/external/multi_fuzzer/helpers/fuzzer_custom_event.h b/applications/external/multi_fuzzer/helpers/fuzzer_custom_event.h index 321187722..5824e3c01 100644 --- a/applications/external/multi_fuzzer/helpers/fuzzer_custom_event.h +++ b/applications/external/multi_fuzzer/helpers/fuzzer_custom_event.h @@ -7,11 +7,21 @@ typedef enum { FuzzerCustomEventViewMainOk, FuzzerCustomEventViewMainPopupErr, - FuzzerCustomEventViewAttackBack, - FuzzerCustomEventViewAttackOk, - // FuzzerCustomEventViewAttackTick, // now not use FuzzerCustomEventViewAttackEnd, + FuzzerCustomEventViewAttackExit, + FuzzerCustomEventViewAttackRunAttack, + FuzzerCustomEventViewAttackPause, + FuzzerCustomEventViewAttackIdle, // Setup + FuzzerCustomEventViewAttackEmulateCurrent, + FuzzerCustomEventViewAttackSave, + FuzzerCustomEventViewAttackNextUid, + FuzzerCustomEventViewAttackPrevUid, + FuzzerCustomEventViewFieldEditorBack, FuzzerCustomEventViewFieldEditorOk, + + FuzzerCustomEventTextEditResult, + + FuzzerCustomEventPopupClosed, } FuzzerCustomEvent; \ No newline at end of file diff --git a/applications/external/multi_fuzzer/helpers/fuzzer_types.h b/applications/external/multi_fuzzer/helpers/fuzzer_types.h index bb608a5f1..a809cda01 100644 --- a/applications/external/multi_fuzzer/helpers/fuzzer_types.h +++ b/applications/external/multi_fuzzer/helpers/fuzzer_types.h @@ -10,7 +10,9 @@ typedef struct { typedef enum { FuzzerAttackStateOff = 0, FuzzerAttackStateIdle, - FuzzerAttackStateRunning, + FuzzerAttackStateAttacking, + FuzzerAttackStateEmulating, + FuzzerAttackStatePause, FuzzerAttackStateEnd, } FuzzerAttackState; @@ -23,6 +25,7 @@ typedef enum { typedef enum { FuzzerViewIDPopup, + FuzzerViewIDTextInput, FuzzerViewIDMain, FuzzerViewIDAttack, diff --git a/applications/external/multi_fuzzer/lib/worker/fake_worker.c b/applications/external/multi_fuzzer/lib/worker/fake_worker.c index 07b0479b4..d3d00d2db 100644 --- a/applications/external/multi_fuzzer/lib/worker/fake_worker.c +++ b/applications/external/multi_fuzzer/lib/worker/fake_worker.c @@ -1,4 +1,5 @@ #include "fake_worker.h" +#include "helpers/hardware_worker.h" #include "protocol_i.h" #include @@ -8,47 +9,27 @@ #include #define TAG "Fuzzer worker" +#define TOTAL_PROTOCOL_COUNT fuzzer_proto_get_count_of_protocols() +#define PROTOCOL_KEY_FOLDER EXT_PATH(PROTOCOL_KEY_FOLDER_NAME) -#if defined(RFID_125_PROTOCOL) - -#include -#include -#include - -#else - -#include -#include - -#endif - -#include +typedef uint8_t FuzzerWorkerPayload[MAX_PAYLOAD_SIZE]; struct FuzzerWorker { -#if defined(RFID_125_PROTOCOL) - LFRFIDWorker* proto_worker; - ProtocolId protocol_id; - ProtocolDict* protocols_items; -#else - iButtonWorker* proto_worker; - iButtonProtocolId protocol_id; // TODO - iButtonProtocols* protocols_items; - iButtonKey* key; -#endif + HardwareWorker* hw_worker; const FuzzerProtocol* protocol; + HwProtocolID* suported_proto; + + FuzzerWorkerPayload payload; + FuzzerWorkerAttackType attack_type; - uint16_t timer_idle_time_ms; - uint16_t timer_emu_time_ms; - - uint8_t payload[MAX_PAYLOAD_SIZE]; - Stream* uids_stream; uint16_t index; - uint8_t chusen_byte; + Stream* uids_stream; - bool treead_running; bool in_emu_phase; FuriTimer* timer; + uint16_t timer_idle_time_ms; + uint16_t timer_emu_time_ms; FuzzerWorkerUidChagedCallback tick_callback; void* tick_context; @@ -57,6 +38,85 @@ struct FuzzerWorker { void* end_context; }; +static bool fuzzer_worker_set_protocol(FuzzerWorker* instance, FuzzerProtocolsID protocol_index) { + if(!(protocol_index < TOTAL_PROTOCOL_COUNT)) { + return false; + } + + instance->protocol = &fuzzer_proto_items[protocol_index]; + return hardware_worker_set_protocol_id_by_name( + instance->hw_worker, fuzzer_proto_items[protocol_index].name); +} + +static FuzzerProtocolsID + fuzzer_worker_is_protocol_valid(FuzzerWorker* instance, HwProtocolID protocol_id) { + for(FuzzerProtocolsID i = 0; i < TOTAL_PROTOCOL_COUNT; i++) { + if(protocol_id == instance->suported_proto[i]) { + return i; + } + } + return TOTAL_PROTOCOL_COUNT; +} + +FuzzerWorkerLoadKeyState fuzzer_worker_load_key_from_file( + FuzzerWorker* instance, + FuzzerProtocolsID* protocol_index, + const char* filename) { + furi_assert(instance); + + FuzzerWorkerLoadKeyState res = FuzzerWorkerLoadKeyStateUnsuportedProto; + if(!hardware_worker_load_key_from_file(instance->hw_worker, filename)) { + FURI_LOG_E(TAG, "Load key file: cant load file"); + res = FuzzerWorkerLoadKeyStateBadFile; + } else { + FuzzerProtocolsID loaded_id = fuzzer_worker_is_protocol_valid( + instance, hardware_worker_get_protocol_id(instance->hw_worker)); + + if(!fuzzer_worker_set_protocol(instance, loaded_id)) { + FURI_LOG_E(TAG, "Load key file: Unsuported protocol"); + res = FuzzerWorkerLoadKeyStateUnsuportedProto; + } else { + if(*protocol_index != loaded_id) { + res = FuzzerWorkerLoadKeyStateDifferentProto; + } else { + res = FuzzerWorkerLoadKeyStateOk; + } + *protocol_index = loaded_id; + + hardware_worker_get_protocol_data( + instance->hw_worker, &instance->payload[0], MAX_PAYLOAD_SIZE); + } + } + + return res; +} + +static bool fuzer_worker_make_key_folder() { + Storage* storage = furi_record_open(RECORD_STORAGE); + + const bool res = storage_simply_mkdir(storage, PROTOCOL_KEY_FOLDER); + + furi_record_close(RECORD_STORAGE); + + return res; +} + +bool fuzzer_worker_save_key(FuzzerWorker* instance, const char* path) { + furi_assert(instance); + bool res = false; + + if(!fuzer_worker_make_key_folder()) { + FURI_LOG_E(TAG, "Cannot create key folder"); + } else if(!hardware_worker_save_key(instance->hw_worker, path)) { + FURI_LOG_E(TAG, "Cannot save key file"); + } else { + FURI_LOG_D(TAG, "Save key Success"); + res = true; + } + + return res; +} + static bool fuzzer_worker_load_key(FuzzerWorker* instance, bool next) { furi_assert(instance); furi_assert(instance->protocol); @@ -67,7 +127,11 @@ static bool fuzzer_worker_load_key(FuzzerWorker* instance, bool next) { switch(instance->attack_type) { case FuzzerWorkerAttackTypeDefaultDict: if(next) { - instance->index++; + if(instance->index < (protocol->dict.len - 1)) { + instance->index++; + } else { + break; + } } if(instance->index < protocol->dict.len) { memcpy( @@ -113,6 +177,7 @@ static bool fuzzer_worker_load_key(FuzzerWorker* instance, bool next) { } break; } + furi_string_free(data_str); } break; @@ -128,18 +193,51 @@ static bool fuzzer_worker_load_key(FuzzerWorker* instance, bool next) { default: break; } -#if defined(RFID_125_PROTOCOL) - protocol_dict_set_data( - instance->protocols_items, instance->protocol_id, instance->payload, MAX_PAYLOAD_SIZE); -#else - ibutton_key_set_protocol_id(instance->key, instance->protocol_id); - iButtonEditableData data; - ibutton_protocols_get_editable_data(instance->protocols_items, instance->key, &data); - // TODO check data.size logic - data.size = MAX_PAYLOAD_SIZE; - memcpy(data.ptr, instance->payload, MAX_PAYLOAD_SIZE); // data.size); -#endif + if(res) { + hardware_worker_set_protocol_data( + instance->hw_worker, &instance->payload[0], protocol->data_size); + } + + return res; +} + +static bool fuzzer_worker_load_previous_key(FuzzerWorker* instance) { + furi_assert(instance); + furi_assert(instance->protocol); + bool res = false; + + const FuzzerProtocol* protocol = instance->protocol; + + switch(instance->attack_type) { + case FuzzerWorkerAttackTypeDefaultDict: + if(instance->index > 0) { + instance->index--; + memcpy( + instance->payload, + &protocol->dict.val[instance->index * protocol->data_size], + protocol->data_size); + res = true; + } + break; + + case FuzzerWorkerAttackTypeLoadFile: + if(instance->payload[instance->index] != 0x00) { + instance->payload[instance->index]--; + res = true; + } + + break; + + default: + break; + } + + if(res) { + hardware_worker_set_protocol_data( + instance->hw_worker, &instance->payload[0], protocol->data_size); + } + return res; } @@ -149,13 +247,7 @@ static void fuzzer_worker_on_tick_callback(void* context) { FuzzerWorker* instance = context; if(instance->in_emu_phase) { - if(instance->treead_running) { -#if defined(RFID_125_PROTOCOL) - lfrfid_worker_stop(instance->proto_worker); -#else - ibutton_worker_stop(instance->proto_worker); -#endif - } + hardware_worker_stop(instance->hw_worker); instance->in_emu_phase = false; furi_timer_start(instance->timer, furi_ms_to_ticks(instance->timer_idle_time_ms)); } else { @@ -165,13 +257,7 @@ static void fuzzer_worker_on_tick_callback(void* context) { instance->end_callback(instance->end_context); } } else { - if(instance->treead_running) { -#if defined(RFID_125_PROTOCOL) - lfrfid_worker_emulate_start(instance->proto_worker, instance->protocol_id); -#else - ibutton_worker_emulate_start(instance->proto_worker, instance->key); -#endif - } + hardware_worker_emulate_start(instance->hw_worker); instance->in_emu_phase = true; furi_timer_start(instance->timer, furi_ms_to_ticks(instance->timer_emu_time_ms)); if(instance->tick_callback) { @@ -190,24 +276,29 @@ void fuzzer_worker_get_current_key(FuzzerWorker* instance, FuzzerPayload* output memcpy(output_key->data, instance->payload, instance->protocol->data_size); } -static void fuzzer_worker_set_protocol(FuzzerWorker* instance, FuzzerProtocolsID protocol_index) { - instance->protocol = &fuzzer_proto_items[protocol_index]; +bool fuzzer_worker_next_key(FuzzerWorker* instance) { + furi_assert(instance); + furi_assert(instance->protocol); -#if defined(RFID_125_PROTOCOL) - instance->protocol_id = - protocol_dict_get_protocol_by_name(instance->protocols_items, instance->protocol->name); -#else - // TODO iButtonProtocolIdInvalid check - instance->protocol_id = - ibutton_protocols_get_id_by_name(instance->protocols_items, instance->protocol->name); -#endif + return fuzzer_worker_load_key(instance, true); +} + +bool fuzzer_worker_previous_key(FuzzerWorker* instance) { + furi_assert(instance); + furi_assert(instance->protocol); + + return fuzzer_worker_load_previous_key(instance); } bool fuzzer_worker_init_attack_dict(FuzzerWorker* instance, FuzzerProtocolsID protocol_index) { furi_assert(instance); bool res = false; - fuzzer_worker_set_protocol(instance, protocol_index); + + if(!fuzzer_worker_set_protocol(instance, protocol_index)) { + instance->attack_type = FuzzerWorkerAttackTypeMax; + return res; + } instance->attack_type = FuzzerWorkerAttackTypeDefaultDict; instance->index = 0; @@ -229,10 +320,15 @@ bool fuzzer_worker_init_attack_file_dict( furi_assert(file_path); bool res = false; - fuzzer_worker_set_protocol(instance, protocol_index); + + if(!fuzzer_worker_set_protocol(instance, protocol_index)) { + instance->attack_type = FuzzerWorkerAttackTypeMax; + return res; + } Storage* storage = furi_record_open(RECORD_STORAGE); instance->uids_stream = buffered_file_stream_alloc(storage); + furi_record_close(RECORD_STORAGE); if(!buffered_file_stream_open( instance->uids_stream, furi_string_get_cstr(file_path), FSAM_READ, FSOM_OPEN_EXISTING)) { @@ -246,7 +342,7 @@ bool fuzzer_worker_init_attack_file_dict( if(!fuzzer_worker_load_key(instance, false)) { instance->attack_type = FuzzerWorkerAttackTypeMax; buffered_file_stream_close(instance->uids_stream); - furi_record_close(RECORD_STORAGE); + stream_free(instance->uids_stream); } else { res = true; } @@ -262,88 +358,49 @@ bool fuzzer_worker_init_attack_bf_byte( furi_assert(instance); bool res = false; - fuzzer_worker_set_protocol(instance, protocol_index); + if(!fuzzer_worker_set_protocol(instance, protocol_index)) { + instance->attack_type = FuzzerWorkerAttackTypeMax; + return res; + } instance->attack_type = FuzzerWorkerAttackTypeLoadFile; instance->index = chusen; memcpy(instance->payload, new_uid->data, instance->protocol->data_size); - res = true; + hardware_worker_set_protocol_data( + instance->hw_worker, &instance->payload[0], instance->protocol->data_size); - return res; -} - -// TODO make it protocol independent -bool fuzzer_worker_load_key_from_file( - FuzzerWorker* instance, - FuzzerProtocolsID protocol_index, - const char* filename) { - furi_assert(instance); - - bool res = false; - fuzzer_worker_set_protocol(instance, protocol_index); - -#if defined(RFID_125_PROTOCOL) - ProtocolId loaded_proto_id = lfrfid_dict_file_load(instance->protocols_items, filename); - if(loaded_proto_id == PROTOCOL_NO) { - // Err Cant load file - FURI_LOG_W(TAG, "Cant load file"); - } else if(instance->protocol_id != loaded_proto_id) { // Err wrong protocol - FURI_LOG_W(TAG, "Wrong protocol"); - FURI_LOG_W( - TAG, - "Selected: %s Loaded: %s", - instance->protocol->name, - protocol_dict_get_name(instance->protocols_items, loaded_proto_id)); - } else { - protocol_dict_get_data( - instance->protocols_items, instance->protocol_id, instance->payload, MAX_PAYLOAD_SIZE); - res = true; - } -#else - if(!ibutton_protocols_load(instance->protocols_items, instance->key, filename)) { - // Err Cant load file - FURI_LOG_W(TAG, "Cant load file"); - } else { - if(instance->protocol_id != ibutton_key_get_protocol_id(instance->key)) { - // Err wrong protocol - FURI_LOG_W(TAG, "Wrong protocol"); - FURI_LOG_W( - TAG, - "Selected: %s Loaded: %s", - instance->protocol->name, - ibutton_protocols_get_name( - instance->protocols_items, ibutton_key_get_protocol_id(instance->key))); - } else { - iButtonEditableData data; - ibutton_protocols_get_editable_data(instance->protocols_items, instance->key, &data); - memcpy(instance->payload, data.ptr, data.size); - res = true; - } - } -#endif - - return res; + return true; } FuzzerWorker* fuzzer_worker_alloc() { FuzzerWorker* instance = malloc(sizeof(FuzzerWorker)); -#if defined(RFID_125_PROTOCOL) - instance->protocols_items = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax); + instance->hw_worker = hardware_worker_alloc(); + hardware_worker_start_thread(instance->hw_worker); - instance->proto_worker = lfrfid_worker_alloc(instance->protocols_items); -#else - instance->protocols_items = ibutton_protocols_alloc(); - instance->key = - ibutton_key_alloc(ibutton_protocols_get_max_data_size(instance->protocols_items)); + instance->suported_proto = malloc(sizeof(HwProtocolID) * TOTAL_PROTOCOL_COUNT); + + for(uint8_t i = 0; i < TOTAL_PROTOCOL_COUNT; i++) { + if(!hardware_worker_set_protocol_id_by_name( + instance->hw_worker, fuzzer_proto_items[i].name)) { + // Check protocol support + FURI_LOG_E(TAG, "Not supported protocol name: %s", fuzzer_proto_items[i].name); + furi_crash("Not supported protocol name"); + } else { + instance->suported_proto[i] = hardware_worker_get_protocol_id(instance->hw_worker); + FURI_LOG_D( + TAG, + "%u: %15s Protocol_id: %lu", + i + 1, + fuzzer_proto_items[i].name, + instance->suported_proto[i]); + } + } - instance->proto_worker = ibutton_worker_alloc(instance->protocols_items); -#endif instance->attack_type = FuzzerWorkerAttackTypeMax; instance->index = 0; - instance->treead_running = false; instance->in_emu_phase = false; memset(instance->payload, 0x00, sizeof(instance->payload)); @@ -364,16 +421,10 @@ void fuzzer_worker_free(FuzzerWorker* instance) { furi_timer_free(instance->timer); -#if defined(RFID_125_PROTOCOL) - lfrfid_worker_free(instance->proto_worker); + free(instance->suported_proto); - protocol_dict_free(instance->protocols_items); -#else - ibutton_worker_free(instance->proto_worker); - - ibutton_key_free(instance->key); - ibutton_protocols_free(instance->protocols_items); -#endif + hardware_worker_stop_thread(instance->hw_worker); + hardware_worker_free(instance->hw_worker); free(instance); } @@ -399,25 +450,8 @@ bool fuzzer_worker_start(FuzzerWorker* instance, uint8_t idle_time, uint8_t emu_ instance->timer_emu_time_ms, instance->timer_idle_time_ms); - if(!instance->treead_running) { -#if defined(RFID_125_PROTOCOL) - lfrfid_worker_start_thread(instance->proto_worker); -#else - ibutton_worker_start_thread(instance->proto_worker); -#endif - FURI_LOG_D(TAG, "Worker Starting"); - instance->treead_running = true; - } else { - FURI_LOG_D(TAG, "Worker UnPaused"); - } + hardware_worker_emulate_start(instance->hw_worker); -#if defined(RFID_125_PROTOCOL) - // lfrfid_worker_start_thread(instance->proto_worker); - lfrfid_worker_emulate_start(instance->proto_worker, instance->protocol_id); -#else - // ibutton_worker_start_thread(instance->proto_worker); - ibutton_worker_emulate_start(instance->proto_worker, instance->key); -#endif instance->in_emu_phase = true; furi_timer_start(instance->timer, furi_ms_to_ticks(instance->timer_emu_time_ms)); return true; @@ -425,19 +459,18 @@ bool fuzzer_worker_start(FuzzerWorker* instance, uint8_t idle_time, uint8_t emu_ return false; } +void fuzzer_worker_start_emulate(FuzzerWorker* instance) { + furi_assert(instance); + + hardware_worker_emulate_start(instance->hw_worker); +} + void fuzzer_worker_pause(FuzzerWorker* instance) { furi_assert(instance); furi_timer_stop(instance->timer); - if(instance->treead_running) { -#if defined(RFID_125_PROTOCOL) - lfrfid_worker_stop(instance->proto_worker); -#else - ibutton_worker_stop(instance->proto_worker); -#endif - FURI_LOG_D(TAG, "Worker Paused"); - } + hardware_worker_stop(instance->hw_worker); } void fuzzer_worker_stop(FuzzerWorker* instance) { @@ -445,25 +478,15 @@ void fuzzer_worker_stop(FuzzerWorker* instance) { furi_timer_stop(instance->timer); - if(instance->treead_running) { -#if defined(RFID_125_PROTOCOL) - lfrfid_worker_stop(instance->proto_worker); - lfrfid_worker_stop_thread(instance->proto_worker); -#else - ibutton_worker_stop(instance->proto_worker); - ibutton_worker_stop_thread(instance->proto_worker); -#endif - FURI_LOG_D(TAG, "Worker Stopping"); - instance->treead_running = false; - } + hardware_worker_stop(instance->hw_worker); if(instance->attack_type == FuzzerWorkerAttackTypeLoadFileCustomUids) { buffered_file_stream_close(instance->uids_stream); - furi_record_close(RECORD_STORAGE); + stream_free(instance->uids_stream); instance->attack_type = FuzzerWorkerAttackTypeMax; } - // TODO anything else + // TODO anything else } void fuzzer_worker_set_uid_chaged_callback( diff --git a/applications/external/multi_fuzzer/lib/worker/fake_worker.h b/applications/external/multi_fuzzer/lib/worker/fake_worker.h index 8b934f300..9aecb56b5 100644 --- a/applications/external/multi_fuzzer/lib/worker/fake_worker.h +++ b/applications/external/multi_fuzzer/lib/worker/fake_worker.h @@ -12,6 +12,14 @@ typedef enum { FuzzerWorkerAttackTypeMax, } FuzzerWorkerAttackType; +typedef enum { + FuzzerWorkerLoadKeyStateBadFile = -2, + FuzzerWorkerLoadKeyStateUnsuportedProto, + FuzzerWorkerLoadKeyStateOk = 0, + FuzzerWorkerLoadKeyStateDifferentProto, + +} FuzzerWorkerLoadKeyState; + typedef void (*FuzzerWorkerUidChagedCallback)(void* context); typedef void (*FuzzerWorkerEndCallback)(void* context); @@ -48,6 +56,8 @@ bool fuzzer_worker_start(FuzzerWorker* instance, uint8_t idle_time, uint8_t emu_ */ void fuzzer_worker_stop(FuzzerWorker* instance); +void fuzzer_worker_start_emulate(FuzzerWorker* instance); + /** * Suspend emulation * @@ -100,6 +110,9 @@ bool fuzzer_worker_init_attack_bf_byte( */ void fuzzer_worker_get_current_key(FuzzerWorker* instance, FuzzerPayload* output_key); +bool fuzzer_worker_next_key(FuzzerWorker* instance); +bool fuzzer_worker_previous_key(FuzzerWorker* instance); + /** * Load UID from Flipper Format Key file * @@ -108,11 +121,13 @@ void fuzzer_worker_get_current_key(FuzzerWorker* instance, FuzzerPayload* output * @param filename file path to the key file * @return bool True if loading is successful */ -bool fuzzer_worker_load_key_from_file( +FuzzerWorkerLoadKeyState fuzzer_worker_load_key_from_file( FuzzerWorker* instance, - FuzzerProtocolsID protocol_index, + FuzzerProtocolsID* protocol_index, const char* filename); +bool fuzzer_worker_save_key(FuzzerWorker* instance, const char* path); + /** * Set callback for uid changed * diff --git a/applications/external/multi_fuzzer/lib/worker/helpers/hardware_worker.c b/applications/external/multi_fuzzer/lib/worker/helpers/hardware_worker.c new file mode 100644 index 000000000..e42996afa --- /dev/null +++ b/applications/external/multi_fuzzer/lib/worker/helpers/hardware_worker.c @@ -0,0 +1,198 @@ +#include "hardware_worker.h" +#include "furi.h" + +#if defined(RFID_125_PROTOCOL) + +#include +#include + +#else + +#include +#include + +#endif + +#define TAG "Fuzzer HW worker" + +struct HardwareWorker { +#if defined(RFID_125_PROTOCOL) + LFRFIDWorker* proto_worker; + ProtocolId protocol_id; + ProtocolDict* protocols_items; +#else + iButtonWorker* proto_worker; + iButtonProtocolId protocol_id; + iButtonProtocols* protocols_items; + iButtonKey* key; +#endif +}; + +HardwareWorker* hardware_worker_alloc() { + HardwareWorker* instance = malloc(sizeof(HardwareWorker)); +#if defined(RFID_125_PROTOCOL) + instance->protocols_items = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax); + + instance->proto_worker = lfrfid_worker_alloc(instance->protocols_items); +#else + instance->protocols_items = ibutton_protocols_alloc(); + instance->key = + ibutton_key_alloc(ibutton_protocols_get_max_data_size(instance->protocols_items)); + + instance->proto_worker = ibutton_worker_alloc(instance->protocols_items); +#endif + return instance; +} + +void hardware_worker_free(HardwareWorker* instance) { +#if defined(RFID_125_PROTOCOL) + lfrfid_worker_free(instance->proto_worker); + + protocol_dict_free(instance->protocols_items); +#else + ibutton_worker_free(instance->proto_worker); + + ibutton_key_free(instance->key); + ibutton_protocols_free(instance->protocols_items); +#endif + free(instance); +} + +void hardware_worker_start_thread(HardwareWorker* instance) { +#if defined(RFID_125_PROTOCOL) + lfrfid_worker_start_thread(instance->proto_worker); +#else + ibutton_worker_start_thread(instance->proto_worker); +#endif +} + +void hardware_worker_stop_thread(HardwareWorker* instance) { +#if defined(RFID_125_PROTOCOL) + lfrfid_worker_stop(instance->proto_worker); + lfrfid_worker_stop_thread(instance->proto_worker); +#else + ibutton_worker_stop(instance->proto_worker); + ibutton_worker_stop_thread(instance->proto_worker); +#endif +} + +void hardware_worker_emulate_start(HardwareWorker* instance) { +#if defined(RFID_125_PROTOCOL) + lfrfid_worker_emulate_start(instance->proto_worker, instance->protocol_id); +#else + ibutton_worker_emulate_start(instance->proto_worker, instance->key); +#endif +} + +void hardware_worker_stop(HardwareWorker* instance) { +#if defined(RFID_125_PROTOCOL) + lfrfid_worker_stop(instance->proto_worker); +#else + ibutton_worker_stop(instance->proto_worker); +#endif +} + +void hardware_worker_set_protocol_data( + HardwareWorker* instance, + uint8_t* payload, + uint8_t payload_size) { +#if defined(RFID_125_PROTOCOL) + protocol_dict_set_data( + instance->protocols_items, instance->protocol_id, payload, payload_size); +#else + ibutton_key_set_protocol_id(instance->key, instance->protocol_id); + iButtonEditableData data; + ibutton_protocols_get_editable_data(instance->protocols_items, instance->key, &data); + + furi_check(payload_size >= data.size); + memcpy(data.ptr, payload, data.size); +#endif +} + +void hardware_worker_get_protocol_data( + HardwareWorker* instance, + uint8_t* payload, + uint8_t payload_size) { +#if defined(RFID_125_PROTOCOL) + protocol_dict_get_data( + instance->protocols_items, instance->protocol_id, payload, payload_size); +#else + iButtonEditableData data; + ibutton_protocols_get_editable_data(instance->protocols_items, instance->key, &data); + furi_check(payload_size >= data.size); + memcpy(payload, data.ptr, data.size); +#endif +} + +static bool hardware_worker_protocol_is_valid(HardwareWorker* instance) { +#if defined(RFID_125_PROTOCOL) + if(instance->protocol_id != PROTOCOL_NO) { + return true; + } +#else + if(instance->protocol_id != iButtonProtocolIdInvalid) { + return true; + } +#endif + return false; +} + +bool hardware_worker_set_protocol_id_by_name(HardwareWorker* instance, const char* protocol_name) { +#if defined(RFID_125_PROTOCOL) + instance->protocol_id = + protocol_dict_get_protocol_by_name(instance->protocols_items, protocol_name); + return (instance->protocol_id != PROTOCOL_NO); +#else + instance->protocol_id = + ibutton_protocols_get_id_by_name(instance->protocols_items, protocol_name); + return (instance->protocol_id != iButtonProtocolIdInvalid); +#endif +} + +HwProtocolID hardware_worker_get_protocol_id(HardwareWorker* instance) { + if(hardware_worker_protocol_is_valid(instance)) { + return instance->protocol_id; + } + return -1; +} + +bool hardware_worker_load_key_from_file(HardwareWorker* instance, const char* filename) { + bool res = false; + +#if defined(RFID_125_PROTOCOL) + ProtocolId loaded_proto_id = lfrfid_dict_file_load(instance->protocols_items, filename); + if(loaded_proto_id == PROTOCOL_NO) { + // Err Cant load file + FURI_LOG_W(TAG, "Cant load file"); + } else { + instance->protocol_id = loaded_proto_id; + res = true; + } +#else + + if(!ibutton_protocols_load(instance->protocols_items, instance->key, filename)) { + // Err Cant load file + FURI_LOG_W(TAG, "Cant load file"); + } else { + instance->protocol_id = ibutton_key_get_protocol_id(instance->key); + res = true; + } + +#endif + + return res; +} + +bool hardware_worker_save_key(HardwareWorker* instance, const char* path) { + furi_assert(instance); + bool res; + +#if defined(RFID_125_PROTOCOL) + res = lfrfid_dict_file_save(instance->protocols_items, instance->protocol_id, path); +#else + + res = ibutton_protocols_save(instance->protocols_items, instance->key, path); +#endif + + return res; +} \ No newline at end of file diff --git a/applications/external/multi_fuzzer/lib/worker/helpers/hardware_worker.h b/applications/external/multi_fuzzer/lib/worker/helpers/hardware_worker.h new file mode 100644 index 000000000..f2cd3842e --- /dev/null +++ b/applications/external/multi_fuzzer/lib/worker/helpers/hardware_worker.h @@ -0,0 +1,48 @@ +#pragma once + +#include +#include + +#if defined(RFID_125_PROTOCOL) + +#include +typedef ProtocolId HwProtocolID; + +#else + +#include +typedef iButtonProtocolId HwProtocolID; + +#endif + +typedef struct HardwareWorker HardwareWorker; + +HardwareWorker* hardware_worker_alloc(); + +void hardware_worker_free(HardwareWorker* instance); + +void hardware_worker_start_thread(HardwareWorker* instance); + +void hardware_worker_stop_thread(HardwareWorker* instance); + +void hardware_worker_emulate_start(HardwareWorker* instance); + +void hardware_worker_stop(HardwareWorker* instance); + +void hardware_worker_set_protocol_data( + HardwareWorker* instance, + uint8_t* payload, + uint8_t payload_size); + +void hardware_worker_get_protocol_data( + HardwareWorker* instance, + uint8_t* payload, + uint8_t payload_size); + +bool hardware_worker_set_protocol_id_by_name(HardwareWorker* instance, const char* protocol_name); + +HwProtocolID hardware_worker_get_protocol_id(HardwareWorker* instance); + +bool hardware_worker_load_key_from_file(HardwareWorker* instance, const char* filename); + +bool hardware_worker_save_key(HardwareWorker* instance, const char* path); \ No newline at end of file diff --git a/applications/external/multi_fuzzer/lib/worker/protocol.c b/applications/external/multi_fuzzer/lib/worker/protocol.c index a64fe8767..5284f87f1 100644 --- a/applications/external/multi_fuzzer/lib/worker/protocol.c +++ b/applications/external/multi_fuzzer/lib/worker/protocol.c @@ -152,79 +152,79 @@ const uint8_t uid_list_h10301[][H10301_DATA_SIZE] = { #if defined(RFID_125_PROTOCOL) const FuzzerProtocol fuzzer_proto_items[] = { - [EM4100] = - { - .name = "EM4100", - .data_size = EM4100_DATA_SIZE, - .dict = - { - .val = (const uint8_t*)&uid_list_em4100, - .len = COUNT_OF(uid_list_em4100), - }, - }, - [HIDProx] = - { - .name = "HIDProx", - .data_size = HIDProx_DATA_SIZE, - .dict = - { - .val = (const uint8_t*)&uid_list_hid, - .len = COUNT_OF(uid_list_hid), - }, - }, - [PAC] = - { - .name = "PAC/Stanley", - .data_size = PAC_DATA_SIZE, - .dict = - { - .val = (const uint8_t*)&uid_list_pac, - .len = COUNT_OF(uid_list_pac), - }, - }, - [H10301] = - { - .name = "H10301", - .data_size = H10301_DATA_SIZE, - .dict = - { - .val = (const uint8_t*)&uid_list_h10301, - .len = COUNT_OF(uid_list_h10301), - }, - }, + // EM4100 + { + .name = "EM4100", + .data_size = EM4100_DATA_SIZE, + .dict = + { + .val = (const uint8_t*)&uid_list_em4100, + .len = COUNT_OF(uid_list_em4100), + }, + }, + // HIDProx + { + .name = "HIDProx", + .data_size = HIDProx_DATA_SIZE, + .dict = + { + .val = (const uint8_t*)&uid_list_hid, + .len = COUNT_OF(uid_list_hid), + }, + }, + // PAC + { + .name = "PAC/Stanley", + .data_size = PAC_DATA_SIZE, + .dict = + { + .val = (const uint8_t*)&uid_list_pac, + .len = COUNT_OF(uid_list_pac), + }, + }, + // H10301 + { + .name = "H10301", + .data_size = H10301_DATA_SIZE, + .dict = + { + .val = (const uint8_t*)&uid_list_h10301, + .len = COUNT_OF(uid_list_h10301), + }, + }, }; #else const FuzzerProtocol fuzzer_proto_items[] = { - [DS1990] = - { - .name = "DS1990", - .data_size = DS1990_DATA_SIZE, - .dict = - { - .val = (const uint8_t*)&uid_list_ds1990, - .len = COUNT_OF(uid_list_ds1990), - }, - }, - [Metakom] = - { - .name = "Metakom", - .data_size = Metakom_DATA_SIZE, - .dict = - { - .val = (const uint8_t*)&uid_list_metakom, - .len = COUNT_OF(uid_list_metakom), - }, - }, - [Cyfral] = - { - .name = "Cyfral", - .data_size = Cyfral_DATA_SIZE, - .dict = - { - .val = (const uint8_t*)&uid_list_cyfral, - .len = COUNT_OF(uid_list_cyfral), - }, - }, + // DS1990 + { + .name = "DS1990", + .data_size = DS1990_DATA_SIZE, + .dict = + { + .val = (const uint8_t*)&uid_list_ds1990, + .len = COUNT_OF(uid_list_ds1990), + }, + }, + // Metakom + { + .name = "Metakom", + .data_size = Metakom_DATA_SIZE, + .dict = + { + .val = (const uint8_t*)&uid_list_metakom, + .len = COUNT_OF(uid_list_metakom), + }, + }, + // Cyfral + { + .name = "Cyfral", + .data_size = Cyfral_DATA_SIZE, + .dict = + { + .val = (const uint8_t*)&uid_list_cyfral, + .len = COUNT_OF(uid_list_cyfral), + }, + }, }; #endif diff --git a/applications/external/multi_fuzzer/lib/worker/protocol.h b/applications/external/multi_fuzzer/lib/worker/protocol.h index 9c5315d00..e7c264b54 100644 --- a/applications/external/multi_fuzzer/lib/worker/protocol.h +++ b/applications/external/multi_fuzzer/lib/worker/protocol.h @@ -6,18 +6,7 @@ typedef struct FuzzerPayload FuzzerPayload; -typedef enum { -#if defined(RFID_125_PROTOCOL) - EM4100, - HIDProx, - PAC, - H10301, -#else - DS1990, - Metakom, - Cyfral, -#endif -} FuzzerProtocolsID; +typedef uint8_t FuzzerProtocolsID; typedef enum { FuzzerAttackIdDefaultValues = 0, @@ -51,8 +40,16 @@ void fuzzer_payload_free(FuzzerPayload*); */ uint8_t fuzzer_proto_get_max_data_size(); -// TODO add description +/** + * Get recomended/default emulation time + * @return Default emulation time + */ uint8_t fuzzer_proto_get_def_emu_time(); + +/** + * Get recomended/default idle time + * @return Default idle time + */ uint8_t fuzzer_proto_get_def_idle_time(); /** diff --git a/applications/external/multi_fuzzer/lib/worker/protocol_i.h b/applications/external/multi_fuzzer/lib/worker/protocol_i.h index 2f1c65fd7..6679b6d7f 100644 --- a/applications/external/multi_fuzzer/lib/worker/protocol_i.h +++ b/applications/external/multi_fuzzer/lib/worker/protocol_i.h @@ -7,11 +7,19 @@ #define PROTOCOL_DEF_IDLE_TIME (4) #define PROTOCOL_DEF_EMU_TIME (5) #define PROTOCOL_TIME_DELAY_MIN PROTOCOL_DEF_IDLE_TIME + PROTOCOL_DEF_EMU_TIME + +#define PROTOCOL_KEY_FOLDER_NAME "lfrfid" +#define PROTOCOL_KEY_EXTENSION ".rfid" + #else + #define MAX_PAYLOAD_SIZE (8) #define PROTOCOL_DEF_IDLE_TIME (2) #define PROTOCOL_DEF_EMU_TIME (2) #define PROTOCOL_TIME_DELAY_MIN PROTOCOL_DEF_IDLE_TIME + PROTOCOL_DEF_EMU_TIME + +#define PROTOCOL_KEY_FOLDER_NAME "ibutton" +#define PROTOCOL_KEY_EXTENSION ".ibtn" #endif typedef struct ProtoDict ProtoDict; diff --git a/applications/external/multi_fuzzer/scenes/fuzzer_scene_attack.c b/applications/external/multi_fuzzer/scenes/fuzzer_scene_attack.c index 6424e62b5..0b0f68093 100644 --- a/applications/external/multi_fuzzer/scenes/fuzzer_scene_attack.c +++ b/applications/external/multi_fuzzer/scenes/fuzzer_scene_attack.c @@ -1,7 +1,7 @@ #include "../fuzzer_i.h" #include "../helpers/fuzzer_custom_event.h" -const NotificationSequence sequence_one_green_50_on_blink_blue = { +const NotificationSequence sequence_one_red_50_on_blink_blue = { &message_red_255, &message_delay_50, &message_red_0, @@ -28,35 +28,38 @@ static void fuzzer_scene_attack_set_state(PacsFuzzerApp* app, FuzzerAttackState switch(state) { case FuzzerAttackStateIdle: notification_message(app->notifications, &sequence_blink_stop); - fuzzer_view_attack_pause(app->attack_view); break; - case FuzzerAttackStateRunning: + case FuzzerAttackStateAttacking: + notification_message(app->notifications, &sequence_blink_start_blue); + break; + case FuzzerAttackStateEmulating: notification_message(app->notifications, &sequence_blink_start_blue); - fuzzer_view_attack_start(app->attack_view); break; case FuzzerAttackStateEnd: notification_message(app->notifications, &sequence_blink_stop); notification_message(app->notifications, &sequence_single_vibro); - fuzzer_view_attack_end(app->attack_view); break; case FuzzerAttackStateOff: notification_message(app->notifications, &sequence_blink_stop); - fuzzer_view_attack_stop(app->attack_view); + break; + + case FuzzerAttackStatePause: + notification_message(app->notifications, &sequence_blink_stop); break; } + + fuzzer_view_update_state(app->attack_view, state); } void fuzzer_scene_attack_worker_tick_callback(void* context) { furi_assert(context); PacsFuzzerApp* app = context; - notification_message(app->notifications, &sequence_one_green_50_on_blink_blue); + notification_message(app->notifications, &sequence_one_red_50_on_blink_blue); fuzzer_scene_attack_update_uid(app); - - // view_dispatcher_send_custom_event(app->view_dispatcher, FuzzerCustomEventViewAttackTick); } void fuzzer_scene_attack_worker_end_callback(void* context) { @@ -100,48 +103,53 @@ bool fuzzer_scene_attack_on_event(void* context, SceneManagerEvent event) { bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { - if(event.event == FuzzerCustomEventViewAttackBack) { - if(scene_manager_get_scene_state(app->scene_manager, FuzzerSceneAttack) == - FuzzerAttackStateRunning) { - // Pause if attack running - fuzzer_worker_pause(app->worker); + if(event.event == FuzzerCustomEventViewAttackExit) { + // Exit + fuzzer_worker_stop(app->worker); - fuzzer_scene_attack_set_state(app, FuzzerAttackStateIdle); + fuzzer_scene_attack_set_state(app, FuzzerAttackStateOff); + if(!scene_manager_previous_scene(app->scene_manager)) { + scene_manager_stop(app->scene_manager); + view_dispatcher_stop(app->view_dispatcher); + } + } else if(event.event == FuzzerCustomEventViewAttackRunAttack) { + if(fuzzer_worker_start( + app->worker, + fuzzer_view_attack_get_time_delay(app->attack_view), + fuzzer_view_attack_get_emu_time(app->attack_view))) { + fuzzer_scene_attack_set_state(app, FuzzerAttackStateAttacking); } else { - // Exit - fuzzer_worker_stop(app->worker); - - fuzzer_scene_attack_set_state(app, FuzzerAttackStateOff); - if(!scene_manager_previous_scene(app->scene_manager)) { - scene_manager_stop(app->scene_manager); - view_dispatcher_stop(app->view_dispatcher); - } + // Error? } - consumed = true; - } else if(event.event == FuzzerCustomEventViewAttackOk) { - if(scene_manager_get_scene_state(app->scene_manager, FuzzerSceneAttack) == - FuzzerAttackStateIdle) { - // Start or Continue Attack - if(fuzzer_worker_start( - app->worker, - fuzzer_view_attack_get_time_delay(app->attack_view), - fuzzer_view_attack_get_emu_time(app->attack_view))) { - fuzzer_scene_attack_set_state(app, FuzzerAttackStateRunning); - } else { - // Error? - } - } else if( - scene_manager_get_scene_state(app->scene_manager, FuzzerSceneAttack) == - FuzzerAttackStateRunning) { - // Pause if attack running - fuzzer_worker_pause(app->worker); + } else if(event.event == FuzzerCustomEventViewAttackEmulateCurrent) { + fuzzer_worker_start_emulate(app->worker); - fuzzer_scene_attack_set_state(app, FuzzerAttackStateIdle); + fuzzer_scene_attack_set_state(app, FuzzerAttackStateEmulating); + } else if(event.event == FuzzerCustomEventViewAttackPause) { + fuzzer_worker_pause(app->worker); + + fuzzer_scene_attack_set_state(app, FuzzerAttackStatePause); + } else if(event.event == FuzzerCustomEventViewAttackIdle) { + fuzzer_worker_pause(app->worker); + + fuzzer_scene_attack_set_state(app, FuzzerAttackStateIdle); + } else if(event.event == FuzzerCustomEventViewAttackNextUid) { + if(fuzzer_worker_next_key(app->worker)) { + fuzzer_scene_attack_update_uid(app); + } else { + notification_message(app->notifications, &sequence_blink_red_100); } - consumed = true; - // } else if(event.event == FuzzerCustomEventViewAttackTick) { - // consumed = true; - } else if(event.event == FuzzerCustomEventViewAttackEnd) { + } else if(event.event == FuzzerCustomEventViewAttackPrevUid) { + if(fuzzer_worker_previous_key(app->worker)) { + fuzzer_scene_attack_update_uid(app); + } else { + notification_message(app->notifications, &sequence_blink_red_100); + } + } else if(event.event == FuzzerCustomEventViewAttackSave) { + scene_manager_next_scene(app->scene_manager, FuzzerSceneSaveName); + } + // Callback from worker + else if(event.event == FuzzerCustomEventViewAttackEnd) { fuzzer_scene_attack_set_state(app, FuzzerAttackStateEnd); consumed = true; } diff --git a/applications/external/multi_fuzzer/scenes/fuzzer_scene_config.h b/applications/external/multi_fuzzer/scenes/fuzzer_scene_config.h index 711ebe1c4..b1ad9d609 100644 --- a/applications/external/multi_fuzzer/scenes/fuzzer_scene_config.h +++ b/applications/external/multi_fuzzer/scenes/fuzzer_scene_config.h @@ -1,3 +1,5 @@ ADD_SCENE(fuzzer, main, Main) ADD_SCENE(fuzzer, attack, Attack) -ADD_SCENE(fuzzer, field_editor, FieldEditor) \ No newline at end of file +ADD_SCENE(fuzzer, field_editor, FieldEditor) +ADD_SCENE(fuzzer, save_name, SaveName) +ADD_SCENE(fuzzer, save_success, SaveSuccess) \ No newline at end of file diff --git a/applications/external/multi_fuzzer/scenes/fuzzer_scene_field_editor.c b/applications/external/multi_fuzzer/scenes/fuzzer_scene_field_editor.c index ccea123dc..a33e337cc 100644 --- a/applications/external/multi_fuzzer/scenes/fuzzer_scene_field_editor.c +++ b/applications/external/multi_fuzzer/scenes/fuzzer_scene_field_editor.c @@ -22,6 +22,7 @@ void fuzzer_scene_field_editor_on_enter(void* context) { break; case FuzzerFieldEditorStateEditingOff: + memset(app->payload->data, 0x00, app->payload->data_size); fuzzer_view_field_editor_reset_data(app->field_editor_view, app->payload, false); break; diff --git a/applications/external/multi_fuzzer/scenes/fuzzer_scene_main.c b/applications/external/multi_fuzzer/scenes/fuzzer_scene_main.c index 0d074a121..7c53d098a 100644 --- a/applications/external/multi_fuzzer/scenes/fuzzer_scene_main.c +++ b/applications/external/multi_fuzzer/scenes/fuzzer_scene_main.c @@ -2,6 +2,7 @@ #include "../helpers/fuzzer_custom_event.h" #include "../lib/worker/protocol.h" +#define TAG "Fuzzer main menu" void fuzzer_scene_main_callback(FuzzerCustomEvent event, void* context) { furi_assert(context); @@ -35,7 +36,7 @@ static bool fuzzer_scene_main_load_custom_dict(void* context) { return res; } -static bool fuzzer_scene_main_load_key(void* context) { +static bool fuzzer_scene_main_load_key_dialog(void* context) { furi_assert(context); PacsFuzzerApp* app = context; @@ -134,22 +135,32 @@ bool fuzzer_scene_main_on_event(void* context, SceneManagerEvent event) { break; case FuzzerAttackIdLoadFile: - if(!fuzzer_scene_main_load_key(app)) { + if(!fuzzer_scene_main_load_key_dialog(app)) { break; } else { - if(fuzzer_worker_load_key_from_file( - app->worker, - app->fuzzer_state.proto_index, - furi_string_get_cstr(app->file_path))) { + switch(fuzzer_worker_load_key_from_file( + app->worker, + &app->fuzzer_state.proto_index, + furi_string_get_cstr(app->file_path))) { + case FuzzerWorkerLoadKeyStateOk: + case FuzzerWorkerLoadKeyStateDifferentProto: scene_manager_set_scene_state( app->scene_manager, FuzzerSceneFieldEditor, FuzzerFieldEditorStateEditingOn); scene_manager_next_scene(app->scene_manager, FuzzerSceneFieldEditor); - FURI_LOG_I("Scene", "Load ok"); - } else { - fuzzer_scene_main_show_error(app, "Unsupported protocol\nor broken file"); - FURI_LOG_W("Scene", "Load err"); + FURI_LOG_I(TAG, "Load ok"); + break; + + case FuzzerWorkerLoadKeyStateBadFile: + fuzzer_scene_main_show_error(app, "Cant load\nor broken file"); + FURI_LOG_E(TAG, "Cant load or broken file"); + break; + + case FuzzerWorkerLoadKeyStateUnsuportedProto: + fuzzer_scene_main_show_error(app, "Unsupported protocol"); + FURI_LOG_E(TAG, "Unsupported protocol"); + break; } } break; diff --git a/applications/external/multi_fuzzer/scenes/fuzzer_scene_save_name.c b/applications/external/multi_fuzzer/scenes/fuzzer_scene_save_name.c new file mode 100644 index 000000000..983bf0a58 --- /dev/null +++ b/applications/external/multi_fuzzer/scenes/fuzzer_scene_save_name.c @@ -0,0 +1,67 @@ +#include "../fuzzer_i.h" + +#include +#include + +static void fuzzer_scene_save_name_text_input_callback(void* context) { + PacsFuzzerApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, FuzzerCustomEventTextEditResult); +} + +void fuzzer_scene_save_name_on_enter(void* context) { + PacsFuzzerApp* app = context; + TextInput* text_input = app->text_input; + + set_random_name(app->key_name, KEY_NAME_SIZE); + + text_input_set_header_text(text_input, "Name the key"); + text_input_set_result_callback( + text_input, + fuzzer_scene_save_name_text_input_callback, + app, + app->key_name, + KEY_NAME_SIZE, + false); + + ValidatorIsFile* validator_is_file = validator_is_file_alloc_init( + app->fuzzer_const->path_key_folder, app->fuzzer_const->key_extension, app->key_name); + text_input_set_validator(text_input, validator_is_file_callback, validator_is_file); + + view_dispatcher_switch_to_view(app->view_dispatcher, FuzzerViewIDTextInput); +} + +bool fuzzer_scene_save_name_on_event(void* context, SceneManagerEvent event) { + PacsFuzzerApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == FuzzerCustomEventTextEditResult) { + consumed = true; + furi_string_printf( + app->file_path, + "%s/%s%s", + app->fuzzer_const->path_key_folder, + app->key_name, + app->fuzzer_const->key_extension); + + if(fuzzer_worker_save_key(app->worker, furi_string_get_cstr(app->file_path))) { + scene_manager_next_scene(app->scene_manager, FuzzerSceneSaveSuccess); + } else { + scene_manager_previous_scene(app->scene_manager); + } + } + } + + return consumed; +} + +void fuzzer_scene_save_name_on_exit(void* context) { + PacsFuzzerApp* app = context; + TextInput* text_input = app->text_input; + + void* validator_context = text_input_get_validator_callback_context(text_input); + text_input_set_validator(text_input, NULL, NULL); + validator_is_file_free((ValidatorIsFile*)validator_context); + + text_input_reset(text_input); +} diff --git a/applications/external/multi_fuzzer/scenes/fuzzer_scene_save_success.c b/applications/external/multi_fuzzer/scenes/fuzzer_scene_save_success.c new file mode 100644 index 000000000..773ba6ddc --- /dev/null +++ b/applications/external/multi_fuzzer/scenes/fuzzer_scene_save_success.c @@ -0,0 +1,44 @@ +#include "../fuzzer_i.h" + +static void fuzzer_scene_save_popup_timeout_callback(void* context) { + PacsFuzzerApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, FuzzerCustomEventPopupClosed); +} + +void fuzzer_scene_save_success_on_enter(void* context) { + PacsFuzzerApp* app = context; + Popup* popup = app->popup; + + popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); + popup_set_header(popup, "Saved!", 5, 7, AlignLeft, AlignTop); + popup_set_context(popup, app); + popup_set_callback(popup, fuzzer_scene_save_popup_timeout_callback); + popup_set_timeout(popup, 1500); + popup_enable_timeout(popup); + + view_dispatcher_switch_to_view(app->view_dispatcher, FuzzerViewIDPopup); +} + +bool fuzzer_scene_save_success_on_event(void* context, SceneManagerEvent event) { + PacsFuzzerApp* app = context; + bool consumed = false; + + if((event.type == SceneManagerEventTypeBack) || + ((event.type == SceneManagerEventTypeCustom) && + (event.event == FuzzerCustomEventPopupClosed))) { + bool result = scene_manager_search_and_switch_to_previous_scene( + app->scene_manager, FuzzerSceneAttack); + if(!result) { + scene_manager_search_and_switch_to_previous_scene(app->scene_manager, FuzzerSceneMain); + } + consumed = true; + } + + return consumed; +} + +void fuzzer_scene_save_success_on_exit(void* context) { + PacsFuzzerApp* app = context; + + popup_reset(app->popup); +} diff --git a/applications/external/multi_fuzzer/views/attack.c b/applications/external/multi_fuzzer/views/attack.c index 87aa9f659..4748c3686 100644 --- a/applications/external/multi_fuzzer/views/attack.c +++ b/applications/external/multi_fuzzer/views/attack.c @@ -6,7 +6,12 @@ #define ATTACK_SCENE_MAX_UID_LENGTH 25 #define UID_MAX_DISPLAYED_LEN (8U) -#define LIFT_RIGHT_OFFSET (3) +#define LEFT_RIGHT_OFFSET (3U) + +#define LINE_1_Y (12U) +#define LINE_2_Y (24U) +#define LINE_3_Y (36U) +#define LINE_4_Y (48U) struct FuzzerViewAttack { View* view; @@ -60,44 +65,11 @@ void fuzzer_view_attack_set_uid(FuzzerViewAttack* view, const FuzzerPayload* uid true); } -void fuzzer_view_attack_start(FuzzerViewAttack* view) { +void fuzzer_view_update_state(FuzzerViewAttack* view, FuzzerAttackState state) { furi_assert(view); with_view_model( - view->view, - FuzzerViewAttackModel * model, - { model->attack_state = FuzzerAttackStateRunning; }, - true); -} - -void fuzzer_view_attack_stop(FuzzerViewAttack* view) { - furi_assert(view); - - with_view_model( - view->view, - FuzzerViewAttackModel * model, - { model->attack_state = FuzzerAttackStateOff; }, - true); -} - -void fuzzer_view_attack_pause(FuzzerViewAttack* view) { - furi_assert(view); - - with_view_model( - view->view, - FuzzerViewAttackModel * model, - { model->attack_state = FuzzerAttackStateIdle; }, - true); -} - -void fuzzer_view_attack_end(FuzzerViewAttack* view) { - furi_assert(view); - - with_view_model( - view->view, - FuzzerViewAttackModel * model, - { model->attack_state = FuzzerAttackStateEnd; }, - true); + view->view, FuzzerViewAttackModel * model, { model->attack_state = state; }, true); } void fuzzer_view_attack_set_callback( @@ -110,35 +82,31 @@ void fuzzer_view_attack_set_callback( view_attack->context = context; } -void fuzzer_view_attack_draw(Canvas* canvas, FuzzerViewAttackModel* model) { - char temp_str[50]; - - canvas_clear(canvas); - canvas_set_color(canvas, ColorBlack); - - canvas_set_font(canvas, FontPrimary); - canvas_draw_str_aligned(canvas, 64, 2, AlignCenter, AlignTop, model->attack_name); - +static void + fuzzer_view_attack_draw_time_delays_line(Canvas* canvas, FuzzerViewAttackModel* model) { + char temp_str[25]; uint16_t crt; + const uint16_t y = LINE_2_Y; + canvas_set_font(canvas, FontPrimary); if(!model->td_emt_cursor) { canvas_set_font(canvas, FontSecondary); snprintf(temp_str, sizeof(temp_str), "Time delay:"); - canvas_draw_str_aligned(canvas, LIFT_RIGHT_OFFSET, 21, AlignLeft, AlignBottom, temp_str); + canvas_draw_str_aligned(canvas, LEFT_RIGHT_OFFSET, y, AlignLeft, AlignBottom, temp_str); crt = canvas_string_width(canvas, temp_str); canvas_set_font(canvas, FontPrimary); snprintf( temp_str, sizeof(temp_str), "%d.%d", model->time_delay / 10, model->time_delay % 10); canvas_draw_str_aligned( - canvas, crt + LIFT_RIGHT_OFFSET + 3, 21, AlignLeft, AlignBottom, temp_str); + canvas, crt + LEFT_RIGHT_OFFSET + 3, y, AlignLeft, AlignBottom, temp_str); canvas_set_font(canvas, FontSecondary); snprintf( temp_str, sizeof(temp_str), "EmT: %d.%d", model->emu_time / 10, model->emu_time % 10); canvas_draw_str_aligned( - canvas, 128 - LIFT_RIGHT_OFFSET, 21, AlignRight, AlignBottom, temp_str); + canvas, 128 - LEFT_RIGHT_OFFSET, y, AlignRight, AlignBottom, temp_str); } else { canvas_set_font(canvas, FontSecondary); snprintf( @@ -148,138 +116,206 @@ void fuzzer_view_attack_draw(Canvas* canvas, FuzzerViewAttackModel* model) { model->time_delay / 10, model->time_delay % 10); - canvas_draw_str_aligned(canvas, LIFT_RIGHT_OFFSET, 21, AlignLeft, AlignBottom, temp_str); + canvas_draw_str_aligned(canvas, LEFT_RIGHT_OFFSET, y, AlignLeft, AlignBottom, temp_str); canvas_set_font(canvas, FontPrimary); snprintf(temp_str, sizeof(temp_str), "%d.%d", model->emu_time / 10, model->emu_time % 10); canvas_draw_str_aligned( - canvas, 128 - LIFT_RIGHT_OFFSET, 21, AlignRight, AlignBottom, temp_str); + canvas, 128 - LEFT_RIGHT_OFFSET, y, AlignRight, AlignBottom, temp_str); crt = canvas_string_width(canvas, temp_str); canvas_set_font(canvas, FontSecondary); snprintf(temp_str, sizeof(temp_str), "Emulation time:"); canvas_draw_str_aligned( - canvas, 128 - LIFT_RIGHT_OFFSET - crt - 3, 21, AlignRight, AlignBottom, temp_str); + canvas, 128 - LEFT_RIGHT_OFFSET - crt - 3, y, AlignRight, AlignBottom, temp_str); } +} + +static void fuzzer_view_attack_draw_time_delays_str(Canvas* canvas, FuzzerViewAttackModel* model) { + char temp_str[20]; + uint16_t crt; + const uint16_t y = LINE_2_Y; canvas_set_font(canvas, FontSecondary); - canvas_draw_str_aligned(canvas, 64, 26, AlignCenter, AlignTop, model->protocol_name); + snprintf( + temp_str, + sizeof(temp_str), + "TD: %d.%d Emt: %d.%d", + model->time_delay / 10, + model->time_delay % 10, + model->emu_time / 10, + model->emu_time % 10); + crt = canvas_string_width(canvas, temp_str); + + canvas_draw_str_aligned( + canvas, 128 - LEFT_RIGHT_OFFSET - crt, y, AlignLeft, AlignBottom, temp_str); +} + +static void fuzzer_view_attack_draw_idle(Canvas* canvas, FuzzerViewAttackModel* model) { + if(model->td_emt_cursor) { + elements_button_center(canvas, "Start"); + elements_button_left(canvas, "EmT -"); + elements_button_right(canvas, "+ EmT"); + } else { + elements_button_center(canvas, "Start"); + elements_button_left(canvas, "TD -"); + elements_button_right(canvas, "+ TD"); + } +} + +static void fuzzer_view_attack_draw_running(Canvas* canvas, FuzzerViewAttackModel* model) { + UNUSED(model); + elements_button_left(canvas, "Stop"); + elements_button_center(canvas, "Pause"); +} + +static void fuzzer_view_attack_draw_end(Canvas* canvas, FuzzerViewAttackModel* model) { + UNUSED(model); + // elements_button_center(canvas, "Restart"); // Reset + elements_button_left(canvas, "Exit"); +} + +void fuzzer_view_attack_draw(Canvas* canvas, FuzzerViewAttackModel* model) { + canvas_clear(canvas); + canvas_set_color(canvas, ColorBlack); + + // Header - Attack name + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 64, LINE_1_Y, AlignCenter, AlignBottom, model->attack_name); + + // Time delays line or Status line + switch(model->attack_state) { + case FuzzerAttackStateIdle: + fuzzer_view_attack_draw_time_delays_line(canvas, model); + break; + + case FuzzerAttackStateAttacking: + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, LEFT_RIGHT_OFFSET, LINE_2_Y, "Attacking"); + fuzzer_view_attack_draw_time_delays_str(canvas, model); + + break; + + case FuzzerAttackStateEmulating: + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 64, LINE_2_Y, AlignCenter, AlignBottom, "Emulating:"); + + break; + + case FuzzerAttackStatePause: + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, LEFT_RIGHT_OFFSET, LINE_2_Y, "Paused"); + + canvas_set_font(canvas, FontSecondary); + canvas_draw_icon_ex(canvas, 62, LINE_2_Y - 9, &I_Pin_arrow_up_7x9, IconRotation180); + canvas_draw_icon(canvas, 69, LINE_2_Y - 9, &I_Pin_arrow_up_7x9); + canvas_draw_str(canvas, 79, LINE_2_Y, "Change UID"); + break; + + case FuzzerAttackStateEnd: + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 64, LINE_2_Y, AlignCenter, AlignBottom, "Attack is over"); + + break; + + default: + break; + } + + // Protocol name + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned(canvas, 64, LINE_3_Y, AlignCenter, AlignBottom, model->protocol_name); + + // Current UID canvas_set_font(canvas, FontPrimary); if(128 < canvas_string_width(canvas, furi_string_get_cstr(model->uid_str))) { canvas_set_font(canvas, FontSecondary); } canvas_draw_str_aligned( - canvas, 64, 38, AlignCenter, AlignTop, furi_string_get_cstr(model->uid_str)); + canvas, 64, LINE_4_Y, AlignCenter, AlignBottom, furi_string_get_cstr(model->uid_str)); + // Btns canvas_set_font(canvas, FontSecondary); - if(model->attack_state == FuzzerAttackStateRunning) { - elements_button_center(canvas, "Stop"); + if(model->attack_state == FuzzerAttackStateAttacking || + model->attack_state == FuzzerAttackStateEmulating) { + fuzzer_view_attack_draw_running(canvas, model); } else if(model->attack_state == FuzzerAttackStateIdle) { - if(model->td_emt_cursor) { - elements_button_center(canvas, "Start"); - elements_button_left(canvas, "EmT -"); - elements_button_right(canvas, "+ EmT"); - } else { - elements_button_center(canvas, "Start"); - elements_button_left(canvas, "TD -"); - elements_button_right(canvas, "+ TD"); - } - + fuzzer_view_attack_draw_idle(canvas, model); + } else if(model->attack_state == FuzzerAttackStatePause) { + elements_button_left(canvas, "Back"); + elements_button_right(canvas, "Save"); + elements_button_center(canvas, "Emu"); } else if(model->attack_state == FuzzerAttackStateEnd) { - // elements_button_center(canvas, "Restart"); // Reset - elements_button_left(canvas, "Exit"); + fuzzer_view_attack_draw_end(canvas, model); } } -bool fuzzer_view_attack_input(InputEvent* event, void* context) { - furi_assert(context); - FuzzerViewAttack* view_attack = context; - +static bool fuzzer_view_attack_input_idle( + FuzzerViewAttack* view_attack, + InputEvent* event, + FuzzerViewAttackModel* model) { if(event->key == InputKeyBack && event->type == InputTypeShort) { - view_attack->callback(FuzzerCustomEventViewAttackBack, view_attack->context); + view_attack->callback(FuzzerCustomEventViewAttackExit, view_attack->context); return true; } else if(event->key == InputKeyOk && event->type == InputTypeShort) { - view_attack->callback(FuzzerCustomEventViewAttackOk, view_attack->context); + view_attack->callback(FuzzerCustomEventViewAttackRunAttack, view_attack->context); return true; } else if(event->key == InputKeyLeft) { - with_view_model( - view_attack->view, - FuzzerViewAttackModel * model, - { - if(model->attack_state == FuzzerAttackStateIdle) { - if(!model->td_emt_cursor) { - // TimeDelay -- - if(event->type == InputTypeShort) { - if(model->time_delay > model->time_delay_min) { - model->time_delay--; - } - } else if(event->type == InputTypeLong) { - if((model->time_delay - 10) >= model->time_delay_min) { - model->time_delay -= 10; - } else { - model->time_delay = model->time_delay_min; - } - } - } else { - // EmuTime -- - if(event->type == InputTypeShort) { - if(model->emu_time > model->emu_time_min) { - model->emu_time--; - } - } else if(event->type == InputTypeLong) { - if((model->emu_time - 10) >= model->emu_time_min) { - model->emu_time -= 10; - } else { - model->emu_time = model->emu_time_min; - } - } - } - } else if( - (model->attack_state == FuzzerAttackStateEnd) && - (event->type == InputTypeShort)) { - // Exit if Ended - view_attack->callback(FuzzerCustomEventViewAttackBack, view_attack->context); + if(!model->td_emt_cursor) { + // TimeDelay -- + if(event->type == InputTypeShort) { + if(model->time_delay > model->time_delay_min) { + model->time_delay--; } - }, - true); + } else if(event->type == InputTypeLong || event->type == InputTypeRepeat) { + if((model->time_delay - 10) >= model->time_delay_min) { + model->time_delay -= 10; + } else { + model->time_delay = model->time_delay_min; + } + } + } else { + // EmuTime -- + if(event->type == InputTypeShort) { + if(model->emu_time > model->emu_time_min) { + model->emu_time--; + } + } else if(event->type == InputTypeLong || event->type == InputTypeRepeat) { + if((model->emu_time - 10) >= model->emu_time_min) { + model->emu_time -= 10; + } else { + model->emu_time = model->emu_time_min; + } + } + } return true; } else if(event->key == InputKeyRight) { - with_view_model( - view_attack->view, - FuzzerViewAttackModel * model, - { - if(model->attack_state == FuzzerAttackStateIdle) { - if(!model->td_emt_cursor) { - // TimeDelay ++ - if(event->type == InputTypeShort) { - if(model->time_delay < FUZZ_TIME_DELAY_MAX) { - model->time_delay++; - } - } else if(event->type == InputTypeLong) { - model->time_delay += 10; - if(model->time_delay > FUZZ_TIME_DELAY_MAX) { - model->time_delay = FUZZ_TIME_DELAY_MAX; - } - } - } else { - // EmuTime ++ - if(event->type == InputTypeShort) { - if(model->emu_time < FUZZ_TIME_DELAY_MAX) { - model->emu_time++; - } - } else if(event->type == InputTypeLong) { - model->emu_time += 10; - if(model->emu_time > FUZZ_TIME_DELAY_MAX) { - model->emu_time = FUZZ_TIME_DELAY_MAX; - } - } - } - } else { - // Nothing + if(!model->td_emt_cursor) { + // TimeDelay ++ + if(event->type == InputTypeShort) { + if(model->time_delay < FUZZ_TIME_DELAY_MAX) { + model->time_delay++; } - }, - true); + } else if(event->type == InputTypeLong || event->type == InputTypeRepeat) { + model->time_delay += 10; + if(model->time_delay > FUZZ_TIME_DELAY_MAX) { + model->time_delay = FUZZ_TIME_DELAY_MAX; + } + } + } else { + // EmuTime ++ + if(event->type == InputTypeShort) { + if(model->emu_time < FUZZ_TIME_DELAY_MAX) { + model->emu_time++; + } + } else if(event->type == InputTypeLong || event->type == InputTypeRepeat) { + model->emu_time += 10; + if(model->emu_time > FUZZ_TIME_DELAY_MAX) { + model->emu_time = FUZZ_TIME_DELAY_MAX; + } + } + } return true; } else if( (event->key == InputKeyUp || event->key == InputKeyDown) && @@ -291,6 +327,81 @@ bool fuzzer_view_attack_input(InputEvent* event, void* context) { true); return true; } + return true; +} + +static bool fuzzer_view_attack_input_end( + FuzzerViewAttack* view_attack, + InputEvent* event, + FuzzerViewAttackModel* model) { + UNUSED(model); + if((event->key == InputKeyBack || event->key == InputKeyLeft) && + event->type == InputTypeShort) { + // Exit if Ended + view_attack->callback(FuzzerCustomEventViewAttackExit, view_attack->context); + } + return true; +} + +bool fuzzer_view_attack_input(InputEvent* event, void* context) { + furi_assert(context); + FuzzerViewAttack* view_attack = context; + + // if(event->key == InputKeyBack && event->type == InputTypeShort) { + // view_attack->callback(FuzzerCustomEventViewAttackBack, view_attack->context); + // return true; + // } else if(event->key == InputKeyOk && event->type == InputTypeShort) { + // view_attack->callback(FuzzerCustomEventViewAttackOk, view_attack->context); + // return true; + // } else + // { + with_view_model( + view_attack->view, + FuzzerViewAttackModel * model, + { + switch(model->attack_state) { + case FuzzerAttackStateIdle: + fuzzer_view_attack_input_idle(view_attack, event, model); + break; + + case FuzzerAttackStateEnd: + fuzzer_view_attack_input_end(view_attack, event, model); + break; + + case FuzzerAttackStateAttacking: + case FuzzerAttackStateEmulating: + if((event->key == InputKeyBack || event->key == InputKeyLeft) && + event->type == InputTypeShort) { + view_attack->callback(FuzzerCustomEventViewAttackIdle, view_attack->context); + } else if(event->key == InputKeyOk && event->type == InputTypeShort) { + view_attack->callback(FuzzerCustomEventViewAttackPause, view_attack->context); + } + break; + + case FuzzerAttackStatePause: + if((event->key == InputKeyBack || event->key == InputKeyLeft) && + event->type == InputTypeShort) { + view_attack->callback(FuzzerCustomEventViewAttackIdle, view_attack->context); + } else if(event->key == InputKeyRight && event->type == InputTypeShort) { + view_attack->callback(FuzzerCustomEventViewAttackSave, view_attack->context); + } else if(event->key == InputKeyOk && event->type == InputTypeShort) { + view_attack->callback( + FuzzerCustomEventViewAttackEmulateCurrent, view_attack->context); + } else if(event->key == InputKeyUp && event->type == InputTypeShort) { + view_attack->callback( + FuzzerCustomEventViewAttackPrevUid, view_attack->context); + } else if(event->key == InputKeyDown && event->type == InputTypeShort) { + view_attack->callback( + FuzzerCustomEventViewAttackNextUid, view_attack->context); + } + break; + + default: + break; + } + }, + true); + // } return true; } diff --git a/applications/external/multi_fuzzer/views/attack.h b/applications/external/multi_fuzzer/views/attack.h index 66e96d7d6..08706db71 100644 --- a/applications/external/multi_fuzzer/views/attack.h +++ b/applications/external/multi_fuzzer/views/attack.h @@ -29,13 +29,7 @@ void fuzzer_view_attack_reset_data( void fuzzer_view_attack_set_uid(FuzzerViewAttack* view, const FuzzerPayload* uid); -void fuzzer_view_attack_start(FuzzerViewAttack* view); - -void fuzzer_view_attack_stop(FuzzerViewAttack* view); - -void fuzzer_view_attack_pause(FuzzerViewAttack* view); - -void fuzzer_view_attack_end(FuzzerViewAttack* view); +void fuzzer_view_update_state(FuzzerViewAttack* view, FuzzerAttackState state); uint8_t fuzzer_view_attack_get_time_delay(FuzzerViewAttack* view); diff --git a/applications/external/subghz_remote/application.fam b/applications/external/subghz_remote/application.fam index 030ed030f..b175f8c8d 100644 --- a/applications/external/subghz_remote/application.fam +++ b/applications/external/subghz_remote/application.fam @@ -6,5 +6,6 @@ App( stack_size=2 * 1024, order=11, fap_icon="subrem_10px.png", + fap_description="SubGhz Remote, uses up to 5 .sub files", fap_category="Sub-GHz", ) diff --git a/applications/external/subghz_remote/helpers/subrem_custom_event.h b/applications/external/subghz_remote/helpers/subrem_custom_event.h index 8d93ab1fd..e6b9e8ac6 100644 --- a/applications/external/subghz_remote/helpers/subrem_custom_event.h +++ b/applications/external/subghz_remote/helpers/subrem_custom_event.h @@ -1,13 +1,27 @@ #pragma once +typedef enum { + SubRemEditMenuStateUP = 0, + SubRemEditMenuStateDOWN, + SubRemEditMenuStateLEFT, + SubRemEditMenuStateRIGHT, + SubRemEditMenuStateOK, +} SubRemEditMenuState; + typedef enum { // StartSubmenuIndex SubmenuIndexSubRemOpenMapFile = 0, + SubmenuIndexSubRemEditMapFile, + SubmenuIndexSubRemNewMapFile, #if FURI_DEBUG SubmenuIndexSubRemRemoteView, #endif // SubmenuIndexSubRemAbout, + // EditSubmenuIndex + EditSubmenuIndexEditLabel, + EditSubmenuIndexEditFile, + // SubRemCustomEvent SubRemCustomEventViewRemoteStartUP = 100, SubRemCustomEventViewRemoteStartDOWN, @@ -17,4 +31,21 @@ typedef enum { SubRemCustomEventViewRemoteBack, SubRemCustomEventViewRemoteStop, SubRemCustomEventViewRemoteForcedStop, + + SubRemCustomEventViewEditMenuBack, + SubRemCustomEventViewEditMenuUP, + SubRemCustomEventViewEditMenuDOWN, + SubRemCustomEventViewEditMenuEdit, + SubRemCustomEventViewEditMenuSave, + + SubRemCustomEventSceneEditsubmenu, + SubRemCustomEventSceneEditLabelInputDone, + SubRemCustomEventSceneEditLabelWidgetAcces, + SubRemCustomEventSceneEditLabelWidgetBack, + + SubRemCustomEventSceneEditOpenSubErrorPopup, + + SubRemCustomEventSceneEditPreviewSaved, + + SubRemCustomEventSceneNewName, } SubRemCustomEvent; \ No newline at end of file diff --git a/applications/external/subghz_remote/helpers/subrem_types.h b/applications/external/subghz_remote/helpers/subrem_types.h index b392de17e..b43f8499d 100644 --- a/applications/external/subghz_remote/helpers/subrem_types.h +++ b/applications/external/subghz_remote/helpers/subrem_types.h @@ -18,7 +18,11 @@ typedef enum { typedef enum { SubRemViewIDSubmenu, + SubRemViewIDWidget, + SubRemViewIDPopup, + SubRemViewIDTextInput, SubRemViewIDRemote, + SubRemViewIDEditMenu, } SubRemViewID; typedef enum { diff --git a/applications/external/subghz_remote/icon.png b/applications/external/subghz_remote/icon.png deleted file mode 100644 index c6b410f4c598d6b241b851826875a568c74f4d20..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5000 zcmeHLeQXow8NVhhfsmvNbWJE`yEq*mNzeCX`_4BziDT!(3JxJLO+Xvv_1$~UEw<0t zm)N1})-a|4OCbE6;07#-3sX@sAxkg$U?hnu$6%_P+PX_Jv(ud zLf1xG|CwUz?7R1Up5OC4zxVgNce*W&4YheW_vK(1mglK+H=%$1JZE+m`hA@F<1zI2 zyA8fptqH{ONK}=TAjGw<2*hDRkufZBKGgVD-U({Lf_l{mhyV1gGrhn5dOvmG&rb;X|2#O* z`;Gdu`y8=lFC6>o;MdlzEhu;(gUPC_z|AEGi;B+`t?Ze5z3-kKC+@xe!rsi0?9s|K zy?-daKNrL9+N8K#jUJb4ydqS`GmmU@)cv;7aPpz%>TUZsFY*}}-;x*iZ59ty6_jpT zvujoMQ}z8jJ+AG;!%Gj}Yq-_gD;(ypTplW&z40q}pQ&N1scCq0d(~q_cR%sbwf8Sv zdVdlA`l;oI1plLZ-jYiT3faL`zr6A#=Lo=7=AJsu{N?^-b1q)%coKW)>T~u}qi^rn z-7>H`clPEJwEVR7TGqAGdqR;5OY#pr*E?^={1s1Y&f(g=vM=|qHywW9AEyugs9|9K z_qUv^T38l3y>(BG-D_BB`RVoV^}JI09`V|mBd`AW<~wBWyCXky1n0|1Nlg+*V)QGN;EdcVFdq|MubW(V_Tn9{lz<&(!Cf?0&8A zl@E$Ck9Ky~46J|Y$whnDXUy8sU3Tos>?t>Un9|+}sNpj`p?cz$4F;W6I^yu1td=)j zwphHBH{ybAO5KJiY~Ik|6F0PrHpy5~o?}l42p|MCfG0x1a7;)zj7eMpo$JG-5l@?` zHXBJXB*PHMf{1m6HIN{}u@W63h2e%VF{(r~MGfORCh)5rn!{*B^Z0mvp@`R;h7ZTa zSU`M`2@oM^6GetXP{HeN+v@{V%k5_5e+8G zkwg*(VF;PVP*i$K$XbuLG3}vK5Kuyqq!%K4ie;ot)zny<8cCZ^NiaQ~ENpU0nj%lI zJjF+!xy>BKy>oC09YBCl_0@MKq5HSOc8#H zHxrPt@G@uPXxUw}=@F^kKtO1=<+RE`fZLx72 zM^h}%PZ&K2qcJ389h0U^tT{O|!J$hHs!^{hL5Gn|PU-6=pgIxrK<@yAog7DH3a%&w z8g!!r!BMD#C>udrd^9VVErOXZqga7TC7!lcqdrv)I*fX4xSm29%!}Gu0vbreA!k;g z%|4nF%vOQs$|!0w97;_$K_%JJIG$`y z0f?!B#blXMGE;<>npEzfp3l7GX_S~MYjF^T&H&=qVRY(yC*C;TeK^CKEcntEB`m4& z*s`e!#M_|0il0b3`57vUflm0by2LgR4nVX&k8KG5wO*Mw+x#3L%ravoDAo)K9*8VK z`z@R#$`oXol=A*h>SY-g(0){rAj zX|i`eX-Qc`CULv;$ClJi>UW`W?b^xP)oq_>=<%(|iFP_&{;^5&uL6OoA}L2u$;}G@ zRN$B(Zj5Yq}83M;=f=r9w8MiVD2l{4`U z0fy0oX&k*FI5pSMi{36|`Ri-l*r@*9d2H`fXk<>LZgmX9OeOkpSK|4KPBfUUdA!xx z?`7r}mfscene_manager, SubRemSceneStart); - if(load_state == SubRemLoadMapStateOK || load_state == SubRemLoadMapStateNotAllOK) { + if(load_state == SubRemLoadMapStateBack) { + scene_manager_previous_scene(app->scene_manager); + } else if(start_scene_state == SubmenuIndexSubRemEditMapFile) { + scene_manager_set_scene_state(app->scene_manager, SubRemSceneEditMenu, SubRemSubKeyNameUp); + scene_manager_next_scene(app->scene_manager, SubRemSceneEditMenu); + } else if(start_scene_state == SubmenuIndexSubRemOpenMapFile) { scene_manager_next_scene(app->scene_manager, SubRemSceneRemote); - } else { - if(load_state != SubRemLoadMapStateBack) { -#ifdef SUBREM_LIGHT - dialog_message_show_storage_error(app->dialogs, "Can't load\nMap file"); -#else - DialogMessage* message = dialog_message_alloc(); - - dialog_message_set_header(message, "Map File Error", 64, 8, AlignCenter, AlignCenter); - dialog_message_set_text( - message, "Can't load\nMap file", 64, 32, AlignCenter, AlignCenter); - dialog_message_set_buttons(message, "Back", NULL, NULL); - dialog_message_show(app->dialogs, message); - - dialog_message_free(message); -#endif - } - // TODO: Map Preset Reset - if(!scene_manager_previous_scene(app->scene_manager)) { - scene_manager_stop(app->scene_manager); - view_dispatcher_stop(app->view_dispatcher); - } } } diff --git a/applications/external/subghz_remote_configurator/scenes/subrem_scene_open_sub_file.c b/applications/external/subghz_remote/scenes/subrem_scene_open_sub_file.c similarity index 100% rename from applications/external/subghz_remote_configurator/scenes/subrem_scene_open_sub_file.c rename to applications/external/subghz_remote/scenes/subrem_scene_open_sub_file.c diff --git a/applications/external/subghz_remote/scenes/subrem_scene_remote.c b/applications/external/subghz_remote/scenes/subrem_scene_remote.c index ebc582991..e8d57dae7 100644 --- a/applications/external/subghz_remote/scenes/subrem_scene_remote.c +++ b/applications/external/subghz_remote/scenes/subrem_scene_remote.c @@ -39,6 +39,9 @@ void subrem_scene_remote_on_enter(void* context) { SubGhzRemoteApp* app = context; subrem_view_remote_update_data_labels(app->subrem_remote_view, app->map_preset->subs_preset); + subrem_view_remote_set_radio( + app->subrem_remote_view, + subghz_txrx_radio_device_get(app->txrx) != SubGhzRadioDeviceTypeInternal); subrem_view_remote_set_callback(app->subrem_remote_view, subrem_scene_remote_callback, app); diff --git a/applications/external/subghz_remote/scenes/subrem_scene_start.c b/applications/external/subghz_remote/scenes/subrem_scene_start.c index e780b54ce..0f3399b7c 100644 --- a/applications/external/subghz_remote/scenes/subrem_scene_start.c +++ b/applications/external/subghz_remote/scenes/subrem_scene_start.c @@ -27,16 +27,28 @@ void subrem_scene_start_on_enter(void* context) { subrem_scene_start_submenu_callback, app); #endif + submenu_add_item( + submenu, + "Edit Map File", + SubmenuIndexSubRemEditMapFile, + subrem_scene_start_submenu_callback, + app); + submenu_add_item( + submenu, + "New Map File", + SubmenuIndexSubRemNewMapFile, + subrem_scene_start_submenu_callback, + app); // submenu_add_item( // submenu, // "About", // SubmenuIndexSubGhzRemoteAbout, // subrem_scene_start_submenu_callback, // app); -#ifndef SUBREM_LIGHT + submenu_set_selected_item( submenu, scene_manager_get_scene_state(app->scene_manager, SubRemSceneStart)); -#endif + view_dispatcher_switch_to_view(app->view_dispatcher, SubRemViewIDSubmenu); } @@ -48,23 +60,33 @@ bool subrem_scene_start_on_event(void* context, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { if(event.event == SubmenuIndexSubRemOpenMapFile) { -#ifndef SUBREM_LIGHT scene_manager_set_scene_state( app->scene_manager, SubRemSceneStart, SubmenuIndexSubRemOpenMapFile); -#endif + scene_manager_next_scene(app->scene_manager, SubRemSceneOpenMapFile); consumed = true; } - // } else if(event.event == SubmenuIndexSubRemAbout) { - // scene_manager_next_scene(app->scene_manager, SubRemSceneAbout); - // consumed = true; - // } #if FURI_DEBUG else if(event.event == SubmenuIndexSubRemRemoteView) { scene_manager_next_scene(app->scene_manager, SubRemSceneRemote); consumed = true; } #endif + else if(event.event == SubmenuIndexSubRemEditMapFile) { + scene_manager_set_scene_state( + app->scene_manager, SubRemSceneStart, SubmenuIndexSubRemEditMapFile); + scene_manager_next_scene(app->scene_manager, SubRemSceneOpenMapFile); + consumed = true; + } else if(event.event == SubmenuIndexSubRemNewMapFile) { + scene_manager_set_scene_state( + app->scene_manager, SubRemSceneStart, SubmenuIndexSubRemNewMapFile); + scene_manager_next_scene(app->scene_manager, SubRemSceneEnterNewName); + consumed = true; + } + // } else if(event.event == SubmenuIndexSubRemAbout) { + // scene_manager_next_scene(app->scene_manager, SubRemSceneAbout); + // consumed = true; + // } } return consumed; diff --git a/applications/external/subghz_remote/subghz_remote_app.c b/applications/external/subghz_remote/subghz_remote_app.c index 1f04ffc08..1af39f57d 100644 --- a/applications/external/subghz_remote/subghz_remote_app.c +++ b/applications/external/subghz_remote/subghz_remote_app.c @@ -1,5 +1,4 @@ #include "subghz_remote_app_i.h" -#include static bool subghz_remote_app_custom_event_callback(void* context, uint32_t event) { furi_assert(context); @@ -19,26 +18,23 @@ static void subghz_remote_app_tick_event_callback(void* context) { scene_manager_handle_tick_event(app->scene_manager); } -SubGhzRemoteApp* subghz_remote_app_alloc(char* p) { - SubGhzRemoteApp* app = malloc(sizeof(SubGhzRemoteApp)); +static void subghz_remote_make_app_folder(SubGhzRemoteApp* app) { + furi_assert(app); Storage* storage = furi_record_open(RECORD_STORAGE); + + // Migrate old users data storage_common_migrate(storage, EXT_PATH("unirf"), SUBREM_APP_FOLDER); - storage_common_migrate(storage, EXT_PATH("subghz/unirf"), SUBREM_APP_FOLDER); if(!storage_simply_mkdir(storage, SUBREM_APP_FOLDER)) { - //FURI_LOG_E(TAG, "Could not create folder %s", SUBREM_APP_FOLDER); + // FURI_LOG_E(TAG, "Could not create folder %s", SUBREM_APP_FOLDER); + dialog_message_show_storage_error(app->dialogs, "Cannot create\napp folder"); } furi_record_close(RECORD_STORAGE); +} - // // Enable power for External CC1101 if it is connected - // furi_hal_subghz_enable_ext_power(); - // // Auto switch to internal radio if external radio is not available - // furi_delay_ms(15); - // if(!furi_hal_subghz_check_radio()) { - // furi_hal_subghz_select_radio_type(SubGhzRadioInternal); - // furi_hal_subghz_init_radio_type(SubGhzRadioInternal); - // } +SubGhzRemoteApp* subghz_remote_app_alloc() { + SubGhzRemoteApp* app = malloc(sizeof(SubGhzRemoteApp)); furi_hal_power_suppress_charge_enter(); @@ -75,6 +71,20 @@ SubGhzRemoteApp* subghz_remote_app_alloc(char* p) { // Dialog app->dialogs = furi_record_open(RECORD_DIALOGS); + // TextInput + app->text_input = text_input_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, SubRemViewIDTextInput, text_input_get_view(app->text_input)); + + // Widget + app->widget = widget_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, SubRemViewIDWidget, widget_get_view(app->widget)); + + // Popup + app->popup = popup_alloc(); + view_dispatcher_add_view(app->view_dispatcher, SubRemViewIDPopup, popup_get_view(app->popup)); + // Remote view app->subrem_remote_view = subrem_view_remote_alloc(); view_dispatcher_add_view( @@ -82,6 +92,13 @@ SubGhzRemoteApp* subghz_remote_app_alloc(char* p) { SubRemViewIDRemote, subrem_view_remote_get_view(app->subrem_remote_view)); + // Edit Menu view + app->subrem_edit_menu = subrem_view_edit_menu_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + SubRemViewIDEditMenu, + subrem_view_edit_menu_get_view(app->subrem_edit_menu)); + app->map_preset = malloc(sizeof(SubRemMapPreset)); for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) { app->map_preset->subs_preset[i] = subrem_sub_file_preset_alloc(); @@ -91,19 +108,7 @@ SubGhzRemoteApp* subghz_remote_app_alloc(char* p) { subghz_txrx_set_need_save_callback(app->txrx, subrem_save_active_sub, app); - if(p && strlen(p)) { - furi_string_set(app->file_path, p); - subrem_map_file_load(app, furi_string_get_cstr(app->file_path)); - scene_manager_next_scene(app->scene_manager, SubRemSceneRemote); - } else { -#ifdef SUBREM_LIGHT - scene_manager_next_scene(app->scene_manager, SubRemSceneOpenMapFile); -#else - scene_manager_next_scene(app->scene_manager, SubRemSceneStart); - scene_manager_set_scene_state( - app->scene_manager, SubRemSceneStart, SubmenuIndexSubRemOpenMapFile); -#endif - } + app->map_not_saved = false; return app; } @@ -113,11 +118,6 @@ void subghz_remote_app_free(SubGhzRemoteApp* app) { furi_hal_power_suppress_charge_exit(); - // // Disable power for External CC1101 if it was enabled and module is connected - // furi_hal_subghz_disable_ext_power(); - // // Reinit SPI handles for internal radio / nfc - // furi_hal_subghz_init_radio_type(SubGhzRadioInternal); - // Submenu view_dispatcher_remove_view(app->view_dispatcher, SubRemViewIDSubmenu); submenu_free(app->submenu); @@ -125,10 +125,26 @@ void subghz_remote_app_free(SubGhzRemoteApp* app) { // Dialog furi_record_close(RECORD_DIALOGS); + // TextInput + view_dispatcher_remove_view(app->view_dispatcher, SubRemViewIDTextInput); + text_input_free(app->text_input); + + // Widget + view_dispatcher_remove_view(app->view_dispatcher, SubRemViewIDWidget); + widget_free(app->widget); + + // Popup + view_dispatcher_remove_view(app->view_dispatcher, SubRemViewIDPopup); + popup_free(app->popup); + // Remote view view_dispatcher_remove_view(app->view_dispatcher, SubRemViewIDRemote); subrem_view_remote_free(app->subrem_remote_view); + // Edit view + view_dispatcher_remove_view(app->view_dispatcher, SubRemViewIDEditMenu); + subrem_view_edit_menu_free(app->subrem_edit_menu); + scene_manager_free(app->scene_manager); view_dispatcher_free(app->view_dispatcher); @@ -152,10 +168,33 @@ void subghz_remote_app_free(SubGhzRemoteApp* app) { free(app); } -int32_t subghz_remote_app(char* p) { - UNUSED(p); - dolphin_deed(DolphinDeedPluginStart); - SubGhzRemoteApp* subghz_remote_app = subghz_remote_app_alloc(p); +int32_t subghz_remote_app(void* arg) { + SubGhzRemoteApp* subghz_remote_app = subghz_remote_app_alloc(); + + subghz_remote_make_app_folder(subghz_remote_app); + + bool map_loaded = false; + + if((arg != NULL) && (strlen(arg) != 0)) { + furi_string_set(subghz_remote_app->file_path, (const char*)arg); + SubRemLoadMapState load_state = subrem_map_file_load( + subghz_remote_app, furi_string_get_cstr(subghz_remote_app->file_path)); + + if(load_state == SubRemLoadMapStateOK || load_state == SubRemLoadMapStateNotAllOK) { + map_loaded = true; + } else { + // TODO Replace + dialog_message_show_storage_error(subghz_remote_app->dialogs, "Cannot load\nmap file"); + } + } + + if(map_loaded) { + scene_manager_next_scene(subghz_remote_app->scene_manager, SubRemSceneRemote); + } else { + furi_string_set(subghz_remote_app->file_path, SUBREM_APP_FOLDER); + scene_manager_next_scene(subghz_remote_app->scene_manager, SubRemSceneStart); + scene_manager_next_scene(subghz_remote_app->scene_manager, SubRemSceneOpenMapFile); + } view_dispatcher_run(subghz_remote_app->view_dispatcher); diff --git a/applications/external/subghz_remote/subghz_remote_app_i.c b/applications/external/subghz_remote/subghz_remote_app_i.c index 26659ccb1..82e762c2a 100644 --- a/applications/external/subghz_remote/subghz_remote_app_i.c +++ b/applications/external/subghz_remote/subghz_remote_app_i.c @@ -7,10 +7,8 @@ // #include // #include -#ifdef APP_SUBGHZREMOTE #include #include -#endif #define TAG "SubGhzRemote" @@ -22,7 +20,7 @@ static const char* map_file_labels[SubRemSubKeyNameMaxCount][2] = { [SubRemSubKeyNameOk] = {"OK", "OKLABEL"}, }; -static void subrem_map_preset_reset(SubRemMapPreset* map_preset) { +void subrem_map_preset_reset(SubRemMapPreset* map_preset) { furi_assert(map_preset); for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) { @@ -228,9 +226,7 @@ bool subrem_tx_start_sub(SubGhzRemoteApp* app, SubRemSubFilePreset* sub_preset) NULL, 0); -#ifdef APP_SUBGHZREMOTE subghz_custom_btns_reset(); -#endif if(subghz_txrx_tx_start(app->txrx, sub_preset->fff_data) == SubGhzTxRxStartTxStateOk) { ret = true; @@ -246,12 +242,12 @@ bool subrem_tx_stop_sub(SubGhzRemoteApp* app, bool forced) { if(forced || (sub_preset->type != SubGhzProtocolTypeRAW)) { subghz_txrx_stop(app->txrx); -#ifdef APP_SUBGHZREMOTE + if(sub_preset->type == SubGhzProtocolTypeDynamic) { subghz_txrx_reset_dynamic_and_custom_btns(app->txrx); } subghz_custom_btns_reset(); -#endif + return true; } @@ -278,3 +274,47 @@ SubRemLoadMapState subrem_load_from_file(SubGhzRemoteApp* app) { return ret; } + +bool subrem_save_map_to_file(SubGhzRemoteApp* app) { + furi_assert(app); + + const char* file_name = furi_string_get_cstr(app->file_path); + bool saved = false; + FlipperFormat* fff_data = flipper_format_string_alloc(); + + SubRemSubFilePreset* sub_preset; + + flipper_format_write_header_cstr( + fff_data, SUBREM_APP_APP_FILE_TYPE, SUBREM_APP_APP_FILE_VERSION); + for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) { + sub_preset = app->map_preset->subs_preset[i]; + if(!furi_string_empty(sub_preset->file_path)) { + flipper_format_write_string(fff_data, map_file_labels[i][0], sub_preset->file_path); + } + } + for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) { + sub_preset = app->map_preset->subs_preset[i]; + if(!furi_string_empty(sub_preset->file_path)) { + flipper_format_write_string(fff_data, map_file_labels[i][1], sub_preset->label); + } + } + + Storage* storage = furi_record_open(RECORD_STORAGE); + Stream* flipper_format_stream = flipper_format_get_raw_stream(fff_data); + + do { + if(!storage_simply_remove(storage, file_name)) { + break; + } + //ToDo check Write + stream_seek(flipper_format_stream, 0, StreamOffsetFromStart); + stream_save_to_file(flipper_format_stream, storage, file_name, FSOM_CREATE_ALWAYS); + + saved = true; + } while(0); + + furi_record_close(RECORD_STORAGE); + flipper_format_free(fff_data); + + return saved; +} \ No newline at end of file diff --git a/applications/external/subghz_remote/subghz_remote_app_i.h b/applications/external/subghz_remote/subghz_remote_app_i.h index fbcc55111..42e9576fd 100644 --- a/applications/external/subghz_remote/subghz_remote_app_i.h +++ b/applications/external/subghz_remote/subghz_remote_app_i.h @@ -1,8 +1,5 @@ #pragma once -#define SUBREM_LIGHT 1 -#define APP_SUBGHZREMOTE 1 - #include "helpers/subrem_types.h" #include "helpers/subrem_presets.h" #include "scenes/subrem_scene.h" @@ -12,6 +9,7 @@ #include #include "views/remote.h" +#include "views/edit_menu.h" #include #include @@ -35,16 +33,23 @@ typedef struct { SceneManager* scene_manager; NotificationApp* notifications; DialogsApp* dialogs; + Widget* widget; + Popup* popup; + TextInput* text_input; Submenu* submenu; FuriString* file_path; + char file_name_tmp[SUBREM_MAX_LEN_NAME]; SubRemViewRemote* subrem_remote_view; + SubRemViewEditMenu* subrem_edit_menu; SubRemMapPreset* map_preset; SubGhzTxRx* txrx; + bool map_not_saved; + uint8_t chusen_sub; } SubGhzRemoteApp; @@ -54,6 +59,10 @@ bool subrem_tx_start_sub(SubGhzRemoteApp* app, SubRemSubFilePreset* sub_preset); bool subrem_tx_stop_sub(SubGhzRemoteApp* app, bool forced); -void subrem_save_active_sub(void* context); - SubRemLoadMapState subrem_map_file_load(SubGhzRemoteApp* app, const char* file_path); + +void subrem_map_preset_reset(SubRemMapPreset* map_preset); + +bool subrem_save_map_to_file(SubGhzRemoteApp* app); + +void subrem_save_active_sub(void* context); diff --git a/applications/external/subghz_remote_configurator/views/edit_menu.c b/applications/external/subghz_remote/views/edit_menu.c similarity index 100% rename from applications/external/subghz_remote_configurator/views/edit_menu.c rename to applications/external/subghz_remote/views/edit_menu.c diff --git a/applications/external/subghz_remote_configurator/views/edit_menu.h b/applications/external/subghz_remote/views/edit_menu.h similarity index 100% rename from applications/external/subghz_remote_configurator/views/edit_menu.h rename to applications/external/subghz_remote/views/edit_menu.h diff --git a/applications/external/subghz_remote/views/remote.c b/applications/external/subghz_remote/views/remote.c index c2b41cfd6..fc7608624 100644 --- a/applications/external/subghz_remote/views/remote.c +++ b/applications/external/subghz_remote/views/remote.c @@ -22,6 +22,7 @@ typedef struct { SubRemViewRemoteState state; uint8_t pressed_btn; + bool is_external; } SubRemViewRemoteModel; void subrem_view_remote_set_callback( @@ -106,6 +107,15 @@ void subrem_view_remote_set_state( true); } +void subrem_view_remote_set_radio(SubRemViewRemote* subrem_view_remote, bool external) { + furi_assert(subrem_view_remote); + with_view_model( + subrem_view_remote->view, + SubRemViewRemoteModel * model, + { model->is_external = external; }, + true); +} + void subrem_view_remote_draw(Canvas* canvas, SubRemViewRemoteModel* model) { canvas_clear(canvas); canvas_set_color(canvas, ColorBlack); @@ -143,6 +153,8 @@ void subrem_view_remote_draw(Canvas* canvas, SubRemViewRemoteModel* model) { elements_button_right(canvas, "Save"); } else { canvas_draw_str_aligned(canvas, 11, 62, AlignLeft, AlignBottom, "Hold=Exit."); + canvas_draw_str_aligned( + canvas, 126, 62, AlignRight, AlignBottom, ((model->is_external) ? "Ext" : "Int")); } //Status text and indicator @@ -267,6 +279,7 @@ SubRemViewRemote* subrem_view_remote_alloc() { } model->pressed_btn = 0; + model->is_external = false; }, true); return subrem_view_remote; diff --git a/applications/external/subghz_remote/views/remote.h b/applications/external/subghz_remote/views/remote.h index 5b1e8153a..39f9a007d 100644 --- a/applications/external/subghz_remote/views/remote.h +++ b/applications/external/subghz_remote/views/remote.h @@ -33,4 +33,6 @@ void subrem_view_remote_update_data_labels( void subrem_view_remote_set_state( SubRemViewRemote* subrem_view_remote, SubRemViewRemoteState state, - uint8_t presed_btn); \ No newline at end of file + uint8_t presed_btn); + +void subrem_view_remote_set_radio(SubRemViewRemote* subrem_view_remote, bool external); \ No newline at end of file diff --git a/applications/external/subghz_remote_configurator/application.fam b/applications/external/subghz_remote_configurator/application.fam deleted file mode 100644 index 29b7ed082..000000000 --- a/applications/external/subghz_remote_configurator/application.fam +++ /dev/null @@ -1,15 +0,0 @@ -App( - appid="subrem_configurator", - name="Remote Maker", - apptype=FlipperAppType.EXTERNAL, - entry_point="subghz_remote_config_app", - requires=[ - "gui", - "dialogs", - ], - stack_size=2 * 1024, - order=50, - fap_description="File Editor for the SubGhz Remote app", - fap_category="Sub-GHz", - fap_icon="subrem_10px.png", -) diff --git a/applications/external/subghz_remote_configurator/helpers/subrem_custom_event.h b/applications/external/subghz_remote_configurator/helpers/subrem_custom_event.h deleted file mode 100644 index da3de2aae..000000000 --- a/applications/external/subghz_remote_configurator/helpers/subrem_custom_event.h +++ /dev/null @@ -1,50 +0,0 @@ -#pragma once - -typedef enum { - SubRemEditMenuStateUP = 0, - SubRemEditMenuStateDOWN, - SubRemEditMenuStateLEFT, - SubRemEditMenuStateRIGHT, - SubRemEditMenuStateOK, -} SubRemEditMenuState; - -typedef enum { - // StartSubmenuIndex - SubmenuIndexSubRemEditMapFile = 0, - SubmenuIndexSubRemNewMapFile, -#if FURI_DEBUG - SubmenuIndexSubRemRemoteView, -#endif - // SubmenuIndexSubRemAbout, - - // EditSubmenuIndex - EditSubmenuIndexEditLabel, - EditSubmenuIndexEditFile, - - // SubRemCustomEvent - SubRemCustomEventViewRemoteStartUP = 100, - SubRemCustomEventViewRemoteStartDOWN, - SubRemCustomEventViewRemoteStartLEFT, - SubRemCustomEventViewRemoteStartRIGHT, - SubRemCustomEventViewRemoteStartOK, - SubRemCustomEventViewRemoteBack, - SubRemCustomEventViewRemoteStop, - SubRemCustomEventViewRemoteForcedStop, - - SubRemCustomEventViewEditMenuBack, - SubRemCustomEventViewEditMenuUP, - SubRemCustomEventViewEditMenuDOWN, - SubRemCustomEventViewEditMenuEdit, - SubRemCustomEventViewEditMenuSave, - - SubRemCustomEventSceneEditsubmenu, - SubRemCustomEventSceneEditLabelInputDone, - SubRemCustomEventSceneEditLabelWidgetAcces, - SubRemCustomEventSceneEditLabelWidgetBack, - - SubRemCustomEventSceneEditOpenSubErrorPopup, - - SubRemCustomEventSceneEditPreviewSaved, - - SubRemCustomEventSceneNewName, -} SubRemCustomEvent; \ No newline at end of file diff --git a/applications/external/subghz_remote_configurator/helpers/subrem_presets.c b/applications/external/subghz_remote_configurator/helpers/subrem_presets.c deleted file mode 100644 index 75ced8e00..000000000 --- a/applications/external/subghz_remote_configurator/helpers/subrem_presets.c +++ /dev/null @@ -1,181 +0,0 @@ -#include "subrem_presets.h" - -#define TAG "SubRemPresets" - -SubRemSubFilePreset* subrem_sub_file_preset_alloc() { - SubRemSubFilePreset* sub_preset = malloc(sizeof(SubRemSubFilePreset)); - - sub_preset->fff_data = flipper_format_string_alloc(); - sub_preset->file_path = furi_string_alloc(); - sub_preset->protocaol_name = furi_string_alloc(); - sub_preset->label = furi_string_alloc(); - - sub_preset->freq_preset.name = furi_string_alloc(); - - sub_preset->type = SubGhzProtocolTypeUnknown; - sub_preset->load_state = SubRemLoadSubStateNotSet; - - return sub_preset; -} - -void subrem_sub_file_preset_free(SubRemSubFilePreset* sub_preset) { - furi_assert(sub_preset); - - furi_string_free(sub_preset->label); - furi_string_free(sub_preset->protocaol_name); - furi_string_free(sub_preset->file_path); - flipper_format_free(sub_preset->fff_data); - - furi_string_free(sub_preset->freq_preset.name); - - free(sub_preset); -} - -void subrem_sub_file_preset_reset(SubRemSubFilePreset* sub_preset) { - furi_assert(sub_preset); - - furi_string_set_str(sub_preset->label, ""); - furi_string_reset(sub_preset->protocaol_name); - furi_string_reset(sub_preset->file_path); - - Stream* fff_data_stream = flipper_format_get_raw_stream(sub_preset->fff_data); - stream_clean(fff_data_stream); - - sub_preset->type = SubGhzProtocolTypeUnknown; - sub_preset->load_state = SubRemLoadSubStateNotSet; -} - -SubRemLoadSubState subrem_sub_preset_load( - SubRemSubFilePreset* sub_preset, - SubGhzTxRx* txrx, - FlipperFormat* fff_data_file) { - furi_assert(sub_preset); - furi_assert(txrx); - furi_assert(fff_data_file); - - Stream* fff_data_stream = flipper_format_get_raw_stream(sub_preset->fff_data); - - SubRemLoadSubState ret; - FuriString* temp_str = furi_string_alloc(); - uint32_t temp_data32; - uint32_t repeat = 200; - - ret = SubRemLoadSubStateError; - - do { - stream_clean(fff_data_stream); - if(!flipper_format_read_header(fff_data_file, temp_str, &temp_data32)) { - FURI_LOG_E(TAG, "Missing or incorrect header"); - break; - } - - if(((!strcmp(furi_string_get_cstr(temp_str), SUBGHZ_KEY_FILE_TYPE)) || - (!strcmp(furi_string_get_cstr(temp_str), SUBGHZ_RAW_FILE_TYPE))) && - temp_data32 == SUBGHZ_KEY_FILE_VERSION) { - } else { - FURI_LOG_E(TAG, "Type or version mismatch"); - break; - } - - SubGhzSetting* setting = subghz_txrx_get_setting(txrx); - - //Load frequency or using default from settings - ret = SubRemLoadSubStateErrorFreq; - if(!flipper_format_read_uint32(fff_data_file, "Frequency", &temp_data32, 1)) { - FURI_LOG_W(TAG, "Cannot read frequency. Set default frequency"); - sub_preset->freq_preset.frequency = subghz_setting_get_default_frequency(setting); - } else if(!subghz_txrx_radio_device_is_frequecy_valid(txrx, temp_data32)) { - FURI_LOG_E(TAG, "Frequency not supported on chosen radio module"); - break; - } - sub_preset->freq_preset.frequency = temp_data32; - - //Load preset - ret = SubRemLoadSubStateErrorMod; - if(!flipper_format_read_string(fff_data_file, "Preset", temp_str)) { - FURI_LOG_E(TAG, "Missing Preset"); - break; - } - - furi_string_set_str( - temp_str, subghz_txrx_get_preset_name(txrx, furi_string_get_cstr(temp_str))); - if(!strcmp(furi_string_get_cstr(temp_str), "")) { - break; - } - - if(!strcmp(furi_string_get_cstr(temp_str), "CUSTOM")) { - FURI_LOG_E(TAG, "CUSTOM preset is not supported"); - break; - // TODO Custom preset loading logic if need - // sub_preset->freq_preset.preset_index = - // subghz_setting_get_inx_preset_by_name(setting, furi_string_get_cstr(temp_str)); - } - - furi_string_set(sub_preset->freq_preset.name, temp_str); - - // Load protocol - ret = SubRemLoadSubStateErrorProtocol; - if(!flipper_format_read_string(fff_data_file, "Protocol", temp_str)) { - FURI_LOG_E(TAG, "Missing Protocol"); - break; - } - - FlipperFormat* fff_data = sub_preset->fff_data; - if(!strcmp(furi_string_get_cstr(temp_str), "RAW")) { - //if RAW - subghz_protocol_raw_gen_fff_data( - fff_data, - furi_string_get_cstr(sub_preset->file_path), - subghz_txrx_radio_device_get_name(txrx)); - } else { - stream_copy_full( - flipper_format_get_raw_stream(fff_data_file), - flipper_format_get_raw_stream(fff_data)); - } - - if(subghz_txrx_load_decoder_by_name_protocol(txrx, furi_string_get_cstr(temp_str))) { - SubGhzProtocolStatus status = - subghz_protocol_decoder_base_deserialize(subghz_txrx_get_decoder(txrx), fff_data); - if(status != SubGhzProtocolStatusOk) { - break; - } - } else { - FURI_LOG_E(TAG, "Protocol not found"); - break; - } - - const SubGhzProtocol* protocol = subghz_txrx_get_decoder(txrx)->protocol; - - if(protocol->flag & SubGhzProtocolFlag_Send) { - if((protocol->type == SubGhzProtocolTypeStatic) || - (protocol->type == SubGhzProtocolTypeDynamic) || - (protocol->type == SubGhzProtocolTypeBinRAW) || - (protocol->type == SubGhzProtocolTypeRAW)) { - sub_preset->type = protocol->type; - } else { - FURI_LOG_E(TAG, "Unsuported Protocol"); - break; - } - - furi_string_set(sub_preset->protocaol_name, temp_str); - } else { - FURI_LOG_E(TAG, "Protocol does not support transmission"); - break; - } - - if(!flipper_format_insert_or_update_uint32(fff_data, "Repeat", &repeat, 1)) { - FURI_LOG_E(TAG, "Unable Repeat"); - break; - } - - ret = SubRemLoadSubStateOK; - -#if FURI_DEBUG - FURI_LOG_I(TAG, "%-16s - protocol Loaded", furi_string_get_cstr(sub_preset->label)); -#endif - } while(false); - - furi_string_free(temp_str); - sub_preset->load_state = ret; - return ret; -} diff --git a/applications/external/subghz_remote_configurator/helpers/subrem_presets.h b/applications/external/subghz_remote_configurator/helpers/subrem_presets.h deleted file mode 100644 index d66181b90..000000000 --- a/applications/external/subghz_remote_configurator/helpers/subrem_presets.h +++ /dev/null @@ -1,39 +0,0 @@ -#pragma once - -#include "subrem_types.h" -#include "txrx/subghz_txrx.h" - -#include -#include - -typedef struct { - FuriString* name; - uint32_t frequency; - // size_t preset_index; // Need for custom preset -} FreqPreset; - -// Sub File preset -typedef struct { - FlipperFormat* fff_data; - FreqPreset freq_preset; - FuriString* file_path; - FuriString* protocaol_name; - FuriString* label; - SubGhzProtocolType type; - SubRemLoadSubState load_state; -} SubRemSubFilePreset; - -typedef struct { - SubRemSubFilePreset* subs_preset[SubRemSubKeyNameMaxCount]; -} SubRemMapPreset; - -SubRemSubFilePreset* subrem_sub_file_preset_alloc(); - -void subrem_sub_file_preset_free(SubRemSubFilePreset* sub_preset); - -void subrem_sub_file_preset_reset(SubRemSubFilePreset* sub_preset); - -SubRemLoadSubState subrem_sub_preset_load( - SubRemSubFilePreset* sub_preset, - SubGhzTxRx* txrx, - FlipperFormat* fff_data_file); diff --git a/applications/external/subghz_remote_configurator/helpers/subrem_types.h b/applications/external/subghz_remote_configurator/helpers/subrem_types.h deleted file mode 100644 index b43f8499d..000000000 --- a/applications/external/subghz_remote_configurator/helpers/subrem_types.h +++ /dev/null @@ -1,48 +0,0 @@ -#pragma once - -#include -#include - -#define SUBREM_APP_APP_FILE_VERSION 1 -#define SUBREM_APP_APP_FILE_TYPE "Flipper SubRem Map file" -#define SUBREM_APP_EXTENSION ".txt" - -typedef enum { - SubRemSubKeyNameUp = (0U), - SubRemSubKeyNameDown, - SubRemSubKeyNameLeft, - SubRemSubKeyNameRight, - SubRemSubKeyNameOk, - SubRemSubKeyNameMaxCount, -} SubRemSubKeyName; - -typedef enum { - SubRemViewIDSubmenu, - SubRemViewIDWidget, - SubRemViewIDPopup, - SubRemViewIDTextInput, - SubRemViewIDRemote, - SubRemViewIDEditMenu, -} SubRemViewID; - -typedef enum { - SubRemLoadSubStateNotSet = 0, - SubRemLoadSubStatePreloaded, - SubRemLoadSubStateError, - SubRemLoadSubStateErrorIncorectPath, - SubRemLoadSubStateErrorNoFile, - SubRemLoadSubStateErrorFreq, - SubRemLoadSubStateErrorMod, - SubRemLoadSubStateErrorProtocol, - SubRemLoadSubStateOK, -} SubRemLoadSubState; - -typedef enum { - SubRemLoadMapStateBack = 0, - SubRemLoadMapStateError, - SubRemLoadMapStateErrorOpenError, - SubRemLoadMapStateErrorStorage, - SubRemLoadMapStateErrorBrokenFile, - SubRemLoadMapStateNotAllOK, - SubRemLoadMapStateOK, -} SubRemLoadMapState; \ No newline at end of file diff --git a/applications/external/subghz_remote_configurator/helpers/txrx/subghz_txrx.c b/applications/external/subghz_remote_configurator/helpers/txrx/subghz_txrx.c deleted file mode 100644 index db485a2aa..000000000 --- a/applications/external/subghz_remote_configurator/helpers/txrx/subghz_txrx.c +++ /dev/null @@ -1,670 +0,0 @@ -#include "subghz_txrx_i.h" - -#include -#include -#include - -#include - -#define TAG "SubGhz" - -static void subghz_txrx_radio_device_power_on(SubGhzTxRx* instance) { - UNUSED(instance); - uint8_t attempts = 0; - while(!furi_hal_power_is_otg_enabled() && attempts++ < 5) { - furi_hal_power_enable_otg(); - //CC1101 power-up time - furi_delay_ms(10); - } -} - -static void subghz_txrx_radio_device_power_off(SubGhzTxRx* instance) { - UNUSED(instance); - if(furi_hal_power_is_otg_enabled()) furi_hal_power_disable_otg(); -} - -SubGhzTxRx* subghz_txrx_alloc() { - SubGhzTxRx* instance = malloc(sizeof(SubGhzTxRx)); - instance->setting = subghz_setting_alloc(); - subghz_setting_load(instance->setting, EXT_PATH("subghz/assets/setting_user")); - - instance->preset = malloc(sizeof(SubGhzRadioPreset)); - instance->preset->name = furi_string_alloc(); - subghz_txrx_set_preset( - instance, "AM650", subghz_setting_get_default_frequency(instance->setting), NULL, 0); - - instance->txrx_state = SubGhzTxRxStateSleep; - - subghz_txrx_hopper_set_state(instance, SubGhzHopperStateOFF); - subghz_txrx_speaker_set_state(instance, SubGhzSpeakerStateDisable); - subghz_txrx_set_debug_pin_state(instance, false); - - instance->worker = subghz_worker_alloc(); - instance->fff_data = flipper_format_string_alloc(); - - instance->environment = subghz_environment_alloc(); - instance->is_database_loaded = - subghz_environment_load_keystore(instance->environment, SUBGHZ_KEYSTORE_DIR_NAME); - subghz_environment_load_keystore(instance->environment, SUBGHZ_KEYSTORE_DIR_USER_NAME); - subghz_environment_set_came_atomo_rainbow_table_file_name( - instance->environment, SUBGHZ_CAME_ATOMO_DIR_NAME); - subghz_environment_set_alutech_at_4n_rainbow_table_file_name( - instance->environment, SUBGHZ_ALUTECH_AT_4N_DIR_NAME); - subghz_environment_set_nice_flor_s_rainbow_table_file_name( - instance->environment, SUBGHZ_NICE_FLOR_S_DIR_NAME); - subghz_environment_set_protocol_registry( - instance->environment, (void*)&subghz_protocol_registry); - instance->receiver = subghz_receiver_alloc_init(instance->environment); - - subghz_worker_set_overrun_callback( - instance->worker, (SubGhzWorkerOverrunCallback)subghz_receiver_reset); - subghz_worker_set_pair_callback( - instance->worker, (SubGhzWorkerPairCallback)subghz_receiver_decode); - subghz_worker_set_context(instance->worker, instance->receiver); - - //set default device Internal - subghz_devices_init(); - instance->radio_device_type = SubGhzRadioDeviceTypeInternal; - instance->radio_device_type = - subghz_txrx_radio_device_set(instance, SubGhzRadioDeviceTypeInternal); - - return instance; -} - -void subghz_txrx_free(SubGhzTxRx* instance) { - furi_assert(instance); - - if(instance->radio_device_type != SubGhzRadioDeviceTypeInternal) { - subghz_txrx_radio_device_power_off(instance); - subghz_devices_end(instance->radio_device); - } - - subghz_devices_deinit(); - - subghz_worker_free(instance->worker); - subghz_receiver_free(instance->receiver); - subghz_environment_free(instance->environment); - flipper_format_free(instance->fff_data); - furi_string_free(instance->preset->name); - subghz_setting_free(instance->setting); - - free(instance->preset); - free(instance); -} - -bool subghz_txrx_is_database_loaded(SubGhzTxRx* instance) { - furi_assert(instance); - return instance->is_database_loaded; -} - -void subghz_txrx_set_preset( - SubGhzTxRx* instance, - const char* preset_name, - uint32_t frequency, - uint8_t* preset_data, - size_t preset_data_size) { - furi_assert(instance); - furi_string_set(instance->preset->name, preset_name); - SubGhzRadioPreset* preset = instance->preset; - preset->frequency = frequency; - preset->data = preset_data; - preset->data_size = preset_data_size; -} - -const char* subghz_txrx_get_preset_name(SubGhzTxRx* instance, const char* preset) { - UNUSED(instance); - const char* preset_name = ""; - if(!strcmp(preset, "FuriHalSubGhzPresetOok270Async")) { - preset_name = "AM270"; - } else if(!strcmp(preset, "FuriHalSubGhzPresetOok650Async")) { - preset_name = "AM650"; - } else if(!strcmp(preset, "FuriHalSubGhzPreset2FSKDev238Async")) { - preset_name = "FM238"; - } else if(!strcmp(preset, "FuriHalSubGhzPreset2FSKDev476Async")) { - preset_name = "FM476"; - } else if(!strcmp(preset, "FuriHalSubGhzPresetCustom")) { - preset_name = "CUSTOM"; - } else { - FURI_LOG_E(TAG, "Unknown preset"); - } - return preset_name; -} - -SubGhzRadioPreset subghz_txrx_get_preset(SubGhzTxRx* instance) { - furi_assert(instance); - return *instance->preset; -} - -void subghz_txrx_get_frequency_and_modulation( - SubGhzTxRx* instance, - FuriString* frequency, - FuriString* modulation, - bool long_name) { - furi_assert(instance); - SubGhzRadioPreset* preset = instance->preset; - if(frequency != NULL) { - furi_string_printf( - frequency, - "%03ld.%02ld", - preset->frequency / 1000000 % 1000, - preset->frequency / 10000 % 100); - } - if(modulation != NULL) { - if(long_name) { - furi_string_printf(modulation, "%s", furi_string_get_cstr(preset->name)); - } else { - furi_string_printf(modulation, "%.2s", furi_string_get_cstr(preset->name)); - } - } -} - -static void subghz_txrx_begin(SubGhzTxRx* instance, uint8_t* preset_data) { - furi_assert(instance); - subghz_devices_reset(instance->radio_device); - subghz_devices_idle(instance->radio_device); - subghz_devices_load_preset(instance->radio_device, FuriHalSubGhzPresetCustom, preset_data); - instance->txrx_state = SubGhzTxRxStateIDLE; -} - -static uint32_t subghz_txrx_rx(SubGhzTxRx* instance, uint32_t frequency) { - furi_assert(instance); - furi_assert( - instance->txrx_state != SubGhzTxRxStateRx && instance->txrx_state != SubGhzTxRxStateSleep); - - subghz_devices_idle(instance->radio_device); - - uint32_t value = subghz_devices_set_frequency(instance->radio_device, frequency); - subghz_devices_flush_rx(instance->radio_device); - subghz_txrx_speaker_on(instance); - - subghz_devices_start_async_rx( - instance->radio_device, subghz_worker_rx_callback, instance->worker); - subghz_worker_start(instance->worker); - instance->txrx_state = SubGhzTxRxStateRx; - return value; -} - -static void subghz_txrx_idle(SubGhzTxRx* instance) { - furi_assert(instance); - furi_assert(instance->txrx_state != SubGhzTxRxStateSleep); - subghz_devices_idle(instance->radio_device); - subghz_txrx_speaker_off(instance); - instance->txrx_state = SubGhzTxRxStateIDLE; -} - -static void subghz_txrx_rx_end(SubGhzTxRx* instance) { - furi_assert(instance); - furi_assert(instance->txrx_state == SubGhzTxRxStateRx); - - if(subghz_worker_is_running(instance->worker)) { - subghz_worker_stop(instance->worker); - subghz_devices_stop_async_rx(instance->radio_device); - } - subghz_devices_idle(instance->radio_device); - subghz_txrx_speaker_off(instance); - instance->txrx_state = SubGhzTxRxStateIDLE; -} - -void subghz_txrx_sleep(SubGhzTxRx* instance) { - furi_assert(instance); - subghz_devices_sleep(instance->radio_device); - instance->txrx_state = SubGhzTxRxStateSleep; -} - -static bool subghz_txrx_tx(SubGhzTxRx* instance, uint32_t frequency) { - furi_assert(instance); - furi_assert(instance->txrx_state != SubGhzTxRxStateSleep); - - subghz_devices_idle(instance->radio_device); - subghz_devices_set_frequency(instance->radio_device, frequency); - - bool ret = subghz_devices_set_tx(instance->radio_device); - if(ret) { - subghz_txrx_speaker_on(instance); - instance->txrx_state = SubGhzTxRxStateTx; - } - - return ret; -} - -SubGhzTxRxStartTxState subghz_txrx_tx_start(SubGhzTxRx* instance, FlipperFormat* flipper_format) { - furi_assert(instance); - furi_assert(flipper_format); - - subghz_txrx_stop(instance); - - SubGhzTxRxStartTxState ret = SubGhzTxRxStartTxStateErrorParserOthers; - FuriString* temp_str = furi_string_alloc(); - uint32_t repeat = 200; - do { - if(!flipper_format_rewind(flipper_format)) { - FURI_LOG_E(TAG, "Rewind error"); - break; - } - if(!flipper_format_read_string(flipper_format, "Protocol", temp_str)) { - FURI_LOG_E(TAG, "Missing Protocol"); - break; - } - if(!flipper_format_insert_or_update_uint32(flipper_format, "Repeat", &repeat, 1)) { - FURI_LOG_E(TAG, "Unable Repeat"); - break; - } - ret = SubGhzTxRxStartTxStateOk; - - SubGhzRadioPreset* preset = instance->preset; - instance->transmitter = - subghz_transmitter_alloc_init(instance->environment, furi_string_get_cstr(temp_str)); - - if(instance->transmitter) { - if(subghz_transmitter_deserialize(instance->transmitter, flipper_format) == - SubGhzProtocolStatusOk) { - if(strcmp(furi_string_get_cstr(preset->name), "") != 0) { - subghz_txrx_begin( - instance, - subghz_setting_get_preset_data_by_name( - instance->setting, furi_string_get_cstr(preset->name))); - if(preset->frequency) { - if(!subghz_txrx_tx(instance, preset->frequency)) { - FURI_LOG_E(TAG, "Only Rx"); - ret = SubGhzTxRxStartTxStateErrorOnlyRx; - } - } else { - ret = SubGhzTxRxStartTxStateErrorParserOthers; - } - - } else { - FURI_LOG_E( - TAG, "Unknown name preset \" %s \"", furi_string_get_cstr(preset->name)); - ret = SubGhzTxRxStartTxStateErrorParserOthers; - } - - if(ret == SubGhzTxRxStartTxStateOk) { - //Start TX - subghz_devices_start_async_tx( - instance->radio_device, subghz_transmitter_yield, instance->transmitter); - } - } else { - ret = SubGhzTxRxStartTxStateErrorParserOthers; - } - } else { - ret = SubGhzTxRxStartTxStateErrorParserOthers; - } - if(ret != SubGhzTxRxStartTxStateOk) { - subghz_transmitter_free(instance->transmitter); - if(instance->txrx_state != SubGhzTxRxStateIDLE) { - subghz_txrx_idle(instance); - } - } - - } while(false); - furi_string_free(temp_str); - return ret; -} - -void subghz_txrx_rx_start(SubGhzTxRx* instance) { - furi_assert(instance); - subghz_txrx_stop(instance); - subghz_txrx_begin( - instance, - subghz_setting_get_preset_data_by_name( - subghz_txrx_get_setting(instance), furi_string_get_cstr(instance->preset->name))); - subghz_txrx_rx(instance, instance->preset->frequency); -} - -void subghz_txrx_set_need_save_callback( - SubGhzTxRx* instance, - SubGhzTxRxNeedSaveCallback callback, - void* context) { - furi_assert(instance); - instance->need_save_callback = callback; - instance->need_save_context = context; -} - -static void subghz_txrx_tx_stop(SubGhzTxRx* instance) { - furi_assert(instance); - furi_assert(instance->txrx_state == SubGhzTxRxStateTx); - //Stop TX - subghz_devices_stop_async_tx(instance->radio_device); - subghz_transmitter_stop(instance->transmitter); - subghz_transmitter_free(instance->transmitter); - - //if protocol dynamic then we save the last upload - if(instance->decoder_result->protocol->type == SubGhzProtocolTypeDynamic) { - if(instance->need_save_callback) { - instance->need_save_callback(instance->need_save_context); - } - } - subghz_txrx_idle(instance); - subghz_txrx_speaker_off(instance); - //Todo: Show message -} - -FlipperFormat* subghz_txrx_get_fff_data(SubGhzTxRx* instance) { - furi_assert(instance); - return instance->fff_data; -} - -SubGhzSetting* subghz_txrx_get_setting(SubGhzTxRx* instance) { - furi_assert(instance); - return instance->setting; -} - -void subghz_txrx_stop(SubGhzTxRx* instance) { - furi_assert(instance); - - switch(instance->txrx_state) { - case SubGhzTxRxStateTx: - subghz_txrx_tx_stop(instance); - subghz_txrx_speaker_unmute(instance); - break; - case SubGhzTxRxStateRx: - subghz_txrx_rx_end(instance); - subghz_txrx_speaker_mute(instance); - break; - - default: - break; - } -} - -void subghz_txrx_hopper_update(SubGhzTxRx* instance) { - furi_assert(instance); - - switch(instance->hopper_state) { - case SubGhzHopperStateOFF: - case SubGhzHopperStatePause: - return; - case SubGhzHopperStateRSSITimeOut: - if(instance->hopper_timeout != 0) { - instance->hopper_timeout--; - return; - } - break; - default: - break; - } - float rssi = -127.0f; - if(instance->hopper_state != SubGhzHopperStateRSSITimeOut) { - // See RSSI Calculation timings in CC1101 17.3 RSSI - rssi = subghz_devices_get_rssi(instance->radio_device); - - // Stay if RSSI is high enough - if(rssi > -90.0f) { - instance->hopper_timeout = 10; - instance->hopper_state = SubGhzHopperStateRSSITimeOut; - return; - } - } else { - instance->hopper_state = SubGhzHopperStateRunning; - } - // Select next frequency - if(instance->hopper_idx_frequency < - subghz_setting_get_hopper_frequency_count(instance->setting) - 1) { - instance->hopper_idx_frequency++; - } else { - instance->hopper_idx_frequency = 0; - } - - if(instance->txrx_state == SubGhzTxRxStateRx) { - subghz_txrx_rx_end(instance); - }; - if(instance->txrx_state == SubGhzTxRxStateIDLE) { - subghz_receiver_reset(instance->receiver); - instance->preset->frequency = - subghz_setting_get_hopper_frequency(instance->setting, instance->hopper_idx_frequency); - subghz_txrx_rx(instance, instance->preset->frequency); - } -} - -SubGhzHopperState subghz_txrx_hopper_get_state(SubGhzTxRx* instance) { - furi_assert(instance); - return instance->hopper_state; -} - -void subghz_txrx_hopper_set_state(SubGhzTxRx* instance, SubGhzHopperState state) { - furi_assert(instance); - instance->hopper_state = state; -} - -void subghz_txrx_hopper_unpause(SubGhzTxRx* instance) { - furi_assert(instance); - if(instance->hopper_state == SubGhzHopperStatePause) { - instance->hopper_state = SubGhzHopperStateRunning; - } -} - -void subghz_txrx_hopper_pause(SubGhzTxRx* instance) { - furi_assert(instance); - if(instance->hopper_state == SubGhzHopperStateRunning) { - instance->hopper_state = SubGhzHopperStatePause; - } -} - -void subghz_txrx_speaker_on(SubGhzTxRx* instance) { - furi_assert(instance); - if(instance->debug_pin_state) { - subghz_devices_set_async_mirror_pin(instance->radio_device, &gpio_ibutton); - } - - if(instance->speaker_state == SubGhzSpeakerStateEnable) { - if(furi_hal_speaker_acquire(30)) { - if(!instance->debug_pin_state) { - subghz_devices_set_async_mirror_pin(instance->radio_device, &gpio_speaker); - } - } else { - instance->speaker_state = SubGhzSpeakerStateDisable; - } - } -} - -void subghz_txrx_speaker_off(SubGhzTxRx* instance) { - furi_assert(instance); - if(instance->debug_pin_state) { - subghz_devices_set_async_mirror_pin(instance->radio_device, NULL); - } - if(instance->speaker_state != SubGhzSpeakerStateDisable) { - if(furi_hal_speaker_is_mine()) { - if(!instance->debug_pin_state) { - subghz_devices_set_async_mirror_pin(instance->radio_device, NULL); - } - furi_hal_speaker_release(); - if(instance->speaker_state == SubGhzSpeakerStateShutdown) - instance->speaker_state = SubGhzSpeakerStateDisable; - } - } -} - -void subghz_txrx_speaker_mute(SubGhzTxRx* instance) { - furi_assert(instance); - if(instance->debug_pin_state) { - subghz_devices_set_async_mirror_pin(instance->radio_device, NULL); - } - if(instance->speaker_state == SubGhzSpeakerStateEnable) { - if(furi_hal_speaker_is_mine()) { - if(!instance->debug_pin_state) { - subghz_devices_set_async_mirror_pin(instance->radio_device, NULL); - } - } - } -} - -void subghz_txrx_speaker_unmute(SubGhzTxRx* instance) { - furi_assert(instance); - if(instance->debug_pin_state) { - subghz_devices_set_async_mirror_pin(instance->radio_device, &gpio_ibutton); - } - if(instance->speaker_state == SubGhzSpeakerStateEnable) { - if(furi_hal_speaker_is_mine()) { - if(!instance->debug_pin_state) { - subghz_devices_set_async_mirror_pin(instance->radio_device, &gpio_speaker); - } - } - } -} - -void subghz_txrx_speaker_set_state(SubGhzTxRx* instance, SubGhzSpeakerState state) { - furi_assert(instance); - instance->speaker_state = state; -} - -SubGhzSpeakerState subghz_txrx_speaker_get_state(SubGhzTxRx* instance) { - furi_assert(instance); - return instance->speaker_state; -} - -bool subghz_txrx_load_decoder_by_name_protocol(SubGhzTxRx* instance, const char* name_protocol) { - furi_assert(instance); - furi_assert(name_protocol); - bool res = false; - instance->decoder_result = - subghz_receiver_search_decoder_base_by_name(instance->receiver, name_protocol); - if(instance->decoder_result) { - res = true; - } - return res; -} - -SubGhzProtocolDecoderBase* subghz_txrx_get_decoder(SubGhzTxRx* instance) { - furi_assert(instance); - return instance->decoder_result; -} - -bool subghz_txrx_protocol_is_serializable(SubGhzTxRx* instance) { - furi_assert(instance); - return ( - (instance->decoder_result->protocol->flag & SubGhzProtocolFlag_Save) == - SubGhzProtocolFlag_Save); -} - -bool subghz_txrx_protocol_is_transmittable(SubGhzTxRx* instance, bool check_type) { - furi_assert(instance); - const SubGhzProtocol* protocol = instance->decoder_result->protocol; - if(check_type) { - return ( - ((protocol->flag & SubGhzProtocolFlag_Send) == SubGhzProtocolFlag_Send) && - protocol->encoder->deserialize && protocol->type == SubGhzProtocolTypeStatic); - } - return ( - ((protocol->flag & SubGhzProtocolFlag_Send) == SubGhzProtocolFlag_Send) && - protocol->encoder->deserialize); -} - -void subghz_txrx_receiver_set_filter(SubGhzTxRx* instance, SubGhzProtocolFlag filter) { - furi_assert(instance); - subghz_receiver_set_filter(instance->receiver, filter); -} - -void subghz_txrx_set_rx_calback( - SubGhzTxRx* instance, - SubGhzReceiverCallback callback, - void* context) { - subghz_receiver_set_rx_callback(instance->receiver, callback, context); -} - -void subghz_txrx_set_raw_file_encoder_worker_callback_end( - SubGhzTxRx* instance, - SubGhzProtocolEncoderRAWCallbackEnd callback, - void* context) { - subghz_protocol_raw_file_encoder_worker_set_callback_end( - (SubGhzProtocolEncoderRAW*)subghz_transmitter_get_protocol_instance(instance->transmitter), - callback, - context); -} - -bool subghz_txrx_radio_device_is_external_connected(SubGhzTxRx* instance, const char* name) { - furi_assert(instance); - - bool is_connect = false; - bool is_otg_enabled = furi_hal_power_is_otg_enabled(); - - if(!is_otg_enabled) { - subghz_txrx_radio_device_power_on(instance); - } - - const SubGhzDevice* device = subghz_devices_get_by_name(name); - if(device) { - is_connect = subghz_devices_is_connect(device); - } - - if(!is_otg_enabled) { - subghz_txrx_radio_device_power_off(instance); - } - return is_connect; -} - -SubGhzRadioDeviceType - subghz_txrx_radio_device_set(SubGhzTxRx* instance, SubGhzRadioDeviceType radio_device_type) { - furi_assert(instance); - - if(radio_device_type == SubGhzRadioDeviceTypeExternalCC1101 && - subghz_txrx_radio_device_is_external_connected(instance, SUBGHZ_DEVICE_CC1101_EXT_NAME)) { - subghz_txrx_radio_device_power_on(instance); - instance->radio_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_EXT_NAME); - subghz_devices_begin(instance->radio_device); - instance->radio_device_type = SubGhzRadioDeviceTypeExternalCC1101; - } else { - subghz_txrx_radio_device_power_off(instance); - if(instance->radio_device_type != SubGhzRadioDeviceTypeInternal) { - subghz_devices_end(instance->radio_device); - } - instance->radio_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME); - instance->radio_device_type = SubGhzRadioDeviceTypeInternal; - } - - return instance->radio_device_type; -} - -SubGhzRadioDeviceType subghz_txrx_radio_device_get(SubGhzTxRx* instance) { - furi_assert(instance); - return instance->radio_device_type; -} - -float subghz_txrx_radio_device_get_rssi(SubGhzTxRx* instance) { - furi_assert(instance); - return subghz_devices_get_rssi(instance->radio_device); -} - -const char* subghz_txrx_radio_device_get_name(SubGhzTxRx* instance) { - furi_assert(instance); - return subghz_devices_get_name(instance->radio_device); -} - -bool subghz_txrx_radio_device_is_frequecy_valid(SubGhzTxRx* instance, uint32_t frequency) { - furi_assert(instance); - return subghz_devices_is_frequency_valid(instance->radio_device, frequency); -} - -bool subghz_txrx_radio_device_is_tx_alowed(SubGhzTxRx* instance, uint32_t frequency) { - furi_assert(instance); - furi_assert(instance->txrx_state != SubGhzTxRxStateSleep); - - subghz_devices_idle(instance->radio_device); - subghz_devices_set_frequency(instance->radio_device, frequency); - - bool ret = subghz_devices_set_tx(instance->radio_device); - subghz_devices_idle(instance->radio_device); - - return ret; -} - -void subghz_txrx_set_debug_pin_state(SubGhzTxRx* instance, bool state) { - furi_assert(instance); - instance->debug_pin_state = state; -} - -bool subghz_txrx_get_debug_pin_state(SubGhzTxRx* instance) { - furi_assert(instance); - return instance->debug_pin_state; -} - -void subghz_txrx_reset_dynamic_and_custom_btns(SubGhzTxRx* instance) { - furi_assert(instance); - subghz_environment_reset_keeloq(instance->environment); - - subghz_custom_btns_reset(); -} - -SubGhzReceiver* subghz_txrx_get_receiver(SubGhzTxRx* instance) { - furi_assert(instance); - return instance->receiver; -} \ No newline at end of file diff --git a/applications/external/subghz_remote_configurator/helpers/txrx/subghz_txrx.h b/applications/external/subghz_remote_configurator/helpers/txrx/subghz_txrx.h deleted file mode 100644 index 8bb7f2aee..000000000 --- a/applications/external/subghz_remote_configurator/helpers/txrx/subghz_txrx.h +++ /dev/null @@ -1,375 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include - -typedef struct SubGhzTxRx SubGhzTxRx; - -typedef void (*SubGhzTxRxNeedSaveCallback)(void* context); - -typedef enum { - SubGhzTxRxStartTxStateOk, - SubGhzTxRxStartTxStateErrorOnlyRx, - SubGhzTxRxStartTxStateErrorParserOthers, -} SubGhzTxRxStartTxState; - -// Type from subghz_types.h need for txrx working -/** SubGhzTxRx state */ -typedef enum { - SubGhzTxRxStateIDLE, - SubGhzTxRxStateRx, - SubGhzTxRxStateTx, - SubGhzTxRxStateSleep, -} SubGhzTxRxState; - -/** SubGhzHopperState state */ -typedef enum { - SubGhzHopperStateOFF, - SubGhzHopperStateRunning, - SubGhzHopperStatePause, - SubGhzHopperStateRSSITimeOut, -} SubGhzHopperState; - -/** SubGhzSpeakerState state */ -typedef enum { - SubGhzSpeakerStateDisable, - SubGhzSpeakerStateShutdown, - SubGhzSpeakerStateEnable, -} SubGhzSpeakerState; - -/** SubGhzRadioDeviceType */ -typedef enum { - SubGhzRadioDeviceTypeAuto, - SubGhzRadioDeviceTypeInternal, - SubGhzRadioDeviceTypeExternalCC1101, -} SubGhzRadioDeviceType; - -/** - * Allocate SubGhzTxRx - * - * @return SubGhzTxRx* pointer to SubGhzTxRx - */ -SubGhzTxRx* subghz_txrx_alloc(); - -/** - * Free SubGhzTxRx - * - * @param instance Pointer to a SubGhzTxRx - */ -void subghz_txrx_free(SubGhzTxRx* instance); - -/** - * Check if the database is loaded - * - * @param instance Pointer to a SubGhzTxRx - * @return bool True if the database is loaded - */ -bool subghz_txrx_is_database_loaded(SubGhzTxRx* instance); - -/** - * Set preset - * - * @param instance Pointer to a SubGhzTxRx - * @param preset_name Name of preset - * @param frequency Frequency in Hz - * @param preset_data Data of preset - * @param preset_data_size Size of preset data - */ -void subghz_txrx_set_preset( - SubGhzTxRx* instance, - const char* preset_name, - uint32_t frequency, - uint8_t* preset_data, - size_t preset_data_size); - -/** - * Get name of preset - * - * @param instance Pointer to a SubGhzTxRx - * @param preset String of preset - * @return const char* Name of preset - */ -const char* subghz_txrx_get_preset_name(SubGhzTxRx* instance, const char* preset); - -/** - * Get of preset - * - * @param instance Pointer to a SubGhzTxRx - * @return SubGhzRadioPreset Preset - */ -SubGhzRadioPreset subghz_txrx_get_preset(SubGhzTxRx* instance); - -/** - * Get string frequency and modulation - * - * @param instance Pointer to a SubGhzTxRx - * @param frequency Pointer to a string frequency - * @param modulation Pointer to a string modulation - */ -void subghz_txrx_get_frequency_and_modulation( - SubGhzTxRx* instance, - FuriString* frequency, - FuriString* modulation, - bool long_name); - -/** - * Start TX CC1101 - * - * @param instance Pointer to a SubGhzTxRx - * @param flipper_format Pointer to a FlipperFormat - * @return SubGhzTxRxStartTxState - */ -SubGhzTxRxStartTxState subghz_txrx_tx_start(SubGhzTxRx* instance, FlipperFormat* flipper_format); - -/** - * Start RX CC1101 - * - * @param instance Pointer to a SubGhzTxRx - */ -void subghz_txrx_rx_start(SubGhzTxRx* instance); - -/** - * Stop TX/RX CC1101 - * - * @param instance Pointer to a SubGhzTxRx - */ -void subghz_txrx_stop(SubGhzTxRx* instance); - -/** - * Set sleep mode CC1101 - * - * @param instance Pointer to a SubGhzTxRx - */ -void subghz_txrx_sleep(SubGhzTxRx* instance); - -/** - * Update frequency CC1101 in automatic mode (hopper) - * - * @param instance Pointer to a SubGhzTxRx - */ -void subghz_txrx_hopper_update(SubGhzTxRx* instance); - -/** - * Get state hopper - * - * @param instance Pointer to a SubGhzTxRx - * @return SubGhzHopperState - */ -SubGhzHopperState subghz_txrx_hopper_get_state(SubGhzTxRx* instance); - -/** - * Set state hopper - * - * @param instance Pointer to a SubGhzTxRx - * @param state State hopper - */ -void subghz_txrx_hopper_set_state(SubGhzTxRx* instance, SubGhzHopperState state); - -/** - * Unpause hopper - * - * @param instance Pointer to a SubGhzTxRx - */ -void subghz_txrx_hopper_unpause(SubGhzTxRx* instance); - -/** - * Set pause hopper - * - * @param instance Pointer to a SubGhzTxRx - */ -void subghz_txrx_hopper_pause(SubGhzTxRx* instance); - -/** - * Speaker on - * - * @param instance Pointer to a SubGhzTxRx - */ -void subghz_txrx_speaker_on(SubGhzTxRx* instance); - -/** - * Speaker off - * - * @param instance Pointer to a SubGhzTxRx - */ -void subghz_txrx_speaker_off(SubGhzTxRx* instance); - -/** - * Speaker mute - * - * @param instance Pointer to a SubGhzTxRx - */ -void subghz_txrx_speaker_mute(SubGhzTxRx* instance); - -/** - * Speaker unmute - * - * @param instance Pointer to a SubGhzTxRx - */ -void subghz_txrx_speaker_unmute(SubGhzTxRx* instance); - -/** - * Set state speaker - * - * @param instance Pointer to a SubGhzTxRx - * @param state State speaker - */ -void subghz_txrx_speaker_set_state(SubGhzTxRx* instance, SubGhzSpeakerState state); - -/** - * Get state speaker - * - * @param instance Pointer to a SubGhzTxRx - * @return SubGhzSpeakerState - */ -SubGhzSpeakerState subghz_txrx_speaker_get_state(SubGhzTxRx* instance); - -/** - * load decoder by name protocol - * - * @param instance Pointer to a SubGhzTxRx - * @param name_protocol Name protocol - * @return bool True if the decoder is loaded - */ -bool subghz_txrx_load_decoder_by_name_protocol(SubGhzTxRx* instance, const char* name_protocol); - -/** - * Get decoder - * - * @param instance Pointer to a SubGhzTxRx - * @return SubGhzProtocolDecoderBase* Pointer to a SubGhzProtocolDecoderBase - */ -SubGhzProtocolDecoderBase* subghz_txrx_get_decoder(SubGhzTxRx* instance); - -/** - * Set callback for save data - * - * @param instance Pointer to a SubGhzTxRx - * @param callback Callback for save data - * @param context Context for callback - */ -void subghz_txrx_set_need_save_callback( - SubGhzTxRx* instance, - SubGhzTxRxNeedSaveCallback callback, - void* context); - -/** - * Get pointer to a load data key - * - * @param instance Pointer to a SubGhzTxRx - * @return FlipperFormat* - */ -FlipperFormat* subghz_txrx_get_fff_data(SubGhzTxRx* instance); - -/** - * Get pointer to a SugGhzSetting - * - * @param instance Pointer to a SubGhzTxRx - * @return SubGhzSetting* - */ -SubGhzSetting* subghz_txrx_get_setting(SubGhzTxRx* instance); - -/** - * Is it possible to save this protocol - * - * @param instance Pointer to a SubGhzTxRx - * @return bool True if it is possible to save this protocol - */ -bool subghz_txrx_protocol_is_serializable(SubGhzTxRx* instance); - -/** - * Is it possible to send this protocol - * - * @param instance Pointer to a SubGhzTxRx - * @return bool True if it is possible to send this protocol - */ -bool subghz_txrx_protocol_is_transmittable(SubGhzTxRx* instance, bool check_type); - -/** - * Set filter, what types of decoder to use - * - * @param instance Pointer to a SubGhzTxRx - * @param filter Filter - */ -void subghz_txrx_receiver_set_filter(SubGhzTxRx* instance, SubGhzProtocolFlag filter); - -/** - * Set callback for receive data - * - * @param instance Pointer to a SubGhzTxRx - * @param callback Callback for receive data - * @param context Context for callback - */ -void subghz_txrx_set_rx_calback( - SubGhzTxRx* instance, - SubGhzReceiverCallback callback, - void* context); - -/** - * Set callback for Raw decoder, end of data transfer - * - * @param instance Pointer to a SubGhzTxRx - * @param callback Callback for Raw decoder, end of data transfer - * @param context Context for callback - */ -void subghz_txrx_set_raw_file_encoder_worker_callback_end( - SubGhzTxRx* instance, - SubGhzProtocolEncoderRAWCallbackEnd callback, - void* context); - -/* Checking if an external radio device is connected -* -* @param instance Pointer to a SubGhzTxRx -* @param name Name of external radio device -* @return bool True if is connected to the external radio device -*/ -bool subghz_txrx_radio_device_is_external_connected(SubGhzTxRx* instance, const char* name); - -/* Set the selected radio device to use -* -* @param instance Pointer to a SubGhzTxRx -* @param radio_device_type Radio device type -* @return SubGhzRadioDeviceType Type of installed radio device -*/ -SubGhzRadioDeviceType - subghz_txrx_radio_device_set(SubGhzTxRx* instance, SubGhzRadioDeviceType radio_device_type); - -/* Get the selected radio device to use -* -* @param instance Pointer to a SubGhzTxRx -* @return SubGhzRadioDeviceType Type of installed radio device -*/ -SubGhzRadioDeviceType subghz_txrx_radio_device_get(SubGhzTxRx* instance); - -/* Get RSSI the selected radio device to use -* -* @param instance Pointer to a SubGhzTxRx -* @return float RSSI -*/ -float subghz_txrx_radio_device_get_rssi(SubGhzTxRx* instance); - -/* Get name the selected radio device to use -* -* @param instance Pointer to a SubGhzTxRx -* @return const char* Name of installed radio device -*/ -const char* subghz_txrx_radio_device_get_name(SubGhzTxRx* instance); - -/* Get get intelligence whether frequency the selected radio device to use -* -* @param instance Pointer to a SubGhzTxRx -* @return bool True if the frequency is valid -*/ -bool subghz_txrx_radio_device_is_frequecy_valid(SubGhzTxRx* instance, uint32_t frequency); - -bool subghz_txrx_radio_device_is_tx_alowed(SubGhzTxRx* instance, uint32_t frequency); - -void subghz_txrx_set_debug_pin_state(SubGhzTxRx* instance, bool state); -bool subghz_txrx_get_debug_pin_state(SubGhzTxRx* instance); - -void subghz_txrx_reset_dynamic_and_custom_btns(SubGhzTxRx* instance); - -SubGhzReceiver* subghz_txrx_get_receiver(SubGhzTxRx* instance); // TODO use only in DecodeRaw diff --git a/applications/external/subghz_remote_configurator/helpers/txrx/subghz_txrx_i.h b/applications/external/subghz_remote_configurator/helpers/txrx/subghz_txrx_i.h deleted file mode 100644 index f058c2282..000000000 --- a/applications/external/subghz_remote_configurator/helpers/txrx/subghz_txrx_i.h +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once - -#include "subghz_txrx.h" - -struct SubGhzTxRx { - SubGhzWorker* worker; - - SubGhzEnvironment* environment; - SubGhzReceiver* receiver; - SubGhzTransmitter* transmitter; - SubGhzProtocolDecoderBase* decoder_result; - FlipperFormat* fff_data; - - SubGhzRadioPreset* preset; - SubGhzSetting* setting; - - uint8_t hopper_timeout; - uint8_t hopper_idx_frequency; - bool is_database_loaded; - SubGhzHopperState hopper_state; - - SubGhzTxRxState txrx_state; - SubGhzSpeakerState speaker_state; - const SubGhzDevice* radio_device; - SubGhzRadioDeviceType radio_device_type; - - SubGhzTxRxNeedSaveCallback need_save_callback; - void* need_save_context; - - bool debug_pin_state; -}; diff --git a/applications/external/subghz_remote_configurator/scenes/subrem_scene.c b/applications/external/subghz_remote_configurator/scenes/subrem_scene.c deleted file mode 100644 index c45285b96..000000000 --- a/applications/external/subghz_remote_configurator/scenes/subrem_scene.c +++ /dev/null @@ -1,30 +0,0 @@ -#include "../subghz_remote_app_i.h" - -// Generate scene on_enter handlers array -#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, -void (*const subrem_scene_on_enter_handlers[])(void*) = { -#include "subrem_scene_config.h" -}; -#undef ADD_SCENE - -// Generate scene on_event handlers array -#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event, -bool (*const subrem_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = { -#include "subrem_scene_config.h" -}; -#undef ADD_SCENE - -// Generate scene on_exit handlers array -#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit, -void (*const subrem_scene_on_exit_handlers[])(void* context) = { -#include "subrem_scene_config.h" -}; -#undef ADD_SCENE - -// Initialize scene handlers configuration structure -const SceneManagerHandlers subrem_scene_handlers = { - .on_enter_handlers = subrem_scene_on_enter_handlers, - .on_event_handlers = subrem_scene_on_event_handlers, - .on_exit_handlers = subrem_scene_on_exit_handlers, - .scene_num = SubRemSceneNum, -}; diff --git a/applications/external/subghz_remote_configurator/scenes/subrem_scene.h b/applications/external/subghz_remote_configurator/scenes/subrem_scene.h deleted file mode 100644 index 5c01f8ca5..000000000 --- a/applications/external/subghz_remote_configurator/scenes/subrem_scene.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -#include - -// Generate scene id and total number -#define ADD_SCENE(prefix, name, id) SubRemScene##id, -typedef enum { -#include "subrem_scene_config.h" - SubRemSceneNum, -} SubRemScene; -#undef ADD_SCENE - -extern const SceneManagerHandlers subrem_scene_handlers; - -// Generate scene on_enter handlers declaration -#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); -#include "subrem_scene_config.h" -#undef ADD_SCENE - -// Generate scene on_event handlers declaration -#define ADD_SCENE(prefix, name, id) \ - bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event); -#include "subrem_scene_config.h" -#undef ADD_SCENE - -// Generate scene on_exit handlers declaration -#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context); -#include "subrem_scene_config.h" -#undef ADD_SCENE diff --git a/applications/external/subghz_remote_configurator/scenes/subrem_scene_config.h b/applications/external/subghz_remote_configurator/scenes/subrem_scene_config.h deleted file mode 100644 index 720eba42f..000000000 --- a/applications/external/subghz_remote_configurator/scenes/subrem_scene_config.h +++ /dev/null @@ -1,8 +0,0 @@ -ADD_SCENE(subrem, start, Start) -ADD_SCENE(subrem, open_map_file, OpenMapFile) -ADD_SCENE(subrem, edit_menu, EditMenu) -ADD_SCENE(subrem, edit_submenu, EditSubMenu) -ADD_SCENE(subrem, edit_label, EditLabel) -ADD_SCENE(subrem, open_sub_file, OpenSubFile) -ADD_SCENE(subrem, edit_preview, EditPreview) -ADD_SCENE(subrem, enter_new_name, EnterNewName) \ No newline at end of file diff --git a/applications/external/subghz_remote_configurator/scenes/subrem_scene_open_map_file.c b/applications/external/subghz_remote_configurator/scenes/subrem_scene_open_map_file.c deleted file mode 100644 index 77f2d2d56..000000000 --- a/applications/external/subghz_remote_configurator/scenes/subrem_scene_open_map_file.c +++ /dev/null @@ -1,29 +0,0 @@ -#include "../subghz_remote_app_i.h" - -void subrem_scene_open_map_file_on_enter(void* context) { - furi_assert(context); - SubGhzRemoteApp* app = context; - - SubRemLoadMapState load_state = subrem_load_from_file(app); - uint32_t start_scene_state = - scene_manager_get_scene_state(app->scene_manager, SubRemSceneStart); - - // TODO if optimization - - if(load_state == SubRemLoadMapStateBack) { - scene_manager_previous_scene(app->scene_manager); - } else if(start_scene_state == SubmenuIndexSubRemEditMapFile) { - scene_manager_set_scene_state(app->scene_manager, SubRemSceneEditMenu, SubRemSubKeyNameUp); - scene_manager_next_scene(app->scene_manager, SubRemSceneEditMenu); - } -} - -bool subrem_scene_open_map_file_on_event(void* context, SceneManagerEvent event) { - UNUSED(context); - UNUSED(event); - return false; -} - -void subrem_scene_open_map_file_on_exit(void* context) { - UNUSED(context); -} diff --git a/applications/external/subghz_remote_configurator/scenes/subrem_scene_start.c b/applications/external/subghz_remote_configurator/scenes/subrem_scene_start.c deleted file mode 100644 index e5a254111..000000000 --- a/applications/external/subghz_remote_configurator/scenes/subrem_scene_start.c +++ /dev/null @@ -1,73 +0,0 @@ -#include "../subghz_remote_app_i.h" -#include "../helpers/subrem_custom_event.h" - -void subrem_scene_start_submenu_callback(void* context, uint32_t index) { - furi_assert(context); - SubGhzRemoteApp* app = context; - - view_dispatcher_send_custom_event(app->view_dispatcher, index); -} - -void subrem_scene_start_on_enter(void* context) { - furi_assert(context); - - SubGhzRemoteApp* app = context; - Submenu* submenu = app->submenu; - submenu_add_item( - submenu, - "Edit Map File", - SubmenuIndexSubRemEditMapFile, - subrem_scene_start_submenu_callback, - app); - submenu_add_item( - submenu, - "New Map File", - SubmenuIndexSubRemNewMapFile, - subrem_scene_start_submenu_callback, - app); - // submenu_add_item( - // submenu, - // "About", - // SubmenuIndexSubGhzRemoteAbout, - // subrem_scene_start_submenu_callback, - // app); - - submenu_set_selected_item( - submenu, scene_manager_get_scene_state(app->scene_manager, SubRemSceneStart)); - - view_dispatcher_switch_to_view(app->view_dispatcher, SubRemViewIDSubmenu); -} - -bool subrem_scene_start_on_event(void* context, SceneManagerEvent event) { - furi_assert(context); - - SubGhzRemoteApp* app = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SubmenuIndexSubRemEditMapFile) { - scene_manager_set_scene_state( - app->scene_manager, SubRemSceneStart, SubmenuIndexSubRemEditMapFile); - scene_manager_next_scene(app->scene_manager, SubRemSceneOpenMapFile); - consumed = true; - } else if(event.event == SubmenuIndexSubRemNewMapFile) { - scene_manager_set_scene_state( - app->scene_manager, SubRemSceneStart, SubmenuIndexSubRemNewMapFile); - scene_manager_next_scene(app->scene_manager, SubRemSceneEnterNewName); - consumed = true; - } - // } else if(event.event == SubmenuIndexSubRemAbout) { - // scene_manager_next_scene(app->scene_manager, SubRemSceneAbout); - // consumed = true; - // } - } - - return consumed; -} - -void subrem_scene_start_on_exit(void* context) { - furi_assert(context); - - SubGhzRemoteApp* app = context; - submenu_reset(app->submenu); -} diff --git a/applications/external/subghz_remote_configurator/subghz_remote_app.c b/applications/external/subghz_remote_configurator/subghz_remote_app.c deleted file mode 100644 index 7cbbabfcb..000000000 --- a/applications/external/subghz_remote_configurator/subghz_remote_app.c +++ /dev/null @@ -1,184 +0,0 @@ -#include "subghz_remote_app_i.h" - -static bool subghz_remote_app_custom_event_callback(void* context, uint32_t event) { - furi_assert(context); - SubGhzRemoteApp* app = context; - return scene_manager_handle_custom_event(app->scene_manager, event); -} - -static bool subghz_remote_app_back_event_callback(void* context) { - furi_assert(context); - SubGhzRemoteApp* app = context; - return scene_manager_handle_back_event(app->scene_manager); -} - -static void subghz_remote_app_tick_event_callback(void* context) { - furi_assert(context); - SubGhzRemoteApp* app = context; - scene_manager_handle_tick_event(app->scene_manager); -} - -SubGhzRemoteApp* subghz_remote_app_alloc() { - SubGhzRemoteApp* app = malloc(sizeof(SubGhzRemoteApp)); - - Storage* storage = furi_record_open(RECORD_STORAGE); - storage_common_migrate(storage, EXT_PATH("unirf"), SUBREM_APP_FOLDER); - storage_common_migrate(storage, EXT_PATH("subghz/unirf"), SUBREM_APP_FOLDER); - - if(!storage_simply_mkdir(storage, SUBREM_APP_FOLDER)) { - //FURI_LOG_E(TAG, "Could not create folder %s", SUBREM_APP_FOLDER); - } - furi_record_close(RECORD_STORAGE); - - // furi_hal_power_suppress_charge_enter(); - - app->file_path = furi_string_alloc(); - furi_string_set(app->file_path, SUBREM_APP_FOLDER); - - // GUI - app->gui = furi_record_open(RECORD_GUI); - - // View Dispatcher - app->view_dispatcher = view_dispatcher_alloc(); - - app->scene_manager = scene_manager_alloc(&subrem_scene_handlers, app); - view_dispatcher_enable_queue(app->view_dispatcher); - - view_dispatcher_set_event_callback_context(app->view_dispatcher, app); - view_dispatcher_set_custom_event_callback( - app->view_dispatcher, subghz_remote_app_custom_event_callback); - view_dispatcher_set_navigation_event_callback( - app->view_dispatcher, subghz_remote_app_back_event_callback); - view_dispatcher_set_tick_event_callback( - app->view_dispatcher, subghz_remote_app_tick_event_callback, 100); - - view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); - - // Open Notification record - app->notifications = furi_record_open(RECORD_NOTIFICATION); - - // SubMenu - app->submenu = submenu_alloc(); - view_dispatcher_add_view( - app->view_dispatcher, SubRemViewIDSubmenu, submenu_get_view(app->submenu)); - - // Dialog - app->dialogs = furi_record_open(RECORD_DIALOGS); - - // TextInput - app->text_input = text_input_alloc(); - view_dispatcher_add_view( - app->view_dispatcher, SubRemViewIDTextInput, text_input_get_view(app->text_input)); - - // Widget - app->widget = widget_alloc(); - view_dispatcher_add_view( - app->view_dispatcher, SubRemViewIDWidget, widget_get_view(app->widget)); - - // Popup - app->popup = popup_alloc(); - view_dispatcher_add_view(app->view_dispatcher, SubRemViewIDPopup, popup_get_view(app->popup)); - - // Remote view - app->subrem_remote_view = subrem_view_remote_alloc(); - view_dispatcher_add_view( - app->view_dispatcher, - SubRemViewIDRemote, - subrem_view_remote_get_view(app->subrem_remote_view)); - - // Edit Menu view - app->subrem_edit_menu = subrem_view_edit_menu_alloc(); - view_dispatcher_add_view( - app->view_dispatcher, - SubRemViewIDEditMenu, - subrem_view_edit_menu_get_view(app->subrem_edit_menu)); - - app->map_preset = malloc(sizeof(SubRemMapPreset)); - for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) { - app->map_preset->subs_preset[i] = subrem_sub_file_preset_alloc(); - } - - app->txrx = subghz_txrx_alloc(); - - subghz_txrx_set_need_save_callback(app->txrx, subrem_save_active_sub, app); - - app->map_not_saved = false; - -#ifdef SUBREM_LIGHT - scene_manager_next_scene(app->scene_manager, SubRemSceneOpenMapFile); -#else - scene_manager_next_scene(app->scene_manager, SubRemSceneStart); - scene_manager_set_scene_state( - app->scene_manager, SubRemSceneStart, SubmenuIndexSubRemEditMapFile); -#endif - - return app; -} - -void subghz_remote_app_free(SubGhzRemoteApp* app) { - furi_assert(app); - - // furi_hal_power_suppress_charge_exit(); - - // Submenu - view_dispatcher_remove_view(app->view_dispatcher, SubRemViewIDSubmenu); - submenu_free(app->submenu); - - // Dialog - furi_record_close(RECORD_DIALOGS); - - // TextInput - view_dispatcher_remove_view(app->view_dispatcher, SubRemViewIDTextInput); - text_input_free(app->text_input); - - // Widget - view_dispatcher_remove_view(app->view_dispatcher, SubRemViewIDWidget); - widget_free(app->widget); - - // Popup - view_dispatcher_remove_view(app->view_dispatcher, SubRemViewIDPopup); - popup_free(app->popup); - - // Remote view - view_dispatcher_remove_view(app->view_dispatcher, SubRemViewIDRemote); - subrem_view_remote_free(app->subrem_remote_view); - - // Edit view - view_dispatcher_remove_view(app->view_dispatcher, SubRemViewIDEditMenu); - subrem_view_edit_menu_free(app->subrem_edit_menu); - - scene_manager_free(app->scene_manager); - view_dispatcher_free(app->view_dispatcher); - - subghz_txrx_free(app->txrx); - - for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) { - subrem_sub_file_preset_free(app->map_preset->subs_preset[i]); - } - free(app->map_preset); - - // Notifications - furi_record_close(RECORD_NOTIFICATION); - app->notifications = NULL; - - // Close records - furi_record_close(RECORD_GUI); - - // Path strings - furi_string_free(app->file_path); - - free(app); -} - -int32_t subghz_remote_config_app(void* p) { - UNUSED(p); - SubGhzRemoteApp* subghz_remote_app = subghz_remote_app_alloc(); - - furi_string_set(subghz_remote_app->file_path, SUBREM_APP_FOLDER); - - view_dispatcher_run(subghz_remote_app->view_dispatcher); - - subghz_remote_app_free(subghz_remote_app); - - return 0; -} diff --git a/applications/external/subghz_remote_configurator/subghz_remote_app_i.c b/applications/external/subghz_remote_configurator/subghz_remote_app_i.c deleted file mode 100644 index 1eb64541f..000000000 --- a/applications/external/subghz_remote_configurator/subghz_remote_app_i.c +++ /dev/null @@ -1,270 +0,0 @@ -#include "subghz_remote_app_i.h" -#include -#include - -#include "helpers/txrx/subghz_txrx.h" - -// #include -// #include - -// #include -// #include - -#define TAG "SubGhzRemote" - -static const char* map_file_labels[SubRemSubKeyNameMaxCount][2] = { - [SubRemSubKeyNameUp] = {"UP", "ULABEL"}, - [SubRemSubKeyNameDown] = {"DOWN", "DLABEL"}, - [SubRemSubKeyNameLeft] = {"LEFT", "LLABEL"}, - [SubRemSubKeyNameRight] = {"RIGHT", "RLABEL"}, - [SubRemSubKeyNameOk] = {"OK", "OKLABEL"}, -}; - -void subrem_map_preset_reset(SubRemMapPreset* map_preset) { - furi_assert(map_preset); - - for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) { - subrem_sub_file_preset_reset(map_preset->subs_preset[i]); - } -} - -static SubRemLoadMapState subrem_map_preset_check( - SubRemMapPreset* map_preset, - SubGhzTxRx* txrx, - FlipperFormat* fff_data_file) { - furi_assert(map_preset); - furi_assert(txrx); - - bool all_loaded = true; - SubRemLoadMapState ret = SubRemLoadMapStateErrorBrokenFile; - - SubRemLoadSubState sub_loadig_state; - SubRemSubFilePreset* sub_preset; - - for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) { - sub_preset = map_preset->subs_preset[i]; - - sub_loadig_state = SubRemLoadSubStateErrorNoFile; - - if(furi_string_empty(sub_preset->file_path)) { - // FURI_LOG_I(TAG, "Empty file path"); - } else if(!flipper_format_file_open_existing( - fff_data_file, furi_string_get_cstr(sub_preset->file_path))) { - sub_preset->load_state = SubRemLoadSubStateErrorNoFile; - FURI_LOG_W(TAG, "Error open file %s", furi_string_get_cstr(sub_preset->file_path)); - } else { - sub_loadig_state = subrem_sub_preset_load(sub_preset, txrx, fff_data_file); - } - - if(sub_loadig_state != SubRemLoadSubStateOK) { - all_loaded = false; - } else { - ret = SubRemLoadMapStateNotAllOK; - } - - if(ret != SubRemLoadMapStateErrorBrokenFile && all_loaded) { - ret = SubRemLoadMapStateOK; - } - - flipper_format_file_close(fff_data_file); - } - - return ret; -} - -static bool subrem_map_preset_load(SubRemMapPreset* map_preset, FlipperFormat* fff_data_file) { - furi_assert(map_preset); - bool ret = false; - SubRemSubFilePreset* sub_preset; - for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) { - sub_preset = map_preset->subs_preset[i]; - if(!flipper_format_read_string( - fff_data_file, map_file_labels[i][0], sub_preset->file_path)) { -#if FURI_DEBUG - FURI_LOG_W(TAG, "No file patch for %s", map_file_labels[i][0]); -#endif - sub_preset->type = SubGhzProtocolTypeUnknown; - } else if(!path_contains_only_ascii(furi_string_get_cstr(sub_preset->file_path))) { - FURI_LOG_E(TAG, "Incorrect characters in [%s] file path", map_file_labels[i][0]); - sub_preset->type = SubGhzProtocolTypeUnknown; - } else if(!flipper_format_rewind(fff_data_file)) { - // Rewind error - } else if(!flipper_format_read_string( - fff_data_file, map_file_labels[i][1], sub_preset->label)) { -#if FURI_DEBUG - FURI_LOG_W(TAG, "No Label for %s", map_file_labels[i][0]); -#endif - ret = true; - } else { - ret = true; - } - if(ret) { - // Preload seccesful - FURI_LOG_I( - TAG, - "%-5s: %s %s", - map_file_labels[i][0], - furi_string_get_cstr(sub_preset->label), - furi_string_get_cstr(sub_preset->file_path)); - sub_preset->load_state = SubRemLoadSubStatePreloaded; - } - - flipper_format_rewind(fff_data_file); - } - return ret; -} - -SubRemLoadMapState subrem_map_file_load(SubGhzRemoteApp* app, const char* file_path) { - furi_assert(app); - furi_assert(file_path); -#if FURI_DEBUG - FURI_LOG_I(TAG, "Load Map File Start"); -#endif - Storage* storage = furi_record_open(RECORD_STORAGE); - FlipperFormat* fff_data_file = flipper_format_file_alloc(storage); - SubRemLoadMapState ret = SubRemLoadMapStateErrorOpenError; -#if FURI_DEBUG - FURI_LOG_I(TAG, "Open Map File.."); -#endif - subrem_map_preset_reset(app->map_preset); - - if(!flipper_format_file_open_existing(fff_data_file, file_path)) { - FURI_LOG_E(TAG, "Could not open MAP file %s", file_path); - ret = SubRemLoadMapStateErrorOpenError; - } else { - if(!subrem_map_preset_load(app->map_preset, fff_data_file)) { - FURI_LOG_E(TAG, "Could no Sub file path in MAP file"); - // ret = // error for popup - } else if(!flipper_format_file_close(fff_data_file)) { - ret = SubRemLoadMapStateErrorOpenError; - } else { - ret = subrem_map_preset_check(app->map_preset, app->txrx, fff_data_file); - } - } - - if(ret == SubRemLoadMapStateOK) { - FURI_LOG_I(TAG, "Load Map File Seccesful"); - } else if(ret == SubRemLoadMapStateNotAllOK) { - FURI_LOG_I(TAG, "Load Map File Seccesful [Not all files]"); - } else { - FURI_LOG_E(TAG, "Broken Map File"); - } - - flipper_format_file_close(fff_data_file); - flipper_format_free(fff_data_file); - - furi_record_close(RECORD_STORAGE); - return ret; -} - -bool subrem_save_protocol_to_file(FlipperFormat* flipper_format, const char* sub_file_name) { - furi_assert(flipper_format); - furi_assert(sub_file_name); - - Storage* storage = furi_record_open(RECORD_STORAGE); - Stream* flipper_format_stream = flipper_format_get_raw_stream(flipper_format); - - bool saved = false; - uint32_t repeat = 200; - FuriString* file_dir = furi_string_alloc(); - - path_extract_dirname(sub_file_name, file_dir); - do { - // removing additional fields - flipper_format_delete_key(flipper_format, "Repeat"); - // flipper_format_delete_key(flipper_format, "Manufacture"); - - if(!storage_simply_remove(storage, sub_file_name)) { - break; - } - - //ToDo check Write - stream_seek(flipper_format_stream, 0, StreamOffsetFromStart); - stream_save_to_file(flipper_format_stream, storage, sub_file_name, FSOM_CREATE_ALWAYS); - - if(!flipper_format_insert_or_update_uint32(flipper_format, "Repeat", &repeat, 1)) { - FURI_LOG_E(TAG, "Unable Repeat"); - break; - } - - saved = true; - } while(0); - - furi_string_free(file_dir); - furi_record_close(RECORD_STORAGE); - return saved; -} - -void subrem_save_active_sub(void* context) { - furi_assert(context); - SubGhzRemoteApp* app = context; - - SubRemSubFilePreset* sub_preset = app->map_preset->subs_preset[app->chusen_sub]; - subrem_save_protocol_to_file( - sub_preset->fff_data, furi_string_get_cstr(sub_preset->file_path)); -} - -SubRemLoadMapState subrem_load_from_file(SubGhzRemoteApp* app) { - furi_assert(app); - - FuriString* file_path = furi_string_alloc(); - SubRemLoadMapState ret = SubRemLoadMapStateBack; - - DialogsFileBrowserOptions browser_options; - dialog_file_browser_set_basic_options(&browser_options, SUBREM_APP_EXTENSION, &I_subrem_10px); - browser_options.base_path = SUBREM_APP_FOLDER; - - // Input events and views are managed by file_select - if(!dialog_file_browser_show(app->dialogs, app->file_path, app->file_path, &browser_options)) { - } else { - ret = subrem_map_file_load(app, furi_string_get_cstr(app->file_path)); - } - - furi_string_free(file_path); - - return ret; -} - -bool subrem_save_map_to_file(SubGhzRemoteApp* app) { - furi_assert(app); - - const char* file_name = furi_string_get_cstr(app->file_path); - bool saved = false; - FlipperFormat* fff_data = flipper_format_string_alloc(); - - SubRemSubFilePreset* sub_preset; - - flipper_format_write_header_cstr( - fff_data, SUBREM_APP_APP_FILE_TYPE, SUBREM_APP_APP_FILE_VERSION); - for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) { - sub_preset = app->map_preset->subs_preset[i]; - if(!furi_string_empty(sub_preset->file_path)) { - flipper_format_write_string(fff_data, map_file_labels[i][0], sub_preset->file_path); - } - } - for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) { - sub_preset = app->map_preset->subs_preset[i]; - if(!furi_string_empty(sub_preset->file_path)) { - flipper_format_write_string(fff_data, map_file_labels[i][1], sub_preset->label); - } - } - - Storage* storage = furi_record_open(RECORD_STORAGE); - Stream* flipper_format_stream = flipper_format_get_raw_stream(fff_data); - - do { - if(!storage_simply_remove(storage, file_name)) { - break; - } - //ToDo check Write - stream_seek(flipper_format_stream, 0, StreamOffsetFromStart); - stream_save_to_file(flipper_format_stream, storage, file_name, FSOM_CREATE_ALWAYS); - - saved = true; - } while(0); - - furi_record_close(RECORD_STORAGE); - flipper_format_free(fff_data); - - return saved; -} \ No newline at end of file diff --git a/applications/external/subghz_remote_configurator/subghz_remote_app_i.h b/applications/external/subghz_remote_configurator/subghz_remote_app_i.h deleted file mode 100644 index f096a7d46..000000000 --- a/applications/external/subghz_remote_configurator/subghz_remote_app_i.h +++ /dev/null @@ -1,63 +0,0 @@ -#pragma once - -#include "helpers/subrem_types.h" -#include "helpers/subrem_presets.h" -#include "scenes/subrem_scene.h" - -#include "helpers/txrx/subghz_txrx.h" -#include - -#include "views/remote.h" -#include "views/edit_menu.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#define SUBREM_APP_FOLDER EXT_PATH("subghz/remote") -#define SUBREM_MAX_LEN_NAME 64 - -typedef struct { - Gui* gui; - ViewDispatcher* view_dispatcher; - SceneManager* scene_manager; - NotificationApp* notifications; - DialogsApp* dialogs; - Widget* widget; - Popup* popup; - TextInput* text_input; - Submenu* submenu; - - FuriString* file_path; - char file_name_tmp[SUBREM_MAX_LEN_NAME]; - - SubRemViewRemote* subrem_remote_view; - SubRemViewEditMenu* subrem_edit_menu; - - SubRemMapPreset* map_preset; - - SubGhzTxRx* txrx; - - bool map_not_saved; - - uint8_t chusen_sub; -} SubGhzRemoteApp; - -SubRemLoadMapState subrem_load_from_file(SubGhzRemoteApp* app); - -SubRemLoadMapState subrem_map_file_load(SubGhzRemoteApp* app, const char* file_path); - -void subrem_map_preset_reset(SubRemMapPreset* map_preset); - -bool subrem_save_map_to_file(SubGhzRemoteApp* app); - -void subrem_save_active_sub(void* context); diff --git a/applications/external/subghz_remote_configurator/subrem_10px.png b/applications/external/subghz_remote_configurator/subrem_10px.png deleted file mode 100644 index c6b410f4c598d6b241b851826875a568c74f4d20..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5000 zcmeHLeQXow8NVhhfsmvNbWJE`yEq*mNzeCX`_4BziDT!(3JxJLO+Xvv_1$~UEw<0t zm)N1})-a|4OCbE6;07#-3sX@sAxkg$U?hnu$6%_P+PX_Jv(ud zLf1xG|CwUz?7R1Up5OC4zxVgNce*W&4YheW_vK(1mglK+H=%$1JZE+m`hA@F<1zI2 zyA8fptqH{ONK}=TAjGw<2*hDRkufZBKGgVD-U({Lf_l{mhyV1gGrhn5dOvmG&rb;X|2#O* z`;Gdu`y8=lFC6>o;MdlzEhu;(gUPC_z|AEGi;B+`t?Ze5z3-kKC+@xe!rsi0?9s|K zy?-daKNrL9+N8K#jUJb4ydqS`GmmU@)cv;7aPpz%>TUZsFY*}}-;x*iZ59ty6_jpT zvujoMQ}z8jJ+AG;!%Gj}Yq-_gD;(ypTplW&z40q}pQ&N1scCq0d(~q_cR%sbwf8Sv zdVdlA`l;oI1plLZ-jYiT3faL`zr6A#=Lo=7=AJsu{N?^-b1q)%coKW)>T~u}qi^rn z-7>H`clPEJwEVR7TGqAGdqR;5OY#pr*E?^={1s1Y&f(g=vM=|qHywW9AEyugs9|9K z_qUv^T38l3y>(BG-D_BB`RVoV^}JI09`V|mBd`AW<~wBWyCXky1n0|1Nlg+*V)QGN;EdcVFdq|MubW(V_Tn9{lz<&(!Cf?0&8A zl@E$Ck9Ky~46J|Y$whnDXUy8sU3Tos>?t>Un9|+}sNpj`p?cz$4F;W6I^yu1td=)j zwphHBH{ybAO5KJiY~Ik|6F0PrHpy5~o?}l42p|MCfG0x1a7;)zj7eMpo$JG-5l@?` zHXBJXB*PHMf{1m6HIN{}u@W63h2e%VF{(r~MGfORCh)5rn!{*B^Z0mvp@`R;h7ZTa zSU`M`2@oM^6GetXP{HeN+v@{V%k5_5e+8G zkwg*(VF;PVP*i$K$XbuLG3}vK5Kuyqq!%K4ie;ot)zny<8cCZ^NiaQ~ENpU0nj%lI zJjF+!xy>BKy>oC09YBCl_0@MKq5HSOc8#H zHxrPt@G@uPXxUw}=@F^kKtO1=<+RE`fZLx72 zM^h}%PZ&K2qcJ389h0U^tT{O|!J$hHs!^{hL5Gn|PU-6=pgIxrK<@yAog7DH3a%&w z8g!!r!BMD#C>udrd^9VVErOXZqga7TC7!lcqdrv)I*fX4xSm29%!}Gu0vbreA!k;g z%|4nF%vOQs$|!0w97;_$K_%JJIG$`y z0f?!B#blXMGE;<>npEzfp3l7GX_S~MYjF^T&H&=qVRY(yC*C;TeK^CKEcntEB`m4& z*s`e!#M_|0il0b3`57vUflm0by2LgR4nVX&k8KG5wO*Mw+x#3L%ravoDAo)K9*8VK z`z@R#$`oXol=A*h>SY-g(0){rAj zX|i`eX-Qc`CULv;$ClJi>UW`W?b^xP)oq_>=<%(|iFP_&{;^5&uL6OoA}L2u$;}G@ zRN$B(Zj5Yq}83M;=f=r9w8MiVD2l{4`U z0fy0oX&k*FI5pSMi{36|`Ri-l*r@*9d2H`fXk<>LZgmX9OeOkpSK|4KPBfUUdA!xx z?`7r}mf -#include - -#include - -#define SUBREM_VIEW_REMOTE_MAX_LABEL_LENGTH 30 -#define SUBREM_VIEW_REMOTE_LEFT_OFFSET 10 -#define SUBREM_VIEW_REMOTE_RIGHT_OFFSET 22 - -struct SubRemViewRemote { - View* view; - SubRemViewRemoteCallback callback; - void* context; -}; - -typedef struct { - char* labels[SubRemSubKeyNameMaxCount]; - - SubRemViewRemoteState state; - - uint8_t pressed_btn; -} SubRemViewRemoteModel; - -void subrem_view_remote_set_callback( - SubRemViewRemote* subrem_view_remote, - SubRemViewRemoteCallback callback, - void* context) { - furi_assert(subrem_view_remote); - - subrem_view_remote->callback = callback; - subrem_view_remote->context = context; -} - -void subrem_view_remote_update_data_labels( - SubRemViewRemote* subrem_view_remote, - SubRemSubFilePreset** subs_presets) { - furi_assert(subrem_view_remote); - furi_assert(subs_presets); - - FuriString* labels[SubRemSubKeyNameMaxCount]; - SubRemSubFilePreset* sub_preset; - - for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) { - sub_preset = subs_presets[i]; - switch(sub_preset->load_state) { - case SubRemLoadSubStateOK: - if(!furi_string_empty(sub_preset->label)) { - labels[i] = furi_string_alloc_set(sub_preset->label); - } else if(!furi_string_empty(sub_preset->file_path)) { - labels[i] = furi_string_alloc(); - path_extract_filename(sub_preset->file_path, labels[i], true); - } else { - labels[i] = furi_string_alloc_set("Empty Label"); - } - break; - - case SubRemLoadSubStateErrorNoFile: - labels[i] = furi_string_alloc_set("[X] Can't open file"); - break; - - case SubRemLoadSubStateErrorFreq: - case SubRemLoadSubStateErrorMod: - case SubRemLoadSubStateErrorProtocol: - labels[i] = furi_string_alloc_set("[X] Error in .sub file"); - break; - - default: - labels[i] = furi_string_alloc_set(""); - break; - } - } - - with_view_model( - subrem_view_remote->view, - SubRemViewRemoteModel * model, - { - for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) { - strncpy( - model->labels[i], - furi_string_get_cstr(labels[i]), - SUBREM_VIEW_REMOTE_MAX_LABEL_LENGTH); - } - }, - true); - - for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) { - furi_string_free(labels[i]); - } -} - -void subrem_view_remote_set_state( - SubRemViewRemote* subrem_view_remote, - SubRemViewRemoteState state, - uint8_t presed_btn) { - furi_assert(subrem_view_remote); - with_view_model( - subrem_view_remote->view, - SubRemViewRemoteModel * model, - { - model->state = state; - model->pressed_btn = presed_btn; - }, - true); -} - -void subrem_view_remote_draw(Canvas* canvas, SubRemViewRemoteModel* model) { - canvas_clear(canvas); - canvas_set_color(canvas, ColorBlack); - - canvas_clear(canvas); - - //Icons for Labels - //canvas_draw_icon(canvas, 0, 0, &I_SubGHzRemote_LeftAlignedButtons_9x64); - canvas_draw_icon(canvas, 1, 5, &I_ButtonUp_7x4); - canvas_draw_icon(canvas, 1, 15, &I_ButtonDown_7x4); - canvas_draw_icon(canvas, 2, 23, &I_ButtonLeft_4x7); - canvas_draw_icon(canvas, 2, 33, &I_ButtonRight_4x7); - canvas_draw_icon(canvas, 0, 42, &I_Ok_btn_9x9); - canvas_draw_icon(canvas, 0, 53, &I_back_10px); - - //Labels - canvas_set_font(canvas, FontSecondary); - uint8_t y = 0; - for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) { - elements_text_box( - canvas, - SUBREM_VIEW_REMOTE_LEFT_OFFSET, - y + 2, - 126 - SUBREM_VIEW_REMOTE_LEFT_OFFSET - SUBREM_VIEW_REMOTE_RIGHT_OFFSET, - 12, - AlignLeft, - AlignBottom, - model->labels[i], - false); - y += 10; - } - - if(model->state == SubRemViewRemoteStateOFF) { - elements_button_left(canvas, "Back"); - elements_button_right(canvas, "Save"); - } else { - canvas_draw_str_aligned(canvas, 11, 62, AlignLeft, AlignBottom, "Hold=Exit."); - } - - //Status text and indicator - canvas_draw_icon(canvas, 113, 15, &I_Pin_cell_13x13); - - if(model->state == SubRemViewRemoteStateIdle || model->state == SubRemViewRemoteStateOFF) { - canvas_draw_str_aligned(canvas, 126, 10, AlignRight, AlignBottom, "Idle"); - } else { - switch(model->state) { - case SubRemViewRemoteStateSending: - canvas_draw_str_aligned(canvas, 126, 10, AlignRight, AlignBottom, "Send"); - break; - case SubRemViewRemoteStateLoading: - canvas_draw_str_aligned(canvas, 126, 10, AlignRight, AlignBottom, "Load"); - break; - default: -#if FURI_DEBUG - canvas_draw_str_aligned(canvas, 126, 10, AlignRight, AlignBottom, "Wrong_state"); -#endif - break; - } - - switch(model->pressed_btn) { - case SubRemSubKeyNameUp: - canvas_draw_icon(canvas, 116, 17, &I_Pin_arrow_up_7x9); - break; - case SubRemSubKeyNameDown: - canvas_draw_icon_ex(canvas, 116, 17, &I_Pin_arrow_up_7x9, IconRotation180); - break; - case SubRemSubKeyNameLeft: - canvas_draw_icon_ex(canvas, 115, 18, &I_Pin_arrow_up_7x9, IconRotation270); - break; - case SubRemSubKeyNameRight: - canvas_draw_icon_ex(canvas, 115, 18, &I_Pin_arrow_up_7x9, IconRotation90); - break; - case SubRemSubKeyNameOk: - canvas_draw_icon(canvas, 116, 18, &I_Pin_star_7x7); - break; - } - } -} - -bool subrem_view_remote_input(InputEvent* event, void* context) { - furi_assert(context); - SubRemViewRemote* subrem_view_remote = context; - - if(event->key == InputKeyBack && event->type == InputTypeLong) { - subrem_view_remote->callback(SubRemCustomEventViewRemoteBack, subrem_view_remote->context); - return true; - } else if(event->key == InputKeyBack && event->type == InputTypeShort) { - with_view_model( - subrem_view_remote->view, - SubRemViewRemoteModel * model, - { model->pressed_btn = 0; }, - true); - subrem_view_remote->callback( - SubRemCustomEventViewRemoteForcedStop, subrem_view_remote->context); - return true; - } else if(event->key == InputKeyBack) { - return true; - } - // BACK button processing end - - if(event->key == InputKeyUp && event->type == InputTypePress) { - subrem_view_remote->callback( - SubRemCustomEventViewRemoteStartUP, subrem_view_remote->context); - return true; - } else if(event->key == InputKeyDown && event->type == InputTypePress) { - subrem_view_remote->callback( - SubRemCustomEventViewRemoteStartDOWN, subrem_view_remote->context); - return true; - } else if(event->key == InputKeyLeft && event->type == InputTypePress) { - subrem_view_remote->callback( - SubRemCustomEventViewRemoteStartLEFT, subrem_view_remote->context); - return true; - } else if(event->key == InputKeyRight && event->type == InputTypePress) { - subrem_view_remote->callback( - SubRemCustomEventViewRemoteStartRIGHT, subrem_view_remote->context); - return true; - } else if(event->key == InputKeyOk && event->type == InputTypePress) { - subrem_view_remote->callback( - SubRemCustomEventViewRemoteStartOK, subrem_view_remote->context); - return true; - } else if(event->type == InputTypeRelease) { - subrem_view_remote->callback(SubRemCustomEventViewRemoteStop, subrem_view_remote->context); - return true; - } - - return true; -} - -void subrem_view_remote_enter(void* context) { - furi_assert(context); -} - -void subrem_view_remote_exit(void* context) { - furi_assert(context); -} - -SubRemViewRemote* subrem_view_remote_alloc() { - SubRemViewRemote* subrem_view_remote = malloc(sizeof(SubRemViewRemote)); - - // View allocation and configuration - subrem_view_remote->view = view_alloc(); - view_allocate_model( - subrem_view_remote->view, ViewModelTypeLocking, sizeof(SubRemViewRemoteModel)); - view_set_context(subrem_view_remote->view, subrem_view_remote); - view_set_draw_callback(subrem_view_remote->view, (ViewDrawCallback)subrem_view_remote_draw); - view_set_input_callback(subrem_view_remote->view, subrem_view_remote_input); - view_set_enter_callback(subrem_view_remote->view, subrem_view_remote_enter); - view_set_exit_callback(subrem_view_remote->view, subrem_view_remote_exit); - - with_view_model( - subrem_view_remote->view, - SubRemViewRemoteModel * model, - { - model->state = SubRemViewRemoteStateIdle; - - for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) { - model->labels[i] = malloc(sizeof(char) * SUBREM_VIEW_REMOTE_MAX_LABEL_LENGTH + 1); - strcpy(model->labels[i], ""); - } - - model->pressed_btn = 0; - }, - true); - return subrem_view_remote; -} - -void subrem_view_remote_free(SubRemViewRemote* subghz_remote) { - furi_assert(subghz_remote); - - with_view_model( - subghz_remote->view, - SubRemViewRemoteModel * model, - { - for(uint8_t i = 0; i < SubRemSubKeyNameMaxCount; i++) { - free(model->labels[i]); - } - }, - true); - view_free(subghz_remote->view); - free(subghz_remote); -} - -View* subrem_view_remote_get_view(SubRemViewRemote* subrem_view_remote) { - furi_assert(subrem_view_remote); - return subrem_view_remote->view; -} \ No newline at end of file diff --git a/applications/external/subghz_remote_configurator/views/remote.h b/applications/external/subghz_remote_configurator/views/remote.h deleted file mode 100644 index 5b1e8153a..000000000 --- a/applications/external/subghz_remote_configurator/views/remote.h +++ /dev/null @@ -1,36 +0,0 @@ -#pragma once - -#include -#include "../helpers/subrem_custom_event.h" -#include "../helpers/subrem_presets.h" - -typedef enum { - SubRemViewRemoteStateIdle, - SubRemViewRemoteStateLoading, - SubRemViewRemoteStateSending, - SubRemViewRemoteStateOFF, -} SubRemViewRemoteState; - -typedef struct SubRemViewRemote SubRemViewRemote; - -typedef void (*SubRemViewRemoteCallback)(SubRemCustomEvent event, void* context); - -void subrem_view_remote_set_callback( - SubRemViewRemote* subrem_view_remote, - SubRemViewRemoteCallback callback, - void* context); - -SubRemViewRemote* subrem_view_remote_alloc(); - -void subrem_view_remote_free(SubRemViewRemote* subrem_view_remote); - -View* subrem_view_remote_get_view(SubRemViewRemote* subrem_view_remote); - -void subrem_view_remote_update_data_labels( - SubRemViewRemote* subrem_view_remote, - SubRemSubFilePreset** subs_presets); - -void subrem_view_remote_set_state( - SubRemViewRemote* subrem_view_remote, - SubRemViewRemoteState state, - uint8_t presed_btn); \ No newline at end of file From 849c801a373d75a94533dec6f46dbe3e228ef334 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 14 Jul 2023 17:53:07 +0200 Subject: [PATCH 179/364] Format --- scripts/fbt_tools/fbt_extapps.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scripts/fbt_tools/fbt_extapps.py b/scripts/fbt_tools/fbt_extapps.py index b48df6095..7619c3263 100644 --- a/scripts/fbt_tools/fbt_extapps.py +++ b/scripts/fbt_tools/fbt_extapps.py @@ -176,7 +176,12 @@ class AppBuilder: deployable = False app_artifacts.dist_entries.append((deployable, fal_path)) else: - category = 'assets' if self.app.apptype == FlipperAppType.MENUEXTERNAL and not self.app.fap_category else self.app.fap_category + category = ( + "assets" + if self.app.apptype == FlipperAppType.MENUEXTERNAL + and not self.app.fap_category + else self.app.fap_category + ) fap_path = f"apps/{category}/{app_artifacts.compact.name}" app_artifacts.dist_entries.append( (self.app.is_default_deployable, fap_path) From b5744fac2198b7f76df8784047efc81a261518ec Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 14 Jul 2023 19:48:41 +0200 Subject: [PATCH 180/364] Storage check trailing slash on subdir check --- applications/services/storage/storage_external_api.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/applications/services/storage/storage_external_api.c b/applications/services/storage/storage_external_api.c index d2c98f290..f77fb4d03 100644 --- a/applications/services/storage/storage_external_api.c +++ b/applications/services/storage/storage_external_api.c @@ -421,9 +421,14 @@ FS_Error storage_common_remove(Storage* storage, const char* path) { } bool storage_is_subdir(const char* a, const char* b) { - char test[strlen(b) + 2]; - snprintf(test, sizeof(test), "%s/", b); - return strncmp(a, test, sizeof(test) - 1) == 0; + size_t len = strlen(b) + 2; + char test[len]; + strncpy(test, b, len); + if(test[len - 3] != '/') { + test[len - 2] = '/'; + test[len - 1] = '\0'; + } + return strncmp(a, test, len - 1) == 0; } FS_Error storage_common_rename(Storage* storage, const char* old_path, const char* new_path) { From 241a8e4897e2b4ef71a4d11482a56bec801796bb Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 14 Jul 2023 21:21:17 +0200 Subject: [PATCH 181/364] Fix variable item list centered scrolling text --- applications/services/gui/modules/variable_item_list.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/applications/services/gui/modules/variable_item_list.c b/applications/services/gui/modules/variable_item_list.c index e37af509a..423ab3179 100644 --- a/applications/services/gui/modules/variable_item_list.c +++ b/applications/services/gui/modules/variable_item_list.c @@ -94,14 +94,15 @@ static void variable_item_list_draw_callback(Canvas* canvas, void* _model) { canvas_draw_str(canvas, 73, item_text_y, "<"); } - elements_scrollable_text_line( + elements_scrollable_text_line_centered( canvas, (115 + 73) / 2 + 1, item_text_y, 37, item->current_value_text, scroll_counter, - false); + false, + true); if(item->current_value_index < (item->values_count - 1)) { canvas_draw_str(canvas, 115, item_text_y, ">"); From fb91f09bda67e9645a7eaaabf492082338c52b9e Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 14 Jul 2023 21:21:46 +0200 Subject: [PATCH 182/364] Fix IR Remote fap path for archive --- applications/main/archive/scenes/archive_scene_browser.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/main/archive/scenes/archive_scene_browser.c b/applications/main/archive/scenes/archive_scene_browser.c index 285b1ebf9..63561d4be 100644 --- a/applications/main/archive/scenes/archive_scene_browser.c +++ b/applications/main/archive/scenes/archive_scene_browser.c @@ -28,7 +28,7 @@ const char* archive_get_flipper_app_name(ArchiveFileTypeEnum file_type) { case ArchiveFileTypeSubghzRemote: return EXT_PATH("apps/Sub-Ghz/subghz_remote.fap"); case ArchiveFileTypeInfraredRemote: - return EXT_PATH("apps/Tools/ir_remote.fap"); + return EXT_PATH("apps/Infrared/ir_remote.fap"); case ArchiveFileTypeBadKb: return "Bad KB"; case ArchiveFileTypeU2f: From 4a56baf1584c2d35346dc25b2dbc203fd7142a9d Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 14 Jul 2023 21:23:50 +0200 Subject: [PATCH 183/364] Fix desktop keybinds path --- applications/services/desktop/desktop_settings.h | 3 ++- furi/flipper.c | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/applications/services/desktop/desktop_settings.h b/applications/services/desktop/desktop_settings.h index 3cd85870e..8a981e464 100644 --- a/applications/services/desktop/desktop_settings.h +++ b/applications/services/desktop/desktop_settings.h @@ -11,7 +11,8 @@ #define DESKTOP_SETTINGS_MAGIC (0x17) #define DESKTOP_SETTINGS_VER (11) -#define DESKTOP_KEYBINDS_PATH CFG_PATH(".desktop.keybinds") +#define DESKTOP_KEYBINDS_OLD_PATH CFG_PATH(".desktop.keybinds") +#define DESKTOP_KEYBINDS_PATH CFG_PATH("desktop.keybinds") #define DESKTOP_KEYBINDS_MAGIC (0x14) #define DESKTOP_KEYBINDS_VER (1) diff --git a/furi/flipper.c b/furi/flipper.c index f74287bc6..7298030f9 100644 --- a/furi/flipper.c +++ b/furi/flipper.c @@ -49,6 +49,8 @@ void flipper_migrate_files() { // Migrate files storage_common_copy(storage, ARCHIVE_FAV_OLD_PATH, ARCHIVE_FAV_PATH); storage_common_remove(storage, ARCHIVE_FAV_OLD_PATH); + storage_common_copy(storage, DESKTOP_KEYBINDS_OLD_PATH, DESKTOP_KEYBINDS_PATH); + storage_common_remove(storage, DESKTOP_KEYBINDS_OLD_PATH); // Int -> Ext storage_common_copy(storage, BT_SETTINGS_OLD_PATH, BT_SETTINGS_PATH); storage_common_remove(storage, BT_SETTINGS_OLD_PATH); From 4df647470dfdd15d4ecb7860c264cc3faf0a947b Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 14 Jul 2023 22:05:36 +0200 Subject: [PATCH 184/364] Saved struct fail on bigger file than expected --- lib/toolbox/saved_struct.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/toolbox/saved_struct.c b/lib/toolbox/saved_struct.c index 02b73f210..505a87b0c 100644 --- a/lib/toolbox/saved_struct.c +++ b/lib/toolbox/saved_struct.c @@ -79,12 +79,17 @@ bool saved_struct_load(const char* path, void* data, size_t size, uint8_t magic, } if(result) { - uint16_t bytes_count = storage_file_read(file, &header, sizeof(SavedStructHeader)); - bytes_count += storage_file_read(file, data_read, size); + size_t bytes_expected = sizeof(SavedStructHeader) + size; + uint64_t bytes_file = storage_file_size(file); + result = bytes_file == bytes_expected; + if(result) { + uint16_t bytes_count = storage_file_read(file, &header, sizeof(SavedStructHeader)); + bytes_count += storage_file_read(file, data_read, size); + result = bytes_count == bytes_expected; + } - if(bytes_count != (sizeof(SavedStructHeader) + size)) { + if(!result) { FURI_LOG_E(TAG, "Size mismatch of file \"%s\"", path); - result = false; } } From 2ef96be7033266fa1ee7e03453f070ee1758458b Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 14 Jul 2023 22:08:15 +0200 Subject: [PATCH 185/364] Consistent RFID archive tab name --- applications/main/archive/views/archive_browser_view.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/main/archive/views/archive_browser_view.c b/applications/main/archive/views/archive_browser_view.c index 3cb0e88ca..7a06188d2 100644 --- a/applications/main/archive/views/archive_browser_view.c +++ b/applications/main/archive/views/archive_browser_view.c @@ -13,7 +13,7 @@ static const char* ArchiveTabNames[] = { [ArchiveTabIButton] = "iButton", [ArchiveTabNFC] = "NFC", [ArchiveTabSubGhz] = "Sub-GHz", - [ArchiveTabLFRFID] = "RFID LF", + [ArchiveTabLFRFID] = "RFID", [ArchiveTabInfrared] = "Infrared", [ArchiveTabBadKb] = "Bad KB", [ArchiveTabU2f] = "U2F", From bfb00c5e4f525f5af1252b03c52846f9f8e7ebad Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 14 Jul 2023 23:01:45 +0200 Subject: [PATCH 186/364] Add option to reset desktop keybinds --- .../desktop_settings/desktop_settings_app.c | 5 ++ .../desktop_settings/desktop_settings_app.h | 3 + .../scenes/desktop_settings_scene_config.h | 1 + .../desktop_settings_scene_keybinds_reset.c | 56 +++++++++++++++++++ .../scenes/desktop_settings_scene_start.c | 9 +++ 5 files changed, 74 insertions(+) create mode 100644 applications/settings/desktop_settings/scenes/desktop_settings_scene_keybinds_reset.c diff --git a/applications/settings/desktop_settings/desktop_settings_app.c b/applications/settings/desktop_settings/desktop_settings_app.c index aad33c96c..533619363 100644 --- a/applications/settings/desktop_settings/desktop_settings_app.c +++ b/applications/settings/desktop_settings/desktop_settings_app.c @@ -58,6 +58,7 @@ DesktopSettingsApp* desktop_settings_app_alloc() { app->popup = popup_alloc(); app->submenu = submenu_alloc(); + app->dialog_ex = dialog_ex_alloc(); app->variable_item_list = variable_item_list_alloc(); app->pin_input_view = desktop_view_pin_input_alloc(); app->pin_setup_howto_view = desktop_settings_view_pin_setup_howto_alloc(); @@ -71,6 +72,8 @@ DesktopSettingsApp* desktop_settings_app_alloc() { variable_item_list_get_view(app->variable_item_list)); view_dispatcher_add_view( app->view_dispatcher, DesktopSettingsAppViewIdPopup, popup_get_view(app->popup)); + view_dispatcher_add_view( + app->view_dispatcher, DesktopSettingsAppViewDialogEx, dialog_ex_get_view(app->dialog_ex)); view_dispatcher_add_view( app->view_dispatcher, DesktopSettingsAppViewIdPinInput, @@ -92,10 +95,12 @@ void desktop_settings_app_free(DesktopSettingsApp* app) { view_dispatcher_remove_view(app->view_dispatcher, DesktopSettingsAppViewMenu); view_dispatcher_remove_view(app->view_dispatcher, DesktopSettingsAppViewVarItemList); view_dispatcher_remove_view(app->view_dispatcher, DesktopSettingsAppViewIdPopup); + view_dispatcher_remove_view(app->view_dispatcher, DesktopSettingsAppViewDialogEx); view_dispatcher_remove_view(app->view_dispatcher, DesktopSettingsAppViewIdPinInput); view_dispatcher_remove_view(app->view_dispatcher, DesktopSettingsAppViewIdPinSetupHowto); view_dispatcher_remove_view(app->view_dispatcher, DesktopSettingsAppViewIdPinSetupHowto2); variable_item_list_free(app->variable_item_list); + dialog_ex_free(app->dialog_ex); submenu_free(app->submenu); popup_free(app->popup); desktop_view_pin_input_free(app->pin_input_view); diff --git a/applications/settings/desktop_settings/desktop_settings_app.h b/applications/settings/desktop_settings/desktop_settings_app.h index 092ed8e86..936592f4c 100644 --- a/applications/settings/desktop_settings/desktop_settings_app.h +++ b/applications/settings/desktop_settings/desktop_settings_app.h @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -18,6 +19,7 @@ typedef enum { DesktopSettingsAppViewMenu, DesktopSettingsAppViewVarItemList, DesktopSettingsAppViewIdPopup, + DesktopSettingsAppViewDialogEx, DesktopSettingsAppViewIdPinInput, DesktopSettingsAppViewIdPinSetupHowto, DesktopSettingsAppViewIdPinSetupHowto2, @@ -42,6 +44,7 @@ typedef struct { VariableItemList* variable_item_list; Submenu* submenu; Popup* popup; + DialogEx* dialog_ex; DesktopViewPinInput* pin_input_view; DesktopSettingsViewPinSetupHowto* pin_setup_howto_view; DesktopSettingsViewPinSetupHowto2* pin_setup_howto2_view; diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_config.h b/applications/settings/desktop_settings/scenes/desktop_settings_scene_config.h index 458ba6bf2..9987705c8 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_config.h +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_config.h @@ -3,6 +3,7 @@ ADD_SCENE(desktop_settings, keybinds_type, KeybindsType) ADD_SCENE(desktop_settings, keybinds_key, KeybindsKey) ADD_SCENE(desktop_settings, keybinds_action_type, KeybindsActionType) ADD_SCENE(desktop_settings, keybinds_action, KeybindsAction) +ADD_SCENE(desktop_settings, keybinds_reset, KeybindsReset) ADD_SCENE(desktop_settings, pin_menu, PinMenu) ADD_SCENE(desktop_settings, pin_auth, PinAuth) diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_keybinds_reset.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_keybinds_reset.c new file mode 100644 index 000000000..99d70f662 --- /dev/null +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_keybinds_reset.c @@ -0,0 +1,56 @@ +#include "../desktop_settings_app.h" +// #include + +static void + desktop_settings_scene_keybinds_reset_dialog_callback(DialogExResult result, void* context) { + DesktopSettingsApp* app = context; + + view_dispatcher_send_custom_event(app->view_dispatcher, result); +} + +void desktop_settings_scene_keybinds_reset_on_enter(void* context) { + DesktopSettingsApp* app = context; + DialogEx* dialog_ex = app->dialog_ex; + + dialog_ex_set_header(dialog_ex, "Reset Desktop Keybinds?", 64, 10, AlignCenter, AlignCenter); + dialog_ex_set_text(dialog_ex, "Your edits will be lost!", 64, 32, AlignCenter, AlignCenter); + dialog_ex_set_left_button_text(dialog_ex, "Cancel"); + dialog_ex_set_right_button_text(dialog_ex, "Reset"); + + dialog_ex_set_context(dialog_ex, app); + dialog_ex_set_result_callback( + dialog_ex, desktop_settings_scene_keybinds_reset_dialog_callback); + + view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewDialogEx); +} + +bool desktop_settings_scene_keybinds_reset_on_event(void* context, SceneManagerEvent event) { + DesktopSettingsApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + switch(event.event) { + case DialogExResultRight: + storage_common_remove(furi_record_open(RECORD_STORAGE), DESKTOP_KEYBINDS_PATH); + furi_record_close(RECORD_STORAGE); + DESKTOP_KEYBINDS_LOAD(&app->desktop->keybinds, sizeof(app->desktop->keybinds)); + /* fall through */ + case DialogExResultLeft: + consumed = scene_manager_previous_scene(app->scene_manager); + break; + default: + break; + } + } else if(event.type == SceneManagerEventTypeBack) { + consumed = true; + } + + return consumed; +} + +void desktop_settings_scene_keybinds_reset_on_exit(void* context) { + DesktopSettingsApp* app = context; + DialogEx* dialog_ex = app->dialog_ex; + + dialog_ex_reset(dialog_ex); +} diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_start.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_start.c index 05a18b9ea..cc331e586 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_start.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_start.c @@ -6,6 +6,7 @@ enum VarItemListIndex { VarItemListIndexKeybinds, + VarItemListIndexResetKeybinds, VarItemListIndexPinSetup, VarItemListIndexAutoLockTime, VarItemListIndexAutoLockPin, @@ -58,6 +59,8 @@ void desktop_settings_scene_start_on_enter(void* context) { variable_item_list_add(variable_item_list, "Keybinds Setup", 1, NULL, NULL); + variable_item_list_add(variable_item_list, "Reset Keybinds to Default", 1, NULL, NULL); + variable_item_list_add(variable_item_list, "PIN Setup", 1, NULL, NULL); item = variable_item_list_add( @@ -101,6 +104,12 @@ bool desktop_settings_scene_start_on_event(void* context, SceneManagerEvent even scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneKeybindsType); consumed = true; break; + case VarItemListIndexResetKeybinds: + scene_manager_set_scene_state( + app->scene_manager, DesktopSettingsAppSceneKeybindsType, 0); + scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneKeybindsReset); + consumed = true; + break; case VarItemListIndexPinSetup: scene_manager_next_scene(app->scene_manager, DesktopSettingsAppScenePinMenu); consumed = true; From 881ab4a564a09ad9b2ec784bf2041f2db18c7906 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 14 Jul 2023 23:09:29 +0200 Subject: [PATCH 187/364] Remove random comment --- applications/services/desktop/views/desktop_view_main.c | 1 - 1 file changed, 1 deletion(-) diff --git a/applications/services/desktop/views/desktop_view_main.c b/applications/services/desktop/views/desktop_view_main.c index 54c2f5566..2cf1f6881 100644 --- a/applications/services/desktop/views/desktop_view_main.c +++ b/applications/services/desktop/views/desktop_view_main.c @@ -36,7 +36,6 @@ bool desktop_main_input_callback(InputEvent* event, void* context) { DesktopMainView* main_view = context; - // DesktopMainEventOpenDebug if(event->type == InputTypeShort || event->type == InputTypeLong) { if(event->key == InputKeyOk) { main_view->callback( From c7ec0d70ceb696b8e2e16f325b9f73909fd88425 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 14 Jul 2023 23:13:04 +0200 Subject: [PATCH 188/364] Uptime in 00h:00m:00s --- applications/settings/power_settings_app/views/battery_info.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/applications/settings/power_settings_app/views/battery_info.c b/applications/settings/power_settings_app/views/battery_info.c index bbd6680eb..e78e0e326 100644 --- a/applications/settings/power_settings_app/views/battery_info.c +++ b/applications/settings/power_settings_app/views/battery_info.c @@ -138,10 +138,10 @@ static void battery_info_draw_callback(Canvas* canvas, void* context) { if(model->alt) { elements_button_left(canvas, "Back"); elements_button_right(canvas, "Next"); - char uptime[17]; + char uptime[15]; uint32_t sec = furi_get_tick() / furi_kernel_get_tick_frequency(); snprintf( - uptime, sizeof(uptime), "Up %02lu:%02lu:%02lu", sec / 3600, sec / 60 % 60, sec % 60); + uptime, sizeof(uptime), "%02luh%02lum%02lus", sec / 3600, sec / 60 % 60, sec % 60); canvas_draw_str_aligned(canvas, 64, 61, AlignCenter, AlignBottom, uptime); } } From 56aa576e1c9f384e9a93c8564083334d6d450500 Mon Sep 17 00:00:00 2001 From: Sil 333033 <333033@student.mboutrecht.nl> Date: Sat, 15 Jul 2023 00:26:01 +0200 Subject: [PATCH 189/364] fixed the subghz part of the tanks game --- .../{.tanksgame => tanksgame}/application.fam | 0 .../{.tanksgame => tanksgame}/constants.h | 0 .../tanksgame/helpers/radio_device_loader.c | 64 ++++++++++++++++++ .../tanksgame/helpers/radio_device_loader.h | 15 ++++ .../images/HappyFlipper_128x64.png | Bin .../images/TanksSplashScreen_128x64.png | Bin .../images/enemy_down.png | Bin .../images/enemy_left.png | Bin .../images/enemy_right.png | Bin .../images/enemy_up.png | Bin .../images/projectile_down.png | Bin .../images/projectile_left.png | Bin .../images/projectile_right.png | Bin .../images/projectile_up.png | Bin .../images/tank_base.png | Bin .../images/tank_down.png | Bin .../images/tank_explosion.png | Bin .../images/tank_hedgehog.png | Bin .../images/tank_left.png | Bin .../images/tank_right.png | Bin .../images/tank_stone.png | Bin .../images/tank_up.png | Bin .../images/tank_wall.png | Bin .../{.tanksgame => tanksgame}/tanksIcon.png | Bin .../{.tanksgame => tanksgame}/tanks_game.c | 13 +++- 25 files changed, 91 insertions(+), 1 deletion(-) rename applications/external/{.tanksgame => tanksgame}/application.fam (100%) rename applications/external/{.tanksgame => tanksgame}/constants.h (100%) create mode 100644 applications/external/tanksgame/helpers/radio_device_loader.c create mode 100644 applications/external/tanksgame/helpers/radio_device_loader.h rename applications/external/{.tanksgame => tanksgame}/images/HappyFlipper_128x64.png (100%) rename applications/external/{.tanksgame => tanksgame}/images/TanksSplashScreen_128x64.png (100%) rename applications/external/{.tanksgame => tanksgame}/images/enemy_down.png (100%) rename applications/external/{.tanksgame => tanksgame}/images/enemy_left.png (100%) rename applications/external/{.tanksgame => tanksgame}/images/enemy_right.png (100%) rename applications/external/{.tanksgame => tanksgame}/images/enemy_up.png (100%) rename applications/external/{.tanksgame => tanksgame}/images/projectile_down.png (100%) rename applications/external/{.tanksgame => tanksgame}/images/projectile_left.png (100%) rename applications/external/{.tanksgame => tanksgame}/images/projectile_right.png (100%) rename applications/external/{.tanksgame => tanksgame}/images/projectile_up.png (100%) rename applications/external/{.tanksgame => tanksgame}/images/tank_base.png (100%) rename applications/external/{.tanksgame => tanksgame}/images/tank_down.png (100%) rename applications/external/{.tanksgame => tanksgame}/images/tank_explosion.png (100%) rename applications/external/{.tanksgame => tanksgame}/images/tank_hedgehog.png (100%) rename applications/external/{.tanksgame => tanksgame}/images/tank_left.png (100%) rename applications/external/{.tanksgame => tanksgame}/images/tank_right.png (100%) rename applications/external/{.tanksgame => tanksgame}/images/tank_stone.png (100%) rename applications/external/{.tanksgame => tanksgame}/images/tank_up.png (100%) rename applications/external/{.tanksgame => tanksgame}/images/tank_wall.png (100%) rename applications/external/{.tanksgame => tanksgame}/tanksIcon.png (100%) rename applications/external/{.tanksgame => tanksgame}/tanks_game.c (99%) diff --git a/applications/external/.tanksgame/application.fam b/applications/external/tanksgame/application.fam similarity index 100% rename from applications/external/.tanksgame/application.fam rename to applications/external/tanksgame/application.fam diff --git a/applications/external/.tanksgame/constants.h b/applications/external/tanksgame/constants.h similarity index 100% rename from applications/external/.tanksgame/constants.h rename to applications/external/tanksgame/constants.h diff --git a/applications/external/tanksgame/helpers/radio_device_loader.c b/applications/external/tanksgame/helpers/radio_device_loader.c new file mode 100644 index 000000000..d2cffde58 --- /dev/null +++ b/applications/external/tanksgame/helpers/radio_device_loader.c @@ -0,0 +1,64 @@ +#include "radio_device_loader.h" + +#include +#include + +static void radio_device_loader_power_on() { + uint8_t attempts = 0; + while(!furi_hal_power_is_otg_enabled() && attempts++ < 5) { + furi_hal_power_enable_otg(); + //CC1101 power-up time + furi_delay_ms(10); + } +} + +static void radio_device_loader_power_off() { + if(furi_hal_power_is_otg_enabled()) furi_hal_power_disable_otg(); +} + +bool radio_device_loader_is_connect_external(const char* name) { + bool is_connect = false; + bool is_otg_enabled = furi_hal_power_is_otg_enabled(); + + if(!is_otg_enabled) { + radio_device_loader_power_on(); + } + + const SubGhzDevice* device = subghz_devices_get_by_name(name); + if(device) { + is_connect = subghz_devices_is_connect(device); + } + + if(!is_otg_enabled) { + radio_device_loader_power_off(); + } + return is_connect; +} + +const SubGhzDevice* radio_device_loader_set( + const SubGhzDevice* current_radio_device, + SubGhzRadioDeviceType radio_device_type) { + const SubGhzDevice* radio_device; + + if(radio_device_type == SubGhzRadioDeviceTypeExternalCC1101 && + radio_device_loader_is_connect_external(SUBGHZ_DEVICE_CC1101_EXT_NAME)) { + radio_device_loader_power_on(); + radio_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_EXT_NAME); + subghz_devices_begin(radio_device); + } else if(current_radio_device == NULL) { + radio_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME); + } else { + radio_device_loader_end(current_radio_device); + radio_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME); + } + + return radio_device; +} + +void radio_device_loader_end(const SubGhzDevice* radio_device) { + furi_assert(radio_device); + radio_device_loader_power_off(); + if(radio_device != subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME)) { + subghz_devices_end(radio_device); + } +} \ No newline at end of file diff --git a/applications/external/tanksgame/helpers/radio_device_loader.h b/applications/external/tanksgame/helpers/radio_device_loader.h new file mode 100644 index 000000000..bee4e2c36 --- /dev/null +++ b/applications/external/tanksgame/helpers/radio_device_loader.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +/** SubGhzRadioDeviceType */ +typedef enum { + SubGhzRadioDeviceTypeInternal, + SubGhzRadioDeviceTypeExternalCC1101, +} SubGhzRadioDeviceType; + +const SubGhzDevice* radio_device_loader_set( + const SubGhzDevice* current_radio_device, + SubGhzRadioDeviceType radio_device_type); + +void radio_device_loader_end(const SubGhzDevice* radio_device); \ No newline at end of file diff --git a/applications/external/.tanksgame/images/HappyFlipper_128x64.png b/applications/external/tanksgame/images/HappyFlipper_128x64.png similarity index 100% rename from applications/external/.tanksgame/images/HappyFlipper_128x64.png rename to applications/external/tanksgame/images/HappyFlipper_128x64.png diff --git a/applications/external/.tanksgame/images/TanksSplashScreen_128x64.png b/applications/external/tanksgame/images/TanksSplashScreen_128x64.png similarity index 100% rename from applications/external/.tanksgame/images/TanksSplashScreen_128x64.png rename to applications/external/tanksgame/images/TanksSplashScreen_128x64.png diff --git a/applications/external/.tanksgame/images/enemy_down.png b/applications/external/tanksgame/images/enemy_down.png similarity index 100% rename from applications/external/.tanksgame/images/enemy_down.png rename to applications/external/tanksgame/images/enemy_down.png diff --git a/applications/external/.tanksgame/images/enemy_left.png b/applications/external/tanksgame/images/enemy_left.png similarity index 100% rename from applications/external/.tanksgame/images/enemy_left.png rename to applications/external/tanksgame/images/enemy_left.png diff --git a/applications/external/.tanksgame/images/enemy_right.png b/applications/external/tanksgame/images/enemy_right.png similarity index 100% rename from applications/external/.tanksgame/images/enemy_right.png rename to applications/external/tanksgame/images/enemy_right.png diff --git a/applications/external/.tanksgame/images/enemy_up.png b/applications/external/tanksgame/images/enemy_up.png similarity index 100% rename from applications/external/.tanksgame/images/enemy_up.png rename to applications/external/tanksgame/images/enemy_up.png diff --git a/applications/external/.tanksgame/images/projectile_down.png b/applications/external/tanksgame/images/projectile_down.png similarity index 100% rename from applications/external/.tanksgame/images/projectile_down.png rename to applications/external/tanksgame/images/projectile_down.png diff --git a/applications/external/.tanksgame/images/projectile_left.png b/applications/external/tanksgame/images/projectile_left.png similarity index 100% rename from applications/external/.tanksgame/images/projectile_left.png rename to applications/external/tanksgame/images/projectile_left.png diff --git a/applications/external/.tanksgame/images/projectile_right.png b/applications/external/tanksgame/images/projectile_right.png similarity index 100% rename from applications/external/.tanksgame/images/projectile_right.png rename to applications/external/tanksgame/images/projectile_right.png diff --git a/applications/external/.tanksgame/images/projectile_up.png b/applications/external/tanksgame/images/projectile_up.png similarity index 100% rename from applications/external/.tanksgame/images/projectile_up.png rename to applications/external/tanksgame/images/projectile_up.png diff --git a/applications/external/.tanksgame/images/tank_base.png b/applications/external/tanksgame/images/tank_base.png similarity index 100% rename from applications/external/.tanksgame/images/tank_base.png rename to applications/external/tanksgame/images/tank_base.png diff --git a/applications/external/.tanksgame/images/tank_down.png b/applications/external/tanksgame/images/tank_down.png similarity index 100% rename from applications/external/.tanksgame/images/tank_down.png rename to applications/external/tanksgame/images/tank_down.png diff --git a/applications/external/.tanksgame/images/tank_explosion.png b/applications/external/tanksgame/images/tank_explosion.png similarity index 100% rename from applications/external/.tanksgame/images/tank_explosion.png rename to applications/external/tanksgame/images/tank_explosion.png diff --git a/applications/external/.tanksgame/images/tank_hedgehog.png b/applications/external/tanksgame/images/tank_hedgehog.png similarity index 100% rename from applications/external/.tanksgame/images/tank_hedgehog.png rename to applications/external/tanksgame/images/tank_hedgehog.png diff --git a/applications/external/.tanksgame/images/tank_left.png b/applications/external/tanksgame/images/tank_left.png similarity index 100% rename from applications/external/.tanksgame/images/tank_left.png rename to applications/external/tanksgame/images/tank_left.png diff --git a/applications/external/.tanksgame/images/tank_right.png b/applications/external/tanksgame/images/tank_right.png similarity index 100% rename from applications/external/.tanksgame/images/tank_right.png rename to applications/external/tanksgame/images/tank_right.png diff --git a/applications/external/.tanksgame/images/tank_stone.png b/applications/external/tanksgame/images/tank_stone.png similarity index 100% rename from applications/external/.tanksgame/images/tank_stone.png rename to applications/external/tanksgame/images/tank_stone.png diff --git a/applications/external/.tanksgame/images/tank_up.png b/applications/external/tanksgame/images/tank_up.png similarity index 100% rename from applications/external/.tanksgame/images/tank_up.png rename to applications/external/tanksgame/images/tank_up.png diff --git a/applications/external/.tanksgame/images/tank_wall.png b/applications/external/tanksgame/images/tank_wall.png similarity index 100% rename from applications/external/.tanksgame/images/tank_wall.png rename to applications/external/tanksgame/images/tank_wall.png diff --git a/applications/external/.tanksgame/tanksIcon.png b/applications/external/tanksgame/tanksIcon.png similarity index 100% rename from applications/external/.tanksgame/tanksIcon.png rename to applications/external/tanksgame/tanksIcon.png diff --git a/applications/external/.tanksgame/tanks_game.c b/applications/external/tanksgame/tanks_game.c similarity index 99% rename from applications/external/.tanksgame/tanks_game.c rename to applications/external/tanksgame/tanks_game.c index 52ce6b36e..95fc56ca7 100644 --- a/applications/external/.tanksgame/tanks_game.c +++ b/applications/external/tanksgame/tanks_game.c @@ -11,6 +11,7 @@ #include #include #include +#include "helpers/radio_device_loader.h" #include "constants.h" @@ -1215,7 +1216,15 @@ int32_t tanks_game_app(void* p) { size_t message_max_len = 180; uint8_t incomingMessage[180] = {0}; SubGhzTxRxWorker* subghz_txrx = subghz_tx_rx_worker_alloc(); - subghz_tx_rx_worker_start(subghz_txrx, frequency); + + subghz_devices_init(); + const SubGhzDevice* subghz_device = + radio_device_loader_set(NULL, SubGhzRadioDeviceTypeExternalCC1101); + + subghz_devices_reset(subghz_device); + subghz_devices_load_preset(subghz_device, FuriHalSubGhzPresetOok650Async, NULL); + + subghz_tx_rx_worker_start(subghz_txrx, subghz_device, frequency); furi_hal_power_suppress_charge_enter(); for(bool processing = true; processing;) { @@ -1438,6 +1447,8 @@ int32_t tanks_game_app(void* p) { subghz_tx_rx_worker_free(subghz_txrx); } + subghz_devices_deinit(); + furi_timer_free(timer); view_port_enabled_set(view_port, false); gui_remove_view_port(gui, view_port); From f8eaf31a7740999b3a8c4670bc31661557404284 Mon Sep 17 00:00:00 2001 From: Sil 333033 <333033@student.mboutrecht.nl> Date: Sat, 15 Jul 2023 00:28:23 +0200 Subject: [PATCH 190/364] fixed weird NULL pointer at main menu --- applications/external/tanksgame/tanks_game.c | 60 +++++++++++--------- 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/applications/external/tanksgame/tanks_game.c b/applications/external/tanksgame/tanks_game.c index 95fc56ca7..a32721a82 100644 --- a/applications/external/tanksgame/tanks_game.c +++ b/applications/external/tanksgame/tanks_game.c @@ -1286,39 +1286,43 @@ int32_t tanks_game_app(void* p) { } break; case InputKeyRight: - if(tanks_state->state == GameStateCooperativeClient) { - FuriString* goesRight = furi_string_alloc(); - char arr[2]; - arr[0] = GoesRight; - arr[1] = 0; - furi_string_set(goesRight, (char*)&arr); + if(!(tanks_state->state == GameStateMenu)) { + if(tanks_state->state == GameStateCooperativeClient) { + FuriString* goesRight = furi_string_alloc(); + char arr[2]; + arr[0] = GoesRight; + arr[1] = 0; + furi_string_set(goesRight, (char*)&arr); - subghz_tx_rx_worker_write( - subghz_txrx, - (uint8_t*)furi_string_get_cstr(goesRight), - strlen(furi_string_get_cstr(goesRight))); - furi_string_free(goesRight); - } else { - tanks_state->p1->moving = true; - tanks_state->p1->direction = DirectionRight; + subghz_tx_rx_worker_write( + subghz_txrx, + (uint8_t*)furi_string_get_cstr(goesRight), + strlen(furi_string_get_cstr(goesRight))); + furi_string_free(goesRight); + } else { + tanks_state->p1->moving = true; + tanks_state->p1->direction = DirectionRight; + } } break; case InputKeyLeft: - if(tanks_state->state == GameStateCooperativeClient) { - FuriString* goesLeft = furi_string_alloc(); - char arr[2]; - arr[0] = GoesLeft; - arr[1] = 0; - furi_string_set(goesLeft, (char*)&arr); + if(!(tanks_state->state == GameStateMenu)) { + if(tanks_state->state == GameStateCooperativeClient) { + FuriString* goesLeft = furi_string_alloc(); + char arr[2]; + arr[0] = GoesLeft; + arr[1] = 0; + furi_string_set(goesLeft, (char*)&arr); - subghz_tx_rx_worker_write( - subghz_txrx, - (uint8_t*)furi_string_get_cstr(goesLeft), - strlen(furi_string_get_cstr(goesLeft))); - furi_string_free(goesLeft); - } else { - tanks_state->p1->moving = true; - tanks_state->p1->direction = DirectionLeft; + subghz_tx_rx_worker_write( + subghz_txrx, + (uint8_t*)furi_string_get_cstr(goesLeft), + strlen(furi_string_get_cstr(goesLeft))); + furi_string_free(goesLeft); + } else { + tanks_state->p1->moving = true; + tanks_state->p1->direction = DirectionLeft; + } } break; case InputKeyOk: From caa1bc92536500358fc42ce919733133ae8893ac Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Sun, 16 Jul 2023 03:40:15 +0200 Subject: [PATCH 191/364] Archive merge menus, change buttons, cleanup code --- .../main/archive/helpers/archive_browser.c | 15 ++- .../archive/scenes/archive_scene_browser.c | 15 +-- .../main/archive/views/archive_browser_view.c | 122 +++++++++--------- .../main/archive/views/archive_browser_view.h | 1 + 4 files changed, 84 insertions(+), 69 deletions(-) diff --git a/applications/main/archive/helpers/archive_browser.c b/applications/main/archive/helpers/archive_browser.c index b5be220fc..de1c30c21 100644 --- a/applications/main/archive/helpers/archive_browser.c +++ b/applications/main/archive/helpers/archive_browser.c @@ -469,18 +469,31 @@ void archive_show_file_menu(ArchiveBrowserView* browser, bool show, bool manage) { if(show) { model->menu = true; - model->menu_manage = manage; model->menu_idx = 0; menu_array_reset(model->context_menu); + model->menu_manage = manage; + model->menu_can_switch = true; if(archive_is_item_in_array(model, model->item_idx)) { ArchiveFile_t* selected = files_array_get(model->files, model->item_idx - model->array_offset); selected->fav = archive_is_favorite("%s", furi_string_get_cstr(selected->path)); + if(selected->type == ArchiveFileTypeSearch) { + if(!furi_string_cmp_str(selected->path, "/app:search/Search for files")) { + model->menu_manage = false; + model->menu_can_switch = false; + } else { + model->menu = false; + } + } + } else { + model->menu_manage = true; + model->menu_can_switch = false; } } else { model->menu = false; model->menu_idx = 0; + menu_array_reset(model->context_menu); } }, true); diff --git a/applications/main/archive/scenes/archive_scene_browser.c b/applications/main/archive/scenes/archive_scene_browser.c index 63561d4be..b2f1a52c2 100644 --- a/applications/main/archive/scenes/archive_scene_browser.c +++ b/applications/main/archive/scenes/archive_scene_browser.c @@ -59,14 +59,14 @@ static void const char* app_name = archive_get_flipper_app_name(selected->type); - if(selected->type == ArchiveFileTypeSearch) { + if(selected->type == ArchiveFileTypeFolder) { + archive_switch_tab(browser, TAB_LEFT); + archive_enter_dir(browser, selected->path); + } else if(selected->type == ArchiveFileTypeSearch) { while(archive_get_tab(browser) != ArchiveTabSearch) { archive_switch_tab(browser, TAB_LEFT); } - ArchiveApp* archive; - with_view_model( - browser->view, ArchiveBrowserViewModel * model, { archive = model->archive; }, false); - view_dispatcher_send_custom_event(archive->view_dispatcher, ArchiveBrowserEventSearch); + browser->callback(ArchiveBrowserEventSearch, browser->context); } else if(app_name) { if(selected->is_app) { char* param = strrchr(furi_string_get_cstr(selected->path), '/'); @@ -150,10 +150,7 @@ bool archive_scene_browser_on_event(void* context, SceneManagerEvent event) { consumed = true; break; case ArchiveBrowserEventFileMenuRun: - if(selected->type == ArchiveFileTypeFolder) { - archive_switch_tab(browser, TAB_LEFT); - archive_enter_dir(browser, selected->path); - } else if(archive_is_known_app(selected->type)) { + if(archive_is_known_app(selected->type)) { archive_run_in_app(browser, selected, favorites); } archive_show_file_menu(browser, false, false); diff --git a/applications/main/archive/views/archive_browser_view.c b/applications/main/archive/views/archive_browser_view.c index 7a06188d2..931c0c7a5 100644 --- a/applications/main/archive/views/archive_browser_view.c +++ b/applications/main/archive/views/archive_browser_view.c @@ -132,22 +132,28 @@ static void render_item_menu(Canvas* canvas, ArchiveBrowserViewModel* model) { const uint8_t menu_height = 48; const uint8_t line_height = 10; const uint8_t calc_height = menu_height - ((MENU_ITEMS - size_menu - 1) * line_height); - const uint8_t off = (MENU_ITEMS - size_menu) * (line_height / 2); canvas_set_color(canvas, ColorWhite); - canvas_draw_box(canvas, 72, off + 2, 56, calc_height + 4); + canvas_draw_box(canvas, 72, 2, 56, calc_height + 4); canvas_set_color(canvas, ColorBlack); - elements_slightly_rounded_frame(canvas, 71, off + 2, 57, calc_height + 4); + elements_slightly_rounded_frame(canvas, 71, 2, 57, calc_height + 4); - canvas_draw_str(canvas, 74, off + 11, model->menu_manage ? "Manage:" : "Actions:"); + canvas_draw_str_aligned( + canvas, 100, 11, AlignCenter, AlignBottom, model->menu_manage ? "Manage:" : "Actions:"); + if(model->menu_can_switch) { + if(model->menu_manage) { + canvas_draw_icon(canvas, 74, 4, &I_ButtonLeft_4x7); + } else { + canvas_draw_icon(canvas, 121, 4, &I_ButtonRight_4x7); + } + } for(size_t i = 0; i < size_menu; i++) { ArchiveContextMenuItem_t* current = menu_array_get(model->context_menu, i); canvas_draw_str( - canvas, 82, off + 11 + (i + 1) * line_height, furi_string_get_cstr(current->text)); + canvas, 82, 11 + (i + 1) * line_height, furi_string_get_cstr(current->text)); } - canvas_draw_icon( - canvas, 74, off + 4 + (model->menu_idx + 1) * line_height, &I_ButtonRight_4x7); + canvas_draw_icon(canvas, 74, 4 + (model->menu_idx + 1) * line_height, &I_ButtonRight_4x7); } static void archive_draw_frame(Canvas* canvas, uint16_t idx, bool scrollbar, bool moving) { @@ -346,7 +352,6 @@ static bool archive_view_input(InputEvent* event, void* context) { ArchiveBrowserView* browser = context; bool in_menu; - int32_t cur_item_idx; bool move_fav_mode; bool is_loading; with_view_model( @@ -354,7 +359,6 @@ static bool archive_view_input(InputEvent* event, void* context) { ArchiveBrowserViewModel * model, { in_menu = model->menu; - cur_item_idx = model->item_idx; move_fav_mode = model->move_fav; is_loading = model->folder_loading || model->list_loading; }, @@ -365,7 +369,7 @@ static bool archive_view_input(InputEvent* event, void* context) { } if(in_menu) { if(event->type != InputTypeShort) { - return true; // RETURN + return true; // Return without doing anything } if(event->key == InputKeyUp || event->key == InputKeyDown) { with_view_model( @@ -380,6 +384,20 @@ static bool archive_view_input(InputEvent* event, void* context) { } }, true); + } else if(event->key == InputKeyLeft || event->key == InputKeyRight) { + with_view_model( + browser->view, + ArchiveBrowserViewModel * model, + { + if(model->menu_can_switch) { + if((event->key == InputKeyLeft && model->menu_manage) || + (event->key == InputKeyRight && !model->menu_manage)) { + model->menu_manage = !model->menu_manage; + menu_array_reset(model->context_menu); + } + } + }, + true); } else if(event->key == InputKeyOk) { uint32_t idx; with_view_model( @@ -396,19 +414,7 @@ static bool archive_view_input(InputEvent* event, void* context) { browser->callback(ArchiveBrowserEventFileMenuClose, browser->context); } } else { - if(event->type == InputTypeShort) { - if(event->key == InputKeyLeft || event->key == InputKeyRight) { - if(move_fav_mode) return false; - archive_switch_tab(browser, event->key); - } else if(event->key == InputKeyBack) { - if(move_fav_mode) { - browser->callback(ArchiveBrowserEventExitFavMove, browser->context); - } else { - browser->callback(ArchiveBrowserEventExit, browser->context); - } - } - } - + ArchiveFile_t* selected = archive_get_current_file(browser); if((event->key == InputKeyUp || event->key == InputKeyDown) && (event->type == InputTypeShort || event->type == InputTypeRepeat)) { with_view_model( @@ -493,46 +499,44 @@ static bool archive_view_input(InputEvent* event, void* context) { }, false); archive_update_offset(browser); - } - - ArchiveFile_t* selected = archive_get_current_file(browser); - bool favorites = archive_get_tab(browser) == ArchiveTabFavorites; - if(selected && selected->type == ArchiveFileTypeSearch) { - if((cur_item_idx == 0 || favorites) && event->key == InputKeyOk) { - if(event->type == InputTypeShort) { - browser->callback( - favorites ? ArchiveBrowserEventFileMenuRun : ArchiveBrowserEventSearch, - browser->context); - } else if(event->type == InputTypeLong) { + } else if(event->type == InputTypeShort) { + if(event->key == InputKeyLeft || event->key == InputKeyRight) { + if(move_fav_mode) { + return true; // Return without doing anything + } else { + archive_switch_tab(browser, event->key); + } + } else if(event->key == InputKeyOk) { + if(move_fav_mode) { + browser->callback(ArchiveBrowserEventSaveFavMove, browser->context); + } else if(selected && selected->type == ArchiveFileTypeFolder) { + browser->callback(ArchiveBrowserEventEnterDir, browser->context); + } else if(selected && archive_is_known_app(selected->type)) { + browser->callback(ArchiveBrowserEventFileMenuRun, browser->context); + } else { browser->callback(ArchiveBrowserEventFileMenuOpen, browser->context); } - } - } else if(event->key == InputKeyOk) { - if(selected) { - bool folder = selected->type == ArchiveFileTypeFolder; - - if(event->type == InputTypeShort) { - if(favorites) { - if(move_fav_mode) { - browser->callback(ArchiveBrowserEventSaveFavMove, browser->context); - } else { - browser->callback(ArchiveBrowserEventFileMenuRun, browser->context); - } - } else if(folder) { - browser->callback(ArchiveBrowserEventEnterDir, browser->context); - } else { - browser->callback(ArchiveBrowserEventFileMenuOpen, browser->context); - } - } else if(event->type == InputTypeLong) { - if(move_fav_mode) { - browser->callback(ArchiveBrowserEventSaveFavMove, browser->context); - } else if(folder || favorites) { - browser->callback(ArchiveBrowserEventFileMenuOpen, browser->context); - } + } else if(event->key == InputKeyBack) { + if(move_fav_mode) { + browser->callback(ArchiveBrowserEventExitFavMove, browser->context); + } else { + browser->callback(ArchiveBrowserEventExit, browser->context); + } + } + } else if(event->type == InputTypeLong) { + if(event->key == InputKeyOk) { + if(move_fav_mode) { + browser->callback(ArchiveBrowserEventSaveFavMove, browser->context); + } else { + browser->callback(ArchiveBrowserEventFileMenuOpen, browser->context); + } + } else if(event->key == InputKeyBack) { + if(move_fav_mode) { + browser->callback(ArchiveBrowserEventExitFavMove, browser->context); + } else { + browser->callback(ArchiveBrowserEventManageMenuOpen, browser->context); } } - } else if(event->key == InputKeyBack && event->type == InputTypeLong) { - browser->callback(ArchiveBrowserEventManageMenuOpen, browser->context); } } diff --git a/applications/main/archive/views/archive_browser_view.h b/applications/main/archive/views/archive_browser_view.h index f87d59a2b..2cfa65145 100644 --- a/applications/main/archive/views/archive_browser_view.h +++ b/applications/main/archive/views/archive_browser_view.h @@ -100,6 +100,7 @@ typedef struct { uint8_t menu_idx; bool menu; bool menu_manage; + bool menu_can_switch; char* clipboard; bool clipboard_copy; menu_array_t context_menu; From 9f94f2aa82765cd376f6a6e2ac3d493e798fb85e Mon Sep 17 00:00:00 2001 From: Sil 333033 <333033@student.mboutrecht.nl> Date: Sun, 16 Jul 2023 16:27:15 +0200 Subject: [PATCH 192/364] updated nrf24 sniffer & mousejacker --- .../nrf24mousejacker/lib/nrf24/nrf24.c | 10 +++ .../nrf24mousejacker/lib/nrf24/nrf24.h | 7 ++ .../external/nrf24mousejacker/mousejacker.c | 65 ++++++++++--------- .../nrf24mousejacker/mousejacker_ducky.h | 1 + .../external/nrf24sniff/lib/nrf24/nrf24.c | 10 +++ .../external/nrf24sniff/lib/nrf24/nrf24.h | 7 ++ applications/external/nrf24sniff/nrfsniff.c | 31 ++++++--- 7 files changed, 92 insertions(+), 39 deletions(-) diff --git a/applications/external/nrf24mousejacker/lib/nrf24/nrf24.c b/applications/external/nrf24mousejacker/lib/nrf24/nrf24.c index 8b3776445..67c5dde1c 100644 --- a/applications/external/nrf24mousejacker/lib/nrf24/nrf24.c +++ b/applications/external/nrf24mousejacker/lib/nrf24/nrf24.c @@ -517,4 +517,14 @@ uint8_t nrf24_find_channel( } return ch; +} + +bool nrf24_check_connected(FuriHalSpiBusHandle* handle) { + uint8_t status = nrf24_status(handle); + + if(status != 0x00) { + return true; + } else { + return false; + } } \ No newline at end of file diff --git a/applications/external/nrf24mousejacker/lib/nrf24/nrf24.h b/applications/external/nrf24mousejacker/lib/nrf24/nrf24.h index 3cfcfe089..046784bc7 100644 --- a/applications/external/nrf24mousejacker/lib/nrf24/nrf24.h +++ b/applications/external/nrf24mousejacker/lib/nrf24/nrf24.h @@ -361,6 +361,13 @@ void int32_to_bytes(uint32_t val, uint8_t* out, bool bigendian); */ uint32_t bytes_to_int32(uint8_t* bytes, bool bigendian); +/** Check if the nrf24 is connected + * @param handle - pointer to FuriHalSpiHandle + * + * @return true if connected, otherwise false +*/ +bool nrf24_check_connected(FuriHalSpiBusHandle* handle); + #ifdef __cplusplus } #endif \ No newline at end of file diff --git a/applications/external/nrf24mousejacker/mousejacker.c b/applications/external/nrf24mousejacker/mousejacker.c index 31ed1bcab..1a4d7f4ed 100644 --- a/applications/external/nrf24mousejacker/mousejacker.c +++ b/applications/external/nrf24mousejacker/mousejacker.c @@ -16,9 +16,10 @@ #define TAG "mousejacker" #define LOGITECH_MAX_CHANNEL 85 #define NRFSNIFF_APP_PATH_FOLDER EXT_PATH("apps_data/nrf24sniff") +#define NRFSNIFF_APP_PATH_FOLDER_ADDRESSES EXT_PATH("apps_data/nrf24sniff/addresses.txt") #define NRFSNIFF_APP_PATH_EXTENSION ".txt" #define NRFSNIFF_APP_FILENAME "addresses.txt" -#define MOUSEJACKER_APP_PATH_FOLDER STORAGE_APP_DATA_PATH_PREFIX +#define MOUSEJACKER_APP_PATH_FOLDER EXT_PATH("apps_data/mousejacker") #define MOUSEJACKER_APP_PATH_EXTENSION ".txt" #define MAX_ADDRS 100 @@ -33,12 +34,13 @@ typedef struct { } PluginEvent; uint8_t addrs_count = 0; -uint8_t addr_idx = 0; +int8_t addr_idx = 0; uint8_t loaded_addrs[MAX_ADDRS][6]; // first byte is rate, the rest are the address char target_fmt_text[] = "Target addr: %s"; char target_address_str[12] = "None"; char target_text[30]; +char index_text[30]; static void render_callback(Canvas* const canvas, void* ctx) { furi_assert(ctx); @@ -54,8 +56,15 @@ static void render_callback(Canvas* const canvas, void* ctx) { snprintf(target_text, sizeof(target_text), target_fmt_text, target_address_str); canvas_draw_str_aligned(canvas, 7, 10, AlignLeft, AlignBottom, target_text); canvas_draw_str_aligned(canvas, 22, 20, AlignLeft, AlignBottom, "<- select address ->"); - canvas_draw_str_aligned(canvas, 10, 30, AlignLeft, AlignBottom, "Press Ok button to "); - canvas_draw_str_aligned(canvas, 10, 40, AlignLeft, AlignBottom, "browse for ducky script"); + snprintf( + index_text, sizeof(index_text), "Address index: %d/%d", addr_idx + 1, addrs_count); + canvas_draw_str_aligned(canvas, 10, 30, AlignLeft, AlignBottom, index_text); + canvas_draw_str_aligned(canvas, 10, 40, AlignLeft, AlignBottom, "Press Ok button to "); + canvas_draw_str_aligned(canvas, 10, 50, AlignLeft, AlignBottom, "browse for ducky script"); + if(!plugin_state->is_nrf24_connected) { + canvas_draw_str_aligned( + canvas, 10, 60, AlignLeft, AlignBottom, "Connect NRF24 to GPIO!"); + } } else if(plugin_state->addr_err) { canvas_draw_str_aligned( canvas, 10, 10, AlignLeft, AlignBottom, "Error: No nrfsniff folder"); @@ -95,6 +104,7 @@ static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queu static void mousejacker_state_init(PluginState* const plugin_state) { plugin_state->is_thread_running = false; + plugin_state->is_nrf24_connected = true; } static void hexlify(uint8_t* in, uint8_t size, char* out) { @@ -133,27 +143,17 @@ static bool open_ducky_script(Stream* stream, PluginState* plugin_state) { } static bool open_addrs_file(Stream* stream) { - DialogsApp* dialogs = furi_record_open("dialogs"); bool result = false; FuriString* path; path = furi_string_alloc(); - furi_string_set(path, NRFSNIFF_APP_PATH_FOLDER); + furi_string_set(path, NRFSNIFF_APP_PATH_FOLDER_ADDRESSES); - DialogsFileBrowserOptions browser_options; - dialog_file_browser_set_basic_options( - &browser_options, NRFSNIFF_APP_PATH_EXTENSION, &I_sub1_10px); - browser_options.hide_ext = false; - - bool ret = dialog_file_browser_show(dialogs, path, path, &browser_options); - - furi_record_close("dialogs"); - if(ret) { - if(!file_stream_open(stream, furi_string_get_cstr(path), FSAM_READ, FSOM_OPEN_EXISTING)) { - FURI_LOG_D(TAG, "Cannot open file \"%s\"", furi_string_get_cstr(path)); - } else { - result = true; - } + if(!file_stream_open(stream, furi_string_get_cstr(path), FSAM_READ, FSOM_OPEN_EXISTING)) { + FURI_LOG_D(TAG, "Cannot open file \"%s\"", furi_string_get_cstr(path)); + } else { + result = true; } + furi_string_free(path); return result; } @@ -278,12 +278,6 @@ static int32_t mj_worker_thread(void* ctx) { return 0; } -void start_mjthread(PluginState* plugin_state) { - if(!plugin_state->is_thread_running) { - furi_thread_start(plugin_state->mjthread); - } -} - int32_t mousejacker_app(void* p) { UNUSED(p); FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent)); @@ -320,6 +314,10 @@ int32_t mousejacker_app(void* p) { furi_thread_set_context(plugin_state->mjthread, plugin_state); furi_thread_set_callback(plugin_state->mjthread, mj_worker_thread); + while(!furi_hal_speaker_acquire(100)) { + furi_delay_ms(100); + } + // spawn load file dialog to choose sniffed addresses file if(load_addrs_file(plugin_state->file_stream)) { addr_idx = 0; @@ -348,21 +346,27 @@ int32_t mousejacker_app(void* p) { case InputKeyRight: if(!plugin_state->addr_err) { addr_idx++; - if(addr_idx > addrs_count) addr_idx = 0; + if(addr_idx >= addrs_count) addr_idx = 0; hexlify(loaded_addrs[addr_idx] + 1, 5, target_address_str); } break; case InputKeyLeft: if(!plugin_state->addr_err) { addr_idx--; - if(addr_idx == 0) addr_idx = addrs_count - 1; + if(addr_idx < 0) addr_idx = addrs_count - 1; hexlify(loaded_addrs[addr_idx] + 1, 5, target_address_str); } break; case InputKeyOk: if(!plugin_state->addr_err) { - if(!plugin_state->is_thread_running) { - start_mjthread(plugin_state); + if(!nrf24_check_connected(nrf24_HANDLE)) { + plugin_state->is_nrf24_connected = false; + view_port_update(view_port); + furi_hal_speaker_start(100, 100); + furi_delay_ms(100); + furi_hal_speaker_stop(); + } else if(!plugin_state->is_thread_running) { + furi_thread_start(plugin_state->mjthread); view_port_update(view_port); } } @@ -389,6 +393,7 @@ int32_t mousejacker_app(void* p) { furi_thread_free(plugin_state->mjthread); nrf24_deinit(); + furi_hal_speaker_release(); view_port_enabled_set(view_port, false); gui_remove_view_port(gui, view_port); furi_record_close(RECORD_GUI); diff --git a/applications/external/nrf24mousejacker/mousejacker_ducky.h b/applications/external/nrf24mousejacker/mousejacker_ducky.h index e1a422ea7..01dc32c30 100644 --- a/applications/external/nrf24mousejacker/mousejacker_ducky.h +++ b/applications/external/nrf24mousejacker/mousejacker_ducky.h @@ -26,6 +26,7 @@ typedef struct { bool addr_err; bool is_thread_running; bool is_ducky_running; + bool is_nrf24_connected; bool close_thread_please; Storage* storage; FuriThread* mjthread; diff --git a/applications/external/nrf24sniff/lib/nrf24/nrf24.c b/applications/external/nrf24sniff/lib/nrf24/nrf24.c index 8b3776445..67c5dde1c 100644 --- a/applications/external/nrf24sniff/lib/nrf24/nrf24.c +++ b/applications/external/nrf24sniff/lib/nrf24/nrf24.c @@ -517,4 +517,14 @@ uint8_t nrf24_find_channel( } return ch; +} + +bool nrf24_check_connected(FuriHalSpiBusHandle* handle) { + uint8_t status = nrf24_status(handle); + + if(status != 0x00) { + return true; + } else { + return false; + } } \ No newline at end of file diff --git a/applications/external/nrf24sniff/lib/nrf24/nrf24.h b/applications/external/nrf24sniff/lib/nrf24/nrf24.h index 3cfcfe089..046784bc7 100644 --- a/applications/external/nrf24sniff/lib/nrf24/nrf24.h +++ b/applications/external/nrf24sniff/lib/nrf24/nrf24.h @@ -361,6 +361,13 @@ void int32_to_bytes(uint32_t val, uint8_t* out, bool bigendian); */ uint32_t bytes_to_int32(uint8_t* bytes, bool bigendian); +/** Check if the nrf24 is connected + * @param handle - pointer to FuriHalSpiHandle + * + * @return true if connected, otherwise false +*/ +bool nrf24_check_connected(FuriHalSpiBusHandle* handle); + #ifdef __cplusplus } #endif \ No newline at end of file diff --git a/applications/external/nrf24sniff/nrfsniff.c b/applications/external/nrf24sniff/nrfsniff.c index 9d5fffe80..f15f7e80f 100644 --- a/applications/external/nrf24sniff/nrfsniff.c +++ b/applications/external/nrf24sniff/nrfsniff.c @@ -11,11 +11,11 @@ #define LOGITECH_MAX_CHANNEL 85 #define COUNT_THRESHOLD 2 -#define DEFAULT_SAMPLE_TIME 8000 +#define DEFAULT_SAMPLE_TIME 4000 #define MAX_ADDRS 100 #define MAX_CONFIRMED 32 -#define NRFSNIFF_APP_PATH_FOLDER STORAGE_APP_DATA_PATH_PREFIX +#define NRFSNIFF_APP_PATH_FOLDER EXT_PATH("apps_data/nrf24sniff") #define NRFSNIFF_APP_FILENAME "addresses.txt" #define TAG "nrfsniff" @@ -346,6 +346,10 @@ int32_t nrfsniff_app(void* p) { storage_common_migrate(storage, EXT_PATH("nrfsniff"), NRFSNIFF_APP_PATH_FOLDER); storage_common_mkdir(storage, NRFSNIFF_APP_PATH_FOLDER); + while(!furi_hal_speaker_acquire(100)) { + furi_delay_ms(100); + } + PluginEvent event; for(bool processing = true; processing;) { FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100); @@ -390,13 +394,21 @@ int32_t nrfsniff_app(void* p) { break; case InputKeyOk: // toggle sniffing - sniffing_state = !sniffing_state; - if(sniffing_state) { - clear_cache(); - start_sniffing(); - start = furi_get_tick(); - } else - wrap_up(storage, notification); + if(nrf24_check_connected(nrf24_HANDLE)) { + sniffing_state = !sniffing_state; + if(sniffing_state) { + clear_cache(); + start_sniffing(); + start = furi_get_tick(); + } else { + wrap_up(storage, notification); + } + } else { + furi_hal_speaker_start(100, 100); + furi_delay_ms(100); + furi_hal_speaker_stop(); + } + break; case InputKeyBack: if(event.input.type == InputTypeLong) processing = false; @@ -445,6 +457,7 @@ int32_t nrfsniff_app(void* p) { target_rate = 8; // rate can be either 8 (2Mbps) or 0 (1Mbps) sniffing_state = false; nrf24_deinit(); + furi_hal_speaker_release(); view_port_enabled_set(view_port, false); gui_remove_view_port(gui, view_port); furi_record_close(RECORD_GUI); From 59d9002198efc1cac2e1f6b8bc18d62277f690f6 Mon Sep 17 00:00:00 2001 From: Sil 333033 <333033@student.mboutrecht.nl> Date: Sun, 16 Jul 2023 17:58:18 +0200 Subject: [PATCH 193/364] changed hardcoded path and removed mj folder --- applications/external/nrf24mousejacker/mousejacker.c | 10 ++-------- applications/external/nrf24sniff/nrfsniff.c | 2 +- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/applications/external/nrf24mousejacker/mousejacker.c b/applications/external/nrf24mousejacker/mousejacker.c index 1a4d7f4ed..5b3cce336 100644 --- a/applications/external/nrf24mousejacker/mousejacker.c +++ b/applications/external/nrf24mousejacker/mousejacker.c @@ -15,11 +15,8 @@ #define TAG "mousejacker" #define LOGITECH_MAX_CHANNEL 85 -#define NRFSNIFF_APP_PATH_FOLDER EXT_PATH("apps_data/nrf24sniff") #define NRFSNIFF_APP_PATH_FOLDER_ADDRESSES EXT_PATH("apps_data/nrf24sniff/addresses.txt") -#define NRFSNIFF_APP_PATH_EXTENSION ".txt" -#define NRFSNIFF_APP_FILENAME "addresses.txt" -#define MOUSEJACKER_APP_PATH_FOLDER EXT_PATH("apps_data/mousejacker") +#define BADKB_FOLDER EXT_PATH("badkb") #define MOUSEJACKER_APP_PATH_EXTENSION ".txt" #define MAX_ADDRS 100 @@ -118,7 +115,7 @@ static bool open_ducky_script(Stream* stream, PluginState* plugin_state) { bool result = false; FuriString* path; path = furi_string_alloc(); - furi_string_set(path, MOUSEJACKER_APP_PATH_FOLDER); + furi_string_set(path, BADKB_FOLDER); DialogsFileBrowserOptions browser_options; dialog_file_browser_set_basic_options( @@ -303,9 +300,6 @@ int32_t mousejacker_app(void* p) { gui_add_view_port(gui, view_port, GuiLayerFullscreen); plugin_state->storage = furi_record_open(RECORD_STORAGE); - storage_common_migrate( - plugin_state->storage, EXT_PATH("mousejacker"), MOUSEJACKER_APP_PATH_FOLDER); - storage_common_mkdir(plugin_state->storage, MOUSEJACKER_APP_PATH_FOLDER); plugin_state->file_stream = file_stream_alloc(plugin_state->storage); plugin_state->mjthread = furi_thread_alloc(); diff --git a/applications/external/nrf24sniff/nrfsniff.c b/applications/external/nrf24sniff/nrfsniff.c index f15f7e80f..18cbe6fb7 100644 --- a/applications/external/nrf24sniff/nrfsniff.c +++ b/applications/external/nrf24sniff/nrfsniff.c @@ -15,7 +15,7 @@ #define MAX_ADDRS 100 #define MAX_CONFIRMED 32 -#define NRFSNIFF_APP_PATH_FOLDER EXT_PATH("apps_data/nrf24sniff") +#define NRFSNIFF_APP_PATH_FOLDER STORAGE_APP_DATA_PATH_PREFIX #define NRFSNIFF_APP_FILENAME "addresses.txt" #define TAG "nrfsniff" From da919372e22f737af991b94c78f99ddb01d36352 Mon Sep 17 00:00:00 2001 From: gid9798 <30450294+gid9798@users.noreply.github.com> Date: Sun, 16 Jul 2023 20:28:15 +0300 Subject: [PATCH 194/364] Archive separate menu --- .../main/archive/helpers/archive_browser.c | 2 + .../main/archive/helpers/archive_menu.h | 2 +- .../main/archive/views/archive_browser_view.c | 247 ++++++++++-------- .../main/archive/views/archive_browser_view.h | 1 + 4 files changed, 145 insertions(+), 107 deletions(-) diff --git a/applications/main/archive/helpers/archive_browser.c b/applications/main/archive/helpers/archive_browser.c index 43888cb4d..e83426d53 100644 --- a/applications/main/archive/helpers/archive_browser.c +++ b/applications/main/archive/helpers/archive_browser.c @@ -421,6 +421,7 @@ void archive_show_file_menu(ArchiveBrowserView* browser, bool show) { if(show) { if(archive_is_item_in_array(model, model->item_idx)) { model->menu = true; + model->filemang = false; model->menu_idx = 0; menu_array_reset(model->context_menu); ArchiveFile_t* selected = @@ -430,6 +431,7 @@ void archive_show_file_menu(ArchiveBrowserView* browser, bool show) { } } else { model->menu = false; + model->filemang = false; model->menu_idx = 0; } }, diff --git a/applications/main/archive/helpers/archive_menu.h b/applications/main/archive/helpers/archive_menu.h index 4a85b851d..bd5200b21 100644 --- a/applications/main/archive/helpers/archive_menu.h +++ b/applications/main/archive/helpers/archive_menu.h @@ -43,7 +43,7 @@ ARRAY_DEF( #pragma GCC diagnostic ignored "-Wunused-function" // Using in applications/archive/views/archive_browser_view.c static void - archive_menu_add_item(ArchiveContextMenuItem_t* obj, FuriString* text, uint32_t event) { + archive_menu_add_item(ArchiveContextMenuItem_t* obj, const char* text, uint32_t event) { obj->text = furi_string_alloc_set(text); obj->event = event; } diff --git a/applications/main/archive/views/archive_browser_view.c b/applications/main/archive/views/archive_browser_view.c index fd7de727d..6df78fae3 100644 --- a/applications/main/archive/views/archive_browser_view.c +++ b/applications/main/archive/views/archive_browser_view.c @@ -53,61 +53,65 @@ void archive_browser_set_callback( static void render_item_menu(Canvas* canvas, ArchiveBrowserViewModel* model) { if(menu_array_size(model->context_menu) == 0) { // Context menu is empty, init array - FuriString* item_run = furi_string_alloc_set("Run In App"); - FuriString* item_pin = furi_string_alloc_set("Pin"); - FuriString* item_info = furi_string_alloc_set("Info"); - FuriString* item_show = furi_string_alloc_set("Show"); - FuriString* item_rename = furi_string_alloc_set("Rename"); - FuriString* item_delete = furi_string_alloc_set("Delete"); + const char* item_pin = "Pin"; // Need init context menu ArchiveFile_t* selected = files_array_get(model->files, model->item_idx - model->array_offset); if((selected->fav) || (model->tab_idx == ArchiveTabFavorites)) { - furi_string_set(item_pin, "Unpin"); + item_pin = "Unpin"; } if(selected->type == ArchiveFileTypeFolder) { + // Folder + // Rename, Delete + model->filemang = true; + //FURI_LOG_D(TAG, "Directory type"); archive_menu_add_item( menu_array_push_raw(model->context_menu), - item_rename, + "Rename", ArchiveBrowserEventFileMenuRename); archive_menu_add_item( menu_array_push_raw(model->context_menu), - item_delete, + "Delete", ArchiveBrowserEventFileMenuDelete); } else if(!archive_is_known_app(selected->type)) { + // UnKnown app type //FURI_LOG_D(TAG, "Unknown type"); - archive_menu_add_item( - menu_array_push_raw(model->context_menu), - item_info, - ArchiveBrowserEventFileMenuInfo); - if(selected->is_text_file) { + if(model->filemang) { + // Rename, Delete archive_menu_add_item( menu_array_push_raw(model->context_menu), - item_show, - ArchiveBrowserEventFileMenuShow); + "Rename", + ArchiveBrowserEventFileMenuRename); + archive_menu_add_item( + menu_array_push_raw(model->context_menu), + "Delete", + ArchiveBrowserEventFileMenuDelete); + } else { + // Info, [Show], + archive_menu_add_item( + menu_array_push_raw(model->context_menu), + "Info", + ArchiveBrowserEventFileMenuInfo); + if(selected->is_text_file) { + archive_menu_add_item( + menu_array_push_raw(model->context_menu), + "Show", + ArchiveBrowserEventFileMenuShow); + } } - archive_menu_add_item( - menu_array_push_raw(model->context_menu), - item_rename, - ArchiveBrowserEventFileMenuRename); - archive_menu_add_item( - menu_array_push_raw(model->context_menu), - item_delete, - ArchiveBrowserEventFileMenuDelete); + } else if(model->tab_idx == ArchiveTabFavorites) { + // Favorites tab + // Run, Unpin, [Show], Move + //FURI_LOG_D(TAG, "ArchiveTabFavorites"); - - furi_string_set(item_rename, "Move"); - archive_menu_add_item( - menu_array_push_raw(model->context_menu), - item_run, - ArchiveBrowserEventFileMenuRun); + menu_array_push_raw(model->context_menu), "Run", ArchiveBrowserEventFileMenuRun); archive_menu_add_item( menu_array_push_raw(model->context_menu), item_pin, @@ -115,73 +119,73 @@ static void render_item_menu(Canvas* canvas, ArchiveBrowserViewModel* model) { if(selected->type <= ArchiveFileTypeBadUsb) { archive_menu_add_item( menu_array_push_raw(model->context_menu), - item_show, + "Show", ArchiveBrowserEventFileMenuShow); } archive_menu_add_item( - menu_array_push_raw(model->context_menu), - item_rename, - ArchiveBrowserEventFileMenuRename); + menu_array_push_raw(model->context_menu), "Move", ArchiveBrowserEventEnterFavMove); } else if(selected->is_app) { + // Only U2F? + // Run, Info, [Show], Pin, Delete + model->filemang = false; + //FURI_LOG_D(TAG, "3 types"); archive_menu_add_item( - menu_array_push_raw(model->context_menu), - item_run, - ArchiveBrowserEventFileMenuRun); + menu_array_push_raw(model->context_menu), "Run", ArchiveBrowserEventFileMenuRun); archive_menu_add_item( - menu_array_push_raw(model->context_menu), - item_info, - ArchiveBrowserEventFileMenuInfo); + menu_array_push_raw(model->context_menu), "Info", ArchiveBrowserEventFileMenuInfo); if(selected->type <= ArchiveFileTypeBadUsb) { archive_menu_add_item( menu_array_push_raw(model->context_menu), - item_show, + "Show", ArchiveBrowserEventFileMenuShow); } archive_menu_add_item( menu_array_push_raw(model->context_menu), item_pin, ArchiveBrowserEventFileMenuPin); + archive_menu_add_item( menu_array_push_raw(model->context_menu), - item_delete, + "Delete", ArchiveBrowserEventFileMenuDelete); } else { + // Other //FURI_LOG_D(TAG, "All menu"); - archive_menu_add_item( - menu_array_push_raw(model->context_menu), - item_run, - ArchiveBrowserEventFileMenuRun); - archive_menu_add_item( - menu_array_push_raw(model->context_menu), - item_pin, - ArchiveBrowserEventFileMenuPin); - archive_menu_add_item( - menu_array_push_raw(model->context_menu), - item_info, - ArchiveBrowserEventFileMenuInfo); - if(selected->type <= ArchiveFileTypeBadUsb) { + + if(model->filemang) { + // Rename, Delete archive_menu_add_item( menu_array_push_raw(model->context_menu), - item_show, - ArchiveBrowserEventFileMenuShow); - } - archive_menu_add_item( - menu_array_push_raw(model->context_menu), - item_rename, - ArchiveBrowserEventFileMenuRename); - archive_menu_add_item( - menu_array_push_raw(model->context_menu), - item_delete, - ArchiveBrowserEventFileMenuDelete); - } + "Rename", + ArchiveBrowserEventFileMenuRename); + archive_menu_add_item( + menu_array_push_raw(model->context_menu), + "Delete", + ArchiveBrowserEventFileMenuDelete); + } else { + // Run, Pin, Info, [Show] - furi_string_free(item_run); - furi_string_free(item_pin); - furi_string_free(item_info); - furi_string_free(item_show); - furi_string_free(item_rename); - furi_string_free(item_delete); + archive_menu_add_item( + menu_array_push_raw(model->context_menu), + "Run", + ArchiveBrowserEventFileMenuRun); + archive_menu_add_item( + menu_array_push_raw(model->context_menu), + item_pin, + ArchiveBrowserEventFileMenuPin); + archive_menu_add_item( + menu_array_push_raw(model->context_menu), + "Info", + ArchiveBrowserEventFileMenuInfo); + if(selected->type <= ArchiveFileTypeBadUsb) { + archive_menu_add_item( + menu_array_push_raw(model->context_menu), + "Show", + ArchiveBrowserEventFileMenuShow); + } + } + } } /*else { FURI_LOG_D(TAG, "menu_array_size already set: %d", menu_array_size(model->context_menu)); }*/ @@ -190,23 +194,30 @@ static void render_item_menu(Canvas* canvas, ArchiveBrowserViewModel* model) { const uint8_t line_height = 10; canvas_set_color(canvas, ColorWhite); - uint8_t calc_height = menu_height - ((MENU_ITEMS - size_menu) * line_height); - canvas_draw_box(canvas, 71, 1, 57, calc_height + 4); + uint8_t calc_height = menu_height - ((MENU_ITEMS - size_menu - 1) * line_height); + canvas_draw_box(canvas, 71, 1, 57, calc_height + 4 + 2); canvas_set_color(canvas, ColorBlack); - elements_slightly_rounded_frame(canvas, 70, 2, 58, calc_height + 4); + elements_slightly_rounded_frame(canvas, 70, 2, 58, calc_height + 4 + 2); + canvas_draw_line(canvas, 70, 13, 128, 13); /*FURI_LOG_D( TAG, "size_menu: %d, calc_height: %d, menu_idx: %d", size_menu, calc_height, model->menu_idx);*/ + if(model->filemang) { + canvas_draw_str(canvas, 82, 11, "FileMan"); // XXX + } else { + canvas_draw_str(canvas, 82, 11, "Actions"); + } for(size_t i = 0; i < size_menu; i++) { ArchiveContextMenuItem_t* current = menu_array_get(model->context_menu, i); - canvas_draw_str(canvas, 82, 11 + i * line_height, furi_string_get_cstr(current->text)); + canvas_draw_str( + canvas, 82, 11 + 2 + (i + 1) * line_height, furi_string_get_cstr(current->text)); } - canvas_draw_icon(canvas, 74, 4 + model->menu_idx * line_height, &I_ButtonRight_4x7); + canvas_draw_icon(canvas, 74, 4 + 2 + (model->menu_idx + 1) * line_height, &I_ButtonRight_4x7); } static void archive_draw_frame(Canvas* canvas, uint16_t idx, bool scrollbar, bool moving) { @@ -392,6 +403,57 @@ static bool is_file_list_load_required(ArchiveBrowserViewModel* model) { return false; } +static inline void + archive_view_menu_input_processing(ArchiveBrowserView* browser, InputEvent* event) { + // only InputShort type + if(event->key == InputKeyUp || event->key == InputKeyDown) { + with_view_model( + browser->view, + ArchiveBrowserViewModel * model, + { + size_t size_menu = menu_array_size(model->context_menu); + if(event->key == InputKeyUp) { + model->menu_idx = ((model->menu_idx - 1) + size_menu) % size_menu; + } else if(event->key == InputKeyDown) { + model->menu_idx = (model->menu_idx + 1) % size_menu; + } + }, + true); + } else if(event->key == InputKeyLeft || event->key == InputKeyRight) { + with_view_model( + browser->view, + ArchiveBrowserViewModel * model, + { + ArchiveFile_t* selected = + files_array_get(model->files, model->item_idx - model->array_offset); + + if(selected->type != ArchiveFileTypeFolder && + model->tab_idx != ArchiveTabFavorites) { + model->filemang = !model->filemang; + model->menu_idx = 0; + menu_array_reset(model->context_menu); + selected->fav = + archive_is_favorite("%s", furi_string_get_cstr(selected->path)); + } + }, + true); + } else if(event->key == InputKeyOk) { + uint32_t idx; + with_view_model( + browser->view, + ArchiveBrowserViewModel * model, + { + ArchiveContextMenuItem_t* current = + menu_array_get(model->context_menu, model->menu_idx); + idx = current->event; + }, + false); + browser->callback(idx, browser->context); + } else if(event->key == InputKeyBack) { + browser->callback(ArchiveBrowserEventFileMenuClose, browser->context); + } +} + static bool archive_view_input(InputEvent* event, void* context) { furi_assert(event); furi_assert(context); @@ -418,34 +480,7 @@ static bool archive_view_input(InputEvent* event, void* context) { if(event->type != InputTypeShort) { return true; // RETURN } - if(event->key == InputKeyUp || event->key == InputKeyDown) { - with_view_model( - browser->view, - ArchiveBrowserViewModel * model, - { - size_t size_menu = menu_array_size(model->context_menu); - if(event->key == InputKeyUp) { - model->menu_idx = ((model->menu_idx - 1) + size_menu) % size_menu; - } else if(event->key == InputKeyDown) { - model->menu_idx = (model->menu_idx + 1) % size_menu; - } - }, - true); - } else if(event->key == InputKeyOk) { - uint32_t idx; - with_view_model( - browser->view, - ArchiveBrowserViewModel * model, - { - ArchiveContextMenuItem_t* current = - menu_array_get(model->context_menu, model->menu_idx); - idx = current->event; - }, - false); - browser->callback(idx, browser->context); - } else if(event->key == InputKeyBack) { - browser->callback(ArchiveBrowserEventFileMenuClose, browser->context); - } + archive_view_menu_input_processing(browser, event); } else { if(event->type == InputTypeShort) { if(event->key == InputKeyLeft || event->key == InputKeyRight) { diff --git a/applications/main/archive/views/archive_browser_view.h b/applications/main/archive/views/archive_browser_view.h index d3a57f755..ff0963719 100644 --- a/applications/main/archive/views/archive_browser_view.h +++ b/applications/main/archive/views/archive_browser_view.h @@ -97,6 +97,7 @@ typedef struct { bool move_fav; bool list_loading; bool folder_loading; + bool filemang; // XXX uint32_t item_cnt; int32_t item_idx; From 5de2f8d201369dffe7978afbd41a955f898614a3 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Sun, 16 Jul 2023 23:51:01 +0100 Subject: [PATCH 195/364] Archive fix folder favorites --- applications/main/archive/scenes/archive_scene_browser.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/applications/main/archive/scenes/archive_scene_browser.c b/applications/main/archive/scenes/archive_scene_browser.c index b2f1a52c2..096ed673d 100644 --- a/applications/main/archive/scenes/archive_scene_browser.c +++ b/applications/main/archive/scenes/archive_scene_browser.c @@ -59,10 +59,7 @@ static void const char* app_name = archive_get_flipper_app_name(selected->type); - if(selected->type == ArchiveFileTypeFolder) { - archive_switch_tab(browser, TAB_LEFT); - archive_enter_dir(browser, selected->path); - } else if(selected->type == ArchiveFileTypeSearch) { + if(selected->type == ArchiveFileTypeSearch) { while(archive_get_tab(browser) != ArchiveTabSearch) { archive_switch_tab(browser, TAB_LEFT); } @@ -297,6 +294,9 @@ bool archive_scene_browser_on_event(void* context, SceneManagerEvent event) { consumed = true; break; case ArchiveBrowserEventEnterDir: + if(favorites) { + archive_switch_tab(browser, TAB_LEFT); + } archive_enter_dir(browser, selected->path); consumed = true; break; From 20f6394ad8177e7e7589ad185e16193f7bab5f11 Mon Sep 17 00:00:00 2001 From: hedger Date: Mon, 17 Jul 2023 11:51:15 +0400 Subject: [PATCH 196/364] [FL-3431] Radio headers in SDK (#2881) --- applications/drivers/subghz/application.fam | 1 + firmware/targets/f18/api_symbols.csv | 3 ++- firmware/targets/f7/api_symbols.csv | 7 ++++++- lib/drivers/SConscript | 3 +++ lib/subghz/SConscript | 1 + scripts/fbt/appmanifest.py | 11 ++++++++++- 6 files changed, 23 insertions(+), 3 deletions(-) diff --git a/applications/drivers/subghz/application.fam b/applications/drivers/subghz/application.fam index aaf0e1bd9..2ee114833 100644 --- a/applications/drivers/subghz/application.fam +++ b/applications/drivers/subghz/application.fam @@ -4,5 +4,6 @@ App( targets=["f7"], entry_point="subghz_device_cc1101_ext_ep", requires=["subghz"], + sdk_headers=["cc1101_ext/cc1101_ext_interconnect.h"], fap_libs=["hwdrivers"], ) diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index bbeaa3b1a..20c829755 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,34.1,, +Version,+,34.2,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -82,6 +82,7 @@ Header,+,firmware/targets/furi_hal_include/furi_hal_usb_hid_u2f.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_version.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_vibro.h,, Header,+,lib/digital_signal/digital_signal.h,, +Header,+,lib/drivers/cc1101_regs.h,, Header,+,lib/flipper_application/api_hashtable/api_hashtable.h,, Header,+,lib/flipper_application/api_hashtable/compilesort.hpp,, Header,+,lib/flipper_application/flipper_application.h,, diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 0d33f70f6..cc1d016dd 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,6 @@ entry,status,name,type,params -Version,+,34.1,, +Version,+,34.2,, +Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -87,6 +88,7 @@ Header,+,firmware/targets/furi_hal_include/furi_hal_usb_hid_u2f.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_version.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_vibro.h,, Header,+,lib/digital_signal/digital_signal.h,, +Header,+,lib/drivers/cc1101_regs.h,, Header,+,lib/flipper_application/api_hashtable/api_hashtable.h,, Header,+,lib/flipper_application/api_hashtable/compilesort.hpp,, Header,+,lib/flipper_application/flipper_application.h,, @@ -192,6 +194,7 @@ Header,+,lib/subghz/blocks/encoder.h,, Header,+,lib/subghz/blocks/generic.h,, Header,+,lib/subghz/blocks/math.h,, Header,+,lib/subghz/devices/cc1101_configs.h,, +Header,+,lib/subghz/devices/cc1101_int/cc1101_int_interconnect.h,, Header,+,lib/subghz/environment.h,, Header,+,lib/subghz/protocols/raw.h,, Header,+,lib/subghz/receiver.h,, @@ -2697,6 +2700,7 @@ Function,+,subghz_block_generic_deserialize,SubGhzProtocolStatus,"SubGhzBlockGen Function,+,subghz_block_generic_deserialize_check_count_bit,SubGhzProtocolStatus,"SubGhzBlockGeneric*, FlipperFormat*, uint16_t" Function,+,subghz_block_generic_get_preset_name,void,"const char*, FuriString*" Function,+,subghz_block_generic_serialize,SubGhzProtocolStatus,"SubGhzBlockGeneric*, FlipperFormat*, SubGhzRadioPreset*" +Function,-,subghz_device_cc1101_ext_ep,const FlipperAppPluginDescriptor*, Function,+,subghz_devices_begin,_Bool,const SubGhzDevice* Function,+,subghz_devices_deinit,void, Function,+,subghz_devices_end,void,const SubGhzDevice* @@ -3436,6 +3440,7 @@ Variable,+,sequence_set_vibro_on,const NotificationSequence, Variable,+,sequence_single_vibro,const NotificationSequence, Variable,+,sequence_solid_yellow,const NotificationSequence, Variable,+,sequence_success,const NotificationSequence, +Variable,-,subghz_device_cc1101_int,const SubGhzDevice, Variable,+,subghz_device_cc1101_preset_2fsk_dev2_38khz_async_regs,const uint8_t[], Variable,+,subghz_device_cc1101_preset_2fsk_dev47_6khz_async_regs,const uint8_t[], Variable,+,subghz_device_cc1101_preset_gfsk_9_99kb_async_regs,const uint8_t[], diff --git a/lib/drivers/SConscript b/lib/drivers/SConscript index 3b7ee2401..103472ccb 100644 --- a/lib/drivers/SConscript +++ b/lib/drivers/SConscript @@ -4,6 +4,9 @@ env.Append( CPPPATH=[ "#/lib/drivers", ], + SDK_HEADERS=[ + File("cc1101_regs.h"), + ], ) diff --git a/lib/subghz/SConscript b/lib/subghz/SConscript index 2c42a5157..82c925c1a 100644 --- a/lib/subghz/SConscript +++ b/lib/subghz/SConscript @@ -20,6 +20,7 @@ env.Append( File("subghz_setting.h"), File("subghz_protocol_registry.h"), File("devices/cc1101_configs.h"), + File("devices/cc1101_int/cc1101_int_interconnect.h"), ], ) diff --git a/scripts/fbt/appmanifest.py b/scripts/fbt/appmanifest.py index 5b830dda9..0064b3591 100644 --- a/scripts/fbt/appmanifest.py +++ b/scripts/fbt/appmanifest.py @@ -325,7 +325,16 @@ class AppBuildset: def get_sdk_headers(self): sdk_headers = [] for app in self.apps: - sdk_headers.extend([app._appdir.File(header) for header in app.sdk_headers]) + sdk_headers.extend( + [ + src._appdir.File(header) + for src in [ + app, + *(plugin for plugin in app._plugins), + ] + for header in src.sdk_headers + ] + ) return sdk_headers def get_apps_of_type(self, apptype: FlipperAppType, all_known: bool = False): From 9bb04832a84901b0fcf1b0921b472825fbca6bef Mon Sep 17 00:00:00 2001 From: Dzhos Oleksii <35292229+Programistich@users.noreply.github.com> Date: Mon, 17 Jul 2023 11:03:27 +0300 Subject: [PATCH 197/364] IButton: on delete scene key name not fully display if so long (#2882) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: ã‚ã --- .../main/ibutton/scenes/ibutton_scene_delete_confirm.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/applications/main/ibutton/scenes/ibutton_scene_delete_confirm.c b/applications/main/ibutton/scenes/ibutton_scene_delete_confirm.c index 587cb748c..b293af952 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_delete_confirm.c +++ b/applications/main/ibutton/scenes/ibutton_scene_delete_confirm.c @@ -12,15 +12,15 @@ void ibutton_scene_delete_confirm_on_enter(void* context) { widget_add_button_element( widget, GuiButtonTypeRight, "Delete", ibutton_widget_callback, context); - furi_string_printf(tmp, "Delete %s?", ibutton->key_name); - widget_add_string_element( - widget, 128 / 2, 0, AlignCenter, AlignTop, FontPrimary, furi_string_get_cstr(tmp)); + furi_string_printf(tmp, "\e#Delete %s?\e#", ibutton->key_name); + widget_add_text_box_element( + widget, 0, 0, 128, 23, AlignCenter, AlignCenter, furi_string_get_cstr(tmp), false); furi_string_reset(tmp); ibutton_protocols_render_brief_data(ibutton->protocols, key, tmp); widget_add_string_multiline_element( - widget, 128 / 2, 16, AlignCenter, AlignTop, FontSecondary, furi_string_get_cstr(tmp)); + widget, 128 / 2, 24, AlignCenter, AlignTop, FontSecondary, furi_string_get_cstr(tmp)); view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget); furi_string_free(tmp); From 4915bd769715256e15dbc7f96bd1757024e3de65 Mon Sep 17 00:00:00 2001 From: ClaraCrazy Date: Mon, 17 Jul 2023 16:39:20 +0200 Subject: [PATCH 198/364] Add ESP-Flasher --- .../external/esp-flasher/application.fam | 29 + .../assets/DolphinCommon_56x48.png | Bin 0 -> 1416 bytes .../assets/KeyBackspaceSelected_16x9.png | Bin 0 -> 1812 bytes .../esp-flasher/assets/KeyBackspace_16x9.png | Bin 0 -> 1829 bytes .../assets/KeyKeyboardSelected_10x11.png | Bin 0 -> 7210 bytes .../esp-flasher/assets/KeyKeyboard_10x11.png | Bin 0 -> 7763 bytes .../assets/KeySaveSelected_24x11.png | Bin 0 -> 1853 bytes .../esp-flasher/assets/KeySave_24x11.png | Bin 0 -> 1863 bytes .../esp-flasher/assets/Text_10x10.png | Bin 0 -> 158 bytes .../assets/WarningDolphin_45x42.png | Bin 0 -> 1139 bytes .../external/esp-flasher/esp_flasher_app.c | 135 ++++ .../external/esp-flasher/esp_flasher_app.h | 13 + .../external/esp-flasher/esp_flasher_app_i.h | 75 ++ .../esp-flasher/esp_flasher_custom_event.h | 9 + .../external/esp-flasher/esp_flasher_uart.c | 106 +++ .../external/esp-flasher/esp_flasher_uart.h | 14 + .../external/esp-flasher/esp_flasher_worker.c | 255 +++++++ .../external/esp-flasher/esp_flasher_worker.h | 17 + .../esp-flasher/file/sequential_file.c | 46 ++ .../esp-flasher/file/sequential_file.h | 15 + .../lib/esp-serial-flasher/CMakeCache.txt | 366 ++++++++++ .../CMakeFiles/3.16.3/CMakeCCompiler.cmake | 76 ++ .../CMakeFiles/3.16.3/CMakeCXXCompiler.cmake | 88 +++ .../3.16.3/CMakeDetermineCompilerABI_C.bin | Bin 0 -> 16552 bytes .../3.16.3/CMakeDetermineCompilerABI_CXX.bin | Bin 0 -> 16560 bytes .../CMakeFiles/3.16.3/CMakeSystem.cmake | 15 + .../3.16.3/CompilerIdC/CMakeCCompilerId.c | 671 ++++++++++++++++++ .../CMakeFiles/3.16.3/CompilerIdC/a.out | Bin 0 -> 16712 bytes .../CompilerIdCXX/CMakeCXXCompilerId.cpp | 660 +++++++++++++++++ .../CMakeFiles/3.16.3/CompilerIdCXX/a.out | Bin 0 -> 16720 bytes .../CMakeFiles/CMakeOutput.log | 463 ++++++++++++ .../CMakeFiles/cmake.check_cache | 1 + .../lib/esp-serial-flasher/CMakeLists.txt | 119 ++++ .../lib/esp-serial-flasher/Kconfig | 34 + .../lib/esp-serial-flasher/idf_component.yml | 3 + .../esp-serial-flasher/include/esp_loader.h | 313 ++++++++ .../include/esp_loader_io.h | 112 +++ .../esp-serial-flasher/include/serial_io.h | 24 + .../lib/esp-serial-flasher/port/esp32_port.c | 181 +++++ .../lib/esp-serial-flasher/port/esp32_port.h | 59 ++ .../esp-serial-flasher/port/esp32_spi_port.c | 298 ++++++++ .../esp-serial-flasher/port/esp32_spi_port.h | 60 ++ .../esp-serial-flasher/port/raspberry_port.c | 302 ++++++++ .../esp-serial-flasher/port/raspberry_port.h | 36 + .../lib/esp-serial-flasher/port/stm32_port.c | 140 ++++ .../lib/esp-serial-flasher/port/stm32_port.h | 38 + .../lib/esp-serial-flasher/port/zephyr_port.c | 170 +++++ .../lib/esp-serial-flasher/port/zephyr_port.h | 39 + .../private_include/esp_targets.h | 33 + .../private_include/md5_hash.h | 28 + .../private_include/protocol.h | 230 ++++++ .../private_include/protocol_prv.h | 31 + .../esp-serial-flasher/private_include/slip.h | 28 + .../lib/esp-serial-flasher/src/esp_loader.c | 415 +++++++++++ .../lib/esp-serial-flasher/src/esp_targets.c | 263 +++++++ .../lib/esp-serial-flasher/src/md5_hash.c | 262 +++++++ .../esp-serial-flasher/src/protocol_common.c | 301 ++++++++ .../lib/esp-serial-flasher/src/protocol_spi.c | 312 ++++++++ .../esp-serial-flasher/src/protocol_uart.c | 114 +++ .../lib/esp-serial-flasher/src/slip.c | 125 ++++ .../submodules/CMakeLists.txt | 20 + .../esp-serial-flasher/zephyr/CMakeLists.txt | 30 + .../lib/esp-serial-flasher/zephyr/Kconfig | 16 + .../lib/esp-serial-flasher/zephyr/module.yml | 5 + .../esp-flasher/scenes/esp_flasher_scene.c | 30 + .../esp-flasher/scenes/esp_flasher_scene.h | 29 + .../scenes/esp_flasher_scene_about.c | 56 ++ .../scenes/esp_flasher_scene_browse.c | 266 +++++++ .../scenes/esp_flasher_scene_config.h | 4 + .../scenes/esp_flasher_scene_console_output.c | 77 ++ .../scenes/esp_flasher_scene_start.c | 63 ++ .../external/esp-flasher/wifi_10px.png | Bin 0 -> 1781 bytes 72 files changed, 7720 insertions(+) create mode 100644 applications/external/esp-flasher/application.fam create mode 100644 applications/external/esp-flasher/assets/DolphinCommon_56x48.png create mode 100644 applications/external/esp-flasher/assets/KeyBackspaceSelected_16x9.png create mode 100644 applications/external/esp-flasher/assets/KeyBackspace_16x9.png create mode 100644 applications/external/esp-flasher/assets/KeyKeyboardSelected_10x11.png create mode 100644 applications/external/esp-flasher/assets/KeyKeyboard_10x11.png create mode 100644 applications/external/esp-flasher/assets/KeySaveSelected_24x11.png create mode 100644 applications/external/esp-flasher/assets/KeySave_24x11.png create mode 100644 applications/external/esp-flasher/assets/Text_10x10.png create mode 100644 applications/external/esp-flasher/assets/WarningDolphin_45x42.png create mode 100644 applications/external/esp-flasher/esp_flasher_app.c create mode 100644 applications/external/esp-flasher/esp_flasher_app.h create mode 100644 applications/external/esp-flasher/esp_flasher_app_i.h create mode 100644 applications/external/esp-flasher/esp_flasher_custom_event.h create mode 100644 applications/external/esp-flasher/esp_flasher_uart.c create mode 100644 applications/external/esp-flasher/esp_flasher_uart.h create mode 100644 applications/external/esp-flasher/esp_flasher_worker.c create mode 100644 applications/external/esp-flasher/esp_flasher_worker.h create mode 100644 applications/external/esp-flasher/file/sequential_file.c create mode 100644 applications/external/esp-flasher/file/sequential_file.h create mode 100644 applications/external/esp-flasher/lib/esp-serial-flasher/CMakeCache.txt create mode 100644 applications/external/esp-flasher/lib/esp-serial-flasher/CMakeFiles/3.16.3/CMakeCCompiler.cmake create mode 100644 applications/external/esp-flasher/lib/esp-serial-flasher/CMakeFiles/3.16.3/CMakeCXXCompiler.cmake create mode 100755 applications/external/esp-flasher/lib/esp-serial-flasher/CMakeFiles/3.16.3/CMakeDetermineCompilerABI_C.bin create mode 100755 applications/external/esp-flasher/lib/esp-serial-flasher/CMakeFiles/3.16.3/CMakeDetermineCompilerABI_CXX.bin create mode 100644 applications/external/esp-flasher/lib/esp-serial-flasher/CMakeFiles/3.16.3/CMakeSystem.cmake create mode 100644 applications/external/esp-flasher/lib/esp-serial-flasher/CMakeFiles/3.16.3/CompilerIdC/CMakeCCompilerId.c create mode 100755 applications/external/esp-flasher/lib/esp-serial-flasher/CMakeFiles/3.16.3/CompilerIdC/a.out create mode 100644 applications/external/esp-flasher/lib/esp-serial-flasher/CMakeFiles/3.16.3/CompilerIdCXX/CMakeCXXCompilerId.cpp create mode 100755 applications/external/esp-flasher/lib/esp-serial-flasher/CMakeFiles/3.16.3/CompilerIdCXX/a.out create mode 100644 applications/external/esp-flasher/lib/esp-serial-flasher/CMakeFiles/CMakeOutput.log create mode 100644 applications/external/esp-flasher/lib/esp-serial-flasher/CMakeFiles/cmake.check_cache create mode 100644 applications/external/esp-flasher/lib/esp-serial-flasher/CMakeLists.txt create mode 100644 applications/external/esp-flasher/lib/esp-serial-flasher/Kconfig create mode 100644 applications/external/esp-flasher/lib/esp-serial-flasher/idf_component.yml create mode 100644 applications/external/esp-flasher/lib/esp-serial-flasher/include/esp_loader.h create mode 100644 applications/external/esp-flasher/lib/esp-serial-flasher/include/esp_loader_io.h create mode 100644 applications/external/esp-flasher/lib/esp-serial-flasher/include/serial_io.h create mode 100644 applications/external/esp-flasher/lib/esp-serial-flasher/port/esp32_port.c create mode 100644 applications/external/esp-flasher/lib/esp-serial-flasher/port/esp32_port.h create mode 100644 applications/external/esp-flasher/lib/esp-serial-flasher/port/esp32_spi_port.c create mode 100644 applications/external/esp-flasher/lib/esp-serial-flasher/port/esp32_spi_port.h create mode 100644 applications/external/esp-flasher/lib/esp-serial-flasher/port/raspberry_port.c create mode 100644 applications/external/esp-flasher/lib/esp-serial-flasher/port/raspberry_port.h create mode 100644 applications/external/esp-flasher/lib/esp-serial-flasher/port/stm32_port.c create mode 100644 applications/external/esp-flasher/lib/esp-serial-flasher/port/stm32_port.h create mode 100644 applications/external/esp-flasher/lib/esp-serial-flasher/port/zephyr_port.c create mode 100644 applications/external/esp-flasher/lib/esp-serial-flasher/port/zephyr_port.h create mode 100644 applications/external/esp-flasher/lib/esp-serial-flasher/private_include/esp_targets.h create mode 100644 applications/external/esp-flasher/lib/esp-serial-flasher/private_include/md5_hash.h create mode 100644 applications/external/esp-flasher/lib/esp-serial-flasher/private_include/protocol.h create mode 100644 applications/external/esp-flasher/lib/esp-serial-flasher/private_include/protocol_prv.h create mode 100644 applications/external/esp-flasher/lib/esp-serial-flasher/private_include/slip.h create mode 100644 applications/external/esp-flasher/lib/esp-serial-flasher/src/esp_loader.c create mode 100644 applications/external/esp-flasher/lib/esp-serial-flasher/src/esp_targets.c create mode 100644 applications/external/esp-flasher/lib/esp-serial-flasher/src/md5_hash.c create mode 100644 applications/external/esp-flasher/lib/esp-serial-flasher/src/protocol_common.c create mode 100644 applications/external/esp-flasher/lib/esp-serial-flasher/src/protocol_spi.c create mode 100644 applications/external/esp-flasher/lib/esp-serial-flasher/src/protocol_uart.c create mode 100644 applications/external/esp-flasher/lib/esp-serial-flasher/src/slip.c create mode 100644 applications/external/esp-flasher/lib/esp-serial-flasher/submodules/CMakeLists.txt create mode 100644 applications/external/esp-flasher/lib/esp-serial-flasher/zephyr/CMakeLists.txt create mode 100644 applications/external/esp-flasher/lib/esp-serial-flasher/zephyr/Kconfig create mode 100644 applications/external/esp-flasher/lib/esp-serial-flasher/zephyr/module.yml create mode 100644 applications/external/esp-flasher/scenes/esp_flasher_scene.c create mode 100644 applications/external/esp-flasher/scenes/esp_flasher_scene.h create mode 100644 applications/external/esp-flasher/scenes/esp_flasher_scene_about.c create mode 100644 applications/external/esp-flasher/scenes/esp_flasher_scene_browse.c create mode 100644 applications/external/esp-flasher/scenes/esp_flasher_scene_config.h create mode 100644 applications/external/esp-flasher/scenes/esp_flasher_scene_console_output.c create mode 100644 applications/external/esp-flasher/scenes/esp_flasher_scene_start.c create mode 100644 applications/external/esp-flasher/wifi_10px.png diff --git a/applications/external/esp-flasher/application.fam b/applications/external/esp-flasher/application.fam new file mode 100644 index 000000000..9db70c860 --- /dev/null +++ b/applications/external/esp-flasher/application.fam @@ -0,0 +1,29 @@ +App( + appid="esp_flasher", + name="[GPIO] ESP Flasher", + apptype=FlipperAppType.EXTERNAL, + entry_point="esp_flasher_app", + requires=["gui"], + stack_size=4 * 1024, + order=90, + fap_icon="wifi_10px.png", + fap_category="GPIO", + fap_private_libs=[ + Lib( + name="esp-serial-flasher", + fap_include_paths=["include"], + sources=[ + "src/esp_loader.c", + "src/esp_targets.c", + "src/md5_hash.c", + "src/protocol_common.c", + "src/protocol_uart.c", + "src/slip.c" + ], + cincludes=["lib/esp-serial-flasher/private_include"], + cdefines=["SERIAL_FLASHER_INTERFACE_UART=1", "MD5_ENABLED=1"], + ), + ], + cdefines=["SERIAL_FLASHER_INTERFACE_UART=1"], + fap_icon_assets="assets", +) diff --git a/applications/external/esp-flasher/assets/DolphinCommon_56x48.png b/applications/external/esp-flasher/assets/DolphinCommon_56x48.png new file mode 100644 index 0000000000000000000000000000000000000000..089aaed83507431993a76ca25d32fdd9664c1c84 GIT binary patch literal 1416 zcmaJ>eNYr-7(dh;KXS5&nWVIBjS_NizYg|x=Pr^vz*7zxJO|P-dw2IeZq?gec9-rD zoPZchQ_6}yP{Slc4I!!28K==nodOJ_nsCY-(wOq2uZbLx!rlYU{KIi)_Wj!D_j`WN z^FGgREXdEDF)ewT&1Re7Tj(uBvlG44lnH3;I%IzsO|z`*Vr!`uv?9QOwgs{#Ld+Ki zC9n_zxxBOkx@@+IwMwAaD)#3Ik`}gun2kLe))Crfb7e+#AgzHGCc+X$b>qJuIf`S7 z?8b}I{ghw#z>uiaLknQh@LJUrqHcVYS3v97F^OZN zCe|7^J|?QzUx0Zu17e(=CM1fYFpjtLk|a4~$g}e?hGH0!VoBOT&<=s(1ct%J9~?O} z$)jW_dkX9yTX~%W*i_IM%0{ z7EmP^_pKn`<5>E(SixgJU};7`)7Hidp&+DLnizsebUk}_-GfgbN^il9b`v)f+ z{o5Zry)d<7`fHQ^uw_;+x>mcPw0&8iW69x{k92O{Q}`yFdH=5d$pbf49w1&NS)G+vhr6y}5TMsofQirRDUmKilk5=(KGouJ{H9hW=$X zgi;)vI!jl!_4H3jD(?Jz=8By|i47I&tKA1y9{nfp;_|FxKBDNWp{hN9hJ1nU?z%J6 z?>UxyzWvO}Pgc~rCZ#5%Eq+_hNS~bBdiGlT&f%%e`hHjSySR2=JuK2^+%;$R3#Wz~ z=e_mfqW23bPa0fhe)HdE5+GelU&!jS3ckUZOQ)CC5?mo zo=tzG_4|RuvPUO|mhCwA>y)1c%SWC%a4?a-x|J*?ch~+n=R7o@>p6J2dE=$stKZmK z-xoTRwET2^Wu)&1U7!Ebw!!D?x`xwQX3pMnrRwCT?`4GHt4&?|cIiI{_^XYp-np>6 xE^lPSXzOYCC4X`6tl@OB1M5_S7jml-Y~(TPp{aTIejNKZ`m*!Atyxdk{0EAy49frj literal 0 HcmV?d00001 diff --git a/applications/external/esp-flasher/assets/KeyBackspaceSelected_16x9.png b/applications/external/esp-flasher/assets/KeyBackspaceSelected_16x9.png new file mode 100644 index 0000000000000000000000000000000000000000..7cc0759a8ca6acdb9b9c2e3dc00edde2f0e93a67 GIT binary patch literal 1812 zcmcIl&x_()N{W z-JN@vFI~T+Y1-v}FWiIo6?k5J;aT|qw)dv2CwcE-scA1=t)FMK&%d~)Y0rO}4EC%2 z=aK4R$F?2(hE6fX7H(UFBH{$t4v4EaKLer_Vi@d&Z#A)C)-lFal?RqJo6XEw%T&e4 zBEIiim|Bz~ut4QeR$2KDgeVQ)Gl9#&Q7)}LS*mHl<@TY>s++4|`B+t|9IGdATYvr+ zL&4Vp^Jy_zlt*w&PGkz$CD@V$zdYy`l2xi0C^cC%YIhY;r^KZCtp`aa)U15HX4E*y zkX5o{K-UPu6k#$T&@w-;?b`$g7%xpD(1BnTyO^;O$?)hRrco61v$A3tm;JC~04Xy` zM9{K5&YYn{cI-;z+O~({*abcDHnS;pNXu(4c!7VY__VG>?Z1?*P#iGU)eM+zGa?B_ zvgI?>CU%T`*JH?wZ6SP@IW~7zXzvsW>>M^Zjasu3fJoi8ARJ`vf+uoa+eOUrBobcB zw>cJ!4w<1pj@wleRYXcabz7&```zwtp@zu>K9qa+w)FmX*CD>+AZijr7d#lMB4r@7 zBxNIM<=Lo~JFR)Z8qvz(k!=8Gk?gq@8g zfS#k0rCF(l)r=K#a|A8Qr4h!%R?w1c= z{pq*skHW8}pZxgsP>68$^3NZ9>0PQ& Cw>kg- literal 0 HcmV?d00001 diff --git a/applications/external/esp-flasher/assets/KeyBackspace_16x9.png b/applications/external/esp-flasher/assets/KeyBackspace_16x9.png new file mode 100644 index 0000000000000000000000000000000000000000..9946232d953ef1cbfbf0e6754be6645e5ea2747b GIT binary patch literal 1829 zcmcIl&u`pB6gEEvMG+BPN`-{wNT>+Lo*8@XwOcpZ?1t{5I;81J4Y!WR<6SFjkFlNX zCgRjnK|!3jpo$Y0E}Xb=;LeTzpnm{T)f-4i;dy_hpfu#tmAsxAzxTcGz4y(`m)l!6 zS1w(-q$tWtuiM#y_bNQEzxE>h|J=PM>Pg=HtW=aY-mae)lh*~S0I8^$I!Q-a=}mlXitE9+UN$s!YEtd_TB{DI?graxTNXmKb&NR1RCQdP z*p_AEk5q~&HgLlr6cO9QmPZ_Q{?i~@5yjq4=i_-SnEBeUs&daT#^bR*Hg#DH4C1=3 zfvG_$0t-|gW)+*DtXx|lbVSLEB(D;gsWl=C<$mRBz;u>EnlE9qa$Y7Vm@#3wL3CWF zv@i^U^G(xqX_e|ijf0zqnN0f5E;9~PYWYyXtSU!}MEQj(L+?JpJ#W3Q_ zfcbtgnwBTxh8T$yuuHHdQ+~PEE(EJ&(U)?xXw>#1qDqNQ)vI@tERy5$gPPIYL3CIp zd=0ur5T*!|K7p3G9x*>8*u!{c8h{QWRntB?yEl08lWCYa({L}SbyS-h=I2pl*a_8oT+S_c~#IXiq#zs}!z^4spCcOR_0+*o_q`N6;X{rjK1`QsBs`Gcx|z4v#k QTVG_o&8^N)8~5)21Ktij=l}o! literal 0 HcmV?d00001 diff --git a/applications/external/esp-flasher/assets/KeyKeyboardSelected_10x11.png b/applications/external/esp-flasher/assets/KeyKeyboardSelected_10x11.png new file mode 100644 index 0000000000000000000000000000000000000000..231880386a9d9e83911088c9fcd083f84a7f75bb GIT binary patch literal 7210 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 literal 0 HcmV?d00001 diff --git a/applications/external/esp-flasher/assets/KeyKeyboard_10x11.png b/applications/external/esp-flasher/assets/KeyKeyboard_10x11.png new file mode 100644 index 0000000000000000000000000000000000000000..1f4c03478f68cb15c74ef99888ce4faf50e0a140 GIT binary patch literal 7763 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 GFa8a7)lmol literal 0 HcmV?d00001 diff --git a/applications/external/esp-flasher/assets/KeySave_24x11.png b/applications/external/esp-flasher/assets/KeySave_24x11.png new file mode 100644 index 0000000000000000000000000000000000000000..e7dba987a04dad7dd96001913c55566dbde96c8e GIT binary patch literal 1863 zcmcIlO^6&t6dpxnjjSF75f9pQJZy|LUDdzSZ6$n-*6-2D5s-9_fx~uK( zotfQBVDX?Q(UXLzXF6aX)%U*l z-d9y`w^q+Do_PF3p-@5qOL5h2N9RU z^i-~BIziNECdw*wjUcQeOxncsbnKa>(*%1MPoPck0jC)~9$50g-#!ks+4LGwn$d`f zMy;%ZsA3Rsk2!`#ELuW>2#g3fF>;CFBHMCo-El0(@X1&g%&$qdl~*F4Kd~*B3^?Z1 z^bEmDf}0)Wn??sYC6l9$X;6esLO7#_ZCmDy?ZqU3l|%anS#wn!7%f39-Qp(l9fyJ- z(?=x}n~2%2PcX9#VmYdECvH{tWzv)!s%sn^Z&a(TMEXG=KBQ~smzBm!)h4cOBfSV| zapw6l2`LyY2x(Vnan#Li4>BO#dXPeox2Fr~f_P*4)DM)gJ3Y$sMNw8+?gqit>2PpJ znU9yygm%~yKzf8rCa_fc*^nlp(uJ1%rwg^aiBIX^Xz9mu$p0vPT2|JhQCGkYtEqW1 zTD})enxg%?Uw4c#Ggk#{pLa8zmSLH8=LI=?xR>pc=yYsHAgcQUxz^arx`9fR>e$sw zj@}Uy75!kQXF{tT9e=F+z^*!*3|n>nI6oucWq!(t2og`=40-O!tAE1z^ID@;X)nEd zVK7xqj`wg%Ed2Op{k=a*YZpKHX787$ zzhKuN9(?M5ojmn%xcU1}OP>$^`ZD?cW@lIaTn=x3xBtP#z16o~{qVMZ>hecsD?jQQ ME3387mS5lf8`39Il>h($ literal 0 HcmV?d00001 diff --git a/applications/external/esp-flasher/assets/Text_10x10.png b/applications/external/esp-flasher/assets/Text_10x10.png new file mode 100644 index 0000000000000000000000000000000000000000..8e8a6183dd50535729dc9c9b4f220a12dd4c600f GIT binary patch literal 158 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2VGmzZ%#=aj&u?6^qxB}__|Nk$&IsYz@rRC}3 z7*a7OIiZ2U&CSi=;0cBn1vTatM&Z;3u7g(^G9`qQn09G2aWeNXaKC0S=Q~tg57Z@F z;u=vBoS#-wo>-L1;E+?AmspUPnOCA;ke9BToS%}K{MA`f4ycg9)78&qol`;+00Iau A9smFU literal 0 HcmV?d00001 diff --git a/applications/external/esp-flasher/assets/WarningDolphin_45x42.png b/applications/external/esp-flasher/assets/WarningDolphin_45x42.png new file mode 100644 index 0000000000000000000000000000000000000000..d766ffbb444db1739f2ccd030e506e8bada11ee8 GIT binary patch literal 1139 zcmaJ=TWAz#6rQ4%BpO*okt7zz3Bkm=bK9L=XV}qhW_HceY#KHzP4Z$UGyf(-b}pUy z<8ESW=_MtCk6tj|`k)VrCbo(SMH0n`eW+KIU`wG5O`$IeMg)Vzf7adDhv+af=lqBB zo%5Z`zqhqzdu2s+1%_dji6%LPq#u2o%9fzN?;7Dlq6)^^VVjkKImH23RI|DPo-mXi zkOGP}@Wrnnf?-SQ^>jOIPc{pxWsr*JL*@+|p)oA7EpIDoAAoo_=+RA)c=F3Qf$N$` ze9k55q%DD7y=l+^ZG$aob+Aw6HDcRVJdzhs00Te;&l_3O74jlch$|r7GgAa!aDjay z@rG1;vK5ys2jF3n@vAgV<6)izn!k6Qhd>D(EhD7l zcrhJ1i9|1iwm?z2T#n2INXzM=7@p@Tnx$CQk39VDfC-hn-*jtB5oF-1j&4KUGI1}W z(rxuakw9eMRAJc3%8 zlBq3$QTyJX$a6$&1ldyi4Pe5AEE32Sg@vDzY~7qR?1u@oXh zd9(fBtV<@eK%Tm=yy&p7{=h^#@1W)WFFSe!U5pP~o71uR`FW)7xc*=d5_b}EG@XBZ z@<7MRp-;+|o}SzJvMyfx>37Y4etBizf%0H*zaIF?2ObZt?$D;{o|esJ z*PgAOIO^*8tL%z2)|}k$DP5kN5}8qo*wNa8y?R2=%wO{gCD(`sHt}TzWQuK zMDIJap(nb2=7*ns*oDcRBbC23yy`kvKYV`ovU`a=EmxNv-90;;5r6+l8hQTp^u`Hn XX6*+%8gHC`h)Tl}u@-r>vFqE{F~5F& literal 0 HcmV?d00001 diff --git a/applications/external/esp-flasher/esp_flasher_app.c b/applications/external/esp-flasher/esp_flasher_app.c new file mode 100644 index 000000000..e29bb148f --- /dev/null +++ b/applications/external/esp-flasher/esp_flasher_app.c @@ -0,0 +1,135 @@ +#include "esp_flasher_app_i.h" + +#include +#include + +static bool esp_flasher_app_custom_event_callback(void* context, uint32_t event) { + furi_assert(context); + EspFlasherApp* app = context; + return scene_manager_handle_custom_event(app->scene_manager, event); +} + +static bool esp_flasher_app_back_event_callback(void* context) { + furi_assert(context); + EspFlasherApp* app = context; + return scene_manager_handle_back_event(app->scene_manager); +} + +static void esp_flasher_app_tick_event_callback(void* context) { + furi_assert(context); + EspFlasherApp* app = context; + scene_manager_handle_tick_event(app->scene_manager); +} + +EspFlasherApp* esp_flasher_app_alloc() { + EspFlasherApp* app = malloc(sizeof(EspFlasherApp)); + + app->gui = furi_record_open(RECORD_GUI); + app->dialogs = furi_record_open(RECORD_DIALOGS); + app->storage = furi_record_open(RECORD_STORAGE); + + app->view_dispatcher = view_dispatcher_alloc(); + app->scene_manager = scene_manager_alloc(&esp_flasher_scene_handlers, app); + view_dispatcher_enable_queue(app->view_dispatcher); + view_dispatcher_set_event_callback_context(app->view_dispatcher, app); + + view_dispatcher_set_custom_event_callback( + app->view_dispatcher, esp_flasher_app_custom_event_callback); + view_dispatcher_set_navigation_event_callback( + app->view_dispatcher, esp_flasher_app_back_event_callback); + view_dispatcher_set_tick_event_callback( + app->view_dispatcher, esp_flasher_app_tick_event_callback, 100); + + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + + app->var_item_list = variable_item_list_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + EspFlasherAppViewVarItemList, + variable_item_list_get_view(app->var_item_list)); + + app->text_box = text_box_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, EspFlasherAppViewConsoleOutput, text_box_get_view(app->text_box)); + app->text_box_store = furi_string_alloc(); + furi_string_reserve(app->text_box_store, ESP_FLASHER_TEXT_BOX_STORE_SIZE); + + app->widget = widget_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, EspFlasherAppViewWidget, widget_get_view(app->widget)); + + // Submenu + app->submenu = submenu_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, EspFlasherAppViewSubmenu, submenu_get_view(app->submenu)); + + app->flash_worker_busy = false; + + scene_manager_next_scene(app->scene_manager, EspFlasherSceneStart); + + return app; +} + +void esp_flasher_make_app_folder(EspFlasherApp* app) { + furi_assert(app); + + if(!storage_simply_mkdir(app->storage, ESP_APP_FOLDER)) { + dialog_message_show_storage_error(app->dialogs, "Cannot create\napp folder"); + } +} + +void esp_flasher_app_free(EspFlasherApp* app) { + furi_assert(app); + + // Views + view_dispatcher_remove_view(app->view_dispatcher, EspFlasherAppViewVarItemList); + view_dispatcher_remove_view(app->view_dispatcher, EspFlasherAppViewConsoleOutput); + view_dispatcher_remove_view(app->view_dispatcher, EspFlasherAppViewWidget); + view_dispatcher_remove_view(app->view_dispatcher, EspFlasherAppViewSubmenu); + + widget_free(app->widget); + text_box_free(app->text_box); + furi_string_free(app->text_box_store); + submenu_free(app->submenu); + variable_item_list_free(app->var_item_list); + + // View dispatcher + view_dispatcher_free(app->view_dispatcher); + scene_manager_free(app->scene_manager); + + esp_flasher_uart_free(app->uart); + + // Close records + furi_record_close(RECORD_GUI); + furi_record_close(RECORD_STORAGE); + furi_record_close(RECORD_DIALOGS); + + free(app); +} + +int32_t esp_flasher_app(void* p) { + UNUSED(p); + + uint8_t attempts = 0; + while(!furi_hal_power_is_otg_enabled() && attempts++ < 5) { + furi_hal_power_enable_otg(); + furi_delay_ms(10); + } + furi_delay_ms(200); + + EspFlasherApp* esp_flasher_app = esp_flasher_app_alloc(); + + esp_flasher_make_app_folder(esp_flasher_app); + + esp_flasher_app->uart = esp_flasher_usart_init(esp_flasher_app); + + view_dispatcher_run(esp_flasher_app->view_dispatcher); + + esp_flasher_app_free(esp_flasher_app); + + if(furi_hal_power_is_otg_enabled()) { + furi_hal_power_disable_otg(); + } + + return 0; +} diff --git a/applications/external/esp-flasher/esp_flasher_app.h b/applications/external/esp-flasher/esp_flasher_app.h new file mode 100644 index 000000000..413dd58a3 --- /dev/null +++ b/applications/external/esp-flasher/esp_flasher_app.h @@ -0,0 +1,13 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#define ESP_FLASHER_APP_VERSION "v1.0" + +typedef struct EspFlasherApp EspFlasherApp; + +#ifdef __cplusplus +} +#endif diff --git a/applications/external/esp-flasher/esp_flasher_app_i.h b/applications/external/esp-flasher/esp_flasher_app_i.h new file mode 100644 index 000000000..9b8b81a29 --- /dev/null +++ b/applications/external/esp-flasher/esp_flasher_app_i.h @@ -0,0 +1,75 @@ +//** Includes sniffbt and sniffskim for compatible ESP32-WROOM hardware. +// esp_flasher_scene_start.c also changed **// +#pragma once + +#include "esp_flasher_app.h" +#include "scenes/esp_flasher_scene.h" +#include "esp_flasher_custom_event.h" +#include "esp_flasher_uart.h" +#include "file/sequential_file.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define ESP_FLASHER_TEXT_BOX_STORE_SIZE (4096) + +#define ESP_APP_FOLDER_USER "apps_data/esp_flasher" +#define ESP_APP_FOLDER EXT_PATH(ESP_APP_FOLDER_USER) + +typedef enum SelectedFlashOptions { + SelectedFlashS3Mode, + SelectedFlashBoot, + SelectedFlashPart, + SelectedFlashNvs, + SelectedFlashBootApp0, + SelectedFlashApp, + SelectedFlashCustom, + NUM_FLASH_OPTIONS +} SelectedFlashOptions; + +struct EspFlasherApp { + Gui* gui; + ViewDispatcher* view_dispatcher; + SceneManager* scene_manager; + + FuriString* text_box_store; + size_t text_box_store_strlen; + TextBox* text_box; + Storage* storage; + DialogsApp* dialogs; + + VariableItemList* var_item_list; + Widget* widget; + Submenu* submenu; + + EspFlasherUart* uart; + + bool selected_flash_options[NUM_FLASH_OPTIONS]; + int num_selected_flash_options; + char bin_file_path_boot[100]; + char bin_file_path_part[100]; + char bin_file_path_nvs[100]; + char bin_file_path_boot_app0[100]; + char bin_file_path_app[100]; + char bin_file_path_custom[100]; + FuriThread* flash_worker; + bool flash_worker_busy; +}; + +typedef enum { + EspFlasherAppViewVarItemList, + EspFlasherAppViewConsoleOutput, + EspFlasherAppViewTextInput, + EspFlasherAppViewWidget, + EspFlasherAppViewSubmenu, +} EspFlasherAppView; diff --git a/applications/external/esp-flasher/esp_flasher_custom_event.h b/applications/external/esp-flasher/esp_flasher_custom_event.h new file mode 100644 index 000000000..07d362a6e --- /dev/null +++ b/applications/external/esp-flasher/esp_flasher_custom_event.h @@ -0,0 +1,9 @@ +#pragma once + +typedef enum { + EspFlasherEventRefreshConsoleOutput = 0, + EspFlasherEventStartConsole, + EspFlasherEventStartKeyboard, + EspFlasherEventStartFlasher, + EspFlasherEventRefreshSubmenu +} EspFlasherCustomEvent; diff --git a/applications/external/esp-flasher/esp_flasher_uart.c b/applications/external/esp-flasher/esp_flasher_uart.c new file mode 100644 index 000000000..7dd2904b5 --- /dev/null +++ b/applications/external/esp-flasher/esp_flasher_uart.c @@ -0,0 +1,106 @@ +#include "esp_flasher_app_i.h" +#include "esp_flasher_uart.h" + +#define UART_CH (FuriHalUartIdUSART1) +#define BAUDRATE (115200) + +struct EspFlasherUart { + EspFlasherApp* app; + FuriHalUartId channel; + FuriThread* rx_thread; + FuriStreamBuffer* rx_stream; + uint8_t rx_buf[RX_BUF_SIZE + 1]; + void (*handle_rx_data_cb)(uint8_t* buf, size_t len, void* context); +}; + +typedef enum { + WorkerEvtStop = (1 << 0), + WorkerEvtRxDone = (1 << 1), +} WorkerEvtFlags; + +void esp_flasher_uart_set_handle_rx_data_cb( + EspFlasherUart* uart, + void (*handle_rx_data_cb)(uint8_t* buf, size_t len, void* context)) { + furi_assert(uart); + uart->handle_rx_data_cb = handle_rx_data_cb; +} + +#define WORKER_ALL_RX_EVENTS (WorkerEvtStop | WorkerEvtRxDone) + +void esp_flasher_uart_on_irq_cb(UartIrqEvent ev, uint8_t data, void* context) { + EspFlasherUart* uart = (EspFlasherUart*)context; + + if(ev == UartIrqEventRXNE) { + furi_stream_buffer_send(uart->rx_stream, &data, 1, 0); + furi_thread_flags_set(furi_thread_get_id(uart->rx_thread), WorkerEvtRxDone); + } +} + +static int32_t uart_worker(void* context) { + EspFlasherUart* uart = (void*)context; + + while(1) { + uint32_t events = + furi_thread_flags_wait(WORKER_ALL_RX_EVENTS, FuriFlagWaitAny, FuriWaitForever); + furi_check((events & FuriFlagError) == 0); + if(events & WorkerEvtStop) break; + if(events & WorkerEvtRxDone) { + size_t len = furi_stream_buffer_receive(uart->rx_stream, uart->rx_buf, RX_BUF_SIZE, 0); + if(len > 0) { + if(uart->handle_rx_data_cb) uart->handle_rx_data_cb(uart->rx_buf, len, uart->app); + } + } + } + + furi_stream_buffer_free(uart->rx_stream); + + return 0; +} + +void esp_flasher_uart_tx(uint8_t* data, size_t len) { + furi_hal_uart_tx(UART_CH, data, len); +} + +EspFlasherUart* + esp_flasher_uart_init(EspFlasherApp* app, FuriHalUartId channel, const char* thread_name) { + EspFlasherUart* uart = malloc(sizeof(EspFlasherUart)); + + uart->app = app; + uart->channel = channel; + uart->rx_stream = furi_stream_buffer_alloc(RX_BUF_SIZE, 1); + uart->rx_thread = furi_thread_alloc(); + furi_thread_set_name(uart->rx_thread, thread_name); + furi_thread_set_stack_size(uart->rx_thread, 1024); + furi_thread_set_context(uart->rx_thread, uart); + furi_thread_set_callback(uart->rx_thread, uart_worker); + furi_thread_start(uart->rx_thread); + if(channel == FuriHalUartIdUSART1) { + furi_hal_console_disable(); + } else if(channel == FuriHalUartIdLPUART1) { + furi_hal_uart_init(channel, BAUDRATE); + } + furi_hal_uart_set_br(channel, BAUDRATE); + furi_hal_uart_set_irq_cb(channel, esp_flasher_uart_on_irq_cb, uart); + + return uart; +} + +EspFlasherUart* esp_flasher_usart_init(EspFlasherApp* app) { + return esp_flasher_uart_init(app, UART_CH, "EspFlasherUartRxThread"); +} + +void esp_flasher_uart_free(EspFlasherUart* uart) { + furi_assert(uart); + + furi_thread_flags_set(furi_thread_get_id(uart->rx_thread), WorkerEvtStop); + furi_thread_join(uart->rx_thread); + furi_thread_free(uart->rx_thread); + + furi_hal_uart_set_irq_cb(uart->channel, NULL, NULL); + if(uart->channel == FuriHalUartIdLPUART1) { + furi_hal_uart_deinit(uart->channel); + } + furi_hal_console_enable(); + + free(uart); +} diff --git a/applications/external/esp-flasher/esp_flasher_uart.h b/applications/external/esp-flasher/esp_flasher_uart.h new file mode 100644 index 000000000..9710c92b3 --- /dev/null +++ b/applications/external/esp-flasher/esp_flasher_uart.h @@ -0,0 +1,14 @@ +#pragma once + +#include "furi_hal.h" + +#define RX_BUF_SIZE (2048) + +typedef struct EspFlasherUart EspFlasherUart; + +void esp_flasher_uart_set_handle_rx_data_cb( + EspFlasherUart* uart, + void (*handle_rx_data_cb)(uint8_t* buf, size_t len, void* context)); +void esp_flasher_uart_tx(uint8_t* data, size_t len); +EspFlasherUart* esp_flasher_usart_init(EspFlasherApp* app); +void esp_flasher_uart_free(EspFlasherUart* uart); diff --git a/applications/external/esp-flasher/esp_flasher_worker.c b/applications/external/esp-flasher/esp_flasher_worker.c new file mode 100644 index 000000000..b5a647d4b --- /dev/null +++ b/applications/external/esp-flasher/esp_flasher_worker.c @@ -0,0 +1,255 @@ +#include "esp_flasher_worker.h" + +FuriStreamBuffer* flash_rx_stream; // TODO make safe +EspFlasherApp* global_app; // TODO make safe +FuriTimer* timer; // TODO make + +static uint32_t _remaining_time = 0; +static void _timer_callback(void* context) { + UNUSED(context); + if(_remaining_time > 0) { + _remaining_time--; + } +} + +static esp_loader_error_t _flash_file(EspFlasherApp* app, char* filepath, uint32_t addr) { + // TODO cleanup + esp_loader_error_t err; + static uint8_t payload[1024]; + File* bin_file = storage_file_alloc(app->storage); + + char user_msg[256]; + + // open file + if(!storage_file_open(bin_file, filepath, FSAM_READ, FSOM_OPEN_EXISTING)) { + storage_file_close(bin_file); + storage_file_free(bin_file); + dialog_message_show_storage_error(app->dialogs, "Cannot open file"); + return ESP_LOADER_ERROR_FAIL; + } + + uint64_t size = storage_file_size(bin_file); + + loader_port_debug_print("Erasing flash...this may take a while\n"); + err = esp_loader_flash_start(addr, size, sizeof(payload)); + if(err != ESP_LOADER_SUCCESS) { + storage_file_close(bin_file); + storage_file_free(bin_file); + snprintf(user_msg, sizeof(user_msg), "Erasing flash failed with error %d\n", err); + loader_port_debug_print(user_msg); + return err; + } + + loader_port_debug_print("Start programming\n"); + uint64_t last_updated = size; + while(size > 0) { + if((last_updated - size) > 50000) { + // inform user every 50k bytes + // TODO: draw a progress bar next update + snprintf(user_msg, sizeof(user_msg), "%llu bytes left.\n", size); + loader_port_debug_print(user_msg); + last_updated = size; + } + size_t to_read = MIN(size, sizeof(payload)); + uint16_t num_bytes = storage_file_read(bin_file, payload, to_read); + err = esp_loader_flash_write(payload, num_bytes); + if(err != ESP_LOADER_SUCCESS) { + snprintf(user_msg, sizeof(user_msg), "Packet could not be written! Error: %u\n", err); + storage_file_close(bin_file); + storage_file_free(bin_file); + loader_port_debug_print(user_msg); + return err; + } + + size -= num_bytes; + } + + loader_port_debug_print("Finished programming\n"); + + // TODO verify + + storage_file_close(bin_file); + storage_file_free(bin_file); + + return ESP_LOADER_SUCCESS; +} + +typedef struct { + SelectedFlashOptions selected; + const char* description; + char* path; + uint32_t addr; +} FlashItem; + +static void _flash_all_files(EspFlasherApp* app) { + esp_loader_error_t err; + const int num_steps = app->num_selected_flash_options; + +#define NUM_FLASH_ITEMS 6 + FlashItem items[NUM_FLASH_ITEMS] = { + {SelectedFlashBoot, + "bootloader", + app->bin_file_path_boot, + app->selected_flash_options[SelectedFlashS3Mode] ? ESP_ADDR_BOOT_S3 : ESP_ADDR_BOOT}, + {SelectedFlashPart, "partition table", app->bin_file_path_part, ESP_ADDR_PART}, + {SelectedFlashNvs, "NVS", app->bin_file_path_nvs, ESP_ADDR_NVS}, + {SelectedFlashBootApp0, "boot_app0", app->bin_file_path_boot_app0, ESP_ADDR_BOOT_APP0}, + {SelectedFlashApp, "firmware", app->bin_file_path_app, ESP_ADDR_APP}, + {SelectedFlashCustom, "custom data", app->bin_file_path_custom, 0x0}, + /* if you add more entries, update NUM_FLASH_ITEMS above! */ + }; + + char user_msg[256]; + + int current_step = 1; + for(FlashItem* item = &items[0]; item < &items[NUM_FLASH_ITEMS]; ++item) { + if(app->selected_flash_options[item->selected]) { + snprintf( + user_msg, + sizeof(user_msg), + "Flashing %s (%d/%d) to address 0x%lx\n", + item->description, + current_step++, + num_steps, + item->addr); + loader_port_debug_print(user_msg); + err = _flash_file(app, item->path, item->addr); + if(err) { + break; + } + } + } +} + +static int32_t esp_flasher_flash_bin(void* context) { + EspFlasherApp* app = (void*)context; + esp_loader_error_t err; + + app->flash_worker_busy = true; + + // alloc global objects + flash_rx_stream = furi_stream_buffer_alloc(RX_BUF_SIZE, 1); + timer = furi_timer_alloc(_timer_callback, FuriTimerTypePeriodic, app); + + loader_port_debug_print("Connecting\n"); + esp_loader_connect_args_t connect_config = ESP_LOADER_CONNECT_DEFAULT(); + err = esp_loader_connect(&connect_config); + if(err != ESP_LOADER_SUCCESS) { + char err_msg[256]; + snprintf( + err_msg, + sizeof(err_msg), + "Cannot connect to target. Error: %u\nMake sure the device is in bootloader/reflash mode, then try again.\n", + err); + loader_port_debug_print(err_msg); + } + +#if 0 // still getting packet drops with this + // higher BR + if(!err) { + loader_port_debug_print("Increasing speed for faster flash\n"); + err = esp_loader_change_transmission_rate(230400); + if (err != ESP_LOADER_SUCCESS) { + char err_msg[256]; + snprintf( + err_msg, + sizeof(err_msg), + "Cannot change transmission rate. Error: %u\n", + err); + loader_port_debug_print(err_msg); + } + furi_hal_uart_set_br(FuriHalUartIdUSART1, 230400); + } +#endif + + if(!err) { + loader_port_debug_print("Connected\n"); + _flash_all_files(app); +#if 0 + loader_port_debug_print("Restoring transmission rate\n"); + furi_hal_uart_set_br(FuriHalUartIdUSART1, 115200); +#endif + loader_port_debug_print("Done flashing. Please reset the board manually.\n"); + } + + // done + app->flash_worker_busy = false; + + // cleanup + furi_stream_buffer_free(flash_rx_stream); + flash_rx_stream = NULL; + furi_timer_free(timer); + return 0; +} + +void esp_flasher_worker_start_thread(EspFlasherApp* app) { + global_app = app; + + app->flash_worker = furi_thread_alloc(); + furi_thread_set_name(app->flash_worker, "EspFlasherFlashWorker"); + furi_thread_set_stack_size(app->flash_worker, 2048); + furi_thread_set_context(app->flash_worker, app); + furi_thread_set_callback(app->flash_worker, esp_flasher_flash_bin); + furi_thread_start(app->flash_worker); +} + +void esp_flasher_worker_stop_thread(EspFlasherApp* app) { + furi_thread_join(app->flash_worker); + furi_thread_free(app->flash_worker); +} + +esp_loader_error_t loader_port_read(uint8_t* data, uint16_t size, uint32_t timeout) { + size_t read = furi_stream_buffer_receive(flash_rx_stream, data, size, pdMS_TO_TICKS(timeout)); + if(read < size) { + return ESP_LOADER_ERROR_TIMEOUT; + } else { + return ESP_LOADER_SUCCESS; + } +} + +esp_loader_error_t loader_port_write(const uint8_t* data, uint16_t size, uint32_t timeout) { + UNUSED(timeout); + esp_flasher_uart_tx((uint8_t*)data, size); + return ESP_LOADER_SUCCESS; +} + +void loader_port_enter_bootloader(void) { + // unimplemented +} + +void loader_port_delay_ms(uint32_t ms) { + furi_delay_ms(ms); +} + +void loader_port_start_timer(uint32_t ms) { + _remaining_time = ms; + furi_timer_start(timer, pdMS_TO_TICKS(1)); +} + +uint32_t loader_port_remaining_time(void) { + return _remaining_time; +} + +extern void esp_flasher_console_output_handle_rx_data_cb( + uint8_t* buf, + size_t len, + void* context); // TODO cleanup +void loader_port_debug_print(const char* str) { + if(global_app) + esp_flasher_console_output_handle_rx_data_cb((uint8_t*)str, strlen(str), global_app); +} + +void loader_port_spi_set_cs(uint32_t level) { + UNUSED(level); + // unimplemented +} + +void esp_flasher_worker_handle_rx_data_cb(uint8_t* buf, size_t len, void* context) { + UNUSED(context); + if(flash_rx_stream) { + furi_stream_buffer_send(flash_rx_stream, buf, len, 0); + } else { + // done flashing + if(global_app) esp_flasher_console_output_handle_rx_data_cb(buf, len, global_app); + } +} \ No newline at end of file diff --git a/applications/external/esp-flasher/esp_flasher_worker.h b/applications/external/esp-flasher/esp_flasher_worker.h new file mode 100644 index 000000000..00c43a301 --- /dev/null +++ b/applications/external/esp-flasher/esp_flasher_worker.h @@ -0,0 +1,17 @@ +#pragma once + +#include "esp_flasher_app_i.h" +#include "esp_flasher_uart.h" +#define SERIAL_FLASHER_INTERFACE_UART /* TODO why is application.fam not passing this via cdefines */ +#include "esp_loader_io.h" + +#define ESP_ADDR_BOOT_S3 0x0 +#define ESP_ADDR_BOOT 0x1000 +#define ESP_ADDR_PART 0x8000 +#define ESP_ADDR_NVS 0x9000 +#define ESP_ADDR_BOOT_APP0 0xE000 +#define ESP_ADDR_APP 0x10000 + +void esp_flasher_worker_start_thread(EspFlasherApp* app); +void esp_flasher_worker_stop_thread(EspFlasherApp* app); +void esp_flasher_worker_handle_rx_data_cb(uint8_t* buf, size_t len, void* context); \ No newline at end of file diff --git a/applications/external/esp-flasher/file/sequential_file.c b/applications/external/esp-flasher/file/sequential_file.c new file mode 100644 index 000000000..d780deb12 --- /dev/null +++ b/applications/external/esp-flasher/file/sequential_file.c @@ -0,0 +1,46 @@ +#include "sequential_file.h" + +char* sequential_file_resolve_path( + Storage* storage, + const char* dir, + const char* prefix, + const char* extension) { + if(storage == NULL || dir == NULL || prefix == NULL || extension == NULL) { + return NULL; + } + + char file_path[256]; + int file_index = 0; + + do { + if(snprintf( + file_path, sizeof(file_path), "%s/%s_%d.%s", dir, prefix, file_index, extension) < + 0) { + return NULL; + } + file_index++; + } while(storage_file_exists(storage, file_path)); + + return strdup(file_path); +} + +bool sequential_file_open( + Storage* storage, + File* file, + const char* dir, + const char* prefix, + const char* extension) { + if(storage == NULL || file == NULL || dir == NULL || prefix == NULL || extension == NULL) { + return false; + } + + char* file_path = sequential_file_resolve_path(storage, dir, prefix, extension); + if(file_path == NULL) { + return false; + } + + bool success = storage_file_open(file, file_path, FSAM_WRITE, FSOM_CREATE_ALWAYS); + free(file_path); + + return success; +} \ No newline at end of file diff --git a/applications/external/esp-flasher/file/sequential_file.h b/applications/external/esp-flasher/file/sequential_file.h new file mode 100644 index 000000000..4fd4794c2 --- /dev/null +++ b/applications/external/esp-flasher/file/sequential_file.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +char* sequential_file_resolve_path( + Storage* storage, + const char* dir, + const char* prefix, + const char* extension); +bool sequential_file_open( + Storage* storage, + File* file, + const char* dir, + const char* prefix, + const char* extension); \ No newline at end of file diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/CMakeCache.txt b/applications/external/esp-flasher/lib/esp-serial-flasher/CMakeCache.txt new file mode 100644 index 000000000..9d4eeffb3 --- /dev/null +++ b/applications/external/esp-flasher/lib/esp-serial-flasher/CMakeCache.txt @@ -0,0 +1,366 @@ +# This is the CMakeCache file. +# For build in directory: /home/cococode/flipperzero-firmware/lib/esp-serial-flasher +# It was generated by CMake: /usr/bin/cmake +# You can edit this file to change values found and used by cmake. +# If you do not want to change any of the values, simply exit the editor. +# If you do want to change a value, simply edit, save, and exit the editor. +# The syntax for the file is as follows: +# KEY:TYPE=VALUE +# KEY is the name of a variable in the cache. +# TYPE is a hint to GUIs for the type of VALUE, DO NOT EDIT TYPE!. +# VALUE is the current value for the KEY. + +######################## +# EXTERNAL cache entries +######################## + +//Path to a program. +CMAKE_ADDR2LINE:FILEPATH=/usr/bin/addr2line + +//Path to a program. +CMAKE_AR:FILEPATH=/usr/bin/ar + +//Choose the type of build, options are: None Debug Release RelWithDebInfo +// MinSizeRel ... +CMAKE_BUILD_TYPE:STRING= + +//Enable/Disable color output during build. +CMAKE_COLOR_MAKEFILE:BOOL=ON + +//CXX compiler +CMAKE_CXX_COMPILER:FILEPATH=/usr/bin/c++ + +//A wrapper around 'ar' adding the appropriate '--plugin' option +// for the GCC compiler +CMAKE_CXX_COMPILER_AR:FILEPATH=/usr/bin/gcc-ar-9 + +//A wrapper around 'ranlib' adding the appropriate '--plugin' option +// for the GCC compiler +CMAKE_CXX_COMPILER_RANLIB:FILEPATH=/usr/bin/gcc-ranlib-9 + +//Flags used by the CXX compiler during all build types. +CMAKE_CXX_FLAGS:STRING= + +//Flags used by the CXX compiler during DEBUG builds. +CMAKE_CXX_FLAGS_DEBUG:STRING=-g + +//Flags used by the CXX compiler during MINSIZEREL builds. +CMAKE_CXX_FLAGS_MINSIZEREL:STRING=-Os -DNDEBUG + +//Flags used by the CXX compiler during RELEASE builds. +CMAKE_CXX_FLAGS_RELEASE:STRING=-O3 -DNDEBUG + +//Flags used by the CXX compiler during RELWITHDEBINFO builds. +CMAKE_CXX_FLAGS_RELWITHDEBINFO:STRING=-O2 -g -DNDEBUG + +//C compiler +CMAKE_C_COMPILER:FILEPATH=/usr/bin/cc + +//A wrapper around 'ar' adding the appropriate '--plugin' option +// for the GCC compiler +CMAKE_C_COMPILER_AR:FILEPATH=/usr/bin/gcc-ar-9 + +//A wrapper around 'ranlib' adding the appropriate '--plugin' option +// for the GCC compiler +CMAKE_C_COMPILER_RANLIB:FILEPATH=/usr/bin/gcc-ranlib-9 + +//Flags used by the C compiler during all build types. +CMAKE_C_FLAGS:STRING= + +//Flags used by the C compiler during DEBUG builds. +CMAKE_C_FLAGS_DEBUG:STRING=-g + +//Flags used by the C compiler during MINSIZEREL builds. +CMAKE_C_FLAGS_MINSIZEREL:STRING=-Os -DNDEBUG + +//Flags used by the C compiler during RELEASE builds. +CMAKE_C_FLAGS_RELEASE:STRING=-O3 -DNDEBUG + +//Flags used by the C compiler during RELWITHDEBINFO builds. +CMAKE_C_FLAGS_RELWITHDEBINFO:STRING=-O2 -g -DNDEBUG + +//Path to a program. +CMAKE_DLLTOOL:FILEPATH=CMAKE_DLLTOOL-NOTFOUND + +//Flags used by the linker during all build types. +CMAKE_EXE_LINKER_FLAGS:STRING= + +//Flags used by the linker during DEBUG builds. +CMAKE_EXE_LINKER_FLAGS_DEBUG:STRING= + +//Flags used by the linker during MINSIZEREL builds. +CMAKE_EXE_LINKER_FLAGS_MINSIZEREL:STRING= + +//Flags used by the linker during RELEASE builds. +CMAKE_EXE_LINKER_FLAGS_RELEASE:STRING= + +//Flags used by the linker during RELWITHDEBINFO builds. +CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO:STRING= + +//Enable/Disable output of compile commands during generation. +CMAKE_EXPORT_COMPILE_COMMANDS:BOOL=OFF + +//Install path prefix, prepended onto install directories. +CMAKE_INSTALL_PREFIX:PATH=/usr/local + +//Path to a program. +CMAKE_LINKER:FILEPATH=/usr/bin/ld + +//Path to a program. +CMAKE_MAKE_PROGRAM:FILEPATH=/usr/bin/make + +//Flags used by the linker during the creation of modules during +// all build types. +CMAKE_MODULE_LINKER_FLAGS:STRING= + +//Flags used by the linker during the creation of modules during +// DEBUG builds. +CMAKE_MODULE_LINKER_FLAGS_DEBUG:STRING= + +//Flags used by the linker during the creation of modules during +// MINSIZEREL builds. +CMAKE_MODULE_LINKER_FLAGS_MINSIZEREL:STRING= + +//Flags used by the linker during the creation of modules during +// RELEASE builds. +CMAKE_MODULE_LINKER_FLAGS_RELEASE:STRING= + +//Flags used by the linker during the creation of modules during +// RELWITHDEBINFO builds. +CMAKE_MODULE_LINKER_FLAGS_RELWITHDEBINFO:STRING= + +//Path to a program. +CMAKE_NM:FILEPATH=/usr/bin/nm + +//Path to a program. +CMAKE_OBJCOPY:FILEPATH=/usr/bin/objcopy + +//Path to a program. +CMAKE_OBJDUMP:FILEPATH=/usr/bin/objdump + +//Value Computed by CMake +CMAKE_PROJECT_DESCRIPTION:STATIC= + +//Value Computed by CMake +CMAKE_PROJECT_HOMEPAGE_URL:STATIC= + +//Value Computed by CMake +CMAKE_PROJECT_NAME:STATIC=Project + +//Path to a program. +CMAKE_RANLIB:FILEPATH=/usr/bin/ranlib + +//Path to a program. +CMAKE_READELF:FILEPATH=/usr/bin/readelf + +//Flags used by the linker during the creation of shared libraries +// during all build types. +CMAKE_SHARED_LINKER_FLAGS:STRING= + +//Flags used by the linker during the creation of shared libraries +// during DEBUG builds. +CMAKE_SHARED_LINKER_FLAGS_DEBUG:STRING= + +//Flags used by the linker during the creation of shared libraries +// during MINSIZEREL builds. +CMAKE_SHARED_LINKER_FLAGS_MINSIZEREL:STRING= + +//Flags used by the linker during the creation of shared libraries +// during RELEASE builds. +CMAKE_SHARED_LINKER_FLAGS_RELEASE:STRING= + +//Flags used by the linker during the creation of shared libraries +// during RELWITHDEBINFO builds. +CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO:STRING= + +//If set, runtime paths are not added when installing shared libraries, +// but are added when building. +CMAKE_SKIP_INSTALL_RPATH:BOOL=NO + +//If set, runtime paths are not added when using shared libraries. +CMAKE_SKIP_RPATH:BOOL=NO + +//Flags used by the linker during the creation of static libraries +// during all build types. +CMAKE_STATIC_LINKER_FLAGS:STRING= + +//Flags used by the linker during the creation of static libraries +// during DEBUG builds. +CMAKE_STATIC_LINKER_FLAGS_DEBUG:STRING= + +//Flags used by the linker during the creation of static libraries +// during MINSIZEREL builds. +CMAKE_STATIC_LINKER_FLAGS_MINSIZEREL:STRING= + +//Flags used by the linker during the creation of static libraries +// during RELEASE builds. +CMAKE_STATIC_LINKER_FLAGS_RELEASE:STRING= + +//Flags used by the linker during the creation of static libraries +// during RELWITHDEBINFO builds. +CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO:STRING= + +//Path to a program. +CMAKE_STRIP:FILEPATH=/usr/bin/strip + +//If this value is on, makefiles will be generated without the +// .SILENT directive, and all commands will be echoed to the console +// during the make. This is useful for debugging only. With Visual +// Studio IDE projects all commands are done without /nologo. +CMAKE_VERBOSE_MAKEFILE:BOOL=FALSE + +//Value Computed by CMake +Project_BINARY_DIR:STATIC=/home/cococode/flipperzero-firmware/lib/esp-serial-flasher + +//Value Computed by CMake +Project_SOURCE_DIR:STATIC=/home/cococode/flipperzero-firmware/lib/esp-serial-flasher + + +######################## +# INTERNAL cache entries +######################## + +//ADVANCED property for variable: CMAKE_ADDR2LINE +CMAKE_ADDR2LINE-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_AR +CMAKE_AR-ADVANCED:INTERNAL=1 +//This is the directory where this CMakeCache.txt was created +CMAKE_CACHEFILE_DIR:INTERNAL=/home/cococode/flipperzero-firmware/lib/esp-serial-flasher +//Major version of cmake used to create the current loaded cache +CMAKE_CACHE_MAJOR_VERSION:INTERNAL=3 +//Minor version of cmake used to create the current loaded cache +CMAKE_CACHE_MINOR_VERSION:INTERNAL=16 +//Patch version of cmake used to create the current loaded cache +CMAKE_CACHE_PATCH_VERSION:INTERNAL=3 +//ADVANCED property for variable: CMAKE_COLOR_MAKEFILE +CMAKE_COLOR_MAKEFILE-ADVANCED:INTERNAL=1 +//Path to CMake executable. +CMAKE_COMMAND:INTERNAL=/usr/bin/cmake +//Path to cpack program executable. +CMAKE_CPACK_COMMAND:INTERNAL=/usr/bin/cpack +//Path to ctest program executable. +CMAKE_CTEST_COMMAND:INTERNAL=/usr/bin/ctest +//ADVANCED property for variable: CMAKE_CXX_COMPILER +CMAKE_CXX_COMPILER-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_CXX_COMPILER_AR +CMAKE_CXX_COMPILER_AR-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_CXX_COMPILER_RANLIB +CMAKE_CXX_COMPILER_RANLIB-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_CXX_FLAGS +CMAKE_CXX_FLAGS-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_CXX_FLAGS_DEBUG +CMAKE_CXX_FLAGS_DEBUG-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_CXX_FLAGS_MINSIZEREL +CMAKE_CXX_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_CXX_FLAGS_RELEASE +CMAKE_CXX_FLAGS_RELEASE-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_CXX_FLAGS_RELWITHDEBINFO +CMAKE_CXX_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_C_COMPILER +CMAKE_C_COMPILER-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_C_COMPILER_AR +CMAKE_C_COMPILER_AR-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_C_COMPILER_RANLIB +CMAKE_C_COMPILER_RANLIB-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_C_FLAGS +CMAKE_C_FLAGS-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_C_FLAGS_DEBUG +CMAKE_C_FLAGS_DEBUG-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_C_FLAGS_MINSIZEREL +CMAKE_C_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_C_FLAGS_RELEASE +CMAKE_C_FLAGS_RELEASE-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_C_FLAGS_RELWITHDEBINFO +CMAKE_C_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_DLLTOOL +CMAKE_DLLTOOL-ADVANCED:INTERNAL=1 +//Executable file format +CMAKE_EXECUTABLE_FORMAT:INTERNAL=ELF +//ADVANCED property for variable: CMAKE_EXE_LINKER_FLAGS +CMAKE_EXE_LINKER_FLAGS-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_EXE_LINKER_FLAGS_DEBUG +CMAKE_EXE_LINKER_FLAGS_DEBUG-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_EXE_LINKER_FLAGS_MINSIZEREL +CMAKE_EXE_LINKER_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_EXE_LINKER_FLAGS_RELEASE +CMAKE_EXE_LINKER_FLAGS_RELEASE-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO +CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_EXPORT_COMPILE_COMMANDS +CMAKE_EXPORT_COMPILE_COMMANDS-ADVANCED:INTERNAL=1 +//Name of external makefile project generator. +CMAKE_EXTRA_GENERATOR:INTERNAL= +//Name of generator. +CMAKE_GENERATOR:INTERNAL=Unix Makefiles +//Generator instance identifier. +CMAKE_GENERATOR_INSTANCE:INTERNAL= +//Name of generator platform. +CMAKE_GENERATOR_PLATFORM:INTERNAL= +//Name of generator toolset. +CMAKE_GENERATOR_TOOLSET:INTERNAL= +//Source directory with the top level CMakeLists.txt file for this +// project +CMAKE_HOME_DIRECTORY:INTERNAL=/home/cococode/flipperzero-firmware/lib/esp-serial-flasher +//Install .so files without execute permission. +CMAKE_INSTALL_SO_NO_EXE:INTERNAL=1 +//ADVANCED property for variable: CMAKE_LINKER +CMAKE_LINKER-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_MAKE_PROGRAM +CMAKE_MAKE_PROGRAM-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_MODULE_LINKER_FLAGS +CMAKE_MODULE_LINKER_FLAGS-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_MODULE_LINKER_FLAGS_DEBUG +CMAKE_MODULE_LINKER_FLAGS_DEBUG-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_MODULE_LINKER_FLAGS_MINSIZEREL +CMAKE_MODULE_LINKER_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_MODULE_LINKER_FLAGS_RELEASE +CMAKE_MODULE_LINKER_FLAGS_RELEASE-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_MODULE_LINKER_FLAGS_RELWITHDEBINFO +CMAKE_MODULE_LINKER_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_NM +CMAKE_NM-ADVANCED:INTERNAL=1 +//number of local generators +CMAKE_NUMBER_OF_MAKEFILES:INTERNAL=1 +//ADVANCED property for variable: CMAKE_OBJCOPY +CMAKE_OBJCOPY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_OBJDUMP +CMAKE_OBJDUMP-ADVANCED:INTERNAL=1 +//Platform information initialized +CMAKE_PLATFORM_INFO_INITIALIZED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_RANLIB +CMAKE_RANLIB-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_READELF +CMAKE_READELF-ADVANCED:INTERNAL=1 +//Path to CMake installation. +CMAKE_ROOT:INTERNAL=/usr/share/cmake-3.16 +//ADVANCED property for variable: CMAKE_SHARED_LINKER_FLAGS +CMAKE_SHARED_LINKER_FLAGS-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_SHARED_LINKER_FLAGS_DEBUG +CMAKE_SHARED_LINKER_FLAGS_DEBUG-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_SHARED_LINKER_FLAGS_MINSIZEREL +CMAKE_SHARED_LINKER_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_SHARED_LINKER_FLAGS_RELEASE +CMAKE_SHARED_LINKER_FLAGS_RELEASE-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO +CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_SKIP_INSTALL_RPATH +CMAKE_SKIP_INSTALL_RPATH-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_SKIP_RPATH +CMAKE_SKIP_RPATH-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_STATIC_LINKER_FLAGS +CMAKE_STATIC_LINKER_FLAGS-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_STATIC_LINKER_FLAGS_DEBUG +CMAKE_STATIC_LINKER_FLAGS_DEBUG-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_STATIC_LINKER_FLAGS_MINSIZEREL +CMAKE_STATIC_LINKER_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_STATIC_LINKER_FLAGS_RELEASE +CMAKE_STATIC_LINKER_FLAGS_RELEASE-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO +CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_STRIP +CMAKE_STRIP-ADVANCED:INTERNAL=1 +//uname command +CMAKE_UNAME:INTERNAL=/usr/bin/uname +//ADVANCED property for variable: CMAKE_VERBOSE_MAKEFILE +CMAKE_VERBOSE_MAKEFILE-ADVANCED:INTERNAL=1 + diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/CMakeFiles/3.16.3/CMakeCCompiler.cmake b/applications/external/esp-flasher/lib/esp-serial-flasher/CMakeFiles/3.16.3/CMakeCCompiler.cmake new file mode 100644 index 000000000..c5ece7b85 --- /dev/null +++ b/applications/external/esp-flasher/lib/esp-serial-flasher/CMakeFiles/3.16.3/CMakeCCompiler.cmake @@ -0,0 +1,76 @@ +set(CMAKE_C_COMPILER "/usr/bin/cc") +set(CMAKE_C_COMPILER_ARG1 "") +set(CMAKE_C_COMPILER_ID "GNU") +set(CMAKE_C_COMPILER_VERSION "9.4.0") +set(CMAKE_C_COMPILER_VERSION_INTERNAL "") +set(CMAKE_C_COMPILER_WRAPPER "") +set(CMAKE_C_STANDARD_COMPUTED_DEFAULT "11") +set(CMAKE_C_COMPILE_FEATURES "c_std_90;c_function_prototypes;c_std_99;c_restrict;c_variadic_macros;c_std_11;c_static_assert") +set(CMAKE_C90_COMPILE_FEATURES "c_std_90;c_function_prototypes") +set(CMAKE_C99_COMPILE_FEATURES "c_std_99;c_restrict;c_variadic_macros") +set(CMAKE_C11_COMPILE_FEATURES "c_std_11;c_static_assert") + +set(CMAKE_C_PLATFORM_ID "Linux") +set(CMAKE_C_SIMULATE_ID "") +set(CMAKE_C_COMPILER_FRONTEND_VARIANT "") +set(CMAKE_C_SIMULATE_VERSION "") + + + +set(CMAKE_AR "/usr/bin/ar") +set(CMAKE_C_COMPILER_AR "/usr/bin/gcc-ar-9") +set(CMAKE_RANLIB "/usr/bin/ranlib") +set(CMAKE_C_COMPILER_RANLIB "/usr/bin/gcc-ranlib-9") +set(CMAKE_LINKER "/usr/bin/ld") +set(CMAKE_MT "") +set(CMAKE_COMPILER_IS_GNUCC 1) +set(CMAKE_C_COMPILER_LOADED 1) +set(CMAKE_C_COMPILER_WORKS TRUE) +set(CMAKE_C_ABI_COMPILED TRUE) +set(CMAKE_COMPILER_IS_MINGW ) +set(CMAKE_COMPILER_IS_CYGWIN ) +if(CMAKE_COMPILER_IS_CYGWIN) + set(CYGWIN 1) + set(UNIX 1) +endif() + +set(CMAKE_C_COMPILER_ENV_VAR "CC") + +if(CMAKE_COMPILER_IS_MINGW) + set(MINGW 1) +endif() +set(CMAKE_C_COMPILER_ID_RUN 1) +set(CMAKE_C_SOURCE_FILE_EXTENSIONS c;m) +set(CMAKE_C_IGNORE_EXTENSIONS h;H;o;O;obj;OBJ;def;DEF;rc;RC) +set(CMAKE_C_LINKER_PREFERENCE 10) + +# Save compiler ABI information. +set(CMAKE_C_SIZEOF_DATA_PTR "8") +set(CMAKE_C_COMPILER_ABI "ELF") +set(CMAKE_C_LIBRARY_ARCHITECTURE "x86_64-linux-gnu") + +if(CMAKE_C_SIZEOF_DATA_PTR) + set(CMAKE_SIZEOF_VOID_P "${CMAKE_C_SIZEOF_DATA_PTR}") +endif() + +if(CMAKE_C_COMPILER_ABI) + set(CMAKE_INTERNAL_PLATFORM_ABI "${CMAKE_C_COMPILER_ABI}") +endif() + +if(CMAKE_C_LIBRARY_ARCHITECTURE) + set(CMAKE_LIBRARY_ARCHITECTURE "x86_64-linux-gnu") +endif() + +set(CMAKE_C_CL_SHOWINCLUDES_PREFIX "") +if(CMAKE_C_CL_SHOWINCLUDES_PREFIX) + set(CMAKE_CL_SHOWINCLUDES_PREFIX "${CMAKE_C_CL_SHOWINCLUDES_PREFIX}") +endif() + + + + + +set(CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES "/usr/lib/gcc/x86_64-linux-gnu/9/include;/usr/local/include;/usr/include/x86_64-linux-gnu;/usr/include") +set(CMAKE_C_IMPLICIT_LINK_LIBRARIES "gcc;gcc_s;c;gcc;gcc_s") +set(CMAKE_C_IMPLICIT_LINK_DIRECTORIES "/usr/lib/gcc/x86_64-linux-gnu/9;/usr/lib/x86_64-linux-gnu;/usr/lib;/lib/x86_64-linux-gnu;/lib") +set(CMAKE_C_IMPLICIT_LINK_FRAMEWORK_DIRECTORIES "") diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/CMakeFiles/3.16.3/CMakeCXXCompiler.cmake b/applications/external/esp-flasher/lib/esp-serial-flasher/CMakeFiles/3.16.3/CMakeCXXCompiler.cmake new file mode 100644 index 000000000..278ef39ee --- /dev/null +++ b/applications/external/esp-flasher/lib/esp-serial-flasher/CMakeFiles/3.16.3/CMakeCXXCompiler.cmake @@ -0,0 +1,88 @@ +set(CMAKE_CXX_COMPILER "/usr/bin/c++") +set(CMAKE_CXX_COMPILER_ARG1 "") +set(CMAKE_CXX_COMPILER_ID "GNU") +set(CMAKE_CXX_COMPILER_VERSION "9.4.0") +set(CMAKE_CXX_COMPILER_VERSION_INTERNAL "") +set(CMAKE_CXX_COMPILER_WRAPPER "") +set(CMAKE_CXX_STANDARD_COMPUTED_DEFAULT "14") +set(CMAKE_CXX_COMPILE_FEATURES "cxx_std_98;cxx_template_template_parameters;cxx_std_11;cxx_alias_templates;cxx_alignas;cxx_alignof;cxx_attributes;cxx_auto_type;cxx_constexpr;cxx_decltype;cxx_decltype_incomplete_return_types;cxx_default_function_template_args;cxx_defaulted_functions;cxx_defaulted_move_initializers;cxx_delegating_constructors;cxx_deleted_functions;cxx_enum_forward_declarations;cxx_explicit_conversions;cxx_extended_friend_declarations;cxx_extern_templates;cxx_final;cxx_func_identifier;cxx_generalized_initializers;cxx_inheriting_constructors;cxx_inline_namespaces;cxx_lambdas;cxx_local_type_template_args;cxx_long_long_type;cxx_noexcept;cxx_nonstatic_member_init;cxx_nullptr;cxx_override;cxx_range_for;cxx_raw_string_literals;cxx_reference_qualified_functions;cxx_right_angle_brackets;cxx_rvalue_references;cxx_sizeof_member;cxx_static_assert;cxx_strong_enums;cxx_thread_local;cxx_trailing_return_types;cxx_unicode_literals;cxx_uniform_initialization;cxx_unrestricted_unions;cxx_user_literals;cxx_variadic_macros;cxx_variadic_templates;cxx_std_14;cxx_aggregate_default_initializers;cxx_attribute_deprecated;cxx_binary_literals;cxx_contextual_conversions;cxx_decltype_auto;cxx_digit_separators;cxx_generic_lambdas;cxx_lambda_init_captures;cxx_relaxed_constexpr;cxx_return_type_deduction;cxx_variable_templates;cxx_std_17;cxx_std_20") +set(CMAKE_CXX98_COMPILE_FEATURES "cxx_std_98;cxx_template_template_parameters") +set(CMAKE_CXX11_COMPILE_FEATURES "cxx_std_11;cxx_alias_templates;cxx_alignas;cxx_alignof;cxx_attributes;cxx_auto_type;cxx_constexpr;cxx_decltype;cxx_decltype_incomplete_return_types;cxx_default_function_template_args;cxx_defaulted_functions;cxx_defaulted_move_initializers;cxx_delegating_constructors;cxx_deleted_functions;cxx_enum_forward_declarations;cxx_explicit_conversions;cxx_extended_friend_declarations;cxx_extern_templates;cxx_final;cxx_func_identifier;cxx_generalized_initializers;cxx_inheriting_constructors;cxx_inline_namespaces;cxx_lambdas;cxx_local_type_template_args;cxx_long_long_type;cxx_noexcept;cxx_nonstatic_member_init;cxx_nullptr;cxx_override;cxx_range_for;cxx_raw_string_literals;cxx_reference_qualified_functions;cxx_right_angle_brackets;cxx_rvalue_references;cxx_sizeof_member;cxx_static_assert;cxx_strong_enums;cxx_thread_local;cxx_trailing_return_types;cxx_unicode_literals;cxx_uniform_initialization;cxx_unrestricted_unions;cxx_user_literals;cxx_variadic_macros;cxx_variadic_templates") +set(CMAKE_CXX14_COMPILE_FEATURES "cxx_std_14;cxx_aggregate_default_initializers;cxx_attribute_deprecated;cxx_binary_literals;cxx_contextual_conversions;cxx_decltype_auto;cxx_digit_separators;cxx_generic_lambdas;cxx_lambda_init_captures;cxx_relaxed_constexpr;cxx_return_type_deduction;cxx_variable_templates") +set(CMAKE_CXX17_COMPILE_FEATURES "cxx_std_17") +set(CMAKE_CXX20_COMPILE_FEATURES "cxx_std_20") + +set(CMAKE_CXX_PLATFORM_ID "Linux") +set(CMAKE_CXX_SIMULATE_ID "") +set(CMAKE_CXX_COMPILER_FRONTEND_VARIANT "") +set(CMAKE_CXX_SIMULATE_VERSION "") + + + +set(CMAKE_AR "/usr/bin/ar") +set(CMAKE_CXX_COMPILER_AR "/usr/bin/gcc-ar-9") +set(CMAKE_RANLIB "/usr/bin/ranlib") +set(CMAKE_CXX_COMPILER_RANLIB "/usr/bin/gcc-ranlib-9") +set(CMAKE_LINKER "/usr/bin/ld") +set(CMAKE_MT "") +set(CMAKE_COMPILER_IS_GNUCXX 1) +set(CMAKE_CXX_COMPILER_LOADED 1) +set(CMAKE_CXX_COMPILER_WORKS TRUE) +set(CMAKE_CXX_ABI_COMPILED TRUE) +set(CMAKE_COMPILER_IS_MINGW ) +set(CMAKE_COMPILER_IS_CYGWIN ) +if(CMAKE_COMPILER_IS_CYGWIN) + set(CYGWIN 1) + set(UNIX 1) +endif() + +set(CMAKE_CXX_COMPILER_ENV_VAR "CXX") + +if(CMAKE_COMPILER_IS_MINGW) + set(MINGW 1) +endif() +set(CMAKE_CXX_COMPILER_ID_RUN 1) +set(CMAKE_CXX_SOURCE_FILE_EXTENSIONS C;M;c++;cc;cpp;cxx;m;mm;CPP) +set(CMAKE_CXX_IGNORE_EXTENSIONS inl;h;hpp;HPP;H;o;O;obj;OBJ;def;DEF;rc;RC) + +foreach (lang C OBJC OBJCXX) + if (CMAKE_${lang}_COMPILER_ID_RUN) + foreach(extension IN LISTS CMAKE_${lang}_SOURCE_FILE_EXTENSIONS) + list(REMOVE_ITEM CMAKE_CXX_SOURCE_FILE_EXTENSIONS ${extension}) + endforeach() + endif() +endforeach() + +set(CMAKE_CXX_LINKER_PREFERENCE 30) +set(CMAKE_CXX_LINKER_PREFERENCE_PROPAGATES 1) + +# Save compiler ABI information. +set(CMAKE_CXX_SIZEOF_DATA_PTR "8") +set(CMAKE_CXX_COMPILER_ABI "ELF") +set(CMAKE_CXX_LIBRARY_ARCHITECTURE "x86_64-linux-gnu") + +if(CMAKE_CXX_SIZEOF_DATA_PTR) + set(CMAKE_SIZEOF_VOID_P "${CMAKE_CXX_SIZEOF_DATA_PTR}") +endif() + +if(CMAKE_CXX_COMPILER_ABI) + set(CMAKE_INTERNAL_PLATFORM_ABI "${CMAKE_CXX_COMPILER_ABI}") +endif() + +if(CMAKE_CXX_LIBRARY_ARCHITECTURE) + set(CMAKE_LIBRARY_ARCHITECTURE "x86_64-linux-gnu") +endif() + +set(CMAKE_CXX_CL_SHOWINCLUDES_PREFIX "") +if(CMAKE_CXX_CL_SHOWINCLUDES_PREFIX) + set(CMAKE_CL_SHOWINCLUDES_PREFIX "${CMAKE_CXX_CL_SHOWINCLUDES_PREFIX}") +endif() + + + + + +set(CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES "/usr/include/c++/9;/usr/include/x86_64-linux-gnu/c++/9;/usr/include/c++/9/backward;/usr/lib/gcc/x86_64-linux-gnu/9/include;/usr/local/include;/usr/include/x86_64-linux-gnu;/usr/include") +set(CMAKE_CXX_IMPLICIT_LINK_LIBRARIES "stdc++;m;gcc_s;gcc;c;gcc_s;gcc") +set(CMAKE_CXX_IMPLICIT_LINK_DIRECTORIES "/usr/lib/gcc/x86_64-linux-gnu/9;/usr/lib/x86_64-linux-gnu;/usr/lib;/lib/x86_64-linux-gnu;/lib") +set(CMAKE_CXX_IMPLICIT_LINK_FRAMEWORK_DIRECTORIES "") diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/CMakeFiles/3.16.3/CMakeDetermineCompilerABI_C.bin b/applications/external/esp-flasher/lib/esp-serial-flasher/CMakeFiles/3.16.3/CMakeDetermineCompilerABI_C.bin new file mode 100755 index 0000000000000000000000000000000000000000..35de1468eef863e60eec8592df6b83860a5cea2c GIT binary patch literal 16552 zcmeHOeQX>@6`%9jiIX^Y*EDHO5}IvkMMCk#Hi>aUOz&);y#^;KiA{|1v7CKp`{?=3 zz1vIeC`}^;B2K9xsz3=;R8bJ9qWy#Z!AB7kM=q^|R7fNUffnH=RJbK=okEbINsjks z-aGHE&#Kg*{=-hR_vZcPeaya@-OPCBJre0`EAw~+7q9rVKrR$eNQfE+H&F#hNHmJ2 z@cUu0S}Xv*Tw3?5YB9wJ4(`O4vis)ebb`#`-4 zY$D!CuaX0%++Osjp)wRr{ub#V=`pqHVQi*rl>Bs)ooW=9wM1DDS=19jB@e@)3ej=$ z$CTrVF~O^m;_0NCvQq56Q?G~goV1nnn4%9XJ6|(n@5kT$tfxscqR~OOC|EFMf1d+A z85e4QFT%sTgZ#}m4grckQwpzGpG@>NY*?R+)g%+C+(^wxV?#~DhF~@wte38_U+4qp zRQtAW0cOS#qcF;`SWY~)`*%N|kJh$cdVT$~C(f=q_1wz8_xylupih*+g8Doqm`5Lo z;nY4JI4s1Sw=J%$s#scCE<)J02rfTd&5_?<{?ekB^FO=v*uiu0-wai4KC$HK&t4k; z(@U40sPBFG%?l6qtbX8;Zy&5$e({NCt1EsAD)SbMzgD7kC1A@+;12_)mB4>K51s%# zAZ{0dkX>n*aN)q8l6bYKcHm0@zY8vB$pD~%eW#sA0lx)DPE#5@za9s6ned6>TNEBw zMC>O#L=$Q~;Xec#5Y=<~|51`(M|Rpt{^NvC5{~1Br3WtTPe4OXh!u(m0rRg9r(1(C zjJ}bm(Vs{~lZgXyV6n-_n$e7D3`G+uM*6Zjlu8Jrqid@Xi)Z2kiL4pVbZu=(rc?2* zXm2tu5~=>Qkp(;Hej_$)W`q%sMa?MKK%%SyGcc4+kqZOtZHIa+kuq}Gc+Bb!yqRDj zg=|bXC7OoEjZ6D#Ahrx`$pU? zZUo#2xDjw8;6}iWfE$4iIs*Uj-|-*)$mL4?Sj8V7l?n2KzZPPA-Pr|`MNhd^5^#H`HT9IzfRxZ)j3vi6qZo>*iyeFO=rMTVMPX1 z{&`#k5U! zR1AJzsJIThZqHuOwdue95HzBpZqUm=d|A)uCSQzP!)_jnT-HzOV=I2;gXsLYJm7zB z+!MLR=!*{Y+TIB?6W-N1)|7=ELHP3>`SaoKj{MbdS2%yITOX^50lc$w-E|z3DdR?= zpr5(!HP_U=O!4TR<#0XG6}1l$O?5%@oefXDW=QHYLh zZQD0nz7OrLZQP5Yka)B=u^Y|{AmCZ${Su&L;VjT6fmYMU75p}e#dBbX@QegJtEv`O9)~u>SHtxr*utH&@`!KkqZO^b+M^3}U*nO5?Y>aT*BJKI zhJ9#JagzwrU<7B>QJ1l$O?5pW~mM!=1L8v!>0ZUo#2 zeBcq_^KpD0j<#dPGjCPgBe}^EaL!wmDLPm8A>#Sm-74bwOdFmL#lq+BaNmW6+ppdz zq>(>Ghvs;XbdoB?Grr|0CB$My#6_Zb-V;l;BD3cuiRZI;<8lAx;v4FS4rtWDmX!OP=Eh+dj!3p!Rage40OYe4*fZX2&Z9kCz=^Bu>#d+VRE3`DDjocG7cW$E$=^ z5??B6OX9v_{)OF+U+{Bi$H9nZMO57jCyV)fy|vW~C!I%1%HLW%PivQ7UVNVIIGk24 zG5@^cHo?!IU4HI)Y;5Vs)p~aGCaTSh$o!DkoYMFekbAl z9#mF@_#{+d))voo^K;!M@wssrB0KzEWjmu%er`PR?_;I<|Fo13h_xD2pkTkT%y+*( zl5NXG3~$6yO4;86hRPsIz&U>=r0%VA#`kxCd*J*$zZclWs}i3(?r)GC{(phxugl8Z zc^H8Hpnd20|J4$oJAZyH`#<;jss|j;?N2(+)i(pKaVISFgv960!+n64I)|SIyi}gT ze!xrRL41?sMabe5s;2?h?2zQ10lZW`!Z_fi@)3RuT9|j>%!7EGlybHMe1FIFpdSAxOnmIUe)F)#mmV5U#?5#v*jU?8S6WCWGZ@iKCOQf!24-ed^kTX<_ni(O1+UjPJYJ4Qq&b9(CzfFRw14Yzl0Zw_}F+uPc9M!JlyaC2t_-O2oo zo6Od5jT(}lyeO`Yt z)u7ET**15~W~J+vNUir;6f#ku_IfJUo6ujiO*R%NI=^>+dGFIY=+|9StwbPwtC za?RQQD753=f%S_H$k9m*E)?t|R#~6-1(>gfx6Jyyj%0cf+N_1l6`=>AhI<9p=k+Gj zyGWAz&vHx;K|Ag}nCEpVQ;qbU{U=zAK?QG>^?5&3q$J4xo%eqN+VP&a9sW~Mg(&X> z2`z?~`R_o*s;`kAQ#Qx)%riah(C2kCQRff9L|4R=25ltyDomOn|m&c!hKR^Z7 zr1aPq_X(U=>y&4n=_SzhTlD$w12Z)MLSZ?@&GL-D4GlQ=*ne@~5mXxEG=4b$v9KP; z@%w!{?pQ@bY>*yoe2x*~{_y(`k}ivVx9QWwz1?A){jw}B&-vaN PL%OR`aa9fl2P^&yA*+L> literal 0 HcmV?d00001 diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/CMakeFiles/3.16.3/CMakeDetermineCompilerABI_CXX.bin b/applications/external/esp-flasher/lib/esp-serial-flasher/CMakeFiles/3.16.3/CMakeDetermineCompilerABI_CXX.bin new file mode 100755 index 0000000000000000000000000000000000000000..9cba7ec766c0da3a854716392dd048bc86eb07ca GIT binary patch literal 16560 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 Hjb;A=Q^$PL literal 0 HcmV?d00001 diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/CMakeFiles/3.16.3/CMakeSystem.cmake b/applications/external/esp-flasher/lib/esp-serial-flasher/CMakeFiles/3.16.3/CMakeSystem.cmake new file mode 100644 index 000000000..0ad36cc9a --- /dev/null +++ b/applications/external/esp-flasher/lib/esp-serial-flasher/CMakeFiles/3.16.3/CMakeSystem.cmake @@ -0,0 +1,15 @@ +set(CMAKE_HOST_SYSTEM "Linux-5.10.16.3-microsoft-standard-WSL2") +set(CMAKE_HOST_SYSTEM_NAME "Linux") +set(CMAKE_HOST_SYSTEM_VERSION "5.10.16.3-microsoft-standard-WSL2") +set(CMAKE_HOST_SYSTEM_PROCESSOR "x86_64") + + + +set(CMAKE_SYSTEM "Linux-5.10.16.3-microsoft-standard-WSL2") +set(CMAKE_SYSTEM_NAME "Linux") +set(CMAKE_SYSTEM_VERSION "5.10.16.3-microsoft-standard-WSL2") +set(CMAKE_SYSTEM_PROCESSOR "x86_64") + +set(CMAKE_CROSSCOMPILING "FALSE") + +set(CMAKE_SYSTEM_LOADED 1) diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/CMakeFiles/3.16.3/CompilerIdC/CMakeCCompilerId.c b/applications/external/esp-flasher/lib/esp-serial-flasher/CMakeFiles/3.16.3/CompilerIdC/CMakeCCompilerId.c new file mode 100644 index 000000000..d884b5090 --- /dev/null +++ b/applications/external/esp-flasher/lib/esp-serial-flasher/CMakeFiles/3.16.3/CompilerIdC/CMakeCCompilerId.c @@ -0,0 +1,671 @@ +#ifdef __cplusplus +# error "A C++ compiler has been selected for C." +#endif + +#if defined(__18CXX) +# define ID_VOID_MAIN +#endif +#if defined(__CLASSIC_C__) +/* cv-qualifiers did not exist in K&R C */ +# define const +# define volatile +#endif + + +/* Version number components: V=Version, R=Revision, P=Patch + Version date components: YYYY=Year, MM=Month, DD=Day */ + +#if defined(__INTEL_COMPILER) || defined(__ICC) +# define COMPILER_ID "Intel" +# if defined(_MSC_VER) +# define SIMULATE_ID "MSVC" +# endif +# if defined(__GNUC__) +# define SIMULATE_ID "GNU" +# endif + /* __INTEL_COMPILER = VRP */ +# define COMPILER_VERSION_MAJOR DEC(__INTEL_COMPILER/100) +# define COMPILER_VERSION_MINOR DEC(__INTEL_COMPILER/10 % 10) +# if defined(__INTEL_COMPILER_UPDATE) +# define COMPILER_VERSION_PATCH DEC(__INTEL_COMPILER_UPDATE) +# else +# define COMPILER_VERSION_PATCH DEC(__INTEL_COMPILER % 10) +# endif +# if defined(__INTEL_COMPILER_BUILD_DATE) + /* __INTEL_COMPILER_BUILD_DATE = YYYYMMDD */ +# define COMPILER_VERSION_TWEAK DEC(__INTEL_COMPILER_BUILD_DATE) +# endif +# if defined(_MSC_VER) + /* _MSC_VER = VVRR */ +# define SIMULATE_VERSION_MAJOR DEC(_MSC_VER / 100) +# define SIMULATE_VERSION_MINOR DEC(_MSC_VER % 100) +# endif +# if defined(__GNUC__) +# define SIMULATE_VERSION_MAJOR DEC(__GNUC__) +# elif defined(__GNUG__) +# define SIMULATE_VERSION_MAJOR DEC(__GNUG__) +# endif +# if defined(__GNUC_MINOR__) +# define SIMULATE_VERSION_MINOR DEC(__GNUC_MINOR__) +# endif +# if defined(__GNUC_PATCHLEVEL__) +# define SIMULATE_VERSION_PATCH DEC(__GNUC_PATCHLEVEL__) +# endif + +#elif defined(__PATHCC__) +# define COMPILER_ID "PathScale" +# define COMPILER_VERSION_MAJOR DEC(__PATHCC__) +# define COMPILER_VERSION_MINOR DEC(__PATHCC_MINOR__) +# if defined(__PATHCC_PATCHLEVEL__) +# define COMPILER_VERSION_PATCH DEC(__PATHCC_PATCHLEVEL__) +# endif + +#elif defined(__BORLANDC__) && defined(__CODEGEARC_VERSION__) +# define COMPILER_ID "Embarcadero" +# define COMPILER_VERSION_MAJOR HEX(__CODEGEARC_VERSION__>>24 & 0x00FF) +# define COMPILER_VERSION_MINOR HEX(__CODEGEARC_VERSION__>>16 & 0x00FF) +# define COMPILER_VERSION_PATCH DEC(__CODEGEARC_VERSION__ & 0xFFFF) + +#elif defined(__BORLANDC__) +# define COMPILER_ID "Borland" + /* __BORLANDC__ = 0xVRR */ +# define COMPILER_VERSION_MAJOR HEX(__BORLANDC__>>8) +# define COMPILER_VERSION_MINOR HEX(__BORLANDC__ & 0xFF) + +#elif defined(__WATCOMC__) && __WATCOMC__ < 1200 +# define COMPILER_ID "Watcom" + /* __WATCOMC__ = VVRR */ +# define COMPILER_VERSION_MAJOR DEC(__WATCOMC__ / 100) +# define COMPILER_VERSION_MINOR DEC((__WATCOMC__ / 10) % 10) +# if (__WATCOMC__ % 10) > 0 +# define COMPILER_VERSION_PATCH DEC(__WATCOMC__ % 10) +# endif + +#elif defined(__WATCOMC__) +# define COMPILER_ID "OpenWatcom" + /* __WATCOMC__ = VVRP + 1100 */ +# define COMPILER_VERSION_MAJOR DEC((__WATCOMC__ - 1100) / 100) +# define COMPILER_VERSION_MINOR DEC((__WATCOMC__ / 10) % 10) +# if (__WATCOMC__ % 10) > 0 +# define COMPILER_VERSION_PATCH DEC(__WATCOMC__ % 10) +# endif + +#elif defined(__SUNPRO_C) +# define COMPILER_ID "SunPro" +# if __SUNPRO_C >= 0x5100 + /* __SUNPRO_C = 0xVRRP */ +# define COMPILER_VERSION_MAJOR HEX(__SUNPRO_C>>12) +# define COMPILER_VERSION_MINOR HEX(__SUNPRO_C>>4 & 0xFF) +# define COMPILER_VERSION_PATCH HEX(__SUNPRO_C & 0xF) +# else + /* __SUNPRO_CC = 0xVRP */ +# define COMPILER_VERSION_MAJOR HEX(__SUNPRO_C>>8) +# define COMPILER_VERSION_MINOR HEX(__SUNPRO_C>>4 & 0xF) +# define COMPILER_VERSION_PATCH HEX(__SUNPRO_C & 0xF) +# endif + +#elif defined(__HP_cc) +# define COMPILER_ID "HP" + /* __HP_cc = VVRRPP */ +# define COMPILER_VERSION_MAJOR DEC(__HP_cc/10000) +# define COMPILER_VERSION_MINOR DEC(__HP_cc/100 % 100) +# define COMPILER_VERSION_PATCH DEC(__HP_cc % 100) + +#elif defined(__DECC) +# define COMPILER_ID "Compaq" + /* __DECC_VER = VVRRTPPPP */ +# define COMPILER_VERSION_MAJOR DEC(__DECC_VER/10000000) +# define COMPILER_VERSION_MINOR DEC(__DECC_VER/100000 % 100) +# define COMPILER_VERSION_PATCH DEC(__DECC_VER % 10000) + +#elif defined(__IBMC__) && defined(__COMPILER_VER__) +# define COMPILER_ID "zOS" + /* __IBMC__ = VRP */ +# define COMPILER_VERSION_MAJOR DEC(__IBMC__/100) +# define COMPILER_VERSION_MINOR DEC(__IBMC__/10 % 10) +# define COMPILER_VERSION_PATCH DEC(__IBMC__ % 10) + +#elif defined(__ibmxl__) && defined(__clang__) +# define COMPILER_ID "XLClang" +# define COMPILER_VERSION_MAJOR DEC(__ibmxl_version__) +# define COMPILER_VERSION_MINOR DEC(__ibmxl_release__) +# define COMPILER_VERSION_PATCH DEC(__ibmxl_modification__) +# define COMPILER_VERSION_TWEAK DEC(__ibmxl_ptf_fix_level__) + + +#elif defined(__IBMC__) && !defined(__COMPILER_VER__) && __IBMC__ >= 800 +# define COMPILER_ID "XL" + /* __IBMC__ = VRP */ +# define COMPILER_VERSION_MAJOR DEC(__IBMC__/100) +# define COMPILER_VERSION_MINOR DEC(__IBMC__/10 % 10) +# define COMPILER_VERSION_PATCH DEC(__IBMC__ % 10) + +#elif defined(__IBMC__) && !defined(__COMPILER_VER__) && __IBMC__ < 800 +# define COMPILER_ID "VisualAge" + /* __IBMC__ = VRP */ +# define COMPILER_VERSION_MAJOR DEC(__IBMC__/100) +# define COMPILER_VERSION_MINOR DEC(__IBMC__/10 % 10) +# define COMPILER_VERSION_PATCH DEC(__IBMC__ % 10) + +#elif defined(__PGI) +# define COMPILER_ID "PGI" +# define COMPILER_VERSION_MAJOR DEC(__PGIC__) +# define COMPILER_VERSION_MINOR DEC(__PGIC_MINOR__) +# if defined(__PGIC_PATCHLEVEL__) +# define COMPILER_VERSION_PATCH DEC(__PGIC_PATCHLEVEL__) +# endif + +#elif defined(_CRAYC) +# define COMPILER_ID "Cray" +# define COMPILER_VERSION_MAJOR DEC(_RELEASE_MAJOR) +# define COMPILER_VERSION_MINOR DEC(_RELEASE_MINOR) + +#elif defined(__TI_COMPILER_VERSION__) +# define COMPILER_ID "TI" + /* __TI_COMPILER_VERSION__ = VVVRRRPPP */ +# define COMPILER_VERSION_MAJOR DEC(__TI_COMPILER_VERSION__/1000000) +# define COMPILER_VERSION_MINOR DEC(__TI_COMPILER_VERSION__/1000 % 1000) +# define COMPILER_VERSION_PATCH DEC(__TI_COMPILER_VERSION__ % 1000) + +#elif defined(__FUJITSU) || defined(__FCC_VERSION) || defined(__fcc_version) +# define COMPILER_ID "Fujitsu" + +#elif defined(__ghs__) +# define COMPILER_ID "GHS" +/* __GHS_VERSION_NUMBER = VVVVRP */ +# ifdef __GHS_VERSION_NUMBER +# define COMPILER_VERSION_MAJOR DEC(__GHS_VERSION_NUMBER / 100) +# define COMPILER_VERSION_MINOR DEC(__GHS_VERSION_NUMBER / 10 % 10) +# define COMPILER_VERSION_PATCH DEC(__GHS_VERSION_NUMBER % 10) +# endif + +#elif defined(__TINYC__) +# define COMPILER_ID "TinyCC" + +#elif defined(__BCC__) +# define COMPILER_ID "Bruce" + +#elif defined(__SCO_VERSION__) +# define COMPILER_ID "SCO" + +#elif defined(__ARMCC_VERSION) && !defined(__clang__) +# define COMPILER_ID "ARMCC" +#if __ARMCC_VERSION >= 1000000 + /* __ARMCC_VERSION = VRRPPPP */ + # define COMPILER_VERSION_MAJOR DEC(__ARMCC_VERSION/1000000) + # define COMPILER_VERSION_MINOR DEC(__ARMCC_VERSION/10000 % 100) + # define COMPILER_VERSION_PATCH DEC(__ARMCC_VERSION % 10000) +#else + /* __ARMCC_VERSION = VRPPPP */ + # define COMPILER_VERSION_MAJOR DEC(__ARMCC_VERSION/100000) + # define COMPILER_VERSION_MINOR DEC(__ARMCC_VERSION/10000 % 10) + # define COMPILER_VERSION_PATCH DEC(__ARMCC_VERSION % 10000) +#endif + + +#elif defined(__clang__) && defined(__apple_build_version__) +# define COMPILER_ID "AppleClang" +# if defined(_MSC_VER) +# define SIMULATE_ID "MSVC" +# endif +# define COMPILER_VERSION_MAJOR DEC(__clang_major__) +# define COMPILER_VERSION_MINOR DEC(__clang_minor__) +# define COMPILER_VERSION_PATCH DEC(__clang_patchlevel__) +# if defined(_MSC_VER) + /* _MSC_VER = VVRR */ +# define SIMULATE_VERSION_MAJOR DEC(_MSC_VER / 100) +# define SIMULATE_VERSION_MINOR DEC(_MSC_VER % 100) +# endif +# define COMPILER_VERSION_TWEAK DEC(__apple_build_version__) + +#elif defined(__clang__) && defined(__ARMCOMPILER_VERSION) +# define COMPILER_ID "ARMClang" + # define COMPILER_VERSION_MAJOR DEC(__ARMCOMPILER_VERSION/1000000) + # define COMPILER_VERSION_MINOR DEC(__ARMCOMPILER_VERSION/10000 % 100) + # define COMPILER_VERSION_PATCH DEC(__ARMCOMPILER_VERSION % 10000) +# define COMPILER_VERSION_INTERNAL DEC(__ARMCOMPILER_VERSION) + +#elif defined(__clang__) +# define COMPILER_ID "Clang" +# if defined(_MSC_VER) +# define SIMULATE_ID "MSVC" +# endif +# define COMPILER_VERSION_MAJOR DEC(__clang_major__) +# define COMPILER_VERSION_MINOR DEC(__clang_minor__) +# define COMPILER_VERSION_PATCH DEC(__clang_patchlevel__) +# if defined(_MSC_VER) + /* _MSC_VER = VVRR */ +# define SIMULATE_VERSION_MAJOR DEC(_MSC_VER / 100) +# define SIMULATE_VERSION_MINOR DEC(_MSC_VER % 100) +# endif + +#elif defined(__GNUC__) +# define COMPILER_ID "GNU" +# define COMPILER_VERSION_MAJOR DEC(__GNUC__) +# if defined(__GNUC_MINOR__) +# define COMPILER_VERSION_MINOR DEC(__GNUC_MINOR__) +# endif +# if defined(__GNUC_PATCHLEVEL__) +# define COMPILER_VERSION_PATCH DEC(__GNUC_PATCHLEVEL__) +# endif + +#elif defined(_MSC_VER) +# define COMPILER_ID "MSVC" + /* _MSC_VER = VVRR */ +# define COMPILER_VERSION_MAJOR DEC(_MSC_VER / 100) +# define COMPILER_VERSION_MINOR DEC(_MSC_VER % 100) +# if defined(_MSC_FULL_VER) +# if _MSC_VER >= 1400 + /* _MSC_FULL_VER = VVRRPPPPP */ +# define COMPILER_VERSION_PATCH DEC(_MSC_FULL_VER % 100000) +# else + /* _MSC_FULL_VER = VVRRPPPP */ +# define COMPILER_VERSION_PATCH DEC(_MSC_FULL_VER % 10000) +# endif +# endif +# if defined(_MSC_BUILD) +# define COMPILER_VERSION_TWEAK DEC(_MSC_BUILD) +# endif + +#elif defined(__VISUALDSPVERSION__) || defined(__ADSPBLACKFIN__) || defined(__ADSPTS__) || defined(__ADSP21000__) +# define COMPILER_ID "ADSP" +#if defined(__VISUALDSPVERSION__) + /* __VISUALDSPVERSION__ = 0xVVRRPP00 */ +# define COMPILER_VERSION_MAJOR HEX(__VISUALDSPVERSION__>>24) +# define COMPILER_VERSION_MINOR HEX(__VISUALDSPVERSION__>>16 & 0xFF) +# define COMPILER_VERSION_PATCH HEX(__VISUALDSPVERSION__>>8 & 0xFF) +#endif + +#elif defined(__IAR_SYSTEMS_ICC__) || defined(__IAR_SYSTEMS_ICC) +# define COMPILER_ID "IAR" +# if defined(__VER__) && defined(__ICCARM__) +# define COMPILER_VERSION_MAJOR DEC((__VER__) / 1000000) +# define COMPILER_VERSION_MINOR DEC(((__VER__) / 1000) % 1000) +# define COMPILER_VERSION_PATCH DEC((__VER__) % 1000) +# define COMPILER_VERSION_INTERNAL DEC(__IAR_SYSTEMS_ICC__) +# elif defined(__VER__) && (defined(__ICCAVR__) || defined(__ICCRX__) || defined(__ICCRH850__) || defined(__ICCRL78__) || defined(__ICC430__) || defined(__ICCRISCV__) || defined(__ICCV850__) || defined(__ICC8051__)) +# define COMPILER_VERSION_MAJOR DEC((__VER__) / 100) +# define COMPILER_VERSION_MINOR DEC((__VER__) - (((__VER__) / 100)*100)) +# define COMPILER_VERSION_PATCH DEC(__SUBVERSION__) +# define COMPILER_VERSION_INTERNAL DEC(__IAR_SYSTEMS_ICC__) +# endif + +#elif defined(__SDCC_VERSION_MAJOR) || defined(SDCC) +# define COMPILER_ID "SDCC" +# if defined(__SDCC_VERSION_MAJOR) +# define COMPILER_VERSION_MAJOR DEC(__SDCC_VERSION_MAJOR) +# define COMPILER_VERSION_MINOR DEC(__SDCC_VERSION_MINOR) +# define COMPILER_VERSION_PATCH DEC(__SDCC_VERSION_PATCH) +# else + /* SDCC = VRP */ +# define COMPILER_VERSION_MAJOR DEC(SDCC/100) +# define COMPILER_VERSION_MINOR DEC(SDCC/10 % 10) +# define COMPILER_VERSION_PATCH DEC(SDCC % 10) +# endif + + +/* These compilers are either not known or too old to define an + identification macro. Try to identify the platform and guess that + it is the native compiler. */ +#elif defined(__hpux) || defined(__hpua) +# define COMPILER_ID "HP" + +#else /* unknown compiler */ +# define COMPILER_ID "" +#endif + +/* Construct the string literal in pieces to prevent the source from + getting matched. Store it in a pointer rather than an array + because some compilers will just produce instructions to fill the + array rather than assigning a pointer to a static array. */ +char const* info_compiler = "INFO" ":" "compiler[" COMPILER_ID "]"; +#ifdef SIMULATE_ID +char const* info_simulate = "INFO" ":" "simulate[" SIMULATE_ID "]"; +#endif + +#ifdef __QNXNTO__ +char const* qnxnto = "INFO" ":" "qnxnto[]"; +#endif + +#if defined(__CRAYXE) || defined(__CRAYXC) +char const *info_cray = "INFO" ":" "compiler_wrapper[CrayPrgEnv]"; +#endif + +#define STRINGIFY_HELPER(X) #X +#define STRINGIFY(X) STRINGIFY_HELPER(X) + +/* Identify known platforms by name. */ +#if defined(__linux) || defined(__linux__) || defined(linux) +# define PLATFORM_ID "Linux" + +#elif defined(__CYGWIN__) +# define PLATFORM_ID "Cygwin" + +#elif defined(__MINGW32__) +# define PLATFORM_ID "MinGW" + +#elif defined(__APPLE__) +# define PLATFORM_ID "Darwin" + +#elif defined(_WIN32) || defined(__WIN32__) || defined(WIN32) +# define PLATFORM_ID "Windows" + +#elif defined(__FreeBSD__) || defined(__FreeBSD) +# define PLATFORM_ID "FreeBSD" + +#elif defined(__NetBSD__) || defined(__NetBSD) +# define PLATFORM_ID "NetBSD" + +#elif defined(__OpenBSD__) || defined(__OPENBSD) +# define PLATFORM_ID "OpenBSD" + +#elif defined(__sun) || defined(sun) +# define PLATFORM_ID "SunOS" + +#elif defined(_AIX) || defined(__AIX) || defined(__AIX__) || defined(__aix) || defined(__aix__) +# define PLATFORM_ID "AIX" + +#elif defined(__hpux) || defined(__hpux__) +# define PLATFORM_ID "HP-UX" + +#elif defined(__HAIKU__) +# define PLATFORM_ID "Haiku" + +#elif defined(__BeOS) || defined(__BEOS__) || defined(_BEOS) +# define PLATFORM_ID "BeOS" + +#elif defined(__QNX__) || defined(__QNXNTO__) +# define PLATFORM_ID "QNX" + +#elif defined(__tru64) || defined(_tru64) || defined(__TRU64__) +# define PLATFORM_ID "Tru64" + +#elif defined(__riscos) || defined(__riscos__) +# define PLATFORM_ID "RISCos" + +#elif defined(__sinix) || defined(__sinix__) || defined(__SINIX__) +# define PLATFORM_ID "SINIX" + +#elif defined(__UNIX_SV__) +# define PLATFORM_ID "UNIX_SV" + +#elif defined(__bsdos__) +# define PLATFORM_ID "BSDOS" + +#elif defined(_MPRAS) || defined(MPRAS) +# define PLATFORM_ID "MP-RAS" + +#elif defined(__osf) || defined(__osf__) +# define PLATFORM_ID "OSF1" + +#elif defined(_SCO_SV) || defined(SCO_SV) || defined(sco_sv) +# define PLATFORM_ID "SCO_SV" + +#elif defined(__ultrix) || defined(__ultrix__) || defined(_ULTRIX) +# define PLATFORM_ID "ULTRIX" + +#elif defined(__XENIX__) || defined(_XENIX) || defined(XENIX) +# define PLATFORM_ID "Xenix" + +#elif defined(__WATCOMC__) +# if defined(__LINUX__) +# define PLATFORM_ID "Linux" + +# elif defined(__DOS__) +# define PLATFORM_ID "DOS" + +# elif defined(__OS2__) +# define PLATFORM_ID "OS2" + +# elif defined(__WINDOWS__) +# define PLATFORM_ID "Windows3x" + +# else /* unknown platform */ +# define PLATFORM_ID +# endif + +#elif defined(__INTEGRITY) +# if defined(INT_178B) +# define PLATFORM_ID "Integrity178" + +# else /* regular Integrity */ +# define PLATFORM_ID "Integrity" +# endif + +#else /* unknown platform */ +# define PLATFORM_ID + +#endif + +/* For windows compilers MSVC and Intel we can determine + the architecture of the compiler being used. This is because + the compilers do not have flags that can change the architecture, + but rather depend on which compiler is being used +*/ +#if defined(_WIN32) && defined(_MSC_VER) +# if defined(_M_IA64) +# define ARCHITECTURE_ID "IA64" + +# elif defined(_M_X64) || defined(_M_AMD64) +# define ARCHITECTURE_ID "x64" + +# elif defined(_M_IX86) +# define ARCHITECTURE_ID "X86" + +# elif defined(_M_ARM64) +# define ARCHITECTURE_ID "ARM64" + +# elif defined(_M_ARM) +# if _M_ARM == 4 +# define ARCHITECTURE_ID "ARMV4I" +# elif _M_ARM == 5 +# define ARCHITECTURE_ID "ARMV5I" +# else +# define ARCHITECTURE_ID "ARMV" STRINGIFY(_M_ARM) +# endif + +# elif defined(_M_MIPS) +# define ARCHITECTURE_ID "MIPS" + +# elif defined(_M_SH) +# define ARCHITECTURE_ID "SHx" + +# else /* unknown architecture */ +# define ARCHITECTURE_ID "" +# endif + +#elif defined(__WATCOMC__) +# if defined(_M_I86) +# define ARCHITECTURE_ID "I86" + +# elif defined(_M_IX86) +# define ARCHITECTURE_ID "X86" + +# else /* unknown architecture */ +# define ARCHITECTURE_ID "" +# endif + +#elif defined(__IAR_SYSTEMS_ICC__) || defined(__IAR_SYSTEMS_ICC) +# if defined(__ICCARM__) +# define ARCHITECTURE_ID "ARM" + +# elif defined(__ICCRX__) +# define ARCHITECTURE_ID "RX" + +# elif defined(__ICCRH850__) +# define ARCHITECTURE_ID "RH850" + +# elif defined(__ICCRL78__) +# define ARCHITECTURE_ID "RL78" + +# elif defined(__ICCRISCV__) +# define ARCHITECTURE_ID "RISCV" + +# elif defined(__ICCAVR__) +# define ARCHITECTURE_ID "AVR" + +# elif defined(__ICC430__) +# define ARCHITECTURE_ID "MSP430" + +# elif defined(__ICCV850__) +# define ARCHITECTURE_ID "V850" + +# elif defined(__ICC8051__) +# define ARCHITECTURE_ID "8051" + +# else /* unknown architecture */ +# define ARCHITECTURE_ID "" +# endif + +#elif defined(__ghs__) +# if defined(__PPC64__) +# define ARCHITECTURE_ID "PPC64" + +# elif defined(__ppc__) +# define ARCHITECTURE_ID "PPC" + +# elif defined(__ARM__) +# define ARCHITECTURE_ID "ARM" + +# elif defined(__x86_64__) +# define ARCHITECTURE_ID "x64" + +# elif defined(__i386__) +# define ARCHITECTURE_ID "X86" + +# else /* unknown architecture */ +# define ARCHITECTURE_ID "" +# endif +#else +# define ARCHITECTURE_ID +#endif + +/* Convert integer to decimal digit literals. */ +#define DEC(n) \ + ('0' + (((n) / 10000000)%10)), \ + ('0' + (((n) / 1000000)%10)), \ + ('0' + (((n) / 100000)%10)), \ + ('0' + (((n) / 10000)%10)), \ + ('0' + (((n) / 1000)%10)), \ + ('0' + (((n) / 100)%10)), \ + ('0' + (((n) / 10)%10)), \ + ('0' + ((n) % 10)) + +/* Convert integer to hex digit literals. */ +#define HEX(n) \ + ('0' + ((n)>>28 & 0xF)), \ + ('0' + ((n)>>24 & 0xF)), \ + ('0' + ((n)>>20 & 0xF)), \ + ('0' + ((n)>>16 & 0xF)), \ + ('0' + ((n)>>12 & 0xF)), \ + ('0' + ((n)>>8 & 0xF)), \ + ('0' + ((n)>>4 & 0xF)), \ + ('0' + ((n) & 0xF)) + +/* Construct a string literal encoding the version number components. */ +#ifdef COMPILER_VERSION_MAJOR +char const info_version[] = { + 'I', 'N', 'F', 'O', ':', + 'c','o','m','p','i','l','e','r','_','v','e','r','s','i','o','n','[', + COMPILER_VERSION_MAJOR, +# ifdef COMPILER_VERSION_MINOR + '.', COMPILER_VERSION_MINOR, +# ifdef COMPILER_VERSION_PATCH + '.', COMPILER_VERSION_PATCH, +# ifdef COMPILER_VERSION_TWEAK + '.', COMPILER_VERSION_TWEAK, +# endif +# endif +# endif + ']','\0'}; +#endif + +/* Construct a string literal encoding the internal version number. */ +#ifdef COMPILER_VERSION_INTERNAL +char const info_version_internal[] = { + 'I', 'N', 'F', 'O', ':', + 'c','o','m','p','i','l','e','r','_','v','e','r','s','i','o','n','_', + 'i','n','t','e','r','n','a','l','[', + COMPILER_VERSION_INTERNAL,']','\0'}; +#endif + +/* Construct a string literal encoding the version number components. */ +#ifdef SIMULATE_VERSION_MAJOR +char const info_simulate_version[] = { + 'I', 'N', 'F', 'O', ':', + 's','i','m','u','l','a','t','e','_','v','e','r','s','i','o','n','[', + SIMULATE_VERSION_MAJOR, +# ifdef SIMULATE_VERSION_MINOR + '.', SIMULATE_VERSION_MINOR, +# ifdef SIMULATE_VERSION_PATCH + '.', SIMULATE_VERSION_PATCH, +# ifdef SIMULATE_VERSION_TWEAK + '.', SIMULATE_VERSION_TWEAK, +# endif +# endif +# endif + ']','\0'}; +#endif + +/* Construct the string literal in pieces to prevent the source from + getting matched. Store it in a pointer rather than an array + because some compilers will just produce instructions to fill the + array rather than assigning a pointer to a static array. */ +char const* info_platform = "INFO" ":" "platform[" PLATFORM_ID "]"; +char const* info_arch = "INFO" ":" "arch[" ARCHITECTURE_ID "]"; + + + + +#if !defined(__STDC__) +# if (defined(_MSC_VER) && !defined(__clang__)) \ + || (defined(__ibmxl__) || defined(__IBMC__)) +# define C_DIALECT "90" +# else +# define C_DIALECT +# endif +#elif __STDC_VERSION__ >= 201000L +# define C_DIALECT "11" +#elif __STDC_VERSION__ >= 199901L +# define C_DIALECT "99" +#else +# define C_DIALECT "90" +#endif +const char* info_language_dialect_default = + "INFO" ":" "dialect_default[" C_DIALECT "]"; + +/*--------------------------------------------------------------------------*/ + +#ifdef ID_VOID_MAIN +void main() {} +#else +# if defined(__CLASSIC_C__) +int main(argc, argv) int argc; char *argv[]; +# else +int main(int argc, char* argv[]) +# endif +{ + int require = 0; + require += info_compiler[argc]; + require += info_platform[argc]; + require += info_arch[argc]; +#ifdef COMPILER_VERSION_MAJOR + require += info_version[argc]; +#endif +#ifdef COMPILER_VERSION_INTERNAL + require += info_version_internal[argc]; +#endif +#ifdef SIMULATE_ID + require += info_simulate[argc]; +#endif +#ifdef SIMULATE_VERSION_MAJOR + require += info_simulate_version[argc]; +#endif +#if defined(__CRAYXE) || defined(__CRAYXC) + require += info_cray[argc]; +#endif + require += info_language_dialect_default[argc]; + (void)argv; + return require; +} +#endif diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/CMakeFiles/3.16.3/CompilerIdC/a.out b/applications/external/esp-flasher/lib/esp-serial-flasher/CMakeFiles/3.16.3/CompilerIdC/a.out new file mode 100755 index 0000000000000000000000000000000000000000..e82fab493fdf7ab5d55eaa479346af6ef1f959e7 GIT binary patch literal 16712 zcmeHOZ)_Y#6`%9jvC}5!OX?6eY3n6TC5P6Rb4ZMn*3F&mv-gmll*Fb%uDh)7*7k|} zWA65-T}TNe6pr8$L?p@wq^d~#4Ij#fR7CFfQW#CK_PWZgIq{*yf^#a zd3Sv-66FIzJJRi&`MtmUW_E99cjlw9fxa4_PjK;z`vtZlK?{ixqhSwKfJ8*6SPRG7 z#cg60v}+}1@go)iu2nA9IB5gnexRt=jFl?%HVXz1R^bSdqTWiS6$668RM2^#UL7*1 z?WQq`lPU8n{uES(qR3-~UPJ*XOznCY%0ioDi-j&HjgUW;DC=QUo!Cxok(@ho2N+o=^-XYR+(_YeJiaxL$eA!I?C;lEFy;F)bBi!^p3l>b--+Mrh zx|Sr~oNzZEAb%_MLy+>%l+r7qhs7x3CTLi85GFpN9mtv5r1a z#ybCxL5;>T(DTMmGZI+g&wWz_(P97m&XMrmI6}T4Gw) z##326oqE)OCbG1Gspm~Cqo=Z>e<0r7qwNUo2<;MDd}vTh8hK+PRWObG&|ptGmo;cCtnxJI*YML*Dd3h$UsBH z;xo?epbJ+NORm|4=LuI__$LW(b>W{U{Ni3A3yuXmA1;?{IG;1}=508SO~e;$IA3Fk zFWPW&0|4@%!ZcvZc{c^hy>L=#Rn(|BP z(H}INmFHOA^)$e*mGAm8kj55)^3eLOZ1oD+ZvSh%bpBzr^gH$F9~K@M8knuS0FS2X z>{?Bdt}|e%yde)Nub)t7yPkuJI$QT`B)b>Q4d8ko*1`4a@}Bk1@nQIIc5`CT7J zvh9XiT2Rki*{hye^sBxX)blsYCNOXp8E7b98gFcYxL6=49r}NM1ej=O81zbqFRP{Er60zwVVLUi4KKn> z{dnxM`lRZ6sqwUE{N9`|b`9|Ftbl)`8g3bw-SDjdWchS$uwvy{b?IxPXRY(Zu%ZtR z%y!)gGnwdnB?rc!AmKLTGV zN1ZYbzV;y_Ur6P$BVnuT4pC(%SHh!0YD}edb3B*Nj11t{RRZ;VVseD)$&{Wp5+-~K z9M_9!bELg}RQNXg@5S$#`0dMlrCd$}JqGk7(33#%+vVYfa`_a{rdP}5A0r=V6TF7N zZ?RZ>j~)=d>7Z}(n)-$lgyR~6PSD3U1ERNHe&O2+)t%S|Tmpe$U!eKjjW<8ia766g zwC5e~+`0wXX!jxb&0iro1fif!6MjDe{m&r~3j`mp=~=s~=2PGq+yHbEei4Y*Kz?5! z_}Q9xp!o@ZETGKR#saOMsp}1dA77;gI*-=(2O`-(XEYFw23orVN_U{SI}q#+G|2Cn zzXtV5u;YEN!$f+;%Yc^wF9Ti%ybO35@G{_Kz{`M_0nPxgYvgr|Fa?r@=^7TKb&hMP z-ARi@Z>2V`m)uNkUI&TmO0n>|Nt_2^VgA({YXd7l{7`wK)zl``;tHKKw0OC1HW=1M`vtF0cjC3;BUSCx zRmPc9epO|BIq`bIt`g-_j`8nM_E}ue%_FFmiO;7Z~`@A`S@=Hd_CkRO#d(Zf6xNB z!p>|oA@SwoLWSmjp0*Vin+F1itCAsf}nj(Z$t0at8tTs;iXg?m51R{_U7%+bCBgA6?nGzjCZGs2cFIzjRg8n@et{VdrJl05(4 zJr6kAabHL0$<8+NE8kUuEnLC14)WRRvhzCPaK3Ik(78eZJ$Kmz_!c(^?GC^(KM}=Z z?jV^SiEk82!~!^ek;t0`vj{7Z5@Kn4h-PNA1a1&1z~+!-PMb*Q#`Lt7G;{farWdCL zzPU~trjZPFhCAA;3*pw3l&0tN`iy2|&HRiQ&+8dOOBOSk8BnpcG>|kMqCJE92aKK` zdO;pfLU@|icOW_#(_;I2HQ1%%)JY1h_kH`KgYh0GM{b7!2;Q)3F_kQ+y$6KWKd`?$ zI-u?E>pK`5(uShl12MEJH_;>tMH&5@Z<{fax~a3u(!QHW#If;4Ln&)>4ahoo+Gxo_ zPMg%TN!*1K-w#sBR8}h%jHF0q$8#FL&SxhW7pBVM^5+BKjui^zLhk&L%4t12QPd|O z;m&vdAP(DqIE}b3sOn}P=k_A!NlE+kQNoG^zcC0AD$Ha|eGI6Xx9B9-Qd!t`G$lgW zoN0t6vc=F;J~w6L%^4dsR!qUhuvC(uXm`BL)F*@#pVSMJB9xrTf(wh9d5gnwNHJQG`J4N^-L-O z;ov|BK_`e1bV>$BJ6(2fnczGCZVh-FkcFS?nDRV;*ix43NbiInJ|nX}KmRdRfM+k2 z`vQR-K<)a6=y{OoR=YCP`F_lB+@;UYlS~yBBtF8`n+k3|4pXUKgc^=RH-Q)LJ z(&zjOg(Qidq9zl9xhcL6H4KsUPmuuA`zn%F#qIwm0NeF>{=sy?rO*A(@Gqc(&+99qK5V+)@fg4akTkM~o@4}?WepsGPhnvC= z0Ag-Gtbcb!(yFk&o$}bRi@u)>s&rxFb$k!}I3B+Ltpn+a literal 0 HcmV?d00001 diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/CMakeFiles/3.16.3/CompilerIdCXX/CMakeCXXCompilerId.cpp b/applications/external/esp-flasher/lib/esp-serial-flasher/CMakeFiles/3.16.3/CompilerIdCXX/CMakeCXXCompilerId.cpp new file mode 100644 index 000000000..69cfdba6b --- /dev/null +++ b/applications/external/esp-flasher/lib/esp-serial-flasher/CMakeFiles/3.16.3/CompilerIdCXX/CMakeCXXCompilerId.cpp @@ -0,0 +1,660 @@ +/* This source file must have a .cpp extension so that all C++ compilers + recognize the extension without flags. Borland does not know .cxx for + example. */ +#ifndef __cplusplus +# error "A C compiler has been selected for C++." +#endif + + +/* Version number components: V=Version, R=Revision, P=Patch + Version date components: YYYY=Year, MM=Month, DD=Day */ + +#if defined(__COMO__) +# define COMPILER_ID "Comeau" + /* __COMO_VERSION__ = VRR */ +# define COMPILER_VERSION_MAJOR DEC(__COMO_VERSION__ / 100) +# define COMPILER_VERSION_MINOR DEC(__COMO_VERSION__ % 100) + +#elif defined(__INTEL_COMPILER) || defined(__ICC) +# define COMPILER_ID "Intel" +# if defined(_MSC_VER) +# define SIMULATE_ID "MSVC" +# endif +# if defined(__GNUC__) +# define SIMULATE_ID "GNU" +# endif + /* __INTEL_COMPILER = VRP */ +# define COMPILER_VERSION_MAJOR DEC(__INTEL_COMPILER/100) +# define COMPILER_VERSION_MINOR DEC(__INTEL_COMPILER/10 % 10) +# if defined(__INTEL_COMPILER_UPDATE) +# define COMPILER_VERSION_PATCH DEC(__INTEL_COMPILER_UPDATE) +# else +# define COMPILER_VERSION_PATCH DEC(__INTEL_COMPILER % 10) +# endif +# if defined(__INTEL_COMPILER_BUILD_DATE) + /* __INTEL_COMPILER_BUILD_DATE = YYYYMMDD */ +# define COMPILER_VERSION_TWEAK DEC(__INTEL_COMPILER_BUILD_DATE) +# endif +# if defined(_MSC_VER) + /* _MSC_VER = VVRR */ +# define SIMULATE_VERSION_MAJOR DEC(_MSC_VER / 100) +# define SIMULATE_VERSION_MINOR DEC(_MSC_VER % 100) +# endif +# if defined(__GNUC__) +# define SIMULATE_VERSION_MAJOR DEC(__GNUC__) +# elif defined(__GNUG__) +# define SIMULATE_VERSION_MAJOR DEC(__GNUG__) +# endif +# if defined(__GNUC_MINOR__) +# define SIMULATE_VERSION_MINOR DEC(__GNUC_MINOR__) +# endif +# if defined(__GNUC_PATCHLEVEL__) +# define SIMULATE_VERSION_PATCH DEC(__GNUC_PATCHLEVEL__) +# endif + +#elif defined(__PATHCC__) +# define COMPILER_ID "PathScale" +# define COMPILER_VERSION_MAJOR DEC(__PATHCC__) +# define COMPILER_VERSION_MINOR DEC(__PATHCC_MINOR__) +# if defined(__PATHCC_PATCHLEVEL__) +# define COMPILER_VERSION_PATCH DEC(__PATHCC_PATCHLEVEL__) +# endif + +#elif defined(__BORLANDC__) && defined(__CODEGEARC_VERSION__) +# define COMPILER_ID "Embarcadero" +# define COMPILER_VERSION_MAJOR HEX(__CODEGEARC_VERSION__>>24 & 0x00FF) +# define COMPILER_VERSION_MINOR HEX(__CODEGEARC_VERSION__>>16 & 0x00FF) +# define COMPILER_VERSION_PATCH DEC(__CODEGEARC_VERSION__ & 0xFFFF) + +#elif defined(__BORLANDC__) +# define COMPILER_ID "Borland" + /* __BORLANDC__ = 0xVRR */ +# define COMPILER_VERSION_MAJOR HEX(__BORLANDC__>>8) +# define COMPILER_VERSION_MINOR HEX(__BORLANDC__ & 0xFF) + +#elif defined(__WATCOMC__) && __WATCOMC__ < 1200 +# define COMPILER_ID "Watcom" + /* __WATCOMC__ = VVRR */ +# define COMPILER_VERSION_MAJOR DEC(__WATCOMC__ / 100) +# define COMPILER_VERSION_MINOR DEC((__WATCOMC__ / 10) % 10) +# if (__WATCOMC__ % 10) > 0 +# define COMPILER_VERSION_PATCH DEC(__WATCOMC__ % 10) +# endif + +#elif defined(__WATCOMC__) +# define COMPILER_ID "OpenWatcom" + /* __WATCOMC__ = VVRP + 1100 */ +# define COMPILER_VERSION_MAJOR DEC((__WATCOMC__ - 1100) / 100) +# define COMPILER_VERSION_MINOR DEC((__WATCOMC__ / 10) % 10) +# if (__WATCOMC__ % 10) > 0 +# define COMPILER_VERSION_PATCH DEC(__WATCOMC__ % 10) +# endif + +#elif defined(__SUNPRO_CC) +# define COMPILER_ID "SunPro" +# if __SUNPRO_CC >= 0x5100 + /* __SUNPRO_CC = 0xVRRP */ +# define COMPILER_VERSION_MAJOR HEX(__SUNPRO_CC>>12) +# define COMPILER_VERSION_MINOR HEX(__SUNPRO_CC>>4 & 0xFF) +# define COMPILER_VERSION_PATCH HEX(__SUNPRO_CC & 0xF) +# else + /* __SUNPRO_CC = 0xVRP */ +# define COMPILER_VERSION_MAJOR HEX(__SUNPRO_CC>>8) +# define COMPILER_VERSION_MINOR HEX(__SUNPRO_CC>>4 & 0xF) +# define COMPILER_VERSION_PATCH HEX(__SUNPRO_CC & 0xF) +# endif + +#elif defined(__HP_aCC) +# define COMPILER_ID "HP" + /* __HP_aCC = VVRRPP */ +# define COMPILER_VERSION_MAJOR DEC(__HP_aCC/10000) +# define COMPILER_VERSION_MINOR DEC(__HP_aCC/100 % 100) +# define COMPILER_VERSION_PATCH DEC(__HP_aCC % 100) + +#elif defined(__DECCXX) +# define COMPILER_ID "Compaq" + /* __DECCXX_VER = VVRRTPPPP */ +# define COMPILER_VERSION_MAJOR DEC(__DECCXX_VER/10000000) +# define COMPILER_VERSION_MINOR DEC(__DECCXX_VER/100000 % 100) +# define COMPILER_VERSION_PATCH DEC(__DECCXX_VER % 10000) + +#elif defined(__IBMCPP__) && defined(__COMPILER_VER__) +# define COMPILER_ID "zOS" + /* __IBMCPP__ = VRP */ +# define COMPILER_VERSION_MAJOR DEC(__IBMCPP__/100) +# define COMPILER_VERSION_MINOR DEC(__IBMCPP__/10 % 10) +# define COMPILER_VERSION_PATCH DEC(__IBMCPP__ % 10) + +#elif defined(__ibmxl__) && defined(__clang__) +# define COMPILER_ID "XLClang" +# define COMPILER_VERSION_MAJOR DEC(__ibmxl_version__) +# define COMPILER_VERSION_MINOR DEC(__ibmxl_release__) +# define COMPILER_VERSION_PATCH DEC(__ibmxl_modification__) +# define COMPILER_VERSION_TWEAK DEC(__ibmxl_ptf_fix_level__) + + +#elif defined(__IBMCPP__) && !defined(__COMPILER_VER__) && __IBMCPP__ >= 800 +# define COMPILER_ID "XL" + /* __IBMCPP__ = VRP */ +# define COMPILER_VERSION_MAJOR DEC(__IBMCPP__/100) +# define COMPILER_VERSION_MINOR DEC(__IBMCPP__/10 % 10) +# define COMPILER_VERSION_PATCH DEC(__IBMCPP__ % 10) + +#elif defined(__IBMCPP__) && !defined(__COMPILER_VER__) && __IBMCPP__ < 800 +# define COMPILER_ID "VisualAge" + /* __IBMCPP__ = VRP */ +# define COMPILER_VERSION_MAJOR DEC(__IBMCPP__/100) +# define COMPILER_VERSION_MINOR DEC(__IBMCPP__/10 % 10) +# define COMPILER_VERSION_PATCH DEC(__IBMCPP__ % 10) + +#elif defined(__PGI) +# define COMPILER_ID "PGI" +# define COMPILER_VERSION_MAJOR DEC(__PGIC__) +# define COMPILER_VERSION_MINOR DEC(__PGIC_MINOR__) +# if defined(__PGIC_PATCHLEVEL__) +# define COMPILER_VERSION_PATCH DEC(__PGIC_PATCHLEVEL__) +# endif + +#elif defined(_CRAYC) +# define COMPILER_ID "Cray" +# define COMPILER_VERSION_MAJOR DEC(_RELEASE_MAJOR) +# define COMPILER_VERSION_MINOR DEC(_RELEASE_MINOR) + +#elif defined(__TI_COMPILER_VERSION__) +# define COMPILER_ID "TI" + /* __TI_COMPILER_VERSION__ = VVVRRRPPP */ +# define COMPILER_VERSION_MAJOR DEC(__TI_COMPILER_VERSION__/1000000) +# define COMPILER_VERSION_MINOR DEC(__TI_COMPILER_VERSION__/1000 % 1000) +# define COMPILER_VERSION_PATCH DEC(__TI_COMPILER_VERSION__ % 1000) + +#elif defined(__FUJITSU) || defined(__FCC_VERSION) || defined(__fcc_version) +# define COMPILER_ID "Fujitsu" + +#elif defined(__ghs__) +# define COMPILER_ID "GHS" +/* __GHS_VERSION_NUMBER = VVVVRP */ +# ifdef __GHS_VERSION_NUMBER +# define COMPILER_VERSION_MAJOR DEC(__GHS_VERSION_NUMBER / 100) +# define COMPILER_VERSION_MINOR DEC(__GHS_VERSION_NUMBER / 10 % 10) +# define COMPILER_VERSION_PATCH DEC(__GHS_VERSION_NUMBER % 10) +# endif + +#elif defined(__SCO_VERSION__) +# define COMPILER_ID "SCO" + +#elif defined(__ARMCC_VERSION) && !defined(__clang__) +# define COMPILER_ID "ARMCC" +#if __ARMCC_VERSION >= 1000000 + /* __ARMCC_VERSION = VRRPPPP */ + # define COMPILER_VERSION_MAJOR DEC(__ARMCC_VERSION/1000000) + # define COMPILER_VERSION_MINOR DEC(__ARMCC_VERSION/10000 % 100) + # define COMPILER_VERSION_PATCH DEC(__ARMCC_VERSION % 10000) +#else + /* __ARMCC_VERSION = VRPPPP */ + # define COMPILER_VERSION_MAJOR DEC(__ARMCC_VERSION/100000) + # define COMPILER_VERSION_MINOR DEC(__ARMCC_VERSION/10000 % 10) + # define COMPILER_VERSION_PATCH DEC(__ARMCC_VERSION % 10000) +#endif + + +#elif defined(__clang__) && defined(__apple_build_version__) +# define COMPILER_ID "AppleClang" +# if defined(_MSC_VER) +# define SIMULATE_ID "MSVC" +# endif +# define COMPILER_VERSION_MAJOR DEC(__clang_major__) +# define COMPILER_VERSION_MINOR DEC(__clang_minor__) +# define COMPILER_VERSION_PATCH DEC(__clang_patchlevel__) +# if defined(_MSC_VER) + /* _MSC_VER = VVRR */ +# define SIMULATE_VERSION_MAJOR DEC(_MSC_VER / 100) +# define SIMULATE_VERSION_MINOR DEC(_MSC_VER % 100) +# endif +# define COMPILER_VERSION_TWEAK DEC(__apple_build_version__) + +#elif defined(__clang__) && defined(__ARMCOMPILER_VERSION) +# define COMPILER_ID "ARMClang" + # define COMPILER_VERSION_MAJOR DEC(__ARMCOMPILER_VERSION/1000000) + # define COMPILER_VERSION_MINOR DEC(__ARMCOMPILER_VERSION/10000 % 100) + # define COMPILER_VERSION_PATCH DEC(__ARMCOMPILER_VERSION % 10000) +# define COMPILER_VERSION_INTERNAL DEC(__ARMCOMPILER_VERSION) + +#elif defined(__clang__) +# define COMPILER_ID "Clang" +# if defined(_MSC_VER) +# define SIMULATE_ID "MSVC" +# endif +# define COMPILER_VERSION_MAJOR DEC(__clang_major__) +# define COMPILER_VERSION_MINOR DEC(__clang_minor__) +# define COMPILER_VERSION_PATCH DEC(__clang_patchlevel__) +# if defined(_MSC_VER) + /* _MSC_VER = VVRR */ +# define SIMULATE_VERSION_MAJOR DEC(_MSC_VER / 100) +# define SIMULATE_VERSION_MINOR DEC(_MSC_VER % 100) +# endif + +#elif defined(__GNUC__) || defined(__GNUG__) +# define COMPILER_ID "GNU" +# if defined(__GNUC__) +# define COMPILER_VERSION_MAJOR DEC(__GNUC__) +# else +# define COMPILER_VERSION_MAJOR DEC(__GNUG__) +# endif +# if defined(__GNUC_MINOR__) +# define COMPILER_VERSION_MINOR DEC(__GNUC_MINOR__) +# endif +# if defined(__GNUC_PATCHLEVEL__) +# define COMPILER_VERSION_PATCH DEC(__GNUC_PATCHLEVEL__) +# endif + +#elif defined(_MSC_VER) +# define COMPILER_ID "MSVC" + /* _MSC_VER = VVRR */ +# define COMPILER_VERSION_MAJOR DEC(_MSC_VER / 100) +# define COMPILER_VERSION_MINOR DEC(_MSC_VER % 100) +# if defined(_MSC_FULL_VER) +# if _MSC_VER >= 1400 + /* _MSC_FULL_VER = VVRRPPPPP */ +# define COMPILER_VERSION_PATCH DEC(_MSC_FULL_VER % 100000) +# else + /* _MSC_FULL_VER = VVRRPPPP */ +# define COMPILER_VERSION_PATCH DEC(_MSC_FULL_VER % 10000) +# endif +# endif +# if defined(_MSC_BUILD) +# define COMPILER_VERSION_TWEAK DEC(_MSC_BUILD) +# endif + +#elif defined(__VISUALDSPVERSION__) || defined(__ADSPBLACKFIN__) || defined(__ADSPTS__) || defined(__ADSP21000__) +# define COMPILER_ID "ADSP" +#if defined(__VISUALDSPVERSION__) + /* __VISUALDSPVERSION__ = 0xVVRRPP00 */ +# define COMPILER_VERSION_MAJOR HEX(__VISUALDSPVERSION__>>24) +# define COMPILER_VERSION_MINOR HEX(__VISUALDSPVERSION__>>16 & 0xFF) +# define COMPILER_VERSION_PATCH HEX(__VISUALDSPVERSION__>>8 & 0xFF) +#endif + +#elif defined(__IAR_SYSTEMS_ICC__) || defined(__IAR_SYSTEMS_ICC) +# define COMPILER_ID "IAR" +# if defined(__VER__) && defined(__ICCARM__) +# define COMPILER_VERSION_MAJOR DEC((__VER__) / 1000000) +# define COMPILER_VERSION_MINOR DEC(((__VER__) / 1000) % 1000) +# define COMPILER_VERSION_PATCH DEC((__VER__) % 1000) +# define COMPILER_VERSION_INTERNAL DEC(__IAR_SYSTEMS_ICC__) +# elif defined(__VER__) && (defined(__ICCAVR__) || defined(__ICCRX__) || defined(__ICCRH850__) || defined(__ICCRL78__) || defined(__ICC430__) || defined(__ICCRISCV__) || defined(__ICCV850__) || defined(__ICC8051__)) +# define COMPILER_VERSION_MAJOR DEC((__VER__) / 100) +# define COMPILER_VERSION_MINOR DEC((__VER__) - (((__VER__) / 100)*100)) +# define COMPILER_VERSION_PATCH DEC(__SUBVERSION__) +# define COMPILER_VERSION_INTERNAL DEC(__IAR_SYSTEMS_ICC__) +# endif + + +/* These compilers are either not known or too old to define an + identification macro. Try to identify the platform and guess that + it is the native compiler. */ +#elif defined(__hpux) || defined(__hpua) +# define COMPILER_ID "HP" + +#else /* unknown compiler */ +# define COMPILER_ID "" +#endif + +/* Construct the string literal in pieces to prevent the source from + getting matched. Store it in a pointer rather than an array + because some compilers will just produce instructions to fill the + array rather than assigning a pointer to a static array. */ +char const* info_compiler = "INFO" ":" "compiler[" COMPILER_ID "]"; +#ifdef SIMULATE_ID +char const* info_simulate = "INFO" ":" "simulate[" SIMULATE_ID "]"; +#endif + +#ifdef __QNXNTO__ +char const* qnxnto = "INFO" ":" "qnxnto[]"; +#endif + +#if defined(__CRAYXE) || defined(__CRAYXC) +char const *info_cray = "INFO" ":" "compiler_wrapper[CrayPrgEnv]"; +#endif + +#define STRINGIFY_HELPER(X) #X +#define STRINGIFY(X) STRINGIFY_HELPER(X) + +/* Identify known platforms by name. */ +#if defined(__linux) || defined(__linux__) || defined(linux) +# define PLATFORM_ID "Linux" + +#elif defined(__CYGWIN__) +# define PLATFORM_ID "Cygwin" + +#elif defined(__MINGW32__) +# define PLATFORM_ID "MinGW" + +#elif defined(__APPLE__) +# define PLATFORM_ID "Darwin" + +#elif defined(_WIN32) || defined(__WIN32__) || defined(WIN32) +# define PLATFORM_ID "Windows" + +#elif defined(__FreeBSD__) || defined(__FreeBSD) +# define PLATFORM_ID "FreeBSD" + +#elif defined(__NetBSD__) || defined(__NetBSD) +# define PLATFORM_ID "NetBSD" + +#elif defined(__OpenBSD__) || defined(__OPENBSD) +# define PLATFORM_ID "OpenBSD" + +#elif defined(__sun) || defined(sun) +# define PLATFORM_ID "SunOS" + +#elif defined(_AIX) || defined(__AIX) || defined(__AIX__) || defined(__aix) || defined(__aix__) +# define PLATFORM_ID "AIX" + +#elif defined(__hpux) || defined(__hpux__) +# define PLATFORM_ID "HP-UX" + +#elif defined(__HAIKU__) +# define PLATFORM_ID "Haiku" + +#elif defined(__BeOS) || defined(__BEOS__) || defined(_BEOS) +# define PLATFORM_ID "BeOS" + +#elif defined(__QNX__) || defined(__QNXNTO__) +# define PLATFORM_ID "QNX" + +#elif defined(__tru64) || defined(_tru64) || defined(__TRU64__) +# define PLATFORM_ID "Tru64" + +#elif defined(__riscos) || defined(__riscos__) +# define PLATFORM_ID "RISCos" + +#elif defined(__sinix) || defined(__sinix__) || defined(__SINIX__) +# define PLATFORM_ID "SINIX" + +#elif defined(__UNIX_SV__) +# define PLATFORM_ID "UNIX_SV" + +#elif defined(__bsdos__) +# define PLATFORM_ID "BSDOS" + +#elif defined(_MPRAS) || defined(MPRAS) +# define PLATFORM_ID "MP-RAS" + +#elif defined(__osf) || defined(__osf__) +# define PLATFORM_ID "OSF1" + +#elif defined(_SCO_SV) || defined(SCO_SV) || defined(sco_sv) +# define PLATFORM_ID "SCO_SV" + +#elif defined(__ultrix) || defined(__ultrix__) || defined(_ULTRIX) +# define PLATFORM_ID "ULTRIX" + +#elif defined(__XENIX__) || defined(_XENIX) || defined(XENIX) +# define PLATFORM_ID "Xenix" + +#elif defined(__WATCOMC__) +# if defined(__LINUX__) +# define PLATFORM_ID "Linux" + +# elif defined(__DOS__) +# define PLATFORM_ID "DOS" + +# elif defined(__OS2__) +# define PLATFORM_ID "OS2" + +# elif defined(__WINDOWS__) +# define PLATFORM_ID "Windows3x" + +# else /* unknown platform */ +# define PLATFORM_ID +# endif + +#elif defined(__INTEGRITY) +# if defined(INT_178B) +# define PLATFORM_ID "Integrity178" + +# else /* regular Integrity */ +# define PLATFORM_ID "Integrity" +# endif + +#else /* unknown platform */ +# define PLATFORM_ID + +#endif + +/* For windows compilers MSVC and Intel we can determine + the architecture of the compiler being used. This is because + the compilers do not have flags that can change the architecture, + but rather depend on which compiler is being used +*/ +#if defined(_WIN32) && defined(_MSC_VER) +# if defined(_M_IA64) +# define ARCHITECTURE_ID "IA64" + +# elif defined(_M_X64) || defined(_M_AMD64) +# define ARCHITECTURE_ID "x64" + +# elif defined(_M_IX86) +# define ARCHITECTURE_ID "X86" + +# elif defined(_M_ARM64) +# define ARCHITECTURE_ID "ARM64" + +# elif defined(_M_ARM) +# if _M_ARM == 4 +# define ARCHITECTURE_ID "ARMV4I" +# elif _M_ARM == 5 +# define ARCHITECTURE_ID "ARMV5I" +# else +# define ARCHITECTURE_ID "ARMV" STRINGIFY(_M_ARM) +# endif + +# elif defined(_M_MIPS) +# define ARCHITECTURE_ID "MIPS" + +# elif defined(_M_SH) +# define ARCHITECTURE_ID "SHx" + +# else /* unknown architecture */ +# define ARCHITECTURE_ID "" +# endif + +#elif defined(__WATCOMC__) +# if defined(_M_I86) +# define ARCHITECTURE_ID "I86" + +# elif defined(_M_IX86) +# define ARCHITECTURE_ID "X86" + +# else /* unknown architecture */ +# define ARCHITECTURE_ID "" +# endif + +#elif defined(__IAR_SYSTEMS_ICC__) || defined(__IAR_SYSTEMS_ICC) +# if defined(__ICCARM__) +# define ARCHITECTURE_ID "ARM" + +# elif defined(__ICCRX__) +# define ARCHITECTURE_ID "RX" + +# elif defined(__ICCRH850__) +# define ARCHITECTURE_ID "RH850" + +# elif defined(__ICCRL78__) +# define ARCHITECTURE_ID "RL78" + +# elif defined(__ICCRISCV__) +# define ARCHITECTURE_ID "RISCV" + +# elif defined(__ICCAVR__) +# define ARCHITECTURE_ID "AVR" + +# elif defined(__ICC430__) +# define ARCHITECTURE_ID "MSP430" + +# elif defined(__ICCV850__) +# define ARCHITECTURE_ID "V850" + +# elif defined(__ICC8051__) +# define ARCHITECTURE_ID "8051" + +# else /* unknown architecture */ +# define ARCHITECTURE_ID "" +# endif + +#elif defined(__ghs__) +# if defined(__PPC64__) +# define ARCHITECTURE_ID "PPC64" + +# elif defined(__ppc__) +# define ARCHITECTURE_ID "PPC" + +# elif defined(__ARM__) +# define ARCHITECTURE_ID "ARM" + +# elif defined(__x86_64__) +# define ARCHITECTURE_ID "x64" + +# elif defined(__i386__) +# define ARCHITECTURE_ID "X86" + +# else /* unknown architecture */ +# define ARCHITECTURE_ID "" +# endif +#else +# define ARCHITECTURE_ID +#endif + +/* Convert integer to decimal digit literals. */ +#define DEC(n) \ + ('0' + (((n) / 10000000)%10)), \ + ('0' + (((n) / 1000000)%10)), \ + ('0' + (((n) / 100000)%10)), \ + ('0' + (((n) / 10000)%10)), \ + ('0' + (((n) / 1000)%10)), \ + ('0' + (((n) / 100)%10)), \ + ('0' + (((n) / 10)%10)), \ + ('0' + ((n) % 10)) + +/* Convert integer to hex digit literals. */ +#define HEX(n) \ + ('0' + ((n)>>28 & 0xF)), \ + ('0' + ((n)>>24 & 0xF)), \ + ('0' + ((n)>>20 & 0xF)), \ + ('0' + ((n)>>16 & 0xF)), \ + ('0' + ((n)>>12 & 0xF)), \ + ('0' + ((n)>>8 & 0xF)), \ + ('0' + ((n)>>4 & 0xF)), \ + ('0' + ((n) & 0xF)) + +/* Construct a string literal encoding the version number components. */ +#ifdef COMPILER_VERSION_MAJOR +char const info_version[] = { + 'I', 'N', 'F', 'O', ':', + 'c','o','m','p','i','l','e','r','_','v','e','r','s','i','o','n','[', + COMPILER_VERSION_MAJOR, +# ifdef COMPILER_VERSION_MINOR + '.', COMPILER_VERSION_MINOR, +# ifdef COMPILER_VERSION_PATCH + '.', COMPILER_VERSION_PATCH, +# ifdef COMPILER_VERSION_TWEAK + '.', COMPILER_VERSION_TWEAK, +# endif +# endif +# endif + ']','\0'}; +#endif + +/* Construct a string literal encoding the internal version number. */ +#ifdef COMPILER_VERSION_INTERNAL +char const info_version_internal[] = { + 'I', 'N', 'F', 'O', ':', + 'c','o','m','p','i','l','e','r','_','v','e','r','s','i','o','n','_', + 'i','n','t','e','r','n','a','l','[', + COMPILER_VERSION_INTERNAL,']','\0'}; +#endif + +/* Construct a string literal encoding the version number components. */ +#ifdef SIMULATE_VERSION_MAJOR +char const info_simulate_version[] = { + 'I', 'N', 'F', 'O', ':', + 's','i','m','u','l','a','t','e','_','v','e','r','s','i','o','n','[', + SIMULATE_VERSION_MAJOR, +# ifdef SIMULATE_VERSION_MINOR + '.', SIMULATE_VERSION_MINOR, +# ifdef SIMULATE_VERSION_PATCH + '.', SIMULATE_VERSION_PATCH, +# ifdef SIMULATE_VERSION_TWEAK + '.', SIMULATE_VERSION_TWEAK, +# endif +# endif +# endif + ']','\0'}; +#endif + +/* Construct the string literal in pieces to prevent the source from + getting matched. Store it in a pointer rather than an array + because some compilers will just produce instructions to fill the + array rather than assigning a pointer to a static array. */ +char const* info_platform = "INFO" ":" "platform[" PLATFORM_ID "]"; +char const* info_arch = "INFO" ":" "arch[" ARCHITECTURE_ID "]"; + + + + +#if defined(__INTEL_COMPILER) && defined(_MSVC_LANG) && _MSVC_LANG < 201403L +# if defined(__INTEL_CXX11_MODE__) +# if defined(__cpp_aggregate_nsdmi) +# define CXX_STD 201402L +# else +# define CXX_STD 201103L +# endif +# else +# define CXX_STD 199711L +# endif +#elif defined(_MSC_VER) && defined(_MSVC_LANG) +# define CXX_STD _MSVC_LANG +#else +# define CXX_STD __cplusplus +#endif + +const char* info_language_dialect_default = "INFO" ":" "dialect_default[" +#if CXX_STD > 201703L + "20" +#elif CXX_STD >= 201703L + "17" +#elif CXX_STD >= 201402L + "14" +#elif CXX_STD >= 201103L + "11" +#else + "98" +#endif +"]"; + +/*--------------------------------------------------------------------------*/ + +int main(int argc, char* argv[]) +{ + int require = 0; + require += info_compiler[argc]; + require += info_platform[argc]; +#ifdef COMPILER_VERSION_MAJOR + require += info_version[argc]; +#endif +#ifdef COMPILER_VERSION_INTERNAL + require += info_version_internal[argc]; +#endif +#ifdef SIMULATE_ID + require += info_simulate[argc]; +#endif +#ifdef SIMULATE_VERSION_MAJOR + require += info_simulate_version[argc]; +#endif +#if defined(__CRAYXE) || defined(__CRAYXC) + require += info_cray[argc]; +#endif + require += info_language_dialect_default[argc]; + (void)argv; + return require; +} diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/CMakeFiles/3.16.3/CompilerIdCXX/a.out b/applications/external/esp-flasher/lib/esp-serial-flasher/CMakeFiles/3.16.3/CompilerIdCXX/a.out new file mode 100755 index 0000000000000000000000000000000000000000..34ec4a035bbb8902315ba9a0b3b352eb275bc2d7 GIT binary patch literal 16720 zcmeHOe{3699e<9KHtkx+?bQr$0;*-GblIRNj4bB+efRx3 zcNcd^jDJA)B;V(~&-cfB_kHiq_wL^NL?qTzRtq*%;%*fB))sf28YMR^JVQy;p+OoTI3 zA0x*TV}ieSil-A5(@VMePP=}xb7D8yF=8E}0wc3~Yj zrVbqH6X0eBISQlfi%nEUxzYN?FPtmBdGpe9P1o0aWwL3p_VF*V53Cb)FrhsU2`pnC z>p&gz{67gf8q0u}lfM{~#4Qf(z$+DGTY=kHf$s%Qsldl?!QTe__6qs};uptowFym& zPw3ikDx;@Uk0qgqA}w#|IYS%MQyFm}7VYfPS^_PBJwl829@G-aTyi9pHz4Uh za!?yG<;-*0@8A{1)T?*!IpP%u{~6*N9sE0ZUo#2xDjw8@P8A5zt-LTraE=KRy|eoo83aFpPn(Q zN-wEXKdQYT&#|=U8KB=N?fg1GU4uY3`~$tQ>6(l1 zXsS-HKP;i+Ja{T?&Vk9_POH-$--C=gUGp5m-UVYbRDB5ZQ1zP9Y+b_%toQ;U=oh8_ zo=+j{T2hPi>iHY{)$0ZUo#2xDjw8;6~toBmy4W*T$8Njvnecyf2;|8&9Q^xdHe} zIcV3h$+eFsbNN&@GY~Y>-T-A|Py0M7v8x69-6rP4XT>eox9p930zo8UDB zev8HAdF+VrO!z(9)~&8RO+2nKXa{?IGa$NG%P)L8Alr&%;3eVn_xS2RSa;j8+7n{` z)_w1}Z^w2Nqu)p1H+O^dkc5Oj4fy>O>?e_k`20^+b**1jH4ZgHH9(z#Ul7`xM0t}%}wDV@IhPM^QiS1Z3~ z{ua#p!H@gB4g={XHv(=1+z7Z4a3kPGz>R<#0XG6}1ULe`u94Rm1ip zxseu&zKhDdUUD0ic^xFKE5*d?CUG8wiRCw!N?9zQrNxLmXX&p-me;rn@e`7-HED62 z5Z9Yxsy9JwCS)KpF+=MZSCgQYD&lr1bi%L*R3`R!H|rDrvt+gx)TJqWQI+kVqxQm= zVOi$*!8A;kUncoKsLbskxBorh_2KW(Bd=do6w?u}Pv!Nfy#Dk+SJytJv2Uo5F$&7w z08H$gnhUbj{6tGI5Nr)J?=ty+-ykonV6Ep4v#5LFodmC6x6rUQ+ADZ{x}C2Uk5}|l zQyyn_{Z-}hW#?B59*1_mR&amX`89&aft_C~c)Zzp7`=2|+4*%ssmQMv!HT@Eyg$S4 zr%v#7YUja;Vr9&{S8OP+zqd-gurhqEqWUbxzW6?wiJE!*IASS?;JEi+~g?`u`UC&nG|gmoB_KkMM{A)fD(rWYaJ2N@W( z<@R8Gn0nynfww_?-;*R9aB)7?%lBR8@tu%Itc>T=z*lPjvr^wLLJDLcwU_`tD(jyI z-UI#kt9wY%0+sy#Qp#^%(f(QBJ$RRO^y90f&;J9m2!}S4`-@F>1HZ9q<@M5!JS$o782NFI&y&C_ z7Cp{B1^kApErRc>%%4RbTEs^5HzA&t{1)NdC-4IB7>94ueuN}Bx(eurao8SW&n6xx z{Sb}k-6VdI{QF6t{|B!EkA8y0le&0?{OqE7h>I;ha8})DnfUu}Ltolr+#ZY^q%c z^^YdI`un@+6?rrfh>wp8?ZdI=mQ-dq%df=2PtTF?!H5<))UCnZ6}zW|(7Hc#D10#5 zWtYfJGC;w5cP*ll2etc%&3YSGlz&o-fFAdi%{Z$%Jm` zY_hx?Cls=6$I(#B+;RiD_Wd_nBA?Yp^-Kcy=0p#JRw9+r3i)J0$W!N+{9FmP3j=6M z`J)0zhw^!QqV`B8|+^KKpi#(aTsy8P{l1k_Dx6jlal`NV}lTZ{N$LS z4*?oE6Gu6h%E11kaS_O5jbvaXQwWUbvg65|F=?TO3MtqjmP!y6?u<4W`iPL~qk4W+ z1QL@OsKP`eXG-WRiLIc4ESF5{XdnVIN`djTA@C3w$wJ0RPQVezDrjb9=LM3ZG&)BU zV9zDf-W;Z;1?R!JK9-6@bFl9N5O)!Pei(yc&lli3M4S)6EduW8X5!~NMxGClSn6^f z@!jylXJxkM=RigUWY$!^KM>glXtnRB=R(FER%6KX{g>&e!=9fj85LF}blN`*{NLf3 zpWDy#97e7mzhPl=wqFMh=QwQ7^B~64Xaot*5#!AEJby_6ho{W;{5;FZ^C(b}34K`8 z$Dn|78n)->UPhiDa{F12@d>cUITDxo`Iu3m%AD;dT1-O*W6JjY9L=augSmd^`F|N~ zQHACBS1S`C&sT|E(f?lqfz@6iJ4SZL_FQIs#$nIT?~JT>EBkLb>_cS0c+JsaPJiEV z*z~SVzjOS4m+U$ILLp7Ub5vv?nv3Fxki#aj{W(%#yuYkzW}NlE2(;Cn=OK*q z4twr@rhg3?e5U8=AU~h-{$Hm)ml^*EwsjVJex6@w0umDInWg12^RIyb#~#;Ter|^K z-I!*m|8V?cVmt1~ZvclXY|rm8X6QjK1Tt%4d&UJ2TJ1%Mw1XxR5V>4G6HAamUtB*w z&jrbl>tTI59WDwl5Xd>}Vf%Z_nr4RWt;l1?s`?RfsM3Xv*D-bpw}ViH#OEd3>-!AFKC{O)c^nh literal 0 HcmV?d00001 diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/CMakeFiles/CMakeOutput.log b/applications/external/esp-flasher/lib/esp-serial-flasher/CMakeFiles/CMakeOutput.log new file mode 100644 index 000000000..50d59997f --- /dev/null +++ b/applications/external/esp-flasher/lib/esp-serial-flasher/CMakeFiles/CMakeOutput.log @@ -0,0 +1,463 @@ +The system is: Linux - 5.10.16.3-microsoft-standard-WSL2 - x86_64 +Compiling the C compiler identification source file "CMakeCCompilerId.c" succeeded. +Compiler: /usr/bin/cc +Build flags: +Id flags: + +The output was: +0 + + +Compilation of the C compiler identification source "CMakeCCompilerId.c" produced "a.out" + +The C compiler identification is GNU, found in "/home/cococode/flipperzero-firmware/lib/esp-serial-flasher/CMakeFiles/3.16.3/CompilerIdC/a.out" + +Compiling the CXX compiler identification source file "CMakeCXXCompilerId.cpp" succeeded. +Compiler: /usr/bin/c++ +Build flags: +Id flags: + +The output was: +0 + + +Compilation of the CXX compiler identification source "CMakeCXXCompilerId.cpp" produced "a.out" + +The CXX compiler identification is GNU, found in "/home/cococode/flipperzero-firmware/lib/esp-serial-flasher/CMakeFiles/3.16.3/CompilerIdCXX/a.out" + +Determining if the C compiler works passed with the following output: +Change Dir: /home/cococode/flipperzero-firmware/lib/esp-serial-flasher/CMakeFiles/CMakeTmp + +Run Build Command(s):/usr/bin/make cmTC_8695b/fast && /usr/bin/make -f CMakeFiles/cmTC_8695b.dir/build.make CMakeFiles/cmTC_8695b.dir/build +make[1]: Entering directory '/home/cococode/flipperzero-firmware/lib/esp-serial-flasher/CMakeFiles/CMakeTmp' +Building C object CMakeFiles/cmTC_8695b.dir/testCCompiler.c.o +/usr/bin/cc -o CMakeFiles/cmTC_8695b.dir/testCCompiler.c.o -c /home/cococode/flipperzero-firmware/lib/esp-serial-flasher/CMakeFiles/CMakeTmp/testCCompiler.c +Linking C executable cmTC_8695b +/usr/bin/cmake -E cmake_link_script CMakeFiles/cmTC_8695b.dir/link.txt --verbose=1 +/usr/bin/cc -rdynamic CMakeFiles/cmTC_8695b.dir/testCCompiler.c.o -o cmTC_8695b +make[1]: Leaving directory '/home/cococode/flipperzero-firmware/lib/esp-serial-flasher/CMakeFiles/CMakeTmp' + + + +Detecting C compiler ABI info compiled with the following output: +Change Dir: /home/cococode/flipperzero-firmware/lib/esp-serial-flasher/CMakeFiles/CMakeTmp + +Run Build Command(s):/usr/bin/make cmTC_8c7d9/fast && /usr/bin/make -f CMakeFiles/cmTC_8c7d9.dir/build.make CMakeFiles/cmTC_8c7d9.dir/build +make[1]: Entering directory '/home/cococode/flipperzero-firmware/lib/esp-serial-flasher/CMakeFiles/CMakeTmp' +Building C object CMakeFiles/cmTC_8c7d9.dir/CMakeCCompilerABI.c.o +/usr/bin/cc -v -o CMakeFiles/cmTC_8c7d9.dir/CMakeCCompilerABI.c.o -c /usr/share/cmake-3.16/Modules/CMakeCCompilerABI.c +Using built-in specs. +COLLECT_GCC=/usr/bin/cc +OFFLOAD_TARGET_NAMES=nvptx-none:hsa +OFFLOAD_TARGET_DEFAULT=1 +Target: x86_64-linux-gnu +Configured with: ../src/configure -v --with-pkgversion='Ubuntu 9.4.0-1ubuntu1~20.04.1' --with-bugurl=file:///usr/share/doc/gcc-9/README.Bugs --enable-languages=c,ada,c++,go,brig,d,fortran,objc,obj-c++,gm2 --prefix=/usr --with-gcc-major-version-only --program-suffix=-9 --program-prefix=x86_64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-plugin --enable-default-pie --with-system-zlib --with-target-system-zlib=auto --enable-objc-gc=auto --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-offload-targets=nvptx-none=/build/gcc-9-Av3uEd/gcc-9-9.4.0/debian/tmp-nvptx/usr,hsa --without-cuda-driver --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu +Thread model: posix +gcc version 9.4.0 (Ubuntu 9.4.0-1ubuntu1~20.04.1) +COLLECT_GCC_OPTIONS='-v' '-o' 'CMakeFiles/cmTC_8c7d9.dir/CMakeCCompilerABI.c.o' '-c' '-mtune=generic' '-march=x86-64' + /usr/lib/gcc/x86_64-linux-gnu/9/cc1 -quiet -v -imultiarch x86_64-linux-gnu /usr/share/cmake-3.16/Modules/CMakeCCompilerABI.c -quiet -dumpbase CMakeCCompilerABI.c -mtune=generic -march=x86-64 -auxbase-strip CMakeFiles/cmTC_8c7d9.dir/CMakeCCompilerABI.c.o -version -fasynchronous-unwind-tables -fstack-protector-strong -Wformat -Wformat-security -fstack-clash-protection -fcf-protection -o /tmp/ccBr8JWY.s +GNU C17 (Ubuntu 9.4.0-1ubuntu1~20.04.1) version 9.4.0 (x86_64-linux-gnu) + compiled by GNU C version 9.4.0, GMP version 6.2.0, MPFR version 4.0.2, MPC version 1.1.0, isl version isl-0.22.1-GMP + +GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072 +ignoring nonexistent directory "/usr/local/include/x86_64-linux-gnu" +ignoring nonexistent directory "/usr/lib/gcc/x86_64-linux-gnu/9/include-fixed" +ignoring nonexistent directory "/usr/lib/gcc/x86_64-linux-gnu/9/../../../../x86_64-linux-gnu/include" +#include "..." search starts here: +#include <...> search starts here: + /usr/lib/gcc/x86_64-linux-gnu/9/include + /usr/local/include + /usr/include/x86_64-linux-gnu + /usr/include +End of search list. +GNU C17 (Ubuntu 9.4.0-1ubuntu1~20.04.1) version 9.4.0 (x86_64-linux-gnu) + compiled by GNU C version 9.4.0, GMP version 6.2.0, MPFR version 4.0.2, MPC version 1.1.0, isl version isl-0.22.1-GMP + +GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072 +Compiler executable checksum: c0c95c0b4209efec1c1892d5ff24030b +COLLECT_GCC_OPTIONS='-v' '-o' 'CMakeFiles/cmTC_8c7d9.dir/CMakeCCompilerABI.c.o' '-c' '-mtune=generic' '-march=x86-64' + as -v --64 -o CMakeFiles/cmTC_8c7d9.dir/CMakeCCompilerABI.c.o /tmp/ccBr8JWY.s +GNU assembler version 2.34 (x86_64-linux-gnu) using BFD version (GNU Binutils for Ubuntu) 2.34 +COMPILER_PATH=/usr/lib/gcc/x86_64-linux-gnu/9/:/usr/lib/gcc/x86_64-linux-gnu/9/:/usr/lib/gcc/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/9/:/usr/lib/gcc/x86_64-linux-gnu/ +LIBRARY_PATH=/usr/lib/gcc/x86_64-linux-gnu/9/:/usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/9/../../../../lib/:/lib/x86_64-linux-gnu/:/lib/../lib/:/usr/lib/x86_64-linux-gnu/:/usr/lib/../lib/:/usr/lib/gcc/x86_64-linux-gnu/9/../../../:/lib/:/usr/lib/ +COLLECT_GCC_OPTIONS='-v' '-o' 'CMakeFiles/cmTC_8c7d9.dir/CMakeCCompilerABI.c.o' '-c' '-mtune=generic' '-march=x86-64' +Linking C executable cmTC_8c7d9 +/usr/bin/cmake -E cmake_link_script CMakeFiles/cmTC_8c7d9.dir/link.txt --verbose=1 +/usr/bin/cc -v -rdynamic CMakeFiles/cmTC_8c7d9.dir/CMakeCCompilerABI.c.o -o cmTC_8c7d9 +Using built-in specs. +COLLECT_GCC=/usr/bin/cc +COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/9/lto-wrapper +OFFLOAD_TARGET_NAMES=nvptx-none:hsa +OFFLOAD_TARGET_DEFAULT=1 +Target: x86_64-linux-gnu +Configured with: ../src/configure -v --with-pkgversion='Ubuntu 9.4.0-1ubuntu1~20.04.1' --with-bugurl=file:///usr/share/doc/gcc-9/README.Bugs --enable-languages=c,ada,c++,go,brig,d,fortran,objc,obj-c++,gm2 --prefix=/usr --with-gcc-major-version-only --program-suffix=-9 --program-prefix=x86_64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-plugin --enable-default-pie --with-system-zlib --with-target-system-zlib=auto --enable-objc-gc=auto --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-offload-targets=nvptx-none=/build/gcc-9-Av3uEd/gcc-9-9.4.0/debian/tmp-nvptx/usr,hsa --without-cuda-driver --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu +Thread model: posix +gcc version 9.4.0 (Ubuntu 9.4.0-1ubuntu1~20.04.1) +COMPILER_PATH=/usr/lib/gcc/x86_64-linux-gnu/9/:/usr/lib/gcc/x86_64-linux-gnu/9/:/usr/lib/gcc/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/9/:/usr/lib/gcc/x86_64-linux-gnu/ +LIBRARY_PATH=/usr/lib/gcc/x86_64-linux-gnu/9/:/usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/9/../../../../lib/:/lib/x86_64-linux-gnu/:/lib/../lib/:/usr/lib/x86_64-linux-gnu/:/usr/lib/../lib/:/usr/lib/gcc/x86_64-linux-gnu/9/../../../:/lib/:/usr/lib/ +COLLECT_GCC_OPTIONS='-v' '-rdynamic' '-o' 'cmTC_8c7d9' '-mtune=generic' '-march=x86-64' + /usr/lib/gcc/x86_64-linux-gnu/9/collect2 -plugin /usr/lib/gcc/x86_64-linux-gnu/9/liblto_plugin.so -plugin-opt=/usr/lib/gcc/x86_64-linux-gnu/9/lto-wrapper -plugin-opt=-fresolution=/tmp/cc78SKyq.res -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_s -plugin-opt=-pass-through=-lc -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_s --build-id --eh-frame-hdr -m elf_x86_64 --hash-style=gnu --as-needed -export-dynamic -dynamic-linker /lib64/ld-linux-x86-64.so.2 -pie -z now -z relro -o cmTC_8c7d9 /usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/Scrt1.o /usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/crti.o /usr/lib/gcc/x86_64-linux-gnu/9/crtbeginS.o -L/usr/lib/gcc/x86_64-linux-gnu/9 -L/usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu -L/usr/lib/gcc/x86_64-linux-gnu/9/../../../../lib -L/lib/x86_64-linux-gnu -L/lib/../lib -L/usr/lib/x86_64-linux-gnu -L/usr/lib/../lib -L/usr/lib/gcc/x86_64-linux-gnu/9/../../.. CMakeFiles/cmTC_8c7d9.dir/CMakeCCompilerABI.c.o -lgcc --push-state --as-needed -lgcc_s --pop-state -lc -lgcc --push-state --as-needed -lgcc_s --pop-state /usr/lib/gcc/x86_64-linux-gnu/9/crtendS.o /usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/crtn.o +COLLECT_GCC_OPTIONS='-v' '-rdynamic' '-o' 'cmTC_8c7d9' '-mtune=generic' '-march=x86-64' +make[1]: Leaving directory '/home/cococode/flipperzero-firmware/lib/esp-serial-flasher/CMakeFiles/CMakeTmp' + + + +Parsed C implicit include dir info from above output: rv=done + found start of include info + found start of implicit include info + add: [/usr/lib/gcc/x86_64-linux-gnu/9/include] + add: [/usr/local/include] + add: [/usr/include/x86_64-linux-gnu] + add: [/usr/include] + end of search list found + collapse include dir [/usr/lib/gcc/x86_64-linux-gnu/9/include] ==> [/usr/lib/gcc/x86_64-linux-gnu/9/include] + collapse include dir [/usr/local/include] ==> [/usr/local/include] + collapse include dir [/usr/include/x86_64-linux-gnu] ==> [/usr/include/x86_64-linux-gnu] + collapse include dir [/usr/include] ==> [/usr/include] + implicit include dirs: [/usr/lib/gcc/x86_64-linux-gnu/9/include;/usr/local/include;/usr/include/x86_64-linux-gnu;/usr/include] + + +Parsed C implicit link information from above output: + link line regex: [^( *|.*[/\])(ld|CMAKE_LINK_STARTFILE-NOTFOUND|([^/\]+-)?ld|collect2)[^/\]*( |$)] + ignore line: [Change Dir: /home/cococode/flipperzero-firmware/lib/esp-serial-flasher/CMakeFiles/CMakeTmp] + ignore line: [] + ignore line: [Run Build Command(s):/usr/bin/make cmTC_8c7d9/fast && /usr/bin/make -f CMakeFiles/cmTC_8c7d9.dir/build.make CMakeFiles/cmTC_8c7d9.dir/build] + ignore line: [make[1]: Entering directory '/home/cococode/flipperzero-firmware/lib/esp-serial-flasher/CMakeFiles/CMakeTmp'] + ignore line: [Building C object CMakeFiles/cmTC_8c7d9.dir/CMakeCCompilerABI.c.o] + ignore line: [/usr/bin/cc -v -o CMakeFiles/cmTC_8c7d9.dir/CMakeCCompilerABI.c.o -c /usr/share/cmake-3.16/Modules/CMakeCCompilerABI.c] + ignore line: [Using built-in specs.] + ignore line: [COLLECT_GCC=/usr/bin/cc] + ignore line: [OFFLOAD_TARGET_NAMES=nvptx-none:hsa] + ignore line: [OFFLOAD_TARGET_DEFAULT=1] + ignore line: [Target: x86_64-linux-gnu] + ignore line: [Configured with: ../src/configure -v --with-pkgversion='Ubuntu 9.4.0-1ubuntu1~20.04.1' --with-bugurl=file:///usr/share/doc/gcc-9/README.Bugs --enable-languages=c ada c++ go brig d fortran objc obj-c++ gm2 --prefix=/usr --with-gcc-major-version-only --program-suffix=-9 --program-prefix=x86_64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-plugin --enable-default-pie --with-system-zlib --with-target-system-zlib=auto --enable-objc-gc=auto --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32 m64 mx32 --enable-multilib --with-tune=generic --enable-offload-targets=nvptx-none=/build/gcc-9-Av3uEd/gcc-9-9.4.0/debian/tmp-nvptx/usr hsa --without-cuda-driver --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu] + ignore line: [Thread model: posix] + ignore line: [gcc version 9.4.0 (Ubuntu 9.4.0-1ubuntu1~20.04.1) ] + ignore line: [COLLECT_GCC_OPTIONS='-v' '-o' 'CMakeFiles/cmTC_8c7d9.dir/CMakeCCompilerABI.c.o' '-c' '-mtune=generic' '-march=x86-64'] + ignore line: [ /usr/lib/gcc/x86_64-linux-gnu/9/cc1 -quiet -v -imultiarch x86_64-linux-gnu /usr/share/cmake-3.16/Modules/CMakeCCompilerABI.c -quiet -dumpbase CMakeCCompilerABI.c -mtune=generic -march=x86-64 -auxbase-strip CMakeFiles/cmTC_8c7d9.dir/CMakeCCompilerABI.c.o -version -fasynchronous-unwind-tables -fstack-protector-strong -Wformat -Wformat-security -fstack-clash-protection -fcf-protection -o /tmp/ccBr8JWY.s] + ignore line: [GNU C17 (Ubuntu 9.4.0-1ubuntu1~20.04.1) version 9.4.0 (x86_64-linux-gnu)] + ignore line: [ compiled by GNU C version 9.4.0 GMP version 6.2.0 MPFR version 4.0.2 MPC version 1.1.0 isl version isl-0.22.1-GMP] + ignore line: [] + ignore line: [GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072] + ignore line: [ignoring nonexistent directory "/usr/local/include/x86_64-linux-gnu"] + ignore line: [ignoring nonexistent directory "/usr/lib/gcc/x86_64-linux-gnu/9/include-fixed"] + ignore line: [ignoring nonexistent directory "/usr/lib/gcc/x86_64-linux-gnu/9/../../../../x86_64-linux-gnu/include"] + ignore line: [#include "..." search starts here:] + ignore line: [#include <...> search starts here:] + ignore line: [ /usr/lib/gcc/x86_64-linux-gnu/9/include] + ignore line: [ /usr/local/include] + ignore line: [ /usr/include/x86_64-linux-gnu] + ignore line: [ /usr/include] + ignore line: [End of search list.] + ignore line: [GNU C17 (Ubuntu 9.4.0-1ubuntu1~20.04.1) version 9.4.0 (x86_64-linux-gnu)] + ignore line: [ compiled by GNU C version 9.4.0 GMP version 6.2.0 MPFR version 4.0.2 MPC version 1.1.0 isl version isl-0.22.1-GMP] + ignore line: [] + ignore line: [GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072] + ignore line: [Compiler executable checksum: c0c95c0b4209efec1c1892d5ff24030b] + ignore line: [COLLECT_GCC_OPTIONS='-v' '-o' 'CMakeFiles/cmTC_8c7d9.dir/CMakeCCompilerABI.c.o' '-c' '-mtune=generic' '-march=x86-64'] + ignore line: [ as -v --64 -o CMakeFiles/cmTC_8c7d9.dir/CMakeCCompilerABI.c.o /tmp/ccBr8JWY.s] + ignore line: [GNU assembler version 2.34 (x86_64-linux-gnu) using BFD version (GNU Binutils for Ubuntu) 2.34] + ignore line: [COMPILER_PATH=/usr/lib/gcc/x86_64-linux-gnu/9/:/usr/lib/gcc/x86_64-linux-gnu/9/:/usr/lib/gcc/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/9/:/usr/lib/gcc/x86_64-linux-gnu/] + ignore line: [LIBRARY_PATH=/usr/lib/gcc/x86_64-linux-gnu/9/:/usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/9/../../../../lib/:/lib/x86_64-linux-gnu/:/lib/../lib/:/usr/lib/x86_64-linux-gnu/:/usr/lib/../lib/:/usr/lib/gcc/x86_64-linux-gnu/9/../../../:/lib/:/usr/lib/] + ignore line: [COLLECT_GCC_OPTIONS='-v' '-o' 'CMakeFiles/cmTC_8c7d9.dir/CMakeCCompilerABI.c.o' '-c' '-mtune=generic' '-march=x86-64'] + ignore line: [Linking C executable cmTC_8c7d9] + ignore line: [/usr/bin/cmake -E cmake_link_script CMakeFiles/cmTC_8c7d9.dir/link.txt --verbose=1] + ignore line: [/usr/bin/cc -v -rdynamic CMakeFiles/cmTC_8c7d9.dir/CMakeCCompilerABI.c.o -o cmTC_8c7d9 ] + ignore line: [Using built-in specs.] + ignore line: [COLLECT_GCC=/usr/bin/cc] + ignore line: [COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/9/lto-wrapper] + ignore line: [OFFLOAD_TARGET_NAMES=nvptx-none:hsa] + ignore line: [OFFLOAD_TARGET_DEFAULT=1] + ignore line: [Target: x86_64-linux-gnu] + ignore line: [Configured with: ../src/configure -v --with-pkgversion='Ubuntu 9.4.0-1ubuntu1~20.04.1' --with-bugurl=file:///usr/share/doc/gcc-9/README.Bugs --enable-languages=c ada c++ go brig d fortran objc obj-c++ gm2 --prefix=/usr --with-gcc-major-version-only --program-suffix=-9 --program-prefix=x86_64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-plugin --enable-default-pie --with-system-zlib --with-target-system-zlib=auto --enable-objc-gc=auto --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32 m64 mx32 --enable-multilib --with-tune=generic --enable-offload-targets=nvptx-none=/build/gcc-9-Av3uEd/gcc-9-9.4.0/debian/tmp-nvptx/usr hsa --without-cuda-driver --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu] + ignore line: [Thread model: posix] + ignore line: [gcc version 9.4.0 (Ubuntu 9.4.0-1ubuntu1~20.04.1) ] + ignore line: [COMPILER_PATH=/usr/lib/gcc/x86_64-linux-gnu/9/:/usr/lib/gcc/x86_64-linux-gnu/9/:/usr/lib/gcc/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/9/:/usr/lib/gcc/x86_64-linux-gnu/] + ignore line: [LIBRARY_PATH=/usr/lib/gcc/x86_64-linux-gnu/9/:/usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/9/../../../../lib/:/lib/x86_64-linux-gnu/:/lib/../lib/:/usr/lib/x86_64-linux-gnu/:/usr/lib/../lib/:/usr/lib/gcc/x86_64-linux-gnu/9/../../../:/lib/:/usr/lib/] + ignore line: [COLLECT_GCC_OPTIONS='-v' '-rdynamic' '-o' 'cmTC_8c7d9' '-mtune=generic' '-march=x86-64'] + link line: [ /usr/lib/gcc/x86_64-linux-gnu/9/collect2 -plugin /usr/lib/gcc/x86_64-linux-gnu/9/liblto_plugin.so -plugin-opt=/usr/lib/gcc/x86_64-linux-gnu/9/lto-wrapper -plugin-opt=-fresolution=/tmp/cc78SKyq.res -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_s -plugin-opt=-pass-through=-lc -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_s --build-id --eh-frame-hdr -m elf_x86_64 --hash-style=gnu --as-needed -export-dynamic -dynamic-linker /lib64/ld-linux-x86-64.so.2 -pie -z now -z relro -o cmTC_8c7d9 /usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/Scrt1.o /usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/crti.o /usr/lib/gcc/x86_64-linux-gnu/9/crtbeginS.o -L/usr/lib/gcc/x86_64-linux-gnu/9 -L/usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu -L/usr/lib/gcc/x86_64-linux-gnu/9/../../../../lib -L/lib/x86_64-linux-gnu -L/lib/../lib -L/usr/lib/x86_64-linux-gnu -L/usr/lib/../lib -L/usr/lib/gcc/x86_64-linux-gnu/9/../../.. CMakeFiles/cmTC_8c7d9.dir/CMakeCCompilerABI.c.o -lgcc --push-state --as-needed -lgcc_s --pop-state -lc -lgcc --push-state --as-needed -lgcc_s --pop-state /usr/lib/gcc/x86_64-linux-gnu/9/crtendS.o /usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/crtn.o] + arg [/usr/lib/gcc/x86_64-linux-gnu/9/collect2] ==> ignore + arg [-plugin] ==> ignore + arg [/usr/lib/gcc/x86_64-linux-gnu/9/liblto_plugin.so] ==> ignore + arg [-plugin-opt=/usr/lib/gcc/x86_64-linux-gnu/9/lto-wrapper] ==> ignore + arg [-plugin-opt=-fresolution=/tmp/cc78SKyq.res] ==> ignore + arg [-plugin-opt=-pass-through=-lgcc] ==> ignore + arg [-plugin-opt=-pass-through=-lgcc_s] ==> ignore + arg [-plugin-opt=-pass-through=-lc] ==> ignore + arg [-plugin-opt=-pass-through=-lgcc] ==> ignore + arg [-plugin-opt=-pass-through=-lgcc_s] ==> ignore + arg [--build-id] ==> ignore + arg [--eh-frame-hdr] ==> ignore + arg [-m] ==> ignore + arg [elf_x86_64] ==> ignore + arg [--hash-style=gnu] ==> ignore + arg [--as-needed] ==> ignore + arg [-export-dynamic] ==> ignore + arg [-dynamic-linker] ==> ignore + arg [/lib64/ld-linux-x86-64.so.2] ==> ignore + arg [-pie] ==> ignore + arg [-znow] ==> ignore + arg [-zrelro] ==> ignore + arg [-o] ==> ignore + arg [cmTC_8c7d9] ==> ignore + arg [/usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/Scrt1.o] ==> ignore + arg [/usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/crti.o] ==> ignore + arg [/usr/lib/gcc/x86_64-linux-gnu/9/crtbeginS.o] ==> ignore + arg [-L/usr/lib/gcc/x86_64-linux-gnu/9] ==> dir [/usr/lib/gcc/x86_64-linux-gnu/9] + arg [-L/usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu] ==> dir [/usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu] + arg [-L/usr/lib/gcc/x86_64-linux-gnu/9/../../../../lib] ==> dir [/usr/lib/gcc/x86_64-linux-gnu/9/../../../../lib] + arg [-L/lib/x86_64-linux-gnu] ==> dir [/lib/x86_64-linux-gnu] + arg [-L/lib/../lib] ==> dir [/lib/../lib] + arg [-L/usr/lib/x86_64-linux-gnu] ==> dir [/usr/lib/x86_64-linux-gnu] + arg [-L/usr/lib/../lib] ==> dir [/usr/lib/../lib] + arg [-L/usr/lib/gcc/x86_64-linux-gnu/9/../../..] ==> dir [/usr/lib/gcc/x86_64-linux-gnu/9/../../..] + arg [CMakeFiles/cmTC_8c7d9.dir/CMakeCCompilerABI.c.o] ==> ignore + arg [-lgcc] ==> lib [gcc] + arg [--push-state] ==> ignore + arg [--as-needed] ==> ignore + arg [-lgcc_s] ==> lib [gcc_s] + arg [--pop-state] ==> ignore + arg [-lc] ==> lib [c] + arg [-lgcc] ==> lib [gcc] + arg [--push-state] ==> ignore + arg [--as-needed] ==> ignore + arg [-lgcc_s] ==> lib [gcc_s] + arg [--pop-state] ==> ignore + arg [/usr/lib/gcc/x86_64-linux-gnu/9/crtendS.o] ==> ignore + arg [/usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/crtn.o] ==> ignore + collapse library dir [/usr/lib/gcc/x86_64-linux-gnu/9] ==> [/usr/lib/gcc/x86_64-linux-gnu/9] + collapse library dir [/usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu] ==> [/usr/lib/x86_64-linux-gnu] + collapse library dir [/usr/lib/gcc/x86_64-linux-gnu/9/../../../../lib] ==> [/usr/lib] + collapse library dir [/lib/x86_64-linux-gnu] ==> [/lib/x86_64-linux-gnu] + collapse library dir [/lib/../lib] ==> [/lib] + collapse library dir [/usr/lib/x86_64-linux-gnu] ==> [/usr/lib/x86_64-linux-gnu] + collapse library dir [/usr/lib/../lib] ==> [/usr/lib] + collapse library dir [/usr/lib/gcc/x86_64-linux-gnu/9/../../..] ==> [/usr/lib] + implicit libs: [gcc;gcc_s;c;gcc;gcc_s] + implicit dirs: [/usr/lib/gcc/x86_64-linux-gnu/9;/usr/lib/x86_64-linux-gnu;/usr/lib;/lib/x86_64-linux-gnu;/lib] + implicit fwks: [] + + +Determining if the CXX compiler works passed with the following output: +Change Dir: /home/cococode/flipperzero-firmware/lib/esp-serial-flasher/CMakeFiles/CMakeTmp + +Run Build Command(s):/usr/bin/make cmTC_9b51d/fast && /usr/bin/make -f CMakeFiles/cmTC_9b51d.dir/build.make CMakeFiles/cmTC_9b51d.dir/build +make[1]: Entering directory '/home/cococode/flipperzero-firmware/lib/esp-serial-flasher/CMakeFiles/CMakeTmp' +Building CXX object CMakeFiles/cmTC_9b51d.dir/testCXXCompiler.cxx.o +/usr/bin/c++ -o CMakeFiles/cmTC_9b51d.dir/testCXXCompiler.cxx.o -c /home/cococode/flipperzero-firmware/lib/esp-serial-flasher/CMakeFiles/CMakeTmp/testCXXCompiler.cxx +Linking CXX executable cmTC_9b51d +/usr/bin/cmake -E cmake_link_script CMakeFiles/cmTC_9b51d.dir/link.txt --verbose=1 +/usr/bin/c++ -rdynamic CMakeFiles/cmTC_9b51d.dir/testCXXCompiler.cxx.o -o cmTC_9b51d +make[1]: Leaving directory '/home/cococode/flipperzero-firmware/lib/esp-serial-flasher/CMakeFiles/CMakeTmp' + + + +Detecting CXX compiler ABI info compiled with the following output: +Change Dir: /home/cococode/flipperzero-firmware/lib/esp-serial-flasher/CMakeFiles/CMakeTmp + +Run Build Command(s):/usr/bin/make cmTC_b76ad/fast && /usr/bin/make -f CMakeFiles/cmTC_b76ad.dir/build.make CMakeFiles/cmTC_b76ad.dir/build +make[1]: Entering directory '/home/cococode/flipperzero-firmware/lib/esp-serial-flasher/CMakeFiles/CMakeTmp' +Building CXX object CMakeFiles/cmTC_b76ad.dir/CMakeCXXCompilerABI.cpp.o +/usr/bin/c++ -v -o CMakeFiles/cmTC_b76ad.dir/CMakeCXXCompilerABI.cpp.o -c /usr/share/cmake-3.16/Modules/CMakeCXXCompilerABI.cpp +Using built-in specs. +COLLECT_GCC=/usr/bin/c++ +OFFLOAD_TARGET_NAMES=nvptx-none:hsa +OFFLOAD_TARGET_DEFAULT=1 +Target: x86_64-linux-gnu +Configured with: ../src/configure -v --with-pkgversion='Ubuntu 9.4.0-1ubuntu1~20.04.1' --with-bugurl=file:///usr/share/doc/gcc-9/README.Bugs --enable-languages=c,ada,c++,go,brig,d,fortran,objc,obj-c++,gm2 --prefix=/usr --with-gcc-major-version-only --program-suffix=-9 --program-prefix=x86_64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-plugin --enable-default-pie --with-system-zlib --with-target-system-zlib=auto --enable-objc-gc=auto --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-offload-targets=nvptx-none=/build/gcc-9-Av3uEd/gcc-9-9.4.0/debian/tmp-nvptx/usr,hsa --without-cuda-driver --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu +Thread model: posix +gcc version 9.4.0 (Ubuntu 9.4.0-1ubuntu1~20.04.1) +COLLECT_GCC_OPTIONS='-v' '-o' 'CMakeFiles/cmTC_b76ad.dir/CMakeCXXCompilerABI.cpp.o' '-c' '-shared-libgcc' '-mtune=generic' '-march=x86-64' + /usr/lib/gcc/x86_64-linux-gnu/9/cc1plus -quiet -v -imultiarch x86_64-linux-gnu -D_GNU_SOURCE /usr/share/cmake-3.16/Modules/CMakeCXXCompilerABI.cpp -quiet -dumpbase CMakeCXXCompilerABI.cpp -mtune=generic -march=x86-64 -auxbase-strip CMakeFiles/cmTC_b76ad.dir/CMakeCXXCompilerABI.cpp.o -version -fasynchronous-unwind-tables -fstack-protector-strong -Wformat -Wformat-security -fstack-clash-protection -fcf-protection -o /tmp/ccTEwRoV.s +GNU C++14 (Ubuntu 9.4.0-1ubuntu1~20.04.1) version 9.4.0 (x86_64-linux-gnu) + compiled by GNU C version 9.4.0, GMP version 6.2.0, MPFR version 4.0.2, MPC version 1.1.0, isl version isl-0.22.1-GMP + +GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072 +ignoring duplicate directory "/usr/include/x86_64-linux-gnu/c++/9" +ignoring nonexistent directory "/usr/local/include/x86_64-linux-gnu" +ignoring nonexistent directory "/usr/lib/gcc/x86_64-linux-gnu/9/include-fixed" +ignoring nonexistent directory "/usr/lib/gcc/x86_64-linux-gnu/9/../../../../x86_64-linux-gnu/include" +#include "..." search starts here: +#include <...> search starts here: + /usr/include/c++/9 + /usr/include/x86_64-linux-gnu/c++/9 + /usr/include/c++/9/backward + /usr/lib/gcc/x86_64-linux-gnu/9/include + /usr/local/include + /usr/include/x86_64-linux-gnu + /usr/include +End of search list. +GNU C++14 (Ubuntu 9.4.0-1ubuntu1~20.04.1) version 9.4.0 (x86_64-linux-gnu) + compiled by GNU C version 9.4.0, GMP version 6.2.0, MPFR version 4.0.2, MPC version 1.1.0, isl version isl-0.22.1-GMP + +GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072 +Compiler executable checksum: 65fe925b83d3956b533de4aaba7dace0 +COLLECT_GCC_OPTIONS='-v' '-o' 'CMakeFiles/cmTC_b76ad.dir/CMakeCXXCompilerABI.cpp.o' '-c' '-shared-libgcc' '-mtune=generic' '-march=x86-64' + as -v --64 -o CMakeFiles/cmTC_b76ad.dir/CMakeCXXCompilerABI.cpp.o /tmp/ccTEwRoV.s +GNU assembler version 2.34 (x86_64-linux-gnu) using BFD version (GNU Binutils for Ubuntu) 2.34 +COMPILER_PATH=/usr/lib/gcc/x86_64-linux-gnu/9/:/usr/lib/gcc/x86_64-linux-gnu/9/:/usr/lib/gcc/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/9/:/usr/lib/gcc/x86_64-linux-gnu/ +LIBRARY_PATH=/usr/lib/gcc/x86_64-linux-gnu/9/:/usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/9/../../../../lib/:/lib/x86_64-linux-gnu/:/lib/../lib/:/usr/lib/x86_64-linux-gnu/:/usr/lib/../lib/:/usr/lib/gcc/x86_64-linux-gnu/9/../../../:/lib/:/usr/lib/ +COLLECT_GCC_OPTIONS='-v' '-o' 'CMakeFiles/cmTC_b76ad.dir/CMakeCXXCompilerABI.cpp.o' '-c' '-shared-libgcc' '-mtune=generic' '-march=x86-64' +Linking CXX executable cmTC_b76ad +/usr/bin/cmake -E cmake_link_script CMakeFiles/cmTC_b76ad.dir/link.txt --verbose=1 +/usr/bin/c++ -v -rdynamic CMakeFiles/cmTC_b76ad.dir/CMakeCXXCompilerABI.cpp.o -o cmTC_b76ad +Using built-in specs. +COLLECT_GCC=/usr/bin/c++ +COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/9/lto-wrapper +OFFLOAD_TARGET_NAMES=nvptx-none:hsa +OFFLOAD_TARGET_DEFAULT=1 +Target: x86_64-linux-gnu +Configured with: ../src/configure -v --with-pkgversion='Ubuntu 9.4.0-1ubuntu1~20.04.1' --with-bugurl=file:///usr/share/doc/gcc-9/README.Bugs --enable-languages=c,ada,c++,go,brig,d,fortran,objc,obj-c++,gm2 --prefix=/usr --with-gcc-major-version-only --program-suffix=-9 --program-prefix=x86_64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-plugin --enable-default-pie --with-system-zlib --with-target-system-zlib=auto --enable-objc-gc=auto --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-offload-targets=nvptx-none=/build/gcc-9-Av3uEd/gcc-9-9.4.0/debian/tmp-nvptx/usr,hsa --without-cuda-driver --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu +Thread model: posix +gcc version 9.4.0 (Ubuntu 9.4.0-1ubuntu1~20.04.1) +COMPILER_PATH=/usr/lib/gcc/x86_64-linux-gnu/9/:/usr/lib/gcc/x86_64-linux-gnu/9/:/usr/lib/gcc/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/9/:/usr/lib/gcc/x86_64-linux-gnu/ +LIBRARY_PATH=/usr/lib/gcc/x86_64-linux-gnu/9/:/usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/9/../../../../lib/:/lib/x86_64-linux-gnu/:/lib/../lib/:/usr/lib/x86_64-linux-gnu/:/usr/lib/../lib/:/usr/lib/gcc/x86_64-linux-gnu/9/../../../:/lib/:/usr/lib/ +COLLECT_GCC_OPTIONS='-v' '-rdynamic' '-o' 'cmTC_b76ad' '-shared-libgcc' '-mtune=generic' '-march=x86-64' + /usr/lib/gcc/x86_64-linux-gnu/9/collect2 -plugin /usr/lib/gcc/x86_64-linux-gnu/9/liblto_plugin.so -plugin-opt=/usr/lib/gcc/x86_64-linux-gnu/9/lto-wrapper -plugin-opt=-fresolution=/tmp/ccnXxdBn.res -plugin-opt=-pass-through=-lgcc_s -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lc -plugin-opt=-pass-through=-lgcc_s -plugin-opt=-pass-through=-lgcc --build-id --eh-frame-hdr -m elf_x86_64 --hash-style=gnu --as-needed -export-dynamic -dynamic-linker /lib64/ld-linux-x86-64.so.2 -pie -z now -z relro -o cmTC_b76ad /usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/Scrt1.o /usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/crti.o /usr/lib/gcc/x86_64-linux-gnu/9/crtbeginS.o -L/usr/lib/gcc/x86_64-linux-gnu/9 -L/usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu -L/usr/lib/gcc/x86_64-linux-gnu/9/../../../../lib -L/lib/x86_64-linux-gnu -L/lib/../lib -L/usr/lib/x86_64-linux-gnu -L/usr/lib/../lib -L/usr/lib/gcc/x86_64-linux-gnu/9/../../.. CMakeFiles/cmTC_b76ad.dir/CMakeCXXCompilerABI.cpp.o -lstdc++ -lm -lgcc_s -lgcc -lc -lgcc_s -lgcc /usr/lib/gcc/x86_64-linux-gnu/9/crtendS.o /usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/crtn.o +COLLECT_GCC_OPTIONS='-v' '-rdynamic' '-o' 'cmTC_b76ad' '-shared-libgcc' '-mtune=generic' '-march=x86-64' +make[1]: Leaving directory '/home/cococode/flipperzero-firmware/lib/esp-serial-flasher/CMakeFiles/CMakeTmp' + + + +Parsed CXX implicit include dir info from above output: rv=done + found start of include info + found start of implicit include info + add: [/usr/include/c++/9] + add: [/usr/include/x86_64-linux-gnu/c++/9] + add: [/usr/include/c++/9/backward] + add: [/usr/lib/gcc/x86_64-linux-gnu/9/include] + add: [/usr/local/include] + add: [/usr/include/x86_64-linux-gnu] + add: [/usr/include] + end of search list found + collapse include dir [/usr/include/c++/9] ==> [/usr/include/c++/9] + collapse include dir [/usr/include/x86_64-linux-gnu/c++/9] ==> [/usr/include/x86_64-linux-gnu/c++/9] + collapse include dir [/usr/include/c++/9/backward] ==> [/usr/include/c++/9/backward] + collapse include dir [/usr/lib/gcc/x86_64-linux-gnu/9/include] ==> [/usr/lib/gcc/x86_64-linux-gnu/9/include] + collapse include dir [/usr/local/include] ==> [/usr/local/include] + collapse include dir [/usr/include/x86_64-linux-gnu] ==> [/usr/include/x86_64-linux-gnu] + collapse include dir [/usr/include] ==> [/usr/include] + implicit include dirs: [/usr/include/c++/9;/usr/include/x86_64-linux-gnu/c++/9;/usr/include/c++/9/backward;/usr/lib/gcc/x86_64-linux-gnu/9/include;/usr/local/include;/usr/include/x86_64-linux-gnu;/usr/include] + + +Parsed CXX implicit link information from above output: + link line regex: [^( *|.*[/\])(ld|CMAKE_LINK_STARTFILE-NOTFOUND|([^/\]+-)?ld|collect2)[^/\]*( |$)] + ignore line: [Change Dir: /home/cococode/flipperzero-firmware/lib/esp-serial-flasher/CMakeFiles/CMakeTmp] + ignore line: [] + ignore line: [Run Build Command(s):/usr/bin/make cmTC_b76ad/fast && /usr/bin/make -f CMakeFiles/cmTC_b76ad.dir/build.make CMakeFiles/cmTC_b76ad.dir/build] + ignore line: [make[1]: Entering directory '/home/cococode/flipperzero-firmware/lib/esp-serial-flasher/CMakeFiles/CMakeTmp'] + ignore line: [Building CXX object CMakeFiles/cmTC_b76ad.dir/CMakeCXXCompilerABI.cpp.o] + ignore line: [/usr/bin/c++ -v -o CMakeFiles/cmTC_b76ad.dir/CMakeCXXCompilerABI.cpp.o -c /usr/share/cmake-3.16/Modules/CMakeCXXCompilerABI.cpp] + ignore line: [Using built-in specs.] + ignore line: [COLLECT_GCC=/usr/bin/c++] + ignore line: [OFFLOAD_TARGET_NAMES=nvptx-none:hsa] + ignore line: [OFFLOAD_TARGET_DEFAULT=1] + ignore line: [Target: x86_64-linux-gnu] + ignore line: [Configured with: ../src/configure -v --with-pkgversion='Ubuntu 9.4.0-1ubuntu1~20.04.1' --with-bugurl=file:///usr/share/doc/gcc-9/README.Bugs --enable-languages=c ada c++ go brig d fortran objc obj-c++ gm2 --prefix=/usr --with-gcc-major-version-only --program-suffix=-9 --program-prefix=x86_64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-plugin --enable-default-pie --with-system-zlib --with-target-system-zlib=auto --enable-objc-gc=auto --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32 m64 mx32 --enable-multilib --with-tune=generic --enable-offload-targets=nvptx-none=/build/gcc-9-Av3uEd/gcc-9-9.4.0/debian/tmp-nvptx/usr hsa --without-cuda-driver --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu] + ignore line: [Thread model: posix] + ignore line: [gcc version 9.4.0 (Ubuntu 9.4.0-1ubuntu1~20.04.1) ] + ignore line: [COLLECT_GCC_OPTIONS='-v' '-o' 'CMakeFiles/cmTC_b76ad.dir/CMakeCXXCompilerABI.cpp.o' '-c' '-shared-libgcc' '-mtune=generic' '-march=x86-64'] + ignore line: [ /usr/lib/gcc/x86_64-linux-gnu/9/cc1plus -quiet -v -imultiarch x86_64-linux-gnu -D_GNU_SOURCE /usr/share/cmake-3.16/Modules/CMakeCXXCompilerABI.cpp -quiet -dumpbase CMakeCXXCompilerABI.cpp -mtune=generic -march=x86-64 -auxbase-strip CMakeFiles/cmTC_b76ad.dir/CMakeCXXCompilerABI.cpp.o -version -fasynchronous-unwind-tables -fstack-protector-strong -Wformat -Wformat-security -fstack-clash-protection -fcf-protection -o /tmp/ccTEwRoV.s] + ignore line: [GNU C++14 (Ubuntu 9.4.0-1ubuntu1~20.04.1) version 9.4.0 (x86_64-linux-gnu)] + ignore line: [ compiled by GNU C version 9.4.0 GMP version 6.2.0 MPFR version 4.0.2 MPC version 1.1.0 isl version isl-0.22.1-GMP] + ignore line: [] + ignore line: [GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072] + ignore line: [ignoring duplicate directory "/usr/include/x86_64-linux-gnu/c++/9"] + ignore line: [ignoring nonexistent directory "/usr/local/include/x86_64-linux-gnu"] + ignore line: [ignoring nonexistent directory "/usr/lib/gcc/x86_64-linux-gnu/9/include-fixed"] + ignore line: [ignoring nonexistent directory "/usr/lib/gcc/x86_64-linux-gnu/9/../../../../x86_64-linux-gnu/include"] + ignore line: [#include "..." search starts here:] + ignore line: [#include <...> search starts here:] + ignore line: [ /usr/include/c++/9] + ignore line: [ /usr/include/x86_64-linux-gnu/c++/9] + ignore line: [ /usr/include/c++/9/backward] + ignore line: [ /usr/lib/gcc/x86_64-linux-gnu/9/include] + ignore line: [ /usr/local/include] + ignore line: [ /usr/include/x86_64-linux-gnu] + ignore line: [ /usr/include] + ignore line: [End of search list.] + ignore line: [GNU C++14 (Ubuntu 9.4.0-1ubuntu1~20.04.1) version 9.4.0 (x86_64-linux-gnu)] + ignore line: [ compiled by GNU C version 9.4.0 GMP version 6.2.0 MPFR version 4.0.2 MPC version 1.1.0 isl version isl-0.22.1-GMP] + ignore line: [] + ignore line: [GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072] + ignore line: [Compiler executable checksum: 65fe925b83d3956b533de4aaba7dace0] + ignore line: [COLLECT_GCC_OPTIONS='-v' '-o' 'CMakeFiles/cmTC_b76ad.dir/CMakeCXXCompilerABI.cpp.o' '-c' '-shared-libgcc' '-mtune=generic' '-march=x86-64'] + ignore line: [ as -v --64 -o CMakeFiles/cmTC_b76ad.dir/CMakeCXXCompilerABI.cpp.o /tmp/ccTEwRoV.s] + ignore line: [GNU assembler version 2.34 (x86_64-linux-gnu) using BFD version (GNU Binutils for Ubuntu) 2.34] + ignore line: [COMPILER_PATH=/usr/lib/gcc/x86_64-linux-gnu/9/:/usr/lib/gcc/x86_64-linux-gnu/9/:/usr/lib/gcc/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/9/:/usr/lib/gcc/x86_64-linux-gnu/] + ignore line: [LIBRARY_PATH=/usr/lib/gcc/x86_64-linux-gnu/9/:/usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/9/../../../../lib/:/lib/x86_64-linux-gnu/:/lib/../lib/:/usr/lib/x86_64-linux-gnu/:/usr/lib/../lib/:/usr/lib/gcc/x86_64-linux-gnu/9/../../../:/lib/:/usr/lib/] + ignore line: [COLLECT_GCC_OPTIONS='-v' '-o' 'CMakeFiles/cmTC_b76ad.dir/CMakeCXXCompilerABI.cpp.o' '-c' '-shared-libgcc' '-mtune=generic' '-march=x86-64'] + ignore line: [Linking CXX executable cmTC_b76ad] + ignore line: [/usr/bin/cmake -E cmake_link_script CMakeFiles/cmTC_b76ad.dir/link.txt --verbose=1] + ignore line: [/usr/bin/c++ -v -rdynamic CMakeFiles/cmTC_b76ad.dir/CMakeCXXCompilerABI.cpp.o -o cmTC_b76ad ] + ignore line: [Using built-in specs.] + ignore line: [COLLECT_GCC=/usr/bin/c++] + ignore line: [COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/9/lto-wrapper] + ignore line: [OFFLOAD_TARGET_NAMES=nvptx-none:hsa] + ignore line: [OFFLOAD_TARGET_DEFAULT=1] + ignore line: [Target: x86_64-linux-gnu] + ignore line: [Configured with: ../src/configure -v --with-pkgversion='Ubuntu 9.4.0-1ubuntu1~20.04.1' --with-bugurl=file:///usr/share/doc/gcc-9/README.Bugs --enable-languages=c ada c++ go brig d fortran objc obj-c++ gm2 --prefix=/usr --with-gcc-major-version-only --program-suffix=-9 --program-prefix=x86_64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-plugin --enable-default-pie --with-system-zlib --with-target-system-zlib=auto --enable-objc-gc=auto --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32 m64 mx32 --enable-multilib --with-tune=generic --enable-offload-targets=nvptx-none=/build/gcc-9-Av3uEd/gcc-9-9.4.0/debian/tmp-nvptx/usr hsa --without-cuda-driver --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu] + ignore line: [Thread model: posix] + ignore line: [gcc version 9.4.0 (Ubuntu 9.4.0-1ubuntu1~20.04.1) ] + ignore line: [COMPILER_PATH=/usr/lib/gcc/x86_64-linux-gnu/9/:/usr/lib/gcc/x86_64-linux-gnu/9/:/usr/lib/gcc/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/9/:/usr/lib/gcc/x86_64-linux-gnu/] + ignore line: [LIBRARY_PATH=/usr/lib/gcc/x86_64-linux-gnu/9/:/usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/9/../../../../lib/:/lib/x86_64-linux-gnu/:/lib/../lib/:/usr/lib/x86_64-linux-gnu/:/usr/lib/../lib/:/usr/lib/gcc/x86_64-linux-gnu/9/../../../:/lib/:/usr/lib/] + ignore line: [COLLECT_GCC_OPTIONS='-v' '-rdynamic' '-o' 'cmTC_b76ad' '-shared-libgcc' '-mtune=generic' '-march=x86-64'] + link line: [ /usr/lib/gcc/x86_64-linux-gnu/9/collect2 -plugin /usr/lib/gcc/x86_64-linux-gnu/9/liblto_plugin.so -plugin-opt=/usr/lib/gcc/x86_64-linux-gnu/9/lto-wrapper -plugin-opt=-fresolution=/tmp/ccnXxdBn.res -plugin-opt=-pass-through=-lgcc_s -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lc -plugin-opt=-pass-through=-lgcc_s -plugin-opt=-pass-through=-lgcc --build-id --eh-frame-hdr -m elf_x86_64 --hash-style=gnu --as-needed -export-dynamic -dynamic-linker /lib64/ld-linux-x86-64.so.2 -pie -z now -z relro -o cmTC_b76ad /usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/Scrt1.o /usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/crti.o /usr/lib/gcc/x86_64-linux-gnu/9/crtbeginS.o -L/usr/lib/gcc/x86_64-linux-gnu/9 -L/usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu -L/usr/lib/gcc/x86_64-linux-gnu/9/../../../../lib -L/lib/x86_64-linux-gnu -L/lib/../lib -L/usr/lib/x86_64-linux-gnu -L/usr/lib/../lib -L/usr/lib/gcc/x86_64-linux-gnu/9/../../.. CMakeFiles/cmTC_b76ad.dir/CMakeCXXCompilerABI.cpp.o -lstdc++ -lm -lgcc_s -lgcc -lc -lgcc_s -lgcc /usr/lib/gcc/x86_64-linux-gnu/9/crtendS.o /usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/crtn.o] + arg [/usr/lib/gcc/x86_64-linux-gnu/9/collect2] ==> ignore + arg [-plugin] ==> ignore + arg [/usr/lib/gcc/x86_64-linux-gnu/9/liblto_plugin.so] ==> ignore + arg [-plugin-opt=/usr/lib/gcc/x86_64-linux-gnu/9/lto-wrapper] ==> ignore + arg [-plugin-opt=-fresolution=/tmp/ccnXxdBn.res] ==> ignore + arg [-plugin-opt=-pass-through=-lgcc_s] ==> ignore + arg [-plugin-opt=-pass-through=-lgcc] ==> ignore + arg [-plugin-opt=-pass-through=-lc] ==> ignore + arg [-plugin-opt=-pass-through=-lgcc_s] ==> ignore + arg [-plugin-opt=-pass-through=-lgcc] ==> ignore + arg [--build-id] ==> ignore + arg [--eh-frame-hdr] ==> ignore + arg [-m] ==> ignore + arg [elf_x86_64] ==> ignore + arg [--hash-style=gnu] ==> ignore + arg [--as-needed] ==> ignore + arg [-export-dynamic] ==> ignore + arg [-dynamic-linker] ==> ignore + arg [/lib64/ld-linux-x86-64.so.2] ==> ignore + arg [-pie] ==> ignore + arg [-znow] ==> ignore + arg [-zrelro] ==> ignore + arg [-o] ==> ignore + arg [cmTC_b76ad] ==> ignore + arg [/usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/Scrt1.o] ==> ignore + arg [/usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/crti.o] ==> ignore + arg [/usr/lib/gcc/x86_64-linux-gnu/9/crtbeginS.o] ==> ignore + arg [-L/usr/lib/gcc/x86_64-linux-gnu/9] ==> dir [/usr/lib/gcc/x86_64-linux-gnu/9] + arg [-L/usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu] ==> dir [/usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu] + arg [-L/usr/lib/gcc/x86_64-linux-gnu/9/../../../../lib] ==> dir [/usr/lib/gcc/x86_64-linux-gnu/9/../../../../lib] + arg [-L/lib/x86_64-linux-gnu] ==> dir [/lib/x86_64-linux-gnu] + arg [-L/lib/../lib] ==> dir [/lib/../lib] + arg [-L/usr/lib/x86_64-linux-gnu] ==> dir [/usr/lib/x86_64-linux-gnu] + arg [-L/usr/lib/../lib] ==> dir [/usr/lib/../lib] + arg [-L/usr/lib/gcc/x86_64-linux-gnu/9/../../..] ==> dir [/usr/lib/gcc/x86_64-linux-gnu/9/../../..] + arg [CMakeFiles/cmTC_b76ad.dir/CMakeCXXCompilerABI.cpp.o] ==> ignore + arg [-lstdc++] ==> lib [stdc++] + arg [-lm] ==> lib [m] + arg [-lgcc_s] ==> lib [gcc_s] + arg [-lgcc] ==> lib [gcc] + arg [-lc] ==> lib [c] + arg [-lgcc_s] ==> lib [gcc_s] + arg [-lgcc] ==> lib [gcc] + arg [/usr/lib/gcc/x86_64-linux-gnu/9/crtendS.o] ==> ignore + arg [/usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/crtn.o] ==> ignore + collapse library dir [/usr/lib/gcc/x86_64-linux-gnu/9] ==> [/usr/lib/gcc/x86_64-linux-gnu/9] + collapse library dir [/usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu] ==> [/usr/lib/x86_64-linux-gnu] + collapse library dir [/usr/lib/gcc/x86_64-linux-gnu/9/../../../../lib] ==> [/usr/lib] + collapse library dir [/lib/x86_64-linux-gnu] ==> [/lib/x86_64-linux-gnu] + collapse library dir [/lib/../lib] ==> [/lib] + collapse library dir [/usr/lib/x86_64-linux-gnu] ==> [/usr/lib/x86_64-linux-gnu] + collapse library dir [/usr/lib/../lib] ==> [/usr/lib] + collapse library dir [/usr/lib/gcc/x86_64-linux-gnu/9/../../..] ==> [/usr/lib] + implicit libs: [stdc++;m;gcc_s;gcc;c;gcc_s;gcc] + implicit dirs: [/usr/lib/gcc/x86_64-linux-gnu/9;/usr/lib/x86_64-linux-gnu;/usr/lib;/lib/x86_64-linux-gnu;/lib] + implicit fwks: [] + + diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/CMakeFiles/cmake.check_cache b/applications/external/esp-flasher/lib/esp-serial-flasher/CMakeFiles/cmake.check_cache new file mode 100644 index 000000000..3dccd7317 --- /dev/null +++ b/applications/external/esp-flasher/lib/esp-serial-flasher/CMakeFiles/cmake.check_cache @@ -0,0 +1 @@ +# This file is generated by cmake for dependency checking of the CMakeCache.txt file diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/CMakeLists.txt b/applications/external/esp-flasher/lib/esp-serial-flasher/CMakeLists.txt new file mode 100644 index 000000000..7c83d2542 --- /dev/null +++ b/applications/external/esp-flasher/lib/esp-serial-flasher/CMakeLists.txt @@ -0,0 +1,119 @@ +cmake_minimum_required(VERSION 3.5) + +set(srcs + src/esp_targets.c + src/md5_hash.c + src/esp_loader.c + src/protocol_common.c +) + +if (DEFINED ESP_PLATFORM) + if (${CONFIG_SERIAL_FLASHER_INTERFACE_UART}) + list(APPEND srcs + src/protocol_uart.c + src/slip.c + port/esp32_port.c + ) + elseif (${CONFIG_SERIAL_FLASHER_INTERFACE_SPI}) + list(APPEND srcs + src/protocol_spi.c + port/esp32_spi_port.c + ) + endif() + # Register component to esp-idf build system + if ("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER "4.0") + # esp_timer component was introduced in v4.2 + set(priv_requires driver) + if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER "4.1") + list(APPEND priv_requires esp_timer) + endif() + + idf_component_register(SRCS ${srcs} + INCLUDE_DIRS include port + PRIV_INCLUDE_DIRS private_include + PRIV_REQUIRES ${priv_requires}) + + set(target ${COMPONENT_LIB}) + component_compile_options(-Wstrict-prototypes) + else() + # Remove when dropping support for IDF 3.3 + set(COMPONENT_SRCS ${srcs}) + set(COMPONENT_ADD_INCLUDEDIRS include port) + set(COMPONENT_PRIV_INCLUDEDIRS private_include) + register_component() + set(target ${COMPONENT_TARGET}) + endif() + +else() + if (NOT DEFINED SERIAL_FLASHER_INTERFACE_UART AND NOT DEFINED SERIAL_FLASHER_INTERFACE_SPI) + set(SERIAL_FLASHER_INTERFACE_UART true) + endif() + + if (DEFINED SERIAL_FLASHER_INTERFACE_UART) + list(APPEND srcs + src/protocol_uart.c + src/slip.c + ) + elseif (DEFINED SERIAL_FLASHER_INTERFACE_SPI) + list(APPEND srcs src/protocol_spi.c) + endif() + + # Create traditional CMake target + add_library(flasher ${srcs}) + + target_include_directories(flasher PUBLIC include port PRIVATE private_include) + + if(PORT STREQUAL "STM32") + target_link_libraries(flasher PUBLIC stm_cube) + target_sources(flasher PRIVATE port/stm32_port.c) + elseif(PORT STREQUAL "RASPBERRY_PI") + find_library(pigpio_LIB pigpio) + target_link_libraries(flasher PUBLIC ${pigpio_LIB}) + target_sources(flasher PRIVATE port/raspberry_port.c) + else() + message(FATAL_ERROR "Selected port is not supported") + endif() + + set(target flasher) + +endif() + +if (DEFINED SERIAL_FLASHER_INTERFACE_UART OR CONFIG_SERIAL_FLASHER_INTERFACE_UART STREQUAL "y") + target_compile_definitions(${target} + PUBLIC + SERIAL_FLASHER_INTERFACE_UART + ) + if (DEFINED MD5_ENABLED OR CONFIG_SERIAL_FLASHER_MD5_ENABLED) + target_compile_definitions(${target} PUBLIC MD5_ENABLED=1) + endif() +elseif (DEFINED SERIAL_FLASHER_INTERFACE_SPI OR CONFIG_SERIAL_FLASHER_INTERFACE_SPI STREQUAL "y") + target_compile_definitions(${target} + PUBLIC + SERIAL_FLASHER_INTERFACE_SPI + ) +endif() + +if(DEFINED SERIAL_FLASHER_DEBUG_TRACE OR CONFIG_SERIAL_FLASHER_DEBUG_TRACE) + target_compile_definitions(${target} PUBLIC SERIAL_FLASHER_DEBUG_TRACE) +endif() + +if(DEFINED CONFIG_SERIAL_FLASHER_RESET_HOLD_TIME_MS AND DEFINED CONFIG_SERIAL_FLASHER_BOOT_HOLD_TIME_MS) + target_compile_definitions(${target} + PUBLIC + SERIAL_FLASHER_RESET_HOLD_TIME_MS=${CONFIG_SERIAL_FLASHER_RESET_HOLD_TIME_MS} + SERIAL_FLASHER_BOOT_HOLD_TIME_MS=${CONFIG_SERIAL_FLASHER_BOOT_HOLD_TIME_MS} + ) +else() + if(NOT DEFINED SERIAL_FLASHER_RESET_HOLD_TIME_MS) + target_compile_definitions(${target} + PUBLIC + SERIAL_FLASHER_RESET_HOLD_TIME_MS=100 + ) + endif() + if(NOT DEFINED SERIAL_FLASHER_BOOT_HOLD_TIME_MS) + target_compile_definitions(${target} + PUBLIC + SERIAL_FLASHER_BOOT_HOLD_TIME_MS=50 + ) + endif() +endif() \ No newline at end of file diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/Kconfig b/applications/external/esp-flasher/lib/esp-serial-flasher/Kconfig new file mode 100644 index 000000000..41d776f11 --- /dev/null +++ b/applications/external/esp-flasher/lib/esp-serial-flasher/Kconfig @@ -0,0 +1,34 @@ +menu "ESP serial flasher" + config SERIAL_FLASHER_MD5_ENABLED + bool "Enable MD5 check" + default y + help + Select this option to enable MD5 hashsum check after flashing. + + choice SERIAL_FLASHER_INTERFACE + prompt "Hardware interface to use for firmware download" + default SERIAL_FLASHER_INTERFACE_UART + help + esp-serial-flasher can work with UART and SPI interfaces. + + config SERIAL_FLASHER_INTERFACE_UART + bool "UART" + + config SERIAL_FLASHER_INTERFACE_SPI + bool "SPI (Only supports downloading to RAM, experimental)" + + endchoice + + config SERIAL_FLASHER_RESET_HOLD_TIME_MS + int "Time for which the reset pin is asserted when doing a hard reset" + default 100 + + config SERIAL_FLASHER_BOOT_HOLD_TIME_MS + int "Time for which the boot pin is asserted when doing a hard reset" + default 50 + + config SERIAL_FLASHER_DEBUG_TRACE + bool "Enable debug tracing output (only transfer data tracing is supported at the time)" + default n + +endmenu diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/idf_component.yml b/applications/external/esp-flasher/lib/esp-serial-flasher/idf_component.yml new file mode 100644 index 000000000..e2166924c --- /dev/null +++ b/applications/external/esp-flasher/lib/esp-serial-flasher/idf_component.yml @@ -0,0 +1,3 @@ +version: "0.2.0" +description: Serial flasher component provides portable library for flashing or loading ram loadble app to Espressif SoCs from other host microcontroller +url: https://github.com/espressif/esp-serial-flasher diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/include/esp_loader.h b/applications/external/esp-flasher/lib/esp-serial-flasher/include/esp_loader.h new file mode 100644 index 000000000..43f4fb9d9 --- /dev/null +++ b/applications/external/esp-flasher/lib/esp-serial-flasher/include/esp_loader.h @@ -0,0 +1,313 @@ +/* Copyright 2020-2023 Espressif Systems (Shanghai) CO LTD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Used for backwards compatibility with the previous API */ +#define esp_loader_change_baudrate esp_loader_change_transmission_rate + +/** + * Macro which can be used to check the error code, + * and return in case the code is not ESP_LOADER_SUCCESS. + */ +#define RETURN_ON_ERROR(x) do { \ + esp_loader_error_t _err_ = (x); \ + if (_err_ != ESP_LOADER_SUCCESS) { \ + return _err_; \ + } \ +} while(0) + +/** + * @brief Error codes + */ +typedef enum { + ESP_LOADER_SUCCESS, /*!< Success */ + ESP_LOADER_ERROR_FAIL, /*!< Unspecified error */ + ESP_LOADER_ERROR_TIMEOUT, /*!< Timeout elapsed */ + ESP_LOADER_ERROR_IMAGE_SIZE, /*!< Image size to flash is larger than flash size */ + ESP_LOADER_ERROR_INVALID_MD5, /*!< Computed and received MD5 does not match */ + ESP_LOADER_ERROR_INVALID_PARAM, /*!< Invalid parameter passed to function */ + ESP_LOADER_ERROR_INVALID_TARGET, /*!< Connected target is invalid */ + ESP_LOADER_ERROR_UNSUPPORTED_CHIP, /*!< Attached chip is not supported */ + ESP_LOADER_ERROR_UNSUPPORTED_FUNC, /*!< Function is not supported on attached target */ + ESP_LOADER_ERROR_INVALID_RESPONSE /*!< Internal error */ +} esp_loader_error_t; + +/** + * @brief Supported targets + */ +typedef enum { + ESP8266_CHIP = 0, + ESP32_CHIP = 1, + ESP32S2_CHIP = 2, + ESP32C3_CHIP = 3, + ESP32S3_CHIP = 4, + ESP32C2_CHIP = 5, + ESP32H4_CHIP = 6, + ESP32H2_CHIP = 7, + ESP_MAX_CHIP = 8, + ESP_UNKNOWN_CHIP = 8 +} target_chip_t; + +/** + * @brief Application binary header + */ +typedef struct { + uint8_t magic; + uint8_t segments; + uint8_t flash_mode; + uint8_t flash_size_freq; + uint32_t entrypoint; +} esp_loader_bin_header_t; + +/** + * @brief Segment binary header + */ +typedef struct { + uint32_t addr; + uint32_t size; + uint8_t *data; +} esp_loader_bin_segment_t; + +/** + * @brief SPI pin configuration arguments + */ +typedef union { + struct { + uint32_t pin_clk: 6; + uint32_t pin_q: 6; + uint32_t pin_d: 6; + uint32_t pin_cs: 6; + uint32_t pin_hd: 6; + uint32_t zero: 2; + }; + uint32_t val; +} esp_loader_spi_config_t; + +/** + * @brief Connection arguments + */ +typedef struct { + uint32_t sync_timeout; /*!< Maximum time to wait for response from serial interface. */ + int32_t trials; /*!< Number of trials to connect to target. If greater than 1, + 100 millisecond delay is inserted after each try. */ +} esp_loader_connect_args_t; + +#define ESP_LOADER_CONNECT_DEFAULT() { \ + .sync_timeout = 100, \ + .trials = 10, \ +} + +/** + * @brief Connects to the target + * + * @param connect_args[in] Timing parameters to be used for connecting to target. + * + * @return + * - ESP_LOADER_SUCCESS Success + * - ESP_LOADER_ERROR_TIMEOUT Timeout + * - ESP_LOADER_ERROR_INVALID_RESPONSE Internal error + */ +esp_loader_error_t esp_loader_connect(esp_loader_connect_args_t *connect_args); + +/** + * @brief Returns attached target chip. + * + * @warning This function can only be called after connection with target + * has been successfully established by calling esp_loader_connect(). + * + * @return One of target_chip_t + */ +target_chip_t esp_loader_get_target(void); + + +#ifdef SERIAL_FLASHER_INTERFACE_UART +/** + * @brief Initiates flash operation + * + * @param offset[in] Address from which flash operation will be performed. + * @param image_size[in] Size of the whole binary to be loaded into flash. + * @param block_size[in] Size of buffer used in subsequent calls to esp_loader_flash_write. + * + * @note image_size is size of the whole image, whereas, block_size is chunk of data sent + * to the target, each time esp_loader_flash_write function is called. + * + * @return + * - ESP_LOADER_SUCCESS Success + * - ESP_LOADER_ERROR_TIMEOUT Timeout + * - ESP_LOADER_ERROR_INVALID_RESPONSE Internal error + */ +esp_loader_error_t esp_loader_flash_start(uint32_t offset, uint32_t image_size, uint32_t block_size); + +/** + * @brief Writes supplied data to target's flash memory. + * + * @param payload[in] Data to be flashed into target's memory. + * @param size[in] Size of payload in bytes. + * + * @note size must not be greater that block_size supplied to previously called + * esp_loader_flash_start function. If size is less than block_size, + * remaining bytes of payload buffer will be padded with 0xff. + * Therefore, size of payload buffer has to be equal or greater than block_size. + * + * @return + * - ESP_LOADER_SUCCESS Success + * - ESP_LOADER_ERROR_TIMEOUT Timeout + * - ESP_LOADER_ERROR_INVALID_RESPONSE Internal error + */ +esp_loader_error_t esp_loader_flash_write(void *payload, uint32_t size); + +/** + * @brief Ends flash operation. + * + * @param reboot[in] reboot the target if true. + * + * @return + * - ESP_LOADER_SUCCESS Success + * - ESP_LOADER_ERROR_TIMEOUT Timeout + * - ESP_LOADER_ERROR_INVALID_RESPONSE Internal error + */ +esp_loader_error_t esp_loader_flash_finish(bool reboot); +#endif /* SERIAL_FLASHER_INTERFACE_UART */ + + +/** + * @brief Initiates mem operation, initiates loading for program into target RAM + * + * @param offset[in] Address from which mem operation will be performed. + * @param size[in] Size of the whole binary to be loaded into mem. + * @param block_size[in] Size of buffer used in subsequent calls to esp_loader_mem_write. + * + * @note image_size is size of the whole image, whereas, block_size is chunk of data sent + * to the target, each time esp_mem_flash_write function is called. + * + * @return + * - ESP_LOADER_SUCCESS Success + * - ESP_LOADER_ERROR_TIMEOUT Timeout + * - ESP_LOADER_ERROR_INVALID_RESPONSE Internal error + */ +esp_loader_error_t esp_loader_mem_start(uint32_t offset, uint32_t size, uint32_t block_size); + + +/** + * @brief Writes supplied data to target's mem memory. + * + * @param payload[in] Data to be loaded into target's memory. + * @param size[in] Size of data in bytes. + * + * @note size must not be greater that block_size supplied to previously called + * esp_loader_mem_start function. + * Therefore, size of data buffer has to be equal or greater than block_size. + * + * @return + * - ESP_LOADER_SUCCESS Success + * - ESP_LOADER_ERROR_TIMEOUT Timeout + * - ESP_LOADER_ERROR_INVALID_RESPONSE Internal error + */ +esp_loader_error_t esp_loader_mem_write(const void *payload, uint32_t size); + + +/** + * @brief Ends mem operation, finish loading for program into target RAM + * and send the entrypoint of ram_loadable app + * + * @param entrypoint[in] entrypoint of ram program. + * + * @return + * - ESP_LOADER_SUCCESS Success + * - ESP_LOADER_ERROR_TIMEOUT Timeout + * - ESP_LOADER_ERROR_INVALID_RESPONSE Internal error + */ +esp_loader_error_t esp_loader_mem_finish(uint32_t entrypoint); + + +/** + * @brief Writes register. + * + * @param address[in] Address of register. + * @param reg_value[in] New register value. + * + * @return + * - ESP_LOADER_SUCCESS Success + * - ESP_LOADER_ERROR_TIMEOUT Timeout + * - ESP_LOADER_ERROR_INVALID_RESPONSE Internal error + */ +esp_loader_error_t esp_loader_write_register(uint32_t address, uint32_t reg_value); + +/** + * @brief Reads register. + * + * @param address[in] Address of register. + * @param reg_value[out] Register value. + * + * @return + * - ESP_LOADER_SUCCESS Success + * - ESP_LOADER_ERROR_TIMEOUT Timeout + * - ESP_LOADER_ERROR_INVALID_RESPONSE Internal error + */ +esp_loader_error_t esp_loader_read_register(uint32_t address, uint32_t *reg_value); + +/** + * @brief Change baud rate. + * + * @note Baud rate has to be also adjusted accordingly on host MCU, as + * target's baud rate is changed upon return from this function. + * + * @param transmission_rate[in] new baud rate to be set. + * + * @return + * - ESP_LOADER_SUCCESS Success + * - ESP_LOADER_ERROR_TIMEOUT Timeout + * - ESP_LOADER_ERROR_INVALID_RESPONSE Internal error + * - ESP_LOADER_ERROR_UNSUPPORTED_FUNC Unsupported on the target + */ +esp_loader_error_t esp_loader_change_transmission_rate(uint32_t transmission_rate); + +/** + * @brief Verify target's flash integrity by checking MD5. + * MD5 checksum is computed from data pushed to target's memory by calling + * esp_loader_flash_write() function and compared against target's MD5. + * Target computes checksum based on offset and image_size passed to + * esp_loader_flash_start() function. + * + * @note This function is only available if MD5_ENABLED is set. + * + * @return + * - ESP_LOADER_SUCCESS Success + * - ESP_LOADER_ERROR_INVALID_MD5 MD5 does not match + * - ESP_LOADER_ERROR_TIMEOUT Timeout + * - ESP_LOADER_ERROR_INVALID_RESPONSE Internal error + * - ESP_LOADER_ERROR_UNSUPPORTED_FUNC Unsupported on the target + */ +#if MD5_ENABLED +esp_loader_error_t esp_loader_flash_verify(void); +#endif +/** + * @brief Toggles reset pin. + */ +void esp_loader_reset_target(void); + + + +#ifdef __cplusplus +} +#endif diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/include/esp_loader_io.h b/applications/external/esp-flasher/lib/esp-serial-flasher/include/esp_loader_io.h new file mode 100644 index 000000000..2ff99b4f9 --- /dev/null +++ b/applications/external/esp-flasher/lib/esp-serial-flasher/include/esp_loader_io.h @@ -0,0 +1,112 @@ +/* Copyright 2020-2023 Espressif Systems (Shanghai) CO LTD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + #pragma once + +#include +#include "esp_loader.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Changes the transmission rate of the used peripheral. + */ +esp_loader_error_t loader_port_change_transmission_rate(uint32_t transmission_rate); + +/** + * @brief Writes data over the io interface. + * + * @param data[in] Buffer with data to be written. + * @param size[in] Size of data in bytes. + * @param timeout[in] Timeout in milliseconds. + * + * @return + * - ESP_LOADER_SUCCESS Success + * - ESP_LOADER_ERROR_TIMEOUT Timeout elapsed + */ +esp_loader_error_t loader_port_write(const uint8_t *data, uint16_t size, uint32_t timeout); + +/** + * @brief Reads data from the io interface. + * + * @param data[out] Buffer into which received data will be written. + * @param size[in] Number of bytes to read. + * @param timeout[in] Timeout in milliseconds. + * + * @return + * - ESP_LOADER_SUCCESS Success + * - ESP_LOADER_ERROR_TIMEOUT Timeout elapsed + */ +esp_loader_error_t loader_port_read(uint8_t *data, uint16_t size, uint32_t timeout); + +/** + * @brief Delay in milliseconds. + * + * @param ms[in] Number of milliseconds. + * + */ +void loader_port_delay_ms(uint32_t ms); + +/** + * @brief Starts timeout timer. + * + * @param ms[in] Number of milliseconds. + * + */ +void loader_port_start_timer(uint32_t ms); + +/** + * @brief Returns remaining time since timer was started by calling esp_loader_start_timer. + * 0 if timer has elapsed. + * + * @return Number of milliseconds. + * + */ +uint32_t loader_port_remaining_time(void); + +/** + * @brief Asserts bootstrap pins to enter boot mode and toggles reset pin. + * + * @note Reset pin should stay asserted for at least 20 milliseconds. + */ +void loader_port_enter_bootloader(void); + +/** + * @brief Toggles reset pin. + * + * @note Reset pin should stay asserted for at least 20 milliseconds. + */ +void loader_port_reset_target(void); + +/** + * @brief Function can be defined by user to print debug message. + * + * @note Empty weak function is used, otherwise. + * + */ +void loader_port_debug_print(const char *str); + +#ifdef SERIAL_FLASHER_INTERFACE_SPI +/** + * @brief Sets the chip select to a defined level + */ +void loader_port_spi_set_cs(uint32_t level); +#endif /* SERIAL_FLASHER_INTERFACE_SPI */ + +#ifdef __cplusplus +} +#endif diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/include/serial_io.h b/applications/external/esp-flasher/lib/esp-serial-flasher/include/serial_io.h new file mode 100644 index 000000000..e34939300 --- /dev/null +++ b/applications/external/esp-flasher/lib/esp-serial-flasher/include/serial_io.h @@ -0,0 +1,24 @@ +/* Copyright 2020-2023 Espressif Systems (Shanghai) CO LTD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#warning Please replace serial_io.h with esp_loader_io.h and change the function names \ + to match the new API + +/* Defines used to avoid breaking existing ports */ +#define loader_port_change_baudrate loader_port_change_transmission_rate +#define loader_port_serial_write loader_port_write +#define loader_port_serial_read loader_port_read + +#include "esp_loader_io.h" diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/port/esp32_port.c b/applications/external/esp-flasher/lib/esp-serial-flasher/port/esp32_port.c new file mode 100644 index 000000000..a6c8687c6 --- /dev/null +++ b/applications/external/esp-flasher/lib/esp-serial-flasher/port/esp32_port.c @@ -0,0 +1,181 @@ +/* Copyright 2020-2023 Espressif Systems (Shanghai) CO LTD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "esp32_port.h" +#include "driver/uart.h" +#include "driver/gpio.h" +#include "esp_timer.h" +#include "esp_log.h" +#include "esp_idf_version.h" +#include + +#ifdef SERIAL_FLASHER_DEBUG_TRACE +static void transfer_debug_print(const uint8_t *data, uint16_t size, bool write) +{ + static bool write_prev = false; + + if (write_prev != write) { + write_prev = write; + printf("\n--- %s ---\n", write ? "WRITE" : "READ"); + } + + for (uint32_t i = 0; i < size; i++) { + printf("%02x ", data[i]); + } +} +#endif + +static int64_t s_time_end; +static int32_t s_uart_port; +static int32_t s_reset_trigger_pin; +static int32_t s_gpio0_trigger_pin; + +esp_loader_error_t loader_port_esp32_init(const loader_esp32_config_t *config) +{ + s_uart_port = config->uart_port; + s_reset_trigger_pin = config->reset_trigger_pin; + s_gpio0_trigger_pin = config->gpio0_trigger_pin; + + // Initialize UART + uart_config_t uart_config = { + .baud_rate = config->baud_rate, + .data_bits = UART_DATA_8_BITS, + .parity = UART_PARITY_DISABLE, + .stop_bits = UART_STOP_BITS_1, + .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) + .source_clk = UART_SCLK_DEFAULT, +#endif + }; + + int rx_buffer_size = config->rx_buffer_size ? config->rx_buffer_size : 400; + int tx_buffer_size = config->tx_buffer_size ? config->tx_buffer_size : 400; + QueueHandle_t *uart_queue = config->uart_queue ? config->uart_queue : NULL; + int queue_size = config->queue_size ? config->queue_size : 0; + + if ( uart_param_config(s_uart_port, &uart_config) != ESP_OK ) { + return ESP_LOADER_ERROR_FAIL; + } + if ( uart_set_pin(s_uart_port, config->uart_tx_pin, config->uart_rx_pin, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE) != ESP_OK ) { + return ESP_LOADER_ERROR_FAIL; + } + if ( uart_driver_install(s_uart_port, rx_buffer_size, tx_buffer_size, queue_size, uart_queue, 0) != ESP_OK ) { + return ESP_LOADER_ERROR_FAIL; + } + + // Initialize boot pin selection pins + gpio_reset_pin(s_reset_trigger_pin); + gpio_set_pull_mode(s_reset_trigger_pin, GPIO_PULLUP_ONLY); + gpio_set_direction(s_reset_trigger_pin, GPIO_MODE_OUTPUT); + + gpio_reset_pin(s_gpio0_trigger_pin); + gpio_set_pull_mode(s_gpio0_trigger_pin, GPIO_PULLUP_ONLY); + gpio_set_direction(s_gpio0_trigger_pin, GPIO_MODE_OUTPUT); + + return ESP_LOADER_SUCCESS; +} + +void loader_port_esp32_deinit(void) +{ + uart_driver_delete(s_uart_port); +} + + +esp_loader_error_t loader_port_write(const uint8_t *data, uint16_t size, uint32_t timeout) +{ + uart_write_bytes(s_uart_port, (const char *)data, size); + esp_err_t err = uart_wait_tx_done(s_uart_port, pdMS_TO_TICKS(timeout)); + + if (err == ESP_OK) { +#ifdef SERIAL_FLASHER_DEBUG_TRACE + transfer_debug_print(data, size, true); +#endif + return ESP_LOADER_SUCCESS; + } else if (err == ESP_ERR_TIMEOUT) { + return ESP_LOADER_ERROR_TIMEOUT; + } else { + return ESP_LOADER_ERROR_FAIL; + } +} + + +esp_loader_error_t loader_port_read(uint8_t *data, uint16_t size, uint32_t timeout) +{ + int read = uart_read_bytes(s_uart_port, data, size, pdMS_TO_TICKS(timeout)); + + if (read < 0) { + return ESP_LOADER_ERROR_FAIL; + } else if (read < size) { +#ifdef SERIAL_FLASHER_DEBUG_TRACE + transfer_debug_print(data, read, false); +#endif + return ESP_LOADER_ERROR_TIMEOUT; + } else { +#ifdef SERIAL_FLASHER_DEBUG_TRACE + transfer_debug_print(data, read, false); +#endif + return ESP_LOADER_SUCCESS; + } +} + + +// Set GPIO0 LOW, then +// assert reset pin for 50 milliseconds. +void loader_port_enter_bootloader(void) +{ + gpio_set_level(s_gpio0_trigger_pin, 0); + loader_port_reset_target(); + loader_port_delay_ms(SERIAL_FLASHER_BOOT_HOLD_TIME_MS); + gpio_set_level(s_gpio0_trigger_pin, 1); +} + + +void loader_port_reset_target(void) +{ + gpio_set_level(s_reset_trigger_pin, 0); + loader_port_delay_ms(SERIAL_FLASHER_RESET_HOLD_TIME_MS); + gpio_set_level(s_reset_trigger_pin, 1); +} + + +void loader_port_delay_ms(uint32_t ms) +{ + usleep(ms * 1000); +} + + +void loader_port_start_timer(uint32_t ms) +{ + s_time_end = esp_timer_get_time() + ms * 1000; +} + + +uint32_t loader_port_remaining_time(void) +{ + int64_t remaining = (s_time_end - esp_timer_get_time()) / 1000; + return (remaining > 0) ? (uint32_t)remaining : 0; +} + + +void loader_port_debug_print(const char *str) +{ + printf("DEBUG: %s\n", str); +} + +esp_loader_error_t loader_port_change_transmission_rate(uint32_t baudrate) +{ + esp_err_t err = uart_set_baudrate(s_uart_port, baudrate); + return (err == ESP_OK) ? ESP_LOADER_SUCCESS : ESP_LOADER_ERROR_FAIL; +} \ No newline at end of file diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/port/esp32_port.h b/applications/external/esp-flasher/lib/esp-serial-flasher/port/esp32_port.h new file mode 100644 index 000000000..c098762d2 --- /dev/null +++ b/applications/external/esp-flasher/lib/esp-serial-flasher/port/esp32_port.h @@ -0,0 +1,59 @@ +/* Copyright 2020-2023 Espressif Systems (Shanghai) CO LTD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + #pragma once + +#include "esp_loader_io.h" +#include "freertos/FreeRTOS.h" +#include "freertos/queue.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct +{ + uint32_t baud_rate; /*!< Initial baud rate, can be changed later */ + uint32_t uart_port; /*!< UART port */ + uint32_t uart_rx_pin; /*!< This pin will be configured as UART Rx pin */ + uint32_t uart_tx_pin; /*!< This pin will be configured as UART Tx pin */ + uint32_t reset_trigger_pin; /*!< This pin will be used to reset target chip */ + uint32_t gpio0_trigger_pin; /*!< This pin will be used to toggle set IO0 of target chip */ + uint32_t rx_buffer_size; /*!< Set to zero for default RX buffer size */ + uint32_t tx_buffer_size; /*!< Set to zero for default TX buffer size */ + uint32_t queue_size; /*!< Set to zero for default UART queue size */ + QueueHandle_t *uart_queue; /*!< Set to NULL, if UART queue handle is not + necessary. Otherwise, it will be assigned here */ +} loader_esp32_config_t; + +/** + * @brief Initializes serial interface. + * + * @param baud_rate[in] Communication speed. + * + * @return + * - ESP_LOADER_SUCCESS Success + * - ESP_LOADER_ERROR_FAIL Initialization failure + */ +esp_loader_error_t loader_port_esp32_init(const loader_esp32_config_t *config); + +/** + * @brief Deinitialize serial interface. + */ +void loader_port_esp32_deinit(void); + +#ifdef __cplusplus +} +#endif diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/port/esp32_spi_port.c b/applications/external/esp-flasher/lib/esp-serial-flasher/port/esp32_spi_port.c new file mode 100644 index 000000000..55e8f3e57 --- /dev/null +++ b/applications/external/esp-flasher/lib/esp-serial-flasher/port/esp32_spi_port.c @@ -0,0 +1,298 @@ +/* Copyright 2020-2023 Espressif Systems (Shanghai) CO LTD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "esp32_spi_port.h" +#include "esp_log.h" +#include "driver/gpio.h" +#include "esp_timer.h" +#include "esp_log.h" +#include "esp_idf_version.h" +#include + +// #define SERIAL_DEBUG_ENABLE + +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 3, 0) +#define DMA_CHAN SPI_DMA_CH_AUTO +#else +#define DMA_CHAN 1 +#endif + +#define WORD_ALIGNED(ptr) ((size_t)ptr % sizeof(size_t) == 0) + +#ifdef SERIAL_DEBUG_ENABLE + +static void dec_to_hex_str(const uint8_t dec, uint8_t hex_str[3]) +{ + static const uint8_t dec_to_hex[] = { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' + }; + + hex_str[0] = dec_to_hex[dec >> 4]; + hex_str[1] = dec_to_hex[dec & 0xF]; + hex_str[2] = '\0'; +} + +static void serial_debug_print(const uint8_t *data, uint16_t size, bool write) +{ + static bool write_prev = false; + uint8_t hex_str[3]; + + if(write_prev != write) { + write_prev = write; + printf("\n--- %s ---\n", write ? "WRITE" : "READ"); + } + + for(uint32_t i = 0; i < size; i++) { + dec_to_hex_str(data[i], hex_str); + printf("%s ", hex_str); + } +} + +#else +static void serial_debug_print(const uint8_t *data, uint16_t size, bool write) { } +#endif + +static spi_host_device_t s_spi_bus; +static spi_bus_config_t s_spi_config; +static spi_device_handle_t s_device_h; +static spi_device_interface_config_t s_device_config; +static int64_t s_time_end; +static uint32_t s_reset_trigger_pin; +static uint32_t s_strap_bit0_pin; +static uint32_t s_strap_bit1_pin; +static uint32_t s_strap_bit2_pin; +static uint32_t s_strap_bit3_pin; +static uint32_t s_spi_cs_pin; + +esp_loader_error_t loader_port_esp32_spi_init(const loader_esp32_spi_config_t *config) +{ + /* Initialize the global static variables*/ + s_spi_bus = config->spi_bus; + s_reset_trigger_pin = config->reset_trigger_pin; + s_strap_bit0_pin = config->strap_bit0_pin; + s_strap_bit1_pin = config->strap_bit1_pin; + s_strap_bit2_pin = config->strap_bit2_pin; + s_strap_bit3_pin = config->strap_bit3_pin; + s_spi_cs_pin = config->spi_cs_pin; + + /* Configure and initialize the SPI bus*/ + s_spi_config.mosi_io_num = config->spi_mosi_pin; + s_spi_config.miso_io_num = config->spi_miso_pin; + s_spi_config.sclk_io_num = config->spi_clk_pin; + s_spi_config.quadwp_io_num = config->spi_quadwp_pin; + s_spi_config.quadhd_io_num = config->spi_quadhd_pin; + s_spi_config.max_transfer_sz = 4096 * 4; + + if (spi_bus_initialize(s_spi_bus, &s_spi_config, DMA_CHAN) != ESP_OK) { + return ESP_LOADER_ERROR_FAIL; + } + + /* Configure and add the device */ + s_device_config.clock_speed_hz = config->frequency; + s_device_config.spics_io_num = -1; /* We're using the chip select pin as GPIO as we need to + chain multiple transactions with CS pulled down */ + s_device_config.flags = SPI_DEVICE_HALFDUPLEX; + s_device_config.queue_size = 16; + + if (spi_bus_add_device(s_spi_bus, &s_device_config, &s_device_h) != ESP_OK) { + return ESP_LOADER_ERROR_FAIL; + } + + /* Initialize the pins except for the strapping ones */ + gpio_reset_pin(s_reset_trigger_pin); + gpio_set_pull_mode(s_reset_trigger_pin, GPIO_PULLUP_ONLY); + gpio_set_direction(s_reset_trigger_pin, GPIO_MODE_OUTPUT); + gpio_set_level(s_reset_trigger_pin, 1); + + gpio_reset_pin(s_spi_cs_pin); + gpio_set_pull_mode(s_spi_cs_pin, GPIO_PULLUP_ONLY); + gpio_set_direction(s_spi_cs_pin, GPIO_MODE_OUTPUT); + gpio_set_level(s_spi_cs_pin, 1); + + return ESP_LOADER_SUCCESS; +} + + +void loader_port_esp32_spi_deinit(void) +{ + gpio_reset_pin(s_reset_trigger_pin); + gpio_reset_pin(s_spi_cs_pin); + spi_bus_remove_device(s_device_h); + spi_bus_free(s_spi_bus); +} + + +void loader_port_spi_set_cs(const uint32_t level) { + gpio_set_level(s_spi_cs_pin, level); +} + + +esp_loader_error_t loader_port_write(const uint8_t *data, const uint16_t size, const uint32_t timeout) +{ + /* Due to the fact that the SPI driver uses DMA for larger transfers, + and the DMA requirements, the buffer must be word aligned */ + if (data == NULL || !WORD_ALIGNED(data)) { + return ESP_LOADER_ERROR_INVALID_PARAM; + } + + serial_debug_print(data, size, true); + + spi_transaction_t transaction = { + .tx_buffer = data, + .rx_buffer = NULL, + .length = size * 8U, + .rxlength = 0, + }; + + esp_err_t err = spi_device_transmit(s_device_h, &transaction); + + if (err == ESP_OK) { + serial_debug_print(data, size, false); + return ESP_LOADER_SUCCESS; + } else if (err == ESP_ERR_TIMEOUT) { + return ESP_LOADER_ERROR_TIMEOUT; + } else { + return ESP_LOADER_ERROR_FAIL; + } +} + + +esp_loader_error_t loader_port_read(uint8_t *data, const uint16_t size, const uint32_t timeout) +{ + /* Due to the fact that the SPI driver uses DMA for larger transfers, + and the DMA requirements, the buffer must be word aligned */ + if (data == NULL || !WORD_ALIGNED(data)) { + return ESP_LOADER_ERROR_INVALID_PARAM; + } + + serial_debug_print(data, size, true); + + spi_transaction_t transaction = { + .tx_buffer = NULL, + .rx_buffer = data, + .rxlength = size * 8, + }; + + esp_err_t err = spi_device_transmit(s_device_h, &transaction); + + if (err == ESP_OK) { + serial_debug_print(data, size, false); + return ESP_LOADER_SUCCESS; + } else if (err == ESP_ERR_TIMEOUT) { + return ESP_LOADER_ERROR_TIMEOUT; + } else { + return ESP_LOADER_ERROR_FAIL; + } +} + + +void loader_port_enter_bootloader(void) +{ + /* + We have to initialize the GPIO pins for the target strapping pins here, + as they may overlap with target SPI pins. + For instance in the case of ESP32C3 MISO and strapping bit 0 pins overlap. + */ + spi_bus_remove_device(s_device_h); + spi_bus_free(s_spi_bus); + + gpio_reset_pin(s_strap_bit0_pin); + gpio_set_pull_mode(s_strap_bit0_pin, GPIO_PULLUP_ONLY); + gpio_set_direction(s_strap_bit0_pin, GPIO_MODE_OUTPUT); + + gpio_reset_pin(s_strap_bit1_pin); + gpio_set_pull_mode(s_strap_bit1_pin, GPIO_PULLUP_ONLY); + gpio_set_direction(s_strap_bit1_pin, GPIO_MODE_OUTPUT); + + gpio_reset_pin(s_strap_bit2_pin); + gpio_set_pull_mode(s_strap_bit2_pin, GPIO_PULLUP_ONLY); + gpio_set_direction(s_strap_bit2_pin, GPIO_MODE_OUTPUT); + + gpio_reset_pin(s_strap_bit3_pin); + gpio_set_pull_mode(s_strap_bit3_pin, GPIO_PULLUP_ONLY); + gpio_set_direction(s_strap_bit3_pin, GPIO_MODE_OUTPUT); + + /* Set the strapping pins and perform the reset sequence */ + gpio_set_level(s_strap_bit0_pin, 1); + gpio_set_level(s_strap_bit1_pin, 0); + gpio_set_level(s_strap_bit2_pin, 0); + gpio_set_level(s_strap_bit3_pin, 0); + loader_port_reset_target(); + loader_port_delay_ms(SERIAL_FLASHER_BOOT_HOLD_TIME_MS); + gpio_set_level(s_strap_bit3_pin, 1); + gpio_set_level(s_strap_bit0_pin, 0); + + /* Disable the strapping pins so they can be used by the slave later */ + gpio_reset_pin(s_strap_bit0_pin); + gpio_reset_pin(s_strap_bit1_pin); + gpio_reset_pin(s_strap_bit2_pin); + gpio_reset_pin(s_strap_bit3_pin); + + /* Restore the SPI bus pins */ + spi_bus_initialize(s_spi_bus, &s_spi_config, DMA_CHAN); + spi_bus_add_device(s_spi_bus, &s_device_config, &s_device_h); +} + + +void loader_port_reset_target(void) +{ + gpio_set_level(s_reset_trigger_pin, 0); + loader_port_delay_ms(SERIAL_FLASHER_RESET_HOLD_TIME_MS); + gpio_set_level(s_reset_trigger_pin, 1); +} + + +void loader_port_delay_ms(const uint32_t ms) +{ + usleep(ms * 1000); +} + + +void loader_port_start_timer(const uint32_t ms) +{ + s_time_end = esp_timer_get_time() + ms * 1000; +} + + +uint32_t loader_port_remaining_time(void) +{ + int64_t remaining = (s_time_end - esp_timer_get_time()) / 1000; + return (remaining > 0) ? (uint32_t)remaining : 0; +} + + +void loader_port_debug_print(const char *str) +{ + printf("DEBUG: %s\n", str); +} + + +esp_loader_error_t loader_port_change_transmission_rate(const uint32_t frequency) +{ + if (spi_bus_remove_device(s_device_h) != ESP_OK) { + return ESP_LOADER_ERROR_FAIL; + } + + uint32_t old_frequency = s_device_config.clock_speed_hz; + s_device_config.clock_speed_hz = frequency; + + if (spi_bus_add_device(s_spi_bus, &s_device_config, &s_device_h) != ESP_OK) { + s_device_config.clock_speed_hz = old_frequency; + return ESP_LOADER_ERROR_FAIL; + } + + return ESP_LOADER_SUCCESS; +} diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/port/esp32_spi_port.h b/applications/external/esp-flasher/lib/esp-serial-flasher/port/esp32_spi_port.h new file mode 100644 index 000000000..72304c5df --- /dev/null +++ b/applications/external/esp-flasher/lib/esp-serial-flasher/port/esp32_spi_port.h @@ -0,0 +1,60 @@ +/* Copyright 2020-2023 Espressif Systems (Shanghai) CO LTD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + #pragma once + +#include "esp_loader_io.h" +#include "driver/spi_master.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct +{ + spi_host_device_t spi_bus; + uint32_t frequency; + uint32_t spi_clk_pin; + uint32_t spi_miso_pin; + uint32_t spi_mosi_pin; + uint32_t spi_cs_pin; + uint32_t spi_quadwp_pin; + uint32_t spi_quadhd_pin; + uint32_t reset_trigger_pin; + uint32_t strap_bit0_pin; + uint32_t strap_bit1_pin; + uint32_t strap_bit2_pin; + uint32_t strap_bit3_pin; +} loader_esp32_spi_config_t; + +/** + * @brief Initializes the SPI interface. + * + * @param config[in] Configuration structure + * + * @return + * - ESP_LOADER_SUCCESS Success + * - ESP_LOADER_ERROR_FAIL Initialization failure + */ +esp_loader_error_t loader_port_esp32_spi_init(const loader_esp32_spi_config_t *config); + +/** + * @brief Deinitializes the SPI interface. + */ +void loader_port_esp32_spi_deinit(void); + +#ifdef __cplusplus +} +#endif diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/port/raspberry_port.c b/applications/external/esp-flasher/lib/esp-serial-flasher/port/raspberry_port.c new file mode 100644 index 000000000..d733db702 --- /dev/null +++ b/applications/external/esp-flasher/lib/esp-serial-flasher/port/raspberry_port.c @@ -0,0 +1,302 @@ +/* Copyright 2020-2023 Espressif Systems (Shanghai) CO LTD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "esp_loader_io.h" +#include "protocol.h" +#include +#include "raspberry_port.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef SERIAL_FLASHER_DEBUG_TRACE +static void transfer_debug_print(const uint8_t *data, uint16_t size, bool write) +{ + static bool write_prev = false; + + if (write_prev != write) { + write_prev = write; + printf("\n--- %s ---\n", write ? "WRITE" : "READ"); + } + + for (uint32_t i = 0; i < size; i++) { + printf("%02x ", data[i]); + } +} +#endif + +static int serial; +static int64_t s_time_end; +static int32_t s_reset_trigger_pin; +static int32_t s_gpio0_trigger_pin; + + +static speed_t convert_baudrate(int baud) +{ + switch (baud) { + case 50: return B50; + case 75: return B75; + case 110: return B110; + case 134: return B134; + case 150: return B150; + case 200: return B200; + case 300: return B300; + case 600: return B600; + case 1200: return B1200; + case 1800: return B1800; + case 2400: return B2400; + case 4800: return B4800; + case 9600: return B9600; + case 19200: return B19200; + case 38400: return B38400; + case 57600: return B57600; + case 115200: return B115200; + case 230400: return B230400; + case 460800: return B460800; + case 500000: return B500000; + case 576000: return B576000; + case 921600: return B921600; + case 1000000: return B1000000; + case 1152000: return B1152000; + case 1500000: return B1500000; + case 2000000: return B2000000; + case 2500000: return B2500000; + case 3000000: return B3000000; + case 3500000: return B3500000; + case 4000000: return B4000000; + default: return -1; + } +} + +static int serialOpen (const char *device, uint32_t baudrate) +{ + struct termios options; + int status, fd; + + if ((fd = open (device, O_RDWR | O_NOCTTY | O_NDELAY | O_NONBLOCK)) == -1) { + printf("Error occured while opening serial port !\n"); + return -1 ; + } + + fcntl (fd, F_SETFL, O_RDWR) ; + + // Get and modify current options: + + tcgetattr (fd, &options); + speed_t baud = convert_baudrate(baudrate); + + if(baud < 0) { + printf("Invalid baudrate!\n"); + return -1; + } + + cfmakeraw (&options) ; + cfsetispeed (&options, baud) ; + cfsetospeed (&options, baud) ; + + options.c_cflag |= (CLOCAL | CREAD) ; + options.c_cflag &= ~(PARENB | CSTOPB | CSIZE) ; + options.c_cflag |= CS8 ; + options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG) ; + options.c_oflag &= ~OPOST ; + options.c_iflag &= ~(IXON | IXOFF | IXANY); // Turn off s/w flow ctrl + options.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL); // Disable any special handling of received bytes + + options.c_cc [VMIN] = 0 ; + options.c_cc [VTIME] = 10 ; // 1 Second + + tcsetattr (fd, TCSANOW, &options) ; + + ioctl (fd, TIOCMGET, &status); + + status |= TIOCM_DTR ; + status |= TIOCM_RTS ; + + ioctl (fd, TIOCMSET, &status); + + usleep (10000) ; // 10mS + + return fd ; +} + +static esp_loader_error_t change_baudrate(int file_desc, int baudrate) +{ + struct termios options; + speed_t baud = convert_baudrate(baudrate); + + if(baud < 0) { + return ESP_LOADER_ERROR_INVALID_PARAM; + } + + tcgetattr (file_desc, &options); + + cfmakeraw (&options) ; + cfsetispeed (&options, baud); + cfsetospeed (&options, baud); + + tcsetattr (file_desc, TCSANOW, &options); + + return ESP_LOADER_SUCCESS; +} + +static void set_timeout(uint32_t timeout) +{ + struct termios options; + + timeout /= 100; + timeout = MAX(timeout, 1); + + tcgetattr(serial, &options); + options.c_cc[VTIME] = timeout; + tcsetattr(serial, TCSANOW, &options); +} + +static esp_loader_error_t read_char(char *c, uint32_t timeout) +{ + set_timeout(timeout); + int read_bytes = read(serial, c, 1); + + if (read_bytes == 1) { + return ESP_LOADER_SUCCESS; + } else if (read_bytes == 0) { + return ESP_LOADER_ERROR_TIMEOUT; + } else { + return ESP_LOADER_ERROR_FAIL; + } +} + +static esp_loader_error_t read_data(char *buffer, uint32_t size) +{ + for (int i = 0; i < size; i++) { + uint32_t remaining_time = loader_port_remaining_time(); + RETURN_ON_ERROR( read_char(&buffer[i], remaining_time) ); + } + + return ESP_LOADER_SUCCESS; +} + +esp_loader_error_t loader_port_raspberry_init(const loader_raspberry_config_t *config) +{ + s_reset_trigger_pin = config->reset_trigger_pin; + s_gpio0_trigger_pin = config->gpio0_trigger_pin; + + serial = serialOpen(config->device, config->baudrate); + if (serial < 0) { + printf("Serial port could not be opened!\n"); + return ESP_LOADER_ERROR_FAIL; + } + + if (gpioInitialise() < 0) { + printf("pigpio initialisation failed\n"); + return ESP_LOADER_ERROR_FAIL; + } + + gpioSetMode(config->reset_trigger_pin, PI_OUTPUT); + gpioSetMode(config->gpio0_trigger_pin, PI_OUTPUT); + + return ESP_LOADER_SUCCESS; +} + + +esp_loader_error_t loader_port_write(const uint8_t *data, uint16_t size, uint32_t timeout) +{ + int written = write(serial, data, size); + + if (written < 0) { + return ESP_LOADER_ERROR_FAIL; + } else if (written < size) { +#ifdef SERIAL_FLASHER_DEBUG_TRACE + transfer_debug_print(data, written, true); +#endif + return ESP_LOADER_ERROR_TIMEOUT; + } else { +#ifdef SERIAL_FLASHER_DEBUG_TRACE + transfer_debug_print(data, written, true); +#endif + return ESP_LOADER_SUCCESS; + } +} + + +esp_loader_error_t loader_port_read(uint8_t *data, uint16_t size, uint32_t timeout) +{ + RETURN_ON_ERROR( read_data(data, size) ); + +#ifdef SERIAL_FLASHER_DEBUG_TRACE + transfer_debug_print(data, size, false); +#endif + + return ESP_LOADER_SUCCESS; +} + + +// Set GPIO0 LOW, then assert reset pin for 50 milliseconds. +void loader_port_enter_bootloader(void) +{ + gpioWrite(s_gpio0_trigger_pin, 0); + loader_port_reset_target(); + loader_port_delay_ms(SERIAL_FLASHER_BOOT_HOLD_TIME_MS); + gpioWrite(s_gpio0_trigger_pin, 1); +} + + +void loader_port_reset_target(void) +{ + gpioWrite(s_reset_trigger_pin, 0); + loader_port_delay_ms(SERIAL_FLASHER_RESET_HOLD_TIME_MS); + gpioWrite(s_reset_trigger_pin, 1); +} + + +void loader_port_delay_ms(uint32_t ms) +{ + usleep(ms * 1000); +} + + +void loader_port_start_timer(uint32_t ms) +{ + s_time_end = clock() + (ms * (CLOCKS_PER_SEC / 1000)); +} + + +uint32_t loader_port_remaining_time(void) +{ + int64_t remaining = (s_time_end - clock()) / 1000; + return (remaining > 0) ? (uint32_t)remaining : 0; +} + + +void loader_port_debug_print(const char *str) +{ + printf("DEBUG: %s\n", str); +} + +esp_loader_error_t loader_port_change_transmission_rate(uint32_t baudrate) +{ + return change_baudrate(serial, baudrate); +} \ No newline at end of file diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/port/raspberry_port.h b/applications/external/esp-flasher/lib/esp-serial-flasher/port/raspberry_port.h new file mode 100644 index 000000000..803ab72dd --- /dev/null +++ b/applications/external/esp-flasher/lib/esp-serial-flasher/port/raspberry_port.h @@ -0,0 +1,36 @@ +/* Copyright 2020-2023 Espressif Systems (Shanghai) CO LTD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include "esp_loader_io.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + const char *device; + uint32_t baudrate; + uint32_t reset_trigger_pin; + uint32_t gpio0_trigger_pin; +} loader_raspberry_config_t; + +esp_loader_error_t loader_port_raspberry_init(const loader_raspberry_config_t *config); + +#ifdef __cplusplus +} +#endif diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/port/stm32_port.c b/applications/external/esp-flasher/lib/esp-serial-flasher/port/stm32_port.c new file mode 100644 index 000000000..716c9c91b --- /dev/null +++ b/applications/external/esp-flasher/lib/esp-serial-flasher/port/stm32_port.c @@ -0,0 +1,140 @@ +/* Copyright 2020-2023 Espressif Systems (Shanghai) CO LTD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include "stm32_port.h" + +static UART_HandleTypeDef *uart; +static GPIO_TypeDef* gpio_port_io0, *gpio_port_rst; +static uint16_t gpio_num_io0, gpio_num_rst; + +#ifdef SERIAL_FLASHER_DEBUG_TRACE +static void transfer_debug_print(const uint8_t *data, uint16_t size, bool write) +{ + static bool write_prev = false; + + if (write_prev != write) { + write_prev = write; + printf("\n--- %s ---\n", write ? "WRITE" : "READ"); + } + + for (uint32_t i = 0; i < size; i++) { + printf("%02x ", data[i]); + } +} +#endif + +static uint32_t s_time_end; + +esp_loader_error_t loader_port_write(const uint8_t *data, uint16_t size, uint32_t timeout) +{ + HAL_StatusTypeDef err = HAL_UART_Transmit(uart, (uint8_t *)data, size, timeout); + + if (err == HAL_OK) { +#ifdef SERIAL_FLASHER_DEBUG_TRACE + transfer_debug_print(data, size, true); +#endif + return ESP_LOADER_SUCCESS; + } else if (err == HAL_TIMEOUT) { + return ESP_LOADER_ERROR_TIMEOUT; + } else { + return ESP_LOADER_ERROR_FAIL; + } +} + + +esp_loader_error_t loader_port_read(uint8_t *data, uint16_t size, uint32_t timeout) +{ + HAL_StatusTypeDef err = HAL_UART_Receive(uart, data, size, timeout); + + if (err == HAL_OK) { +#ifdef SERIAL_FLASHER_DEBUG_TRACE + transfer_debug_print(data, size, false); +#endif + return ESP_LOADER_SUCCESS; + } else if (err == HAL_TIMEOUT) { + return ESP_LOADER_ERROR_TIMEOUT; + } else { + return ESP_LOADER_ERROR_FAIL; + } +} + +void loader_port_stm32_init(loader_stm32_config_t *config) + +{ + uart = config->huart; + gpio_port_io0 = config->port_io0; + gpio_port_rst = config->port_rst; + gpio_num_io0 = config->pin_num_io0; + gpio_num_rst = config->pin_num_rst; +} + +// Set GPIO0 LOW, then +// assert reset pin for 100 milliseconds. +void loader_port_enter_bootloader(void) +{ + HAL_GPIO_WritePin(gpio_port_io0, gpio_num_io0, GPIO_PIN_RESET); + loader_port_reset_target(); + HAL_Delay(SERIAL_FLASHER_BOOT_HOLD_TIME_MS); + HAL_GPIO_WritePin(gpio_port_io0, gpio_num_io0, GPIO_PIN_SET); +} + + +void loader_port_reset_target(void) +{ + HAL_GPIO_WritePin(gpio_port_rst, gpio_num_rst, GPIO_PIN_RESET); + HAL_Delay(SERIAL_FLASHER_RESET_HOLD_TIME_MS); + HAL_GPIO_WritePin(gpio_port_rst, gpio_num_rst, GPIO_PIN_SET); +} + + +void loader_port_delay_ms(uint32_t ms) +{ + HAL_Delay(ms); +} + + +void loader_port_start_timer(uint32_t ms) +{ + s_time_end = HAL_GetTick() + ms; +} + + +uint32_t loader_port_remaining_time(void) +{ + int32_t remaining = s_time_end - HAL_GetTick(); + return (remaining > 0) ? (uint32_t)remaining : 0; +} + + +void loader_port_debug_print(const char *str) +{ + printf("DEBUG: %s", str); +} + +esp_loader_error_t loader_port_change_transmission_rate(uint32_t baudrate) +{ + uart->Init.BaudRate = baudrate; + + if( HAL_UART_Init(uart) != HAL_OK ) { + return ESP_LOADER_ERROR_FAIL; + } + + return ESP_LOADER_SUCCESS; +} diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/port/stm32_port.h b/applications/external/esp-flasher/lib/esp-serial-flasher/port/stm32_port.h new file mode 100644 index 000000000..a3a89a733 --- /dev/null +++ b/applications/external/esp-flasher/lib/esp-serial-flasher/port/stm32_port.h @@ -0,0 +1,38 @@ +/* Copyright 2020-2023 Espressif Systems (Shanghai) CO LTD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include "esp_loader_io.h" +#include "stm32f4xx_hal.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + UART_HandleTypeDef *huart; + GPIO_TypeDef *port_io0; + uint16_t pin_num_io0; + GPIO_TypeDef *port_rst; + uint16_t pin_num_rst; +} loader_stm32_config_t; + +void loader_port_stm32_init(loader_stm32_config_t *config); + +#ifdef __cplusplus +} +#endif diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/port/zephyr_port.c b/applications/external/esp-flasher/lib/esp-serial-flasher/port/zephyr_port.c new file mode 100644 index 000000000..21270ba0a --- /dev/null +++ b/applications/external/esp-flasher/lib/esp-serial-flasher/port/zephyr_port.c @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2022 KT-Elektronik, Klaucke und Partner GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "zephyr_port.h" +#include +#include + +static const struct device *uart_dev; +static struct gpio_dt_spec enable_spec; +static struct gpio_dt_spec boot_spec; + +static struct tty_serial tty; +static char tty_rx_buf[CONFIG_ESP_SERIAL_FLASHER_UART_BUFSIZE]; +static char tty_tx_buf[CONFIG_ESP_SERIAL_FLASHER_UART_BUFSIZE]; + +#ifdef SERIAL_FLASHER_DEBUG_TRACE +static void transfer_debug_print(const uint8_t *data, uint16_t size, bool write) +{ + static bool write_prev = false; + + if (write_prev != write) { + write_prev = write; + printk("\n--- %s ---\n", write ? "WRITE" : "READ"); + } + + for (uint32_t i = 0; i < size; i++) { + printk("%02x ", data[i]); + } +} +#endif + +esp_loader_error_t configure_tty() +{ + if (tty_init(&tty, uart_dev) < 0 || + tty_set_rx_buf(&tty, tty_rx_buf, sizeof(tty_rx_buf)) < 0 || + tty_set_tx_buf(&tty, tty_tx_buf, sizeof(tty_tx_buf)) < 0) { + return ESP_LOADER_ERROR_FAIL; + } + + return ESP_LOADER_SUCCESS; +} + +esp_loader_error_t loader_port_read(uint8_t *data, const uint16_t size, const uint32_t timeout) +{ + if (!device_is_ready(uart_dev) || data == NULL || size == 0) { + return ESP_LOADER_ERROR_FAIL; + } + + ssize_t total_read = 0; + ssize_t remaining = size; + + tty_set_rx_timeout(&tty, timeout); + while (remaining > 0) { + const uint16_t chunk_size = remaining < CONFIG_ESP_SERIAL_FLASHER_UART_BUFSIZE ? + remaining : CONFIG_ESP_SERIAL_FLASHER_UART_BUFSIZE; + ssize_t read = tty_read(&tty, &data[total_read], chunk_size); + if (read < 0) { + return ESP_LOADER_ERROR_TIMEOUT; + } +#ifdef SERIAL_FLASHER_DEBUG_TRACE + transfer_debug_print(data, read, false); +#endif + total_read += read; + remaining -= read; + } + + return ESP_LOADER_SUCCESS; +} + +esp_loader_error_t loader_port_write(const uint8_t *data, const uint16_t size, const uint32_t timeout) +{ + if (!device_is_ready(uart_dev) || data == NULL || size == 0) { + return ESP_LOADER_ERROR_FAIL; + } + + ssize_t total_written = 0; + ssize_t remaining = size; + + tty_set_tx_timeout(&tty, timeout); + while (remaining > 0) { + const uint16_t chunk_size = remaining < CONFIG_ESP_SERIAL_FLASHER_UART_BUFSIZE ? + remaining : CONFIG_ESP_SERIAL_FLASHER_UART_BUFSIZE; + ssize_t written = tty_write(&tty, &data[total_written], chunk_size); + if (written < 0) { + return ESP_LOADER_ERROR_TIMEOUT; + } +#ifdef SERIAL_FLASHER_DEBUG_TRACE + transfer_debug_print(data, written, true); +#endif + total_written += written; + remaining -= written; + } + + return ESP_LOADER_SUCCESS; +} + +esp_loader_error_t loader_port_zephyr_init(const loader_zephyr_config_t *config) +{ + uart_dev = config->uart_dev; + enable_spec = config->enable_spec; + boot_spec = config->boot_spec; + return configure_tty(); +} + +void loader_port_reset_target(void) +{ + gpio_pin_set_dt(&enable_spec, false); + loader_port_delay_ms(CONFIG_SERIAL_FLASHER_RESET_HOLD_TIME_MS); + gpio_pin_set_dt(&enable_spec, true); +} + +void loader_port_enter_bootloader(void) +{ + gpio_pin_set_dt(&boot_spec, false); + loader_port_reset_target(); + loader_port_delay_ms(CONFIG_SERIAL_FLASHER_BOOT_HOLD_TIME_MS); + gpio_pin_set_dt(&boot_spec, true); +} + +void loader_port_delay_ms(uint32_t ms) +{ + k_msleep(ms); +} + +static uint64_t s_time_end; + +void loader_port_start_timer(uint32_t ms) +{ + s_time_end = sys_clock_timeout_end_calc(Z_TIMEOUT_MS(ms)); +} + +uint32_t loader_port_remaining_time(void) +{ + int64_t remaining = k_ticks_to_ms_floor64(s_time_end - k_uptime_ticks()); + return (remaining > 0) ? (uint32_t)remaining : 0; +} + +esp_loader_error_t loader_port_change_transmission_rate(uint32_t baudrate) +{ + struct uart_config uart_config; + + if (!device_is_ready(uart_dev)) { + return ESP_LOADER_ERROR_FAIL; + } + + if (uart_config_get(uart_dev, &uart_config) != 0) { + return ESP_LOADER_ERROR_FAIL; + } + uart_config.baudrate = baudrate; + + if (uart_configure(uart_dev, &uart_config) != 0) { + return ESP_LOADER_ERROR_FAIL; + } + + /* bitrate-change can require tty re-configuration */ + return configure_tty(); +} diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/port/zephyr_port.h b/applications/external/esp-flasher/lib/esp-serial-flasher/port/zephyr_port.h new file mode 100644 index 000000000..0b104e375 --- /dev/null +++ b/applications/external/esp-flasher/lib/esp-serial-flasher/port/zephyr_port.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2022 KT-Elektronik, Klaucke und Partner GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "esp_loader_io.h" +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + const struct device *uart_dev; + const struct gpio_dt_spec enable_spec; + const struct gpio_dt_spec boot_spec; +} loader_zephyr_config_t; + +esp_loader_error_t loader_port_zephyr_init(const loader_zephyr_config_t *config); + +#ifdef __cplusplus +} +#endif + diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/private_include/esp_targets.h b/applications/external/esp-flasher/lib/esp-serial-flasher/private_include/esp_targets.h new file mode 100644 index 000000000..cf8af91fa --- /dev/null +++ b/applications/external/esp-flasher/lib/esp-serial-flasher/private_include/esp_targets.h @@ -0,0 +1,33 @@ +/* Copyright 2020 Espressif Systems (Shanghai) PTE LTD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include "esp_loader.h" + +typedef struct { + uint32_t cmd; + uint32_t usr; + uint32_t usr1; + uint32_t usr2; + uint32_t w0; + uint32_t mosi_dlen; + uint32_t miso_dlen; +} target_registers_t; + +esp_loader_error_t loader_detect_chip(target_chip_t *target, const target_registers_t **regs); +esp_loader_error_t loader_read_spi_config(target_chip_t target_chip, uint32_t *spi_config); +bool encryption_in_begin_flash_cmd(target_chip_t target); \ No newline at end of file diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/private_include/md5_hash.h b/applications/external/esp-flasher/lib/esp-serial-flasher/private_include/md5_hash.h new file mode 100644 index 000000000..eb5738c8b --- /dev/null +++ b/applications/external/esp-flasher/lib/esp-serial-flasher/private_include/md5_hash.h @@ -0,0 +1,28 @@ +/* + * MD5 hash implementation and interface functions + * Copyright (c) 2003-2005, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif +struct MD5Context { + uint32_t buf[4]; + uint32_t bits[2]; + uint8_t in[64]; +}; + +void MD5Init(struct MD5Context *context); +void MD5Update(struct MD5Context *context, unsigned char const *buf, unsigned len); +void MD5Final(unsigned char digest[16], struct MD5Context *context); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/private_include/protocol.h b/applications/external/esp-flasher/lib/esp-serial-flasher/private_include/protocol.h new file mode 100644 index 000000000..fb8b086fd --- /dev/null +++ b/applications/external/esp-flasher/lib/esp-serial-flasher/private_include/protocol.h @@ -0,0 +1,230 @@ +/* Copyright 2020-2023 Espressif Systems (Shanghai) CO LTD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include "esp_loader.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define STATUS_FAILURE 1 +#define STATUS_SUCCESS 0 + +#define READ_DIRECTION 1 +#define WRITE_DIRECTION 0 + +#define MD5_SIZE 32 + +typedef enum __attribute__((packed)) +{ + FLASH_BEGIN = 0x02, + FLASH_DATA = 0x03, + FLASH_END = 0x04, + MEM_BEGIN = 0x05, + MEM_END = 0x06, + MEM_DATA = 0x07, + SYNC = 0x08, + WRITE_REG = 0x09, + READ_REG = 0x0a, + + SPI_SET_PARAMS = 0x0b, + SPI_ATTACH = 0x0d, + CHANGE_BAUDRATE = 0x0f, + FLASH_DEFL_BEGIN = 0x10, + FLASH_DEFL_DATA = 0x11, + FLASH_DEFL_END = 0x12, + SPI_FLASH_MD5 = 0x13, +} command_t; + +typedef enum __attribute__((packed)) +{ + RESPONSE_OK = 0x00, + INVALID_COMMAND = 0x05, // parameters or length field is invalid + COMMAND_FAILED = 0x06, // Failed to act on received message + INVALID_CRC = 0x07, // Invalid CRC in message + FLASH_WRITE_ERR = 0x08, // After writing a block of data to flash, the ROM loader reads the value back and the 8-bit CRC is compared to the data read from flash. If they don't match, this error is returned. + FLASH_READ_ERR = 0x09, // SPI read failed + READ_LENGTH_ERR = 0x0a, // SPI read request length is too long + DEFLATE_ERROR = 0x0b, // ESP32 compressed uploads only +} error_code_t; + +typedef struct __attribute__((packed)) +{ + uint8_t direction; + uint8_t command; // One of command_t + uint16_t size; + uint32_t checksum; +} command_common_t; + +typedef struct __attribute__((packed)) +{ + command_common_t common; + uint32_t erase_size; + uint32_t packet_count; + uint32_t packet_size; + uint32_t offset; + uint32_t encrypted; +} flash_begin_command_t; + +typedef struct __attribute__((packed)) +{ + command_common_t common; + uint32_t data_size; + uint32_t sequence_number; + uint32_t zero_0; + uint32_t zero_1; +} data_command_t; + +typedef struct __attribute__((packed)) +{ + command_common_t common; + uint32_t stay_in_loader; +} flash_end_command_t; + +typedef struct __attribute__((packed)) +{ + command_common_t common; + uint32_t total_size; + uint32_t blocks; + uint32_t block_size; + uint32_t offset; +} mem_begin_command_t; + +typedef struct __attribute__((packed)) +{ + command_common_t common; + uint32_t stay_in_loader; + uint32_t entry_point_address; +} mem_end_command_t; + +typedef struct __attribute__((packed)) +{ + command_common_t common; + uint8_t sync_sequence[36]; +} sync_command_t; + +typedef struct __attribute__((packed)) +{ + command_common_t common; + uint32_t address; + uint32_t value; + uint32_t mask; + uint32_t delay_us; +} write_reg_command_t; + +typedef struct __attribute__((packed)) +{ + command_common_t common; + uint32_t address; +} read_reg_command_t; + +typedef struct __attribute__((packed)) +{ + command_common_t common; + uint32_t configuration; + uint32_t zero; // ESP32 ROM only +} spi_attach_command_t; + +typedef struct __attribute__((packed)) +{ + command_common_t common; + uint32_t new_baudrate; + uint32_t old_baudrate; +} change_baudrate_command_t; + +typedef struct __attribute__((packed)) +{ + command_common_t common; + uint32_t address; + uint32_t size; + uint32_t reserved_0; + uint32_t reserved_1; +} spi_flash_md5_command_t; + +typedef struct __attribute__((packed)) +{ + uint8_t direction; + uint8_t command; // One of command_t + uint16_t size; + uint32_t value; +} common_response_t; + +typedef struct __attribute__((packed)) +{ + uint8_t failed; + uint8_t error; +} response_status_t; + +typedef struct __attribute__((packed)) +{ + common_response_t common; + response_status_t status; +} response_t; + +typedef struct __attribute__((packed)) +{ + common_response_t common; + uint8_t md5[MD5_SIZE]; // ROM only + response_status_t status; +} rom_md5_response_t; + +typedef struct __attribute__((packed)) +{ + command_common_t common; + uint32_t id; + uint32_t total_size; + uint32_t block_size; + uint32_t sector_size; + uint32_t page_size; + uint32_t status_mask; +} write_spi_command_t; + +esp_loader_error_t loader_initialize_conn(esp_loader_connect_args_t *connect_args); + +#ifdef SERIAL_FLASHER_INTERFACE_UART +esp_loader_error_t loader_flash_begin_cmd(uint32_t offset, uint32_t erase_size, uint32_t block_size, uint32_t blocks_to_write, bool encryption); + +esp_loader_error_t loader_flash_data_cmd(const uint8_t *data, uint32_t size); + +esp_loader_error_t loader_flash_end_cmd(bool stay_in_loader); + +esp_loader_error_t loader_sync_cmd(void); + +esp_loader_error_t loader_spi_attach_cmd(uint32_t config); + +esp_loader_error_t loader_md5_cmd(uint32_t address, uint32_t size, uint8_t *md5_out); + +esp_loader_error_t loader_spi_parameters(uint32_t total_size); +#endif /* SERIAL_FLASHER_INTERFACE_UART */ + +esp_loader_error_t loader_mem_begin_cmd(uint32_t offset, uint32_t size, uint32_t blocks_to_write, uint32_t block_size); + +esp_loader_error_t loader_mem_data_cmd(const uint8_t *data, uint32_t size); + +esp_loader_error_t loader_mem_end_cmd(uint32_t entrypoint); + +esp_loader_error_t loader_write_reg_cmd(uint32_t address, uint32_t value, uint32_t mask, uint32_t delay_us); + +esp_loader_error_t loader_read_reg_cmd(uint32_t address, uint32_t *reg); + +esp_loader_error_t loader_change_baudrate_cmd(uint32_t baudrate); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/private_include/protocol_prv.h b/applications/external/esp-flasher/lib/esp-serial-flasher/private_include/protocol_prv.h new file mode 100644 index 000000000..3b9575606 --- /dev/null +++ b/applications/external/esp-flasher/lib/esp-serial-flasher/private_include/protocol_prv.h @@ -0,0 +1,31 @@ +/* Copyright 2020-2023 Espressif Systems (Shanghai) CO LTD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include "esp_loader.h" +#include "protocol.h" + +void log_loader_internal_error(error_code_t error); + +esp_loader_error_t send_cmd(const void *cmd_data, uint32_t size, uint32_t *reg_value); + +esp_loader_error_t send_cmd_with_data(const void *cmd_data, size_t cmd_size, + const void *data, size_t data_size); + +esp_loader_error_t send_cmd_md5(const void *cmd_data, size_t cmd_size, uint8_t md5_out[MD5_SIZE]); diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/private_include/slip.h b/applications/external/esp-flasher/lib/esp-serial-flasher/private_include/slip.h new file mode 100644 index 000000000..00f1f7d0d --- /dev/null +++ b/applications/external/esp-flasher/lib/esp-serial-flasher/private_include/slip.h @@ -0,0 +1,28 @@ +/* Copyright 2020-2023 Espressif Systems (Shanghai) CO LTD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "esp_loader.h" +#include +#include + +esp_loader_error_t SLIP_receive_data(uint8_t *buff, size_t size); + +esp_loader_error_t SLIP_receive_packet(uint8_t *buff, size_t size); + +esp_loader_error_t SLIP_send(const uint8_t *data, size_t size); + +esp_loader_error_t SLIP_send_delimiter(void); diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/src/esp_loader.c b/applications/external/esp-flasher/lib/esp-serial-flasher/src/esp_loader.c new file mode 100644 index 000000000..6ef32673c --- /dev/null +++ b/applications/external/esp-flasher/lib/esp-serial-flasher/src/esp_loader.c @@ -0,0 +1,415 @@ +/* Copyright 2020-2023 Espressif Systems (Shanghai) CO LTD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "protocol.h" +#include "esp_loader_io.h" +#include "esp_loader.h" +#include "esp_targets.h" +#include "md5_hash.h" +#include +#include + +#ifndef MAX +#define MAX(a, b) ((a) > (b)) ? (a) : (b) +#endif + +#ifndef MIN +#define MIN(a, b) ((a) < (b)) ? (a) : (b) +#endif + +#ifndef ROUNDUP +#define ROUNDUP(a, b) (((int)a + (int)b - 1) / (int)b) +#endif + +static const uint32_t DEFAULT_TIMEOUT = 1000; +static const uint32_t DEFAULT_FLASH_TIMEOUT = 3000; // timeout for most flash operations +static const uint32_t LOAD_RAM_TIMEOUT_PER_MB = 2000000; // timeout (per megabyte) for erasing a region + +typedef enum { + SPI_FLASH_READ_ID = 0x9F +} spi_flash_cmd_t; + +static const target_registers_t *s_reg = NULL; +static target_chip_t s_target = ESP_UNKNOWN_CHIP; + +#if MD5_ENABLED + +static const uint32_t MD5_TIMEOUT_PER_MB = 800; +static struct MD5Context s_md5_context; +static uint32_t s_start_address; +static uint32_t s_image_size; + +static inline void init_md5(uint32_t address, uint32_t size) +{ + s_start_address = address; + s_image_size = size; + MD5Init(&s_md5_context); +} + +static inline void md5_update(const uint8_t *data, uint32_t size) +{ + MD5Update(&s_md5_context, data, size); +} + +static inline void md5_final(uint8_t digets[16]) +{ + MD5Final(digets, &s_md5_context); +} + +#else + +static inline void init_md5(uint32_t address, uint32_t size) { } +static inline void md5_update(const uint8_t *data, uint32_t size) { } +static inline void md5_final(uint8_t digets[16]) { } + +#endif + + +static uint32_t timeout_per_mb(uint32_t size_bytes, uint32_t time_per_mb) +{ + uint32_t timeout = time_per_mb * (size_bytes / 1e6); + return MAX(timeout, DEFAULT_FLASH_TIMEOUT); +} + +esp_loader_error_t esp_loader_connect(esp_loader_connect_args_t *connect_args) +{ + loader_port_enter_bootloader(); + + RETURN_ON_ERROR(loader_initialize_conn(connect_args)); + + RETURN_ON_ERROR(loader_detect_chip(&s_target, &s_reg)); + +#ifdef SERIAL_FLASHER_INTERFACE_UART + esp_loader_error_t err; + uint32_t spi_config; + if (s_target == ESP8266_CHIP) { + err = loader_flash_begin_cmd(0, 0, 0, 0, s_target); + } else { + RETURN_ON_ERROR( loader_read_spi_config(s_target, &spi_config) ); + loader_port_start_timer(DEFAULT_TIMEOUT); + err = loader_spi_attach_cmd(spi_config); + } + return err; +#endif /* SERIAL_FLASHER_INTERFACE_UART */ + return ESP_LOADER_SUCCESS; +} + +target_chip_t esp_loader_get_target(void) +{ + return s_target; +} + +#ifdef SERIAL_FLASHER_INTERFACE_UART +static uint32_t s_flash_write_size = 0; + +static esp_loader_error_t spi_set_data_lengths(size_t mosi_bits, size_t miso_bits) +{ + if (mosi_bits > 0) { + RETURN_ON_ERROR( esp_loader_write_register(s_reg->mosi_dlen, mosi_bits - 1) ); + } + if (miso_bits > 0) { + RETURN_ON_ERROR( esp_loader_write_register(s_reg->miso_dlen, miso_bits - 1) ); + } + + return ESP_LOADER_SUCCESS; +} + +static esp_loader_error_t spi_set_data_lengths_8266(size_t mosi_bits, size_t miso_bits) +{ + uint32_t mosi_mask = (mosi_bits == 0) ? 0 : mosi_bits - 1; + uint32_t miso_mask = (miso_bits == 0) ? 0 : miso_bits - 1; + return esp_loader_write_register(s_reg->usr1, (miso_mask << 8) | (mosi_mask << 17)); +} + +static esp_loader_error_t spi_flash_command(spi_flash_cmd_t cmd, void *data_tx, size_t tx_size, void *data_rx, size_t rx_size) +{ + assert(rx_size <= 32); // Reading more than 32 bits back from a SPI flash operation is unsupported + assert(tx_size <= 64); // Writing more than 64 bytes of data with one SPI command is unsupported + + uint32_t SPI_USR_CMD = (1 << 31); + uint32_t SPI_USR_MISO = (1 << 28); + uint32_t SPI_USR_MOSI = (1 << 27); + uint32_t SPI_CMD_USR = (1 << 18); + uint32_t CMD_LEN_SHIFT = 28; + + // Save SPI configuration + uint32_t old_spi_usr; + uint32_t old_spi_usr2; + RETURN_ON_ERROR( esp_loader_read_register(s_reg->usr, &old_spi_usr) ); + RETURN_ON_ERROR( esp_loader_read_register(s_reg->usr2, &old_spi_usr2) ); + + if (s_target == ESP8266_CHIP) { + RETURN_ON_ERROR( spi_set_data_lengths_8266(tx_size, rx_size) ); + } else { + RETURN_ON_ERROR( spi_set_data_lengths(tx_size, rx_size) ); + } + + uint32_t usr_reg_2 = (7 << CMD_LEN_SHIFT) | cmd; + uint32_t usr_reg = SPI_USR_CMD; + if (rx_size > 0) { + usr_reg |= SPI_USR_MISO; + } + if (tx_size > 0) { + usr_reg |= SPI_USR_MOSI; + } + + RETURN_ON_ERROR( esp_loader_write_register(s_reg->usr, usr_reg) ); + RETURN_ON_ERROR( esp_loader_write_register(s_reg->usr2, usr_reg_2 ) ); + + if (tx_size == 0) { + // clear data register before we read it + RETURN_ON_ERROR( esp_loader_write_register(s_reg->w0, 0) ); + } else { + uint32_t *data = (uint32_t *)data_tx; + uint32_t words_to_write = (tx_size + 31) / (8 * 4); + uint32_t data_reg_addr = s_reg->w0; + + while (words_to_write--) { + uint32_t word = *data++; + RETURN_ON_ERROR( esp_loader_write_register(data_reg_addr, word) ); + data_reg_addr += 4; + } + } + + RETURN_ON_ERROR( esp_loader_write_register(s_reg->cmd, SPI_CMD_USR) ); + + uint32_t trials = 10; + while (trials--) { + uint32_t cmd_reg; + RETURN_ON_ERROR( esp_loader_read_register(s_reg->cmd, &cmd_reg) ); + if ((cmd_reg & SPI_CMD_USR) == 0) { + break; + } + } + + if (trials == 0) { + return ESP_LOADER_ERROR_TIMEOUT; + } + + RETURN_ON_ERROR( esp_loader_read_register(s_reg->w0, data_rx) ); + + // Restore SPI configuration + RETURN_ON_ERROR( esp_loader_write_register(s_reg->usr, old_spi_usr) ); + RETURN_ON_ERROR( esp_loader_write_register(s_reg->usr2, old_spi_usr2) ); + + return ESP_LOADER_SUCCESS; +} + +static esp_loader_error_t detect_flash_size(size_t *flash_size) +{ + uint32_t flash_id = 0; + + RETURN_ON_ERROR( spi_flash_command(SPI_FLASH_READ_ID, NULL, 0, &flash_id, 24) ); + uint32_t size_id = flash_id >> 16; + + if (size_id < 0x12 || size_id > 0x18) { + return ESP_LOADER_ERROR_UNSUPPORTED_CHIP; + } + + *flash_size = 1 << size_id; + + return ESP_LOADER_SUCCESS; +} + +static uint32_t calc_erase_size(const target_chip_t target, const uint32_t offset, + const uint32_t image_size) +{ + if (target != ESP8266_CHIP) { + return image_size; + } else { + /* Needed to fix a bug in the ESP8266 ROM */ + const uint32_t sectors_per_block = 16U; + const uint32_t sector_size = 4096U; + + const uint32_t num_sectors = (image_size + sector_size - 1) / sector_size; + const uint32_t start_sector = offset / sector_size; + + uint32_t head_sectors = sectors_per_block - (start_sector % sectors_per_block); + + /* The ROM bug deletes extra num_sectors if we don't cross the block boundary + and extra head_sectors if we do */ + if (num_sectors <= head_sectors) { + return ((num_sectors + 1) / 2) * sector_size; + } else { + return (num_sectors - head_sectors) * sector_size; + } + } +} + +esp_loader_error_t esp_loader_flash_start(uint32_t offset, uint32_t image_size, uint32_t block_size) +{ + s_flash_write_size = block_size; + + size_t flash_size = 0; + if (detect_flash_size(&flash_size) == ESP_LOADER_SUCCESS) { + if (image_size > flash_size) { + return ESP_LOADER_ERROR_IMAGE_SIZE; + } + loader_port_start_timer(DEFAULT_TIMEOUT); + RETURN_ON_ERROR( loader_spi_parameters(flash_size) ); + } else { + loader_port_debug_print("Flash size detection failed, falling back to default"); + } + + init_md5(offset, image_size); + + bool encryption_in_cmd = encryption_in_begin_flash_cmd(s_target); + const uint32_t erase_size = calc_erase_size(esp_loader_get_target(), offset, image_size); + const uint32_t blocks_to_write = (image_size + block_size - 1) / block_size; + + const uint32_t erase_region_timeout_per_mb = 10000; + loader_port_start_timer(timeout_per_mb(erase_size, erase_region_timeout_per_mb)); + return loader_flash_begin_cmd(offset, erase_size, block_size, blocks_to_write, encryption_in_cmd); +} + + +esp_loader_error_t esp_loader_flash_write(void *payload, uint32_t size) +{ + uint32_t padding_bytes = s_flash_write_size - size; + uint8_t *data = (uint8_t *)payload; + uint32_t padding_index = size; + + if (size > s_flash_write_size) { + return ESP_LOADER_ERROR_INVALID_PARAM; + } + + const uint8_t padding_pattern = 0xFF; + while (padding_bytes--) { + data[padding_index++] = padding_pattern; + } + + md5_update(payload, (size + 3) & ~3); + + loader_port_start_timer(DEFAULT_TIMEOUT); + + return loader_flash_data_cmd(data, s_flash_write_size); +} + + +esp_loader_error_t esp_loader_flash_finish(bool reboot) +{ + loader_port_start_timer(DEFAULT_TIMEOUT); + + return loader_flash_end_cmd(!reboot); +} +#endif /* SERIAL_FLASHER_INTERFACE_UART */ + +esp_loader_error_t esp_loader_mem_start(uint32_t offset, uint32_t size, uint32_t block_size) +{ + uint32_t blocks_to_write = ROUNDUP(size, block_size); + loader_port_start_timer(timeout_per_mb(size, LOAD_RAM_TIMEOUT_PER_MB)); + return loader_mem_begin_cmd(offset, size, blocks_to_write, block_size); +} + + +esp_loader_error_t esp_loader_mem_write(const void *payload, uint32_t size) +{ + const uint8_t *data = (const uint8_t *)payload; + loader_port_start_timer(timeout_per_mb(size, LOAD_RAM_TIMEOUT_PER_MB)); + return loader_mem_data_cmd(data, size); +} + + +esp_loader_error_t esp_loader_mem_finish(uint32_t entrypoint) +{ + loader_port_start_timer(DEFAULT_TIMEOUT); + return loader_mem_end_cmd(entrypoint); +} + + +esp_loader_error_t esp_loader_read_register(uint32_t address, uint32_t *reg_value) +{ + loader_port_start_timer(DEFAULT_TIMEOUT); + + return loader_read_reg_cmd(address, reg_value); +} + + +esp_loader_error_t esp_loader_write_register(uint32_t address, uint32_t reg_value) +{ + loader_port_start_timer(DEFAULT_TIMEOUT); + + return loader_write_reg_cmd(address, reg_value, 0xFFFFFFFF, 0); +} + +esp_loader_error_t esp_loader_change_transmission_rate(uint32_t transmission_rate) +{ + if (s_target == ESP8266_CHIP) { + return ESP_LOADER_ERROR_UNSUPPORTED_FUNC; + } + + loader_port_start_timer(DEFAULT_TIMEOUT); + + return loader_change_baudrate_cmd(transmission_rate); +} + +#if MD5_ENABLED + +static void hexify(const uint8_t raw_md5[16], uint8_t hex_md5_out[32]) +{ + static const uint8_t dec_to_hex[] = { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' + }; + for (int i = 0; i < 16; i++) { + *hex_md5_out++ = dec_to_hex[raw_md5[i] >> 4]; + *hex_md5_out++ = dec_to_hex[raw_md5[i] & 0xF]; + } +} + + +esp_loader_error_t esp_loader_flash_verify(void) +{ + if (s_target == ESP8266_CHIP) { + return ESP_LOADER_ERROR_UNSUPPORTED_FUNC; + } + + uint8_t raw_md5[16] = {0}; + + /* Zero termination and new line character require 2 bytes */ + uint8_t hex_md5[MD5_SIZE + 2] = {0}; + uint8_t received_md5[MD5_SIZE + 2] = {0}; + + md5_final(raw_md5); + hexify(raw_md5, hex_md5); + + loader_port_start_timer(timeout_per_mb(s_image_size, MD5_TIMEOUT_PER_MB)); + + RETURN_ON_ERROR( loader_md5_cmd(s_start_address, s_image_size, received_md5) ); + + bool md5_match = memcmp(hex_md5, received_md5, MD5_SIZE) == 0; + + if (!md5_match) { + hex_md5[MD5_SIZE] = '\n'; + received_md5[MD5_SIZE] = '\n'; + + loader_port_debug_print("Error: MD5 checksum does not match:\n"); + loader_port_debug_print("Expected:\n"); + loader_port_debug_print((char *)received_md5); + loader_port_debug_print("Actual:\n"); + loader_port_debug_print((char *)hex_md5); + + return ESP_LOADER_ERROR_INVALID_MD5; + } + + return ESP_LOADER_SUCCESS; +} + +#endif + +void esp_loader_reset_target(void) +{ + loader_port_reset_target(); +} diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/src/esp_targets.c b/applications/external/esp-flasher/lib/esp-serial-flasher/src/esp_targets.c new file mode 100644 index 000000000..8764d2369 --- /dev/null +++ b/applications/external/esp-flasher/lib/esp-serial-flasher/src/esp_targets.c @@ -0,0 +1,263 @@ +/* Copyright 2020 Espressif Systems (Shanghai) PTE LTD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "esp_targets.h" +#include + +#define MAX_MAGIC_VALUES 2 + +typedef esp_loader_error_t (*read_spi_config_t)(uint32_t efuse_base, uint32_t *spi_config); + +typedef struct { + target_registers_t regs; + uint32_t efuse_base; + uint32_t chip_magic_value[MAX_MAGIC_VALUES]; + read_spi_config_t read_spi_config; + bool encryption_in_begin_flash_cmd; +} esp_target_t; + +// This ROM address has a different value on each chip model +#define CHIP_DETECT_MAGIC_REG_ADDR 0x40001000 + +#define ESP8266_SPI_REG_BASE 0x60000200 +#define ESP32S2_SPI_REG_BASE 0x3f402000 +#define ESP32xx_SPI_REG_BASE 0x60002000 +#define ESP32_SPI_REG_BASE 0x3ff42000 + +static esp_loader_error_t spi_config_esp32(uint32_t efuse_base, uint32_t *spi_config); +static esp_loader_error_t spi_config_esp32xx(uint32_t efuse_base, uint32_t *spi_config); + +static const esp_target_t esp_target[ESP_MAX_CHIP] = { + + // ESP8266 + { + .regs = { + .cmd = ESP8266_SPI_REG_BASE + 0x00, + .usr = ESP8266_SPI_REG_BASE + 0x1c, + .usr1 = ESP8266_SPI_REG_BASE + 0x20, + .usr2 = ESP8266_SPI_REG_BASE + 0x24, + .w0 = ESP8266_SPI_REG_BASE + 0x40, + .mosi_dlen = 0, + .miso_dlen = 0, + }, + .efuse_base = 0, // Not used + .chip_magic_value = { 0xfff0c101, 0 }, + .read_spi_config = NULL, // Not used + }, + + // ESP32 + { + .regs = { + .cmd = ESP32_SPI_REG_BASE + 0x00, + .usr = ESP32_SPI_REG_BASE + 0x1c, + .usr1 = ESP32_SPI_REG_BASE + 0x20, + .usr2 = ESP32_SPI_REG_BASE + 0x24, + .w0 = ESP32_SPI_REG_BASE + 0x80, + .mosi_dlen = ESP32_SPI_REG_BASE + 0x28, + .miso_dlen = ESP32_SPI_REG_BASE + 0x2c, + }, + .efuse_base = 0x3ff5A000, + .chip_magic_value = { 0x00f01d83, 0 }, + .read_spi_config = spi_config_esp32, + }, + + // ESP32S2 + { + .regs = { + .cmd = ESP32S2_SPI_REG_BASE + 0x00, + .usr = ESP32S2_SPI_REG_BASE + 0x18, + .usr1 = ESP32S2_SPI_REG_BASE + 0x1c, + .usr2 = ESP32S2_SPI_REG_BASE + 0x20, + .w0 = ESP32S2_SPI_REG_BASE + 0x58, + .mosi_dlen = ESP32S2_SPI_REG_BASE + 0x24, + .miso_dlen = ESP32S2_SPI_REG_BASE + 0x28, + }, + .efuse_base = 0x3f41A000, + .chip_magic_value = { 0x000007c6, 0 }, + .read_spi_config = spi_config_esp32xx, + }, + + // ESP32C3 + { + .regs = { + .cmd = ESP32xx_SPI_REG_BASE + 0x00, + .usr = ESP32xx_SPI_REG_BASE + 0x18, + .usr1 = ESP32xx_SPI_REG_BASE + 0x1c, + .usr2 = ESP32xx_SPI_REG_BASE + 0x20, + .w0 = ESP32xx_SPI_REG_BASE + 0x58, + .mosi_dlen = ESP32xx_SPI_REG_BASE + 0x24, + .miso_dlen = ESP32xx_SPI_REG_BASE + 0x28, + }, + .efuse_base = 0x60008800, + .chip_magic_value = { 0x6921506f, 0x1b31506f }, + .read_spi_config = spi_config_esp32xx, + }, + + // ESP32S3 + { + .regs = { + .cmd = ESP32xx_SPI_REG_BASE + 0x00, + .usr = ESP32xx_SPI_REG_BASE + 0x18, + .usr1 = ESP32xx_SPI_REG_BASE + 0x1c, + .usr2 = ESP32xx_SPI_REG_BASE + 0x20, + .w0 = ESP32xx_SPI_REG_BASE + 0x58, + .mosi_dlen = ESP32xx_SPI_REG_BASE + 0x24, + .miso_dlen = ESP32xx_SPI_REG_BASE + 0x28, + }, + .efuse_base = 0x60007000, + .chip_magic_value = { 0x00000009, 0 }, + .read_spi_config = spi_config_esp32xx, + }, + + // ESP32C2 + { + .regs = { + .cmd = ESP32xx_SPI_REG_BASE + 0x00, + .usr = ESP32xx_SPI_REG_BASE + 0x18, + .usr1 = ESP32xx_SPI_REG_BASE + 0x1c, + .usr2 = ESP32xx_SPI_REG_BASE + 0x20, + .w0 = ESP32xx_SPI_REG_BASE + 0x58, + .mosi_dlen = ESP32xx_SPI_REG_BASE + 0x24, + .miso_dlen = ESP32xx_SPI_REG_BASE + 0x28, + }, + .efuse_base = 0x60008800, + .chip_magic_value = { 0x6f51306f, 0x7c41a06f }, + .read_spi_config = spi_config_esp32xx, + }, + // ESP32H4 + { + .regs = { + .cmd = ESP32xx_SPI_REG_BASE + 0x00, + .usr = ESP32xx_SPI_REG_BASE + 0x18, + .usr1 = ESP32xx_SPI_REG_BASE + 0x1c, + .usr2 = ESP32xx_SPI_REG_BASE + 0x20, + .w0 = ESP32xx_SPI_REG_BASE + 0x58, + .mosi_dlen = ESP32xx_SPI_REG_BASE + 0x24, + .miso_dlen = ESP32xx_SPI_REG_BASE + 0x28, + }, + .efuse_base = 0x6001A000, + .chip_magic_value = {0xca26cc22, 0x6881b06f}, // ESP32H4-BETA1, ESP32H4-BETA2 + .read_spi_config = spi_config_esp32xx, + }, + // ESP32H2 + { + .regs = { + .cmd = ESP32xx_SPI_REG_BASE + 0x00, + .usr = ESP32xx_SPI_REG_BASE + 0x18, + .usr1 = ESP32xx_SPI_REG_BASE + 0x1c, + .usr2 = ESP32xx_SPI_REG_BASE + 0x20, + .w0 = ESP32xx_SPI_REG_BASE + 0x58, + .mosi_dlen = ESP32xx_SPI_REG_BASE + 0x24, + .miso_dlen = ESP32xx_SPI_REG_BASE + 0x28, + }, + .efuse_base = 0x6001A000, + .chip_magic_value = {0xd7b73e80, 0}, + .read_spi_config = spi_config_esp32xx, + }, +}; + +const target_registers_t *get_esp_target_data(target_chip_t chip) +{ + return (const target_registers_t *)&esp_target[chip]; +} + +esp_loader_error_t loader_detect_chip(target_chip_t *target_chip, const target_registers_t **target_data) +{ + uint32_t magic_value; + RETURN_ON_ERROR( esp_loader_read_register(CHIP_DETECT_MAGIC_REG_ADDR, &magic_value) ); + + for (int chip = 0; chip < ESP_MAX_CHIP; chip++) { + for(int index = 0; index < MAX_MAGIC_VALUES; index++) { + if (magic_value == esp_target[chip].chip_magic_value[index]) { + *target_chip = (target_chip_t)chip; + *target_data = (target_registers_t *)&esp_target[chip]; + return ESP_LOADER_SUCCESS; + } + } + } + + return ESP_LOADER_ERROR_INVALID_TARGET; +} + +esp_loader_error_t loader_read_spi_config(target_chip_t target_chip, uint32_t *spi_config) +{ + const esp_target_t *target = &esp_target[target_chip]; + return target->read_spi_config(target->efuse_base, spi_config); +} + +static inline uint32_t efuse_word_addr(uint32_t efuse_base, uint32_t n) +{ + return efuse_base + (n * 4); +} + +// 30->GPIO32 | 31->GPIO33 +static inline uint8_t adjust_pin_number(uint8_t num) +{ + return (num >= 30) ? num + 2 : num; +} + + +static esp_loader_error_t spi_config_esp32(uint32_t efuse_base, uint32_t *spi_config) +{ + *spi_config = 0; + + uint32_t reg5, reg3; + RETURN_ON_ERROR( esp_loader_read_register(efuse_word_addr(efuse_base, 5), ®5) ); + RETURN_ON_ERROR( esp_loader_read_register(efuse_word_addr(efuse_base, 3), ®3) ); + + uint32_t pins = reg5 & 0xfffff; + + if (pins == 0 || pins == 0xfffff) { + return ESP_LOADER_SUCCESS; + } + + uint8_t clk = adjust_pin_number( (pins >> 0) & 0x1f ); + uint8_t q = adjust_pin_number( (pins >> 5) & 0x1f ); + uint8_t d = adjust_pin_number( (pins >> 10) & 0x1f ); + uint8_t cs = adjust_pin_number( (pins >> 15) & 0x1f ); + uint8_t hd = adjust_pin_number( (reg3 >> 4) & 0x1f ); + + if (clk == cs || clk == d || clk == q || q == cs || q == d || q == d) { + return ESP_LOADER_SUCCESS; + } + + *spi_config = (hd << 24) | (cs << 18) | (d << 12) | (q << 6) | clk; + + return ESP_LOADER_SUCCESS; +} + +// Applies for esp32s2, esp32c3 and esp32c3 +static esp_loader_error_t spi_config_esp32xx(uint32_t efuse_base, uint32_t *spi_config) +{ + *spi_config = 0; + + uint32_t reg1, reg2; + RETURN_ON_ERROR( esp_loader_read_register(efuse_word_addr(efuse_base, 18), ®1) ); + RETURN_ON_ERROR( esp_loader_read_register(efuse_word_addr(efuse_base, 19), ®2) ); + + uint32_t pins = ((reg1 >> 16) | ((reg2 & 0xfffff) << 16)) & 0x3fffffff; + + if (pins == 0 || pins == 0xffffffff) { + return ESP_LOADER_SUCCESS; + } + + *spi_config = pins; + return ESP_LOADER_SUCCESS; +} + +bool encryption_in_begin_flash_cmd(target_chip_t target) +{ + return target == ESP32_CHIP || target == ESP8266_CHIP; +} diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/src/md5_hash.c b/applications/external/esp-flasher/lib/esp-serial-flasher/src/md5_hash.c new file mode 100644 index 000000000..4da76bcb5 --- /dev/null +++ b/applications/external/esp-flasher/lib/esp-serial-flasher/src/md5_hash.c @@ -0,0 +1,262 @@ +/* + * MD5 hash implementation and interface functions + * Copyright (c) 2003-2005, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + + +#include "md5_hash.h" +#include +#include + + +static void MD5Transform(uint32_t buf[4], uint32_t const in[16]); + + +/* ===== start - public domain MD5 implementation ===== */ +/* + * This code implements the MD5 message-digest algorithm. + * The algorithm is due to Ron Rivest. This code was + * written by Colin Plumb in 1993, no copyright is claimed. + * This code is in the public domain; do with it what you wish. + * + * Equivalent code is available from RSA Data Security, Inc. + * This code has been tested against that, and is equivalent, + * except that you don't need to include two pages of legalese + * with every copy. + * + * To compute the message digest of a chunk of bytes, declare an + * MD5Context structure, pass it to MD5Init, call MD5Update as + * needed on buffers full of bytes, and then call MD5Final, which + * will fill a supplied 16-byte array with the digest. + */ + +#ifndef WORDS_BIGENDIAN +#define byteReverse(buf, len) /* Nothing */ +#else +/* + * Note: this code is harmless on little-endian machines. + */ +static void byteReverse(unsigned char *buf, unsigned longs) +{ + uint32_t t; + do { + t = (uint32_t) ((unsigned) buf[3] << 8 | buf[2]) << 16 | + ((unsigned) buf[1] << 8 | buf[0]); + *(uint32_t *) buf = t; + buf += 4; + } while (--longs); +} +#endif + +/* + * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious + * initialization constants. + */ +void MD5Init(struct MD5Context *ctx) +{ + ctx->buf[0] = 0x67452301; + ctx->buf[1] = 0xefcdab89; + ctx->buf[2] = 0x98badcfe; + ctx->buf[3] = 0x10325476; + + ctx->bits[0] = 0; + ctx->bits[1] = 0; +} + +/* + * Update context to reflect the concatenation of another buffer full + * of bytes. + */ +void MD5Update(struct MD5Context *ctx, unsigned char const *buf, unsigned len) +{ + uint32_t t; + + /* Update bitcount */ + + t = ctx->bits[0]; + if ((ctx->bits[0] = t + ((uint32_t) len << 3)) < t) { + ctx->bits[1]++; /* Carry from low to high */ + } + ctx->bits[1] += len >> 29; + + t = (t >> 3) & 0x3f; /* Bytes already in shsInfo->data */ + + /* Handle any leading odd-sized chunks */ + + if (t) { + unsigned char *p = (unsigned char *) ctx->in + t; + + t = 64 - t; + if (len < t) { + memcpy(p, buf, len); + return; + } + memcpy(p, buf, t); + byteReverse(ctx->in, 16); + MD5Transform((uint32_t *)ctx->buf, (uint32_t *) ctx->in); + buf += t; + len -= t; + } + /* Process data in 64-byte chunks */ + + while (len >= 64) { + memcpy(ctx->in, buf, 64); + byteReverse(ctx->in, 16); + MD5Transform((uint32_t *)ctx->buf, (uint32_t *) ctx->in); + buf += 64; + len -= 64; + } + + /* Handle any remaining bytes of data. */ + + memcpy(ctx->in, buf, len); +} + +/* + * Final wrapup - pad to 64-byte boundary with the bit pattern + * 1 0* (64-bit count of bits processed, MSB-first) + */ +void MD5Final(unsigned char digest[16], struct MD5Context *ctx) +{ + unsigned count; + unsigned char *p; + + /* Compute number of bytes mod 64 */ + count = (ctx->bits[0] >> 3) & 0x3F; + + /* Set the first char of padding to 0x80. This is safe since there is + always at least one byte free */ + p = ctx->in + count; + *p++ = 0x80; + + /* Bytes of padding needed to make 64 bytes */ + count = 64 - 1 - count; + + /* Pad out to 56 mod 64 */ + if (count < 8) { + /* Two lots of padding: Pad the first block to 64 bytes */ + memset(p, 0, count); + byteReverse(ctx->in, 16); + MD5Transform((uint32_t *)ctx->buf, (uint32_t *) ctx->in); + + /* Now fill the next block with 56 bytes */ + memset(ctx->in, 0, 56); + } else { + /* Pad block to 56 bytes */ + memset(p, 0, count - 8); + } + byteReverse(ctx->in, 14); + + /* Append length in bits and transform */ + ((uint32_t *) ctx->in)[14] = ctx->bits[0]; + ((uint32_t *) ctx->in)[15] = ctx->bits[1]; + + MD5Transform((uint32_t *)ctx->buf, (uint32_t *) ctx->in); + byteReverse((unsigned char *) ctx->buf, 4); + memcpy(digest, ctx->buf, 16); + memset(ctx, 0, sizeof(struct MD5Context)); /* In case it's sensitive */ +} + +/* The four core functions - F1 is optimized somewhat */ + +/* #define F1(x, y, z) (x & y | ~x & z) */ +#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) (x ^ y ^ z) +#define F4(x, y, z) (y ^ (x | ~z)) + +/* This is the central step in the MD5 algorithm. */ +#define MD5STEP(f, w, x, y, z, data, s) \ + ( w += f(x, y, z) + data, w = w<>(32-s), w += x ) + +/* + * The core of the MD5 algorithm, this alters an existing MD5 hash to + * reflect the addition of 16 longwords of new data. MD5Update blocks + * the data and converts bytes into longwords for this routine. + */ +static void MD5Transform(uint32_t buf[4], uint32_t const in[16]) +{ + register uint32_t a, b, c, d; + + a = buf[0]; + b = buf[1]; + c = buf[2]; + d = buf[3]; + + MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7); + MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12); + MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17); + MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22); + MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7); + MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12); + MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17); + MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22); + MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7); + MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12); + MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17); + MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22); + MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7); + MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12); + MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17); + MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22); + + MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5); + MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9); + MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14); + MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20); + MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5); + MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9); + MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14); + MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20); + MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5); + MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9); + MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14); + MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20); + MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5); + MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9); + MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14); + MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20); + + MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4); + MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11); + MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16); + MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23); + MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4); + MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11); + MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16); + MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23); + MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4); + MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11); + MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16); + MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23); + MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4); + MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11); + MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16); + MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23); + + MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6); + MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10); + MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15); + MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21); + MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6); + MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10); + MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15); + MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21); + MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6); + MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10); + MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15); + MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21); + MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6); + MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10); + MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15); + MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21); + + buf[0] += a; + buf[1] += b; + buf[2] += c; + buf[3] += d; +} +/* ===== end - public domain MD5 implementation ===== */ diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/src/protocol_common.c b/applications/external/esp-flasher/lib/esp-serial-flasher/src/protocol_common.c new file mode 100644 index 000000000..8d1d9dd53 --- /dev/null +++ b/applications/external/esp-flasher/lib/esp-serial-flasher/src/protocol_common.c @@ -0,0 +1,301 @@ +/* Copyright 2020-2023 Espressif Systems (Shanghai) CO LTD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "protocol.h" +#include "protocol_prv.h" +#include "esp_loader_io.h" +#include +#include + +#define CMD_SIZE(cmd) ( sizeof(cmd) - sizeof(command_common_t) ) + +static uint32_t s_sequence_number = 0; + +static uint8_t compute_checksum(const uint8_t *data, uint32_t size) +{ + uint8_t checksum = 0xEF; + + while (size--) { + checksum ^= *data++; + } + + return checksum; +} + +void log_loader_internal_error(error_code_t error) +{ + loader_port_debug_print("Error: "); + + switch (error) { + case INVALID_CRC: loader_port_debug_print("INVALID_CRC"); break; + case INVALID_COMMAND: loader_port_debug_print("INVALID_COMMAND"); break; + case COMMAND_FAILED: loader_port_debug_print("COMMAND_FAILED"); break; + case FLASH_WRITE_ERR: loader_port_debug_print("FLASH_WRITE_ERR"); break; + case FLASH_READ_ERR: loader_port_debug_print("FLASH_READ_ERR"); break; + case READ_LENGTH_ERR: loader_port_debug_print("READ_LENGTH_ERR"); break; + case DEFLATE_ERROR: loader_port_debug_print("DEFLATE_ERROR"); break; + default: loader_port_debug_print("UNKNOWN ERROR"); break; + } + + loader_port_debug_print("\n"); +} + + +esp_loader_error_t loader_flash_begin_cmd(uint32_t offset, + uint32_t erase_size, + uint32_t block_size, + uint32_t blocks_to_write, + bool encryption) +{ + uint32_t encryption_size = encryption ? sizeof(uint32_t) : 0; + + flash_begin_command_t flash_begin_cmd = { + .common = { + .direction = WRITE_DIRECTION, + .command = FLASH_BEGIN, + .size = CMD_SIZE(flash_begin_cmd) - encryption_size, + .checksum = 0 + }, + .erase_size = erase_size, + .packet_count = blocks_to_write, + .packet_size = block_size, + .offset = offset, + .encrypted = 0 + }; + + s_sequence_number = 0; + + return send_cmd(&flash_begin_cmd, sizeof(flash_begin_cmd) - encryption_size, NULL); +} + + +esp_loader_error_t loader_flash_data_cmd(const uint8_t *data, uint32_t size) +{ + data_command_t data_cmd = { + .common = { + .direction = WRITE_DIRECTION, + .command = FLASH_DATA, + .size = CMD_SIZE(data_cmd) + size, + .checksum = compute_checksum(data, size) + }, + .data_size = size, + .sequence_number = s_sequence_number++, + }; + + return send_cmd_with_data(&data_cmd, sizeof(data_cmd), data, size); +} + + +esp_loader_error_t loader_flash_end_cmd(bool stay_in_loader) +{ + flash_end_command_t end_cmd = { + .common = { + .direction = WRITE_DIRECTION, + .command = FLASH_END, + .size = CMD_SIZE(end_cmd), + .checksum = 0 + }, + .stay_in_loader = stay_in_loader + }; + + return send_cmd(&end_cmd, sizeof(end_cmd), NULL); +} + + +esp_loader_error_t loader_mem_begin_cmd(uint32_t offset, uint32_t size, uint32_t blocks_to_write, uint32_t block_size) +{ + + mem_begin_command_t mem_begin_cmd = { + .common = { + .direction = WRITE_DIRECTION, + .command = MEM_BEGIN, + .size = CMD_SIZE(mem_begin_cmd), + .checksum = 0 + }, + .total_size = size, + .blocks = blocks_to_write, + .block_size = block_size, + .offset = offset + }; + + s_sequence_number = 0; + + return send_cmd(&mem_begin_cmd, sizeof(mem_begin_cmd), NULL); +} + + +esp_loader_error_t loader_mem_data_cmd(const uint8_t *data, uint32_t size) +{ + data_command_t data_cmd = { + .common = { + .direction = WRITE_DIRECTION, + .command = MEM_DATA, + .size = CMD_SIZE(data_cmd) + size, + .checksum = compute_checksum(data, size) + }, + .data_size = size, + .sequence_number = s_sequence_number++, + }; + return send_cmd_with_data(&data_cmd, sizeof(data_cmd), data, size); +} + +esp_loader_error_t loader_mem_end_cmd(uint32_t entrypoint) +{ + mem_end_command_t end_cmd = { + .common = { + .direction = WRITE_DIRECTION, + .command = MEM_END, + .size = CMD_SIZE(end_cmd), + }, + .stay_in_loader = (entrypoint == 0), + .entry_point_address = entrypoint + }; + + return send_cmd(&end_cmd, sizeof(end_cmd), NULL); +} + + +esp_loader_error_t loader_sync_cmd(void) +{ + sync_command_t sync_cmd = { + .common = { + .direction = WRITE_DIRECTION, + .command = SYNC, + .size = CMD_SIZE(sync_cmd), + .checksum = 0 + }, + .sync_sequence = { + 0x07, 0x07, 0x12, 0x20, + 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, + 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, + 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, + 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, + } + }; + + return send_cmd(&sync_cmd, sizeof(sync_cmd), NULL); +} + + +esp_loader_error_t loader_write_reg_cmd(uint32_t address, uint32_t value, + uint32_t mask, uint32_t delay_us) +{ + write_reg_command_t write_cmd = { + .common = { + .direction = WRITE_DIRECTION, + .command = WRITE_REG, + .size = CMD_SIZE(write_cmd), + .checksum = 0 + }, + .address = address, + .value = value, + .mask = mask, + .delay_us = delay_us + }; + + return send_cmd(&write_cmd, sizeof(write_cmd), NULL); +} + + +esp_loader_error_t loader_read_reg_cmd(uint32_t address, uint32_t *reg) +{ + read_reg_command_t read_cmd = { + .common = { + .direction = WRITE_DIRECTION, + .command = READ_REG, + .size = CMD_SIZE(read_cmd), + .checksum = 0 + }, + .address = address, + }; + + return send_cmd(&read_cmd, sizeof(read_cmd), reg); +} + + +esp_loader_error_t loader_spi_attach_cmd(uint32_t config) +{ + spi_attach_command_t attach_cmd = { + .common = { + .direction = WRITE_DIRECTION, + .command = SPI_ATTACH, + .size = CMD_SIZE(attach_cmd), + .checksum = 0 + }, + .configuration = config, + .zero = 0 + }; + + return send_cmd(&attach_cmd, sizeof(attach_cmd), NULL); +} + +esp_loader_error_t loader_change_baudrate_cmd(uint32_t baudrate) +{ + change_baudrate_command_t baudrate_cmd = { + .common = { + .direction = WRITE_DIRECTION, + .command = CHANGE_BAUDRATE, + .size = CMD_SIZE(baudrate_cmd), + .checksum = 0 + }, + .new_baudrate = baudrate, + .old_baudrate = 0 // ESP32 ROM only + }; + + return send_cmd(&baudrate_cmd, sizeof(baudrate_cmd), NULL); +} + +esp_loader_error_t loader_md5_cmd(uint32_t address, uint32_t size, uint8_t *md5_out) +{ + spi_flash_md5_command_t md5_cmd = { + .common = { + .direction = WRITE_DIRECTION, + .command = SPI_FLASH_MD5, + .size = CMD_SIZE(md5_cmd), + .checksum = 0 + }, + .address = address, + .size = size, + .reserved_0 = 0, + .reserved_1 = 0 + }; + + return send_cmd_md5(&md5_cmd, sizeof(md5_cmd), md5_out); +} + +esp_loader_error_t loader_spi_parameters(uint32_t total_size) +{ + write_spi_command_t spi_cmd = { + .common = { + .direction = WRITE_DIRECTION, + .command = SPI_SET_PARAMS, + .size = 24, + .checksum = 0 + }, + .id = 0, + .total_size = total_size, + .block_size = 64 * 1024, + .sector_size = 4 * 1024, + .page_size = 0x100, + .status_mask = 0xFFFF, + }; + + return send_cmd(&spi_cmd, sizeof(spi_cmd), NULL); +} + +__attribute__ ((weak)) void loader_port_debug_print(const char *str) +{ + (void)(str); +} diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/src/protocol_spi.c b/applications/external/esp-flasher/lib/esp-serial-flasher/src/protocol_spi.c new file mode 100644 index 000000000..bd04fe79f --- /dev/null +++ b/applications/external/esp-flasher/lib/esp-serial-flasher/src/protocol_spi.c @@ -0,0 +1,312 @@ +/* Copyright 2020-2023 Espressif Systems (Shanghai) CO LTD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "protocol.h" +#include "protocol_prv.h" +#include "esp_loader_io.h" +#include +#include + +typedef struct __attribute__((packed)) { + uint8_t cmd; + uint8_t addr; + uint8_t dummy; +} transaction_preamble_t; + +typedef enum { + TRANS_CMD_WRBUF = 0x01, + TRANS_CMD_RDBUF = 0x02, + TRANS_CMD_WRDMA = 0x03, + TRANS_CMD_RDDMA = 0x04, + TRANS_CMD_SEG_DONE = 0x05, + TRANS_CMD_ENQPI = 0x06, + TRANS_CMD_WR_DONE = 0x07, + TRANS_CMD_CMD8 = 0x08, + TRANS_CMD_CMD9 = 0x09, + TRANS_CMD_CMDA = 0x0A, + TRANS_CMD_EXQPI = 0xDD, +} transaction_cmd_t; + +/* Slave protocol registers */ +typedef enum { + SLAVE_REGISTER_VER = 0, + SLAVE_REGISTER_RXSTA = 4, + SLAVE_REGISTER_TXSTA = 8, + SLAVE_REGISTER_CMD = 12, +} slave_register_addr_t; + +#define SLAVE_STA_TOGGLE_BIT (0x01U << 0) +#define SLAVE_STA_INIT_BIT (0x01U << 1) +#define SLAVE_STA_BUF_LENGTH_POS 2U + +typedef enum { + SLAVE_STATE_INIT = SLAVE_STA_TOGGLE_BIT | SLAVE_STA_INIT_BIT, + SLAVE_STATE_FIRST_PACKET = SLAVE_STA_INIT_BIT, +} slave_state_t; + +typedef enum { + /* Target to host */ + SLAVE_CMD_IDLE = 0xAA, + SLAVE_CMD_READY = 0xA5, + /* Host to target */ + SLAVE_CMD_REBOOT = 0xFE, + SLAVE_CMD_COMM_REINIT = 0x5A, + SLAVE_CMD_DONE = 0x55, +} slave_cmd_t; + +static uint8_t s_slave_seq_tx; +static uint8_t s_slave_seq_rx; + +static esp_loader_error_t write_slave_reg(const uint8_t *data, const uint32_t addr, + const uint8_t size); +static esp_loader_error_t read_slave_reg(uint8_t *out_data, const uint32_t addr, + const uint8_t size); +static esp_loader_error_t handle_slave_state(const uint32_t status_reg_addr, uint8_t *seq_state, + bool *slave_ready, uint32_t *buf_size); +static esp_loader_error_t check_response(command_t cmd, uint32_t *reg_value); + +esp_loader_error_t loader_initialize_conn(esp_loader_connect_args_t *connect_args) +{ + for (uint8_t trial = 0; trial < connect_args->trials; trial++) { + uint8_t slave_ready_flag; + RETURN_ON_ERROR(read_slave_reg(&slave_ready_flag, SLAVE_REGISTER_CMD, + sizeof(slave_ready_flag))); + + if (slave_ready_flag != SLAVE_CMD_IDLE) { + loader_port_debug_print("Waiting for Slave to be idle...\n"); + loader_port_delay_ms(100); + } else { + break; + } + } + + const uint8_t reg_val = SLAVE_CMD_READY; + RETURN_ON_ERROR(write_slave_reg(®_val, SLAVE_REGISTER_CMD, sizeof(reg_val))); + + for (uint8_t trial = 0; trial < connect_args->trials; trial++) { + uint8_t slave_ready_flag; + RETURN_ON_ERROR(read_slave_reg(&slave_ready_flag, SLAVE_REGISTER_CMD, + sizeof(slave_ready_flag))); + + if (slave_ready_flag != SLAVE_CMD_READY) { + loader_port_debug_print("Waiting for Slave to be ready...\n"); + loader_port_delay_ms(100); + } else { + break; + } + } + + return ESP_LOADER_SUCCESS; +} + + +esp_loader_error_t send_cmd(const void *cmd_data, uint32_t size, uint32_t *reg_value) +{ + command_t command = ((const command_common_t *)cmd_data)->command; + + uint32_t buf_size; + bool slave_ready = false; + while (!slave_ready) { + RETURN_ON_ERROR(handle_slave_state(SLAVE_REGISTER_RXSTA, &s_slave_seq_rx, &slave_ready, + &buf_size)); + } + + if (size > buf_size) { + return ESP_LOADER_ERROR_INVALID_PARAM; + } + + /* Start and write the command */ + transaction_preamble_t preamble = {.cmd = TRANS_CMD_WRDMA}; + + loader_port_spi_set_cs(0); + RETURN_ON_ERROR(loader_port_write((const uint8_t *)&preamble, sizeof(preamble), + loader_port_remaining_time())); + RETURN_ON_ERROR(loader_port_write((const uint8_t *)cmd_data, size, + loader_port_remaining_time())); + loader_port_spi_set_cs(1); + + /* Terminate the write */ + loader_port_spi_set_cs(0); + preamble.cmd = TRANS_CMD_WR_DONE; + RETURN_ON_ERROR(loader_port_write((const uint8_t *)&preamble, sizeof(preamble), + loader_port_remaining_time())); + loader_port_spi_set_cs(1); + + return check_response(command, reg_value); +} + + +esp_loader_error_t send_cmd_with_data(const void *cmd_data, size_t cmd_size, + const void *data, size_t data_size) +{ + uint32_t buf_size; + bool slave_ready = false; + while (!slave_ready) { + RETURN_ON_ERROR(handle_slave_state(SLAVE_REGISTER_RXSTA, &s_slave_seq_rx, &slave_ready, + &buf_size)); + } + + if (cmd_size + data_size > buf_size) { + return ESP_LOADER_ERROR_INVALID_PARAM; + } + + /* Start and write the command and the data */ + transaction_preamble_t preamble = {.cmd = TRANS_CMD_WRDMA}; + + loader_port_spi_set_cs(0); + RETURN_ON_ERROR(loader_port_write((const uint8_t *)&preamble, sizeof(preamble), + loader_port_remaining_time())); + RETURN_ON_ERROR(loader_port_write((const uint8_t *)cmd_data, cmd_size, + loader_port_remaining_time())); + RETURN_ON_ERROR(loader_port_write((const uint8_t *)data, data_size, + loader_port_remaining_time())); + loader_port_spi_set_cs(1); + + /* Terminate the write */ + loader_port_spi_set_cs(0); + preamble.cmd = TRANS_CMD_WR_DONE; + RETURN_ON_ERROR(loader_port_write((const uint8_t *)&preamble, sizeof(preamble), + loader_port_remaining_time())); + loader_port_spi_set_cs(1); + + command_t command = ((const command_common_t *)cmd_data)->command; + return check_response(command, NULL); +} + + +static esp_loader_error_t read_slave_reg(uint8_t *out_data, const uint32_t addr, + const uint8_t size) +{ + transaction_preamble_t preamble = { + .cmd = TRANS_CMD_RDBUF, + .addr = addr, + }; + + loader_port_spi_set_cs(0); + RETURN_ON_ERROR(loader_port_write((const uint8_t *)&preamble, sizeof(preamble), + loader_port_remaining_time())); + RETURN_ON_ERROR(loader_port_read(out_data, size, loader_port_remaining_time())); + loader_port_spi_set_cs(1); + + return ESP_LOADER_SUCCESS; +} + + +static esp_loader_error_t write_slave_reg(const uint8_t *data, const uint32_t addr, + const uint8_t size) +{ + transaction_preamble_t preamble = { + .cmd = TRANS_CMD_WRBUF, + .addr = addr, + }; + + loader_port_spi_set_cs(0); + RETURN_ON_ERROR(loader_port_write((const uint8_t *)&preamble, sizeof(preamble), + loader_port_remaining_time())); + RETURN_ON_ERROR(loader_port_write(data, size, loader_port_remaining_time())); + loader_port_spi_set_cs(1); + + return ESP_LOADER_SUCCESS; +} + + +static esp_loader_error_t handle_slave_state(const uint32_t status_reg_addr, uint8_t *seq_state, + bool *slave_ready, uint32_t *buf_size) +{ + uint32_t status_reg; + RETURN_ON_ERROR(read_slave_reg((uint8_t *)&status_reg, status_reg_addr, + sizeof(status_reg))); + const slave_state_t state = status_reg & (SLAVE_STA_TOGGLE_BIT | SLAVE_STA_INIT_BIT); + + switch(state) { + case SLAVE_STATE_INIT: { + const uint32_t initial = 0U; + RETURN_ON_ERROR(write_slave_reg((uint8_t *)&initial, status_reg_addr, sizeof(initial))); + break; + } + + case SLAVE_STATE_FIRST_PACKET: { + *seq_state = state & SLAVE_STA_TOGGLE_BIT; + *buf_size = status_reg >> SLAVE_STA_BUF_LENGTH_POS; + *slave_ready = true; + break; + } + + default: { + const uint8_t new_seq = state & SLAVE_STA_TOGGLE_BIT; + if (new_seq != *seq_state) { + *seq_state = new_seq; + *buf_size = status_reg >> SLAVE_STA_BUF_LENGTH_POS; + *slave_ready = true; + } + break; + } + } + + return ESP_LOADER_SUCCESS; +} + + +static esp_loader_error_t check_response(command_t cmd, uint32_t *reg_value) +{ + response_t resp; + + uint32_t buf_size; + bool slave_ready = false; + while (!slave_ready) { + RETURN_ON_ERROR(handle_slave_state(SLAVE_REGISTER_TXSTA, &s_slave_seq_tx, &slave_ready, + &buf_size)); + } + + if (sizeof(resp) > buf_size) { + return ESP_LOADER_ERROR_INVALID_PARAM; + } + + transaction_preamble_t preamble = { + .cmd = TRANS_CMD_RDDMA, + }; + + loader_port_spi_set_cs(0); + RETURN_ON_ERROR(loader_port_write((const uint8_t *)&preamble, sizeof(preamble), + loader_port_remaining_time())); + RETURN_ON_ERROR(loader_port_read((uint8_t *)&resp, sizeof(resp), + loader_port_remaining_time())); + loader_port_spi_set_cs(1); + + /* Terminate the read */ + loader_port_spi_set_cs(0); + preamble.cmd = TRANS_CMD_CMD8; + RETURN_ON_ERROR(loader_port_write((const uint8_t *)&preamble, sizeof(preamble), + loader_port_remaining_time())); + loader_port_spi_set_cs(1); + + common_response_t *common = (common_response_t *)&resp; + if ((common->direction != READ_DIRECTION) || (common->command != cmd)) { + return ESP_LOADER_ERROR_INVALID_RESPONSE; + } + + response_status_t *status = + (response_status_t *)((uint8_t *)&resp + sizeof(resp) - sizeof(response_status_t)); + if (status->failed) { + log_loader_internal_error(status->error); + return ESP_LOADER_ERROR_INVALID_RESPONSE; + } + + if (reg_value != NULL) { + *reg_value = common->value; + } + + return ESP_LOADER_SUCCESS; +} diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/src/protocol_uart.c b/applications/external/esp-flasher/lib/esp-serial-flasher/src/protocol_uart.c new file mode 100644 index 000000000..c5a51cec6 --- /dev/null +++ b/applications/external/esp-flasher/lib/esp-serial-flasher/src/protocol_uart.c @@ -0,0 +1,114 @@ +/* Copyright 2020-2023 Espressif Systems (Shanghai) CO LTD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "protocol.h" +#include "protocol_prv.h" +#include "esp_loader_io.h" +#include "slip.h" +#include +#include + +static esp_loader_error_t check_response(command_t cmd, uint32_t *reg_value, void* resp, uint32_t resp_size); + +esp_loader_error_t loader_initialize_conn(esp_loader_connect_args_t *connect_args) { + esp_loader_error_t err; + int32_t trials = connect_args->trials; + + do { + loader_port_start_timer(connect_args->sync_timeout); + err = loader_sync_cmd(); + if (err == ESP_LOADER_ERROR_TIMEOUT) { + if (--trials == 0) { + return ESP_LOADER_ERROR_TIMEOUT; + } + loader_port_delay_ms(100); + } else if (err != ESP_LOADER_SUCCESS) { + return err; + } + } while (err != ESP_LOADER_SUCCESS); + + return err; +} + +esp_loader_error_t send_cmd(const void *cmd_data, uint32_t size, uint32_t *reg_value) +{ + response_t response; + command_t command = ((const command_common_t *)cmd_data)->command; + + RETURN_ON_ERROR( SLIP_send_delimiter() ); + RETURN_ON_ERROR( SLIP_send((const uint8_t *)cmd_data, size) ); + RETURN_ON_ERROR( SLIP_send_delimiter() ); + + return check_response(command, reg_value, &response, sizeof(response)); +} + + +esp_loader_error_t send_cmd_with_data(const void *cmd_data, size_t cmd_size, + const void *data, size_t data_size) +{ + response_t response; + command_t command = ((const command_common_t *)cmd_data)->command; + + RETURN_ON_ERROR( SLIP_send_delimiter() ); + RETURN_ON_ERROR( SLIP_send((const uint8_t *)cmd_data, cmd_size) ); + RETURN_ON_ERROR( SLIP_send(data, data_size) ); + RETURN_ON_ERROR( SLIP_send_delimiter() ); + + return check_response(command, NULL, &response, sizeof(response)); +} + + +esp_loader_error_t send_cmd_md5(const void *cmd_data, size_t cmd_size, uint8_t md5_out[MD5_SIZE]) +{ + rom_md5_response_t response; + command_t command = ((const command_common_t *)cmd_data)->command; + + RETURN_ON_ERROR( SLIP_send_delimiter() ); + RETURN_ON_ERROR( SLIP_send((const uint8_t *)cmd_data, cmd_size) ); + RETURN_ON_ERROR( SLIP_send_delimiter() ); + + RETURN_ON_ERROR( check_response(command, NULL, &response, sizeof(response)) ); + + memcpy(md5_out, response.md5, MD5_SIZE); + + return ESP_LOADER_SUCCESS; +} + + +static esp_loader_error_t check_response(command_t cmd, uint32_t *reg_value, void* resp, uint32_t resp_size) +{ + esp_loader_error_t err; + common_response_t *response = (common_response_t *)resp; + + do { + err = SLIP_receive_packet(resp, resp_size); + if (err != ESP_LOADER_SUCCESS) { + return err; + } + } while ((response->direction != READ_DIRECTION) || (response->command != cmd)); + + response_status_t *status = (response_status_t *)((uint8_t *)resp + resp_size - sizeof(response_status_t)); + + if (status->failed) { + log_loader_internal_error(status->error); + return ESP_LOADER_ERROR_INVALID_RESPONSE; + } + + if (reg_value != NULL) { + *reg_value = response->value; + } + + return ESP_LOADER_SUCCESS; +} diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/src/slip.c b/applications/external/esp-flasher/lib/esp-serial-flasher/src/slip.c new file mode 100644 index 000000000..ba926169c --- /dev/null +++ b/applications/external/esp-flasher/lib/esp-serial-flasher/src/slip.c @@ -0,0 +1,125 @@ +/* Copyright 2020-2023 Espressif Systems (Shanghai) CO LTD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "slip.h" +#include "esp_loader_io.h" + +static const uint8_t DELIMITER = 0xC0; +static const uint8_t C0_REPLACEMENT[2] = {0xDB, 0xDC}; +static const uint8_t DB_REPLACEMENT[2] = {0xDB, 0xDD}; + +static inline esp_loader_error_t peripheral_read(uint8_t *buff, const size_t size) +{ + return loader_port_read(buff, size, loader_port_remaining_time()); +} + +static inline esp_loader_error_t peripheral_write(const uint8_t *buff, const size_t size) +{ + return loader_port_write(buff, size, loader_port_remaining_time()); +} + +esp_loader_error_t SLIP_receive_data(uint8_t *buff, const size_t size) +{ + uint8_t ch; + + for (uint32_t i = 0; i < size; i++) { + RETURN_ON_ERROR( peripheral_read(&ch, 1) ); + + if (ch == 0xDB) { + RETURN_ON_ERROR( peripheral_read(&ch, 1) ); + if (ch == 0xDC) { + buff[i] = 0xC0; + } else if (ch == 0xDD) { + buff[i] = 0xDB; + } else { + return ESP_LOADER_ERROR_INVALID_RESPONSE; + } + } else { + buff[i] = ch; + } + } + + return ESP_LOADER_SUCCESS; +} + + +esp_loader_error_t SLIP_receive_packet(uint8_t *buff, const size_t size) +{ + uint8_t ch; + + // Wait for delimiter + do { + RETURN_ON_ERROR( peripheral_read(&ch, 1) ); + } while (ch != DELIMITER); + + // Workaround: bootloader sends two dummy(0xC0) bytes after response when baud rate is changed. + do { + RETURN_ON_ERROR( peripheral_read(&ch, 1) ); + } while (ch == DELIMITER); + + buff[0] = ch; + + RETURN_ON_ERROR( SLIP_receive_data(&buff[1], size - 1) ); + + // Wait for delimiter + do { + RETURN_ON_ERROR( peripheral_read(&ch, 1) ); + } while (ch != DELIMITER); + + return ESP_LOADER_SUCCESS; +} + + +esp_loader_error_t SLIP_send(const uint8_t *data, const size_t size) +{ + uint32_t to_write = 0; // Bytes ready to write as they are + uint32_t written = 0; // Bytes already written + + for (uint32_t i = 0; i < size; i++) { + if (data[i] != 0xC0 && data[i] != 0xDB) { + to_write++; // Queue this byte for writing + continue; + } + + // We have a byte that needs encoding, write the queue first + if (to_write > 0) { + RETURN_ON_ERROR( peripheral_write(&data[written], to_write) ); + } + + // Write the encoded byte + if (data[i] == 0xC0) { + RETURN_ON_ERROR( peripheral_write(C0_REPLACEMENT, 2) ); + } else { + RETURN_ON_ERROR( peripheral_write(DB_REPLACEMENT, 2) ); + } + + // Update to start again after the encoded byte + written = i + 1; + to_write = 0; + } + + // Write the rest of the bytes that didn't need encoding + if (to_write > 0) { + RETURN_ON_ERROR( peripheral_write(&data[written], to_write) ); + } + + return ESP_LOADER_SUCCESS; +} + + +esp_loader_error_t SLIP_send_delimiter(void) +{ + return peripheral_write(&DELIMITER, 1); +} diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/submodules/CMakeLists.txt b/applications/external/esp-flasher/lib/esp-serial-flasher/submodules/CMakeLists.txt new file mode 100644 index 000000000..59e665ac2 --- /dev/null +++ b/applications/external/esp-flasher/lib/esp-serial-flasher/submodules/CMakeLists.txt @@ -0,0 +1,20 @@ + +cmake_minimum_required(VERSION 3.13) + +set(CMAKE_TOOLCHAIN_FILE ${CMAKE_CURRENT_LIST_DIR}/stm32-cmake/cmake/gcc_stm32.cmake) +set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/stm32-cmake/cmake) + +find_package(CMSIS REQUIRED) +find_package(STM32HAL COMPONENTS gpio tim uart REQUIRED) + +add_library(stm_cube_impl ${CMSIS_SOURCES} ${STM32HAL_SOURCES}) +target_include_directories(stm_cube_impl PUBLIC ${CMSIS_INCLUDE_DIRS} ${STM32HAL_INCLUDE_DIR}) +stm32_set_target_properties(stm_cube_impl) + +# stm_cube target is made to propagate properties of stm_cube_impl, as stm32_set_target_properties +# sets properties privately for given target. This eliminates need to call stm32_set_target_properties +# on every target that links against STM32HAL. +add_library(stm_cube INTERFACE) +target_link_libraries(stm_cube INTERFACE stm_cube_impl) +target_compile_definitions(stm_cube INTERFACE $) +target_link_options(stm_cube INTERFACE -T${CMAKE_CURRENT_BINARY_DIR}/stm_cube_impl_flash.ld) \ No newline at end of file diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/zephyr/CMakeLists.txt b/applications/external/esp-flasher/lib/esp-serial-flasher/zephyr/CMakeLists.txt new file mode 100644 index 000000000..97da4eaae --- /dev/null +++ b/applications/external/esp-flasher/lib/esp-serial-flasher/zephyr/CMakeLists.txt @@ -0,0 +1,30 @@ +cmake_minimum_required(VERSION 3.5) + +if (CONFIG_ESP_SERIAL_FLASHER) + zephyr_include_directories( + "${ZEPHYR_CURRENT_MODULE_DIR}/include" + "${ZEPHYR_CURRENT_MODULE_DIR}/port" + "${ZEPHYR_CURRENT_MODULE_DIR}/private_include" + ) + + zephyr_interface_library_named(esp_flasher) + + zephyr_library() + + zephyr_library_sources(${ZEPHYR_CURRENT_MODULE_DIR}/src/esp_loader.c + ${ZEPHYR_CURRENT_MODULE_DIR}/src/esp_targets.c + ${ZEPHYR_CURRENT_MODULE_DIR}/src/protocol_common.c + ${ZEPHYR_CURRENT_MODULE_DIR}/src/protocol_uart.c + ${ZEPHYR_CURRENT_MODULE_DIR}/src/slip.c + ${ZEPHYR_CURRENT_MODULE_DIR}/src/md5_hash.c + ${ZEPHYR_CURRENT_MODULE_DIR}/port/zephyr_port.c + ) + + target_compile_definitions(esp_flasher INTERFACE SERIAL_FLASHER_INTERFACE_UART) + + zephyr_library_link_libraries(esp_flasher) + + if(DEFINED MD5_ENABLED OR CONFIG_SERIAL_FLASHER_MD5_ENABLED) + target_compile_definitions(esp_flasher INTERFACE -DMD5_ENABLED=1) + endif() +endif() diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/zephyr/Kconfig b/applications/external/esp-flasher/lib/esp-serial-flasher/zephyr/Kconfig new file mode 100644 index 000000000..ebb524c86 --- /dev/null +++ b/applications/external/esp-flasher/lib/esp-serial-flasher/zephyr/Kconfig @@ -0,0 +1,16 @@ +config ESP_SERIAL_FLASHER + bool "Enable ESP serial flasher library" + default y + select CONSOLE_SUBSYS + help + Select this option to enable the ESP serial flasher library. + +config ESP_SERIAL_FLASHER_UART_BUFSIZE + int "ESP Serial Flasher UART buffer size" + default 512 + help + Buffer size for UART TX and RX packets + +if ESP_SERIAL_FLASHER + rsource "../Kconfig" +endif diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/zephyr/module.yml b/applications/external/esp-flasher/lib/esp-serial-flasher/zephyr/module.yml new file mode 100644 index 000000000..01d484fcb --- /dev/null +++ b/applications/external/esp-flasher/lib/esp-serial-flasher/zephyr/module.yml @@ -0,0 +1,5 @@ +name: esp-flasher + +build: + cmake: zephyr + kconfig: zephyr/Kconfig diff --git a/applications/external/esp-flasher/scenes/esp_flasher_scene.c b/applications/external/esp-flasher/scenes/esp_flasher_scene.c new file mode 100644 index 000000000..c3fc13ade --- /dev/null +++ b/applications/external/esp-flasher/scenes/esp_flasher_scene.c @@ -0,0 +1,30 @@ +#include "esp_flasher_scene.h" + +// Generate scene on_enter handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, +void (*const esp_flasher_scene_on_enter_handlers[])(void*) = { +#include "esp_flasher_scene_config.h" +}; +#undef ADD_SCENE + +// Generate scene on_event handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event, +bool (*const esp_flasher_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = { +#include "esp_flasher_scene_config.h" +}; +#undef ADD_SCENE + +// Generate scene on_exit handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit, +void (*const esp_flasher_scene_on_exit_handlers[])(void* context) = { +#include "esp_flasher_scene_config.h" +}; +#undef ADD_SCENE + +// Initialize scene handlers configuration structure +const SceneManagerHandlers esp_flasher_scene_handlers = { + .on_enter_handlers = esp_flasher_scene_on_enter_handlers, + .on_event_handlers = esp_flasher_scene_on_event_handlers, + .on_exit_handlers = esp_flasher_scene_on_exit_handlers, + .scene_num = EspFlasherSceneNum, +}; diff --git a/applications/external/esp-flasher/scenes/esp_flasher_scene.h b/applications/external/esp-flasher/scenes/esp_flasher_scene.h new file mode 100644 index 000000000..f1fc0916f --- /dev/null +++ b/applications/external/esp-flasher/scenes/esp_flasher_scene.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +// Generate scene id and total number +#define ADD_SCENE(prefix, name, id) EspFlasherScene##id, +typedef enum { +#include "esp_flasher_scene_config.h" + EspFlasherSceneNum, +} EspFlasherScene; +#undef ADD_SCENE + +extern const SceneManagerHandlers esp_flasher_scene_handlers; + +// Generate scene on_enter handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); +#include "esp_flasher_scene_config.h" +#undef ADD_SCENE + +// Generate scene on_event handlers declaration +#define ADD_SCENE(prefix, name, id) \ + bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event); +#include "esp_flasher_scene_config.h" +#undef ADD_SCENE + +// Generate scene on_exit handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context); +#include "esp_flasher_scene_config.h" +#undef ADD_SCENE diff --git a/applications/external/esp-flasher/scenes/esp_flasher_scene_about.c b/applications/external/esp-flasher/scenes/esp_flasher_scene_about.c new file mode 100644 index 000000000..e778b2bf7 --- /dev/null +++ b/applications/external/esp-flasher/scenes/esp_flasher_scene_about.c @@ -0,0 +1,56 @@ +#include "../esp_flasher_app_i.h" + +void esp_flasher_scene_about_widget_callback(GuiButtonType result, InputType type, void* context) { + EspFlasherApp* app = context; + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(app->view_dispatcher, result); + } +} + +#define ESP_FLASHER_APP_DESCRIPTION \ + "\e#Information\nVersion: " ESP_FLASHER_APP_VERSION \ + "\nDeveloped by: 0xchocolate\n(@cococode on discord) using espressif's esp-serial-flasher library\nGithub: https://github.com/0xchocolate/flipperzero-esp-flasher\n\n\e#Description\nApp to flash ESP chips from\nthe flipper (over UART) using\nbin files on the sd card.\nReset the chip into bootloader\nmode before flashing.\n\n Supported targets:\n- ESP32\n- ESP8266\n- ESP32-S2\n- ESP32-S3\n- ESP32-C3\n- ESP32-C2\n- ESP32-H2" + +void esp_flasher_scene_about_on_enter(void* context) { + EspFlasherApp* app = context; + + widget_add_text_box_element( + app->widget, + 0, + 0, + 128, + 14, + AlignCenter, + AlignBottom, + "\e#\e! \e!\n", + false); + widget_add_text_box_element( + app->widget, + 0, + 2, + 128, + 14, + AlignCenter, + AlignBottom, + "\e#\e! ESP Flasher \e!\n", + false); + widget_add_text_scroll_element(app->widget, 0, 16, 128, 50, ESP_FLASHER_APP_DESCRIPTION); + + view_dispatcher_switch_to_view(app->view_dispatcher, EspFlasherAppViewWidget); +} + +bool esp_flasher_scene_about_on_event(void* context, SceneManagerEvent event) { + EspFlasherApp* app = context; + bool consumed = false; + UNUSED(app); + UNUSED(event); + + return consumed; +} + +void esp_flasher_scene_about_on_exit(void* context) { + EspFlasherApp* app = context; + + // Clear views + widget_reset(app->widget); +} \ No newline at end of file diff --git a/applications/external/esp-flasher/scenes/esp_flasher_scene_browse.c b/applications/external/esp-flasher/scenes/esp_flasher_scene_browse.c new file mode 100644 index 000000000..88a9b25e4 --- /dev/null +++ b/applications/external/esp-flasher/scenes/esp_flasher_scene_browse.c @@ -0,0 +1,266 @@ +#include "../esp_flasher_app_i.h" +#include "../esp_flasher_worker.h" + +enum SubmenuIndex { + SubmenuIndexS3Mode, + SubmenuIndexBoot, + SubmenuIndexPart, + SubmenuIndexNvs, + SubmenuIndexBootApp0, + SubmenuIndexApp, + SubmenuIndexCustom, + SubmenuIndexFlash, +}; + +static void esp_flasher_scene_browse_callback(void* context, uint32_t index) { + EspFlasherApp* app = context; + + scene_manager_set_scene_state(app->scene_manager, EspFlasherSceneBrowse, index); + + // browse for files + FuriString* predefined_filepath = furi_string_alloc_set_str(ESP_APP_FOLDER); + FuriString* selected_filepath = furi_string_alloc(); + DialogsFileBrowserOptions browser_options; + dialog_file_browser_set_basic_options(&browser_options, ".bin", &I_Text_10x10); + + // TODO refactor + switch(index) { + case SubmenuIndexS3Mode: + // toggle S3 mode + app->selected_flash_options[SelectedFlashS3Mode] = + !app->selected_flash_options[SelectedFlashS3Mode]; + view_dispatcher_send_custom_event(app->view_dispatcher, EspFlasherEventRefreshSubmenu); + break; + case SubmenuIndexBoot: + app->selected_flash_options[SelectedFlashBoot] = + !app->selected_flash_options[SelectedFlashBoot]; + if(app->selected_flash_options[SelectedFlashBoot]) { + if(dialog_file_browser_show( + app->dialogs, selected_filepath, predefined_filepath, &browser_options)) { + strncpy( + app->bin_file_path_boot, + furi_string_get_cstr(selected_filepath), + sizeof(app->bin_file_path_boot)); + } + } + if(app->bin_file_path_boot[0] == '\0') { + // if user didn't select a file, leave unselected + app->selected_flash_options[SelectedFlashBoot] = false; + } + view_dispatcher_send_custom_event(app->view_dispatcher, EspFlasherEventRefreshSubmenu); + break; + case SubmenuIndexPart: + app->selected_flash_options[SelectedFlashPart] = + !app->selected_flash_options[SelectedFlashPart]; + if(dialog_file_browser_show( + app->dialogs, selected_filepath, predefined_filepath, &browser_options)) { + strncpy( + app->bin_file_path_part, + furi_string_get_cstr(selected_filepath), + sizeof(app->bin_file_path_part)); + } + if(app->bin_file_path_part[0] == '\0') { + // if user didn't select a file, leave unselected + app->selected_flash_options[SelectedFlashPart] = false; + } + view_dispatcher_send_custom_event(app->view_dispatcher, EspFlasherEventRefreshSubmenu); + break; + case SubmenuIndexNvs: + app->selected_flash_options[SelectedFlashNvs] = + !app->selected_flash_options[SelectedFlashNvs]; + if(dialog_file_browser_show( + app->dialogs, selected_filepath, predefined_filepath, &browser_options)) { + strncpy( + app->bin_file_path_nvs, + furi_string_get_cstr(selected_filepath), + sizeof(app->bin_file_path_nvs)); + } + if(app->bin_file_path_nvs[0] == '\0') { + // if user didn't select a file, leave unselected + app->selected_flash_options[SelectedFlashNvs] = false; + } + view_dispatcher_send_custom_event(app->view_dispatcher, EspFlasherEventRefreshSubmenu); + break; + case SubmenuIndexBootApp0: + app->selected_flash_options[SelectedFlashBootApp0] = + !app->selected_flash_options[SelectedFlashBootApp0]; + if(dialog_file_browser_show( + app->dialogs, selected_filepath, predefined_filepath, &browser_options)) { + strncpy( + app->bin_file_path_boot_app0, + furi_string_get_cstr(selected_filepath), + sizeof(app->bin_file_path_boot_app0)); + } + if(app->bin_file_path_boot_app0[0] == '\0') { + // if user didn't select a file, leave unselected + app->selected_flash_options[SelectedFlashBootApp0] = false; + } + view_dispatcher_send_custom_event(app->view_dispatcher, EspFlasherEventRefreshSubmenu); + break; + case SubmenuIndexApp: + app->selected_flash_options[SelectedFlashApp] = + !app->selected_flash_options[SelectedFlashApp]; + if(dialog_file_browser_show( + app->dialogs, selected_filepath, predefined_filepath, &browser_options)) { + strncpy( + app->bin_file_path_app, + furi_string_get_cstr(selected_filepath), + sizeof(app->bin_file_path_app)); + } + if(app->bin_file_path_app[0] == '\0') { + // if user didn't select a file, leave unselected + app->selected_flash_options[SelectedFlashApp] = false; + } + view_dispatcher_send_custom_event(app->view_dispatcher, EspFlasherEventRefreshSubmenu); + break; + case SubmenuIndexCustom: + app->selected_flash_options[SelectedFlashCustom] = + !app->selected_flash_options[SelectedFlashCustom]; + if(dialog_file_browser_show( + app->dialogs, selected_filepath, predefined_filepath, &browser_options)) { + strncpy( + app->bin_file_path_custom, + furi_string_get_cstr(selected_filepath), + sizeof(app->bin_file_path_custom)); + } + if(app->bin_file_path_custom[0] == '\0') { + // if user didn't select a file, leave unselected + app->selected_flash_options[SelectedFlashCustom] = false; + } + view_dispatcher_send_custom_event(app->view_dispatcher, EspFlasherEventRefreshSubmenu); + break; + case SubmenuIndexFlash: + // count how many options are selected + app->num_selected_flash_options = 0; + for(bool* option = &app->selected_flash_options[SelectedFlashBoot]; + option < &app->selected_flash_options[NUM_FLASH_OPTIONS]; + ++option) { + if(*option) { + ++app->num_selected_flash_options; + } + } + if(app->num_selected_flash_options) { + // only start next scene if at least one option is selected + scene_manager_next_scene(app->scene_manager, EspFlasherSceneConsoleOutput); + } + break; + } + + furi_string_free(selected_filepath); + furi_string_free(predefined_filepath); +} + +#define STR_SELECT "[x]" +#define STR_UNSELECT "[ ]" +#define STR_BOOT "Bootloader (" TOSTRING(ESP_ADDR_BOOT) ")" +#define STR_BOOT_S3 "Bootloader (" TOSTRING(ESP_ADDR_BOOT_S3) ")" +#define STR_PART "Part Table (" TOSTRING(ESP_ADDR_PART) ")" +#define STR_NVS "NVS (" TOSTRING(ESP_ADDR_NVS) ")" +#define STR_BOOT_APP0 "boot_app0 (" TOSTRING(ESP_ADDR_BOOT_APP0) ")" +#define STR_APP "Firmware (" TOSTRING(ESP_ADDR_APP) ")" +#define STR_CUSTOM "Custom" +#define STR_FLASH_S3 "[>] FLASH (ESP32-S3)" +#define STR_FLASH "[>] FLASH" +static void _refresh_submenu(EspFlasherApp* app) { + Submenu* submenu = app->submenu; + + submenu_reset(app->submenu); + + submenu_set_header(submenu, "Browse for files to flash"); + submenu_add_item( + submenu, + app->selected_flash_options[SelectedFlashS3Mode] ? "[x] Using ESP32-S3" : + "[ ] Select if using S3", + SubmenuIndexS3Mode, + esp_flasher_scene_browse_callback, + app); + const char* strSelectBootloader = STR_UNSELECT " " STR_BOOT; + if(app->selected_flash_options[SelectedFlashS3Mode]) { + if(app->selected_flash_options[SelectedFlashBoot]) { + strSelectBootloader = STR_SELECT " " STR_BOOT_S3; + } else { + strSelectBootloader = STR_UNSELECT " " STR_BOOT_S3; + } + } else { + if(app->selected_flash_options[SelectedFlashBoot]) { + strSelectBootloader = STR_SELECT " " STR_BOOT; + } else { + strSelectBootloader = STR_UNSELECT " " STR_BOOT; + } + } + submenu_add_item( + submenu, strSelectBootloader, SubmenuIndexBoot, esp_flasher_scene_browse_callback, app); + submenu_add_item( + submenu, + app->selected_flash_options[SelectedFlashPart] ? STR_SELECT " " STR_PART : + STR_UNSELECT " " STR_PART, + SubmenuIndexPart, + esp_flasher_scene_browse_callback, + app); + submenu_add_item( + submenu, + app->selected_flash_options[SelectedFlashNvs] ? STR_SELECT " " STR_NVS : + STR_UNSELECT " " STR_NVS, + SubmenuIndexNvs, + esp_flasher_scene_browse_callback, + app); + submenu_add_item( + submenu, + app->selected_flash_options[SelectedFlashBootApp0] ? STR_SELECT " " STR_BOOT_APP0 : + STR_UNSELECT " " STR_BOOT_APP0, + SubmenuIndexBootApp0, + esp_flasher_scene_browse_callback, + app); + submenu_add_item( + submenu, + app->selected_flash_options[SelectedFlashApp] ? STR_SELECT " " STR_APP : + STR_UNSELECT " " STR_APP, + SubmenuIndexApp, + esp_flasher_scene_browse_callback, + app); + // TODO: custom addr + //submenu_add_item( + // submenu, app->selected_flash_options[SelectedFlashCustom] ? STR_SELECT " " STR_CUSTOM : STR_UNSELECT " " STR_CUSTOM, SubmenuIndexCustom, esp_flasher_scene_browse_callback, app); + submenu_add_item( + submenu, + app->selected_flash_options[SelectedFlashS3Mode] ? STR_FLASH_S3 : STR_FLASH, + SubmenuIndexFlash, + esp_flasher_scene_browse_callback, + app); + + submenu_set_selected_item( + submenu, scene_manager_get_scene_state(app->scene_manager, EspFlasherSceneBrowse)); + view_dispatcher_switch_to_view(app->view_dispatcher, EspFlasherAppViewSubmenu); +} + +void esp_flasher_scene_browse_on_enter(void* context) { + EspFlasherApp* app = context; + + memset(app->selected_flash_options, 0, sizeof(app->selected_flash_options)); + app->bin_file_path_boot[0] = '\0'; + app->bin_file_path_part[0] = '\0'; + app->bin_file_path_nvs[0] = '\0'; + app->bin_file_path_boot_app0[0] = '\0'; + app->bin_file_path_app[0] = '\0'; + app->bin_file_path_custom[0] = '\0'; + + _refresh_submenu(app); +} + +bool esp_flasher_scene_browse_on_event(void* context, SceneManagerEvent event) { + EspFlasherApp* app = context; + bool consumed = false; + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == EspFlasherEventRefreshSubmenu) { + _refresh_submenu(app); + consumed = true; + } + } + + return consumed; +} + +void esp_flasher_scene_browse_on_exit(void* context) { + EspFlasherApp* app = context; + submenu_reset(app->submenu); +} diff --git a/applications/external/esp-flasher/scenes/esp_flasher_scene_config.h b/applications/external/esp-flasher/scenes/esp_flasher_scene_config.h new file mode 100644 index 000000000..21a5162a9 --- /dev/null +++ b/applications/external/esp-flasher/scenes/esp_flasher_scene_config.h @@ -0,0 +1,4 @@ +ADD_SCENE(esp_flasher, start, Start) +ADD_SCENE(esp_flasher, console_output, ConsoleOutput) +ADD_SCENE(esp_flasher, about, About) +ADD_SCENE(esp_flasher, browse, Browse) diff --git a/applications/external/esp-flasher/scenes/esp_flasher_scene_console_output.c b/applications/external/esp-flasher/scenes/esp_flasher_scene_console_output.c new file mode 100644 index 000000000..1e6d7654f --- /dev/null +++ b/applications/external/esp-flasher/scenes/esp_flasher_scene_console_output.c @@ -0,0 +1,77 @@ +#include "../esp_flasher_app_i.h" + +#include "../esp_flasher_worker.h" + +void esp_flasher_console_output_handle_rx_data_cb(uint8_t* buf, size_t len, void* context) { + furi_assert(context); + EspFlasherApp* app = context; + + // If text box store gets too big, then truncate it + app->text_box_store_strlen += len; + if(app->text_box_store_strlen >= ESP_FLASHER_TEXT_BOX_STORE_SIZE - 1) { + furi_string_right(app->text_box_store, app->text_box_store_strlen / 2); + app->text_box_store_strlen = furi_string_size(app->text_box_store) + len; + } + + // Null-terminate buf and append to text box store + buf[len] = '\0'; + furi_string_cat_printf(app->text_box_store, "%s", buf); + view_dispatcher_send_custom_event(app->view_dispatcher, EspFlasherEventRefreshConsoleOutput); +} + +void esp_flasher_scene_console_output_on_enter(void* context) { + EspFlasherApp* app = context; + + // Reset text box and set font + TextBox* text_box = app->text_box; + text_box_reset(text_box); + text_box_set_font(text_box, TextBoxFontText); + + // Set focus on end + text_box_set_focus(text_box, TextBoxFocusEnd); + + // Set starting text + text_box_set_text(app->text_box, furi_string_get_cstr(app->text_box_store)); + + // Set scene state and switch view + scene_manager_set_scene_state(app->scene_manager, EspFlasherSceneConsoleOutput, 0); + view_dispatcher_switch_to_view(app->view_dispatcher, EspFlasherAppViewConsoleOutput); + + // Register callbacks to receive data + // setup callback for general log rx thread + esp_flasher_uart_set_handle_rx_data_cb( + app->uart, + esp_flasher_worker_handle_rx_data_cb); // setup callback for general log rx thread + + // Start flash worker + esp_flasher_worker_start_thread(app); +} + +bool esp_flasher_scene_console_output_on_event(void* context, SceneManagerEvent event) { + EspFlasherApp* app = context; + + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + text_box_set_text(app->text_box, furi_string_get_cstr(app->text_box_store)); + consumed = true; + } else if(event.type == SceneManagerEventTypeTick) { + consumed = true; + } else { + if(app->flash_worker_busy) { + // ignore button presses while flashing + consumed = true; + } + } + + return consumed; +} + +void esp_flasher_scene_console_output_on_exit(void* context) { + EspFlasherApp* app = context; + + esp_flasher_worker_stop_thread(app); + + // Unregister rx callback + esp_flasher_uart_set_handle_rx_data_cb(app->uart, NULL); +} diff --git a/applications/external/esp-flasher/scenes/esp_flasher_scene_start.c b/applications/external/esp-flasher/scenes/esp_flasher_scene_start.c new file mode 100644 index 000000000..82a3f5ebf --- /dev/null +++ b/applications/external/esp-flasher/scenes/esp_flasher_scene_start.c @@ -0,0 +1,63 @@ +#include "../esp_flasher_app_i.h" + +enum SubmenuIndex { + SubmenuIndexEspFlasherFlash, + SubmenuIndexEspFlasherAbout, +}; + +void esp_flasher_scene_start_submenu_callback(void* context, uint32_t index) { + furi_assert(context); + EspFlasherApp* app = context; + + view_dispatcher_send_custom_event(app->view_dispatcher, index); +} + +void esp_flasher_scene_start_on_enter(void* context) { + furi_assert(context); + + EspFlasherApp* app = context; + Submenu* submenu = app->submenu; + submenu_add_item( + submenu, + "Flash ESP", + SubmenuIndexEspFlasherFlash, + esp_flasher_scene_start_submenu_callback, + app); + submenu_add_item( + submenu, + "About", + SubmenuIndexEspFlasherAbout, + esp_flasher_scene_start_submenu_callback, + app); + + submenu_set_selected_item( + submenu, scene_manager_get_scene_state(app->scene_manager, EspFlasherSceneStart)); + + view_dispatcher_switch_to_view(app->view_dispatcher, EspFlasherAppViewSubmenu); +} + +bool esp_flasher_scene_start_on_event(void* context, SceneManagerEvent event) { + furi_assert(context); + + EspFlasherApp* app = context; + bool consumed = false; + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubmenuIndexEspFlasherAbout) { + scene_manager_next_scene(app->scene_manager, EspFlasherSceneAbout); + consumed = true; + } else if(event.event == SubmenuIndexEspFlasherFlash) { + scene_manager_next_scene(app->scene_manager, EspFlasherSceneBrowse); + consumed = true; + } + scene_manager_set_scene_state(app->scene_manager, EspFlasherSceneStart, event.event); + } + + return consumed; +} + +void esp_flasher_scene_start_on_exit(void* context) { + furi_assert(context); + + EspFlasherApp* app = context; + submenu_reset(app->submenu); +} \ No newline at end of file diff --git a/applications/external/esp-flasher/wifi_10px.png b/applications/external/esp-flasher/wifi_10px.png new file mode 100644 index 0000000000000000000000000000000000000000..c13534660e188305fba6c5625ec1ce6841e238f0 GIT binary patch literal 1781 zcmcIl&u`pB6gH`Z%BmFygoHpsmU98Y_~+VQo4O&%2C@S^in|uYPoPg;=lp^061{vuK;mH0)!Ap1aEfjZ9#3COD(NEb)27p@I<-_@3^l{^ zJWV$>(?ozk<#ATw8OqAlhM~(!QY3km@JtqrIO0cDpn%lH9@2bCo0YXs;FvbUxn`)k z*i!=x!_%Q@x?Xi*{6@+a~SXQkz!SWHWcsb$^(6;>hKME;X z2F#C&@!TFtG&W`_aF>8J=K6AfvtYR5MW6F=ld0V%qHJ2KEx&Lj$ta(eFA7EV0@lS9 z3lxzxq6WkS3up+1Kyii72Ie%0kIw=PL%)}m70w9jbpr)NsYyb8 zJMsvNA`daU;~>vzcTnUo15vl_+OF=#%yQ$(Q&5MYCuiK3ViGt#NsF`|a;%_4zM2aoS%6{;vOnh}dV&+$;la51vmz|(PT2kC7> z4aZ`UqccUm8+VhBc@c67?plgWi68SK#ZW&o;wry~ZWrzC#?D|uW^alzJy;PgX_OM8&`5>HN- zezwFvLg}@wWQ4OJgU+cbVl$d(F^i;V|IgRmd^a_-4%IBOS=Kge;NjAc%S|z1Y*Ei7 z7`aZzLY~=nQ76_TL}+YM(=~`2JBzsEq*1US(oT;!*LqKTY!*Y$6~kq>hspB1U>R(+ zfYV^|L-!V(Bpbc%djFYEuYc5QN%Dnr&;9Yw)#snQ{?-RkcJOWM!}Gms%3q(obL%Er ziEn@L-j7dhz5Vr9H}KQ1Uj1|b*V}jgzWLMVKMy{+^U80llJ&7Tn0i|~-ETJDeg9wV C&oCwc literal 0 HcmV?d00001 From bcb8468e27b8cdd8a34e41c1087c2b7e117536d6 Mon Sep 17 00:00:00 2001 From: ClaraCrazy Date: Mon, 17 Jul 2023 16:46:04 +0200 Subject: [PATCH 199/364] <:Nami2:939038794794020874> --- .../external/esp-flasher/application.fam | 29 ------------------ .../external/esp_flasher/application.fam | 29 ++++++++++++++++++ .../assets/DolphinCommon_56x48.png | Bin .../assets/KeyBackspaceSelected_16x9.png | Bin .../assets/KeyBackspace_16x9.png | Bin .../assets/KeyKeyboardSelected_10x11.png | Bin .../assets/KeyKeyboard_10x11.png | Bin .../assets/KeySaveSelected_24x11.png | Bin .../assets/KeySave_24x11.png | Bin .../assets/Text_10x10.png | Bin .../assets/WarningDolphin_45x42.png | Bin .../esp_flasher_app.c | 0 .../esp_flasher_app.h | 0 .../esp_flasher_app_i.h | 0 .../esp_flasher_custom_event.h | 0 .../esp_flasher_uart.c | 0 .../esp_flasher_uart.h | 0 .../esp_flasher_worker.c | 0 .../esp_flasher_worker.h | 0 .../file/sequential_file.c | 0 .../file/sequential_file.h | 0 .../lib/esp-serial-flasher/CMakeCache.txt | 0 .../CMakeFiles/3.16.3/CMakeCCompiler.cmake | 0 .../CMakeFiles/3.16.3/CMakeCXXCompiler.cmake | 0 .../3.16.3/CMakeDetermineCompilerABI_C.bin | Bin .../3.16.3/CMakeDetermineCompilerABI_CXX.bin | Bin .../CMakeFiles/3.16.3/CMakeSystem.cmake | 0 .../3.16.3/CompilerIdC/CMakeCCompilerId.c | 0 .../CMakeFiles/3.16.3/CompilerIdC/a.out | Bin .../CompilerIdCXX/CMakeCXXCompilerId.cpp | 0 .../CMakeFiles/3.16.3/CompilerIdCXX/a.out | Bin .../CMakeFiles/CMakeOutput.log | 0 .../CMakeFiles/cmake.check_cache | 0 .../lib/esp-serial-flasher/CMakeLists.txt | 0 .../lib/esp-serial-flasher/Kconfig | 0 .../lib/esp-serial-flasher/idf_component.yml | 0 .../esp-serial-flasher/include/esp_loader.h | 0 .../include/esp_loader_io.h | 0 .../esp-serial-flasher/include/serial_io.h | 0 .../lib/esp-serial-flasher/port/esp32_port.c | 0 .../lib/esp-serial-flasher/port/esp32_port.h | 0 .../esp-serial-flasher/port/esp32_spi_port.c | 0 .../esp-serial-flasher/port/esp32_spi_port.h | 0 .../esp-serial-flasher/port/raspberry_port.c | 0 .../esp-serial-flasher/port/raspberry_port.h | 0 .../lib/esp-serial-flasher/port/stm32_port.c | 0 .../lib/esp-serial-flasher/port/stm32_port.h | 0 .../lib/esp-serial-flasher/port/zephyr_port.c | 0 .../lib/esp-serial-flasher/port/zephyr_port.h | 0 .../private_include/esp_targets.h | 0 .../private_include/md5_hash.h | 0 .../private_include/protocol.h | 0 .../private_include/protocol_prv.h | 0 .../esp-serial-flasher/private_include/slip.h | 0 .../lib/esp-serial-flasher/src/esp_loader.c | 0 .../lib/esp-serial-flasher/src/esp_targets.c | 0 .../lib/esp-serial-flasher/src/md5_hash.c | 0 .../esp-serial-flasher/src/protocol_common.c | 0 .../lib/esp-serial-flasher/src/protocol_spi.c | 0 .../esp-serial-flasher/src/protocol_uart.c | 0 .../lib/esp-serial-flasher/src/slip.c | 0 .../submodules/CMakeLists.txt | 0 .../esp-serial-flasher/zephyr/CMakeLists.txt | 0 .../lib/esp-serial-flasher/zephyr/Kconfig | 0 .../lib/esp-serial-flasher/zephyr/module.yml | 0 .../scenes/esp_flasher_scene.c | 0 .../scenes/esp_flasher_scene.h | 0 .../scenes/esp_flasher_scene_about.c | 0 .../scenes/esp_flasher_scene_browse.c | 0 .../scenes/esp_flasher_scene_config.h | 0 .../scenes/esp_flasher_scene_console_output.c | 0 .../scenes/esp_flasher_scene_start.c | 0 .../wifi_10px.png | Bin 73 files changed, 29 insertions(+), 29 deletions(-) delete mode 100644 applications/external/esp-flasher/application.fam create mode 100644 applications/external/esp_flasher/application.fam rename applications/external/{esp-flasher => esp_flasher}/assets/DolphinCommon_56x48.png (100%) rename applications/external/{esp-flasher => esp_flasher}/assets/KeyBackspaceSelected_16x9.png (100%) rename applications/external/{esp-flasher => esp_flasher}/assets/KeyBackspace_16x9.png (100%) rename applications/external/{esp-flasher => esp_flasher}/assets/KeyKeyboardSelected_10x11.png (100%) rename applications/external/{esp-flasher => esp_flasher}/assets/KeyKeyboard_10x11.png (100%) rename applications/external/{esp-flasher => esp_flasher}/assets/KeySaveSelected_24x11.png (100%) rename applications/external/{esp-flasher => esp_flasher}/assets/KeySave_24x11.png (100%) rename applications/external/{esp-flasher => esp_flasher}/assets/Text_10x10.png (100%) rename applications/external/{esp-flasher => esp_flasher}/assets/WarningDolphin_45x42.png (100%) rename applications/external/{esp-flasher => esp_flasher}/esp_flasher_app.c (100%) rename applications/external/{esp-flasher => esp_flasher}/esp_flasher_app.h (100%) rename applications/external/{esp-flasher => esp_flasher}/esp_flasher_app_i.h (100%) rename applications/external/{esp-flasher => esp_flasher}/esp_flasher_custom_event.h (100%) rename applications/external/{esp-flasher => esp_flasher}/esp_flasher_uart.c (100%) rename applications/external/{esp-flasher => esp_flasher}/esp_flasher_uart.h (100%) rename applications/external/{esp-flasher => esp_flasher}/esp_flasher_worker.c (100%) rename applications/external/{esp-flasher => esp_flasher}/esp_flasher_worker.h (100%) rename applications/external/{esp-flasher => esp_flasher}/file/sequential_file.c (100%) rename applications/external/{esp-flasher => esp_flasher}/file/sequential_file.h (100%) rename applications/external/{esp-flasher => esp_flasher}/lib/esp-serial-flasher/CMakeCache.txt (100%) rename applications/external/{esp-flasher => esp_flasher}/lib/esp-serial-flasher/CMakeFiles/3.16.3/CMakeCCompiler.cmake (100%) rename applications/external/{esp-flasher => esp_flasher}/lib/esp-serial-flasher/CMakeFiles/3.16.3/CMakeCXXCompiler.cmake (100%) rename applications/external/{esp-flasher => esp_flasher}/lib/esp-serial-flasher/CMakeFiles/3.16.3/CMakeDetermineCompilerABI_C.bin (100%) rename applications/external/{esp-flasher => esp_flasher}/lib/esp-serial-flasher/CMakeFiles/3.16.3/CMakeDetermineCompilerABI_CXX.bin (100%) rename applications/external/{esp-flasher => esp_flasher}/lib/esp-serial-flasher/CMakeFiles/3.16.3/CMakeSystem.cmake (100%) rename applications/external/{esp-flasher => esp_flasher}/lib/esp-serial-flasher/CMakeFiles/3.16.3/CompilerIdC/CMakeCCompilerId.c (100%) rename applications/external/{esp-flasher => esp_flasher}/lib/esp-serial-flasher/CMakeFiles/3.16.3/CompilerIdC/a.out (100%) rename applications/external/{esp-flasher => esp_flasher}/lib/esp-serial-flasher/CMakeFiles/3.16.3/CompilerIdCXX/CMakeCXXCompilerId.cpp (100%) rename applications/external/{esp-flasher => esp_flasher}/lib/esp-serial-flasher/CMakeFiles/3.16.3/CompilerIdCXX/a.out (100%) rename applications/external/{esp-flasher => esp_flasher}/lib/esp-serial-flasher/CMakeFiles/CMakeOutput.log (100%) rename applications/external/{esp-flasher => esp_flasher}/lib/esp-serial-flasher/CMakeFiles/cmake.check_cache (100%) rename applications/external/{esp-flasher => esp_flasher}/lib/esp-serial-flasher/CMakeLists.txt (100%) rename applications/external/{esp-flasher => esp_flasher}/lib/esp-serial-flasher/Kconfig (100%) rename applications/external/{esp-flasher => esp_flasher}/lib/esp-serial-flasher/idf_component.yml (100%) rename applications/external/{esp-flasher => esp_flasher}/lib/esp-serial-flasher/include/esp_loader.h (100%) rename applications/external/{esp-flasher => esp_flasher}/lib/esp-serial-flasher/include/esp_loader_io.h (100%) rename applications/external/{esp-flasher => esp_flasher}/lib/esp-serial-flasher/include/serial_io.h (100%) rename applications/external/{esp-flasher => esp_flasher}/lib/esp-serial-flasher/port/esp32_port.c (100%) rename applications/external/{esp-flasher => esp_flasher}/lib/esp-serial-flasher/port/esp32_port.h (100%) rename applications/external/{esp-flasher => esp_flasher}/lib/esp-serial-flasher/port/esp32_spi_port.c (100%) rename applications/external/{esp-flasher => esp_flasher}/lib/esp-serial-flasher/port/esp32_spi_port.h (100%) rename applications/external/{esp-flasher => esp_flasher}/lib/esp-serial-flasher/port/raspberry_port.c (100%) rename applications/external/{esp-flasher => esp_flasher}/lib/esp-serial-flasher/port/raspberry_port.h (100%) rename applications/external/{esp-flasher => esp_flasher}/lib/esp-serial-flasher/port/stm32_port.c (100%) rename applications/external/{esp-flasher => esp_flasher}/lib/esp-serial-flasher/port/stm32_port.h (100%) rename applications/external/{esp-flasher => esp_flasher}/lib/esp-serial-flasher/port/zephyr_port.c (100%) rename applications/external/{esp-flasher => esp_flasher}/lib/esp-serial-flasher/port/zephyr_port.h (100%) rename applications/external/{esp-flasher => esp_flasher}/lib/esp-serial-flasher/private_include/esp_targets.h (100%) rename applications/external/{esp-flasher => esp_flasher}/lib/esp-serial-flasher/private_include/md5_hash.h (100%) rename applications/external/{esp-flasher => esp_flasher}/lib/esp-serial-flasher/private_include/protocol.h (100%) rename applications/external/{esp-flasher => esp_flasher}/lib/esp-serial-flasher/private_include/protocol_prv.h (100%) rename applications/external/{esp-flasher => esp_flasher}/lib/esp-serial-flasher/private_include/slip.h (100%) rename applications/external/{esp-flasher => esp_flasher}/lib/esp-serial-flasher/src/esp_loader.c (100%) rename applications/external/{esp-flasher => esp_flasher}/lib/esp-serial-flasher/src/esp_targets.c (100%) rename applications/external/{esp-flasher => esp_flasher}/lib/esp-serial-flasher/src/md5_hash.c (100%) rename applications/external/{esp-flasher => esp_flasher}/lib/esp-serial-flasher/src/protocol_common.c (100%) rename applications/external/{esp-flasher => esp_flasher}/lib/esp-serial-flasher/src/protocol_spi.c (100%) rename applications/external/{esp-flasher => esp_flasher}/lib/esp-serial-flasher/src/protocol_uart.c (100%) rename applications/external/{esp-flasher => esp_flasher}/lib/esp-serial-flasher/src/slip.c (100%) rename applications/external/{esp-flasher => esp_flasher}/lib/esp-serial-flasher/submodules/CMakeLists.txt (100%) rename applications/external/{esp-flasher => esp_flasher}/lib/esp-serial-flasher/zephyr/CMakeLists.txt (100%) rename applications/external/{esp-flasher => esp_flasher}/lib/esp-serial-flasher/zephyr/Kconfig (100%) rename applications/external/{esp-flasher => esp_flasher}/lib/esp-serial-flasher/zephyr/module.yml (100%) rename applications/external/{esp-flasher => esp_flasher}/scenes/esp_flasher_scene.c (100%) rename applications/external/{esp-flasher => esp_flasher}/scenes/esp_flasher_scene.h (100%) rename applications/external/{esp-flasher => esp_flasher}/scenes/esp_flasher_scene_about.c (100%) rename applications/external/{esp-flasher => esp_flasher}/scenes/esp_flasher_scene_browse.c (100%) rename applications/external/{esp-flasher => esp_flasher}/scenes/esp_flasher_scene_config.h (100%) rename applications/external/{esp-flasher => esp_flasher}/scenes/esp_flasher_scene_console_output.c (100%) rename applications/external/{esp-flasher => esp_flasher}/scenes/esp_flasher_scene_start.c (100%) rename applications/external/{esp-flasher => esp_flasher}/wifi_10px.png (100%) diff --git a/applications/external/esp-flasher/application.fam b/applications/external/esp-flasher/application.fam deleted file mode 100644 index 9db70c860..000000000 --- a/applications/external/esp-flasher/application.fam +++ /dev/null @@ -1,29 +0,0 @@ -App( - appid="esp_flasher", - name="[GPIO] ESP Flasher", - apptype=FlipperAppType.EXTERNAL, - entry_point="esp_flasher_app", - requires=["gui"], - stack_size=4 * 1024, - order=90, - fap_icon="wifi_10px.png", - fap_category="GPIO", - fap_private_libs=[ - Lib( - name="esp-serial-flasher", - fap_include_paths=["include"], - sources=[ - "src/esp_loader.c", - "src/esp_targets.c", - "src/md5_hash.c", - "src/protocol_common.c", - "src/protocol_uart.c", - "src/slip.c" - ], - cincludes=["lib/esp-serial-flasher/private_include"], - cdefines=["SERIAL_FLASHER_INTERFACE_UART=1", "MD5_ENABLED=1"], - ), - ], - cdefines=["SERIAL_FLASHER_INTERFACE_UART=1"], - fap_icon_assets="assets", -) diff --git a/applications/external/esp_flasher/application.fam b/applications/external/esp_flasher/application.fam new file mode 100644 index 000000000..ff5d0774b --- /dev/null +++ b/applications/external/esp_flasher/application.fam @@ -0,0 +1,29 @@ +App( + appid="esp_flasher", + name="[GPIO] ESP Flasher", + apptype=FlipperAppType.EXTERNAL, + entry_point="esp_flasher_app", + requires=["gui"], + stack_size=4 * 1024, + order=90, + fap_icon="wifi_10px.png", + fap_category="GPIO", + fap_private_libs=[ + Lib( + name="esp-serial-flasher", + fap_include_paths=["include"], + sources=[ + "src/esp_loader.c", + "src/esp_targets.c", + "src/md5_hash.c", + "src/protocol_common.c", + "src/protocol_uart.c", + "src/slip.c", + ], + cincludes=["lib/esp-serial-flasher/private_include"], + cdefines=["SERIAL_FLASHER_INTERFACE_UART=1", "MD5_ENABLED=1"], + ), + ], + cdefines=["SERIAL_FLASHER_INTERFACE_UART=1"], + fap_icon_assets="assets", +) diff --git a/applications/external/esp-flasher/assets/DolphinCommon_56x48.png b/applications/external/esp_flasher/assets/DolphinCommon_56x48.png similarity index 100% rename from applications/external/esp-flasher/assets/DolphinCommon_56x48.png rename to applications/external/esp_flasher/assets/DolphinCommon_56x48.png diff --git a/applications/external/esp-flasher/assets/KeyBackspaceSelected_16x9.png b/applications/external/esp_flasher/assets/KeyBackspaceSelected_16x9.png similarity index 100% rename from applications/external/esp-flasher/assets/KeyBackspaceSelected_16x9.png rename to applications/external/esp_flasher/assets/KeyBackspaceSelected_16x9.png diff --git a/applications/external/esp-flasher/assets/KeyBackspace_16x9.png b/applications/external/esp_flasher/assets/KeyBackspace_16x9.png similarity index 100% rename from applications/external/esp-flasher/assets/KeyBackspace_16x9.png rename to applications/external/esp_flasher/assets/KeyBackspace_16x9.png diff --git a/applications/external/esp-flasher/assets/KeyKeyboardSelected_10x11.png b/applications/external/esp_flasher/assets/KeyKeyboardSelected_10x11.png similarity index 100% rename from applications/external/esp-flasher/assets/KeyKeyboardSelected_10x11.png rename to applications/external/esp_flasher/assets/KeyKeyboardSelected_10x11.png diff --git a/applications/external/esp-flasher/assets/KeyKeyboard_10x11.png b/applications/external/esp_flasher/assets/KeyKeyboard_10x11.png similarity index 100% rename from applications/external/esp-flasher/assets/KeyKeyboard_10x11.png rename to applications/external/esp_flasher/assets/KeyKeyboard_10x11.png diff --git a/applications/external/esp-flasher/assets/KeySaveSelected_24x11.png b/applications/external/esp_flasher/assets/KeySaveSelected_24x11.png similarity index 100% rename from applications/external/esp-flasher/assets/KeySaveSelected_24x11.png rename to applications/external/esp_flasher/assets/KeySaveSelected_24x11.png diff --git a/applications/external/esp-flasher/assets/KeySave_24x11.png b/applications/external/esp_flasher/assets/KeySave_24x11.png similarity index 100% rename from applications/external/esp-flasher/assets/KeySave_24x11.png rename to applications/external/esp_flasher/assets/KeySave_24x11.png diff --git a/applications/external/esp-flasher/assets/Text_10x10.png b/applications/external/esp_flasher/assets/Text_10x10.png similarity index 100% rename from applications/external/esp-flasher/assets/Text_10x10.png rename to applications/external/esp_flasher/assets/Text_10x10.png diff --git a/applications/external/esp-flasher/assets/WarningDolphin_45x42.png b/applications/external/esp_flasher/assets/WarningDolphin_45x42.png similarity index 100% rename from applications/external/esp-flasher/assets/WarningDolphin_45x42.png rename to applications/external/esp_flasher/assets/WarningDolphin_45x42.png diff --git a/applications/external/esp-flasher/esp_flasher_app.c b/applications/external/esp_flasher/esp_flasher_app.c similarity index 100% rename from applications/external/esp-flasher/esp_flasher_app.c rename to applications/external/esp_flasher/esp_flasher_app.c diff --git a/applications/external/esp-flasher/esp_flasher_app.h b/applications/external/esp_flasher/esp_flasher_app.h similarity index 100% rename from applications/external/esp-flasher/esp_flasher_app.h rename to applications/external/esp_flasher/esp_flasher_app.h diff --git a/applications/external/esp-flasher/esp_flasher_app_i.h b/applications/external/esp_flasher/esp_flasher_app_i.h similarity index 100% rename from applications/external/esp-flasher/esp_flasher_app_i.h rename to applications/external/esp_flasher/esp_flasher_app_i.h diff --git a/applications/external/esp-flasher/esp_flasher_custom_event.h b/applications/external/esp_flasher/esp_flasher_custom_event.h similarity index 100% rename from applications/external/esp-flasher/esp_flasher_custom_event.h rename to applications/external/esp_flasher/esp_flasher_custom_event.h diff --git a/applications/external/esp-flasher/esp_flasher_uart.c b/applications/external/esp_flasher/esp_flasher_uart.c similarity index 100% rename from applications/external/esp-flasher/esp_flasher_uart.c rename to applications/external/esp_flasher/esp_flasher_uart.c diff --git a/applications/external/esp-flasher/esp_flasher_uart.h b/applications/external/esp_flasher/esp_flasher_uart.h similarity index 100% rename from applications/external/esp-flasher/esp_flasher_uart.h rename to applications/external/esp_flasher/esp_flasher_uart.h diff --git a/applications/external/esp-flasher/esp_flasher_worker.c b/applications/external/esp_flasher/esp_flasher_worker.c similarity index 100% rename from applications/external/esp-flasher/esp_flasher_worker.c rename to applications/external/esp_flasher/esp_flasher_worker.c diff --git a/applications/external/esp-flasher/esp_flasher_worker.h b/applications/external/esp_flasher/esp_flasher_worker.h similarity index 100% rename from applications/external/esp-flasher/esp_flasher_worker.h rename to applications/external/esp_flasher/esp_flasher_worker.h diff --git a/applications/external/esp-flasher/file/sequential_file.c b/applications/external/esp_flasher/file/sequential_file.c similarity index 100% rename from applications/external/esp-flasher/file/sequential_file.c rename to applications/external/esp_flasher/file/sequential_file.c diff --git a/applications/external/esp-flasher/file/sequential_file.h b/applications/external/esp_flasher/file/sequential_file.h similarity index 100% rename from applications/external/esp-flasher/file/sequential_file.h rename to applications/external/esp_flasher/file/sequential_file.h diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/CMakeCache.txt b/applications/external/esp_flasher/lib/esp-serial-flasher/CMakeCache.txt similarity index 100% rename from applications/external/esp-flasher/lib/esp-serial-flasher/CMakeCache.txt rename to applications/external/esp_flasher/lib/esp-serial-flasher/CMakeCache.txt diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/CMakeFiles/3.16.3/CMakeCCompiler.cmake b/applications/external/esp_flasher/lib/esp-serial-flasher/CMakeFiles/3.16.3/CMakeCCompiler.cmake similarity index 100% rename from applications/external/esp-flasher/lib/esp-serial-flasher/CMakeFiles/3.16.3/CMakeCCompiler.cmake rename to applications/external/esp_flasher/lib/esp-serial-flasher/CMakeFiles/3.16.3/CMakeCCompiler.cmake diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/CMakeFiles/3.16.3/CMakeCXXCompiler.cmake b/applications/external/esp_flasher/lib/esp-serial-flasher/CMakeFiles/3.16.3/CMakeCXXCompiler.cmake similarity index 100% rename from applications/external/esp-flasher/lib/esp-serial-flasher/CMakeFiles/3.16.3/CMakeCXXCompiler.cmake rename to applications/external/esp_flasher/lib/esp-serial-flasher/CMakeFiles/3.16.3/CMakeCXXCompiler.cmake diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/CMakeFiles/3.16.3/CMakeDetermineCompilerABI_C.bin b/applications/external/esp_flasher/lib/esp-serial-flasher/CMakeFiles/3.16.3/CMakeDetermineCompilerABI_C.bin similarity index 100% rename from applications/external/esp-flasher/lib/esp-serial-flasher/CMakeFiles/3.16.3/CMakeDetermineCompilerABI_C.bin rename to applications/external/esp_flasher/lib/esp-serial-flasher/CMakeFiles/3.16.3/CMakeDetermineCompilerABI_C.bin diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/CMakeFiles/3.16.3/CMakeDetermineCompilerABI_CXX.bin b/applications/external/esp_flasher/lib/esp-serial-flasher/CMakeFiles/3.16.3/CMakeDetermineCompilerABI_CXX.bin similarity index 100% rename from applications/external/esp-flasher/lib/esp-serial-flasher/CMakeFiles/3.16.3/CMakeDetermineCompilerABI_CXX.bin rename to applications/external/esp_flasher/lib/esp-serial-flasher/CMakeFiles/3.16.3/CMakeDetermineCompilerABI_CXX.bin diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/CMakeFiles/3.16.3/CMakeSystem.cmake b/applications/external/esp_flasher/lib/esp-serial-flasher/CMakeFiles/3.16.3/CMakeSystem.cmake similarity index 100% rename from applications/external/esp-flasher/lib/esp-serial-flasher/CMakeFiles/3.16.3/CMakeSystem.cmake rename to applications/external/esp_flasher/lib/esp-serial-flasher/CMakeFiles/3.16.3/CMakeSystem.cmake diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/CMakeFiles/3.16.3/CompilerIdC/CMakeCCompilerId.c b/applications/external/esp_flasher/lib/esp-serial-flasher/CMakeFiles/3.16.3/CompilerIdC/CMakeCCompilerId.c similarity index 100% rename from applications/external/esp-flasher/lib/esp-serial-flasher/CMakeFiles/3.16.3/CompilerIdC/CMakeCCompilerId.c rename to applications/external/esp_flasher/lib/esp-serial-flasher/CMakeFiles/3.16.3/CompilerIdC/CMakeCCompilerId.c diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/CMakeFiles/3.16.3/CompilerIdC/a.out b/applications/external/esp_flasher/lib/esp-serial-flasher/CMakeFiles/3.16.3/CompilerIdC/a.out similarity index 100% rename from applications/external/esp-flasher/lib/esp-serial-flasher/CMakeFiles/3.16.3/CompilerIdC/a.out rename to applications/external/esp_flasher/lib/esp-serial-flasher/CMakeFiles/3.16.3/CompilerIdC/a.out diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/CMakeFiles/3.16.3/CompilerIdCXX/CMakeCXXCompilerId.cpp b/applications/external/esp_flasher/lib/esp-serial-flasher/CMakeFiles/3.16.3/CompilerIdCXX/CMakeCXXCompilerId.cpp similarity index 100% rename from applications/external/esp-flasher/lib/esp-serial-flasher/CMakeFiles/3.16.3/CompilerIdCXX/CMakeCXXCompilerId.cpp rename to applications/external/esp_flasher/lib/esp-serial-flasher/CMakeFiles/3.16.3/CompilerIdCXX/CMakeCXXCompilerId.cpp diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/CMakeFiles/3.16.3/CompilerIdCXX/a.out b/applications/external/esp_flasher/lib/esp-serial-flasher/CMakeFiles/3.16.3/CompilerIdCXX/a.out similarity index 100% rename from applications/external/esp-flasher/lib/esp-serial-flasher/CMakeFiles/3.16.3/CompilerIdCXX/a.out rename to applications/external/esp_flasher/lib/esp-serial-flasher/CMakeFiles/3.16.3/CompilerIdCXX/a.out diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/CMakeFiles/CMakeOutput.log b/applications/external/esp_flasher/lib/esp-serial-flasher/CMakeFiles/CMakeOutput.log similarity index 100% rename from applications/external/esp-flasher/lib/esp-serial-flasher/CMakeFiles/CMakeOutput.log rename to applications/external/esp_flasher/lib/esp-serial-flasher/CMakeFiles/CMakeOutput.log diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/CMakeFiles/cmake.check_cache b/applications/external/esp_flasher/lib/esp-serial-flasher/CMakeFiles/cmake.check_cache similarity index 100% rename from applications/external/esp-flasher/lib/esp-serial-flasher/CMakeFiles/cmake.check_cache rename to applications/external/esp_flasher/lib/esp-serial-flasher/CMakeFiles/cmake.check_cache diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/CMakeLists.txt b/applications/external/esp_flasher/lib/esp-serial-flasher/CMakeLists.txt similarity index 100% rename from applications/external/esp-flasher/lib/esp-serial-flasher/CMakeLists.txt rename to applications/external/esp_flasher/lib/esp-serial-flasher/CMakeLists.txt diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/Kconfig b/applications/external/esp_flasher/lib/esp-serial-flasher/Kconfig similarity index 100% rename from applications/external/esp-flasher/lib/esp-serial-flasher/Kconfig rename to applications/external/esp_flasher/lib/esp-serial-flasher/Kconfig diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/idf_component.yml b/applications/external/esp_flasher/lib/esp-serial-flasher/idf_component.yml similarity index 100% rename from applications/external/esp-flasher/lib/esp-serial-flasher/idf_component.yml rename to applications/external/esp_flasher/lib/esp-serial-flasher/idf_component.yml diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/include/esp_loader.h b/applications/external/esp_flasher/lib/esp-serial-flasher/include/esp_loader.h similarity index 100% rename from applications/external/esp-flasher/lib/esp-serial-flasher/include/esp_loader.h rename to applications/external/esp_flasher/lib/esp-serial-flasher/include/esp_loader.h diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/include/esp_loader_io.h b/applications/external/esp_flasher/lib/esp-serial-flasher/include/esp_loader_io.h similarity index 100% rename from applications/external/esp-flasher/lib/esp-serial-flasher/include/esp_loader_io.h rename to applications/external/esp_flasher/lib/esp-serial-flasher/include/esp_loader_io.h diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/include/serial_io.h b/applications/external/esp_flasher/lib/esp-serial-flasher/include/serial_io.h similarity index 100% rename from applications/external/esp-flasher/lib/esp-serial-flasher/include/serial_io.h rename to applications/external/esp_flasher/lib/esp-serial-flasher/include/serial_io.h diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/port/esp32_port.c b/applications/external/esp_flasher/lib/esp-serial-flasher/port/esp32_port.c similarity index 100% rename from applications/external/esp-flasher/lib/esp-serial-flasher/port/esp32_port.c rename to applications/external/esp_flasher/lib/esp-serial-flasher/port/esp32_port.c diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/port/esp32_port.h b/applications/external/esp_flasher/lib/esp-serial-flasher/port/esp32_port.h similarity index 100% rename from applications/external/esp-flasher/lib/esp-serial-flasher/port/esp32_port.h rename to applications/external/esp_flasher/lib/esp-serial-flasher/port/esp32_port.h diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/port/esp32_spi_port.c b/applications/external/esp_flasher/lib/esp-serial-flasher/port/esp32_spi_port.c similarity index 100% rename from applications/external/esp-flasher/lib/esp-serial-flasher/port/esp32_spi_port.c rename to applications/external/esp_flasher/lib/esp-serial-flasher/port/esp32_spi_port.c diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/port/esp32_spi_port.h b/applications/external/esp_flasher/lib/esp-serial-flasher/port/esp32_spi_port.h similarity index 100% rename from applications/external/esp-flasher/lib/esp-serial-flasher/port/esp32_spi_port.h rename to applications/external/esp_flasher/lib/esp-serial-flasher/port/esp32_spi_port.h diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/port/raspberry_port.c b/applications/external/esp_flasher/lib/esp-serial-flasher/port/raspberry_port.c similarity index 100% rename from applications/external/esp-flasher/lib/esp-serial-flasher/port/raspberry_port.c rename to applications/external/esp_flasher/lib/esp-serial-flasher/port/raspberry_port.c diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/port/raspberry_port.h b/applications/external/esp_flasher/lib/esp-serial-flasher/port/raspberry_port.h similarity index 100% rename from applications/external/esp-flasher/lib/esp-serial-flasher/port/raspberry_port.h rename to applications/external/esp_flasher/lib/esp-serial-flasher/port/raspberry_port.h diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/port/stm32_port.c b/applications/external/esp_flasher/lib/esp-serial-flasher/port/stm32_port.c similarity index 100% rename from applications/external/esp-flasher/lib/esp-serial-flasher/port/stm32_port.c rename to applications/external/esp_flasher/lib/esp-serial-flasher/port/stm32_port.c diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/port/stm32_port.h b/applications/external/esp_flasher/lib/esp-serial-flasher/port/stm32_port.h similarity index 100% rename from applications/external/esp-flasher/lib/esp-serial-flasher/port/stm32_port.h rename to applications/external/esp_flasher/lib/esp-serial-flasher/port/stm32_port.h diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/port/zephyr_port.c b/applications/external/esp_flasher/lib/esp-serial-flasher/port/zephyr_port.c similarity index 100% rename from applications/external/esp-flasher/lib/esp-serial-flasher/port/zephyr_port.c rename to applications/external/esp_flasher/lib/esp-serial-flasher/port/zephyr_port.c diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/port/zephyr_port.h b/applications/external/esp_flasher/lib/esp-serial-flasher/port/zephyr_port.h similarity index 100% rename from applications/external/esp-flasher/lib/esp-serial-flasher/port/zephyr_port.h rename to applications/external/esp_flasher/lib/esp-serial-flasher/port/zephyr_port.h diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/private_include/esp_targets.h b/applications/external/esp_flasher/lib/esp-serial-flasher/private_include/esp_targets.h similarity index 100% rename from applications/external/esp-flasher/lib/esp-serial-flasher/private_include/esp_targets.h rename to applications/external/esp_flasher/lib/esp-serial-flasher/private_include/esp_targets.h diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/private_include/md5_hash.h b/applications/external/esp_flasher/lib/esp-serial-flasher/private_include/md5_hash.h similarity index 100% rename from applications/external/esp-flasher/lib/esp-serial-flasher/private_include/md5_hash.h rename to applications/external/esp_flasher/lib/esp-serial-flasher/private_include/md5_hash.h diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/private_include/protocol.h b/applications/external/esp_flasher/lib/esp-serial-flasher/private_include/protocol.h similarity index 100% rename from applications/external/esp-flasher/lib/esp-serial-flasher/private_include/protocol.h rename to applications/external/esp_flasher/lib/esp-serial-flasher/private_include/protocol.h diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/private_include/protocol_prv.h b/applications/external/esp_flasher/lib/esp-serial-flasher/private_include/protocol_prv.h similarity index 100% rename from applications/external/esp-flasher/lib/esp-serial-flasher/private_include/protocol_prv.h rename to applications/external/esp_flasher/lib/esp-serial-flasher/private_include/protocol_prv.h diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/private_include/slip.h b/applications/external/esp_flasher/lib/esp-serial-flasher/private_include/slip.h similarity index 100% rename from applications/external/esp-flasher/lib/esp-serial-flasher/private_include/slip.h rename to applications/external/esp_flasher/lib/esp-serial-flasher/private_include/slip.h diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/src/esp_loader.c b/applications/external/esp_flasher/lib/esp-serial-flasher/src/esp_loader.c similarity index 100% rename from applications/external/esp-flasher/lib/esp-serial-flasher/src/esp_loader.c rename to applications/external/esp_flasher/lib/esp-serial-flasher/src/esp_loader.c diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/src/esp_targets.c b/applications/external/esp_flasher/lib/esp-serial-flasher/src/esp_targets.c similarity index 100% rename from applications/external/esp-flasher/lib/esp-serial-flasher/src/esp_targets.c rename to applications/external/esp_flasher/lib/esp-serial-flasher/src/esp_targets.c diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/src/md5_hash.c b/applications/external/esp_flasher/lib/esp-serial-flasher/src/md5_hash.c similarity index 100% rename from applications/external/esp-flasher/lib/esp-serial-flasher/src/md5_hash.c rename to applications/external/esp_flasher/lib/esp-serial-flasher/src/md5_hash.c diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/src/protocol_common.c b/applications/external/esp_flasher/lib/esp-serial-flasher/src/protocol_common.c similarity index 100% rename from applications/external/esp-flasher/lib/esp-serial-flasher/src/protocol_common.c rename to applications/external/esp_flasher/lib/esp-serial-flasher/src/protocol_common.c diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/src/protocol_spi.c b/applications/external/esp_flasher/lib/esp-serial-flasher/src/protocol_spi.c similarity index 100% rename from applications/external/esp-flasher/lib/esp-serial-flasher/src/protocol_spi.c rename to applications/external/esp_flasher/lib/esp-serial-flasher/src/protocol_spi.c diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/src/protocol_uart.c b/applications/external/esp_flasher/lib/esp-serial-flasher/src/protocol_uart.c similarity index 100% rename from applications/external/esp-flasher/lib/esp-serial-flasher/src/protocol_uart.c rename to applications/external/esp_flasher/lib/esp-serial-flasher/src/protocol_uart.c diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/src/slip.c b/applications/external/esp_flasher/lib/esp-serial-flasher/src/slip.c similarity index 100% rename from applications/external/esp-flasher/lib/esp-serial-flasher/src/slip.c rename to applications/external/esp_flasher/lib/esp-serial-flasher/src/slip.c diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/submodules/CMakeLists.txt b/applications/external/esp_flasher/lib/esp-serial-flasher/submodules/CMakeLists.txt similarity index 100% rename from applications/external/esp-flasher/lib/esp-serial-flasher/submodules/CMakeLists.txt rename to applications/external/esp_flasher/lib/esp-serial-flasher/submodules/CMakeLists.txt diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/zephyr/CMakeLists.txt b/applications/external/esp_flasher/lib/esp-serial-flasher/zephyr/CMakeLists.txt similarity index 100% rename from applications/external/esp-flasher/lib/esp-serial-flasher/zephyr/CMakeLists.txt rename to applications/external/esp_flasher/lib/esp-serial-flasher/zephyr/CMakeLists.txt diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/zephyr/Kconfig b/applications/external/esp_flasher/lib/esp-serial-flasher/zephyr/Kconfig similarity index 100% rename from applications/external/esp-flasher/lib/esp-serial-flasher/zephyr/Kconfig rename to applications/external/esp_flasher/lib/esp-serial-flasher/zephyr/Kconfig diff --git a/applications/external/esp-flasher/lib/esp-serial-flasher/zephyr/module.yml b/applications/external/esp_flasher/lib/esp-serial-flasher/zephyr/module.yml similarity index 100% rename from applications/external/esp-flasher/lib/esp-serial-flasher/zephyr/module.yml rename to applications/external/esp_flasher/lib/esp-serial-flasher/zephyr/module.yml diff --git a/applications/external/esp-flasher/scenes/esp_flasher_scene.c b/applications/external/esp_flasher/scenes/esp_flasher_scene.c similarity index 100% rename from applications/external/esp-flasher/scenes/esp_flasher_scene.c rename to applications/external/esp_flasher/scenes/esp_flasher_scene.c diff --git a/applications/external/esp-flasher/scenes/esp_flasher_scene.h b/applications/external/esp_flasher/scenes/esp_flasher_scene.h similarity index 100% rename from applications/external/esp-flasher/scenes/esp_flasher_scene.h rename to applications/external/esp_flasher/scenes/esp_flasher_scene.h diff --git a/applications/external/esp-flasher/scenes/esp_flasher_scene_about.c b/applications/external/esp_flasher/scenes/esp_flasher_scene_about.c similarity index 100% rename from applications/external/esp-flasher/scenes/esp_flasher_scene_about.c rename to applications/external/esp_flasher/scenes/esp_flasher_scene_about.c diff --git a/applications/external/esp-flasher/scenes/esp_flasher_scene_browse.c b/applications/external/esp_flasher/scenes/esp_flasher_scene_browse.c similarity index 100% rename from applications/external/esp-flasher/scenes/esp_flasher_scene_browse.c rename to applications/external/esp_flasher/scenes/esp_flasher_scene_browse.c diff --git a/applications/external/esp-flasher/scenes/esp_flasher_scene_config.h b/applications/external/esp_flasher/scenes/esp_flasher_scene_config.h similarity index 100% rename from applications/external/esp-flasher/scenes/esp_flasher_scene_config.h rename to applications/external/esp_flasher/scenes/esp_flasher_scene_config.h diff --git a/applications/external/esp-flasher/scenes/esp_flasher_scene_console_output.c b/applications/external/esp_flasher/scenes/esp_flasher_scene_console_output.c similarity index 100% rename from applications/external/esp-flasher/scenes/esp_flasher_scene_console_output.c rename to applications/external/esp_flasher/scenes/esp_flasher_scene_console_output.c diff --git a/applications/external/esp-flasher/scenes/esp_flasher_scene_start.c b/applications/external/esp_flasher/scenes/esp_flasher_scene_start.c similarity index 100% rename from applications/external/esp-flasher/scenes/esp_flasher_scene_start.c rename to applications/external/esp_flasher/scenes/esp_flasher_scene_start.c diff --git a/applications/external/esp-flasher/wifi_10px.png b/applications/external/esp_flasher/wifi_10px.png similarity index 100% rename from applications/external/esp-flasher/wifi_10px.png rename to applications/external/esp_flasher/wifi_10px.png From 6493dae10250c174679ae289418f3c862b1dca83 Mon Sep 17 00:00:00 2001 From: ClaraCrazy Date: Mon, 17 Jul 2023 16:54:14 +0200 Subject: [PATCH 200/364] Add example .bin files for esp-flasher --- .../apps_data/esp_flasher/Evil Portal.bin | Bin 0 -> 799536 bytes .../apps_data/esp_flasher/Marauder.bin | Bin 0 -> 908128 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 assets/resources/apps_data/esp_flasher/Evil Portal.bin create mode 100644 assets/resources/apps_data/esp_flasher/Marauder.bin diff --git a/assets/resources/apps_data/esp_flasher/Evil Portal.bin b/assets/resources/apps_data/esp_flasher/Evil Portal.bin new file mode 100644 index 0000000000000000000000000000000000000000..993755ee5ac56515245ea3307a91804aaf1a7dd1 GIT binary patch literal 799536 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 z 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�}^St(tCz+NECSYMr?ueKM6bOquf>@P(|)!_C+ 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

`Ci%`IDp43rZ2_t;7>_A!N#uoR{zK>g}6Li-; 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%0A`_u^6pa)U$Tt(nJ4*m|yf;8W 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%0At)8u4Ti(Y;JJ)X3JcAPZJ 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 z%0D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 z zHlvDC=|7J2nzP`Zli`hStTgVvJWVhZx~ z=>`T>DbPP<7{U7O_!vRBgSP+q>O0{7$yWaZ8oPba@Tj}<2r#S}Jpg_UyUyy}8e3WD zM?*da^-o922nGrYF+fHT^{cRX`34Z$D6}#Kg}M0{zIRBaDj-uyG&4-6*=zEO3VA}g z_cn?ehSM4H3ouGSeVMuZl!Lj-In3^5k_?4K`D;LEq$PJwzuK@m7xPoTVI^`YxRnDx zM@>QSKg?j>;y7jrnVkp{N#XxmQYWharxPzvPSU0o&b)tK&?y!_mB zLqWkX4vX>)BRKeF9L_5)%-1`5fX~fAPMQ2v{HvX?!jO*kTdk-;xx&N1OPmutj}pEl?BWWs@6E}@ zlJ(R68pg#)vxXOl(b)Xu(*E6ax8Z&i&TprtU#{Ihi;a=`-7&@OJ1j#^=IH&^Ji@&& z>$1Sl@Vf>=n+2n2`u4hxuojNtf*t0Q216b;_vmfHy)bF(3k?OJLP^v3FN95xEQp*&X4J^^ zl=Q;<5hPgzH%%@o9?tj5mcRxn|sM?wi;dd`!hX>oX8l$mc>TVz<1zFtNZ z7wU6Th@Y7~n@LKZld>FI@F{Ug^Ab~Lgg^YR)zwiT>N$$ZkBv=OyqHI^|7 zc5^9(JX1?y&MOF|GBNn08CF%mGCA363`7W#S+ph{9}l8`bl zDe2LqlsWP9<}9MrOCDR2yx`$^NhymLrz9_!vuH_50zBCCs2F`-T1sIy*#DF~Lw-t8 zK`23BI1?Pqumo}#uple9Cn&u9~3M>SQQy@q)>enTir{e(2K z^f}WC))y3JuSOa00Jo#PDYNIeMBQi}30;Tx!;0z(Fc00ZT!%0nahJv*9L40Pm$ z%BaGeg3J_5Ln(Q=xjE6y!w#Nmbx}bfsU+kLD+v>ZDyoVc^BpG1JE!59f#HN{hQf6Q z!y1xG4_(^j?W3mVfuV>a2L7;=dkiVY&8OdSV&znZRQb;v` zfwY|5^e0mab5rsP^VPhE$30*5k+9nVrZC#Y3+6wPvSh)Ed7*q&_=IHJajVVP9-%99 zPz6T`*#!i#hT$Se&pHx~^eKsFDdipDQ`Ue@awc^YNCKGKG9ZNm=Z;Jqc@UgBQr(`x zE!rBL3t`4a>aqb08cTtopW@p`9DMzm%xnWF5D4d6ivkI@S&GkzG>SNW6_W4%bj&V= z7)ifCFj&+^^*L!qZl)Ro+d)p)nT0Z8%$#^_QcCQCWagpSWOx_m=jKd(s30dLdrgL+ z7-PB!Y%QtLEw~(vPNpymRlgmIrAE4wjJuJ{h-ffn1eeUTQE@*nXjCp4xrPEBL96wJ z=~*m=0$1zv`0DWc$~AgWD8HYWitQx|%wTd1PZ@F`H7z87*rPe{I#T#B<)F0$A^(W1 zhKJH

&CzG1aKn}~LQjnYSeeopPT;+z!tYji(CgM4X@q$wnnne31NOm^gw;@FX; zweP4_ti?S?$&{tM&O$ zV!qO272wC>ZS>FNWvwsB#vaSrYK(ype#njPso`NxQaVcD^Wdw@_bNsy$s2UYiyucy zEGQy`GBWxhrY%(8o=tXcQNd3OCtyDre*d7NKPiH*+DKJ@xr~u?qyfX^ID-9QiX&^r z@T^yupS>E(85R+K*aOQsm~Wk3i)Cn~fuEV20s6Fp-2AlLVhI8qX-T2Zr~=Z@PB&iy zA6H<|=ci|3=aXHi$HM3sw@xS&ius10mni4(CaZoyVSZ71AqDaR)$y^;R1j$!@*VS- zGi1)ANl8l+l9~8<+JtCkbx}@XHlf(Od()#;kY^hTaach}hu>n@|P??)C`R_?qA zjn0B1IWfxF%p*$|FQ!Ifu>ax_mXf_XFX#4f2XE|%xV>(%5XF-LMCzi-yO)_)oad+; zIA>IhSqTz!IAzq&Qx<|@{%2D1B@Ea2r<+jHoN1$)0dBe=m&`a=a7P8NG!&-r?L*&W z@N>fG*kNKBm4NSOeHu2l>QH7KhABoPIbA|a6Xu{%6a#@Ue^La^H|I%1b^)P=jE8Vi zsV^YCdTWP=JssKo7oZmDWL4lNWF^_tQQ0`9nu5~S5<4Bc1u%Pg!)~l_#Q9V%**Rty zc+edE${aXk6u+ND0cYSf zT`=CE!C4`3OUV~bOo<*8EdE0j9Y`#RyA@6%;URG)FvP*Y;xmM;0q^JPLUG>YNccOL z?$<)P*7kzzk@F&2D@)&9&}1CSPxI{aSC6*fJqD+ z6s!)LI*ss2;EcfSV>p6X90Wo(89W=0Q?(-OWQbU5)EDN2;eQ#mT3?*95=$kRqufc}vbTvB05h9O7qIQ~^Cl~b4}`C1cPre$N` zs=^~P97w}pBY}hw`&lSy)6W76i}N#o7MPY+m&uZts?uTTo6&y?KEOc1pr zG&4Ot#ju9VmkvMaVnV4YIoYexiin7`l=QT9k>SyhISmul&tO*m6EJIj4wDH1!|lbP zs2R5v^(!C|5e^bB$ScYx3;EiV^vsnh5u_O;!Vw9jZM=#8tZ@{lFcbN zGGzGSkin2e9H*5k(CVD|+69k*h?zL2143~puqxvLk|2fuhvbeg4nahw;uyBzNuY+n z4*?R3VQs*uhl`&Ecz8zz5XO@P|K-RCK!kdDa5BPp1|rYF=8!=AJNghF)KrEI&L~nL z5QT=-Wbxu7iPp{V9EjyZjt}NhJi^)?j+q^EyFlW`P!`O?;h2w0FxCHmynP8=R7cit z_3edTuG)Yi0!eOPY-vzuy3r^m8oCh?TryxHA;!!ArV+D^8Z)9}CVg9@jUo8}GZ7p! z=_V4|hr|0b2Ayo%<0BQh%S<0MaF-w2{qWV0yd>Lig=dyo| z?KB$5{{3^16)TrP+(0=PDq=jAL(eY7k&)>zgWM|Y_AFRmFQxpCfn+DQKah6*2bH#e zjmKgOJJ5p}aQ3(zXKT8AXK7a9OiVf_x4HrJEKL!h3I4@BjC7X9-Hmfd@YAz2Ps2^` zV65}tlEJ^BvotjL1inY0zUek_dIyH^`J>^HASLafNxHg5o?6px;zTuU;JGN z$C;e5xW9!yli`da$~X!Cc9dZS(Bz*Yyxb_a=AevfxW`df5$ehO{hAE={K6m(?Z3d` z+(h92E0iJW1^;^-_Dt~M;6VD@aTtj@PNGa&$1ic1hi4RrrBB>$wu7eav#)Wqx4C7% z=bnMyG^mHcmU)}W?haEP&O;0_85T`A6!V1r56hNd!#x{P9p)XnTMbhI_UczI!|It^ z`0O%RBDth^u;lNbUo~ji1gB8t+%Q8@u79bj{>+fKe2_!Smj4bjhkfO;C1A?i*#6;c zpe-GsS@ZknIV@~Ay3(iXz(J!Um+?F>dhYq>mg5k{1gN7pNEMMbe`W+-{M|B4V#ExM zgF7lirN6)$G_JsREGRF%kADCE#Y<`92m8@S1adn?Xurqf`x+D{HF)IzB~B)kHyuFh z_$6ev6;*AJ_gJ(~%`m5(=8XJUPUGn{l8@mZ4o>m;r@$=s1d zCQ~Z?U;cfj(SNPsnly1YV@a)xQj{V?j)-tDLFqW-JP_&;{OnxBgm}bE^3xHYRugl>n2U| zR!#a&&GL_Y==_(hIVRPACW*V6WbRM0T9cBKlhV_ZmM>3oIFj=7licp4ivJ9kV;cP# z4R=*jf%^M3*14MG$293rX_o&{<9Jz<{}+w>9Zki7{{)v4fiJ1CD*JE$eHl1w#Q!A! zSAnx$r)$#x?%&se%8~z*`o9TO4*#Fz|2FU?x~lze|1}1F_AQS1{ry_y6lZ`W@~rd>iTtpuQewvhfWldPm)w@g(*Iz;*yNN;v*B z!rC-Itnv7U(>bKePmhaz4KNR34u{6oNBUwl)9^klAQrFyKsYP%;wdZ~P-N++U6%V! zl8c`pEPuFPjD`C>02lS)rbTK4pYfW=^K@# zLc9m>O`;rX#0Y(W#I^!l0I0&aL7*=-4CS12%B<2mo=Ed&Jo^Fg9sqTc9Y|C5j?9{f zbLh%+U;vNu`=8e)2ftvXIUNcXEYH($k9I zPet4nS~g%0!q)-6M%;A3g=CGjn%7zD0bdQ%SzRZIwFB`nHWJGLMnn+u{@wWQ5OCr) z$gHRHWY+1g;I3J?3b^#ld>>#B${spv58`Azjc3yxNcW(|dVf5L{Vm|yOyG=fWj#F} z`H=qj-8$=b0G0pQA^ovUn3K?qWC}UBOOn*d2JcS%UhGOBvCM4T9SN|`!F`S*k=`GU zkJVY{&D2?~fcS+PYfLK6`TOgv$8Qj8*CY5I6sNO39IgY+#eO60Q}q4~lrcY%#6AG{ z6!_3@6lKG|9PqHs8k+`K3`hq&0mud<;-1FY#HK#|k<2;)ZO;a@pq=diaP23exEg5V zGRS-UNQFPUci?C0?XMfJR|7uuQ0ww!hiIyzzKl&Ek5}XX{*4cn23J0x3F#vz1M@Z}wUy#@{a9aSi00Y9QfHI`50K5;N45;6TyZ|c4 zK1JME@J`o;zSs!@V>Q4pPF0aEv}K2rvGSC!NDN6{&sf8$-&Jc$f3+_*?mH6u%s+4s zDcqHSS@8dP3wRgqc7&a9^FTkx4vf{NzSxKH{eTOR_;%QS8NPc4xdzCi03HSViurKU zz=uV+FSPC|=zk4ug&ThkyaU+dmZvM4(S}{^5G3gLsLS8SHf)Nq~FCBBGX2SP7Cjc3RGFjjzv9xxKHiX*ZAFp=1C2JjIeAMgpli8#?nVsF47 zz>`>9R~h>!z=wcdz;Zkv84g(i*blf2z%@y+IexkUx3++XIPm(H^E371j*-|seM(X?0fZcY{}5o{6p3x~hXSx; zxHS!Q3PxHPAr21iNI)O*J)MSc4#K|}kO-InAOI_%1<#uR?SL*o2g)3aI>vmBxel-z z@D<`eKt1cRF_e#R5&WxkcxMgq+Yo*p{!ih)g!p?w%X5go0=NJ;4p@Qs`@VsUg!>89 z6)W_N7ZLsm;aA}%!aaxhNVwf_w;@gse`C*;J$>o9?WHp@Gph` zUC53a_}_!;kMP%Uli{X)$5i<}DDdY5e6ukG?z?~lfc|&*E+gPme6JBbi1}MZ9!C5|z|(+Lkbm)jdDQjD)2)C+ z00GPcqydrv4+Eh8^^qn>9BW)GNsfz!?yolapG$ zKNaD8z!!kY)kqK509OvEfIhnlys#WV-yw}hTsmMC=r{_^!^M5H9<; zFLp#tU+k;!e~f;hcVXA(5NqEp)eBpiAi1j+&p+vl{TA?h_;r9s036@}Ks@X_`EVhx-BG-sjg6 z@^>%_@;(jt3i>k5gFCHCTQnJ!eO2#?^y1AM8sWV@H}AiQcLg`$HY}4aK1-poU(=MU z@a(P@mwfJ|T3k@b%;h(Bp_j4liguIe3{^Bw*s*vL9X^UpFfXuK7wXONHr!xm#e1Aq z1^u03W9f}c=7k(PGW9cc4820$89vJK^yJiW{xJ+|^kp~CwBZsubB4m2#j&%@IQoD> z@Rr}y!R&|~mHIk|3?q>tGkjEf`n{=H(YUubNN~}6&Qf0ND|oRlN4c(xoxAQ}>ZNIA z%(|O`KpzQf`m&X5E<0-7A5))vz?6{{z?A+#Z>mprr{gk5caPQWPA~c59=CZF3AB<` zo^_qev&^_#FXO4crsw?EW@W2_eo~ZW;VL`3MlC$iH-1Zw8ux2$TMbl-MzfpU#q<)n z;?YuY-CB1sy``^sH0!q}bxqJ3)tYfDv-p(>Y2jI@&HJh(%5`6RPBOK+Lg*R;xi`6Ne- zJgCiYrJOxDwJzKcv`38^SCoYFE5q+O8D15B*W9+V-moS=Qnq4cIA0iU*u8q?5n1?zBbWO||VXeaF(gP$Tz<&9p)r~dgx#{CD~#r`b&`IykA z)BfN60}n3@Yic@|_c44YhlMsAKi-@=Y=D;Iq8Wovy!ZWaUvCGOMBN?qo!<#>a8rYS z=}pz?CxtV;E_Gg4^Yd(t*87iM&p&#F?|Z?HG4kW9ff(}V1hR>a)s%PE1WLhm)wpl? z=imhO9Zy-)P_SjP|D2gQOlUl9^DK$#OGhV66@^+<8C2h0`Yf&U<6h4vy{fa6sod$f zeDvQiddKpG=Hx7WN<}Ya^o1^EP_S}=diaWWdJirv@*Drq@rC}29tA>0qdf2S4zl?u zDSV6TeZgB?KdLMn#Cm~j{gQ8kuAH7j8y3OhC+xyQq z_7XhSM6-3bzG_V)2JkH`DZnT>2GM^>Dnq&h_fz{!!dXDFdgjcobS|GpWC z%=k%kBu$~^M?Wz-K^*w^<*>Vd5xzk*1s}{R`wPmB(8(hP1)5N{ciFsl86kD4omUSmjI_b^1cV zL%k(?rXagZgKKNy=4yoB&@zkY&6UOK(k&>f`E_4e!W z#g5Ipki1Guz9_4jL!9-pEY}rw46EjH*Q2IXlhT~KKDAz+@9@#maIq`)#KY#LxI8t)tk*(92k|XAlI9ceV z@w}>4WSN9EI%n7j?v|dQ=D1bK$*T%w+|ugdjp4oC@ZPGD*&e>vp3H7VjG@!n}Xm%CiP z*Z>-fgS-eu(x?mBhT*-qv9o6jcTLZUvX*P~(M4Lug&c#rS2rnrk_?Z`5778&w92I5 zyfp@SFVDzuQzJVZF)`T_hug%S!q^rB6mBoz`E%C4p@^&RvMySmT+HF__4&$% zq}pv;f=-dm^P}TQsOx6YAzCi-Md~*8(z`kv&N|(WT=_gXE>QOLOjF*~!n$(h(e50C z|JtLH(lnnVLQxMC9S85A?dAz$2JtI+y~pV$mH9o+B_yBcP*{n(wyDT}e=$-PX(Gn( zp{|`pS3NEyQzL%2FMex4NT@68!c~^46~wTyC(1=!{<(5C096v_Nh`3E^d`*E$|TF9 zhBd0yfun^NdJ10X+3b9Q*|aZE_+wAOIs`WDQ}Z5rOD^UAoE}zDXp%K;@Zaoosb5bV zK&e}+{}~!T^|ectD|^D`*#9JRFXFcEFa4GnFNq}v`3Y}Woj173JJNVvEXtU@Pi_2` z;KJ6!VI(o7@pnCQiVKg(ns)e`i)-C{t@%^O)*Z~ov1IFOOu_@iu%PD+Mby|ihqi_> z2?k>LT~7|P=H9&7g`cs7OBD+;32#>z9_yKAjA4>S;TBQGlrl}p#n+lvka+*7cpjIC z9w!ZxaVPyaHqDTvoJOzvexRpmzyIdT!u>s<;DzCvds;`3y#Lr=RjoUIfcxmLiqPGk z-*?KoyOyu(3vHOXjX8XVUdwq(ELW(ysSNN;>}lHP?>^x8;nzJS5wx#Qj3*fMXBo16 z_|Cf~HClSS(LJ8%o+|e>Bg^i*%YY=ibY&?gSGaf8lHLB56MDYeUF?5ECZHrWsh1V` zmoOx>ICOVO9GSa1l;3zrWaabapi;u$MesYfygm7A&iX00z{8}TXN1u`_r!|w`5&>IGT5_Z9M>^li<{KzEhi1H_%&Rb2%c+7^WVI3(Zg$$5V2ED zEmu{z>s?`N5+B!=E9@iL1l({BUbP=27FADlI0<@)afXwKqNPQFl{dP*H@X{dbh`pg z;Zd$lk(_H%p2;*bGSM_Mm$C0BsY?`%q{lnZUFET=l$1#-b~&6{l6rwOc6EEZ5FhQi z-d)U$}O$vJz#>xJ&5-n-?uBpubi`^))O#AINSty2u z3b)5fWeHz(r+m=8c~WwmK5CM6QZ25%W|{jYPko;iPIOz{9jtP8wVaZv%F8aukeAkr zVBx&)$}D-?D!ttkmBOKJs?dMjy(k{@2QAY^^!{AwpVV1VN_d)I#qGj~6KcEZJ;PF+ zToq?s;4tjzb_MVr#UnAlW|@eQQJyW`#~AlJdiP_sM}K&^an1)uDzr|AR2z;d79A*; z3l+4cGK=xNI5_^HZ?D+XeOlr2D`#A4Jj`2UerpT8v$%|LX`LqjJl8Q*@&_D!A}^Q6 z_|<4*0?U~uIZ<^|`Q!8`Hw9!yn5H^COoYkg^vE`qn1U8C!lG_i34>x}&0p!<`#En> zx2LH442?!f_HT7gk785dDIg)d+8tpUBfQeRsU+0tQEv*kB)r_csqkZZ-7+PxklQWE z65gZQI5@A)U^01?VPkj7pSlwnlLF1!KubM%Ht$K6QNcC?D@~wDSuTg9YhuVm?G>#n zutw!F(Ul0=p`}k*ohbmy8bjvBPt*pj?WOdH_9)%q>o?-|W*?19IN=#ua>f(gk85*O zpamXIl5h7Br&7!)&|; z7yO>c%wQm9gWgxADT3^(#F0TGRVGt$7eK2kNq5Yla8_zcEmz#ZjF1T$@Q&}Idi zA-8vcg@V3N*sFDw5#8RX?lano%b5IRLL^O?7~EjD#`x!j}xNod!l z?(Su~9*GG~3oMr}Fb0993q0yawrQQ=3TJ)ahbCD$IDocY>dT4Rja2=g;vpF~yCPSZ zR3+Xp6YneNyWwdoaz?q926ctztPa{4isPXXd7qcviwni&=+(Rs}`H<9T*6>YN*$S6$Tm?n3tnTm}LU^?~D6`+B8fxGoW!VbhWS1$V z;M*?K*<4R?WtG_OP@nzih!`UlvKY0*)&snwk8SVMEbtP_6@!op3dsXT- z*+gB+LDZ9`giKgqlz~L(uQ@kC`*Kaquw$y+lw$u=mW#G-gK($|JyyfGQi}XfS}xeS z41P}rSD!row*T&T>a`_NvfX=j?p0Rq>3U?ktcD5AI($wftWLruD5UN1C4_>DRxIC4 zmFMa)d$Lzg8N2+Yv!^e({)n2CD0=O}mg}juj7f;l+++hSV=qF3YYJ*{1A- zhSWHZY@z~UF{av32P=b$xK=!)b!IR@a|T>YnMD$w^R-tNm01!Et`O_4T zj9>bPThONY+C5RX4bvElDcrOmsHCTCQjH2tQ{iInXA~Q+h-FNTqCNyenMIbow{dT{ z3S%pd_MFRq`NG<@u3W69Q@`p>wRhpZcH0A838&EZI1iKXEv12n%9W|0fr*h={}pX& zh~8w$4#*tmAEeS}^V#FJtIDE}DRX5t3fc@=UWolqx}ZbDk!Zo}51EZIWYMbCaHo)< z=UJd5H)&r`nhJsBE<=*P$q>}4$gp%xl&3FUf_8^EO(A(Mu!)+WswXuC!Ci>eNc2bz z@hh{``@2HUs4$g)Khsy&BHbfX$2Ia@UcSq7S69_ruQb1?o1n>Gy5tzTpv0>e{AgQw zum13PET-7tV@!dfYq@E?yM7DTz?n2fmeUQUfEqSeR;~d~F4>huCjTcVJ;_J3stE82;eIghDf&J%9jR6)W!K`$zbrpRP`hTK59;xeeI4pWBh z0$nWd8KX@K5RpocjGmi0mTVQ|+Gr->H{{B9*@mER0&^Z&v}EZ=%;9;&oId$rg5iyu zF;v9nwKdyyutuD|>A8AS`0Guy#d49B7-txbU#LG*Ut?65%`ehNL8AtexyeDTI>W0s zVO_*r9@N%Xnjy`ZM{j!0+@u10d3t>69~7LuHmqxDb!?rRgIE(jrr9T`of$IWB=x!! zgG~60cF%6%D;i}<cnK%Suc(QJZjD3}b2bcWHt!RYC~Vfs_74A;SK*zGjPHuH~EZHao$Wmum6 za8z|_XFD*R-nBG_Q8kM5q{_C?#Pd|k{1d(sF_Ehdh;+;*?1L5p9fGUltY2|8a#q`X zNGx3|EwiW&1chz6j?T;Q8=kw#+I0&Xo}ZyCS02W`2`OiU?X=jPlVA!Eim7*oHu{j< z`Cgx6lD=~3P0TI!1DM77rYWR(R4NCN!hg`Tbmf9_gMK89c{jazH$AW1ta7h5KB6mi z$XwBdJOA$3T)SzXb{Efk{PGmjF!D`2x4YJ`;pWb&%WNRa9PXz256>u2r1gC+wS)+N zq6MRUd0OAP2+xJJH=S6|sE5{D!XFSOKZlzKz~h5$EdAauD~f5p?q|=Dm1HtM$M=f% z)rgT}!9kN!deC+}ep@?)3>qlmiI7e`WdSTB{FeF#%N7>Xz%E93OmguU4=8?VH*Rmbb92|A9x6RFwh`liq((~RB;G7B5o4d&+(CrdG=08=XLAc_ z?xm?{U9mJxbGKO9MxXlnbp{<8CWhlcVWeS)(d9N9BE(2_{)+yxx{7x=HcXf-MMX$a zT45Y@eR;H{?L+!(SoWUQI9@hxY%plS^Rlb}qj@h^`n4)dofQ(qs&VoP5A0bg_Z-gn zsh`t5ap!AaQQ1%G?}1C`9dG)S!{8QHc~sOIIiF zR=8&71tTF}Cq^IY%VMrKPxlkrXx7x-M7Tjc4I%b>2pT}?@441#->gRI-So*st@m=L z=W=J2o46Zlg$^1wZh_u&vD16Llhx6<78+OjlPFyGWe8eC*Coa-(XLSlmuVdJT%?}C z-wTpo`n7!9xl6t;aB*7=!#>SwLwVsx`fzIDHRE3}Ac4kV8utFZ6A84&R3aSfv>c;t zHL1-!wfRM=g>R+uKB2$czcbA0G~sKsvgQ#~zJA*2`Lt6=ppWQc>55q8mbt&+z$(H~ z8aYwBXPYIa6I%v9_Ls7{8Cvw8RO3~x_k&K)hn;6Ivk6mZQq^^l81|xY$a*?n^fE4> zQ4H`dXEGF|F^T*nXEGS_zFgx^yh%rHoXF5CzfzKGh#f`F%85pq*Fw(8ZSQpwg`S)X zWS4KmK@qo-$rwq#LL#|rcPIHN@EO^tS>&@*O0@w2wLf`3P;Cje={ejXAlFN$dC^CL zyK@sWyz$LWue;OZM&~SP9;PEsf6m1j#&pu(?UY2hLo&lqM1$Z}2Y1eq-yBiQydLb6 zZ30=>(>#myG1ER`;@}w1Rr) zv!O40n%~n<$N04dKc_T?7dtUOyBJeIt}Qafcu0eg@d*93f2%R%;9;t^3)6l+JB@L2_rrJ{hZaP-!Kl5$1Om z%O}=h_`fxDV?x#8t%m}S8}M|*a?{=2~8?TVZ&L#VJVME z^`}wH#z<1<)v0C=c0b&U6=Pnka(*xVxS*;Xd|WiE>wo2iq!(hL*jK`(WAu5{EW?B~UV??vJ{-vQNHDNLd1R9NH6gP!YDeM9YG zARdIG-optM^dU77mCKr`Vp)UbrtzXQ8=ya%wQRCi12bWIqbB(q&4MK)`LJfe6Oss_ z{cZWO12eWuF3IRne(t0!=MBm>^qFw1gB^pt3ZbC`d@C`(y2Up5te#dYd`^Gakmiq| z7zv-zz#z|+=7|k+KqGrT2uSgO4iO6JR&#rrev#E7uKKn(Y(*4&(2*B_AMk!zE|}w^ z9!A*PQS0)zCe~F}(P-DBjvfB)J+=y$esIR2`(ds}J$pK=*2%T;GVI4#6Y0Q8dUOz< z#?o!*TdzS`^${~RX_#u?|{inG?P@}(>N z`j#F7+`k{9@2%&}CvQE+o`CgJj*cm&S%a0p5N_ZHxfBFWzMZk=2UWM>4xRSmP`UOC zerSjNvL9^((%aAb4Q{4g@RL+K#0jY#E_H*6Y9vKDdAUU~TX{uM;#U5u%LS!_=F1={ z4|eF{X5Tx(Z^ z?cuSces#n2oK4tkTC_A+3C3x1IA_^1N$m#?+32n2sl1o2F&Y z&BWT1Ew&ifXp1aCbNURHj@=uho?NCxe>vfXXo%>DirU4@EuplmVanO& zmD-??PT#%(^jja>+z_FsHGG$cg-Hi&%8U?7`I)Z6K`YCc4y>lKU-R||1~GF7xp)eX!G9+G63 z&rFm>dU&sSnJuy)V|6?0eAKcgC9-mXJGf!yM0oZkn>?X|8cnAVuQ(#al5=1ZpCu%xyx6T&t|S{f!#RH*z3Zy4dMQ#dn5 zY#KMu^n>fhW*$2UF^tKQ>Vze$+^_izelU=%ATeh}_DScEL7|8cH(@+TF%8g%3(z0PM21+lAO^_x$LuLEa z=o^Oe_KY{$Iin+Dih=)(mduW(1O4yfGq71G8h5pOccJjjo~`YrFKB^j^D)u=B$v9M zG?uh`-@>CU30m} z4U;;W)F_RMw5OvQx3qf;QATCLc{;9OIc^i(hnB$BjeZnp?#lg*bv#fD3HhrHSCHFUu#JUWlR2%lqZO77=rR+icO;e1g3q{sGx!+ZBSd-yS9$P$Ym$mKlH=ex=&<>Z5+~# zz&bU4gL92xd^?wE9aMZ!p25^E$lFbfgLg_Y!7#RcUbl-e^tWw_RN|L2`QnAU+Xwef zaP|O#B8tP_Z9z!8yX3@)NFoHc*Y5UbnTktt;9Re+<;};B*6y5sZ67_t;8JrY^I-j! z5$%xK=mArJ5xZY2Vc33E!p#{#{4R`g1?15k>>{<3XEte6uIREjP|395aJwYCZN%vE zvkbKPiZJH-3H@z`-nKX_;dl}>mNd`O#<^v2iNtyi!p^u^Cv>(!s6%_*0KRD6gqqOi z=JkGtt8MFgF%@m_6Rx$DsfEjJI02CwNt$iiLN|e4(Y!%t7qF*;j8dM4Ob}U`w3=V^ zv;1=qgJy@HE8w81Fqo{ngi~CFX{1`6e?VqpDyl7I%@LIX{;vBM`f&T8yy{49rPMWE4I)Dv_;iHg_|sO3^c zE~cV#l`EJW2yZyq_;H)}<2KJHZB->Vnjg>>sq>3v`9+qGifIR0Qi}#&{|ojx z*r&@Fic&JI%=nxiy2AbkI(=eN9mEO911*Zt9-0V?`fuj;gTN&`@mqVo`S)lBGExH+ zcH1aDpY>Z&0(xTm1`nYnna!)9OydjyPH7^=rmJ_w*X3YW^?-qvx>U$L%;n9)LC>o`7K=xiMqcbCxCIxb+e zaHAFWF`>P6T%C^oo=^%ctyxv)sWytdo*S)L0x`nH8>oFy9vbiW^IT~?&ihkMcDJMX ze#)pwTgQwjINw@Zt7tmtzkioYdpbBbGg7;2Wy3>dOm#rBS+Y@FU`_^<1@D~U_wBZ^ zW$K+D-&2;8o4IRcq_(6kcvnv9TmAcsV>A(4zNF*y7461$u`D}vk+>@}GTT`noV)Tc zjulAbH`>p0l;RMzyYybE{tO}+Ps~{yf~jxIOUIHqb8s4U=VaLoRVFhc`G6xmU9o>sxys%mCNa|j zY?ZAs(X5VkWb{5BYEcUmON?`@%`ulrxY0V!sw}00bxcvk1Y|RVHDux32zlgw_M<;D zv+q;cjo9ybTxYzC%vs9@?wl%P<#*JHb?3AI%f~ld(dLDEOJVC8#_ZB3r*TRAqridc zO&5H%Uz2J_AJDpaKg-5em(;!-)a^nh+Pa`JWi&IwY=(wb`lBek(2DaNyNjwdMVWb# z^(&98sbAUn{*7YRTXlog9AS!i?;l!=qE7|X7VC?nKPn8`?J}u7D_bj9w&vkTauxP* z4{@b|)Kq|z1+1L5p7PpLtwjM4q?^VJYh2dqUDoP(vUT3k<|a*99LyKTfdJP_gGqCQ!q|edo8VvkKYhJq+U!88S98$?&LUnQcwVG*512-bx%Lt91Ei1vB<%C3_cvZQ4xVU&b+eGs=by z;pw?xKfC=@p7Zu1uZKOU!YNK3Z zkzKn1+h~l%>`jWXnPGXYl&3s*118dB!VzUYcR`MS%TG7VaqdLLGqU3f^4SK(kUh2X zmOYi?_U~eSM%}^cS1Y(md32rJTp2jap$KS{1$grr2r+I8y@!_;ai<;FX9Sj-AfbBr zhDJ6_Mj8W^zg5T%QM`dCcTR3TE4O)XK>R{+!%is*i!syoSbgoW#on-@F^YU$91^6i z#Nn@ba_f(QvjfAz+UPO(Nwv1B8`en`iSqPuTxK|Jg zf$Km=hE!4Vo}grW%ZJ;Ez*!F~WbQk0`|A*HRSG1&4Yw+}E%?92?d5-i8%|hBypRIC zK=(Ro-L4wy4Sn>TxE_*l-Ai%(lH&SDAFf{pzLcbp<=lzuA8y0-%iD3?E4N*3`QOkn z^cEe{{}tE6K$YUEobAK)5XE)xEnFuNRu2XzAIeE(Ao)g)?tN)4(N6(03>oRamh0)K zGsw4c{Zu#@Q?~gxsFtfRHmGolp&IILv!Wryn5!wtBv@YA8&vs7mw{v9SDv>UR9PX| zAdJ`(NhOZl^-|)`3;hAxx$}N<>ssWCrcdN#Qd`Umg!A(B6AS+%v3$|Xv z>W+HLmHRBsu}UloLsFj`h&ITB9>TU2F&+|iJUfz=bCb82x%9(OE}(*98JiWawHuX? zHwpAiRWqxU*5SZC^(<*L-0&K1c<#AT<+<0m2}i0auFXNHI|dqa>Lt<`dczxf!xIF| z_8VW)RQx2bsF)mdRF*Z9Xmm%@_l zW#>s@x(b%U*hr$&>2(p^M*FwQt)Ss+L*bv>Fc)VWee^2vvN%RyZ}+8vJ-&oPxcQ=Z z)C0r2FO4q_)AUhK>Ck-Ui^Cjv)MNL>L3eVuNVZ#j3+4!+w#B6`G8OYhF|Od^=-dF| zU`uWZ3w4TBJ2R&^Gp9N;V`%v{lJZdt8;FVEx+r`=v(DF+E1%Idw~E4E8k{o+iVQ1% z5_6Y#N=xIx>*ZM12qWnuZ*)sz_4Qp?m{Z$F8I9T%Akm^+o)>8E8QP-;U*ss!{E{WI z+)26Nx$>7ixdvy399x1WgOpy-v=nGs?${4`@p^&t`oG%`v2Rs*UZve^FH-SzYa4Aa zzOM4*wit_t`m(gEPl|8u^SOrgEm+k4AQm`VT$<3bU7A#vqQKGOakN1Bz3pXz`0FJl z_aN$yU7#X$NqN)Y+e+$!RV|(euiudiOKx9(g8!avNqM$~r7HC_1)Y|Sud3irWXhFr zqBBYJtE#CFQ)YizjCev>;(o%ujvjZT{qT0c@W%8OZ+eR-15EmSbBf0H7;PqaG*{K+ z^fxSSDOYUfY1MeW3kn&0xeT5eQfMoZB;g?O9p%I3o*}+7{H6urQk@#VC)~2Sk~CJ= z+~LR0sM}FNHvo(sSnxNGRznfJTblg!|H&jQZDYRV_iyIlVJ*^T<-)ZC@X_{oM_%_W zWSjpvAepuihuS{&wHG>BR7;^-2FweyiC}6uT3M!UE`>12i^0C`S}?!V7RaYyqf#=c z4~2t_iaWxijd;gmXv`t~7$YNv&*2^a3xF{C9{e7>llU6m0qm2-HqBtIkX4bgY5rls zwoWnQrkHY5jHphFQIx4--ik8fm})dbYP49;=LM=p(?FRj6z7>&0o&*}9FQ?pVb9lc zS;SEp92X6F*;^w+M2rDuu7_e2F^5os4x^1_+|Kofqv#i=-h*+6U}6O-hpTH*sk|c%mXr znK^b$+*lL4yGcQgY4w#t-+2Ea1j2X4!bWOG@ zbKKRX`wk{3Nn?#z`Tn(j`BjFUz&+|N8xI>yRh*h4Z3WV+t z+uwX%v|SZvGb%Qey#(#wb{PRWQ(;}PI+J>E26^gArnE`ilC@%uz`-yef<&gldC?aKoDt_U+Yxulzoy&ai@PJ;ynRx8`MNhb$13&+wG#Ll@G8E` zZIZ@B@PF|fxY*WS$NL_rI!$56MpP;1Pj?C)?qRYBY(+`3kGiP>PMa%Y#+SVa?|?MHeTo?rsP7q|-S z*ST;G<|jv%ztBZ1!{B05@(I$92+^Z+TU=BR-L3- z?&2&qT6iwqUQg6a-Eh+~gN73scqN&aK?-H&ZO)B&-<-7ik+UjtLP_cr&`3!MLvm%9 z0$udX{G`zM(1oE{p{q9)e(S@_P$jZqE~bWcnwFe!x0Se;fMyVjvFzPWll|C;n&Irk z5_hIAau^b^d2HxV#0hr`joI^a4Eq9&*gh0tVy46Jw*(Wsl!x-0VoX2nC&O#v33ovZ z6P6bpzJxwqijf|}AP(WMhTCIq&={%6p)^v=>7Ubt-Gw9*CO~f(D#sTyeJF{Gv78lY z3*`^{a9Juf2tkVOVqY@a?!lPB`frOFtj!m*kmh>&_FPiTx_^neG8AJ%$qOkZ`g}3I zb1up0B;EHpC%U!B;B9NX4+3845evS%h7K-M=WnAdE*+sLQx|lIcZOSOc>4(tcIkdT zYJqgnly>V29>1EujXNx-yf{?Ag^ced@wlZ?My?KH=r+ zyrwFzxx^de%pms9>5zqyCx&+0G%8>~Q_HCVj%#QgnBj=8< zqGMpXFGh|rVx{!_frJ6!82-HiA4$Rr!w&Jb`_=_SQ@WbNzT|C|34Amq?DPqf!UA|( zbhK>-Z=1wE#JiuM9brq1wmrbxlET8{-V=D+9NspUUFo#NIc+nYws@y)meZEtw9SsT zO`fM_*T>oJ2|3{%Tj%{%l{cou8J1~Lc!&>yS7Ie(^i3QT!M8_=Ukf9;hC5^>jUmuF42u3>o(_r%fcCem4zxdizA6TH>Knp_+9Rao@a5 zy;UZ%Q+FAjGL7)|)xnB1Y?RPShdsp^c_CMXZzxV>8pmeJ;JLo{e=UXqHq0ciK{sX4 zsR8;MFpLg=VxsoUa2!FAn`>e%fc6@{TNSe_-&H!kYjG6+-*gBsR1v-q;xFF1Ii$`&H~cSkX>E^;zec{Zi<-8`2aF)Mf6+TebJ6d;nRfZ zJxxAefu%)stPF>(+RRt6fgEpe@Zpgj!)4L-kqCv<5=Gl*Qowwa8YfyV_p@)tK&_*^ zF@w#!)sWH_v1kQ5@)ah31KrnuM!U;4LmQd$IFnm>7@xUQ&(MK%Z%JyP*1kw4JTJZ> zZN{p<>0;U7)loRYR#u6Od7JaHt36xaVGd6uo}(h(bKCx1eTL@?F;~{Fxhg-Ss9~HL z!wNH4p=bd8P~>$rLc76-twY^Mx74~<<@F?|nf>hAk6sI29r=8WO%+DcQcl~`gsnz> zTEQcDNtGe&$fOh?g&p?E*1feA7455cDdy~j%l`SJd-a|Qk$p!HXzA=*3HUD00#Ex# zAB^zq7O}3+SZLv+7G$I&jjt0T>@x6Pk-wS=KO98tu4{33~g-vL0mo*og?GOb(#5_4v$qXEAJAuM7>Ff;N#2mLi8@fGfrAs<`gLV7%V*6D=*5?r#@qg&##) zs^c;(^{n(@u+-DHN~MHLhujdTuI|%_O-%Bn{Am!O&s(hh&>4&5&QYW=@_xLpmYaeX z3Eb{>SITj0tZTOI-~VA%DB0$%T1>_lXXRdN^=GR5<>_zAS`Nta4{(JFQs*yAFZPGu z@%kzn;}7ewwb;L8G&$bjUwedeI38|!-Il+dJMQwY*e$#8@i7K34!L)6e4VUmI0=o% z@EgC-W!p50QJtX@5}*D%E=v!2gXi{D}<`Ey<*Cqzy~>DV)i4YY88Y^|U=l zOu?qxxvQW@oRQ$jkRzUom?UM&bun&~G3YCW&9DE2GSQ`|8x`&}jlz2nYT_Ja)bWnr z+6ZHHI4YA}dcW+T?e|rl6VrG5SM0BoLJ6@>&{Xf5vEv2VxK1-bWH{XgHM5y zIvMWYFs_dK#uj*VkmtZMLsGk-{g(_19ju^*66BZD*SSkWDyMK!EVx9IxYV8z{SL=# zbtn5A4dHc4x32fyB_De?yY;2h41F%=RLIP9eu@t z{LL>H)(?cb@^kmnJ=%}T#ugrJ*;=MMajgBsqQzKAu?&JixvyV zL@FR+3PTn<-xVDD!Z(c%aK7u<{IcUs<>p+6fHV;*KT^0T0=v;wA&U#QML3Rz&pq|| zL}lTTzO8!{Rf{5UG`P(#dzj@9eI?r>DE03KsRc;CKs2dHZJs9etF%NV1oRaBnsA7Y zkVK3*hy#&qC**0w|Mh*Wu0EwQqKnS^5GO(hrHW53v{AW$P_EVkvtR4a4>0WvJ`k=+ zv1&*IBrH}JToDDy`?JKN%w=sN=??=&;JjNp+%&*qKr%oSTO^(1rdYU(7E-3J#L{55 zzHkQ_QZuC3d79gTMLOZ>OWtduW0$_*GS&eU&0<}_MbY!@C3vgx@nssdiw=Tnw&XqH zKA?wLZ7`OVl6Y!F#CqPoJe1b+kHIG9>I*$j=DWCplQd#bCO#cA#Bsv+{RJm{sM4j$ zXMMLJMi(}hXzWi{?u=ay-jlHON$P@cei5F2i#H3Nxhy>EQ(uN^q%=GYJqsMAM?VQT zZ8Vp*^Op?%yI&Og>mkw%R@!nad{>J*our>cnbmMnu4+46d{I(tM;t!U+Xv>Q_MC!F zQLssMK~IvnK~r!>6x?@4QCu`a_GQOzAK5(^Ux~C0)!O{ONX^z3NOJ_`3tuV1%Km~1 zNdf!C!tl&3Bn$o_3Ii9VvWIdNWc3N(NVLDDwh7M+6wH?BMGNvAr%xq?wOU*Fu9*K9 zDqM=Sv=nv07m@;dzJtb)p#7s)Zv@gm6Jh{m_q#7_}7&x>(^?|CZVl0S*!bQ%`r z_|}_Qwu9!e)`|(?1fL(3rH=)OG8{3RCns+BUjt04tUi*4Jd5EEre!qXz6toJjFXd* z2y^gLmAe|>5*#V{NuS#1I_o7@CC#%ZML~NfHwb5YJstg3>VrNmtk(*SqT>K&JAJ`% z(UGSI=Q>`Nh)i3J3Hd0-tG?jqZOkkrV=9*B3QfUbQ8;{yq8`rx@(j-8>s*+~@s*_^ zDNrK{H8iCp9-N-~TMzd6Vufn4vR0b>Ke{C$JRe{K;Z`PLFMUvOrk_?K^@&D!@gh+1 zNP5m-z0UUttvKyg`DLO|_H!9;-zuEqqI14WAO;FOZx0SIoyQ{9@l zQgE8$m@GY2nUbQ0+mCe~3;9o=BP)>RBw(XWS{He~=oc>g>Z-gf_2tDo$t|3hr0!1? zHJ{#BOxx~5?MI5*ol2kW67Als+t>>0Z>k{TcaX6PPKknKAI#MZid;>>>Hc4ibI+He zFq4WM-|PgQAL7fuLrtceE7F4rx7ZRKFC3vwk@RaSHxFTrMs2ZW>g{I>_eir$$(^%I z!2#MXdJe3>a7!XfIcWMp0~Qs>x-27Yc=el~sQjr@@!<@Tv~ubTgzneE*~p z=sCK6@H53uzCRxA9^_KFrE`F64wvFRr!Q zua>*~^9$+VMuJ@FND!%o{rM0ep4uTZ=(^qTkDd}3Hq*gc>@dBT9(Ojjik8QFN(v)fa;NET zN!6=jQli6CJU@y}bQLS{O!Moa{R7c>TEsVS=m#jqBO!I8Bp07W#No=dA`G#3y?ZeN z_O;UE5O_^^#p~BibR}h`^N?JjoD5f`V~54S9X>+uPv;=4SES_$6pU^WOCU-jHMGGY zFU7-^&Oknk5q&8nec?h0uhItw*iamz(is2_YT`y7m(J~~{V*oj(=K8YwUK^k$M+Tw zBmCha)$eHUqZNWiXLw##^wRTN228;`q;#z{H6y+jHv;EMnSYsCnd@hRtEt96zms7 zLvF9-b2{XHwgO54OMAaLh88!CvDXb4%LkAx`dyK7PuYNBYj4RHZahrG+1gd+d#Q|_ zMy>rYJ?_2KvfCe~N8wmMo(&FQ`Yes1LjNWGpaMao7NEO4h@ zQ~D(4G{(}<&zDONTA4<`D7wsr27j|@G z2)oo4p$Dd@&bU$RgY18>FB}_A*ZGt!obPQ^DG6fVV_#_c8z-4geDQBPXUgtsxyK=j zN3ljRCrEt(GtA(Ep+xC?H^Kw?`HHu+w|Fs{I09u(rh%A^fbJDi;1I&DI( z7TX+oYe7um%Amn&vWujf0r<47gW?PO+&GDwZmCWf44xgdfp@CD8V}w>7@Xw_4Evd8q84M6;xnwjpo->~ zED&)27isSU7gd@4kKeiThXJlO{Bgp6H8VIckZufEg4XUZA%a$mq=s(o3Oa*1|^PJ~A=Q+=L&NHen{Oar|%e2X_mENc^mJ_3qAfvggqavc1LyfC-v;fP$ zvEsB2h-L6Nm!@<%iLRXCSaenGugPcb&=osAM<4bA_uauYB4lGtycLGt#;Y*Dx?qoC zIB9LFyh_(_Y_>027EIf1YcR&6XF3ifx!!s>Sr(1w$0sbDr zU-6s8Z!X32nO)E9T8^jVpyOZ(o^?HSJ&)tL5`Sy(w-$fIFV6v2Kva1+CTK7#Oyq>` z2;Lv0FMc@mJsb$m)!V|WheS|hv#7Ay7RjFpMS*q1X?le$HSnX?Q$c9;sQ^FvdXhkx z!$oZbp5l56&)y(h+`HG>ui9QEN`NP#;QUQv-O1XD&O)}K^sM%|t9UwCt3zMB_JSB% z+C3U5e!`ijm=41gVo-+t(f z?z}>=q*l9#3M$1Qwe?;7@%OD~Z~m5>%qmm1SS#3Y1=|jpBxTK(W!m3g=3Wb`{(q#m zi}$k63Np*lK5)igLS|nW&pn^Ck6%je$EF9Gr!-fAmvod?tpD8#al8s!fRY zy7KfwAYJCJ4o{7Lh?|A59NE3t9Dm4Nt;DB{nVIT}Zv`i&`c8pPeR!3~n=SjU;Q*H# z9;d!1N&w#nB|v9~iT4r#V4U#oHA;i_(}SL){w*B3MRAgP)&O@fm{vHP3=?C(YpmmX zeJT>GBd#k%KQNh$Ls#i^#8noEaPoE*vIo17CrqL!Z&>7U<6)C`!-)2#a!2EiK1b{E ze?3RzIaj|JUb)S|I-?Cb%rZE}5j5W*oHj2wJL&$9c}{A}96^mg);=JGS035KG}|xF z;(MO1_@S?1{}XAON1wquR_C7ObI*bb9O2F5M(6~>oU@^2Pq=ONS$;N!iN;aqQu}_%FZ}oCj;H=cAgx8-Wa&`5p0^BwABBBIh_Rm0reMeI%ncF-{%^4{24- z6sm3kPfmN&`4mSA9DKT5|H0~Er9%ZzpA^->qvMTl`QhdXmi?drZKpqTaD-k4DgScH zKV1e}4^ia)g=6ve>5c3i;oj{`lS9?~u?QmB!VxKaG=hl2LHue)>jAR* za0C&(Zy>q|4Y#$8RJT#GI;GzMYdvm!*|6B>w)aLfRSTN;jhwHOAh=%HRHtg*JJJL{ zsW+PqH18VGygUWal)9)N2Y5#`brgX8ftv!HO3hT6yAiers+GE^s+;}2R{zdF`W?a* z=wbVyjUVB8s-aV<AfA!EZ$3-~@n^&0=5J z{x*Ul5L&MUbE>W!QL{4dj`vY%z{IT{M>y}Gf#~KjUI zZ-(0nNBGx>0XUWL`BJCSxUrjraJ1!ja}d91MAvp5q5MN5nzuHN zCUf&sZ8NApN$KC&koi@dwJOU*KrAPw`j9RDxKQdBld&Wxo~UJ#!!D% z&l=$~N8o5!Y`IIP;?E2Nc`#i9ASs-iX}j%48B?F11B=TUBf^Xkeg?+qQO_QQjvuha zjU>jf*+~pi%ou5!;mGA1bI73kvbE$83ITvcqbU4`?q zShBXj@j=?>zsBmVSLvIp6u&KNuYUQtcFc%qrXemEbq2<~<9i$nKs|LO@I$;Jxb+lNMx!PA*A(Pkv)|8u=eMmJ2G%{i0v=w^i2C^+>R7jd zaW%vyOqi`+f}L~2>S58Bj{X(m5gmU1OTX<&G5xCwB*LP*#$~{|hpZQO zw{2tVqRVx=Q_(rqA5f+a;tbAk9O|#Ck$t(ihBtj#@<=3?gBLLRMy1!~;?i-1yfGZm zk{W(M^h2pWcO9<@ai_el^9hD+ic`U{qnVniv0h+wQDCLEX39Qf^9Q=6=^*Q>?M*-U z5(3Z|h=VtcD(sx3Yyv;23hs;1KuK3tzvkz!4_CqCwA&6qh7#u1wpxF6E#;lAM8HVu zSgmz9w3#Mux@f2Rvqhi9u$S2&#*?PZjNQJGn>e+?aw$+8qp`XD&ggXIjPdH~+F`fH zM0tg~g5`#D(hS`7(6O^c_s$)%`cxQ#16RdZp;r#qG`x?fsRqXFGN5R&LM_pjqk;NV z&+7q^ffUDgx?M`oU001y_-#*6={Rv@)v0U>KPcYzmhZ*ssJkxIMFpX|^U`85u=_Vr<_{g`;t zTI7XO%M&vuX|}bwnlm@=b?t5Al)GxQ<u9(jYp>O$uv7G>?OXkn-xK&J5%*Rw zo7Fd>jp>|9x_?a&GzM_PnDZS}N^7u`G1@49*N)Q&ILJ25jOy%Ar39Vz2TSRkNy%?2 z!^uKPGf+1=p|VES-{ho98l+3Hpw+3WMw@zVxaKWd+Zm)lKxnZ;nn~an-VJ1n7LN10 z5h$L(Di$&BL)XnNiEF{S%x=PIiEL>}X0bjxY~&Vvt(DoK=?PxuAhS3Fe~R$b1s;ef z9yg&;zEAG|YoPkju-i=}J36!4S1`6y!;5O-sC4Clq9kqqaAW+y-qBp zoaM&YJ{+RCyOe(UtfA`q;d`lf3{n>keyx^i-=dr_^Cf1MsTd~VE;hR{z{wTc;YC$n z)(|KjrwW!XW+%KbTy{EeUaU?KU>(lx1Aio3*pryBlGkm28?Jtd@&qe^scw*!q?Rzo zmBTfnJ(WXRRji`@Gt864gqao6;&Ey`3#3Jj0o?C`*Tqu!aR~FT5&1w6&w%38u*Sbd zC%#p^7z>Mmhn;drSGCcPV4|pgDuk0&H_&@?S3Ew;b7!WIIqbVM*r1kT3HNoUKB1#pxbo-2kzbK1Vl&NRtk z88@cWEx|yw%?wuNUUFzjyewf@>lsAj9pOg(Fv(0@f#}Ig1eN|R# z?l~O1&6O9NGc9bLYhHXVw|-RKO|iyqQTnb83D<_a?iKu{A=JYXuW+{2?&N$|hJ;H1 z{}zdv`mPy!B7OfE621lGckLJ8sqnHx+7i8fFX#LBknk0tf57E9j3b7-W0#WieKjPU z18ikBybXtyMEX7-5{B`;3a-IQ-)(iMxJg)rob#5)lpapM4YjB&aCk4fFFDoey2NF_ zba&jreuuD3wHBFOoTb(!En=-TY;yOv4_nbHTd?0=7thCNF)rPd0 z=?*r-TTwovrQi4Iknj;oD{DU#aJp;b-U;j~0Ci44+{ILq^6wX<1aMspD z>Ch*Gk-mQp32lfl38&Jl;QPqydw)oH4}i%eo7W%QnjvfH-jxyQ`^S*b0`P6^Uk19z zb2{+mgK^#AC*B>?(dK)5NN7gb?Krr%#cYV!R{v^5;=7!0$B^(Az=;Z&gdD6t7rtRC zH<`{MkAaSI(O601EXkIAzO6%o8Ynl@XnB8+IupeUArJtnVenJ|SQ)AaD*KeXJlQ|$A*96+Sy{;y`CPZ5`muoOY95C^n9_o6}y^8ms=2@y#3(ObEGyPZ-kwOMbcSQijaYwH~x` z_mD6X0aGEH0hej6G*!KK!w@%dYh9#M2g$psJcXpDt-HCZ&8;s+p51-^MXnv^#k%QZ z-%z)%vr|@i&A__qT2ntzRn@f4*!>B#;paEEp56W7i(^7Qev)5UynpfAi{DthY4P)% zJV{o5bn)=wDGzU)VVpFi(Pm~$H%=UyrOU`NP8iBqKtIb%KSH@3Gv9ofXKJF`A4iMs7&gb{>gu8A^&p;>aQGSV|v(^l<3F#Un~21x*J?%MBq) zIECY9IXVUSski6rxQA!T3c4PiIo5}}y<|iD$w4pwxu;+5TG%@0pA+*wB$sTqKb`@@ zw(G_&Nn7H`%W5rXD<52JueX!i7TX#MOCKz-&qXiJot@s5YoD=6n%$ur@=tW&_4b>gx7U`7vx-mCSTk3%-yjMzY_%o*2NcY z^?f=h3?Oh9DBt(-pg@&-2c$P^Q|W<~&#E@m*8X!^{)d-0yjiYZtk`B(k5e3$qe{P| zDmez1q)nvS<~`F|KFG~ptS)GyN^|nuh{Y2&G*IQY*=gU*r2-CZsTl1`a<&&)! z*Z-d_fNb$)h`xfyi*Y6xI65q|87ZrMaD(bOgxMeG{ugJinE&i5PoE@T8(ZY6TGFuC znC}{P7b=z(S@ZI(UafQ~yxPo_Buoff?P8{pdjXtc#PKv(d1GB>DPaB-kuZU6hfJp9 zrdD;R9QoauUSVqG)Pt);j}&6Buc}&Xc{IYI?KYx0+0fyjIj#+Y&9In?gA0qtdd=p?-9vyYs zhC#tF$X`6;<)`Rhhtru**^jdQtS^30h)2tR%m3>PSht>+zmvxKG=suz0RFBW0zyK3 z*sOfrj;or5@VfaWwEX&A*c0K@oUBo<$zEsqha7t1KSgv*cV(}u+>g_UJAgC&4zZZ7 zwtGmq!u`Ba;n@X!NmXyxjqi7ulVOL+{Uiw1(;R^GL;Fh^+Kw)tWKfU{df$-phtBAo za!$d;nC{+KqJ`qF<07W|@6QNV&UoLDiECk|BpN>P6}K}bx?787oO;oHVV-(PYTG?a z8Zx3%UBlaqf0EBkt&iFl=DAf;JYgZ|N^1F-ElGu(O}7s`5m7uLxkX!GK1|81D4zkbu^EFN}O*|3ZIwrVMfu_|+nu;qN#je1bZg_?OQ3E95w^sxXypgayiD8^=E8jP$ji5e^_Y z6HTJ_gpKAm+NjIdF)E&0O@qSo{*Nnvm%~i{+C&P+NI4DvF0Q*bVh!;g$QB+{-EH^y zN0?&uTWHBXd4pR&!q8m?ob(B4JNW{$iS>KqjIi|#|9jNoF~3#L-2(n$E@ZyCrAwG} z_!eBCr{4wPxp;yyF-B@wl_WJ3iZoOxrhVy*PPvDH4Zn#<}qNHsA^Pwt^dk`uL$U!Xsz+ z$w+gT=W)4#cB6uFgziQ5))nC`?s_jaPPe`iQ-6+Q-<-hJ*E^g#tYJ2@RXWjN9QlA# zD-%D{*4v$4S9xpgOwQ2-LzenBNC;JqC+!C>zwBTP(Hh4xk}EhRu5KAEgVK$!x3{!8 zy}LHY)^3i10S=;*HIO6}zjlutnU-A%IJ%l}-mbq%P(OshWfxpe*JfN z263rnQofL;+$+0bJInl+6jCo-kqm+y}f47z~`UjvUOgS&r+-EhNkI`tD zm&P;rHGM8k$!eXWJ5@z}r)9K#1L>^hbLmW-=6uZk3S;*`XV4DprROAC-$1a*XfGD$ z(eYHALDDg3Flx?zo32$xrHsT7a^z#hVhLy1{|t{x(ysj3P?E8*ePF3hHA|-v-QUbE zQ5dv%)huJQdw<9n+u70#NpunOA#B#QL0l)z&nV1Ywb-_z`dRoIsuQ;PcApt6jQXZ9 zIz=i?EsS}UX)9EB72aA?7&wd|E-9ujiRm)x|UkmnPXdz@%wVLpTZw zdhlt5rp#*kpT8!nDd+$wSXVfBY168^w71r1bcm4_LR_tQ-DpKYcXNEt84&QM!;v)>O0e;DxctN7m!c&5s2HK(ojsVd*_Jw70;0C+VPK^?FARtyLhd_4))TjY|M zrfBjKHezGSzgwaj>PXUC%G$M3Lzw1u)t8Ph zH*Yy+(C(8}MmPWC6kIvvXnRjXU3pjzUB^MJfe2BdA>c+{b-bd+96sY}to zKNuVx>K|pdzYWjFN4~en*l7{9-;D=bROpwmhOiu?W}T`(w%^dG08x9Eu*Qm0SWBSA z>oQG)m-`=N{Wgh%tv9DQG#?k0vn}jno7JV4acyf;mdXr2AXW*SgFMKR z>jL57Q_hI=39aR-rh}@jSCk!9UulplCCvX1M zY0tBgcI*U-`I+192O3Op>UtlGUu2d($i?cj;OIVO%DN!UwTasY`MR+Xjuvf^q_Yoj z9Z%Nl{GUo5h{(`Q#NIr!c#u%mf3!Z@Hsut!s3SU6r<&kWuRG(^_ zM8S5Q%9(1lF0H6bXX{dD?3O!q73Jlr#Z$sL*_?6PBNw)R7T{#$CW`3Vtc!*>ZvfW{~*oRxD z;x%73`>^!mX0+1t4K4#+!@LkbEuvUC9zg}0DqnK%pW<00;bL7=HiGu1lTbQwA)-;r ztl8Nl0)QTXi$>{|M;xh6-62PLYHhmI!Ir1Cq}oPKqFq6J0jDG;fL{I6L?tFWyQpmn zq($bT_>pllQ+JR)J=Hv$YBCM?g*3`6cLd{Q<+rIQ-n#gaxO+$AWl`_k!B!EG>7v!M zCD2zwD*QTtdzhY-M3Q$Bc&VaR)TqaO2WNwGC0GjJ+pd$T9p8h*^|`pS$+I#P_bkcg zMAvIi3u{mFYfgLl+dZGlLbdkj;Ga+B{HC6*+F;)nU9q{z(OOYar{hS|ST|UnDjF-; z%~@1{d3NnaZLp-;IL_Us@|Y!+GaIG)+V4?m|LrWq3Dg?_emU!In_`i(3XB%?DV4wE zw6Fw)7xE9C_6*A4(W>CIum}K)C?Am@>Tq(vS+!vWb|?6b3T<*61G>|x~a*n~sjmS|c~QC~km>k;E=;clem`Q+2+P<^*7#ckgnpOV}* z2dYv8-ig3wtmkz*-rDMo^i4Z0Bm?qx_O}7|p#>hlj5zoN7>hIZx!Es9s9|++XhCWb z7)|asuF0d{xUg@FHI$)aS54qn9CaH-}g z;R4_pv}0=sQ+wq#F2?-fb@cp;;U@dKq67C^O>2ms*A%k;s}j_sWr*`WsR=pqU zjl)=;0cbdD*&RqtjDGjNO19%D{7Q#eS_74H-LULiO_Ra*5)L>rB&r9mc;-pAMmv4I zr-b7u*aX+DzzJnht8TmMJ}|{HLDEjw8Di-p#lRb7(6L;1e~9l35S9i~f=-$e=4 z$^R`Lh+z4aDpnJg#niDGYMxa58IQ3_pmue@B z#WJ%sfZ+3zSK0QqK#S3RK;>6Tuta(fVE@+=%;T2t6_*2m>~%Iki~vqP4n)6bHCe70_iX-VZ` zBKPi2?btv`uvEg?iVy|u{J~Ah0GHy*0@id63S0`-?*J$dCI>{ z)*nxV(B=E{DdBlQH6R4wlW5^0(ZX}5gg*nU#UdG^+v;}WcVS6J|0%+vbHs2lF@xjS znh&pY6RO-B6DOnrtW(S;Z5FmnG;2JYaiLm#v2vjJc@+;KNi z7qqR>WzTMhy7tCczUP#Xk7^72j8imTUXcZ<{lQbh{Q!TMeFbiv`dP!2$~NEJQ^Fj; z4nZr;FiV#)`83;LdhZllIK1`($3C9rj`+bq8gjfXrftaWQ45wYkLb8tB6ocHcnu^J5r4{j7m*6Y z4kUgngul;mNtY7JhvfnF%&-Dk|2WFizg1E{MV~C0Mhc>O30MxK7y()d&ogv?@m8=L z%WdCd0~A6YV!S~mJBHQgE<`#EGQ5`$FETThJju2x_|Q#}fuXzitMu(6X8jXLH}$&6 zc+nuj@@jHr4`ox?=OR{DiJ!cLf_@cHFS(*ly++lt{GAGF*;M&Q!~RLHU{8B*E2=qD z(*DkLndNIRGR~38;hymFRB@hyw298dWdrKZuiJG}f-7*3 zT8QqKofIBJFMQ5to%H|u2HpSWq)-It*Fm}m_HU;92Tlr05cuu?qI>;I)NfNq>E7=H z4*eTNy7yEE>0Vzg_S^p>-TVI*>^J`>LArlQq0dFnh3MY0l|;BlLHCvyX+0nd(Y?Qd z$`I+kAeAWJUqED^Ub8rrXnu-kyVx^&i6~!{|3LGe0_qTtLqfjxJr9cmU_EHwpBYdm zf0uob?kRdz3V!i@zcAsXcWV`YvEMQec!Qh{npU3_#sOL##M{bcOpz$VD(Z|Ln@BT* zoYV<#opU-J|;XX#h}p8#%U_vc8etyDIQTyTRsy{w>)b4Y_^D8NkINKUf}3p$jFe z)=>P^L}nt%AsJyt4SnZG7sb-zIf;m9ZCLTGIfak+|4A|FWe}`+zr-_Bvid;mW1G&e zdaS=O%%362Ebd3`GbFsF-=8kwf7S27?=nz?#P$H?_pEYNAwA8HF?g}-B6GrU1^mRU z+^|)@?&o3=;tV4v9a(%;mZqWs-??F#zw94qh-!Fao>zZ2O@H@GGUtnRlWm_4J}UR; z(%?&%!nfp3gEw(!7$++Gh3^_g-@dt^`^?$F1dE<0Bk~++P8oLy|0cuprt zTGmTb8TH#e{gBTQ#T?HVPxax&9#5b3QhPVK{g~I_PnGod0^*QXr}Ve9P*`~BIcB2C z>ae+j)>7fc>@9$|Ncs5wd3!C%s0QVMoHSFcx)RA8Z>;tTWPPNF9&hb)NNm5MOsHg3*J~CF1wf(bipe9N@B}vyY-CGXR^f^vdB33O_qyNUzv{DGlj3eigo0wc+OUp8KBix|-sg3D zuv%lV{Wp#-I;yHzX9dMQYdhE1rX5#NY2CaI%gjrC!Ue>9GyAK6onL@zl{e$Z*}Q&y zr0?H-!dD3U2j~niyEan&7`uJe?gRD{5-zDGXMQ95tmGTP@Kxv@5Ito7F#qv@froya zP2$PJG3KymSN%zrd8nr`Y@aNt(Eret7b8l)d3OR7|NMgT`TqPX4@5ZTZ=YFyNY{(K zHM@EKHp%5x^@U=lVVD^GEtonyz5uO>8kKs9ESnVXz?*9sQ~ePk6y9!$Et3)5CgKeu z&N3P8yA&Amn;DF+-4z7cDsGCm$Su(>A~@`Zi3h&iC%oO){LenVw$Fc7s%x7b%1@23 zraqymuerI8uj&a4^v7gF7e}ul1jRYP9Jqe``T#sT`_!F zA4V$zwVMbC#d2_{vWs4qX`doc4pCB$Og+r_Hunjy^fkZI$FJ$rpO$7mPr*exqN&v) zWvwov_Z70tjTB;#P1NC$B-6VB+U3El=Is1ACo|sGH-DuVLz?a#h>x17!*N}W-b!iM zcXTX-bH1?86C=^CqgbdD1Wf3}h~YE1E-jCs4ibKG0h^=rTfAhj&|A&V4F)*UOVXX{ z+MTqxtz*lE0;}6)U@iWujPuRPTz7k%6@#gNbI<-%fadb$+xyg9dwW zD6`&=r|adSM8G!xqObnor2B1|L^hS*A&y(@g%*nBXC*dG-=yy;7bUOmtGZ%>;n2V< zQG3b<98ndA0Pp5HPWtZb6YlJ5PU+(%eU>(9_oNMu?#T^~o=NLpl{qHcAD8T@8>oxg zyxB3{k&YxVqg2vEY%Pl|FGfPvF~rv{_6FPPxyWSJ_d=>gJcBI1O-6HopIOw)mUvfP z=pA@D>bGK+wFqAu-v8LDOTD7QAuwk(u{hWtQVC%t?#icetq5qAYYjW>8E&HXbF^)2ZWwy<`0PYlUH_&0{T}B);kdX|HV90vkZw0^6i7cn(Dt~ zw5NK5G7PK#iotUGe6Mi6mw&I&8PCc<867(zJkeP0?iIRwn|pfsH+uDNNi(++=177c zVr+|hG0`H27Nz&&IQ^$|rS=wZ8U%&LFr`1(D;(-={4Krm3S;4k3+hTFm*)P&h%)jd)LE^L`#XVRTQzvt(><2;RhJ)y59P?0d?XU zcxHkrcti@{I=i=iC(3UL=c1|Eb{@89_%07e%^Dp1-MuvC z8gNyeX9weskFrhc<>VC=)_k$e>2OuO-JtF3DWE)Xpx&E!SClrpSM)TM0WF5*1FD>Q zZlGnSr7Qr41gNv^9+AqG<*Y3+^mb!^8F%@0`Eq-O+}>u;Lu9Z2c`0_9a(ac?z0Gra zq1?@k3^mffm#N;@qZP636`6-2Tn9mv&EG=f+<;>e_)InDbJ6W%;9X;yG9f(0Ho3Qc z96}E&!F?UAIG#OqV`b_zZtLE0t~*+5y=vM^uCvxGV?Y!C0p3BsyU+s6zEWBpiG5%j z+sl#Wf7EZtNQ}PF-xJ(W@G>2%wY5D6#pE}ThMB=!G%mkL-Kg+*#ZE)}U&IElC-fP?5Gq5?TLFA53gge6fORACd9Nt^PL0908O(6l$ z;Z_%5?GUH0VyZnoBx{t1&!~Bjf2jxeDV8&~m#LfrWpHmu$;mPF|IQSTivZnvlHpn0 zk{-wut)SJ;K(W=B+*a(jNU1sd0HIk-mn}K11c_u#s7Kc%k7pBUStJ@sB>;8fB8&89T~<*6K) z8`Y4-KiD&n1&g4$4W6g4YXkp>)|Y7^)KQ*k?wRYr$ChU^zwIue3`C}McX;NndpI)H zUqKU5j(pLY%avxDtD*v#i>N{nvga@Cfyxi>G#%J4_^(~TDC!Jobv@OyD9T!DP)Yb> zyExy99-+9Wxuhriz2E`dV8~KkE5(o{zC%)uZVK~`ZW>-3;gODH2VTm5fj_tWu-0I?3PbrhQkyXSqh3|EXEN87^?XG9;V#}s5 zhGj2zF-UAsto2T+S*dGHS~j4M%F|ir%X`4NSI5ioo>;qN-jj`CmJ-IM?CD=3az3ZB zbP$Nu=xkNuW~F%6NtZx*VJa%pMRL&tZ$|yLVZ}~xW;#5Z%HssszOV)lVq_@wzoyA) z4Gk`_bb~i(Cl{Sf`agWHRTRa?r0?R!&A3~FGcR&+o(0_*d%8pP^#^8A7sNxN3(Lw7 zU99g$(i~Z0Y?LpiM~LZZj_KhKc2ggvNzGGYJKpfAdW11O&8nX4D*^NVK#eZau8T1j z#jYR!_M+JD&22Ia*(FT%f$ru{y1(30?-hADae{JW>`?YQM5mnxBZ;8ud3cz2q_hTy zCuM}&5UdTYh}m`rT&qNGE5g)()u)?p4T_hMSe#anY`IU?uucCUL%S$wQ|24gCXxn< zaF=U@T^EbX7Du{;v)#>SyLo4~-XP7_#pIR2GW1%^o@e-S@Wo+}PwGL&Xlk=VG z7W%uJPj#1JC)-h!;eB~6Gs&5f-kMsZ7P_AKGm#C}n}et;Iuj>j@GTzBawn1eO-<18E}aIow>DVCX|~~YF^(S!AepCt`jGl& zb*OJV3mMxKs+@sllzRGcD(m-5a>C}qP}C?bUttOQ2tgW)D6)C-8QZPF6qPjM>XkLg zu@mcRejAJK#W$F+K0m1U0q=o4^hUSvMt8HPo6qaktEGhxQ7#IgbiP8&g+|3{-9!B+ z@HEKn;0>Y(X*cJo-w;*q3ak+I`>&9Ec|TdqHew4`q(gDo=^m==+WsWc!p{#4cJs1_ z@SCs4NnR%Z7>)1L*?CCo&k^SaF=C{-o%St&2a?zR(k=X@yV=^!8@lzJv~s8K&)vcw zyPN;ijj5P18bXND-^UbT>UGiB&sXH(*Pbo@>K=lW8(d5lJ(PeWEZ_<5$~S$#?-qXB z-TeFR?9T#)Q@g!waY$04;BV{x^7yoQ!+exgR)@UH>C>ZL;5qc2HHzlN@oXZ*J_2@|@CBM$i{cMG?5H{aIH?>k}nH;eK~ z&`XEyp=8VJCfsiCTGOALL~7xQ%(j@|Bc3cTaB<^xQLkY6Ca^8jkDvLTZv)#3{rFjR zFRR9dZ>&F!S+qxneNZWQxm2A`mm7Lg8TFp)B*PkJZfMvVz1onf?oyDkiTDjS0k<&# z<%cOQKv#ztHLOBQkZ;1Uor(f@JF8d@J!x*lG)X!`X=85fP6&P4E%GXZsi7$kG$ zud@p(?p7|mUil0{P6yO)UP@WgIxRdeaowZr#H@La;;bRMs1kVEU@wm@y;7c9ZswGA zI$*gR1{{EM%5w8)A3LI*hVq!Yc&8y_e1;+EKEz6ALyCscI8NyXdvW+=huIld7n6}x z9-k5S(}*ud)W@|%?Am>C)ZVB*+NpDx>yo#Pua8UIEUAm*!t2b)+@Or4nuN~Xq6E}u zo-X(lg*<%gbP$`Vo+LfvsaeK&Ub|+ycpS_QX>qFi1;?nblKGoY2+k9)vUwe?rR?_| znhsU#wpRO&l@%3muM2@Ra{cbv8PPBdOSQYIjLF9X2m&QsCVI`hlT^7qI#Ojt3*b095?Q$`~gD09FJi$MB zqN|X(Hio-R-+nz|!WiGY6T-X`{QV~$ndomJQy}UaTi?;@?xTF~(F*(Kn)Q3e*p45q zZNc@%!P}*6Sts~4$FQ1=UcU!>Nxb<4SbgmtGX1dXG}z?L9$kY&7X5zo?y$m-j=JAq zmcE1QFfTtEu}=a0pFZX?G?{59gtQZ0R~vuF34asY;qe(y2zLVT0hsnScCj@74Q8>p zr%}i}#efmsUC11!jfl}7E%PF*WW{>>uLbgj^Cjbo+Rwkplu013J*p@=aDJ`&8Sl4d z1jyHpTOoTkwnV@7t`$?Boeq0hEMyq7DMe`$`;pO5{-9AT!uKgFk3kxT4mr=cv8rhe z=;@xzTsSw58E;}f-K09*6pyKvo58GDb^UsM`AlFYk=$O7>$^LrYc6`ifJFc z^ILNkk%{Kc9p^TKfJP4y!!_0LaW47CkTb_07uT%eqxx*y$>Tx$CJ|-S*VV_5mwp~f zB`So(d+tC3M~|Cc%0xh&($;mH?>dgxB&9~Ppuvrk1XA1bqaZQuC&xwEEX0FsA5$!+ zGGY9s>W_|d6Y#N(tf`#J;*3$>EVlg^V1SLTM^kizFv^!|($@vI%}8f#dI*>N#CH70 z>vA5J?eXH~^;{}#^7&cImlkD$-{wB7{LHsWXo!;OAfqPPB|a8Cm+Zt7qdthpIDpmb^s_itN2N zvNL02=_%^*Bm7T;yAE9shQBv(BUvK^IK=+~V?~Qb2Y{VgV z9ByxVJ<7G6M2T%wr+V|5rwGH_3f$~is$))MZ)fNJl#@4=pF{AHP!{ha3)hV-O57}+ zs6SZZdS#t%$6vr!tKe6D3uRwNC~@X!e{(#t0k*0x*M9hq2WVT)H1S-n4Tg^Vz9U@t z);Kl42tBwpZfy0!r6UTN}41kl|nNJU52%(qA%2>RrIkcav{uh zAwcHkQ;fwypz)8CL(*D`QIa|4IHJ%uB~P!{K6%8HM&B9r^j!kmUiuL8cA_|ZBsuY8 zFwon4dZVzULVBBT{Qg)dH!-BpczN`@usE45yPJ6@*;G82ElXf7&Sl#ZnS>>5_H;ri zlK$pMijHa-%yj3oSB8`H}wpG z(lZNFj{xoY88jG(R4DO$i5-_8mA% zjSC8@SFT*j+-hPL^46f-9-A*^7ri7CdE46MOJ7)59d^WOl~p~XN^wZ6>!}Up%ki^z zO=K}1&k|zR7Q*W%-Bb3AYUyJXV+BD==x4>6BB_L;Ebn|X3Qj%7H~**0sS9|jPBxCL+Ex$22yjqJ*Ws-+gOf^QI9R(Q2* zX|UO{0ybZ-0*>0GY~>>@X^k@dR;U#rb>~*Sylmgt!{KgWQqQV#W1Y4~@>!H^s0(Zs zc8E@G%POo;&tZcXIqpubDsJ8U^~7hov6J6^G{k#LqD6ZOXEDTtv(J*~V^0XPZkP=} zI;G`kDi@w`Fs$?nqcl}jRa>n~X#xB^UlmmEk^;zC+&P<&H&J-A%ha;Yv{%wvrwVbE z;^vHF>tkh+&j;0ZH3<=!smJhcf{&AOYO1|B0>9=2#wK*Z+8pn>iX3>@Qw_+N%BK#C z=6V*V*jCJm&S*q%gfChfI-f8qvyMIu(c%;aWhXq{X{L*6ND@IvESa@tokrkI_nHL>52od#qH#%>W8`S*kPHstpGD$1tt0E z%oqE=XFhvjnC;IL|Hjk@)dcDU-u+-#kawRw8syz0M}Ne-hmYROyW1TM5J0>ej2m3` z$GkhIE2U|4;1gQ_JFmIH&WU~B)dlvg_$B3$UJ@GdCBYj*X%sjm#+0tYgGX`9K9n&* z)4`mnUowIp^yZ+*YYP**BBw7+!yPE_f2QH2+{7oO2&h|Y`rSr~P@>6}6i)6!@J*Z> z0XZew30>gaJJ)Hl;1NdB^fqNL&i#h9kz~uCWTa?D-3|sZh;uKX7>fyNFdD~nQIsI( zHrF#h=G^kG|C@6gYmfdH^VU+H+E5`K#hVX<5%l#J# z3#kT05GSc63g|!p$%w*_Gs-yN`vG|&P!EBO^h=GXBWQY5StE{-&Ykkk+-b#28UQh9 zjgnBINx>=sm}C$SrUrxG@nwUj2H%U1k^(UJ{;4k;Cn44|xUDF0pe8jP$mqO)3#hG9Al1w?uIhnq9ss&pKSlg>fJB z17}~Zcv1fFczOOufjld5ru-@4HT6u@LRFB>=PwWU#$(gOvp-;$k~XMdIj+!wj6b3I zuRiOxbtdGsP~{5p#^tqfvPg%uDwt1%y`HfyR8_q|m9j4<=8`{!vDti$Y-kGkAH0-K zWQwwTh!;AF&#ooS7Z@RabJgOOFWB6%C@{Gve4RgKa<3}aPnO$pxr0~l77qa04ZN34Ok#&AYApFe_P9k6q23T>h}io(u?k^68; z6k1a`qMNhD&RUCja8EdVBs6GgWH?KjPxwOLCDo7mg06QmAkQN(Lu3w0qh*vPmFY!c zLt*K-hetIKJZA@9i~N6fbN(Ga$^ZQy@}GSpe=1sOeD6qz*#9eIB%%3%=A4{jTR1`Snp>=Q2W;8I z6mlR)+?Yhz;3OjUt0Nl1Dk8uEqg&Hl(A5#s{I}8SD;*Ks4a}l`@O6iclS#H{A8-v5 zUYa{>{!aIHLS2m^ft!eDosn`gVvJ0h>k1C+AcW$SB;eO=NC>(@bc>%g21BqrYD!2c zNO^YtkZ0)^|L=KHn{UdKK0|p@pZ#rftVxu%JO6EhSjbwC+`fTyU2FkVqCap^E%p}aPZb7 zO;8pfIYNwUPf83iq!o!GodBLpsecORZ#iNcL)dUeG#~VpW4sZu!q@e`I-4Ks6ya!w z2%--q|EtarRbV+!V+r?RQZ&Q4koJ`V#=Bb?_rZba# zNYoiW()L6_hwCb9(eA(Df04`Ry>bboO(_=TjXMvJiO%ThLQ)A+&IcYzGv5-g$k?=Lj0_9dP3~v$>$yH zhBR>(o2rwh9n+}^_J11s{U>3sAxxtnav*(4Wm-baB%IG-;ck%9noXunDUdXCsH<|# zWtRg^R{M8i6OLR2EzKgIfT7S_OqZ0$BjL3%Q&Y+Z$&JdUE@4v_|5BIN<;97~dN!r5 zjnd8~Z-%41y?ic>WzZ9>JrL+liVC{Rb%_#F>Wa}l_ffhFJ`2HP(5sECEvpke2Ggjm zv@0P-Y5QYWLOhG#^5_iK5nVmUr+v07FHg4U+bCqNE4hPy4f@Qdx~jxAsIEjGgkYr+ z6l`ulGN0)BDP+ZeL5hD0F$b5;K>;KrDg{5x;{7%^vj1`3G2HVean-6Ti4iNLDqVvZ z!S!~23DFsIFuQ<7#KZ%o>fofBq25XaALK_|8Cuy&AhG=8z z4g5q0AxswhEQ-}A2|khXpdN%${F{C`2VcHW$-24#KnX_H<80Z$`xgql$PNpK4l zsw3G%VAd%0YU)(jY|2|qRnHga1gdLfNb1wws9D%gAcWE+np|+cU2`GW^w2keK)Gn} ztScFxy-%cJN3?-mirW*9x9^cB*G&d5MOA^~JlAeGfbopHWBZ7fWqez|>JLj3QG(<;Td(c1k!s2Qt%6rDQGTyiTIMkr1T#Yy=eabgwX z4IZh6Xl)b4$@?F1#wkR@ZHiM*aaR71IBLcFWM@S9tgiKS6rtQ2A~x|ko~&p^zC+>s z=}-Cx32sOZ^8K0;nn!93`q584*6Jedl@Ox<<%|YIw}<94F`v1e9p8*5B=W2&LBi1l zK|nO^nTG21WvUpKtbPBJQ34E>8^hU00nkeSsN^L06%G+=5_sy3HuUR4d2Rn8uOE^^ zO^!0*>m6hw>vtonJxQ;f*l9{5HX6LAC3OWVEB3cNDO%h6spdyv(Zs1K53sh56;u`> zbCh_4`4bL0Y7ho~p}7->V8z$y>`@*2KK7(k$n-6m!U!fGpXnR?__EDJavGJ3ygOoy z_xf~gS4-O~^BvJkTT~rTl9pcGkZ ziNRd;t%(UI#gpb|3p9oE1|wajT@=j@5zaEPCXII%W0#eoSr!+1ZqT62fQzT;X|RjjE_gG(o{yJ~{( zwuufiR?F5oPtDmq&HDQVOS8I6bzO~e>t7eR;FiR`8UER#Dk^i7i}p)>3ZD>$d_UtA zKEE24`;{w9S4>kQObxBp{rboyt(bOW$(lEdXXK>0G1aA5XHP*eHq~MZv$@e_?}~lhp(CkH1D}Tn`BRn^B&)HII5{>LFU0u*qAi6uCe~LRdY4U)Sx=w zps6T-Jltx{!d)@UWD|3WKhxT2tJh}21Gx!P4)IV^D9o#{;V$l6|f(RdnIXoX)b&K-0di!x0Jdv_4O#@H*mN$9^^#$iU2~&1oM=xyW>L%Fh?fxMR%3 z2E3(Y%}5?@ZwfFeGwpkV;!u)w;$D$@h|V8{gPxAqkf*560yyDaS<$kj>4Vli|MVXV z>BiKrPfPtk>fHg`&!#k$zn0mW#+G>@*K;2??R?U;{;BnQ*|OUi)Ay!H-@>t%Yrnqr zzE&=Vi8V24d}P|5wDmiST@NlWIl8QA&{4TeS#y%uY&Uao^+0aa-1|$fIb#yfK(5;r9eY>R5x&W$@4 zLF&&J_|gm3=TipYts|o291Vn*U{06fEHdS)a*G@>Vdo-!X`RA!6#QiNhk??MsvIiw z$EpoOU2Z2I+rJdxvY%BJ@gSohjD z?;Y41wP#;WMRdj@S3P(JJ%ZVv%gBY3ovmCbc z7`ii;HN=z-1CkVT?^t5BoV1LXGRA}Y+iCU>E_)3U>p@LZl}!B^63D`hNbs zPyPFu(*35rs<~-1XSKv|$*#S?Q%hXAl#6MCb#vU-b!^$s=?q1S?~_l3Pd@eX598Dq zQy&-hd8F^-PlZE(6u@pb-3(5Wvg@f`dnRG5rtIx%YUsK*yd}NcaCNWm?N5b&0zror zLxBgvD}QVH&NMaBH1Gdw?ds#2I`93-NeB>*8eWXZOHG1cw6dE7t3mCS0M#J27gP}K zx)BhIUAK>;?Va_~-6S|YD7_zEYzwUIPH?m#69=d*NV%UJtr$px!nW2r-DoZKwXJt* zrAp2HJ|{u#y6vyaAHX^1_dL(n)%uy?w^dCjddn3K4 za_oHcQ-pS9`qHC3iV%5)AQG@9*(w7s-dtulmAwV5I&e-vHoE($0YMq43M} z&UqB8uI+slTl2JZE>E8#@<$=C9^yE8o;-PMg=5Zm1l)6Mg$SIx-{Fk?Y((p(8WU5sXv6 zqccO>Q>tAihv6P@^nX}Q5BLH-59MWa_RE+4u08$~@ZW#NRU^Y!FCvzMb$nSY$Ab6h^><%_WFz+$cWK_MsY&j03mn21v zB%kCH(D0W78w23Mu|YmV7JRlNgkX(~ErqWM(F7o5$p{>Dv@{qZI}a**arrN-bE@cl z3Va`iE2j{edxe-PI!^_oW{z`#r(`5>h+^H{WrwOx;6CX|dAWgl`|t;&$0jP08E;9Q zr1S7|wI7YdoaB>08@C*{hEp3UTtBrQn|yaMJ+PW`edc*6uV`y64q~nEK0=3IO(8)6 z5o`Y`nIhqKzqsW6GTUk&WH|8*F82hm+R6GoFcNl>4!@a*EO3Oj1AE41$}#13m?QAOB=SM|AJ^Y*5jE627( zKeLIMxlQ^2-`)zoE%i-&n{Q`f8}9%#S4_{cYUi`3ik}v z<;ZbvvNDMoIWWv00BrKkHv`ng1m@7#SX};(z@brU{i3Lm_F?`ld{z&<8Zaj_X+~ol zE=hWmixcuU$cGBo9G^7LY!52|Z=Y#S07C2Ji~@B-I~)4rhJ;c7q~hFX#mV90^gs%w zkX%3qg0q0jpL8lT5zV9h5yg+1(-n_gP#n7az&Lgm+FPc^@#!~oWE>Y3SB~XHuX>D8 zY?F{td(}~Ov+fn`R>aPd_%4}C$x?mC(RoVwE+xf%%{9(re zz*fPoILWZYAQg^IDdZu!dK4Do2bHZy?Q+a%apzqq_O@Tlx{7Xlfv zF$ikgS}Gd<-{8N9MN90`+SLFaza7w~DC;QfneOeQ8#tK$63-D*al9BUTZ~}( zA(5r)QCY~C2{jX>F9{Z;*NBT^+j%fK(gnYyyq$DnyEA5^t33we`!QNGt4WOBn3bdl zXzq&uQEkZhC;4ztDz+7nX;H+0TaI7BNQ{3H=<@mf;x5W0 zDZN?LAeych&1gWtHnUjRo8zqc=)~zL{x|*;$uoUrOq5_^L^19;^crTX{RF_|m%@xDfguCTTVfi{XhCF`CzV&2A0 z8yv`RtS%bAMs`l9b+ez};&(jl?{=tC)gxf% zzq~0HoCeVqYGTW25Opd|dH0QrK<2muAPs#SXtju2d8UoRNg!zub>Q522t-3{JO^)t zZ2b^6)3(Hq>FV4a&9QD&>SA$wYE>T%t`(KeZ-+DkoDb;5BM&Y7T=S;%iZGj65 zsXTd-oca*E+!aGvyl@IlGq2;=+$h8dgn6#*1+pj^dig@UZRHoJ3&L$%$t%@u-xwE- z{9Q`5h-)<|UCN`J94w#}S;ub?<>iX5U(D=pA@~HaKm-+MgCj7~TVZENC)Vv(N zfT&V`Ok^u3AnK!%&DodnX8i5ji!HQ8HN+5G;R>nHbSC~SwPf4!N$aH!dG~=p!e(aw zk$12dY-dRM^7n2O-InOi_$+69+Dhv$7SpOf`IatPvN+xJAwkyo#7r2u0omnZqf4nb zWhOCtZd*6M3^?meOSAY)n}^h+8}eC(d`ll$vY0M7?$ZtWG7EfhrenVOHkHSx8}QAq zzoVM(P!0HUE%Uon$4S3NHt<<|TqSQ%b8y%=Q6WJ*CpW2!TU*vIE#-3-yyXwe&v4s5 z7{tqsYDG4cm-CCgAD|z^()ss`5;iZW=H9ZRxZ1pE{I&bBXGy%1wlz0HIVXekkZQmA z06l;*H{T4c5Ae}{pMnJ|FQ0mki6Pwgmk*XRuC*I8TG3)rtO(>-z8VE?H4?;w z>v;V6Nu)l}9)myoJ(?m-5xU^__(t~lVDt4d{W6_B1#WA^osX zOjMFD&(SNg9U*hW#M@0$rr2JTDr#Ot2i0Nv*uZwx1NU zb0>r%d0i~LphM$l6XX_w*(X$;iwkn5LR-^lpaE=%Z6P&_L0y8(x1|mu_ZFrJleMdv z9{=<8kV-*tL^yMTWv<9pCsbtsmUiwpLYu1!*m#2E1{2z4MP2iJOw5s<&a7%<=kva0 z)`uHk?^aEE3@{XosJ&j^A3$ceIX=f6pY0oufM_f7b=TeGoN`n2Acl}aLhHI4MwS?l z)resbgZ6D{gG+j*kHq@;IG-cV*Uh~$emp=1AkSfYmLQa=S}4||PjnJ%h_#!-^SZ!J z(KlgTqP#*VFZS_LpF@K3j&Xjf{11cW_1x)iS0Ic{sC<3wVEzv4uHNY*x4ir|vD7&E=b6lRV~`qdg!c&1L6K1pajH$m3HeEWj8b~b+1 z$nXp0rs5@fYsLBv~v||N7%0BX|}^2 zYld9^2R9R;)l{QS)D_fMEtD72np#fzT&#}Cg~2_}xN4^I&kA*TLUp|3{Z1WhY5kj? zF4<6Ble~j|cXk9+z6Ne4!aVT%yssaT022DU%h4_l-Zfmr zF{@;(?glZ5PN%-goTHdO&C%@HJo`VIDra4ycm5ky|FX9!lJSm4N|;`0ER~kK_?_G` zN1bQ$hP43MqOSiHzISt1Mk7TH+*NIFBT_F!Y6ly2PkOXpD5a4YQh2z*lg&jk4QMkA>PuJ1nl&I_2Q z`U)>hY@L;FN6PCr0d6_46ff`Hd5L_pc*zwJRsUlzcF3eIdHs`sNm@1<8OCJpL`oj2 zzBmZ8*b(9baT|0t2cbD9ZOk@hnP!@0UW+-d)Do^Qi0kI#V2(M(=l?*&DKs5S|D>Q? zp`Vb`FVU(eJ%ag(hVCGCQxMUbS*7Y&wfdngx-lApQ3}!UG~y!MWs|0Lc^(wYQq96|R?^Qf@BO$lQC~nr`DxH}N@$u2BJ?Dca5_c~mVuBuJetiB!X?N^;z*3U_Bb zE-hL!QS{2!10)nB>BmoL)qD>_497Hds22(~dSMYaKmu~2{Dwy27%e;dqHfNE_@>L2 z#Qi74@Ba*}hd>2IP#DZtk~ke|W!`b6Q6c zyzBZ2)Z=zesgN1btl` zRQ=cq+h0%YF$HBwTytEF#1>C_!?1RfVs($bAkIlIjIf&)Xk=I*nwVT0x|J?-N_yg= zcEe1?05g?Ncgbt7r#Jlmn_a#0RQ40l?i93W{6~b(_Y66DhiuiPpRA-Qj$~Ve-6A?+ zJ51E5c5!d)07-8-B?pPDnh-_>#J`zH7g=7HT_XbQ)g%2fB7$t!roK0azO(W8>>FZX zwN2%Ppl4KZYi2Wv0QMaL3!ZJYN$OawJ*WntoIPe*nn$Ucdji@%238R-Dqk5OsaH1+ zIV?lAxn!uIizBvELAaf>)-UkXsGKy5g$hnyzi~*do(T)Q8r_~QVOq%!?%rGoI>{@+ z!4|XtM^X`%sd01*-9o>i(XaUpW@jV7rR5ct3^i6F4!Jn5!_#kX0&BKgqIR5 zSTkuWE^97nOF|($Oa!+>XsZcr)wNjbc2vKoK}vNov6Ytq#8An^8F86Wc^^vy(9k=i zOIl=xn0`s98~-2eh5h69H2=E2djruupTbBmiRjawZ}n+U^?Mag`dk<*^GjG3`w4)3 zt6#;!=s)(a+OdH2?$J7QTGG;Nv?LrW`Pl9Qw~x5+P}@WXkCn0806vi0t<-(DoZR9&}2z#Bbkr1xc&;T<%-1!kApH% zIZ|B0tCLogY${_7PJO==&8CT)A+U-TeckE^m;Q~M{eZr46z3U((lfsrzPBN52s8>k zi7-;o4x9B;q&z`d=dU!2Ku1zcT)OP)Zp6$iR<4g%v5I~f+XEw9G(bWiV_?%|21RX) z;UJD&$RkdjV$~@#yEP<+HTVG56XaaWJdJ`$RI8(cFYnb{ba}Eur*}l;7rFCO8cD9U zqMo+UG;GZ{1;fP{*7)H`Quy zHgHEVU9UApG-WZjUt22Vf)e&EQ*-_dDBBFn=A^>YRw^(&-;nQy6O zRz-|GEw)CtamTJq5ffFK_K+~Ir4H#!GN>}rxm4=k?smlt@-c%%mBD$5O+J9()Wwr( zrgWL~@Fj6uZ3*PVJ#FSO^5mp1>MEekRD94cAl)tQ`cY$&s8O*;E72g+a76N zmmyYI4MDzC9H$3gB2*Y;CfI|SW3bi9noE+7NvI;3)ch^>g_Dn|HqEpxoHSYAd)&?` z;f-SFu&E8l7A<~BrU=>u#JWV-s7e5J_8BK_mf7b<7sZPRUo#8zzbMqQ8C=w}17M1T zyW~XgJ9w=6dpwqg5j_E2&qCM7$+*?O_IG&Lxw8URr$d-P!N7sjKgDJ4gShN{02i!x z4o;PW&#sRXIKrx_%I}OBlQ9FPjkYea*usS2@X0EBD6V+O?_|_+~ zs0=*ZD!qk^Z*otqhf$^tCXFb81Ud`pdp%;*!8|o8%JS!d{LjRXQzfbQ6ZU)_m?p^= za`%7{FIG{?^RRG(_2Px=D7E$b=QGhXdC!QopIXTHq$ri25}e4%KY1@P2H!7IDv`M& u_=)(j|C1l*=j!iG9QIFI98H(#C!UhL_FIcY%ejB-yL08x%lv;{8vQ@)4Qnd^ literal 0 HcmV?d00001 diff --git a/assets/resources/apps_data/esp_flasher/Marauder.bin b/assets/resources/apps_data/esp_flasher/Marauder.bin new file mode 100644 index 0000000000000000000000000000000000000000..379d6c31355a8a172c1858c8f4def5511fe0fea2 GIT binary patch literal 908128 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 EqfXjU~q6-%KH z6>Xs<1k}A2=oQXjcYuW<-TtuJ7mEXXI$x^4M-OZgMs*@$-Np_~jZLi*QRS&a&F_ojCT~kyWyc&}Jq6bju%eENH6ovwC(C zw^DlVn~>?Xycc2Z-T*5kn{Hg?|B$ZM$?dBuxn1h%b#&;57BdxN9ny4sO|(0jiz~H9 zl-|#6Lj+=QwVZ)1&Aa}Tm3jR$4#d8yo0;gs7=FdrH#iE8I90Z8*{tg(XgE29mg*lX zexilnAG6yI4;nT7fvKw7YU9(L+_bLCZ}5pMCm6{|0*8m^+%^^uw|?)k{3vV?bxUFJ zN?tM9a!{e6cp*|*bH;`~7ue^^%_83C9-CxFwitwr_6R4O8M~1=*UAk3 zg9F05e9J-&Rl_fNU`W2NqDx{6T?VjwIW4#T*X5>tu##%MzqMnm{}l^9-Dx7|fn*t0 za70}bKKPsvYC;X~Dvo$@)Pj=HVpyhSIV$blX_dHguCWIB0;zLqM{6Fj(czkCG!8yWK% zTfqcJnv;CF-hN=)6IeDTFpfddNt8m3%NZ)>(L?1VC~bGOvOB5NO~ag;v#~U5JO%i4%t zXR^eS5KSY^%1W?(ldfKi^jb>h(Nt70>`_`N-Lk};$x$uV?s;9Wi1;HYZ=^ocJU(fy zu9LGpV*XRHuOxCQ5qQq9C<+v|>DQLQLt`OK>pw05Oi|;`lA`XGzRr85(Ryw$=eySa zW=D%h=8k>TH}0C13e;~)PiE)ocgCngBVG;+8Vl28p`K$PY}ds8JS zUR)p{P}ug_ewMGwZy1+{iN*=SwZQjXPhS?8IHVlgq+W_JZHdiZB3D6WVytp_H1byK zeKGmq;kaE-;g9Vp2X*?MpcamV()RS(SdBdTY`u5~**OMUs{lyxti6`%tG+T7WuDZ^ z0eO{&S^Mu+dZlzds1!~Z9zx|XIgv*X#8&UyAT6U4U~wOR*tKm^S^g=i_TVr_ynx(9U*c3dMs&?wY<$p)n=;JZSXni%9(C^iF>^B6zrDbCToH!C9XjHD^xNPo7T#9T+9k zlf{NmF|(kyb57b)uoyZr!rPhnBOor>VJ3j#0nC*7`kypVmG>UG5O1o#%=W4~ddL1Uf*F-7zQuKf*l{Dw#*+Wl|0aY`Ldx>qk?v%($x zwxxrq201=Tbk_#S@CI5hrxjv*nR1opeYcV5L@{4Hzw`auVwtb+_Wlj(eq{dweG|}+ zJ{mLAJoWo3uC5~vlJFZ9S7x6H#790#0%Byp;d0L&E0+bsEc_PgVRtX-%SRIZ4ejv~ z6Ns=U+@wjYr!6GcG)mwpI-{RY#)Ev60RIqiOdV7+M5;wk9RSIq88Kp`k1padeP?2u zDV{XC2aQ4?%3mcIC};p!mq1K9j#|(+zo{S@SSf44V0ePCsC-1dHG~MvckRqg0+GA& zZkix-8PQ$$v%J+;rJj2;jS%0&B%egSu6hzKVn7@Sf{jzl`jlq?O&wT9!J@dTHwkmz zdK+P?E-r`|o4VVVnL*kikVZd`zz2dBnhH8+{=k%0Q@yibjiT&P%h8DWtc4j^6}pLK zqoaoY3ISww;Zr#y1IK#kJ1(u5U^ERmnbt}G)@SU!FdIy!k;zOR6h z2%NdrHhB78mLU#Cnw}X~EfQ+|fjZkVW|jiA8B4De7yl_P_MyJ6>tBu=T6f6+f2zOTXWXgg;4qM1M*RV0Hese?DbH#$^*ErRvSZ@<xK6uC>7J;;6 z27OW*yya>M^NnUuGIG_{rN5Fx7hl%TEzTwkYvnoX0Q^#?Z?Y@UY|II%CJkMoWhYBH ze9A#9wZ6-G10?K4O(UPuDVdoZqBEGgn^%qpKt7Da`Ox$u2n%GiXJHQPE7cEe+pDVN zme5b`eW&ZFz)L{=k#{vRT1x_9G48-s_s1URV?6S2bftcDlbv%Qrnb{)t(W_n#VM+r*d}`CIXZkZl%(hur?duuPP7fy(0E9 zO$oi9l7E*C9S%v0u$G?LBI(fTXwcVCR5m-gg)e`p@*Jiig5A|6W)+oQ9TX~ik(nKP zOJ7x!W`pXC5|&3h1J9EfKl8qqb9UpynihvrN)XI>K zJPxgz*wb~^-hxk=#k-gBKK|odrJNL<$9uaY8;z^MkCdH09u8Ncqj-6SsKz- zR#+@!9Q6;M!ap;XnTR3S#L=;Lev&uJ)02p$-FZ;3r`AQy+00X>`Uzxk!Q`>(GhJjO zun8!M#;Ss@wE*-JIxl_|?>u;9(kc8C!$rLdFmpLt)UG#8L$D$!lzCQ%ZWc%rjt~J< z-pX}DjLPmE4n|INZUsGEu-z+haA~B^Wqv*RUSRuoI|dMdCYD3TswR&oZ!in(a9UN| zH(S!6W@ka*%>di&U4EChpV%0mwZuvyc1)bSM@%yQ2yrlfvZogGIvjnvdZ47i5T%@* z;hN_V@fBvJj*1ie(bmnIoeCZ}9Ut14fW-t;TuK_xY=$jPwq>2SsnoAr46NVI#u0L4 z3O(JLY{dagy^R<>(iw7`3Nr$I*2|$Ik+#(}VTavssi+)H6W0AHs}-2Ce`tq@V&1*1 zNMd0G#5PV}+q2N?spk^jz#<-5gp`q>o$6FQh??rOaJ??7aq!Y8vCEu}B{x$YPqnR* z^z)g~VYiqO`I7z|6R3bm8W8_H9h$W@sLrPAu#{Wa3hY(Lu-j`;g#x`3lwQMF4E)W? zVGFaVP6-<2)@gyw$G2Z=CFvrMxBqyw9u1QM@o@cZlj*rs-<8U%!X-H<2-;oqu9*w_ z7Z({q2fBRrM8PYaw!!IzlZ9Y!B)uP<1Ya5t@r!&k}kf=Cwp&PNf2AjkxmE-#xl^LF>$rq ze^_vGH%>i%@LHY%%&Y}pEm*Yz%Vw*ia=fiQ{iP>~Y3m%mHKe7Le>gI?J`t{IiVy<2 zdH8&lm&87CTAVZ^jDDC$^TPqR%ILsvqHb|GTp<|P1m3e=?-NhmTt*YZi99ph*_-a% z$U70e(x0H;mxVTan?TMrwbTqwOse+ga6r|t#Dq>@c+>Uq+r0{&LiQha@fQ|9L^|EU za(a#+F#CABk8bFSP;tZ##_YE-jd|70lFZ5!nM|`+;VX0enK+C}% zKS^41QsXEH8axvRJk?|AGc8!l3F{?L19O8ANe`4 zSO|r}LBM&EK0vU`Ugwovrx0z`^Z?7ia4onibB1s@h|@d0C6KFC5^?G{PsD+>O`Rb) zgsFGNks^XnTI@J2$`d7ouhXzdrFi#2_o>Gvnwwk#oMVW!{@_i;p3*t1ceC|<%QmB_ z7wn-@{qqhk6oSSP?O|n*w<#|Xuw?856VopGTh3q`Ow_T zw?}&#%wyraO|Yf|hi_uPqZ@iv^2q| zw2dV4TR6H>p2=jiyj$#x%_5KfY_9$c56d!#5gbVJIFpaqp;X@MOF}2>ML2q9Up}Y% z>2`g$kz)4F`C`AVe6YuU)Rpq3Ta+69s9yB>eyfXbU$lJlt9DonM|}kC^t|zLup~sW z*>g`Kxu%JrIqC7&c(k9a*Lz%;9`ki98jU3@v*V1vnDK2^qNJ4@+`uyYr_=E@^Szx& z)+ljPXq&axRL^`gY)OV>YCx2Ps{Bs;>W0?dGiI5v6SQVs$5DiuvJy3=&$B^FTUd5_ z3?-O1U96LM0qvH@p2~uLlKQ(q9P%~*>cjb;hS&Gqf#XPbdn#IviT0%yQKB)nLHw__ z@sv?_U?S1<)w=BL>iK%G688cG(^(Osl>n8Kpj9M zY`3bBU?zdR@hICTwu>G)Z=2Fg=6v?(1BA_CCyKFkQ= z^vbTd$W5`ReSyWWNf?)-l@5E6vB{3MNSqLb1>;6G@i9>s%rwsCNp0ZcXGCE`2o=KSs1%#As{N3jZI>52N@XKpTCu!}@ z0+ER-s*;LlJJz4*r1-^qLd)jbqZU?cuKsiRDq^qK6E#=7&V4x{x0v#Ja^M}#y_@{Q zrP}KQbh*CU<%i9>fQJ2$ibpY(y*p&IK=fz52@dI^NN+CH9e*?WrAHhSxUinz>juPf zmFVbQ>?Sp^kMEtwSCu$hA|tj8K_y&lX;XBpMqhGcb1~JcW>u}FRVO_(IN6jXl8Aw% zjMzCSm6}Pw_0PC|gkGj5N|K7&5G83Q@of<49`Yf`pG;k$egPp-Ln4mS8I?9=^{vwt z(9#^NJdf^UyQw=ixl(L;!xCI*PC+fVX(oxY>a8ab;laA5%@B|n$ler6#wq`Bj5NOA z@(&#W&@wkg8i*~rXSQ8CE#x#=Z0N1uMGta-A^S_+d8v|jz&)l++<$dz#EOBA(4Y2Z zoK$8j2Js3RY4sB}voVngSTuLSTWlLENyIDzO=|;Hei7YD=XcOdmvH{jo*AWZDQMrqg`OsoNz9{m{GuAQvS^GG-=&wR=AFhXl^p47dT86mMD}3 zQ)a2dT)7btLSFb2;SsclEDeYdI?ZsJhie_vr3c{x9e@6kj{g+4?Nbqx&B9i4@}r`M23LObH)3K?{u zYKWH2z)Lqo*iFU?k*hWYG&em1{d6dSn8MQXb6NWtGL{Gs-}EdDQ(p;iq@39mICGvp zWizD1p;JC*xUK|Ra=)p;&BX;lrGNwgU9L*d3G;$_D>2X+S zjd0ssv~^>X;ys%e8QUrB1ePmn=b)PM0zyh(wt|FJFs-pK<46$xSW6V^Flygw!7VtF z2!cCA+pf37{A2-tIs1lg*u=90Z%zgviU0zK%{G6jM|?O`ZXMs{MAj*InA!em%(HV# zsX54T!yZ7;IZDAFz`F}U2#9Qqr<`Ko&3;X^bLImLK;R3JpzheDM;%={dFTCUd9?2& zIi0TX^>UeK1+vby)tvOUc4P1d<*jU-X=UTk6iFilDbbYZahy3s1>uJ%3g54<%+zkt zu{fp5=P@z_WdXyWdK*S>3aw{)|cn2TE%0g#^KAg2p0Knd6 zz1y1=J3p`9S5{D&=GYKMX7jW~0e~9kT@fW!OhB-V^b1%j4!{gIK*wYNDY}R)a3kc$ zJqf}8BD&I+cypQnj^Z(o<`vgo7VGI?OK!EM)=nAMe{K_Ev$bd-at`g%Nl0Cy*%!Pc z5CkV3bJiP!;Sk}l6zEJxIpnAKLGT-gP8cHQg-9DE9KoFZ{&Z{?d8JdB&TR@)Lh@YU z#0H+9SkySvvBLoo;oMn?qg~Xw_EveG{O(tY3I4|s&zZ0Wq}0~6-H317V{BVm5~^4| z5RbRajs0HTo{w$l>K+LW7hsG_urG=gWK~Wc%cSjHMa}c*=gl{xcw93rJULXY_5GdE z-EWJ%xz-kqEL(;PVtXKnk+skq595#{@Avc(2CsHO6yXCbaFI*sv%5qaM(PYH51R0A znFWGsaKaKDqQ}K6Q!$YYDGK?rE0l}JZ;F+7RLSCQ`&b)Mr45v>G=T@FTUg)iJ*cf| zU!H2iGlF=a1Fljqf|yz2OGr`WOw=-)N(Z=z?tzs_x2(fwzClUKi}?^wa>54r-AZQO z#fjBp-Js)G<3?bs3XrkIVdY6r8fCGH{f^o^iXFeDVj2d@t!CwDu+7?B+o1KDRaI;P zDnW##@X(Lhb(}wTj z@ZK~j2fSIU^Yf47l;Arz$-RD3o(!Q~UxAAX!4OyPr3i;E%`;o1aO|PJ03e6HLBD_(>t{ZFN0IS@h(Gxm_;UVeNzZi=YB&GcT>KLkY_z7|zAN&3{1O(Ax1Mfq z`#H8FUEfF{`Y}&*1}A$Z9a!OF~h0F6{TN z{rJ9C*VTD~$hY+Oqwr(;`z-8-`a^*NBTxl^1Qsk;v)4L!c5qhsqSzFg$-yWBjc z)6`6&F0Sw71AU`EF|YVcubnnEwkIA(4;6ACQASikmqM8~X4=GWRCyg2bCv|lcv(vm zoyRrag@yrhrN8ZBZKc2A=*|tR#Y*BaW3FVP7RAT;CPc-|wufRlJuuCASQM+91F+(O2#$Ce_@~W8 zo=O1A;QI2#<+MuMU) zsp)T}0b2CAmgs7%XnoX%X1|WE#-SmR8!foCei^q4F(9KJG*UY;ds3BE`xD0|9b#Is z%SfOKm_sUM$v;54} zI7ij6=Z@*#-yM_CBry_l5!Zt{MjAmbDDK!(oY_ zW(_s#EmCbe3VPZWdc?O*r1mPBv9U(z#Vy?pkjt5q=-xV_HR>+Ju7dys$mAqgc%~!8t&mxxG}w5>z+zI% z4HnOrg9Sm|JjxaJ>xwh9Y{hiIpTkcVPPYX(A+c$pxsK_Dtr3P6##MHKR7{IN@~?5i zmuNPc*$89xF-iw#>TfZ!74@kzh3?D@RT<-vxobSqv0HIoHGHbrM&GD8r+n(|w(t-5 zHK!s!Y!@hFuH|JAZcu+|RAYVFUdEgn;HDI*Iw<8on%T>cyQ~t_I|yUgoDOtqn@n3= z!ZwpO#Z||Rrp?3*Y$ADouOwMmR>=yBm~Ae_Q@73y*b)byI%`v@qheL-Bx{OY|2fy( zsXbM#nV{cjf}TIQdhz`7$+H)p#ExyyKbD-)g5$Vql)`xH5sP+-yHhuAk=b)2;a^oB zh>NIX2|%P#hDA1Inqf!5s4&psY;Bfs6PT7z4BL zL`7d#Dq{5%pG31Vh6VS?*_=}wG)~N_REIVmn?O2CIunz#DH^Be^&wSXFPw_Ay3pH*g!!wB0cs z{p|Qt`|hnF|C-c!#>rD!W29PQ+i`_^v|!MZ16Y@-I|7z(N}1PC5><%(u{K)f?8rfb zLcP*Tjyn9-p8d7{h)zBNbHNf> zFxT!BJ0Lctq^?%HDKBI%j1_XGkNKv_Qq`QhikV{ZEEkFc>Fww1C9$4)_J-Z+;FawflUs^C5yfMHG?3y5mF{-1 zt&hB9{r+NC{6?EHOxD{h-(Jv@!A7T93r()Of%cpV+52oDgAe1o{dypug;yK;jQDoc zgzNo!6JGt>fODdSpY;EYV8XTKCUB@4vd?rBYLS{$2UZZ-6nR(w=7}NbdV{6kttv81V3}Q1kn^MicRF{c(Uw08gB7oX^~oO=g$6aH1-#)x)NEpD#>e zzm~H<@j3S(jphvl>i7AgI1wruMRUZMr5e93*Yrfm36QeX^|ghe6J@7IWM%T5qz&K3 zr?@2TWhywFJ^{#`{*`%sBVRZ>E}&S;}0jf|dA)aLDolHi2(zUN`W~+@E-uxZIC`)^?3GabuGkexu_%a4^kzkt)+__a_5cJkMRi%qT$NU z+E1}&31V=iKgOlsgNfdRtfK25Xy<8~scZ5hAi#U{L);8j!|{E8fY)6&QT2Mu1^d~N zRkJ%Y;CV-fb~Tz>PM#bPV)oZO(~D4tej(1z6(OP zBdv9pPdkmCKVG*L)RYtZq>l0;L2Q#qBSS<`HV270ew@}c0{0-`Ewvh7TW`nV1erLI zczxY#pnHTXn8UM<$~p1mdeYSJ}; z0}R^+APi7lhBrrSNW+sjG!JX+c)|`<492L9Woeq=Ua|?F+413nH^Jm${&qW_XAf2<9~NzD zV$t`0RFiD}d+lB?y%V@Y&Faziis*ZL(y>);*qjat zBt2SFkMQBtv0w0W_=9moA%f^EK=kT4qWlpo8b3BC*c(G>Zc$u{$bFJTk4XB7-9bc{ zu6W%E;Dq@H`LA(_d8KPhyBxi~8R%nH2Koq^G$Ql~qMu?jj4~8iNh87z-^XAg8ucvS z7A&KM3aK^^5zAAjI#L~0lfA-*b3C!Orkvt|t4l2{5D1XBfWseL-~Z04R+rLU!M-ZH zX2Wf0#0Hgww_0zP@j{2<;FykYV=nYI7Hq^jm`J&yUqLUqR^Imyhhp~%DIIf0b~O8; z1QfSS_sA2Hn}`89iN+1)*cn??M>J+HnaFCLf9SbthQz^1%$ye6AAO&v`<+dthMh*Q zJ_8&O=$1vO1b1ur#7s+=5w*lUeXF|y=o2-c9L`g+$g3{)zs{LAoo7lRC;dEeNf#cNhuiNLtr=ELa(Hrs<))bk&y#tP z=Iwo+ucBXL<(8`&=F&@i*Ww8YPjiuu68B>4s;QO@?KEw)v?EA}SWzk0We^Ino8sUo zyC^Z{pa|fkl^OSuv2`}qqKN90$tA~pn$%9hv^)vvxVhUlg|9NojX;j=u7$H$qeF?N zqLp0kH@5rb`=i?4wWLBIRP#HHxf9UK?u6LWc_r60C6Sr4ku#TLqtmE=_%v31b=Ce( zE9#_HqZ6U|_06nal(Qc^O}gb>wZ7dx-)MQb`=~Sp5jQVWzscqbi6(8(VQS-ho|;tq zUSjPZRRek*Aa&m?&7 z4KwV^b0yE&$O191nT-J@HcY~Pg!daaW#UoKydIg)dUK3hk@ zwD^5!ky2$S{I1Js@YwI8GWoq72l9VQNTB60AE^)ZG1mryO~?-_b|GyYH0I)rWgfuU z#O>A`G|%@q^P2B^pIoreAzjmoJ>|>qUI&-Y9ETf^zx6m#`}?Q?lFBX+4B!~gBq|{U zkVq^M2@rNb=&l0-b|LZWrwh92x?jU6y)1(@RlZgW@ zVvfa1Fwt@sAqApm7fFytObe)dWTYhBbR5xLDejP7Z<9GW6WLE za}UqrN^D@Hw-`7%5IF5U(jL&}GXXA`IM;$?ekGAAWmhtX$H8U%%@C5_oahIcK&-t7 zTocXnID8;Vv7w@%qMme=-lewykt$sULP!DxLK0GFV#khxpa@pPiehhwT|pEJsHoVx zsMx#M{b3XJ=<;=k`+XKT56~J)+O5iM8Ka23-rtA2e|5 zq3oS*S=R>6u)4c?%{`036E6#^2fAsi@2jqlsJ>`3e8=YJ6el$&1t+=dcr)w2+c!sgt@qrx^V?g!<#VR=6K=K*2)>-# zY|q=_<2+-aZdr(2h;Z9pRYCelcQKo~=zeB$X~bfy>AObTP3e}9EI6K+qU~}qZ@T5P z6=#mFSU++S&lRjzreG0d5&jz4sicz2W@^)TM7xUS~-OV;qyRpfN(K{vm;zN+CDr>t0bZhL_IbMwQK)`z_qnlR;}xFIiR z=J6uNG4^ny?BF~6UfsJ$yJdyx5f+WzEgPSXd3VX_;l3ZiN>;+% zidv4}HODMUWxD9kDA(9HGT(Dg!_t%UFOG6M%2-6h3h~EFR^JG^)jt*YDt&ihdx77I z(;iXzA1bay^u4L{siz*b-*ex&S9YHGJ$&)0)0GpFZm<`>Ia%%eBRsOgEG*65!{I@E zpHqzin+nFemj(7F@B{c3X;7aBO^gT@0%@-&~|u5#Jhj{ra?&15r0QCM%1$B{YSoCD(GZ zzWkc`tDvTFd`WTt)6;E}PWMaP@-6t2M)3jfih}poJhjULpR!^mMxVUh@^OEw;dcLK zzZbTv-iEu>?5i>@3Yj~rp8Mv{`3%~Q{xurp8KA4GN1k_ z!%ST57CHIEp_`G)m;2z0O2a19m1r$5Nr>{Kid{5fl|#xhu6+2gzR0Zb%Fm)mW1EbS z`O9C11T+oIdi|KCTR9<6@6>s%Q=3N&`B@^~Yjn>dJ%!7EsnRoV-0Rz8SJtk3|IUtM z8kc%#+*6kPvFZ(HDc)8dM~^Pd>3uG%h0?=A)rqp@aD^VLytHqto@-4DCA{ulPziT; z5yQj9d-oznn3wasd8Z=Rd~RMEJ33CZqDJn@gYb3RTJ`kC%(Ye>blCfOlhynuk2lA? zvZ=VYDp=l2>}60CW$k#d;C@xnCa<~qLzOGmZNU}8`dQcPvaY$;fyvyCvx3J1B_Eo7``Rv=XV>jjI?bqY7V-4Kgo-13-a4ORA zI-GMSTYIm0+^g{~Md!}uUw-Xqv})R?ISgUUS-+E(^_#P5SC@w0HlDLTC;cStwY{8H z?IM-Bikz1U_9=#mgFn329`kuy!ni4^=1O{B6(YwUniF~A<+m}%EQYEmSh`@Mvt=9S zDymFgtNeP|)~&dPD1Vt!;wIg1xfR)4p9KZ(;DsD=wAnOD+^q8P^_lZqZJ$OBy2>6N z(L6@`F{4~NzIQJ_!N!KMJw_Or)K(Q`E~V9u9-tW&9FU|{xctoeq-Fiivn=-K_tAKy z_#~=qkVBqw{W_&Ltuu3;;dWlkkKMQF9xc2W&eeR(JbtIw3ysjm2NSf`c|I-k91;>S zAH2s42U?pq9D98>^wQLMjw_3{=??U`$#pfjb*pq2Ti#Yq=X1YVCF_Fj7;m0xdi~4R zd=+-d;)iowIA8mHp}Ay@rfR*7>|3~Tsp^}SF!op-vF5jL2ht}scfYP=r<_cnlyUEcJ1iTg>+VxT70p=lj90Q%4a#u$hV6tyxw&E^p4#r zAw9#@)fFOVHje$a*LBtLybb3jTz;9KclFH0ssXc^=I_5|oLihe%Hqwl7uwDH94c~N zX>>Q#%bzp))`BGSU8)fm502<(RPpB2v5)gsElmIZc5rm$lM}T+k2>>}TN(O;_pQ)1 zr(485D;OGkxIc5rtP*90Q1OJr;Ag8o?9r<5)|u;e?b%eL&u-!RrKb*E{j`3QR^62< z+ZgK0UV;5rIuF?yvt+jDS0537riJtJdBc9c8uebMZlq%AOh3+^Wf@n;$p3i#aYNSn zyw+Q1ZcWmAF|SW%;MI3Ec~2)ZCtAooRG&9NZ*iX$xmE?V-L}o8?v@X}t()Eb{d4Mt zBco1@`O%Ylr7kbvi81Hn)=|So)tt4faVdE0``g5&{_Oy(8Dj6Hd$nzTRp%ePv~XAW zTk-8r^BpW+={JZR=fu^73R~h{9T~8Ch|kTYNxjvni8GHm#P^?Z`T2}BVNY|eC^T=l zG2!*{H>`2o3)c#8jrg@AA}ohCxK;&S-WYtgF}%zpn?F!*mv#g;MrBS|p8tmzzn}Sh z^(dUbZ1T4Ay>j|D9oqQ2@w3M;b(`{T^1BKG7N069#-_LSYUDfct;GvN``UNkaHC&o zu1*kb=#Q%d)0EFJ&z+v}I$7t;)u)fgnb6rQQpX0*ZlDB5v~C&I${2NM|MK!{H2bB> zAzY6f>$TpyZ$A&9#oS9b-Q+g9`!2t|CrYD_%eic^IJmSr)%xu|uNK^O+&xjnSJTl; z_F8Y+&a!)=pw#+PVbs`HvHLHNjhk1k-SjF}eu1Wrje0Y0Ld%SIa=f>j=NH(3Bb9iG zPTlv~alytJFCH13O|93Ue%dkri>FAl&%4S+%ZL2@{$}mQJp+pM=GFCid+F?Sxv?LH z`m}0Vy|2CGd4BfHsj9lAX3gtw7ylftA7^`qJ2%(^pBq{I`QU2pr%d6=>!TG89(31Q zSe;XD?v|4HwdJWtpVEC7^iNI6NS$T8BSG=>&E(>$Q>#7?j5ailu7*QI4!m2C=VTQ9 zjm+qdeS59goT5_VmG;DSYC^&CUA>x=sL-^$gy9Gre~AW1ek26#Yw_yRu|J z6C+#Cq(@z)M)qQAMf9>$Cr0w7MZ${XqHp|-sCCEu9|kN~k#aYns!#3Ir(;eJK0wuN zm^W#A@^@QXebsK>nC1JV_l`rpH)mXrZw;T2h2hgGMOhA4az;eVsJN$h z^VE26jU=IUUcU+DHu_I@#$|^+(+tiO^*q?Kpk~H)zS?UmcJy+3<@AP+PJXNQ2Iic8 zcd)Q&MO{Fp=66QI9iP`d_RQUKM(M$Ts$#ZB<>{AO2A_!2^_!OF`;)eswrT0Tl7|7O z&K)`FEkC(r7ykIfZ#b;^K#K3om$9mQm!EO|ys&kR(NeSfpEd|YeY3uL?Yyo^(c$hi zv0tIHY)`iz6ZFQ7--cHt;}eSPjhEeg&SI5^9BR}YIpW*>5sSIzO<@ts66+L4s*6Li ziw_v3%75n?1uepOmj{%6_)u5rdSaP&;NGW)a)bRtSWoK}q8HT3;d^HJk9_m9b*odu zl%H4kxGhW{D7v1(R3DPLV3rBk#+GVDw6M-=_%!x*s`xyt=e+R>`3vL(VU_h(x390g zbN&9sLvF5jPl`{z#oi|U;BDU|g0k@nMB*k9W z>v=UN+Bs2SEB)4dPGoo2Ze@+OF0NMQg`T-~B2%Lyr61GQHb)=cd^6>7+<}qv8x9-K zUOcEroHe_6GGZ}L<nB}}O%C4mTH{?P^#ptUh7fg|pLP=$ z)R!6h_ID3>YmQ!DptJ9veLbwO`6s>K<pyfe@7mja0^_x# z2F#qCS~ygF(Djx3&WH3(4u44hS)udhT%x6}=8Fls##W6SSC5L74@DmKYVwMP+l{e`@ruZuaCTJ=+XC)1u-ami*S#8TPR|k!` zcAUz*tn_`0+@xpQB63)JW6c{hGB=IMShae_)INHJ3eBrC1Y>qi@>N_Ba^ZY`EZg?% zy8Sv&)tHwI^fNTK`tMcM^qmx`Rntc|FW6`Ast*@udpwnAWtCb?)ks+^w`ut3GR^Rv zCGx`6*Q3(!e7_hVT(^R&ePjg23Mq(p(yNP|rIE7IX5dSW8k3Ph2kd7KHWIB2^ccC% z{K}o)^!L^Nl!DO9eL10)ihAa=HCx$V!m9@P&#ph2x5QGhvHOZXfd>y3YHe!RX{~(o z)f+vx0jc>Hrs^21c&)qIp|;@aS*@mZjrU{iG!IQ26K2#DXNB{>YZ>xHa>u4wfixRa2%m+eY45uWR0YFK2Y}0>PA+n3m6p6UOf=TK-tLYDBir zVyPonT1kDn&~nU!`n2@3yPMYBc|4*rruEM8 zG*$J`BZba4gqqWDsa*?`%Pt?8=u`3iU~8|R?!pljJRXM^Oc|Q z7lSj}gKJJsH~Ot|)PvXQ_n)(U_)=wn+}TvthjCQ%*k{zN4=;yFE+TR#H}XH!)8k^!Q4Z!stnJR}N|k<*a{Gv3N$7VxLT%m8Yh}6ljGA*_ z&dpMPW>z@O-s9{rN7adpyfe&UbMCJ1HOF|?uif1?X=UdIFys0~?Thi1zZG-ws_wdM zBWsKE)#o-Z{Zg=V>8mu~tBup=jmSBR4;{R9N9vLt!DabVzW%a4sn!2VUCGC)7|-a$ zqvgAwUbyi@BkIVvSJ6j*me#*}GbnY}?$1l(-;XwTsy>;^jtU!V^wP-wqP3M%d7S?8 zYUTG!*e|}^?wPc4qe5j)@iS)9yl)#58n4Tbi4Hp5Z|9o5-#2fNce{U8<6zwRvXp_I zS<70C!{(2Ot%xdUoNzO%SMG)6b@rnTw%EOARGHOIC=Po$mtyYca{KC(E61Z2>rbz0 z&0k`k`XnXkjsvF4Hz@;gr|RaQ4f#Bg&T|oyA^xss15SgpKio> za7{@kmvkL#5d|G2fsnZBm}`HR3Kn{o)TzdYLX>zR6^-ITG<601UY_H-MaUA8dYb^cf3 zzV!3UH*B}OYb(w+eHV*UeoqPxs9w^9PoXuvHCcBf(kQC?&`ZYO9qf*8cPa6)HCIWfC+$-D!>{l=>mY*J=uUtYaxr{Ed2D#Lts zGi!VD%H8^o@82H(J!$@+ig9PxYkRo3m>#kid6{iBc-esOdG;C07X83J4owgKboAli z%E8eKbuAUwEDwm7`7Lq6^>-1oEr#{gJHs?RN4Hv~SvBCiMg7mMS~Mo#>5-_vaeeKX zZ?k(1UAeelbasvYP|tpgzfU{Z(^BE{wN(b2GOb1jS)3?{Tt0s_y*2T5Q-I<*i-k|u z%~(d4|Lu0uHoM&ZO8+5Gd-WdoT99M8e9P}mdq+>?-a1qE=EK1LbsOYQ4x%_rDRy-W zS~R-EXwj?4v;xEN*4?LV_3Up!HO{S`x@OYrR|BzCeiwSq<62csk9jjiROt{=qo&PH zeh?~u!E`12z4A=o4PWVRhV2OP(ty>Dz4h3rf(KXze#U)!yjTk40^?9QLt z=YY?JYdhxtEZKB#X#e#~l=Q-!HeJZuHA{mp2z%tD!R!9LF6&T*)mW=R`|uGSGpq*0 zym+9FAi+hMt{fv7~ws)MB7&|K?YQ){CQ-9R^OrJAzh0&zupHV8Ub9cI3$Gx=O zM(1lrdA%6^ymUiN)Ue2l)adPFChUM^XH_*Tbz-7g$*Rosq+#OK*+$RbFfz6LgZuy1 z4<51MZD2Zm%7YyvGwm!&swYrdNBa#bOImT=BGYBnkxXM=X@q6gG@AL?S;N%g-%dML zlIk^P-h?u>z@FbX?|Jv2oDudsRy=2Z?%}~(CMkTredA#2o?4@w=bztxxNfxLwo(o1 zJ=cEC%@+J#7Me-@`&qeMzBuFjm#uMB+L4?12mMty-a5W6IB|gAceL|}p@&AFT%Y)9 z_T=&v$(5O5K?55n|Gbfxx3BeaK}8b(#NzGN+6pU=B^L+Iox&PscY14YUD}`zW6BsO z>r=)r>@#Q`<%qlLf}vY97krNIT^v%h^Ilxbu~YSv2PxYbU;3W3!s+;Y&ZN`6D^nkx z+Ou2RVBjK^F^zMZO0sI+^x*y!%G%?*fu=;-t=v1!@u+p?ENtGGi~h} zJZtE-x%~@|p7MG3?%my)f?=Ny;-NcCA5J%?+%P|PbpP5O`&CUZkG&XoX6U!~@5a4p zR_0tW7JllMvW`384MktS-)hzo*Q>N#n=4sLibLg=Y)x6D=2kJlV>5G?uUX&xm(8YS z&)jdtHw>A0RTSFT^XBowIp?_HO5V>l@I8K*kJW!1*nKH;$B=thx@m{)u$}X;=*^wV z$Jvud{hsGDdWut1>*k0jTO(&ZUSyq+$hy%zB)}zquz8NorA5tS3)lRZCAaPQ@Z1Gf zn;RY+`{-sup{LgTOx{%MyZ^S*@VYM|rP`0%zTX{O|Jg5~zIBo+0X4J4QJZzg zMfW`%`@;DBfnHAwUmV@oa`BXd`nS6dP8SSwZe*^XJL=cLmy}J4Yj(R$eQP!2tBv?7 z->3W267{Bna_z^JW9{#zuYLLI!>s#5u1$6uWv@Md!pRG_))%`)ZQZt|(9ix|-Gn^O zr_~phHaxg!sP#i**Z#Cirkiisx%XJy!~Kl*9PPM9ttH;a*IvgLd^~z0;m7_r&t|?f z{5(H!Vf6Ho%Pu~@YBFQh?AGo%W4vOu1F~JmtwSFN-rhhT z9_UxQEo*V*g{6UG8`n8Sh5>WXH!+yX936uJYYFNB@S}p~WipTYtpoyXY6ZDA>L`p2_&bgm|8y!iG`?7!%C@*`al|6HXVt@> zsZ@2x8=EKgS+@Jp-O(9`?}bE!^j_|7e8cgGr`AoXeqiEsY*_tnJ zn}wgWIaum&-gMB|p*bb5?1s%#Dhtkn7r4WHLf8rpQ+~GgU{;><3^?%IR`;ROt(DJv z*33DmFU&~V(0}iV#OJ*x3ama-)3-hxq9IHfbx-^9x5}heZy!g+gxS4z>EG(s^0v;n z^o0V}G_(3tk!6j0!a8iq<;IFSZjD;);r@C%R2>8SU3wbn-`%;ir6Jc?V{JxK;#0-R z?;icVaV@;nrtipL*A29@}E$ za@ljPwtRXPAGyQ-an0w^nl>sHngipNcTC$pF?y+{{J2~c<6YUzgRFy7Mik zt2~-g_5PI7w8XbF&a3})nUeYGRJ?Foxw*f~64wSM`{E&sNgDjvoK5wsCsWny(_d6@ zUQjo~3oE7D!dqUxecLPTReIwF(+KSsn=Vuo@1Q3Oy_TrC6ptHdu#tA{^2igMYZC^K z>^|7SD34x6@mP-@wg?q5qnGI~rJod0uKcx9@{>XmJG?dZ&1lN3_2XiBRhKi2LVjz0|7 z$cIB-R(%_pe`>BHU^_B{`mgzReUyIJ%}5Th zKhSoVKQE~0lGn!YM`Z^SjuwiVdS&XC`}_*JSX7g=qUvRI>+y%TcHUn!;q{X1HOdau zrqvay>X%#UN7>L?qSmC0Xx@Hc4m)A$q?tY3MKAh2$gOWYn-V(rMfLaOS)W;ZbIum4 zOdZ!Fw<2-8Ze=0eA$~}cSJR>!+jZj~#cyfKSf>#8ylKspS;yq<`pg)U-|9YzS@yYi zbzL}pBxP^>$?}1XZl#$`g)UnLt@`$*_H{sMX~VWv*+!2XDckZ4ihXR7b>+k4M|T%l z-|;@u5OnXvsP)rkS8m;7q&#Tl!9gwKtBo$F8u8b@4t^D|RCP4|Ix3;+Lgk*`;q)Hv zp0BP5tETGrVE3DByl=9{9P^2v923O@;wW!dEh`9b38Q50wH~n6)7mpqOL2K`72ER5 z8hwU@Oqzu=b{!~Jn)EsQ?aDke>cfyZMjWLXcd>;g-)`q#^)h?oTRd;_v>U!f+24^Nv%Gh-a z%7ihcy~8Qd-bd*%J$G+?DI7UK=he)j%6BEjkNf@9D$P1|uA-nGL7Vl9k_BFav z9OV5nw8^~0?11L4EQM{g2AeYr#xq_HGoF^F!#*@QS7@^?Z8{~cHLHA)zu~t>r?&4H z5nZNDL}mH7vU)((i@sduAEF1Wb&oD2IXrgAz-r|S02C}ExY)eDU4j}KmZ z7nGZ+tarTT);oOrvD15hJZ%~9?Zbe@S$adupD0?*JHj}o6y7Wt=D&7kGxJrP>x(gy zrlj6{?zR1D`Ch8Sz%6~R+`Kw;Zq#?%V{1-qACk3tcjPI#c%9{|)>`i!G%)bS-4Se^ zkfeTN7c48g%Cxep&Y4vcG372ter?l$)8l{WtbOil{ot(GF4Z*8$$H$rv`^~mt}@lu z*{|tqdAZxhrJudZ)~3^)`}GKkQIb;^L}^}|tu!ds4BD&|A06Cnmgq{OZK!w?%eyhE zht=7#1H6lO8SDuWis!!<8Fin?@>r>Ldce*3I&6hErm;O)5hhm`U4Ca)yV!o^orjKB zS614Nrf1Y`KlCoBDSppw_Fi}MtLtlqD83t5o0qqm-9MyZ!zdGt6COQHD$LG5uU=Bv zSU$VBUR|R{ndg)B%_*)m8|P5Zy#Jsap))MEs?QR;mqkMV#KOE3wR+crvM(ym**ZIq zj{nt|WA^Cf`ggNUztnmQHmVNqvGeq{jR!5}+*jcy^gVP%$?8XiMd`2}rKgr$EW4T* z<7%cdX)9;@J*}e1Lsdyd3%8VfQ-9)_cIh#0`r`G%*V|@I3(kzI zs~S^zw%Ogt?!}54VRV7b>T|)#_X36wP~UI$>$FWk?i%OPpUyn_;YMiRxeL#qy0fk%($DL6QM&W?)`8c5t=5ybA2aO5(Wk*vt;QaS zOFXwEd8kjOhW&Ufo%dyJp!Ma50lPxSCpbJTIAxz}zH8Mvi+7`^E`Guaf7McCb&V@$ zVZfuL&At0xVE8ko*ZMSF2k}Y0qqB~zD862>=yG!2xt;;~;)0r20W)4FEu%ynJh^vW z>II9IgTENx=gIH;G<-$Hwut)d@}uLIEcUAzmb&SO>8~}1EhhYA={(?Upzq(g=+2@i z4^PaDU!fa!plOy{mhlVCm)5Ux=P#j@7iK$+_)__}J7wpxHT%n2s?JoUB#xWoKlR1g z2m8EJ7C#ojfiu>ZR;!*rInmvDLaOt{ejCQjGIqYTzjg^d^~=Ng{qG*ws2%oXo#^F` zLpf_^nOiB^a8oD?E6>|a`KIvI(bL@HNz9f0%a2{1`9yt(d}Z9sg)w?{(@vf;?*3zj zgZAd&*}MF=MnCMm#mn@-*RziGAq&@CTQt+C@A$@vdbifa&^=8Jb6J-+Z`AI6pjab9PQG3SBvwM2#7uEBl z?!@PZ3L~87*lZJiKcQZJYx4?+&zU+7M(W?^pB-qaKAnH(?gQi7foG%o3Cpa0zCAod z(J;g{yuwg*W7MO0@2=gSXR>v~2p4}NjSbburY)LbC%62@>BwmwgKagZJh*Wza7}XJ zQTItRuNWuJnviETTqEz*0N0R)wW`15UEF+p7tWmatZG%@jRd>8B!=Kg%!f=Q4#$5O%3Nt%Le-z10}z(T~i|mWh@x1H*op8 z^Lxg|jMv})a^k>}a7{~XxtzR3kB`xeW^D%z5A~7{fJtzLiDmD>k5xudUdtWrK3^9hff^oFy?3N zR5`=n1Jq^)%VFjgmR8m_wsvF8Qq$op>zQIUk53me;}a5-l9M@E8H}_zZVEmwf)X4$ zK8zX|q0DKFmwU6WD1i^et-~zAbBJ(IE*w zLQAB;=ia%PnPrDGftZWaiSH6)W>&;E$C)hnPA!oEGqdghn?i?gtD_n3-*dBoPZf-%W=vvqOps8%w+@w2MA#h5+KZkuo^-ogq@fp zCMVxbp?i-G|KZ!XauBA%M{RsS99;EV5%DO7^VvM$1!`k-^354 zNPlmC%VTm%GjbLb>6rO&x}j`xO1b?t;3<}4JY<`k`tRQuHbnxCijw?~;ov4j|dC+0A5 zE>DEMIgCD^F5)qG9NZpo5&EmFp~Vh#rr3B)Xn_#ijNWZBc1_HitG zR=hoNk9h+zxje2hUC3awaYiCt0H1_s!8gifdk{VbFJ$3C>0EjOPsB)M<4Hmu7vs`X zSQySv=Q05rU1;#(a7-mcOmI>>6rG6^IhiE9lh}mI9CY0Ag>+Z zj)lBG5H7es-cQ+81i|hE)D^Cp0K%A$$g$ z0sTY?2{NX7df^~wg)s7QHX7p|epEb|MQ3JWX=WzoW+rwRg^NBD??>^JerR0yCqEEO zAo&itP{sIv`e6aUJWz6ICLdkG=^Qb-hQLS6+dqDeL<>}#_|Un~gecC7&gQ_d0eQg? zu;?Nd8t70%BcS=tfWx8au*&l50M zLH@o}Pb`kd<6r_hH-Uwvu(?d$A##|zuYh=&AGy}Uwj zf1VgVLr>OM<}>l}Y;M~(*(Gg~QkZB7&g8L#gal+|u!SP&m_S#v&A?L-m^2tS5b&$2 zu8y(Ft~=rWHiQhgH-caQ@y9PJyXp#cg|hjj_lJ}Fx|RYAx=Q1A!gB*%*|i~2*|ir- z*)@^@0j@gn%C1ciq;Rfr%C4ah5+M{rI0NAs1XX~4nE>?w918G!AMW!3rxC;#!&Q`| z?CJ^kBDk00!l5iH2$c}0#(^;od8KL6>luUt(ontTru@BLb^zmt-~c`ruJ>iiX%Z{D zeuSWL4djW4q}xll?oCy8HG{YyfwF5U&`V0Um3*iNiWfrNA)hORXMpc*8jNK~I}GvC z`hA7xUVtY90uCV-@=4Q-;Mo<$K?sEq327`L-_k;8VT?iNm{~+h14b7x#cVE*2Ko`q z&Qc5*wq(At>t+ZmA@5!YsLz+cl>>27sO$w$PYBfz|4afCbY0c87X(cRb`WISC(YLc zd0s+@g>)=e*_97hcRCYy6C{XJpc+^J52pwOJOSp#p$il72x7!ZIGT*22&}lp90DPD2tT6ci6Hwjd>) zE?{A9aXf*D#l*}q%*+6oD}sg^gTBM(2??B79ETT&(fNE1F(dQ1rWwZR>FLJtJVA=F zSioU%89XNBPXSTFhRIK4+J?oC#Z3?;Vm>?=wn!)u)2FluBnL51iXSDEf_YOyF&~PX zCl(YIiUkIRQUe1*u+U&P4+`cH7&IR9_w@+$!~6roDOgZ&V2~Fz06^g;$Su^v2MY`E z3-s{Cf+_AOJ}iK^xOwX3e zL#bZlA!|sOdx%Fc)g8(SBLI9o-25m3o^HXY6l#DkrkepW1hs+91sx+KosF~u=#C(X zY6gjfm}X*bpcL>CWdlIja08`=2kZip7nFP|7f}!HL8kzol8^*+B9QHEPeiZMIc?AW z1c5<=^GM{eL373Yc}SlMVgV*DbQ&iUL@o;t1no%xSVbUkne2ErR3HqgG6G&kCKk$q zZ&C{Af=pmBCR@N_hzJf0CN(uIjEj^nsAm@B3ZiEsrU%GWF@Q3Wu#aOinZTw9JIP)C zEP!X?1bcBfymSB~Sq-%vO{f@AFpk;}$RsS(j#E-H!R*F}+kqDGz-b~-)3_{* zj_1_Ce0Un-0Y?Ue+b2QaJOJ|wBFgBXz!3QP96Auc%O9v<9pYuBa1yYPkRf36WiXN| zTYHuc@Mr1VY zb2OC^^-1T&%N8JupTOQJ-2^>bNNb06m}T4GWztPpu-ugS$>LArGEGv1L?d`{Nl3CI z`ox3Q(*Bc$C-8SlH|az+fNl^X;Uz0Fp*{_d9*Mw zGqE&f@C0oD1ekFmonwrUn=r^inF7#E1W+m%bT}NMTpCwkgbOlgapHKGOlU$-3yg44 zr)LW@BV12XO&R)t=|{{mG{VgcN%Wu@(o<+Kj=@&dj$Dt(LB>h|K?S1m+1z#*QAQA( zi|RpK!rX#GX+hKgT0kJp!^bVan_`6j87G5_!{)M}RcQ6kYDY?B388uixcSjSBRy%< zfN(sUL?np~CSwhuZ;ily7zyUconU2*RR)t}f3Q^OLV_=ZA_x!QSqFelLdfZcVb35a zDT4j7511%4G0fGDAc7a_*U*eWE~sj#;qELn?Q(cLekkH51Aay@%+(~djh!>7B36nJ z-XkCu>&`$)Tu%=-=mfy7CQWnf0;Qw|9%2Eh?nwFu zR1W+P@C<)C(Q#-|hk8^dlbA^OJH0J{-b8i&$9(2(`Tn3OBF3L}gtijBFuD`5Gl)v_ zGpNE4SnY{$;3)%xG#;CYk1~XD{1>9i@RG4JpeB00D-r`W{x|wbSy!6tzYq{OMG8)k z``4>cBv1zh|exm61EM~6d;N&b*R{5Le6NH4|mA8G8yAl77Eb8_bnkpll?#r~G* zZv!$Tn3aNP3rNwtz>7CPLyIImO$5yHKe4{u-&vS6vUEjuR2beh50B!fq3T%Rj=1F3 z9BX5181abA9b_F60R~`j64`<18)XRHK}d?fk*q7&f1nh>UVk)!9Bvuz#O#WO9u7ht zlRP6S47CrY26(%NdC@}02T^GLZjl`XA;u@0*=e96f!`*B+60R1)Y7H=M>Od;0f0b) zN%7BkByc2>s>=#sGR)-763I%$wDxLvvS3$}g~`N_;}8+v3wDF3A;DcIVthfGWIM^5 z1-cNMAqAHyR{|hS`6G*L9VM``Q6VfYQ<_%>D~d+NL#vQzCFP|cjWEv<^=6BNo}^hF zfgx}d?Fn;d0FMx=C|WWgQHo3~;3Qz%v$xYj2B!y)pNZgzrF$F74mChVL6WObY0?am zSV{E}3YnPpqNI^BttKs6SOTBT!zj3cuGkRQg-sM)p@A;b5M5O;?;vU*4q9p)*g=S0 zNHPX2I-tbjC?^e4Ws_j60ic*K!qM_cHiZxSXm|=36(x}}MUrTobeTAV&c%74NFhTf zp((4#Rt;4LX^D6WC{`pI{11Q*IK)CQ{m_LZE~0c53`!HQ5{S_SwdIQhAlL%J{QUmV ziAdeq6g3lcA)>XUPD|eNm^3C^NLYJ_GsU_>iBLXlCjKuCKg z4SUG8NT4SaY9J{Mk+eOE1D16T`3Q{@bVdv@BwiYi3u-$UiV0){Xy*cnNYslY4C+7R zsDMy*iUHdpfd+wy`5gh^8Isu+4B3u^N7&#_fz=xm+z+_PIIK;0XlEFV-2|W66Wi#d z&WywUcp4jw(Xht{8(}500gnJMrm>T3m^AGe4^CP8uXJuQ$SY!?wLnPoJU7N^{x>L+y0f#~d zcZ!p8TSrJqw+T{2vJ?*X55ctab$Z=SLE{lgf3)y0UB%(wZa^On= zwh2y`S!Cep68{=WsW4m-QM<}4lj&cFDT4J3riKF(cjmt^A=;sl$Owr5L8T&>6TT4a z5*=zR*>{841y6^?Hw_FPOqjnrz{qmF2pQWsbP==xXn_1rI)I{j$$&^|A=%4E?9&1I zFNn!bVvD+jB}^-zN&yW)k|om;v)Va`tPTu$S^~@r;7x>PT^a$0q1be^g-H|A(S`*P z(Oy+Bny^JAkPI1!2#GwCbf9dMm@6B}77EY|3fuOuC54KEnS$6dkr;xAILSpj>%9xmXx&#r5c>I3>5oF-;u;E{jE# zm&&BSR}OanV22D*Q&KpO!~BQxfz+~kTbBicdGOz1M8bauV*D2%g4O>VW6~~^46Z-M zpfsJJ5?ZeQL!(JbqK^J6nmDitC%5af9q7_#E@d%k99SR!y)%e{fEOf{24L7M|2KHi zzr!Qbrj!-_Dm7(+{*cGuScNSGQ>KbahV1wffi{nZBVt-7T$c&URqz2~r+{9chP;`G zNEOoDiv}-{LL~8FKyB@$1<6K9Wd;!^WBn#^A<0D&322IjAR;W81ZyxtktQVsQud|s zbS5p1@Sr4cl44FaLRO>^AcTyO$RZ*_)?(e>C@@Wv4;^G1kwH=wG-_akkx+cP2uVB| zVGft*Y6P%EMeAH~L^oNBHHn3w0C;o?U>~`?DsdfR5DZiqw!qsVWS>zw5>7ObU&3~f zPEiO7TvZ9)MZOzET~VcEBVhx9#)EF)@rAPdfhH+p4;Y6;)K47-2I%m7X#(Nc#WX=X z=HSGtYnP#d@e?zJG}sLjqU9ENjM2RO+(LY49&X^WVG-mrK7{7wM*VZ;1v@r0F&_#e zwmYP?0wtL=UlOY|R8<5=%zmJDo7*7!vsQ{&hy*!Uyon_Q&`RP&6UPF5goPuXL3#st z)Uicc7%}P42%Q0HNFbKd0TGQ0Tg#G~pl%>hVX_ct;|B2kB^6b$EgxA`vb6YPd5qe@;UPx> zn#7mER48DW%0$N_e=6uTXdF)l5%GvdEg@_HxfMYZ2`qP0` zbkTtz;GzgqE0jqi07xy~9tU0oR+Pz3!mu(}3^7zfvJfS%u)0Q_OYAtyR)R3Z2tg#0 zI1!yV3^AyOP&I@e$PZRavLG2HgusK-MI=ljVTb^s3Z{41k_SI8L_!SWF=j+|d6Wa#GReKQ5Vu02KLMqIF0-3?()f z`87+%M;l0o?0-;=XedbqNyE(0K$mSamu#Z6mJ-B&5GO>^3aAw7dYsN=!XzX_v;vey zZ8asw1)-#oNgYa)sUYff*Z|Unyy(B7q_aAJlY~SP36_4SL(&B>N?09f)=m^ct3b$r zRz$0ea1Qc9LYfwss?2Tt-BmCdUI2`Vva}$nk76a2!HsdU3dDFMEM-tX=};%KlS?!t zFuSx$$uwAY*s+=MvO`apNQZ$ZfJUO7Fj?k@6bow`8aU{(p{}yn$OJ@atc?P)je+$c z@>(QiQ3u{3e8M`=IjD`Vh%_RjRK|4xL|xx5B9R}@8dlWHe&~)k6%8NB()h4UiX>S7;m&syEDK@Y;ujM`UD=L@>c@0hA=? zQZ$5BFc3mOxfJlp0#yMX{-OPdQ%D-n`D`FT=d4J9k}Z;~6&K#ULVlryf&o*nM3@ok z2pF?LJ0bK`uynE$fW1460ZGF;p^(T;DF)IL7aT=-@qi8HGqI3HCpQe41kaFlCYcos zPGogDr^q}@7#0?CG??NuuNkd8q}NT4dZk7IL@6Dld- z5@B?(1Vk76<;B^ri7?5ZI;UVP6Kqk>Ym;f@6m0BW`HUnq+l zPtaTsD?b+4X29(VwoObDfyO8|+9VB(H(&~Zq7q^e+Yp^usk$eEl1*kV12VBe*5<(Z* z$VeQJWZpTRKne+<9XUw42%co~Mq@+;+lXfmpA!>>BO{4Hs9syqvY8MwMT6Z%`pXYc zaa|8V6sY2QAFf7l4T7*9f?AM@>lwICgZP(lt%PeYxJvV?-GX;q07et8o^Z{95Cvg9 z1Q7%Sh_`|ug?EMf`w&lP2QV@z0inU;X&!-L0ipjgJkh{Hssb^RQb0t&Dg_228ZpF! zy?s~!HGmpQb@QW6fLH7OeP9yUkPIco1RzU+S5<@}c!z;WLE1~3{E)3B8V5uwACpGb|+llq$5-PasEl!BG&};z=VAVVz3( z69BP_iI5K~0Hg_Z8peoB_V#oRScnMxgbP1Gw>Dmt@+`qgBs>91KpB!;_J27Hx-6$N z|D&;m*uQhUbX=HQ{C!;f3kf>5zRTMDAq`2gAhSNXC1&s!h!j6qK@ij|ezcNC^oNL&+r5QA){Fpq&jmcf+!_OtQ{TNJ_P+7)l3NP7DCZTtv~24 zDallF1+`jciG$pNkQun`m8HKFBmhqcWOz*hvswle(p6=|T%tNs5J`D7;+aM!O35gR zgmW+8t)|SjSlCBrfRBraLl~92w2^db4GoHQ+ znw5!_iMg@4aYCG(nYCG*rHwTU>#YJp4d@DHTOnb+gXK3`EfI?xu(6KptvtbC@iDmdFcHu!blCELYxWqLPUPhm|;}W$o4qY;x>JbzMwnEa+OD39laTLn#=wA~AKH=~V zBasD`JYs=>1<%NV$Ph;~g8gM=i$&7_+9jb8yly}V4WWg4o6#aXLur1LaEc$;BSM2| zA%S7R9^l{)uNX+_LxX)6(Cc7L2Cp56fM8&aMeh_CI}_+glY$}Eh10q)6SkpKOr?l` zaZx3t+tXm#XThN0Lz$frhBfMcKq{p*h z=N8C_Jmz2-4(~&-kVWnf`nL~e!~&fQL`F|DyGYcR@NR}I3SgN(hB~5;glg!8NmC(@ zLB3QXv@m6Gl7WI4rp8p$M{w{A9SRNu2EgMYqehA{+)#dE{v_*jqOjcufO-?k;Fhxl8%Y9Q@C3&h0xqG)LknI+EoEXH013T;$6@^`xn&4VaHbg9L`<2iG< zCyIe#nxqp2>_>5Z-Q#!h-OMtWmTqQ>0JNz`ydqJ|q1HQbP>;f6$wg}4P1 z`7uhQhlg8O2nF4QBQI0T&z<6p(lB&_19?N(G$01UHa^8%c0j0vZ~{UdgewqkLwEw= zHH0q^ux89f8A5*uLm}uuFos|Q!3Ba3gmDliKwv=NKoCQqz(y;{G^lUHTv9P%n!p(g zgdmjg5+)Ia5}lA>QpE27CdmQE^mJfC%#$YuLsvUj%77+=s|kxElLWs10v>wfAEYJP z`Xy=vLjr^=;d&4H6QLG@XAkms1hviVA5+a?3lr?-plA~A?4$z$Aq!xmniT4YH31bV z3gN^7Du=^LpmSt~Ar_J;L@KlF(DXthlR})Wf>!Cs3d@o_B0qKWwkpHvY)BU;Y6+UK zCmT6@yJt=SPgyL;gD|ndkrNOLr9$a#$lTCw3UP=FQIjA~H&~H&DiF>oLJM3nmIR}E zbq&`Zi{L{2-2K^b6oB*|oz&q2b02ypTGm!0vdxk>ULqUT>aw+zB5JEPia^*B0b5&S zJ2n&*Bxu7!kWJ>uKKLGdbMj(AUM$Ir6?w5HFE-@G7G2=?4R{!ThPl{7@P!Z$ zAsfPS2$c|?L-+wf;W_3q1cD)iF%VoKghGgdkN_e7J{hv$egTBF5cWVg3*jn+ClC>1Qh25*D%RFdS<~r2Z9hn7KG^#=0eDWPy_)T9a#U4JYBFBUOGqT z3VN7f7`&N_$>R{n7KY%m=xMe2}YiJ`mVk zu<#pzV+&YBMTtg87lZd9;THB=S@CS}HRv3JJY6L3QFT^|0n`)%C}>iLTo!?r)Yp(# z7G`SJL3xujVEt0k4*XSeMbSBMe;Y!zOq>M#UoRR(BtA#DMoI2bree514510aSDAQ} zrnaoz7LA_KFB@P?%`sC8EFP?`PQ;!t+&SWwe=IvBZKt7D0s(~Z-b2R3cxE(Elf_D= zAxk;ZgaZNtC?qm?ffpRQB-jX#H9{XDo!3Zho5&@N0NsFq0yk)(#Gfu6G-44Mi#E+U zl3S#YlJ!5omTZ$%S$o^EOI2JWA>KfeMne9#|K}kug3N`yMv{B3LfvSd6fg3@Er_^BYXCTIk{wSY zrx!T7MP_{p#sFP#BO`n(kYiq?3I=Kfm)zQgQxZY2+vChc2A@F~FFX zBx)*?L{wa2wxYroGtp0QmcV8Obi65X4Gbmvh&WP&G!KUOk=-Wbz`+?oLF5A;c1KWG zqrQdXm(XrAhV>xXf#iv|ptneV$*(~+FOhRWKD~pbkup~#$6^4+7xFOSzP&A+bSP7X zIZ(gD>N3t=()Y+x35SHiX-AUQ(16E)=csVfEjmMi;t7_e46rN#O07|91#hb^oQNvzU$81dtq=@&68z!B{kQCB0!J%1 z^r{*15JJ`#WLT1{KJ8W7@Ebcz-hUuOGb|RsoWch;OlkRT>EsZz@05t>3Ihe+i=@TF zaOyf0-aCOr-43IHtPi}b0n6%45NP1OV9-`*mnJWQSxr%aGGhWg!his7lI;%|^aNF< zXJZ-R@J0wS(UM-x;0S;g7Mye{I&fBU7(_=3$&Le}jj*zkt)EO6B36QzRoKWh2;2+( z82{&cRHU5gg5Um`Jv!osmobp439C6`v?9A9IJ>et*f)XIU?LzDVKC{CRcYgHBI)nk z2pxcq_hrW;w50t)2uDm1#u9|7LrEQh37PVDU}Q^g7eG>G1db+3Bbq}-I_dJk(n>HZ z@~Q&a4o)1%jhARG#4yF=;e9OR-O^ui7Zgws5dm3*{=eTdGxvF(o0~Mg{@&l`|Ns2C?c~gJ=FGmG znK^UDoQ}4_YeQ9KpxIQSEpmg>CC}{CK=3HlFkN}3MoV8is6(;Og@ioT6jDvxAg0uk z>0iY&`|hEQ9e01-?@D}^)M z$QCGauSi+vlBGZu+L+0L`ePsL`FG-Hdo=~d`4c*%r3D*|i_nW9&dj7Zs|=(rDWeiH zTXAd;DH8=bHGgIt3$_`BWeX7{mL?tUh*Bfr=p+&gHUrTec#?!Z2SYyNs@w8n=fNEP zv%J{0pXSBR1RQ|57v@@+mw;OW*mXDLTVU3}elqC)0Jz{T(868@cn-`DU~)kdyC*Mp z)i1$^--j??EQmq9ICe~A=ee$_vww3ZQo-R6@}an<2?kcB&Y+7?`ZxjWPxiz+aXJO* z5M!0=Md=H&&3#RBd4XC`dvFwj`wOIItC~-@PDuWZ2--ME(8el3(+cU@xJ3b|G!#Nc zaj9Gw@^v&&!5pOqY1P-gG1=rURtfA^I9kL7N0|Zq6-)IKJa%+$(UQ`&W+N4B2l&>8 zQp<+=I%6sJG6$lAtr+xqIF)f`2Jc44Ug&OUsEx0xuPrGnEoo@XG-w1#>&vl0$Gy3X zq$WOyhT_(VWd}^ViGDxFpRH27M$p%RE4ko2Q`@(8x(jq}X4l*Gw|C(fG~`Ml6AI~1 zS+sD}353rgpq_0g8U;g`UVvthl2F)VR5$L!QOS5s?RrIoIri`s>H5yO6`h8=!u_j2 z3M(X^BgD1?6>>N zgTs{&Kz<_L-Q?DO-kj`yM(^NlFs*X~y_tL5W0889 zvvU}e2)+vo&^8<~TjBDN_X1oBr3*{8Y?je~=_c$|+=S^$!(^Z9tg$I@A4x)#F#JV` z3lVmE6jg20+k_Ixm||aXR@xgf;2A^%HMFx!jbpBqI9QyXe)LU);KUGtR|t+M(CFL_ zbXxQn&{;o0fpb?{sriQo=D`Yez16EwUXlaz?_IJ;JB^nGiTvR(3J#7Z%hcG9DxhC% zi&2t}{Ewo`+{4;hB3TPCV zD#K=K8u+Ix;0gV;oSwC}OZi@y~5XQ@df_^R4WL;#c zg|E0M)j*n@cU(wH)r0Yw_KCfcjRVS+1-6o)iW2*_V-6~mKbYUz!x064F_(*mZaL<{ zO1-}y%DCmPY2H!m&`b^1xEyVJK75)B2lgZy6Tm=Xh2r^BR|hzEHBYRJK{yGqF!7Ka zLO|wIZkfo5zrveE{@=&^AaD1QQex-#j(+n%U^toL*=D25Y;e2lLUx`RN9kZfLh1B# z3F~^bd|)Jk0-iXeoLAQr6EhnuPPfQMiN(ZDYajXrPvcfF(mrZm!r?hoa@m_p<4Xhg zq9iPK8VPA+KoBTNk>I^1JVW9zCIJSg?#d3Wyc6bjnA>1(g}DXhCYT#wu7kN2=4zO$ zV6K4q0nBAEm%>~Eb1}@D+0(ICkgH;$0-Ois!W5{O+7Gw_Riz$Lb!rU;L~f$@K>*L| zrh*$su7wWSZtiWzWu`ui@hfCTLN(w$A-Vs|i`l%;=LXRt6XD#$WG#miintT4^CmDm z1^PBVv2g<|e6#c?hlcuNgj*iGGnjG(M=8v~b)=Xs)`PD2Yg_eD#Wnh+#aar8#)j;2RxQYHCM zCtwjOY{8{6O4flFaFK1<3~uU5;#znj)zBrZ)R)VQo9bAzoqfw>z(>a(B_)|DI7uwC znIXTNqLRzWne#Lj9sJ9@V{$)|(nghv2FaCMa*0e1yu*qo0;K8seOMG*v$gt)+0px( zgTI zC-mS+7U-4(=VYAF-pvTumugMoC57LV!ea!BLkg%H3mbcJd6Vni5*F}nQJfwbZWLK{ zV*v(ae34}>#@?tjYQ}*c$t!4`J+tk7L2yf=>laYS9a?l&V0QzTRl(gDCrRW0xEilT z5~Z93F*#+*p^HE{E+6vw8S#_ch#erz!IV)Egisnr!Xmm*O2*0v;y71L#pZdjHs^@E z8w@Mc*z8%FxgBayXj^gfZJ0<2*A}&Pz&@BoMeDeBy<^4$QK2d0qzUR1P*xOX;YK=E8qv*( z=y5!rGP!FlQl%_gDvXfEXp}9WBF#|7R59VgUXU4Xi-Xk@jGQ2mhm!WcB3f11&{$qG zj>N$W633EoUB&R8xXERAJb+(7g1LtY$HP`?LdP)!$`YkTg>9SV86uFaoGr;PH)7)b zV2yD?7neu!Qk}4+8H2)RzayLKg5fs&FqyNU7<82RA$M)#;Gt=vXD%X1N*vG>fn(&p zEnceyCN*Xl?wcn{*uG&B8B(gMM<)YoYNF@)!{}t_|KkWu22o~%(>J8meN!j0A-PDj zp&C)vV4{;|zro87?y_P$6*7>cEO?;oIlCZ6abtObeU%JZts1z<0qI06F@b0M`zW}J~yoDxNxUPGoWL0^lXF>X)E_{z1{RHjn4)Sfxf zpndH+1DT*wA-gBYm_modt)-?dHq|oQs_8%b=gLVixiCA!MD(QT3yvKQ$+NvV=Eo2) zPGG%&wKO#~${6Bx#}*F6JDwRCOee2$&;oWenZRz27Ygb|T#R^xO{p0If<&gzqTu9Y zle`gCwPUsfaiXV~6^<=AGclna`Sk1xYN^8}r#TA+71eVv87X)FY!z@YQ=JSEm>!VR zc^vQtN#A$|! zRJwNP%?#j@u{5uUUJ7TFkd4R8L{bO7QYR|{K2%nvj&rblDY`uvhIEeARD$JVy=onE z+}$BraCot4%s7zyMtn;qjmD)PAJJl~>P;k=O=B6A)LhaUNtsH}nB$;7*tDs}%wowv zTSjqmm)q*SB0DH-F{e8-g5Yhp!_3%{;^HKC;C#7JE@~1}s|&V4NdRvwvhSN7C1pLb zPYYTG8DSS(S2q)dZIeQS{T_;z8@uhw@*#1F%^suJjQAQ8`uX?<3$WjIv=}uk+c4d7 z2?GJf6tsG~Y{9AyrglKN&4I~0#@50JkD z3&HbHgXQDa!VySHtxq(kk)E5OPtJHsMvV`0q_U|=`Jt2wbqsna5^^f!l(|L~iJ+k* zwsWv3N{r#odN2%WJmt@+_rkM8Y|)lqwI{f-hu)#%P&gb+REPAsz6Pi>%!=kU38*$Xbic=MN0|%=VlnHODnKf&^U;wBC94uF zZN}A~7>2B59CvIf5RbV(!?MQEjOw=y4LyUSA1RI3t)Q^&>5+3O1tpGlaV6=LHcvqS zj|wf)*TIAMv;~prxg%CVxb`aLoYF`}EsaIW_HA)uXSW=fuT&scDZ-?#Y+X`3l3;RJ zs2dX~$u8k;(!Ei6JQz)^)WB#{tJX;0BS<9csW5#7VeV<$K}_($0`!xnjh=od|0w6j z`-e!3zTMq8Ta`A0<*cvmu>O7DJ}aA5+NXqrYcoFozP2Z5JNDYyu1qWGd!q>R@tj7DWQ4nj;9$06Lbp*t-x6TXT!~-f!9@q3c^@ zzW?yy#2C+F4ew%lcG@;N`^b*7h(M6L9Rww-OOMFcTQ~uwJbQTKL8>vZSDBJt+QIm2 z`45In{)3PW$=l{j#c8HwoVM}$Y6`0;>TG!iY>;5Nv4WfrIS4W`a*lLh6mMW18ZBFc zdB{Gj4FwD1*(U7l8(K1-F`(pp5oM0zKpU~dnJGSB2%?vJWH5(}Or+lvTjEGh2jY>! zQKLiKz@*bAw>^sSPLZ=45jc}6$_S*>VWXbn|J*h+M7Hzd@Op)jF5yYRm^`JVoJZs= z_!VF`lLLJG+%Z+mOc9tS!@1bQX`Z2goi>LHrs=fewd+<)S*^p85ZCF=`AX`vjml}8 z95>6D!O(c;J&; zrk=q*^I?vnc=y_To3@FzjJ-&S!y;Vi1dtR#_appv)7TBhs~Z)H?4b|9y2x9b?|5X8 zR*p+FP5o@=_-hXQDWKz>&IHhA^@bHMuHcl^%Z-=D@;Js{JTE`K4sW89J}*Pa+`~FH zv+||$xFacQIl!|V&?1mzOz}1ADL2W!D-4GT18qo--ck(%XjGz{^@{Y=$nZ_VeZl!M z&3vM*K{iQt(6NvTe_qM{qjBGw?;cn#2IXAwp@J{jd`* zhB%2!M2iAWj!=~4ngHz18a;rM_rju*N{f-zs*6u`;vS}%GqClE2cfX9%2r{|cEIA3 zz@5jKlPf5T2)=R}6s-|y5@{;*iiYBk!W{50mR@;B9Wr?6a4tF*{tlXkB*}^SNa(^- zAZ#P-;zg)B+mSuGj(0E;4;+lfn@2>zJTe}f18>B`TnK0UYx#CMF-BI_EsL)iH%}$W z^Qr~v)loy^%~?+?%XyQ3ifhJkwP7Q=@V+@2cV)yNv2%h>OD#GwrF1mmqziA_Byg~V zgFGYArVyY!48=`R=)7_xEYUK&N(e$FL+mE8+I_!x)8Ifa)_S-jX>Yh2z9i#xw#?01 z)KD>Ci+TqwSsKE84C~?t8FyCe-PRC$*Y@pZoaG)mg$_hXklbX-ShS+e3e9ivqWR1h z)qc(ADyyl#g|AR}VNt-?7nYTs#L5yn3DGG{!Q-^S?E5Uh;=Vt+aJH<1AmNCnyqU`d zb45qB<$aynqb3kx)hd?6&^V)Od5x@Xokjfql?wn+P=L|PJZlz*Xu3QFP&^B0rCTq&m&Sa zy_mh5`XpbeV*%_vIL49s6YzPcA`nstlMiWF~@ZR{>;VvLi= zhfoQ{HI-^Pv7v=+fqW7M4T#^P&#=)rShA|lXGTsnouU1bIb%s- zDBU6#_1I~^-eMx7Kno9Dl~7~Z9vF#{UkD{v-+CHxIGl$bc|&P8ph@?ZmHOp+9LiDqnqNhOs6 z9y~HjEq~>hyj7LrqBa{>lNCu}^M}MKq)*W!IJy;?qGN>q88J`~QhI3=zQUyh5RN|i zJOFg?rIb3Q0?u6MDLT9HfVU5Zco2>0mRITd3U(_;Qd>Lz(g(LI9nq9Lk~)uxbEmHc z>!Y807~9C^dG`H)L!>GK_30?EV?Q!F=V%&z98$(2YRQI3u8h&)DNNHdutnnAP&Nku zSo4_za%)fVfnGonamp!!rvO{xf)Xjpy3$pt%5F7#p*Rvj;S3HWq1g$OFB^&#?v#R- zj_sDoelMxR+%DV(!Mj}npXRXif9BUF7_F(F@>90Knf-m_W;2_Rttp=Qun@S+mR8~h zh(K>ZEumIWoPoMY)vFVu=Fnu;l@cecN_?71Mw!#cf>hrMlxvDG**cHHQ<6a2-5b`d zgJ#Y$bMeIaLd8nPMqnXxOy+z9-fT`=0hd$_r8gFHTs^`wGmkL>+fV5pa70Z`O+_28_7{E5@d7)_Qkwu{@%1z--7w zV)`Aa0-U-j=JOAt=;QeX1LiaBqXdJEh1rU)g&;I&s9kJzZxy$~!S~G&U0)PMt88?* zZ3Az}PD(f!vaeb^%W>P7D8Jy*;Eco|*$MC(? z_q`S0+kD^K@U1@e`Ok)b-**h(TYcYK@x9IWy$#=LyU)Ldf8Tcu-&=j(Tk*Zke1{Z% z+wlF;hf;nVv-vBlFR4~#^D7DPoGAewtR_(ErGrB&v!G}&j|v{_Zv+kj{}FU_o;2O) zEo5A;0T=Q4qx+kr6Qwqu9-FM{FRx*nzhW&=zk z%#kqbV7>rz1kA}NGo87r5Ox*iAAE?qN0jPT*kUmFv5X@yd-4On)5htKS38|QhH>UN z{V_kzAHz6joc@@2=a2Bbar&cvOXrVqUpP*GQJ+7S`^Dq*7xVekuwOb(e_MV2a$x_# zIQ_8#UH(``SB=*n3g7vg0Dsqx*B=Ve`J=9z8^-GoiF5wges3ACKUAReN8a+b@%oE; z{wBlUo#XWv^Zez*-?s7k+v@q70)O|8*B>g-<&SOk{&D*Yr~I*x|9ag1vQqx0!rvp~ z_LrOTw-fw5K5l;nDSsTYpB}frXv*I-_~Vb`j2m~@m)S4b$Jk%k_SufvCfUB&R#@+> zL)Hh&mgU4eGyO~p!{r*}fBSc!dP&VuuOl6QLpnZGGZ8QIQm6_;Q`K&vom5e1XEi%C z3pnPFzx4d2b82M$J8)0VX!)8CEBL0?US4shlL(#}j4HyyqSf9Tnsr2ZX<0cGAyrhx z@%lo2dD-k)vobr^)Ye#vkt>n3*s;{KFPk9x;i11h&ku5j@xrnOp2`<2@5mfalW~vx;iBs!K}8RE(4&a@#0P*q(u6 zD~J)pYo66(CGSYhj4mHVMR8i5p%};=e)^J=sDR`w1C5uiYiO*kP6?(3P7|n9h`6Z9u$P=7}%5k7z;1(N07M4md~#qX;H@;APnX%Y!|>M4?wEw{*V@3!%JZ{v}5 z<%%rXf_MWtGGhgMRsk(m!nrMoyRW+P`pSj;YN&#KCw?t*U-1QL-wUl@2^5cwA|nA# zzpgTh=ww6E9tvUg9@Mu3RxwKNW~(qpo__`Z%opxIcm|WAwE|0!BsM#p^Pt^p{!IJ8 zEOyUC$rNApq#A%&6l#6>WIHz34vE$=vUq7p=^9zb$#NH0RE?yo?M$Lv50c_MN)+{m z*yDm`oOzjF81Z#N?@$8|#+dSm_a#v(=oJF}r6VbcR+eGegCk9BwD5Tqv#!ZA)DZUI z6OTT9_p+rUitLeFNX6sP9b~--ZL7Y%ww_A4;uR&8Rbr7hU2D0-N`1OEQBuEp1P;P3 zjd)7{A<(0vQgZo~=w~>e~f|RbL&(6fZ#MP+DI) zf*4P&HegXW0*|hh!byVg++xETU+jFxc2$Wl79vRLf0xlE)w!i*3qIIq;?*hPo1H74k>nGFnqoo!MbcNlh6y#3n{NC6&xTvwhs zrn-{)Mk&;h1y@tPK5i>7y|o~T8}RT|c};13rhL(@tbBC0(u&oid81Q?=7|U;GCGZH zrz{wh<=CymKAA*96j6O+poDOaggfZaVqIBLf%Y13TVIdrk?4=CYxK9|byao221$ZHNIM?Q4e$=BA9hNYl^3>kmm}-=@kT>8_KJ2o`%F$)VdU>3WM(Q zhA5Mn)||y}bx9SNk@8YDn}DzS+N!EmP->XTSIW&5P_WULSIE;S7|v?f#^obk3iTGQ z>{6I`WkXyZybik5m)Dgexn=RX`dY6(N^8q1{aw!M^6P34U70kJRaH0!_nJakNj09E zFOyNj@41rbU^yF9MO8_|8p&}gwZ_3_G=&v)hJlI_j2r5LbkYMFQHj2>Bt;HJrLHdB zp|TB)cuf>6ls}#YePb-34U$WLT zsvzz9+I3ZBajA-+1Ixjk{suy?s|70*z;M2xes|xrXwr+)%7kaoNO-L`Kbd52!0^z3 zCJuHg5I%Q38yf3Nyf$xgRA(liAn|%&8@!nu0YUv{u=!Zri2Ap9|D>NH4X_6m8|)4y zA_f{9>Mb#DN0isslz1bL#g}hD-cng`#9ossgKmG01?%d)MnzmBWJWTeT_-PTZ1jq& zx*Q!P&;bM^rN{)M?tJ65xQ!^B6qdu9Z|F_oM+wBvCaD~z=%u24eaSl5t?nQ_s$`vL z-9h;X+a<(!>(Ms?%5Aa#2C7h*Im+nm$m{QSCd%-$@_U@#U>t&M?b!3SWF=WG_N zXqSO{Ocn?)!2gMxxn>cU^#(k7E!wI#EQqHKm`M=hdgx*F^$T&5CRO2Qp#;sG zmeiMdQ(iFn);Wkgq81MvdgHf=xw_J8n1V4SLbfjGyUrWAgs#54^azaORe0jjYqZQU zWHl-)f-TWQyQYi6EvfcG^bu4D>%#awmeOJIO=<$ zpP49Qc!Hpz*>75!_m@clIjO)6Z6uTK;sXrEf9}|3e$?B3yug3>y$wC^XH7?VHja6x@His-NsbNuL z?O0od5fD@7MT9*rnsDwmGPUo9-;+EOS-t0<|i^eyHbt!X!oS->(VSdos* z#02&gTmgp8;A+hg+1eocoZgxoNy7-<*tzgZwJ9;)tWM>rdR2KDORcmVOEI$NRY!u? z9qEi`h0E$!twstlsidXPh0)MJ2D~vh3VT%Y z4NDh%E?#PKGO{TA>6Ab*pO}n~5j!%p&h%+tNAibcy2B_T)U9QqA}L(>jY7j>I8UBiflj6XGvl_EWxG1cp9t16_9c2G}#x^Wb~BbO$GQMq18#BQn7z~(M;Cd0`R7wT+qfX<=zj6W`gJwlf+OWr z7hhB6G0Hx41D*y=3yBPB>c)2%@C3jJ;5mRQ`p z0XN`#5@0{zWWa9#<^x^{I0f(?zyiSM0Sf^?2Am4G>*1l5I{_{R+!?S2a2jA2;4Xk? z0PYHSE#P#({|4L*@Q;8z91O2TegJm@3XG{ z6fgq#4qz5weii5eivcxY86X9c+5tm=gMeYcuK`8?F9pm3yb~}R@IgQg_%@)5;+(S@ z{s4Ce31oOr;ai3jWli~ybim<9L)z-+)<05#x!fH{D_1Iz_{18@T1KLPUq zC)c390rv)+1h^D%GT`BW`G8G;Qvi<#oCV2(T5f0x$vi1;94Ie!zCXQvf>vF9PfYydLm4!21C=0=@*;1^6D|1%Nqq zp_LZ`?hg2Ez(s&6rqo)%5a2O@VZbeb5x}znDNS`HAf=-20@Q#{0pcrIWV@JE0Vzv2702TmN0u};x08Rz`2H;MBKL*?x z@Fl=$fKxW09srjD?h5!t!0CWr0o)DnQotF2_X6$?_$RM;9W|<5a0>?z7+MwZ@_B-!+`e_e}hub z5fAuR;sGZfg>nHb2FwCn1(*$Z44?)a0L%eA6EGL>2Y_muQa=F<0X_s62K)7*<;);llK&jyLl)qK5)S7S;bKL0ngCCN5BW>W<#Bn*nQ{BmMXLG zf>6toICx{jBnraX?M*FPtN?`xPGni*y|`IeEXy%yo2hr<&Jo=-5LuY*%Z2d(HK(1u zt(`4ndrC#z)ZdM}o-P^qLZChb1jgHX6dtw0g@ujr;ejT+x6q&1#N&PlSwo0;iM$8A zk5L>T*Eb<4fp>>+ZcS?5`f^9x=2&|}V@cJDlDbvAj3yT+w?zB-k|D&oGs&6w3d_}? z=%bEPB!k5sEHjJrA9l0mJs}Zy8_hk;gtg!UOubC#ca>?zfPO#F!^JilOspAR_T z067E)jFv=E4N&0ilr}(m!aTL7@Jdn=dhv0Sq!qW2cpqb)sXRQ1702W75>_#nPL(*j zHIABfIY zzK~UMbZ~f8=RmTtcRen#R-hry66Mzm?5(K^_b z=tr>(@Kyp6KzEAtG(s&sqsFC<+4zS$o%9FQ!Zu#HE<`8grKc1cTSiNbN7aCP51CVp zdPBmJ%ogq^A!p3y+{1>eaNV-Jr*&T6a1EcaKt_sr8w~;|bg87|(m?b8rm{G?gD{ZZ zA+K!9@W>BT!}v-UiDRUGl;kQM$utVCZ}{_riNOT?mG<`Vsw@WH^nRXds~8Uk`LJsz^f`jcWme!z^vrSfy!X_Rf~%MUlZfTcb3e5lvsxD6O7tCOtmfFd|xr zY=))P+G;@esq9$=eLqQDI`a884b}CoR1~;rG1B8~6yXRg3c0#zxKu9dOHV@J<}$v) z;R1+9VasHQiEr#Rg`T-YY!$NhQ#qCPomPYEq^wNuErrthI$TiYV3o1UE+Vn%vcB+b zX(9B55?IQquVoFzq!_KQ%23cLiotoq4esHmdBfGb2EPV3j3C67maB1lazjw*ASCa4 z2H5Y{K*q8QqYxUa!~iHI7bqLVx4@RJbG6A&OUa1*kD7(Fe4CbH2(EO{HScGx5!k3XoQZ8Aff$Q4TTp$KdX5Ku(PEB*Q1<6U;j`|FCNL+ zBB==sqb72n9~*8dr1}OD((N!!q|#wsnH*_9NcA9e%MQ83F*%tXOsY(GFf|0mzen@no0!QFDCDVD261QX0hZ6D#=3n11+8eb-v%_=p zj!I{#8SHLO^w+j=2o$Do#0-XRw^G6gOl%y;R*Xa&mc_&>EI~crHx%B2aVC$#P~F(u zSBHfJy5E-QF&q!sX@lwDJOI_bsTZYDQP50om<5Id`??;{RDk@T8K8E+u-6Vsv+y`2 z<;J=Z?3VP+k?hvL=0REu`6A}eribaE5_@6Tq@$Q7$kC;5*p0cRtB#Up22RklkmA|L z7&?}Pbd^MMO`@ySdPZ`0;F)tGUU)Q|sWg!k#&mMQNqIE(8cZs}F8S_Ew4yN&CRjx_ zEbH?aCjLJbtgDj7WH`OjRp1VVopX$!smZ>f6f*_{JD4}I;f6D$L5J(*;UsCP(Ya_x zYKY}$2OATmfBYKk8sRMCp7?SNsjL%PBVw0{9A9&dv&eV**PijgSkltc=B93$&Y=`L$&+M`YHZ^IPLW>W8T}j$?WW1aF zl^QQK)CZVp&idmL)#OJs71{gCGEE4A3RkGBS#pVsywv!f;2YwcnGLe3Wz!&Zt)-C0 zpV8=$zP4!@9-a>qZy#*xZ$;tt^(pA1aIZgM*|-lLr+}gh?338IqD@gDj&APgZ0U$@ zZc0+8j*Sy#S_J=;+utHZpYknF2BrDMyk!`#sWT(c zH;nnWn3&}Ey_Z-H)qxVa_H!2U@9)~y1j9y3HPp+s4qz^AO;;sjj zoS_v^^^vSwq~g#hN8DI}X9x#Cd{-XTvlZ2i7vegh+=g1gp!z=95pC(9PBAW2xu!#5 zvTakv*V|B<>D{BjHy7|hDzzw z{h*nhf&57Q2ORsLn`1;1G_lS1iWP$~^(R8j2Gyl-=;X#B^d)g%vHO=8S=zCs1P!i< zVKxwIyG^w-^FW?$e)}lw8nw+mteGQTUlX3}p+YnhDfDwfjgjxWEr}fyKO+6xJQVGx zz)BWccKsLz%8dHL<$#qL6%Wu2B)YoN=#q)V#x#`C9~6uM3MG&V7GfY;qrk#!VM?3j zc!WChk0oA_GMRFZF3Q_tb4)igR@TJKwDv03!h9;wZ~^aM^a$NBxZTH_d*kwmOJ=2O zp~4>9lq+W9G{d|tE7qB~<~EGxC-=50DmO$S2wT2E(Nr3E4-!&@mKm z88BJ{g(DbcKYMd$TPL4Z7y=L33wEPj4p8vqEyg&W0i@jH-R(Hz@rM73RMAt*p?P7R z!#ui$y{~9-e1B$Ya2I&sN3=+p}JjnKiF7zY1}r&LUEEzQbHM)?tZH5b3|$q^Omhbhig zp(xCBm;#tw7~D=DXlj?GdOAlVw|6<4EMA05qZ|;;eO?~-4e8aS z!bh--&mqyLIvOb-SM}3*?9xbEi1~uyaju5Rg82zA+T%w&&66&gCF$>(CtDP=XR)dqkaLpUFvf?%DPeS0Nt+gxs^=E2Y%E? zK(`xUT$n$#{-~#bZnyc|R$DjfFQD5tpIeJ{E3j_&``jkm_#oCK{(cF_yfcg+!BGDJ zM406JK|tsBD&)uLMnl@)!niW}57-&y&MtX2uz+SM^2) zYW2e2B1RahA%ngw>M1K0{ag5IGLO<4#iz-m5Xmv~z$ro{*H{!XVNsi&J;&mfG2YLC zHakgye}u2+bGV#k`RpsM;Nv`u>l%6WML1+U1_CAnvtI{xbn;X)4n48|z*Sm5Q%V^K z#^M_mZH`Rpk)aGum~-S6Ka}L0OG{S9V9H=Nz#I)T408g^g)kSx+y-+e%+oN>!F&Mo zPnhZ7FIl+<%yO7RU^c)U4KobGN-8OXSgtpl%}mNu$_cD=o1vT%n%r9Po(LX}=!bNP zXdcD7VL_hq(00MD`Z~7I7%7K9h%2JFTqzO_EH;s9@NEIj!=f8XetU+Je6lUsApw92 z3pLqt=dWs%qK6 zJ|gKR4C^Prcx3iOWgP#iA6uONvOm-rSWrHWHilQP5Hhi#^%Wy(3rh0%Fgw*WOLwO@<6;;S9Yo znPamXpZ#{`$Km~UKaI0@y9Cx7(gr+4$wSbpi|exunL zrb|2R?nj=AKf3wtcA6G9NAG9|`SYifeqH#^FTX3Py}#5<3QEM9q8k%iI1V-80Z)u& zm8%=qrlCyJRMllm6|U|q=(e!^zID%xv(=I1+b$N&235~{ljWaZ?vSm0li(0HAGCW3tQ2F z0SPqnBHXC1IKb)Sqyk+~BRp56mfm*Fg{IygO}YhkoxRWS4RG_?f;L&JFhj=OkSP^iSJQaruj3WF%IEYeew;9h%c0sb!#+Vpx;k&xr z*_Z8irmhLro_6ff%ZYd(lnhM_0jA#f@Npn)7bt^&t(jp*7|hTHXH;ziwh^1KZQq|# z_U?3mWps0_1o~WWe^K-Z!>Dl&4aX0T=`y?}#zWw^yO=4G?xyz67Nc&eC|WHaV+Z1@ zskWgq%B;%{NiP{#N&dW!Xm{(~Wy^p{sO3u&i}3P= z<#{?2iNx{+ixw_~Ogm0ydNJnBY?0$1o`tE-wqe!W(la09N^?SWw|5VymVqwS)SYY{ zRD9VTlNQwcV|Pw9C6m2)r4Ku4!x%Q32=IQn>QBglJG6ww2U`{bYyuR}Z2;(Sf;zXP zp`=cEso}eWO?^ns^iP+lB@5QUl=+>>0_B{j2eUh}Y9dR*`-f(z$)0bSfbSzsF~eV$=Yh;*HWdvRcTY7s;gcLy=b!1v-U2EvY6Su z8U+@J2y5i5iMw#$h4U=;W*q+zg}p%WFMe5q4*N3Hi(*HHjHEf-3GC+%2L?A)t4?oO~3YkebI`Th&Z& z^C+LiFyu>Y$SJu|Y~sl$clGX~h~wROFRIr>ngk#0|L*SW z=|_Y@3%fKfhSk^I7B{(|tzX=m2f8h;A2$(i zArVe(NOix$dS)I@hNbu6?$LddV>`jbVczqZQ(?agW~cin#~%6B(gEFU`Yu-gLb{Es*bR z$KZRyg4{XKWXb@x-hn^4$ELpDeiYx6x>Dc8`{H}b)KqwN75L8IFBRUUXX1PE8lfkz zoatxt6iUleXgqn!6yE<$+?6Unb}fv9-+=uz7{Ub$eKpIji>e*$*$5qzJ&Fr&?9w!0 zkT7-#@%S48do6fqcv3i2R}cbFEy*U8?83w7%-?$l&z{^6#@B ztCcn14y{~pF7BYgG{C+d<|vprOccgz(`Gq=h&V5}dE7&eIqf$sZdtOtbs5t0epP;K z&+7cxVwfhFE|{BOehKp?%vzX#z)Y*jk8v%PH_?{@Y&XVjS$J48jk#?L56=_je(Vp* zkyu&*b4aBl2Pw~ojG8zh;wBu1mY^d`)gzV3+H3`Q$dO5dcsN)?$QpY@Egg8>V>1rQ zeK;9^JrXm`d*7(zI3A6QC!6eRvOy<@c8{d{0;W$qs|Ua7VYc*|7r{1nA){1CN=pw$ zWnnoWfXoq{g2=1ZFIu41m)FtE#Z6s;g$tIbT8IhAnW0)=S27pMEC?6P~`r&*;(!QD^f)mY=>9 z&-BQVyCBOcn2z+953|L#<66%qdHYZtdG-t0u+a%pxoa3~ar@x;lM7>hGYQ|j!hAfb zF!mnIn=ohN+i%M7-3xO8%ziNQVV1+3=QFji9|04GX@fZ#CbwV`j*zl24{@}@Oc=^m z^KdZoB2GVc#T@g5>{QX6vqD&1h7%LIf5o~F!hId)x;Z!zxf*jE;ktF$kHM+Z)mJOb zUK7>(INTzB6Ap6-Us-P4f3?ZDfBm}#-dkhvCoM6sd4EG+f`cIXyAg+4gkQx$8sRn^ z#1Y>A4dee0n9~^Vs`&=?W6q=d_c50e{xr?sTpaQd&(ov4FcJ^$^v~hhfd6NH-~L^G zEZwvKcL_{K1~<~u?Q)nO!R-5de(d_^@?(F0HfYL#ALy4ZrmpyXe(YN?C&6^V+?n?M z_)Gb*Phs{4{xFzZV1FHEC)n3n9NqX$I=ayh=`Mw#pRwGJdpSRLB+Ocv6);O-zC4Cm z0Jo-B@?-Td)i7&dN?-=YFo(cx>rAy)wQjqr?icF!e9buc9f3-X?KC_3WoPA0$lbmj zCKSoe!AeDs>SNNx{+4=7{2ekfRCQweU-h@FP7KtO)fHPwcVFwEi0G@$t%)Iy;$?{; z+oj7o`v!9TA2<)Bta?Kk;d@RWv=VAA(eV{{9i%P=r zA9k(k8=&16Dr}&~3o7Qk15G0-%1t%OGL|)9OBwt_Z_il5qap7X359YZ*`ZJvOQ1+r zR(2#Sl9fHTWlHR=<|(l}K-%kIl4<5+xbNn3hsbl!AVr`l{ftV2pgBYwTM|Yd95vG0 zuSTVTcqHWHqFXQh`>(9*sJ9kcr9RiaD<8Z~W*TGC84oSs3*!EdF6Gk=SXWWkd-i=dYPs7x` zT_C2b3;tlQ0^Av93G58RJ!HtEzb*v4@Su@c=Ix5K@cVkF#7>6kfN^oY4*Nf0(&K!) zZ%V8cW;sl9pmoIxf8wp@UvHWcdkN-Vn2O|-*kqV}VQBA!IWx`ditj}*&b|K2XcsW^ zVQ3$OxiHP_i|@l=oV!am;U6!Y5_<+F^6e?Hi7@;ITc95Lc>DGTv33@A1>Z|E;DAzh ze*NhSZhG|MLl$3o)f4-^U-`L1uY9-clPN!3^xX^2d^q>^qh^-&UfWu%I|}n}MBQFD zB~}7+G0gKfOo=@Ub27|TFo~O{#C{8W8Q@$Pemg__Xoj?Yw=dXlKmI3U_;-F?{P%;F z^e%3P&lCpZBHqQxu$~WYpf?a{|509myfj3-=A#!Pu?>nb}r(7_rEg5Kd5)dJ&-n4Q&OXPu##S|aM9u=OP3wA{BupsEfD1O zAxc)^nVAZen2DQts`-^Ev4NMT#Lmku2$-K{z>%f`^y4w<;rYLBM;QEm6YwM$emmo* zqdm{k(teiD&hMY6eG~U1AMQb)oo>#!`2Pm?*IxDFCjL$2`**;(cz=!WBH;7?fI0*G z1jdDPAMDd%F7e@9{KV&BWa3LuSy(4&tc`N9zR1HGXcD9i^YP+Pfv-|bEwy)a9=EZp zyQjBr6W&J|9NIj*<#_1b$}5PSmQxV>2F&X)v+=z@%p(&DV(-G-II$qMXKq349KbCw z{C0-;(F|$*ZdcfEKRz;sf9Kc5zcXk_@8Wj&OkprC;$55!%h~<(w6BC2f{7xpN5Gs9 zb1S~*AdJUQ4usnQX=q=cUl5x<-d-g03f68pn?);@gfBZ|+;(rhE5w{#^ zqrXB6BYPIaeu4P++s|`j{C}QY5L@Dl|129n@!p7JY-z(1WD&shLy%F`G}S1)Wm40K zV_#l1!x|t zR&G~#xu9}`Dy731zyGhhKQdj#vW{0VX0hv6AFXs{z;YPN1ee?7<4PJzD=Wt+sU5ki z|MP9ByAQWOY~3Pdlu`b&P2u?X%KP)w)|U?2@{5wvU3;>B{Nvlst$6Rlm4AxVF0Wp+ z=YkK{Y(DV|huod~%Ja3;%wdpv+pZJ#i9l=OO(E!w|MxL;GXx1+lBXYRn&ecH+M4`~DN`Z+95FHvqR=`@-W2Vh4@G zxH!gwcOjfJHhSq9i-e1$3L0J$! z1xQ2t;v29Y`EO&&e0JhKx!!ZDgj?N>=6fyR7p?u9Hx99jj0N|)xghoi%vdyj zx1;VzqrKaFKex@p|Hr+a-S0P(o$s^O;vly@xh0M5Wal6^CJ%*xA@=$KrD@V4V>?iI#VX;+$wD?|9)Q(&3_1S}V&TfXl-HxS%$fTc8@7 z+S^gtBvmCX1C7HgqR{y#_lnQ}_#A+3C`EX~RK#2USK}~c&$cJkoOUU@qOm$!&!ci& zK$p7oa<>Y~X$GQoqO_NXEpwsW7}2L^B5lX+%)A+{zII-bUbTS+MBM*kh^$dK#LWL`ic$?$yyQ z`ZreK?PoSd^xvMILudBkdUYRb%sNWq=#&W$IZYcB27*4QcZJMrGw+z$BSkrFlTC+Y zQm!5JPzD`=yVe68HxSR*+ysqJCP{&CkPyUxp@J%3AzdknPN!hn#v&0}QaJ&|?Me|R z8|@mN+|Og`#w5s;3}kpmDJ98RN$s?f;qv}<=9U0G3e?x!ljxMkn9CX(8mKh|<1pps zp>-^}rjeUJq9O#N@SJFCKc*I(o7)y)hI@#~baC{8rlGXi#*%eX5}1lDC=jy(4Z(Co z=oMGaj#)3-($=mZ4ZH{{cc8;U-e#uUyCIfz70>LOoj!#bS$ila;e>}4OqqC3=AXjy zZpgrWRfswJZUhuf&PyeL*VG_ZkI`544@c3KH>16x5o0tq$CpOS1vQ^Vlaw!_{v7l) zDlH^YXVPU}ZttoA1Ofs%B~FFWx&_K%a6P~}j_#Wcemrz8Kqh(2)b5>kiLhode}r1H3-M`yWB4RI!@t5H6$8A;V+PsL1`x6LnS zjZVV@_R9@-4ENCI4*B%{&aN$JPrcm2CW;WTy&cS_{c@v+8S6v{7MHIe{J|idqj2eIZv5nUxVZy@opL{CuxBpY znCIP*@XERiii{1VCOoRN%up(kx9sF{g3F;rX3l~Q0OF`|OkR-H?HsU70_R)nirx{Y z{LYFfmDiA?7lGCy)iJPW0bW3$fU9BbY!_gHo#%=aYhcx-MN3Po>kPRPYZ{lnB3iQW z&@rdQXvv}-5Y*KiI5a89(op;T;;k=btT4tYn)y%Aj~K_&&0?1O4y-h-vss%3w7JVI>W9=a$zQSYo)=u%aq z*Q;i|Q4Q&@sMACG+>p8?q^}IAn?m}Ikh(9V9}cN!Li&}EdMl*=5mG0H^%-Gxepr7m ztga60o5Sj^u>Mt8JsQ@}hSjTK{nxPiD6CJ4sIwyaf{3~-qOXalTO<1Jh9e!cce3>5S?anh{gW(pPnLc#OFfyTU(8Z}&eDI+QlDn&Q?u2# zvh_vT>WXZAeYUziTmK?kJ(R7V%2qFD>o>C1``LPXwmMzwbG5oe>npXoN$Webx=-te zwR%SDSG0Od>wjodya9Q|01dNBt_4WE_I4$CKh zcgXV}r_T(jQ$i5`M~O|Efr}1{^s&I2Zw1=m{!2(DcY8XdE)Oj-iO2(b-=a0D-gvquv`BxNr&j8RCa5;rzNG&)L0u4{ z^@@;wcLEzc?9Za9Pf(wP_JjTUu>NR*dMK=4%Tv#V^(Pb5$726l|pby1GKF;D#@M?aOP?$6N==Bamb^fP(t z#a#Vyp88vE{f7Gr% zm@o~l7v$;7JJeNqg{OC@C-e3N=ERBmiVk(@MEbpLqP_)qvA;1<|FT1!Hi`J}P0~+v zs5>VeO8UwAkD#9{^po{_pcng_ll3W`>hyf#FU{Bg*{SXl`(yd~hn?zg`FjI@!4&=D zPIdPb(mXmvztgE+o}%yTR3C}`^aA~2r@E$K8t@Mn%y_v|eXo#|*A?pbI@JS(^I<=6 zsy^j7b?H>WPu15Sr|zCwc>Zzft*QIK^*cN1n~qbr?L^o6cGAxtr=HrW@Sfw;MLQF7 z_0IZ*PGe2&V_Fsr>>bs*W0J*(>JQ$P1_mv_owNbH>%TiDg4exb^9)K z{nak|;f?C)T?%jAs4m)7Lfch8x>4OP_9u7MA0xC~3t!%-u9+^OP1m1-UhJ<<*H?C_ ze@vfxR+qYCH@ZHso4&eBy)O3mchiq`sZ(c6y}e64ID@Xw%+ODEsSm{dwcYjhE_K1~ zQ(x^;&+Jau*LT+^cB@nOp#A(k^fle;$~~rjt6Tk5FrV(Buj^J9?@8BN_SAQFt4GED z`kwkX-Rf(55r5HM`uT2k+g{UQe|j%{YL9w-ufn&w)irz5_13-h={@S6Q%1@QT=j{`g3%z7kkt>MTNKasAr3af2T;F+pA9AXU}tb)m8fx{;@~B zu@CVl?yK+VRp;%y=iR;PwtWlF?^UPnNBsHw>0kD$Tg3kB{qz&P>d#`|I#d6#S6wob zG`Gyu@Aaxj#s1n%eM+A?c^2^(&(i1isoTZ=2>$k|x5R$Z{`yAX_b1Im`>%avle)ZE z|GZD#Rjgruuz1Gr`_xCp2f}{cZ2i?u>aN*S|JJ8om`&F=XX{fpsZVE5J$aM5<$xk! z&N)!uut|OIK>c5v)C~s~p1w)FD46r-F#K!g%m&SKbM&q7JE!30P3rWy4ClhR`p!-2 z>bX;Yx=Fnun2+Y_dp4;v<`v$(N!>SZ7ts7+p1yyRdUsyIFE^>P=hN3E^YufU)b;ZV zAKau~oqsSeS1-_yZ&G(GDE#dv_0a;-oVHLugYXv?KD9|bx^P!u-dU($+N4feRQUWR zb^9XHJh(`|x=FpZh<)K(i}ia5VKGztz+!z$zj|Hl+ZXHq>{k~rA^w&n`tpADnAl%l zq95#6e_v8~Q@^@q={&f;yi`BbufDd7lou`2&-bg_#Qw-K{g;0Ame@}^NPp6=Zat_J zG^Z}tCl09ZEZ=K;QvGVV{%2A>wY=!Vq}slGulJMcn$PLKC)MqroAC{}u3(tYuFxM1 zsJB+^^^XB{(ZTxN0d>v6MSmMmj~%?%TLWtQ!TOB>b@m~Jrwpjm4{Zber-$k*2Gu!- z?REK}y7{mfpA4uE4r4fHt<;YWs!Lby_3)tj#md4T4XUeSy8{1kO#g65y&T);vLSUw z$zIrt7Z0iXN@hGUsBT&n0nHt&B3BKmSH%9-s@j`})C;A=|G6~p=R@k2GKT-( zWsy&Z)Dva9emtZ;D%<_HL+XL@{eb_ZJaXM;b$Z3F*KAhTRqX!N&Fa&NgMhzlwZ3t) zdSNyF{(ZIn`DS&_8rrW~qkp?u-6!^E*XS2Fs}IC}PNn|KW_4F3X&$Q77YwUsEA^+F z)meuVbLHXsJHzUM!}Zs;sHYFtR}HI=4qp!Yy=(Q|!|JiMkz0n<`)eb>oKdBJHLSi} z6}fj<-CVUlFwa%#2Zq%`wsv+jF z8hz3h^>R&QE9|w5{f%1v^)2eGI{ok#b#a}(aEtn7-S5KqxU@mvyhYvGa5U@}HR@k) zQCBq<-nvEoblqlPUO0lD-#x4^oxkWv& zAp*>E8}tv4SEn2qIq!IN-I2s>J5t|vy!z9Tk*khZCw+k-T>k}zdDj>8U7#2HyI;`1 zIbMD1C@H3+^z+B7d&U0DQTi{(tM|oz=F$3-Cy!?L*nYIW@k{FT zWAwMbq`q?ut8?2i`j#)L-yJgt_OrjJpZJox_=|;ie@VUeMajz-_0wNc=f!EiHm?8l zCG~*VUyADwzob4D`?<&Jub-f9IF>Z`9jh-qLA@^akB-%sd|6%Cq%XqM*rb1Og1WtF zf7st>(%Vi@A2b!*c!Ij1S-)|Dx~Z8ow>RszPEb!a?+5!yE&AOP)HyAMe>*|F(4s#) zLA~EXn$ueKKTl9swoZfn!B)K;uC2uXy;YwAMj=sn;+NIU31aR`=nFA$C-iCP#%+dg z*==`7-2I2XH>A!C0fV)~<017Bt>6@=?EQHt4GZb3WRYPwzP;fzJ90}%9SqCr2s~1| z?uj9Ne@I;znj=J?ty#uiv+PWcc;9K=>JYt7Rq1Zkj&GFrt~&$nOjGa3>mOn3k~a&mhkC76UyXqBiio~mt6$OjYDE81t5dUx&C>S+OY8gCE!FDc zY^dJ2DO*1SZ1yar-pSU_Yjr8x1LpstWq@ad^;;2jQCPnb;kGUGW8D_k+al^^h7F=U zXF%oq!Bc)2QniKHiTg4(^S&L5fcv`Jww9d?!FNe;a)|t!_!|X1Yd<*8uYNnE&I;+X z!whR{SY02Q4EsGHeJ1vZZ2$-E3(5s0c+6_3=JBw4GNk_*R=*EL{t{LnhbF^*R#<-+ zRIUwC=08oDTSX)bC}Uz)=WaoEp_M4)@@N0A8ZcSQ7e zbJar;{heI(Vr1F{x$2}WeO|6QH>(g$n^nm7W(c$NH?q{)U!iip8;;zZrLG9i zxFkzm6G;lmVf%yRV13dA9A|8upjxiT`XpDKlU;ZT$}L;Neoyup;g53F6FHIJ=c+&F z%!2({Ch+tLli-SEMrZ5<4SF2ss(@av(FeL<*{rV*ac5X!Ic5*+FRO2b^}~pniMu_l zU&>OC)A~wSzmjE=e0oH`nx!sh6S*m(|CFU3r1hDIelttGODk&quURZti~w2soh)@< z7F6&c5!*tCoQ@bC4C(hKtEWOmuTN%Q#VaIX{qbb=Y#2N3{|M`|^VLa_qBHYND|{-V zzm=~(j==4LEPY|Vx;(4sKl9bASuqX?&xLNzSLf#F8}ilna`wI`Uwt*V3=!lWd1t=5 zZi2owUp+g4p-#~EAk@5~yYtmg^3bu~$kX@dtAFGb{W4$OI1v;NPt*_QtM?`@fKKR1 z`k{RF$fPeSb@OEXM83Lva?zvt>TCH(DKe3v=b5d4^x4Bf@E}WmVI-YX_DAo> zW;%j^gP{Ip593J%%Ovjz{cUe3&1=;E8B%xHNGGu!&f4h^Obe2TrhNbp()Z<|w0@DN z;=c)P%TsrU^?y!4cisKdTy=J2BQPgs>DMNx^Rfz`#^FTvqMzldv-Q&3(SfzTHBY^w zXWyKMil6c43F@TW{o(p3j}#ut)372zpUu;OdHS>SzgNaQA05HRM0e>|>a68J@x2II>EY6lFaGR|*?-3TlFaRx=wHk^3xoV;oiP27`BjfuhQ>S2B)dRE z-75WhNPXcFQ(~4=8&=EfgkqHM4z=R2&q{b43+>>%R4}y5H^ZDNA?AVgf|OvaV%47_ z>dKJ*MMT{kD*SOoJr~lzY!B&|BA_aKKB8_36Z2G9zZwA_RQL+;;Cp}}vx9mpuGr<< zSzLCWpT&@#h44pc2JB~tk3TnyVj`bL)CZP!@7=-9ublSGM65NRny6lxka&EedN@yi zccOY|q6u!=zF13rkW~pyQ7BLm%q(19U>HX>dVeft5qN>vW@iTuIW`+?4K}E5= zA|w{&s8nRMb5%4@s!7qvWtaa58_EP@qU&b^WsQo8%q=W;1EXCkD>N%B+pw&tsIYa* zT574K)^@F^tOTd#_kPa3cewM9+}hXvKA+cjk3MtGectDJ&U60%xqk?l)ToRsR+Lqu z;R3cmDziu%{KZS~1O=Xa;H4ND1OLpGI9zx@;Yo2aqUV(@fBo@O zCH5E^!vT7#N_A>79qnH1{&huiiu-Ax*HovgT3N9|y?;rhwgmwqylZ8#mFyET)(orvlrE_Oz( zlh`9#3S+_gZan*w*6>a|`>WRQRy_MmYd9Q_&qx|xnZ)kUQTQ1=4mOGQ!@ZN}p|IcR z*(3S{@ZZxLu8(IY^=BY_f0&^up1l$_5#eKDhFjvo|W9Vv{5S~}usn|d#FnhZ~v zupv4R{4Y(02jbZ+X0qf(v*97cG*3nN#)#E4K9FGih#@Nksw`FM7Rbtb}ZSr2Fr#k0F(bbpIs@5IbO z_|8~ecRbq@t9v_^y&jtY&Udl8R}g2M;k9`7)VN19V7)hi%JtI;l;)_E-JjBboF+!o93jxsdIvWKD!AH}ejqn03ix7E-P%bv0l z{}rp@x>z=BjYYU6+HgZGdn|ei!tX^JZbG4qMQJ`BOG#0`oP#U7&cRkng*}n_apG(o z@8?-@yuVdLC;iGHf6V0ouQL1-XHtfG4LhirEA?%vs6@j;cA2E(1&wt2^pb(u3@;d1 zdR)5!#|}6pz{b^po1Zw^gUZ8rmXQv!KER`x2F#y^w}8KtWK;fE%caZXk%>)`QqHkPKko~cXcM`@G>;ooptv0Wktf^}`d4HHiE~3I_0X z28-6{Pt#4q+^?bU5#`|veNOcu>J zLwwj7z%-PqjFN7Jpc5!}I;PpkEE@jzWpUK*b^OyOkpkzZc)9ciaX6Wu5rUr=g6|5! zZwkS055eCbf`2#!|5ymVUam8O)%ltnf}a|KpB;i<5`w=m1RuwdUZ=KaR|x*W5PY01 zdYxL&@eq6qTDaoW@e@Pv(?jrcLhvg?@Ebz#cZA^Y4Z-gT!N-W{byB`;yglPyj@PO4 z8;cI7I5j^d1V1ALKQ9E|6@uRsg5Ms3zdwL4iglU(X{+B2s*bKREY+42Ielo7nCvbNq_5FfPdm5 zqlgzmnEtmt_(PIZXpsW_9c*%=$u-V5iZs0QWL0wMTvGaxFbYRHNN29Z=D#Y8rx!3z z@(7Y#b8xu=(zy$Cbb*|Qr(gBSLpNa;8^zV|{p|#6)coaU8pQ>^aw4Ti`8y2kcZ9wB znvx#JD1YMXRuLy80qIfxTvN5RjzB(y?&A7=Vjp7)&K7`M|2?8qfX@e0IN*}`=;0kd6d6!E5o8sl;uz_NjTL0G=m7T%U<9uO}x ziZt))8@w2o>j$<27*>7(I^Do}fZ-OH4>lRsaTp_y1KS)1Y@1gn?ww12t33^Y+T}*^ z_c-6wXY?xNT}OGMddvkbY%_}QAgp&EQE-tk7ce)lCkdPQwhz_}Y~OE;qCgl(U01t+ z9RcX@e+|}>+q{q+{((W>fTM4_~i}7*=Yt(+x z135lkw0q@vdfz8UI5}0j;d0cUdhRxgv%T0zJEi`lIDfei;|13lV!YrO^(V#o>!kjq zI29&R{S6S%D(C&+UzGCXItdzWtYq!7+(6&ICP#|lRJQQ+sLT|n!nmCw%B+C5K}86@ zN{;KKGRx>NijNbfe^0TKw}}vCCQ_dXUS^hXoQW|?^C-xApPc`AuU?)epwY{XBEb3E z8Y2H(C*|Mzpq$=?e^v5vO6d(^>^6JT8)=7>UgB=_d%}L&uju4uL+SMZ3pM7S0G3K} zS9|5SEmYnqdyL{kgw=a7Z+Z7ASeq|K<=ym%QS2hQ1HRY*c-ec6BJF;pG8@SQ_C?Iq zYZb>5_P$q++d^ahamewp;zKVcQvK1GzwuF{cpR$qUwJVuM`OP838OgQFFk4(H0JLG zRzz~+K2XZ64s1qx$&J=$T41Z6UK`0#T?_!jjf#LgQe8|wgl~Wc!DzfldpclFFCd*% z7aK!h)b_i8t$?p8Q*H~@Mcf~a;-VlJ)kOg?9LuZac)3wsYy)<75RB^L;4?ovI2BcU8Hpx#e=>Wx0C9^4GfQY1jy0+n|2_0dNlv0qdbGPZJK|bkZbeR$pb=HJ+7gUiwUTNy$w71+KIat>e(A>?QumiJunbyp*>9Uv##{7M15iUd&1D(*~^hWqFLl!5>DwJ1itMx zqxe^Z{rd&(3NqL0==;7HZ~JawM}W~+u~l*$ivuIE*C^s)$N(&0AI62H{egprA>@^# zP$bW1B`^^>?;>o4Hy^!9KAM2{0Y6FjDlg8FfbsE^l2fg(`+=PZ zA?F6BI3DCb(z-kj>rTb_W2wQh0;&rKtykYLiVlP&ITW(I)~^gM$IGS>SPtYq@|EjU z%BCZPEU$lH>3<5IW^T^v< zBQV-0@c9|Ix(;z4faO9i*f`t;tQnXsh|WG>UBG@%*sr|pset;_31H=K8pRFNr*O^m zCj3~!Ne|(1;b>ED8O7;d9Je2Ui-dvFfLq=+icwyi*Rx1i4zS7Z7{v@fjPyEz75v30 zuEY4FZzh|J;9A0|qDdAv7ub<^u?F{*U zorK#5l(ET+7XXj_1bq{2O@Fg5PWi0^p8SPTTtn%Hf2hb85ViwY%a=xRF=3Oun1k%v z2TXBNe(5Tb+fA}Zg5!KFISy>#xKV5*y=VLCwW1kRer*(Kf2q&%V&1XXM$JTas&ogT zikF82c>4*X*h=M5>6PdERRklZd{IVO{MCc=_UBV%hfusUFEgqi=Y zq{-z5fF+tu;?D?6^kM?(#GlMz4ZwnpEt7%m2gc8rQoV9qXBx1)2wCT)UX1I^Avs{d z)|*aXgTMr`h0<5eRZYOG7L)h}v48H>$kBNQ2wv981IldULqZ7c4m2h%W zUMVE4 z*^r&R!1hpmz55Tv7VmtP2fSgnNn8yx~H;|mm7o+^Sf$?+K1}}C>^$=>GN_NsdmDZl>c0qX{JM$Kp#Hb(` z*-7KnVqc8+f3madF#gtpFUISg>cN;{5*PYn-g4Z)iYbOQ4_&EwNzkbHs1f8P{ zqFhM7e_7KxN_SRpo&CTDfCbY@=P04ZZ#qYz@!PMCcpm5+g~o3@C#1H8=bg?`X#BSO zVqA{SQE2>5^2@_yT+=y-vRft zd4$Mjn8@b_zLb%LRkB_@5#xn%3FbM<(;lyFUOa<_!w~XhTS;)-N%^h|j(K&r0vEDP z;-{4M3*NL_$#&A+1>E@O;O(LhnDtVVcrD2yu3Toko=yO3zASkC#v*bbuo=E`ybf)^ zjF+24zjnyWneJx`z)nY9=*^!gb=XO^QeNpCMR7dHe@5zo&QZi=STE5$d!AQ*kgAsC zcL5i!L_LuHE4_Ff;bcz_@U)-GV_dZt7qDdF&i|R6WRzPh5MKYPXLiz+@^_HvcC_+Yk?=0nZ(_M?+e6RfFA+=N5WtB#i{J~0dHAl5;F-0smtOpuzp~cAlM+V zW59$U81)lNxm=%p-ZXiACR3XNRz!0BUW}JT2C!aWc-28|i?=LFfV+NyvkAfny|Nsq zypS8M{iRjPHl=*-)5fU%DNZT_x{~%%)cz_VM|I&}KGgmiftC1Tyw0ipDNd!6W7Pf> z=Z{hQ)2}v(_2k>Uc5=A^@EU>rny_py#_IYzR2geJn;LfOd^$uq(_QR*AAz83~A+hn!tC0f0j4CG#0hv zT5(dklK%Y)6TX^%I0XM#0AHGmX)aToq{qSSr#`AUi7(kR+Fa%vU(z2qmx-q|HhR|+ zq||S0Jc>Cg*!d{6%^fJisU$KY$J+{(J9{(u+K$?0Tp;Fcn>Jv4t>UMX+GZEh!}Ih3 z=?#E)0vI2|^So`7_jwu{Q?8b=0$+^Ey%rcBZ%cf!G+@oZrUgka2iRUraIc^K>lN{Bip!cw8pVS2GU=TT4LpW;WHJo31{lLmmX0bu!+`tqk)e&9E_uDX@ zDNconG?vjmNpb!d?UNMekI_C!asC+XlUk97U}IT3u>HVtAS}sA6Fb?`6qJ`C^&C z27&SUkCaR0;-EUB=YE@OP0}+oEnYdEhdN+s>*amJh#bf0aM5a4wq;Uh}W8xAMj6sM<+3 z4}8T*vUDYt0j=5gUK@OkxeZuP2#jxrWu^W)dmuLeIRUuJR)K6e3QTb-Io{s%l=r&e zW2^uy8Q7-~_SZ>!o4ERb`seB80uzCq8AOhrLsgv07B1I33btz$EY$f8<$WNy9M3xz zOkSt5MI^b&z$%f4s}WY^onvXi<#--)M!}rG`k|BWm)m{&D7}vmyU8R@@l8*lvAP3T zDzLAS7k#H!u9vb#wsr$g`E|ft$n!V=OmV6_O8Msu2~}Y{9|7+p@CefLE@{E#3dok6 z;FyCj=O`GR-zbg;`Hz$yJqM#We~kFS#xE*2fk4@*ZP(4T8_>RK?Fg6zdj~xD|#-@q782J0MM24`0j4>2f<|KCPkKuQQ2v z!k6@P@wIik9G5(G{QbBV8<8&YxjoVv{V+w8ofKbM6QBATT3Y+kTCN}Z{nzxTzJ`|4 z4O~M@Yw?X#aXxlRd>S(qr_L{(2PsbCi+rt5YdOWK`F$by#{>9vyqp8igQW6I^dEEZ zh(;_D3_K5#@|zPPe!yBzuJ?w3_)@-h1n{Nu*&DzYSg3Yvx!xpR3|sx%v2S@v^_`(G zDL#c|`vTjsWZ!7*SW%_c)2=Y#OMD0Kx5N(C=c!K%pdBIhHtE0=$Ms1{d4JCZkb}H$ zf-hmbgNUS)>^=hQHeZa7X|!gkyg}aEAgmM|$?c+R*{RawI%&;P2RVFrPMw}Was!}k ziq6-(<2}b1+Twm-<{)y^{}iW6Pm%*LTA7Wu{|u#*%1m+o=>^m^k07moe!$s?x6Fd1 z$1y51#rfMpWu`cPY_u|?{iovm<#K{!ye-lGQ*r)swEt9`KSujc#i=l!zko4|N09a* z{lVq9E$-kLZxeK%PI0RAxLk<+Czm6cAKI3VqHiWZUP@1z6sgVso7)mEvk?7{=YjfP z?2RU|G>9z&;B5rP?}NM%*#7`?=U-<3=C;J`4AGW&9sPi|#C3*fOB@T)mN@nU+7g!= ztu2K(%iwYW^B*s8X$+yW48^JHfy=dzf_068g*eOLIw`Hu+WZl;7k>8jrf+-szBwmU z9)7_1$?g2UHXkZIYV(Ryl_zg!A;wRR{g^f{HkrirNL1hFEi)-S@J1`^PZb9A9{PA;uSug&1E>@&1jp=1x&65n?s%Z(%9SzxnSqa z)K0sA{e*O$@XiwgjU^%W5j?$Jz}d~FpwAbEI4|Zp2S`qKQXc3+T656YY=h3{z)|Io z_w9go$|Fc}KVTohZK1LGIMQ3`n;yr$Z|&!}CHQj+)J{u)`JFTJJkZ$O0POW3>A8XB zG@Hae!t`G#_s@A*)7Wgg)g<2JE5~()7@K(>5Pj5nz8^Z{N#_J_dR&g$lH&a9h}u&7 z*5LQ|s4evaI}g69{;m=}$u03>JUwdjwYSU92Oz9Ii{Nek4wKkU>@u$$ucHC*+JVKR z9`&_etbqC$wfU~?@|oe|UX0&Eq&A;(r%8;&Gwk|9gyG>kyw_C+J(Cei2VT-)66yUG z{WZsxHbXGuy5Wzu&KvDa;`ko<8InJJrRe2lK;@eINbvh0RIata65t1`{~f?$gJ4vy ziASsf@BL67s9Y_31LY|HRIZAX(xa=$Rul`*f54o_BS~Y1K#^ppk`*XTDpzltqd2N@ zHaEB&FB=!IQ0=i9Sg7$i;2c$DXLs=Q9AxJJumY4Dnnyr+Gcj36>EZ`X*UJnOHk?S7?a|7E8oeP8H zA&!njWan>ZDlj+Xs88zI*GjwReb_>& zD)%DYO$h4s-zas-andc&P%1l>Zr(rAgJaz0+~8QNVskCf~|)u-pUkJDe51 z82!VpA>EwB5!P^DL zXkDc^e>qxLDNcp)^k`kBIDhQ>)>X}DFXJFA$&IuZT32-dkq!mEu%(wo-9X-IEK}FL=8EHrjrpANE)f$3Gvr;2jHr(YSg7*y%y!_C$^hT#@tsq0 zfE@)U_{#D2>jY-(4*rbW_nlop9d}Y5C@9_MJP4hNQ(?S~_5%};UJ)YbO(&G~6vy1a z8h|Ae7VV4C*+mzy48&2DnMiG7w6lx<;JGr-Ls#&&&9S~wuoI(T^bD^4<$ylO>yMtn z6@cxfG6Tu&jAQf+uHsbnz{`xD!Bw0ZqrOAV|G6pliu`OQ$wf+M#3?+c^uu*TiXop@ zQKCxmq%+h&`MQwrKGY(8J_hmhN94DG&-<8)pP?iMU!5M!i;7e8ssDFC5ATmEJx$;r z0e?1pY1ni8%lWhPsNboO>?ZqC!2g|BZ!3T9Xf*p;us>EVi0~d z_=;24M+x}pLE>)=5q}%_If!54wa3Bjp?P~B_#@A`N##T1(*XGX_1y>laq#`y*>Ug{ zr?!*Ueb(3H_c~PVhVB8Rg707c>Hd9OzV|@$b|&Kc+fxaCF8KcTG=$KzBYnk^95c=Pt-$=w(fN2x z{k{v>Mez0GeCL~H@D#`MBv(Ayxk~}y`(rfkE6yJqPyqNUjQ1m&_Z6qYxSiCm6sN-Y z7*F%Q;`}k1_Z8=l(Y&uX6~@yGG4J#ALflX0a@4OB=dY9IefB!$Z+QBok+QbP0{-(& zh7woRuTl`V5qxz&2`T?4`Cb&4ADOQjz`vAi;CyMEAe#<@Ki7{RI37#!sof}!=QC2` zHz`c`5`SboRv#$&}ve36e^f#b2nKc+;6uZ~aSvEtPHfOaGIR~nBMr?zLb@z_6p zsB-s@M`K`$!i29*FCz#)l-{0??Lq(d!1wG)ZyNY~9VyrUsf;g*Iow9sP?Adr zg<6x-_@X#}IpU8to`sU5^h)NM`M97zccib7EtK9yU{tPBdQyQ9M(wg0*lNNq@XB#L zd_23*?EelT@3*@UUvd6<_`dNh5jx3UeU>*pK2FehRsam2a|*DN+NBfNErea^E62yP zbaT-6x@kO%1IF($SNO_>7|*yZG@j)`ZZV~|-dB#svvvi$-WLloo^hQto((E;+kNHS zz!aw{PcBFMnDmRx()YXW^OXy+kKuA5_AwlzbwUGl^6~2lU!5VIzvFW4kn4ind@7fh zeC22#qyLGFz3q$9J||=O(v`%PJxg0CDe!@zOk8z~?=hCoG~_oP+)&@tgVk zHW}cH^W^?Z^(N1^c?dRvPyI%Y&)2&&4t6ati*EQ5Us?w@;d+0DSv>0HJ9)b8xNcu) z7SHsiE5+Xre(xf)c(Y&p!?-TE*eqsy)0Olb1AkYRS@oWelrNf}GZ&jhyH}6E)1`5+ z5&Ubtd@249nV*eI0`+W!e#P;si|`_#ksEE^Yra(OTNAx~nYSUDNA>~Z^EN4wlyZ#by?$W9*7#K4mdnhd zKq4Tu9L;+vz!HLByTEgVz-Zoc0kZ{>8vw5z*wi2x&3oM;bW(jE19pb59Pf8D@5MUI zLC^MR-cy{EKf036!b8k^yv#zZ<2m-D*7342YFjKaFJK*yJn?g7RXa;T+`J(C4Demx ztJn9j6c^M0K7Z~^rHAUhJqW)E{Da`D_syZ~6E2tAhDtBlmjV7rznA&}*>@QHN8w9N z)5iM;+1IqxEUxq8lYNTgWrcnd%8uk^X0Z;Dk(BVyn3W^pz*X5{L8$2$sQU{S*}_7J_5H#Do+|u`@rAgjW6i zEIpUP?P=!oC7nMhPAX?f53MV+^8@44`BQd*SzP9|Q;JXLPp#nd_n%ez>HMjsSUpdi z+WUb%HpvZu+DJOcU1ApJQTh}~u57|+o)AmT((_7w7`0_v z6=tyjar7x(IU!VEY)4$`d+Ihw*S+BLxs8{N!0RLf{1f2wb}jQm=}jxOsJ=}n$&=m! z@MnALdPMKGGK+YPpB>c4EvqczCKdlw^565BRZJxrkeo3-Z&6+BFNzZX5(J~VXeo}8 z&RY;x%TZk%1(rta5sZ%kH2w(q3v?u}!uY(k3%pujlY?M1XKw>m?u&6d2f(AhYWE`A zqCVYQhf>|6><+GqlKvh}t{3CBKy1`@;V6#^`ivWuiwhW^gPdNSJP(wwW?+23=JLg; zTn2&hGxu6w4A7{ylvISar4bm}q~GYPlWb9(RMvDQ-Nz$a4nmIS;U=#fFHfqYL0}Q^ z^|yMlkbT09xV*0AHXa8#a-;P*$`3!gr--UPPWkZW#}A|Yr2ZmG`Wty87`K`7(*ca% zBlW}hoOe}}bg$<(Bl#gasm-MUON6iA?u${IQ=F>&cs)~_Q=C6W>%=_hT#5+#-}>sL zd8`u{FWU!4uu$Uy8?>tKQAu+KjSH2PQKCTlzh_)Iig-n;c&Ab~*{h?Zz24sMx32?n z{qMn$efl*~(t6%+JhXxDP}vhI-*t#bn3BI!vAYQ}yj&rt2$CGNsTN>=BzE`r=c%PC z%IABOp=?ZZMg^Sf#0i`fMs7qDQ@9fjy8TqpIDIz{IgU!BxXc7)JL{bV07UZ2rkxsYR!Po73ui~s)> zug?`FK8rj_?OAFQDKMu0nkd!1&5^c}u`WvW9lrwJhVl^KQX8e(b4l+5QJtoO|0#TW zo>Ts_E{y7%#=gY$QPOu4B&-!?aE$77AFw;&%W__f>NICVl(>U1dQQ?3&aqeosZIxh zSqW41Z#S;lFQcS2w3L?9LOeMCz2iQ_TM51-KQgYxZd8vC5wLpKq!7`@!E8M9*RH_k%wkzB>QMaD5nj z{*4Ni9@^8CZ}A<^s2r({yTGTgS`Y1M8o__r%eNsUIr>|@^tW#5Z+_C>;iSJuNq<|C z{=Opp%|rS-ee|~s=j z&wkj_16yig%Q4sz3p;4NL~9Y6Gif}aQW*3M;ka$s&idgKF$d^`orx#yIVo;?0jU2Y zx7|AI0iU5P5Kcp#Ikh6S{lx}mKM#}$8VfRlz71pcFF+rI{)p?h;lBcU0(2kfHz52? zSo_tWpMvoGw|zRu1o~Rf?8iVKfL;SV0eS$m6VwK}8FVeE9Fz;155jZw_DLW!=o=lg z{|)p8=yA~9pqoI~faZW^fN;KU*ML6PGJ7xRHWRa7584E(1+4<*fwDo1Ko^0|1x*J{ z0!4sM8kzl{pm#vMpyxnOfF1(f4Z0Py8B_(z16=~LgJyyfKodY#P&mjF4!c1gf_gy* zL63kQ0^I}J4!Qw!4X6T?2f7554muMw5o85S9!%hI^TP3@94Zda1@f9{x=>jeQdQ1W+<)E%;}{p94w*%>!)#Edk|# zmV&MX6@W@WSAnhpT?_g(=qAwZplzUqpjOcDL63lTgP3ieoq5QrQ*_SxcFn&M-%l;& zn_Tuxx838hKSE(D53dvXku(l*VHcbdhvP`wpXbF35Wggd3>O^vQQ|8;x|!DwDb_rn zYqEC$w&oSN?W_R(6aGN4+rBa1Z7+e}T?js$qA!$B>2UpZ{7)2MFp$a}MfrdBzmiv} z3b_(!03AE@&qr2TiTxED60Biu)} z!49&;4IPJd-8ZCr)XsxA&xMWx_%8Sv6Lezb zrO_gb(}@j8r;E}BwSW>qX`mdC6Esw|BB zp9)F`4d6N!{$x-hCmYC(~9u8Pi=;-aiqe18e8aEFZ=Ym5najrGC*%QVt1q}DR>E6pz`qW`-8mcV~Y;J+pC-xBzLxCBP#329!)!8%Pp zNhexClR-96I@UtlKEgWX7MvekkF^>ovB)A0+>G@UNCcJKgf$d+6px6Cw66)z9Nq{S zP$t67bls>G9iS4KTH$wsxD4?OF`)mYQ#skYWSL()=0D$wZ87gwavy1v9?)E6FV=5>0;+CXS&4s z3lW-sG5svX&w_bObNYfz3SCM+SJKZ){A5tPj3xM4NTG$x@Utk3en{-%`Se4fOp21p zLr@=^MUk^eRTfEPQM!vAEG%{jqD;sp1=+;QrpVbV@N)@WUXlwVFU>~i(#z-v7M$*& zO9!D#iMNb2FCzuZD9M~F5M}u?h`BvRZEV=&@C0K@MCzEd$n>cB*37YvSdCVv4>N=t zP38#87%b{i{zPRW?Nee`MDjIg-||Slo@|Ft>&bHXw4O|XPwUBJIgxxlxevY(*LCn| z9Z8PXk!)EcKM!_*PwPl>w2l;TP3y=(;IxkHf=}znX85#@%!NlfH&kClm0Q6B(Y}M? zQazKu>o(LWi25AW3lTZnwn#Ar`R5nGegx@^C=nQfUPt=L3H(?3AsfjF{8#!>(i!0i zEXJ)=VBGJ&p-9ButcA+Emp%Zl!q8z8A z@7&(P9M9n#nKn*vw}YA?P(DvOXW`8M{`|KD{#ye7XG(xijWgK@M%0c@uoDl9)(Zi&JpSy4o>X6NO7LS8Gk$ zNDAj+%+p3{%@oeX6DeA=Hj2VaG0$tGv=J0u!WcqQI1Q(VN*K10{s^*D*3ZMTejb+f z^RTR+hq?X;DUY&#o?ov2-;?(#+VR?PWIt@1pdF`;mBZt;vDz3pJWd;<9ZTT?tV6V8 zwb2w_&KS5-7`7p#wGtosMMx@#3o*7UT1uGG;$b;09+uPMVL2@x z=4r|0Bd5j72WkD7_7JL_NOg*K#X`FEET7Rgs6Uwc0G9P+&2dEE0m&u=`99w&}aOKePg z#Dn66qiw`@nsofqUZ@=RE-4qZpRn%6J<5kQ_JNgJ@dRjZ8qSwNi5I}n#~Eps+it^I zZyqQUyyODxJHUfDYmt4*+YfQ=6I%EouFVkEx*A2O~(2{#1Y3} z^f6t;T5tIY>%vV(p)H&pgRMtD*NV0;kPpyCP%Eetq&FCiv=(Y&IbN+ap==e@6 z)`E_J(s14Gfh@?f1!wH1>BOFIagA^t!i^f8m^_U5@VgL~?B0uT52zP1IfXS>)dwV8BqY@&2$dCUN`<5nbJ^N8 z#dA?0=F(bcb!klv^z%!$aG4{2;nJm-EzMt$xp2V}<}9tQs479FCC?Jl(}g*<8F=o1 z`Bh(eVI@@|uda$U#g%JIN@o(UW_^vTVs&YCer4r3xA!)|6MRtt=6WN`;b&Ybt6g ziYiNmqV>W&$NZ5B6_P8~2$y7Tn2`|7Os`3>57o4`rnH2i?n{{|$%*n1YAQCMtdeR` za?TlMRa{g4 zDj78IRcp|UP%cSp@#`wBImgU`%kYF0!J<;vy3*1$BvrVkL@g{2)x0{Wu+WTq~u$mVU(r|}5R^7SN2b5jo0$ zGIETRzS81o)K(g~R!saR7}S}k>XnX}-s zrAwDPa)iu<^Bi+AcvreAsC_!mDW02DB9xT6Fndr#;WN1{t=7yiw?GAk)ZlZ9S_LCb z!qkg|OP6P7vk?SKBVIiD6|0?y&K65(9<_ ztI*)2pqT|qy09`i6-l0gVzK}Fkg_vVl?`WAMNJirFPJ}7>{X?%e4anlLj4FZl^JnuRq~ ztKP*ImRqa_CKQ)r;OArSR9b7Xii?s;QjpZu(i$BzZhYLN$)^hmiDx9AdG<`&zhK8g zT@CB*QmI-i)|8aiV%1q9U~{q-Gd?ZeRX+ff|7Nzjur_}cW><=jbzK$hUYzTyFG{K` zNf9tx$$`RJDNtSJs=bKxrJz;wrY4jW=kq2>0cw-jS=3fsJy%dgtuCvsy~szXs7Tg= z#>!KkWt(f8GuL)LTU1&k%(4lyZL?>ylJm|lF0-A-5Gkof`p+<_^B)wqYEaVM>@oJWNMMV-%icwOrN_{DBCDbKdSY5a}zYHUf zQjo>f*u9}`)s$9ZxR+a7A?8!r0B(!K<_6_s?98t!D?_7}xMjt~`K4=Uu#~Q;im9dM zS5~Y>E@sUt$}cWjH`_K>C@xnTRY06orx0gNV4N~+{C$%{QmH;kjS^+nEUAgtIM-Iw zEPi!ZIiH8Y>4LNQ#Wt7-D$Af1>hV<4sv3mnE# z)+O}YFtTH8*BBxM){1f1hVi%#Lp@zNF1Dx5PEnnZtf>+(7~(LfrqrygbFjaqg9)5G z*lK6lxC>bK)Ho}s$<~z5TwS`FF3PKFFjiDoV7S4`Kv8iPc0OxXVacA3GX<|QwFowr ztEl~>Bb72WP+DDBqpb4+`Pf&vs;XrHyfgHrR%G8u=cP`Zd*)5ni90}aUb+p`hj96j zmY%`ndop=zGnxqq@ zIa$N{5_F>F3|#xU#wj{6Tfmtu#ns?G6ymuMFAeg2)1>$XxVAudEUt6l_aLopNT;Z3 zeIcpai1z}>hCx3YHqOwA`ZJ+}uM0xkkeMvm5Qq3`8>~paIbyqXq>*8NRJ9sw>}x8DONDji7}arXfz_mdEp>G% z*8A8f3(MD3;Dk`f%0QE>TI-Te9MMu}Wj^&HzOwdTr;ap8ZwTX1g)<*B2M*GT(I{2T zKBpXJQ}Y$pSFIJWhY?DuN^5A#Cm#k1g==9^&Dt7g=^AwFsx>$)pPqZkg6Xt-_Z}Fc z$0+^4Cov@IXEn+=1MhZFJvvbL|Eacg9Bqr&qf`h?T_RNPM`WAYr~i9xItYD7K$M1m z+d2+D*;IkEPr~!?o(q+W`#)rZ73svGPmm3(Yb%P$hJM)w0sQ||-H5Ucu5y%7iBty7 z=+El(e{3HgZJg;qT2uz>D%KR?j7n-7>x!_yzsV)<0(nA70s7uuc>{(g|s);vAcJNLM!}+LmAwK8mpl>>Jl>K>uEwiQg?Y zp3+NM?2yU&c9!sQmTf3YtjlsVXBD(&IXh3$oR=VM5_^GMjDwFe5KSuton1wI?82Ljn&&U3l@;el>aO{tfKh<&y5l@`Qw5?ip zG$yQ8&t`{+Gz$B?db}TZ7v4n#u{y@;;MdYM-dAkd;kGvy%(rg?HRHMg)D0fJBbhtL zZQrn;*?$H4Er<>-tnXOG{_mLBhj;=W0yv7euXf{I)|Z%F2O1Azf{3_4krWoU^vJ4H zdNRQC46b!K-+Juu{|fpB^qF)(;z1O8K3XHXp|c&n^4x&P>Iy4w(unDG8c}G_h%Wdk z@QuHPoz}WSI!A#+nntniCkP3KE{~m_oMoM!DR@2s>Cpkm6|XV-Ij`egUier>v&P@y z8Cd8T*rXBple~y?2-s8#?XByzM?YQ2FI0xaUc53+g?l8hz!RvwEX3$YwpM~qIK((NBypx`?(jumV@-i%9 z;}Is#I>hWQP#!3Xo_)l#6oZ*=dpgQQ58D3=vl#gjkpi`Ggeqc zD~RR-dS~2;`3cKR2a)4$l;5)`2gIl9=>IFT{{wVMKi*mX0Pk{p7=IpoFWx=u0Hs&s z`(ZVBzCFt#_I}R9xV3m+FxMjP_?ZRe=lM2?p_8D---i6T?=$-upo7p&@9jAd&IRSn z6YaU6JWv6s1XK>nWZCwM**s(4i6i` z#%s)!E|s}h0NvMogm=5)*MKHP;=OA4w}4(8!|d&d+mG@{M4koEzI==5Zoqr~8<3wF zs2h4f0>^F^w1LB2C>PcNMp%~z=|rBdvEP`cv1h?|Bkkqz*AoxKSU9@}SnUGT7y3&z z!gwy`nY)0^M)+;`^WevT1W+!>B3PLdZBm?%uCe!^KK3Eh$>q9&a>xq^$AKn;4ll8Y z|5%{04};zX9mTbi;#jloi6A$!?)Id?&Tugm*SR1Rg`EvP-9_E69M_e^8~pa}{mU$3 z8p6(n8hbsc33Ml@1N0c^1<;$IRHT~*!VU*|PuTUm{A&?**gJ35h;@rJ_H!=Q*guEA z5w=(a(H;j9Nd|laD8nY&dqJLS*`N;X!AjvN%+2~eo;ZXZAUg8nkJ{m-f01zepoevZ zv227hL3WgUD*V;~CT4&de~LbsXAuvBlF{A{{?sB~h_vd?b=xzMb_TbZp*eUwW8n%* zP~N2)`*!%dKpR2tg2sa9K-^qV9>{^5wLnKBh=$fm@DkCE>r&nJ;|*GID#D)g4Xh4% z&E2BKw>b684SyPZ*4UNYc_Hd!t;W9O8jU>@z5_H3;q@sR`+4v;;JN^Q6Y9IJT_bkv zaocy`oe*1zMJ#v{<+<2xZvr)vkG_I#y|FhGihkQ22bb^_)bBJmV3w-S)dd8$gV3985PN{3z&Y zP#mQPY9Je4#K4E@_k8K`bm4kQ_LA%Z_;`{L1^l_kGX*OD2CRX2Ms^VXb9lkP<3aiu zB3-1z<2l)!uVIg1?tv5jf;1W~aof)T*+8`@&m8#i#&{z-bQC>LwfVjH1vDeH4~q5{ z%ttxB^X!Lv=3#yk?Sl@C6L=TC14Pz)?nVlq(WvdY7Qc14y0#LS1!-GBv7nevX5X-n z*`J61I%wu@X8$>8DtNzpl-ZjR-VOQ?blM(fUk6{1pIpnJ#UkyJU{%!D^z<&mQHvE6Ue+qolWBA?%t|!7z13duVBKX_j z*MpXW&H%j#o(cXp@ZSaPKsi4K>PMREmSe6$_$a6yZP$hNZo%_8hacLvF<~7 zH|R8kPrzSQg|YD$8v7;i6X4rHzXClCx&XW*@TXs;v6q5AhK{xH--5rDu0fRUd6gRb z>mUK~?*Z1;p%HI|{}ygn7a{f1NGthLtHo9itoXojJgm{!&qH6{QaPIP&3Vg4`VF>8pQQs zPzUHJs0Gvr+5u_>{Ruj?7!$sMf4Wm+Uk=&`{%uHSGj2|;!Syu=yCRSX__erRi||hP z#o(WdvTOo>8>pFZ&}HDqU9GXF!+!?L9ue!6pW*r-t}Ef60e=AeaQH*;Yr*@p2ICc8 zD(ZoMFZ}DkdmQ065xy1Sx8UCm|7*w`0`(%?2LE*v7C6ItBjq@QcAahPm}&gcl;*im~D@gxlbM2LDC)=YT)?FIsU9{7$5ki*zit zT04=Ah4G^?L4$A1AipJ`f-;SL3#j=)jfk61>=42%J6e1b{x8r@wV-Z%FJW@8McfD< zOTjLW=MT&?#lWn<=7W+!E{u5@pltHnbOjNZqQ-O z!6WB&p1ac#dL6{a+^X-uW3;+OO)$7TW}9ypq2Hq$^enj4ZI1vw3gX}PV9<%Ns%`EO z2Nc`_!T^gTFbHFu#+b@Sy1&C8f_~_p4D~ve+B(nv7IgH1-UpR5M~RgtoR^~9nl8iK zh2+t+HC+gQ3$ml%Ukd+hhudCtIoj>l9#0$mF!0}j9}hns{@L&s!aolBD#vY~y3}p| z82)eI{~07KbKC!l>s7GUx!p8c-+bY|uTRQqXOn%b{~K=rCv}=wZ-)kYyas zUdKN9K<$bLpZo;Z(^t6dg`ihJn?d_PcY^5K6P;KG8y`X+1JU!we*ym+&}8IWbA{U; z143CJz?OoYj&~u);X3_iu;Zt0dlJGXl#%gQOzf^@q8sxkue~VTLPZfz#@X=FpS$f> zfpiGp4ga5@y`VQh$FcTL1Yh{IS!933H{Ec)>zsn~UHAgd0O>uzUc|}98Qirv<9!^I zx|)fH!TS#3-{RXZHc%Gm+_f9b`a;(Qm=Dc~#VzNxS$t2vcxvtK2M@yLP zhRt`x8Mhm@ho3eGd)u1bx8CCJFt?amu_n4dy198^_bu+$?Lu3VdtXF{xwCWN!1ZqS zR5aku`-QIULf8F5XLt0=&-Lz$zIdPZiVIEG8=eR^97D5YmjBtKWuKVY7_qZ+cW0-* z^W)7QTO!_F(o;6(!}!Md1jFYP3pRat$TamD_nwD6!cO7CH%#>|_g3S$zK`2#+1;9LXS*@!CFm;ck#v=In+kJC zSHbUv(w9v2OWmyr(JK=g}lgHi){1fIWnxaoOuPyt&R-A=FrpcdGjp4cFeKbG6c)G6~~Pbh=oAW zv~dNMsVf#mk0`J$;NpdPu}rWmwBd1-3@d`R^XFL_L?{4v+={X(Dg_1St~hU2ht@K_ z{w#ODkzJnP-ksoX4BI$l$=>+%iYL=LwDm~@+*;s8- zn2y?n*jgaoZ*3hGTU!fuzC5MX=3+4-TidqrYiF6QAj4W1>F%$I-cVL<82^#JqioNC zxO*dw_2KTbZmTu+ZjZim9Wv80!Eyr{5klK1@XL)GTDMc_Z=YZZU%z7g`1OYMXH=IZ zR4=lWm#-TizOUAHkI-*mgZD(2Yv<0T1kY_l^x0(-wme3Srl7T;Ky8%6#m0%u)i(QK zk8_UAR%V@b^ko0qXy=@|+0)qgdkob&%l`A9oi=rRdN>li`pC5EX;b!0J8kE*ge6~o zaA0zOIBU7OdRoG!X;bf9S6!S?J?*sS#;#4pQ`awRjOm`XZ`l#ui)r(FdyjW@9e*&a z^gU0@w8mxq>!UX`*2Y}Fta)A6`j}eXiYvlvr>)neXnNqTaB>-K1i<;98d{e98z`~~3AG5!4mO@Bn_$e8$^-rf&ajQNiAG?HN4 z8xQ}rSE|{#ooAjt{+;kQ4#f5MMBnQ%^!=j#*C!i|O(#lEvpj7+u=!-$=9Bf;okZ(P z)Ag={VUVwlv1Hb*qjs`BhF@;nSGSJLk}p$?KBy&Gxg1Q5Uc8`I8=paWUPvWBf7Jv- zx~IMzNsjLxN*YgTmYr-{b<*$w8jyecks7tDZR1zYNV%_UQ;r`pbw#cVVj7Ju%th0ME@eLvQw?UOVPg| zdTwg1tY7MtDC#f1!`NFNdvdE0f8fJnY8!iUYbHB@-_~Y}v2}ah4fjFg9y5EnW6xr? zsasRlc+4`I zC5^*{>}hR^cB{s=YER^$PlpeDHoRx&#ciJsf2gtCvBz@gli>sZ04H< zb~)pW>0f@6Mg=;QjGqfA=QGH_gyivOyfkLxmu#ZQZfk44&DNY?rMd=`RzB}$D4{`s z$LBeeuWJq69;)9rS}D&<3z_XpLF{+EI_BvyLaVE^-*dK^uL&%c$v1WF7k0MZo%F4z z{@P)7W^`SGwY6?-U4l@*akvgMNrFJ>p$!W=h5FiIj7su#{W`j+cMacX3E2Y?$$qz51E zEw0ms4LnZiC!YG*!&oTQCDb9wgaihTT&B047~0gzcAq)CshBk<2vD}OwF}eQliLxq z#Wa0lLVV*NT0Ut@7~a%s1pj`Cf5+HK@r`9iKe1>@&(tZyXEw6N=<9Wsu}G3_IwHb4 zlsnd4;p@Y)W?eaTyOR zO4DYIi=T6WHu>@k!i0>jV`OlhHT) zv;NORD8ZwVHw=Ff#hPw;a?ieq-lv4ef2WVX|HZj)i}!aWJnN3{IsZ=W3-5a3Gq$|# zc~EZ{qV-hUi$ncSM7KUw_x`OzJEv2>d*dwDf4}g7mhFiA!)bF4y*zZ_m7%s*hW506 zTKZ7LA5NoAd|yPztKe=$AD0FASmSc57OqcWPPu_W1jE&Sy*3 z#V2fe&STMCqC+VydfkIQD>#ok{w#+-xzmsTH?INhju?cv>27I3D>P_K2|2P|Mu0J>WhU%3$>%%aeoS!`SiQ#G8 zgL+ia&&&_%?|m#{(bpIou(vqqnRWLNtpb;6H%(*HTUNEi?7ng6z>PzPZX9ZiNuG9Q z<8?EH#yL&N$qQy=CNH>N`{A>m6~+2P+lLO^F|?;mG=xzj>AbIQlbNkJ$`0K+bl_I- zXSX#DefZmmP2C>aS}bNOT3G+}(JwcmT;4XuZ{Kp%lQw732c8FYA3hi%jT40|X}>4w z190teY0*xPJ+m!g=v8g&1J>4`cD?fcjfWOL zn)Cq%-o8ouroO6Q`P75D`YcMRV^-4NmHsb^uB#Ug_30bKAJjG)0XD6)-E$ia)*aeL z^QPqJrp8wdjzrB2B9XAYEWn+HtJ-ewS`nw*iy-`64Q;M7pE8Hp+R@8R%DS5BTIthV^jDSXZ>K zFDHPycGHAW&bTMe%63gPBZ8MLUV5&_S2_OJrR3@7yY6-_uvh2{BDp*{_g!(>RIkPT zkb7!keD)*S0)6UmZEB)kZ7o>u=8W?2HS7BCt*nS7S^spFH7(`p{<_)Jm1h6%rc=j4 zzm}BZ`MPVb8PJP-I!mcP6G$nEFPXUG+qmm&B{{`+tg*_3T8Um*?p~Fo9gWhmKe$ky zvZXd5FgTDUbKD=8SCX>)j^$RLUhVYtEl**6XhP1jwC&Cz|m&|4*4?`kP&&oO9MjMo-9Ia)Fx1eRpQ* zdG@=q<5Gn4Y$oS*yvd=T*Lz8v*CKA{+Q9FkJg;?SrQEQ^l9ZBB+Eroo+1MOnTlU7Y zuCCkD;&b{{Sf?h&u|21nj@#Edi!QSj)t!6OXQ{w_S#RazDCbhSoVd!=rmyTun&-2Z z)+YG8-35K#J7?a40f9MjT|*W;7Po!~4?Pbrey{vj`)t)#94#JPHaWU{o+~miIA_6Q z11t+lasqcuk1HEA`SxSk*AFVW^XhH7Jx{ywmfPV>ZHvdg`MG1+W8ycjK9>F2z_>Pt z*Vp@G-?_HCt>yE4gT3Ct*{Lp1L09>_K=(csH^SwL-gBd-c^%1dGtT?$7gzAki1K;< zd0oE2D+{`O$CjIGD!0`7RFzNOSz;cl+<%{Y-Jbk;)y_bi>(XP6Bn_pWT z49?G9YZ(H8))4Y4~+lZF-lP-nFH4Zr+6G zzym$w1Jj?rt#EeD(vGmp;Tlx!2yS~WmrIWA?m9M8{r7?R3fG!g$|;GBd*b`*D4)JB z+Lv1GET4DEc0~45{CyvPJwv*d(o?GVE~HU&Ji~XjKlNpQn&H*PPq)k%rqgS2vorff zTYk!dxQB+v#C_w6yRz4U>_F_YxAe?Z?aq=HVY0R*u zz#d51FfZSipIM(h-n5M3ysC;uyf5w?TIL4Qf-*?8a*f@JkzBhl^I(vEQzKG?PYMZQvrFd|oJ34Mx zZzh_0!<+ijH0{p$kHM?7xJO)6$Le$bA;PzcN^bB>jP-fqT5Qul=p1Fuoimg59$e%b zTpXa8I5ZY@f7Hq?o#aNT?U6k=vOJ|OHZb^zox=$2GxxC?rn~*>VGY|mYqxirZ+32Z z`I$U#^vI|cb7twSHz zUCS*MQPe45IeDiq_WmpH_a?>K53vPYF3+k?LIS4vQcp!3u`$sz&QTmS_c`ba^#7^T zPINC~dr2s|%3|?Ow|drej-Re`Wan^^QPe4k=>vT>Hljq2>Cbg8NXfSo*9PjDztv8D zp6`kKq~C(u@0>a3Eq%`kGC_`OG~!JwVq3~spDl{r}=271n+{}3E7X@+<7$# zU303^cLv>rHGhuGKGIP%-Qu4o$$+h=5zcjP+UTDvSw6DVCa}6(s*#OyW*!bFxMcP$ znIOTQIha#{L$M`;_eHKs>d#??Bh0#M{CCUPhMdmYoKExR&MmL>PIN>sptJ=8DJN2! z>-66yQ)IvSLy^bwwD$xbwpKmM;kRA+chUmw>N|*sfCS_i6`0bi_JX9 ziMidrJuLfd<~GhiIDc^ObQ=fV)mE3YaaXwU33Zt$=RXzJ#D3~zjkKp%rzR(JkFWA%Ubl>xxy=BDlMHHTN+>0;+{S?#-@&;c%7k< z>G#QOa@E3c+*evdVrOk)r#Y;1OYpI>ADnqXOCWXC`VR}Au35Itxxwo-Ba3Zg*z(j=;-)yJ^XLUK&e;r zS4g6~uS@s0ORA@A{{|V@p!=H@8O;cmnUKG!WxUriY-l|5dcBs}eG`*kcPBR56CAS# z$GIGtHYv1mzCC!OyX>9FqTs0Ys}IV)RBcWCz`mzlwn1qyE^en*!Jf%*`ET_c}9WwP1Zs_x2t(HOBmVN9~3VSG449k?HczTu2;Y z^KX{4Bwmw*{~tg3s~qzDwIeln?*@I#3=6ha>#he~&LLj^dYSI(Ri_)b(r8)qpJcdp zbq5nT8*kD4t2$Cv$@=*m$zJJ%NpkpKQbjM3zw18t#B}(0gOVk&#z_oX-eE59@W;w1 zt|7SM-x9P0KXB8<;eS>#M>#jGO|f>QS>wJs-?+m$&WUGF%ZO9X+9x{9Cp(T1{rwin zu{VV^&jJc39!i_)W$D`x?ps?BMgPV`?Q3h4N84)Er(3kzi94tFIWRru`r{GWX>tZ= z$E;dZ#5!J4YR!1KL$k$b$NRYomeP)0llAHSwS(5E6wB{Aw6FWkwG6mUi})_W;bFiL zt8MM)NQuvgar1PRHHK%H@x-6ku}tsXFsq|Bufxp4jG1}wIJKf!cfjrWvR(d8m(8>M z;y&fMTLx*Hh3?~pD;kHd(O-z~-uM05!ZV2>UhFBIKz}k53!)Nd*t{>FaKBwzJEfzc zSBHCJk^heFt;FaJQTue)I9K$a%)I9$d9!QCpUkpVQG&*|qWH5t$}^#Z<5xiU^}iljb$P%!v5Jjp}bz6kf;3!suG+E%c97f3KCl zp5%_P4z*UEUv{ioAMQ?$>@ZWh*VS`*4@dMcshj>lJCphxbn3t)hf9udv>309E}Ok;PE?d-MW}$I8AP^ze+ayH|{M#TS3(f4+TXN&C}YSNu;W zm1S1w#Si-(vck;lpVdxcPxo~g*O*>Qs#jlC7aF6NL>1Mwt?`ySHuP=$f#^AWaNO{h zqdjNa-B-qV&hXb=y6b#m2Qf%>^hvy=e`Bhf%UljRUk}iiCTTl^F@(7ai+`ys#XQ|! zZENfdS6M#Uu6a(hySK-vzi#i|^Qj(7-0Dd4>vsQ_GO1h-ey8U7s@;D?5^_ZOS`y{4 zu6~W7@TRrpmQUW+JfF6^H^z8A;jg>&gY$`}=x@1tE$3LSe#H-Yz5TsAPr9#JJSxH! zm*a`M-nnU}+0f2mq^v3IeJyP6mkOPh3v}~?_I=iB-S3t~WPTmiDlGJJRatU3CUftP z`cB!=K6!hL`s<#$_xys`xLG#y?RNj0G6OfU{O?G@e5>7GDT#_m|Jy3#4HiKl%sUaz z-qM-f+L_}SWsSSO%d8}yzJbjDgf;W;?VNr__$@NSo|Z5ZT-jm&So(|DfCR!A)9rsm zMoJe-g{AQeInm&ftnq|80^n&%u$}N0u&TH_#Cy|~vx}OgX{dd9ZM$m__a*#m+G(4r zfjdGOJ>#C2V!6c3J8Uft68B_qMfbS8moL=cNIWaszy&7DkYnf&iCi*GPrEBSA9h8P z_ta1FYF>{uw(q1X+VAhqW!{EG?X`>AU6*s0`EtUcD(}lD+8dr|-x<_tBKVz{rABlw z`^$&h8y^12xZnO{9Ot)Gm?Z~CxG%0_<$diG!7+tAslKdt?X32mNwXwdEwlFC_U@(S z-SA`HojrMtSN}K9sF~kAU0IanVtH)uewLo~{_prk*MG;;A5X^~v$2C;%318ix-nn8 z8l+C$cl*oo>_4eHN1RJ8mRq(zlJzM2D6+?cvJWGBX4xN!!D=1+SF{Hl^*%W}C|OXv zyd>>KudO8OMenIVfD-^&_vu>3@b=-OM?}u-=h}V4Vy|l`_amloT{Hn!e zn{r9}f{3HH#?~(zGGX@UkjuGes8;t_{n!P|xFvMwoP7HnYimX1`zeXF*5r*bbtzY` zJsvspk-*i{W7}8uZNERTY)t-5Gq`4Qtw|{x5Jee6w{?Q|#hFD_}&+iTCa zjlCO?b?nu=?C?< zxBb{Q-KlO*OPklL&DMKWe$hq%$vfA5(p&lOwyU@3{N>d56j|A8FJu%jP;JXQDT5o& z*EY6QOK;bgQr=Q}3c22!=(*W4(Hvi!JaMIK$;8S#Qtr!md`iNS`s*sJnLG|uKa1zEEKMM!%cW1w?HI%p2ZeY|OGrPi#L2K}pnDwj2l@5zc-sP^2DC_T(t=9Di zUO?z(!_qg;iEG^8uJT8#_3VLKbwu7^=iw0zYuai{C}UmbH?pqu7`BFkyJphIX}se0 zTV&hHBAddaZ1xY;@vuUdexgKTptJtF;$5{*V}nHgML+d*yxX&|+t=9I``ToOvt4wS zr<7)ixBQj%P^R3y^FrKZ7p8Q!R$KQ)u;6Zwo#$)kw^h?~>Ccz4c^E-Fz9Z#s%N^#_ z+T=S{y57B`QaSuo{e%kJQl1U`(c#1-ak$#59IkWVR$EwZ>;{)VN56RVg!(M*?8fDs z6U&z+_83lNS8*wMYEg1(DI1O*I{QkCY=Hx;Z}HRnA~*{?+p1dd|En&0e%9I&6aGhC zI9f+fh`aG(d%L)JI+45E+sY4HM>^%K@kyJZ*kYn@=zF7O7a}xuk8< z4*?x4ON#IeX>zwyUq~}EI!n3r3MGV~Jktwz>A~9TgTV;@0%;?SbE!k(JS?%F3UdlsAN`0ey4j{6M_6{j&N)4cl64x3!vY zw{9s7HC~7+ajaNlSy7Vm)*6{5^1fdOa99&)mp)e_?yl#m`8$dwz2A zd`>F4*PWX?|CIemgoFr!SmJ$j+UMOJSrJ*5t2}4kvT2di|IEC(ue!_b{@J`TTlXP7 z@j+|ioskuGX6^Rxj!4tbJY{$OYz7uqP%!^|H_1>D@31Opbq8WRDg1SNZfPA6;2L|`7tEE^Lu z+oxyJ(~bWE|yH3v>uFXEA?@82VSAD-Sq17CysvgDq@lfMPXV4oH$EJ^K zb^F{&R&^1U7L{a6uq`ECu8eLqqg9>d2}tvUmSDiWT%t=_RC1whxlK-yV#Y++8`*zW zYOSW_$7#bY_O>vq!Uo@9O|iEo$SHK1wQzCFa@$B7=PG_p#@XrfJlnERp4^m6!I&f| z-RE#8)2rpWjBbP^?gH8OQ9c{Mz8BJ7{T(gCihk(|3{M;A?`#?7z0BX-GHm=M{?jeP z`VRD;Y@uJs-_$Z}XSDo15aIv6W%ibD$@z-e?+gldCOSx$_*I;h`2D_i=v1Pp3GBsG!ogdxvm=9Zh+w?J0wqK&HcY zx^`%}WBogqmM>U7W5Yv9&eEOn8x~~0eE#hvJ$_so9CLT!-$QI->T(L*ZgOiInMJ4*xuS|Jt-yeZSLaDj*sBT%(8px zp2~gj@ewIa&H2_;(;D}JE!At8;nk97yBW-W)mk=GyYa@r912g=$MnrO)|{5=ik9G2XJbiKRLP9uq`HURf4uIYhJS_DxN7S|uBi9*HQw6w%_XUy^{rVGvnKVe zRdE{wK8N{obLGp;#i`}`7VhEia+md!ZZLX{Tvl0omhWHJT+)}5qh*77HLPu}UE6HF z)I4c#+03Z&@pR4YvmTyRKK_U;@0;+Z!CyY#+_0*7XYjX`tDE!kZRO)#y%JA`i`Ra+ zvbmwSd0Il|57G~I6}~Q`%mXi<*uSfYlJ{R(Dupj@t_?Jsi<_tYsqtd@0NXtD~rw7y4AL7eA_-{S~KVA5slmJ{=1sh6U9lk zG5xdR52ckj>U^J$j=143k?(D@t$5#k#5SATCA}ieo10yimwgl2_@=!gf+pTmXY+<; zF2&EO(?F>s;VR|MPYocv#I z3shR8V!12uels@(B4*l{L3;hWZw)j^-@m7s?*6ot^9amDX!hlPmkZ zJJZ&;!P2*O#ln!={hZcdl~wxbJq>k3L)BbW@k~gcuEki~T0_5x@it3Wh#FHv_i=0T zF>6M9h_fi-Y1{!DMWrRnni$<{k})}Cuo|`%(c_sQ`yrn5n`AY<>UTrGK?$u!o57=E z8DEEtD>r9ZN7?iHv2OP7!lUfBU2l^I0^Uux{bYmgl$$JDWNRLf&Gy;FIvwcuZko;V z>Cfx*%*8sbvN8Ej>a@k0arysUr`rEnr_65&xbI#i+kL0po7r-4yT_#dbi3`6I=(J- z{7CA!4^1s&csgY90T~f!_|69i^Wnaix z6_Tq=TiH`~o;IoCLb={nKR7Wqs&s}%_epyPrhiZ#LW>IKM&|el8CH>IN5yJ+5B(KJCkKQqL_5^ueuQ58x6}`8%B4S%g zUjGO>jRt2wd?D3ijk|?TZ7r`Z9PM@WcUj$|pG$V0YD^@<=iWXf}I zjjcA-XGYz#kMV2AQ0?rHIV-fq{8eHfe&Qr`ZTyyHrnD+)?iX3-4J0O^+Mjvqhf-IVx!PhQ42*`{p0!~#3dFRc5BVprMjb{2o$@as@* zddN(pHV-7)qPVz^jkrUnN9^|(W0fmG`X;znARC!w@+&AA{_k^%t_11AZ~R*IBC7Ff zxbfmJH#6nX*A&J!nSV>rCc~#M4*#^iM^$~NYo)a=9`~SEOwBW0R+8byZ^L^{ZUA*} z)9ySDse8?`i^rbsJdRg;&AjeB&U1nxb&p|m6Y<<15ehg;d~3WVX@U4PsmuHNEurP5 zE>3-1j-nY?70tN1XhvH1cARpq$<>cw-xT(rky&qcRz%E=ZfptryJYaj8#(E4S?}jK zQ#-$@;rr7SbSwC;mQl3@O%30i-au!V^i!UatPOqDF{Hv;+%NCU#b*hoYk4U6Z%_Yy-c-B!SySF^7ya>k+QfNH zV|e8!O@XK(7nmPL2c*B9Hz-li#gn_O}#JjN!6 zxU!q=q_gyj2#`8M%lnP}>RY8Tz8D@pE21ZaQ2Ci9)9Ex|_` zzi?z!NtJ0s6YWQfVm;fMDr`@CWzqadkCPix%XP+i{vlPIsCHh}E~K3_p1IgAJe!(i zc(2^iI1se#T)*EHo%m*C?fNEe@Ny$5?+xh?JH(%||EL4$Ph#-Jd)<{B#D2Wj{Zuc-NRcdsw!H-V;AK?^lsm9P1iHtnq} zcQmeIXDv>1IrE-kgQ|5Qe#9q6NH--J8`;YsmLyrm#3saNbcCmOhNpCfhgapK*~;x{FONt(YSmJ8t=dV4 zS8+f4Kp7~v58;{c`P4RbJP#WAwj9;9l<&hla>OIIga1@pZ6PDgL-+A5X|}Xoln~9{ z;L?Ldi}#F>hn09D%t`wl@l+9yBs%GxUKd}q^URxRu@qtVdgZahJu#Z=(6YWUBW^2- zkY6b=5z8yOUdv&UoHtyb9F5SX{3|@C?`2uGoQN4i2aO-#s_jUK1W0m2aOh?9`lXJ2E4*hU#$Txl>#c>#mWm z`~7c+sh=xeohoT*Y%{jM|3X+ElCJK)p(zbj%hlZc=9+N{#~UN$ye^TmyCQe+8h2cl z1SB$P-VV)pJ*N2DgpGOMhErO@SL=4y5Z8AEnUoL%>Jjh8@MAM{PRXx+NY6VKUN&QM zvgMem31vejiPF-kpDQ z7tVLRFTG@u9$oRsIP}}r>jA!1Aqo=u<%A#{@dEE3Yb9c9g4eZU__K<4Y7Ip>8 zliefK<#G3E4+L9FgEQG?guQgvFN%C_-hs8^H&MJTR#WeKN`i&_#yl0SfY-txoB%h&n_&&i zhWp{Y@F>iOAvh0edAu6~#=ympdz9(LFd04%4ft0$8Lo%9a5F4`+h8Hw30J_qFbEIA z&F~1UfnUS@un8W8T`&YKI?JDZF5L-ZU_a=F2{0KF&e9F|OE?)`4RfIZ3*bap2&cjo z@GcmHv*2bp7uLW>;eNOX9)$rIf-9jmhxZV`82B=D!%Z+5z5xyR4x9|%gSoKk@jgZY zly`@Pd4Gqz+s5F1PX~8xG5h2BU!pYLH$mClvr}F%t?~*gub}b@DzBjO3M#Ll@(L=i zpzug5E>yn@Or zsJw#8E2zAJ$}6b6g32qXyn@OrsJw#8E2zAJ$}6b6y1&OOsJw#8E2zAJ$}6b6f(0n} z(mC}`w#@^y^xOY}rys_scb}Ev(@7b}JH$e$5APMzmJu&Ad0z?XG;|Ak*~|OQm}fKZ zBPlNA-7M%)@(a;oRB|ZBSityaNv}rh(5pz^Q+E3x+G;%S8bT2$6Y)Z(^iWzaiZl#-ez%j`HG}$4z=FT7YIU z%@Zt9MPt9MPJJb=mrs62qm1E@TJ z$^%${c-4<~Vkz&;`m|@CAW!)Ll^;;~0hJ$6`2m$5Q27CsA5i%*ug4Fl{D8_2sQiG+ z52*Zr$`7dgfXWZ3{D8_2sQiG+52*Zr$`7dgc&Nt@sQiG+52*Zr$`7dgfXWZ3{D8_2 zsQiG+52*Zr$`7dgfXWZ3{CK#>52*Zr$`7dgfXWZ3{D1{W?GsRW0F?(&c>t9MPt9MPt9MPt9MPt9MPL7Fz>`hUX+4fOW>T8YlU3q7oP)v==Qk;iqGXxe6E7xa~%|)4?*$y1QegUp!n?g zW4F)oP_EH^P<&2<;&T=hpL3x2oDaq41yFnrK=HW*iqGXxe6E7xa~%|)4?*$y1QegU zp!n=q)a`RTlxuV!6raXUc`(uo0j5|zvBCXk-A)nQD7 z^T=C;%Fuj<11vlLubj`Lqi7@wN&IbyF>XZQu3`JF<(lRuOL{nL(XF-!d-va( z2NzH2eD(z6c(0_&N_we#o#mS7;G0h4!IX39C%b5;P(CHUfbombbLb`XPZYuW9Dwae zwo}Y-O^e~0@51Xx()+JtU$`mOxRv4jk-TrR%VA79<}g;X?!y^p(Q?1L0Y2E_Fpjr5 z4BKT<#@OQyW7Y|_=_!ZdYjGGQtq!AzY2>|)*RhPrNUp`?`ly6*-e6o1y^3B#Cceq_ z^t7*HjI9jsK)cz`hTzplq~AsFqYu#`bPUNGHZ?8q=V!RK3;da_CV6-fhejgjO5Qok zwf@LaT=O&hyBN#RX>98>uB#TJ-N^M3zM*xf3(sy~I=X?iI)=A0T=+u#P0jgV1>Wd7 z{_FnCo1d*23FsVNIiAss+&eU5;ZwZMuSheh{F;%&{QDUv3NYQ>49`IM=n1qMy@B3F zJJDVwu|$p+balsMvp5=YXY#E~p0aU=)60rR26kp)oVNC1`~+5X3&c+dvL1KW~r5Bfv#U?>z1 zMnmyn0u&E!g%ZnWLGfTd6c3g_@nAI+4_<-d!B!|9?1tjOK`0&^hvGpS6c21qb$ies ziU&iXcrY4@2NR%pa4VEpJ`0Km^PzaK1d0c%p?L5L6c4sS@nAO;4-P`{;5ZZy+Msw~ z<628R=nut%p-?;+4aI{AP&~L5N-UoR#e?}!JXiw7gVj(xcm;|FTcLQc8;S=9p?Gi{ ziux0Cst(2&yeZcq-#2joM$_(>{f_)eXc6h-#HM88xcD~tPu;%dLh-Et%Dz(w#kUnu zd<#PHZ8H?#YM}VGABt~Bq4*Yp;+t07?OO~K-`r4qONQc`0mZk;P<+dU;#&cfeWwtL zZ!4hq7KGy4W+=YZK=Exq6yJ_Q@ht?!H|?2j-(sNn=7!>1G8EqoD85aG;#)2h-wL4k zRtUwn6;ON&Lh)@g6yIu~__iO4Z%3i{7J}lN_UCTjVxaiuhT>Z?6yFRe$JyO@G6DI} za=dZl%VD$wO=g+}Y?~{7>aKvq4;(Vif^uG zyM0T5;@e0lzKwIXt-OHB z3#hz+$_uExfXWM~yja!a1yo)@Q* zCz-yBw48T1=mWTTz9IXjgK;+cmj}Znl!bE90?K^?mZR5E6?zBlM0-#JlC}}Ubf1$x zhNQ0{3=>RZeotS6)Lpe3sM-xw?FOoL168|$s@*`}fYpwHv6~4OHz0s&)fayMe0RK-F%b+@tZ8 z;xFG&sKb-0A)I@D7GumOt;ypIg(wCYeE;ANnj!wp$2akF3DfjvUirqz>SM$R^3xd3 zMKWFSC$z_ZSPacJOLP2#dm^)K#zMwbaW7Q92(^&$cFK-GCpkwy#JDyj-<*gfZ#Q*Y zLB8W)j_)W7O+%B%o=e|FzUJ1nI@tfo zD8o%zBcF;gV#dbuJ%?ChC;7Fg1||KHHUSHNP3gIsHtX>yqvRT%e<07txN~qTs{5F4 zcnq={S_$7@A-{gFZiMP}W9&ZNNM_hUz74sMe2ZZ;dHqSppm@YtiIzq^rXky>x?ww@ z8=F7VjcIFWGmHB9Ce_`SGZO|Bha;I61op9N9sQ2 zI<9G{YZv7lLRH)7-&n0_z6XByngS-DA!I*6M2HRu$=Ef zq_Hgq(W+i!TmzE(f`{Sv=wtdzj#Jm;Q&k!@}g999hym*x50bSgXmGT96gUp(d(!hy@x(W-=fI%^d%q<8iuYyH=>#7 zakK($M!V2w=v&l|qNwv=^ayqQ6MNZ$x^(B0_w=rQyhdLC^-7yFPj+R29u2iQhK znNPNH03Tcr=*IpEu9tbf`s7Et@dj!42T=ywbkVf`&~Cogjlx8>D?UFsRWpi_>lV#; ziR)O|cB&0LrW^90_U26%qd)s%j$bn-GrfCJx0jn9*C>zANiFK$vDCN9vaj(e ziZEB}t1U({s(Vj2cHrTy%)jIV-5C0XZUh$V#tB-1e7kGKH@YF~*Lpe}4xt4%#~O2` zZs^v#_&)D57GuFesgFS$({foN%195tzS}mCx`kfT(yxSL->0ozK@2CYZN_8RpK-aU zfU+E4Mj1Y|`GVBFk0#$q3v1!40Q zeoHrMw(3S2tb3Dh_PoJ&dRQl8Jm0r@n|4w~8$=(X`C~a=##@aE1Fgm=#@W$m>dE$F zb!x)5#qRIZxsAR*w1#n#Js5t;ys{0WwsURC^z)l^V+HzwaVHS32}$3D&N1B8!`|d}e5a&8nuO$=fb!jv7$o0Fk?#barHs5CTKXt-@%w=}tjBK|z7}2l zhF})U_*E_a735VTS)6>Z=@;Z(QB6#T+3;~B?aI#ZI?^pQ#18l&YDHQuZG&%O@wqCk ziglndc7-inKt3HP-?j=jUHa7bQ{Wd&6u$f&wl!sZ$ug&rt|z?_$~O&*S@+EWVl~=E z{t@!!yMTlLN&f-kVs6omTL)W>M*Nt+f{Wa4(i&m3<9sUO`ChbC+ z+y1lf4Nhm={;9e#mvL$6_pIA^l!Z32oC4YY0VDk|bvj2I(+VTC9pqaqHti63TS?2e zt<*PU3awh(LE7UU-B`%J7Y}0?9=b#~GTtT5oaER_xf9;ijrDtoYou4fMd)4955j3^ z61oQYP+xSGd5@v7J9+K`&Vc7+-zNPUTFLMm=reQy{e`^CcF|TTGnV0EhUN1&Pr!r3 zq5I%?#@_*#!Yb-HlJbTzJOJ$@@APis80jX`my-T1nva5rFQSAWjCLBIlP*Wr0h+dh zde)(@(dv72afs$ax5o?`Fz3K!-OFoAy(W0j8UUB4ageHT4f77wJDU&m^8zd4*w};Rw_lIngS{eRhy-4C5I-0zdzV^*BKL zU^p2rXZQlgj~vp!WLkd0p)Dl+2hy9FzCxy@zT-Y@m9HsD z@R50^!E)+S4Er#=8h-RSz6#OU&p5t(%Ck(23vzxcc#N%_@$Paq_?3|G>AMG z-}Kx>eK(_NM<|Q@y@y$s7r4mZ%CrxVzaBk{Y@8b`dBABL zY}AeK7~aqLgXm}^-@SJb=c25}!#8q$f$~s)%82<7%STV3N#w1BLG%{dhZZnjKINXH z4IO-t_C|VI0d?TJrD4YZ>kH1ekg-nFM%%2$3Onb?%#(#C%;Q^|jH^atneU%p9@my^ zVgBvx+pzAa>|Y#=3XOE%W$X_O*P|Mg6Q+;+b>cIUZ<}^;j1DilR_l89$=;!Ud5%pX zgYEq{TC!XFFfxrFjLCN|!{J*Y?c@Am)_XVQ+UDxUNxo^Ba0NeCgY!ZTO}=HCz`ARH zW7{y@P}1^^&^fbb&klY`yFsfTbsF;R(9NXxq0f+H)^$#)>-`Y1qJuB%^w@-5do=%f7jGkkCFF+Kgzqk6i`>*CxkcRAbo zUe28uFW*1aCUBj>@0C<3EDyzdFS}x<9R6O&vHK`#CpniRHva_)aNTL z{VsGbn#FKFnn~JWDV)9qy5MOj<>H5qYza)`M%LrBIp{;bCH+^VG5v8E1Ko`*kLBJ2 zXQKPilV~+M_c+IdCy6hd|CKTP4*CEcl5r@E27XQXC>0fc!?`)zb{y&XXb|giGkg#| zjsoaK6vMHw<~#OzrY~T7`~ZUtzw!sCF&a5ea7-Y7?C;o5ey;4cJ zr@k`C)$Ahv@}0J@MaF-D{)rZ$@P&6@FI_3$xFlXOvQJ2J8|j)cUVRfT_j}?xV(%uV z+O$6WC6-&6_-qa9orYg>oxGoEl1_3gMY;Gn3!PioeI7oI^sVd{X^b0>mM~l_d8DsL zlTH(RC}#zEtC4%H)6ibfv|OGi)@~x0_k|AH#}X@-Z=^qPaN5;dlW%JV`HgYzS8-b5 zl3x(~Vzr}lex+$qxNll)p8wHZ6?boe9EH5GhLyZU&_2qUj|A*xiqIXXfSRa8Y}tx zoW=yoo`jaX?=;CmjWJArGr9u}ZFd@X-k@ps&@LtK8B~c5Bi~uhd581P zB6yPRoDA<&|7dS^o zHgt&muh7x+v_rHT4Y@$uN9)k;29BfQ^w;M<%&~kV=XU7CUg9L>+!`i!!Wh&a9cKDh zESB_B@GZ`<&7KsDR-K8`uZ0VJA)CTE0K~9{DaC-#3Nz=mPT=!Q*H%)7PMN zq~&{*bKg6oetWW=VM2uEU1(6=Q0Ct;D%!{(J(l#FC>I4#1$rLUqVx#Lg}vY?bStVs ze|A{XKZiEEB|Qb*f}TW2$baznnl^b{v@r{>mFjmhva`m z{#S5pg~d20F(WP7xRrXvM$#@AmkOt%{a3U7;SBPM;ac=0=^bz){0~fqcBEC)H-x0! zB}ZG*o5+hNJ(ct&s2>`^uz{u{hY@WQX%_8a(sLMBNP0c#o#=XoKZh--x6_jDWY`TY z@MhSFW}!vs6nYU=prdFv`WS6xy%w_0b?_nP8|Sj5Z--x@%SlHejW$1)>#X9wmh}GA zJs!Ex4U8)yF9#N)-;pkccfd)lB|Ve*u7?TCa}T_V;m6<`%xl8Es0)pVv81nKoTV@vei>e%;onQ%(`YOD z3OSj_&vHu8Iut~&pv_FbBo5EuYKFCbl*@1_>A|EQBpo1~PWmO%#iZwuUICZE*U1aQ zMqSsJ0dW*cigNY63epG}up@Zms#+`=u z%QQnQ>DRy!a0I*odf{{!SHQJCtfpS;;3o37!p{+xB6>4iL!OQCx5DA@dUzdt5e=oi z-2?sTa@ONhl#goI<~z`e)zQZD4DUr%497noZG=esIGzr5bKHUtqhGU6mcVPs+YH^b zzxN?$BieD;m;95%nTPz*NG=TI=re)*l*@<7}Hf zMiMjs1C!A>Bz?mTa5{Pf$#XFWDL;p@ZcfB|_&mCn^d>0t$CLh;w0)?=S52D@FC$O7 zizi%eNxu<2O zoDX+1d~{c|Q7>eBBE6LSOBsIx;r+Dfe0ON2BS8{M94ibM zK12MZ3^&LBEarI&$$60+_vLq`avZ(DymyiR7u28SiU&(c41)P5sRSQw_FErVOZ{8zCrp1 z(np~5onL@y3`^g&dnDhWC!Gf4;Y4VIav${+>ATPuXbH=bds?~FVIIRzB1eSFn8vV1 z+m(AR($~C|{D+xe+UF)Al54)=?a_woI+t-U%4OJp$FU4bn}2vdeQd-OInLVL(MIki z(MG}GXrnONWfUL-C6GTFx>0{rMVX1ew4|SfaxZF$)S(bR;AFg+gtE|ThVxMXJ%~cg zQwSHJg=htug=9Nl$GFjbT*e$I+jKGf6n%i|f9*2H{>o*vk*>P}|B!)hL$VI@(_F^! zaW3O3elP!Ys4@_$u*L9dnO_as6RC~t8rfQSWlhbMeE#)@=k%qgwA5Xp3agACJZR6E zaOI+qxhN!qse|kmZR82QzFN63WG#Y2GCvPj&JCG!Re6`Iyg=s1;mZ3% z=KZR?5h`y1Z{4h%6Ef$h@_Z^UKXY%ma+bW_M(Q_Q<>h4V30F=JnbTEy!&F{YW^K6g zo{)KuDlbvxrDg62SKcLWzG2?R%T%5(^Q~}YPRPtr<#|IWc5TROKb8 zyt>Ra;mXXAnW@SfqVlRTSBERF3z^rc@&>8A^2}$$l}5-kRC)0#uO##7aOJp=IZl-~ zP~`)vCPyDlb3t$#CW9kU3hF*H7i;WIh_MObMAO zs=U4`FDr9ixbl}F^Ovf;Se2KS`Cz#6@{oDCDzA^q^JUHnSNcMxPnFkO<;7>t2v;VC z%tTdQFO}!WygOX!4Vhk5o=fsvU70!I%1cA$rK&uq$~%#HOSm#2WG1NcqEz0Y%-@77 z2ZhW*syv6vtIM1iu8a$rajLvXl~;%=B<&OvsE;<=IqTN#-@- z%3dL}mnzSy@&cK^3RgNqrc;%ttGor7$>GY#kQu4U(^Ou5=H=l^TgbGj^1|`59yyuA z!j+bgX;I}}P>uY5cxQDmRtfRC%L4?}?eN zB?mmI8Lpy?UOR)f4H_{ntbYPv`MJ6)c7MUuQH zW2zqOf3K>P7o%&{?B_L{tEHHEiJrTn3ZH)Gvfu|el;jDWW}srUM1Jl<6K7_T>y2K7n& z|2y7qkC%A=yC(B@s(pPV?XNrDKhR`8pxWO7m8ar;ev_Fmd3g;gPsRJZCNod+68Eb- z74K&>nKM**|5AA>-rw6~-mA*nr}9+1pVnkfQ{~mGJQeTnXfp3m!RGy0Wlbg)Rs=W79o{IN3H<>r9^7g1a74L6sGH+Dn?N)gz-rvw< z-k{3crSep~pU`AZQ047Zc`Dv#G?^KyyjscYj`zQAGJmbg`=`oN@&4K-^IBEjJ1S4b z`?MxAO_f)z@>IP4Rg?KERo)Jjr{aBTlbNc@dt2qHcz;Ecd4(!(o61x1eq@t5Qk7Sw z@>IM}YBG~ld2gva74L^NnZs0hZ>T&K?=NdIFH_}hRe37jyPHh6D(`ibr{eu3P39%4 zye%qE#rwfc=3rIcW|gPn{lF%3pepY*m8ar;|0c7)D(@dEPsRJ#CNox*S0Q=b@xFJH z*;|$Oca^8&eRPuINcG?@-n-UgMY;(bJu8KKH6S9vPl>rJMv%6nDisdyhg zZH7-vJS|gsD&C(vZJv|7yjN77iuXU9Hh+-3#Ftf`iuWC-%?`mpL^E*}EI+ds5{Wqu0Z&Z0N#aDiP+WcCL zU;97fy;R~C5BJ3T|D5iQ_lx3F7M}hw-UqyOsUM{6c+S<25aP0?j*m&zV>j(8-ci-p zJaF1&ZEPEuI>WlttH;%ATfCaNkG!(hfn&TfZC{xy=8{lxd6jOycY2?rv6(SDz1FyM z=XqhPx$|^tP%8@!9FwX(rz`KhPEoH#`u~V~`}iiREN(na(kZloz9daU3)%(-3W7_E z3Mj55l+yCD(kfVVQ5zWq1f^INlwF&uHN40|eFJ>U4Gfen1*R%nz$&d!5vf3dzN9KI zI*1^?t?vSP&N+ATQpJ6qKYpLj@1JDm%)Q@x?z!ijd*;r}xgN~ZwgmR#)Lh+$REtLs zyVI_&$IWR!VB$kI$R-XkHKa`{fuBsn0GnPK50ekh30Ygd%692so2Hiy9@u+iiCtV^ zgH%wjmE`^!!08RkfTM+6QSht2QnC`JCT!dB+E;*8WQ3n-B}L}b$yX{be+5Nw0H3zu zC2R|lSPxI03p8nry?=5}U0>Mc^D6X@(#y7@zRT4b%As|ijsLGM@t94~ah}sk~T$oqrnmW@)Wo07{5&HDJqz(NBEOR=G z4YRULF-3w^!WksmUHTOWC2#yQu?N-{W!?6|$xYc#rww<;t1&LgaL>c5o{W9t7v(J) z4{7FLCX0SQqa*QzLANiXJ@IsE|7n72Z$i=7XJRHl^8}G#=C z8hG@$&D3p4j>rCTo+mpWB9pwNw4D4TlMzH_!bvaRF8m6a{h+9{xP%YDmkYRl6r`Yj z583F+p-iAgSX~XYxpAMBQ-9-?900dvDq@ z>KrjTDb_6zpL))xe7DVGuQx1#{Zx>FQd_<~53CM3%tro%bCCBDVa96xKeeF7sFsv8 z{T7TB$_?>DU&sB^ADLxp>1#ODE-o&TIXfVpBc}9=~Q&{O6G~pAFLU@p?VE zl1tayl%v7=aWMQAoA|iP*YbW#NBFom=(1|h z&m&zkD9U-LHt^ewEx=RnfsiwGVE=*F4YUr-9XQpVYnzsZIOAZe2UPxl+=K|MBR z>nrTA=ONdO2HW6)&&0mCwr73!kA|62=1&<4dI;Nv41VO1wKnT9U41veMR`=%ntl#I zfnh@-`}fd?(x(o;R3GchFq9YT-LE08C&Lg~4LhQT!gRy`KrG;yF! zc?$UiqIiex#+d0bO`irJD`;=`3PiB}8WpZTHC+F2u)b*HmF0f6f)ebwGb0{;vqjwy zVYmhRD*ckd+mh^BSvA8(T&Oh=fMUb%EWkaMntlr8YH@di&T;4Yf(H;U(@4oO2Yy=> zof&Jl<93T1*Iii`T@#fV*9@r@V}W3g8e9`4^|l?TOdAGn0j$LCMW0;sE&Ply4o}vL z-)4YKfxpeT<9wAq2t@f8GoMA%#oW&Yj`Q{C@H3-7V5ZG*U@wjX_S6C}DXo{eHf5qF zF7+{6#trA|af`_DC@M{e7R{rYZT*eo6o*z=zRG6WTyNAAc47!nEww2lNnalUeFdoD z2t~Ff#RtXJ)28%3ulfH^Q-H5Lg}5AG8Hw`!6y!_#VB$@yaa=j&O$&tIxSh(e8W!j` zPFP_(g^h%auoob9a|C3g?ENh0WIXb)v35jIAy?bV_rLS>>&7%Y43Tetx_5UeT*B!^ z`j@Q`|i2U9E_!6+o4TH;_gFWvqY4jXf z(lX-Hk*BCU-=fO>6Be;nLlR85`S=UC44qE-=u1cpnqm_!1g?wHJKDY+n1K>77M%Z* zhG}@lkuQB-@4(`E*t#RNdZ2sYs(~f;;@E+T-7ysSl+CtUuQzoUUk_Ss1IG--6ROLw z-#@3gwvOm;bOGgKV>51Yms$;U@fBOS_XJc+@RTJ$w)-k7%mSf`(hn(cTpyiUZQny~ zvIjQOtPeJ`!Zryu$?y`G{9Q`LX|OB2UT@%}nt+|~eOpJQ+EaH``;eKJG<;BCjz$aqg|i-na&iQS29l2=Qa~$bZvu;cSb%JX!LrW4JYa{5)PF4 z_S^~}@=3(xeL9OQoBq*=xsR{Ws;sa9O?mlC&ea{9Ykp1qJjHrzqG>S{_BG4^EXfDk z2=YVuVcXz-<5K&F&L6T3E*-aa4qSCVjzbs+8;4>YvD8+P0Om2ny(p3_FTnb1-@ywH z;)U&lUdQA4RLGBf_O5N1L0m~s+F&m&gvZ9+_hOMB2OX+2K&iaAW_{lJR7`W)BtXt% z7-lGg?e@Tq@m~2;U~3pS3jr2((E+(V1?pHWeW04!mQv_FkvgS(>#a*=Ja0`gCD$3C z5o|keJ+?C+90oG@3QTYzL5h!(ASZmO=~Zsz__w53pGz^lOoGfo)Mo;qUCymDQa!a^ z-DpxSxfnFg{&ebJ{%}RT%Qkr7(`*0fYshhD28D<)(w(*@Gsrf_ZI2|Ye<#^)cY06w zpoyI7_3l)>zJ1U-ESo}QQN)d2@b5XmFmQrg&9mWN(kSR8U;^uUs;k^_SpLUB@BTsF zF_0G+8>%tpWu+c~eJNqQp@e1+ z+f$aSX)`(3(wix1cA-}w?GbRZx~dG^nI5aZ6aYrisHapTuhLBc_rK49o661@j!vuF zfM@l>)o9)7(;2~T%iDOjp;|S{Q5?mmMrqQ5+#drKKLnb-4{!}mtS1SAo%I!sTH4eSC*XfG0pyV zrNCj(@kQG*=AQHJOy^UYREpO$`FbPDsRvUUnYXav#NmbKYBRc{KifMWWivYvuJB5V zi=C+5GCy@D&`hSC+chBYqs*(^cTPL)X@d=mG-lK_Db{zbigb>*ug*(R?$M4#I4;(X zrMW4}UD~mHUy3q4Sbk25a!0T{98U|DpPi!I7A!w2MVS&Te{YI%ORzi~7X-`COi^wQ zmX}hLNx||nQj`h7@^G9VEPqdmGA>x&m70G#stg5u_IXi?iA&wVEMaJlpBNP z?@Uo{2$qNAk-_rQQZyd>K@j$5X6N8d=)8 zLhlTx-{3Nq-uA*3%y!PCha~s*9P}RQKN4B;IXw6F=d%`(z#g328dJmO)_mz6LW$}B4xkgC#KYD$-I|r}l!gJ;>GB@t5 z<1jMWj?#QqJN+o4&(f?EFr>}jJ2R3u^y>#AOGSB^dhgu9$4d%8_t`sVQ+HvTJE6Bj+iPY+yJE1U4hngVnA=z%%+7VkL+EI%^wuw+$sp4`{7hP7isv+RLJuF-4 z&Jd zJC>^C@OH#IFFSVPnM;vlCp%LdnP>aQ#E=YCS4@9_A4>Pcz+mLQgF9wmQ+LO}qe~D# zv(d@NCI{Nx`34`@8>#;Tg8knX+5b@xs9uR(F>C$5HEeqcQ_xSC}XsWawa z{|jh3;r*YPy34zP47Rh2%+zbCPE3%GNnuH29K_`;ws#SloK1Cp`$8N3@t2XadbEqo zq`#!}k4aR%{(^n|1&BtJ;&UA!8Qo0dV8^*HpsV#p5{o4DgaLz_4kMIOzv&5AIQ^=W z+}SwGqjiFsqk+yy+rt?U2L}2J{kqr*_-(ZBqhFYMlZA_;;%Ni8{?TK)l#`}Xx&2?5 zOxzQUj1T*5u^;qv#?(DO;ZVH$1s}H?QUs$;dFKoE0Cq(E(VA<5esl$WGDWGwt8xX{ z3zMkecUC~r1a<}Da=1P?vfzd>)X9A}=CZmXrjf45n!Lpo5xS!G0{^p5dF>0$EYDln zE7+d&gjMJhSe_~@G(rQ-v0>Ms*=aIiKC5A>o|Ng~`O1=FRim-f%Xz2e!N_pEL1X`_ z-?;_pp^#gUK7(#*Zb3|dT3VN-_!N_=cP9<2^j-JdEA^3HA$r^PGL9{o0tNF)AK*?D zyYYC+ybxbvT#B`V_?pG}68lE-CE9<-mwI1_FV!FBOBTX>snN(+oX`Qfg)RmLg64H0 zILAl^lG7wjPSV4X{OE)HLPM@Vy8I{~N&|>9Nuys5h~^$23@C?C(2#OBk;)0`DutZXGa7(YDD)w1Aa_b2~&U{U2`;; zPS|j`LY^Xi4h8v{63Nd=tMw`3=Y5TzU$B*+-H1P1gKk0-w*x10HvJ4bl5G(TeS{2g z`E(*bX+jq8)5T(f^QhE8{A>yGGcl5%_8>nSHGaP7!q3;g=Vt`*Gm`k(7{SkxFh4EG z&mBS!;3v{vqC@s_i1=9^;SJf-pTc^7_8|DK<}5I@Pp&#Mvq><{zvKJs&e z@C5{kk@ga;#d*?u#LgVvo_u`@-8pgbi%nMR5gM+0G9BI{wAQ_+!;(YaRJ|VUA+hWI!d$Bo`#F2zG%Av=hIM6Tm#`T+vpofg9r+ zRezL&sSjwiLs1`09pU79x`%Z)TOz0mS)f?AL#Ou1lhsN;sMo@NX zdv|X4?a9MSlP`ht6&i2zfj{)XE#xDr-q$b5vF^0uJDs2C@SXnl<?<~L$zoK;*oy!bmQm_LvLLJt)*%Fei)F*=Bd}Nr*B+trjLVlM4xO7!_&>m!F z4Xpvlxu~>0R2shSHP-q&f53-w%%!G%AWJ9<+V#k3?svCat^3pR+%t{(+)O@((tt}B z)*|;0XxvXD?&oRpE($TvQGLKWpQaM6!h_ma-J^9?okDfvd0lbJ@5f4hH9S^wk8sze zk=S+3zDD4D@EZ%Lxz7iRSpVoEWNw1S&dt=oPbx2~{w>6vR(W+uv~o-YKB=%)BpdOU z37-davlOr(VSpxw#7h?*#iq;}_c$Cg6nGhiX1KgiOLQ()2IU||S%oZ^v%E@QUQ`}S z6+hH?lygEgIRny)5RcNulD{TsMzfoiO$urTc0A zkOiE9_18FVkI|Q=`YED0r4pO!DB|TrP+m9uju-i<5HIrgVP2&4Ffa0zRy$WWd$mu{ zU%;Mz8N|De;GgA~k#_d_(}xW6ReI*jtR%DvU;WMLoOA@9=K zdD0pgma6gAKiN-pxU|JRULk%5m1I&;C}( zt4)DwCeyYz%QRi+PW4_3_G@uOJ+YhBYQ`lbWwj>X^zclYFq783H=y&Wz8JZTCL6KO z$~C->+B-axMy@@o8!krNOGr`H{>E8Y?Vp+)dXc>Q1o^W4UfwQ{H(#?64@b!RLUJYZ zu4Fj#g1%<;R9U>5eZ=5|CkJEbLBVPxJTmKVh*cymRP{rseIm3d2D0Ib@zX`*W_nPb zb0Xv-|Crp!yt_-v4w!`jdc~;D--uDC*pu8KkN=0kyu1#G(&{doYpQhaGd^VCS6snJBNgJAY8(S~)y2p)R_V)R@UTKXK9XJPFzE~T zn(18F=E|&uZFyJJs*a>)*bEhw>p$*WpkOz*F^O8&t$3!!TxfY z%2!*JYU=RN|0z>(uvO`U<)o`4TDiPTWnI@cq)e3MPoHA#7b9dTzRP7PH-==XHguJx zy1T0^)pxqeQhmFtEY!e#!4ue@orUsDO+KtP8az8B&KZcO3z1kTUrf8Zxx<0tXY`RRddEJZfN#mSmOtLn;! zxG}_sv>_S3rPM9HP;V4i0H2}dp3=FzCzRyRQfKxa>}$$%3!lORER#OV{pmn(IuS1{ zwd#D{eM$^>f$C1|f^$n97bv|qB6n&X@a`M0J)RW}R^=ioR5Wy95;zhVnj#tA*9Bh? zt@#2Y%1~!j9>9XC9tuv6#rA9=-lspT4%`X=7}pZda9cBH8JRzyFrC|!4vDLfz7@|bU|p@`MypfBH+SGsN6L*Qe_>LGlu-7*_)*6ZMg z0xF=aKEViwe44Z*dDSLtxH~osRBSu7x=4ohd>Ic%v|VF_rlC!rW3JON51NHGeU1m9 ziqmm_tz$kknOY5vIvpjojs@E$<18XSfdX@UcS>3HDOl3A_#&nB61+zRwTj|k5Q}7( z+5nfDDvE_>X(}p2*IgeQoE~=KSR|||YeYlaO>?|_GFf?qhWpcGxCt8#553Qx0-kGd z={a?Z_L%Q6XkH6!)kl#PK9P!#;`rC1F~hq>{F!53vNGdSek@K(&U3?C&hE6CP5br_M!FQ<#5@cOX((iLGWsgOhjizc{+2G8|hr?#cZG&CP*a(p*= zNGuOOZceu9dw@3`a%#GMAk1VD{&WybWG*&ij$;-?SF<*NfeJd8aWFkAR40JOS|2vz zC0Bo3JBEuU!Id+3mZ9G05o%&Qv&xHUW*d94MwsPpj={Iw1Rb?1<|U_1AslI3GU(UI z0B=Hrv0es7a%eE{QH+Knk4c_Yk~VX8NyV3e=IFW-nyA%*7li^{!LKLb_1u!|jNZc` z7@eSN*4H-7gL&FOeGnf`hR;kWn0pl+{K1nrCOwjQ2S;+>u8x3?3Y`#^#SEsh z>hR>cXlpA<(4YyI%P%@CzgPfLvo6*fMzH3X8Ua3Hfib1=REH(c0{`JE?#69Z%}&mR z`+glqi5_H@%>qS?k)_QW!;q+D4j$nkv5D!Ck^=sbwGp?_a`N$cap{L@cx_I zN&n<#1KPQ!#M@pkL?{Ap#tt&2eJiWb1bpP~x7a=D`OK&W0NnG~85WfN2fC z9EIDFiacGFpU;D41kCv9;s$+qRXK zXU1CZAkFQj^R6zfbd2||gjp%S(lMbiYP_j?rQ_zxsQmJV{IUayD-WbscGK57Cc(tD ztq$g~iKiMS)jIyBTa&nZO?qQDLwV1rmZ(WphB8lLUF;!GdP`4z*?}nC$BAd|gSurO zr=x}R)WHt)_NIdY_)-Iyzf7aO{=aP$nt?W_2&V$gLVYyZko?1iru%ipyG~BmPtt2P zG&|CUPO(=xi-fpS79810)JthapSsiRYJX?e2@+#h~4m%mOI9PyvrRqAm?(& zIQP1fK8KU$__0;pY8|J3n@B1xybc9OTy2c0Au9XEi#f-}V% zHMBc*CfeP%C-Cr?1&V17I*Qw#7=^ai93U>n6BiRq-C7#*%N;kn-M~kFV~;3eWnxvg z@+ch4vIFVmJ#a898&1Gr>O6_H`SoP)#alC$ z7QE0Hwb9qBP5_s%FfMhCXXAn`XPWhoZUPB4C4jF{#FN}z1}u30Lvgd5o?IWzTgk)m zT}0#i9}5qvKeg}*pIXv}HMQec5BnBGea{`mDNCJT9Uv@($!X!nn&=ZTI#bLnVZqqJ z3qi$ej0^7tE~SKI&sZN@xFn~Xy-}_7US7t^++QCDh6IZ$pfQHY(X0*v7)%%abOD5dA4=fQ* zgAWeQXK!7A;066K+{+8Ez_V-RqZ5MT2XM~lAG^HlDz?xX=9?$9cwua0{B_7;HKfD1 zD)*n@26V;(gTbRBh<))n8Lh0S3odk7@M#8n@+!MEOi8sg25aGSC4L|Iz=g!+21e}BtXwv;W4)M{%QS% zuv-zovM}?BdYaiEhZ#Qixr@r`?}dZ7}`0q!H4^&{f!@J76S#Dl&@8_dTQkfcm;o#3I0{2|B%U7lX^XJ1>w z%*iQe6K@=c4#^c*j#`##Ej2NM`DY#4J3dJnR=qBF_C@8E6T%zcgPl1+usDaqn1-$fHfAY6QfXNv^Ji5Err3+iCkNhZtkK4>Lwb~BgZ-GYQyEmRGipF z*W?XU=Pj*{{I&%~R?~yvvr;v$iyBDlsLHnET<*KVI-R-(RGP1ojlBS?p4#oO7xg#8 zb)23u)$6tAqIH&fy&VOXmkK@;b!59GWBPFr6+TB1QEaI>5ZJ$=4a`g0_|8oZ-e;?8 zEd|q$S4C?tc)gGlwQ+qSgp9@_2pQ?V6PMU^%DLlO7`hF|48~Rq?$yH)W2^pR!SB+L zk6D&nQ3nD?ey7yO(88d)7nF})FcyaLWj=hy$2EQx=S;jO8BCHlC9bj*Tt}?A`eX3? zLO#Ux3w-PoY)zVLsrWp6cArJ~ETXJ5$0D4F zJfCe5;CuNHZ_+G_0FyNmG>3IB$bk1lo@o&d2P%FGfyasL7U`@^66kF&SV-Q=t+am0Y;6uI3B5aR5ztbYrMxIZ%2wNi0|85bgBhT-!2pb~L zZ?_1LbT`ybb(%$Z70$ziqR?22MHb=30DM&)WKINyh3{o`O+SW6>J$rvaS_#TwFpZ& zp0;3fOGp;tEf(R4NLh%5mZpDFWz9efLS?1N7GYszS?Oj=(}NLZrN4#B%9AX@yvVZh zL`&1Gh_dp8P+4`nMR4)58h`nrYhu1dxFZrr%(FC2jldDdh002~7GZK^S!t}L39=i9 zbU?}pm6gX>gt3ujWv8VHwk8Udl^vn7>S&8_ePmhnCQH+`5oOiChRTY6wFrNSEGyn< zX&Mw!RvZ;7E8Sob`bCzNuD3K@8P-E-q($gWRki7KghjALo)5PO7B~;_EL|7EmanzI z`<#*XaE+y@TLd5Suuxew+al;A%c@!EoX`XEXF%#uD66T%P_X|?6+ZgMc9u|l)1<2XQ^lkpY^o}??#jr?H1vk$ny-Ghv4RCtBoF= zis=^NKatn34EjS&K37=aqeh($oFw>F_$)FgIJtLN-f7%F=iy^Zh}R)S2z;Me@l~Md zi-2%Gkb4HdZEOPi*#P7?sW=mWga`0qM(!z0rPXvI08SH9`Q_p(c)Uxh)zY-;QdmC8 zVE++3sur;S$aAwrs0d5IY^f;6%324KLVQYz7Gd$F2py6VEKQGu+mYfeLTLo9)YBqB z!txLwQV)wT7tWo0GU97LqDh+o<6r{So*%`My1{c-P2IZxPMx}uy1-*yOw*``-Y(Zv_LV1rRug8+p5;(c)w?#^ZA<$$)b>JTVP^ z2dnR@_EHq_fsd1U%%RifS$JlO^Q;sw3)ftxT^?6lGy{{U;j3DBmcticg9F&dti&;q1%DQ-exRB(Z%v4wW$s8Oy=|^)p_6HtYaejX= zgPGwo-!q)w@6AFtu*Gi!kTj;^`#{rA0p9#~WN<-at$U|pExf6gp?rj-x1 zch)Q%hxS6}Uzml?$n!I1p)JIn^trj>NciluSvUk`LzN{pBpdlNv+(}K2wj#xH8<_i zR-#UsbK#qEF3wZtrT}EMfjl2zlfp&RBf5!~PiXQ!fkMUrX*my&?)$B&78(U%63@a{ zfr_sKxfcR(0G~t$`yqd17B*evZK$2WaaqpC<=)=}-$ig6!GTmecw17386W@)^f?G5 zwws$?xX70;+RV7N%-1_w%|sJhQI+5x4cMd5wfHw-8E21QKMHC&V?WDYvEFbnqp9fZy0zuzp}1tZFxk@P+ngu2gMG3_D<0yxudXWugmQzF`uo6JH%fVW7lCsDKB|@80sFt0 zdp-cq5FqFkz5~CHF8m90;pmIbYxeaP?`|%*agguXK*jk$lXwx+hCnW@f&-m(W>^K+ zvL&5Pvn%g+YWGPkc)^(w1HU7phd-y!9jL>0SM-4_Ok-^Q)5g_)a0%WKbsy=3rKh^_ zxR62#m88?=Svkv3^Os!TH1q#cb4|kRocv*V<`0y(oP0Cg3P{Jz0(|R+xw+7=Pnq-j zF8_?QLRPp&$)C(M8T0$*56g#o{Py7^Gtd9B)tuX=<=~RHXm}IvhoR)T!=P`^cLM9P zFb~x1k{52(mG_N$x}RNH73|+0>Uc2k6Z9{g`d8NHz@9uq-gB6q1)fU_KL+AF<5mkm zdJ-npxHoDH?iaDgdE@8B)*E@Ar#7=E*ORv-H^{xLao;jG*k6(v@4z9g1M9%qX!&P+ z8vmp3r*U!UAoC<7LK)CEadqm8>rC!PvB7$y+-%0=S1@kHcVgd)1o(j*Xi2yA?KJ{2 z)qwo7`{FZy@k0_9F3SkI3-^Ss(Yrwh%#fxN#u%=`Y6%UQyf!S0{*VI;^9ZO9^r0MB z(hc)4S=aPf_gLgYadeW+OWVwGuR}(jTakDpNP>HUcg=ZY^2bgaQ-t}jrcBfFVV%+P zVLiwDsbb$%$MH_(W9_59YV-fhV=4X146a@|+R2V~qF!z^LvBHy$MSF| zJB&1O1JNjt<%gZ@L!_zeiAH%WKj>s1AWep$fVz~&a$hIgM|eJ>Q69^^oop}RF``i( z%f?RDNO-Rijq+H&)5+c;yjO`vc`V=RWN#7PD@3C_mODDx4#InxXq3ltdnemYcx#D9 zc`RS=WUmw68lq7i%Wa)(8{w@c8s)L9>0~v8_af0Kk7acyt0uhXiAH%WH+Hg(g!dfM zcpl63ooqefJxesoW63%hBfLtYQ69@zJK3v*w~}a-$MU64_7dU!i)fU`a&;$LO?WGa zMtLls?_|#t-ZMm_JeHN6tdj7S5smU#{;QMyi}02bjq+G7?_|pfZwb*TkL8k1wuJCJ zM58>G6`ibt@G6K#c`TpkWKR&@Q$(XYmSvr+jPRZ$8s)M4M<@FS;XOe#%47LxCwr9e z+(e^1mXCC@M+mPBX*`c*X(uZsyvK<~c`QphSqb6&lW3I3^8QYCKjAGV8s)LPuan(J zc#jc{@>tI5WU~nGQKC^EOR1Adgtv%jl*iK5$y|i@2+=5yovpWRnSRKG7(T<)ltF ziSQmUD-%1}M7n;z8NNe^%v+2``rSOtj{y$s3#}e{NX8I@Tm4SZn zYnkwj8vX{Bpn=XdXAJQ3c}d=#v>~+*t}T=Al^y8n$mx}F$Er@^-dD0(!29t+6SV*Z z{%LXYi+M0njP92X4e%K*yDzvsy`0_$`D3~)!C#O0>O7D*5dM}Zz=Zf?AeiV(TnIk2 zH+SdkH!Z(du37oDgZoo)hxuhl?_`2nY7V+q52hru_tg!sVa_)(wU2VKgZ;Myj-@0F zv$X7JPTm9P6JblokSrp_)B>WBZ#?B^rwDH{(a1L*_p{@KcQet*H-6-29}(W) zh(^A#!_PVhZz9piH@5m&E8$Hb8u`W}es+ZL#uJTv;~_sgM0oi`Bj4EUXU&8+j%ef? z_xss?!pkKZ`NsGB>^;I8OEmJ0d;Dw<;pGsGd}D*3H4vT?Y1}uy?PqTjo`Y!Q8+ZBH zF2Wm4H1dsgepW|#HxZ3|gpKT_* z>xo9bag(2IBD@hqBj32e&o&U=aH5fKT<2%&2=6+gk#F?+nV0acB^vq0SN!Z1!W%|3 z@{Mc#Y%Sqs6ODZ1i+=VZ;bjqxeB*O|_8j32B^vq0RerXL@UBK0_l+z3Yz5&BAsYF{ zWq!7d@CFl&e51$DJcM@@(a1MGC0b8u`Y@{p@kV8$dMjjf?$k zG2!(m8u`XWezu74`VozM-)vlESc<9t7xPk0$bBi}gB&*l-{ zl|&=oILFWC5Z)C;Bj0$hpWRD%eay;CKbuL{dtb&k%HE)FywgvycS0~Ci|7Yai1Y2 z|8Jim{hEaF)L1_oOZ+BbAGptO`k51HVj|JVXWZmxHz7@pCmQ*TQGPax`qz_aO_jnMt48! zPI$i%jeLgSX9D5Qt20t?p-j75hpP}tN>y z?@OYQ&p6Y;&Jf-?q;a32b}*Ij&JvA$#;Fc=itxT58u^Ul9qc&aeNHs;86S19j|lHH z(a2|Xbg&M>Q;9}CqqT#z65eM-BcE}kgB>BfQ$!=5aj1hGBD|ACBcIXS!I}y01kuQ6 z?C)Uv3GX=3$Y;FQ!QLahkBLS;V^0U$LwFw%jeJH!2Wud_PNI>|c)NqWO?dt!rM`pJ z)Af#|%fwUDgFfTU4jxavos@M;gwJp&9m>`YiW#J1N&owPYA4zMh7PuY+G|06xc#r| zVC#sV!$c$d_jWKZ($s@QBl~})gS~<@`9q?S{jcp{YYDHJXk`B{cCZ%-?*P%r{-5h$ z&k^2!qLKZt>R_t~?|q_?{jca?D+q5N(a8Rnb+BcG_a4#6{yiPcLwI|MM)vm8rlE64mOYQb`p*3e@+LRLwGxgM)rSi2fLT>>WD`6 zKcj=qAiV8FBl|DzV8w*@Ceg_L@9bcA65bm`Bm2LjgWW-RuM>^zzo>&15uQRcvi~U^ zYzpCRBO2L%VFxQDysbnd`@gw^-As5}h(`85v4c${yv;--`_J!S`Gogxq;dPt?O?ft zS4}js|1lkG4B>4e8rlEo4mO(bHWH2O|Hck>BjIfz8rlE#9qf9-lZi(5KfHqtC%pAU zBm2LmgIz;-KBAHRXLYbF!ec}u`@gz_T}^miqLKYy)xoYJyw`|E_Mh3oG70Y$qLKZJ z9ZV#=mx)I9Z|`7s!h4BmWdB!ouqz2~ZIW_D2fKoE^i9u3Tz2_59eF zz|zt3SH?j?yZR^}WY-0oUjq4GZSJ4jAq27h3QJK$d@?R$M=N}WpxwHSNm9OU=f^5+ zbwXvNvN-&pf#2}lt`Ws&5Dj5}3^YNieH}kje+bk>0af||Sknr8&q*TuVm;gkOte>j``;d|}uy>@KUjpM`p;huK32Wes% z(a0Y*w6g}JssAJz`NOx{+1p5y|3NhJhr8O@F2Y+(H1daa?W~UQ9wQq0!#CU6n}oNB zXygxT+gUB)EhHNG!>#RXE8#stH1da=+u3HqdzfhC4>z^5O@voUH1dZV+Svxedyr`4 z57)J`b%eKoXygyQ?aWJfB}5~C_)0r_h43CA8u`Pu?QAXK-H$Zx4_|C&FB0B7qLDv* zuAMzccyozH{%}=0TSa(th(`W!MLSzTc(aK{{%~14TSj=Zh(`X<)6P7EcQ4V%A3oL2 zo+3PnXygye+gUl`%^({2!^hj%aSVBD@=jM)q%QXI8=+Ni?#5b2~E=-Uyq|7U|Bu_)$Ao7m8ri?Ujrj>LooHnLZEdWL@UA2p+5gctc9ih0AR5{K;Wl=d z@cIyq?Ek|y_95Y=5smEsgEsa7;iV#t+yA~cwvX_75smDBZyVc7cs8Pu{WrF;M#8fa zjqLxOHuetTB@>P8|E)Im7U5ZlM)tp>jqM;jGttQYx3{tFgqKJ(vj5lH*z1IsKs2)d zZEb8D;l&e;?7yas)ev4!qLKYqx3Ox%GZBsKe`6clNO*BXBl}<9#?}*FEYZmRSsP=7 z*F8z`wlObV?{+!+Ummpo)otAV&nISG7h(VIXWM*@Mu=~ubBR1AfVJLbN0k+Aus)k< zU@MNo|3F;rmEKOwSk|U2X@h56E86hEm#2-^lGPzeN=2KtU^^tq)T6PXcW}R;VF{cr ztBL%o7bL6;CoWu9oksrU$eOue4*d?(OYA4|5fwC#7$)J4~ z(Ct1RFD;ro#7J>oR{l}tR+K}Y0hX@e-^us)_%>0wIoRKC6CGpQsLSvSQTu43pVn!q zl{2p%OSIb|VC!91h}}J(*x+buY3L2T=W*BeVEH47*cffrVHjY^MEvL4eybQmm6P3O}eorSldi|c?UcvT1(D>aS6K>{5EO)Hfj9c^GAL&zCEH`XoW71&~y1_bNO~ADnGSqb-q=|r+x5n-}iSDmkmGiCT-E>nk;k|6FuGE&;aJE$wv;lkanpRVOE0GO83KVew_Fx1` z97bq>c>vI)RazIMof`fQ-UazK>;muwk7sHBk&O3R?e-xa{RLaO-aMi4kuy>Il9Yq3 ztl@~bIMEcp_#)fidOrS}gXxFr;alb&2Od>+9btQ0<;^`{z-HQ#_nl%5t+kD#|xz`q0!z$+11K6A5kAp^sPH1ZBJ^#H+nI4cW-Z9 zqQ{>k_nWOt?51wzP7`br<#fN^x}*?)T+j|Y8_#(0h_VgkoNd>^@5qv@HAggAx)a$8 zt?uepFlQ5fq9|=M(Z6iYMe$sLzv^H6DSM7^Q{Nf|mW{H69&&@OA_L8x{@Kd@i8OUP(J0nD_cN#3ehOmc%YR%KzQ4TMzO}+RyLRLwi1nEjoGbiHsNg{8pRqjTiHy) z+e|cyHSTF;_YmH{iAJ%;-L33y!mB14#TwIF*>u9&L^O&uZf|9`6W&Io@mS-wR(2cV zZ6F%O8n?ExTM17l8pRp~t*n6X))S3ljlZ?BzY(5~XcTKqXk`-!j}eVxjl5QtM|fVM zQLHhxm5n95*N8^3hO?D93GWr6QLJ%OE4zvCUM3pF8lzg-D8hS*XcTLVY-J+}Z!OU% z*0`>fT}ODUiAJ%;uvRvV@LnVu#Tr9f*-*lJfoK$K3~6OU2=95KQLHhjl?@`iXOYHZ zjRCD}0O3^{t*js6ts)x58X2uDgYZ@ojbe=}TGkKNl5Md~b{CIdCu|1gE2q=`%RW zE>3Yet}xx>c(oAkVg9*2|f zge`HjHsnEC8}igdWtY~5Iw-NWAr(4^JYAP->iI=fK}pL#dtH+0W>X*d;1GFo#wpu^ z;Y6*oLI2@7;sit zoKO&bjNV%kC&TPI!u|X)m`k{1#M=YP6JZqT&O~<^qLddWx*tD=yEWxO?+2bNFHS&y zCsO^VBI-|tozx@h-=64xFj#-VAM58){f8pz7Y3Bs5%s4gmdrdhkPSSUaqqE3hwa6Q zB{Pm?NXJe!jA&`dZ1lpHu*lD%20QViV(dD?UCi+^-FJn7rHQaLI@b%aMO_)>F5y*wD@+y9zk5u5=5Uo2+utm^$e7_xR7Gnwa z+)=3_A*25>xJ!T)LQv$a4M&Gpe#7qx8gy9|hk*sYzv;JK_cyJH()Ks4iGp~FK7YZY z-y+Mjzv+ckj6(q9p+Cil*dtZ7Uj{?`nJ|iV`B|Q%kr@YmCKmGQ4b4VSw;{2x_~|vN z4Rad3CVcLD*I}Tpwm%cQX3**kn7`I^tBmTo#O^E7SLO+xq%_ZxB#f3|XUe8;(8m<( zO#4S>PzNiFjQxKo?|%E}5`C3@ zd9`Oyxl1qII7fgTGPQBUHD10y>55da!&dmcRb;1n{p4EgF*@O#rLh1vcvUOhU>jc``sF#o;38b_0+3F^*l)j+JoaC z8vSl6n^SJrXV1}r9PwRC&nEj4{)i5@mBp8;Qs^fL#bE1A%SHPGY^kYQQ zY4d2)VpH5z%>CXWXf->^P#&eXM;Xh8B@XqZ`=VlW_QcAl==99=>?ol;s#{rgV%eG~ zNC4m+mcG)SUN$Vf&o}+erp=z)ByC!3GcA6Jxkq7paHK%nBk>r&%NqfE43BsMURm{Kmf9T#Z#pEi(AynGOj(f$ z83zQ&!QgX5+Z{2rj&4my0?_|*dsMbpx53t{zb(~g*XN~m*bK4c^*~t zLsurXeV%`SeV%`C?H#4<^E@i=``qw8&)>s7&)>WDjOwz_^Pi7*3GuoAPxzdp@wtD| zzD4{0l+U3>doRo9Uf^@DYwu8v&!KsHBKX_`eC~1W8QO(UeCNI1VA2myjbHrT@@iAk z@_XFlkq4aZ$R=N2P?rt<@?v25+49Qh988?8?~qqpmIgWnjJbm!os zrs#2%;1f;BPH-iau04nDzPq&28+C8CzS66+?`bg!CQEwm6D#{A+?%8q9@8n;9D!Hv z*DSu^>ZWrx9U48lblk{{jW=YTn3Gt&;hXBj(Q}7xye@Tob(*K?#{l3)!*>?oDGtnh z>nTUZk&SQIR*6T7#Um!;#vP;YuJ)znc-J`q1Z>W3=;qv7Hrk$GT0h#J?)BbnAJc7< z0k?qFg)cn`mxvCFqrU{oa(2U3nB8>aSGzty##gQ^y?0b}ng{2X!JmzLX;4Gl5#Zq^ z=$ms7@p?nb#!)?kyvD7mUJq5$Jm}1!jLUoc?wo{)Q}4Cth3kM2HMsTO=f+>X5eZ2y%r(OGf590TVLhLy%=zO0%hSRqHMk)cOM9@>9c^fpKjZ> zyMeE%>%Z0w?7P$M?rec!-jFn=xUD6|)$_!YiDeaKwV671E7H?)(latZlvC}OmXFP7 zYDupi8kXChpxgunHeMCZ26V;mwWO!tZM9#FahU*VX+>Q(_nt@`_|j>J(Da(-=kxSM z_7Rp$6?P{ zjQiCVQ?I44mX*Hb2~=a8LmX>c3R#rNV6x^r^IiGn(|jhQX^QfE%Yw$y*@gOSr;uNn zUpuWO|J1baYGP*VUCyBx9lw~MtY{f*@-gD)_u_mb-4QbQP4l3LCwtkcQCh#5zvbL(al4U9QsXv3@7}C~H>BXlEI2 zjm{JDEKMk6U*29=Z{YWh;*taxyD-NE-{uH-3*t%e`Ao{+TPX8|^mKyLt1E2LJ6%s_ z6}330BxJjmX5H4J+}4uqTGr^Do9#{a?H%gwhwWy&sF6)x9Cm#f+X@?LFr zxoTXlEiTtqmrLm#MC73vYai>v1hVf zyX-9nr?V8skLS>p5^C^M8xj8T&2)K20{^}Y{^|AesQbV+K@(w`#(3kTF01E3$LtTQ z40scPZ@t@#&6%;8cG$doNGwm%8V3o9lmRWi2E2Kc7ba90M#PTj3sDQbJV7r-tVzI^ zG4Q(7kN_!G?)VT7hbHh>CjJihcX&;`Bcb5#4^4)uXisc;CZ_7zxNdS?G+gvRLO05% z>B~sMYjJQ*Yp*B)auwkxW`rf*reTS}Rl1<*UQmdib2K{p72%OA~_c;j6XS&kXFRnvuY-P6FO& zzwb$*=PbYjJ!g^asZ>Uupp_9PX#Af+{>jFH$J7rU!vCGeCXGk+uFLQUJ6Q&Kl&g@( zpPPY4Ife5mj|sLv<_|ndHqN8u(8{QzIgj!uAs!t%{5Va0Afe0N;rBPAUK<8yI|qX= z%7}6tYaWGeFz?_ ze}QAz{sxalJcbNAcr0BF$B=ZE$`47{)Swj~oRG1O&V{1**|I z6m-u04-_r*n(LoBB_vw)%CM-^R5*sMC`p`n+(Q#5JeDek)bb=w8yv@L@=Sr_1g(5Z zg8RW{UvXklH~9QyA=vE1h2wx7^%IcoL`oM1B;>D8f_qkTQ8!O?P4vPlZsj0ftN`i0 z*c6a275ge)D-Vp2(TJ%a=(HSwkb1!z=A}X%8gqfOd1yLH&6#l}{AP>rt;{fN=6o++ zO}1*|W7P)Wbr=9_K}=47Z?<+vU57*_XX9E zxT%(WltY&diTw0-Lx;88V2YK$%rTjmt-0Q$J{f0<6%BF9i-%xhtX>bcYl>B+9C?0@ zcriy@WoPUVXuQ-aILCA#6!HtqmUk=d-BF`|dStg%YY#m=O|Q~J*6UbQ!JY#+>>~Pe zXYp0;=kR{q>D|^GtE@Z(A9`<{$#Vv{=DMH3OE?+%8qnS)a3}1e2}<=k=hj!t#l`w~e)S8mkKS1SOgs!AMH%uFFoHl*Kg!XT&B z-}WKj>bp5C{gB!m;#k@qVpr`1UeSJ|95)?c@hJBqqX|DiE-daVR|%}=p@O7C5ctlH zmScY+X{qy}8EHQ#yL@Ag={Gp8&Y2hc@}Poi4=CNhfMj2MZ&SSh2XDliqL(Sa(@Qyp zj&6mHS%vn}+6Fk7T@W}3(^Fw_Z}nNMdwoIw1Ilj)!9dklwaV(YcnRQBrMIvPy&sbQ zlXKg%e_{VU2(kvrgQbx0P08}6oWe03(m^is?2Nv^JM7Iqz}!=0y?XDx&AgwTKG^ha zXbq3~4~mz5WJeFmYc;v4_h5(WL2mNU9Pv76RD2T~mA2)uj)T(9kla*XNN&LIvViw|UBfNj-Kw$~6HO*093FEyR!mB+fe*Yued{AB) z#G8hAuOnXn9C0w>{hRPMn=-cMkTvyQI-`WJ$}2Pe-28{5HmI z;-UC1ahvV~aGaN`0nZQ|Gd>_ggP&>N7MV{c4;Tdt_V7xQ3oCJd_+tQz;PZ#Zc5ab;`#r+Sst?~I?gk@?-Fz27+ZZ5!O6i_ ztk=Zn;u=Shq25sR6r@6k)OjI>9buZhw z`-Nq?$~+ycslc-YxYu;2V&TU9QKlX+L`Jc90;G93hc}9Y)27F&PsC5&x!dPlW9oj} zz@*#DO@_hU(>(uKXRXXzlQyj4LSXHYmL9sbjV%+`rmcZ6C)-|H?aLm&p=ysQ4m!II zVp^Z`1vpF7&axXz>kU5Vi}0q8w+ys)Yym8N@nb0`d@PSC7;)-`VIxl^Sapt*`$487 zhQm*$ae!H`PU%ImhddXZ`+fcJrV(WS4{&xMa*!wvz$4P_XcOiChqbqlZ>q@t$I~=T z)AS~^rIb>NG-)psEZC$V1yM;_OF>XtwSv04Byvk(*Ilf2@p+q|eGpuspaP=iMz53# zwh7oKfl41Blmb5B6YC?W5k*}=9z-bQcg~rc@}QsH&-e5C{nOr@nftyoXJ*cshco9S zn8E*$%4~hyY<vP?T5q;4 zalGIA|G>lDm>GDu-vuUS?a`rFWa3(6BC<}k{uTZ7l?`!<>CT4YwL=SG%iIclGJ6`{ zvKG9`eR_7i<2nS`6wO7Soj<9_s!|Nivrb<-w5j~%e-p!qL!b?K^92?OrVGzfuFNiE z)M8{*3@}P4Kt^2+d|J^9U6b(%P>@0Q3grVL43nl}5B(cXwWYbNBguVI`V;#p_sP&7 z*-yDohW@~Q%6&3)7yBvq$1AFFo2!( z`2eR@F(b`sT`Lt6Mffa2d0v>xD4#|s>x3erz+_&2>jnM>M)^2Gd66#!3T*3vpoTLX z&*^<;>q{^=xlhg3jb}xN8yU<+mv#=weWn_ECborZXkPitKX|*UBI7asawg*FM1AI6lquGdWdbj4a{SXDN&R3GVmZyJqFb?^BT=FvmUuKXEEpFd${g zaCByxA3tP*Ia3Q{5ESN-sqkR7<7|<)42Wnj_$8r&L+TBY<>1u=?E9nKMb|?sA>jA{ z?xHE|SsDk=7WN#j|LS5PE1Yh+;M5!WwrEfOLGwg$JZaR}J7aUh(D$ zjfqdb8d&YtD%Li*r-3}j-6wl@36#O}=sqY$&N6rt%HkM_pirWQZmYA~vI*qmv#S-} z>5BNep-mvc!K-@rkS=k2?Uh zxW!($15 z#(xJqlM)iFF)$HY{B=}gj8lB`NoRecQ*3|I={dUhXK~w?;@16QpiA_h`T59~^-&-1 z@9p9r`m;3}7RyoA;_cRG;Ig&2!x{}-wib6^4Won2USO;v199?a}O%;lp&IA@s1BR+SYBKzk`t&JR4~3BiaX>H?1A$aH z;K^>U3Z2nMgpBYJaE1>Lu&ZG6WAQgpjVinN=0f{|M7!9&(C%s4+s~GBu}8lAc>jW^ z-Y%))&zIuXtpK6qEZPp}{7F5g!RUs~Wen6*C&H7ns6#q_d4h)$I{BY3Pw1qCF5&3q z39wxUP~D+_U!HJ?5_SkbUY@Xm5_X2ZzdV7JD##zXJb{&Jm-OxB39M8-{Nc+JSgCeP zq017S20eWp2axoY9*!{7a)V2=B9GE5{g3=Bvnsy4rhLJdL-UGi!xlR=H|?jB9N=3? z4mcE+yL!k$&1{_i&AGQ^>|WpfC>~Gg#SJ}sXiH?d8olDvwk7|me%S|N*52Ii3~yH! ztgv-FfGa670NViCAIHSlBw!$hM7 zKlqa#X{l4DY{6gu*6$r?-O*=O@@{1oSd6&9O{ z1A<=MIJ(-VEZMosF5;$Kag4iJGuxR~?y26>^eQ!b!K_)>(EL04vdzhtG;=^!dRoxRq5}6d!?cVg;Pu*SG_DOm7oomwC_R@W*x1tW}&}55Mt_wP>8u! z6~lQH1cw8ki$J$VbpT2Bzu2r#c|6{#9NXY}$7)HkDz92=vswmPmAPE3)sk#gnyi*V zRyZlOq*#@CR?FbIMHvm9Ne|UK!BJsMy@?%kL0Bk|NM*44bJ~cDP5L^+YE$i+KMq}d zV|n@fy-cocf@u#5QdaQtb`^{2AcfD<*Xz1@%rmIrtK9UMH3TtC8S%ghFil2D

XUe~I$HECNHk`wmbdME)H*fnP_^kp(w76BH(yp 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%0DITB0)XcmVB47rZOoS4(%)QuTil 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

^-T_407^lAQ|VGF&t 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 Date: Mon, 17 Jul 2023 18:11:53 +0200 Subject: [PATCH 201/364] Add BlackMagic and Fix EP name --- .../apps_data/esp_flasher/Black_Magic.bin | Bin 0 -> 925024 bytes .../{Evil Portal.bin => Evil_Portal.bin} | Bin 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 assets/resources/apps_data/esp_flasher/Black_Magic.bin rename assets/resources/apps_data/esp_flasher/{Evil Portal.bin => Evil_Portal.bin} (100%) diff --git a/assets/resources/apps_data/esp_flasher/Black_Magic.bin b/assets/resources/apps_data/esp_flasher/Black_Magic.bin new file mode 100644 index 0000000000000000000000000000000000000000..337fbe60320a1bcac2e47f68d2da3eb57ba68ec3 GIT binary patch literal 925024 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 z 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�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 O!&U?o`VIOdEITo(3>K664Ut*3Ws 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 literal 0 HcmV?d00001 diff --git a/assets/resources/apps_data/esp_flasher/Evil Portal.bin b/assets/resources/apps_data/esp_flasher/Evil_Portal.bin similarity index 100% rename from assets/resources/apps_data/esp_flasher/Evil Portal.bin rename to assets/resources/apps_data/esp_flasher/Evil_Portal.bin From 1b095c3f90dbe4b07047a1f0d2cebc338707bf88 Mon Sep 17 00:00:00 2001 From: ClaraCrazy Date: Mon, 17 Jul 2023 18:34:13 +0200 Subject: [PATCH 202/364] Add Evil Portal --- .../external/evil_portal/application.fam | 12 ++ .../external/evil_portal/evil_portal_app.c | 119 +++++++++++++ .../external/evil_portal/evil_portal_app.h | 11 ++ .../external/evil_portal/evil_portal_app_i.h | 60 +++++++ .../evil_portal/evil_portal_custom_event.h | 8 + .../external/evil_portal/evil_portal_uart.c | 147 ++++++++++++++++ .../external/evil_portal/evil_portal_uart.h | 14 ++ .../evil_portal/helpers/evil_portal_storage.c | 118 +++++++++++++ .../evil_portal/helpers/evil_portal_storage.h | 17 ++ .../evil_portal/icons/evil_portal_10px.png | Bin 0 -> 1781 bytes .../evil_portal/scenes/evil_portal_scene.c | 31 ++++ .../evil_portal/scenes/evil_portal_scene.h | 31 ++++ .../scenes/evil_portal_scene_config.h | 2 + .../scenes/evil_portal_scene_console_output.c | 157 +++++++++++++++++ .../scenes/evil_portal_scene_start.c | 158 ++++++++++++++++++ .../apps_data/evil_portal/ap.config.txt | 1 + .../apps_data/evil_portal/index.html | 1 + 17 files changed, 887 insertions(+) create mode 100644 applications/external/evil_portal/application.fam create mode 100644 applications/external/evil_portal/evil_portal_app.c create mode 100644 applications/external/evil_portal/evil_portal_app.h create mode 100644 applications/external/evil_portal/evil_portal_app_i.h create mode 100644 applications/external/evil_portal/evil_portal_custom_event.h create mode 100644 applications/external/evil_portal/evil_portal_uart.c create mode 100644 applications/external/evil_portal/evil_portal_uart.h create mode 100644 applications/external/evil_portal/helpers/evil_portal_storage.c create mode 100644 applications/external/evil_portal/helpers/evil_portal_storage.h create mode 100644 applications/external/evil_portal/icons/evil_portal_10px.png create mode 100644 applications/external/evil_portal/scenes/evil_portal_scene.c create mode 100644 applications/external/evil_portal/scenes/evil_portal_scene.h create mode 100644 applications/external/evil_portal/scenes/evil_portal_scene_config.h create mode 100644 applications/external/evil_portal/scenes/evil_portal_scene_console_output.c create mode 100644 applications/external/evil_portal/scenes/evil_portal_scene_start.c create mode 100644 assets/resources/apps_data/evil_portal/ap.config.txt create mode 100644 assets/resources/apps_data/evil_portal/index.html diff --git a/applications/external/evil_portal/application.fam b/applications/external/evil_portal/application.fam new file mode 100644 index 000000000..b9936fe04 --- /dev/null +++ b/applications/external/evil_portal/application.fam @@ -0,0 +1,12 @@ +App( + appid="evil_portal", + name="[ESP32] Evil Portal", + apptype=FlipperAppType.EXTERNAL, + entry_point="evil_portal_app", + cdefines=["APP_EVIL_PORTAL"], + requires=["gui"], + stack_size=1 * 1024, + order=90, + fap_icon="icons/evil_portal_10px.png", + fap_category="GPIO", +) diff --git a/applications/external/evil_portal/evil_portal_app.c b/applications/external/evil_portal/evil_portal_app.c new file mode 100644 index 000000000..cce26c9ba --- /dev/null +++ b/applications/external/evil_portal/evil_portal_app.c @@ -0,0 +1,119 @@ +#include "evil_portal_app_i.h" +#include "helpers/evil_portal_storage.h" + +#include +#include + +static bool evil_portal_app_custom_event_callback(void *context, + uint32_t event) { + furi_assert(context); + Evil_PortalApp *app = context; + return scene_manager_handle_custom_event(app->scene_manager, event); +} + +static bool evil_portal_app_back_event_callback(void *context) { + furi_assert(context); + Evil_PortalApp *app = context; + return scene_manager_handle_back_event(app->scene_manager); +} + +static void evil_portal_app_tick_event_callback(void *context) { + furi_assert(context); + Evil_PortalApp *app = context; + scene_manager_handle_tick_event(app->scene_manager); +} + +Evil_PortalApp *evil_portal_app_alloc() { + Evil_PortalApp *app = malloc(sizeof(Evil_PortalApp)); + + app->sent_html = false; + app->sent_ap = false; + app->sent_reset = false; + app->has_command_queue = false; + app->command_index = 0; + app->portal_logs = furi_string_alloc(); + + app->gui = furi_record_open(RECORD_GUI); + + app->view_dispatcher = view_dispatcher_alloc(); + app->scene_manager = scene_manager_alloc(&evil_portal_scene_handlers, app); + view_dispatcher_enable_queue(app->view_dispatcher); + view_dispatcher_set_event_callback_context(app->view_dispatcher, app); + + view_dispatcher_set_custom_event_callback( + app->view_dispatcher, evil_portal_app_custom_event_callback); + view_dispatcher_set_navigation_event_callback( + app->view_dispatcher, evil_portal_app_back_event_callback); + view_dispatcher_set_tick_event_callback( + app->view_dispatcher, evil_portal_app_tick_event_callback, 100); + + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, + ViewDispatcherTypeFullscreen); + + app->var_item_list = variable_item_list_alloc(); + view_dispatcher_add_view(app->view_dispatcher, Evil_PortalAppViewVarItemList, + variable_item_list_get_view(app->var_item_list)); + + for (int i = 0; i < NUM_MENU_ITEMS; ++i) { + app->selected_option_index[i] = 0; + } + + app->text_box = text_box_alloc(); + view_dispatcher_add_view(app->view_dispatcher, + Evil_PortalAppViewConsoleOutput, + text_box_get_view(app->text_box)); + app->text_box_store = furi_string_alloc(); + furi_string_reserve(app->text_box_store, EVIL_PORTAL_TEXT_BOX_STORE_SIZE); + + scene_manager_next_scene(app->scene_manager, Evil_PortalSceneStart); + + return app; +} + +void evil_portal_app_free(Evil_PortalApp *app) { + + // save latest logs + if (furi_string_utf8_length(app->portal_logs) > 0) { + write_logs(app->portal_logs); + furi_string_free(app->portal_logs); + } + + // Send reset event to dev board + evil_portal_uart_tx((uint8_t *)(RESET_CMD), strlen(RESET_CMD)); + evil_portal_uart_tx((uint8_t *)("\n"), 1); + + furi_assert(app); + + // Views + view_dispatcher_remove_view(app->view_dispatcher, + Evil_PortalAppViewVarItemList); + view_dispatcher_remove_view(app->view_dispatcher, + Evil_PortalAppViewConsoleOutput); + + text_box_free(app->text_box); + furi_string_free(app->text_box_store); + + // View dispatcher + view_dispatcher_free(app->view_dispatcher); + scene_manager_free(app->scene_manager); + + evil_portal_uart_free(app->uart); + + // Close records + furi_record_close(RECORD_GUI); + + free(app); +} + +int32_t evil_portal_app(void *p) { + UNUSED(p); + Evil_PortalApp *evil_portal_app = evil_portal_app_alloc(); + + evil_portal_app->uart = evil_portal_uart_init(evil_portal_app); + + view_dispatcher_run(evil_portal_app->view_dispatcher); + + evil_portal_app_free(evil_portal_app); + + return 0; +} diff --git a/applications/external/evil_portal/evil_portal_app.h b/applications/external/evil_portal/evil_portal_app.h new file mode 100644 index 000000000..65c047ea5 --- /dev/null +++ b/applications/external/evil_portal/evil_portal_app.h @@ -0,0 +1,11 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct Evil_PortalApp Evil_PortalApp; + +#ifdef __cplusplus +} +#endif diff --git a/applications/external/evil_portal/evil_portal_app_i.h b/applications/external/evil_portal/evil_portal_app_i.h new file mode 100644 index 000000000..a5b67b072 --- /dev/null +++ b/applications/external/evil_portal/evil_portal_app_i.h @@ -0,0 +1,60 @@ +#pragma once + +#include "evil_portal_app.h" +#include "evil_portal_custom_event.h" +#include "evil_portal_uart.h" +#include "scenes/evil_portal_scene.h" + +#include +#include +#include +#include +#include + +#define NUM_MENU_ITEMS (4) + +#define EVIL_PORTAL_TEXT_BOX_STORE_SIZE (4096) +#define UART_CH (FuriHalUartIdUSART1) + +#define SET_HTML_CMD "sethtml" +#define SET_AP_CMD "setap" +#define RESET_CMD "reset" + +struct Evil_PortalApp { + Gui *gui; + ViewDispatcher *view_dispatcher; + SceneManager *scene_manager; + + FuriString* portal_logs; + const char *command_queue[1]; + int command_index; + bool has_command_queue; + + FuriString *text_box_store; + size_t text_box_store_strlen; + TextBox *text_box; + + VariableItemList *var_item_list; + Evil_PortalUart *uart; + + int selected_menu_index; + int selected_option_index[NUM_MENU_ITEMS]; + const char *selected_tx_string; + bool is_command; + bool is_custom_tx_string; + bool focus_console_start; + bool show_stopscan_tip; + bool sent_ap; + bool sent_html; + bool sent_reset; + int BAUDRATE; + + uint8_t *index_html; + uint8_t *ap_name; +}; + +typedef enum { + Evil_PortalAppViewVarItemList, + Evil_PortalAppViewConsoleOutput, + Evil_PortalAppViewStartPortal, +} Evil_PortalAppView; diff --git a/applications/external/evil_portal/evil_portal_custom_event.h b/applications/external/evil_portal/evil_portal_custom_event.h new file mode 100644 index 000000000..dfcdadfa9 --- /dev/null +++ b/applications/external/evil_portal/evil_portal_custom_event.h @@ -0,0 +1,8 @@ +#pragma once + +typedef enum { + Evil_PortalEventRefreshConsoleOutput = 0, + Evil_PortalEventStartConsole, + Evil_PortalEventStartKeyboard, + Evil_PortalEventStartPortal, +} Evil_PortalCustomEvent; diff --git a/applications/external/evil_portal/evil_portal_uart.c b/applications/external/evil_portal/evil_portal_uart.c new file mode 100644 index 000000000..57848cabc --- /dev/null +++ b/applications/external/evil_portal/evil_portal_uart.c @@ -0,0 +1,147 @@ +#include "evil_portal_app_i.h" +#include "evil_portal_uart.h" +#include "helpers/evil_portal_storage.h" + +struct Evil_PortalUart { + Evil_PortalApp *app; + FuriThread *rx_thread; + FuriStreamBuffer *rx_stream; + uint8_t rx_buf[RX_BUF_SIZE + 1]; + void (*handle_rx_data_cb)(uint8_t *buf, size_t len, void *context); +}; + +typedef enum { + WorkerEvtStop = (1 << 0), + WorkerEvtRxDone = (1 << 1), +} WorkerEvtFlags; + +void evil_portal_uart_set_handle_rx_data_cb( + Evil_PortalUart *uart, + void (*handle_rx_data_cb)(uint8_t *buf, size_t len, void *context)) { + furi_assert(uart); + uart->handle_rx_data_cb = handle_rx_data_cb; +} + +#define WORKER_ALL_RX_EVENTS (WorkerEvtStop | WorkerEvtRxDone) + +void evil_portal_uart_on_irq_cb(UartIrqEvent ev, uint8_t data, void *context) { + Evil_PortalUart *uart = (Evil_PortalUart *)context; + + if (ev == UartIrqEventRXNE) { + furi_stream_buffer_send(uart->rx_stream, &data, 1, 0); + furi_thread_flags_set(furi_thread_get_id(uart->rx_thread), WorkerEvtRxDone); + } +} + +static int32_t uart_worker(void *context) { + Evil_PortalUart *uart = (void *)context; + + while (1) { + + uint32_t events = furi_thread_flags_wait(WORKER_ALL_RX_EVENTS, + FuriFlagWaitAny, FuriWaitForever); + furi_check((events & FuriFlagError) == 0); + if (events & WorkerEvtStop) + break; + if (events & WorkerEvtRxDone) { + size_t len = furi_stream_buffer_receive(uart->rx_stream, uart->rx_buf, + RX_BUF_SIZE, 0); + + if (len > 0) { + if (uart->handle_rx_data_cb) { + uart->handle_rx_data_cb(uart->rx_buf, len, uart->app); + + if (uart->app->has_command_queue) { + if (uart->app->command_index < 1) { + if (0 == + strncmp(SET_AP_CMD, + uart->app->command_queue[uart->app->command_index], + strlen(SET_AP_CMD))) { + FuriString *out_data = furi_string_alloc(); + + furi_string_cat(out_data, "setap="); + furi_string_cat(out_data, (char *)uart->app->ap_name); + + evil_portal_uart_tx((uint8_t *)(furi_string_get_cstr(out_data)), + strlen(furi_string_get_cstr(out_data))); + evil_portal_uart_tx((uint8_t *)("\n"), 1); + + uart->app->sent_ap = true; + + free(out_data); + free(uart->app->ap_name); + } + + uart->app->command_index = 0; + uart->app->has_command_queue = false; + uart->app->command_queue[0] = ""; + } + } + + if (uart->app->sent_reset == false) { + furi_string_cat(uart->app->portal_logs, (char *)uart->rx_buf); + } + + if (furi_string_utf8_length(uart->app->portal_logs) > 4000) { + write_logs(uart->app->portal_logs); + furi_string_reset(uart->app->portal_logs); + } + } else { + uart->rx_buf[len] = '\0'; + if (uart->app->sent_reset == false) { + furi_string_cat(uart->app->portal_logs, (char *)uart->rx_buf); + } + + if (furi_string_utf8_length(uart->app->portal_logs) > 4000) { + write_logs(uart->app->portal_logs); + furi_string_reset(uart->app->portal_logs); + } + } + } + } + } + + furi_stream_buffer_free(uart->rx_stream); + + return 0; +} + +void evil_portal_uart_tx(uint8_t *data, size_t len) { + furi_hal_uart_tx(UART_CH, data, len); +} + +Evil_PortalUart *evil_portal_uart_init(Evil_PortalApp *app) { + Evil_PortalUart *uart = malloc(sizeof(Evil_PortalUart)); + uart->app = app; + // Init all rx stream and thread early to avoid crashes + uart->rx_stream = furi_stream_buffer_alloc(RX_BUF_SIZE, 1); + uart->rx_thread = furi_thread_alloc(); + furi_thread_set_name(uart->rx_thread, "Evil_PortalUartRxThread"); + furi_thread_set_stack_size(uart->rx_thread, 1024); + furi_thread_set_context(uart->rx_thread, uart); + furi_thread_set_callback(uart->rx_thread, uart_worker); + + furi_thread_start(uart->rx_thread); + + furi_hal_console_disable(); + if (app->BAUDRATE == 0) { + app->BAUDRATE = 115200; + } + furi_hal_uart_set_br(UART_CH, app->BAUDRATE); + furi_hal_uart_set_irq_cb(UART_CH, evil_portal_uart_on_irq_cb, uart); + + return uart; +} + +void evil_portal_uart_free(Evil_PortalUart *uart) { + furi_assert(uart); + + furi_thread_flags_set(furi_thread_get_id(uart->rx_thread), WorkerEvtStop); + furi_thread_join(uart->rx_thread); + furi_thread_free(uart->rx_thread); + + furi_hal_uart_set_irq_cb(UART_CH, NULL, NULL); + furi_hal_console_enable(); + + free(uart); +} diff --git a/applications/external/evil_portal/evil_portal_uart.h b/applications/external/evil_portal/evil_portal_uart.h new file mode 100644 index 000000000..97e1a46fa --- /dev/null +++ b/applications/external/evil_portal/evil_portal_uart.h @@ -0,0 +1,14 @@ +#pragma once + +#include "furi_hal.h" + +#define RX_BUF_SIZE (320) + +typedef struct Evil_PortalUart Evil_PortalUart; + +void evil_portal_uart_set_handle_rx_data_cb( + Evil_PortalUart *uart, + void (*handle_rx_data_cb)(uint8_t *buf, size_t len, void *context)); +void evil_portal_uart_tx(uint8_t *data, size_t len); +Evil_PortalUart *evil_portal_uart_init(Evil_PortalApp *app); +void evil_portal_uart_free(Evil_PortalUart *uart); diff --git a/applications/external/evil_portal/helpers/evil_portal_storage.c b/applications/external/evil_portal/helpers/evil_portal_storage.c new file mode 100644 index 000000000..e31c8c8de --- /dev/null +++ b/applications/external/evil_portal/helpers/evil_portal_storage.c @@ -0,0 +1,118 @@ +#include "evil_portal_storage.h" + +static Storage *evil_portal_open_storage() { + return furi_record_open(RECORD_STORAGE); +} + +static void evil_portal_close_storage() { furi_record_close(RECORD_STORAGE); } + +void evil_portal_read_index_html(void *context) { + + Evil_PortalApp *app = context; + Storage *storage = evil_portal_open_storage(); + FileInfo fi; + + if (storage_common_stat(storage, EVIL_PORTAL_INDEX_SAVE_PATH, &fi) == + FSE_OK) { + File *index_html = storage_file_alloc(storage); + if (storage_file_open(index_html, EVIL_PORTAL_INDEX_SAVE_PATH, FSAM_READ, + FSOM_OPEN_EXISTING)) { + app->index_html = malloc((size_t)fi.size); + uint8_t *buf_ptr = app->index_html; + size_t read = 0; + while (read < fi.size) { + size_t to_read = fi.size - read; + if (to_read > UINT16_MAX) + to_read = UINT16_MAX; + uint16_t now_read = + storage_file_read(index_html, buf_ptr, (uint16_t)to_read); + read += now_read; + buf_ptr += now_read; + } + free(buf_ptr); + } + storage_file_close(index_html); + storage_file_free(index_html); + } else { + char *html_error = + "Evil portal
Unable to read the html file.
" + "Is the SD Card set up correctly?
See instructions @ " + "github.com/bigbrodude6119/flipper-zero-evil-portal
" + "Under the 'Install pre-built app on the flipper' section."; + app->index_html = (uint8_t *)html_error; + } + + evil_portal_close_storage(); +} + +void evil_portal_read_ap_name(void *context) { + Evil_PortalApp *app = context; + Storage *storage = evil_portal_open_storage(); + FileInfo fi; + + if (storage_common_stat(storage, EVIL_PORTAL_AP_SAVE_PATH, &fi) == FSE_OK) { + File *ap_name = storage_file_alloc(storage); + if (storage_file_open(ap_name, EVIL_PORTAL_AP_SAVE_PATH, FSAM_READ, + FSOM_OPEN_EXISTING)) { + app->ap_name = malloc((size_t)fi.size); + uint8_t *buf_ptr = app->ap_name; + size_t read = 0; + while (read < fi.size) { + size_t to_read = fi.size - read; + if (to_read > UINT16_MAX) + to_read = UINT16_MAX; + uint16_t now_read = + storage_file_read(ap_name, buf_ptr, (uint16_t)to_read); + read += now_read; + buf_ptr += now_read; + } + free(buf_ptr); + } + storage_file_close(ap_name); + storage_file_free(ap_name); + } else { + char *app_default = "Evil Portal"; + app->ap_name = (uint8_t *)app_default; + } + evil_portal_close_storage(); +} + +char *sequential_file_resolve_path(Storage *storage, const char *dir, + const char *prefix, const char *extension) { + if (storage == NULL || dir == NULL || prefix == NULL || extension == NULL) { + return NULL; + } + + char file_path[256]; + int file_index = 0; + + do { + if (snprintf(file_path, sizeof(file_path), "%s/%s_%d.%s", dir, prefix, + file_index, extension) < 0) { + return NULL; + } + file_index++; + } while (storage_file_exists(storage, file_path)); + + return strdup(file_path); +} + +void write_logs(FuriString *portal_logs) { + Storage *storage = evil_portal_open_storage(); + + if (!storage_file_exists(storage, EVIL_PORTAL_LOG_SAVE_PATH)) { + storage_simply_mkdir(storage, EVIL_PORTAL_LOG_SAVE_PATH); + } + + char *seq_file_path = sequential_file_resolve_path( + storage, EVIL_PORTAL_LOG_SAVE_PATH, "log", "txt"); + + File *file = storage_file_alloc(storage); + + if (storage_file_open(file, seq_file_path, FSAM_WRITE, FSOM_CREATE_ALWAYS)) { + storage_file_write(file, furi_string_get_cstr(portal_logs), furi_string_utf8_length(portal_logs)); + } + storage_file_close(file); + storage_file_free(file); + evil_portal_close_storage(); +} \ No newline at end of file diff --git a/applications/external/evil_portal/helpers/evil_portal_storage.h b/applications/external/evil_portal/helpers/evil_portal_storage.h new file mode 100644 index 000000000..74ace6acc --- /dev/null +++ b/applications/external/evil_portal/helpers/evil_portal_storage.h @@ -0,0 +1,17 @@ +#include "../evil_portal_app_i.h" +#include +#include +#include +#include +#include + +#define PORTAL_FILE_DIRECTORY_PATH EXT_PATH("apps_data/evil_portal") +#define EVIL_PORTAL_INDEX_SAVE_PATH PORTAL_FILE_DIRECTORY_PATH "/index.html" +#define EVIL_PORTAL_AP_SAVE_PATH PORTAL_FILE_DIRECTORY_PATH "/ap.config.txt" +#define EVIL_PORTAL_LOG_SAVE_PATH PORTAL_FILE_DIRECTORY_PATH "/logs" + +void evil_portal_read_index_html(void *context); +void evil_portal_read_ap_name(void *context); +void write_logs(FuriString* portal_logs); +char *sequential_file_resolve_path(Storage *storage, const char *dir, + const char *prefix, const char *extension); diff --git a/applications/external/evil_portal/icons/evil_portal_10px.png b/applications/external/evil_portal/icons/evil_portal_10px.png new file mode 100644 index 0000000000000000000000000000000000000000..c13534660e188305fba6c5625ec1ce6841e238f0 GIT binary patch literal 1781 zcmcIl&u`pB6gH`Z%BmFygoHpsmU98Y_~+VQo4O&%2C@S^in|uYPoPg;=lp^061{vuK;mH0)!Ap1aEfjZ9#3COD(NEb)27p@I<-_@3^l{^ zJWV$>(?ozk<#ATw8OqAlhM~(!QY3km@JtqrIO0cDpn%lH9@2bCo0YXs;FvbUxn`)k z*i!=x!_%Q@x?Xi*{6@+a~SXQkz!SWHWcsb$^(6;>hKME;X z2F#C&@!TFtG&W`_aF>8J=K6AfvtYR5MW6F=ld0V%qHJ2KEx&Lj$ta(eFA7EV0@lS9 z3lxzxq6WkS3up+1Kyii72Ie%0kIw=PL%)}m70w9jbpr)NsYyb8 zJMsvNA`daU;~>vzcTnUo15vl_+OF=#%yQ$(Q&5MYCuiK3ViGt#NsF`|a;%_4zM2aoS%6{;vOnh}dV&+$;la51vmz|(PT2kC7> z4aZ`UqccUm8+VhBc@c67?plgWi68SK#ZW&o;wry~ZWrzC#?D|uW^alzJy;PgX_OM8&`5>HN- zezwFvLg}@wWQ4OJgU+cbVl$d(F^i;V|IgRmd^a_-4%IBOS=Kge;NjAc%S|z1Y*Ei7 z7`aZzLY~=nQ76_TL}+YM(=~`2JBzsEq*1US(oT;!*LqKTY!*Y$6~kq>hspB1U>R(+ zfYV^|L-!V(Bpbc%djFYEuYc5QN%Dnr&;9Yw)#snQ{?-RkcJOWM!}Gms%3q(obL%Er ziEn@L-j7dhz5Vr9H}KQ1Uj1|b*V}jgzWLMVKMy{+^U80llJ&7Tn0i|~-ETJDeg9wV C&oCwc literal 0 HcmV?d00001 diff --git a/applications/external/evil_portal/scenes/evil_portal_scene.c b/applications/external/evil_portal/scenes/evil_portal_scene.c new file mode 100644 index 000000000..4573642e4 --- /dev/null +++ b/applications/external/evil_portal/scenes/evil_portal_scene.c @@ -0,0 +1,31 @@ +#include "evil_portal_scene.h" + +// Generate scene on_enter handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, +void (*const evil_portal_scene_on_enter_handlers[])(void *) = { +#include "evil_portal_scene_config.h" +}; +#undef ADD_SCENE + +// Generate scene on_event handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event, +bool (*const evil_portal_scene_on_event_handlers[])(void *context, + SceneManagerEvent event) = { +#include "evil_portal_scene_config.h" +}; +#undef ADD_SCENE + +// Generate scene on_exit handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit, +void (*const evil_portal_scene_on_exit_handlers[])(void *context) = { +#include "evil_portal_scene_config.h" +}; +#undef ADD_SCENE + +// Initialize scene handlers configuration structure +const SceneManagerHandlers evil_portal_scene_handlers = { + .on_enter_handlers = evil_portal_scene_on_enter_handlers, + .on_event_handlers = evil_portal_scene_on_event_handlers, + .on_exit_handlers = evil_portal_scene_on_exit_handlers, + .scene_num = Evil_PortalSceneNum, +}; diff --git a/applications/external/evil_portal/scenes/evil_portal_scene.h b/applications/external/evil_portal/scenes/evil_portal_scene.h new file mode 100644 index 000000000..59a8c711e --- /dev/null +++ b/applications/external/evil_portal/scenes/evil_portal_scene.h @@ -0,0 +1,31 @@ +#pragma once + +#include + +// Generate scene id and total number +#define ADD_SCENE(prefix, name, id) Evil_PortalScene##id, +typedef enum { +#include "evil_portal_scene_config.h" + Evil_PortalSceneNum, +} Evil_PortalScene; +#undef ADD_SCENE + +extern const SceneManagerHandlers evil_portal_scene_handlers; + +// Generate scene on_enter handlers declaration +#define ADD_SCENE(prefix, name, id) \ + void prefix##_scene_##name##_on_enter(void *); +#include "evil_portal_scene_config.h" +#undef ADD_SCENE + +// Generate scene on_event handlers declaration +#define ADD_SCENE(prefix, name, id) \ + bool prefix##_scene_##name##_on_event(void *context, SceneManagerEvent event); +#include "evil_portal_scene_config.h" +#undef ADD_SCENE + +// Generate scene on_exit handlers declaration +#define ADD_SCENE(prefix, name, id) \ + void prefix##_scene_##name##_on_exit(void *context); +#include "evil_portal_scene_config.h" +#undef ADD_SCENE diff --git a/applications/external/evil_portal/scenes/evil_portal_scene_config.h b/applications/external/evil_portal/scenes/evil_portal_scene_config.h new file mode 100644 index 000000000..94b09ae46 --- /dev/null +++ b/applications/external/evil_portal/scenes/evil_portal_scene_config.h @@ -0,0 +1,2 @@ +ADD_SCENE(evil_portal, start, Start) +ADD_SCENE(evil_portal, console_output, ConsoleOutput) diff --git a/applications/external/evil_portal/scenes/evil_portal_scene_console_output.c b/applications/external/evil_portal/scenes/evil_portal_scene_console_output.c new file mode 100644 index 000000000..b81da3116 --- /dev/null +++ b/applications/external/evil_portal/scenes/evil_portal_scene_console_output.c @@ -0,0 +1,157 @@ +#include "../evil_portal_app_i.h" +#include "../helpers/evil_portal_storage.h" + +void evil_portal_console_output_handle_rx_data_cb(uint8_t *buf, size_t len, + void *context) { + furi_assert(context); + Evil_PortalApp *app = context; + + // If text box store gets too big, then truncate it + app->text_box_store_strlen += len; + if (app->text_box_store_strlen >= EVIL_PORTAL_TEXT_BOX_STORE_SIZE - 1) { + furi_string_right(app->text_box_store, app->text_box_store_strlen / 2); + app->text_box_store_strlen = furi_string_size(app->text_box_store) + len; + } + + // Null-terminate buf and append to text box store + buf[len] = '\0'; + furi_string_cat_printf(app->text_box_store, "%s", buf); + + view_dispatcher_send_custom_event(app->view_dispatcher, + Evil_PortalEventRefreshConsoleOutput); +} + +void evil_portal_scene_console_output_on_enter(void *context) { + Evil_PortalApp *app = context; + + TextBox *text_box = app->text_box; + text_box_reset(app->text_box); + text_box_set_font(text_box, TextBoxFontText); + if (app->focus_console_start) { + text_box_set_focus(text_box, TextBoxFocusStart); + } else { + text_box_set_focus(text_box, TextBoxFocusEnd); + } + + if (app->is_command) { + furi_string_reset(app->text_box_store); + app->text_box_store_strlen = 0; + app->sent_reset = false; + + if (0 == strncmp("help", app->selected_tx_string, strlen("help"))) { + const char *help_msg = + "BLUE = Waiting\nGREEN = Good\nRED = Bad\n\nThis project is a " + "WIP.\ngithub.com/bigbrodude6119/flipper-zero-evil-portal\n\n" + "Version 0.0.2\n\n"; + furi_string_cat_str(app->text_box_store, help_msg); + app->text_box_store_strlen += strlen(help_msg); + if (app->show_stopscan_tip) { + const char *msg = "Press BACK to return\n"; + furi_string_cat_str(app->text_box_store, msg); + app->text_box_store_strlen += strlen(msg); + } + } + + if (0 == strncmp("savelogs", app->selected_tx_string, strlen("savelogs"))) { + const char *help_msg = "Logs saved.\n\n"; + furi_string_cat_str(app->text_box_store, help_msg); + app->text_box_store_strlen += strlen(help_msg); + write_logs(app->portal_logs); + furi_string_reset(app->portal_logs); + if (app->show_stopscan_tip) { + const char *msg = "Press BACK to return\n"; + furi_string_cat_str(app->text_box_store, msg); + app->text_box_store_strlen += strlen(msg); + } + } + + if (0 == + strncmp(SET_HTML_CMD, app->selected_tx_string, strlen(SET_HTML_CMD))) { + app->command_queue[0] = SET_AP_CMD; + app->has_command_queue = true; + app->command_index = 0; + if (app->show_stopscan_tip) { + const char *msg = + "Starting portal\nIf no response press\nBACK to return\n"; + furi_string_cat_str(app->text_box_store, msg); + app->text_box_store_strlen += strlen(msg); + } + } + + if (0 == strncmp(RESET_CMD, app->selected_tx_string, strlen(RESET_CMD))) { + app->sent_reset = true; + if (app->show_stopscan_tip) { + const char *msg = "Reseting portal\nPress BACK to return\n\n\n\n"; + furi_string_cat_str(app->text_box_store, msg); + app->text_box_store_strlen += strlen(msg); + } + } + } + + text_box_set_text(app->text_box, furi_string_get_cstr(app->text_box_store)); + + scene_manager_set_scene_state(app->scene_manager, + Evil_PortalSceneConsoleOutput, 0); + view_dispatcher_switch_to_view(app->view_dispatcher, + Evil_PortalAppViewConsoleOutput); + + // Register callback to receive data + evil_portal_uart_set_handle_rx_data_cb( + app->uart, evil_portal_console_output_handle_rx_data_cb); + + if (app->is_command && app->selected_tx_string) { + if (0 == + strncmp(SET_HTML_CMD, app->selected_tx_string, strlen(SET_HTML_CMD))) { + evil_portal_read_index_html(context); + + FuriString *data = furi_string_alloc(); + furi_string_cat(data, "sethtml="); + furi_string_cat(data, (char *)app->index_html); + + evil_portal_uart_tx((uint8_t *)(furi_string_get_cstr(data)), + strlen(furi_string_get_cstr(data))); + evil_portal_uart_tx((uint8_t *)("\n"), 1); + + app->sent_html = true; + + free(data); + free(app->index_html); + + evil_portal_read_ap_name(context); + } else if (0 == + strncmp(RESET_CMD, app->selected_tx_string, strlen(RESET_CMD))) { + app->sent_html = false; + app->sent_ap = false; + evil_portal_uart_tx((uint8_t *)(app->selected_tx_string), + strlen(app->selected_tx_string)); + evil_portal_uart_tx((uint8_t *)("\n"), 1); + } else if (1 == strncmp("help", app->selected_tx_string, strlen("help"))) { + evil_portal_uart_tx((uint8_t *)(app->selected_tx_string), + strlen(app->selected_tx_string)); + evil_portal_uart_tx((uint8_t *)("\n"), 1); + } + } +} + +bool evil_portal_scene_console_output_on_event(void *context, + SceneManagerEvent event) { + Evil_PortalApp *app = context; + + bool consumed = false; + + if (event.type == SceneManagerEventTypeCustom) { + text_box_set_text(app->text_box, furi_string_get_cstr(app->text_box_store)); + consumed = true; + } else if (event.type == SceneManagerEventTypeTick) { + consumed = true; + } + + return consumed; +} + +void evil_portal_scene_console_output_on_exit(void *context) { + Evil_PortalApp *app = context; + + // Unregister rx callback + evil_portal_uart_set_handle_rx_data_cb(app->uart, NULL); +} diff --git a/applications/external/evil_portal/scenes/evil_portal_scene_start.c b/applications/external/evil_portal/scenes/evil_portal_scene_start.c new file mode 100644 index 000000000..6de5af083 --- /dev/null +++ b/applications/external/evil_portal/scenes/evil_portal_scene_start.c @@ -0,0 +1,158 @@ +#include "../evil_portal_app_i.h" + +// For each command, define whether additional arguments are needed +// (enabling text input to fill them out), and whether the console +// text box should focus at the start of the output or the end +typedef enum { NO_ARGS = 0, INPUT_ARGS, TOGGLE_ARGS } InputArgs; + +typedef enum { + FOCUS_CONSOLE_END = 0, + FOCUS_CONSOLE_START, + FOCUS_CONSOLE_TOGGLE +} FocusConsole; + +#define SHOW_STOPSCAN_TIP (true) +#define NO_TIP (false) + +#define MAX_OPTIONS (9) +typedef struct { + const char *item_string; + const char *options_menu[MAX_OPTIONS]; + int num_options_menu; + const char *actual_commands[MAX_OPTIONS]; + InputArgs needs_keyboard; + FocusConsole focus_console; + bool show_stopscan_tip; +} Evil_PortalItem; + +// NUM_MENU_ITEMS defined in evil_portal_app_i.h - if you add an entry here, +// increment it! +const Evil_PortalItem items[NUM_MENU_ITEMS] = { + // send command + {"Start portal", + {""}, + 1, + {SET_HTML_CMD}, + NO_ARGS, + FOCUS_CONSOLE_END, + SHOW_STOPSCAN_TIP}, + + // stop portal + {"Stop portal", {""}, 1, {RESET_CMD}, NO_ARGS, FOCUS_CONSOLE_START, SHOW_STOPSCAN_TIP}, + + // console + {"Save logs", + {""}, + 1, + {"savelogs"}, + NO_ARGS, + FOCUS_CONSOLE_START, + SHOW_STOPSCAN_TIP}, + + // help + {"Help", + {""}, + 1, + {"help"}, + NO_ARGS, + FOCUS_CONSOLE_START, + SHOW_STOPSCAN_TIP}, +}; + +static void evil_portal_scene_start_var_list_enter_callback(void *context, + uint32_t index) { + furi_assert(context); + Evil_PortalApp *app = context; + + furi_assert(index < NUM_MENU_ITEMS); + const Evil_PortalItem *item = &items[index]; + + const int selected_option_index = app->selected_option_index[index]; + furi_assert(selected_option_index < item->num_options_menu); + app->selected_tx_string = item->actual_commands[selected_option_index]; + app->is_command = true; + app->is_custom_tx_string = false; + app->selected_menu_index = index; + app->focus_console_start = (item->focus_console == FOCUS_CONSOLE_TOGGLE) + ? (selected_option_index == 0) + : item->focus_console; + app->show_stopscan_tip = item->show_stopscan_tip; + + view_dispatcher_send_custom_event(app->view_dispatcher, + Evil_PortalEventStartConsole); +} + +static void +evil_portal_scene_start_var_list_change_callback(VariableItem *item) { + furi_assert(item); + + Evil_PortalApp *app = variable_item_get_context(item); + furi_assert(app); + + const Evil_PortalItem *menu_item = &items[app->selected_menu_index]; + uint8_t item_index = variable_item_get_current_value_index(item); + furi_assert(item_index < menu_item->num_options_menu); + variable_item_set_current_value_text(item, + menu_item->options_menu[item_index]); + app->selected_option_index[app->selected_menu_index] = item_index; +} + +void evil_portal_scene_start_on_enter(void *context) { + Evil_PortalApp *app = context; + VariableItemList *var_item_list = app->var_item_list; + + variable_item_list_set_enter_callback( + var_item_list, evil_portal_scene_start_var_list_enter_callback, app); + + VariableItem *item; + for (int i = 0; i < NUM_MENU_ITEMS; ++i) { + item = variable_item_list_add( + var_item_list, items[i].item_string, items[i].num_options_menu, + evil_portal_scene_start_var_list_change_callback, app); + variable_item_set_current_value_index(item, app->selected_option_index[i]); + variable_item_set_current_value_text( + item, items[i].options_menu[app->selected_option_index[i]]); + } + + variable_item_list_set_selected_item( + var_item_list, + scene_manager_get_scene_state(app->scene_manager, Evil_PortalSceneStart)); + + view_dispatcher_switch_to_view(app->view_dispatcher, + Evil_PortalAppViewVarItemList); +} + +bool evil_portal_scene_start_on_event(void *context, SceneManagerEvent event) { + UNUSED(context); + Evil_PortalApp *app = context; + bool consumed = false; + + if (event.type == SceneManagerEventTypeCustom) { + if (event.event == Evil_PortalEventStartPortal) { + scene_manager_set_scene_state(app->scene_manager, Evil_PortalSceneStart, + app->selected_menu_index); + scene_manager_next_scene(app->scene_manager, + Evil_PortalAppViewStartPortal); + } else if (event.event == Evil_PortalEventStartKeyboard) { + scene_manager_set_scene_state(app->scene_manager, Evil_PortalSceneStart, + app->selected_menu_index); + } else if (event.event == Evil_PortalEventStartConsole) { + scene_manager_set_scene_state(app->scene_manager, Evil_PortalSceneStart, + app->selected_menu_index); + scene_manager_next_scene(app->scene_manager, + Evil_PortalAppViewConsoleOutput); + } + consumed = true; + } else if (event.type == SceneManagerEventTypeTick) { + app->selected_menu_index = + variable_item_list_get_selected_item_index(app->var_item_list); + consumed = true; + } + + return consumed; +} + +void evil_portal_scene_start_on_exit(void *context) { + Evil_PortalApp *app = context; + variable_item_list_reset(app->var_item_list); +} diff --git a/assets/resources/apps_data/evil_portal/ap.config.txt b/assets/resources/apps_data/evil_portal/ap.config.txt new file mode 100644 index 000000000..a8d23beee --- /dev/null +++ b/assets/resources/apps_data/evil_portal/ap.config.txt @@ -0,0 +1 @@ +Google Free WiFi \ No newline at end of file diff --git a/assets/resources/apps_data/evil_portal/index.html b/assets/resources/apps_data/evil_portal/index.html new file mode 100644 index 000000000..41f674991 --- /dev/null +++ b/assets/resources/apps_data/evil_portal/index.html @@ -0,0 +1 @@ +

\ No newline at end of file From 5988521cc88bab1482ddb7a22f23337aceaec881 Mon Sep 17 00:00:00 2001 From: ClaraCrazy Date: Mon, 17 Jul 2023 18:36:51 +0200 Subject: [PATCH 203/364] <:Nami2:939038794794020874> --- .../external/evil_portal/application.fam | 2 +- .../external/evil_portal/evil_portal_app.c | 156 +++++----- .../external/evil_portal/evil_portal_app_i.h | 56 ++-- .../evil_portal/evil_portal_custom_event.h | 8 +- .../external/evil_portal/evil_portal_uart.c | 238 ++++++++-------- .../external/evil_portal/evil_portal_uart.h | 10 +- .../evil_portal/helpers/evil_portal_storage.c | 189 +++++++------ .../evil_portal/helpers/evil_portal_storage.h | 11 +- .../evil_portal/scenes/evil_portal_scene.c | 7 +- .../evil_portal/scenes/evil_portal_scene.h | 12 +- .../scenes/evil_portal_scene_console_output.c | 266 +++++++++--------- .../scenes/evil_portal_scene_start.c | 200 ++++++------- 12 files changed, 555 insertions(+), 600 deletions(-) diff --git a/applications/external/evil_portal/application.fam b/applications/external/evil_portal/application.fam index b9936fe04..83a7adbf8 100644 --- a/applications/external/evil_portal/application.fam +++ b/applications/external/evil_portal/application.fam @@ -8,5 +8,5 @@ App( stack_size=1 * 1024, order=90, fap_icon="icons/evil_portal_10px.png", - fap_category="GPIO", + fap_category="GPIO", ) diff --git a/applications/external/evil_portal/evil_portal_app.c b/applications/external/evil_portal/evil_portal_app.c index cce26c9ba..121533900 100644 --- a/applications/external/evil_portal/evil_portal_app.c +++ b/applications/external/evil_portal/evil_portal_app.c @@ -4,116 +4,112 @@ #include #include -static bool evil_portal_app_custom_event_callback(void *context, - uint32_t event) { - furi_assert(context); - Evil_PortalApp *app = context; - return scene_manager_handle_custom_event(app->scene_manager, event); +static bool evil_portal_app_custom_event_callback(void* context, uint32_t event) { + furi_assert(context); + Evil_PortalApp* app = context; + return scene_manager_handle_custom_event(app->scene_manager, event); } -static bool evil_portal_app_back_event_callback(void *context) { - furi_assert(context); - Evil_PortalApp *app = context; - return scene_manager_handle_back_event(app->scene_manager); +static bool evil_portal_app_back_event_callback(void* context) { + furi_assert(context); + Evil_PortalApp* app = context; + return scene_manager_handle_back_event(app->scene_manager); } -static void evil_portal_app_tick_event_callback(void *context) { - furi_assert(context); - Evil_PortalApp *app = context; - scene_manager_handle_tick_event(app->scene_manager); +static void evil_portal_app_tick_event_callback(void* context) { + furi_assert(context); + Evil_PortalApp* app = context; + scene_manager_handle_tick_event(app->scene_manager); } -Evil_PortalApp *evil_portal_app_alloc() { - Evil_PortalApp *app = malloc(sizeof(Evil_PortalApp)); +Evil_PortalApp* evil_portal_app_alloc() { + Evil_PortalApp* app = malloc(sizeof(Evil_PortalApp)); - app->sent_html = false; - app->sent_ap = false; - app->sent_reset = false; - app->has_command_queue = false; - app->command_index = 0; - app->portal_logs = furi_string_alloc(); + app->sent_html = false; + app->sent_ap = false; + app->sent_reset = false; + app->has_command_queue = false; + app->command_index = 0; + app->portal_logs = furi_string_alloc(); - app->gui = furi_record_open(RECORD_GUI); + app->gui = furi_record_open(RECORD_GUI); - app->view_dispatcher = view_dispatcher_alloc(); - app->scene_manager = scene_manager_alloc(&evil_portal_scene_handlers, app); - view_dispatcher_enable_queue(app->view_dispatcher); - view_dispatcher_set_event_callback_context(app->view_dispatcher, app); + app->view_dispatcher = view_dispatcher_alloc(); + app->scene_manager = scene_manager_alloc(&evil_portal_scene_handlers, app); + view_dispatcher_enable_queue(app->view_dispatcher); + view_dispatcher_set_event_callback_context(app->view_dispatcher, app); - view_dispatcher_set_custom_event_callback( - app->view_dispatcher, evil_portal_app_custom_event_callback); - view_dispatcher_set_navigation_event_callback( - app->view_dispatcher, evil_portal_app_back_event_callback); - view_dispatcher_set_tick_event_callback( - app->view_dispatcher, evil_portal_app_tick_event_callback, 100); + view_dispatcher_set_custom_event_callback( + app->view_dispatcher, evil_portal_app_custom_event_callback); + view_dispatcher_set_navigation_event_callback( + app->view_dispatcher, evil_portal_app_back_event_callback); + view_dispatcher_set_tick_event_callback( + app->view_dispatcher, evil_portal_app_tick_event_callback, 100); - view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, - ViewDispatcherTypeFullscreen); + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); - app->var_item_list = variable_item_list_alloc(); - view_dispatcher_add_view(app->view_dispatcher, Evil_PortalAppViewVarItemList, - variable_item_list_get_view(app->var_item_list)); + app->var_item_list = variable_item_list_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + Evil_PortalAppViewVarItemList, + variable_item_list_get_view(app->var_item_list)); - for (int i = 0; i < NUM_MENU_ITEMS; ++i) { - app->selected_option_index[i] = 0; - } + for(int i = 0; i < NUM_MENU_ITEMS; ++i) { + app->selected_option_index[i] = 0; + } - app->text_box = text_box_alloc(); - view_dispatcher_add_view(app->view_dispatcher, - Evil_PortalAppViewConsoleOutput, - text_box_get_view(app->text_box)); - app->text_box_store = furi_string_alloc(); - furi_string_reserve(app->text_box_store, EVIL_PORTAL_TEXT_BOX_STORE_SIZE); + app->text_box = text_box_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, Evil_PortalAppViewConsoleOutput, text_box_get_view(app->text_box)); + app->text_box_store = furi_string_alloc(); + furi_string_reserve(app->text_box_store, EVIL_PORTAL_TEXT_BOX_STORE_SIZE); - scene_manager_next_scene(app->scene_manager, Evil_PortalSceneStart); + scene_manager_next_scene(app->scene_manager, Evil_PortalSceneStart); - return app; + return app; } -void evil_portal_app_free(Evil_PortalApp *app) { +void evil_portal_app_free(Evil_PortalApp* app) { + // save latest logs + if(furi_string_utf8_length(app->portal_logs) > 0) { + write_logs(app->portal_logs); + furi_string_free(app->portal_logs); + } - // save latest logs - if (furi_string_utf8_length(app->portal_logs) > 0) { - write_logs(app->portal_logs); - furi_string_free(app->portal_logs); - } + // Send reset event to dev board + evil_portal_uart_tx((uint8_t*)(RESET_CMD), strlen(RESET_CMD)); + evil_portal_uart_tx((uint8_t*)("\n"), 1); - // Send reset event to dev board - evil_portal_uart_tx((uint8_t *)(RESET_CMD), strlen(RESET_CMD)); - evil_portal_uart_tx((uint8_t *)("\n"), 1); + furi_assert(app); - furi_assert(app); + // Views + view_dispatcher_remove_view(app->view_dispatcher, Evil_PortalAppViewVarItemList); + view_dispatcher_remove_view(app->view_dispatcher, Evil_PortalAppViewConsoleOutput); - // Views - view_dispatcher_remove_view(app->view_dispatcher, - Evil_PortalAppViewVarItemList); - view_dispatcher_remove_view(app->view_dispatcher, - Evil_PortalAppViewConsoleOutput); + text_box_free(app->text_box); + furi_string_free(app->text_box_store); - text_box_free(app->text_box); - furi_string_free(app->text_box_store); + // View dispatcher + view_dispatcher_free(app->view_dispatcher); + scene_manager_free(app->scene_manager); - // View dispatcher - view_dispatcher_free(app->view_dispatcher); - scene_manager_free(app->scene_manager); + evil_portal_uart_free(app->uart); - evil_portal_uart_free(app->uart); + // Close records + furi_record_close(RECORD_GUI); - // Close records - furi_record_close(RECORD_GUI); - - free(app); + free(app); } -int32_t evil_portal_app(void *p) { - UNUSED(p); - Evil_PortalApp *evil_portal_app = evil_portal_app_alloc(); +int32_t evil_portal_app(void* p) { + UNUSED(p); + Evil_PortalApp* evil_portal_app = evil_portal_app_alloc(); - evil_portal_app->uart = evil_portal_uart_init(evil_portal_app); + evil_portal_app->uart = evil_portal_uart_init(evil_portal_app); - view_dispatcher_run(evil_portal_app->view_dispatcher); + view_dispatcher_run(evil_portal_app->view_dispatcher); - evil_portal_app_free(evil_portal_app); + evil_portal_app_free(evil_portal_app); - return 0; + return 0; } diff --git a/applications/external/evil_portal/evil_portal_app_i.h b/applications/external/evil_portal/evil_portal_app_i.h index a5b67b072..949b64ce6 100644 --- a/applications/external/evil_portal/evil_portal_app_i.h +++ b/applications/external/evil_portal/evil_portal_app_i.h @@ -21,40 +21,40 @@ #define RESET_CMD "reset" struct Evil_PortalApp { - Gui *gui; - ViewDispatcher *view_dispatcher; - SceneManager *scene_manager; + Gui* gui; + ViewDispatcher* view_dispatcher; + SceneManager* scene_manager; - FuriString* portal_logs; - const char *command_queue[1]; - int command_index; - bool has_command_queue; + FuriString* portal_logs; + const char* command_queue[1]; + int command_index; + bool has_command_queue; - FuriString *text_box_store; - size_t text_box_store_strlen; - TextBox *text_box; + FuriString* text_box_store; + size_t text_box_store_strlen; + TextBox* text_box; - VariableItemList *var_item_list; - Evil_PortalUart *uart; + VariableItemList* var_item_list; + Evil_PortalUart* uart; - int selected_menu_index; - int selected_option_index[NUM_MENU_ITEMS]; - const char *selected_tx_string; - bool is_command; - bool is_custom_tx_string; - bool focus_console_start; - bool show_stopscan_tip; - bool sent_ap; - bool sent_html; - bool sent_reset; - int BAUDRATE; + int selected_menu_index; + int selected_option_index[NUM_MENU_ITEMS]; + const char* selected_tx_string; + bool is_command; + bool is_custom_tx_string; + bool focus_console_start; + bool show_stopscan_tip; + bool sent_ap; + bool sent_html; + bool sent_reset; + int BAUDRATE; - uint8_t *index_html; - uint8_t *ap_name; + uint8_t* index_html; + uint8_t* ap_name; }; typedef enum { - Evil_PortalAppViewVarItemList, - Evil_PortalAppViewConsoleOutput, - Evil_PortalAppViewStartPortal, + Evil_PortalAppViewVarItemList, + Evil_PortalAppViewConsoleOutput, + Evil_PortalAppViewStartPortal, } Evil_PortalAppView; diff --git a/applications/external/evil_portal/evil_portal_custom_event.h b/applications/external/evil_portal/evil_portal_custom_event.h index dfcdadfa9..a566ca62e 100644 --- a/applications/external/evil_portal/evil_portal_custom_event.h +++ b/applications/external/evil_portal/evil_portal_custom_event.h @@ -1,8 +1,8 @@ #pragma once typedef enum { - Evil_PortalEventRefreshConsoleOutput = 0, - Evil_PortalEventStartConsole, - Evil_PortalEventStartKeyboard, - Evil_PortalEventStartPortal, + Evil_PortalEventRefreshConsoleOutput = 0, + Evil_PortalEventStartConsole, + Evil_PortalEventStartKeyboard, + Evil_PortalEventStartPortal, } Evil_PortalCustomEvent; diff --git a/applications/external/evil_portal/evil_portal_uart.c b/applications/external/evil_portal/evil_portal_uart.c index 57848cabc..2698a4410 100644 --- a/applications/external/evil_portal/evil_portal_uart.c +++ b/applications/external/evil_portal/evil_portal_uart.c @@ -3,145 +3,143 @@ #include "helpers/evil_portal_storage.h" struct Evil_PortalUart { - Evil_PortalApp *app; - FuriThread *rx_thread; - FuriStreamBuffer *rx_stream; - uint8_t rx_buf[RX_BUF_SIZE + 1]; - void (*handle_rx_data_cb)(uint8_t *buf, size_t len, void *context); + Evil_PortalApp* app; + FuriThread* rx_thread; + FuriStreamBuffer* rx_stream; + uint8_t rx_buf[RX_BUF_SIZE + 1]; + void (*handle_rx_data_cb)(uint8_t* buf, size_t len, void* context); }; typedef enum { - WorkerEvtStop = (1 << 0), - WorkerEvtRxDone = (1 << 1), + WorkerEvtStop = (1 << 0), + WorkerEvtRxDone = (1 << 1), } WorkerEvtFlags; void evil_portal_uart_set_handle_rx_data_cb( - Evil_PortalUart *uart, - void (*handle_rx_data_cb)(uint8_t *buf, size_t len, void *context)) { - furi_assert(uart); - uart->handle_rx_data_cb = handle_rx_data_cb; + Evil_PortalUart* uart, + void (*handle_rx_data_cb)(uint8_t* buf, size_t len, void* context)) { + furi_assert(uart); + uart->handle_rx_data_cb = handle_rx_data_cb; } #define WORKER_ALL_RX_EVENTS (WorkerEvtStop | WorkerEvtRxDone) -void evil_portal_uart_on_irq_cb(UartIrqEvent ev, uint8_t data, void *context) { - Evil_PortalUart *uart = (Evil_PortalUart *)context; +void evil_portal_uart_on_irq_cb(UartIrqEvent ev, uint8_t data, void* context) { + Evil_PortalUart* uart = (Evil_PortalUart*)context; - if (ev == UartIrqEventRXNE) { - furi_stream_buffer_send(uart->rx_stream, &data, 1, 0); - furi_thread_flags_set(furi_thread_get_id(uart->rx_thread), WorkerEvtRxDone); - } -} - -static int32_t uart_worker(void *context) { - Evil_PortalUart *uart = (void *)context; - - while (1) { - - uint32_t events = furi_thread_flags_wait(WORKER_ALL_RX_EVENTS, - FuriFlagWaitAny, FuriWaitForever); - furi_check((events & FuriFlagError) == 0); - if (events & WorkerEvtStop) - break; - if (events & WorkerEvtRxDone) { - size_t len = furi_stream_buffer_receive(uart->rx_stream, uart->rx_buf, - RX_BUF_SIZE, 0); - - if (len > 0) { - if (uart->handle_rx_data_cb) { - uart->handle_rx_data_cb(uart->rx_buf, len, uart->app); - - if (uart->app->has_command_queue) { - if (uart->app->command_index < 1) { - if (0 == - strncmp(SET_AP_CMD, - uart->app->command_queue[uart->app->command_index], - strlen(SET_AP_CMD))) { - FuriString *out_data = furi_string_alloc(); - - furi_string_cat(out_data, "setap="); - furi_string_cat(out_data, (char *)uart->app->ap_name); - - evil_portal_uart_tx((uint8_t *)(furi_string_get_cstr(out_data)), - strlen(furi_string_get_cstr(out_data))); - evil_portal_uart_tx((uint8_t *)("\n"), 1); - - uart->app->sent_ap = true; - - free(out_data); - free(uart->app->ap_name); - } - - uart->app->command_index = 0; - uart->app->has_command_queue = false; - uart->app->command_queue[0] = ""; - } - } - - if (uart->app->sent_reset == false) { - furi_string_cat(uart->app->portal_logs, (char *)uart->rx_buf); - } - - if (furi_string_utf8_length(uart->app->portal_logs) > 4000) { - write_logs(uart->app->portal_logs); - furi_string_reset(uart->app->portal_logs); - } - } else { - uart->rx_buf[len] = '\0'; - if (uart->app->sent_reset == false) { - furi_string_cat(uart->app->portal_logs, (char *)uart->rx_buf); - } - - if (furi_string_utf8_length(uart->app->portal_logs) > 4000) { - write_logs(uart->app->portal_logs); - furi_string_reset(uart->app->portal_logs); - } - } - } + if(ev == UartIrqEventRXNE) { + furi_stream_buffer_send(uart->rx_stream, &data, 1, 0); + furi_thread_flags_set(furi_thread_get_id(uart->rx_thread), WorkerEvtRxDone); } - } - - furi_stream_buffer_free(uart->rx_stream); - - return 0; } -void evil_portal_uart_tx(uint8_t *data, size_t len) { - furi_hal_uart_tx(UART_CH, data, len); +static int32_t uart_worker(void* context) { + Evil_PortalUart* uart = (void*)context; + + while(1) { + uint32_t events = + furi_thread_flags_wait(WORKER_ALL_RX_EVENTS, FuriFlagWaitAny, FuriWaitForever); + furi_check((events & FuriFlagError) == 0); + if(events & WorkerEvtStop) break; + if(events & WorkerEvtRxDone) { + size_t len = furi_stream_buffer_receive(uart->rx_stream, uart->rx_buf, RX_BUF_SIZE, 0); + + if(len > 0) { + if(uart->handle_rx_data_cb) { + uart->handle_rx_data_cb(uart->rx_buf, len, uart->app); + + if(uart->app->has_command_queue) { + if(uart->app->command_index < 1) { + if(0 == strncmp( + SET_AP_CMD, + uart->app->command_queue[uart->app->command_index], + strlen(SET_AP_CMD))) { + FuriString* out_data = furi_string_alloc(); + + furi_string_cat(out_data, "setap="); + furi_string_cat(out_data, (char*)uart->app->ap_name); + + evil_portal_uart_tx( + (uint8_t*)(furi_string_get_cstr(out_data)), + strlen(furi_string_get_cstr(out_data))); + evil_portal_uart_tx((uint8_t*)("\n"), 1); + + uart->app->sent_ap = true; + + free(out_data); + free(uart->app->ap_name); + } + + uart->app->command_index = 0; + uart->app->has_command_queue = false; + uart->app->command_queue[0] = ""; + } + } + + if(uart->app->sent_reset == false) { + furi_string_cat(uart->app->portal_logs, (char*)uart->rx_buf); + } + + if(furi_string_utf8_length(uart->app->portal_logs) > 4000) { + write_logs(uart->app->portal_logs); + furi_string_reset(uart->app->portal_logs); + } + } else { + uart->rx_buf[len] = '\0'; + if(uart->app->sent_reset == false) { + furi_string_cat(uart->app->portal_logs, (char*)uart->rx_buf); + } + + if(furi_string_utf8_length(uart->app->portal_logs) > 4000) { + write_logs(uart->app->portal_logs); + furi_string_reset(uart->app->portal_logs); + } + } + } + } + } + + furi_stream_buffer_free(uart->rx_stream); + + return 0; } -Evil_PortalUart *evil_portal_uart_init(Evil_PortalApp *app) { - Evil_PortalUart *uart = malloc(sizeof(Evil_PortalUart)); - uart->app = app; - // Init all rx stream and thread early to avoid crashes - uart->rx_stream = furi_stream_buffer_alloc(RX_BUF_SIZE, 1); - uart->rx_thread = furi_thread_alloc(); - furi_thread_set_name(uart->rx_thread, "Evil_PortalUartRxThread"); - furi_thread_set_stack_size(uart->rx_thread, 1024); - furi_thread_set_context(uart->rx_thread, uart); - furi_thread_set_callback(uart->rx_thread, uart_worker); - - furi_thread_start(uart->rx_thread); - - furi_hal_console_disable(); - if (app->BAUDRATE == 0) { - app->BAUDRATE = 115200; - } - furi_hal_uart_set_br(UART_CH, app->BAUDRATE); - furi_hal_uart_set_irq_cb(UART_CH, evil_portal_uart_on_irq_cb, uart); - - return uart; +void evil_portal_uart_tx(uint8_t* data, size_t len) { + furi_hal_uart_tx(UART_CH, data, len); } -void evil_portal_uart_free(Evil_PortalUart *uart) { - furi_assert(uart); +Evil_PortalUart* evil_portal_uart_init(Evil_PortalApp* app) { + Evil_PortalUart* uart = malloc(sizeof(Evil_PortalUart)); + uart->app = app; + // Init all rx stream and thread early to avoid crashes + uart->rx_stream = furi_stream_buffer_alloc(RX_BUF_SIZE, 1); + uart->rx_thread = furi_thread_alloc(); + furi_thread_set_name(uart->rx_thread, "Evil_PortalUartRxThread"); + furi_thread_set_stack_size(uart->rx_thread, 1024); + furi_thread_set_context(uart->rx_thread, uart); + furi_thread_set_callback(uart->rx_thread, uart_worker); - furi_thread_flags_set(furi_thread_get_id(uart->rx_thread), WorkerEvtStop); - furi_thread_join(uart->rx_thread); - furi_thread_free(uart->rx_thread); + furi_thread_start(uart->rx_thread); - furi_hal_uart_set_irq_cb(UART_CH, NULL, NULL); - furi_hal_console_enable(); + furi_hal_console_disable(); + if(app->BAUDRATE == 0) { + app->BAUDRATE = 115200; + } + furi_hal_uart_set_br(UART_CH, app->BAUDRATE); + furi_hal_uart_set_irq_cb(UART_CH, evil_portal_uart_on_irq_cb, uart); - free(uart); + return uart; +} + +void evil_portal_uart_free(Evil_PortalUart* uart) { + furi_assert(uart); + + furi_thread_flags_set(furi_thread_get_id(uart->rx_thread), WorkerEvtStop); + furi_thread_join(uart->rx_thread); + furi_thread_free(uart->rx_thread); + + furi_hal_uart_set_irq_cb(UART_CH, NULL, NULL); + furi_hal_console_enable(); + + free(uart); } diff --git a/applications/external/evil_portal/evil_portal_uart.h b/applications/external/evil_portal/evil_portal_uart.h index 97e1a46fa..d7980a8e2 100644 --- a/applications/external/evil_portal/evil_portal_uart.h +++ b/applications/external/evil_portal/evil_portal_uart.h @@ -7,8 +7,8 @@ typedef struct Evil_PortalUart Evil_PortalUart; void evil_portal_uart_set_handle_rx_data_cb( - Evil_PortalUart *uart, - void (*handle_rx_data_cb)(uint8_t *buf, size_t len, void *context)); -void evil_portal_uart_tx(uint8_t *data, size_t len); -Evil_PortalUart *evil_portal_uart_init(Evil_PortalApp *app); -void evil_portal_uart_free(Evil_PortalUart *uart); + Evil_PortalUart* uart, + void (*handle_rx_data_cb)(uint8_t* buf, size_t len, void* context)); +void evil_portal_uart_tx(uint8_t* data, size_t len); +Evil_PortalUart* evil_portal_uart_init(Evil_PortalApp* app); +void evil_portal_uart_free(Evil_PortalUart* uart); diff --git a/applications/external/evil_portal/helpers/evil_portal_storage.c b/applications/external/evil_portal/helpers/evil_portal_storage.c index e31c8c8de..b28c97afa 100644 --- a/applications/external/evil_portal/helpers/evil_portal_storage.c +++ b/applications/external/evil_portal/helpers/evil_portal_storage.c @@ -1,118 +1,117 @@ #include "evil_portal_storage.h" -static Storage *evil_portal_open_storage() { - return furi_record_open(RECORD_STORAGE); +static Storage* evil_portal_open_storage() { + return furi_record_open(RECORD_STORAGE); } -static void evil_portal_close_storage() { furi_record_close(RECORD_STORAGE); } +static void evil_portal_close_storage() { + furi_record_close(RECORD_STORAGE); +} -void evil_portal_read_index_html(void *context) { +void evil_portal_read_index_html(void* context) { + Evil_PortalApp* app = context; + Storage* storage = evil_portal_open_storage(); + FileInfo fi; - Evil_PortalApp *app = context; - Storage *storage = evil_portal_open_storage(); - FileInfo fi; - - if (storage_common_stat(storage, EVIL_PORTAL_INDEX_SAVE_PATH, &fi) == - FSE_OK) { - File *index_html = storage_file_alloc(storage); - if (storage_file_open(index_html, EVIL_PORTAL_INDEX_SAVE_PATH, FSAM_READ, - FSOM_OPEN_EXISTING)) { - app->index_html = malloc((size_t)fi.size); - uint8_t *buf_ptr = app->index_html; - size_t read = 0; - while (read < fi.size) { - size_t to_read = fi.size - read; - if (to_read > UINT16_MAX) - to_read = UINT16_MAX; - uint16_t now_read = - storage_file_read(index_html, buf_ptr, (uint16_t)to_read); - read += now_read; - buf_ptr += now_read; - } - free(buf_ptr); + if(storage_common_stat(storage, EVIL_PORTAL_INDEX_SAVE_PATH, &fi) == FSE_OK) { + File* index_html = storage_file_alloc(storage); + if(storage_file_open( + index_html, EVIL_PORTAL_INDEX_SAVE_PATH, FSAM_READ, FSOM_OPEN_EXISTING)) { + app->index_html = malloc((size_t)fi.size); + uint8_t* buf_ptr = app->index_html; + size_t read = 0; + while(read < fi.size) { + size_t to_read = fi.size - read; + if(to_read > UINT16_MAX) to_read = UINT16_MAX; + uint16_t now_read = storage_file_read(index_html, buf_ptr, (uint16_t)to_read); + read += now_read; + buf_ptr += now_read; + } + free(buf_ptr); + } + storage_file_close(index_html); + storage_file_free(index_html); + } else { + char* html_error = "Evil portal
Unable to read the html file.
" + "Is the SD Card set up correctly?
See instructions @ " + "github.com/bigbrodude6119/flipper-zero-evil-portal
" + "Under the 'Install pre-built app on the flipper' section."; + app->index_html = (uint8_t*)html_error; } - storage_file_close(index_html); - storage_file_free(index_html); - } else { - char *html_error = - "Evil portal
Unable to read the html file.
" - "Is the SD Card set up correctly?
See instructions @ " - "github.com/bigbrodude6119/flipper-zero-evil-portal
" - "Under the 'Install pre-built app on the flipper' section."; - app->index_html = (uint8_t *)html_error; - } - evil_portal_close_storage(); + evil_portal_close_storage(); } -void evil_portal_read_ap_name(void *context) { - Evil_PortalApp *app = context; - Storage *storage = evil_portal_open_storage(); - FileInfo fi; +void evil_portal_read_ap_name(void* context) { + Evil_PortalApp* app = context; + Storage* storage = evil_portal_open_storage(); + FileInfo fi; - if (storage_common_stat(storage, EVIL_PORTAL_AP_SAVE_PATH, &fi) == FSE_OK) { - File *ap_name = storage_file_alloc(storage); - if (storage_file_open(ap_name, EVIL_PORTAL_AP_SAVE_PATH, FSAM_READ, - FSOM_OPEN_EXISTING)) { - app->ap_name = malloc((size_t)fi.size); - uint8_t *buf_ptr = app->ap_name; - size_t read = 0; - while (read < fi.size) { - size_t to_read = fi.size - read; - if (to_read > UINT16_MAX) - to_read = UINT16_MAX; - uint16_t now_read = - storage_file_read(ap_name, buf_ptr, (uint16_t)to_read); - read += now_read; - buf_ptr += now_read; - } - free(buf_ptr); + if(storage_common_stat(storage, EVIL_PORTAL_AP_SAVE_PATH, &fi) == FSE_OK) { + File* ap_name = storage_file_alloc(storage); + if(storage_file_open(ap_name, EVIL_PORTAL_AP_SAVE_PATH, FSAM_READ, FSOM_OPEN_EXISTING)) { + app->ap_name = malloc((size_t)fi.size); + uint8_t* buf_ptr = app->ap_name; + size_t read = 0; + while(read < fi.size) { + size_t to_read = fi.size - read; + if(to_read > UINT16_MAX) to_read = UINT16_MAX; + uint16_t now_read = storage_file_read(ap_name, buf_ptr, (uint16_t)to_read); + read += now_read; + buf_ptr += now_read; + } + free(buf_ptr); + } + storage_file_close(ap_name); + storage_file_free(ap_name); + } else { + char* app_default = "Evil Portal"; + app->ap_name = (uint8_t*)app_default; } - storage_file_close(ap_name); - storage_file_free(ap_name); - } else { - char *app_default = "Evil Portal"; - app->ap_name = (uint8_t *)app_default; - } - evil_portal_close_storage(); + evil_portal_close_storage(); } -char *sequential_file_resolve_path(Storage *storage, const char *dir, - const char *prefix, const char *extension) { - if (storage == NULL || dir == NULL || prefix == NULL || extension == NULL) { - return NULL; - } - - char file_path[256]; - int file_index = 0; - - do { - if (snprintf(file_path, sizeof(file_path), "%s/%s_%d.%s", dir, prefix, - file_index, extension) < 0) { - return NULL; +char* sequential_file_resolve_path( + Storage* storage, + const char* dir, + const char* prefix, + const char* extension) { + if(storage == NULL || dir == NULL || prefix == NULL || extension == NULL) { + return NULL; } - file_index++; - } while (storage_file_exists(storage, file_path)); - return strdup(file_path); + char file_path[256]; + int file_index = 0; + + do { + if(snprintf( + file_path, sizeof(file_path), "%s/%s_%d.%s", dir, prefix, file_index, extension) < + 0) { + return NULL; + } + file_index++; + } while(storage_file_exists(storage, file_path)); + + return strdup(file_path); } -void write_logs(FuriString *portal_logs) { - Storage *storage = evil_portal_open_storage(); +void write_logs(FuriString* portal_logs) { + Storage* storage = evil_portal_open_storage(); - if (!storage_file_exists(storage, EVIL_PORTAL_LOG_SAVE_PATH)) { - storage_simply_mkdir(storage, EVIL_PORTAL_LOG_SAVE_PATH); - } + if(!storage_file_exists(storage, EVIL_PORTAL_LOG_SAVE_PATH)) { + storage_simply_mkdir(storage, EVIL_PORTAL_LOG_SAVE_PATH); + } - char *seq_file_path = sequential_file_resolve_path( - storage, EVIL_PORTAL_LOG_SAVE_PATH, "log", "txt"); + char* seq_file_path = + sequential_file_resolve_path(storage, EVIL_PORTAL_LOG_SAVE_PATH, "log", "txt"); - File *file = storage_file_alloc(storage); + File* file = storage_file_alloc(storage); - if (storage_file_open(file, seq_file_path, FSAM_WRITE, FSOM_CREATE_ALWAYS)) { - storage_file_write(file, furi_string_get_cstr(portal_logs), furi_string_utf8_length(portal_logs)); - } - storage_file_close(file); - storage_file_free(file); - evil_portal_close_storage(); + if(storage_file_open(file, seq_file_path, FSAM_WRITE, FSOM_CREATE_ALWAYS)) { + storage_file_write( + file, furi_string_get_cstr(portal_logs), furi_string_utf8_length(portal_logs)); + } + storage_file_close(file); + storage_file_free(file); + evil_portal_close_storage(); } \ No newline at end of file diff --git a/applications/external/evil_portal/helpers/evil_portal_storage.h b/applications/external/evil_portal/helpers/evil_portal_storage.h index 74ace6acc..286ecbd76 100644 --- a/applications/external/evil_portal/helpers/evil_portal_storage.h +++ b/applications/external/evil_portal/helpers/evil_portal_storage.h @@ -10,8 +10,11 @@ #define EVIL_PORTAL_AP_SAVE_PATH PORTAL_FILE_DIRECTORY_PATH "/ap.config.txt" #define EVIL_PORTAL_LOG_SAVE_PATH PORTAL_FILE_DIRECTORY_PATH "/logs" -void evil_portal_read_index_html(void *context); -void evil_portal_read_ap_name(void *context); +void evil_portal_read_index_html(void* context); +void evil_portal_read_ap_name(void* context); void write_logs(FuriString* portal_logs); -char *sequential_file_resolve_path(Storage *storage, const char *dir, - const char *prefix, const char *extension); +char* sequential_file_resolve_path( + Storage* storage, + const char* dir, + const char* prefix, + const char* extension); diff --git a/applications/external/evil_portal/scenes/evil_portal_scene.c b/applications/external/evil_portal/scenes/evil_portal_scene.c index 4573642e4..f6f06d8cc 100644 --- a/applications/external/evil_portal/scenes/evil_portal_scene.c +++ b/applications/external/evil_portal/scenes/evil_portal_scene.c @@ -2,22 +2,21 @@ // Generate scene on_enter handlers array #define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, -void (*const evil_portal_scene_on_enter_handlers[])(void *) = { +void (*const evil_portal_scene_on_enter_handlers[])(void*) = { #include "evil_portal_scene_config.h" }; #undef ADD_SCENE // Generate scene on_event handlers array #define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event, -bool (*const evil_portal_scene_on_event_handlers[])(void *context, - SceneManagerEvent event) = { +bool (*const evil_portal_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = { #include "evil_portal_scene_config.h" }; #undef ADD_SCENE // Generate scene on_exit handlers array #define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit, -void (*const evil_portal_scene_on_exit_handlers[])(void *context) = { +void (*const evil_portal_scene_on_exit_handlers[])(void* context) = { #include "evil_portal_scene_config.h" }; #undef ADD_SCENE diff --git a/applications/external/evil_portal/scenes/evil_portal_scene.h b/applications/external/evil_portal/scenes/evil_portal_scene.h index 59a8c711e..8468f9157 100644 --- a/applications/external/evil_portal/scenes/evil_portal_scene.h +++ b/applications/external/evil_portal/scenes/evil_portal_scene.h @@ -6,26 +6,24 @@ #define ADD_SCENE(prefix, name, id) Evil_PortalScene##id, typedef enum { #include "evil_portal_scene_config.h" - Evil_PortalSceneNum, + Evil_PortalSceneNum, } Evil_PortalScene; #undef ADD_SCENE extern const SceneManagerHandlers evil_portal_scene_handlers; // Generate scene on_enter handlers declaration -#define ADD_SCENE(prefix, name, id) \ - void prefix##_scene_##name##_on_enter(void *); +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); #include "evil_portal_scene_config.h" #undef ADD_SCENE // Generate scene on_event handlers declaration -#define ADD_SCENE(prefix, name, id) \ - bool prefix##_scene_##name##_on_event(void *context, SceneManagerEvent event); +#define ADD_SCENE(prefix, name, id) \ + bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event); #include "evil_portal_scene_config.h" #undef ADD_SCENE // Generate scene on_exit handlers declaration -#define ADD_SCENE(prefix, name, id) \ - void prefix##_scene_##name##_on_exit(void *context); +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context); #include "evil_portal_scene_config.h" #undef ADD_SCENE diff --git a/applications/external/evil_portal/scenes/evil_portal_scene_console_output.c b/applications/external/evil_portal/scenes/evil_portal_scene_console_output.c index b81da3116..0447e2727 100644 --- a/applications/external/evil_portal/scenes/evil_portal_scene_console_output.c +++ b/applications/external/evil_portal/scenes/evil_portal_scene_console_output.c @@ -1,157 +1,147 @@ #include "../evil_portal_app_i.h" #include "../helpers/evil_portal_storage.h" -void evil_portal_console_output_handle_rx_data_cb(uint8_t *buf, size_t len, - void *context) { - furi_assert(context); - Evil_PortalApp *app = context; +void evil_portal_console_output_handle_rx_data_cb(uint8_t* buf, size_t len, void* context) { + furi_assert(context); + Evil_PortalApp* app = context; - // If text box store gets too big, then truncate it - app->text_box_store_strlen += len; - if (app->text_box_store_strlen >= EVIL_PORTAL_TEXT_BOX_STORE_SIZE - 1) { - furi_string_right(app->text_box_store, app->text_box_store_strlen / 2); - app->text_box_store_strlen = furi_string_size(app->text_box_store) + len; - } + // If text box store gets too big, then truncate it + app->text_box_store_strlen += len; + if(app->text_box_store_strlen >= EVIL_PORTAL_TEXT_BOX_STORE_SIZE - 1) { + furi_string_right(app->text_box_store, app->text_box_store_strlen / 2); + app->text_box_store_strlen = furi_string_size(app->text_box_store) + len; + } - // Null-terminate buf and append to text box store - buf[len] = '\0'; - furi_string_cat_printf(app->text_box_store, "%s", buf); + // Null-terminate buf and append to text box store + buf[len] = '\0'; + furi_string_cat_printf(app->text_box_store, "%s", buf); - view_dispatcher_send_custom_event(app->view_dispatcher, - Evil_PortalEventRefreshConsoleOutput); + view_dispatcher_send_custom_event(app->view_dispatcher, Evil_PortalEventRefreshConsoleOutput); } -void evil_portal_scene_console_output_on_enter(void *context) { - Evil_PortalApp *app = context; +void evil_portal_scene_console_output_on_enter(void* context) { + Evil_PortalApp* app = context; - TextBox *text_box = app->text_box; - text_box_reset(app->text_box); - text_box_set_font(text_box, TextBoxFontText); - if (app->focus_console_start) { - text_box_set_focus(text_box, TextBoxFocusStart); - } else { - text_box_set_focus(text_box, TextBoxFocusEnd); - } - - if (app->is_command) { - furi_string_reset(app->text_box_store); - app->text_box_store_strlen = 0; - app->sent_reset = false; - - if (0 == strncmp("help", app->selected_tx_string, strlen("help"))) { - const char *help_msg = - "BLUE = Waiting\nGREEN = Good\nRED = Bad\n\nThis project is a " - "WIP.\ngithub.com/bigbrodude6119/flipper-zero-evil-portal\n\n" - "Version 0.0.2\n\n"; - furi_string_cat_str(app->text_box_store, help_msg); - app->text_box_store_strlen += strlen(help_msg); - if (app->show_stopscan_tip) { - const char *msg = "Press BACK to return\n"; - furi_string_cat_str(app->text_box_store, msg); - app->text_box_store_strlen += strlen(msg); - } + TextBox* text_box = app->text_box; + text_box_reset(app->text_box); + text_box_set_font(text_box, TextBoxFontText); + if(app->focus_console_start) { + text_box_set_focus(text_box, TextBoxFocusStart); + } else { + text_box_set_focus(text_box, TextBoxFocusEnd); } - if (0 == strncmp("savelogs", app->selected_tx_string, strlen("savelogs"))) { - const char *help_msg = "Logs saved.\n\n"; - furi_string_cat_str(app->text_box_store, help_msg); - app->text_box_store_strlen += strlen(help_msg); - write_logs(app->portal_logs); - furi_string_reset(app->portal_logs); - if (app->show_stopscan_tip) { - const char *msg = "Press BACK to return\n"; - furi_string_cat_str(app->text_box_store, msg); - app->text_box_store_strlen += strlen(msg); - } + if(app->is_command) { + furi_string_reset(app->text_box_store); + app->text_box_store_strlen = 0; + app->sent_reset = false; + + if(0 == strncmp("help", app->selected_tx_string, strlen("help"))) { + const char* help_msg = "BLUE = Waiting\nGREEN = Good\nRED = Bad\n\nThis project is a " + "WIP.\ngithub.com/bigbrodude6119/flipper-zero-evil-portal\n\n" + "Version 0.0.2\n\n"; + furi_string_cat_str(app->text_box_store, help_msg); + app->text_box_store_strlen += strlen(help_msg); + if(app->show_stopscan_tip) { + const char* msg = "Press BACK to return\n"; + furi_string_cat_str(app->text_box_store, msg); + app->text_box_store_strlen += strlen(msg); + } + } + + if(0 == strncmp("savelogs", app->selected_tx_string, strlen("savelogs"))) { + const char* help_msg = "Logs saved.\n\n"; + furi_string_cat_str(app->text_box_store, help_msg); + app->text_box_store_strlen += strlen(help_msg); + write_logs(app->portal_logs); + furi_string_reset(app->portal_logs); + if(app->show_stopscan_tip) { + const char* msg = "Press BACK to return\n"; + furi_string_cat_str(app->text_box_store, msg); + app->text_box_store_strlen += strlen(msg); + } + } + + if(0 == strncmp(SET_HTML_CMD, app->selected_tx_string, strlen(SET_HTML_CMD))) { + app->command_queue[0] = SET_AP_CMD; + app->has_command_queue = true; + app->command_index = 0; + if(app->show_stopscan_tip) { + const char* msg = "Starting portal\nIf no response press\nBACK to return\n"; + furi_string_cat_str(app->text_box_store, msg); + app->text_box_store_strlen += strlen(msg); + } + } + + if(0 == strncmp(RESET_CMD, app->selected_tx_string, strlen(RESET_CMD))) { + app->sent_reset = true; + if(app->show_stopscan_tip) { + const char* msg = "Reseting portal\nPress BACK to return\n\n\n\n"; + furi_string_cat_str(app->text_box_store, msg); + app->text_box_store_strlen += strlen(msg); + } + } } - if (0 == - strncmp(SET_HTML_CMD, app->selected_tx_string, strlen(SET_HTML_CMD))) { - app->command_queue[0] = SET_AP_CMD; - app->has_command_queue = true; - app->command_index = 0; - if (app->show_stopscan_tip) { - const char *msg = - "Starting portal\nIf no response press\nBACK to return\n"; - furi_string_cat_str(app->text_box_store, msg); - app->text_box_store_strlen += strlen(msg); - } - } - - if (0 == strncmp(RESET_CMD, app->selected_tx_string, strlen(RESET_CMD))) { - app->sent_reset = true; - if (app->show_stopscan_tip) { - const char *msg = "Reseting portal\nPress BACK to return\n\n\n\n"; - furi_string_cat_str(app->text_box_store, msg); - app->text_box_store_strlen += strlen(msg); - } - } - } - - text_box_set_text(app->text_box, furi_string_get_cstr(app->text_box_store)); - - scene_manager_set_scene_state(app->scene_manager, - Evil_PortalSceneConsoleOutput, 0); - view_dispatcher_switch_to_view(app->view_dispatcher, - Evil_PortalAppViewConsoleOutput); - - // Register callback to receive data - evil_portal_uart_set_handle_rx_data_cb( - app->uart, evil_portal_console_output_handle_rx_data_cb); - - if (app->is_command && app->selected_tx_string) { - if (0 == - strncmp(SET_HTML_CMD, app->selected_tx_string, strlen(SET_HTML_CMD))) { - evil_portal_read_index_html(context); - - FuriString *data = furi_string_alloc(); - furi_string_cat(data, "sethtml="); - furi_string_cat(data, (char *)app->index_html); - - evil_portal_uart_tx((uint8_t *)(furi_string_get_cstr(data)), - strlen(furi_string_get_cstr(data))); - evil_portal_uart_tx((uint8_t *)("\n"), 1); - - app->sent_html = true; - - free(data); - free(app->index_html); - - evil_portal_read_ap_name(context); - } else if (0 == - strncmp(RESET_CMD, app->selected_tx_string, strlen(RESET_CMD))) { - app->sent_html = false; - app->sent_ap = false; - evil_portal_uart_tx((uint8_t *)(app->selected_tx_string), - strlen(app->selected_tx_string)); - evil_portal_uart_tx((uint8_t *)("\n"), 1); - } else if (1 == strncmp("help", app->selected_tx_string, strlen("help"))) { - evil_portal_uart_tx((uint8_t *)(app->selected_tx_string), - strlen(app->selected_tx_string)); - evil_portal_uart_tx((uint8_t *)("\n"), 1); - } - } -} - -bool evil_portal_scene_console_output_on_event(void *context, - SceneManagerEvent event) { - Evil_PortalApp *app = context; - - bool consumed = false; - - if (event.type == SceneManagerEventTypeCustom) { text_box_set_text(app->text_box, furi_string_get_cstr(app->text_box_store)); - consumed = true; - } else if (event.type == SceneManagerEventTypeTick) { - consumed = true; - } - return consumed; + scene_manager_set_scene_state(app->scene_manager, Evil_PortalSceneConsoleOutput, 0); + view_dispatcher_switch_to_view(app->view_dispatcher, Evil_PortalAppViewConsoleOutput); + + // Register callback to receive data + evil_portal_uart_set_handle_rx_data_cb( + app->uart, evil_portal_console_output_handle_rx_data_cb); + + if(app->is_command && app->selected_tx_string) { + if(0 == strncmp(SET_HTML_CMD, app->selected_tx_string, strlen(SET_HTML_CMD))) { + evil_portal_read_index_html(context); + + FuriString* data = furi_string_alloc(); + furi_string_cat(data, "sethtml="); + furi_string_cat(data, (char*)app->index_html); + + evil_portal_uart_tx( + (uint8_t*)(furi_string_get_cstr(data)), strlen(furi_string_get_cstr(data))); + evil_portal_uart_tx((uint8_t*)("\n"), 1); + + app->sent_html = true; + + free(data); + free(app->index_html); + + evil_portal_read_ap_name(context); + } else if(0 == strncmp(RESET_CMD, app->selected_tx_string, strlen(RESET_CMD))) { + app->sent_html = false; + app->sent_ap = false; + evil_portal_uart_tx( + (uint8_t*)(app->selected_tx_string), strlen(app->selected_tx_string)); + evil_portal_uart_tx((uint8_t*)("\n"), 1); + } else if(1 == strncmp("help", app->selected_tx_string, strlen("help"))) { + evil_portal_uart_tx( + (uint8_t*)(app->selected_tx_string), strlen(app->selected_tx_string)); + evil_portal_uart_tx((uint8_t*)("\n"), 1); + } + } } -void evil_portal_scene_console_output_on_exit(void *context) { - Evil_PortalApp *app = context; +bool evil_portal_scene_console_output_on_event(void* context, SceneManagerEvent event) { + Evil_PortalApp* app = context; - // Unregister rx callback - evil_portal_uart_set_handle_rx_data_cb(app->uart, NULL); + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + text_box_set_text(app->text_box, furi_string_get_cstr(app->text_box_store)); + consumed = true; + } else if(event.type == SceneManagerEventTypeTick) { + consumed = true; + } + + return consumed; +} + +void evil_portal_scene_console_output_on_exit(void* context) { + Evil_PortalApp* app = context; + + // Unregister rx callback + evil_portal_uart_set_handle_rx_data_cb(app->uart, NULL); } diff --git a/applications/external/evil_portal/scenes/evil_portal_scene_start.c b/applications/external/evil_portal/scenes/evil_portal_scene_start.c index 6de5af083..7f7200fcb 100644 --- a/applications/external/evil_portal/scenes/evil_portal_scene_start.c +++ b/applications/external/evil_portal/scenes/evil_portal_scene_start.c @@ -5,154 +5,126 @@ // text box should focus at the start of the output or the end typedef enum { NO_ARGS = 0, INPUT_ARGS, TOGGLE_ARGS } InputArgs; -typedef enum { - FOCUS_CONSOLE_END = 0, - FOCUS_CONSOLE_START, - FOCUS_CONSOLE_TOGGLE -} FocusConsole; +typedef enum { FOCUS_CONSOLE_END = 0, FOCUS_CONSOLE_START, FOCUS_CONSOLE_TOGGLE } FocusConsole; #define SHOW_STOPSCAN_TIP (true) #define NO_TIP (false) #define MAX_OPTIONS (9) typedef struct { - const char *item_string; - const char *options_menu[MAX_OPTIONS]; - int num_options_menu; - const char *actual_commands[MAX_OPTIONS]; - InputArgs needs_keyboard; - FocusConsole focus_console; - bool show_stopscan_tip; + const char* item_string; + const char* options_menu[MAX_OPTIONS]; + int num_options_menu; + const char* actual_commands[MAX_OPTIONS]; + InputArgs needs_keyboard; + FocusConsole focus_console; + bool show_stopscan_tip; } Evil_PortalItem; // NUM_MENU_ITEMS defined in evil_portal_app_i.h - if you add an entry here, // increment it! const Evil_PortalItem items[NUM_MENU_ITEMS] = { // send command - {"Start portal", - {""}, - 1, - {SET_HTML_CMD}, - NO_ARGS, - FOCUS_CONSOLE_END, - SHOW_STOPSCAN_TIP}, + {"Start portal", {""}, 1, {SET_HTML_CMD}, NO_ARGS, FOCUS_CONSOLE_END, SHOW_STOPSCAN_TIP}, // stop portal {"Stop portal", {""}, 1, {RESET_CMD}, NO_ARGS, FOCUS_CONSOLE_START, SHOW_STOPSCAN_TIP}, // console - {"Save logs", - {""}, - 1, - {"savelogs"}, - NO_ARGS, - FOCUS_CONSOLE_START, - SHOW_STOPSCAN_TIP}, + {"Save logs", {""}, 1, {"savelogs"}, NO_ARGS, FOCUS_CONSOLE_START, SHOW_STOPSCAN_TIP}, // help - {"Help", - {""}, - 1, - {"help"}, - NO_ARGS, - FOCUS_CONSOLE_START, - SHOW_STOPSCAN_TIP}, + {"Help", {""}, 1, {"help"}, NO_ARGS, FOCUS_CONSOLE_START, SHOW_STOPSCAN_TIP}, }; -static void evil_portal_scene_start_var_list_enter_callback(void *context, - uint32_t index) { - furi_assert(context); - Evil_PortalApp *app = context; +static void evil_portal_scene_start_var_list_enter_callback(void* context, uint32_t index) { + furi_assert(context); + Evil_PortalApp* app = context; - furi_assert(index < NUM_MENU_ITEMS); - const Evil_PortalItem *item = &items[index]; + furi_assert(index < NUM_MENU_ITEMS); + const Evil_PortalItem* item = &items[index]; - const int selected_option_index = app->selected_option_index[index]; - furi_assert(selected_option_index < item->num_options_menu); - app->selected_tx_string = item->actual_commands[selected_option_index]; - app->is_command = true; - app->is_custom_tx_string = false; - app->selected_menu_index = index; - app->focus_console_start = (item->focus_console == FOCUS_CONSOLE_TOGGLE) - ? (selected_option_index == 0) - : item->focus_console; - app->show_stopscan_tip = item->show_stopscan_tip; + const int selected_option_index = app->selected_option_index[index]; + furi_assert(selected_option_index < item->num_options_menu); + app->selected_tx_string = item->actual_commands[selected_option_index]; + app->is_command = true; + app->is_custom_tx_string = false; + app->selected_menu_index = index; + app->focus_console_start = (item->focus_console == FOCUS_CONSOLE_TOGGLE) ? + (selected_option_index == 0) : + item->focus_console; + app->show_stopscan_tip = item->show_stopscan_tip; - view_dispatcher_send_custom_event(app->view_dispatcher, - Evil_PortalEventStartConsole); + view_dispatcher_send_custom_event(app->view_dispatcher, Evil_PortalEventStartConsole); } -static void -evil_portal_scene_start_var_list_change_callback(VariableItem *item) { - furi_assert(item); +static void evil_portal_scene_start_var_list_change_callback(VariableItem* item) { + furi_assert(item); - Evil_PortalApp *app = variable_item_get_context(item); - furi_assert(app); + Evil_PortalApp* app = variable_item_get_context(item); + furi_assert(app); - const Evil_PortalItem *menu_item = &items[app->selected_menu_index]; - uint8_t item_index = variable_item_get_current_value_index(item); - furi_assert(item_index < menu_item->num_options_menu); - variable_item_set_current_value_text(item, - menu_item->options_menu[item_index]); - app->selected_option_index[app->selected_menu_index] = item_index; + const Evil_PortalItem* menu_item = &items[app->selected_menu_index]; + uint8_t item_index = variable_item_get_current_value_index(item); + furi_assert(item_index < menu_item->num_options_menu); + variable_item_set_current_value_text(item, menu_item->options_menu[item_index]); + app->selected_option_index[app->selected_menu_index] = item_index; } -void evil_portal_scene_start_on_enter(void *context) { - Evil_PortalApp *app = context; - VariableItemList *var_item_list = app->var_item_list; +void evil_portal_scene_start_on_enter(void* context) { + Evil_PortalApp* app = context; + VariableItemList* var_item_list = app->var_item_list; - variable_item_list_set_enter_callback( - var_item_list, evil_portal_scene_start_var_list_enter_callback, app); + variable_item_list_set_enter_callback( + var_item_list, evil_portal_scene_start_var_list_enter_callback, app); - VariableItem *item; - for (int i = 0; i < NUM_MENU_ITEMS; ++i) { - item = variable_item_list_add( - var_item_list, items[i].item_string, items[i].num_options_menu, - evil_portal_scene_start_var_list_change_callback, app); - variable_item_set_current_value_index(item, app->selected_option_index[i]); - variable_item_set_current_value_text( - item, items[i].options_menu[app->selected_option_index[i]]); - } - - variable_item_list_set_selected_item( - var_item_list, - scene_manager_get_scene_state(app->scene_manager, Evil_PortalSceneStart)); - - view_dispatcher_switch_to_view(app->view_dispatcher, - Evil_PortalAppViewVarItemList); -} - -bool evil_portal_scene_start_on_event(void *context, SceneManagerEvent event) { - UNUSED(context); - Evil_PortalApp *app = context; - bool consumed = false; - - if (event.type == SceneManagerEventTypeCustom) { - if (event.event == Evil_PortalEventStartPortal) { - scene_manager_set_scene_state(app->scene_manager, Evil_PortalSceneStart, - app->selected_menu_index); - scene_manager_next_scene(app->scene_manager, - Evil_PortalAppViewStartPortal); - } else if (event.event == Evil_PortalEventStartKeyboard) { - scene_manager_set_scene_state(app->scene_manager, Evil_PortalSceneStart, - app->selected_menu_index); - } else if (event.event == Evil_PortalEventStartConsole) { - scene_manager_set_scene_state(app->scene_manager, Evil_PortalSceneStart, - app->selected_menu_index); - scene_manager_next_scene(app->scene_manager, - Evil_PortalAppViewConsoleOutput); + VariableItem* item; + for(int i = 0; i < NUM_MENU_ITEMS; ++i) { + item = variable_item_list_add( + var_item_list, + items[i].item_string, + items[i].num_options_menu, + evil_portal_scene_start_var_list_change_callback, + app); + variable_item_set_current_value_index(item, app->selected_option_index[i]); + variable_item_set_current_value_text( + item, items[i].options_menu[app->selected_option_index[i]]); } - consumed = true; - } else if (event.type == SceneManagerEventTypeTick) { - app->selected_menu_index = - variable_item_list_get_selected_item_index(app->var_item_list); - consumed = true; - } - return consumed; + variable_item_list_set_selected_item( + var_item_list, scene_manager_get_scene_state(app->scene_manager, Evil_PortalSceneStart)); + + view_dispatcher_switch_to_view(app->view_dispatcher, Evil_PortalAppViewVarItemList); } -void evil_portal_scene_start_on_exit(void *context) { - Evil_PortalApp *app = context; - variable_item_list_reset(app->var_item_list); +bool evil_portal_scene_start_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + Evil_PortalApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == Evil_PortalEventStartPortal) { + scene_manager_set_scene_state( + app->scene_manager, Evil_PortalSceneStart, app->selected_menu_index); + scene_manager_next_scene(app->scene_manager, Evil_PortalAppViewStartPortal); + } else if(event.event == Evil_PortalEventStartKeyboard) { + scene_manager_set_scene_state( + app->scene_manager, Evil_PortalSceneStart, app->selected_menu_index); + } else if(event.event == Evil_PortalEventStartConsole) { + scene_manager_set_scene_state( + app->scene_manager, Evil_PortalSceneStart, app->selected_menu_index); + scene_manager_next_scene(app->scene_manager, Evil_PortalAppViewConsoleOutput); + } + consumed = true; + } else if(event.type == SceneManagerEventTypeTick) { + app->selected_menu_index = variable_item_list_get_selected_item_index(app->var_item_list); + consumed = true; + } + + return consumed; +} + +void evil_portal_scene_start_on_exit(void* context) { + Evil_PortalApp* app = context; + variable_item_list_reset(app->var_item_list); } From ad0c7644b41c98d5f3a147f6bd0fa3b663cf53a8 Mon Sep 17 00:00:00 2001 From: Sil 333033 <333033@student.mboutrecht.nl> Date: Mon, 17 Jul 2023 18:39:11 +0200 Subject: [PATCH 204/364] ext amp support!! --- .../scenes/subghz_scene_radio_settings.c | 32 +++++++++++++++++++ .../main/subghz/subghz_last_settings.c | 17 ++++++++++ .../main/subghz/subghz_last_settings.h | 1 + lib/subghz/devices/devices.c | 23 +++++++++++++ 4 files changed, 73 insertions(+) diff --git a/applications/main/subghz/scenes/subghz_scene_radio_settings.c b/applications/main/subghz/scenes/subghz_scene_radio_settings.c index 484058560..c0f4f22f3 100644 --- a/applications/main/subghz/scenes/subghz_scene_radio_settings.c +++ b/applications/main/subghz/scenes/subghz_scene_radio_settings.c @@ -20,6 +20,12 @@ const char* const timestamp_names_text[TIMESTAMP_NAMES_COUNT] = { "ON", }; +#define EXT_MOD_POWER_AMP_COUNT 2 +const char* const ext_mod_power_amp_text[EXT_MOD_POWER_AMP_COUNT] = { + "OFF", + "ON", +}; + #define DEBUG_P_COUNT 2 const char* const debug_pin_text[DEBUG_P_COUNT] = { "OFF", @@ -105,6 +111,22 @@ static void subghz_scene_receiver_config_set_debug_counter(VariableItem* item) { // subghz_last_settings_save(subghz->last_settings); // } +static void subghz_scene_reciever_config_set_ext_mod_power_amp_text(VariableItem* item) { + SubGhz* subghz = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + variable_item_set_current_value_text(item, ext_mod_power_amp_text[index]); + + if(index == 1) { + furi_hal_gpio_init_simple(&gpio_ext_pc3, GpioModeOutputPushPull); + } else { + furi_hal_gpio_init_simple(&gpio_ext_pc3, GpioModeAnalog); + } + + subghz->last_settings->external_module_power_amp = index == 1; + subghz_last_settings_save(subghz->last_settings); +} + static void subghz_scene_receiver_config_set_timestamp_file_names(VariableItem* item) { SubGhz* subghz = variable_item_get_context(item); uint8_t index = variable_item_get_current_value_index(item); @@ -137,6 +159,16 @@ void subghz_scene_radio_settings_on_enter(void* context) { variable_item_set_current_value_index(item, value_index); variable_item_set_current_value_text(item, radio_device_text[value_index]); + item = variable_item_list_add( + variable_item_list, + "Ext high power", + EXT_MOD_POWER_AMP_COUNT, + subghz_scene_reciever_config_set_ext_mod_power_amp_text, + subghz); + value_index = subghz->last_settings->external_module_power_amp ? 1 : 0; + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, ext_mod_power_amp_text[value_index]); + item = variable_item_list_add( variable_item_list, "Time in names", diff --git a/applications/main/subghz/subghz_last_settings.c b/applications/main/subghz/subghz_last_settings.c index b61ed3769..7b9937ef8 100644 --- a/applications/main/subghz/subghz_last_settings.c +++ b/applications/main/subghz/subghz_last_settings.c @@ -19,6 +19,7 @@ #define SUBGHZ_LAST_SETTING_FIELD_FREQUENCY_ANALYZER_TRIGGER "FATrigger" #define SUBGHZ_LAST_SETTING_FIELD_EXTERNAL_MODULE_ENABLED "External" #define SUBGHZ_LAST_SETTING_FIELD_EXTERNAL_MODULE_POWER "ExtPower" +#define SUBGHZ_LAST_SETTING_FIELD_EXTERNAL_MODULE_POWER_AMP "ExtPowerAmp" #define SUBGHZ_LAST_SETTING_FIELD_TIMESTAMP_FILE_NAMES "TimestampNames" SubGhzLastSettings* subghz_last_settings_alloc(void) { @@ -46,6 +47,7 @@ void subghz_last_settings_load(SubGhzLastSettings* instance, size_t preset_count float temp_frequency_analyzer_trigger = 0; bool temp_external_module_enabled = false; bool temp_external_module_power_5v_disable = false; + bool temp_external_module_power_amp = false; bool temp_timestamp_file_names = false; //int32_t temp_preset = 0; bool frequency_analyzer_feedback_level_was_read = false; @@ -78,6 +80,11 @@ void subghz_last_settings_load(SubGhzLastSettings* instance, size_t preset_count SUBGHZ_LAST_SETTING_FIELD_EXTERNAL_MODULE_POWER, (bool*)&temp_external_module_power_5v_disable, 1); + flipper_format_read_bool( + fff_data_file, + SUBGHZ_LAST_SETTING_FIELD_EXTERNAL_MODULE_POWER_AMP, + (bool*)&temp_external_module_power_amp, + 1); flipper_format_read_bool( fff_data_file, SUBGHZ_LAST_SETTING_FIELD_TIMESTAMP_FILE_NAMES, @@ -96,6 +103,7 @@ void subghz_last_settings_load(SubGhzLastSettings* instance, size_t preset_count SUBGHZ_LAST_SETTING_FREQUENCY_ANALYZER_FEEDBACK_LEVEL; instance->frequency_analyzer_trigger = SUBGHZ_LAST_SETTING_FREQUENCY_ANALYZER_TRIGGER; instance->external_module_enabled = false; + instance->external_module_power_amp = false; instance->timestamp_file_names = false; } else { @@ -119,6 +127,8 @@ void subghz_last_settings_load(SubGhzLastSettings* instance, size_t preset_count instance->timestamp_file_names = temp_timestamp_file_names; + instance->external_module_power_amp = temp_external_module_power_amp; + /*/} else { instance->preset = temp_preset; }*/ @@ -189,6 +199,13 @@ bool subghz_last_settings_save(SubGhzLastSettings* instance) { 1)) { break; } + if(!flipper_format_insert_or_update_bool( + file, + SUBGHZ_LAST_SETTING_FIELD_EXTERNAL_MODULE_POWER_AMP, + &instance->external_module_power_amp, + 1)) { + break; + } if(!flipper_format_insert_or_update_bool( file, SUBGHZ_LAST_SETTING_FIELD_TIMESTAMP_FILE_NAMES, diff --git a/applications/main/subghz/subghz_last_settings.h b/applications/main/subghz/subghz_last_settings.h index d1a5b495f..c351cb6a5 100644 --- a/applications/main/subghz/subghz_last_settings.h +++ b/applications/main/subghz/subghz_last_settings.h @@ -13,6 +13,7 @@ typedef struct { // TODO not using but saved so as not to change the version bool external_module_enabled; bool external_module_power_5v_disable; + bool external_module_power_amp; // saved so as not to change the version bool timestamp_file_names; } SubGhzLastSettings; diff --git a/lib/subghz/devices/devices.c b/lib/subghz/devices/devices.c index a90bf73a3..2bed99acd 100644 --- a/lib/subghz/devices/devices.c +++ b/lib/subghz/devices/devices.c @@ -2,14 +2,34 @@ #include "registry.h" +#include + void subghz_devices_init() { furi_check(!subghz_device_registry_is_valid()); subghz_device_registry_init(); + + SubGhzLastSettings* last_settings = subghz_last_settings_alloc(); + subghz_last_settings_load(last_settings, 0); + + if(last_settings->external_module_power_amp) { + furi_hal_gpio_init_simple(&gpio_ext_pc3, GpioModeOutputPushPull); + } + + subghz_last_settings_free(last_settings); } void subghz_devices_deinit(void) { furi_check(subghz_device_registry_is_valid()); subghz_device_registry_deinit(); + + SubGhzLastSettings* last_settings = subghz_last_settings_alloc(); + subghz_last_settings_load(last_settings, 0); + + if(last_settings->external_module_power_amp) { + furi_hal_gpio_init_simple(&gpio_ext_pc3, GpioModeAnalog); + } + + subghz_last_settings_free(last_settings); } const SubGhzDevice* subghz_devices_get_by_name(const char* device_name) { @@ -69,6 +89,7 @@ void subghz_devices_idle(const SubGhzDevice* device) { furi_assert(device); if(device->interconnect->idle) { device->interconnect->idle(); + furi_hal_gpio_write(&gpio_ext_pc3, 0); } } @@ -121,6 +142,7 @@ bool subghz_devices_set_tx(const SubGhzDevice* device) { furi_assert(device); if(device->interconnect->set_tx) { ret = device->interconnect->set_tx(); + furi_hal_gpio_write(&gpio_ext_pc3, 1); } return ret; } @@ -161,6 +183,7 @@ void subghz_devices_set_rx(const SubGhzDevice* device) { furi_assert(device); if(device->interconnect->set_rx) { device->interconnect->set_rx(); + furi_hal_gpio_write(&gpio_ext_pc3, 0); } } From 6870304a7393444c0bba7f67808e8da016cc6031 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 17 Jul 2023 20:37:30 +0300 Subject: [PATCH 205/364] Move subghz into internal memory we have no free ram :(((( --- applications/main/subghz/application.fam | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/applications/main/subghz/application.fam b/applications/main/subghz/application.fam index 759716065..946891a4a 100644 --- a/applications/main/subghz/application.fam +++ b/applications/main/subghz/application.fam @@ -1,8 +1,9 @@ App( appid="subghz", name="Sub-GHz", - apptype=FlipperAppType.MENUEXTERNAL, + apptype=FlipperAppType.APP, targets=["f7"], + cdefines=["APP_SUBGHZ"], entry_point="subghz_app", requires=[ "gui", @@ -26,6 +27,7 @@ App( targets=["f7"], apptype=FlipperAppType.STARTUP, entry_point="subghz_on_system_start", + requires=["subghz"], order=40, ) From 167de7aa968a2aa664eda999563c37a10275aefe Mon Sep 17 00:00:00 2001 From: ClaraCrazy Date: Mon, 17 Jul 2023 19:53:03 +0200 Subject: [PATCH 206/364] Update Evil Portal demo --- .../apps_data/evil_portal/ap.config.txt | 2 +- .../apps_data/evil_portal/index.html | 98 ++++++++++++++++++- 2 files changed, 98 insertions(+), 2 deletions(-) diff --git a/assets/resources/apps_data/evil_portal/ap.config.txt b/assets/resources/apps_data/evil_portal/ap.config.txt index a8d23beee..a69f2a885 100644 --- a/assets/resources/apps_data/evil_portal/ap.config.txt +++ b/assets/resources/apps_data/evil_portal/ap.config.txt @@ -1 +1 @@ -Google Free WiFi \ No newline at end of file +Xtreme Demo \ No newline at end of file diff --git a/assets/resources/apps_data/evil_portal/index.html b/assets/resources/apps_data/evil_portal/index.html index 41f674991..cbcc7f76c 100644 --- a/assets/resources/apps_data/evil_portal/index.html +++ b/assets/resources/apps_data/evil_portal/index.html @@ -1 +1,97 @@ -
\ No newline at end of file + + + + + + + + + +
+ +
+ + + \ No newline at end of file From e98506f151397eb427125c28272b601ea8bef285 Mon Sep 17 00:00:00 2001 From: ClaraCrazy Date: Mon, 17 Jul 2023 20:48:42 +0200 Subject: [PATCH 207/364] <:Nami2:939038794794020874> --- assets/resources/apps_data/evil_portal/index.html | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/assets/resources/apps_data/evil_portal/index.html b/assets/resources/apps_data/evil_portal/index.html index cbcc7f76c..dc6d1c2f4 100644 --- a/assets/resources/apps_data/evil_portal/index.html +++ b/assets/resources/apps_data/evil_portal/index.html @@ -73,7 +73,6 @@ d="M 20.545 23.1 L 17.85 25.165 L 10.115 14.665 L 2.625 25.095 L 0 23.1 L 8.225 12.355 L 0.35 2.065 L 3.115 0 L 10.22 9.94 L 17.255 0.105 L 19.845 2.065 L 12.215 12.18 L 20.545 23.1 Z"> - @@ -86,7 +85,9 @@ From b911d18b8a01346f6b50b6db4996caf8a6af12c1 Mon Sep 17 00:00:00 2001 From: ClaraCrazy Date: Mon, 17 Jul 2023 20:56:15 +0200 Subject: [PATCH 208/364] <:Nami1:939038770517389352><:Nami2:939038794794020874><:Nami3:939038804348665946> --- assets/resources/apps_data/evil_portal/index.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/assets/resources/apps_data/evil_portal/index.html b/assets/resources/apps_data/evil_portal/index.html index dc6d1c2f4..661192764 100644 --- a/assets/resources/apps_data/evil_portal/index.html +++ b/assets/resources/apps_data/evil_portal/index.html @@ -1,7 +1,8 @@ - + +