Add POCSAG Receiver plugin

This commit is contained in:
MX 2022-12-10 20:08:17 +03:00
parent 35a4eef3fc
commit 2fcff37f88
No known key found for this signature in database
GPG key ID: 6C4C311DFD4B4AB5
35 changed files with 2718 additions and 0 deletions

View file

@ -109,6 +109,7 @@ Also check changelog in releases for latest updates!
- BH1750 - Lightmeter [(by oleksiikutuzov)](https://github.com/oleksiikutuzov/flipperzero-lightmeter)
- iButton Fuzzer [(by xMasterX)](https://github.com/xMasterX/ibutton-fuzzer)
- HEX Viewer [(by QtRoS)](https://github.com/QtRoS/flipper-zero-hex-viewer)
- POCSAG Pager [(by xMasterX & Shmuma)](https://github.com/xMasterX/flipper-pager)
Games:
- DOOM (fixed) [(by p4nic4ttack)](https://github.com/p4nic4ttack/doom-flipper-zero/)

View file

@ -0,0 +1,13 @@
App(
appid="pocsag_pager",
name="POCSAG Pager",
apptype=FlipperAppType.PLUGIN,
entry_point="pocsag_pager_app",
cdefines=["APP_POCSAG_PAGER"],
requires=["gui"],
stack_size=4 * 1024,
order=50,
fap_icon="pocsag_pager_10px.png",
fap_category="Tools",
fap_icon_assets="images",
)

View file

@ -0,0 +1,14 @@
#pragma once
typedef enum {
//PCSGCustomEvent
PCSGCustomEventStartId = 100,
PCSGCustomEventSceneSettingLock,
PCSGCustomEventViewReceiverOK,
PCSGCustomEventViewReceiverConfig,
PCSGCustomEventViewReceiverBack,
PCSGCustomEventViewReceiverOffDisplay,
PCSGCustomEventViewReceiverUnlock,
} PCSGCustomEvent;

View file

@ -0,0 +1,49 @@
#pragma once
#include <furi.h>
#include <furi_hal.h>
#define PCSG_VERSION_APP "0.1"
#define PCSG_DEVELOPED "@xMasterX & @Shmuma"
#define PCSG_GITHUB "https://github.com/xMasterX/flipper-pager"
#define PCSG_KEY_FILE_VERSION 1
#define PCSG_KEY_FILE_TYPE "Flipper POCSAG Pager Key File"
/** PCSGRxKeyState state */
typedef enum {
PCSGRxKeyStateIDLE,
PCSGRxKeyStateBack,
PCSGRxKeyStateStart,
PCSGRxKeyStateAddKey,
} PCSGRxKeyState;
/** PCSGHopperState state */
typedef enum {
PCSGHopperStateOFF,
PCSGHopperStateRunnig,
PCSGHopperStatePause,
PCSGHopperStateRSSITimeOut,
} PCSGHopperState;
/** PCSGLock */
typedef enum {
PCSGLockOff,
PCSGLockOn,
} PCSGLock;
typedef enum {
POCSAGPagerViewVariableItemList,
POCSAGPagerViewSubmenu,
POCSAGPagerViewReceiver,
POCSAGPagerViewReceiverInfo,
POCSAGPagerViewWidget,
} POCSAGPagerView;
/** POCSAGPagerTxRx state */
typedef enum {
PCSGTxRxStateIDLE,
PCSGTxRxStateRx,
PCSGTxRxStateTx,
PCSGTxRxStateSleep,
} PCSGTxRxState;

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 B

View file

@ -0,0 +1,210 @@
#include "pocsag_pager_app_i.h"
#include <furi.h>
#include <furi_hal.h>
#include <lib/flipper_format/flipper_format.h>
#include "protocols/protocol_items.h"
// Comment next line to build on OFW
#define IS_UNLEASHED
static bool pocsag_pager_app_custom_event_callback(void* context, uint32_t event) {
furi_assert(context);
POCSAGPagerApp* app = context;
return scene_manager_handle_custom_event(app->scene_manager, event);
}
static bool pocsag_pager_app_back_event_callback(void* context) {
furi_assert(context);
POCSAGPagerApp* app = context;
return scene_manager_handle_back_event(app->scene_manager);
}
static void pocsag_pager_app_tick_event_callback(void* context) {
furi_assert(context);
POCSAGPagerApp* app = context;
scene_manager_handle_tick_event(app->scene_manager);
}
POCSAGPagerApp* pocsag_pager_app_alloc() {
POCSAGPagerApp* app = malloc(sizeof(POCSAGPagerApp));
// GUI
app->gui = furi_record_open(RECORD_GUI);
// View Dispatcher
app->view_dispatcher = view_dispatcher_alloc();
app->scene_manager = scene_manager_alloc(&pocsag_pager_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, pocsag_pager_app_custom_event_callback);
view_dispatcher_set_navigation_event_callback(
app->view_dispatcher, pocsag_pager_app_back_event_callback);
view_dispatcher_set_tick_event_callback(
app->view_dispatcher, pocsag_pager_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);
// Variable Item List
app->variable_item_list = variable_item_list_alloc();
view_dispatcher_add_view(
app->view_dispatcher,
POCSAGPagerViewVariableItemList,
variable_item_list_get_view(app->variable_item_list));
// SubMenu
app->submenu = submenu_alloc();
view_dispatcher_add_view(
app->view_dispatcher, POCSAGPagerViewSubmenu, submenu_get_view(app->submenu));
// Widget
app->widget = widget_alloc();
view_dispatcher_add_view(
app->view_dispatcher, POCSAGPagerViewWidget, widget_get_view(app->widget));
// Receiver
app->pcsg_receiver = pcsg_view_receiver_alloc();
view_dispatcher_add_view(
app->view_dispatcher,
POCSAGPagerViewReceiver,
pcsg_view_receiver_get_view(app->pcsg_receiver));
// Receiver Info
app->pcsg_receiver_info = pcsg_view_receiver_info_alloc();
view_dispatcher_add_view(
app->view_dispatcher,
POCSAGPagerViewReceiverInfo,
pcsg_view_receiver_info_get_view(app->pcsg_receiver_info));
//init setting
app->setting = subghz_setting_alloc();
//ToDo FIX file name setting
#ifdef IS_UNLEASHED
subghz_setting_load(app->setting, EXT_PATH("pocsag/settings.txt"), true);
#else
subghz_setting_load(app->setting, EXT_PATH("pocsag/settings.txt"));
#endif
//init Worker & Protocol & History
app->lock = PCSGLockOff;
app->txrx = malloc(sizeof(POCSAGPagerTxRx));
app->txrx->preset = malloc(sizeof(SubGhzRadioPreset));
app->txrx->preset->name = furi_string_alloc();
// Custom Presets load without using config file
FlipperFormat* temp_fm_preset = flipper_format_string_alloc();
flipper_format_write_string_cstr(
temp_fm_preset,
(const char*)"Custom_preset_data",
(const char*)"02 0D 0B 06 08 32 07 04 14 00 13 02 12 04 11 83 10 67 15 24 18 18 19 16 1D 91 1C 00 1B 07 20 FB 22 10 21 56 00 00 C0 00 00 00 00 00 00 00");
flipper_format_rewind(temp_fm_preset);
subghz_setting_load_custom_preset(app->setting, (const char*)"FM95", temp_fm_preset);
flipper_format_free(temp_fm_preset);
FlipperFormat* temp_fm_preset2 = flipper_format_string_alloc();
flipper_format_write_string_cstr(
temp_fm_preset2,
(const char*)"Custom_preset_data",
(const char*)"02 0D 0B 06 08 32 07 04 14 00 13 02 12 04 11 83 10 67 15 31 18 18 19 16 1D 91 1C 00 1B 07 20 FB 22 10 21 56 00 00 C0 00 00 00 00 00 00 00");
flipper_format_rewind(temp_fm_preset2);
subghz_setting_load_custom_preset(app->setting, (const char*)"FM150", temp_fm_preset2);
flipper_format_free(temp_fm_preset2);
// custom presets loading - end
pcsg_preset_init(app, "FM95", 439987500, NULL, 0);
app->txrx->hopper_state = PCSGHopperStateOFF;
app->txrx->history = pcsg_history_alloc();
app->txrx->worker = subghz_worker_alloc();
app->txrx->environment = subghz_environment_alloc();
subghz_environment_set_protocol_registry(
app->txrx->environment, (void*)&pocsag_pager_protocol_registry);
app->txrx->receiver = subghz_receiver_alloc_init(app->txrx->environment);
subghz_receiver_set_filter(app->txrx->receiver, SubGhzProtocolFlag_Decodable);
subghz_worker_set_overrun_callback(
app->txrx->worker, (SubGhzWorkerOverrunCallback)subghz_receiver_reset);
subghz_worker_set_pair_callback(
app->txrx->worker, (SubGhzWorkerPairCallback)subghz_receiver_decode);
subghz_worker_set_context(app->txrx->worker, app->txrx->receiver);
furi_hal_power_suppress_charge_enter();
scene_manager_next_scene(app->scene_manager, POCSAGPagerSceneStart);
return app;
}
void pocsag_pager_app_free(POCSAGPagerApp* app) {
furi_assert(app);
//CC1101 off
pcsg_sleep(app);
// Submenu
view_dispatcher_remove_view(app->view_dispatcher, POCSAGPagerViewSubmenu);
submenu_free(app->submenu);
// Variable Item List
view_dispatcher_remove_view(app->view_dispatcher, POCSAGPagerViewVariableItemList);
variable_item_list_free(app->variable_item_list);
// Widget
view_dispatcher_remove_view(app->view_dispatcher, POCSAGPagerViewWidget);
widget_free(app->widget);
// Receiver
view_dispatcher_remove_view(app->view_dispatcher, POCSAGPagerViewReceiver);
pcsg_view_receiver_free(app->pcsg_receiver);
// Receiver Info
view_dispatcher_remove_view(app->view_dispatcher, POCSAGPagerViewReceiverInfo);
pcsg_view_receiver_info_free(app->pcsg_receiver_info);
//setting
subghz_setting_free(app->setting);
//Worker & Protocol & History
subghz_receiver_free(app->txrx->receiver);
subghz_environment_free(app->txrx->environment);
pcsg_history_free(app->txrx->history);
subghz_worker_free(app->txrx->worker);
furi_string_free(app->txrx->preset->name);
free(app->txrx->preset);
free(app->txrx);
// 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);
furi_hal_power_suppress_charge_exit();
free(app);
}
int32_t pocsag_pager_app(void* p) {
UNUSED(p);
POCSAGPagerApp* pocsag_pager_app = pocsag_pager_app_alloc();
view_dispatcher_run(pocsag_pager_app->view_dispatcher);
pocsag_pager_app_free(pocsag_pager_app);
return 0;
}

View file

@ -0,0 +1,141 @@
#include "pocsag_pager_app_i.h"
#define TAG "POCSAGPager"
#include <flipper_format/flipper_format_i.h>
void pcsg_preset_init(
void* context,
const char* preset_name,
uint32_t frequency,
uint8_t* preset_data,
size_t preset_data_size) {
furi_assert(context);
POCSAGPagerApp* app = context;
furi_string_set(app->txrx->preset->name, preset_name);
app->txrx->preset->frequency = frequency;
app->txrx->preset->data = preset_data;
app->txrx->preset->data_size = preset_data_size;
}
void pcsg_get_frequency_modulation(
POCSAGPagerApp* app,
FuriString* frequency,
FuriString* modulation) {
furi_assert(app);
if(frequency != NULL) {
furi_string_printf(
frequency,
"%03ld.%02ld",
app->txrx->preset->frequency / 1000000 % 1000,
app->txrx->preset->frequency / 10000 % 100);
}
if(modulation != NULL) {
furi_string_printf(modulation, "%.2s", furi_string_get_cstr(app->txrx->preset->name));
}
}
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(&gpio_cc1101_g0, 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)) {
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(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow);
furi_hal_subghz_flush_rx();
furi_hal_subghz_rx();
furi_hal_subghz_start_async_rx(subghz_worker_rx_callback, app->txrx->worker);
subghz_worker_start(app->txrx->worker);
app->txrx->txrx_state = PCSGTxRxStateRx;
return value;
}
void pcsg_idle(POCSAGPagerApp* app) {
furi_assert(app);
furi_assert(app->txrx->txrx_state != PCSGTxRxStateSleep);
furi_hal_subghz_idle();
app->txrx->txrx_state = PCSGTxRxStateIDLE;
}
void pcsg_rx_end(POCSAGPagerApp* app) {
furi_assert(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();
}
furi_hal_subghz_idle();
app->txrx->txrx_state = PCSGTxRxStateIDLE;
}
void pcsg_sleep(POCSAGPagerApp* app) {
furi_assert(app);
furi_hal_subghz_sleep();
app->txrx->txrx_state = PCSGTxRxStateSleep;
}
void pcsg_hopper_update(POCSAGPagerApp* app) {
furi_assert(app);
switch(app->txrx->hopper_state) {
case PCSGHopperStateOFF:
return;
break;
case PCSGHopperStatePause:
return;
break;
case PCSGHopperStateRSSITimeOut:
if(app->txrx->hopper_timeout != 0) {
app->txrx->hopper_timeout--;
return;
}
break;
default:
break;
}
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();
// Stay if RSSI is high enough
if(rssi > -90.0f) {
app->txrx->hopper_timeout = 10;
app->txrx->hopper_state = PCSGHopperStateRSSITimeOut;
return;
}
} else {
app->txrx->hopper_state = PCSGHopperStateRunnig;
}
// Select next frequency
if(app->txrx->hopper_idx_frequency <
subghz_setting_get_hopper_frequency_count(app->setting) - 1) {
app->txrx->hopper_idx_frequency++;
} else {
app->txrx->hopper_idx_frequency = 0;
}
if(app->txrx->txrx_state == PCSGTxRxStateRx) {
pcsg_rx_end(app);
};
if(app->txrx->txrx_state == PCSGTxRxStateIDLE) {
subghz_receiver_reset(app->txrx->receiver);
app->txrx->preset->frequency =
subghz_setting_get_hopper_frequency(app->setting, app->txrx->hopper_idx_frequency);
pcsg_rx(app, app->txrx->preset->frequency);
}
}

View file

@ -0,0 +1,72 @@
#pragma once
#include "helpers/pocsag_pager_types.h"
#include "scenes/pocsag_pager_scene.h"
#include <gui/gui.h>
#include <gui/view_dispatcher.h>
#include <gui/scene_manager.h>
#include <gui/modules/submenu.h>
#include <gui/modules/variable_item_list.h>
#include <gui/modules/widget.h>
#include <notification/notification_messages.h>
#include "views/pocsag_pager_receiver.h"
#include "views/pocsag_pager_receiver_info.h"
#include "pocsag_pager_history.h"
#include <lib/subghz/subghz_setting.h>
#include <lib/subghz/subghz_worker.h>
#include <lib/subghz/receiver.h>
#include <lib/subghz/transmitter.h>
#include <lib/subghz/registry.h>
typedef struct POCSAGPagerApp POCSAGPagerApp;
struct POCSAGPagerTxRx {
SubGhzWorker* worker;
SubGhzEnvironment* environment;
SubGhzReceiver* receiver;
SubGhzRadioPreset* preset;
PCSGHistory* history;
uint16_t idx_menu_chosen;
PCSGTxRxState txrx_state;
PCSGHopperState hopper_state;
uint8_t hopper_timeout;
uint8_t hopper_idx_frequency;
PCSGRxKeyState rx_key_state;
};
typedef struct POCSAGPagerTxRx POCSAGPagerTxRx;
struct POCSAGPagerApp {
Gui* gui;
ViewDispatcher* view_dispatcher;
POCSAGPagerTxRx* txrx;
SceneManager* scene_manager;
NotificationApp* notifications;
VariableItemList* variable_item_list;
Submenu* submenu;
Widget* widget;
PCSGReceiver* pcsg_receiver;
PCSGReceiverInfo* pcsg_receiver_info;
PCSGLock lock;
SubGhzSetting* setting;
};
void pcsg_preset_init(
void* context,
const char* preset_name,
uint32_t frequency,
uint8_t* preset_data,
size_t preset_data_size);
void pcsg_get_frequency_modulation(
POCSAGPagerApp* app,
FuriString* frequency,
FuriString* modulation);
void pcsg_begin(POCSAGPagerApp* app, uint8_t* preset_data);
uint32_t pcsg_rx(POCSAGPagerApp* app, uint32_t frequency);
void pcsg_idle(POCSAGPagerApp* app);
void pcsg_rx_end(POCSAGPagerApp* app);
void pcsg_sleep(POCSAGPagerApp* app);
void pcsg_hopper_update(POCSAGPagerApp* app);

View file

@ -0,0 +1,223 @@
#include "pocsag_pager_history.h"
#include <flipper_format/flipper_format_i.h>
#include <lib/toolbox/stream/stream.h>
#include <lib/subghz/receiver.h>
#include "protocols/pcsg_generic.h"
#include <furi.h>
#define PCSG_HISTORY_MAX 50
#define TAG "PCSGHistory"
typedef struct {
FuriString* item_str;
FlipperFormat* flipper_string;
uint8_t type;
SubGhzRadioPreset* preset;
} PCSGHistoryItem;
ARRAY_DEF(PCSGHistoryItemArray, PCSGHistoryItem, M_POD_OPLIST)
#define M_OPL_PCSGHistoryItemArray_t() ARRAY_OPLIST(PCSGHistoryItemArray, M_POD_OPLIST)
typedef struct {
PCSGHistoryItemArray_t data;
} PCSGHistoryStruct;
struct PCSGHistory {
uint32_t last_update_timestamp;
uint16_t last_index_write;
uint8_t code_last_hash_data;
FuriString* tmp_string;
PCSGHistoryStruct* history;
};
PCSGHistory* pcsg_history_alloc(void) {
PCSGHistory* instance = malloc(sizeof(PCSGHistory));
instance->tmp_string = furi_string_alloc();
instance->history = malloc(sizeof(PCSGHistoryStruct));
PCSGHistoryItemArray_init(instance->history->data);
return instance;
}
void pcsg_history_free(PCSGHistory* instance) {
furi_assert(instance);
furi_string_free(instance->tmp_string);
for
M_EACH(item, instance->history->data, PCSGHistoryItemArray_t) {
furi_string_free(item->item_str);
furi_string_free(item->preset->name);
free(item->preset);
flipper_format_free(item->flipper_string);
item->type = 0;
}
PCSGHistoryItemArray_clear(instance->history->data);
free(instance->history);
free(instance);
}
uint32_t pcsg_history_get_frequency(PCSGHistory* instance, uint16_t idx) {
furi_assert(instance);
PCSGHistoryItem* item = PCSGHistoryItemArray_get(instance->history->data, idx);
return item->preset->frequency;
}
SubGhzRadioPreset* pcsg_history_get_radio_preset(PCSGHistory* instance, uint16_t idx) {
furi_assert(instance);
PCSGHistoryItem* item = PCSGHistoryItemArray_get(instance->history->data, idx);
return item->preset;
}
const char* pcsg_history_get_preset(PCSGHistory* instance, uint16_t idx) {
furi_assert(instance);
PCSGHistoryItem* item = PCSGHistoryItemArray_get(instance->history->data, idx);
return furi_string_get_cstr(item->preset->name);
}
void pcsg_history_reset(PCSGHistory* instance) {
furi_assert(instance);
furi_string_reset(instance->tmp_string);
for
M_EACH(item, instance->history->data, PCSGHistoryItemArray_t) {
furi_string_free(item->item_str);
furi_string_free(item->preset->name);
free(item->preset);
flipper_format_free(item->flipper_string);
item->type = 0;
}
PCSGHistoryItemArray_reset(instance->history->data);
instance->last_index_write = 0;
instance->code_last_hash_data = 0;
}
uint16_t pcsg_history_get_item(PCSGHistory* instance) {
furi_assert(instance);
return instance->last_index_write;
}
uint8_t pcsg_history_get_type_protocol(PCSGHistory* instance, uint16_t idx) {
furi_assert(instance);
PCSGHistoryItem* item = PCSGHistoryItemArray_get(instance->history->data, idx);
return item->type;
}
const char* pcsg_history_get_protocol_name(PCSGHistory* instance, uint16_t idx) {
furi_assert(instance);
PCSGHistoryItem* item = PCSGHistoryItemArray_get(instance->history->data, idx);
flipper_format_rewind(item->flipper_string);
if(!flipper_format_read_string(item->flipper_string, "Protocol", instance->tmp_string)) {
FURI_LOG_E(TAG, "Missing Protocol");
furi_string_reset(instance->tmp_string);
}
return furi_string_get_cstr(instance->tmp_string);
}
FlipperFormat* pcsg_history_get_raw_data(PCSGHistory* instance, uint16_t idx) {
furi_assert(instance);
PCSGHistoryItem* item = PCSGHistoryItemArray_get(instance->history->data, idx);
if(item->flipper_string) {
return item->flipper_string;
} else {
return NULL;
}
}
bool pcsg_history_get_text_space_left(PCSGHistory* instance, FuriString* output) {
furi_assert(instance);
if(instance->last_index_write == PCSG_HISTORY_MAX) {
if(output != NULL) furi_string_printf(output, "Memory is FULL");
return true;
}
if(output != NULL)
furi_string_printf(output, "%02u/%02u", instance->last_index_write, PCSG_HISTORY_MAX);
return false;
}
void pcsg_history_get_text_item_menu(PCSGHistory* instance, FuriString* output, uint16_t idx) {
PCSGHistoryItem* item = PCSGHistoryItemArray_get(instance->history->data, idx);
furi_string_set(output, item->item_str);
}
PCSGHistoryStateAddKey
pcsg_history_add_to_history(PCSGHistory* instance, void* context, SubGhzRadioPreset* preset) {
furi_assert(instance);
furi_assert(context);
if(instance->last_index_write >= PCSG_HISTORY_MAX) return PCSGHistoryStateAddKeyOverflow;
SubGhzProtocolDecoderBase* decoder_base = context;
if((instance->code_last_hash_data ==
subghz_protocol_decoder_base_get_hash_data(decoder_base)) &&
((furi_get_tick() - instance->last_update_timestamp) < 500)) {
instance->last_update_timestamp = furi_get_tick();
return PCSGHistoryStateAddKeyTimeOut;
}
instance->code_last_hash_data = subghz_protocol_decoder_base_get_hash_data(decoder_base);
instance->last_update_timestamp = furi_get_tick();
FlipperFormat* fff = flipper_format_string_alloc();
subghz_protocol_decoder_base_serialize(decoder_base, fff, preset);
do {
if(!flipper_format_rewind(fff)) {
FURI_LOG_E(TAG, "Rewind error");
break;
}
} while(false);
flipper_format_free(fff);
PCSGHistoryItem* item = PCSGHistoryItemArray_push_raw(instance->history->data);
item->preset = malloc(sizeof(SubGhzRadioPreset));
item->type = decoder_base->protocol->type;
item->preset->frequency = preset->frequency;
item->preset->name = furi_string_alloc();
furi_string_set(item->preset->name, preset->name);
item->preset->data = preset->data;
item->preset->data_size = preset->data_size;
item->item_str = furi_string_alloc();
item->flipper_string = flipper_format_string_alloc();
subghz_protocol_decoder_base_serialize(decoder_base, item->flipper_string, preset);
do {
if(!flipper_format_rewind(item->flipper_string)) {
FURI_LOG_E(TAG, "Rewind error");
break;
}
if(!flipper_format_read_string(item->flipper_string, "Protocol", instance->tmp_string)) {
FURI_LOG_E(TAG, "Missing Protocol");
break;
}
if(!flipper_format_rewind(item->flipper_string)) {
FURI_LOG_E(TAG, "Rewind error");
break;
}
FuriString* temp_ric = furi_string_alloc();
if(!flipper_format_read_string(item->flipper_string, "Ric", temp_ric)) {
FURI_LOG_E(TAG, "Missing Ric");
break;
}
FuriString* temp_message = furi_string_alloc();
if(!flipper_format_read_string(item->flipper_string, "Message", temp_message)) {
FURI_LOG_E(TAG, "Missing Message");
break;
}
furi_string_printf(
item->item_str,
"%s%s",
furi_string_get_cstr(temp_ric),
furi_string_get_cstr(temp_message));
furi_string_free(temp_message);
furi_string_free(temp_ric);
} while(false);
instance->last_index_write++;
return PCSGHistoryStateAddKeyNewDada;
return PCSGHistoryStateAddKeyUnknown;
}

View file

@ -0,0 +1,112 @@
#pragma once
#include <math.h>
#include <furi.h>
#include <furi_hal.h>
#include <lib/flipper_format/flipper_format.h>
#include <lib/subghz/types.h>
typedef struct PCSGHistory PCSGHistory;
/** History state add key */
typedef enum {
PCSGHistoryStateAddKeyUnknown,
PCSGHistoryStateAddKeyTimeOut,
PCSGHistoryStateAddKeyNewDada,
PCSGHistoryStateAddKeyUpdateData,
PCSGHistoryStateAddKeyOverflow,
} PCSGHistoryStateAddKey;
/** Allocate PCSGHistory
*
* @return PCSGHistory*
*/
PCSGHistory* pcsg_history_alloc(void);
/** Free PCSGHistory
*
* @param instance - PCSGHistory instance
*/
void pcsg_history_free(PCSGHistory* instance);
/** Clear history
*
* @param instance - PCSGHistory instance
*/
void pcsg_history_reset(PCSGHistory* instance);
/** Get frequency to history[idx]
*
* @param instance - PCSGHistory instance
* @param idx - record index
* @return frequency - frequency Hz
*/
uint32_t pcsg_history_get_frequency(PCSGHistory* instance, uint16_t idx);
SubGhzRadioPreset* pcsg_history_get_radio_preset(PCSGHistory* instance, uint16_t idx);
/** Get preset to history[idx]
*
* @param instance - PCSGHistory instance
* @param idx - record index
* @return preset - preset name
*/
const char* pcsg_history_get_preset(PCSGHistory* instance, uint16_t idx);
/** Get history index write
*
* @param instance - PCSGHistory instance
* @return idx - current record index
*/
uint16_t pcsg_history_get_item(PCSGHistory* instance);
/** Get type protocol to history[idx]
*
* @param instance - PCSGHistory instance
* @param idx - record index
* @return type - type protocol
*/
uint8_t pcsg_history_get_type_protocol(PCSGHistory* instance, uint16_t idx);
/** Get name protocol to history[idx]
*
* @param instance - PCSGHistory instance
* @param idx - record index
* @return name - const char* name protocol
*/
const char* pcsg_history_get_protocol_name(PCSGHistory* instance, uint16_t idx);
/** Get string item menu to history[idx]
*
* @param instance - PCSGHistory instance
* @param output - FuriString* output
* @param idx - record index
*/
void pcsg_history_get_text_item_menu(PCSGHistory* instance, FuriString* output, uint16_t idx);
/** Get string the remaining number of records to history
*
* @param instance - PCSGHistory instance
* @param output - FuriString* output
* @return bool - is FUUL
*/
bool pcsg_history_get_text_space_left(PCSGHistory* instance, FuriString* output);
/** Add protocol to history
*
* @param instance - PCSGHistory instance
* @param context - SubGhzProtocolCommon context
* @param preset - SubGhzRadioPreset preset
* @return PCSGHistoryStateAddKey;
*/
PCSGHistoryStateAddKey
pcsg_history_add_to_history(PCSGHistory* instance, void* context, SubGhzRadioPreset* preset);
/** Get SubGhzProtocolCommonLoad to load into the protocol decoder bin data
*
* @param instance - PCSGHistory instance
* @param idx - record index
* @return SubGhzProtocolCommonLoad*
*/
FlipperFormat* pcsg_history_get_raw_data(PCSGHistory* instance, uint16_t idx);

View file

@ -0,0 +1,123 @@
#include "pcsg_generic.h"
#include <lib/toolbox/stream/stream.h>
#include <lib/flipper_format/flipper_format_i.h>
#include "../helpers/pocsag_pager_types.h"
#define TAG "PCSGBlockGeneric"
void pcsg_block_generic_get_preset_name(const char* preset_name, FuriString* preset_str) {
const char* preset_name_temp;
if(!strcmp(preset_name, "AM270")) {
preset_name_temp = "FuriHalSubGhzPresetOok270Async";
} else if(!strcmp(preset_name, "AM650")) {
preset_name_temp = "FuriHalSubGhzPresetOok650Async";
} else if(!strcmp(preset_name, "FM238")) {
preset_name_temp = "FuriHalSubGhzPreset2FSKDev238Async";
} else if(!strcmp(preset_name, "FM476")) {
preset_name_temp = "FuriHalSubGhzPreset2FSKDev476Async";
} else {
preset_name_temp = "FuriHalSubGhzPresetCustom";
}
furi_string_set(preset_str, preset_name_temp);
}
bool pcsg_block_generic_serialize(
PCSGBlockGeneric* instance,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
furi_assert(instance);
bool res = false;
FuriString* temp_str;
temp_str = furi_string_alloc();
do {
stream_clean(flipper_format_get_raw_stream(flipper_format));
if(!flipper_format_write_header_cstr(
flipper_format, PCSG_KEY_FILE_TYPE, PCSG_KEY_FILE_VERSION)) {
FURI_LOG_E(TAG, "Unable to add header");
break;
}
if(!flipper_format_write_uint32(flipper_format, "Frequency", &preset->frequency, 1)) {
FURI_LOG_E(TAG, "Unable to add Frequency");
break;
}
pcsg_block_generic_get_preset_name(furi_string_get_cstr(preset->name), temp_str);
if(!flipper_format_write_string_cstr(
flipper_format, "Preset", furi_string_get_cstr(temp_str))) {
FURI_LOG_E(TAG, "Unable to add Preset");
break;
}
if(!strcmp(furi_string_get_cstr(temp_str), "FuriHalSubGhzPresetCustom")) {
if(!flipper_format_write_string_cstr(
flipper_format, "Custom_preset_module", "CC1101")) {
FURI_LOG_E(TAG, "Unable to add Custom_preset_module");
break;
}
if(!flipper_format_write_hex(
flipper_format, "Custom_preset_data", preset->data, preset->data_size)) {
FURI_LOG_E(TAG, "Unable to add Custom_preset_data");
break;
}
}
if(!flipper_format_write_string_cstr(flipper_format, "Protocol", instance->protocol_name)) {
FURI_LOG_E(TAG, "Unable to add Protocol");
break;
}
if(!flipper_format_write_string(flipper_format, "Ric", instance->result_ric)) {
FURI_LOG_E(TAG, "Unable to add Ric");
break;
}
if(!flipper_format_write_string(flipper_format, "Message", instance->result_msg)) {
FURI_LOG_E(TAG, "Unable to add Message");
break;
}
res = true;
} while(false);
furi_string_free(temp_str);
return res;
}
bool pcsg_block_generic_deserialize(PCSGBlockGeneric* instance, FlipperFormat* flipper_format) {
furi_assert(instance);
bool res = false;
FuriString* temp_data = furi_string_alloc();
FuriString* temp_data2 = furi_string_alloc();
do {
if(!flipper_format_rewind(flipper_format)) {
FURI_LOG_E(TAG, "Rewind error");
break;
}
if(!flipper_format_read_string(flipper_format, "Ric", temp_data2)) {
FURI_LOG_E(TAG, "Missing Ric");
break;
}
if(instance->result_ric != NULL) {
furi_string_set(instance->result_ric, temp_data2);
} else {
instance->result_ric = furi_string_alloc_set(temp_data2);
}
if(!flipper_format_read_string(flipper_format, "Message", temp_data)) {
FURI_LOG_E(TAG, "Missing Message");
break;
}
if(instance->result_msg != NULL) {
furi_string_set(instance->result_msg, temp_data);
} else {
instance->result_msg = furi_string_alloc_set(temp_data);
}
res = true;
} while(0);
furi_string_free(temp_data);
furi_string_free(temp_data2);
return res;
}

View file

@ -0,0 +1,55 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <stddef.h>
#include <lib/flipper_format/flipper_format.h>
#include "furi.h"
#include "furi_hal.h"
#include <lib/subghz/types.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef struct PCSGBlockGeneric PCSGBlockGeneric;
struct PCSGBlockGeneric {
const char* protocol_name;
FuriString* result_ric;
FuriString* result_msg;
};
/**
* Get name preset.
* @param preset_name name preset
* @param preset_str Output name preset
*/
void pcsg_block_generic_get_preset_name(const char* preset_name, FuriString* preset_str);
/**
* Serialize data PCSGBlockGeneric.
* @param instance Pointer to a PCSGBlockGeneric instance
* @param flipper_format Pointer to a FlipperFormat instance
* @param preset The modulation on which the signal was received, SubGhzRadioPreset
* @return true On success
*/
bool pcsg_block_generic_serialize(
PCSGBlockGeneric* instance,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset);
/**
* Deserialize data PCSGBlockGeneric.
* @param instance Pointer to a PCSGBlockGeneric instance
* @param flipper_format Pointer to a FlipperFormat instance
* @return true On success
*/
bool pcsg_block_generic_deserialize(PCSGBlockGeneric* instance, FlipperFormat* flipper_format);
float pcsg_block_generic_fahrenheit_to_celsius(float fahrenheit);
#ifdef __cplusplus
}
#endif

View file

@ -0,0 +1,371 @@
#include "pocsag.h"
#include <inttypes.h>
#include <lib/flipper_format/flipper_format_i.h>
#include <furi/core/string.h>
#define TAG "POCSAG"
static const SubGhzBlockConst pocsag_const = {
.te_short = 833,
.te_delta = 100,
};
// Minimal amount of sync bits (interleaving zeros and ones)
#define POCSAG_MIN_SYNC_BITS 32
#define POCSAG_CW_BITS 32
#define POCSAG_CW_MASK 0xFFFFFFFF
#define POCSAG_FRAME_SYNC_CODE 0x7CD215D8
#define POCSAG_IDLE_CODE_WORD 0x7A89C197
#define POCSAG_FUNC_NUM 0
#define POCSAG_FUNC_ALERT1 1
#define POCSAG_FUNC_ALERT2 2
#define POCSAG_FUNC_ALPHANUM 3
static const char* func_msg[] = {"\e#Num:\e# ", "\e#Alert\e#", "\e#Alert:\e# ", "\e#Msg:\e# "};
static const char* bcd_chars = "*U -)(";
struct SubGhzProtocolDecoderPocsag {
SubGhzProtocolDecoderBase base;
SubGhzBlockDecoder decoder;
PCSGBlockGeneric generic;
uint8_t codeword_idx;
uint32_t ric;
uint8_t func;
// partially decoded character
uint8_t char_bits;
uint8_t char_data;
// message being decoded
FuriString* msg;
// Done messages, ready to be serialized/deserialized
FuriString* done_msg;
};
typedef struct SubGhzProtocolDecoderPocsag SubGhzProtocolDecoderPocsag;
typedef enum {
PocsagDecoderStepReset = 0,
PocsagDecoderStepFoundSync,
PocsagDecoderStepFoundPreamble,
PocsagDecoderStepMessage,
} PocsagDecoderStep;
void* subghz_protocol_decoder_pocsag_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolDecoderPocsag* instance = malloc(sizeof(SubGhzProtocolDecoderPocsag));
instance->base.protocol = &subghz_protocol_pocsag;
instance->generic.protocol_name = instance->base.protocol->name;
instance->msg = furi_string_alloc();
instance->done_msg = furi_string_alloc();
if(instance->generic.result_msg == NULL) {
instance->generic.result_msg = furi_string_alloc();
}
if(instance->generic.result_ric == NULL) {
instance->generic.result_ric = furi_string_alloc();
}
return instance;
}
void subghz_protocol_decoder_pocsag_free(void* context) {
furi_assert(context);
SubGhzProtocolDecoderPocsag* instance = context;
furi_string_free(instance->msg);
furi_string_free(instance->done_msg);
free(instance);
}
void subghz_protocol_decoder_pocsag_reset(void* context) {
furi_assert(context);
SubGhzProtocolDecoderPocsag* instance = context;
instance->decoder.parser_step = PocsagDecoderStepReset;
instance->decoder.decode_data = 0UL;
instance->decoder.decode_count_bit = 0;
instance->codeword_idx = 0;
instance->char_bits = 0;
instance->char_data = 0;
furi_string_reset(instance->msg);
furi_string_reset(instance->done_msg);
furi_string_reset(instance->generic.result_msg);
furi_string_reset(instance->generic.result_ric);
}
static void pocsag_decode_address_word(SubGhzProtocolDecoderPocsag* instance, uint32_t data) {
instance->ric = (data >> 13);
instance->ric = (instance->ric << 3) | (instance->codeword_idx >> 1);
instance->func = (data >> 11) & 0b11;
}
static bool decode_message_alphanumeric(SubGhzProtocolDecoderPocsag* instance, uint32_t data) {
for(uint8_t i = 0; i < 20; i++) {
instance->char_data >>= 1;
if(data & (1 << 30)) {
instance->char_data |= 1 << 6;
}
instance->char_bits++;
if(instance->char_bits == 7) {
if(instance->char_data == 0) return false;
furi_string_push_back(instance->msg, instance->char_data);
instance->char_data = 0;
instance->char_bits = 0;
}
data <<= 1;
}
return true;
}
static void decode_message_numeric(SubGhzProtocolDecoderPocsag* instance, uint32_t data) {
// 5 groups with 4 bits each
uint8_t val;
for(uint8_t i = 0; i < 5; i++) {
val = (data >> (27 - i * 4)) & 0b1111;
// reverse the order of 4 bits
val = (val & 0x5) << 1 | (val & 0xA) >> 1;
val = (val & 0x3) << 2 | (val & 0xC) >> 2;
if(val <= 9)
val += '0';
else
val = bcd_chars[val - 10];
furi_string_push_back(instance->msg, val);
}
}
// decode message word, maintaining instance state for partial decoding. Return true if more data
// might follow or false if end of message reached.
static bool pocsag_decode_message_word(SubGhzProtocolDecoderPocsag* instance, uint32_t data) {
switch(instance->func) {
case POCSAG_FUNC_ALERT2:
case POCSAG_FUNC_ALPHANUM:
return decode_message_alphanumeric(instance, data);
case POCSAG_FUNC_NUM:
decode_message_numeric(instance, data);
return true;
}
return false;
}
// Function called when current message got decoded, but other messages might follow
static void pocsag_message_done(SubGhzProtocolDecoderPocsag* instance) {
// append the message to the long-term storage string
furi_string_cat_printf(
instance->generic.result_ric, "\e#RIC: %" PRIu32 "\e# | ", instance->ric);
furi_string_cat_str(instance->generic.result_ric, func_msg[instance->func]);
if(instance->func != POCSAG_FUNC_ALERT1) {
furi_string_cat(instance->done_msg, instance->msg);
}
furi_string_cat_str(instance->done_msg, " ");
furi_string_cat(instance->generic.result_msg, instance->done_msg);
// reset the state
instance->char_bits = 0;
instance->char_data = 0;
furi_string_reset(instance->msg);
}
void subghz_protocol_decoder_pocsag_feed(void* context, bool level, uint32_t duration) {
furi_assert(context);
SubGhzProtocolDecoderPocsag* instance = context;
// reset state - waiting for 32 bits of interleaving 1s and 0s
if(instance->decoder.parser_step == PocsagDecoderStepReset) {
if(DURATION_DIFF(duration, pocsag_const.te_short) < pocsag_const.te_delta) {
// POCSAG signals are inverted
subghz_protocol_blocks_add_bit(&instance->decoder, !level);
if(instance->decoder.decode_count_bit == POCSAG_MIN_SYNC_BITS) {
instance->decoder.parser_step = PocsagDecoderStepFoundSync;
}
} else if(instance->decoder.decode_count_bit > 0) {
subghz_protocol_decoder_pocsag_reset(context);
}
return;
}
int bits_count = duration / pocsag_const.te_short;
uint32_t extra = duration - pocsag_const.te_short * bits_count;
if(DURATION_DIFF(extra, pocsag_const.te_short) < pocsag_const.te_delta)
bits_count++;
else if(extra > pocsag_const.te_delta) {
// in non-reset state we faced the error signal - we reached the end of the packet, flush data
if(furi_string_size(instance->done_msg) > 0) {
if(instance->base.callback)
instance->base.callback(&instance->base, instance->base.context);
}
subghz_protocol_decoder_pocsag_reset(context);
return;
}
uint32_t codeword;
// handle state machine for every incoming bit
while(bits_count-- > 0) {
subghz_protocol_blocks_add_bit(&instance->decoder, !level);
switch(instance->decoder.parser_step) {
case PocsagDecoderStepFoundSync:
if((instance->decoder.decode_data & POCSAG_CW_MASK) == POCSAG_FRAME_SYNC_CODE) {
instance->decoder.parser_step = PocsagDecoderStepFoundPreamble;
instance->decoder.decode_count_bit = 0;
instance->decoder.decode_data = 0UL;
}
break;
case PocsagDecoderStepFoundPreamble:
// handle codewords
if(instance->decoder.decode_count_bit == POCSAG_CW_BITS) {
codeword = (uint32_t)(instance->decoder.decode_data & POCSAG_CW_MASK);
switch(codeword) {
case POCSAG_IDLE_CODE_WORD:
instance->codeword_idx++;
break;
case POCSAG_FRAME_SYNC_CODE:
instance->codeword_idx = 0;
break;
default:
// Here we expect only address messages
if(codeword >> 31 == 0) {
pocsag_decode_address_word(instance, codeword);
instance->decoder.parser_step = PocsagDecoderStepMessage;
}
instance->codeword_idx++;
}
instance->decoder.decode_count_bit = 0;
instance->decoder.decode_data = 0UL;
}
break;
case PocsagDecoderStepMessage:
if(instance->decoder.decode_count_bit == POCSAG_CW_BITS) {
codeword = (uint32_t)(instance->decoder.decode_data & POCSAG_CW_MASK);
switch(codeword) {
case POCSAG_IDLE_CODE_WORD:
// Idle during the message stops the message
instance->codeword_idx++;
instance->decoder.parser_step = PocsagDecoderStepFoundPreamble;
pocsag_message_done(instance);
break;
case POCSAG_FRAME_SYNC_CODE:
instance->codeword_idx = 0;
break;
default:
// In this state, both address and message words can arrive
if(codeword >> 31 == 0) {
pocsag_message_done(instance);
pocsag_decode_address_word(instance, codeword);
} else {
if(!pocsag_decode_message_word(instance, codeword)) {
instance->decoder.parser_step = PocsagDecoderStepFoundPreamble;
pocsag_message_done(instance);
}
}
instance->codeword_idx++;
}
instance->decoder.decode_count_bit = 0;
instance->decoder.decode_data = 0UL;
}
break;
}
}
}
uint8_t subghz_protocol_decoder_pocsag_get_hash_data(void* context) {
furi_assert(context);
SubGhzProtocolDecoderPocsag* instance = context;
uint8_t hash = 0;
for(size_t i = 0; i < furi_string_size(instance->done_msg); i++)
hash ^= furi_string_get_char(instance->done_msg, i);
return hash;
}
bool subghz_protocol_decoder_pocsag_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
furi_assert(context);
SubGhzProtocolDecoderPocsag* instance = context;
uint32_t msg_len;
if(!pcsg_block_generic_serialize(&instance->generic, flipper_format, preset)) return false;
msg_len = furi_string_size(instance->done_msg);
if(!flipper_format_write_uint32(flipper_format, "MsgLen", &msg_len, 1)) {
FURI_LOG_E(TAG, "Error adding MsgLen");
return false;
}
uint8_t* s = (uint8_t*)furi_string_get_cstr(instance->done_msg);
if(!flipper_format_write_hex(flipper_format, "Msg", s, msg_len)) {
FURI_LOG_E(TAG, "Error adding Msg");
return false;
}
return true;
}
bool subghz_protocol_decoder_pocsag_deserialize(void* context, FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolDecoderPocsag* instance = context;
bool ret = false;
uint32_t msg_len;
uint8_t* buf;
do {
if(!pcsg_block_generic_deserialize(&instance->generic, flipper_format)) {
break;
}
if(!flipper_format_read_uint32(flipper_format, "MsgLen", &msg_len, 1)) {
FURI_LOG_E(TAG, "Missing MsgLen");
break;
}
buf = malloc(msg_len);
if(!flipper_format_read_hex(flipper_format, "Msg", buf, msg_len)) {
FURI_LOG_E(TAG, "Missing Msg");
free(buf);
break;
}
furi_string_set_strn(instance->done_msg, (const char*)buf, msg_len);
free(buf);
ret = true;
} while(false);
return ret;
}
void subhz_protocol_decoder_pocsag_get_string(void* context, FuriString* output) {
furi_assert(context);
SubGhzProtocolDecoderPocsag* instance = context;
furi_string_cat_printf(output, "%s\r\n", instance->generic.protocol_name);
furi_string_cat(output, instance->done_msg);
}
const SubGhzProtocolDecoder subghz_protocol_pocsag_decoder = {
.alloc = subghz_protocol_decoder_pocsag_alloc,
.free = subghz_protocol_decoder_pocsag_free,
.reset = subghz_protocol_decoder_pocsag_reset,
.feed = subghz_protocol_decoder_pocsag_feed,
.get_hash_data = subghz_protocol_decoder_pocsag_get_hash_data,
.serialize = subghz_protocol_decoder_pocsag_serialize,
.deserialize = subghz_protocol_decoder_pocsag_deserialize,
.get_string = subhz_protocol_decoder_pocsag_get_string,
};
const SubGhzProtocol subghz_protocol_pocsag = {
.name = SUBGHZ_PROTOCOL_POCSAG_NAME,
.type = SubGhzProtocolTypeStatic,
.flag = SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Save |
SubGhzProtocolFlag_Load,
.decoder = &subghz_protocol_pocsag_decoder,
};

