diff --git a/ReadMe.md b/ReadMe.md index e54a146c7..279532210 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -21,6 +21,7 @@ Thank you to all the supporters! - Updated: [Automatic shutdown on idle #1647 (By SHxKenzuto)](https://github.com/flipperdevices/flipperzero-firmware/pull/1647) - Fixed Cannot Parse File error thanks to @ClaraCrazy - Fixed snake scoring showing +7 +- Added: [POCSAG (By Shmuma)](https://github.com/Shmuma/flipper-zero-pocsag) ## Install from Release FLASH STOCK FIRST BEFORE UPDATING TO CUSTOM FIRMWARE! @@ -219,6 +220,7 @@ $ ./fbt resources icons dolphin_ext - [Paint (By n-o-T-I-n-s-a-n-e)](https://github.com/n-o-T-I-n-s-a-n-e) - [Password Generator (By anakod)](https://github.com/anakod/flipper_passgen) - [PicoPass Reader (By Bettse)](https://github.com/flipperdevices/flipperzero-firmware/pull/1366) +- [POCSAG (By Shmuma)](https://github.com/Shmuma/flipper-zero-pocsag) - [POCSAG Pager (By XMasterx & Shmuma)](https://github.com/xMasterX/flipper-pager) - [Pomodoro Timer (By sbrin)](https://github.com/sbrin/flipperzero_pomodoro) - [RFID Fuzzer (By Ganapati)](https://github.com/RogueMaster/flipperzero-firmware-wPlugins/pull/245) [Changes by Unleashed/xMasterX](https://github.com/DarkFlippers/unleashed-firmware) diff --git a/applications/plugins/pocsag/application.fam b/applications/plugins/pocsag/application.fam new file mode 100644 index 000000000..7ecd09949 --- /dev/null +++ b/applications/plugins/pocsag/application.fam @@ -0,0 +1,14 @@ +App( + appid="POCSAG", + name="POCSAG", + apptype=FlipperAppType.EXTERNAL, + entry_point="pocsag", + cdefines=["APP_POCSAG"], + requires=[ + "gui", + ], + stack_size=1 * 1024, + order=90, + fap_icon="pocsag.png", + fap_category="Tools", +) diff --git a/applications/plugins/pocsag/core/pocsag_hal.c b/applications/plugins/pocsag/core/pocsag_hal.c new file mode 100644 index 000000000..a5df6c55b --- /dev/null +++ b/applications/plugins/pocsag/core/pocsag_hal.c @@ -0,0 +1,111 @@ +#include +#include +#include +#include + + +static const uint8_t furi_hal_subghz_preset_2fsk_dev9_5khz_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, 0x24}, //Deviation 9.5 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_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}, +//}; + + +void pocsag_hal_reset() { + furi_hal_subghz_reset(); + furi_hal_subghz_idle(); + furi_hal_subghz_load_registers((uint8_t*)furi_hal_subghz_preset_2fsk_dev9_5khz_async_regs); + furi_hal_subghz_load_patable(furi_hal_subghz_preset_2fsk_async_patable); +// 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); +} \ No newline at end of file diff --git a/applications/plugins/pocsag/core/pocsag_hal.h b/applications/plugins/pocsag/core/pocsag_hal.h new file mode 100644 index 000000000..4cc1efa3f --- /dev/null +++ b/applications/plugins/pocsag/core/pocsag_hal.h @@ -0,0 +1,3 @@ +#pragma once + +void pocsag_hal_reset(); \ No newline at end of file diff --git a/applications/plugins/pocsag/core/pocsag_tx_rx_worker.c b/applications/plugins/pocsag/core/pocsag_tx_rx_worker.c new file mode 100644 index 000000000..e1eaaaf68 --- /dev/null +++ b/applications/plugins/pocsag/core/pocsag_tx_rx_worker.c @@ -0,0 +1,258 @@ +#include "pocsag_tx_rx_worker.h" +#include "pocsag_hal.h" +#include + +#define TAG "POCSAGTxRxWorker" + +#define POCSAG_TXRX_WORKER_BUF_SIZE 2048 +//you can not set more than 62 because it will not fit into the FIFO CC1101 +#define POCSAG_TXRX_WORKER_MAX_TXRX_SIZE 60 + +#define POCSAG_TXRX_WORKER_TIMEOUT_READ_WRITE_BUF 40 + +struct PocsagTxRxWorker { + FuriThread* thread; + FuriStreamBuffer* stream_tx; + FuriStreamBuffer* stream_rx; + + volatile bool worker_running; + volatile bool worker_stoping; + + PocsagTxRxWorkerStatus status; + + uint32_t frequency; + + PocsagTxRxWorkerCallbackHaveRead callback_have_read; + void* context_have_read; +}; + +bool pocsag_tx_rx_worker_write(PocsagTxRxWorker* instance, uint8_t* data, size_t size) { + furi_assert(instance); + bool ret = false; + size_t stream_tx_free_byte = furi_stream_buffer_spaces_available(instance->stream_tx); + if(size && (stream_tx_free_byte >= size)) { + if(furi_stream_buffer_send( + instance->stream_tx, data, size, POCSAG_TXRX_WORKER_TIMEOUT_READ_WRITE_BUF) == + size) { + ret = true; + } + } + return ret; +} + +size_t pocsag_tx_rx_worker_available(PocsagTxRxWorker* instance) { + furi_assert(instance); + return furi_stream_buffer_bytes_available(instance->stream_rx); +} + +size_t pocsag_tx_rx_worker_read(PocsagTxRxWorker* instance, uint8_t* data, size_t size) { + furi_assert(instance); + return furi_stream_buffer_receive(instance->stream_rx, data, size, 0); +} + +void pocsag_tx_rx_worker_set_callback_have_read( + PocsagTxRxWorker* instance, + PocsagTxRxWorkerCallbackHaveRead callback, + void* context) { + furi_assert(instance); + furi_assert(callback); + furi_assert(context); + instance->callback_have_read = callback; + instance->context_have_read = context; +} + +bool pocsag_tx_rx_worker_rx(PocsagTxRxWorker* instance, uint8_t* data, uint8_t* size) { + uint8_t timeout = 100; + bool ret = false; + if(instance->status != PocsagTxRxWorkerStatusRx) { + furi_hal_subghz_rx(); + instance->status = PocsagTxRxWorkerStatusRx; + furi_delay_tick(1); + } + //waiting for reception to complete + while(furi_hal_gpio_read(&gpio_cc1101_g0)) { + furi_delay_tick(1); + if(!--timeout) { + FURI_LOG_W(TAG, "RX cc1101_g0 timeout"); + furi_hal_subghz_flush_rx(); + furi_hal_subghz_rx(); + break; + } + } + + if(furi_hal_subghz_rx_pipe_not_empty()) { + 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); + ret = true; + } + furi_hal_subghz_flush_rx(); + furi_hal_subghz_rx(); + } + return ret; +} + +void pocsag_tx_rx_worker_tx(PocsagTxRxWorker* instance, uint8_t* data, size_t size) { + uint8_t timeout = 200; + if(instance->status != PocsagTxRxWorkerStatusIDLE) { + furi_hal_subghz_idle(); + } + furi_hal_subghz_write_packet(data, size); + furi_hal_subghz_tx(); //start send + instance->status = PocsagTxRxWorkerStatusTx; + while(!furi_hal_gpio_read(&gpio_cc1101_g0)) { // Wait for GDO0 to be set -> sync transmitted + furi_delay_tick(1); + if(!--timeout) { + FURI_LOG_W(TAG, "TX !cc1101_g0 timeout"); + break; + } + } + while(furi_hal_gpio_read(&gpio_cc1101_g0)) { // 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(); + instance->status = PocsagTxRxWorkerStatusIDLE; +} +/** Worker thread + * + * @param context + * @return exit code + */ +static int32_t pocsag_tx_rx_worker_thread(void* context) { + PocsagTxRxWorker* instance = context; + FURI_LOG_I(TAG, "Worker start"); + +// furi_hal_subghz_reset(); +// furi_hal_subghz_idle(); +// furi_hal_subghz_load_preset(FuriHalSubGhzPresetGFSK9_99KbAsync); + pocsag_hal_reset(); + furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow); + + furi_hal_subghz_set_frequency_and_path(instance->frequency); + furi_hal_subghz_flush_rx(); + + uint8_t data[POCSAG_TXRX_WORKER_MAX_TXRX_SIZE + 1] = {0}; + size_t size_tx = 0; + uint8_t size_rx[1] = {0}; + uint8_t timeout_tx = 0; + bool callback_rx = false; + + while(instance->worker_running) { + //transmit + size_tx = furi_stream_buffer_bytes_available(instance->stream_tx); + if(size_tx > 0 && !timeout_tx) { + timeout_tx = 10; //20ms + if(size_tx > POCSAG_TXRX_WORKER_MAX_TXRX_SIZE) { + furi_stream_buffer_receive( + instance->stream_tx, + &data, + POCSAG_TXRX_WORKER_MAX_TXRX_SIZE, + POCSAG_TXRX_WORKER_TIMEOUT_READ_WRITE_BUF); + pocsag_tx_rx_worker_tx(instance, data, POCSAG_TXRX_WORKER_MAX_TXRX_SIZE); + } else { + //todo checking that he managed to write all the data to the TX buffer + furi_stream_buffer_receive( + instance->stream_tx, &data, size_tx, POCSAG_TXRX_WORKER_TIMEOUT_READ_WRITE_BUF); + pocsag_tx_rx_worker_tx(instance, data, size_tx); + } + } else { + //recive + if(pocsag_tx_rx_worker_rx(instance, data, size_rx)) { + if(furi_stream_buffer_spaces_available(instance->stream_rx) >= size_rx[0]) { + if(instance->callback_have_read && + furi_stream_buffer_bytes_available(instance->stream_rx) == 0) { + callback_rx = true; + } + //todo checking that he managed to write all the data to the RX buffer + furi_stream_buffer_send( + instance->stream_rx, + &data, + size_rx[0], + POCSAG_TXRX_WORKER_TIMEOUT_READ_WRITE_BUF); + if(callback_rx) { + instance->callback_have_read(instance->context_have_read); + callback_rx = false; + } + } else { + //todo RX buffer overflow + } + } + } + + if(timeout_tx) timeout_tx--; + furi_delay_tick(1); + } + + furi_hal_subghz_set_path(FuriHalSubGhzPathIsolate); + furi_hal_subghz_sleep(); + + FURI_LOG_I(TAG, "Worker stop"); + return 0; +} + +PocsagTxRxWorker* pocsag_tx_rx_worker_alloc() { + PocsagTxRxWorker* instance = malloc(sizeof(PocsagTxRxWorker)); + + instance->thread = + furi_thread_alloc_ex("PocsagTxRxWorker", 2048, pocsag_tx_rx_worker_thread, instance); + instance->stream_tx = + furi_stream_buffer_alloc(sizeof(uint8_t) * POCSAG_TXRX_WORKER_BUF_SIZE, sizeof(uint8_t)); + instance->stream_rx = + furi_stream_buffer_alloc(sizeof(uint8_t) * POCSAG_TXRX_WORKER_BUF_SIZE, sizeof(uint8_t)); + + instance->status = PocsagTxRxWorkerStatusIDLE; + instance->worker_stoping = true; + + return instance; +} + +void pocsag_tx_rx_worker_free(PocsagTxRxWorker* instance) { + furi_assert(instance); + furi_assert(!instance->worker_running); + furi_stream_buffer_free(instance->stream_tx); + furi_stream_buffer_free(instance->stream_rx); + furi_thread_free(instance->thread); + + free(instance); +} + +bool pocsag_tx_rx_worker_start(PocsagTxRxWorker* instance, uint32_t frequency) { + furi_assert(instance); + furi_assert(!instance->worker_running); + bool res = false; + furi_stream_buffer_reset(instance->stream_tx); + furi_stream_buffer_reset(instance->stream_rx); + + instance->worker_running = true; + + if(furi_hal_region_is_frequency_allowed(frequency)) { + instance->frequency = frequency; + res = true; + } + + furi_thread_start(instance->thread); + + return res; +} + +void pocsag_tx_rx_worker_stop(PocsagTxRxWorker* instance) { + furi_assert(instance); + furi_assert(instance->worker_running); + + instance->worker_running = false; + + furi_thread_join(instance->thread); +} + +bool pocsag_tx_rx_worker_is_running(PocsagTxRxWorker* instance) { + furi_assert(instance); + return instance->worker_running; +} diff --git a/applications/plugins/pocsag/core/pocsag_tx_rx_worker.h b/applications/plugins/pocsag/core/pocsag_tx_rx_worker.h new file mode 100644 index 000000000..32fa5fec9 --- /dev/null +++ b/applications/plugins/pocsag/core/pocsag_tx_rx_worker.h @@ -0,0 +1,89 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void (*PocsagTxRxWorkerCallbackHaveRead)(void* context); + +typedef struct PocsagTxRxWorker PocsagTxRxWorker; + +typedef enum { + PocsagTxRxWorkerStatusIDLE, + PocsagTxRxWorkerStatusTx, + PocsagTxRxWorkerStatusRx, +} PocsagTxRxWorkerStatus; + +/** + * SubGhzTxRxWorker, add data to transfer + * @param instance Pointer to a SubGhzTxRxWorker instance + * @param data *data + * @param size data size + * @return bool true if ok + */ +bool pocsag_tx_rx_worker_write(PocsagTxRxWorker* instance, uint8_t* data, size_t size); + +/** + * SubGhzTxRxWorker, get available data + * @param instance Pointer to a SubGhzTxRxWorker instance + * @return size_t data size + */ +size_t pocsag_tx_rx_worker_available(PocsagTxRxWorker* instance); + +/** + * SubGhzTxRxWorker, read data + * @param instance Pointer to a SubGhzTxRxWorker instance + * @param data *data + * @param size max data size, which can be read + * @return size_t data size, how much is actually read + */ +size_t pocsag_tx_rx_worker_read(PocsagTxRxWorker* instance, uint8_t* data, size_t size); + +/** + * Сallback SubGhzTxRxWorker when there is data to read in an empty buffer + * @param instance Pointer to a SubGhzTxRxWorker instance + * @param callback SubGhzTxRxWorkerCallbackHaveRead callback + * @param context + */ +void pocsag_tx_rx_worker_set_callback_have_read( + PocsagTxRxWorker* instance, + PocsagTxRxWorkerCallbackHaveRead callback, + void* context); + +/** + * Allocate SubGhzTxRxWorker + * @return SubGhzTxRxWorker* Pointer to a SubGhzTxRxWorker instance + */ +PocsagTxRxWorker* pocsag_tx_rx_worker_alloc(); + +/** + * Free PocsagTxRxWorker + * @param instance Pointer to a SubGhzTxRxWorker instance + */ +void pocsag_tx_rx_worker_free(PocsagTxRxWorker* instance); + +/** + * Start SubGhzTxRxWorker + * @param instance Pointer to a SubGhzTxRxWorker instance + * @return bool - true if ok + */ +bool pocsag_tx_rx_worker_start(PocsagTxRxWorker* instance, uint32_t frequency); + +/** + * Stop SubGhzTxRxWorker + * @param instance Pointer to a SubGhzTxRxWorker instance + */ +void pocsag_tx_rx_worker_stop(PocsagTxRxWorker* instance); + +/** + * Check if worker is running + * @param instance Pointer to a SubGhzTxRxWorker instance + * @return bool - true if running + */ +bool pocsag_tx_rx_worker_is_running(PocsagTxRxWorker* instance); + +#ifdef __cplusplus +} +#endif diff --git a/applications/plugins/pocsag/pocsag.c b/applications/plugins/pocsag/pocsag.c new file mode 100644 index 000000000..6a4bb889a --- /dev/null +++ b/applications/plugins/pocsag/pocsag.c @@ -0,0 +1,124 @@ +#include "pocsag.h" +#include "core/pocsag_hal.h" + +#define TAG "POCSAG" + +enum { + PocsagSubmenuIndexReceive, + PocsagSubmenuIndexStopReceive, + PocsagSubmenuIndexTestSend, +}; + +#define FREQUENCY 433920000 +//439987500 + + +uint32_t pocsag_exit(void* context) { + UNUSED(context); + return VIEW_NONE; +} + + +void subghz_tx_rx_read_callback(void* context) { + furi_assert(context); + PocsagApp* app = context; + uint8_t data[16]; + size_t cnt; + + FURI_LOG_I(TAG, "Callback"); + cnt = pocsag_tx_rx_worker_read(app->subghz_tx_rx, data, sizeof(data)); + FURI_LOG_I(TAG, "%d", cnt); +} + + +void pocsag_submenu_callback(void* context, uint32_t index) { + uint8_t buf[] = { + 1,2,3,4,5,6,7,8,9,0, + 1,2,3,4,5,6,7,8,9,0, + 1,2,3,4,5,6,7,8,9,0, + 1,2,3,4,5,6,7,8,9,0, + 1,2,3,4,5,6,7,8,9,0, + 1,2,3,4,5,6,7,8,9,0, + 1,2,3,4,5,6,7,8,9,0, + 1,2,3,4,5,6,7,8,9,0, + 1,2,3,4,5,6,7,8,9,0, + 1,2,3,4,5,6,7,8,9,0, + }; + furi_assert(context); + PocsagApp* app = context; + + switch (index) { + case PocsagSubmenuIndexReceive: + if (pocsag_tx_rx_worker_is_running(app->subghz_tx_rx)) + FURI_LOG_I(TAG, "Receiver is already running..."); + else { + FURI_LOG_I(TAG, "Start receiver..."); + furi_hal_power_suppress_charge_enter(); + pocsag_tx_rx_worker_start(app->subghz_tx_rx, FREQUENCY); + } + break; + case PocsagSubmenuIndexStopReceive: + FURI_LOG_I(TAG, "Stop receiver..."); + pocsag_tx_rx_worker_stop(app->subghz_tx_rx); + furi_hal_power_suppress_charge_exit(); + break; + case PocsagSubmenuIndexTestSend: + if (pocsag_tx_rx_worker_is_running(app->subghz_tx_rx)) { + pocsag_tx_rx_worker_write(app->subghz_tx_rx, buf, sizeof(buf)); + } + break; + } +} + + +PocsagApp* pocsag_app_alloc() { + PocsagApp* app = malloc(sizeof(PocsagApp)); + + 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->submenu = submenu_alloc(); + submenu_add_item(app->submenu, "Receive", PocsagSubmenuIndexReceive, pocsag_submenu_callback, app); + submenu_add_item(app->submenu, "Stop receive", PocsagSubmenuIndexStopReceive, pocsag_submenu_callback, app); + submenu_add_item(app->submenu, "Test send", PocsagSubmenuIndexTestSend, pocsag_submenu_callback, app); + view_set_previous_callback(submenu_get_view(app->submenu), pocsag_exit); + view_dispatcher_add_view( + app->view_dispatcher, PocsagViewSubmenu, submenu_get_view(app->submenu)); + + app->view_id = PocsagViewSubmenu; + view_dispatcher_switch_to_view(app->view_dispatcher, app->view_id); + + app->subghz_tx_rx = pocsag_tx_rx_worker_alloc(); + pocsag_tx_rx_worker_set_callback_have_read(app->subghz_tx_rx, subghz_tx_rx_read_callback, app); +// pocsag_hal_reset(); + + return app; +} + + +void pocsag_app_free(PocsagApp* app) { + furi_assert(app); + view_dispatcher_remove_view(app->view_dispatcher, PocsagViewSubmenu); + submenu_free(app->submenu); + furi_record_close(RECORD_GUI); + app->gui = NULL; + + if(pocsag_tx_rx_worker_is_running(app->subghz_tx_rx)) { + pocsag_tx_rx_worker_stop(app->subghz_tx_rx); + furi_hal_power_suppress_charge_exit(); + } + pocsag_tx_rx_worker_free(app->subghz_tx_rx); + free(app); +} + + +int32_t pocsag(void* p) { + UNUSED(p); + + PocsagApp* app = pocsag_app_alloc(); + view_dispatcher_run(app->view_dispatcher); + pocsag_app_free(app); + return 0; +} diff --git a/applications/plugins/pocsag/pocsag.h b/applications/plugins/pocsag/pocsag.h new file mode 100644 index 000000000..1cb6965f5 --- /dev/null +++ b/applications/plugins/pocsag/pocsag.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include +#include "core/pocsag_tx_rx_worker.h" + + +typedef struct { + Gui* gui; + ViewDispatcher* view_dispatcher; + Submenu* submenu; + + uint32_t view_id; + PocsagTxRxWorker* subghz_tx_rx; +} PocsagApp; + + +enum { + PocsagViewSubmenu, +}; \ No newline at end of file diff --git a/applications/plugins/pocsag/pocsag.png b/applications/plugins/pocsag/pocsag.png new file mode 100644 index 000000000..6bfdb639c Binary files /dev/null and b/applications/plugins/pocsag/pocsag.png differ