Compare commits
22 Commits
un2-f61a8f
...
un1-72713d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6c08564d37 | ||
|
|
5b8311cdea | ||
|
|
68429e191d | ||
|
|
5e7bcea29d | ||
|
|
a139f015b9 | ||
|
|
6579576490 | ||
|
|
57251eb028 | ||
|
|
f33ea3ebe0 | ||
|
|
84d12da45a | ||
|
|
72713d6f4e | ||
|
|
468bc1dace | ||
|
|
56f760aa07 | ||
|
|
6db6d123d5 | ||
|
|
68009c6230 | ||
|
|
02c27becb0 | ||
|
|
576dab02a4 | ||
|
|
4942bd2105 | ||
|
|
1ee82ba865 | ||
|
|
6e9658608e | ||
|
|
d49ca17824 | ||
|
|
fc776446de | ||
|
|
76aecb597a |
23
CHANGELOG.md
@@ -1,19 +1,12 @@
|
||||
### New changes
|
||||
* SubGHz: Keeloq - added support for FAAC RC,XT, Mutancode, Normstahl
|
||||
* LFRFID: CLI - Add raw_analyze to usage help
|
||||
* PR -> Plugins: Minesweeper: Set cursor to initial position on death (by @TQMatvey | PR #113)
|
||||
* PR -> Plugins: Flappy bird: Border hitboxes, bigger Pilars (by @TQMatvey | PR #114)
|
||||
* Plugins: Updated Minesweeper [(by panki27)](https://github.com/panki27/minesweeper)
|
||||
* Plugins: Added DTMF Dolphin [(by litui)](https://github.com/litui/dtmf_dolphin)
|
||||
* Plugins: Updated TOTP (Authenticator) [(by akopachov)](https://github.com/akopachov/flipper-zero_authenticator)
|
||||
* Infrared: Updated universal remote assets (by @Amec0e)
|
||||
* OFW: Feature: infrared add remote to cli
|
||||
* OFW: Remove the back button from MFC keys list
|
||||
* OFW: NFC fixes part 3
|
||||
* OFW: Enable all `view_` methods in SDK
|
||||
* OFW: fbt: fix for cincludes in app's private library definition
|
||||
* OFW: Desktop: fix fap in settings
|
||||
* OFW: Add BLE disconnect request
|
||||
* PR -> NFC: New mifare classic keys (by @ankris812 | PR #119)
|
||||
* New API version 3.2 -> 4.1 (due to breaking changes in subghz part) (old apps needs to be recompiled, update for extra apps pack is coming soon)
|
||||
* OFW PR: WeatherStation plugin and SubGHz changes (OFW PR 1833 by Skorpionm)
|
||||
* OFW: Allow pins 0 and 1 as RTS/DTR for USB UART Bridge
|
||||
* OFW: Picopass: Read Elite
|
||||
* OFW: SubGhz: CAME Wrong number of bits in key (add protocol Airforce)
|
||||
* OFW: Forced RAW receive option for Infrared CLI
|
||||
* OFW: scripts: fixed c2 bundle format
|
||||
|
||||
#### [🎲 Download extra apps pack](https://download-directory.github.io/?url=https://github.com/UberGuidoZ/Flipper/tree/main/Applications/Unleashed)
|
||||
|
||||
|
||||
@@ -97,6 +97,7 @@ Games:
|
||||
- Tic Tac Toe (refactored by xMasterX) [(by gotnull)](https://github.com/gotnull/flipperzero-firmware-wPlugins)
|
||||
- Tetris (with fixes) [(by jeffplang)](https://github.com/jeffplang/flipperzero-firmware/tree/tetris_game/applications/tetris_game)
|
||||
- Minesweeper [(by panki27)](https://github.com/panki27/minesweeper)
|
||||
- Heap Defence (aka Stack Attack) [(original by wquinoa & Vedmein)](https://github.com/Vedmein/flipperzero-firmware/tree/hd/svisto-perdelki) -> Ported to latest firmware by @xMasterX
|
||||
|
||||
### Other changes
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#include <lib/subghz/transmitter.h>
|
||||
#include <lib/subghz/subghz_keystore.h>
|
||||
#include <lib/subghz/subghz_file_encoder_worker.h>
|
||||
#include <lib/subghz/protocols/registry.h>
|
||||
#include <lib/subghz/protocols/protocol_items.h>
|
||||
#include <flipper_format/flipper_format_i.h>
|
||||
|
||||
#define TAG "SubGhz TEST"
|
||||
@@ -43,6 +43,8 @@ static void subghz_test_init(void) {
|
||||
environment_handler, CAME_ATOMO_DIR_NAME);
|
||||
subghz_environment_set_nice_flor_s_rainbow_table_file_name(
|
||||
environment_handler, NICE_FLOR_S_DIR_NAME);
|
||||
subghz_environment_set_protocol_registry(
|
||||
environment_handler, (void*)&subghz_protocol_registry);
|
||||
|
||||
receiver_handler = subghz_receiver_alloc_init(environment_handler);
|
||||
subghz_receiver_set_filter(receiver_handler, SubGhzProtocolFlag_Decodable);
|
||||
@@ -413,11 +415,11 @@ MU_TEST(subghz_decoder_honeywell_wdb_test) {
|
||||
"Test decoder " SUBGHZ_PROTOCOL_HONEYWELL_WDB_NAME " error\r\n");
|
||||
}
|
||||
|
||||
MU_TEST(subghz_decoder_magellen_test) {
|
||||
MU_TEST(subghz_decoder_magellan_test) {
|
||||
mu_assert(
|
||||
subghz_decoder_test(
|
||||
EXT_PATH("unit_tests/subghz/magellen_raw.sub"), SUBGHZ_PROTOCOL_MAGELLEN_NAME),
|
||||
"Test decoder " SUBGHZ_PROTOCOL_MAGELLEN_NAME " error\r\n");
|
||||
EXT_PATH("unit_tests/subghz/magellan_raw.sub"), SUBGHZ_PROTOCOL_MAGELLAN_NAME),
|
||||
"Test decoder " SUBGHZ_PROTOCOL_MAGELLAN_NAME " error\r\n");
|
||||
}
|
||||
|
||||
MU_TEST(subghz_decoder_intertechno_v3_test) {
|
||||
@@ -545,10 +547,10 @@ MU_TEST(subghz_encoder_honeywell_wdb_test) {
|
||||
"Test encoder " SUBGHZ_PROTOCOL_HONEYWELL_WDB_NAME " error\r\n");
|
||||
}
|
||||
|
||||
MU_TEST(subghz_encoder_magellen_test) {
|
||||
MU_TEST(subghz_encoder_magellan_test) {
|
||||
mu_assert(
|
||||
subghz_encoder_test(EXT_PATH("unit_tests/subghz/magellen.sub")),
|
||||
"Test encoder " SUBGHZ_PROTOCOL_MAGELLEN_NAME " error\r\n");
|
||||
subghz_encoder_test(EXT_PATH("unit_tests/subghz/magellan.sub")),
|
||||
"Test encoder " SUBGHZ_PROTOCOL_MAGELLAN_NAME " error\r\n");
|
||||
}
|
||||
|
||||
MU_TEST(subghz_encoder_intertechno_v3_test) {
|
||||
@@ -600,7 +602,7 @@ MU_TEST_SUITE(subghz) {
|
||||
MU_RUN_TEST(subghz_decoder_doitrand_test);
|
||||
MU_RUN_TEST(subghz_decoder_phoenix_v2_test);
|
||||
MU_RUN_TEST(subghz_decoder_honeywell_wdb_test);
|
||||
MU_RUN_TEST(subghz_decoder_magellen_test);
|
||||
MU_RUN_TEST(subghz_decoder_magellan_test);
|
||||
MU_RUN_TEST(subghz_decoder_intertechno_v3_test);
|
||||
MU_RUN_TEST(subghz_decoder_clemsa_test);
|
||||
MU_RUN_TEST(subghz_decoder_oregon2_test);
|
||||
@@ -622,7 +624,7 @@ MU_TEST_SUITE(subghz) {
|
||||
MU_RUN_TEST(subghz_encoder_doitrand_test);
|
||||
MU_RUN_TEST(subghz_encoder_phoenix_v2_test);
|
||||
MU_RUN_TEST(subghz_encoder_honeywell_wdb_test);
|
||||
MU_RUN_TEST(subghz_encoder_magellen_test);
|
||||
MU_RUN_TEST(subghz_encoder_magellan_test);
|
||||
MU_RUN_TEST(subghz_encoder_intertechno_v3_test);
|
||||
MU_RUN_TEST(subghz_encoder_clemsa_test);
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ struct GpioApp {
|
||||
Widget* widget;
|
||||
|
||||
VariableItemList* var_item_list;
|
||||
VariableItem* var_item_flow;
|
||||
GpioTest* gpio_test;
|
||||
GpioUsbUart* gpio_usb_uart;
|
||||
UsbUartBridge* usb_uart_bridge;
|
||||
|
||||
@@ -13,7 +13,7 @@ static UsbUartConfig* cfg_set;
|
||||
|
||||
static const char* vcp_ch[] = {"0 (CLI)", "1"};
|
||||
static const char* uart_ch[] = {"13,14", "15,16"};
|
||||
static const char* flow_pins[] = {"None", "2,3", "6,7"};
|
||||
static const char* flow_pins[] = {"None", "2,3", "6,7", "16,15"};
|
||||
static const char* baudrate_mode[] = {"Host"};
|
||||
static const uint32_t baudrate_list[] = {
|
||||
2400,
|
||||
@@ -33,6 +33,24 @@ bool gpio_scene_usb_uart_cfg_on_event(void* context, SceneManagerEvent event) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void line_ensure_flow_invariant(GpioApp* app) {
|
||||
// GPIO pins PC0, PC1 (16,15) are unavailable for RTS/DTR when LPUART is
|
||||
// selected. This function enforces that invariant by resetting flow_pins
|
||||
// to None if it is configured to 16,15 when LPUART is selected.
|
||||
|
||||
uint8_t available_flow_pins = cfg_set->uart_ch == FuriHalUartIdLPUART1 ? 3 : 4;
|
||||
VariableItem* item = app->var_item_flow;
|
||||
variable_item_set_values_count(item, available_flow_pins);
|
||||
|
||||
if(cfg_set->flow_pins >= available_flow_pins) {
|
||||
cfg_set->flow_pins = 0;
|
||||
usb_uart_set_config(app->usb_uart_bridge, cfg_set);
|
||||
|
||||
variable_item_set_current_value_index(item, cfg_set->flow_pins);
|
||||
variable_item_set_current_value_text(item, flow_pins[cfg_set->flow_pins]);
|
||||
}
|
||||
}
|
||||
|
||||
static void line_vcp_cb(VariableItem* item) {
|
||||
GpioApp* app = variable_item_get_context(item);
|
||||
uint8_t index = variable_item_get_current_value_index(item);
|
||||
@@ -54,6 +72,7 @@ static void line_port_cb(VariableItem* item) {
|
||||
else if(index == 1)
|
||||
cfg_set->uart_ch = FuriHalUartIdLPUART1;
|
||||
usb_uart_set_config(app->usb_uart_bridge, cfg_set);
|
||||
line_ensure_flow_invariant(app);
|
||||
}
|
||||
|
||||
static void line_flow_cb(VariableItem* item) {
|
||||
@@ -116,9 +135,12 @@ void gpio_scene_usb_uart_cfg_on_enter(void* context) {
|
||||
variable_item_set_current_value_index(item, cfg_set->uart_ch);
|
||||
variable_item_set_current_value_text(item, uart_ch[cfg_set->uart_ch]);
|
||||
|
||||
item = variable_item_list_add(var_item_list, "RTS/DTR Pins", 3, line_flow_cb, app);
|
||||
item = variable_item_list_add(
|
||||
var_item_list, "RTS/DTR Pins", COUNT_OF(flow_pins), line_flow_cb, app);
|
||||
variable_item_set_current_value_index(item, cfg_set->flow_pins);
|
||||
variable_item_set_current_value_text(item, flow_pins[cfg_set->flow_pins]);
|
||||
app->var_item_flow = item;
|
||||
line_ensure_flow_invariant(app);
|
||||
|
||||
variable_item_list_set_selected_item(
|
||||
var_item_list, scene_manager_get_scene_state(app->scene_manager, GpioAppViewUsbUartCfg));
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
static const GpioPin* flow_pins[][2] = {
|
||||
{&gpio_ext_pa7, &gpio_ext_pa6}, // 2, 3
|
||||
{&gpio_ext_pb2, &gpio_ext_pc3}, // 6, 7
|
||||
{&gpio_ext_pc0, &gpio_ext_pc1}, // 16, 15
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
|
||||
@@ -71,25 +71,9 @@ static void signal_received_callback(void* context, InfraredWorkerSignal* receiv
|
||||
}
|
||||
}
|
||||
|
||||
static void infrared_cli_start_ir_rx(Cli* cli, FuriString* args) {
|
||||
UNUSED(cli);
|
||||
UNUSED(args);
|
||||
InfraredWorker* worker = infrared_worker_alloc();
|
||||
infrared_worker_rx_start(worker);
|
||||
infrared_worker_rx_set_received_signal_callback(worker, signal_received_callback, cli);
|
||||
|
||||
printf("Receiving INFRARED...\r\nPress Ctrl+C to abort\r\n");
|
||||
while(!cli_cmd_interrupt_received(cli)) {
|
||||
furi_delay_ms(50);
|
||||
}
|
||||
|
||||
infrared_worker_rx_stop(worker);
|
||||
infrared_worker_free(worker);
|
||||
}
|
||||
|
||||
static void infrared_cli_print_usage(void) {
|
||||
printf("Usage:\r\n");
|
||||
printf("\tir rx\r\n");
|
||||
printf("\tir rx [raw]\r\n");
|
||||
printf("\tir tx <protocol> <address> <command>\r\n");
|
||||
printf("\t<command> and <address> are hex-formatted\r\n");
|
||||
printf("\tAvailable protocols:");
|
||||
@@ -108,6 +92,35 @@ static void infrared_cli_print_usage(void) {
|
||||
printf("\tir universal list <tv, ac>\r\n");
|
||||
}
|
||||
|
||||
static void infrared_cli_start_ir_rx(Cli* cli, FuriString* args) {
|
||||
UNUSED(cli);
|
||||
|
||||
bool enable_decoding = true;
|
||||
|
||||
if(!furi_string_empty(args)) {
|
||||
if(!furi_string_cmp_str(args, "raw")) {
|
||||
enable_decoding = false;
|
||||
} else {
|
||||
printf("Wrong arguments.\r\n");
|
||||
infrared_cli_print_usage();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
InfraredWorker* worker = infrared_worker_alloc();
|
||||
infrared_worker_rx_enable_signal_decoding(worker, enable_decoding);
|
||||
infrared_worker_rx_start(worker);
|
||||
infrared_worker_rx_set_received_signal_callback(worker, signal_received_callback, cli);
|
||||
|
||||
printf("Receiving %s INFRARED...\r\nPress Ctrl+C to abort\r\n", enable_decoding ? "" : "RAW");
|
||||
while(!cli_cmd_interrupt_received(cli)) {
|
||||
furi_delay_ms(50);
|
||||
}
|
||||
|
||||
infrared_worker_rx_stop(worker);
|
||||
infrared_worker_free(worker);
|
||||
}
|
||||
|
||||
static bool infrared_cli_parse_message(const char* str, InfraredSignal* signal) {
|
||||
char protocol_name[32];
|
||||
InfraredMessage message;
|
||||
|
||||
@@ -72,18 +72,7 @@ typedef enum {
|
||||
SubGhzViewIdTestPacket,
|
||||
} SubGhzViewId;
|
||||
|
||||
struct SubGhzPresetDefinition {
|
||||
FuriString* name;
|
||||
uint32_t frequency;
|
||||
uint8_t* data;
|
||||
size_t data_size;
|
||||
};
|
||||
|
||||
typedef struct SubGhzPresetDefinition SubGhzPresetDefinition;
|
||||
|
||||
typedef enum {
|
||||
SubGhzViewReceiverModeLive,
|
||||
SubGhzViewReceiverModeFile,
|
||||
} SubGhzViewReceiverMode;
|
||||
|
||||
#define SUBGHZ_HISTORY_REMOVE_SAVED_ITEMS 1
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#include <dolphin/dolphin.h>
|
||||
#include <flipper_format/flipper_format_i.h>
|
||||
#include <lib/toolbox/stream/stream.h>
|
||||
#include <lib/subghz/protocols/registry.h>
|
||||
#include <lib/subghz/protocols/protocol_items.h>
|
||||
|
||||
#define TAG "SubGhzSetType"
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <subghz/types.h>
|
||||
#include <lib/toolbox/path.h>
|
||||
#include "subghz_i.h"
|
||||
#include <lib/subghz/protocols/protocol_items.h>
|
||||
|
||||
#define TAG "SubGhzApp"
|
||||
|
||||
@@ -243,7 +244,8 @@ SubGhz* subghz_alloc(bool alloc_for_tx_only) {
|
||||
subghz->txrx->environment, EXT_PATH("subghz/assets/came_atomo"));
|
||||
subghz_environment_set_nice_flor_s_rainbow_table_file_name(
|
||||
subghz->txrx->environment, EXT_PATH("subghz/assets/nice_flor_s"));
|
||||
|
||||
subghz_environment_set_protocol_registry(
|
||||
subghz->txrx->environment, (void*)&subghz_protocol_registry);
|
||||
subghz->txrx->receiver = subghz_receiver_alloc_init(subghz->txrx->environment);
|
||||
#ifdef SUBGHZ_SAVE_DETECT_RAW_SETTING
|
||||
subghz_last_settings_set_detect_raw_values(subghz);
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <lib/subghz/receiver.h>
|
||||
#include <lib/subghz/transmitter.h>
|
||||
#include <lib/subghz/subghz_file_encoder_worker.h>
|
||||
#include <lib/subghz/protocols/protocol_items.h>
|
||||
|
||||
#include "helpers/subghz_chat.h"
|
||||
|
||||
@@ -159,6 +160,7 @@ void subghz_cli_command_tx(Cli* cli, FuriString* args, void* context) {
|
||||
stream_write_cstring(stream, furi_string_get_cstr(flipper_format_string));
|
||||
|
||||
SubGhzEnvironment* environment = subghz_environment_alloc();
|
||||
subghz_environment_set_protocol_registry(environment, (void*)&subghz_protocol_registry);
|
||||
|
||||
SubGhzTransmitter* transmitter = subghz_transmitter_alloc_init(environment, "Princeton");
|
||||
subghz_transmitter_deserialize(transmitter, flipper_format);
|
||||
@@ -252,6 +254,7 @@ void subghz_cli_command_rx(Cli* cli, FuriString* args, void* context) {
|
||||
environment, EXT_PATH("subghz/assets/came_atomo"));
|
||||
subghz_environment_set_nice_flor_s_rainbow_table_file_name(
|
||||
environment, EXT_PATH("subghz/assets/nice_flor_s"));
|
||||
subghz_environment_set_protocol_registry(environment, (void*)&subghz_protocol_registry);
|
||||
|
||||
SubGhzReceiver* receiver = subghz_receiver_alloc_init(environment);
|
||||
subghz_receiver_set_filter(receiver, SubGhzProtocolFlag_Decodable);
|
||||
@@ -371,6 +374,7 @@ void subghz_cli_command_decode_raw(Cli* cli, FuriString* args, void* context) {
|
||||
environment, EXT_PATH("subghz/assets/came_atomo"));
|
||||
subghz_environment_set_nice_flor_s_rainbow_table_file_name(
|
||||
environment, EXT_PATH("subghz/assets/nice_flor_s"));
|
||||
subghz_environment_set_protocol_registry(environment, (void*)&subghz_protocol_registry);
|
||||
|
||||
SubGhzReceiver* receiver = subghz_receiver_alloc_init(environment);
|
||||
subghz_receiver_set_filter(receiver, SubGhzProtocolFlag_Decodable);
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <lib/flipper_format/flipper_format.h>
|
||||
#include "helpers/subghz_types.h"
|
||||
#include <lib/subghz/types.h>
|
||||
|
||||
typedef struct SubGhzHistory SubGhzHistory;
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "helpers/subghz_types.h"
|
||||
#include <lib/subghz/types.h>
|
||||
#include "subghz.h"
|
||||
#include "views/receiver.h"
|
||||
#include "views/transmitter.h"
|
||||
@@ -12,8 +13,6 @@
|
||||
#include "views/subghz_test_static.h"
|
||||
#include "views/subghz_test_packet.h"
|
||||
#endif
|
||||
// #include <furi.h>
|
||||
// #include <furi_hal.h>
|
||||
#include <gui/gui.h>
|
||||
#include <dialogs/dialogs.h>
|
||||
#include <gui/scene_manager.h>
|
||||
@@ -26,16 +25,13 @@
|
||||
#include <gui/modules/widget.h>
|
||||
|
||||
#include <subghz/scenes/subghz_scene.h>
|
||||
|
||||
#include <lib/subghz/subghz_worker.h>
|
||||
|
||||
#include <lib/subghz/subghz_file_encoder_worker.h>
|
||||
|
||||
#include <lib/subghz/subghz_setting.h>
|
||||
#include <lib/subghz/receiver.h>
|
||||
#include <lib/subghz/transmitter.h>
|
||||
|
||||
#include "subghz_history.h"
|
||||
#include "subghz_setting.h"
|
||||
#include "subghz_last_settings.h"
|
||||
|
||||
#include <gui/modules/variable_item_list.h>
|
||||
|
||||
@@ -217,7 +217,7 @@ void subghz_view_receiver_draw(Canvas* canvas, SubGhzViewReceiverModel* model) {
|
||||
} else {
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
}
|
||||
canvas_draw_icon(canvas, 1, 2 + i * FRAME_HEIGHT, ReceiverItemIcons[item_menu->type]);
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
#include <applications/main/subghz/subghz_i.h>
|
||||
|
||||
#include <lib/subghz/protocols/raw.h>
|
||||
#include <lib/subghz/protocols/registry.h>
|
||||
#include <lib/subghz/protocols/protocol_items.h>
|
||||
#include <lib/subghz/types.h>
|
||||
#include <lib/subghz/protocols/keeloq.h>
|
||||
#include <lib/subghz/protocols/star_line.h>
|
||||
@@ -602,10 +602,12 @@ void unirfremix_tx_stop(UniRFRemix* app) {
|
||||
subghz_transmitter_stop(app->tx_transmitter);
|
||||
|
||||
FURI_LOG_D(TAG, "Checking if protocol is dynamic");
|
||||
const SubGhzProtocol* registry =
|
||||
subghz_protocol_registry_get_by_name(furi_string_get_cstr(app->txpreset->protocol));
|
||||
FURI_LOG_D(TAG, "Protocol-TYPE %d", registry->type);
|
||||
if(registry && registry->type == SubGhzProtocolTypeDynamic) {
|
||||
const SubGhzProtocolRegistry* protocol_registry_items =
|
||||
subghz_environment_get_protocol_registry(app->environment);
|
||||
const SubGhzProtocol* proto = subghz_protocol_registry_get_by_name(
|
||||
protocol_registry_items, furi_string_get_cstr(app->txpreset->protocol));
|
||||
FURI_LOG_D(TAG, "Protocol-TYPE %d", proto->type);
|
||||
if(proto && proto->type == SubGhzProtocolTypeDynamic) {
|
||||
FURI_LOG_D(TAG, "Protocol is dynamic. Saving key");
|
||||
unirfremix_save_protocol_to_file(app->tx_fff_data, app->tx_file_path);
|
||||
|
||||
@@ -838,6 +840,7 @@ void unirfremix_subghz_alloc(UniRFRemix* app) {
|
||||
app->environment, EXT_PATH("subghz/assets/came_atomo"));
|
||||
subghz_environment_set_nice_flor_s_rainbow_table_file_name(
|
||||
app->environment, EXT_PATH("subghz/assets/nice_flor_s"));
|
||||
subghz_environment_set_protocol_registry(app->environment, (void*)&subghz_protocol_registry);
|
||||
|
||||
app->subghz_receiver = subghz_receiver_alloc_init(app->environment);
|
||||
}
|
||||
|
||||
11
applications/plugins/heap_defence_game/application.fam
Normal file
@@ -0,0 +1,11 @@
|
||||
App(
|
||||
appid="heap_defence",
|
||||
name="Heap Defence",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="heap_defence_app",
|
||||
requires=["gui"],
|
||||
stack_size=1 * 1024,
|
||||
fap_category="Games",
|
||||
fap_icon="box.png",
|
||||
fap_icon_assets="assets_images",
|
||||
)
|
||||
|
After Width: | Height: | Size: 872 B |
|
After Width: | Height: | Size: 489 B |
|
After Width: | Height: | Size: 448 B |
|
After Width: | Height: | Size: 459 B |
|
After Width: | Height: | Size: 463 B |
|
After Width: | Height: | Size: 475 B |
|
After Width: | Height: | Size: 471 B |
|
After Width: | Height: | Size: 470 B |
|
After Width: | Height: | Size: 465 B |
|
After Width: | Height: | Size: 683 B |
|
After Width: | Height: | Size: 683 B |
|
After Width: | Height: | Size: 680 B |
|
After Width: | Height: | Size: 671 B |
|
After Width: | Height: | Size: 667 B |
|
After Width: | Height: | Size: 670 B |
|
After Width: | Height: | Size: 669 B |
|
After Width: | Height: | Size: 677 B |
@@ -0,0 +1 @@
|
||||
4
|
||||
|
After Width: | Height: | Size: 8.7 KiB |
|
After Width: | Height: | Size: 8.9 KiB |
@@ -0,0 +1 @@
|
||||
2
|
||||
|
After Width: | Height: | Size: 475 B |
|
After Width: | Height: | Size: 503 B |
@@ -0,0 +1 @@
|
||||
2
|
||||
|
After Width: | Height: | Size: 214 B |
|
After Width: | Height: | Size: 216 B |
|
After Width: | Height: | Size: 221 B |
|
After Width: | Height: | Size: 216 B |
@@ -0,0 +1 @@
|
||||
2
|
||||
|
After Width: | Height: | Size: 217 B |
|
After Width: | Height: | Size: 218 B |
|
After Width: | Height: | Size: 216 B |
|
After Width: | Height: | Size: 218 B |
@@ -0,0 +1 @@
|
||||
2
|
||||
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
@@ -0,0 +1 @@
|
||||
3
|
||||
|
After Width: | Height: | Size: 8.7 KiB |
|
After Width: | Height: | Size: 8.9 KiB |
|
After Width: | Height: | Size: 475 B |
|
After Width: | Height: | Size: 503 B |
|
After Width: | Height: | Size: 3.8 KiB |
BIN
applications/plugins/heap_defence_game/box.png
Normal file
|
After Width: | Height: | Size: 131 B |
593
applications/plugins/heap_defence_game/heap_defence.c
Normal file
@@ -0,0 +1,593 @@
|
||||
//
|
||||
// Created by moh on 30.11.2021.
|
||||
//
|
||||
// Ported to latest firmware by @xMasterX - 18 Oct 2022
|
||||
//
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "hede_assets.h"
|
||||
#include "heap_defence_icons.h"
|
||||
|
||||
#include <furi.h>
|
||||
#include <gui/gui.h>
|
||||
#include <input/input.h>
|
||||
#include <notification/notification.h>
|
||||
#include <notification/notification_messages.h>
|
||||
#include <gui/canvas_i.h>
|
||||
|
||||
#define Y_FIELD_SIZE 6
|
||||
#define Y_LAST (Y_FIELD_SIZE - 1)
|
||||
#define X_FIELD_SIZE 12
|
||||
#define X_LAST (X_FIELD_SIZE - 1)
|
||||
|
||||
#define DRAW_X_OFFSET 4
|
||||
|
||||
#define TAG "HeDe"
|
||||
|
||||
#define BOX_HEIGHT 10
|
||||
#define BOX_WIDTH 10
|
||||
#define TIMER_UPDATE_FREQ 8
|
||||
#define BOX_GENERATION_RATE 15
|
||||
|
||||
static IconAnimation* BOX_DESTROYED;
|
||||
static const Icon* boxes[] = {
|
||||
(Icon*)&A_HD_BoxDestroyed_10x10,
|
||||
&I_Box1_10x10,
|
||||
&I_Box2_10x10,
|
||||
&I_Box3_10x10,
|
||||
&I_Box4_10x10,
|
||||
&I_Box5_10x10};
|
||||
|
||||
static uint8_t BOX_TEXTURE_COUNT = sizeof(boxes) / sizeof(Icon*);
|
||||
|
||||
typedef enum {
|
||||
AnimationGameOver = 0,
|
||||
AnimationPause,
|
||||
AnimationLeft,
|
||||
AnimationRight,
|
||||
} Animations;
|
||||
|
||||
static IconAnimation* animations[4];
|
||||
|
||||
typedef u_int8_t byte;
|
||||
|
||||
typedef enum {
|
||||
GameStatusVibro = 1 << 0,
|
||||
GameStatusInProgress = 1 << 1,
|
||||
} GameStatuses;
|
||||
|
||||
typedef struct {
|
||||
uint8_t x;
|
||||
uint8_t y;
|
||||
} Position;
|
||||
|
||||
typedef enum { PlayerRising = 1, PlayerFalling = -1, PlayerNothing = 0 } PlayerStates;
|
||||
|
||||
typedef struct {
|
||||
Position p;
|
||||
int8_t x_direction;
|
||||
int8_t j_tick;
|
||||
int8_t h_tick;
|
||||
int8_t states;
|
||||
bool right_frame;
|
||||
} Person;
|
||||
|
||||
typedef struct {
|
||||
uint8_t offset : 4;
|
||||
uint8_t box_id : 3;
|
||||
uint8_t exists : 1;
|
||||
} Box;
|
||||
|
||||
static const uint8_t ROW_BYTE_SIZE = sizeof(Box) * X_FIELD_SIZE;
|
||||
|
||||
typedef struct {
|
||||
Box** field;
|
||||
Person* person;
|
||||
Animations animation;
|
||||
GameStatuses game_status;
|
||||
} GameState;
|
||||
|
||||
typedef Box** Field;
|
||||
|
||||
typedef enum { EventGameTick, EventKeyPress } EventType;
|
||||
|
||||
typedef struct {
|
||||
EventType type;
|
||||
InputEvent input;
|
||||
} GameEvent;
|
||||
|
||||
/**
|
||||
* #Construct / Destroy
|
||||
*/
|
||||
|
||||
static void game_reset_field_and_player(GameState* game) {
|
||||
///Reset field
|
||||
bzero(game->field[0], X_FIELD_SIZE * Y_FIELD_SIZE * sizeof(Box));
|
||||
|
||||
///Reset person
|
||||
bzero(game->person, sizeof(Person));
|
||||
game->person->p.x = X_FIELD_SIZE / 2;
|
||||
game->person->p.y = Y_LAST;
|
||||
}
|
||||
|
||||
static GameState* allocGameState() {
|
||||
GameState* game = malloc(sizeof(GameState));
|
||||
|
||||
game->person = malloc(sizeof(Person));
|
||||
|
||||
game->field = malloc(Y_FIELD_SIZE * sizeof(Box*));
|
||||
game->field[0] = malloc(X_FIELD_SIZE * Y_FIELD_SIZE * sizeof(Box));
|
||||
for(int y = 1; y < Y_FIELD_SIZE; ++y) {
|
||||
game->field[y] = game->field[0] + (y * X_FIELD_SIZE);
|
||||
}
|
||||
game_reset_field_and_player(game);
|
||||
|
||||
game->game_status = GameStatusInProgress;
|
||||
return game;
|
||||
}
|
||||
|
||||
static void game_destroy(GameState* game) {
|
||||
furi_assert(game);
|
||||
free(game->field[0]);
|
||||
free(game->field);
|
||||
free(game);
|
||||
}
|
||||
|
||||
static void assets_load() {
|
||||
/// Init animations
|
||||
animations[AnimationPause] = icon_animation_alloc(&A_HD_start_128x64);
|
||||
animations[AnimationGameOver] = icon_animation_alloc(&A_HD_game_over_128x64);
|
||||
animations[AnimationLeft] = icon_animation_alloc(&A_HD_person_left_10x20);
|
||||
animations[AnimationRight] = icon_animation_alloc(&A_HD_person_right_10x20);
|
||||
|
||||
BOX_DESTROYED = icon_animation_alloc(&A_HD_BoxDestroyed_10x10);
|
||||
|
||||
icon_animation_start(animations[AnimationLeft]);
|
||||
icon_animation_start(animations[AnimationRight]);
|
||||
}
|
||||
|
||||
static void assets_clear() {
|
||||
for(int i = 0; i < 4; ++i) {
|
||||
icon_animation_stop(animations[i]);
|
||||
icon_animation_free(animations[i]);
|
||||
}
|
||||
icon_animation_free(BOX_DESTROYED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Box utils
|
||||
*/
|
||||
|
||||
static inline bool is_empty(Box* box) {
|
||||
return !box->exists;
|
||||
}
|
||||
|
||||
static inline bool has_dropped(Box* box) {
|
||||
return box->offset == 0;
|
||||
}
|
||||
|
||||
static Box* get_upper_box(Field field, Position current) {
|
||||
return (&field[current.y - 1][current.x]);
|
||||
}
|
||||
|
||||
static Box* get_lower_box(Field field, Position current) {
|
||||
return (&field[current.y + 1][current.x]);
|
||||
}
|
||||
|
||||
static Box* get_next_box(Field field, Position current, int x_direction) {
|
||||
return (&field[current.y][current.x + x_direction]);
|
||||
}
|
||||
|
||||
static inline void decrement_y_offset_to_zero(Box* n) {
|
||||
if(n->offset) --n->offset;
|
||||
}
|
||||
|
||||
static inline void heap_swap(Box* first, Box* second) {
|
||||
Box temp = *first;
|
||||
|
||||
*first = *second;
|
||||
*second = temp;
|
||||
}
|
||||
|
||||
/**
|
||||
* #Box logic
|
||||
*/
|
||||
|
||||
static void generate_box(GameState const* game) {
|
||||
furi_assert(game);
|
||||
|
||||
static byte tick_count = BOX_GENERATION_RATE;
|
||||
if(tick_count++ != BOX_GENERATION_RATE) {
|
||||
return;
|
||||
}
|
||||
tick_count = 0;
|
||||
|
||||
int x_offset = rand() % X_FIELD_SIZE;
|
||||
while(game->field[1][x_offset].exists) {
|
||||
x_offset = rand() % X_FIELD_SIZE;
|
||||
}
|
||||
|
||||
game->field[1][x_offset].exists = true;
|
||||
game->field[1][x_offset].offset = BOX_HEIGHT;
|
||||
game->field[1][x_offset].box_id = (rand() % (BOX_TEXTURE_COUNT - 1)) + 1;
|
||||
}
|
||||
|
||||
static void drop_box(GameState* game) {
|
||||
furi_assert(game);
|
||||
|
||||
for(int y = Y_LAST; y > 0; y--) {
|
||||
for(int x = 0; x < X_FIELD_SIZE; x++) {
|
||||
Box* current_box = game->field[y] + x;
|
||||
Box* upper_box = game->field[y - 1] + x;
|
||||
|
||||
if(y == Y_LAST) {
|
||||
decrement_y_offset_to_zero(current_box);
|
||||
}
|
||||
|
||||
decrement_y_offset_to_zero(upper_box);
|
||||
|
||||
if(is_empty(current_box) && !is_empty(upper_box) && has_dropped(upper_box)) {
|
||||
upper_box->offset = BOX_HEIGHT;
|
||||
heap_swap(current_box, upper_box);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool clear_rows(Box** field) {
|
||||
for(int x = 0; x < X_FIELD_SIZE; ++x) {
|
||||
if(is_empty(field[Y_LAST] + x) || !has_dropped(field[Y_LAST] + x)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
memset(field[Y_LAST], 128, ROW_BYTE_SIZE);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Input Handling
|
||||
*/
|
||||
|
||||
static inline bool on_ground(Person* person, Field field) {
|
||||
return person->p.y == Y_LAST || field[person->p.y + 1][person->p.x].exists;
|
||||
}
|
||||
|
||||
static void handle_key_presses(Person* person, InputEvent* input, GameState* game) {
|
||||
switch(input->key) {
|
||||
case InputKeyUp:
|
||||
if(person->states == PlayerNothing && on_ground(person, game->field)) {
|
||||
person->states = PlayerRising;
|
||||
person->j_tick = 0;
|
||||
}
|
||||
break;
|
||||
case InputKeyLeft:
|
||||
person->right_frame = false;
|
||||
if(person->h_tick == 0) {
|
||||
person->h_tick = 1;
|
||||
person->x_direction = -1;
|
||||
}
|
||||
break;
|
||||
case InputKeyRight:
|
||||
person->right_frame = true;
|
||||
if(person->h_tick == 0) {
|
||||
person->h_tick = 1;
|
||||
person->x_direction = 1;
|
||||
}
|
||||
break;
|
||||
case InputKeyOk:
|
||||
game->game_status &= ~GameStatusInProgress;
|
||||
game->animation = AnimationPause;
|
||||
icon_animation_start(animations[AnimationPause]);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* #Person logic
|
||||
*/
|
||||
|
||||
static inline bool ground_box_check(Field field, Position new_position) {
|
||||
Box* lower_box = get_lower_box(field, new_position);
|
||||
|
||||
bool ground_box_dropped =
|
||||
(new_position.y == Y_LAST || //Eсли мы и так в самом низу
|
||||
is_empty(lower_box) || // Ecли снизу пустота
|
||||
has_dropped(lower_box)); //Eсли бокс снизу допадал
|
||||
return ground_box_dropped;
|
||||
}
|
||||
|
||||
static inline bool is_movable(Field field, Position box_pos, int x_direction) {
|
||||
//TODO::Moжет и не двух, предположение
|
||||
bool out_of_bounds = box_pos.x == 0 || box_pos.x == X_LAST;
|
||||
if(out_of_bounds) return false;
|
||||
bool box_on_top = box_pos.y < 1 || get_upper_box(field, box_pos)->exists;
|
||||
if(box_on_top) return false;
|
||||
bool has_next_box = get_next_box(field, box_pos, x_direction)->exists;
|
||||
if(has_next_box) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool horizontal_move(Person* person, Field field) {
|
||||
Position new_position = person->p;
|
||||
|
||||
if(!person->x_direction) return false;
|
||||
|
||||
new_position.x += person->x_direction;
|
||||
|
||||
bool on_edge_column = new_position.x > X_LAST;
|
||||
if(on_edge_column) return false;
|
||||
|
||||
if(is_empty(&field[new_position.y][new_position.x])) {
|
||||
bool ground_box_dropped = ground_box_check(field, new_position);
|
||||
if(ground_box_dropped) {
|
||||
person->p = new_position;
|
||||
return true;
|
||||
}
|
||||
} else if(is_movable(field, new_position, person->x_direction)) {
|
||||
*get_next_box(field, new_position, person->x_direction) =
|
||||
field[new_position.y][new_position.x];
|
||||
|
||||
field[new_position.y][new_position.x] = (Box){0};
|
||||
person->p = new_position;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void hd_person_set_state(Person* person, PlayerStates state) {
|
||||
person->states = state;
|
||||
person->j_tick = 0;
|
||||
}
|
||||
|
||||
static void person_move(Person* person, Field field) {
|
||||
/// Left-right logic
|
||||
FURI_LOG_W(TAG, "[JUMP]func:[%s] line: %d", __FUNCTION__, __LINE__);
|
||||
|
||||
if(person->states == PlayerNothing) {
|
||||
if(!on_ground(person, field)) {
|
||||
hd_person_set_state(person, PlayerFalling);
|
||||
}
|
||||
} else if(person->states == PlayerRising) {
|
||||
if(person->j_tick++ == 0) {
|
||||
person->p.y--;
|
||||
} else if(person->j_tick == 6) {
|
||||
hd_person_set_state(person, PlayerNothing);
|
||||
}
|
||||
|
||||
/// Destroy upper box
|
||||
get_upper_box(field, person->p)->box_id = 0;
|
||||
field[person->p.y][person->p.x].box_id = 0;
|
||||
|
||||
} else if(person->states == PlayerFalling) {
|
||||
if(person->j_tick++ == 0) {
|
||||
if(on_ground(person, field)) { // TODO: Test the bugfix
|
||||
hd_person_set_state(person, PlayerNothing);
|
||||
} else {
|
||||
person->p.y++;
|
||||
}
|
||||
} else if(person->j_tick == 5) {
|
||||
if(on_ground(person, field)) {
|
||||
hd_person_set_state(person, PlayerNothing);
|
||||
} else {
|
||||
hd_person_set_state(person, PlayerFalling);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch(person->h_tick) {
|
||||
case 0:
|
||||
break;
|
||||
case 1:
|
||||
person->h_tick++;
|
||||
FURI_LOG_W(TAG, "[JUMP]func:[%s] line: %d", __FUNCTION__, __LINE__);
|
||||
bool moved = horizontal_move(person, field);
|
||||
if(!moved) {
|
||||
person->h_tick = 0;
|
||||
person->x_direction = 0;
|
||||
}
|
||||
break;
|
||||
case 5:
|
||||
FURI_LOG_W(TAG, "[JUMP]func:[%s] line: %d", __FUNCTION__, __LINE__);
|
||||
person->h_tick = 0;
|
||||
person->x_direction = 0;
|
||||
break;
|
||||
default:
|
||||
FURI_LOG_W(TAG, "[JUMP]func:[%s] line: %d", __FUNCTION__, __LINE__);
|
||||
person->h_tick++;
|
||||
}
|
||||
}
|
||||
|
||||
static inline bool is_person_dead(Person* person, Box** field) {
|
||||
return get_upper_box(field, person->p)->box_id != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* #Callback
|
||||
*/
|
||||
|
||||
static void draw_box(Canvas* canvas, Box* box, int x, int y) {
|
||||
if(is_empty(box)) {
|
||||
return;
|
||||
}
|
||||
byte y_screen = y * BOX_HEIGHT - box->offset;
|
||||
byte x_screen = x * BOX_WIDTH + DRAW_X_OFFSET;
|
||||
|
||||
if(box->box_id == 0) {
|
||||
canvas_set_bitmap_mode(canvas, true);
|
||||
icon_animation_start(BOX_DESTROYED);
|
||||
canvas_draw_icon_animation(canvas, x_screen, y_screen, BOX_DESTROYED);
|
||||
if(icon_animation_is_last_frame(BOX_DESTROYED)) {
|
||||
*box = (Box){0};
|
||||
icon_animation_stop(BOX_DESTROYED);
|
||||
}
|
||||
canvas_set_bitmap_mode(canvas, false);
|
||||
} else {
|
||||
canvas_draw_icon(canvas, x_screen, y_screen, boxes[box->box_id]);
|
||||
}
|
||||
}
|
||||
|
||||
static void heap_defense_render_callback(Canvas* const canvas, void* mutex) {
|
||||
furi_assert(mutex);
|
||||
|
||||
const GameState* game = acquire_mutex((ValueMutex*)mutex, 25);
|
||||
|
||||
///Draw GameOver or Pause
|
||||
if(!(game->game_status & GameStatusInProgress)) {
|
||||
FURI_LOG_W(TAG, "[DAED_DRAW]func: [%s] line: %d ", __FUNCTION__, __LINE__);
|
||||
|
||||
canvas_draw_icon_animation(canvas, 0, 0, animations[game->animation]);
|
||||
release_mutex((ValueMutex*)mutex, game);
|
||||
return;
|
||||
}
|
||||
|
||||
///Draw field
|
||||
canvas_draw_icon(canvas, 0, 0, &I_Background_128x64);
|
||||
|
||||
///Draw Person
|
||||
const Person* person = game->person;
|
||||
IconAnimation* player_animation = person->right_frame ? animations[AnimationRight] :
|
||||
animations[AnimationLeft];
|
||||
|
||||
uint8_t x_screen = person->p.x * BOX_WIDTH + DRAW_X_OFFSET;
|
||||
if(person->h_tick && person->h_tick != 1) {
|
||||
if(person->right_frame) {
|
||||
x_screen += (person->h_tick) * 2 - BOX_WIDTH;
|
||||
} else {
|
||||
x_screen -= (person->h_tick) * 2 - BOX_WIDTH;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t y_screen = (person->p.y - 1) * BOX_HEIGHT;
|
||||
if(person->j_tick) {
|
||||
if(person->states == PlayerRising) {
|
||||
y_screen += BOX_HEIGHT - (person->j_tick) * 2;
|
||||
} else if(person->states == PlayerFalling) {
|
||||
y_screen -= BOX_HEIGHT - (person->j_tick) * 2;
|
||||
}
|
||||
}
|
||||
|
||||
canvas_draw_icon_animation(canvas, x_screen, y_screen, player_animation);
|
||||
|
||||
///Draw Boxes
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
for(int y = 1; y < Y_FIELD_SIZE; ++y) {
|
||||
for(int x = 0; x < X_FIELD_SIZE; ++x) {
|
||||
draw_box(canvas, &(game->field[y][x]), x, y);
|
||||
}
|
||||
}
|
||||
|
||||
release_mutex((ValueMutex*)mutex, game);
|
||||
}
|
||||
|
||||
static void heap_defense_input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
|
||||
if(input_event->type != InputTypePress && input_event->type != InputTypeLong) return;
|
||||
|
||||
furi_assert(event_queue);
|
||||
GameEvent event = {.type = EventKeyPress, .input = *input_event};
|
||||
furi_message_queue_put(event_queue, &event, FuriWaitForever);
|
||||
}
|
||||
|
||||
static void heap_defense_timer_callback(FuriMessageQueue* event_queue) {
|
||||
furi_assert(event_queue);
|
||||
|
||||
GameEvent event = {.type = EventGameTick, .input = {0}};
|
||||
furi_message_queue_put(event_queue, &event, 0);
|
||||
}
|
||||
|
||||
int32_t heap_defence_app(void* p) {
|
||||
UNUSED(p);
|
||||
srand(DWT->CYCCNT);
|
||||
|
||||
//FURI_LOG_W(TAG, "Heap defence start %d", __LINE__);
|
||||
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(GameEvent));
|
||||
GameState* game = allocGameState();
|
||||
|
||||
ValueMutex state_mutex;
|
||||
if(!init_mutex(&state_mutex, game, sizeof(GameState))) {
|
||||
game_destroy(game);
|
||||
return 1;
|
||||
}
|
||||
|
||||
assets_load();
|
||||
ViewPort* view_port = view_port_alloc();
|
||||
view_port_draw_callback_set(view_port, heap_defense_render_callback, &state_mutex);
|
||||
view_port_input_callback_set(view_port, heap_defense_input_callback, event_queue);
|
||||
|
||||
FuriTimer* timer =
|
||||
furi_timer_alloc(heap_defense_timer_callback, FuriTimerTypePeriodic, event_queue);
|
||||
furi_timer_start(timer, furi_kernel_get_tick_frequency() / TIMER_UPDATE_FREQ);
|
||||
|
||||
Gui* gui = furi_record_open("gui");
|
||||
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
|
||||
|
||||
NotificationApp* notification = furi_record_open("notification");
|
||||
|
||||
memset(game->field[Y_LAST], 128, ROW_BYTE_SIZE);
|
||||
game->person->p.y -= 2;
|
||||
game->game_status = 0;
|
||||
game->animation = AnimationPause;
|
||||
|
||||
GameEvent event = {0};
|
||||
while(event.input.key != InputKeyBack) {
|
||||
if(furi_message_queue_get(event_queue, &event, 100) != FuriStatusOk) {
|
||||
continue;
|
||||
}
|
||||
|
||||
game = (GameState*)acquire_mutex_block(&state_mutex);
|
||||
|
||||
//unset vibration
|
||||
if(game->game_status & GameStatusVibro) {
|
||||
notification_message(notification, &sequence_reset_vibro);
|
||||
game->game_status &= ~GameStatusVibro;
|
||||
icon_animation_stop(BOX_DESTROYED);
|
||||
memset(game->field[Y_LAST], 0, ROW_BYTE_SIZE);
|
||||
}
|
||||
|
||||
if(!(game->game_status & GameStatusInProgress)) {
|
||||
if(event.type == EventKeyPress && event.input.key == InputKeyOk) {
|
||||
game->game_status |= GameStatusInProgress;
|
||||
icon_animation_stop(animations[game->animation]);
|
||||
}
|
||||
|
||||
} else if(event.type == EventKeyPress) {
|
||||
handle_key_presses(game->person, &(event.input), game);
|
||||
} else { // EventGameTick
|
||||
|
||||
drop_box(game);
|
||||
generate_box(game);
|
||||
if(clear_rows(game->field)) {
|
||||
notification_message(notification, &sequence_set_vibro_on);
|
||||
icon_animation_start(BOX_DESTROYED);
|
||||
game->game_status |= GameStatusVibro;
|
||||
}
|
||||
person_move(game->person, game->field);
|
||||
|
||||
if(is_person_dead(game->person, game->field)) {
|
||||
game->game_status &= ~GameStatusInProgress;
|
||||
game->animation = AnimationGameOver;
|
||||
icon_animation_start(animations[AnimationGameOver]);
|
||||
game_reset_field_and_player(game);
|
||||
notification_message(notification, &sequence_error);
|
||||
}
|
||||
}
|
||||
release_mutex(&state_mutex, game);
|
||||
view_port_update(view_port);
|
||||
}
|
||||
|
||||
furi_timer_free(timer);
|
||||
view_port_enabled_set(view_port, false);
|
||||
gui_remove_view_port(gui, view_port);
|
||||
view_port_free(view_port);
|
||||
furi_record_close("gui");
|
||||
furi_record_close("notification");
|
||||
furi_message_queue_free(event_queue);
|
||||
assets_clear();
|
||||
delete_mutex(&state_mutex);
|
||||
game_destroy(game);
|
||||
|
||||
return 0;
|
||||
}
|
||||
11
applications/plugins/heap_defence_game/hede_assets.c
Normal file
@@ -0,0 +1,11 @@
|
||||
//
|
||||
// Created by user on 15.12.2021.
|
||||
//
|
||||
#include "hede_assets.h"
|
||||
#include <gui/icon_i.h>
|
||||
|
||||
const uint8_t _A_HD_BoxDestroyed_10x10_0[] = {0x01,0x00,0x10,0x00,0x00,0x1d,0xa2,0x01,0xc8,0x80,0x6d,0x20,0x15,0x08,0x06,0x72,0x01,0x48,0x07,0xa0,};
|
||||
const uint8_t _A_HD_BoxDestroyed_10x10_1[] = {0x00,0x00,0x00,0x28,0x01,0x4A,0x00,0xA8,0x01,0x84,0x00,0x22,0x00,0x88,0x00,0x58,0x01,0x22,0x00,0x00,0x00,};
|
||||
const uint8_t _A_HD_BoxDestroyed_10x10_2[] = {0x00,0x00,0x00,0x08,0x01,0x42,0x00,0x09,0x01,0x00,0x02,0x02,0x00,0x01,0x02,0x00,0x01,0x21,0x00,0x42,0x02,};
|
||||
const uint8_t *_A_HD_BoxDestroyed_10x10[] = {_A_HD_BoxDestroyed_10x10_0,_A_HD_BoxDestroyed_10x10_1,_A_HD_BoxDestroyed_10x10_2};
|
||||
const Icon A_HD_BoxDestroyed_10x10 = {.width=10,.height=10,.frame_count=3,.frame_rate=4,.frames=_A_HD_BoxDestroyed_10x10};
|
||||
11
applications/plugins/heap_defence_game/hede_assets.h
Normal file
@@ -0,0 +1,11 @@
|
||||
//
|
||||
// Created by user on 15.12.2021.
|
||||
//
|
||||
|
||||
#ifndef HEDE_ASSETS_H
|
||||
#define HEDE_ASSETS_H
|
||||
#include <gui/icon.h>
|
||||
|
||||
extern const Icon A_HD_BoxDestroyed_10x10;
|
||||
|
||||
#endif
|
||||
151
applications/plugins/picopass/helpers/iclass_elite_dict.c
Normal file
@@ -0,0 +1,151 @@
|
||||
#include "iclass_elite_dict.h"
|
||||
|
||||
#include <lib/toolbox/args.h>
|
||||
#include <lib/flipper_format/flipper_format.h>
|
||||
|
||||
#define ICLASS_ELITE_DICT_FLIPPER_PATH EXT_PATH("picopass/assets/iclass_elite_dict.txt")
|
||||
#define ICLASS_ELITE_DICT_USER_PATH EXT_PATH("picopass/assets/iclass_elite_dict_user.txt")
|
||||
|
||||
#define TAG "IclassEliteDict"
|
||||
|
||||
#define ICLASS_ELITE_KEY_LINE_LEN (17)
|
||||
#define ICLASS_ELITE_KEY_LEN (8)
|
||||
|
||||
struct IclassEliteDict {
|
||||
Stream* stream;
|
||||
uint32_t total_keys;
|
||||
};
|
||||
|
||||
bool iclass_elite_dict_check_presence(IclassEliteDictType dict_type) {
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
|
||||
bool dict_present = false;
|
||||
if(dict_type == IclassEliteDictTypeFlipper) {
|
||||
dict_present = storage_common_stat(storage, ICLASS_ELITE_DICT_FLIPPER_PATH, NULL) ==
|
||||
FSE_OK;
|
||||
} else if(dict_type == IclassEliteDictTypeUser) {
|
||||
dict_present = storage_common_stat(storage, ICLASS_ELITE_DICT_USER_PATH, NULL) == FSE_OK;
|
||||
}
|
||||
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
return dict_present;
|
||||
}
|
||||
|
||||
IclassEliteDict* iclass_elite_dict_alloc(IclassEliteDictType dict_type) {
|
||||
IclassEliteDict* dict = malloc(sizeof(IclassEliteDict));
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
dict->stream = buffered_file_stream_alloc(storage);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
FuriString* next_line = furi_string_alloc();
|
||||
|
||||
bool dict_loaded = false;
|
||||
do {
|
||||
if(dict_type == IclassEliteDictTypeFlipper) {
|
||||
if(!buffered_file_stream_open(
|
||||
dict->stream, ICLASS_ELITE_DICT_FLIPPER_PATH, FSAM_READ, FSOM_OPEN_EXISTING)) {
|
||||
buffered_file_stream_close(dict->stream);
|
||||
break;
|
||||
}
|
||||
} else if(dict_type == IclassEliteDictTypeUser) {
|
||||
if(!buffered_file_stream_open(
|
||||
dict->stream, ICLASS_ELITE_DICT_USER_PATH, FSAM_READ_WRITE, FSOM_OPEN_ALWAYS)) {
|
||||
buffered_file_stream_close(dict->stream);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Read total amount of keys
|
||||
while(true) {
|
||||
if(!stream_read_line(dict->stream, next_line)) break;
|
||||
if(furi_string_get_char(next_line, 0) == '#') continue;
|
||||
if(furi_string_size(next_line) != ICLASS_ELITE_KEY_LINE_LEN) continue;
|
||||
dict->total_keys++;
|
||||
}
|
||||
furi_string_reset(next_line);
|
||||
stream_rewind(dict->stream);
|
||||
|
||||
dict_loaded = true;
|
||||
FURI_LOG_I(TAG, "Loaded dictionary with %lu keys", dict->total_keys);
|
||||
} while(false);
|
||||
|
||||
if(!dict_loaded) {
|
||||
buffered_file_stream_close(dict->stream);
|
||||
free(dict);
|
||||
dict = NULL;
|
||||
}
|
||||
|
||||
furi_string_free(next_line);
|
||||
|
||||
return dict;
|
||||
}
|
||||
|
||||
void iclass_elite_dict_free(IclassEliteDict* dict) {
|
||||
furi_assert(dict);
|
||||
furi_assert(dict->stream);
|
||||
|
||||
buffered_file_stream_close(dict->stream);
|
||||
stream_free(dict->stream);
|
||||
free(dict);
|
||||
}
|
||||
|
||||
uint32_t iclass_elite_dict_get_total_keys(IclassEliteDict* dict) {
|
||||
furi_assert(dict);
|
||||
|
||||
return dict->total_keys;
|
||||
}
|
||||
|
||||
bool iclass_elite_dict_get_next_key(IclassEliteDict* dict, uint8_t* key) {
|
||||
furi_assert(dict);
|
||||
furi_assert(dict->stream);
|
||||
|
||||
uint8_t key_byte_tmp = 0;
|
||||
FuriString* next_line = furi_string_alloc();
|
||||
|
||||
bool key_read = false;
|
||||
*key = 0ULL;
|
||||
while(!key_read) {
|
||||
if(!stream_read_line(dict->stream, next_line)) break;
|
||||
if(furi_string_get_char(next_line, 0) == '#') continue;
|
||||
if(furi_string_size(next_line) != ICLASS_ELITE_KEY_LINE_LEN) continue;
|
||||
for(uint8_t i = 0; i < ICLASS_ELITE_KEY_LEN * 2; i += 2) {
|
||||
args_char_to_hex(
|
||||
furi_string_get_char(next_line, i),
|
||||
furi_string_get_char(next_line, i + 1),
|
||||
&key_byte_tmp);
|
||||
key[i / 2] = key_byte_tmp;
|
||||
}
|
||||
key_read = true;
|
||||
}
|
||||
|
||||
furi_string_free(next_line);
|
||||
return key_read;
|
||||
}
|
||||
|
||||
bool iclass_elite_dict_rewind(IclassEliteDict* dict) {
|
||||
furi_assert(dict);
|
||||
furi_assert(dict->stream);
|
||||
|
||||
return stream_rewind(dict->stream);
|
||||
}
|
||||
|
||||
bool iclass_elite_dict_add_key(IclassEliteDict* dict, uint8_t* key) {
|
||||
furi_assert(dict);
|
||||
furi_assert(dict->stream);
|
||||
|
||||
FuriString* key_str = furi_string_alloc();
|
||||
for(size_t i = 0; i < 6; i++) {
|
||||
furi_string_cat_printf(key_str, "%02X", key[i]);
|
||||
}
|
||||
furi_string_cat_printf(key_str, "\n");
|
||||
|
||||
bool key_added = false;
|
||||
do {
|
||||
if(!stream_seek(dict->stream, 0, StreamOffsetFromEnd)) break;
|
||||
if(!stream_insert_string(dict->stream, key_str)) break;
|
||||
key_added = true;
|
||||
} while(false);
|
||||
|
||||
furi_string_free(key_str);
|
||||
return key_added;
|
||||
}
|
||||
28
applications/plugins/picopass/helpers/iclass_elite_dict.h
Normal file
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <storage/storage.h>
|
||||
#include <lib/flipper_format/flipper_format.h>
|
||||
#include <lib/toolbox/stream/file_stream.h>
|
||||
#include <lib/toolbox/stream/buffered_file_stream.h>
|
||||
|
||||
typedef enum {
|
||||
IclassEliteDictTypeUser,
|
||||
IclassEliteDictTypeFlipper,
|
||||
} IclassEliteDictType;
|
||||
|
||||
typedef struct IclassEliteDict IclassEliteDict;
|
||||
|
||||
bool iclass_elite_dict_check_presence(IclassEliteDictType dict_type);
|
||||
|
||||
IclassEliteDict* iclass_elite_dict_alloc(IclassEliteDictType dict_type);
|
||||
|
||||
void iclass_elite_dict_free(IclassEliteDict* dict);
|
||||
|
||||
uint32_t iclass_elite_dict_get_total_keys(IclassEliteDict* dict);
|
||||
|
||||
bool iclass_elite_dict_get_next_key(IclassEliteDict* dict, uint8_t* key);
|
||||
|
||||
bool iclass_elite_dict_rewind(IclassEliteDict* dict);
|
||||
|
||||
bool iclass_elite_dict_add_key(IclassEliteDict* dict, uint8_t* key);
|
||||
@@ -185,7 +185,7 @@ static void loclass_desencrypt_iclass(uint8_t* iclass_key, uint8_t* input, uint8
|
||||
* @param loclass_hash1 loclass_hash1
|
||||
* @param key_sel output key_sel=h[loclass_hash1[i]]
|
||||
*/
|
||||
void hash2(uint8_t* key64, uint8_t* outp_keytable) {
|
||||
void loclass_hash2(uint8_t* key64, uint8_t* outp_keytable) {
|
||||
/**
|
||||
*Expected:
|
||||
* High Security Key Table
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "rfal_picopass.h"
|
||||
#include <optimized_ikeys.h>
|
||||
#include <optimized_cipher.h>
|
||||
#include "helpers/iclass_elite_dict.h"
|
||||
|
||||
#define PICOPASS_DEV_NAME_MAX_LEN 22
|
||||
#define PICOPASS_READER_DATA_MAX_SIZE 64
|
||||
@@ -49,6 +50,7 @@ typedef struct {
|
||||
bool se_enabled;
|
||||
bool sio;
|
||||
bool biometrics;
|
||||
uint8_t key[8];
|
||||
uint8_t pin_length;
|
||||
PicopassEncryption encryption;
|
||||
uint8_t credential[8];
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "picopass_worker_i.h"
|
||||
|
||||
#include <flipper_format/flipper_format.h>
|
||||
|
||||
#define TAG "PicopassWorker"
|
||||
|
||||
const uint8_t picopass_iclass_key[] = {0xaf, 0xa7, 0x85, 0xa7, 0xda, 0xb3, 0x33, 0x78};
|
||||
@@ -176,7 +178,7 @@ ReturnCode picopass_read_preauth(PicopassBlock* AA1) {
|
||||
return ERR_NONE;
|
||||
}
|
||||
|
||||
ReturnCode picopass_read_card(PicopassBlock* AA1) {
|
||||
ReturnCode picopass_auth(PicopassBlock* AA1, PicopassPacs* pacs) {
|
||||
rfalPicoPassReadCheckRes rcRes;
|
||||
rfalPicoPassCheckRes chkRes;
|
||||
|
||||
@@ -197,10 +199,68 @@ ReturnCode picopass_read_card(PicopassBlock* AA1) {
|
||||
loclass_opt_doReaderMAC(ccnr, div_key, mac);
|
||||
|
||||
err = rfalPicoPassPollerCheck(mac, &chkRes);
|
||||
if(err != ERR_NONE) {
|
||||
FURI_LOG_E(TAG, "rfalPicoPassPollerCheck error %d", err);
|
||||
return err;
|
||||
if(err == ERR_NONE) {
|
||||
return ERR_NONE;
|
||||
}
|
||||
FURI_LOG_E(TAG, "rfalPicoPassPollerCheck error %d", err);
|
||||
|
||||
FURI_LOG_E(TAG, "Starting dictionary attack");
|
||||
|
||||
size_t index = 0;
|
||||
uint8_t key[PICOPASS_BLOCK_LEN] = {0};
|
||||
|
||||
if(!iclass_elite_dict_check_presence(IclassEliteDictTypeFlipper)) {
|
||||
FURI_LOG_E(TAG, "Dictionary not found");
|
||||
return ERR_PARAM;
|
||||
}
|
||||
|
||||
IclassEliteDict* dict = iclass_elite_dict_alloc(IclassEliteDictTypeFlipper);
|
||||
if(!dict) {
|
||||
FURI_LOG_E(TAG, "Dictionary not allocated");
|
||||
return ERR_PARAM;
|
||||
}
|
||||
|
||||
FURI_LOG_D(TAG, "Loaded %lu keys", iclass_elite_dict_get_total_keys(dict));
|
||||
while(iclass_elite_dict_get_next_key(dict, key)) {
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
"Try to auth with key %d %02x%02x%02x%02x%02x%02x%02x%02x",
|
||||
index++,
|
||||
key[0],
|
||||
key[1],
|
||||
key[2],
|
||||
key[3],
|
||||
key[4],
|
||||
key[5],
|
||||
key[6],
|
||||
key[7]);
|
||||
|
||||
err = rfalPicoPassPollerReadCheck(&rcRes);
|
||||
if(err != ERR_NONE) {
|
||||
FURI_LOG_E(TAG, "rfalPicoPassPollerReadCheck error %d", err);
|
||||
return err;
|
||||
}
|
||||
memcpy(ccnr, rcRes.CCNR, sizeof(rcRes.CCNR)); // last 4 bytes left 0
|
||||
|
||||
loclass_iclass_calc_div_key(AA1[PICOPASS_CSN_BLOCK_INDEX].data, key, div_key, true);
|
||||
loclass_opt_doReaderMAC(ccnr, div_key, mac);
|
||||
|
||||
err = rfalPicoPassPollerCheck(mac, &chkRes);
|
||||
if(err == ERR_NONE) {
|
||||
memcpy(pacs->key, key, PICOPASS_BLOCK_LEN);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(dict) {
|
||||
iclass_elite_dict_free(dict);
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
ReturnCode picopass_read_card(PicopassBlock* AA1) {
|
||||
ReturnCode err;
|
||||
|
||||
size_t app_limit = AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[0] < PICOPASS_MAX_APP_LIMIT ?
|
||||
AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[0] :
|
||||
@@ -352,28 +412,39 @@ void picopass_worker_detect(PicopassWorker* picopass_worker) {
|
||||
pacs->se_enabled = (memcmp(AA1[5].data, "\xff\xff\xff\x00\x06\xff\xff\xff", 8) == 0);
|
||||
if(pacs->se_enabled) {
|
||||
FURI_LOG_D(TAG, "SE enabled");
|
||||
nextState = PicopassWorkerEventFail;
|
||||
}
|
||||
|
||||
err = picopass_read_card(AA1);
|
||||
if(err != ERR_NONE) {
|
||||
FURI_LOG_E(TAG, "picopass_read_card error %d", err);
|
||||
nextState = PicopassWorkerEventFail;
|
||||
if(nextState == PicopassWorkerEventSuccess) {
|
||||
err = picopass_auth(AA1, pacs);
|
||||
if(err != ERR_NONE) {
|
||||
FURI_LOG_E(TAG, "picopass_try_auth error %d", err);
|
||||
nextState = PicopassWorkerEventFail;
|
||||
}
|
||||
}
|
||||
|
||||
if(nextState == PicopassWorkerEventSuccess) {
|
||||
err = picopass_read_card(AA1);
|
||||
if(err != ERR_NONE) {
|
||||
FURI_LOG_E(TAG, "picopass_read_card error %d", err);
|
||||
nextState = PicopassWorkerEventFail;
|
||||
}
|
||||
}
|
||||
|
||||
if(nextState == PicopassWorkerEventSuccess) {
|
||||
err = picopass_device_parse_credential(AA1, pacs);
|
||||
}
|
||||
if(err != ERR_NONE) {
|
||||
FURI_LOG_E(TAG, "picopass_device_parse_credential error %d", err);
|
||||
nextState = PicopassWorkerEventFail;
|
||||
if(err != ERR_NONE) {
|
||||
FURI_LOG_E(TAG, "picopass_device_parse_credential error %d", err);
|
||||
nextState = PicopassWorkerEventFail;
|
||||
}
|
||||
}
|
||||
|
||||
if(nextState == PicopassWorkerEventSuccess) {
|
||||
err = picopass_device_parse_wiegand(pacs->credential, &pacs->record);
|
||||
}
|
||||
if(err != ERR_NONE) {
|
||||
FURI_LOG_E(TAG, "picopass_device_parse_wiegand error %d", err);
|
||||
nextState = PicopassWorkerEventFail;
|
||||
if(err != ERR_NONE) {
|
||||
FURI_LOG_E(TAG, "picopass_device_parse_wiegand error %d", err);
|
||||
nextState = PicopassWorkerEventFail;
|
||||
}
|
||||
}
|
||||
|
||||
// Notify caller and exit
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
struct PicopassWorker {
|
||||
FuriThread* thread;
|
||||
Storage* storage;
|
||||
Stream* dict_stream;
|
||||
|
||||
PicopassDeviceData* dev_data;
|
||||
PicopassWorkerCallback callback;
|
||||
|
||||
@@ -15,12 +15,10 @@ void picopass_scene_read_card_success_widget_callback(
|
||||
|
||||
void picopass_scene_read_card_success_on_enter(void* context) {
|
||||
Picopass* picopass = context;
|
||||
FuriString* credential_str;
|
||||
FuriString* wiegand_str;
|
||||
FuriString* sio_str;
|
||||
credential_str = furi_string_alloc();
|
||||
wiegand_str = furi_string_alloc();
|
||||
sio_str = furi_string_alloc();
|
||||
FuriString* csn_str = furi_string_alloc_set("CSN:");
|
||||
FuriString* credential_str = furi_string_alloc();
|
||||
FuriString* wiegand_str = furi_string_alloc();
|
||||
FuriString* sio_str = furi_string_alloc();
|
||||
|
||||
DOLPHIN_DEED(DolphinDeedNfcReadSuccess);
|
||||
|
||||
@@ -28,10 +26,18 @@ void picopass_scene_read_card_success_on_enter(void* context) {
|
||||
notification_message(picopass->notifications, &sequence_success);
|
||||
|
||||
// Setup view
|
||||
PicopassBlock* AA1 = picopass->dev->dev_data.AA1;
|
||||
PicopassPacs* pacs = &picopass->dev->dev_data.pacs;
|
||||
Widget* widget = picopass->widget;
|
||||
|
||||
if(pacs->record.bitLength == 0) {
|
||||
uint8_t csn[PICOPASS_BLOCK_LEN];
|
||||
memcpy(csn, &AA1->data[PICOPASS_CSN_BLOCK_INDEX], PICOPASS_BLOCK_LEN);
|
||||
for(uint8_t i = 0; i < PICOPASS_BLOCK_LEN; i++) {
|
||||
furi_string_cat_printf(csn_str, " %02X", csn[i]);
|
||||
}
|
||||
|
||||
// Neither of these are valid. Indicates the block was all 0x00 or all 0xff
|
||||
if(pacs->record.bitLength == 0 || pacs->record.bitLength == 255) {
|
||||
furi_string_cat_printf(wiegand_str, "Read Failed");
|
||||
|
||||
if(pacs->se_enabled) {
|
||||
@@ -79,18 +85,21 @@ void picopass_scene_read_card_success_on_enter(void* context) {
|
||||
}
|
||||
|
||||
widget_add_string_element(
|
||||
widget, 64, 12, AlignCenter, AlignCenter, FontPrimary, furi_string_get_cstr(wiegand_str));
|
||||
widget, 64, 5, AlignCenter, AlignCenter, FontSecondary, furi_string_get_cstr(csn_str));
|
||||
widget_add_string_element(
|
||||
widget, 64, 20, AlignCenter, AlignCenter, FontPrimary, furi_string_get_cstr(wiegand_str));
|
||||
widget_add_string_element(
|
||||
widget,
|
||||
64,
|
||||
32,
|
||||
36,
|
||||
AlignCenter,
|
||||
AlignCenter,
|
||||
FontSecondary,
|
||||
furi_string_get_cstr(credential_str));
|
||||
widget_add_string_element(
|
||||
widget, 64, 42, AlignCenter, AlignCenter, FontSecondary, furi_string_get_cstr(sio_str));
|
||||
widget, 64, 46, AlignCenter, AlignCenter, FontSecondary, furi_string_get_cstr(sio_str));
|
||||
|
||||
furi_string_free(csn_str);
|
||||
furi_string_free(credential_str);
|
||||
furi_string_free(wiegand_str);
|
||||
furi_string_free(sio_str);
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <lib/toolbox/path.h>
|
||||
#include <assets_icons.h>
|
||||
|
||||
#include <lib/subghz/protocols/protocol_items.h>
|
||||
#include <flipper_format/flipper_format_i.h>
|
||||
#include <applications/main/subghz/subghz_i.h>
|
||||
|
||||
@@ -158,6 +159,7 @@ static int playlist_worker_process(
|
||||
|
||||
// (try to) send file
|
||||
SubGhzEnvironment* environment = subghz_environment_alloc();
|
||||
subghz_environment_set_protocol_registry(environment, (void*)&subghz_protocol_registry);
|
||||
SubGhzTransmitter* transmitter =
|
||||
subghz_transmitter_alloc_init(environment, furi_string_get_cstr(protocol));
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <toolbox/stream/stream.h>
|
||||
#include <flipper_format.h>
|
||||
#include <flipper_format_i.h>
|
||||
#include <lib/subghz/protocols/protocol_items.h>
|
||||
|
||||
#define TAG "SubBruteWorker"
|
||||
#define SUBBRUTE_TX_TIMEOUT 5
|
||||
@@ -30,6 +31,8 @@ SubBruteWorker* subbrute_worker_alloc() {
|
||||
instance->decoder_result = NULL;
|
||||
instance->transmitter = NULL;
|
||||
instance->environment = subghz_environment_alloc();
|
||||
subghz_environment_set_protocol_registry(
|
||||
instance->environment, (void*)&subghz_protocol_registry);
|
||||
|
||||
instance->transmit_mode = false;
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <lib/toolbox/stream/stream.h>
|
||||
#include <lib/flipper_format/flipper_format.h>
|
||||
#include <lib/flipper_format/flipper_format_i.h>
|
||||
#include <lib/subghz/protocols/protocol_items.h>
|
||||
|
||||
#define TAG "SubBruteDevice"
|
||||
|
||||
@@ -18,6 +19,8 @@ SubBruteDevice* subbrute_device_alloc() {
|
||||
instance->decoder_result = NULL;
|
||||
instance->receiver = NULL;
|
||||
instance->environment = subghz_environment_alloc();
|
||||
subghz_environment_set_protocol_registry(
|
||||
instance->environment, (void*)&subghz_protocol_registry);
|
||||
|
||||
#ifdef FURI_DEBUG
|
||||
subbrute_device_attack_set_default_values(instance, SubBruteAttackCAME12bit433);
|
||||
|
||||
13
applications/plugins/weather_station/application.fam
Normal file
@@ -0,0 +1,13 @@
|
||||
App(
|
||||
appid="weather_station",
|
||||
name="Weather Station",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="weather_station_app",
|
||||
cdefines=["APP_WEATHER_STATION"],
|
||||
requires=["gui"],
|
||||
stack_size=4 * 1024,
|
||||
order=50,
|
||||
fap_icon="weather_station_10px.png",
|
||||
fap_category="Tools",
|
||||
fap_icon_assets="images",
|
||||
)
|
||||
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
typedef enum {
|
||||
//WSCustomEvent
|
||||
WSCustomEventStartId = 100,
|
||||
|
||||
WSCustomEventSceneSettingLock,
|
||||
|
||||
WSCustomEventViewReceiverOK,
|
||||
WSCustomEventViewReceiverConfig,
|
||||
WSCustomEventViewReceiverBack,
|
||||
WSCustomEventViewReceiverOffDisplay,
|
||||
WSCustomEventViewReceiverUnlock,
|
||||
} WSCustomEvent;
|
||||
@@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
|
||||
#define WS_VERSION_APP "0.1"
|
||||
#define WS_DEVELOPED "SkorP"
|
||||
#define WS_GITHUB "https://github.com/flipperdevices/flipperzero-firmware"
|
||||
|
||||
#define WS_KEY_FILE_VERSION 1
|
||||
#define WS_KEY_FILE_TYPE "Flipper Weather Station Key File"
|
||||
|
||||
/** WSRxKeyState state */
|
||||
typedef enum {
|
||||
WSRxKeyStateIDLE,
|
||||
WSRxKeyStateBack,
|
||||
WSRxKeyStateStart,
|
||||
WSRxKeyStateAddKey,
|
||||
} WSRxKeyState;
|
||||
|
||||
/** WSHopperState state */
|
||||
typedef enum {
|
||||
WSHopperStateOFF,
|
||||
WSHopperStateRunnig,
|
||||
WSHopperStatePause,
|
||||
WSHopperStateRSSITimeOut,
|
||||
} WSHopperState;
|
||||
|
||||
/** WSLock */
|
||||
typedef enum {
|
||||
WSLockOff,
|
||||
WSLockOn,
|
||||
} WSLock;
|
||||
|
||||
typedef enum {
|
||||
WeatherStationViewVariableItemList,
|
||||
WeatherStationViewSubmenu,
|
||||
WeatherStationViewReceiver,
|
||||
WeatherStationViewReceiverInfo,
|
||||
WeatherStationViewWidget,
|
||||
} WeatherStationView;
|
||||
|
||||
/** WeatherStationTxRx state */
|
||||
typedef enum {
|
||||
WSTxRxStateIDLE,
|
||||
WSTxRxStateRx,
|
||||
WSTxRxStateTx,
|
||||
WSTxRxStateSleep,
|
||||
} WSTxRxState;
|
||||
BIN
applications/plugins/weather_station/images/Humid_10x15.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
applications/plugins/weather_station/images/Therm_7x16.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
applications/plugins/weather_station/images/station_icon.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
341
applications/plugins/weather_station/protocols/gt_wt_03.c
Normal file
@@ -0,0 +1,341 @@
|
||||
#include "gt_wt_03.h"
|
||||
|
||||
#define TAG "WSProtocolGT_WT03"
|
||||
|
||||
/*
|
||||
* Help
|
||||
* https://github.com/merbanan/rtl_433/blob/5f0ff6db624270a4598958ab9dd79bb385ced3ef/src/devices/gt_wt_03.c
|
||||
*
|
||||
*
|
||||
* Globaltronics GT-WT-03 sensor on 433.92MHz.
|
||||
* The 01-set sensor has 60 ms packet gap with 10 repeats.
|
||||
* The 02-set sensor has no packet gap with 23 repeats.
|
||||
* Example:
|
||||
* {41} 17 cf be fa 6a 80 [ S1 C1 26,1 C 78.9 F 48% Bat-Good Manual-Yes ]
|
||||
* {41} 17 cf be fa 6a 80 [ S1 C1 26,1 C 78.9 F 48% Bat-Good Manual-Yes Batt-Changed ]
|
||||
* {41} 17 cf fe fa ea 80 [ S1 C1 26,1 C 78.9 F 48% Bat-Good Manual-No Batt-Changed ]
|
||||
* {41} 01 cf 6f 11 b2 80 [ S2 C2 23,8 C 74.8 F 48% Bat-LOW Manual-No ]
|
||||
* {41} 01 c8 d0 2b 76 80 [ S2 C3 -4,4 C 24.1 F 55% Bat-Good Manual-No Batt-Changed ]
|
||||
* Format string:
|
||||
* ID:8h HUM:8d B:b M:b C:2d TEMP:12d CHK:8h 1x
|
||||
* Data layout:
|
||||
* TYP IIIIIIII HHHHHHHH BMCCTTTT TTTTTTTT XXXXXXXX
|
||||
* - I: Random Device Code: changes with battery reset
|
||||
* - H: Humidity: 8 Bit 00-99, Display LL=10%, Display HH=110% (Range 20-95%)
|
||||
* - B: Battery: 0=OK 1=LOW
|
||||
* - M: Manual Send Button Pressed: 0=not pressed, 1=pressed
|
||||
* - C: Channel: 00=CH1, 01=CH2, 10=CH3
|
||||
* - T: Temperature: 12 Bit 2's complement, scaled by 10, range-50.0 C (-50.1 shown as Lo) to +70.0 C (+70.1 C is shown as Hi)
|
||||
* - X: Checksum, xor shifting key per byte
|
||||
* Humidity:
|
||||
* - the working range is 20-95 %
|
||||
* - if "LL" in display view it sends 10 %
|
||||
* - if "HH" in display view it sends 110%
|
||||
* Checksum:
|
||||
* Per byte xor the key for each 1-bit, shift per bit. Key list per bit, starting at MSB:
|
||||
* - 0x00 [07]
|
||||
* - 0x80 [06]
|
||||
* - 0x40 [05]
|
||||
* - 0x20 [04]
|
||||
* - 0x10 [03]
|
||||
* - 0x88 [02]
|
||||
* - 0xc4 [01]
|
||||
* - 0x62 [00]
|
||||
* Note: this can also be seen as lower byte of a Galois/Fibonacci LFSR-16, gen 0x00, init 0x3100 (or 0x62 if reversed) resetting at every byte.
|
||||
* Battery voltages:
|
||||
* - U=<2,65V +- ~5% Battery indicator
|
||||
* - U=>2.10C +- 5% plausible readings
|
||||
* - U=2,00V +- ~5% Temperature offset -5°C Humidity offset unknown
|
||||
* - U=<1,95V +- ~5% does not initialize anymore
|
||||
* - U=1,90V +- 5% temperature offset -15°C
|
||||
* - U=1,80V +- 5% Display is showing refresh pattern
|
||||
* - U=1.75V +- ~5% TX causes cut out
|
||||
*
|
||||
*/
|
||||
|
||||
static const SubGhzBlockConst ws_protocol_gt_wt_03_const = {
|
||||
.te_short = 285,
|
||||
.te_long = 570,
|
||||
.te_delta = 120,
|
||||
.min_count_bit_for_found = 41,
|
||||
};
|
||||
|
||||
struct WSProtocolDecoderGT_WT03 {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
|
||||
SubGhzBlockDecoder decoder;
|
||||
WSBlockGeneric generic;
|
||||
|
||||
uint16_t header_count;
|
||||
};
|
||||
|
||||
struct WSProtocolEncoderGT_WT03 {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
WSBlockGeneric generic;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
GT_WT03DecoderStepReset = 0,
|
||||
GT_WT03DecoderStepCheckPreambule,
|
||||
GT_WT03DecoderStepSaveDuration,
|
||||
GT_WT03DecoderStepCheckDuration,
|
||||
} GT_WT03DecoderStep;
|
||||
|
||||
const SubGhzProtocolDecoder ws_protocol_gt_wt_03_decoder = {
|
||||
.alloc = ws_protocol_decoder_gt_wt_03_alloc,
|
||||
.free = ws_protocol_decoder_gt_wt_03_free,
|
||||
|
||||
.feed = ws_protocol_decoder_gt_wt_03_feed,
|
||||
.reset = ws_protocol_decoder_gt_wt_03_reset,
|
||||
|
||||
.get_hash_data = ws_protocol_decoder_gt_wt_03_get_hash_data,
|
||||
.serialize = ws_protocol_decoder_gt_wt_03_serialize,
|
||||
.deserialize = ws_protocol_decoder_gt_wt_03_deserialize,
|
||||
.get_string = ws_protocol_decoder_gt_wt_03_get_string,
|
||||
};
|
||||
|
||||
const SubGhzProtocolEncoder ws_protocol_gt_wt_03_encoder = {
|
||||
.alloc = NULL,
|
||||
.free = NULL,
|
||||
|
||||
.deserialize = NULL,
|
||||
.stop = NULL,
|
||||
.yield = NULL,
|
||||
};
|
||||
|
||||
const SubGhzProtocol ws_protocol_gt_wt_03 = {
|
||||
.name = WS_PROTOCOL_GT_WT_03_NAME,
|
||||
.type = SubGhzProtocolWeatherStation,
|
||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 |
|
||||
SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable,
|
||||
|
||||
.decoder = &ws_protocol_gt_wt_03_decoder,
|
||||
.encoder = &ws_protocol_gt_wt_03_encoder,
|
||||
};
|
||||
|
||||
void* ws_protocol_decoder_gt_wt_03_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
WSProtocolDecoderGT_WT03* instance = malloc(sizeof(WSProtocolDecoderGT_WT03));
|
||||
instance->base.protocol = &ws_protocol_gt_wt_03;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void ws_protocol_decoder_gt_wt_03_free(void* context) {
|
||||
furi_assert(context);
|
||||
WSProtocolDecoderGT_WT03* instance = context;
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void ws_protocol_decoder_gt_wt_03_reset(void* context) {
|
||||
furi_assert(context);
|
||||
WSProtocolDecoderGT_WT03* instance = context;
|
||||
instance->decoder.parser_step = GT_WT03DecoderStepReset;
|
||||
}
|
||||
|
||||
static bool ws_protocol_gt_wt_03_check_crc(WSProtocolDecoderGT_WT03* instance) {
|
||||
uint8_t msg[] = {
|
||||
instance->decoder.decode_data >> 33,
|
||||
instance->decoder.decode_data >> 25,
|
||||
instance->decoder.decode_data >> 17,
|
||||
instance->decoder.decode_data >> 9};
|
||||
|
||||
uint8_t sum = 0;
|
||||
for(unsigned k = 0; k < sizeof(msg); ++k) {
|
||||
uint8_t data = msg[k];
|
||||
uint16_t key = 0x3100;
|
||||
for(int i = 7; i >= 0; --i) {
|
||||
// XOR key into sum if data bit is set
|
||||
if((data >> i) & 1) sum ^= key & 0xff;
|
||||
// roll the key right
|
||||
key = (key >> 1);
|
||||
}
|
||||
}
|
||||
return ((sum ^ (uint8_t)((instance->decoder.decode_data >> 1) & 0xFF)) == 0x2D);
|
||||
}
|
||||
|
||||
/**
|
||||
* Analysis of received data
|
||||
* @param instance Pointer to a WSBlockGeneric* instance
|
||||
*/
|
||||
static void ws_protocol_gt_wt_03_remote_controller(WSBlockGeneric* instance) {
|
||||
instance->id = instance->data >> 33;
|
||||
instance->humidity = (instance->data >> 25) & 0xFF;
|
||||
|
||||
if(instance->humidity <= 10) { // actually the sensors sends 10 below working range of 20%
|
||||
instance->humidity = 0;
|
||||
} else if(instance->humidity > 95) { // actually the sensors sends 110 above working range of 90%
|
||||
instance->humidity = 100;
|
||||
}
|
||||
|
||||
instance->battery_low = (instance->data >> 24) & 1;
|
||||
instance->btn = (instance->data >> 23) & 1;
|
||||
instance->channel = ((instance->data >> 21) & 0x03) + 1;
|
||||
|
||||
if(!((instance->data >> 20) & 1)) {
|
||||
instance->temp = (float)((instance->data >> 9) & 0x07FF) / 10.0f;
|
||||
} else {
|
||||
instance->temp = (float)((~(instance->data >> 9) & 0x07FF) + 1) / -10.0f;
|
||||
}
|
||||
}
|
||||
|
||||
void ws_protocol_decoder_gt_wt_03_feed(void* context, bool level, uint32_t duration) {
|
||||
furi_assert(context);
|
||||
WSProtocolDecoderGT_WT03* instance = context;
|
||||
|
||||
switch(instance->decoder.parser_step) {
|
||||
case GT_WT03DecoderStepReset:
|
||||
if((level) && (DURATION_DIFF(duration, ws_protocol_gt_wt_03_const.te_short * 3) <
|
||||
ws_protocol_gt_wt_03_const.te_delta * 2)) {
|
||||
instance->decoder.parser_step = GT_WT03DecoderStepCheckPreambule;
|
||||
instance->decoder.te_last = duration;
|
||||
instance->header_count = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case GT_WT03DecoderStepCheckPreambule:
|
||||
if(level) {
|
||||
instance->decoder.te_last = duration;
|
||||
} else {
|
||||
if((DURATION_DIFF(instance->decoder.te_last, ws_protocol_gt_wt_03_const.te_short * 3) <
|
||||
ws_protocol_gt_wt_03_const.te_delta * 2) &&
|
||||
(DURATION_DIFF(duration, ws_protocol_gt_wt_03_const.te_short * 3) <
|
||||
ws_protocol_gt_wt_03_const.te_delta * 2)) {
|
||||
//Found preambule
|
||||
instance->header_count++;
|
||||
} else if(instance->header_count == 4) {
|
||||
if((DURATION_DIFF(instance->decoder.te_last, ws_protocol_gt_wt_03_const.te_short) <
|
||||
ws_protocol_gt_wt_03_const.te_delta) &&
|
||||
(DURATION_DIFF(duration, ws_protocol_gt_wt_03_const.te_long) <
|
||||
ws_protocol_gt_wt_03_const.te_delta)) {
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
|
||||
instance->decoder.parser_step = GT_WT03DecoderStepSaveDuration;
|
||||
} else if(
|
||||
(DURATION_DIFF(instance->decoder.te_last, ws_protocol_gt_wt_03_const.te_long) <
|
||||
ws_protocol_gt_wt_03_const.te_delta) &&
|
||||
(DURATION_DIFF(duration, ws_protocol_gt_wt_03_const.te_short) <
|
||||
ws_protocol_gt_wt_03_const.te_delta)) {
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
|
||||
instance->decoder.parser_step = GT_WT03DecoderStepSaveDuration;
|
||||
} else {
|
||||
instance->decoder.parser_step = GT_WT03DecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
instance->decoder.parser_step = GT_WT03DecoderStepReset;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case GT_WT03DecoderStepSaveDuration:
|
||||
if(level) {
|
||||
instance->decoder.te_last = duration;
|
||||
instance->decoder.parser_step = GT_WT03DecoderStepCheckDuration;
|
||||
} else {
|
||||
instance->decoder.parser_step = GT_WT03DecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case GT_WT03DecoderStepCheckDuration:
|
||||
if(!level) {
|
||||
if(((DURATION_DIFF(instance->decoder.te_last, ws_protocol_gt_wt_03_const.te_short * 3) <
|
||||
ws_protocol_gt_wt_03_const.te_delta * 2) &&
|
||||
(DURATION_DIFF(duration, ws_protocol_gt_wt_03_const.te_short * 3) <
|
||||
ws_protocol_gt_wt_03_const.te_delta * 2))) {
|
||||
if((instance->decoder.decode_count_bit ==
|
||||
ws_protocol_gt_wt_03_const.min_count_bit_for_found) &&
|
||||
ws_protocol_gt_wt_03_check_crc(instance)) {
|
||||
instance->generic.data = instance->decoder.decode_data;
|
||||
instance->generic.data_count_bit = instance->decoder.decode_count_bit;
|
||||
ws_protocol_gt_wt_03_remote_controller(&instance->generic);
|
||||
if(instance->base.callback)
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->header_count = 1;
|
||||
instance->decoder.parser_step = GT_WT03DecoderStepCheckPreambule;
|
||||
break;
|
||||
} else if(
|
||||
(DURATION_DIFF(instance->decoder.te_last, ws_protocol_gt_wt_03_const.te_short) <
|
||||
ws_protocol_gt_wt_03_const.te_delta) &&
|
||||
(DURATION_DIFF(duration, ws_protocol_gt_wt_03_const.te_long) <
|
||||
ws_protocol_gt_wt_03_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
|
||||
instance->decoder.parser_step = GT_WT03DecoderStepSaveDuration;
|
||||
} else if(
|
||||
(DURATION_DIFF(instance->decoder.te_last, ws_protocol_gt_wt_03_const.te_long) <
|
||||
ws_protocol_gt_wt_03_const.te_delta) &&
|
||||
(DURATION_DIFF(duration, ws_protocol_gt_wt_03_const.te_short) <
|
||||
ws_protocol_gt_wt_03_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
|
||||
instance->decoder.parser_step = GT_WT03DecoderStepSaveDuration;
|
||||
} else {
|
||||
instance->decoder.parser_step = GT_WT03DecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
instance->decoder.parser_step = GT_WT03DecoderStepReset;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t ws_protocol_decoder_gt_wt_03_get_hash_data(void* context) {
|
||||
furi_assert(context);
|
||||
WSProtocolDecoderGT_WT03* instance = context;
|
||||
return subghz_protocol_blocks_get_hash_data(
|
||||
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
|
||||
}
|
||||
|
||||
bool ws_protocol_decoder_gt_wt_03_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzPresetDefinition* preset) {
|
||||
furi_assert(context);
|
||||
WSProtocolDecoderGT_WT03* instance = context;
|
||||
return ws_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
}
|
||||
|
||||
bool ws_protocol_decoder_gt_wt_03_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_assert(context);
|
||||
WSProtocolDecoderGT_WT03* instance = context;
|
||||
bool ret = false;
|
||||
do {
|
||||
if(!ws_block_generic_deserialize(&instance->generic, flipper_format)) {
|
||||
break;
|
||||
}
|
||||
if(instance->generic.data_count_bit !=
|
||||
ws_protocol_gt_wt_03_const.min_count_bit_for_found) {
|
||||
FURI_LOG_E(TAG, "Wrong number of bits in key");
|
||||
break;
|
||||
}
|
||||
ret = true;
|
||||
} while(false);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void ws_protocol_decoder_gt_wt_03_get_string(void* context, FuriString* output) {
|
||||
furi_assert(context);
|
||||
WSProtocolDecoderGT_WT03* instance = context;
|
||||
furi_string_printf(
|
||||
output,
|
||||
"%s %dbit\r\n"
|
||||
"Key:0x%lX%08lX\r\n"
|
||||
"Sn:0x%lX Ch:%d Bat:%d\r\n"
|
||||
"Temp:%d.%d C Hum:%d%%",
|
||||
instance->generic.protocol_name,
|
||||
instance->generic.data_count_bit,
|
||||
(uint32_t)(instance->generic.data >> 32),
|
||||
(uint32_t)(instance->generic.data),
|
||||
instance->generic.id,
|
||||
instance->generic.channel,
|
||||
instance->generic.battery_low,
|
||||
(int16_t)instance->generic.temp,
|
||||
abs(((int16_t)(instance->generic.temp * 10) - (((int16_t)instance->generic.temp) * 10))),
|
||||
instance->generic.humidity);
|
||||
}
|
||||
79
applications/plugins/weather_station/protocols/gt_wt_03.h
Normal file
@@ -0,0 +1,79 @@
|
||||
#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 "ws_generic.h"
|
||||
#include <lib/subghz/blocks/math.h>
|
||||
|
||||
#define WS_PROTOCOL_GT_WT_03_NAME "GT-WT03"
|
||||
|
||||
typedef struct WSProtocolDecoderGT_WT03 WSProtocolDecoderGT_WT03;
|
||||
typedef struct WSProtocolEncoderGT_WT03 WSProtocolEncoderGT_WT03;
|
||||
|
||||
extern const SubGhzProtocolDecoder ws_protocol_gt_wt_03_decoder;
|
||||
extern const SubGhzProtocolEncoder ws_protocol_gt_wt_03_encoder;
|
||||
extern const SubGhzProtocol ws_protocol_gt_wt_03;
|
||||
|
||||
/**
|
||||
* Allocate WSProtocolDecoderGT_WT03.
|
||||
* @param environment Pointer to a SubGhzEnvironment instance
|
||||
* @return WSProtocolDecoderGT_WT03* pointer to a WSProtocolDecoderGT_WT03 instance
|
||||
*/
|
||||
void* ws_protocol_decoder_gt_wt_03_alloc(SubGhzEnvironment* environment);
|
||||
|
||||
/**
|
||||
* Free WSProtocolDecoderGT_WT03.
|
||||
* @param context Pointer to a WSProtocolDecoderGT_WT03 instance
|
||||
*/
|
||||
void ws_protocol_decoder_gt_wt_03_free(void* context);
|
||||
|
||||
/**
|
||||
* Reset decoder WSProtocolDecoderGT_WT03.
|
||||
* @param context Pointer to a WSProtocolDecoderGT_WT03 instance
|
||||
*/
|
||||
void ws_protocol_decoder_gt_wt_03_reset(void* context);
|
||||
|
||||
/**
|
||||
* Parse a raw sequence of levels and durations received from the air.
|
||||
* @param context Pointer to a WSProtocolDecoderGT_WT03 instance
|
||||
* @param level Signal level true-high false-low
|
||||
* @param duration Duration of this level in, us
|
||||
*/
|
||||
void ws_protocol_decoder_gt_wt_03_feed(void* context, bool level, uint32_t duration);
|
||||
|
||||
/**
|
||||
* Getting the hash sum of the last randomly received parcel.
|
||||
* @param context Pointer to a WSProtocolDecoderGT_WT03 instance
|
||||
* @return hash Hash sum
|
||||
*/
|
||||
uint8_t ws_protocol_decoder_gt_wt_03_get_hash_data(void* context);
|
||||
|
||||
/**
|
||||
* Serialize data WSProtocolDecoderGT_WT03.
|
||||
* @param context Pointer to a WSProtocolDecoderGT_WT03 instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @param preset The modulation on which the signal was received, SubGhzPresetDefinition
|
||||
* @return true On success
|
||||
*/
|
||||
bool ws_protocol_decoder_gt_wt_03_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzPresetDefinition* preset);
|
||||
|
||||
/**
|
||||
* Deserialize data WSProtocolDecoderGT_WT03.
|
||||
* @param context Pointer to a WSProtocolDecoderGT_WT03 instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @return true On success
|
||||
*/
|
||||
bool ws_protocol_decoder_gt_wt_03_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
|
||||
/**
|
||||
* Getting a textual representation of the received data.
|
||||
* @param context Pointer to a WSProtocolDecoderGT_WT03 instance
|
||||
* @param output Resulting text
|
||||
*/
|
||||
void ws_protocol_decoder_gt_wt_03_get_string(void* context, FuriString* output);
|
||||
296
applications/plugins/weather_station/protocols/infactory.c
Normal file
@@ -0,0 +1,296 @@
|
||||
#include "infactory.h"
|
||||
|
||||
#define TAG "WSProtocolInfactory"
|
||||
|
||||
/*
|
||||
* Help
|
||||
* https://github.com/merbanan/rtl_433/blob/master/src/devices/infactory.c
|
||||
*
|
||||
* Analysis using Genuino (see http://gitlab.com/hp-uno, e.g. uno_log_433):
|
||||
* Observed On-Off-Key (OOK) data pattern:
|
||||
* preamble syncPrefix data...(40 bit) syncPostfix
|
||||
* HHLL HHLL HHLL HHLL HLLLLLLLLLLLLLLLL (HLLLL HLLLLLLLL HLLLL HLLLLLLLL ....) HLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL
|
||||
* Breakdown:
|
||||
* - four preamble pairs '1'/'0' each with a length of ca. 1000us
|
||||
* - syncPre, syncPost, data0, data1 have a '1' start pulse of ca. 500us
|
||||
* - syncPre pulse before dataPtr has a '0' pulse length of ca. 8000us
|
||||
* - data0 (0-bits) have then a '0' pulse length of ca. 2000us
|
||||
* - data1 (1-bits) have then a '0' pulse length of ca. 4000us
|
||||
* - syncPost after dataPtr has a '0' pulse length of ca. 16000us
|
||||
* This analysis is the reason for the new r_device definitions below.
|
||||
* NB: pulse_slicer_ppm does not use .gap_limit if .tolerance is set.
|
||||
*
|
||||
* Outdoor sensor, transmits temperature and humidity data
|
||||
* - inFactory NC-3982-913/NX-5817-902, Pearl (for FWS-686 station)
|
||||
* - nor-tec 73383 (weather station + sensor), Schou Company AS, Denmark
|
||||
* - DAY 73365 (weather station + sensor), Schou Company AS, Denmark
|
||||
* Known brand names: inFactory, nor-tec, GreenBlue, DAY. Manufacturer in China.
|
||||
* Transmissions includes an id. Every 60 seconds the sensor transmits 6 packets:
|
||||
* 0000 1111 | 0011 0000 | 0101 1100 | 1110 0111 | 0110 0001
|
||||
* iiii iiii | cccc ub?? | tttt tttt | tttt hhhh | hhhh ??nn
|
||||
* - i: identification; changes on battery switch
|
||||
* - c: CRC-4; CCITT checksum, see below for computation specifics
|
||||
* - u: unknown; (sometimes set at power-on, but not always)
|
||||
* - b: battery low; flag to indicate low battery voltage
|
||||
* - h: Humidity; BCD-encoded, each nibble is one digit, 'A0' means 100%rH
|
||||
* - t: Temperature; in °F as binary number with one decimal place + 90 °F offset
|
||||
* - n: Channel; Channel number 1 - 3
|
||||
*
|
||||
*/
|
||||
|
||||
static const SubGhzBlockConst ws_protocol_infactory_const = {
|
||||
.te_short = 500,
|
||||
.te_long = 2000,
|
||||
.te_delta = 150,
|
||||
.min_count_bit_for_found = 40,
|
||||
};
|
||||
|
||||
struct WSProtocolDecoderInfactory {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
|
||||
SubGhzBlockDecoder decoder;
|
||||
WSBlockGeneric generic;
|
||||
|
||||
uint16_t header_count;
|
||||
};
|
||||
|
||||
struct WSProtocolEncoderInfactory {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
WSBlockGeneric generic;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
InfactoryDecoderStepReset = 0,
|
||||
InfactoryDecoderStepCheckPreambule,
|
||||
InfactoryDecoderStepSaveDuration,
|
||||
InfactoryDecoderStepCheckDuration,
|
||||
} InfactoryDecoderStep;
|
||||
|
||||
const SubGhzProtocolDecoder ws_protocol_infactory_decoder = {
|
||||
.alloc = ws_protocol_decoder_infactory_alloc,
|
||||
.free = ws_protocol_decoder_infactory_free,
|
||||
|
||||
.feed = ws_protocol_decoder_infactory_feed,
|
||||
.reset = ws_protocol_decoder_infactory_reset,
|
||||
|
||||
.get_hash_data = ws_protocol_decoder_infactory_get_hash_data,
|
||||
.serialize = ws_protocol_decoder_infactory_serialize,
|
||||
.deserialize = ws_protocol_decoder_infactory_deserialize,
|
||||
.get_string = ws_protocol_decoder_infactory_get_string,
|
||||
};
|
||||
|
||||
const SubGhzProtocolEncoder ws_protocol_infactory_encoder = {
|
||||
.alloc = NULL,
|
||||
.free = NULL,
|
||||
|
||||
.deserialize = NULL,
|
||||
.stop = NULL,
|
||||
.yield = NULL,
|
||||
};
|
||||
|
||||
const SubGhzProtocol ws_protocol_infactory = {
|
||||
.name = WS_PROTOCOL_INFACTORY_NAME,
|
||||
.type = SubGhzProtocolWeatherStation,
|
||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 |
|
||||
SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable,
|
||||
|
||||
.decoder = &ws_protocol_infactory_decoder,
|
||||
.encoder = &ws_protocol_infactory_encoder,
|
||||
};
|
||||
|
||||
void* ws_protocol_decoder_infactory_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
WSProtocolDecoderInfactory* instance = malloc(sizeof(WSProtocolDecoderInfactory));
|
||||
instance->base.protocol = &ws_protocol_infactory;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void ws_protocol_decoder_infactory_free(void* context) {
|
||||
furi_assert(context);
|
||||
WSProtocolDecoderInfactory* instance = context;
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void ws_protocol_decoder_infactory_reset(void* context) {
|
||||
furi_assert(context);
|
||||
WSProtocolDecoderInfactory* instance = context;
|
||||
instance->decoder.parser_step = InfactoryDecoderStepReset;
|
||||
}
|
||||
|
||||
static bool ws_protocol_infactory_check_crc(WSProtocolDecoderInfactory* instance) {
|
||||
uint8_t msg[] = {
|
||||
instance->decoder.decode_data >> 32,
|
||||
(((instance->decoder.decode_data >> 24) & 0x0F) | (instance->decoder.decode_data & 0x0F)
|
||||
<< 4),
|
||||
instance->decoder.decode_data >> 16,
|
||||
instance->decoder.decode_data >> 8,
|
||||
instance->decoder.decode_data};
|
||||
|
||||
uint8_t crc =
|
||||
subghz_protocol_blocks_crc4(msg, 4, 0x13, 0); // Koopmann 0x9, CCITT-4; FP-4; ITU-T G.704
|
||||
crc ^= msg[4] >> 4; // last nibble is only XORed
|
||||
return (crc == ((instance->decoder.decode_data >> 28) & 0x0F));
|
||||
}
|
||||
|
||||
/**
|
||||
* Analysis of received data
|
||||
* @param instance Pointer to a WSBlockGeneric* instance
|
||||
*/
|
||||
static void ws_protocol_infactory_remote_controller(WSBlockGeneric* instance) {
|
||||
instance->id = instance->data >> 32;
|
||||
instance->battery_low = (instance->data >> 26) & 1;
|
||||
instance->temp = ws_block_generic_fahrenheit_to_celsius(
|
||||
((float)((instance->data >> 12) & 0x0FFF) - 900.0f) / 10.0f);
|
||||
instance->humidity =
|
||||
(((instance->data >> 8) & 0x0F) * 10) + ((instance->data >> 4) & 0x0F); // BCD, 'A0'=100%rH
|
||||
instance->channel = instance->data & 0x03;
|
||||
}
|
||||
|
||||
void ws_protocol_decoder_infactory_feed(void* context, bool level, uint32_t duration) {
|
||||
furi_assert(context);
|
||||
WSProtocolDecoderInfactory* instance = context;
|
||||
|
||||
switch(instance->decoder.parser_step) {
|
||||
case InfactoryDecoderStepReset:
|
||||
if((level) && (DURATION_DIFF(duration, ws_protocol_infactory_const.te_short * 2) <
|
||||
ws_protocol_infactory_const.te_delta * 2)) {
|
||||
instance->decoder.parser_step = InfactoryDecoderStepCheckPreambule;
|
||||
instance->decoder.te_last = duration;
|
||||
instance->header_count = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case InfactoryDecoderStepCheckPreambule:
|
||||
if(level) {
|
||||
instance->decoder.te_last = duration;
|
||||
} else {
|
||||
if((DURATION_DIFF(instance->decoder.te_last, ws_protocol_infactory_const.te_short * 2) <
|
||||
ws_protocol_infactory_const.te_delta * 2) &&
|
||||
(DURATION_DIFF(duration, ws_protocol_infactory_const.te_short * 2) <
|
||||
ws_protocol_infactory_const.te_delta * 2)) {
|
||||
//Found preambule
|
||||
instance->header_count++;
|
||||
} else if(
|
||||
(DURATION_DIFF(instance->decoder.te_last, ws_protocol_infactory_const.te_short) <
|
||||
ws_protocol_infactory_const.te_delta) &&
|
||||
(DURATION_DIFF(duration, ws_protocol_infactory_const.te_short * 16) <
|
||||
ws_protocol_infactory_const.te_delta * 8)) {
|
||||
//Found syncPrefix
|
||||
if(instance->header_count > 3) {
|
||||
instance->decoder.parser_step = InfactoryDecoderStepSaveDuration;
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
}
|
||||
} else {
|
||||
instance->decoder.parser_step = InfactoryDecoderStepReset;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case InfactoryDecoderStepSaveDuration:
|
||||
if(level) {
|
||||
instance->decoder.te_last = duration;
|
||||
instance->decoder.parser_step = InfactoryDecoderStepCheckDuration;
|
||||
} else {
|
||||
instance->decoder.parser_step = InfactoryDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case InfactoryDecoderStepCheckDuration:
|
||||
if(!level) {
|
||||
if(duration >= ((uint32_t)ws_protocol_infactory_const.te_short * 30)) {
|
||||
//Found syncPostfix
|
||||
if((instance->decoder.decode_count_bit ==
|
||||
ws_protocol_infactory_const.min_count_bit_for_found) &&
|
||||
ws_protocol_infactory_check_crc(instance)) {
|
||||
instance->generic.data = instance->decoder.decode_data;
|
||||
instance->generic.data_count_bit = instance->decoder.decode_count_bit;
|
||||
ws_protocol_infactory_remote_controller(&instance->generic);
|
||||
if(instance->base.callback)
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->decoder.parser_step = InfactoryDecoderStepReset;
|
||||
break;
|
||||
} else if(
|
||||
(DURATION_DIFF(instance->decoder.te_last, ws_protocol_infactory_const.te_short) <
|
||||
ws_protocol_infactory_const.te_delta) &&
|
||||
(DURATION_DIFF(duration, ws_protocol_infactory_const.te_long) <
|
||||
ws_protocol_infactory_const.te_delta * 2)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
|
||||
instance->decoder.parser_step = InfactoryDecoderStepSaveDuration;
|
||||
} else if(
|
||||
(DURATION_DIFF(instance->decoder.te_last, ws_protocol_infactory_const.te_short) <
|
||||
ws_protocol_infactory_const.te_delta) &&
|
||||
(DURATION_DIFF(duration, ws_protocol_infactory_const.te_long * 2) <
|
||||
ws_protocol_infactory_const.te_delta * 4)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
|
||||
instance->decoder.parser_step = InfactoryDecoderStepSaveDuration;
|
||||
} else {
|
||||
instance->decoder.parser_step = InfactoryDecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
instance->decoder.parser_step = InfactoryDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t ws_protocol_decoder_infactory_get_hash_data(void* context) {
|
||||
furi_assert(context);
|
||||
WSProtocolDecoderInfactory* instance = context;
|
||||
return subghz_protocol_blocks_get_hash_data(
|
||||
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
|
||||
}
|
||||
|
||||
bool ws_protocol_decoder_infactory_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzPresetDefinition* preset) {
|
||||
furi_assert(context);
|
||||
WSProtocolDecoderInfactory* instance = context;
|
||||
return ws_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
}
|
||||
|
||||
bool ws_protocol_decoder_infactory_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_assert(context);
|
||||
WSProtocolDecoderInfactory* instance = context;
|
||||
bool ret = false;
|
||||
do {
|
||||
if(!ws_block_generic_deserialize(&instance->generic, flipper_format)) {
|
||||
break;
|
||||
}
|
||||
if(instance->generic.data_count_bit !=
|
||||
ws_protocol_infactory_const.min_count_bit_for_found) {
|
||||
FURI_LOG_E(TAG, "Wrong number of bits in key");
|
||||
break;
|
||||
}
|
||||
ret = true;
|
||||
} while(false);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void ws_protocol_decoder_infactory_get_string(void* context, FuriString* output) {
|
||||
furi_assert(context);
|
||||
WSProtocolDecoderInfactory* instance = context;
|
||||
furi_string_printf(
|
||||
output,
|
||||
"%s %dbit\r\n"
|
||||
"Key:0x%lX%08lX\r\n"
|
||||
"Sn:0x%lX Ch:%d Bat:%d\r\n"
|
||||
"Temp:%d.%d C Hum:%d%%",
|
||||
instance->generic.protocol_name,
|
||||
instance->generic.data_count_bit,
|
||||
(uint32_t)(instance->generic.data >> 32),
|
||||
(uint32_t)(instance->generic.data),
|
||||
instance->generic.id,
|
||||
instance->generic.channel,
|
||||
instance->generic.battery_low,
|
||||
(int16_t)instance->generic.temp,
|
||||
abs(((int16_t)(instance->generic.temp * 10) - (((int16_t)instance->generic.temp) * 10))),
|
||||
instance->generic.humidity);
|
||||
}
|
||||
79
applications/plugins/weather_station/protocols/infactory.h
Normal file
@@ -0,0 +1,79 @@
|
||||
#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 "ws_generic.h"
|
||||
#include <lib/subghz/blocks/math.h>
|
||||
|
||||
#define WS_PROTOCOL_INFACTORY_NAME "inFactory-TH"
|
||||
|
||||
typedef struct WSProtocolDecoderInfactory WSProtocolDecoderInfactory;
|
||||
typedef struct WSProtocolEncoderInfactory WSProtocolEncoderInfactory;
|
||||
|
||||
extern const SubGhzProtocolDecoder ws_protocol_infactory_decoder;
|
||||
extern const SubGhzProtocolEncoder ws_protocol_infactory_encoder;
|
||||
extern const SubGhzProtocol ws_protocol_infactory;
|
||||
|
||||
/**
|
||||
* Allocate WSProtocolDecoderInfactory.
|
||||
* @param environment Pointer to a SubGhzEnvironment instance
|
||||
* @return WSProtocolDecoderInfactory* pointer to a WSProtocolDecoderInfactory instance
|
||||
*/
|
||||
void* ws_protocol_decoder_infactory_alloc(SubGhzEnvironment* environment);
|
||||
|
||||
/**
|
||||
* Free WSProtocolDecoderInfactory.
|
||||
* @param context Pointer to a WSProtocolDecoderInfactory instance
|
||||
*/
|
||||
void ws_protocol_decoder_infactory_free(void* context);
|
||||
|
||||
/**
|
||||
* Reset decoder WSProtocolDecoderInfactory.
|
||||
* @param context Pointer to a WSProtocolDecoderInfactory instance
|
||||
*/
|
||||
void ws_protocol_decoder_infactory_reset(void* context);
|
||||
|
||||
/**
|
||||
* Parse a raw sequence of levels and durations received from the air.
|
||||
* @param context Pointer to a WSProtocolDecoderInfactory instance
|
||||
* @param level Signal level true-high false-low
|
||||
* @param duration Duration of this level in, us
|
||||
*/
|
||||
void ws_protocol_decoder_infactory_feed(void* context, bool level, uint32_t duration);
|
||||
|
||||
/**
|
||||
* Getting the hash sum of the last randomly received parcel.
|
||||
* @param context Pointer to a WSProtocolDecoderInfactory instance
|
||||
* @return hash Hash sum
|
||||
*/
|
||||
uint8_t ws_protocol_decoder_infactory_get_hash_data(void* context);
|
||||
|
||||
/**
|
||||
* Serialize data WSProtocolDecoderInfactory.
|
||||
* @param context Pointer to a WSProtocolDecoderInfactory instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @param preset The modulation on which the signal was received, SubGhzPresetDefinition
|
||||
* @return true On success
|
||||
*/
|
||||
bool ws_protocol_decoder_infactory_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzPresetDefinition* preset);
|
||||
|
||||
/**
|
||||
* Deserialize data WSProtocolDecoderInfactory.
|
||||
* @param context Pointer to a WSProtocolDecoderInfactory instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @return true On success
|
||||
*/
|
||||
bool ws_protocol_decoder_infactory_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
|
||||
/**
|
||||
* Getting a textual representation of the received data.
|
||||
* @param context Pointer to a WSProtocolDecoderInfactory instance
|
||||
* @param output Resulting text
|
||||
*/
|
||||
void ws_protocol_decoder_infactory_get_string(void* context, FuriString* output);
|
||||
261
applications/plugins/weather_station/protocols/nexus_th.c
Normal file
@@ -0,0 +1,261 @@
|
||||
#include "nexus_th.h"
|
||||
|
||||
#define TAG "WSProtocolNexus_TH"
|
||||
|
||||
/*
|
||||
* Help
|
||||
* https://github.com/merbanan/rtl_433/blob/ef2d37cf51e3264d11cde9149ef87de2f0a4d37a/src/devices/nexus.c
|
||||
*
|
||||
* Nexus sensor protocol with ID, temperature and optional humidity
|
||||
* also FreeTec (Pearl) NC-7345 sensors for FreeTec Weatherstation NC-7344,
|
||||
* also infactory/FreeTec (Pearl) NX-3980 sensors for infactory/FreeTec NX-3974 station,
|
||||
* also Solight TE82S sensors for Solight TE76/TE82/TE83/TE84 stations,
|
||||
* also TFA 30.3209.02 temperature/humidity sensor.
|
||||
* The sensor sends 36 bits 12 times,
|
||||
* the packets are ppm modulated (distance coding) with a pulse of ~500 us
|
||||
* followed by a short gap of ~1000 us for a 0 bit or a long ~2000 us gap for a
|
||||
* 1 bit, the sync gap is ~4000 us.
|
||||
* The data is grouped in 9 nibbles:
|
||||
* [id0] [id1] [flags] [temp0] [temp1] [temp2] [const] [humi0] [humi1]
|
||||
* - The 8-bit id changes when the battery is changed in the sensor.
|
||||
* - flags are 4 bits B 0 C C, where B is the battery status: 1=OK, 0=LOW
|
||||
* - and CC is the channel: 0=CH1, 1=CH2, 2=CH3
|
||||
* - temp is 12 bit signed scaled by 10
|
||||
* - const is always 1111 (0x0F)
|
||||
* - humidity is 8 bits
|
||||
* The sensors can be bought at Clas Ohlsen (Nexus) and Pearl (infactory/FreeTec).
|
||||
*
|
||||
*/
|
||||
|
||||
#define NEXUS_TH_CONST_DATA 0b1111
|
||||
|
||||
static const SubGhzBlockConst ws_protocol_nexus_th_const = {
|
||||
.te_short = 500,
|
||||
.te_long = 2000,
|
||||
.te_delta = 150,
|
||||
.min_count_bit_for_found = 36,
|
||||
};
|
||||
|
||||
struct WSProtocolDecoderNexus_TH {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
|
||||
SubGhzBlockDecoder decoder;
|
||||
WSBlockGeneric generic;
|
||||
};
|
||||
|
||||
struct WSProtocolEncoderNexus_TH {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
WSBlockGeneric generic;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
Nexus_THDecoderStepReset = 0,
|
||||
Nexus_THDecoderStepSaveDuration,
|
||||
Nexus_THDecoderStepCheckDuration,
|
||||
} Nexus_THDecoderStep;
|
||||
|
||||
const SubGhzProtocolDecoder ws_protocol_nexus_th_decoder = {
|
||||
.alloc = ws_protocol_decoder_nexus_th_alloc,
|
||||
.free = ws_protocol_decoder_nexus_th_free,
|
||||
|
||||
.feed = ws_protocol_decoder_nexus_th_feed,
|
||||
.reset = ws_protocol_decoder_nexus_th_reset,
|
||||
|
||||
.get_hash_data = ws_protocol_decoder_nexus_th_get_hash_data,
|
||||
.serialize = ws_protocol_decoder_nexus_th_serialize,
|
||||
.deserialize = ws_protocol_decoder_nexus_th_deserialize,
|
||||
.get_string = ws_protocol_decoder_nexus_th_get_string,
|
||||
};
|
||||
|
||||
const SubGhzProtocolEncoder ws_protocol_nexus_th_encoder = {
|
||||
.alloc = NULL,
|
||||
.free = NULL,
|
||||
|
||||
.deserialize = NULL,
|
||||
.stop = NULL,
|
||||
.yield = NULL,
|
||||
};
|
||||
|
||||
const SubGhzProtocol ws_protocol_nexus_th = {
|
||||
.name = WS_PROTOCOL_NEXUS_TH_NAME,
|
||||
.type = SubGhzProtocolWeatherStation,
|
||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 |
|
||||
SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable,
|
||||
|
||||
.decoder = &ws_protocol_nexus_th_decoder,
|
||||
.encoder = &ws_protocol_nexus_th_encoder,
|
||||
};
|
||||
|
||||
void* ws_protocol_decoder_nexus_th_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
WSProtocolDecoderNexus_TH* instance = malloc(sizeof(WSProtocolDecoderNexus_TH));
|
||||
instance->base.protocol = &ws_protocol_nexus_th;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void ws_protocol_decoder_nexus_th_free(void* context) {
|
||||
furi_assert(context);
|
||||
WSProtocolDecoderNexus_TH* instance = context;
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void ws_protocol_decoder_nexus_th_reset(void* context) {
|
||||
furi_assert(context);
|
||||
WSProtocolDecoderNexus_TH* instance = context;
|
||||
instance->decoder.parser_step = Nexus_THDecoderStepReset;
|
||||
}
|
||||
|
||||
static bool ws_protocol_nexus_th_check(WSProtocolDecoderNexus_TH* instance) {
|
||||
uint8_t type = (instance->decoder.decode_data >> 8) & 0x0F;
|
||||
|
||||
if((type == NEXUS_TH_CONST_DATA) && ((instance->decoder.decode_data >> 4) != 0xffffffff)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Analysis of received data
|
||||
* @param instance Pointer to a WSBlockGeneric* instance
|
||||
*/
|
||||
static void ws_protocol_nexus_th_remote_controller(WSBlockGeneric* instance) {
|
||||
instance->id = (instance->data >> 28) & 0xFF;
|
||||
instance->battery_low = !((instance->data >> 27) & 1);
|
||||
instance->channel = ((instance->data >> 24) & 0x03) + 1;
|
||||
|
||||
if(!((instance->data >> 23) & 1)) {
|
||||
instance->temp = (float)((instance->data >> 12) & 0x07FF) / 10.0f;
|
||||
} else {
|
||||
instance->temp = (float)((~(instance->data >> 12) & 0x07FF) + 1) / -10.0f;
|
||||
}
|
||||
|
||||
instance->humidity = instance->data & 0xFF;
|
||||
}
|
||||
|
||||
void ws_protocol_decoder_nexus_th_feed(void* context, bool level, uint32_t duration) {
|
||||
furi_assert(context);
|
||||
WSProtocolDecoderNexus_TH* instance = context;
|
||||
|
||||
switch(instance->decoder.parser_step) {
|
||||
case Nexus_THDecoderStepReset:
|
||||
if((!level) && (DURATION_DIFF(duration, ws_protocol_nexus_th_const.te_short * 8) <
|
||||
ws_protocol_nexus_th_const.te_delta * 4)) {
|
||||
//Found sync
|
||||
instance->decoder.parser_step = Nexus_THDecoderStepSaveDuration;
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case Nexus_THDecoderStepSaveDuration:
|
||||
if(level) {
|
||||
instance->decoder.te_last = duration;
|
||||
instance->decoder.parser_step = Nexus_THDecoderStepCheckDuration;
|
||||
} else {
|
||||
instance->decoder.parser_step = Nexus_THDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case Nexus_THDecoderStepCheckDuration:
|
||||
if(!level) {
|
||||
if(DURATION_DIFF(duration, ws_protocol_nexus_th_const.te_short * 8) <
|
||||
ws_protocol_nexus_th_const.te_delta * 4) {
|
||||
//Found sync
|
||||
instance->decoder.parser_step = Nexus_THDecoderStepReset;
|
||||
if((instance->decoder.decode_count_bit ==
|
||||
ws_protocol_nexus_th_const.min_count_bit_for_found) &&
|
||||
ws_protocol_nexus_th_check(instance)) {
|
||||
instance->generic.data = instance->decoder.decode_data;
|
||||
instance->generic.data_count_bit = instance->decoder.decode_count_bit;
|
||||
ws_protocol_nexus_th_remote_controller(&instance->generic);
|
||||
if(instance->base.callback)
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
instance->decoder.parser_step = Nexus_THDecoderStepCheckDuration;
|
||||
}
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
|
||||
break;
|
||||
} else if(
|
||||
(DURATION_DIFF(instance->decoder.te_last, ws_protocol_nexus_th_const.te_short) <
|
||||
ws_protocol_nexus_th_const.te_delta) &&
|
||||
(DURATION_DIFF(duration, ws_protocol_nexus_th_const.te_short * 2) <
|
||||
ws_protocol_nexus_th_const.te_delta * 2)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
|
||||
instance->decoder.parser_step = Nexus_THDecoderStepSaveDuration;
|
||||
} else if(
|
||||
(DURATION_DIFF(instance->decoder.te_last, ws_protocol_nexus_th_const.te_short) <
|
||||
ws_protocol_nexus_th_const.te_delta) &&
|
||||
(DURATION_DIFF(duration, ws_protocol_nexus_th_const.te_short * 4) <
|
||||
ws_protocol_nexus_th_const.te_delta * 4)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
|
||||
instance->decoder.parser_step = Nexus_THDecoderStepSaveDuration;
|
||||
} else {
|
||||
instance->decoder.parser_step = Nexus_THDecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
instance->decoder.parser_step = Nexus_THDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t ws_protocol_decoder_nexus_th_get_hash_data(void* context) {
|
||||
furi_assert(context);
|
||||
WSProtocolDecoderNexus_TH* instance = context;
|
||||
return subghz_protocol_blocks_get_hash_data(
|
||||
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
|
||||
}
|
||||
|
||||
bool ws_protocol_decoder_nexus_th_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzPresetDefinition* preset) {
|
||||
furi_assert(context);
|
||||
WSProtocolDecoderNexus_TH* instance = context;
|
||||
return ws_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
}
|
||||
|
||||
bool ws_protocol_decoder_nexus_th_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_assert(context);
|
||||
WSProtocolDecoderNexus_TH* instance = context;
|
||||
bool ret = false;
|
||||
do {
|
||||
if(!ws_block_generic_deserialize(&instance->generic, flipper_format)) {
|
||||
break;
|
||||
}
|
||||
if(instance->generic.data_count_bit !=
|
||||
ws_protocol_nexus_th_const.min_count_bit_for_found) {
|
||||
FURI_LOG_E(TAG, "Wrong number of bits in key");
|
||||
break;
|
||||
}
|
||||
ret = true;
|
||||
} while(false);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void ws_protocol_decoder_nexus_th_get_string(void* context, FuriString* output) {
|
||||
furi_assert(context);
|
||||
WSProtocolDecoderNexus_TH* instance = context;
|
||||
furi_string_printf(
|
||||
output,
|
||||
"%s %dbit\r\n"
|
||||
"Key:0x%lX%08lX\r\n"
|
||||
"Sn:0x%lX Ch:%d Bat:%d\r\n"
|
||||
"Temp:%d.%d C Hum:%d%%",
|
||||
instance->generic.protocol_name,
|
||||
instance->generic.data_count_bit,
|
||||
(uint32_t)(instance->generic.data >> 32),
|
||||
(uint32_t)(instance->generic.data),
|
||||
instance->generic.id,
|
||||
instance->generic.channel,
|
||||
instance->generic.battery_low,
|
||||
(int16_t)instance->generic.temp,
|
||||
abs(((int16_t)(instance->generic.temp * 10) - (((int16_t)instance->generic.temp) * 10))),
|
||||
instance->generic.humidity);
|
||||
}
|
||||
79
applications/plugins/weather_station/protocols/nexus_th.h
Normal file
@@ -0,0 +1,79 @@
|
||||
#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 "ws_generic.h"
|
||||
#include <lib/subghz/blocks/math.h>
|
||||
|
||||
#define WS_PROTOCOL_NEXUS_TH_NAME "Nexus-TH"
|
||||
|
||||
typedef struct WSProtocolDecoderNexus_TH WSProtocolDecoderNexus_TH;
|
||||
typedef struct WSProtocolEncoderNexus_TH WSProtocolEncoderNexus_TH;
|
||||
|
||||
extern const SubGhzProtocolDecoder ws_protocol_nexus_th_decoder;
|
||||
extern const SubGhzProtocolEncoder ws_protocol_nexus_th_encoder;
|
||||
extern const SubGhzProtocol ws_protocol_nexus_th;
|
||||
|
||||
/**
|
||||
* Allocate WSProtocolDecoderNexus_TH.
|
||||
* @param environment Pointer to a SubGhzEnvironment instance
|
||||
* @return WSProtocolDecoderNexus_TH* pointer to a WSProtocolDecoderNexus_TH instance
|
||||
*/
|
||||
void* ws_protocol_decoder_nexus_th_alloc(SubGhzEnvironment* environment);
|
||||
|
||||
/**
|
||||
* Free WSProtocolDecoderNexus_TH.
|
||||
* @param context Pointer to a WSProtocolDecoderNexus_TH instance
|
||||
*/
|
||||
void ws_protocol_decoder_nexus_th_free(void* context);
|
||||
|
||||
/**
|
||||
* Reset decoder WSProtocolDecoderNexus_TH.
|
||||
* @param context Pointer to a WSProtocolDecoderNexus_TH instance
|
||||
*/
|
||||
void ws_protocol_decoder_nexus_th_reset(void* context);
|
||||
|
||||
/**
|
||||
* Parse a raw sequence of levels and durations received from the air.
|
||||
* @param context Pointer to a WSProtocolDecoderNexus_TH instance
|
||||
* @param level Signal level true-high false-low
|
||||
* @param duration Duration of this level in, us
|
||||
*/
|
||||
void ws_protocol_decoder_nexus_th_feed(void* context, bool level, uint32_t duration);
|
||||
|
||||
/**
|
||||
* Getting the hash sum of the last randomly received parcel.
|
||||
* @param context Pointer to a WSProtocolDecoderNexus_TH instance
|
||||
* @return hash Hash sum
|
||||
*/
|
||||
uint8_t ws_protocol_decoder_nexus_th_get_hash_data(void* context);
|
||||
|
||||
/**
|
||||
* Serialize data WSProtocolDecoderNexus_TH.
|
||||
* @param context Pointer to a WSProtocolDecoderNexus_TH instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @param preset The modulation on which the signal was received, SubGhzPresetDefinition
|
||||
* @return true On success
|
||||
*/
|
||||
bool ws_protocol_decoder_nexus_th_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzPresetDefinition* preset);
|
||||
|
||||
/**
|
||||
* Deserialize data WSProtocolDecoderNexus_TH.
|
||||
* @param context Pointer to a WSProtocolDecoderNexus_TH instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @return true On success
|
||||
*/
|
||||
bool ws_protocol_decoder_nexus_th_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
|
||||
/**
|
||||
* Getting a textual representation of the received data.
|
||||
* @param context Pointer to a WSProtocolDecoderNexus_TH instance
|
||||
* @param output Resulting text
|
||||
*/
|
||||
void ws_protocol_decoder_nexus_th_get_string(void* context, FuriString* output);
|
||||
@@ -0,0 +1,12 @@
|
||||
#include "protocol_items.h"
|
||||
|
||||
const SubGhzProtocol* weather_station_protocol_registry_items[] = {
|
||||
&ws_protocol_infactory,
|
||||
&ws_protocol_thermopro_tx4,
|
||||
&ws_protocol_nexus_th,
|
||||
&ws_protocol_gt_wt_03,
|
||||
};
|
||||
|
||||
const SubGhzProtocolRegistry weather_station_protocol_registry = {
|
||||
.items = weather_station_protocol_registry_items,
|
||||
.size = COUNT_OF(weather_station_protocol_registry_items)};
|
||||
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
#include "../weather_station_app_i.h"
|
||||
|
||||
#include "infactory.h"
|
||||
#include "thermopro_tx4.h"
|
||||
#include "nexus_th.h"
|
||||
#include "gt_wt_03.h"
|
||||
|
||||
extern const SubGhzProtocolRegistry weather_station_protocol_registry;
|
||||
260
applications/plugins/weather_station/protocols/thermopro_tx4.c
Normal file
@@ -0,0 +1,260 @@
|
||||
#include "thermopro_tx4.h"
|
||||
|
||||
#define TAG "WSProtocolThermoPRO_TX4"
|
||||
|
||||
/*
|
||||
* Help
|
||||
* https://github.com/merbanan/rtl_433/blob/master/src/devices/thermopro_tx2.c
|
||||
*
|
||||
* The sensor sends 37 bits 6 times, before the first packet there is a sync pulse.
|
||||
* The packets are ppm modulated (distance coding) with a pulse of ~500 us
|
||||
* followed by a short gap of ~2000 us for a 0 bit or a long ~4000 us gap for a
|
||||
* 1 bit, the sync gap is ~9000 us.
|
||||
* The data is grouped in 9 nibbles
|
||||
* [type] [id0] [id1] [flags] [temp0] [temp1] [temp2] [humi0] [humi1]
|
||||
* - type: 4 bit fixed 1001 (9) or 0110 (5)
|
||||
* - id: 8 bit a random id that is generated when the sensor starts, could include battery status
|
||||
* the same batteries often generate the same id
|
||||
* - flags(3): is 1 when the battery is low, otherwise 0 (ok)
|
||||
* - flags(2): is 1 when the sensor sends a reading when pressing the button on the sensor
|
||||
* - flags(1,0): the channel number that can be set by the sensor (1, 2, 3, X)
|
||||
* - temp: 12 bit signed scaled by 10
|
||||
* - humi: 8 bit always 11001100 (0xCC) if no humidity sensor is available
|
||||
*
|
||||
*/
|
||||
|
||||
#define THERMO_PRO_TX4_TYPE_1 0b1001
|
||||
#define THERMO_PRO_TX4_TYPE_2 0b0110
|
||||
|
||||
static const SubGhzBlockConst ws_protocol_thermopro_tx4_const = {
|
||||
.te_short = 500,
|
||||
.te_long = 2000,
|
||||
.te_delta = 150,
|
||||
.min_count_bit_for_found = 37,
|
||||
};
|
||||
|
||||
struct WSProtocolDecoderThermoPRO_TX4 {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
|
||||
SubGhzBlockDecoder decoder;
|
||||
WSBlockGeneric generic;
|
||||
};
|
||||
|
||||
struct WSProtocolEncoderThermoPRO_TX4 {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
WSBlockGeneric generic;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
ThermoPRO_TX4DecoderStepReset = 0,
|
||||
ThermoPRO_TX4DecoderStepSaveDuration,
|
||||
ThermoPRO_TX4DecoderStepCheckDuration,
|
||||
} ThermoPRO_TX4DecoderStep;
|
||||
|
||||
const SubGhzProtocolDecoder ws_protocol_thermopro_tx4_decoder = {
|
||||
.alloc = ws_protocol_decoder_thermopro_tx4_alloc,
|
||||
.free = ws_protocol_decoder_thermopro_tx4_free,
|
||||
|
||||
.feed = ws_protocol_decoder_thermopro_tx4_feed,
|
||||
.reset = ws_protocol_decoder_thermopro_tx4_reset,
|
||||
|
||||
.get_hash_data = ws_protocol_decoder_thermopro_tx4_get_hash_data,
|
||||
.serialize = ws_protocol_decoder_thermopro_tx4_serialize,
|
||||
.deserialize = ws_protocol_decoder_thermopro_tx4_deserialize,
|
||||
.get_string = ws_protocol_decoder_thermopro_tx4_get_string,
|
||||
};
|
||||
|
||||
const SubGhzProtocolEncoder ws_protocol_thermopro_tx4_encoder = {
|
||||
.alloc = NULL,
|
||||
.free = NULL,
|
||||
|
||||
.deserialize = NULL,
|
||||
.stop = NULL,
|
||||
.yield = NULL,
|
||||
};
|
||||
|
||||
const SubGhzProtocol ws_protocol_thermopro_tx4 = {
|
||||
.name = WS_PROTOCOL_THERMOPRO_TX4_NAME,
|
||||
.type = SubGhzProtocolWeatherStation,
|
||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 |
|
||||
SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable,
|
||||
|
||||
.decoder = &ws_protocol_thermopro_tx4_decoder,
|
||||
.encoder = &ws_protocol_thermopro_tx4_encoder,
|
||||
};
|
||||
|
||||
void* ws_protocol_decoder_thermopro_tx4_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
WSProtocolDecoderThermoPRO_TX4* instance = malloc(sizeof(WSProtocolDecoderThermoPRO_TX4));
|
||||
instance->base.protocol = &ws_protocol_thermopro_tx4;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void ws_protocol_decoder_thermopro_tx4_free(void* context) {
|
||||
furi_assert(context);
|
||||
WSProtocolDecoderThermoPRO_TX4* instance = context;
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void ws_protocol_decoder_thermopro_tx4_reset(void* context) {
|
||||
furi_assert(context);
|
||||
WSProtocolDecoderThermoPRO_TX4* instance = context;
|
||||
instance->decoder.parser_step = ThermoPRO_TX4DecoderStepReset;
|
||||
}
|
||||
|
||||
static bool ws_protocol_thermopro_tx4_check(WSProtocolDecoderThermoPRO_TX4* instance) {
|
||||
uint8_t type = instance->decoder.decode_data >> 33;
|
||||
|
||||
if((type == THERMO_PRO_TX4_TYPE_1) || (type == THERMO_PRO_TX4_TYPE_2)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Analysis of received data
|
||||
* @param instance Pointer to a WSBlockGeneric* instance
|
||||
*/
|
||||
static void ws_protocol_thermopro_tx4_remote_controller(WSBlockGeneric* instance) {
|
||||
instance->id = (instance->data >> 25) & 0xFF;
|
||||
instance->battery_low = (instance->data >> 24) & 1;
|
||||
instance->btn = (instance->data >> 23) & 1;
|
||||
instance->channel = ((instance->data >> 21) & 0x03) + 1;
|
||||
|
||||
if(!((instance->data >> 20) & 1)) {
|
||||
instance->temp = (float)((instance->data >> 9) & 0x07FF) / 10.0f;
|
||||
} else {
|
||||
instance->temp = (float)((~(instance->data >> 9) & 0x07FF) + 1) / -10.0f;
|
||||
}
|
||||
|
||||
instance->humidity = (instance->data >> 1) & 0xFF;
|
||||
}
|
||||
|
||||
void ws_protocol_decoder_thermopro_tx4_feed(void* context, bool level, uint32_t duration) {
|
||||
furi_assert(context);
|
||||
WSProtocolDecoderThermoPRO_TX4* instance = context;
|
||||
|
||||
switch(instance->decoder.parser_step) {
|
||||
case ThermoPRO_TX4DecoderStepReset:
|
||||
if((!level) && (DURATION_DIFF(duration, ws_protocol_thermopro_tx4_const.te_short * 18) <
|
||||
ws_protocol_thermopro_tx4_const.te_delta * 10)) {
|
||||
//Found sync
|
||||
instance->decoder.parser_step = ThermoPRO_TX4DecoderStepSaveDuration;
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case ThermoPRO_TX4DecoderStepSaveDuration:
|
||||
if(level) {
|
||||
instance->decoder.te_last = duration;
|
||||
instance->decoder.parser_step = ThermoPRO_TX4DecoderStepCheckDuration;
|
||||
} else {
|
||||
instance->decoder.parser_step = ThermoPRO_TX4DecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case ThermoPRO_TX4DecoderStepCheckDuration:
|
||||
if(!level) {
|
||||
if(DURATION_DIFF(duration, ws_protocol_thermopro_tx4_const.te_short * 18) <
|
||||
ws_protocol_thermopro_tx4_const.te_delta * 10) {
|
||||
//Found sync
|
||||
instance->decoder.parser_step = ThermoPRO_TX4DecoderStepReset;
|
||||
if((instance->decoder.decode_count_bit ==
|
||||
ws_protocol_thermopro_tx4_const.min_count_bit_for_found) &&
|
||||
ws_protocol_thermopro_tx4_check(instance)) {
|
||||
instance->generic.data = instance->decoder.decode_data;
|
||||
instance->generic.data_count_bit = instance->decoder.decode_count_bit;
|
||||
ws_protocol_thermopro_tx4_remote_controller(&instance->generic);
|
||||
if(instance->base.callback)
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
instance->decoder.parser_step = ThermoPRO_TX4DecoderStepCheckDuration;
|
||||
}
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
|
||||
break;
|
||||
} else if(
|
||||
(DURATION_DIFF(
|
||||
instance->decoder.te_last, ws_protocol_thermopro_tx4_const.te_short) <
|
||||
ws_protocol_thermopro_tx4_const.te_delta) &&
|
||||
(DURATION_DIFF(duration, ws_protocol_thermopro_tx4_const.te_long) <
|
||||
ws_protocol_thermopro_tx4_const.te_delta * 2)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
|
||||
instance->decoder.parser_step = ThermoPRO_TX4DecoderStepSaveDuration;
|
||||
} else if(
|
||||
(DURATION_DIFF(
|
||||
instance->decoder.te_last, ws_protocol_thermopro_tx4_const.te_short) <
|
||||
ws_protocol_thermopro_tx4_const.te_delta) &&
|
||||
(DURATION_DIFF(duration, ws_protocol_thermopro_tx4_const.te_long * 2) <
|
||||
ws_protocol_thermopro_tx4_const.te_delta * 4)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
|
||||
instance->decoder.parser_step = ThermoPRO_TX4DecoderStepSaveDuration;
|
||||
} else {
|
||||
instance->decoder.parser_step = ThermoPRO_TX4DecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
instance->decoder.parser_step = ThermoPRO_TX4DecoderStepReset;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t ws_protocol_decoder_thermopro_tx4_get_hash_data(void* context) {
|
||||
furi_assert(context);
|
||||
WSProtocolDecoderThermoPRO_TX4* instance = context;
|
||||
return subghz_protocol_blocks_get_hash_data(
|
||||
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
|
||||
}
|
||||
|
||||
bool ws_protocol_decoder_thermopro_tx4_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzPresetDefinition* preset) {
|
||||
furi_assert(context);
|
||||
WSProtocolDecoderThermoPRO_TX4* instance = context;
|
||||
return ws_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
}
|
||||
|
||||
bool ws_protocol_decoder_thermopro_tx4_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_assert(context);
|
||||
WSProtocolDecoderThermoPRO_TX4* instance = context;
|
||||
bool ret = false;
|
||||
do {
|
||||
if(!ws_block_generic_deserialize(&instance->generic, flipper_format)) {
|
||||
break;
|
||||
}
|
||||
if(instance->generic.data_count_bit !=
|
||||
ws_protocol_thermopro_tx4_const.min_count_bit_for_found) {
|
||||
FURI_LOG_E(TAG, "Wrong number of bits in key");
|
||||
break;
|
||||
}
|
||||
ret = true;
|
||||
} while(false);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void ws_protocol_decoder_thermopro_tx4_get_string(void* context, FuriString* output) {
|
||||
furi_assert(context);
|
||||
WSProtocolDecoderThermoPRO_TX4* instance = context;
|
||||
furi_string_printf(
|
||||
output,
|
||||
"%s %dbit\r\n"
|
||||
"Key:0x%lX%08lX\r\n"
|
||||
"Sn:0x%lX Ch:%d Bat:%d\r\n"
|
||||
"Temp:%d.%d C Hum:%d%%",
|
||||
instance->generic.protocol_name,
|
||||
instance->generic.data_count_bit,
|
||||
(uint32_t)(instance->generic.data >> 32),
|
||||
(uint32_t)(instance->generic.data),
|
||||
instance->generic.id,
|
||||
instance->generic.channel,
|
||||
instance->generic.battery_low,
|
||||
(int16_t)instance->generic.temp,
|
||||
abs(((int16_t)(instance->generic.temp * 10) - (((int16_t)instance->generic.temp) * 10))),
|
||||
instance->generic.humidity);
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
#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 "ws_generic.h"
|
||||
#include <lib/subghz/blocks/math.h>
|
||||
|
||||
#define WS_PROTOCOL_THERMOPRO_TX4_NAME "ThermoPRO-TX4"
|
||||
|
||||
typedef struct WSProtocolDecoderThermoPRO_TX4 WSProtocolDecoderThermoPRO_TX4;
|
||||
typedef struct WSProtocolEncoderThermoPRO_TX4 WSProtocolEncoderThermoPRO_TX4;
|
||||
|
||||
extern const SubGhzProtocolDecoder ws_protocol_thermopro_tx4_decoder;
|
||||
extern const SubGhzProtocolEncoder ws_protocol_thermopro_tx4_encoder;
|
||||
extern const SubGhzProtocol ws_protocol_thermopro_tx4;
|
||||
|
||||
/**
|
||||
* Allocate WSProtocolDecoderThermoPRO_TX4.
|
||||
* @param environment Pointer to a SubGhzEnvironment instance
|
||||
* @return WSProtocolDecoderThermoPRO_TX4* pointer to a WSProtocolDecoderThermoPRO_TX4 instance
|
||||
*/
|
||||
void* ws_protocol_decoder_thermopro_tx4_alloc(SubGhzEnvironment* environment);
|
||||
|
||||
/**
|
||||
* Free WSProtocolDecoderThermoPRO_TX4.
|
||||
* @param context Pointer to a WSProtocolDecoderThermoPRO_TX4 instance
|
||||
*/
|
||||
void ws_protocol_decoder_thermopro_tx4_free(void* context);
|
||||
|
||||
/**
|
||||
* Reset decoder WSProtocolDecoderThermoPRO_TX4.
|
||||
* @param context Pointer to a WSProtocolDecoderThermoPRO_TX4 instance
|
||||
*/
|
||||
void ws_protocol_decoder_thermopro_tx4_reset(void* context);
|
||||
|
||||
/**
|
||||
* Parse a raw sequence of levels and durations received from the air.
|
||||
* @param context Pointer to a WSProtocolDecoderThermoPRO_TX4 instance
|
||||
* @param level Signal level true-high false-low
|
||||
* @param duration Duration of this level in, us
|
||||
*/
|
||||
void ws_protocol_decoder_thermopro_tx4_feed(void* context, bool level, uint32_t duration);
|
||||
|
||||
/**
|
||||
* Getting the hash sum of the last randomly received parcel.
|
||||
* @param context Pointer to a WSProtocolDecoderThermoPRO_TX4 instance
|
||||
* @return hash Hash sum
|
||||
*/
|
||||
uint8_t ws_protocol_decoder_thermopro_tx4_get_hash_data(void* context);
|
||||
|
||||
/**
|
||||
* Serialize data WSProtocolDecoderThermoPRO_TX4.
|
||||
* @param context Pointer to a WSProtocolDecoderThermoPRO_TX4 instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @param preset The modulation on which the signal was received, SubGhzPresetDefinition
|
||||
* @return true On success
|
||||
*/
|
||||
bool ws_protocol_decoder_thermopro_tx4_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzPresetDefinition* preset);
|
||||
|
||||
/**
|
||||
* Deserialize data WSProtocolDecoderThermoPRO_TX4.
|
||||
* @param context Pointer to a WSProtocolDecoderThermoPRO_TX4 instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @return true On success
|
||||
*/
|
||||
bool ws_protocol_decoder_thermopro_tx4_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
|
||||
/**
|
||||
* Getting a textual representation of the received data.
|
||||
* @param context Pointer to a WSProtocolDecoderThermoPRO_TX4 instance
|
||||
* @param output Resulting text
|
||||
*/
|
||||
void ws_protocol_decoder_thermopro_tx4_get_string(void* context, FuriString* output);
|
||||
198
applications/plugins/weather_station/protocols/ws_generic.c
Normal file
@@ -0,0 +1,198 @@
|
||||
#include "ws_generic.h"
|
||||
#include <lib/toolbox/stream/stream.h>
|
||||
#include <lib/flipper_format/flipper_format_i.h>
|
||||
#include "../helpers/weather_station_types.h"
|
||||
|
||||
#define TAG "WSBlockGeneric"
|
||||
|
||||
void ws_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 ws_block_generic_serialize(
|
||||
WSBlockGeneric* instance,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzPresetDefinition* 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, WS_KEY_FILE_TYPE, WS_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;
|
||||
}
|
||||
|
||||
ws_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;
|
||||
}
|
||||
|
||||
uint32_t temp_data = instance->id;
|
||||
if(!flipper_format_write_uint32(flipper_format, "Id", &temp_data, 1)) {
|
||||
FURI_LOG_E(TAG, "Unable to add Id");
|
||||
break;
|
||||
}
|
||||
|
||||
temp_data = instance->data_count_bit;
|
||||
if(!flipper_format_write_uint32(flipper_format, "Bit", &temp_data, 1)) {
|
||||
FURI_LOG_E(TAG, "Unable to add Bit");
|
||||
break;
|
||||
}
|
||||
|
||||
uint8_t key_data[sizeof(uint64_t)] = {0};
|
||||
for(size_t i = 0; i < sizeof(uint64_t); i++) {
|
||||
key_data[sizeof(uint64_t) - i - 1] = (instance->data >> i * 8) & 0xFF;
|
||||
}
|
||||
|
||||
if(!flipper_format_write_hex(flipper_format, "Data", key_data, sizeof(uint64_t))) {
|
||||
FURI_LOG_E(TAG, "Unable to add Data");
|
||||
break;
|
||||
}
|
||||
|
||||
temp_data = instance->battery_low;
|
||||
if(!flipper_format_write_uint32(flipper_format, "Batt", &temp_data, 1)) {
|
||||
FURI_LOG_E(TAG, "Unable to add Battery_low");
|
||||
break;
|
||||
}
|
||||
|
||||
temp_data = instance->humidity;
|
||||
if(!flipper_format_write_uint32(flipper_format, "Hum", &temp_data, 1)) {
|
||||
FURI_LOG_E(TAG, "Unable to add Humidity");
|
||||
break;
|
||||
}
|
||||
|
||||
temp_data = instance->channel;
|
||||
if(!flipper_format_write_uint32(flipper_format, "Ch", &temp_data, 1)) {
|
||||
FURI_LOG_E(TAG, "Unable to add Channel");
|
||||
break;
|
||||
}
|
||||
|
||||
// temp_data = instance->btn;
|
||||
// if(!flipper_format_write_uint32(flipper_format, "Btn", &temp_data, 1)) {
|
||||
// FURI_LOG_E(TAG, "Unable to add Btn");
|
||||
// break;
|
||||
// }
|
||||
|
||||
float temp = instance->temp;
|
||||
if(!flipper_format_write_float(flipper_format, "Temp", &temp, 1)) {
|
||||
FURI_LOG_E(TAG, "Unable to add Temperature");
|
||||
break;
|
||||
}
|
||||
|
||||
res = true;
|
||||
} while(false);
|
||||
furi_string_free(temp_str);
|
||||
return res;
|
||||
}
|
||||
|
||||
bool ws_block_generic_deserialize(WSBlockGeneric* instance, FlipperFormat* flipper_format) {
|
||||
furi_assert(instance);
|
||||
bool res = false;
|
||||
uint32_t temp_data = 0;
|
||||
|
||||
do {
|
||||
if(!flipper_format_rewind(flipper_format)) {
|
||||
FURI_LOG_E(TAG, "Rewind error");
|
||||
break;
|
||||
}
|
||||
|
||||
if(!flipper_format_read_uint32(flipper_format, "Id", (uint32_t*)&temp_data, 1)) {
|
||||
FURI_LOG_E(TAG, "Missing Id");
|
||||
break;
|
||||
}
|
||||
instance->id = (uint32_t)temp_data;
|
||||
|
||||
if(!flipper_format_read_uint32(flipper_format, "Bit", (uint32_t*)&temp_data, 1)) {
|
||||
FURI_LOG_E(TAG, "Missing Bit");
|
||||
break;
|
||||
}
|
||||
instance->data_count_bit = (uint8_t)temp_data;
|
||||
|
||||
uint8_t key_data[sizeof(uint64_t)] = {0};
|
||||
if(!flipper_format_read_hex(flipper_format, "Data", key_data, sizeof(uint64_t))) {
|
||||
FURI_LOG_E(TAG, "Missing Data");
|
||||
break;
|
||||
}
|
||||
|
||||
for(uint8_t i = 0; i < sizeof(uint64_t); i++) {
|
||||
instance->data = instance->data << 8 | key_data[i];
|
||||
}
|
||||
|
||||
if(!flipper_format_read_uint32(flipper_format, "Batt", (uint32_t*)&temp_data, 1)) {
|
||||
FURI_LOG_E(TAG, "Missing Battery_low");
|
||||
break;
|
||||
}
|
||||
instance->battery_low = (uint8_t)temp_data;
|
||||
|
||||
if(!flipper_format_read_uint32(flipper_format, "Hum", (uint32_t*)&temp_data, 1)) {
|
||||
FURI_LOG_E(TAG, "Missing Humidity");
|
||||
break;
|
||||
}
|
||||
instance->humidity = (uint8_t)temp_data;
|
||||
|
||||
if(!flipper_format_read_uint32(flipper_format, "Ch", (uint32_t*)&temp_data, 1)) {
|
||||
FURI_LOG_E(TAG, "Missing Channel");
|
||||
break;
|
||||
}
|
||||
instance->channel = (uint8_t)temp_data;
|
||||
|
||||
// if(!flipper_format_read_uint32(flipper_format, "Btn", (uint32_t*)&temp_data, 1)) {
|
||||
// FURI_LOG_E(TAG, "Missing Btn");
|
||||
// break;
|
||||
// }
|
||||
// instance->btn = (uint8_t)temp_data;
|
||||
|
||||
float temp;
|
||||
if(!flipper_format_read_float(flipper_format, "Temp", (float*)&temp, 1)) {
|
||||
FURI_LOG_E(TAG, "Missing Temperature");
|
||||
break;
|
||||
}
|
||||
instance->temp = temp;
|
||||
|
||||
res = true;
|
||||
} while(0);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
float ws_block_generic_fahrenheit_to_celsius(float fahrenheit) {
|
||||
return (fahrenheit - 32.0f) / 1.8f;
|
||||
}
|
||||
61
applications/plugins/weather_station/protocols/ws_generic.h
Normal file
@@ -0,0 +1,61 @@
|
||||
#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 WSBlockGeneric WSBlockGeneric;
|
||||
|
||||
struct WSBlockGeneric {
|
||||
const char* protocol_name;
|
||||
uint64_t data;
|
||||
uint32_t id;
|
||||
uint8_t data_count_bit;
|
||||
uint8_t battery_low;
|
||||
uint8_t humidity;
|
||||
uint8_t channel;
|
||||
uint8_t btn;
|
||||
float temp;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get name preset.
|
||||
* @param preset_name name preset
|
||||
* @param preset_str Output name preset
|
||||
*/
|
||||
void ws_block_generic_get_preset_name(const char* preset_name, FuriString* preset_str);
|
||||
|
||||
/**
|
||||
* Serialize data WSBlockGeneric.
|
||||
* @param instance Pointer to a WSBlockGeneric instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @param preset The modulation on which the signal was received, SubGhzPresetDefinition
|
||||
* @return true On success
|
||||
*/
|
||||
bool ws_block_generic_serialize(
|
||||
WSBlockGeneric* instance,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzPresetDefinition* preset);
|
||||
|
||||
/**
|
||||
* Deserialize data WSBlockGeneric.
|
||||
* @param instance Pointer to a WSBlockGeneric instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @return true On success
|
||||
*/
|
||||
bool ws_block_generic_deserialize(WSBlockGeneric* instance, FlipperFormat* flipper_format);
|
||||
|
||||
float ws_block_generic_fahrenheit_to_celsius(float fahrenheit);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,207 @@
|
||||
#include "../weather_station_app_i.h"
|
||||
#include "../views/weather_station_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 weather_station_scene_receiver_update_statusbar(void* context) {
|
||||
WeatherStationApp* app = context;
|
||||
FuriString* history_stat_str;
|
||||
history_stat_str = furi_string_alloc();
|
||||
if(!ws_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();
|
||||
|
||||
ws_get_frequency_modulation(app, frequency_str, modulation_str);
|
||||
|
||||
ws_view_receiver_add_data_statusbar(
|
||||
app->ws_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 {
|
||||
ws_view_receiver_add_data_statusbar(
|
||||
app->ws_receiver, furi_string_get_cstr(history_stat_str), "", "");
|
||||
}
|
||||
furi_string_free(history_stat_str);
|
||||
}
|
||||
|
||||
void weather_station_scene_receiver_callback(WSCustomEvent event, void* context) {
|
||||
furi_assert(context);
|
||||
WeatherStationApp* app = context;
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, event);
|
||||
}
|
||||
|
||||
static void weather_station_scene_receiver_add_to_history_callback(
|
||||
SubGhzReceiver* receiver,
|
||||
SubGhzProtocolDecoderBase* decoder_base,
|
||||
void* context) {
|
||||
furi_assert(context);
|
||||
WeatherStationApp* app = context;
|
||||
FuriString* str_buff;
|
||||
str_buff = furi_string_alloc();
|
||||
|
||||
if(ws_history_add_to_history(app->txrx->history, decoder_base, app->txrx->preset) ==
|
||||
WSHistoryStateAddKeyNewDada) {
|
||||
furi_string_reset(str_buff);
|
||||
|
||||
ws_history_get_text_item_menu(
|
||||
app->txrx->history, str_buff, ws_history_get_item(app->txrx->history) - 1);
|
||||
ws_view_receiver_add_item_to_menu(
|
||||
app->ws_receiver,
|
||||
furi_string_get_cstr(str_buff),
|
||||
ws_history_get_type_protocol(
|
||||
app->txrx->history, ws_history_get_item(app->txrx->history) - 1));
|
||||
|
||||
weather_station_scene_receiver_update_statusbar(app);
|
||||
notification_message(app->notifications, &sequence_blink_green_10);
|
||||
if(app->lock != WSLockOn) {
|
||||
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 = WSRxKeyStateAddKey;
|
||||
}
|
||||
|
||||
void weather_station_scene_receiver_on_enter(void* context) {
|
||||
WeatherStationApp* app = context;
|
||||
|
||||
FuriString* str_buff;
|
||||
str_buff = furi_string_alloc();
|
||||
|
||||
if(app->txrx->rx_key_state == WSRxKeyStateIDLE) {
|
||||
ws_preset_init(app, "AM650", subghz_setting_get_default_frequency(app->setting), NULL, 0);
|
||||
ws_history_reset(app->txrx->history);
|
||||
app->txrx->rx_key_state = WSRxKeyStateStart;
|
||||
}
|
||||
|
||||
ws_view_receiver_set_lock(app->ws_receiver, app->lock);
|
||||
|
||||
//Load history to receiver
|
||||
ws_view_receiver_exit(app->ws_receiver);
|
||||
for(uint8_t i = 0; i < ws_history_get_item(app->txrx->history); i++) {
|
||||
furi_string_reset(str_buff);
|
||||
ws_history_get_text_item_menu(app->txrx->history, str_buff, i);
|
||||
ws_view_receiver_add_item_to_menu(
|
||||
app->ws_receiver,
|
||||
furi_string_get_cstr(str_buff),
|
||||
ws_history_get_type_protocol(app->txrx->history, i));
|
||||
app->txrx->rx_key_state = WSRxKeyStateAddKey;
|
||||
}
|
||||
furi_string_free(str_buff);
|
||||
weather_station_scene_receiver_update_statusbar(app);
|
||||
|
||||
ws_view_receiver_set_callback(app->ws_receiver, weather_station_scene_receiver_callback, app);
|
||||
subghz_receiver_set_rx_callback(
|
||||
app->txrx->receiver, weather_station_scene_receiver_add_to_history_callback, app);
|
||||
|
||||
if(app->txrx->txrx_state == WSTxRxStateRx) {
|
||||
ws_rx_end(app);
|
||||
};
|
||||
if((app->txrx->txrx_state == WSTxRxStateIDLE) || (app->txrx->txrx_state == WSTxRxStateSleep)) {
|
||||
ws_begin(
|
||||
app,
|
||||
subghz_setting_get_preset_data_by_name(
|
||||
app->setting, furi_string_get_cstr(app->txrx->preset->name)));
|
||||
|
||||
ws_rx(app, app->txrx->preset->frequency);
|
||||
}
|
||||
|
||||
ws_view_receiver_set_idx_menu(app->ws_receiver, app->txrx->idx_menu_chosen);
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, WeatherStationViewReceiver);
|
||||
}
|
||||
|
||||
bool weather_station_scene_receiver_on_event(void* context, SceneManagerEvent event) {
|
||||
WeatherStationApp* app = context;
|
||||
bool consumed = false;
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
switch(event.event) {
|
||||
case WSCustomEventViewReceiverBack:
|
||||
// Stop CC1101 Rx
|
||||
if(app->txrx->txrx_state == WSTxRxStateRx) {
|
||||
ws_rx_end(app);
|
||||
ws_sleep(app);
|
||||
};
|
||||
app->txrx->hopper_state = WSHopperStateOFF;
|
||||
app->txrx->idx_menu_chosen = 0;
|
||||
subghz_receiver_set_rx_callback(app->txrx->receiver, NULL, app);
|
||||
|
||||
app->txrx->rx_key_state = WSRxKeyStateIDLE;
|
||||
ws_preset_init(
|
||||
app, "AM650", subghz_setting_get_default_frequency(app->setting), NULL, 0);
|
||||
scene_manager_search_and_switch_to_previous_scene(
|
||||
app->scene_manager, WeatherStationSceneStart);
|
||||
consumed = true;
|
||||
break;
|
||||
case WSCustomEventViewReceiverOK:
|
||||
app->txrx->idx_menu_chosen = ws_view_receiver_get_idx_menu(app->ws_receiver);
|
||||
scene_manager_next_scene(app->scene_manager, WeatherStationSceneReceiverInfo);
|
||||
consumed = true;
|
||||
break;
|
||||
case WSCustomEventViewReceiverConfig:
|
||||
app->txrx->idx_menu_chosen = ws_view_receiver_get_idx_menu(app->ws_receiver);
|
||||
scene_manager_next_scene(app->scene_manager, WeatherStationSceneReceiverConfig);
|
||||
consumed = true;
|
||||
break;
|
||||
case WSCustomEventViewReceiverOffDisplay:
|
||||
notification_message(app->notifications, &sequence_display_backlight_off);
|
||||
consumed = true;
|
||||
break;
|
||||
case WSCustomEventViewReceiverUnlock:
|
||||
app->lock = WSLockOff;
|
||||
consumed = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else if(event.type == SceneManagerEventTypeTick) {
|
||||
if(app->txrx->hopper_state != WSHopperStateOFF) {
|
||||
ws_hopper_update(app);
|
||||
weather_station_scene_receiver_update_statusbar(app);
|
||||
}
|
||||
if(app->txrx->txrx_state == WSTxRxStateRx) {
|
||||
notification_message(app->notifications, &sequence_blink_cyan_10);
|
||||
}
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void weather_station_scene_receiver_on_exit(void* context) {
|
||||
UNUSED(context);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
#include "../weather_station_app_i.h"
|
||||
|
||||
// Generate scene on_enter handlers array
|
||||
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
|
||||
void (*const weather_station_scene_on_enter_handlers[])(void*) = {
|
||||
#include "weather_station_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 weather_station_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = {
|
||||
#include "weather_station_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 weather_station_scene_on_exit_handlers[])(void* context) = {
|
||||
#include "weather_station_scene_config.h"
|
||||
};
|
||||
#undef ADD_SCENE
|
||||
|
||||
// Initialize scene handlers configuration structure
|
||||
const SceneManagerHandlers weather_station_scene_handlers = {
|
||||
.on_enter_handlers = weather_station_scene_on_enter_handlers,
|
||||
.on_event_handlers = weather_station_scene_on_event_handlers,
|
||||
.on_exit_handlers = weather_station_scene_on_exit_handlers,
|
||||
.scene_num = WeatherStationSceneNum,
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <gui/scene_manager.h>
|
||||
|
||||
// Generate scene id and total number
|
||||
#define ADD_SCENE(prefix, name, id) WeatherStationScene##id,
|
||||
typedef enum {
|
||||
#include "weather_station_scene_config.h"
|
||||
WeatherStationSceneNum,
|
||||
} WeatherStationScene;
|
||||
#undef ADD_SCENE
|
||||
|
||||
extern const SceneManagerHandlers weather_station_scene_handlers;
|
||||
|
||||
// Generate scene on_enter handlers declaration
|
||||
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*);
|
||||
#include "weather_station_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 "weather_station_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 "weather_station_scene_config.h"
|
||||
#undef ADD_SCENE
|
||||
@@ -0,0 +1,78 @@
|
||||
#include "../weather_station_app_i.h"
|
||||
#include "../helpers/weather_station_types.h"
|
||||
|
||||
void weather_station_scene_about_widget_callback(
|
||||
GuiButtonType result,
|
||||
InputType type,
|
||||
void* context) {
|
||||
WeatherStationApp* app = context;
|
||||
if(type == InputTypeShort) {
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, result);
|
||||
}
|
||||
}
|
||||
|
||||
void weather_station_scene_about_on_enter(void* context) {
|
||||
WeatherStationApp* 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", WS_VERSION_APP);
|
||||
furi_string_cat_printf(temp_str, "Developed by: %s\n", WS_DEVELOPED);
|
||||
furi_string_cat_printf(temp_str, "Github: %s\n\n", WS_GITHUB);
|
||||
|
||||
furi_string_cat_printf(temp_str, "\e#%s\n", "Description");
|
||||
furi_string_cat_printf(
|
||||
temp_str, "Reading messages from\nweather station that work\nwith SubGhz sensors\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! Weather station \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, WeatherStationViewWidget);
|
||||
}
|
||||
|
||||
bool weather_station_scene_about_on_event(void* context, SceneManagerEvent event) {
|
||||
WeatherStationApp* app = context;
|
||||
bool consumed = false;
|
||||
UNUSED(app);
|
||||
UNUSED(event);
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void weather_station_scene_about_on_exit(void* context) {
|
||||
WeatherStationApp* app = context;
|
||||
|
||||
// Clear views
|
||||
widget_reset(app->widget);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
ADD_SCENE(weather_station, start, Start)
|
||||
ADD_SCENE(weather_station, about, About)
|
||||
ADD_SCENE(weather_station, receiver, Receiver)
|
||||
ADD_SCENE(weather_station, receiver_config, ReceiverConfig)
|
||||
ADD_SCENE(weather_station, receiver_info, ReceiverInfo)
|
||||
@@ -0,0 +1,223 @@
|
||||
#include "../weather_station_app_i.h"
|
||||
|
||||
enum WSSettingIndex {
|
||||
WSSettingIndexFrequency,
|
||||
WSSettingIndexHopping,
|
||||
WSSettingIndexModulation,
|
||||
WSSettingIndexLock,
|
||||
};
|
||||
|
||||
#define HOPPING_COUNT 2
|
||||
const char* const hopping_text[HOPPING_COUNT] = {
|
||||
"OFF",
|
||||
"ON",
|
||||
};
|
||||
const uint32_t hopping_value[HOPPING_COUNT] = {
|
||||
WSHopperStateOFF,
|
||||
WSHopperStateRunnig,
|
||||
};
|
||||
|
||||
uint8_t weather_station_scene_receiver_config_next_frequency(const uint32_t value, void* context) {
|
||||
furi_assert(context);
|
||||
WeatherStationApp* 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 weather_station_scene_receiver_config_next_preset(const char* preset_name, void* context) {
|
||||
furi_assert(context);
|
||||
WeatherStationApp* 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 weather_station_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);
|
||||
WeatherStationApp* app = context;
|
||||
|
||||
if(value == values[0]) {
|
||||
return 0;
|
||||
} else {
|
||||
variable_item_set_current_value_text(
|
||||
(VariableItem*)scene_manager_get_scene_state(
|
||||
app->scene_manager, WeatherStationSceneReceiverConfig),
|
||||
" -----");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
static void weather_station_scene_receiver_config_set_frequency(VariableItem* item) {
|
||||
WeatherStationApp* app = variable_item_get_context(item);
|
||||
uint8_t index = variable_item_get_current_value_index(item);
|
||||
|
||||
if(app->txrx->hopper_state == WSHopperStateOFF) {
|
||||
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 weather_station_scene_receiver_config_set_preset(VariableItem* item) {
|
||||
WeatherStationApp* 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));
|
||||
ws_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 weather_station_scene_receiver_config_set_hopping_running(VariableItem* item) {
|
||||
WeatherStationApp* 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] == WSHopperStateOFF) {
|
||||
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, WeatherStationSceneReceiverConfig),
|
||||
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, WeatherStationSceneReceiverConfig),
|
||||
subghz_setting_get_frequency_default_index(app->setting));
|
||||
} else {
|
||||
variable_item_set_current_value_text(
|
||||
(VariableItem*)scene_manager_get_scene_state(
|
||||
app->scene_manager, WeatherStationSceneReceiverConfig),
|
||||
" -----");
|
||||
variable_item_set_current_value_index(
|
||||
(VariableItem*)scene_manager_get_scene_state(
|
||||
app->scene_manager, WeatherStationSceneReceiverConfig),
|
||||
subghz_setting_get_frequency_default_index(app->setting));
|
||||
}
|
||||
|
||||
app->txrx->hopper_state = hopping_value[index];
|
||||
}
|
||||
|
||||
static void
|
||||
weather_station_scene_receiver_config_var_list_enter_callback(void* context, uint32_t index) {
|
||||
furi_assert(context);
|
||||
WeatherStationApp* app = context;
|
||||
if(index == WSSettingIndexLock) {
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, WSCustomEventSceneSettingLock);
|
||||
}
|
||||
}
|
||||
|
||||
void weather_station_scene_receiver_config_on_enter(void* context) {
|
||||
WeatherStationApp* 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),
|
||||
weather_station_scene_receiver_config_set_frequency,
|
||||
app);
|
||||
value_index =
|
||||
weather_station_scene_receiver_config_next_frequency(app->txrx->preset->frequency, app);
|
||||
scene_manager_set_scene_state(
|
||||
app->scene_manager, WeatherStationSceneReceiverConfig, (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,
|
||||
weather_station_scene_receiver_config_set_hopping_running,
|
||||
app);
|
||||
value_index = weather_station_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),
|
||||
weather_station_scene_receiver_config_set_preset,
|
||||
app);
|
||||
value_index = weather_station_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,
|
||||
weather_station_scene_receiver_config_var_list_enter_callback,
|
||||
app);
|
||||
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, WeatherStationViewVariableItemList);
|
||||
}
|
||||
|
||||
bool weather_station_scene_receiver_config_on_event(void* context, SceneManagerEvent event) {
|
||||
WeatherStationApp* app = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == WSCustomEventSceneSettingLock) {
|
||||
app->lock = WSLockOn;
|
||||
scene_manager_previous_scene(app->scene_manager);
|
||||
consumed = true;
|
||||
}
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void weather_station_scene_receiver_config_on_exit(void* context) {
|
||||
WeatherStationApp* 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 "../weather_station_app_i.h"
|
||||
#include "../views/weather_station_receiver.h"
|
||||
|
||||
void weather_station_scene_receiver_info_callback(WSCustomEvent event, void* context) {
|
||||
furi_assert(context);
|
||||
WeatherStationApp* app = context;
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, event);
|
||||
}
|
||||
|
||||
static void weather_station_scene_receiver_info_add_to_history_callback(
|
||||
SubGhzReceiver* receiver,
|
||||
SubGhzProtocolDecoderBase* decoder_base,
|
||||
void* context) {
|
||||
furi_assert(context);
|
||||
WeatherStationApp* app = context;
|
||||
|
||||
if(ws_history_add_to_history(app->txrx->history, decoder_base, app->txrx->preset) ==
|
||||
WSHistoryStateAddKeyUpdateData) {
|
||||
ws_view_receiver_info_update(
|
||||
app->ws_receiver_info,
|
||||
ws_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 = WSRxKeyStateAddKey;
|
||||
}
|
||||
}
|
||||
|
||||
void weather_station_scene_receiver_info_on_enter(void* context) {
|
||||
WeatherStationApp* app = context;
|
||||
|
||||
subghz_receiver_set_rx_callback(
|
||||
app->txrx->receiver, weather_station_scene_receiver_info_add_to_history_callback, app);
|
||||
ws_view_receiver_info_update(
|
||||
app->ws_receiver_info,
|
||||
ws_history_get_raw_data(app->txrx->history, app->txrx->idx_menu_chosen));
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, WeatherStationViewReceiverInfo);
|
||||
}
|
||||
|
||||
bool weather_station_scene_receiver_info_on_event(void* context, SceneManagerEvent event) {
|
||||
WeatherStationApp* app = context;
|
||||
bool consumed = false;
|
||||
UNUSED(app);
|
||||
UNUSED(event);
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void weather_station_scene_receiver_info_on_exit(void* context) {
|
||||
UNUSED(context);
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
#include "../weather_station_app_i.h"
|
||||
|
||||
typedef enum {
|
||||
SubmenuIndexWeatherStationReceiver,
|
||||
SubmenuIndexWeatherStationAbout,
|
||||
} SubmenuIndex;
|
||||
|
||||
void weather_station_scene_start_submenu_callback(void* context, uint32_t index) {
|
||||
WeatherStationApp* app = context;
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, index);
|
||||
}
|
||||
|
||||
void weather_station_scene_start_on_enter(void* context) {
|
||||
UNUSED(context);
|
||||
WeatherStationApp* app = context;
|
||||
Submenu* submenu = app->submenu;
|
||||
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
"Read Weather Station",
|
||||
SubmenuIndexWeatherStationReceiver,
|
||||
weather_station_scene_start_submenu_callback,
|
||||
app);
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
"About",
|
||||
SubmenuIndexWeatherStationAbout,
|
||||
weather_station_scene_start_submenu_callback,
|
||||
app);
|
||||
|
||||
submenu_set_selected_item(
|
||||
submenu, scene_manager_get_scene_state(app->scene_manager, WeatherStationSceneStart));
|
||||
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, WeatherStationViewSubmenu);
|
||||
}
|
||||
|
||||
bool weather_station_scene_start_on_event(void* context, SceneManagerEvent event) {
|
||||
WeatherStationApp* app = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == SubmenuIndexWeatherStationAbout) {
|
||||
scene_manager_next_scene(app->scene_manager, WeatherStationSceneAbout);
|
||||
consumed = true;
|
||||
} else if(event.event == SubmenuIndexWeatherStationReceiver) {
|
||||
scene_manager_next_scene(app->scene_manager, WeatherStationSceneReceiver);
|
||||
consumed = true;
|
||||
}
|
||||
scene_manager_set_scene_state(app->scene_manager, WeatherStationSceneStart, event.event);
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void weather_station_scene_start_on_exit(void* context) {
|
||||
WeatherStationApp* app = context;
|
||||
submenu_reset(app->submenu);
|
||||
}
|
||||