View file

@ -0,0 +1,13 @@
#pragma once
#include <lib/subghz/protocols/base.h>
#include <lib/subghz/blocks/const.h>
#include <lib/subghz/blocks/decoder.h>
#include <lib/subghz/blocks/encoder.h>
#include "pcsg_generic.h"
#include <lib/subghz/blocks/math.h>
#define SUBGHZ_PROTOCOL_POCSAG_NAME "POCSAG"
extern const SubGhzProtocol subghz_protocol_pocsag;

View file

@ -0,0 +1,9 @@
#include "protocol_items.h"
const SubGhzProtocol* pocsag_pager_protocol_registry_items[] = {
&subghz_protocol_pocsag,
};
const SubGhzProtocolRegistry pocsag_pager_protocol_registry = {
.items = pocsag_pager_protocol_registry_items,
.size = COUNT_OF(pocsag_pager_protocol_registry_items)};

View file

@ -0,0 +1,6 @@
#pragma once
#include "../pocsag_pager_app_i.h"
#include "pocsag.h"
extern const SubGhzProtocolRegistry pocsag_pager_protocol_registry;

View file

@ -0,0 +1,207 @@
#include "../pocsag_pager_app_i.h"
#include "../views/pocsag_pager_receiver.h"
static const NotificationSequence subghs_sequence_rx = {
&message_green_255,
&message_vibro_on,
&message_note_c6,
&message_delay_50,
&message_sound_off,
&message_vibro_off,
&message_delay_50,
NULL,
};
static const NotificationSequence subghs_sequence_rx_locked = {
&message_green_255,
&message_display_backlight_on,
&message_vibro_on,
&message_note_c6,
&message_delay_50,
&message_sound_off,
&message_vibro_off,
&message_delay_500,
&message_display_backlight_off,
NULL,
};
static void pocsag_pager_scene_receiver_update_statusbar(void* context) {
POCSAGPagerApp* app = context;
FuriString* history_stat_str;
history_stat_str = furi_string_alloc();
if(!pcsg_history_get_text_space_left(app->txrx->history, history_stat_str)) {
FuriString* frequency_str;
FuriString* modulation_str;
frequency_str = furi_string_alloc();
modulation_str = furi_string_alloc();
pcsg_get_frequency_modulation(app, frequency_str, modulation_str);
pcsg_view_receiver_add_data_statusbar(
app->pcsg_receiver,
furi_string_get_cstr(frequency_str),
furi_string_get_cstr(modulation_str),
furi_string_get_cstr(history_stat_str));
furi_string_free(frequency_str);
furi_string_free(modulation_str);
} else {
pcsg_view_receiver_add_data_statusbar(
app->pcsg_receiver, furi_string_get_cstr(history_stat_str), "", "");
}
furi_string_free(history_stat_str);
}
void pocsag_pager_scene_receiver_callback(PCSGCustomEvent event, void* context) {
furi_assert(context);
POCSAGPagerApp* app = context;
view_dispatcher_send_custom_event(app->view_dispatcher, event);
}
static void pocsag_pager_scene_receiver_add_to_history_callback(
SubGhzReceiver* receiver,
SubGhzProtocolDecoderBase* decoder_base,
void* context) {
furi_assert(context);
POCSAGPagerApp* app = context;
FuriString* str_buff;
str_buff = furi_string_alloc();
if(pcsg_history_add_to_history(app->txrx->history, decoder_base, app->txrx->preset) ==
PCSGHistoryStateAddKeyNewDada) {
furi_string_reset(str_buff);
pcsg_history_get_text_item_menu(
app->txrx->history, str_buff, pcsg_history_get_item(app->txrx->history) - 1);
pcsg_view_receiver_add_item_to_menu(
app->pcsg_receiver,
furi_string_get_cstr(str_buff),
pcsg_history_get_type_protocol(
app->txrx->history, pcsg_history_get_item(app->txrx->history) - 1));
pocsag_pager_scene_receiver_update_statusbar(app);
notification_message(app->notifications, &sequence_blink_green_10);
if(app->lock != PCSGLockOn) {
notification_message(app->notifications, &subghs_sequence_rx);
} else {
notification_message(app->notifications, &subghs_sequence_rx_locked);
}
}
subghz_receiver_reset(receiver);
furi_string_free(str_buff);
app->txrx->rx_key_state = PCSGRxKeyStateAddKey;
}
void pocsag_pager_scene_receiver_on_enter(void* context) {
POCSAGPagerApp* app = context;
FuriString* str_buff;
str_buff = furi_string_alloc();
if(app->txrx->rx_key_state == PCSGRxKeyStateIDLE) {
pcsg_preset_init(app, "FM95", 439987500, NULL, 0);
pcsg_history_reset(app->txrx->history);
app->txrx->rx_key_state = PCSGRxKeyStateStart;
}
pcsg_view_receiver_set_lock(app->pcsg_receiver, app->lock);
//Load history to receiver
pcsg_view_receiver_exit(app->pcsg_receiver);
for(uint8_t i = 0; i < pcsg_history_get_item(app->txrx->history); i++) {
furi_string_reset(str_buff);
pcsg_history_get_text_item_menu(app->txrx->history, str_buff, i);
pcsg_view_receiver_add_item_to_menu(
app->pcsg_receiver,
furi_string_get_cstr(str_buff),
pcsg_history_get_type_protocol(app->txrx->history, i));
app->txrx->rx_key_state = PCSGRxKeyStateAddKey;
}
furi_string_free(str_buff);
pocsag_pager_scene_receiver_update_statusbar(app);
pcsg_view_receiver_set_callback(app->pcsg_receiver, pocsag_pager_scene_receiver_callback, app);
subghz_receiver_set_rx_callback(
app->txrx->receiver, pocsag_pager_scene_receiver_add_to_history_callback, app);
if(app->txrx->txrx_state == PCSGTxRxStateRx) {
pcsg_rx_end(app);
};
if((app->txrx->txrx_state == PCSGTxRxStateIDLE) ||
(app->txrx->txrx_state == PCSGTxRxStateSleep)) {
pcsg_begin(
app,
subghz_setting_get_preset_data_by_name(
app->setting, furi_string_get_cstr(app->txrx->preset->name)));
pcsg_rx(app, app->txrx->preset->frequency);
}
pcsg_view_receiver_set_idx_menu(app->pcsg_receiver, app->txrx->idx_menu_chosen);
view_dispatcher_switch_to_view(app->view_dispatcher, POCSAGPagerViewReceiver);
}
bool pocsag_pager_scene_receiver_on_event(void* context, SceneManagerEvent event) {
POCSAGPagerApp* app = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
switch(event.event) {
case PCSGCustomEventViewReceiverBack:
// Stop CC1101 Rx
if(app->txrx->txrx_state == PCSGTxRxStateRx) {
pcsg_rx_end(app);
pcsg_sleep(app);
};
app->txrx->hopper_state = PCSGHopperStateOFF;
app->txrx->idx_menu_chosen = 0;
subghz_receiver_set_rx_callback(app->txrx->receiver, NULL, app);
app->txrx->rx_key_state = PCSGRxKeyStateIDLE;
pcsg_preset_init(app, "FM95", 439987500, NULL, 0);
scene_manager_search_and_switch_to_previous_scene(
app->scene_manager, POCSAGPagerSceneStart);
consumed = true;
break;
case PCSGCustomEventViewReceiverOK:
app->txrx->idx_menu_chosen = pcsg_view_receiver_get_idx_menu(app->pcsg_receiver);
scene_manager_next_scene(app->scene_manager, POCSAGPagerSceneReceiverInfo);
consumed = true;
break;
case PCSGCustomEventViewReceiverConfig:
app->txrx->idx_menu_chosen = pcsg_view_receiver_get_idx_menu(app->pcsg_receiver);
scene_manager_next_scene(app->scene_manager, POCSAGPagerSceneReceiverConfig);
consumed = true;
break;
case PCSGCustomEventViewReceiverOffDisplay:
notification_message(app->notifications, &sequence_display_backlight_off);
consumed = true;
break;
case PCSGCustomEventViewReceiverUnlock:
app->lock = PCSGLockOff;
consumed = true;
break;
default:
break;
}
} else if(event.type == SceneManagerEventTypeTick) {
if(app->txrx->hopper_state != PCSGHopperStateOFF) {
pcsg_hopper_update(app);
pocsag_pager_scene_receiver_update_statusbar(app);
}
if(app->txrx->txrx_state == PCSGTxRxStateRx) {
notification_message(app->notifications, &sequence_blink_cyan_10);
}
}
return consumed;
}
void pocsag_pager_scene_receiver_on_exit(void* context) {
UNUSED(context);
}

