Merge branch 'UNLEASHED' into 420
@@ -1,23 +1,32 @@
|
||||
### New changes
|
||||
* Plugins: SubGHz Bruteforcer -> Allow to select two bytes in `BF existing dump` ! Change between byte selection size by pressing Up button
|
||||
* Plugins: SubGHz Bruteforcer -> Add CAME 315Mhz
|
||||
* SubGHz: Partially fix strange issue with subghz decode raw
|
||||
* Xmas / New Year animation (by @Svaarich)
|
||||
* OFW: NFC: fix NTAG203 info scene
|
||||
* OFW: Fix crash in iButton notifications routine
|
||||
* OFW: U2F HID descriptor fix
|
||||
* OFW: USB HID: fix key name
|
||||
* OFW: SubGhz: unit_test modification of text files to one frequency and modulation
|
||||
* API: Version was changed due to breaking changes - from 8.x to 10.x - Extra pack was updated, download it by using link below ([- Download latest extra apps pack](https://download-directory.github.io/?url=https://github.com/xMasterX/unleashed-extra-pack/tree/main/apps))
|
||||
* SubGHz: Improve signals visibility in history
|
||||
* Infrared: Update universal remote assets **(TV universal remote now works much faster)** (by @Amec0e)
|
||||
* Plugins: Weather Station -> Improved signal info dashboard, added signal age counter. (PR by @LY2NEO | #197) (Modified and improved by @xMasterX, new icons and UI changes recommendations by @Karator in OFW PR 2087)
|
||||
* Plugins: Weather Station -> Oregon2 - add support for RTHN129
|
||||
* Plugins: iButton Fuzzer -> change minimal delay (after @Tobirg successfully verified stability with lower delay)
|
||||
* Plugins: iButton Fuzzer + RFID Fuzzer -> Attempt to fix crash when exiting from uids from file attack
|
||||
* OFW: NFC bug fixes **(breaking change, affects API version)**
|
||||
* OFW: UART echo: fix race conditions causing null pointer dereference
|
||||
* OFW: File browser base folder **(breaking change, affects API version)**
|
||||
* OFW: Change NFC emulation screens
|
||||
* OFW: SubGhz: add RPC error
|
||||
* OFW: FuriHal, Power, UnitTests: battery charging voltage limit API
|
||||
* OFW: Fix logical error in storage script
|
||||
* OFW: VCP session close fix
|
||||
* OFW: Dolphin: new animation L2_Wake_up_128x64
|
||||
* OFW: FuriHal: add i2c unit tests
|
||||
|
||||
#### [🎲 Download latest extra apps pack](https://download-directory.github.io/?url=https://github.com/xMasterX/unleashed-extra-pack/tree/main/apps)
|
||||
|
||||
[-> How to install firmware](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/HowToInstall.md)
|
||||
|
||||
[-> Download qFlipper 1.2.1 (allows .tgz installation) (official link)](https://update.flipperzero.one/builds/qFlipper/1.2.1/)
|
||||
[-> Download qFlipper (official link)](https://flipperzero.one/update)
|
||||
|
||||
## Please support development of the project
|
||||
* destream (100 EUR min): https://destream.net/live/MMX/donate
|
||||
* cloudtips (only RU payments accepted): https://pay.cloudtips.ru/p/7b3e9d65
|
||||
* YooMoney (only RU payments accepted): https://yoomoney.ru/fundraise/XA49mgQLPA0.221209
|
||||
* USDT(TRC20): `TSXcitMSnWXUFqiUfEXrTVpVewXy2cYhrs`
|
||||
* BCH: `qquxfyzntuqufy2dx0hrfr4sndp0tucvky4sw8qyu3`
|
||||
* ETH/BSC/ERC20-Tokens: `darkflippers.eth` (or `0xFebF1bBc8229418FF2408C07AF6Afa49152fEc6a`)
|
||||
@@ -25,9 +34,12 @@
|
||||
* DOGE: `D6R6gYgBn5LwTNmPyvAQR6bZ9EtGgFCpvv`
|
||||
* LTC: `ltc1q3ex4ejkl0xpx3znwrmth4lyuadr5qgv8tmq8z9`
|
||||
* XMR (Monero): `41xUz92suUu1u5Mu4qkrcs52gtfpu9rnZRdBpCJ244KRHf6xXSvVFevdf2cnjS7RAeYr5hn9MsEfxKoFDRSctFjG5fv1Mhn`
|
||||
* TON: `EQCOqcnYkvzOZUV_9bPE_8oTbOrOF03MnF-VcJyjisTZmpGf`
|
||||
|
||||
**Note: To avoid issues with .dfu, prefer installing using .tgz with qFlipper, web updater or by self update package, all needed assets will be installed**
|
||||
|
||||
Self-update package (update from microSD) - `flipper-z-f7-update-(version).zip` or download `.tgz` for iOS mobile app / qFlipper
|
||||
**Recommended option - Web Updater**
|
||||
|
||||
Self-update package (update from microSD) - `flipper-z-f7-update-(version).zip` or download `.tgz` for mobile app / qFlipper
|
||||
|
||||
|
||||
Update using qFlipper (1.2.0+) is now possible with `.tgz` update package! Also you can use Web Updater or self-update package.
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#include <lib/nfc/helpers/mf_classic_dict.h>
|
||||
#include <lib/digital_signal/digital_signal.h>
|
||||
#include <lib/nfc/nfc_device.h>
|
||||
#include <applications/main/nfc/helpers/nfc_generators.h>
|
||||
#include <lib/nfc/helpers/nfc_generators.h>
|
||||
|
||||
#include <lib/flipper_format/flipper_format_i.h>
|
||||
#include <lib/toolbox/stream/file_stream.h>
|
||||
@@ -102,7 +102,10 @@ static bool nfc_test_digital_signal_test_encode(
|
||||
|
||||
do {
|
||||
// Read test data
|
||||
if(!nfc_test_read_signal_from_file(file_name)) break;
|
||||
if(!nfc_test_read_signal_from_file(file_name)) {
|
||||
FURI_LOG_E(TAG, "Failed to read signal from file");
|
||||
break;
|
||||
}
|
||||
|
||||
// Encode signal
|
||||
FURI_CRITICAL_ENTER();
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include <lib/nfc/nfc_device.h>
|
||||
#include <lib/nfc/helpers/mf_classic_dict.h>
|
||||
#include <lib/nfc/parsers/nfc_supported_card.h>
|
||||
#include <lib/nfc/helpers/nfc_generators.h>
|
||||
|
||||
#include "views/dict_attack.h"
|
||||
#include "views/detect_reader.h"
|
||||
@@ -51,9 +52,6 @@ typedef enum {
|
||||
NfcRpcStateEmulated,
|
||||
} NfcRpcState;
|
||||
|
||||
// Forward declaration due to circular dependency
|
||||
typedef struct NfcGenerator NfcGenerator;
|
||||
|
||||
struct Nfc {
|
||||
NfcWorker* worker;
|
||||
ViewDispatcher* view_dispatcher;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#include "../nfc_i.h"
|
||||
#include "../helpers/nfc_generators.h"
|
||||
#include "lib/nfc/helpers/nfc_generators.h"
|
||||
|
||||
void nfc_scene_generate_info_dialog_callback(DialogExResult result, void* context) {
|
||||
Nfc* nfc = context;
|
||||
@@ -39,7 +39,12 @@ bool nfc_scene_generate_info_on_event(void* context, SceneManagerEvent event) {
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == DialogExResultRight) {
|
||||
scene_manager_next_scene(nfc->scene_manager, nfc->generator->next_scene);
|
||||
// Switch either to NfcSceneMfClassicMenu or NfcSceneMfUltralightMenu
|
||||
if(nfc->dev->dev_data.protocol == NfcDeviceProtocolMifareClassic) {
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicMenu);
|
||||
} else if(nfc->dev->dev_data.protocol == NfcDeviceProtocolMifareUl) {
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightMenu);
|
||||
}
|
||||
consumed = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#include "../nfc_i.h"
|
||||
#include "../helpers/nfc_generators.h"
|
||||
#include "lib/nfc/helpers/nfc_generators.h"
|
||||
|
||||
enum SubmenuIndex {
|
||||
SubmenuIndexNFCA4,
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#include <m-array.h>
|
||||
|
||||
#define FRAME_HEIGHT 12
|
||||
#define MAX_LEN_PX 100
|
||||
#define MAX_LEN_PX 111
|
||||
#define MENU_ITEMS 4u
|
||||
#define UNLOCK_CNT 3
|
||||
|
||||
@@ -211,7 +211,7 @@ void subghz_view_receiver_draw(Canvas* canvas, SubGhzViewReceiverModel* model) {
|
||||
size_t idx = CLAMP((uint16_t)(i + model->list_offset), model->history_item, 0);
|
||||
item_menu = SubGhzReceiverMenuItemArray_get(model->history->data, idx);
|
||||
furi_string_set(str_buff, item_menu->item_str);
|
||||
elements_string_fit_width(canvas, str_buff, scrollbar ? MAX_LEN_PX - 6 : MAX_LEN_PX);
|
||||
elements_string_fit_width(canvas, str_buff, scrollbar ? MAX_LEN_PX - 7 : MAX_LEN_PX);
|
||||
if(model->idx == idx) {
|
||||
subghz_view_receiver_draw_frame(canvas, i, scrollbar);
|
||||
} else {
|
||||
|
||||
@@ -82,6 +82,7 @@ typedef struct {
|
||||
ProtocolDict* dict;
|
||||
ProtocolId protocol;
|
||||
bool workr_rund;
|
||||
bool attack_stop_called;
|
||||
|
||||
uint8_t time_between_cards;
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ void flipfrid_scene_entrypoint_on_enter(FlipFridState* context) {
|
||||
main_menu_items[0] = furi_string_alloc_set("Default Values");
|
||||
main_menu_items[1] = furi_string_alloc_set("BF Customer ID");
|
||||
main_menu_items[2] = furi_string_alloc_set("Load File");
|
||||
main_menu_items[3] = furi_string_alloc_set("Load uids from file");
|
||||
main_menu_items[3] = furi_string_alloc_set("Load UIDs from file");
|
||||
|
||||
context->menu_proto_index = 0;
|
||||
/*for(uint32_t i = 0; i < 4; i++) {
|
||||
|
||||
@@ -80,6 +80,7 @@ uint8_t id_list_h[14][3] = {
|
||||
void flipfrid_scene_run_attack_on_enter(FlipFridState* context) {
|
||||
context->time_between_cards = 10;
|
||||
context->attack_step = 0;
|
||||
context->attack_stop_called = false;
|
||||
context->dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax);
|
||||
context->worker = lfrfid_worker_alloc(context->dict);
|
||||
if(context->proto == HIDProx) {
|
||||
@@ -499,7 +500,6 @@ void flipfrid_scene_run_attack_on_tick(FlipFridState* context) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(counter > context->time_between_cards) {
|
||||
counter = 0;
|
||||
} else {
|
||||
@@ -543,18 +543,23 @@ void flipfrid_scene_run_attack_on_event(FlipFridEvent event, FlipFridState* cont
|
||||
break;
|
||||
case InputKeyBack:
|
||||
context->is_attacking = false;
|
||||
context->attack_step = 0;
|
||||
counter = 0;
|
||||
|
||||
if(context->attack == FlipFridAttackLoadFileCustomUids) {
|
||||
furi_string_reset(context->data_str);
|
||||
stream_rewind(context->uids_stream);
|
||||
buffered_file_stream_close(context->uids_stream);
|
||||
notification_message(context->notify, &sequence_blink_stop);
|
||||
if(context->attack_stop_called) {
|
||||
context->attack_stop_called = false;
|
||||
context->attack_step = 0;
|
||||
if(context->attack == FlipFridAttackLoadFileCustomUids) {
|
||||
furi_string_reset(context->data_str);
|
||||
stream_rewind(context->uids_stream);
|
||||
buffered_file_stream_close(context->uids_stream);
|
||||
}
|
||||
|
||||
furi_string_reset(context->notification_msg);
|
||||
context->current_scene = SceneEntryPoint;
|
||||
}
|
||||
|
||||
furi_string_reset(context->notification_msg);
|
||||
notification_message(context->notify, &sequence_blink_stop);
|
||||
context->current_scene = SceneEntryPoint;
|
||||
context->attack_stop_called = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
||||
@@ -81,6 +81,7 @@ typedef struct {
|
||||
iButtonKeyType keytype;
|
||||
bool workr_rund;
|
||||
bool enter_rerun;
|
||||
bool attack_stop_called;
|
||||
|
||||
uint8_t time_between_cards;
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ void ibtnfuzzer_scene_entrypoint_on_enter(iBtnFuzzerState* context) {
|
||||
|
||||
main_menu_items[0] = furi_string_alloc_set("Default Values");
|
||||
main_menu_items[1] = furi_string_alloc_set("Load File");
|
||||
main_menu_items[2] = furi_string_alloc_set("Load uids from file");
|
||||
main_menu_items[2] = furi_string_alloc_set("Load UIDs from file");
|
||||
|
||||
context->menu_proto_index = 0;
|
||||
/*for(uint32_t i = 0; i < 4; i++) {
|
||||
|
||||
@@ -71,6 +71,7 @@ uint8_t id_list_cyfral[14][2] = {
|
||||
void ibtnfuzzer_scene_run_attack_on_enter(iBtnFuzzerState* context) {
|
||||
context->time_between_cards = 8;
|
||||
context->attack_step = 0;
|
||||
context->attack_stop_called = false;
|
||||
context->key = ibutton_key_alloc();
|
||||
context->worker = ibutton_worker_alloc();
|
||||
if(context->proto == Metakom) {
|
||||
@@ -387,18 +388,23 @@ void ibtnfuzzer_scene_run_attack_on_event(iBtnFuzzerEvent event, iBtnFuzzerState
|
||||
break;
|
||||
case InputKeyBack:
|
||||
context->is_attacking = false;
|
||||
context->attack_step = 0;
|
||||
counter = 0;
|
||||
|
||||
if(context->attack == iBtnFuzzerAttackLoadFileCustomUids) {
|
||||
furi_string_reset(context->data_str);
|
||||
stream_rewind(context->uids_stream);
|
||||
buffered_file_stream_close(context->uids_stream);
|
||||
notification_message(context->notify, &sequence_blink_stop);
|
||||
if(context->attack_stop_called) {
|
||||
context->attack_stop_called = false;
|
||||
context->attack_step = 0;
|
||||
if(context->attack == iBtnFuzzerAttackLoadFileCustomUids) {
|
||||
furi_string_reset(context->data_str);
|
||||
stream_rewind(context->uids_stream);
|
||||
buffered_file_stream_close(context->uids_stream);
|
||||
}
|
||||
|
||||
furi_string_reset(context->notification_msg);
|
||||
context->current_scene = SceneEntryPoint;
|
||||
}
|
||||
|
||||
furi_string_reset(context->notification_msg);
|
||||
notification_message(context->notify, &sequence_blink_stop);
|
||||
context->current_scene = SceneEntryPoint;
|
||||
context->attack_stop_called = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
||||
@@ -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",
|
||||
)
|
||||
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
typedef enum {
|
||||
//PCSGCustomEvent
|
||||
PCSGCustomEventStartId = 100,
|
||||
|
||||
PCSGCustomEventSceneSettingLock,
|
||||
|
||||
PCSGCustomEventViewReceiverOK,
|
||||
PCSGCustomEventViewReceiverConfig,
|
||||
PCSGCustomEventViewReceiverBack,
|
||||
PCSGCustomEventViewReceiverOffDisplay,
|
||||
PCSGCustomEventViewReceiverUnlock,
|
||||
} PCSGCustomEvent;
|
||||
@@ -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;
|
||||
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 130 B |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 134 B |
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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;
|
||||
@@ -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)};
|
||||
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
#include "../pocsag_pager_app_i.h"
|
||||
|
||||
#include "pocsag.h"
|
||||
|
||||
extern const SubGhzProtocolRegistry pocsag_pager_protocol_registry;
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
@@ -9,9 +9,11 @@
|
||||
|
||||
struct WSReceiverInfo {
|
||||
View* view;
|
||||
FuriTimer* timer;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
uint32_t curr_ts;
|
||||
FuriString* protocol_name;
|
||||
WSBlockGeneric* generic;
|
||||
} WSReceiverInfoModel;
|
||||
@@ -28,6 +30,10 @@ void ws_view_receiver_info_update(WSReceiverInfo* ws_receiver_info, FlipperForma
|
||||
flipper_format_read_string(fff, "Protocol", model->protocol_name);
|
||||
|
||||
ws_block_generic_deserialize(model->generic, fff);
|
||||
|
||||
FuriHalRtcDateTime curr_dt;
|
||||
furi_hal_rtc_get_datetime(&curr_dt);
|
||||
model->curr_ts = furi_hal_rtc_datetime_to_timestamp(&curr_dt);
|
||||
},
|
||||
true);
|
||||
}
|
||||
@@ -86,12 +92,8 @@ void ws_view_receiver_info_draw(Canvas* canvas, WSReceiverInfoModel* model) {
|
||||
canvas_draw_str(canvas, 64, 55, buffer);
|
||||
}
|
||||
|
||||
if((int)model->generic->timestamp > 0) {
|
||||
FuriHalRtcDateTime curr_dt;
|
||||
furi_hal_rtc_get_datetime(&curr_dt);
|
||||
uint32_t curr_ts = furi_hal_rtc_datetime_to_timestamp(&curr_dt);
|
||||
|
||||
int ts_diff = (int)curr_ts - (int)model->generic->timestamp;
|
||||
if((int)model->generic->timestamp > 0 && model->curr_ts) {
|
||||
int ts_diff = (int)model->curr_ts - (int)model->generic->timestamp;
|
||||
|
||||
canvas_draw_icon(canvas, 91, 46, &I_Timer_11x11);
|
||||
|
||||
@@ -103,7 +105,7 @@ void ws_view_receiver_info_draw(Canvas* canvas, WSReceiverInfoModel* model) {
|
||||
cnt_min = i;
|
||||
}
|
||||
|
||||
if(curr_ts % 2 == 0) {
|
||||
if(model->curr_ts % 2 == 0) {
|
||||
canvas_draw_str_aligned(canvas, 105, 51, AlignLeft, AlignCenter, "Old");
|
||||
} else {
|
||||
if(cnt_min >= 59) {
|
||||
@@ -132,14 +134,19 @@ bool ws_view_receiver_info_input(InputEvent* event, void* context) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void ws_view_receiver_info_enter(void* context) {
|
||||
furi_assert(context);
|
||||
}
|
||||
|
||||
void ws_view_receiver_info_exit(void* context) {
|
||||
static void ws_view_receiver_info_enter(void* context) {
|
||||
furi_assert(context);
|
||||
WSReceiverInfo* ws_receiver_info = context;
|
||||
|
||||
furi_timer_start(ws_receiver_info->timer, 1000);
|
||||
}
|
||||
|
||||
static void ws_view_receiver_info_exit(void* context) {
|
||||
furi_assert(context);
|
||||
WSReceiverInfo* ws_receiver_info = context;
|
||||
|
||||
furi_timer_stop(ws_receiver_info->timer);
|
||||
|
||||
with_view_model(
|
||||
ws_receiver_info->view,
|
||||
WSReceiverInfoModel * model,
|
||||
@@ -147,6 +154,20 @@ void ws_view_receiver_info_exit(void* context) {
|
||||
false);
|
||||
}
|
||||
|
||||
static void ws_view_receiver_info_timer(void* context) {
|
||||
WSReceiverInfo* ws_receiver_info = context;
|
||||
// Force redraw
|
||||
with_view_model(
|
||||
ws_receiver_info->view,
|
||||
WSReceiverInfoModel * model,
|
||||
{
|
||||
FuriHalRtcDateTime curr_dt;
|
||||
furi_hal_rtc_get_datetime(&curr_dt);
|
||||
model->curr_ts = furi_hal_rtc_datetime_to_timestamp(&curr_dt);
|
||||
},
|
||||
true);
|
||||
}
|
||||
|
||||
WSReceiverInfo* ws_view_receiver_info_alloc() {
|
||||
WSReceiverInfo* ws_receiver_info = malloc(sizeof(WSReceiverInfo));
|
||||
|
||||
@@ -169,12 +190,17 @@ WSReceiverInfo* ws_view_receiver_info_alloc() {
|
||||
},
|
||||
true);
|
||||
|
||||
ws_receiver_info->timer =
|
||||
furi_timer_alloc(ws_view_receiver_info_timer, FuriTimerTypePeriodic, ws_receiver_info);
|
||||
|
||||
return ws_receiver_info;
|
||||
}
|
||||
|
||||
void ws_view_receiver_info_free(WSReceiverInfo* ws_receiver_info) {
|
||||
furi_assert(ws_receiver_info);
|
||||
|
||||
furi_timer_free(ws_receiver_info->timer);
|
||||
|
||||
with_view_model(
|
||||
ws_receiver_info->view,
|
||||
WSReceiverInfoModel * model,
|
||||
|
||||
@@ -61,21 +61,24 @@ void power_draw_battery_callback(Canvas* canvas, void* context) {
|
||||
}
|
||||
} else { //default bar display, added here to serve as fallback/default behaviour.
|
||||
canvas_draw_box(canvas, 2, 2, (power->info.charge + 4) / 5, 4);
|
||||
if(power->info.voltage_battery_charging < 4.2) {
|
||||
// Battery charging voltage is modified, indicate with cross pattern
|
||||
canvas_invert_color(canvas);
|
||||
uint8_t battery_bar_width = (power->info.charge + 4) / 5;
|
||||
bool cross_odd = false;
|
||||
// Start 1 further in from the battery bar's x position
|
||||
for(uint8_t x = 3; x <= battery_bar_width; x++) {
|
||||
// Cross pattern is from the center of the battery bar
|
||||
// y = 2 + 1 (inset) + 1 (for every other)
|
||||
canvas_draw_dot(canvas, x, 3 + (uint8_t)cross_odd);
|
||||
cross_odd = !cross_odd;
|
||||
}
|
||||
canvas_invert_color(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Verify if it displays correctly with custom battery skins !!!
|
||||
if(power->info.voltage_battery_charging < 4.2) {
|
||||
// Battery charging voltage is modified, indicate with cross pattern
|
||||
canvas_invert_color(canvas);
|
||||
uint8_t battery_bar_width = (power->info.charge + 4) / 5;
|
||||
bool cross_odd = false;
|
||||
// Start 1 further in from the battery bar's x position
|
||||
for(uint8_t x = 3; x <= battery_bar_width; x++) {
|
||||
// Cross pattern is from the center of the battery bar
|
||||
// y = 2 + 1 (inset) + 1 (for every other)
|
||||
canvas_draw_dot(canvas, x, 3 + (uint8_t)cross_odd);
|
||||
cross_odd = !cross_odd;
|
||||
}
|
||||
canvas_invert_color(canvas);
|
||||
}
|
||||
|
||||
if(power->state == PowerStateCharging) {
|
||||
canvas_set_bitmap_mode(canvas, 1);
|
||||
// TODO: replace -1 magic for uint8_t with re-framing
|
||||
|
||||
@@ -1,6 +1,36 @@
|
||||
Filetype: IR library file
|
||||
Version: 1
|
||||
# Last Updated 14th Nov, 2022
|
||||
# Last Updated 6th Dec, 2022
|
||||
#
|
||||
name: POWER
|
||||
type: raw
|
||||
frequency: 38000
|
||||
duty_cycle: 0.330000
|
||||
data: 5041 2133 360 1770 356 693 364 685 362 689 358 1772 364 686 361 689 358 692 365 684 363 1768 358 692 365 1767 359 1771 355 694 363 1769 357 1773 363 1768 358 1772 364 1767 359 689 358 692 365 1765 361 689 358 692 365 685 362 688 359 691 366 684 363 1767 359 1773 363 1768 358 1772 364 1767 359 689 358 1774 362 1769 357 691 366 684 363 687 360 690 357 693 364 685 362 689 358 691 366 684 363 687 360 690 357 693 364 1766 360 1773 363 1767 359 1771 355 694 363 687 360 690 357 693 364 29443 5038 2134 359 1774 362 686 361 689 358 692 365 1765 361 689 358 692 365 685 362 688 359 1771 365 685 362 1769 357 1775 361 687 360 1772 364 1767 359 1771 355 1776 360 1770 356 693 364 686 361 1799 327 693 364 686 361 689 358 692 365 685 362 688 359 691 356 694 363 686 361 689 358 1773 363 1769 357 691 366 684 363 1798 328 692 365 1767 359 1771 355 694 363 686 361 689 358 692 365 1765 361 1772 364 684 363 687 360 1801 335 684 363 687 360 690 357 693 364 686 361 1769 357 694 363 686 361 689 358 692 365 685 362 688 359 691 356 694 363 687 360 690 357 693 365 685 362 688 359 691 367 684 363 687 360 690 357 693 365 685 362 688 359 1772 364 1768 358 690 357 693 364 686 361 689 358 692 365 1765 361 689 358 692 365 685 362 688 359 691 356 694 363 687 360 689 358 1773 363 687 360 690 357 693 364 685 362 688 359 1772 364 686 361 1769 357 1776 360 1770 356 1775 361 687 360
|
||||
#
|
||||
name: MODE
|
||||
type: raw
|
||||
frequency: 38000
|
||||
duty_cycle: 0.330000
|
||||
data: 5039 2133 360 1773 363 685 362 688 359 691 356 1776 361 688 359 691 356 694 364 687 360 1772 364 684 363 1769 357 1773 363 685 362 1771 355 1775 361 1770 356 1774 362 1769 357 691 366 684 363 1769 357 692 365 684 363 687 360 690 357 693 364 686 361 1771 355 1776 360 1770 356 1775 361 1769 357 692 366 1766 360 1771 355 693 365 686 361 689 358 692 365 685 362 688 359 691 356 694 363 686 361 689 358 692 365 685 362 1770 356 1774 362 1769 357 1773 363 685 362 688 359 691 366 684 363 29445 5038 2134 359 1773 363 685 362 689 358 692 365 1766 360 689 358 692 365 684 363 687 360 1772 364 684 363 1770 356 1774 363 686 361 1772 354 1776 360 1770 356 1775 361 1769 357 692 365 685 362 1770 356 692 365 685 362 688 359 691 356 694 363 687 360 690 357 693 364 685 362 688 359 1773 363 1767 359 690 357 693 365 1768 358 690 357 1775 361 1769 357 692 365 685 362 688 359 691 356 694 364 1768 358 1773 363 685 362 1770 356 693 364 686 361 689 358 691 366 684 363 1769 357 692 365 684 363 687 360 690 357 693 364 686 361 689 358 692 365 684 363 687 360 690 357 693 364 686 361 689 358 692 365 685 362 688 359 691 356 694 363 686 361 1771 355 1776 360 688 359 691 367 684 363 687 360 690 357 1775 362 687 360 690 357 693 365 686 361 689 358 691 367 684 363 687 360 1772 364 684 363 687 360 690 357 693 365 686 361 1771 355 693 365 1768 358 690 357 1776 361 688 359 1773 363
|
||||
#
|
||||
name: SWING
|
||||
type: raw
|
||||
frequency: 38000
|
||||
duty_cycle: 0.330000
|
||||
data: 5035 2166 327 1776 360 688 359 691 356 694 364 1769 357 691 367 684 363 687 360 690 357 1775 362 687 360 1772 364 1766 360 689 358 1774 362 1768 358 1773 363 1767 359 1772 364 684 363 687 360 1772 364 684 363 687 360 690 357 693 365 686 361 689 358 1774 363 1768 358 1772 364 1767 359 1771 355 694 364 1769 357 1773 363 685 362 688 359 691 366 684 363 687 360 690 357 693 364 685 362 688 359 691 366 684 363 687 360 1772 364 1766 360 1771 355 1775 361 687 360 690 357 693 365 686 361 29447 5037 2136 357 1774 362 686 361 689 358 692 365 1767 359 689 358 692 366 685 362 687 360 1773 363 685 362 1770 356 1775 362 686 361 1772 364 1766 360 1771 355 1775 361 1770 356 692 365 685 362 1770 356 692 366 685 362 688 359 691 356 694 364 686 361 689 358 692 366 685 362 687 360 1773 363 1767 359 689 358 692 366 1767 359 690 357 1775 361 1769 357 1774 363 686 361 688 359 691 367 684 363 1769 357 1774 363 686 361 1771 355 693 365 686 361 689 358 1774 363 1768 358 1773 363 1767 359 689 358 692 365 685 362 688 359 691 356 694 363 686 361 689 358 692 365 685 362 688 359 691 356 694 364 686 361 689 358 693 364 685 362 688 359 691 356 1776 360 1770 356 693 365 685 362 688 359 691 367 684 363 1769 357 692 366 684 363 687 360 690 357 693 364 686 361 689 358 692 366 1766 360 689 358 692 365 685 362 688 359 1772 365 1767 359 689 358 1774 362 1768 358 691 356 1776 361 688 359
|
||||
#
|
||||
name: TEMP-
|
||||
type: raw
|
||||
frequency: 38000
|
||||
duty_cycle: 0.330000
|
||||
data: 5044 2130 363 1768 358 690 357 693 364 686 361 1771 355 694 363 686 361 689 358 692 366 1767 359 689 358 1774 363 1768 358 691 356 1776 361 1770 356 1775 362 1769 357 1773 363 685 362 719 328 1774 362 686 361 689 358 692 365 684 363 687 360 690 357 1775 362 1769 357 1774 362 1768 358 1773 363 685 362 1770 356 1775 361 687 360 690 357 693 365 685 362 688 359 691 367 684 363 687 360 690 357 693 365 685 362 688 359 1773 363 1767 359 1772 364 1766 360 688 359 691 366 684 363 687 360 29448 5036 2135 358 1775 362 686 361 689 358 692 366 1767 359 689 358 692 366 685 362 688 359 1773 364 685 362 1770 356 1774 363 686 361 1772 354 1776 361 1770 356 1775 362 1769 357 692 366 684 363 1769 357 692 365 684 363 687 360 690 357 693 365 686 361 689 358 692 366 684 363 687 360 1772 365 1766 360 689 358 692 365 1766 360 689 358 1774 363 1768 358 1773 363 685 362 688 359 691 356 1776 361 1770 356 692 366 685 362 1770 356 692 365 685 362 688 359 1773 364 1767 359 1771 355 1776 361 688 359 691 356 694 364 686 361 689 358 692 366 685 362 688 359 691 356 694 364 686 361 689 358 692 366 684 363 687 360 690 357 693 365 1768 358 1772 365 684 363 1769 357 692 366 684 363 687 360 690 357 693 365 1768 358 690 357 693 365 686 361 689 358 691 367 684 363 687 360 690 357 1775 362 687 360 689 358 692 366 685 362 1770 356 692 366 685 362 1770 356 692 366 1767 359 690 357 693 365
|
||||
#
|
||||
name: TEMP+
|
||||
type: raw
|
||||
frequency: 38000
|
||||
duty_cycle: 0.330000
|
||||
data: 5038 2134 359 1773 363 685 362 688 359 691 367 1766 360 688 359 691 356 694 364 686 361 1771 355 694 364 1768 358 1773 364 685 362 1770 356 1774 363 1768 358 1773 364 1767 359 689 358 692 366 1767 359 689 358 692 366 685 362 688 359 690 357 693 365 1768 358 1772 365 1767 359 1771 355 1775 362 687 360 1772 365 1767 359 689 358 692 366 684 363 687 360 690 357 693 365 686 361 689 358 691 367 684 363 687 360 690 357 1775 362 1769 357 1773 364 1767 359 690 357 693 365 685 362 688 359 29449 5037 2134 359 1773 364 685 362 688 359 691 356 1776 361 688 359 691 356 694 364 686 361 1771 355 693 365 1768 358 1773 364 685 362 1770 356 1774 363 1768 358 1773 364 1767 359 689 358 692 366 1767 359 689 358 692 366 685 362 688 359 690 357 693 365 686 361 689 358 692 366 684 363 1769 357 1774 363 685 362 689 358 1774 363 685 362 1770 356 1775 362 1769 357 691 367 684 363 687 360 1772 365 1766 360 688 359 691 367 1766 360 688 359 691 367 684 363 1769 357 1773 364 1767 359 1772 365 684 363 687 360 690 357 693 365 685 362 688 359 691 367 684 363 687 360 689 358 692 366 685 362 688 359 691 356 694 364 686 361 689 358 692 366 684 363 1769 357 1774 363 686 361 688 359 691 367 684 363 687 360 1772 354 694 364 686 361 689 358 692 366 685 362 688 359 691 356 694 364 1768 358 691 356 694 364 686 361 689 358 1774 363 1768 358 690 357 1775 362 687 360 1772 365 684 363 687 360
|
||||
#
|
||||
name: POWER
|
||||
type: raw
|
||||
|
||||
@@ -1,6 +1,96 @@
|
||||
Filetype: IR library file
|
||||
Version: 1
|
||||
# Last Updated 14th Nov, 2022
|
||||
# Last Updated 6th Dec, 2022
|
||||
#
|
||||
name: POWER
|
||||
type: parsed
|
||||
protocol: NECext
|
||||
address: 3F 5C 00 00
|
||||
command: 18 E7 00 00
|
||||
#
|
||||
name: VOL+
|
||||
type: parsed
|
||||
protocol: NECext
|
||||
address: 3F 5C 00 00
|
||||
command: 55 AA 00 00
|
||||
#
|
||||
name: VOL-
|
||||
type: parsed
|
||||
protocol: NECext
|
||||
address: 3F 5C 00 00
|
||||
command: 59 A6 00 00
|
||||
#
|
||||
name: MUTE
|
||||
type: parsed
|
||||
protocol: NECext
|
||||
address: 3F 5C 00 00
|
||||
command: 15 EA 00 00
|
||||
#
|
||||
name: MUTE
|
||||
type: parsed
|
||||
protocol: NECext
|
||||
address: 00 FB 00 00
|
||||
command: 1E E1 00 00
|
||||
#
|
||||
name: POWER
|
||||
type: parsed
|
||||
protocol: NECext
|
||||
address: 00 FB 00 00
|
||||
command: 1D E2 00 00
|
||||
#
|
||||
name: VOL-
|
||||
type: parsed
|
||||
protocol: NECext
|
||||
address: 00 FB 00 00
|
||||
command: 18 E7 00 00
|
||||
#
|
||||
name: VOL+
|
||||
type: parsed
|
||||
protocol: NECext
|
||||
address: 00 FB 00 00
|
||||
command: 0A F5 00 00
|
||||
#
|
||||
name: VOL+
|
||||
type: parsed
|
||||
protocol: NECext
|
||||
address: 80 D9 00 00
|
||||
command: 8A 75 00 00
|
||||
#
|
||||
name: VOL-
|
||||
type: parsed
|
||||
protocol: NECext
|
||||
address: 80 D9 00 00
|
||||
command: 88 77 00 00
|
||||
#
|
||||
name: MUTE
|
||||
type: parsed
|
||||
protocol: NECext
|
||||
address: 80 D9 00 00
|
||||
command: 8C 73 00 00
|
||||
#
|
||||
name: POWER
|
||||
type: parsed
|
||||
protocol: NECext
|
||||
address: 78 0E 00 00
|
||||
command: 00 FF 00 00
|
||||
#
|
||||
name: VOL+
|
||||
type: parsed
|
||||
protocol: NECext
|
||||
address: 78 0E 00 00
|
||||
command: 19 E6 00 00
|
||||
#
|
||||
name: VOL-
|
||||
type: parsed
|
||||
protocol: NECext
|
||||
address: 78 0E 00 00
|
||||
command: 1C E3 00 00
|
||||
#
|
||||
name: MUTE
|
||||
type: parsed
|
||||
protocol: NECext
|
||||
address: 78 0E 00 00
|
||||
command: 18 E7 00 00
|
||||
#
|
||||
name: VOL+
|
||||
type: parsed
|
||||
|
||||
@@ -1,6 +1,78 @@
|
||||
Filetype: IR library file
|
||||
Version: 1
|
||||
# Last Updated 14th Nov, 2022
|
||||
# Last Updated 6th Dec, 2022
|
||||
#
|
||||
name: POWER
|
||||
type: parsed
|
||||
protocol: NECext
|
||||
address: 4B 14 00 00
|
||||
command: 80 7F 00 00
|
||||
#
|
||||
name: MODE
|
||||
type: parsed
|
||||
protocol: NECext
|
||||
address: 4B 14 00 00
|
||||
command: 20 DF 00 00
|
||||
#
|
||||
name: SWING
|
||||
type: parsed
|
||||
protocol: NECext
|
||||
address: 4B 14 00 00
|
||||
command: C0 3F 00 00
|
||||
#
|
||||
name: TIMER
|
||||
type: parsed
|
||||
protocol: NECext
|
||||
address: 4B 14 00 00
|
||||
command: A0 5F 00 00
|
||||
#
|
||||
name: POWER
|
||||
type: raw
|
||||
frequency: 38000
|
||||
duty_cycle: 0.330000
|
||||
data: 9137 4430 630 1594 661 464 659 1625 630 1624 631 1625 630 1596 659 1596 660 1624 631 492 631 1624 632 1623 632 1593 662 1597 658 1624 631 1625 630 1595 660 491 632 1595 661 1595 660 1624 631 1624 631 1596 660 1624 631 1624 631 1593 663 435 689 491 632 434 690 491 632 491 632 463 661 490 634
|
||||
#
|
||||
name: SPEED+
|
||||
type: raw
|
||||
frequency: 38000
|
||||
duty_cycle: 0.330000
|
||||
data: 9107 4400 660 1622 633 464 659 1623 632 1596 659 1622 633 1622 633 1623 632 1622 633 493 630 1593 662 1595 660 1623 632 1593 662 1623 632 1596 659 1623 632 1622 633 420 703 1594 661 424 699 1592 663 1594 661 1595 660 1597 658 423 700 1623 632 489 634 1593 662 461 662 431 692 488 635 489 634
|
||||
#
|
||||
name: SPEED-
|
||||
type: raw
|
||||
frequency: 38000
|
||||
duty_cycle: 0.330000
|
||||
data: 9106 4428 632 1595 660 463 660 1595 660 1622 633 1593 662 1622 633 1622 633 1595 660 490 633 1595 660 1595 660 1592 663 1593 662 1623 632 1623 632 1594 661 1623 632 1595 660 1594 661 1596 659 489 634 1595 660 1595 660 1596 659 463 660 490 633 462 662 422 701 1595 660 464 659 490 633 428 695
|
||||
#
|
||||
name: MODE
|
||||
type: raw
|
||||
frequency: 38000
|
||||
duty_cycle: 0.330000
|
||||
data: 9136 4401 659 1624 631 463 660 1624 631 1593 663 1623 632 1624 632 1596 660 1594 661 464 659 1597 658 1593 662 1623 632 1595 660 1623 632 1624 631 1594 662 489 634 1623 633 431 693 1594 662 1623 633 1594 662 1595 661 1593 663 1592 663 461 663 1623 632 463 660 434 689 463 660 463 661 426 698
|
||||
#
|
||||
name: SWING
|
||||
type: raw
|
||||
frequency: 38000
|
||||
duty_cycle: 0.330000
|
||||
data: 9142 4400 661 1593 662 491 632 1624 632 1598 657 1594 662 1594 662 1596 660 1596 660 490 634 1596 660 1596 660 1624 631 1594 662 1623 632 1594 662 1624 632 1624 632 491 632 1624 632 1595 661 1624 631 1624 632 1594 662 1624 631 491 633 1624 632 464 659 492 632 432 692 430 694 464 660 432 692
|
||||
#
|
||||
name: TIMER
|
||||
type: raw
|
||||
frequency: 38000
|
||||
duty_cycle: 0.330000
|
||||
data: 9134 4428 632 1597 658 355 768 1624 631 1623 632 1623 632 1597 658 1623 632 1622 633 463 660 1595 660 1622 633 1596 659 1595 660 1622 633 1623 632 1596 659 1622 633 1623 632 436 687 1623 633 1622 633 1622 633 1622 633 1598 657 434 689 489 634 1622 633 466 657 489 634 490 633 490 633 440 684
|
||||
#
|
||||
name: POWER
|
||||
type: raw
|
||||
frequency: 38000
|
||||
duty_cycle: 0.330000
|
||||
data: 8980 4377 620 1599 620 489 620 488 621 488 621 488 622 488 621 488 621 488 621 488 621 1597 622 1598 621 1598 621 1597 622 1597 622 1597 622 1597 622 1597 621 1598 646 463 646 464 645 464 645 465 644 466 643 466 643 466 643 466 643 1575 643 1576 643 1576 643 1576 643 1576 643 1576 643 1576 643 1576 643 466 643 466 643 466 643 466 643 466 643 466 643 466 644 466 643 1576 643 1576 643 1576 643 1576 643 1576 643 1576 643
|
||||
#
|
||||
name: ROTATE
|
||||
type: raw
|
||||
frequency: 38000
|
||||
duty_cycle: 0.330000
|
||||
data: 9011 4375 620 1598 621 462 647 488 621 488 621 488 676 434 621 488 621 489 620 488 621 1598 621 1598 621 1598 621 1598 621 1597 622 1598 621 1597 622 1597 622 1598 621 488 621 489 644 1576 644 466 643 466 643 466 643 466 643 466 644 1576 643 1576 643 466 643 1576 643 1576 643 1576 643 1576 643 1576 643 466 644 466 643 1576 643 466 644 466 643 466 644 466 643 466 643 1575 644 1576 643 466 643 1575 643 1576 643 1576 643
|
||||
#
|
||||
name: POWER
|
||||
type: raw
|
||||
|
||||
@@ -1,6 +1,78 @@
|
||||
Filetype: IR library file
|
||||
Version: 1
|
||||
# Last Updated 15th Oct, 2022
|
||||
# Last Updated 9th Dec, 2022
|
||||
#
|
||||
name: VOL-
|
||||
type: parsed
|
||||
protocol: NECext
|
||||
address: 00 30 00 00
|
||||
command: 83 7C 00 00
|
||||
#
|
||||
name: VOL+
|
||||
type: parsed
|
||||
protocol: NECext
|
||||
address: 00 30 00 00
|
||||
command: 82 7D 00 00
|
||||
#
|
||||
name: POWER
|
||||
type: parsed
|
||||
protocol: NECext
|
||||
address: 08 13 00 00
|
||||
command: 87 78 00 00
|
||||
#
|
||||
name: POWER
|
||||
type: raw
|
||||
frequency: 38000
|
||||
duty_cycle: 0.330000
|
||||
data: 9055 4338 672 1551 669 1553 618 1603 619 481 617 482 616 481 617 507 591 1605 645 479 619 1577 645 1578 644 1578 644 479 619 480 618 1581 641 480 617 1605 617 1606 616 1606 615 483 615 1608 614 484 614 484 614 484 614 484 614 484 614 484 614 1609 614 484 614 1609 614 1609 613 1609 613 40058 9000 2068 614 95467 9022 2068 614
|
||||
#
|
||||
name: MUTE
|
||||
type: parsed
|
||||
protocol: NECext
|
||||
address: 87 4E 00 00
|
||||
command: 29 D6 00 00
|
||||
#
|
||||
name: VOL+
|
||||
type: parsed
|
||||
protocol: NECext
|
||||
address: 87 4E 00 00
|
||||
command: 08 F7 00 00
|
||||
#
|
||||
name: VOL-
|
||||
type: parsed
|
||||
protocol: NECext
|
||||
address: 87 4E 00 00
|
||||
command: 04 FB 00 00
|
||||
#
|
||||
name: MUTE
|
||||
type: parsed
|
||||
protocol: NECext
|
||||
address: 83 55 00 00
|
||||
command: 93 6C 00 00
|
||||
#
|
||||
name: VOL-
|
||||
type: parsed
|
||||
protocol: NEC
|
||||
address: 02 00 00 00
|
||||
command: 15 00 00 00
|
||||
#
|
||||
name: VOL+
|
||||
type: raw
|
||||
frequency: 38000
|
||||
duty_cycle: 0.330000
|
||||
data: 9032 4462 598 501 627 1604 627 530 598 531 677 423 706 422 706 421 707 451 677 1554 677 451 598 1633 598 1634 597 1634 598 1634 598 1634 625 1606 681 1550 626 502 598 530 599 529 600 1632 600 528 600 528 601 528 601 528 601 1631 600 1631 625 1607 625 504 625 1607 624 1608 624 1608 623
|
||||
#
|
||||
name: MUTE
|
||||
type: parsed
|
||||
protocol: NEC
|
||||
address: 02 00 00 00
|
||||
command: 02 00 00 00
|
||||
#
|
||||
name: POWER
|
||||
type: parsed
|
||||
protocol: NEC
|
||||
address: 02 00 00 00
|
||||
command: 1D 00 00 00
|
||||
#
|
||||
# ON
|
||||
name: POWER
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
<br>
|
||||
|
||||
### **If installing for first time - Update to the latest official firmware before proceeding**
|
||||
### **If you are already using unleashed - no need to install any other FW or version before installing update, just install latest version on top of your current one, all will be fine**
|
||||
|
||||
<br>
|
||||
<br>
|
||||
@@ -63,7 +64,7 @@
|
||||
|
||||
## With qFlipper (1.2.0+)
|
||||
|
||||
- Download qFlipper that allows `.tgz` installation [Download qFlipper 1.2.1 (official link)](https://update.flipperzero.one/builds/qFlipper/1.2.1/)
|
||||
- Download qFlipper that allows `.tgz` installation [Download qFlipper (official link)](https://flipperzero.one/update)
|
||||
- Be sure you updated to latest official release before(only if installing for the first time), and verify that microSD card is installed
|
||||
- Open latest release page - [Releases](https://github.com/DarkFlippers/unleashed-firmware/releases/latest)
|
||||
- Download `flipper-z-f7-update-(version).tgz`
|
||||
|
||||
@@ -81,7 +81,6 @@ FIRMWARE_APPS = {
|
||||
"basic_services",
|
||||
"updater_app",
|
||||
"unit_tests",
|
||||
"nfc",
|
||||
],
|
||||
"debug_pack": [
|
||||
# Svc
|
||||
|
||||
@@ -376,103 +376,86 @@ static void nfc_generate_mf_classic_4k_7b_uid(NfcDeviceData* data) {
|
||||
static const NfcGenerator mf_ul_generator = {
|
||||
.name = "Mifare Ultralight",
|
||||
.generator_func = nfc_generate_mf_ul_orig,
|
||||
.next_scene = NfcSceneMfUltralightMenu,
|
||||
};
|
||||
|
||||
static const NfcGenerator mf_ul_11_generator = {
|
||||
.name = "Mifare Ultralight EV1 11",
|
||||
.generator_func = nfc_generate_mf_ul_11,
|
||||
.next_scene = NfcSceneMfUltralightMenu,
|
||||
};
|
||||
|
||||
static const NfcGenerator mf_ul_h11_generator = {
|
||||
.name = "Mifare Ultralight EV1 H11",
|
||||
.generator_func = nfc_generate_mf_ul_h11,
|
||||
.next_scene = NfcSceneMfUltralightMenu,
|
||||
};
|
||||
|
||||
static const NfcGenerator mf_ul_21_generator = {
|
||||
.name = "Mifare Ultralight EV1 21",
|
||||
.generator_func = nfc_generate_mf_ul_21,
|
||||
.next_scene = NfcSceneMfUltralightMenu,
|
||||
};
|
||||
|
||||
static const NfcGenerator mf_ul_h21_generator = {
|
||||
.name = "Mifare Ultralight EV1 H21",
|
||||
.generator_func = nfc_generate_mf_ul_h21,
|
||||
.next_scene = NfcSceneMfUltralightMenu,
|
||||
};
|
||||
|
||||
static const NfcGenerator ntag203_generator = {
|
||||
.name = "NTAG203",
|
||||
.generator_func = nfc_generate_mf_ul_ntag203,
|
||||
.next_scene = NfcSceneMfUltralightMenu,
|
||||
};
|
||||
|
||||
static const NfcGenerator ntag213_generator = {
|
||||
.name = "NTAG213",
|
||||
.generator_func = nfc_generate_ntag213,
|
||||
.next_scene = NfcSceneMfUltralightMenu,
|
||||
};
|
||||
|
||||
static const NfcGenerator ntag215_generator = {
|
||||
.name = "NTAG215",
|
||||
.generator_func = nfc_generate_ntag215,
|
||||
.next_scene = NfcSceneMfUltralightMenu,
|
||||
};
|
||||
|
||||
static const NfcGenerator ntag216_generator = {
|
||||
.name = "NTAG216",
|
||||
.generator_func = nfc_generate_ntag216,
|
||||
.next_scene = NfcSceneMfUltralightMenu,
|
||||
};
|
||||
|
||||
static const NfcGenerator ntag_i2c_1k_generator = {
|
||||
.name = "NTAG I2C 1k",
|
||||
.generator_func = nfc_generate_ntag_i2c_1k,
|
||||
.next_scene = NfcSceneMfUltralightMenu,
|
||||
};
|
||||
|
||||
static const NfcGenerator ntag_i2c_2k_generator = {
|
||||
.name = "NTAG I2C 2k",
|
||||
.generator_func = nfc_generate_ntag_i2c_2k,
|
||||
.next_scene = NfcSceneMfUltralightMenu,
|
||||
};
|
||||
|
||||
static const NfcGenerator ntag_i2c_plus_1k_generator = {
|
||||
.name = "NTAG I2C Plus 1k",
|
||||
.generator_func = nfc_generate_ntag_i2c_plus_1k,
|
||||
.next_scene = NfcSceneMfUltralightMenu,
|
||||
};
|
||||
|
||||
static const NfcGenerator ntag_i2c_plus_2k_generator = {
|
||||
.name = "NTAG I2C Plus 2k",
|
||||
.generator_func = nfc_generate_ntag_i2c_plus_2k,
|
||||
.next_scene = NfcSceneMfUltralightMenu,
|
||||
};
|
||||
|
||||
static const NfcGenerator mifare_classic_1k_4b_uid_generator = {
|
||||
.name = "Mifare Classic 1k 4byte UID",
|
||||
.generator_func = nfc_generate_mf_classic_1k_4b_uid,
|
||||
.next_scene = NfcSceneMfClassicMenu,
|
||||
};
|
||||
|
||||
static const NfcGenerator mifare_classic_1k_7b_uid_generator = {
|
||||
.name = "Mifare Classic 1k 7byte UID",
|
||||
.generator_func = nfc_generate_mf_classic_1k_7b_uid,
|
||||
.next_scene = NfcSceneMfClassicMenu,
|
||||
};
|
||||
|
||||
static const NfcGenerator mifare_classic_4k_4b_uid_generator = {
|
||||
.name = "Mifare Classic 4k 4byte UID",
|
||||
.generator_func = nfc_generate_mf_classic_4k_4b_uid,
|
||||
.next_scene = NfcSceneMfClassicMenu,
|
||||
};
|
||||
|
||||
static const NfcGenerator mifare_classic_4k_7b_uid_generator = {
|
||||
.name = "Mifare Classic 4k 7byte UID",
|
||||
.generator_func = nfc_generate_mf_classic_4k_7b_uid,
|
||||
.next_scene = NfcSceneMfClassicMenu,
|
||||
};
|
||||
|
||||
const NfcGenerator* const nfc_generators[] = {
|
||||
@@ -1,14 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include "../nfc_i.h"
|
||||
#include "../nfc_device.h"
|
||||
|
||||
typedef void (*NfcGeneratorFunc)(NfcDeviceData* data);
|
||||
|
||||
struct NfcGenerator {
|
||||
typedef struct {
|
||||
const char* name;
|
||||
NfcGeneratorFunc generator_func;
|
||||
NfcScene next_scene;
|
||||
};
|
||||
} NfcGenerator;
|
||||
|
||||
extern const NfcGenerator* const nfc_generators[];
|
||||
|
||||
@@ -77,28 +77,38 @@ def add_env(name, value, file):
|
||||
print(f"{delimeter}", file=file)
|
||||
|
||||
|
||||
def add_envs(data, env_file, args):
|
||||
add_env("COMMIT_MSG", data["commit_comment"], env_file)
|
||||
add_env("COMMIT_HASH", data["commit_hash"], env_file)
|
||||
add_env("COMMIT_SHA", data["commit_sha"], env_file)
|
||||
add_env("SUFFIX", data["suffix"], env_file)
|
||||
add_env("BRANCH_NAME", data["branch_name"], env_file)
|
||||
add_env("DIST_SUFFIX", data["suffix"], env_file)
|
||||
add_env("WORKFLOW_BRANCH_OR_TAG", data["branch_name"], env_file)
|
||||
def add_set_output_var(name, value, file):
|
||||
print(f"{name}={value}", file=file)
|
||||
|
||||
|
||||
def add_envs(data, gh_env_file, gh_out_file, args):
|
||||
add_env("COMMIT_MSG", data["commit_comment"], gh_env_file)
|
||||
add_env("COMMIT_HASH", data["commit_hash"], gh_env_file)
|
||||
add_env("COMMIT_SHA", data["commit_sha"], gh_env_file)
|
||||
add_env("SUFFIX", data["suffix"], gh_env_file)
|
||||
add_env("BRANCH_NAME", data["branch_name"], gh_env_file)
|
||||
add_env("DIST_SUFFIX", data["suffix"], gh_env_file)
|
||||
add_env("WORKFLOW_BRANCH_OR_TAG", data["branch_name"], gh_env_file)
|
||||
add_set_output_var("branch_name", data["branch_name"], gh_out_file)
|
||||
add_set_output_var("commit_sha", data["commit_sha"], gh_out_file)
|
||||
add_set_output_var("default_target", os.getenv("DEFAULT_TARGET"), gh_out_file)
|
||||
add_set_output_var("suffix", data["suffix"], gh_out_file)
|
||||
if args.type == "pull":
|
||||
add_env("PULL_ID", data["pull_id"], env_file)
|
||||
add_env("PULL_NAME", data["pull_name"], env_file)
|
||||
add_env("PULL_ID", data["pull_id"], gh_env_file)
|
||||
add_env("PULL_NAME", data["pull_name"], gh_env_file)
|
||||
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
event_file = open(args.event_file)
|
||||
event_file = open(args.event_file, "r")
|
||||
event = json.load(event_file)
|
||||
env_file = open(os.environ["GITHUB_ENV"], "a")
|
||||
gh_env_file = open(os.environ["GITHUB_ENV"], "a")
|
||||
gh_out_file = open(os.environ["GITHUB_OUTPUT"], "a")
|
||||
data = get_details(event, args)
|
||||
add_envs(data, env_file, args)
|
||||
add_envs(data, gh_env_file, gh_out_file, args)
|
||||
event_file.close()
|
||||
env_file.close()
|
||||
gh_env_file.close()
|
||||
gh_out_file.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import argparse
|
||||
from slack_sdk import WebClient
|
||||
from slack_sdk.errors import SlackApiError
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("slack_token")
|
||||
parser.add_argument("slack_channel")
|
||||
args = parser.parse_args()
|
||||
return args
|
||||
|
||||
|
||||
def checkCommitMessage(msg):
|
||||
regex = re.compile(r"^'?\[FL-\d+\]")
|
||||
if regex.match(msg):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def reportSlack(commit_hash, slack_token, slack_channel, message):
|
||||
client = WebClient(token=slack_token)
|
||||
try:
|
||||
client.chat_postMessage(channel="#" + slack_channel, text=message)
|
||||
except SlackApiError as e:
|
||||
print(e)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
commit_msg = os.getenv("COMMIT_MSG")
|
||||
commit_hash = os.getenv("COMMIT_HASH")
|
||||
commit_sha = os.getenv("COMMIT_SHA")
|
||||
commit_link = (
|
||||
"<https://github.com/flipperdevices/flipperzero-firmware/commit/"
|
||||
+ commit_hash
|
||||
+ "|"
|
||||
+ commit_sha
|
||||
+ ">"
|
||||
)
|
||||
message = "Commit " + commit_link + " merged to dev without 'FL' ticket!"
|
||||
if not checkCommitMessage(commit_msg):
|
||||
reportSlack(commit_hash, args.slack_token, args.slack_channel, message)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||