View file

@ -0,0 +1,30 @@
#include "../pocsag_pager_app_i.h"
// Generate scene on_enter handlers array
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
void (*const pocsag_pager_scene_on_enter_handlers[])(void*) = {
#include "pocsag_pager_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 pocsag_pager_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = {
#include "pocsag_pager_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 pocsag_pager_scene_on_exit_handlers[])(void* context) = {
#include "pocsag_pager_scene_config.h"
};
#undef ADD_SCENE
// Initialize scene handlers configuration structure
const SceneManagerHandlers pocsag_pager_scene_handlers = {
.on_enter_handlers = pocsag_pager_scene_on_enter_handlers,
.on_event_handlers = pocsag_pager_scene_on_event_handlers,
.on_exit_handlers = pocsag_pager_scene_on_exit_handlers,
.scene_num = POCSAGPagerSceneNum,
};

View file

@ -0,0 +1,29 @@
#pragma once
#include <gui/scene_manager.h>
// Generate scene id and total number
#define ADD_SCENE(prefix, name, id) POCSAGPagerScene##id,
typedef enum {
#include "pocsag_pager_scene_config.h"
POCSAGPagerSceneNum,
} POCSAGPagerScene;
#undef ADD_SCENE
extern const SceneManagerHandlers pocsag_pager_scene_handlers;
// Generate scene on_enter handlers declaration
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*);
#include "pocsag_pager_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 "pocsag_pager_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 "pocsag_pager_scene_config.h"
#undef ADD_SCENE

View file

@ -0,0 +1,74 @@
#include "../pocsag_pager_app_i.h"
#include "../helpers/pocsag_pager_types.h"
void pocsag_pager_scene_about_widget_callback(GuiButtonType result, InputType type, void* context) {
POCSAGPagerApp* app = context;
if(type == InputTypeShort) {
view_dispatcher_send_custom_event(app->view_dispatcher, result);
}
}
void pocsag_pager_scene_about_on_enter(void* context) {
POCSAGPagerApp* 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", PCSG_VERSION_APP);
furi_string_cat_printf(temp_str, "Developed by:\n%s\n\n", PCSG_DEVELOPED);
furi_string_cat_printf(temp_str, "Github: %s\n\n", PCSG_GITHUB);
furi_string_cat_printf(temp_str, "\e#%s\n", "Description");
furi_string_cat_printf(temp_str, "Receiving POCSAG Pager \nmessages\n\n");
furi_string_cat_printf(temp_str, "Supported protocols:\n");
size_t i = 0;
const char* protocol_name =
subghz_environment_get_protocol_name_registry(app->txrx->environment, i++);
do {
furi_string_cat_printf(temp_str, "%s\n", protocol_name);
protocol_name = subghz_environment_get_protocol_name_registry(app->txrx->environment, i++);
} while(protocol_name != NULL);
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! POCSAG Pager \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, POCSAGPagerViewWidget);
}
bool pocsag_pager_scene_about_on_event(void* context, SceneManagerEvent event) {
POCSAGPagerApp* app = context;
bool consumed = false;
UNUSED(app);
UNUSED(event);
return consumed;
}
void pocsag_pager_scene_about_on_exit(void* context) {
POCSAGPagerApp* app = context;
// Clear views
widget_reset(app->widget);
}

View file

@ -0,0 +1,5 @@
ADD_SCENE(pocsag_pager, start, Start)
ADD_SCENE(pocsag_pager, about, About)
ADD_SCENE(pocsag_pager, receiver, Receiver)
ADD_SCENE(pocsag_pager, receiver_config, ReceiverConfig)
ADD_SCENE(pocsag_pager, receiver_info, ReceiverInfo)

View file

@ -0,0 +1,221 @@
#include "../pocsag_pager_app_i.h"
enum PCSGSettingIndex {
PCSGSettingIndexFrequency,
PCSGSettingIndexHopping,
PCSGSettingIndexModulation,
PCSGSettingIndexLock,
};
#define HOPPING_COUNT 2
const char* const hopping_text[HOPPING_COUNT] = {
"OFF",
"ON",
};
const uint32_t hopping_value[HOPPING_COUNT] = {
PCSGHopperStateOFF,
PCSGHopperStateRunnig,
};
uint8_t pocsag_pager_scene_receiver_config_next_frequency(const uint32_t value, void* context) {
furi_assert(context);
POCSAGPagerApp* app = context;
uint8_t index = 0;
for(uint8_t i = 0; i < subghz_setting_get_frequency_count(app->setting); i++) {
if(value == subghz_setting_get_frequency(app->setting, i)) {
index = i;
break;
} else {
index = subghz_setting_get_frequency_default_index(app->setting);
}
}
return index;
}
uint8_t pocsag_pager_scene_receiver_config_next_preset(const char* preset_name, void* context) {
furi_assert(context);
POCSAGPagerApp* app = context;
uint8_t index = 0;
for(uint8_t i = 0; i < subghz_setting_get_preset_count(app->setting); i++) {
if(!strcmp(subghz_setting_get_preset_name(app->setting, i), preset_name)) {
index = i;
break;
} else {
// index = subghz_setting_get_frequency_default_index(app ->setting);
}
}
return index;
}
uint8_t pocsag_pager_scene_receiver_config_hopper_value_index(
const uint32_t value,
const uint32_t values[],
uint8_t values_count,
void* context) {
furi_assert(context);
UNUSED(values_count);
POCSAGPagerApp* app = context;
if(value == values[0]) {
return 0;
} else {
variable_item_set_current_value_text(
(VariableItem*)scene_manager_get_scene_state(
app->scene_manager, POCSAGPagerSceneReceiverConfig),
" -----");
return 1;
}
}
static void pocsag_pager_scene_receiver_config_set_frequency(VariableItem* item) {
POCSAGPagerApp* app = variable_item_get_context(item);
uint8_t index = variable_item_get_current_value_index(item);
if(app->txrx->hopper_state == PCSGHopperStateOFF) {
char text_buf[10] = {0};
snprintf(
text_buf,
sizeof(text_buf),
"%lu.%02lu",
subghz_setting_get_frequency(app->setting, index) / 1000000,
(subghz_setting_get_frequency(app->setting, index) % 1000000) / 10000);
variable_item_set_current_value_text(item, text_buf);
app->txrx->preset->frequency = subghz_setting_get_frequency(app->setting, index);
} else {
variable_item_set_current_value_index(
item, subghz_setting_get_frequency_default_index(app->setting));
}
}
static void pocsag_pager_scene_receiver_config_set_preset(VariableItem* item) {
POCSAGPagerApp* app = variable_item_get_context(item);
uint8_t index = variable_item_get_current_value_index(item);
variable_item_set_current_value_text(
item, subghz_setting_get_preset_name(app->setting, index));
pcsg_preset_init(
app,
subghz_setting_get_preset_name(app->setting, index),
app->txrx->preset->frequency,
subghz_setting_get_preset_data(app->setting, index),
subghz_setting_get_preset_data_size(app->setting, index));
}
static void pocsag_pager_scene_receiver_config_set_hopping_running(VariableItem* item) {
POCSAGPagerApp* app = variable_item_get_context(item);
uint8_t index = variable_item_get_current_value_index(item);
variable_item_set_current_value_text(item, hopping_text[index]);
if(hopping_value[index] == PCSGHopperStateOFF) {
char text_buf[10] = {0};
snprintf(
text_buf,
sizeof(text_buf),
"%lu.%02lu",
subghz_setting_get_default_frequency(app->setting) / 1000000,
(subghz_setting_get_default_frequency(app->setting) % 1000000) / 10000);
variable_item_set_current_value_text(
(VariableItem*)scene_manager_get_scene_state(
app->scene_manager, POCSAGPagerSceneReceiverConfig),
text_buf);
app->txrx->preset->frequency = subghz_setting_get_default_frequency(app->setting);
variable_item_set_current_value_index(
(VariableItem*)scene_manager_get_scene_state(
app->scene_manager, POCSAGPagerSceneReceiverConfig),
subghz_setting_get_frequency_default_index(app->setting));
} else {
variable_item_set_current_value_text(
(VariableItem*)scene_manager_get_scene_state(
app->scene_manager, POCSAGPagerSceneReceiverConfig),
" -----");
variable_item_set_current_value_index(
(VariableItem*)scene_manager_get_scene_state(
app->scene_manager, POCSAGPagerSceneReceiverConfig),
subghz_setting_get_frequency_default_index(app->setting));
}
app->txrx->hopper_state = hopping_value[index];
}
static void
pocsag_pager_scene_receiver_config_var_list_enter_callback(void* context, uint32_t index) {
furi_assert(context);
POCSAGPagerApp* app = context;
if(index == PCSGSettingIndexLock) {
view_dispatcher_send_custom_event(app->view_dispatcher, PCSGCustomEventSceneSettingLock);
}
}
void pocsag_pager_scene_receiver_config_on_enter(void* context) {
POCSAGPagerApp* app = context;
VariableItem* item;
uint8_t value_index;
item = variable_item_list_add(
app->variable_item_list,
"Frequency:",
subghz_setting_get_frequency_count(app->setting),
pocsag_pager_scene_receiver_config_set_frequency,
app);
value_index =
pocsag_pager_scene_receiver_config_next_frequency(app->txrx->preset->frequency, app);
scene_manager_set_scene_state(
app->scene_manager, POCSAGPagerSceneReceiverConfig, (uint32_t)item);
variable_item_set_current_value_index(item, value_index);
char text_buf[10] = {0};
snprintf(
text_buf,
sizeof(text_buf),
"%lu.%02lu",
subghz_setting_get_frequency(app->setting, value_index) / 1000000,
(subghz_setting_get_frequency(app->setting, value_index) % 1000000) / 10000);
variable_item_set_current_value_text(item, text_buf);
item = variable_item_list_add(
app->variable_item_list,
"Hopping:",
HOPPING_COUNT,
pocsag_pager_scene_receiver_config_set_hopping_running,
app);
value_index = pocsag_pager_scene_receiver_config_hopper_value_index(
app->txrx->hopper_state, hopping_value, HOPPING_COUNT, app);
variable_item_set_current_value_index(item, value_index);
variable_item_set_current_value_text(item, hopping_text[value_index]);
item = variable_item_list_add(
app->variable_item_list,
"Modulation:",
subghz_setting_get_preset_count(app->setting),
pocsag_pager_scene_receiver_config_set_preset,
app);
value_index = pocsag_pager_scene_receiver_config_next_preset(
furi_string_get_cstr(app->txrx->preset->name), app);
variable_item_set_current_value_index(item, value_index);
variable_item_set_current_value_text(
item, subghz_setting_get_preset_name(app->setting, value_index));
variable_item_list_add(app->variable_item_list, "Lock Keyboard", 1, NULL, NULL);
variable_item_list_set_enter_callback(
app->variable_item_list, pocsag_pager_scene_receiver_config_var_list_enter_callback, app);
view_dispatcher_switch_to_view(app->view_dispatcher, POCSAGPagerViewVariableItemList);
}
bool pocsag_pager_scene_receiver_config_on_event(void* context, SceneManagerEvent event) {
POCSAGPagerApp* app = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == PCSGCustomEventSceneSettingLock) {
app->lock = PCSGLockOn;
scene_manager_previous_scene(app->scene_manager);
consumed = true;
}
}
return consumed;
}
void pocsag_pager_scene_receiver_config_on_exit(void* context) {
POCSAGPagerApp* app = context;
variable_item_list_set_selected_item(app->variable_item_list, 0);
variable_item_list_reset(app->variable_item_list);
}

View file

@ -0,0 +1,50 @@
#include "../pocsag_pager_app_i.h"
#include "../views/pocsag_pager_receiver.h"
void pocsag_pager_scene_receiver_info_callback(PCSGCustomEvent event, void* context) {
furi_assert(context);
POCSAGPagerApp* app = context;
view_dispatcher_send_custom_event(app->view_dispatcher, event);
}
static void pocsag_pager_scene_receiver_info_add_to_history_callback(
SubGhzReceiver* receiver,
SubGhzProtocolDecoderBase* decoder_base,
void* context) {
furi_assert(context);
POCSAGPagerApp* app = context;
if(pcsg_history_add_to_history(app->txrx->history, decoder_base, app->txrx->preset) ==
PCSGHistoryStateAddKeyUpdateData) {
pcsg_view_receiver_info_update(
app->pcsg_receiver_info,
pcsg_history_get_raw_data(app->txrx->history, app->txrx->idx_menu_chosen));
subghz_receiver_reset(receiver);
notification_message(app->notifications, &sequence_blink_green_10);
app->txrx->rx_key_state = PCSGRxKeyStateAddKey;
}
}
void pocsag_pager_scene_receiver_info_on_enter(void* context) {
POCSAGPagerApp* app = context;
subghz_receiver_set_rx_callback(
app->txrx->receiver, pocsag_pager_scene_receiver_info_add_to_history_callback, app);
pcsg_view_receiver_info_update(
app->pcsg_receiver_info,
pcsg_history_get_raw_data(app->txrx->history, app->txrx->idx_menu_chosen));
view_dispatcher_switch_to_view(app->view_dispatcher, POCSAGPagerViewReceiverInfo);
}
bool pocsag_pager_scene_receiver_info_on_event(void* context, SceneManagerEvent event) {
POCSAGPagerApp* app = context;
bool consumed = false;
UNUSED(app);
UNUSED(event);
return consumed;
}
void pocsag_pager_scene_receiver_info_on_exit(void* context) {
UNUSED(context);
}

View file

@ -0,0 +1,58 @@
#include "../pocsag_pager_app_i.h"
typedef enum {
SubmenuIndexPOCSAGPagerReceiver,
SubmenuIndexPOCSAGPagerAbout,
} SubmenuIndex;
void pocsag_pager_scene_start_submenu_callback(void* context, uint32_t index) {
POCSAGPagerApp* app = context;
view_dispatcher_send_custom_event(app->view_dispatcher, index);
}
void pocsag_pager_scene_start_on_enter(void* context) {
UNUSED(context);
POCSAGPagerApp* app = context;
Submenu* submenu = app->submenu;
submenu_add_item(
submenu,
"Receive messages",
SubmenuIndexPOCSAGPagerReceiver,
pocsag_pager_scene_start_submenu_callback,
app);
submenu_add_item(
submenu,
"About",
SubmenuIndexPOCSAGPagerAbout,
pocsag_pager_scene_start_submenu_callback,
app);
submenu_set_selected_item(
submenu, scene_manager_get_scene_state(app->scene_manager, POCSAGPagerSceneStart));
view_dispatcher_switch_to_view(app->view_dispatcher, POCSAGPagerViewSubmenu);
}
bool pocsag_pager_scene_start_on_event(void* context, SceneManagerEvent event) {
POCSAGPagerApp* app = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == SubmenuIndexPOCSAGPagerAbout) {
scene_manager_next_scene(app->scene_manager, POCSAGPagerSceneAbout);
consumed = true;
} else if(event.event == SubmenuIndexPOCSAGPagerReceiver) {
scene_manager_next_scene(app->scene_manager, POCSAGPagerSceneReceiver);
consumed = true;
}
scene_manager_set_scene_state(app->scene_manager, POCSAGPagerSceneStart, event.event);
}
return consumed;
}
void pocsag_pager_scene_start_on_exit(void* context) {
POCSAGPagerApp* app = context;
submenu_reset(app->submenu);
}

View file

@ -0,0 +1,440 @@
#include "pocsag_pager_receiver.h"
#include "../pocsag_pager_app_i.h"
#include <pocsag_pager_icons.h>
#include <math.h>
#include <input/input.h>
#include <gui/elements.h>
#include <m-array.h>
#define FRAME_HEIGHT 12
#define MAX_LEN_PX 112
#define MENU_ITEMS 4u
#define UNLOCK_CNT 3
typedef struct {
FuriString* item_str;
uint8_t type;
} PCSGReceiverMenuItem;
ARRAY_DEF(PCSGReceiverMenuItemArray, PCSGReceiverMenuItem, M_POD_OPLIST)
#define M_OPL_PCSGReceiverMenuItemArray_t() ARRAY_OPLIST(PCSGReceiverMenuItemArray, M_POD_OPLIST)
struct PCSGReceiverHistory {
PCSGReceiverMenuItemArray_t data;
};
typedef struct PCSGReceiverHistory PCSGReceiverHistory;
static const Icon* ReceiverItemIcons[] = {
[SubGhzProtocolTypeUnknown] = &I_Quest_7x8,
[SubGhzProtocolTypeStatic] = &I_Message_8x7,
[SubGhzProtocolTypeDynamic] = &I_Lock_7x8,
};
typedef enum {
PCSGReceiverBarShowDefault,
PCSGReceiverBarShowLock,
PCSGReceiverBarShowToUnlockPress,
PCSGReceiverBarShowUnlock,
} PCSGReceiverBarShow;
struct PCSGReceiver {
PCSGLock lock;
uint8_t lock_count;
FuriTimer* timer;
View* view;
PCSGReceiverCallback callback;
void* context;
};
typedef struct {
FuriString* frequency_str;
FuriString* preset_str;
FuriString* history_stat_str;
PCSGReceiverHistory* history;
uint16_t idx;
uint16_t list_offset;
uint16_t history_item;
PCSGReceiverBarShow bar_show;
} PCSGReceiverModel;
void pcsg_view_receiver_set_lock(PCSGReceiver* pcsg_receiver, PCSGLock lock) {
furi_assert(pcsg_receiver);
pcsg_receiver->lock_count = 0;
if(lock == PCSGLockOn) {
pcsg_receiver->lock = lock;
with_view_model(
pcsg_receiver->view,
PCSGReceiverModel * model,
{ model->bar_show = PCSGReceiverBarShowLock; },
true);
furi_timer_start(pcsg_receiver->timer, pdMS_TO_TICKS(1000));
} else {
with_view_model(
pcsg_receiver->view,
PCSGReceiverModel * model,
{ model->bar_show = PCSGReceiverBarShowDefault; },
true);
}
}
void pcsg_view_receiver_set_callback(
PCSGReceiver* pcsg_receiver,
PCSGReceiverCallback callback,
void* context) {
furi_assert(pcsg_receiver);
furi_assert(callback);
pcsg_receiver->callback = callback;
pcsg_receiver->context = context;
}
static void pcsg_view_receiver_update_offset(PCSGReceiver* pcsg_receiver) {
furi_assert(pcsg_receiver);
with_view_model(
pcsg_receiver->view,
PCSGReceiverModel * model,
{
size_t history_item = model->history_item;
uint16_t bounds = history_item > 3 ? 2 : history_item;
if(history_item > 3 && model->idx >= (int16_t)(history_item - 1)) {
model->list_offset = model->idx - 3;
} else if(model->list_offset < model->idx - bounds) {
model->list_offset =
CLAMP(model->list_offset + 1, (int16_t)(history_item - bounds), 0);
} else if(model->list_offset > model->idx - bounds) {
model->list_offset = CLAMP(model->idx - 1, (int16_t)(history_item - bounds), 0);
}
},
true);
}
void pcsg_view_receiver_add_item_to_menu(
PCSGReceiver* pcsg_receiver,
const char* name,
uint8_t type) {
furi_assert(pcsg_receiver);
with_view_model(
pcsg_receiver->view,
PCSGReceiverModel * model,
{
PCSGReceiverMenuItem* item_menu =
PCSGReceiverMenuItemArray_push_raw(model->history->data);
item_menu->item_str = furi_string_alloc_set(name);
item_menu->type = type;
if((model->idx == model->history_item - 1)) {
model->history_item++;
model->idx++;
} else {
model->history_item++;
}
},
true);
pcsg_view_receiver_update_offset(pcsg_receiver);
}
void pcsg_view_receiver_add_data_statusbar(
PCSGReceiver* pcsg_receiver,
const char* frequency_str,
const char* preset_str,
const char* history_stat_str) {
furi_assert(pcsg_receiver);
with_view_model(
pcsg_receiver->view,
PCSGReceiverModel * model,
{
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);
},
true);
}
static void pcsg_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);
canvas_set_color(canvas, ColorWhite);
canvas_draw_dot(canvas, 0, 0 + idx * FRAME_HEIGHT);
canvas_draw_dot(canvas, 1, 0 + idx * FRAME_HEIGHT);
canvas_draw_dot(canvas, 0, (0 + idx * FRAME_HEIGHT) + 1);
canvas_draw_dot(canvas, 0, (0 + idx * FRAME_HEIGHT) + 11);
canvas_draw_dot(canvas, scrollbar ? 121 : 126, 0 + idx * FRAME_HEIGHT);
canvas_draw_dot(canvas, scrollbar ? 121 : 126, (0 + idx * FRAME_HEIGHT) + 11);
}
void pcsg_view_receiver_draw(Canvas* canvas, PCSGReceiverModel* model) {
canvas_clear(canvas);
canvas_set_color(canvas, ColorBlack);
canvas_set_font(canvas, FontSecondary);
elements_button_left(canvas, "Config");
canvas_draw_line(canvas, 46, 51, 125, 51);
bool scrollbar = model->history_item > 4;
FuriString* str_buff;
str_buff = furi_string_alloc();
PCSGReceiverMenuItem* item_menu;
for(size_t i = 0; i < MIN(model->history_item, MENU_ITEMS); ++i) {
size_t idx = CLAMP((uint16_t)(i + model->list_offset), model->history_item, 0);
item_menu = PCSGReceiverMenuItemArray_get(model->history->data, idx);
furi_string_set(str_buff, item_menu->item_str);
furi_string_replace_all(str_buff, "#", "");
elements_string_fit_width(canvas, str_buff, scrollbar ? MAX_LEN_PX - 7 : MAX_LEN_PX);
if(model->idx == idx) {
pcsg_view_receiver_draw_frame(canvas, i, scrollbar);
} else {
canvas_set_color(canvas, ColorBlack);
}
canvas_draw_icon(canvas, 4, 2 + i * FRAME_HEIGHT, ReceiverItemIcons[item_menu->type]);
canvas_draw_str(canvas, 15, 9 + i * FRAME_HEIGHT, furi_string_get_cstr(str_buff));
furi_string_reset(str_buff);
}
if(scrollbar) {
elements_scrollbar_pos(canvas, 128, 0, 49, model->idx, model->history_item);
}
furi_string_free(str_buff);
canvas_set_color(canvas, ColorBlack);
if(model->history_item == 0) {
canvas_draw_icon(canvas, 0, 0, &I_Scanning_123x52);
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 63, 46, "Scanning...");
canvas_draw_line(canvas, 46, 51, 125, 51);
canvas_set_font(canvas, FontSecondary);
}
switch(model->bar_show) {
case PCSGReceiverBarShowLock:
canvas_draw_icon(canvas, 64, 55, &I_Lock_7x8);
canvas_draw_str(canvas, 74, 62, "Locked");
break;
case PCSGReceiverBarShowToUnlockPress:
canvas_draw_str(canvas, 44, 62, furi_string_get_cstr(model->frequency_str));
canvas_draw_str(canvas, 79, 62, furi_string_get_cstr(model->preset_str));
canvas_draw_str(canvas, 96, 62, furi_string_get_cstr(model->history_stat_str));
canvas_set_font(canvas, FontSecondary);
elements_bold_rounded_frame(canvas, 14, 8, 99, 48);
elements_multiline_text(canvas, 65, 26, "To unlock\npress:");
canvas_draw_icon(canvas, 65, 42, &I_Pin_back_arrow_10x8);
canvas_draw_icon(canvas, 80, 42, &I_Pin_back_arrow_10x8);
canvas_draw_icon(canvas, 95, 42, &I_Pin_back_arrow_10x8);
canvas_draw_icon(canvas, 16, 13, &I_WarningDolphin_45x42);
canvas_draw_dot(canvas, 17, 61);
break;
case PCSGReceiverBarShowUnlock:
canvas_draw_icon(canvas, 64, 55, &I_Unlock_7x8);
canvas_draw_str(canvas, 74, 62, "Unlocked");
break;
default:
canvas_draw_str(canvas, 44, 62, furi_string_get_cstr(model->frequency_str));
canvas_draw_str(canvas, 79, 62, furi_string_get_cstr(model->preset_str));
canvas_draw_str(canvas, 96, 62, furi_string_get_cstr(model->history_stat_str));
break;
}
}
static void pcsg_view_receiver_timer_callback(void* context) {
furi_assert(context);
PCSGReceiver* pcsg_receiver = context;
with_view_model(
pcsg_receiver->view,
PCSGReceiverModel * model,
{ model->bar_show = PCSGReceiverBarShowDefault; },
true);
if(pcsg_receiver->lock_count < UNLOCK_CNT) {
pcsg_receiver->callback(PCSGCustomEventViewReceiverOffDisplay, pcsg_receiver->context);
} else {
pcsg_receiver->lock = PCSGLockOff;
pcsg_receiver->callback(PCSGCustomEventViewReceiverUnlock, pcsg_receiver->context);
}
pcsg_receiver->lock_count = 0;
}
bool pcsg_view_receiver_input(InputEvent* event, void* context) {
furi_assert(context);
PCSGReceiver* pcsg_receiver = context;
if(pcsg_receiver->lock == PCSGLockOn) {
with_view_model(
pcsg_receiver->view,
PCSGReceiverModel * model,
{ model->bar_show = PCSGReceiverBarShowToUnlockPress; },
true);
if(pcsg_receiver->lock_count == 0) {
furi_timer_start(pcsg_receiver->timer, pdMS_TO_TICKS(1000));
}
if(event->key == InputKeyBack && event->type == InputTypeShort) {
pcsg_receiver->lock_count++;
}
if(pcsg_receiver->lock_count >= UNLOCK_CNT) {
pcsg_receiver->callback(PCSGCustomEventViewReceiverUnlock, pcsg_receiver->context);
with_view_model(
pcsg_receiver->view,
PCSGReceiverModel * model,
{ model->bar_show = PCSGReceiverBarShowUnlock; },
true);
pcsg_receiver->lock = PCSGLockOff;
furi_timer_start(pcsg_receiver->timer, pdMS_TO_TICKS(650));
}
return true;
}
if(event->key == InputKeyBack && event->type == InputTypeShort) {
pcsg_receiver->callback(PCSGCustomEventViewReceiverBack, pcsg_receiver->context);
} else if(
event->key == InputKeyUp &&
(event->type == InputTypeShort || event->type == InputTypeRepeat)) {
with_view_model(
pcsg_receiver->view,
PCSGReceiverModel * model,
{
if(model->idx != 0) model->idx--;
},
true);
} else if(
event->key == InputKeyDown &&
(event->type == InputTypeShort || event->type == InputTypeRepeat)) {
with_view_model(
pcsg_receiver->view,
PCSGReceiverModel * model,
{
if(model->idx != model->history_item - 1) model->idx++;
},
true);
} else if(event->key == InputKeyLeft && event->type == InputTypeShort) {
pcsg_receiver->callback(PCSGCustomEventViewReceiverConfig, pcsg_receiver->context);
} else if(event->key == InputKeyOk && event->type == InputTypeShort) {
with_view_model(
pcsg_receiver->view,
PCSGReceiverModel * model,
{
if(model->history_item != 0) {
pcsg_receiver->callback(PCSGCustomEventViewReceiverOK, pcsg_receiver->context);
}
},
false);
}
pcsg_view_receiver_update_offset(pcsg_receiver);
return true;
}
void pcsg_view_receiver_enter(void* context) {
furi_assert(context);
}
void pcsg_view_receiver_exit(void* context) {
furi_assert(context);
PCSGReceiver* pcsg_receiver = context;
with_view_model(
pcsg_receiver->view,
PCSGReceiverModel * model,
{
furi_string_reset(model->frequency_str);
furi_string_reset(model->preset_str);
furi_string_reset(model->history_stat_str);
for
M_EACH(item_menu, model->history->data, PCSGReceiverMenuItemArray_t) {
furi_string_free(item_menu->item_str);
item_menu->type = 0;
}
PCSGReceiverMenuItemArray_reset(model->history->data);
model->idx = 0;
model->list_offset = 0;
model->history_item = 0;
},
false);
furi_timer_stop(pcsg_receiver->timer);
}
PCSGReceiver* pcsg_view_receiver_alloc() {
PCSGReceiver* pcsg_receiver = malloc(sizeof(PCSGReceiver));
// View allocation and configuration
pcsg_receiver->view = view_alloc();
pcsg_receiver->lock = PCSGLockOff;
pcsg_receiver->lock_count = 0;
view_allocate_model(pcsg_receiver->view, ViewModelTypeLocking, sizeof(PCSGReceiverModel));
view_set_context(pcsg_receiver->view, pcsg_receiver);
view_set_draw_callback(pcsg_receiver->view, (ViewDrawCallback)pcsg_view_receiver_draw);
view_set_input_callback(pcsg_receiver->view, pcsg_view_receiver_input);
view_set_enter_callback(pcsg_receiver->view, pcsg_view_receiver_enter);
view_set_exit_callback(pcsg_receiver->view, pcsg_view_receiver_exit);
with_view_model(
pcsg_receiver->view,
PCSGReceiverModel * model,
{
model->frequency_str = furi_string_alloc();
model->preset_str = furi_string_alloc();
model->history_stat_str = furi_string_alloc();
model->bar_show = PCSGReceiverBarShowDefault;
model->history = malloc(sizeof(PCSGReceiverHistory));
PCSGReceiverMenuItemArray_init(model->history->data);
},
true);
pcsg_receiver->timer =
furi_timer_alloc(pcsg_view_receiver_timer_callback, FuriTimerTypeOnce, pcsg_receiver);
return pcsg_receiver;
}
void pcsg_view_receiver_free(PCSGReceiver* pcsg_receiver) {
furi_assert(pcsg_receiver);
with_view_model(
pcsg_receiver->view,
PCSGReceiverModel * model,
{
furi_string_free(model->frequency_str);
furi_string_free(model->preset_str);
furi_string_free(model->history_stat_str);
for
M_EACH(item_menu, model->history->data, PCSGReceiverMenuItemArray_t) {
furi_string_free(item_menu->item_str);
item_menu->type = 0;
}
PCSGReceiverMenuItemArray_clear(model->history->data);
free(model->history);
},
false);
furi_timer_free(pcsg_receiver->timer);
view_free(pcsg_receiver->view);
free(pcsg_receiver);
}
View* pcsg_view_receiver_get_view(PCSGReceiver* pcsg_receiver) {
furi_assert(pcsg_receiver);
return pcsg_receiver->view;
}
uint16_t pcsg_view_receiver_get_idx_menu(PCSGReceiver* pcsg_receiver) {
furi_assert(pcsg_receiver);
uint32_t idx = 0;
with_view_model(
pcsg_receiver->view, PCSGReceiverModel * model, { idx = model->idx; }, false);
return idx;
}
void pcsg_view_receiver_set_idx_menu(PCSGReceiver* pcsg_receiver, uint16_t idx) {
furi_assert(pcsg_receiver);
with_view_model(
pcsg_receiver->view,
PCSGReceiverModel * model,
{
model->idx = idx;
if(model->idx > 2) model->list_offset = idx - 2;
},
true);
pcsg_view_receiver_update_offset(pcsg_receiver);
}

View file

@ -0,0 +1,39 @@
#pragma once
#include <gui/view.h>
#include "../helpers/pocsag_pager_types.h"
#include "../helpers/pocsag_pager_event.h"
typedef struct PCSGReceiver PCSGReceiver;
typedef void (*PCSGReceiverCallback)(PCSGCustomEvent event, void* context);
void pcsg_view_receiver_set_lock(PCSGReceiver* pcsg_receiver, PCSGLock keyboard);
void pcsg_view_receiver_set_callback(
PCSGReceiver* pcsg_receiver,
PCSGReceiverCallback callback,
void* context);
PCSGReceiver* pcsg_view_receiver_alloc();
void pcsg_view_receiver_free(PCSGReceiver* pcsg_receiver);
View* pcsg_view_receiver_get_view(PCSGReceiver* pcsg_receiver);
void pcsg_view_receiver_add_data_statusbar(
PCSGReceiver* pcsg_receiver,
const char* frequency_str,
const char* preset_str,
const char* history_stat_str);
void pcsg_view_receiver_add_item_to_menu(
PCSGReceiver* pcsg_receiver,
const char* name,
uint8_t type);
uint16_t pcsg_view_receiver_get_idx_menu(PCSGReceiver* pcsg_receiver);
void pcsg_view_receiver_set_idx_menu(PCSGReceiver* pcsg_receiver, uint16_t idx);
void pcsg_view_receiver_exit(void* context);

View file

@ -0,0 +1,137 @@
#include "pocsag_pager_receiver.h"
#include "../pocsag_pager_app_i.h"
#include "pocsag_pager_icons.h"
#include "../protocols/pcsg_generic.h"
#include <input/input.h>
#include <gui/elements.h>
#define abs(x) ((x) > 0 ? (x) : -(x))
struct PCSGReceiverInfo {
View* view;
};
typedef struct {
FuriString* protocol_name;
PCSGBlockGeneric* generic;
} PCSGReceiverInfoModel;
void pcsg_view_receiver_info_update(PCSGReceiverInfo* pcsg_receiver_info, FlipperFormat* fff) {
furi_assert(pcsg_receiver_info);
furi_assert(fff);
with_view_model(
pcsg_receiver_info->view,
PCSGReceiverInfoModel * model,
{
flipper_format_rewind(fff);
flipper_format_read_string(fff, "Protocol", model->protocol_name);
pcsg_block_generic_deserialize(model->generic, fff);
},
true);
}
void pcsg_view_receiver_info_draw(Canvas* canvas, PCSGReceiverInfoModel* model) {
canvas_clear(canvas);
canvas_set_color(canvas, ColorBlack);
canvas_set_font(canvas, FontSecondary);
if(model->generic->result_ric != NULL) {
elements_text_box(
canvas,
0,
0,
128,
64,
AlignLeft,
AlignTop,
furi_string_get_cstr(model->generic->result_ric),
false);
}
if(model->generic->result_msg != NULL) {
elements_text_box(
canvas,
0,
12,
128,
64,
AlignLeft,
AlignTop,
furi_string_get_cstr(model->generic->result_msg),
false);
}
}
bool pcsg_view_receiver_info_input(InputEvent* event, void* context) {
furi_assert(context);
//PCSGReceiverInfo* pcsg_receiver_info = context;
if(event->key == InputKeyBack) {
return false;
}
return true;
}
void pcsg_view_receiver_info_enter(void* context) {
furi_assert(context);
}
void pcsg_view_receiver_info_exit(void* context) {
furi_assert(context);
PCSGReceiverInfo* pcsg_receiver_info = context;
with_view_model(
pcsg_receiver_info->view,
PCSGReceiverInfoModel * model,
{ furi_string_reset(model->protocol_name); },
false);
}
PCSGReceiverInfo* pcsg_view_receiver_info_alloc() {
PCSGReceiverInfo* pcsg_receiver_info = malloc(sizeof(PCSGReceiverInfo));
// View allocation and configuration
pcsg_receiver_info->view = view_alloc();
view_allocate_model(
pcsg_receiver_info->view, ViewModelTypeLocking, sizeof(PCSGReceiverInfoModel));
view_set_context(pcsg_receiver_info->view, pcsg_receiver_info);
view_set_draw_callback(
pcsg_receiver_info->view, (ViewDrawCallback)pcsg_view_receiver_info_draw);
view_set_input_callback(pcsg_receiver_info->view, pcsg_view_receiver_info_input);
view_set_enter_callback(pcsg_receiver_info->view, pcsg_view_receiver_info_enter);
view_set_exit_callback(pcsg_receiver_info->view, pcsg_view_receiver_info_exit);
with_view_model(
pcsg_receiver_info->view,
PCSGReceiverInfoModel * model,
{
model->generic = malloc(sizeof(PCSGBlockGeneric));
model->protocol_name = furi_string_alloc();
},
true);
return pcsg_receiver_info;
}
void pcsg_view_receiver_info_free(PCSGReceiverInfo* pcsg_receiver_info) {
furi_assert(pcsg_receiver_info);
with_view_model(
pcsg_receiver_info->view,
PCSGReceiverInfoModel * model,
{
furi_string_free(model->protocol_name);
free(model->generic);
},
false);
view_free(pcsg_receiver_info->view);
free(pcsg_receiver_info);
}
View* pcsg_view_receiver_info_get_view(PCSGReceiverInfo* pcsg_receiver_info) {
furi_assert(pcsg_receiver_info);
return pcsg_receiver_info->view;
}

View file

@ -0,0 +1,16 @@
#pragma once
#include <gui/view.h>
#include "../helpers/pocsag_pager_types.h"
#include "../helpers/pocsag_pager_event.h"
#include <lib/flipper_format/flipper_format.h>
typedef struct PCSGReceiverInfo PCSGReceiverInfo;
void pcsg_view_receiver_info_update(PCSGReceiverInfo* pcsg_receiver_info, FlipperFormat* fff);
PCSGReceiverInfo* pcsg_view_receiver_info_alloc();
void pcsg_view_receiver_info_free(PCSGReceiverInfo* pcsg_receiver_info);
View* pcsg_view_receiver_info_get_view(PCSGReceiverInfo* pcsg_receiver_info);