Merge branch 'dev' into ntag-auto-pwd-capture
2
.github/workflows/build.yml
vendored
@@ -127,7 +127,7 @@ jobs:
|
|||||||
**Compiled firmware for commit `${{steps.names.outputs.commit_sha}}`:**
|
**Compiled firmware for commit `${{steps.names.outputs.commit_sha}}`:**
|
||||||
- [📦 Update package](https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.branch_name}}/flipper-z-${{steps.names.outputs.default_target}}-update-${{steps.names.outputs.suffix}}.tgz)
|
- [📦 Update package](https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.branch_name}}/flipper-z-${{steps.names.outputs.default_target}}-update-${{steps.names.outputs.suffix}}.tgz)
|
||||||
- [📥 DFU file](https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.branch_name}}/flipper-z-${{steps.names.outputs.default_target}}-full-${{steps.names.outputs.suffix}}.dfu)
|
- [📥 DFU file](https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.branch_name}}/flipper-z-${{steps.names.outputs.default_target}}-full-${{steps.names.outputs.suffix}}.dfu)
|
||||||
- [☁️ Web/App updater](https://my.flipp.dev/?url=https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.branch_name}}/flipper-z-${{steps.names.outputs.default_target}}-update-${{steps.names.outputs.suffix}}.tgz&channel=${{steps.names.outputs.branch_name}}&version=${{steps.names.outputs.commit_sha}})
|
- [☁️ Web/App updater](https://lab.flipper.net/?url=https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.branch_name}}/flipper-z-${{steps.names.outputs.default_target}}-update-${{steps.names.outputs.suffix}}.tgz&channel=${{steps.names.outputs.branch_name}}&version=${{steps.names.outputs.commit_sha}})
|
||||||
edit-mode: replace
|
edit-mode: replace
|
||||||
|
|
||||||
compact:
|
compact:
|
||||||
|
|||||||
1
.github/workflows/pvs_studio.yml
vendored
@@ -65,7 +65,6 @@ jobs:
|
|||||||
pvs-studio-analyzer credentials ${{ secrets.PVS_STUDIO_CREDENTIALS }}
|
pvs-studio-analyzer credentials ${{ secrets.PVS_STUDIO_CREDENTIALS }}
|
||||||
pvs-studio-analyzer analyze \
|
pvs-studio-analyzer analyze \
|
||||||
@.pvsoptions \
|
@.pvsoptions \
|
||||||
--disableLicenseExpirationCheck \
|
|
||||||
-j$(grep -c processor /proc/cpuinfo) \
|
-j$(grep -c processor /proc/cpuinfo) \
|
||||||
-f build/f7-firmware-DC/compile_commands.json \
|
-f build/f7-firmware-DC/compile_commands.json \
|
||||||
-o PVS-Studio.log
|
-o PVS-Studio.log
|
||||||
|
|||||||
6
.gitmodules
vendored
@@ -22,12 +22,12 @@
|
|||||||
[submodule "lib/microtar"]
|
[submodule "lib/microtar"]
|
||||||
path = lib/microtar
|
path = lib/microtar
|
||||||
url = https://github.com/amachronic/microtar.git
|
url = https://github.com/amachronic/microtar.git
|
||||||
[submodule "lib/scons"]
|
|
||||||
path = lib/scons
|
|
||||||
url = https://github.com/SCons/scons.git
|
|
||||||
[submodule "lib/mbedtls"]
|
[submodule "lib/mbedtls"]
|
||||||
path = lib/mbedtls
|
path = lib/mbedtls
|
||||||
url = https://github.com/Mbed-TLS/mbedtls.git
|
url = https://github.com/Mbed-TLS/mbedtls.git
|
||||||
[submodule "lib/cxxheaderparser"]
|
[submodule "lib/cxxheaderparser"]
|
||||||
path = lib/cxxheaderparser
|
path = lib/cxxheaderparser
|
||||||
url = https://github.com/robotpy/cxxheaderparser.git
|
url = https://github.com/robotpy/cxxheaderparser.git
|
||||||
|
[submodule "applications/plugins/dap_link/lib/free-dap"]
|
||||||
|
path = applications/plugins/dap_link/lib/free-dap
|
||||||
|
url = https://github.com/ataradov/free-dap.git
|
||||||
|
|||||||
2
.vscode/example/c_cpp_properties.json
vendored
@@ -2,7 +2,7 @@
|
|||||||
"configurations": [
|
"configurations": [
|
||||||
{
|
{
|
||||||
"name": "Win32",
|
"name": "Win32",
|
||||||
"compilerPath": "${workspaceFolder}/toolchain/i686-windows/bin/arm-none-eabi-gcc.exe",
|
"compilerPath": "${workspaceFolder}/toolchain/x86_64-windows/bin/arm-none-eabi-gcc.exe",
|
||||||
"intelliSenseMode": "gcc-arm",
|
"intelliSenseMode": "gcc-arm",
|
||||||
"compileCommands": "${workspaceFolder}/build/latest/compile_commands.json",
|
"compileCommands": "${workspaceFolder}/build/latest/compile_commands.json",
|
||||||
"configurationProvider": "ms-vscode.cpptools",
|
"configurationProvider": "ms-vscode.cpptools",
|
||||||
|
|||||||
19
.vscode/example/launch.json
vendored
@@ -79,6 +79,25 @@
|
|||||||
]
|
]
|
||||||
// "showDevDebugOutput": "raw",
|
// "showDevDebugOutput": "raw",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Attach FW (DAP)",
|
||||||
|
"cwd": "${workspaceFolder}",
|
||||||
|
"executable": "./build/latest/firmware.elf",
|
||||||
|
"request": "attach",
|
||||||
|
"type": "cortex-debug",
|
||||||
|
"servertype": "openocd",
|
||||||
|
"device": "cmsis-dap",
|
||||||
|
"svdFile": "./debug/STM32WB55_CM4.svd",
|
||||||
|
"rtos": "FreeRTOS",
|
||||||
|
"configFiles": [
|
||||||
|
"interface/cmsis-dap.cfg",
|
||||||
|
"./debug/stm32wbx.cfg",
|
||||||
|
],
|
||||||
|
"postAttachCommands": [
|
||||||
|
"source debug/flipperapps.py",
|
||||||
|
],
|
||||||
|
// "showDevDebugOutput": "raw",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "fbt debug",
|
"name": "fbt debug",
|
||||||
"type": "python",
|
"type": "python",
|
||||||
|
|||||||
6
.vscode/example/settings.json
vendored
@@ -6,13 +6,13 @@
|
|||||||
"cortex-debug.enableTelemetry": false,
|
"cortex-debug.enableTelemetry": false,
|
||||||
"cortex-debug.variableUseNaturalFormat": true,
|
"cortex-debug.variableUseNaturalFormat": true,
|
||||||
"cortex-debug.showRTOS": true,
|
"cortex-debug.showRTOS": true,
|
||||||
"cortex-debug.armToolchainPath.windows": "${workspaceFolder}/toolchain/i686-windows/bin",
|
"cortex-debug.armToolchainPath.windows": "${workspaceFolder}/toolchain/x86_64-windows/bin",
|
||||||
"cortex-debug.armToolchainPath.linux": "${workspaceFolder}/toolchain/x86_64-linux/bin",
|
"cortex-debug.armToolchainPath.linux": "${workspaceFolder}/toolchain/x86_64-linux/bin",
|
||||||
"cortex-debug.armToolchainPath.osx": "${workspaceFolder}/toolchain/x86_64-darwin/bin",
|
"cortex-debug.armToolchainPath.osx": "${workspaceFolder}/toolchain/x86_64-darwin/bin",
|
||||||
"cortex-debug.openocdPath.windows": "${workspaceFolder}/toolchain/i686-windows/openocd/bin/openocd.exe",
|
"cortex-debug.openocdPath.windows": "${workspaceFolder}/toolchain/x86_64-windows/openocd/bin/openocd.exe",
|
||||||
"cortex-debug.openocdPath.linux": "${workspaceFolder}/toolchain/x86_64-linux/openocd/bin/openocd",
|
"cortex-debug.openocdPath.linux": "${workspaceFolder}/toolchain/x86_64-linux/openocd/bin/openocd",
|
||||||
"cortex-debug.openocdPath.osx": "${workspaceFolder}/toolchain/x86_64-darwin/openocd/bin/openocd",
|
"cortex-debug.openocdPath.osx": "${workspaceFolder}/toolchain/x86_64-darwin/openocd/bin/openocd",
|
||||||
"cortex-debug.gdbPath.windows": "${workspaceFolder}/toolchain/i686-windows/bin/arm-none-eabi-gdb-py.bat",
|
"cortex-debug.gdbPath.windows": "${workspaceFolder}/toolchain/x86_64-windows/bin/arm-none-eabi-gdb-py.bat",
|
||||||
"cortex-debug.gdbPath.linux": "${workspaceFolder}/toolchain/x86_64-linux/bin/arm-none-eabi-gdb-py",
|
"cortex-debug.gdbPath.linux": "${workspaceFolder}/toolchain/x86_64-linux/bin/arm-none-eabi-gdb-py",
|
||||||
"cortex-debug.gdbPath.osx": "${workspaceFolder}/toolchain/x86_64-darwin/bin/arm-none-eabi-gdb-py",
|
"cortex-debug.gdbPath.osx": "${workspaceFolder}/toolchain/x86_64-darwin/bin/arm-none-eabi-gdb-py",
|
||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
|
|||||||
4
.vscode/example/tasks.json
vendored
@@ -109,13 +109,13 @@
|
|||||||
"label": "[Debug] Build FAPs",
|
"label": "[Debug] Build FAPs",
|
||||||
"group": "build",
|
"group": "build",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "./fbt plugin_dist"
|
"command": "./fbt fap_dist"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "[Release] Build FAPs",
|
"label": "[Release] Build FAPs",
|
||||||
"group": "build",
|
"group": "build",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "./fbt COMPACT=1 DEBUG=0 plugin_dist"
|
"command": "./fbt COMPACT=1 DEBUG=0 fap_dist"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "[Debug] Launch App on Flipper",
|
"label": "[Debug] Launch App on Flipper",
|
||||||
|
|||||||
16
SConstruct
@@ -156,11 +156,9 @@ Depends(fap_dist, firmware_env["FW_EXTAPPS"]["validators"].values())
|
|||||||
Alias("fap_dist", fap_dist)
|
Alias("fap_dist", fap_dist)
|
||||||
# distenv.Default(fap_dist)
|
# distenv.Default(fap_dist)
|
||||||
|
|
||||||
plugin_resources_dist = list(
|
distenv.Depends(
|
||||||
distenv.Install(f"#/assets/resources/apps/{dist_entry[0]}", dist_entry[1])
|
firmware_env["FW_RESOURCES"], firmware_env["FW_EXTAPPS"]["resources_dist"]
|
||||||
for dist_entry in firmware_env["FW_EXTAPPS"]["dist"].values()
|
|
||||||
)
|
)
|
||||||
distenv.Depends(firmware_env["FW_RESOURCES"], plugin_resources_dist)
|
|
||||||
|
|
||||||
|
|
||||||
# Target for bundling core2 package for qFlipper
|
# Target for bundling core2 package for qFlipper
|
||||||
@@ -291,6 +289,16 @@ distenv.PhonyTarget(
|
|||||||
"@echo $( ${BLACKMAGIC_ADDR} $)",
|
"@echo $( ${BLACKMAGIC_ADDR} $)",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Find STLink probe ids
|
||||||
|
distenv.PhonyTarget(
|
||||||
|
"get_stlink",
|
||||||
|
distenv.Action(
|
||||||
|
lambda **kw: distenv.GetDevices(),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
# Prepare vscode environment
|
# Prepare vscode environment
|
||||||
vscode_dist = distenv.Install("#.vscode", distenv.Glob("#.vscode/example/*"))
|
vscode_dist = distenv.Install("#.vscode", distenv.Glob("#.vscode/example/*"))
|
||||||
distenv.Precious(vscode_dist)
|
distenv.Precious(vscode_dist)
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
#define NFC_TEST_RESOURCES_DIR EXT_PATH("unit_tests/nfc/")
|
#define NFC_TEST_RESOURCES_DIR EXT_PATH("unit_tests/nfc/")
|
||||||
#define NFC_TEST_SIGNAL_SHORT_FILE "nfc_nfca_signal_short.nfc"
|
#define NFC_TEST_SIGNAL_SHORT_FILE "nfc_nfca_signal_short.nfc"
|
||||||
#define NFC_TEST_SIGNAL_LONG_FILE "nfc_nfca_signal_long.nfc"
|
#define NFC_TEST_SIGNAL_LONG_FILE "nfc_nfca_signal_long.nfc"
|
||||||
|
#define NFC_TEST_DICT_PATH EXT_PATH("unit_tests/mf_classic_dict.nfc")
|
||||||
|
|
||||||
static const char* nfc_test_file_type = "Flipper NFC test";
|
static const char* nfc_test_file_type = "Flipper NFC test";
|
||||||
static const uint32_t nfc_test_file_version = 1;
|
static const uint32_t nfc_test_file_version = 1;
|
||||||
@@ -220,11 +221,78 @@ MU_TEST(mf_classic_dict_test) {
|
|||||||
furi_string_free(temp_str);
|
furi_string_free(temp_str);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MU_TEST(mf_classic_dict_load_test) {
|
||||||
|
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||||
|
mu_assert(storage != NULL, "storage != NULL assert failed\r\n");
|
||||||
|
|
||||||
|
// Delete unit test dict file if exists
|
||||||
|
if(storage_file_exists(storage, NFC_TEST_DICT_PATH)) {
|
||||||
|
mu_assert(
|
||||||
|
storage_simply_remove(storage, NFC_TEST_DICT_PATH),
|
||||||
|
"remove == true assert failed\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create unit test dict file
|
||||||
|
Stream* file_stream = file_stream_alloc(storage);
|
||||||
|
mu_assert(file_stream != NULL, "file_stream != NULL assert failed\r\n");
|
||||||
|
mu_assert(
|
||||||
|
file_stream_open(file_stream, NFC_TEST_DICT_PATH, FSAM_WRITE, FSOM_OPEN_ALWAYS),
|
||||||
|
"file_stream_open == true assert failed\r\n");
|
||||||
|
|
||||||
|
// Write unit test dict file
|
||||||
|
char key_str[] = "a0a1a2a3a4a5";
|
||||||
|
mu_assert(
|
||||||
|
stream_write_cstring(file_stream, key_str) == strlen(key_str),
|
||||||
|
"write == true assert failed\r\n");
|
||||||
|
// Close unit test dict file
|
||||||
|
mu_assert(file_stream_close(file_stream), "file_stream_close == true assert failed\r\n");
|
||||||
|
|
||||||
|
// Load unit test dict file
|
||||||
|
MfClassicDict* instance = NULL;
|
||||||
|
instance = mf_classic_dict_alloc(MfClassicDictTypeUnitTest);
|
||||||
|
mu_assert(instance != NULL, "mf_classic_dict_alloc\r\n");
|
||||||
|
uint32_t total_keys = mf_classic_dict_get_total_keys(instance);
|
||||||
|
mu_assert(total_keys == 1, "total_keys == 1 assert failed\r\n");
|
||||||
|
|
||||||
|
// Read key
|
||||||
|
uint64_t key_ref = 0xa0a1a2a3a4a5;
|
||||||
|
uint64_t key_dut = 0;
|
||||||
|
FuriString* temp_str = furi_string_alloc();
|
||||||
|
mu_assert(
|
||||||
|
mf_classic_dict_get_next_key_str(instance, temp_str),
|
||||||
|
"get_next_key_str == true assert failed\r\n");
|
||||||
|
mu_assert(furi_string_cmp_str(temp_str, key_str) == 0, "invalid key loaded\r\n");
|
||||||
|
mu_assert(mf_classic_dict_rewind(instance), "mf_classic_dict_rewind == 1 assert failed\r\n");
|
||||||
|
mu_assert(
|
||||||
|
mf_classic_dict_get_next_key(instance, &key_dut),
|
||||||
|
"get_next_key == true assert failed\r\n");
|
||||||
|
mu_assert(key_dut == key_ref, "invalid key loaded\r\n");
|
||||||
|
furi_string_free(temp_str);
|
||||||
|
mf_classic_dict_free(instance);
|
||||||
|
|
||||||
|
// Check that MfClassicDict added new line to the end of the file
|
||||||
|
mu_assert(
|
||||||
|
file_stream_open(file_stream, NFC_TEST_DICT_PATH, FSAM_READ, FSOM_OPEN_EXISTING),
|
||||||
|
"file_stream_open == true assert failed\r\n");
|
||||||
|
mu_assert(stream_seek(file_stream, -1, StreamOffsetFromEnd), "seek == true assert failed\r\n");
|
||||||
|
uint8_t last_char = 0;
|
||||||
|
mu_assert(stream_read(file_stream, &last_char, 1) == 1, "read == true assert failed\r\n");
|
||||||
|
mu_assert(last_char == '\n', "last_char == '\\n' assert failed\r\n");
|
||||||
|
mu_assert(file_stream_close(file_stream), "file_stream_close == true assert failed\r\n");
|
||||||
|
|
||||||
|
// Delete unit test dict file
|
||||||
|
mu_assert(
|
||||||
|
storage_simply_remove(storage, NFC_TEST_DICT_PATH), "remove == true assert failed\r\n");
|
||||||
|
stream_free(file_stream);
|
||||||
|
furi_record_close(RECORD_STORAGE);
|
||||||
|
}
|
||||||
|
|
||||||
MU_TEST_SUITE(nfc) {
|
MU_TEST_SUITE(nfc) {
|
||||||
nfc_test_alloc();
|
nfc_test_alloc();
|
||||||
|
|
||||||
MU_RUN_TEST(nfc_digital_signal_test);
|
MU_RUN_TEST(nfc_digital_signal_test);
|
||||||
MU_RUN_TEST(mf_classic_dict_test);
|
MU_RUN_TEST(mf_classic_dict_test);
|
||||||
|
MU_RUN_TEST(mf_classic_dict_load_test);
|
||||||
|
|
||||||
nfc_test_free();
|
nfc_test_free();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
#include <lib/subghz/transmitter.h>
|
#include <lib/subghz/transmitter.h>
|
||||||
#include <lib/subghz/subghz_keystore.h>
|
#include <lib/subghz/subghz_keystore.h>
|
||||||
#include <lib/subghz/subghz_file_encoder_worker.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>
|
#include <flipper_format/flipper_format_i.h>
|
||||||
|
|
||||||
#define TAG "SubGhz TEST"
|
#define TAG "SubGhz TEST"
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
#define CAME_ATOMO_DIR_NAME EXT_PATH("subghz/assets/came_atomo")
|
#define CAME_ATOMO_DIR_NAME EXT_PATH("subghz/assets/came_atomo")
|
||||||
#define NICE_FLOR_S_DIR_NAME EXT_PATH("subghz/assets/nice_flor_s")
|
#define NICE_FLOR_S_DIR_NAME EXT_PATH("subghz/assets/nice_flor_s")
|
||||||
#define TEST_RANDOM_DIR_NAME EXT_PATH("unit_tests/subghz/test_random_raw.sub")
|
#define TEST_RANDOM_DIR_NAME EXT_PATH("unit_tests/subghz/test_random_raw.sub")
|
||||||
#define TEST_RANDOM_COUNT_PARSE 233
|
#define TEST_RANDOM_COUNT_PARSE 232
|
||||||
#define TEST_TIMEOUT 10000
|
#define TEST_TIMEOUT 10000
|
||||||
|
|
||||||
static SubGhzEnvironment* environment_handler;
|
static SubGhzEnvironment* environment_handler;
|
||||||
@@ -43,6 +43,8 @@ static void subghz_test_init(void) {
|
|||||||
environment_handler, CAME_ATOMO_DIR_NAME);
|
environment_handler, CAME_ATOMO_DIR_NAME);
|
||||||
subghz_environment_set_nice_flor_s_rainbow_table_file_name(
|
subghz_environment_set_nice_flor_s_rainbow_table_file_name(
|
||||||
environment_handler, NICE_FLOR_S_DIR_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);
|
receiver_handler = subghz_receiver_alloc_init(environment_handler);
|
||||||
subghz_receiver_set_filter(receiver_handler, SubGhzProtocolFlag_Decodable);
|
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");
|
"Test decoder " SUBGHZ_PROTOCOL_HONEYWELL_WDB_NAME " error\r\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
MU_TEST(subghz_decoder_magellen_test) {
|
MU_TEST(subghz_decoder_magellan_test) {
|
||||||
mu_assert(
|
mu_assert(
|
||||||
subghz_decoder_test(
|
subghz_decoder_test(
|
||||||
EXT_PATH("unit_tests/subghz/magellen_raw.sub"), SUBGHZ_PROTOCOL_MAGELLEN_NAME),
|
EXT_PATH("unit_tests/subghz/magellan_raw.sub"), SUBGHZ_PROTOCOL_MAGELLAN_NAME),
|
||||||
"Test decoder " SUBGHZ_PROTOCOL_MAGELLEN_NAME " error\r\n");
|
"Test decoder " SUBGHZ_PROTOCOL_MAGELLAN_NAME " error\r\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
MU_TEST(subghz_decoder_intertechno_v3_test) {
|
MU_TEST(subghz_decoder_intertechno_v3_test) {
|
||||||
@@ -435,13 +437,6 @@ MU_TEST(subghz_decoder_clemsa_test) {
|
|||||||
"Test decoder " SUBGHZ_PROTOCOL_CLEMSA_NAME " error\r\n");
|
"Test decoder " SUBGHZ_PROTOCOL_CLEMSA_NAME " error\r\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
MU_TEST(subghz_decoder_oregon2_test) {
|
|
||||||
mu_assert(
|
|
||||||
subghz_decoder_test(
|
|
||||||
EXT_PATH("unit_tests/subghz/oregon2_raw.sub"), SUBGHZ_PROTOCOL_OREGON2_NAME),
|
|
||||||
"Test decoder " SUBGHZ_PROTOCOL_OREGON2_NAME " error\r\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
//test encoders
|
//test encoders
|
||||||
MU_TEST(subghz_encoder_princeton_test) {
|
MU_TEST(subghz_encoder_princeton_test) {
|
||||||
mu_assert(
|
mu_assert(
|
||||||
@@ -545,10 +540,10 @@ MU_TEST(subghz_encoder_honeywell_wdb_test) {
|
|||||||
"Test encoder " SUBGHZ_PROTOCOL_HONEYWELL_WDB_NAME " error\r\n");
|
"Test encoder " SUBGHZ_PROTOCOL_HONEYWELL_WDB_NAME " error\r\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
MU_TEST(subghz_encoder_magellen_test) {
|
MU_TEST(subghz_encoder_magellan_test) {
|
||||||
mu_assert(
|
mu_assert(
|
||||||
subghz_encoder_test(EXT_PATH("unit_tests/subghz/magellen.sub")),
|
subghz_encoder_test(EXT_PATH("unit_tests/subghz/magellan.sub")),
|
||||||
"Test encoder " SUBGHZ_PROTOCOL_MAGELLEN_NAME " error\r\n");
|
"Test encoder " SUBGHZ_PROTOCOL_MAGELLAN_NAME " error\r\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
MU_TEST(subghz_encoder_intertechno_v3_test) {
|
MU_TEST(subghz_encoder_intertechno_v3_test) {
|
||||||
@@ -600,10 +595,9 @@ MU_TEST_SUITE(subghz) {
|
|||||||
MU_RUN_TEST(subghz_decoder_doitrand_test);
|
MU_RUN_TEST(subghz_decoder_doitrand_test);
|
||||||
MU_RUN_TEST(subghz_decoder_phoenix_v2_test);
|
MU_RUN_TEST(subghz_decoder_phoenix_v2_test);
|
||||||
MU_RUN_TEST(subghz_decoder_honeywell_wdb_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_intertechno_v3_test);
|
||||||
MU_RUN_TEST(subghz_decoder_clemsa_test);
|
MU_RUN_TEST(subghz_decoder_clemsa_test);
|
||||||
MU_RUN_TEST(subghz_decoder_oregon2_test);
|
|
||||||
|
|
||||||
MU_RUN_TEST(subghz_encoder_princeton_test);
|
MU_RUN_TEST(subghz_encoder_princeton_test);
|
||||||
MU_RUN_TEST(subghz_encoder_came_test);
|
MU_RUN_TEST(subghz_encoder_came_test);
|
||||||
@@ -622,7 +616,7 @@ MU_TEST_SUITE(subghz) {
|
|||||||
MU_RUN_TEST(subghz_encoder_doitrand_test);
|
MU_RUN_TEST(subghz_encoder_doitrand_test);
|
||||||
MU_RUN_TEST(subghz_encoder_phoenix_v2_test);
|
MU_RUN_TEST(subghz_encoder_phoenix_v2_test);
|
||||||
MU_RUN_TEST(subghz_encoder_honeywell_wdb_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_intertechno_v3_test);
|
||||||
MU_RUN_TEST(subghz_encoder_clemsa_test);
|
MU_RUN_TEST(subghz_encoder_clemsa_test);
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ struct GpioApp {
|
|||||||
Widget* widget;
|
Widget* widget;
|
||||||
|
|
||||||
VariableItemList* var_item_list;
|
VariableItemList* var_item_list;
|
||||||
|
VariableItem* var_item_flow;
|
||||||
GpioTest* gpio_test;
|
GpioTest* gpio_test;
|
||||||
GpioUsbUart* gpio_usb_uart;
|
GpioUsbUart* gpio_usb_uart;
|
||||||
UsbUartBridge* usb_uart_bridge;
|
UsbUartBridge* usb_uart_bridge;
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ static UsbUartConfig* cfg_set;
|
|||||||
|
|
||||||
static const char* vcp_ch[] = {"0 (CLI)", "1"};
|
static const char* vcp_ch[] = {"0 (CLI)", "1"};
|
||||||
static const char* uart_ch[] = {"13,14", "15,16"};
|
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 char* baudrate_mode[] = {"Host"};
|
||||||
static const uint32_t baudrate_list[] = {
|
static const uint32_t baudrate_list[] = {
|
||||||
2400,
|
2400,
|
||||||
@@ -33,6 +33,24 @@ bool gpio_scene_usb_uart_cfg_on_event(void* context, SceneManagerEvent event) {
|
|||||||
return false;
|
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) {
|
static void line_vcp_cb(VariableItem* item) {
|
||||||
GpioApp* app = variable_item_get_context(item);
|
GpioApp* app = variable_item_get_context(item);
|
||||||
uint8_t index = variable_item_get_current_value_index(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)
|
else if(index == 1)
|
||||||
cfg_set->uart_ch = FuriHalUartIdLPUART1;
|
cfg_set->uart_ch = FuriHalUartIdLPUART1;
|
||||||
usb_uart_set_config(app->usb_uart_bridge, cfg_set);
|
usb_uart_set_config(app->usb_uart_bridge, cfg_set);
|
||||||
|
line_ensure_flow_invariant(app);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void line_flow_cb(VariableItem* item) {
|
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_index(item, cfg_set->uart_ch);
|
||||||
variable_item_set_current_value_text(item, uart_ch[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_index(item, cfg_set->flow_pins);
|
||||||
variable_item_set_current_value_text(item, flow_pins[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(
|
variable_item_list_set_selected_item(
|
||||||
var_item_list, scene_manager_get_scene_state(app->scene_manager, GpioAppViewUsbUartCfg));
|
var_item_list, scene_manager_get_scene_state(app->scene_manager, GpioAppViewUsbUartCfg));
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
static const GpioPin* flow_pins[][2] = {
|
static const GpioPin* flow_pins[][2] = {
|
||||||
{&gpio_ext_pa7, &gpio_ext_pa6}, // 2, 3
|
{&gpio_ext_pa7, &gpio_ext_pa6}, // 2, 3
|
||||||
{&gpio_ext_pb2, &gpio_ext_pc3}, // 6, 7
|
{&gpio_ext_pb2, &gpio_ext_pc3}, // 6, 7
|
||||||
|
{&gpio_ext_pc0, &gpio_ext_pc1}, // 16, 15
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#include <cli/cli.h>
|
#include <cli/cli.h>
|
||||||
|
#include <cli/cli_i.h>
|
||||||
#include <infrared.h>
|
#include <infrared.h>
|
||||||
#include <infrared_worker.h>
|
#include <infrared_worker.h>
|
||||||
#include <furi_hal_infrared.h>
|
#include <furi_hal_infrared.h>
|
||||||
@@ -6,12 +7,23 @@
|
|||||||
#include <toolbox/args.h>
|
#include <toolbox/args.h>
|
||||||
|
|
||||||
#include "infrared_signal.h"
|
#include "infrared_signal.h"
|
||||||
|
#include "infrared_brute_force.h"
|
||||||
|
|
||||||
|
#include <m-dict.h>
|
||||||
|
|
||||||
#define INFRARED_CLI_BUF_SIZE 10
|
#define INFRARED_CLI_BUF_SIZE 10
|
||||||
|
|
||||||
|
DICT_DEF2(dict_signals, FuriString*, FURI_STRING_OPLIST, int, M_DEFAULT_OPLIST)
|
||||||
|
|
||||||
|
enum RemoteTypes { TV = 0, AC = 1 };
|
||||||
|
|
||||||
static void infrared_cli_start_ir_rx(Cli* cli, FuriString* args);
|
static void infrared_cli_start_ir_rx(Cli* cli, FuriString* args);
|
||||||
static void infrared_cli_start_ir_tx(Cli* cli, FuriString* args);
|
static void infrared_cli_start_ir_tx(Cli* cli, FuriString* args);
|
||||||
static void infrared_cli_process_decode(Cli* cli, FuriString* args);
|
static void infrared_cli_process_decode(Cli* cli, FuriString* args);
|
||||||
|
static void infrared_cli_process_universal(Cli* cli, FuriString* args);
|
||||||
|
static void infrared_cli_list_remote_signals(enum RemoteTypes remote_type);
|
||||||
|
static void
|
||||||
|
infrared_cli_brute_force_signals(Cli* cli, enum RemoteTypes remote_type, FuriString* signal);
|
||||||
|
|
||||||
static const struct {
|
static const struct {
|
||||||
const char* cmd;
|
const char* cmd;
|
||||||
@@ -20,6 +32,7 @@ static const struct {
|
|||||||
{.cmd = "rx", .process_function = infrared_cli_start_ir_rx},
|
{.cmd = "rx", .process_function = infrared_cli_start_ir_rx},
|
||||||
{.cmd = "tx", .process_function = infrared_cli_start_ir_tx},
|
{.cmd = "tx", .process_function = infrared_cli_start_ir_tx},
|
||||||
{.cmd = "decode", .process_function = infrared_cli_process_decode},
|
{.cmd = "decode", .process_function = infrared_cli_process_decode},
|
||||||
|
{.cmd = "universal", .process_function = infrared_cli_process_universal},
|
||||||
};
|
};
|
||||||
|
|
||||||
static void signal_received_callback(void* context, InfraredWorkerSignal* received_signal) {
|
static void signal_received_callback(void* context, InfraredWorkerSignal* received_signal) {
|
||||||
@@ -57,25 +70,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) {
|
static void infrared_cli_print_usage(void) {
|
||||||
printf("Usage:\r\n");
|
printf("Usage:\r\n");
|
||||||
printf("\tir rx\r\n");
|
printf("\tir rx [raw]\r\n");
|
||||||
printf("\tir tx <protocol> <address> <command>\r\n");
|
printf("\tir tx <protocol> <address> <command>\r\n");
|
||||||
printf("\t<command> and <address> are hex-formatted\r\n");
|
printf("\t<command> and <address> are hex-formatted\r\n");
|
||||||
printf("\tAvailable protocols:");
|
printf("\tAvailable protocols:");
|
||||||
@@ -90,6 +87,37 @@ static void infrared_cli_print_usage(void) {
|
|||||||
INFRARED_MIN_FREQUENCY,
|
INFRARED_MIN_FREQUENCY,
|
||||||
INFRARED_MAX_FREQUENCY);
|
INFRARED_MAX_FREQUENCY);
|
||||||
printf("\tir decode <input_file> [<output_file>]\r\n");
|
printf("\tir decode <input_file> [<output_file>]\r\n");
|
||||||
|
printf("\tir universal <tv, ac> <signal name>\r\n");
|
||||||
|
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) {
|
static bool infrared_cli_parse_message(const char* str, InfraredSignal* signal) {
|
||||||
@@ -328,6 +356,168 @@ static void infrared_cli_process_decode(Cli* cli, FuriString* args) {
|
|||||||
furi_record_close(RECORD_STORAGE);
|
furi_record_close(RECORD_STORAGE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void infrared_cli_process_universal(Cli* cli, FuriString* args) {
|
||||||
|
enum RemoteTypes Remote;
|
||||||
|
|
||||||
|
FuriString* command;
|
||||||
|
FuriString* remote;
|
||||||
|
FuriString* signal;
|
||||||
|
command = furi_string_alloc();
|
||||||
|
remote = furi_string_alloc();
|
||||||
|
signal = furi_string_alloc();
|
||||||
|
|
||||||
|
do {
|
||||||
|
if(!args_read_string_and_trim(args, command)) {
|
||||||
|
infrared_cli_print_usage();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(furi_string_cmp_str(command, "list") == 0) {
|
||||||
|
args_read_string_and_trim(args, remote);
|
||||||
|
if(furi_string_cmp_str(remote, "tv") == 0) {
|
||||||
|
Remote = TV;
|
||||||
|
} else if(furi_string_cmp_str(remote, "ac") == 0) {
|
||||||
|
Remote = AC;
|
||||||
|
} else {
|
||||||
|
printf("Invalid remote type.\r\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
infrared_cli_list_remote_signals(Remote);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(furi_string_cmp_str(command, "tv") == 0) {
|
||||||
|
Remote = TV;
|
||||||
|
} else if(furi_string_cmp_str(command, "ac") == 0) {
|
||||||
|
Remote = AC;
|
||||||
|
} else {
|
||||||
|
printf("Invalid remote type.\r\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
args_read_string_and_trim(args, signal);
|
||||||
|
if(furi_string_empty(signal)) {
|
||||||
|
printf("Must supply a valid signal for type of remote selected.\r\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
infrared_cli_brute_force_signals(cli, Remote, signal);
|
||||||
|
break;
|
||||||
|
|
||||||
|
} while(false);
|
||||||
|
|
||||||
|
furi_string_free(command);
|
||||||
|
furi_string_free(remote);
|
||||||
|
furi_string_free(signal);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void infrared_cli_list_remote_signals(enum RemoteTypes remote_type) {
|
||||||
|
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||||
|
FlipperFormat* ff = flipper_format_buffered_file_alloc(storage);
|
||||||
|
dict_signals_t signals_dict;
|
||||||
|
FuriString* key;
|
||||||
|
const char* remote_file = NULL;
|
||||||
|
bool success = false;
|
||||||
|
int max = 1;
|
||||||
|
|
||||||
|
switch(remote_type) {
|
||||||
|
case TV:
|
||||||
|
remote_file = EXT_PATH("infrared/assets/tv.ir");
|
||||||
|
break;
|
||||||
|
case AC:
|
||||||
|
remote_file = EXT_PATH("infrared/assets/ac.ir");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
dict_signals_init(signals_dict);
|
||||||
|
key = furi_string_alloc();
|
||||||
|
|
||||||
|
success = flipper_format_buffered_file_open_existing(ff, remote_file);
|
||||||
|
if(success) {
|
||||||
|
FuriString* signal_name;
|
||||||
|
signal_name = furi_string_alloc();
|
||||||
|
printf("Valid signals:\r\n");
|
||||||
|
while(flipper_format_read_string(ff, "name", signal_name)) {
|
||||||
|
furi_string_set_str(key, furi_string_get_cstr(signal_name));
|
||||||
|
int* v = dict_signals_get(signals_dict, key);
|
||||||
|
if(v != NULL) {
|
||||||
|
(*v)++;
|
||||||
|
max = M_MAX(*v, max);
|
||||||
|
} else {
|
||||||
|
dict_signals_set_at(signals_dict, key, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dict_signals_it_t it;
|
||||||
|
for(dict_signals_it(it, signals_dict); !dict_signals_end_p(it); dict_signals_next(it)) {
|
||||||
|
const struct dict_signals_pair_s* pair = dict_signals_cref(it);
|
||||||
|
printf("\t%s\r\n", furi_string_get_cstr(pair->key));
|
||||||
|
}
|
||||||
|
furi_string_free(signal_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
furi_string_free(key);
|
||||||
|
dict_signals_clear(signals_dict);
|
||||||
|
flipper_format_free(ff);
|
||||||
|
furi_record_close(RECORD_STORAGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
infrared_cli_brute_force_signals(Cli* cli, enum RemoteTypes remote_type, FuriString* signal) {
|
||||||
|
InfraredBruteForce* brute_force = infrared_brute_force_alloc();
|
||||||
|
const char* remote_file = NULL;
|
||||||
|
uint32_t i = 0;
|
||||||
|
bool success = false;
|
||||||
|
|
||||||
|
switch(remote_type) {
|
||||||
|
case TV:
|
||||||
|
remote_file = EXT_PATH("infrared/assets/tv.ir");
|
||||||
|
break;
|
||||||
|
case AC:
|
||||||
|
remote_file = EXT_PATH("infrared/assets/ac.ir");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
infrared_brute_force_set_db_filename(brute_force, remote_file);
|
||||||
|
infrared_brute_force_add_record(brute_force, i++, furi_string_get_cstr(signal));
|
||||||
|
|
||||||
|
success = infrared_brute_force_calculate_messages(brute_force);
|
||||||
|
if(success) {
|
||||||
|
uint32_t record_count;
|
||||||
|
uint32_t index = 0;
|
||||||
|
int records_sent = 0;
|
||||||
|
bool running = false;
|
||||||
|
|
||||||
|
running = infrared_brute_force_start(brute_force, index, &record_count);
|
||||||
|
if(record_count <= 0) {
|
||||||
|
printf("Invalid signal.\n");
|
||||||
|
infrared_brute_force_reset(brute_force);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("Sending %ld codes to the tv.\r\n", record_count);
|
||||||
|
printf("Press Ctrl-C to stop.\r\n");
|
||||||
|
while(running) {
|
||||||
|
running = infrared_brute_force_send_next(brute_force);
|
||||||
|
|
||||||
|
if(cli_cmd_interrupt_received(cli)) break;
|
||||||
|
|
||||||
|
printf("\r%d%% complete.", (int)((float)records_sent++ / (float)record_count * 100));
|
||||||
|
fflush(stdout);
|
||||||
|
}
|
||||||
|
|
||||||
|
infrared_brute_force_stop(brute_force);
|
||||||
|
} else {
|
||||||
|
printf("Invalid signal.\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
infrared_brute_force_reset(brute_force);
|
||||||
|
infrared_brute_force_free(brute_force);
|
||||||
|
}
|
||||||
|
|
||||||
static void infrared_cli_start_ir(Cli* cli, FuriString* args, void* context) {
|
static void infrared_cli_start_ir(Cli* cli, FuriString* args, void* context) {
|
||||||
UNUSED(context);
|
UNUSED(context);
|
||||||
if(furi_hal_infrared_is_busy()) {
|
if(furi_hal_infrared_is_busy()) {
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ void lfrfid_scene_raw_info_on_enter(void* context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewWidget);
|
view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewWidget);
|
||||||
//string_clear(tmp_string);
|
//furi_string_free(tmp_string);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool lfrfid_scene_raw_info_on_event(void* context, SceneManagerEvent event) {
|
bool lfrfid_scene_raw_info_on_event(void* context, SceneManagerEvent event) {
|
||||||
|
|||||||
@@ -34,8 +34,6 @@ void nfc_scene_mf_classic_keys_on_enter(void* context) {
|
|||||||
widget_add_string_element(nfc->widget, 0, 32, AlignLeft, AlignTop, FontSecondary, temp_str);
|
widget_add_string_element(nfc->widget, 0, 32, AlignLeft, AlignTop, FontSecondary, temp_str);
|
||||||
widget_add_button_element(
|
widget_add_button_element(
|
||||||
nfc->widget, GuiButtonTypeCenter, "Add", nfc_scene_mf_classic_keys_widget_callback, nfc);
|
nfc->widget, GuiButtonTypeCenter, "Add", nfc_scene_mf_classic_keys_widget_callback, nfc);
|
||||||
widget_add_button_element(
|
|
||||||
nfc->widget, GuiButtonTypeLeft, "Back", nfc_scene_mf_classic_keys_widget_callback, nfc);
|
|
||||||
widget_add_icon_element(nfc->widget, 87, 13, &I_Keychain_39x36);
|
widget_add_icon_element(nfc->widget, 87, 13, &I_Keychain_39x36);
|
||||||
if(user_dict_keys_total > 0) {
|
if(user_dict_keys_total > 0) {
|
||||||
widget_add_button_element(
|
widget_add_button_element(
|
||||||
@@ -57,9 +55,6 @@ bool nfc_scene_mf_classic_keys_on_event(void* context, SceneManagerEvent event)
|
|||||||
if(event.event == GuiButtonTypeCenter) {
|
if(event.event == GuiButtonTypeCenter) {
|
||||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicKeysAdd);
|
scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicKeysAdd);
|
||||||
consumed = true;
|
consumed = true;
|
||||||
} else if(event.event == GuiButtonTypeLeft) {
|
|
||||||
scene_manager_previous_scene(nfc->scene_manager);
|
|
||||||
consumed = true;
|
|
||||||
} else if(event.event == GuiButtonTypeRight) {
|
} else if(event.event == GuiButtonTypeRight) {
|
||||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicKeysList);
|
scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicKeysList);
|
||||||
consumed = true;
|
consumed = true;
|
||||||
|
|||||||
@@ -69,12 +69,3 @@ typedef enum {
|
|||||||
SubGhzViewIdTestCarrier,
|
SubGhzViewIdTestCarrier,
|
||||||
SubGhzViewIdTestPacket,
|
SubGhzViewIdTestPacket,
|
||||||
} SubGhzViewId;
|
} SubGhzViewId;
|
||||||
|
|
||||||
struct SubGhzPresetDefinition {
|
|
||||||
FuriString* name;
|
|
||||||
uint32_t frequency;
|
|
||||||
uint8_t* data;
|
|
||||||
size_t data_size;
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef struct SubGhzPresetDefinition SubGhzPresetDefinition;
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
#define RAW_FILE_NAME "Raw_signal_"
|
#define RAW_FILE_NAME "Raw_signal_"
|
||||||
#define TAG "SubGhzSceneReadRAW"
|
#define TAG "SubGhzSceneReadRAW"
|
||||||
|
#define RAW_THRESHOLD_RSSI_LOW_COUNT 10
|
||||||
|
|
||||||
bool subghz_scene_read_raw_update_filename(SubGhz* subghz) {
|
bool subghz_scene_read_raw_update_filename(SubGhz* subghz) {
|
||||||
bool ret = false;
|
bool ret = false;
|
||||||
@@ -72,24 +73,33 @@ void subghz_scene_read_raw_on_enter(void* context) {
|
|||||||
|
|
||||||
switch(subghz->txrx->rx_key_state) {
|
switch(subghz->txrx->rx_key_state) {
|
||||||
case SubGhzRxKeyStateBack:
|
case SubGhzRxKeyStateBack:
|
||||||
subghz_read_raw_set_status(subghz->subghz_read_raw, SubGhzReadRAWStatusIDLE, "");
|
subghz_read_raw_set_status(
|
||||||
|
subghz->subghz_read_raw, SubGhzReadRAWStatusIDLE, "", subghz->txrx->raw_threshold_rssi);
|
||||||
break;
|
break;
|
||||||
case SubGhzRxKeyStateRAWLoad:
|
case SubGhzRxKeyStateRAWLoad:
|
||||||
path_extract_filename(subghz->file_path, file_name, true);
|
path_extract_filename(subghz->file_path, file_name, true);
|
||||||
subghz_read_raw_set_status(
|
subghz_read_raw_set_status(
|
||||||
subghz->subghz_read_raw,
|
subghz->subghz_read_raw,
|
||||||
SubGhzReadRAWStatusLoadKeyTX,
|
SubGhzReadRAWStatusLoadKeyTX,
|
||||||
furi_string_get_cstr(file_name));
|
furi_string_get_cstr(file_name),
|
||||||
|
subghz->txrx->raw_threshold_rssi);
|
||||||
subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE;
|
subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE;
|
||||||
break;
|
break;
|
||||||
case SubGhzRxKeyStateRAWSave:
|
case SubGhzRxKeyStateRAWSave:
|
||||||
path_extract_filename(subghz->file_path, file_name, true);
|
path_extract_filename(subghz->file_path, file_name, true);
|
||||||
subghz_read_raw_set_status(
|
subghz_read_raw_set_status(
|
||||||
subghz->subghz_read_raw, SubGhzReadRAWStatusSaveKey, furi_string_get_cstr(file_name));
|
subghz->subghz_read_raw,
|
||||||
|
SubGhzReadRAWStatusSaveKey,
|
||||||
|
furi_string_get_cstr(file_name),
|
||||||
|
subghz->txrx->raw_threshold_rssi);
|
||||||
subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE;
|
subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
subghz_read_raw_set_status(subghz->subghz_read_raw, SubGhzReadRAWStatusStart, "");
|
subghz_read_raw_set_status(
|
||||||
|
subghz->subghz_read_raw,
|
||||||
|
SubGhzReadRAWStatusStart,
|
||||||
|
"",
|
||||||
|
subghz->txrx->raw_threshold_rssi);
|
||||||
subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE;
|
subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -273,7 +283,7 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) {
|
|||||||
if(subghz->txrx->rx_key_state != SubGhzRxKeyStateIDLE) {
|
if(subghz->txrx->rx_key_state != SubGhzRxKeyStateIDLE) {
|
||||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneNeedSaving);
|
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneNeedSaving);
|
||||||
} else {
|
} else {
|
||||||
//subghz_get_preset_name(subghz, subghz->error_str);
|
subghz->txrx->raw_threshold_rssi_low_count = RAW_THRESHOLD_RSSI_LOW_COUNT;
|
||||||
if(subghz_protocol_raw_save_to_file_init(
|
if(subghz_protocol_raw_save_to_file_init(
|
||||||
(SubGhzProtocolDecoderRAW*)subghz->txrx->decoder_result,
|
(SubGhzProtocolDecoderRAW*)subghz->txrx->decoder_result,
|
||||||
RAW_FILE_NAME,
|
RAW_FILE_NAME,
|
||||||
@@ -319,7 +329,35 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) {
|
|||||||
subghz->subghz_read_raw,
|
subghz->subghz_read_raw,
|
||||||
subghz_protocol_raw_get_sample_write(
|
subghz_protocol_raw_get_sample_write(
|
||||||
(SubGhzProtocolDecoderRAW*)subghz->txrx->decoder_result));
|
(SubGhzProtocolDecoderRAW*)subghz->txrx->decoder_result));
|
||||||
subghz_read_raw_add_data_rssi(subghz->subghz_read_raw, furi_hal_subghz_get_rssi());
|
|
||||||
|
float rssi = furi_hal_subghz_get_rssi();
|
||||||
|
|
||||||
|
if(subghz->txrx->raw_threshold_rssi == SUBGHZ_RAW_TRESHOLD_MIN) {
|
||||||
|
subghz_read_raw_add_data_rssi(subghz->subghz_read_raw, rssi, true);
|
||||||
|
subghz_protocol_raw_save_to_file_pause(
|
||||||
|
(SubGhzProtocolDecoderRAW*)subghz->txrx->decoder_result, false);
|
||||||
|
} else {
|
||||||
|
if(rssi < subghz->txrx->raw_threshold_rssi) {
|
||||||
|
subghz->txrx->raw_threshold_rssi_low_count++;
|
||||||
|
if(subghz->txrx->raw_threshold_rssi_low_count > RAW_THRESHOLD_RSSI_LOW_COUNT) {
|
||||||
|
subghz->txrx->raw_threshold_rssi_low_count = RAW_THRESHOLD_RSSI_LOW_COUNT;
|
||||||
|
}
|
||||||
|
subghz_read_raw_add_data_rssi(subghz->subghz_read_raw, rssi, false);
|
||||||
|
} else {
|
||||||
|
subghz->txrx->raw_threshold_rssi_low_count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(subghz->txrx->raw_threshold_rssi_low_count == RAW_THRESHOLD_RSSI_LOW_COUNT) {
|
||||||
|
subghz_read_raw_add_data_rssi(subghz->subghz_read_raw, rssi, false);
|
||||||
|
subghz_protocol_raw_save_to_file_pause(
|
||||||
|
(SubGhzProtocolDecoderRAW*)subghz->txrx->decoder_result, true);
|
||||||
|
} else {
|
||||||
|
subghz_read_raw_add_data_rssi(subghz->subghz_read_raw, rssi, true);
|
||||||
|
subghz_protocol_raw_save_to_file_pause(
|
||||||
|
(SubGhzProtocolDecoderRAW*)subghz->txrx->decoder_result, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case SubGhzNotificationStateTx:
|
case SubGhzNotificationStateTx:
|
||||||
notification_message(subghz->notifications, &sequence_blink_magenta_10);
|
notification_message(subghz->notifications, &sequence_blink_magenta_10);
|
||||||
|
|||||||
@@ -1,10 +1,41 @@
|
|||||||
#include "../subghz_i.h"
|
#include "../subghz_i.h"
|
||||||
|
#include <lib/toolbox/value_index.h>
|
||||||
|
|
||||||
enum SubGhzSettingIndex {
|
enum SubGhzSettingIndex {
|
||||||
SubGhzSettingIndexFrequency,
|
SubGhzSettingIndexFrequency,
|
||||||
SubGhzSettingIndexHopping,
|
SubGhzSettingIndexHopping,
|
||||||
SubGhzSettingIndexModulation,
|
SubGhzSettingIndexModulation,
|
||||||
SubGhzSettingIndexLock,
|
SubGhzSettingIndexLock,
|
||||||
|
SubGhzSettingIndexRAWThesholdRSSI,
|
||||||
|
};
|
||||||
|
|
||||||
|
#define RAW_THRESHOLD_RSSI_COUNT 11
|
||||||
|
const char* const raw_theshold_rssi_text[RAW_THRESHOLD_RSSI_COUNT] = {
|
||||||
|
"-----",
|
||||||
|
"-85.0",
|
||||||
|
"-80.0",
|
||||||
|
"-75.0",
|
||||||
|
"-70.0",
|
||||||
|
"-65.0",
|
||||||
|
"-60.0",
|
||||||
|
"-55.0",
|
||||||
|
"-50.0",
|
||||||
|
"-45.0",
|
||||||
|
"-40.0",
|
||||||
|
|
||||||
|
};
|
||||||
|
const float raw_theshold_rssi_value[RAW_THRESHOLD_RSSI_COUNT] = {
|
||||||
|
-90.0f,
|
||||||
|
-85.0f,
|
||||||
|
-80.0f,
|
||||||
|
-75.0f,
|
||||||
|
-70.0f,
|
||||||
|
-65.0f,
|
||||||
|
-60.0f,
|
||||||
|
-55.0f,
|
||||||
|
-50.0f,
|
||||||
|
-45.0f,
|
||||||
|
-40.0f,
|
||||||
};
|
};
|
||||||
|
|
||||||
#define HOPPING_COUNT 2
|
#define HOPPING_COUNT 2
|
||||||
@@ -136,6 +167,14 @@ static void subghz_scene_receiver_config_set_hopping_running(VariableItem* item)
|
|||||||
subghz->txrx->hopper_state = hopping_value[index];
|
subghz->txrx->hopper_state = hopping_value[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void subghz_scene_receiver_config_set_raw_threshold_rssi(VariableItem* item) {
|
||||||
|
SubGhz* subghz = variable_item_get_context(item);
|
||||||
|
uint8_t index = variable_item_get_current_value_index(item);
|
||||||
|
|
||||||
|
variable_item_set_current_value_text(item, raw_theshold_rssi_text[index]);
|
||||||
|
subghz->txrx->raw_threshold_rssi = raw_theshold_rssi_value[index];
|
||||||
|
}
|
||||||
|
|
||||||
static void subghz_scene_receiver_config_var_list_enter_callback(void* context, uint32_t index) {
|
static void subghz_scene_receiver_config_var_list_enter_callback(void* context, uint32_t index) {
|
||||||
furi_assert(context);
|
furi_assert(context);
|
||||||
SubGhz* subghz = context;
|
SubGhz* subghz = context;
|
||||||
@@ -204,6 +243,19 @@ void subghz_scene_receiver_config_on_enter(void* context) {
|
|||||||
subghz_scene_receiver_config_var_list_enter_callback,
|
subghz_scene_receiver_config_var_list_enter_callback,
|
||||||
subghz);
|
subghz);
|
||||||
}
|
}
|
||||||
|
if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAW) ==
|
||||||
|
SubGhzCustomEventManagerSet) {
|
||||||
|
item = variable_item_list_add(
|
||||||
|
subghz->variable_item_list,
|
||||||
|
"RSSI Threshold:",
|
||||||
|
RAW_THRESHOLD_RSSI_COUNT,
|
||||||
|
subghz_scene_receiver_config_set_raw_threshold_rssi,
|
||||||
|
subghz);
|
||||||
|
value_index = value_index_float(
|
||||||
|
subghz->txrx->raw_threshold_rssi, raw_theshold_rssi_value, RAW_THRESHOLD_RSSI_COUNT);
|
||||||
|
variable_item_set_current_value_index(item, value_index);
|
||||||
|
variable_item_set_current_value_text(item, raw_theshold_rssi_text[value_index]);
|
||||||
|
}
|
||||||
view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdVariableItemList);
|
view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdVariableItemList);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,8 +28,8 @@ static bool subghz_scene_receiver_info_update_parser(void* context) {
|
|||||||
subghz->txrx->decoder_result,
|
subghz->txrx->decoder_result,
|
||||||
subghz_history_get_raw_data(subghz->txrx->history, subghz->txrx->idx_menu_chosen));
|
subghz_history_get_raw_data(subghz->txrx->history, subghz->txrx->idx_menu_chosen));
|
||||||
|
|
||||||
SubGhzPresetDefinition* preset =
|
SubGhzRadioPreset* preset =
|
||||||
subghz_history_get_preset_def(subghz->txrx->history, subghz->txrx->idx_menu_chosen);
|
subghz_history_get_radio_preset(subghz->txrx->history, subghz->txrx->idx_menu_chosen);
|
||||||
subghz_preset_init(
|
subghz_preset_init(
|
||||||
subghz,
|
subghz,
|
||||||
furi_string_get_cstr(preset->name),
|
furi_string_get_cstr(preset->name),
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
#include <dolphin/dolphin.h>
|
#include <dolphin/dolphin.h>
|
||||||
#include <flipper_format/flipper_format_i.h>
|
#include <flipper_format/flipper_format_i.h>
|
||||||
#include <lib/toolbox/stream/stream.h>
|
#include <lib/toolbox/stream/stream.h>
|
||||||
#include <lib/subghz/protocols/registry.h>
|
#include <lib/subghz/protocols/protocol_items.h>
|
||||||
|
|
||||||
#define TAG "SubGhzSetType"
|
#define TAG "SubGhzSetType"
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include "subghz/types.h"
|
#include "subghz/types.h"
|
||||||
#include "subghz_i.h"
|
#include "subghz_i.h"
|
||||||
#include <lib/toolbox/path.h>
|
#include <lib/toolbox/path.h>
|
||||||
|
#include <lib/subghz/protocols/protocol_items.h>
|
||||||
|
|
||||||
bool subghz_custom_event_callback(void* context, uint32_t event) {
|
bool subghz_custom_event_callback(void* context, uint32_t event) {
|
||||||
furi_assert(context);
|
furi_assert(context);
|
||||||
@@ -169,7 +170,7 @@ SubGhz* subghz_alloc() {
|
|||||||
//init Worker & Protocol & History & KeyBoard
|
//init Worker & Protocol & History & KeyBoard
|
||||||
subghz->lock = SubGhzLockOff;
|
subghz->lock = SubGhzLockOff;
|
||||||
subghz->txrx = malloc(sizeof(SubGhzTxRx));
|
subghz->txrx = malloc(sizeof(SubGhzTxRx));
|
||||||
subghz->txrx->preset = malloc(sizeof(SubGhzPresetDefinition));
|
subghz->txrx->preset = malloc(sizeof(SubGhzRadioPreset));
|
||||||
subghz->txrx->preset->name = furi_string_alloc();
|
subghz->txrx->preset->name = furi_string_alloc();
|
||||||
subghz_preset_init(
|
subghz_preset_init(
|
||||||
subghz, "AM650", subghz_setting_get_default_frequency(subghz->setting), NULL, 0);
|
subghz, "AM650", subghz_setting_get_default_frequency(subghz->setting), NULL, 0);
|
||||||
@@ -177,6 +178,7 @@ SubGhz* subghz_alloc() {
|
|||||||
subghz->txrx->txrx_state = SubGhzTxRxStateSleep;
|
subghz->txrx->txrx_state = SubGhzTxRxStateSleep;
|
||||||
subghz->txrx->hopper_state = SubGhzHopperStateOFF;
|
subghz->txrx->hopper_state = SubGhzHopperStateOFF;
|
||||||
subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE;
|
subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE;
|
||||||
|
subghz->txrx->raw_threshold_rssi = SUBGHZ_RAW_TRESHOLD_MIN;
|
||||||
subghz->txrx->history = subghz_history_alloc();
|
subghz->txrx->history = subghz_history_alloc();
|
||||||
subghz->txrx->worker = subghz_worker_alloc();
|
subghz->txrx->worker = subghz_worker_alloc();
|
||||||
subghz->txrx->fff_data = flipper_format_string_alloc();
|
subghz->txrx->fff_data = flipper_format_string_alloc();
|
||||||
@@ -186,6 +188,8 @@ SubGhz* subghz_alloc() {
|
|||||||
subghz->txrx->environment, EXT_PATH("subghz/assets/came_atomo"));
|
subghz->txrx->environment, EXT_PATH("subghz/assets/came_atomo"));
|
||||||
subghz_environment_set_nice_flor_s_rainbow_table_file_name(
|
subghz_environment_set_nice_flor_s_rainbow_table_file_name(
|
||||||
subghz->txrx->environment, EXT_PATH("subghz/assets/nice_flor_s"));
|
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);
|
subghz->txrx->receiver = subghz_receiver_alloc_init(subghz->txrx->environment);
|
||||||
subghz_receiver_set_filter(subghz->txrx->receiver, SubGhzProtocolFlag_Decodable);
|
subghz_receiver_set_filter(subghz->txrx->receiver, SubGhzProtocolFlag_Decodable);
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include <lib/subghz/receiver.h>
|
#include <lib/subghz/receiver.h>
|
||||||
#include <lib/subghz/transmitter.h>
|
#include <lib/subghz/transmitter.h>
|
||||||
#include <lib/subghz/subghz_file_encoder_worker.h>
|
#include <lib/subghz/subghz_file_encoder_worker.h>
|
||||||
|
#include <lib/subghz/protocols/protocol_items.h>
|
||||||
|
|
||||||
#include "helpers/subghz_chat.h"
|
#include "helpers/subghz_chat.h"
|
||||||
|
|
||||||
@@ -164,6 +165,7 @@ void subghz_cli_command_tx(Cli* cli, FuriString* args, void* context) {
|
|||||||
stream_write_cstring(stream, furi_string_get_cstr(flipper_format_string));
|
stream_write_cstring(stream, furi_string_get_cstr(flipper_format_string));
|
||||||
|
|
||||||
SubGhzEnvironment* environment = subghz_environment_alloc();
|
SubGhzEnvironment* environment = subghz_environment_alloc();
|
||||||
|
subghz_environment_set_protocol_registry(environment, (void*)&subghz_protocol_registry);
|
||||||
|
|
||||||
SubGhzTransmitter* transmitter = subghz_transmitter_alloc_init(environment, "Princeton");
|
SubGhzTransmitter* transmitter = subghz_transmitter_alloc_init(environment, "Princeton");
|
||||||
subghz_transmitter_deserialize(transmitter, flipper_format);
|
subghz_transmitter_deserialize(transmitter, flipper_format);
|
||||||
@@ -257,6 +259,7 @@ void subghz_cli_command_rx(Cli* cli, FuriString* args, void* context) {
|
|||||||
environment, EXT_PATH("subghz/assets/came_atomo"));
|
environment, EXT_PATH("subghz/assets/came_atomo"));
|
||||||
subghz_environment_set_nice_flor_s_rainbow_table_file_name(
|
subghz_environment_set_nice_flor_s_rainbow_table_file_name(
|
||||||
environment, EXT_PATH("subghz/assets/nice_flor_s"));
|
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);
|
SubGhzReceiver* receiver = subghz_receiver_alloc_init(environment);
|
||||||
subghz_receiver_set_filter(receiver, SubGhzProtocolFlag_Decodable);
|
subghz_receiver_set_filter(receiver, SubGhzProtocolFlag_Decodable);
|
||||||
@@ -376,6 +379,7 @@ void subghz_cli_command_decode_raw(Cli* cli, FuriString* args, void* context) {
|
|||||||
environment, EXT_PATH("subghz/assets/came_atomo"));
|
environment, EXT_PATH("subghz/assets/came_atomo"));
|
||||||
subghz_environment_set_nice_flor_s_rainbow_table_file_name(
|
subghz_environment_set_nice_flor_s_rainbow_table_file_name(
|
||||||
environment, EXT_PATH("subghz/assets/nice_flor_s"));
|
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);
|
SubGhzReceiver* receiver = subghz_receiver_alloc_init(environment);
|
||||||
subghz_receiver_set_filter(receiver, SubGhzProtocolFlag_Decodable);
|
subghz_receiver_set_filter(receiver, SubGhzProtocolFlag_Decodable);
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ typedef struct {
|
|||||||
FuriString* item_str;
|
FuriString* item_str;
|
||||||
FlipperFormat* flipper_string;
|
FlipperFormat* flipper_string;
|
||||||
uint8_t type;
|
uint8_t type;
|
||||||
SubGhzPresetDefinition* preset;
|
SubGhzRadioPreset* preset;
|
||||||
} SubGhzHistoryItem;
|
} SubGhzHistoryItem;
|
||||||
|
|
||||||
ARRAY_DEF(SubGhzHistoryItemArray, SubGhzHistoryItem, M_POD_OPLIST)
|
ARRAY_DEF(SubGhzHistoryItemArray, SubGhzHistoryItem, M_POD_OPLIST)
|
||||||
@@ -60,7 +60,7 @@ uint32_t subghz_history_get_frequency(SubGhzHistory* instance, uint16_t idx) {
|
|||||||
return item->preset->frequency;
|
return item->preset->frequency;
|
||||||
}
|
}
|
||||||
|
|
||||||
SubGhzPresetDefinition* subghz_history_get_preset_def(SubGhzHistory* instance, uint16_t idx) {
|
SubGhzRadioPreset* subghz_history_get_radio_preset(SubGhzHistory* instance, uint16_t idx) {
|
||||||
furi_assert(instance);
|
furi_assert(instance);
|
||||||
SubGhzHistoryItem* item = SubGhzHistoryItemArray_get(instance->history->data, idx);
|
SubGhzHistoryItem* item = SubGhzHistoryItemArray_get(instance->history->data, idx);
|
||||||
return item->preset;
|
return item->preset;
|
||||||
@@ -138,7 +138,7 @@ void subghz_history_get_text_item_menu(SubGhzHistory* instance, FuriString* outp
|
|||||||
bool subghz_history_add_to_history(
|
bool subghz_history_add_to_history(
|
||||||
SubGhzHistory* instance,
|
SubGhzHistory* instance,
|
||||||
void* context,
|
void* context,
|
||||||
SubGhzPresetDefinition* preset) {
|
SubGhzRadioPreset* preset) {
|
||||||
furi_assert(instance);
|
furi_assert(instance);
|
||||||
furi_assert(context);
|
furi_assert(context);
|
||||||
|
|
||||||
@@ -158,7 +158,7 @@ bool subghz_history_add_to_history(
|
|||||||
FuriString* text;
|
FuriString* text;
|
||||||
text = furi_string_alloc();
|
text = furi_string_alloc();
|
||||||
SubGhzHistoryItem* item = SubGhzHistoryItemArray_push_raw(instance->history->data);
|
SubGhzHistoryItem* item = SubGhzHistoryItemArray_push_raw(instance->history->data);
|
||||||
item->preset = malloc(sizeof(SubGhzPresetDefinition));
|
item->preset = malloc(sizeof(SubGhzRadioPreset));
|
||||||
item->type = decoder_base->protocol->type;
|
item->type = decoder_base->protocol->type;
|
||||||
item->preset->frequency = preset->frequency;
|
item->preset->frequency = preset->frequency;
|
||||||
item->preset->name = furi_string_alloc();
|
item->preset->name = furi_string_alloc();
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
#include <furi.h>
|
#include <furi.h>
|
||||||
#include <furi_hal.h>
|
#include <furi_hal.h>
|
||||||
#include <lib/flipper_format/flipper_format.h>
|
#include <lib/flipper_format/flipper_format.h>
|
||||||
#include "helpers/subghz_types.h"
|
#include <lib/subghz/types.h>
|
||||||
|
|
||||||
typedef struct SubGhzHistory SubGhzHistory;
|
typedef struct SubGhzHistory SubGhzHistory;
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@ void subghz_history_reset(SubGhzHistory* instance);
|
|||||||
*/
|
*/
|
||||||
uint32_t subghz_history_get_frequency(SubGhzHistory* instance, uint16_t idx);
|
uint32_t subghz_history_get_frequency(SubGhzHistory* instance, uint16_t idx);
|
||||||
|
|
||||||
SubGhzPresetDefinition* subghz_history_get_preset_def(SubGhzHistory* instance, uint16_t idx);
|
SubGhzRadioPreset* subghz_history_get_radio_preset(SubGhzHistory* instance, uint16_t idx);
|
||||||
|
|
||||||
/** Get preset to history[idx]
|
/** Get preset to history[idx]
|
||||||
*
|
*
|
||||||
@@ -88,13 +88,13 @@ bool subghz_history_get_text_space_left(SubGhzHistory* instance, FuriString* out
|
|||||||
*
|
*
|
||||||
* @param instance - SubGhzHistory instance
|
* @param instance - SubGhzHistory instance
|
||||||
* @param context - SubGhzProtocolCommon context
|
* @param context - SubGhzProtocolCommon context
|
||||||
* @param preset - SubGhzPresetDefinition preset
|
* @param preset - SubGhzRadioPreset preset
|
||||||
* @return bool;
|
* @return bool;
|
||||||
*/
|
*/
|
||||||
bool subghz_history_add_to_history(
|
bool subghz_history_add_to_history(
|
||||||
SubGhzHistory* instance,
|
SubGhzHistory* instance,
|
||||||
void* context,
|
void* context,
|
||||||
SubGhzPresetDefinition* preset);
|
SubGhzRadioPreset* preset);
|
||||||
|
|
||||||
/** Get SubGhzProtocolCommonLoad to load into the protocol decoder bin data
|
/** Get SubGhzProtocolCommonLoad to load into the protocol decoder bin data
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -102,8 +102,8 @@ static bool subghz_tx(SubGhz* subghz, uint32_t frequency) {
|
|||||||
furi_assert(subghz->txrx->txrx_state != SubGhzTxRxStateSleep);
|
furi_assert(subghz->txrx->txrx_state != SubGhzTxRxStateSleep);
|
||||||
furi_hal_subghz_idle();
|
furi_hal_subghz_idle();
|
||||||
furi_hal_subghz_set_frequency_and_path(frequency);
|
furi_hal_subghz_set_frequency_and_path(frequency);
|
||||||
|
furi_hal_gpio_write(&gpio_cc1101_g0, false);
|
||||||
furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow);
|
furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow);
|
||||||
furi_hal_gpio_write(&gpio_cc1101_g0, true);
|
|
||||||
bool ret = furi_hal_subghz_tx();
|
bool ret = furi_hal_subghz_tx();
|
||||||
subghz->txrx->txrx_state = SubGhzTxRxStateTx;
|
subghz->txrx->txrx_state = SubGhzTxRxStateTx;
|
||||||
return ret;
|
return ret;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "helpers/subghz_types.h"
|
#include "helpers/subghz_types.h"
|
||||||
|
#include <lib/subghz/types.h>
|
||||||
#include "subghz.h"
|
#include "subghz.h"
|
||||||
#include "views/receiver.h"
|
#include "views/receiver.h"
|
||||||
#include "views/transmitter.h"
|
#include "views/transmitter.h"
|
||||||
@@ -11,8 +12,6 @@
|
|||||||
#include "views/subghz_test_carrier.h"
|
#include "views/subghz_test_carrier.h"
|
||||||
#include "views/subghz_test_packet.h"
|
#include "views/subghz_test_packet.h"
|
||||||
|
|
||||||
// #include <furi.h>
|
|
||||||
// #include <furi_hal.h>
|
|
||||||
#include <gui/gui.h>
|
#include <gui/gui.h>
|
||||||
#include <dialogs/dialogs.h>
|
#include <dialogs/dialogs.h>
|
||||||
#include <gui/scene_manager.h>
|
#include <gui/scene_manager.h>
|
||||||
@@ -24,14 +23,12 @@
|
|||||||
#include <gui/modules/widget.h>
|
#include <gui/modules/widget.h>
|
||||||
|
|
||||||
#include <subghz/scenes/subghz_scene.h>
|
#include <subghz/scenes/subghz_scene.h>
|
||||||
|
|
||||||
#include <lib/subghz/subghz_worker.h>
|
#include <lib/subghz/subghz_worker.h>
|
||||||
|
#include <lib/subghz/subghz_setting.h>
|
||||||
#include <lib/subghz/receiver.h>
|
#include <lib/subghz/receiver.h>
|
||||||
#include <lib/subghz/transmitter.h>
|
#include <lib/subghz/transmitter.h>
|
||||||
|
|
||||||
#include "subghz_history.h"
|
#include "subghz_history.h"
|
||||||
#include "subghz_setting.h"
|
|
||||||
|
|
||||||
#include <gui/modules/variable_item_list.h>
|
#include <gui/modules/variable_item_list.h>
|
||||||
#include <lib/toolbox/path.h>
|
#include <lib/toolbox/path.h>
|
||||||
@@ -49,7 +46,7 @@ struct SubGhzTxRx {
|
|||||||
SubGhzProtocolDecoderBase* decoder_result;
|
SubGhzProtocolDecoderBase* decoder_result;
|
||||||
FlipperFormat* fff_data;
|
FlipperFormat* fff_data;
|
||||||
|
|
||||||
SubGhzPresetDefinition* preset;
|
SubGhzRadioPreset* preset;
|
||||||
SubGhzHistory* history;
|
SubGhzHistory* history;
|
||||||
uint16_t idx_menu_chosen;
|
uint16_t idx_menu_chosen;
|
||||||
SubGhzTxRxState txrx_state;
|
SubGhzTxRxState txrx_state;
|
||||||
@@ -57,6 +54,9 @@ struct SubGhzTxRx {
|
|||||||
uint8_t hopper_timeout;
|
uint8_t hopper_timeout;
|
||||||
uint8_t hopper_idx_frequency;
|
uint8_t hopper_idx_frequency;
|
||||||
SubGhzRxKeyState rx_key_state;
|
SubGhzRxKeyState rx_key_state;
|
||||||
|
|
||||||
|
float raw_threshold_rssi;
|
||||||
|
uint8_t raw_threshold_rssi_low_count;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct SubGhzTxRx SubGhzTxRx;
|
typedef struct SubGhzTxRx SubGhzTxRx;
|
||||||
|
|||||||
@@ -192,7 +192,7 @@ void subghz_view_receiver_draw(Canvas* canvas, SubGhzViewReceiverModel* model) {
|
|||||||
} else {
|
} else {
|
||||||
canvas_set_color(canvas, ColorBlack);
|
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));
|
canvas_draw_str(canvas, 15, 9 + i * FRAME_HEIGHT, furi_string_get_cstr(str_buff));
|
||||||
furi_string_reset(str_buff);
|
furi_string_reset(str_buff);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,10 +23,12 @@ typedef struct {
|
|||||||
FuriString* sample_write;
|
FuriString* sample_write;
|
||||||
FuriString* file_name;
|
FuriString* file_name;
|
||||||
uint8_t* rssi_history;
|
uint8_t* rssi_history;
|
||||||
|
uint8_t rssi_curret;
|
||||||
bool rssi_history_end;
|
bool rssi_history_end;
|
||||||
uint8_t ind_write;
|
uint8_t ind_write;
|
||||||
uint8_t ind_sin;
|
uint8_t ind_sin;
|
||||||
SubGhzReadRAWStatus status;
|
SubGhzReadRAWStatus status;
|
||||||
|
float raw_threshold_rssi;
|
||||||
} SubGhzReadRAWModel;
|
} SubGhzReadRAWModel;
|
||||||
|
|
||||||
void subghz_read_raw_set_callback(
|
void subghz_read_raw_set_callback(
|
||||||
@@ -54,21 +56,27 @@ void subghz_read_raw_add_data_statusbar(
|
|||||||
true);
|
true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void subghz_read_raw_add_data_rssi(SubGhzReadRAW* instance, float rssi) {
|
void subghz_read_raw_add_data_rssi(SubGhzReadRAW* instance, float rssi, bool trace) {
|
||||||
furi_assert(instance);
|
furi_assert(instance);
|
||||||
uint8_t u_rssi = 0;
|
uint8_t u_rssi = 0;
|
||||||
|
|
||||||
if(rssi < -90) {
|
if(rssi < SUBGHZ_RAW_TRESHOLD_MIN) {
|
||||||
u_rssi = 0;
|
u_rssi = 0;
|
||||||
} else {
|
} else {
|
||||||
u_rssi = (uint8_t)((rssi + 90) / 2.7);
|
u_rssi = (uint8_t)((rssi - SUBGHZ_RAW_TRESHOLD_MIN) / 2.7);
|
||||||
}
|
}
|
||||||
|
|
||||||
with_view_model(
|
with_view_model(
|
||||||
instance->view,
|
instance->view,
|
||||||
SubGhzReadRAWModel * model,
|
SubGhzReadRAWModel * model,
|
||||||
{
|
{
|
||||||
model->rssi_history[model->ind_write++] = u_rssi;
|
model->rssi_curret = u_rssi;
|
||||||
|
if(trace) {
|
||||||
|
model->rssi_history[model->ind_write++] = u_rssi;
|
||||||
|
} else {
|
||||||
|
model->rssi_history[model->ind_write] = u_rssi;
|
||||||
|
}
|
||||||
|
|
||||||
if(model->ind_write > SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE) {
|
if(model->ind_write > SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE) {
|
||||||
model->rssi_history_end = true;
|
model->rssi_history_end = true;
|
||||||
model->ind_write = 0;
|
model->ind_write = 0;
|
||||||
@@ -187,24 +195,53 @@ void subghz_read_raw_draw_scale(Canvas* canvas, SubGhzReadRAWModel* model) {
|
|||||||
void subghz_read_raw_draw_rssi(Canvas* canvas, SubGhzReadRAWModel* model) {
|
void subghz_read_raw_draw_rssi(Canvas* canvas, SubGhzReadRAWModel* model) {
|
||||||
int ind = 0;
|
int ind = 0;
|
||||||
int base = 0;
|
int base = 0;
|
||||||
|
uint8_t width = 2;
|
||||||
if(model->rssi_history_end == false) {
|
if(model->rssi_history_end == false) {
|
||||||
for(int i = model->ind_write; i >= 0; i--) {
|
for(int i = model->ind_write; i >= 0; i--) {
|
||||||
canvas_draw_line(canvas, i, 47, i, 47 - model->rssi_history[i]);
|
canvas_draw_line(canvas, i, 47, i, 47 - model->rssi_history[i]);
|
||||||
}
|
}
|
||||||
|
canvas_draw_line(
|
||||||
|
canvas, model->ind_write + 1, 47, model->ind_write + 1, 47 - model->rssi_curret);
|
||||||
if(model->ind_write > 3) {
|
if(model->ind_write > 3) {
|
||||||
canvas_draw_line(canvas, model->ind_write, 47, model->ind_write, 13);
|
canvas_draw_line(
|
||||||
|
canvas, model->ind_write - 1, 47, model->ind_write - 1, 47 - model->rssi_curret);
|
||||||
|
|
||||||
|
for(uint8_t i = 13; i < 47; i += width * 2) {
|
||||||
|
canvas_draw_line(canvas, model->ind_write, i, model->ind_write, i + width);
|
||||||
|
}
|
||||||
canvas_draw_line(canvas, model->ind_write - 2, 12, model->ind_write + 2, 12);
|
canvas_draw_line(canvas, model->ind_write - 2, 12, model->ind_write + 2, 12);
|
||||||
canvas_draw_line(canvas, model->ind_write - 1, 13, model->ind_write + 1, 13);
|
canvas_draw_line(canvas, model->ind_write - 1, 13, model->ind_write + 1, 13);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
int i = 0;
|
||||||
base = SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE - model->ind_write;
|
base = SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE - model->ind_write;
|
||||||
for(int i = SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE; i >= 0; i--) {
|
for(i = SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE; i > 0; i--) {
|
||||||
ind = i - base;
|
ind = i - base;
|
||||||
if(ind < 0) ind += SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE;
|
if(ind < 0) ind += SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE;
|
||||||
canvas_draw_line(canvas, i, 47, i, 47 - model->rssi_history[ind]);
|
canvas_draw_line(canvas, i, 47, i, 47 - model->rssi_history[ind]);
|
||||||
}
|
}
|
||||||
|
|
||||||
canvas_draw_line(
|
canvas_draw_line(
|
||||||
canvas, SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE, 47, SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE, 13);
|
canvas,
|
||||||
|
SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE - 1,
|
||||||
|
47,
|
||||||
|
SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE - 1,
|
||||||
|
47 - model->rssi_curret);
|
||||||
|
canvas_draw_line(
|
||||||
|
canvas,
|
||||||
|
SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE + 1,
|
||||||
|
47,
|
||||||
|
SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE + 1,
|
||||||
|
47 - model->rssi_curret);
|
||||||
|
|
||||||
|
for(uint8_t i = 13; i < 47; i += width * 2) {
|
||||||
|
canvas_draw_line(
|
||||||
|
canvas,
|
||||||
|
SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE,
|
||||||
|
i,
|
||||||
|
SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE,
|
||||||
|
i + width);
|
||||||
|
}
|
||||||
canvas_draw_line(
|
canvas_draw_line(
|
||||||
canvas,
|
canvas,
|
||||||
SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE - 2,
|
SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE - 2,
|
||||||
@@ -220,6 +257,24 @@ void subghz_read_raw_draw_rssi(Canvas* canvas, SubGhzReadRAWModel* model) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void subghz_read_raw_draw_threshold_rssi(Canvas* canvas, SubGhzReadRAWModel* model) {
|
||||||
|
uint8_t x = 118;
|
||||||
|
uint8_t y = 48;
|
||||||
|
|
||||||
|
if(model->raw_threshold_rssi > SUBGHZ_RAW_TRESHOLD_MIN) {
|
||||||
|
uint8_t x = 118;
|
||||||
|
y -= (uint8_t)((model->raw_threshold_rssi - SUBGHZ_RAW_TRESHOLD_MIN) / 2.7);
|
||||||
|
|
||||||
|
uint8_t width = 3;
|
||||||
|
for(uint8_t i = 0; i < x; i += width * 2) {
|
||||||
|
canvas_draw_line(canvas, i, y, i + width, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
canvas_draw_line(canvas, x, y - 2, x, y + 2);
|
||||||
|
canvas_draw_line(canvas, x - 1, y - 1, x - 1, y + 1);
|
||||||
|
canvas_draw_dot(canvas, x - 2, y);
|
||||||
|
}
|
||||||
|
|
||||||
void subghz_read_raw_draw(Canvas* canvas, SubGhzReadRAWModel* model) {
|
void subghz_read_raw_draw(Canvas* canvas, SubGhzReadRAWModel* model) {
|
||||||
uint8_t graphics_mode = 1;
|
uint8_t graphics_mode = 1;
|
||||||
canvas_set_color(canvas, ColorBlack);
|
canvas_set_color(canvas, ColorBlack);
|
||||||
@@ -278,8 +333,9 @@ void subghz_read_raw_draw(Canvas* canvas, SubGhzReadRAWModel* model) {
|
|||||||
} else {
|
} else {
|
||||||
subghz_read_raw_draw_rssi(canvas, model);
|
subghz_read_raw_draw_rssi(canvas, model);
|
||||||
subghz_read_raw_draw_scale(canvas, model);
|
subghz_read_raw_draw_scale(canvas, model);
|
||||||
|
subghz_read_raw_draw_threshold_rssi(canvas, model);
|
||||||
canvas_set_font_direction(canvas, CanvasDirectionBottomToTop);
|
canvas_set_font_direction(canvas, CanvasDirectionBottomToTop);
|
||||||
canvas_draw_str(canvas, 126, 40, "RSSI");
|
canvas_draw_str(canvas, 128, 40, "RSSI");
|
||||||
canvas_set_font_direction(canvas, CanvasDirectionLeftToRight);
|
canvas_set_font_direction(canvas, CanvasDirectionLeftToRight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -433,7 +489,8 @@ bool subghz_read_raw_input(InputEvent* event, void* context) {
|
|||||||
void subghz_read_raw_set_status(
|
void subghz_read_raw_set_status(
|
||||||
SubGhzReadRAW* instance,
|
SubGhzReadRAW* instance,
|
||||||
SubGhzReadRAWStatus status,
|
SubGhzReadRAWStatus status,
|
||||||
const char* file_name) {
|
const char* file_name,
|
||||||
|
float raw_threshold_rssi) {
|
||||||
furi_assert(instance);
|
furi_assert(instance);
|
||||||
|
|
||||||
switch(status) {
|
switch(status) {
|
||||||
@@ -447,6 +504,7 @@ void subghz_read_raw_set_status(
|
|||||||
model->ind_write = 0;
|
model->ind_write = 0;
|
||||||
furi_string_reset(model->file_name);
|
furi_string_reset(model->file_name);
|
||||||
furi_string_set(model->sample_write, "0 spl.");
|
furi_string_set(model->sample_write, "0 spl.");
|
||||||
|
model->raw_threshold_rssi = raw_threshold_rssi;
|
||||||
},
|
},
|
||||||
true);
|
true);
|
||||||
break;
|
break;
|
||||||
@@ -536,6 +594,7 @@ SubGhzReadRAW* subghz_read_raw_alloc() {
|
|||||||
model->sample_write = furi_string_alloc();
|
model->sample_write = furi_string_alloc();
|
||||||
model->file_name = furi_string_alloc();
|
model->file_name = furi_string_alloc();
|
||||||
model->rssi_history = malloc(SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE * sizeof(uint8_t));
|
model->rssi_history = malloc(SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE * sizeof(uint8_t));
|
||||||
|
model->raw_threshold_rssi = -127.0f;
|
||||||
},
|
},
|
||||||
true);
|
true);
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
#include <gui/view.h>
|
#include <gui/view.h>
|
||||||
#include "../helpers/subghz_custom_event.h"
|
#include "../helpers/subghz_custom_event.h"
|
||||||
|
|
||||||
|
#define SUBGHZ_RAW_TRESHOLD_MIN -90.0f
|
||||||
|
|
||||||
typedef struct SubGhzReadRAW SubGhzReadRAW;
|
typedef struct SubGhzReadRAW SubGhzReadRAW;
|
||||||
|
|
||||||
typedef void (*SubGhzReadRAWCallback)(SubGhzCustomEvent event, void* context);
|
typedef void (*SubGhzReadRAWCallback)(SubGhzCustomEvent event, void* context);
|
||||||
@@ -40,11 +42,12 @@ void subghz_read_raw_stop_send(SubGhzReadRAW* instance);
|
|||||||
|
|
||||||
void subghz_read_raw_update_sin(SubGhzReadRAW* instance);
|
void subghz_read_raw_update_sin(SubGhzReadRAW* instance);
|
||||||
|
|
||||||
void subghz_read_raw_add_data_rssi(SubGhzReadRAW* instance, float rssi);
|
void subghz_read_raw_add_data_rssi(SubGhzReadRAW* instance, float rssi, bool trace);
|
||||||
|
|
||||||
void subghz_read_raw_set_status(
|
void subghz_read_raw_set_status(
|
||||||
SubGhzReadRAW* instance,
|
SubGhzReadRAW* instance,
|
||||||
SubGhzReadRAWStatus status,
|
SubGhzReadRAWStatus status,
|
||||||
const char* file_name);
|
const char* file_name,
|
||||||
|
float raw_threshold_rssi);
|
||||||
|
|
||||||
View* subghz_read_raw_get_view(SubGhzReadRAW* subghz_static);
|
View* subghz_read_raw_get_view(SubGhzReadRAW* subghz_static);
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ typedef struct {
|
|||||||
bool down_pressed;
|
bool down_pressed;
|
||||||
bool ok_pressed;
|
bool ok_pressed;
|
||||||
bool connected;
|
bool connected;
|
||||||
|
bool is_cursor_set;
|
||||||
} BtHidTikTokModel;
|
} BtHidTikTokModel;
|
||||||
|
|
||||||
static void bt_hid_tiktok_draw_callback(Canvas* canvas, void* context) {
|
static void bt_hid_tiktok_draw_callback(Canvas* canvas, void* context) {
|
||||||
@@ -88,48 +89,48 @@ static void bt_hid_tiktok_draw_callback(Canvas* canvas, void* context) {
|
|||||||
elements_multiline_text_aligned(canvas, 13, 62, AlignLeft, AlignBottom, "Hold to exit");
|
elements_multiline_text_aligned(canvas, 13, 62, AlignLeft, AlignBottom, "Hold to exit");
|
||||||
}
|
}
|
||||||
|
|
||||||
static void bt_hid_tiktok_process_press(BtHidTikTok* bt_hid_tiktok, InputEvent* event) {
|
static void bt_hid_tiktok_reset_cursor() {
|
||||||
with_view_model(
|
// Set cursor to the phone's left up corner
|
||||||
bt_hid_tiktok->view,
|
// Delays to guarantee one packet per connection interval
|
||||||
BtHidTikTokModel * model,
|
for(size_t i = 0; i < 8; i++) {
|
||||||
{
|
furi_hal_bt_hid_mouse_move(-127, -127);
|
||||||
if(event->key == InputKeyUp) {
|
furi_delay_ms(50);
|
||||||
model->up_pressed = true;
|
}
|
||||||
} else if(event->key == InputKeyDown) {
|
// Move cursor from the corner
|
||||||
model->down_pressed = true;
|
furi_hal_bt_hid_mouse_move(20, 120);
|
||||||
} else if(event->key == InputKeyLeft) {
|
furi_delay_ms(50);
|
||||||
model->left_pressed = true;
|
|
||||||
furi_hal_bt_hid_consumer_key_press(HID_CONSUMER_VOLUME_DECREMENT);
|
|
||||||
} else if(event->key == InputKeyRight) {
|
|
||||||
model->right_pressed = true;
|
|
||||||
furi_hal_bt_hid_consumer_key_press(HID_CONSUMER_VOLUME_INCREMENT);
|
|
||||||
} else if(event->key == InputKeyOk) {
|
|
||||||
model->ok_pressed = true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void bt_hid_tiktok_process_release(BtHidTikTok* bt_hid_tiktok, InputEvent* event) {
|
static void bt_hid_tiktok_process_press(BtHidTikTokModel* model, InputEvent* event) {
|
||||||
with_view_model(
|
if(event->key == InputKeyUp) {
|
||||||
bt_hid_tiktok->view,
|
model->up_pressed = true;
|
||||||
BtHidTikTokModel * model,
|
} else if(event->key == InputKeyDown) {
|
||||||
{
|
model->down_pressed = true;
|
||||||
if(event->key == InputKeyUp) {
|
} else if(event->key == InputKeyLeft) {
|
||||||
model->up_pressed = false;
|
model->left_pressed = true;
|
||||||
} else if(event->key == InputKeyDown) {
|
furi_hal_bt_hid_consumer_key_press(HID_CONSUMER_VOLUME_DECREMENT);
|
||||||
model->down_pressed = false;
|
} else if(event->key == InputKeyRight) {
|
||||||
} else if(event->key == InputKeyLeft) {
|
model->right_pressed = true;
|
||||||
model->left_pressed = false;
|
furi_hal_bt_hid_consumer_key_press(HID_CONSUMER_VOLUME_INCREMENT);
|
||||||
furi_hal_bt_hid_consumer_key_release(HID_CONSUMER_VOLUME_DECREMENT);
|
} else if(event->key == InputKeyOk) {
|
||||||
} else if(event->key == InputKeyRight) {
|
model->ok_pressed = true;
|
||||||
model->right_pressed = false;
|
}
|
||||||
furi_hal_bt_hid_consumer_key_release(HID_CONSUMER_VOLUME_INCREMENT);
|
}
|
||||||
} else if(event->key == InputKeyOk) {
|
|
||||||
model->ok_pressed = false;
|
static void bt_hid_tiktok_process_release(BtHidTikTokModel* model, InputEvent* event) {
|
||||||
}
|
if(event->key == InputKeyUp) {
|
||||||
},
|
model->up_pressed = false;
|
||||||
true);
|
} else if(event->key == InputKeyDown) {
|
||||||
|
model->down_pressed = false;
|
||||||
|
} else if(event->key == InputKeyLeft) {
|
||||||
|
model->left_pressed = false;
|
||||||
|
furi_hal_bt_hid_consumer_key_release(HID_CONSUMER_VOLUME_DECREMENT);
|
||||||
|
} else if(event->key == InputKeyRight) {
|
||||||
|
model->right_pressed = false;
|
||||||
|
furi_hal_bt_hid_consumer_key_release(HID_CONSUMER_VOLUME_INCREMENT);
|
||||||
|
} else if(event->key == InputKeyOk) {
|
||||||
|
model->ok_pressed = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool bt_hid_tiktok_input_callback(InputEvent* event, void* context) {
|
static bool bt_hid_tiktok_input_callback(InputEvent* event, void* context) {
|
||||||
@@ -137,43 +138,59 @@ static bool bt_hid_tiktok_input_callback(InputEvent* event, void* context) {
|
|||||||
BtHidTikTok* bt_hid_tiktok = context;
|
BtHidTikTok* bt_hid_tiktok = context;
|
||||||
bool consumed = false;
|
bool consumed = false;
|
||||||
|
|
||||||
if(event->type == InputTypePress) {
|
with_view_model(
|
||||||
bt_hid_tiktok_process_press(bt_hid_tiktok, event);
|
bt_hid_tiktok->view,
|
||||||
consumed = true;
|
BtHidTikTokModel * model,
|
||||||
} else if(event->type == InputTypeRelease) {
|
{
|
||||||
bt_hid_tiktok_process_release(bt_hid_tiktok, event);
|
if(event->type == InputTypePress) {
|
||||||
consumed = true;
|
bt_hid_tiktok_process_press(model, event);
|
||||||
} else if(event->type == InputTypeShort) {
|
if(model->connected && !model->is_cursor_set) {
|
||||||
if(event->key == InputKeyOk) {
|
bt_hid_tiktok_reset_cursor();
|
||||||
furi_hal_bt_hid_mouse_press(HID_MOUSE_BTN_LEFT);
|
model->is_cursor_set = true;
|
||||||
furi_delay_ms(50);
|
}
|
||||||
furi_hal_bt_hid_mouse_release(HID_MOUSE_BTN_LEFT);
|
consumed = true;
|
||||||
furi_delay_ms(50);
|
} else if(event->type == InputTypeRelease) {
|
||||||
furi_hal_bt_hid_mouse_press(HID_MOUSE_BTN_LEFT);
|
bt_hid_tiktok_process_release(model, event);
|
||||||
furi_delay_ms(50);
|
consumed = true;
|
||||||
furi_hal_bt_hid_mouse_release(HID_MOUSE_BTN_LEFT);
|
} else if(event->type == InputTypeShort) {
|
||||||
consumed = true;
|
if(event->key == InputKeyOk) {
|
||||||
} else if(event->key == InputKeyUp) {
|
furi_hal_bt_hid_mouse_press(HID_MOUSE_BTN_LEFT);
|
||||||
// Emulate up swipe
|
furi_delay_ms(50);
|
||||||
furi_hal_bt_hid_mouse_scroll(-6);
|
furi_hal_bt_hid_mouse_release(HID_MOUSE_BTN_LEFT);
|
||||||
furi_hal_bt_hid_mouse_scroll(-12);
|
furi_delay_ms(50);
|
||||||
furi_hal_bt_hid_mouse_scroll(-19);
|
furi_hal_bt_hid_mouse_press(HID_MOUSE_BTN_LEFT);
|
||||||
furi_hal_bt_hid_mouse_scroll(-12);
|
furi_delay_ms(50);
|
||||||
furi_hal_bt_hid_mouse_scroll(-6);
|
furi_hal_bt_hid_mouse_release(HID_MOUSE_BTN_LEFT);
|
||||||
consumed = true;
|
consumed = true;
|
||||||
} else if(event->key == InputKeyDown) {
|
} else if(event->key == InputKeyUp) {
|
||||||
// Emulate down swipe
|
// Emulate up swipe
|
||||||
furi_hal_bt_hid_mouse_scroll(6);
|
furi_hal_bt_hid_mouse_scroll(-6);
|
||||||
furi_hal_bt_hid_mouse_scroll(12);
|
furi_hal_bt_hid_mouse_scroll(-12);
|
||||||
furi_hal_bt_hid_mouse_scroll(19);
|
furi_hal_bt_hid_mouse_scroll(-19);
|
||||||
furi_hal_bt_hid_mouse_scroll(12);
|
furi_hal_bt_hid_mouse_scroll(-12);
|
||||||
furi_hal_bt_hid_mouse_scroll(6);
|
furi_hal_bt_hid_mouse_scroll(-6);
|
||||||
consumed = true;
|
consumed = true;
|
||||||
} else if(event->key == InputKeyBack) {
|
} else if(event->key == InputKeyDown) {
|
||||||
furi_hal_bt_hid_consumer_key_release_all();
|
// Emulate down swipe
|
||||||
consumed = true;
|
furi_hal_bt_hid_mouse_scroll(6);
|
||||||
}
|
furi_hal_bt_hid_mouse_scroll(12);
|
||||||
}
|
furi_hal_bt_hid_mouse_scroll(19);
|
||||||
|
furi_hal_bt_hid_mouse_scroll(12);
|
||||||
|
furi_hal_bt_hid_mouse_scroll(6);
|
||||||
|
consumed = true;
|
||||||
|
} else if(event->key == InputKeyBack) {
|
||||||
|
furi_hal_bt_hid_consumer_key_release_all();
|
||||||
|
consumed = true;
|
||||||
|
}
|
||||||
|
} else if(event->type == InputTypeLong) {
|
||||||
|
if(event->key == InputKeyBack) {
|
||||||
|
furi_hal_bt_hid_consumer_key_release_all();
|
||||||
|
model->is_cursor_set = false;
|
||||||
|
consumed = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
true);
|
||||||
|
|
||||||
return consumed;
|
return consumed;
|
||||||
}
|
}
|
||||||
@@ -203,5 +220,11 @@ View* bt_hid_tiktok_get_view(BtHidTikTok* bt_hid_tiktok) {
|
|||||||
void bt_hid_tiktok_set_connected_status(BtHidTikTok* bt_hid_tiktok, bool connected) {
|
void bt_hid_tiktok_set_connected_status(BtHidTikTok* bt_hid_tiktok, bool connected) {
|
||||||
furi_assert(bt_hid_tiktok);
|
furi_assert(bt_hid_tiktok);
|
||||||
with_view_model(
|
with_view_model(
|
||||||
bt_hid_tiktok->view, BtHidTikTokModel * model, { model->connected = connected; }, true);
|
bt_hid_tiktok->view,
|
||||||
|
BtHidTikTokModel * model,
|
||||||
|
{
|
||||||
|
model->connected = connected;
|
||||||
|
model->is_cursor_set = false;
|
||||||
|
},
|
||||||
|
true);
|
||||||
}
|
}
|
||||||
|
|||||||
105
applications/plugins/dap_link/README.md
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
# Flipper Zero as CMSIS DAP/DAP Link
|
||||||
|
Flipper Zero as a [Free-DAP](https://github.com/ataradov/free-dap) based SWD\JTAG debugger. Free-DAP is a free and open source firmware implementation of the [CMSIS-DAP](https://www.keil.com/pack/doc/CMSIS_Dev/DAP/html/index.html) debugger.
|
||||||
|
|
||||||
|
## Protocols
|
||||||
|
SWD, JTAG , CMSIS-DAP v1 (18 KiB/s), CMSIS-DAP v2 (46 KiB/s), VCP (USB-UART).
|
||||||
|
|
||||||
|
WinUSB for driverless installation for Windows 8 and above.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### VSCode + Cortex-Debug
|
||||||
|
Set `"device": "cmsis-dap"`
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>BluePill configuration example</summary>
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "Attach (DAP)",
|
||||||
|
"cwd": "${workspaceFolder}",
|
||||||
|
"executable": "./build/firmware.elf",
|
||||||
|
"request": "attach",
|
||||||
|
"type": "cortex-debug",
|
||||||
|
"servertype": "openocd",
|
||||||
|
"device": "cmsis-dap",
|
||||||
|
"configFiles": [
|
||||||
|
"interface/cmsis-dap.cfg",
|
||||||
|
"target/stm32f1x.cfg",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Flipper Zero configuration example</summary>
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "Attach (DAP)",
|
||||||
|
"cwd": "${workspaceFolder}",
|
||||||
|
"executable": "./build/latest/firmware.elf",
|
||||||
|
"request": "attach",
|
||||||
|
"type": "cortex-debug",
|
||||||
|
"servertype": "openocd",
|
||||||
|
"device": "cmsis-dap",
|
||||||
|
"svdFile": "./debug/STM32WB55_CM4.svd",
|
||||||
|
"rtos": "FreeRTOS",
|
||||||
|
"configFiles": [
|
||||||
|
"interface/cmsis-dap.cfg",
|
||||||
|
"./debug/stm32wbx.cfg",
|
||||||
|
],
|
||||||
|
"postAttachCommands": [
|
||||||
|
"source debug/flipperapps.py",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### OpenOCD
|
||||||
|
Use `interface/cmsis-dap.cfg`. You will need OpenOCD v0.11.0.
|
||||||
|
|
||||||
|
Additional commands:
|
||||||
|
* `cmsis_dap_backend hid` for CMSIS-DAP v1 protocol.
|
||||||
|
* `cmsis_dap_backend usb_bulk` for CMSIS-DAP v2 protocol.
|
||||||
|
* `cmsis_dap_serial DAP_Oyevoxo` use DAP-Link running on Flipper named `Oyevoxo`.
|
||||||
|
* `cmsis-dap cmd 81` - reboot connected DAP-Link.
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Flash BluePill</summary>
|
||||||
|
|
||||||
|
```
|
||||||
|
openocd -f interface/cmsis-dap.cfg -f target/stm32f1x.cfg -c init -c "program build/firmware.bin reset exit 0x8000000"
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Flash Flipper Zero using DAP v2 protocol</summary>
|
||||||
|
|
||||||
|
```
|
||||||
|
openocd -f interface/cmsis-dap.cfg -c "cmsis_dap_backend usb_bulk" -f debug/stm32wbx.cfg -c init -c "program build/latest/firmware.bin reset exit 0x8000000"
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Reboot connected DAP-Link on Flipper named Oyevoxo</summary>
|
||||||
|
|
||||||
|
```
|
||||||
|
openocd -f interface/cmsis-dap.cfg -c "cmsis_dap_serial DAP_Oyevoxo" -c "transport select swd" -c "adapter speed 4000000" -c init -c "cmsis-dap cmd 81" -c "exit"
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### PlatformIO
|
||||||
|
Use `debug_tool = cmsis-dap` and `upload_protocol = cmsis-dap`. [Documentation](https://docs.platformio.org/en/latest/plus/debug-tools/cmsis-dap.html#debugging-tool-cmsis-dap). Remember that Windows 8 and above do not require drivers.
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>BluePill platformio.ini example</summary>
|
||||||
|
|
||||||
|
```
|
||||||
|
[env:bluepill_f103c8]
|
||||||
|
platform = ststm32
|
||||||
|
board = bluepill_f103c8
|
||||||
|
debug_tool = cmsis-dap
|
||||||
|
upload_protocol = cmsis-dap
|
||||||
|
```
|
||||||
|
</details>
|
||||||
24
applications/plugins/dap_link/application.fam
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
App(
|
||||||
|
appid="dap_link",
|
||||||
|
name="DAP Link",
|
||||||
|
apptype=FlipperAppType.PLUGIN,
|
||||||
|
entry_point="dap_link_app",
|
||||||
|
requires=[
|
||||||
|
"gui",
|
||||||
|
"dialogs",
|
||||||
|
],
|
||||||
|
stack_size=4 * 1024,
|
||||||
|
order=20,
|
||||||
|
fap_icon="dap_link.png",
|
||||||
|
fap_category="Tools",
|
||||||
|
fap_private_libs=[
|
||||||
|
Lib(
|
||||||
|
name="free-dap",
|
||||||
|
cincludes=["."],
|
||||||
|
sources=[
|
||||||
|
"dap.c",
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
fap_icon_assets="icons",
|
||||||
|
)
|
||||||
234
applications/plugins/dap_link/dap_config.h
Normal file
@@ -0,0 +1,234 @@
|
|||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
// Copyright (c) 2022, Alex Taradov <alex@taradov.com>. All rights reserved.
|
||||||
|
|
||||||
|
#ifndef _DAP_CONFIG_H_
|
||||||
|
#define _DAP_CONFIG_H_
|
||||||
|
|
||||||
|
/*- Includes ----------------------------------------------------------------*/
|
||||||
|
#include <furi_hal_gpio.h>
|
||||||
|
|
||||||
|
/*- Definitions -------------------------------------------------------------*/
|
||||||
|
#define DAP_CONFIG_ENABLE_JTAG
|
||||||
|
|
||||||
|
#define DAP_CONFIG_DEFAULT_PORT DAP_PORT_SWD
|
||||||
|
#define DAP_CONFIG_DEFAULT_CLOCK 4200000 // Hz
|
||||||
|
|
||||||
|
#define DAP_CONFIG_PACKET_SIZE 64
|
||||||
|
#define DAP_CONFIG_PACKET_COUNT 1
|
||||||
|
|
||||||
|
#define DAP_CONFIG_JTAG_DEV_COUNT 8
|
||||||
|
|
||||||
|
// DAP_CONFIG_PRODUCT_STR must contain "CMSIS-DAP" to be compatible with the standard
|
||||||
|
#define DAP_CONFIG_VENDOR_STR "Flipper Zero"
|
||||||
|
#define DAP_CONFIG_PRODUCT_STR "Generic CMSIS-DAP Adapter"
|
||||||
|
#define DAP_CONFIG_SER_NUM_STR usb_serial_number
|
||||||
|
#define DAP_CONFIG_CMSIS_DAP_VER_STR "2.0.0"
|
||||||
|
|
||||||
|
#define DAP_CONFIG_RESET_TARGET_FN dap_app_target_reset
|
||||||
|
#define DAP_CONFIG_VENDOR_FN dap_app_vendor_cmd
|
||||||
|
|
||||||
|
// Attribute to use for performance-critical functions
|
||||||
|
#define DAP_CONFIG_PERFORMANCE_ATTR
|
||||||
|
|
||||||
|
// A value at which dap_clock_test() produces 1 kHz output on the SWCLK pin
|
||||||
|
// #define DAP_CONFIG_DELAY_CONSTANT 19000
|
||||||
|
#define DAP_CONFIG_DELAY_CONSTANT 6290
|
||||||
|
|
||||||
|
// A threshold for switching to fast clock (no added delays)
|
||||||
|
// This is the frequency produced by dap_clock_test(1) on the SWCLK pin
|
||||||
|
#define DAP_CONFIG_FAST_CLOCK 2400000 // Hz
|
||||||
|
|
||||||
|
/*- Prototypes --------------------------------------------------------------*/
|
||||||
|
extern char usb_serial_number[16];
|
||||||
|
|
||||||
|
/*- Implementations ---------------------------------------------------------*/
|
||||||
|
extern GpioPin flipper_dap_swclk_pin;
|
||||||
|
extern GpioPin flipper_dap_swdio_pin;
|
||||||
|
extern GpioPin flipper_dap_reset_pin;
|
||||||
|
extern GpioPin flipper_dap_tdo_pin;
|
||||||
|
extern GpioPin flipper_dap_tdi_pin;
|
||||||
|
|
||||||
|
extern void dap_app_vendor_cmd(uint8_t cmd);
|
||||||
|
extern void dap_app_target_reset();
|
||||||
|
extern void dap_app_disconnect();
|
||||||
|
extern void dap_app_connect_swd();
|
||||||
|
extern void dap_app_connect_jtag();
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
static inline void DAP_CONFIG_SWCLK_TCK_write(int value) {
|
||||||
|
furi_hal_gpio_write(&flipper_dap_swclk_pin, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
static inline void DAP_CONFIG_SWDIO_TMS_write(int value) {
|
||||||
|
furi_hal_gpio_write(&flipper_dap_swdio_pin, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
static inline void DAP_CONFIG_TDI_write(int value) {
|
||||||
|
#ifdef DAP_CONFIG_ENABLE_JTAG
|
||||||
|
furi_hal_gpio_write(&flipper_dap_tdi_pin, value);
|
||||||
|
#else
|
||||||
|
(void)value;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
static inline void DAP_CONFIG_TDO_write(int value) {
|
||||||
|
#ifdef DAP_CONFIG_ENABLE_JTAG
|
||||||
|
furi_hal_gpio_write(&flipper_dap_tdo_pin, value);
|
||||||
|
#else
|
||||||
|
(void)value;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
static inline void DAP_CONFIG_nTRST_write(int value) {
|
||||||
|
(void)value;
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
static inline void DAP_CONFIG_nRESET_write(int value) {
|
||||||
|
furi_hal_gpio_write(&flipper_dap_reset_pin, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
static inline int DAP_CONFIG_SWCLK_TCK_read(void) {
|
||||||
|
return furi_hal_gpio_read(&flipper_dap_swclk_pin);
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
static inline int DAP_CONFIG_SWDIO_TMS_read(void) {
|
||||||
|
return furi_hal_gpio_read(&flipper_dap_swdio_pin);
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
static inline int DAP_CONFIG_TDO_read(void) {
|
||||||
|
#ifdef DAP_CONFIG_ENABLE_JTAG
|
||||||
|
return furi_hal_gpio_read(&flipper_dap_tdo_pin);
|
||||||
|
#else
|
||||||
|
return 0;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
static inline int DAP_CONFIG_TDI_read(void) {
|
||||||
|
#ifdef DAP_CONFIG_ENABLE_JTAG
|
||||||
|
return furi_hal_gpio_read(&flipper_dap_tdi_pin);
|
||||||
|
#else
|
||||||
|
return 0;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
static inline int DAP_CONFIG_nTRST_read(void) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
static inline int DAP_CONFIG_nRESET_read(void) {
|
||||||
|
return furi_hal_gpio_read(&flipper_dap_reset_pin);
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
static inline void DAP_CONFIG_SWCLK_TCK_set(void) {
|
||||||
|
LL_GPIO_SetOutputPin(flipper_dap_swclk_pin.port, flipper_dap_swclk_pin.pin);
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
static inline void DAP_CONFIG_SWCLK_TCK_clr(void) {
|
||||||
|
LL_GPIO_ResetOutputPin(flipper_dap_swclk_pin.port, flipper_dap_swclk_pin.pin);
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
static inline void DAP_CONFIG_SWDIO_TMS_in(void) {
|
||||||
|
LL_GPIO_SetPinMode(flipper_dap_swdio_pin.port, flipper_dap_swdio_pin.pin, LL_GPIO_MODE_INPUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
static inline void DAP_CONFIG_SWDIO_TMS_out(void) {
|
||||||
|
LL_GPIO_SetPinMode(flipper_dap_swdio_pin.port, flipper_dap_swdio_pin.pin, LL_GPIO_MODE_OUTPUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
static inline void DAP_CONFIG_SETUP(void) {
|
||||||
|
furi_hal_gpio_init(&flipper_dap_swdio_pin, GpioModeInput, GpioPullNo, GpioSpeedVeryHigh);
|
||||||
|
furi_hal_gpio_init(&flipper_dap_swclk_pin, GpioModeInput, GpioPullNo, GpioSpeedVeryHigh);
|
||||||
|
furi_hal_gpio_init(&flipper_dap_reset_pin, GpioModeInput, GpioPullNo, GpioSpeedVeryHigh);
|
||||||
|
#ifdef DAP_CONFIG_ENABLE_JTAG
|
||||||
|
furi_hal_gpio_init(&flipper_dap_tdo_pin, GpioModeInput, GpioPullNo, GpioSpeedVeryHigh);
|
||||||
|
furi_hal_gpio_init(&flipper_dap_tdi_pin, GpioModeInput, GpioPullNo, GpioSpeedVeryHigh);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
static inline void DAP_CONFIG_DISCONNECT(void) {
|
||||||
|
furi_hal_gpio_init(&flipper_dap_swdio_pin, GpioModeInput, GpioPullNo, GpioSpeedVeryHigh);
|
||||||
|
furi_hal_gpio_init(&flipper_dap_swclk_pin, GpioModeInput, GpioPullNo, GpioSpeedVeryHigh);
|
||||||
|
furi_hal_gpio_init(&flipper_dap_reset_pin, GpioModeInput, GpioPullNo, GpioSpeedVeryHigh);
|
||||||
|
#ifdef DAP_CONFIG_ENABLE_JTAG
|
||||||
|
furi_hal_gpio_init(&flipper_dap_tdo_pin, GpioModeInput, GpioPullNo, GpioSpeedVeryHigh);
|
||||||
|
furi_hal_gpio_init(&flipper_dap_tdi_pin, GpioModeInput, GpioPullNo, GpioSpeedVeryHigh);
|
||||||
|
#endif
|
||||||
|
dap_app_disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
static inline void DAP_CONFIG_CONNECT_SWD(void) {
|
||||||
|
furi_hal_gpio_init(
|
||||||
|
&flipper_dap_swdio_pin, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
|
||||||
|
furi_hal_gpio_write(&flipper_dap_swdio_pin, true);
|
||||||
|
|
||||||
|
furi_hal_gpio_init(
|
||||||
|
&flipper_dap_swclk_pin, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
|
||||||
|
furi_hal_gpio_write(&flipper_dap_swclk_pin, true);
|
||||||
|
|
||||||
|
furi_hal_gpio_init(
|
||||||
|
&flipper_dap_reset_pin, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
|
||||||
|
furi_hal_gpio_write(&flipper_dap_reset_pin, true);
|
||||||
|
|
||||||
|
#ifdef DAP_CONFIG_ENABLE_JTAG
|
||||||
|
furi_hal_gpio_init(&flipper_dap_tdo_pin, GpioModeInput, GpioPullNo, GpioSpeedVeryHigh);
|
||||||
|
furi_hal_gpio_init(&flipper_dap_tdi_pin, GpioModeInput, GpioPullNo, GpioSpeedVeryHigh);
|
||||||
|
#endif
|
||||||
|
dap_app_connect_swd();
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
static inline void DAP_CONFIG_CONNECT_JTAG(void) {
|
||||||
|
furi_hal_gpio_init(
|
||||||
|
&flipper_dap_swdio_pin, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
|
||||||
|
furi_hal_gpio_write(&flipper_dap_swdio_pin, true);
|
||||||
|
|
||||||
|
furi_hal_gpio_init(
|
||||||
|
&flipper_dap_swclk_pin, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
|
||||||
|
furi_hal_gpio_write(&flipper_dap_swclk_pin, true);
|
||||||
|
|
||||||
|
furi_hal_gpio_init(
|
||||||
|
&flipper_dap_reset_pin, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
|
||||||
|
furi_hal_gpio_write(&flipper_dap_reset_pin, true);
|
||||||
|
|
||||||
|
#ifdef DAP_CONFIG_ENABLE_JTAG
|
||||||
|
furi_hal_gpio_init(&flipper_dap_tdo_pin, GpioModeInput, GpioPullNo, GpioSpeedVeryHigh);
|
||||||
|
|
||||||
|
furi_hal_gpio_init(
|
||||||
|
&flipper_dap_tdi_pin, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
|
||||||
|
furi_hal_gpio_write(&flipper_dap_tdi_pin, true);
|
||||||
|
#endif
|
||||||
|
dap_app_connect_jtag();
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
static inline void DAP_CONFIG_LED(int index, int state) {
|
||||||
|
(void)index;
|
||||||
|
(void)state;
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
__attribute__((always_inline)) static inline void DAP_CONFIG_DELAY(uint32_t cycles) {
|
||||||
|
asm volatile("1: subs %[cycles], %[cycles], #1 \n"
|
||||||
|
" bne 1b \n"
|
||||||
|
: [cycles] "+l"(cycles));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // _DAP_CONFIG_H_
|
||||||
521
applications/plugins/dap_link/dap_link.c
Normal file
@@ -0,0 +1,521 @@
|
|||||||
|
#include <dap.h>
|
||||||
|
#include <furi.h>
|
||||||
|
#include <furi_hal_version.h>
|
||||||
|
#include <furi_hal_gpio.h>
|
||||||
|
#include <furi_hal_uart.h>
|
||||||
|
#include <furi_hal_console.h>
|
||||||
|
#include <furi_hal_resources.h>
|
||||||
|
#include <furi_hal_power.h>
|
||||||
|
#include <stm32wbxx_ll_usart.h>
|
||||||
|
#include <stm32wbxx_ll_lpuart.h>
|
||||||
|
|
||||||
|
#include "dap_link.h"
|
||||||
|
#include "dap_config.h"
|
||||||
|
#include "gui/dap_gui.h"
|
||||||
|
#include "usb/dap_v2_usb.h"
|
||||||
|
|
||||||
|
/***************************************************************************/
|
||||||
|
/****************************** DAP COMMON *********************************/
|
||||||
|
/***************************************************************************/
|
||||||
|
|
||||||
|
struct DapApp {
|
||||||
|
FuriThread* dap_thread;
|
||||||
|
FuriThread* cdc_thread;
|
||||||
|
FuriThread* gui_thread;
|
||||||
|
|
||||||
|
DapState state;
|
||||||
|
DapConfig config;
|
||||||
|
};
|
||||||
|
|
||||||
|
void dap_app_get_state(DapApp* app, DapState* state) {
|
||||||
|
*state = app->state;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define DAP_PROCESS_THREAD_TICK 500
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
DapThreadEventStop = (1 << 0),
|
||||||
|
} DapThreadEvent;
|
||||||
|
|
||||||
|
void dap_thread_send_stop(FuriThread* thread) {
|
||||||
|
furi_thread_flags_set(furi_thread_get_id(thread), DapThreadEventStop);
|
||||||
|
}
|
||||||
|
|
||||||
|
GpioPin flipper_dap_swclk_pin;
|
||||||
|
GpioPin flipper_dap_swdio_pin;
|
||||||
|
GpioPin flipper_dap_reset_pin;
|
||||||
|
GpioPin flipper_dap_tdo_pin;
|
||||||
|
GpioPin flipper_dap_tdi_pin;
|
||||||
|
|
||||||
|
/***************************************************************************/
|
||||||
|
/****************************** DAP PROCESS ********************************/
|
||||||
|
/***************************************************************************/
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8_t data[DAP_CONFIG_PACKET_SIZE];
|
||||||
|
uint8_t size;
|
||||||
|
} DapPacket;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
DAPThreadEventStop = DapThreadEventStop,
|
||||||
|
DAPThreadEventRxV1 = (1 << 1),
|
||||||
|
DAPThreadEventRxV2 = (1 << 2),
|
||||||
|
DAPThreadEventUSBConnect = (1 << 3),
|
||||||
|
DAPThreadEventUSBDisconnect = (1 << 4),
|
||||||
|
DAPThreadEventApplyConfig = (1 << 5),
|
||||||
|
DAPThreadEventAll = DAPThreadEventStop | DAPThreadEventRxV1 | DAPThreadEventRxV2 |
|
||||||
|
DAPThreadEventUSBConnect | DAPThreadEventUSBDisconnect |
|
||||||
|
DAPThreadEventApplyConfig,
|
||||||
|
} DAPThreadEvent;
|
||||||
|
|
||||||
|
#define USB_SERIAL_NUMBER_LEN 16
|
||||||
|
char usb_serial_number[USB_SERIAL_NUMBER_LEN] = {0};
|
||||||
|
|
||||||
|
const char* dap_app_get_serial(DapApp* app) {
|
||||||
|
UNUSED(app);
|
||||||
|
return usb_serial_number;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dap_app_rx1_callback(void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
FuriThreadId thread_id = (FuriThreadId)context;
|
||||||
|
furi_thread_flags_set(thread_id, DAPThreadEventRxV1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dap_app_rx2_callback(void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
FuriThreadId thread_id = (FuriThreadId)context;
|
||||||
|
furi_thread_flags_set(thread_id, DAPThreadEventRxV2);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dap_app_usb_state_callback(bool state, void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
FuriThreadId thread_id = (FuriThreadId)context;
|
||||||
|
if(state) {
|
||||||
|
furi_thread_flags_set(thread_id, DAPThreadEventUSBConnect);
|
||||||
|
} else {
|
||||||
|
furi_thread_flags_set(thread_id, DAPThreadEventUSBDisconnect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dap_app_process_v1() {
|
||||||
|
DapPacket tx_packet;
|
||||||
|
DapPacket rx_packet;
|
||||||
|
memset(&tx_packet, 0, sizeof(DapPacket));
|
||||||
|
rx_packet.size = dap_v1_usb_rx(rx_packet.data, DAP_CONFIG_PACKET_SIZE);
|
||||||
|
dap_process_request(rx_packet.data, rx_packet.size, tx_packet.data, DAP_CONFIG_PACKET_SIZE);
|
||||||
|
dap_v1_usb_tx(tx_packet.data, DAP_CONFIG_PACKET_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dap_app_process_v2() {
|
||||||
|
DapPacket tx_packet;
|
||||||
|
DapPacket rx_packet;
|
||||||
|
memset(&tx_packet, 0, sizeof(DapPacket));
|
||||||
|
rx_packet.size = dap_v2_usb_rx(rx_packet.data, DAP_CONFIG_PACKET_SIZE);
|
||||||
|
size_t len = dap_process_request(
|
||||||
|
rx_packet.data, rx_packet.size, tx_packet.data, DAP_CONFIG_PACKET_SIZE);
|
||||||
|
dap_v2_usb_tx(tx_packet.data, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
void dap_app_vendor_cmd(uint8_t cmd) {
|
||||||
|
// openocd -c "cmsis-dap cmd 81"
|
||||||
|
if(cmd == 0x01) {
|
||||||
|
furi_hal_power_reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void dap_app_target_reset() {
|
||||||
|
FURI_LOG_I("DAP", "Target reset");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dap_init_gpio(DapSwdPins swd_pins) {
|
||||||
|
switch(swd_pins) {
|
||||||
|
case DapSwdPinsPA7PA6:
|
||||||
|
flipper_dap_swclk_pin = gpio_ext_pa7;
|
||||||
|
flipper_dap_swdio_pin = gpio_ext_pa6;
|
||||||
|
break;
|
||||||
|
case DapSwdPinsPA14PA13:
|
||||||
|
flipper_dap_swclk_pin = (GpioPin){.port = GPIOA, .pin = LL_GPIO_PIN_14};
|
||||||
|
flipper_dap_swdio_pin = (GpioPin){.port = GPIOA, .pin = LL_GPIO_PIN_13};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
flipper_dap_reset_pin = gpio_ext_pa4;
|
||||||
|
flipper_dap_tdo_pin = gpio_ext_pb3;
|
||||||
|
flipper_dap_tdi_pin = gpio_ext_pb2;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dap_deinit_gpio(DapSwdPins swd_pins) {
|
||||||
|
// setup gpio pins to default state
|
||||||
|
furi_hal_gpio_init(&flipper_dap_reset_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
|
||||||
|
furi_hal_gpio_init(&flipper_dap_tdo_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
|
||||||
|
furi_hal_gpio_init(&flipper_dap_tdi_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
|
||||||
|
|
||||||
|
if(DapSwdPinsPA14PA13 == swd_pins) {
|
||||||
|
// PA14 and PA13 are used by SWD
|
||||||
|
furi_hal_gpio_init_ex(
|
||||||
|
&flipper_dap_swclk_pin,
|
||||||
|
GpioModeAltFunctionPushPull,
|
||||||
|
GpioPullDown,
|
||||||
|
GpioSpeedLow,
|
||||||
|
GpioAltFn0JTCK_SWCLK);
|
||||||
|
furi_hal_gpio_init_ex(
|
||||||
|
&flipper_dap_swdio_pin,
|
||||||
|
GpioModeAltFunctionPushPull,
|
||||||
|
GpioPullUp,
|
||||||
|
GpioSpeedVeryHigh,
|
||||||
|
GpioAltFn0JTMS_SWDIO);
|
||||||
|
} else {
|
||||||
|
furi_hal_gpio_init(&flipper_dap_swclk_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
|
||||||
|
furi_hal_gpio_init(&flipper_dap_swdio_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int32_t dap_process(void* p) {
|
||||||
|
DapApp* app = p;
|
||||||
|
DapState* dap_state = &(app->state);
|
||||||
|
|
||||||
|
// allocate resources
|
||||||
|
FuriHalUsbInterface* usb_config_prev;
|
||||||
|
app->config.swd_pins = DapSwdPinsPA7PA6;
|
||||||
|
DapSwdPins swd_pins_prev = app->config.swd_pins;
|
||||||
|
|
||||||
|
// init pins
|
||||||
|
dap_init_gpio(swd_pins_prev);
|
||||||
|
|
||||||
|
// init dap
|
||||||
|
dap_init();
|
||||||
|
|
||||||
|
// get name
|
||||||
|
const char* name = furi_hal_version_get_name_ptr();
|
||||||
|
if(!name) {
|
||||||
|
name = "Flipper";
|
||||||
|
}
|
||||||
|
snprintf(usb_serial_number, USB_SERIAL_NUMBER_LEN, "DAP_%s", name);
|
||||||
|
|
||||||
|
// init usb
|
||||||
|
usb_config_prev = furi_hal_usb_get_config();
|
||||||
|
dap_common_usb_alloc_name(usb_serial_number);
|
||||||
|
dap_common_usb_set_context(furi_thread_get_id(furi_thread_get_current()));
|
||||||
|
dap_v1_usb_set_rx_callback(dap_app_rx1_callback);
|
||||||
|
dap_v2_usb_set_rx_callback(dap_app_rx2_callback);
|
||||||
|
dap_common_usb_set_state_callback(dap_app_usb_state_callback);
|
||||||
|
furi_hal_usb_set_config(&dap_v2_usb_hid, NULL);
|
||||||
|
|
||||||
|
// work
|
||||||
|
uint32_t events;
|
||||||
|
while(1) {
|
||||||
|
events = furi_thread_flags_wait(DAPThreadEventAll, FuriFlagWaitAny, FuriWaitForever);
|
||||||
|
|
||||||
|
if(!(events & FuriFlagError)) {
|
||||||
|
if(events & DAPThreadEventRxV1) {
|
||||||
|
dap_app_process_v1();
|
||||||
|
dap_state->dap_counter++;
|
||||||
|
dap_state->dap_version = DapVersionV1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(events & DAPThreadEventRxV2) {
|
||||||
|
dap_app_process_v2();
|
||||||
|
dap_state->dap_counter++;
|
||||||
|
dap_state->dap_version = DapVersionV2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(events & DAPThreadEventUSBConnect) {
|
||||||
|
dap_state->usb_connected = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(events & DAPThreadEventUSBDisconnect) {
|
||||||
|
dap_state->usb_connected = false;
|
||||||
|
dap_state->dap_version = DapVersionUnknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(events & DAPThreadEventApplyConfig) {
|
||||||
|
if(swd_pins_prev != app->config.swd_pins) {
|
||||||
|
dap_deinit_gpio(swd_pins_prev);
|
||||||
|
swd_pins_prev = app->config.swd_pins;
|
||||||
|
dap_init_gpio(swd_pins_prev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(events & DAPThreadEventStop) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// deinit usb
|
||||||
|
furi_hal_usb_set_config(usb_config_prev, NULL);
|
||||||
|
dap_common_wait_for_deinit();
|
||||||
|
dap_common_usb_free_name();
|
||||||
|
dap_deinit_gpio(swd_pins_prev);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/***************************************************************************/
|
||||||
|
/****************************** CDC PROCESS ********************************/
|
||||||
|
/***************************************************************************/
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
CDCThreadEventStop = DapThreadEventStop,
|
||||||
|
CDCThreadEventUARTRx = (1 << 1),
|
||||||
|
CDCThreadEventCDCRx = (1 << 2),
|
||||||
|
CDCThreadEventCDCConfig = (1 << 3),
|
||||||
|
CDCThreadEventApplyConfig = (1 << 4),
|
||||||
|
CDCThreadEventAll = CDCThreadEventStop | CDCThreadEventUARTRx | CDCThreadEventCDCRx |
|
||||||
|
CDCThreadEventCDCConfig | CDCThreadEventApplyConfig,
|
||||||
|
} CDCThreadEvent;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
FuriStreamBuffer* rx_stream;
|
||||||
|
FuriThreadId thread_id;
|
||||||
|
FuriHalUartId uart_id;
|
||||||
|
struct usb_cdc_line_coding line_coding;
|
||||||
|
} CDCProcess;
|
||||||
|
|
||||||
|
static void cdc_uart_irq_cb(UartIrqEvent ev, uint8_t data, void* ctx) {
|
||||||
|
CDCProcess* app = ctx;
|
||||||
|
|
||||||
|
if(ev == UartIrqEventRXNE) {
|
||||||
|
furi_stream_buffer_send(app->rx_stream, &data, 1, 0);
|
||||||
|
furi_thread_flags_set(app->thread_id, CDCThreadEventUARTRx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cdc_usb_rx_callback(void* context) {
|
||||||
|
CDCProcess* app = context;
|
||||||
|
furi_thread_flags_set(app->thread_id, CDCThreadEventCDCRx);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cdc_usb_control_line_callback(uint8_t state, void* context) {
|
||||||
|
UNUSED(context);
|
||||||
|
UNUSED(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cdc_usb_config_callback(struct usb_cdc_line_coding* config, void* context) {
|
||||||
|
CDCProcess* app = context;
|
||||||
|
app->line_coding = *config;
|
||||||
|
furi_thread_flags_set(app->thread_id, CDCThreadEventCDCConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
static FuriHalUartId cdc_init_uart(
|
||||||
|
DapUartType type,
|
||||||
|
DapUartTXRX swap,
|
||||||
|
uint32_t baudrate,
|
||||||
|
void (*cb)(UartIrqEvent ev, uint8_t data, void* ctx),
|
||||||
|
void* ctx) {
|
||||||
|
FuriHalUartId uart_id = FuriHalUartIdUSART1;
|
||||||
|
if(baudrate == 0) baudrate = 115200;
|
||||||
|
|
||||||
|
switch(type) {
|
||||||
|
case DapUartTypeUSART1:
|
||||||
|
uart_id = FuriHalUartIdUSART1;
|
||||||
|
furi_hal_console_disable();
|
||||||
|
furi_hal_uart_deinit(uart_id);
|
||||||
|
if(swap == DapUartTXRXSwap) {
|
||||||
|
LL_USART_SetTXRXSwap(USART1, LL_USART_TXRX_SWAPPED);
|
||||||
|
} else {
|
||||||
|
LL_USART_SetTXRXSwap(USART1, LL_USART_TXRX_STANDARD);
|
||||||
|
}
|
||||||
|
furi_hal_uart_init(uart_id, baudrate);
|
||||||
|
furi_hal_uart_set_irq_cb(uart_id, cb, ctx);
|
||||||
|
break;
|
||||||
|
case DapUartTypeLPUART1:
|
||||||
|
uart_id = FuriHalUartIdLPUART1;
|
||||||
|
furi_hal_uart_deinit(uart_id);
|
||||||
|
if(swap == DapUartTXRXSwap) {
|
||||||
|
LL_LPUART_SetTXRXSwap(LPUART1, LL_LPUART_TXRX_SWAPPED);
|
||||||
|
} else {
|
||||||
|
LL_LPUART_SetTXRXSwap(LPUART1, LL_LPUART_TXRX_STANDARD);
|
||||||
|
}
|
||||||
|
furi_hal_uart_init(uart_id, baudrate);
|
||||||
|
furi_hal_uart_set_irq_cb(uart_id, cb, ctx);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return uart_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cdc_deinit_uart(DapUartType type) {
|
||||||
|
switch(type) {
|
||||||
|
case DapUartTypeUSART1:
|
||||||
|
furi_hal_uart_deinit(FuriHalUartIdUSART1);
|
||||||
|
LL_USART_SetTXRXSwap(USART1, LL_USART_TXRX_STANDARD);
|
||||||
|
furi_hal_console_init();
|
||||||
|
break;
|
||||||
|
case DapUartTypeLPUART1:
|
||||||
|
furi_hal_uart_deinit(FuriHalUartIdLPUART1);
|
||||||
|
LL_LPUART_SetTXRXSwap(LPUART1, LL_LPUART_TXRX_STANDARD);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int32_t cdc_process(void* p) {
|
||||||
|
DapApp* dap_app = p;
|
||||||
|
DapState* dap_state = &(dap_app->state);
|
||||||
|
|
||||||
|
dap_app->config.uart_pins = DapUartTypeLPUART1;
|
||||||
|
dap_app->config.uart_swap = DapUartTXRXNormal;
|
||||||
|
|
||||||
|
DapUartType uart_pins_prev = dap_app->config.uart_pins;
|
||||||
|
DapUartTXRX uart_swap_prev = dap_app->config.uart_swap;
|
||||||
|
|
||||||
|
CDCProcess* app = malloc(sizeof(CDCProcess));
|
||||||
|
app->thread_id = furi_thread_get_id(furi_thread_get_current());
|
||||||
|
app->rx_stream = furi_stream_buffer_alloc(512, 1);
|
||||||
|
|
||||||
|
const uint8_t rx_buffer_size = 64;
|
||||||
|
uint8_t* rx_buffer = malloc(rx_buffer_size);
|
||||||
|
|
||||||
|
app->uart_id = cdc_init_uart(
|
||||||
|
uart_pins_prev, uart_swap_prev, dap_state->cdc_baudrate, cdc_uart_irq_cb, app);
|
||||||
|
|
||||||
|
dap_cdc_usb_set_context(app);
|
||||||
|
dap_cdc_usb_set_rx_callback(cdc_usb_rx_callback);
|
||||||
|
dap_cdc_usb_set_control_line_callback(cdc_usb_control_line_callback);
|
||||||
|
dap_cdc_usb_set_config_callback(cdc_usb_config_callback);
|
||||||
|
|
||||||
|
uint32_t events;
|
||||||
|
while(1) {
|
||||||
|
events = furi_thread_flags_wait(CDCThreadEventAll, FuriFlagWaitAny, FuriWaitForever);
|
||||||
|
|
||||||
|
if(!(events & FuriFlagError)) {
|
||||||
|
if(events & CDCThreadEventCDCConfig) {
|
||||||
|
if(dap_state->cdc_baudrate != app->line_coding.dwDTERate) {
|
||||||
|
dap_state->cdc_baudrate = app->line_coding.dwDTERate;
|
||||||
|
if(dap_state->cdc_baudrate > 0) {
|
||||||
|
furi_hal_uart_set_br(app->uart_id, dap_state->cdc_baudrate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(events & CDCThreadEventUARTRx) {
|
||||||
|
size_t len =
|
||||||
|
furi_stream_buffer_receive(app->rx_stream, rx_buffer, rx_buffer_size, 0);
|
||||||
|
|
||||||
|
if(len > 0) {
|
||||||
|
dap_cdc_usb_tx(rx_buffer, len);
|
||||||
|
}
|
||||||
|
dap_state->cdc_rx_counter += len;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(events & CDCThreadEventCDCRx) {
|
||||||
|
size_t len = dap_cdc_usb_rx(rx_buffer, rx_buffer_size);
|
||||||
|
if(len > 0) {
|
||||||
|
furi_hal_uart_tx(app->uart_id, rx_buffer, len);
|
||||||
|
}
|
||||||
|
dap_state->cdc_tx_counter += len;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(events & CDCThreadEventApplyConfig) {
|
||||||
|
if(uart_pins_prev != dap_app->config.uart_pins ||
|
||||||
|
uart_swap_prev != dap_app->config.uart_swap) {
|
||||||
|
cdc_deinit_uart(uart_pins_prev);
|
||||||
|
uart_pins_prev = dap_app->config.uart_pins;
|
||||||
|
uart_swap_prev = dap_app->config.uart_swap;
|
||||||
|
app->uart_id = cdc_init_uart(
|
||||||
|
uart_pins_prev,
|
||||||
|
uart_swap_prev,
|
||||||
|
dap_state->cdc_baudrate,
|
||||||
|
cdc_uart_irq_cb,
|
||||||
|
app);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(events & CDCThreadEventStop) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cdc_deinit_uart(uart_pins_prev);
|
||||||
|
free(rx_buffer);
|
||||||
|
furi_stream_buffer_free(app->rx_stream);
|
||||||
|
free(app);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/***************************************************************************/
|
||||||
|
/******************************* MAIN APP **********************************/
|
||||||
|
/***************************************************************************/
|
||||||
|
|
||||||
|
static FuriThread* furi_thread_alloc_ex(
|
||||||
|
const char* name,
|
||||||
|
uint32_t stack_size,
|
||||||
|
FuriThreadCallback callback,
|
||||||
|
void* context) {
|
||||||
|
FuriThread* thread = furi_thread_alloc();
|
||||||
|
furi_thread_set_name(thread, name);
|
||||||
|
furi_thread_set_stack_size(thread, stack_size);
|
||||||
|
furi_thread_set_callback(thread, callback);
|
||||||
|
furi_thread_set_context(thread, context);
|
||||||
|
return thread;
|
||||||
|
}
|
||||||
|
|
||||||
|
static DapApp* dap_app_alloc() {
|
||||||
|
DapApp* dap_app = malloc(sizeof(DapApp));
|
||||||
|
dap_app->dap_thread = furi_thread_alloc_ex("DAP Process", 1024, dap_process, dap_app);
|
||||||
|
dap_app->cdc_thread = furi_thread_alloc_ex("DAP CDC", 1024, cdc_process, dap_app);
|
||||||
|
dap_app->gui_thread = furi_thread_alloc_ex("DAP GUI", 1024, dap_gui_thread, dap_app);
|
||||||
|
return dap_app;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dap_app_free(DapApp* dap_app) {
|
||||||
|
furi_assert(dap_app);
|
||||||
|
furi_thread_free(dap_app->dap_thread);
|
||||||
|
furi_thread_free(dap_app->cdc_thread);
|
||||||
|
furi_thread_free(dap_app->gui_thread);
|
||||||
|
free(dap_app);
|
||||||
|
}
|
||||||
|
|
||||||
|
static DapApp* app_handle = NULL;
|
||||||
|
|
||||||
|
void dap_app_disconnect() {
|
||||||
|
app_handle->state.dap_mode = DapModeDisconnected;
|
||||||
|
}
|
||||||
|
|
||||||
|
void dap_app_connect_swd() {
|
||||||
|
app_handle->state.dap_mode = DapModeSWD;
|
||||||
|
}
|
||||||
|
|
||||||
|
void dap_app_connect_jtag() {
|
||||||
|
app_handle->state.dap_mode = DapModeJTAG;
|
||||||
|
}
|
||||||
|
|
||||||
|
void dap_app_set_config(DapApp* app, DapConfig* config) {
|
||||||
|
app->config = *config;
|
||||||
|
furi_thread_flags_set(furi_thread_get_id(app->dap_thread), DAPThreadEventApplyConfig);
|
||||||
|
furi_thread_flags_set(furi_thread_get_id(app->cdc_thread), CDCThreadEventApplyConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
DapConfig* dap_app_get_config(DapApp* app) {
|
||||||
|
return &app->config;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t dap_link_app(void* p) {
|
||||||
|
UNUSED(p);
|
||||||
|
|
||||||
|
// alloc app
|
||||||
|
DapApp* app = dap_app_alloc();
|
||||||
|
app_handle = app;
|
||||||
|
|
||||||
|
furi_thread_start(app->dap_thread);
|
||||||
|
furi_thread_start(app->cdc_thread);
|
||||||
|
furi_thread_start(app->gui_thread);
|
||||||
|
|
||||||
|
// wait until gui thread is finished
|
||||||
|
furi_thread_join(app->gui_thread);
|
||||||
|
|
||||||
|
// send stop event to threads
|
||||||
|
dap_thread_send_stop(app->dap_thread);
|
||||||
|
dap_thread_send_stop(app->cdc_thread);
|
||||||
|
|
||||||
|
// wait for threads to stop
|
||||||
|
furi_thread_join(app->dap_thread);
|
||||||
|
furi_thread_join(app->cdc_thread);
|
||||||
|
|
||||||
|
// free app
|
||||||
|
dap_app_free(app);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
55
applications/plugins/dap_link/dap_link.h
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
DapModeDisconnected,
|
||||||
|
DapModeSWD,
|
||||||
|
DapModeJTAG,
|
||||||
|
} DapMode;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
DapVersionUnknown,
|
||||||
|
DapVersionV1,
|
||||||
|
DapVersionV2,
|
||||||
|
} DapVersion;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
bool usb_connected;
|
||||||
|
DapMode dap_mode;
|
||||||
|
DapVersion dap_version;
|
||||||
|
uint32_t dap_counter;
|
||||||
|
uint32_t cdc_baudrate;
|
||||||
|
uint32_t cdc_tx_counter;
|
||||||
|
uint32_t cdc_rx_counter;
|
||||||
|
} DapState;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
DapSwdPinsPA7PA6, // Pins 2, 3
|
||||||
|
DapSwdPinsPA14PA13, // Pins 10, 12
|
||||||
|
} DapSwdPins;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
DapUartTypeUSART1, // Pins 13, 14
|
||||||
|
DapUartTypeLPUART1, // Pins 15, 16
|
||||||
|
} DapUartType;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
DapUartTXRXNormal,
|
||||||
|
DapUartTXRXSwap,
|
||||||
|
} DapUartTXRX;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
DapSwdPins swd_pins;
|
||||||
|
DapUartType uart_pins;
|
||||||
|
DapUartTXRX uart_swap;
|
||||||
|
} DapConfig;
|
||||||
|
|
||||||
|
typedef struct DapApp DapApp;
|
||||||
|
|
||||||
|
void dap_app_get_state(DapApp* app, DapState* state);
|
||||||
|
|
||||||
|
const char* dap_app_get_serial(DapApp* app);
|
||||||
|
|
||||||
|
void dap_app_set_config(DapApp* app, DapConfig* config);
|
||||||
|
|
||||||
|
DapConfig* dap_app_get_config(DapApp* app);
|
||||||
BIN
applications/plugins/dap_link/dap_link.png
Normal file
|
After Width: | Height: | Size: 143 B |
92
applications/plugins/dap_link/gui/dap_gui.c
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
#include "dap_gui.h"
|
||||||
|
#include "dap_gui_i.h"
|
||||||
|
|
||||||
|
#define DAP_GUI_TICK 250
|
||||||
|
|
||||||
|
static bool dap_gui_custom_event_callback(void* context, uint32_t event) {
|
||||||
|
furi_assert(context);
|
||||||
|
DapGuiApp* app = context;
|
||||||
|
return scene_manager_handle_custom_event(app->scene_manager, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool dap_gui_back_event_callback(void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
DapGuiApp* app = context;
|
||||||
|
return scene_manager_handle_back_event(app->scene_manager);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dap_gui_tick_event_callback(void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
DapGuiApp* app = context;
|
||||||
|
scene_manager_handle_tick_event(app->scene_manager);
|
||||||
|
}
|
||||||
|
|
||||||
|
DapGuiApp* dap_gui_alloc() {
|
||||||
|
DapGuiApp* app = malloc(sizeof(DapGuiApp));
|
||||||
|
app->gui = furi_record_open(RECORD_GUI);
|
||||||
|
app->view_dispatcher = view_dispatcher_alloc();
|
||||||
|
app->scene_manager = scene_manager_alloc(&dap_scene_handlers, app);
|
||||||
|
view_dispatcher_enable_queue(app->view_dispatcher);
|
||||||
|
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
||||||
|
|
||||||
|
view_dispatcher_set_custom_event_callback(app->view_dispatcher, dap_gui_custom_event_callback);
|
||||||
|
view_dispatcher_set_navigation_event_callback(
|
||||||
|
app->view_dispatcher, dap_gui_back_event_callback);
|
||||||
|
view_dispatcher_set_tick_event_callback(
|
||||||
|
app->view_dispatcher, dap_gui_tick_event_callback, DAP_GUI_TICK);
|
||||||
|
|
||||||
|
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
||||||
|
|
||||||
|
app->notifications = furi_record_open(RECORD_NOTIFICATION);
|
||||||
|
|
||||||
|
app->var_item_list = variable_item_list_alloc();
|
||||||
|
view_dispatcher_add_view(
|
||||||
|
app->view_dispatcher,
|
||||||
|
DapGuiAppViewVarItemList,
|
||||||
|
variable_item_list_get_view(app->var_item_list));
|
||||||
|
|
||||||
|
app->main_view = dap_main_view_alloc();
|
||||||
|
view_dispatcher_add_view(
|
||||||
|
app->view_dispatcher, DapGuiAppViewMainView, dap_main_view_get_view(app->main_view));
|
||||||
|
|
||||||
|
app->widget = widget_alloc();
|
||||||
|
view_dispatcher_add_view(
|
||||||
|
app->view_dispatcher, DapGuiAppViewWidget, widget_get_view(app->widget));
|
||||||
|
|
||||||
|
scene_manager_next_scene(app->scene_manager, DapSceneMain);
|
||||||
|
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
|
||||||
|
void dap_gui_free(DapGuiApp* app) {
|
||||||
|
view_dispatcher_remove_view(app->view_dispatcher, DapGuiAppViewVarItemList);
|
||||||
|
variable_item_list_free(app->var_item_list);
|
||||||
|
|
||||||
|
view_dispatcher_remove_view(app->view_dispatcher, DapGuiAppViewMainView);
|
||||||
|
dap_main_view_free(app->main_view);
|
||||||
|
|
||||||
|
view_dispatcher_remove_view(app->view_dispatcher, DapGuiAppViewWidget);
|
||||||
|
widget_free(app->widget);
|
||||||
|
|
||||||
|
// View dispatcher
|
||||||
|
view_dispatcher_free(app->view_dispatcher);
|
||||||
|
scene_manager_free(app->scene_manager);
|
||||||
|
|
||||||
|
// Close records
|
||||||
|
furi_record_close(RECORD_GUI);
|
||||||
|
furi_record_close(RECORD_NOTIFICATION);
|
||||||
|
|
||||||
|
free(app);
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t dap_gui_thread(void* arg) {
|
||||||
|
DapGuiApp* app = dap_gui_alloc();
|
||||||
|
app->dap_app = arg;
|
||||||
|
|
||||||
|
notification_message_block(app->notifications, &sequence_display_backlight_enforce_on);
|
||||||
|
view_dispatcher_run(app->view_dispatcher);
|
||||||
|
notification_message_block(app->notifications, &sequence_display_backlight_enforce_auto);
|
||||||
|
|
||||||
|
dap_gui_free(app);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
4
applications/plugins/dap_link/gui/dap_gui.h
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
int32_t dap_gui_thread(void* arg);
|
||||||
7
applications/plugins/dap_link/gui/dap_gui_custom_event.h
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
DapAppCustomEventConfig,
|
||||||
|
DapAppCustomEventHelp,
|
||||||
|
DapAppCustomEventAbout,
|
||||||
|
} DapAppCustomEvent;
|
||||||
34
applications/plugins/dap_link/gui/dap_gui_i.h
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <gui/gui.h>
|
||||||
|
#include <gui/view_dispatcher.h>
|
||||||
|
#include <gui/scene_manager.h>
|
||||||
|
#include <gui/modules/submenu.h>
|
||||||
|
#include <notification/notification_messages.h>
|
||||||
|
#include <gui/modules/variable_item_list.h>
|
||||||
|
#include <gui/modules/widget.h>
|
||||||
|
|
||||||
|
#include "dap_gui.h"
|
||||||
|
#include "../dap_link.h"
|
||||||
|
#include "scenes/config/dap_scene.h"
|
||||||
|
#include "dap_gui_custom_event.h"
|
||||||
|
#include "views/dap_main_view.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
DapApp* dap_app;
|
||||||
|
|
||||||
|
Gui* gui;
|
||||||
|
NotificationApp* notifications;
|
||||||
|
ViewDispatcher* view_dispatcher;
|
||||||
|
SceneManager* scene_manager;
|
||||||
|
|
||||||
|
VariableItemList* var_item_list;
|
||||||
|
DapMainView* main_view;
|
||||||
|
Widget* widget;
|
||||||
|
} DapGuiApp;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
DapGuiAppViewVarItemList,
|
||||||
|
DapGuiAppViewMainView,
|
||||||
|
DapGuiAppViewWidget,
|
||||||
|
} DapGuiAppView;
|
||||||
30
applications/plugins/dap_link/gui/scenes/config/dap_scene.c
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
#include "dap_scene.h"
|
||||||
|
|
||||||
|
// Generate scene on_enter handlers array
|
||||||
|
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
|
||||||
|
void (*const dap_scene_on_enter_handlers[])(void*) = {
|
||||||
|
#include "dap_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 dap_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = {
|
||||||
|
#include "dap_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 dap_scene_on_exit_handlers[])(void* context) = {
|
||||||
|
#include "dap_scene_config.h"
|
||||||
|
};
|
||||||
|
#undef ADD_SCENE
|
||||||
|
|
||||||
|
// Initialize scene handlers configuration structure
|
||||||
|
const SceneManagerHandlers dap_scene_handlers = {
|
||||||
|
.on_enter_handlers = dap_scene_on_enter_handlers,
|
||||||
|
.on_event_handlers = dap_scene_on_event_handlers,
|
||||||
|
.on_exit_handlers = dap_scene_on_exit_handlers,
|
||||||
|
.scene_num = DapSceneNum,
|
||||||
|
};
|
||||||
29
applications/plugins/dap_link/gui/scenes/config/dap_scene.h
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <gui/scene_manager.h>
|
||||||
|
|
||||||
|
// Generate scene id and total number
|
||||||
|
#define ADD_SCENE(prefix, name, id) DapScene##id,
|
||||||
|
typedef enum {
|
||||||
|
#include "dap_scene_config.h"
|
||||||
|
DapSceneNum,
|
||||||
|
} DapScene;
|
||||||
|
#undef ADD_SCENE
|
||||||
|
|
||||||
|
extern const SceneManagerHandlers dap_scene_handlers;
|
||||||
|
|
||||||
|
// Generate scene on_enter handlers declaration
|
||||||
|
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*);
|
||||||
|
#include "dap_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 "dap_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 "dap_scene_config.h"
|
||||||
|
#undef ADD_SCENE
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
ADD_SCENE(dap, main, Main)
|
||||||
|
ADD_SCENE(dap, config, Config)
|
||||||
|
ADD_SCENE(dap, help, Help)
|
||||||
|
ADD_SCENE(dap, about, About)
|
||||||
68
applications/plugins/dap_link/gui/scenes/dap_scene_about.c
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
#include "../dap_gui_i.h"
|
||||||
|
|
||||||
|
#define DAP_VERSION_APP "0.1.0"
|
||||||
|
#define DAP_DEVELOPED "Dr_Zlo"
|
||||||
|
#define DAP_GITHUB "https://github.com/flipperdevices/flipperzero-firmware"
|
||||||
|
|
||||||
|
void dap_scene_about_on_enter(void* context) {
|
||||||
|
DapGuiApp* 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", DAP_VERSION_APP);
|
||||||
|
furi_string_cat_printf(temp_str, "Developed by: %s\n", DAP_DEVELOPED);
|
||||||
|
furi_string_cat_printf(temp_str, "Github: %s\n\n", DAP_GITHUB);
|
||||||
|
|
||||||
|
furi_string_cat_printf(temp_str, "\e#%s\n", "Description");
|
||||||
|
furi_string_cat_printf(
|
||||||
|
temp_str, "CMSIS-DAP debugger\nbased on Free-DAP\nThanks to Alex Taradov\n\n");
|
||||||
|
|
||||||
|
furi_string_cat_printf(
|
||||||
|
temp_str,
|
||||||
|
"Supported protocols:\n"
|
||||||
|
"SWD, JTAG, UART\n"
|
||||||
|
"DAP v1 (cmsis_backend hid), DAP v2 (cmsis_backend usb_bulk), VCP\n");
|
||||||
|
|
||||||
|
widget_add_text_box_element(
|
||||||
|
app->widget,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
128,
|
||||||
|
14,
|
||||||
|
AlignCenter,
|
||||||
|
AlignBottom,
|
||||||
|
"\e#\e! \e!\n",
|
||||||
|
false);
|
||||||
|
widget_add_text_box_element(
|
||||||
|
app->widget,
|
||||||
|
0,
|
||||||
|
2,
|
||||||
|
128,
|
||||||
|
14,
|
||||||
|
AlignCenter,
|
||||||
|
AlignBottom,
|
||||||
|
"\e#\e! DAP Link \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, DapGuiAppViewWidget);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool dap_scene_about_on_event(void* context, SceneManagerEvent event) {
|
||||||
|
DapGuiApp* app = context;
|
||||||
|
bool consumed = false;
|
||||||
|
UNUSED(app);
|
||||||
|
UNUSED(event);
|
||||||
|
|
||||||
|
return consumed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void dap_scene_about_on_exit(void* context) {
|
||||||
|
DapGuiApp* app = context;
|
||||||
|
|
||||||
|
// Clear views
|
||||||
|
widget_reset(app->widget);
|
||||||
|
}
|
||||||
107
applications/plugins/dap_link/gui/scenes/dap_scene_config.c
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
#include "../dap_gui_i.h"
|
||||||
|
|
||||||
|
static const char* swd_pins[] = {[DapSwdPinsPA7PA6] = "2,3", [DapSwdPinsPA14PA13] = "10,12"};
|
||||||
|
static const char* uart_pins[] = {[DapUartTypeUSART1] = "13,14", [DapUartTypeLPUART1] = "15,16"};
|
||||||
|
static const char* uart_swap[] = {[DapUartTXRXNormal] = "No", [DapUartTXRXSwap] = "Yes"};
|
||||||
|
|
||||||
|
static void swd_pins_cb(VariableItem* item) {
|
||||||
|
DapGuiApp* app = variable_item_get_context(item);
|
||||||
|
uint8_t index = variable_item_get_current_value_index(item);
|
||||||
|
|
||||||
|
variable_item_set_current_value_text(item, swd_pins[index]);
|
||||||
|
|
||||||
|
DapConfig* config = dap_app_get_config(app->dap_app);
|
||||||
|
config->swd_pins = index;
|
||||||
|
dap_app_set_config(app->dap_app, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void uart_pins_cb(VariableItem* item) {
|
||||||
|
DapGuiApp* app = variable_item_get_context(item);
|
||||||
|
uint8_t index = variable_item_get_current_value_index(item);
|
||||||
|
|
||||||
|
variable_item_set_current_value_text(item, uart_pins[index]);
|
||||||
|
|
||||||
|
DapConfig* config = dap_app_get_config(app->dap_app);
|
||||||
|
config->uart_pins = index;
|
||||||
|
dap_app_set_config(app->dap_app, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void uart_swap_cb(VariableItem* item) {
|
||||||
|
DapGuiApp* app = variable_item_get_context(item);
|
||||||
|
uint8_t index = variable_item_get_current_value_index(item);
|
||||||
|
|
||||||
|
variable_item_set_current_value_text(item, uart_swap[index]);
|
||||||
|
|
||||||
|
DapConfig* config = dap_app_get_config(app->dap_app);
|
||||||
|
config->uart_swap = index;
|
||||||
|
dap_app_set_config(app->dap_app, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ok_cb(void* context, uint32_t index) {
|
||||||
|
DapGuiApp* app = context;
|
||||||
|
switch(index) {
|
||||||
|
case 3:
|
||||||
|
view_dispatcher_send_custom_event(app->view_dispatcher, DapAppCustomEventHelp);
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
view_dispatcher_send_custom_event(app->view_dispatcher, DapAppCustomEventAbout);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void dap_scene_config_on_enter(void* context) {
|
||||||
|
DapGuiApp* app = context;
|
||||||
|
VariableItemList* var_item_list = app->var_item_list;
|
||||||
|
VariableItem* item;
|
||||||
|
DapConfig* config = dap_app_get_config(app->dap_app);
|
||||||
|
|
||||||
|
item = variable_item_list_add(
|
||||||
|
var_item_list, "SWC SWD Pins", COUNT_OF(swd_pins), swd_pins_cb, app);
|
||||||
|
variable_item_set_current_value_index(item, config->swd_pins);
|
||||||
|
variable_item_set_current_value_text(item, swd_pins[config->swd_pins]);
|
||||||
|
|
||||||
|
item =
|
||||||
|
variable_item_list_add(var_item_list, "UART Pins", COUNT_OF(uart_pins), uart_pins_cb, app);
|
||||||
|
variable_item_set_current_value_index(item, config->uart_pins);
|
||||||
|
variable_item_set_current_value_text(item, uart_pins[config->uart_pins]);
|
||||||
|
|
||||||
|
item = variable_item_list_add(
|
||||||
|
var_item_list, "Swap TX RX", COUNT_OF(uart_swap), uart_swap_cb, app);
|
||||||
|
variable_item_set_current_value_index(item, config->uart_swap);
|
||||||
|
variable_item_set_current_value_text(item, uart_swap[config->uart_swap]);
|
||||||
|
|
||||||
|
item = variable_item_list_add(var_item_list, "Help and Pinout", 0, NULL, NULL);
|
||||||
|
item = variable_item_list_add(var_item_list, "About", 0, NULL, NULL);
|
||||||
|
|
||||||
|
variable_item_list_set_selected_item(
|
||||||
|
var_item_list, scene_manager_get_scene_state(app->scene_manager, DapSceneConfig));
|
||||||
|
|
||||||
|
variable_item_list_set_enter_callback(var_item_list, ok_cb, app);
|
||||||
|
|
||||||
|
view_dispatcher_switch_to_view(app->view_dispatcher, DapGuiAppViewVarItemList);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool dap_scene_config_on_event(void* context, SceneManagerEvent event) {
|
||||||
|
DapGuiApp* app = context;
|
||||||
|
if(event.type == SceneManagerEventTypeCustom) {
|
||||||
|
if(event.event == DapAppCustomEventHelp) {
|
||||||
|
scene_manager_next_scene(app->scene_manager, DapSceneHelp);
|
||||||
|
return true;
|
||||||
|
} else if(event.event == DapAppCustomEventAbout) {
|
||||||
|
scene_manager_next_scene(app->scene_manager, DapSceneAbout);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void dap_scene_config_on_exit(void* context) {
|
||||||
|
DapGuiApp* app = context;
|
||||||
|
scene_manager_set_scene_state(
|
||||||
|
app->scene_manager,
|
||||||
|
DapSceneConfig,
|
||||||
|
variable_item_list_get_selected_item_index(app->var_item_list));
|
||||||
|
variable_item_list_reset(app->var_item_list);
|
||||||
|
}
|
||||||
102
applications/plugins/dap_link/gui/scenes/dap_scene_help.c
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
#include "../dap_gui_i.h"
|
||||||
|
|
||||||
|
void dap_scene_help_on_enter(void* context) {
|
||||||
|
DapGuiApp* app = context;
|
||||||
|
DapConfig* config = dap_app_get_config(app->dap_app);
|
||||||
|
FuriString* string = furi_string_alloc();
|
||||||
|
|
||||||
|
furi_string_cat(string, "CMSIS DAP/DAP Link v2\r\n");
|
||||||
|
furi_string_cat_printf(string, "Serial: %s\r\n", dap_app_get_serial(app->dap_app));
|
||||||
|
furi_string_cat(
|
||||||
|
string,
|
||||||
|
"Pinout:\r\n"
|
||||||
|
"\e#SWD:\r\n");
|
||||||
|
|
||||||
|
switch(config->swd_pins) {
|
||||||
|
case DapSwdPinsPA7PA6:
|
||||||
|
furi_string_cat(
|
||||||
|
string,
|
||||||
|
" SWC: 2 [A7]\r\n"
|
||||||
|
" SWD: 3 [A6]\r\n");
|
||||||
|
break;
|
||||||
|
case DapSwdPinsPA14PA13:
|
||||||
|
furi_string_cat(
|
||||||
|
string,
|
||||||
|
" SWC: 10 [SWC]\r\n"
|
||||||
|
" SWD: 12 [SIO]\r\n");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
furi_string_cat(string, "\e#JTAG:\r\n");
|
||||||
|
switch(config->swd_pins) {
|
||||||
|
case DapSwdPinsPA7PA6:
|
||||||
|
furi_string_cat(
|
||||||
|
string,
|
||||||
|
" TCK: 2 [A7]\r\n"
|
||||||
|
" TMS: 3 [A6]\r\n"
|
||||||
|
" RST: 4 [A4]\r\n"
|
||||||
|
" TDO: 5 [B3]\r\n"
|
||||||
|
" TDI: 6 [B2]\r\n");
|
||||||
|
break;
|
||||||
|
case DapSwdPinsPA14PA13:
|
||||||
|
furi_string_cat(
|
||||||
|
string,
|
||||||
|
" RST: 4 [A4]\r\n"
|
||||||
|
" TDO: 5 [B3]\r\n"
|
||||||
|
" TDI: 6 [B2]\r\n"
|
||||||
|
" TCK: 10 [SWC]\r\n"
|
||||||
|
" TMS: 12 [SIO]\r\n");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
furi_string_cat(string, "\e#UART:\r\n");
|
||||||
|
switch(config->uart_pins) {
|
||||||
|
case DapUartTypeUSART1:
|
||||||
|
if(config->uart_swap == DapUartTXRXNormal) {
|
||||||
|
furi_string_cat(
|
||||||
|
string,
|
||||||
|
" TX: 13 [TX]\r\n"
|
||||||
|
" RX: 14 [RX]\r\n");
|
||||||
|
} else {
|
||||||
|
furi_string_cat(
|
||||||
|
string,
|
||||||
|
" RX: 13 [TX]\r\n"
|
||||||
|
" TX: 14 [RX]\r\n");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case DapUartTypeLPUART1:
|
||||||
|
if(config->uart_swap == DapUartTXRXNormal) {
|
||||||
|
furi_string_cat(
|
||||||
|
string,
|
||||||
|
" TX: 15 [C1]\r\n"
|
||||||
|
" RX: 16 [C0]\r\n");
|
||||||
|
} else {
|
||||||
|
furi_string_cat(
|
||||||
|
string,
|
||||||
|
" RX: 15 [C1]\r\n"
|
||||||
|
" TX: 16 [C0]\r\n");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
widget_add_text_scroll_element(app->widget, 0, 0, 128, 64, furi_string_get_cstr(string));
|
||||||
|
furi_string_free(string);
|
||||||
|
view_dispatcher_switch_to_view(app->view_dispatcher, DapGuiAppViewWidget);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool dap_scene_help_on_event(void* context, SceneManagerEvent event) {
|
||||||
|
UNUSED(context);
|
||||||
|
UNUSED(event);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void dap_scene_help_on_exit(void* context) {
|
||||||
|
DapGuiApp* app = context;
|
||||||
|
widget_reset(app->widget);
|
||||||
|
}
|
||||||
154
applications/plugins/dap_link/gui/scenes/dap_scene_main.c
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
#include "../dap_gui_i.h"
|
||||||
|
#include "../../dap_link.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
DapState dap_state;
|
||||||
|
bool dap_active;
|
||||||
|
bool tx_active;
|
||||||
|
bool rx_active;
|
||||||
|
} DapSceneMainState;
|
||||||
|
|
||||||
|
static bool process_dap_state(DapGuiApp* app) {
|
||||||
|
DapSceneMainState* state =
|
||||||
|
(DapSceneMainState*)scene_manager_get_scene_state(app->scene_manager, DapSceneMain);
|
||||||
|
if(state == NULL) return true;
|
||||||
|
|
||||||
|
DapState* prev_state = &state->dap_state;
|
||||||
|
DapState next_state;
|
||||||
|
dap_app_get_state(app->dap_app, &next_state);
|
||||||
|
bool need_to_update = false;
|
||||||
|
|
||||||
|
if(prev_state->dap_mode != next_state.dap_mode) {
|
||||||
|
switch(next_state.dap_mode) {
|
||||||
|
case DapModeDisconnected:
|
||||||
|
dap_main_view_set_mode(app->main_view, DapMainViewModeDisconnected);
|
||||||
|
notification_message(app->notifications, &sequence_blink_stop);
|
||||||
|
break;
|
||||||
|
case DapModeSWD:
|
||||||
|
dap_main_view_set_mode(app->main_view, DapMainViewModeSWD);
|
||||||
|
notification_message(app->notifications, &sequence_blink_start_blue);
|
||||||
|
break;
|
||||||
|
case DapModeJTAG:
|
||||||
|
dap_main_view_set_mode(app->main_view, DapMainViewModeJTAG);
|
||||||
|
notification_message(app->notifications, &sequence_blink_start_magenta);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
need_to_update = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(prev_state->dap_version != next_state.dap_version) {
|
||||||
|
switch(next_state.dap_version) {
|
||||||
|
case DapVersionUnknown:
|
||||||
|
dap_main_view_set_version(app->main_view, DapMainViewVersionUnknown);
|
||||||
|
break;
|
||||||
|
case DapVersionV1:
|
||||||
|
dap_main_view_set_version(app->main_view, DapMainViewVersionV1);
|
||||||
|
break;
|
||||||
|
case DapVersionV2:
|
||||||
|
dap_main_view_set_version(app->main_view, DapMainViewVersionV2);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
need_to_update = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(prev_state->usb_connected != next_state.usb_connected) {
|
||||||
|
dap_main_view_set_usb_connected(app->main_view, next_state.usb_connected);
|
||||||
|
need_to_update = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(prev_state->dap_counter != next_state.dap_counter) {
|
||||||
|
if(!state->dap_active) {
|
||||||
|
state->dap_active = true;
|
||||||
|
dap_main_view_set_dap(app->main_view, state->dap_active);
|
||||||
|
need_to_update = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if(state->dap_active) {
|
||||||
|
state->dap_active = false;
|
||||||
|
dap_main_view_set_dap(app->main_view, state->dap_active);
|
||||||
|
need_to_update = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(prev_state->cdc_baudrate != next_state.cdc_baudrate) {
|
||||||
|
dap_main_view_set_baudrate(app->main_view, next_state.cdc_baudrate);
|
||||||
|
need_to_update = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(prev_state->cdc_tx_counter != next_state.cdc_tx_counter) {
|
||||||
|
if(!state->tx_active) {
|
||||||
|
state->tx_active = true;
|
||||||
|
dap_main_view_set_tx(app->main_view, state->tx_active);
|
||||||
|
need_to_update = true;
|
||||||
|
notification_message(app->notifications, &sequence_blink_start_red);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if(state->tx_active) {
|
||||||
|
state->tx_active = false;
|
||||||
|
dap_main_view_set_tx(app->main_view, state->tx_active);
|
||||||
|
need_to_update = true;
|
||||||
|
notification_message(app->notifications, &sequence_blink_stop);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(prev_state->cdc_rx_counter != next_state.cdc_rx_counter) {
|
||||||
|
if(!state->rx_active) {
|
||||||
|
state->rx_active = true;
|
||||||
|
dap_main_view_set_rx(app->main_view, state->rx_active);
|
||||||
|
need_to_update = true;
|
||||||
|
notification_message(app->notifications, &sequence_blink_start_green);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if(state->rx_active) {
|
||||||
|
state->rx_active = false;
|
||||||
|
dap_main_view_set_rx(app->main_view, state->rx_active);
|
||||||
|
need_to_update = true;
|
||||||
|
notification_message(app->notifications, &sequence_blink_stop);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(need_to_update) {
|
||||||
|
dap_main_view_update(app->main_view);
|
||||||
|
}
|
||||||
|
|
||||||
|
*prev_state = next_state;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dap_scene_main_on_left(void* context) {
|
||||||
|
DapGuiApp* app = (DapGuiApp*)context;
|
||||||
|
view_dispatcher_send_custom_event(app->view_dispatcher, DapAppCustomEventConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
void dap_scene_main_on_enter(void* context) {
|
||||||
|
DapGuiApp* app = context;
|
||||||
|
DapSceneMainState* state = malloc(sizeof(DapSceneMainState));
|
||||||
|
dap_main_view_set_left_callback(app->main_view, dap_scene_main_on_left, app);
|
||||||
|
view_dispatcher_switch_to_view(app->view_dispatcher, DapGuiAppViewMainView);
|
||||||
|
scene_manager_set_scene_state(app->scene_manager, DapSceneMain, (uint32_t)state);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool dap_scene_main_on_event(void* context, SceneManagerEvent event) {
|
||||||
|
DapGuiApp* app = context;
|
||||||
|
|
||||||
|
if(event.type == SceneManagerEventTypeCustom) {
|
||||||
|
if(event.event == DapAppCustomEventConfig) {
|
||||||
|
scene_manager_next_scene(app->scene_manager, DapSceneConfig);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else if(event.type == SceneManagerEventTypeTick) {
|
||||||
|
return process_dap_state(app);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void dap_scene_main_on_exit(void* context) {
|
||||||
|
DapGuiApp* app = context;
|
||||||
|
DapSceneMainState* state =
|
||||||
|
(DapSceneMainState*)scene_manager_get_scene_state(app->scene_manager, DapSceneMain);
|
||||||
|
scene_manager_set_scene_state(app->scene_manager, DapSceneMain, (uint32_t)NULL);
|
||||||
|
FURI_SW_MEMBARRIER();
|
||||||
|
free(state);
|
||||||
|
notification_message(app->notifications, &sequence_blink_stop);
|
||||||
|
}
|
||||||
189
applications/plugins/dap_link/gui/views/dap_main_view.c
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
#include "dap_main_view.h"
|
||||||
|
#include "dap_link_icons.h"
|
||||||
|
#include <gui/elements.h>
|
||||||
|
|
||||||
|
// extern const Icon I_ArrowDownEmpty_12x18;
|
||||||
|
// extern const Icon I_ArrowDownFilled_12x18;
|
||||||
|
// extern const Icon I_ArrowUpEmpty_12x18;
|
||||||
|
// extern const Icon I_ArrowUpFilled_12x18;
|
||||||
|
|
||||||
|
struct DapMainView {
|
||||||
|
View* view;
|
||||||
|
DapMainViewButtonCallback cb_left;
|
||||||
|
void* cb_context;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
DapMainViewMode mode;
|
||||||
|
DapMainViewVersion version;
|
||||||
|
bool usb_connected;
|
||||||
|
uint32_t baudrate;
|
||||||
|
bool dap_active;
|
||||||
|
bool tx_active;
|
||||||
|
bool rx_active;
|
||||||
|
} DapMainViewModel;
|
||||||
|
|
||||||
|
static void dap_main_view_draw_callback(Canvas* canvas, void* _model) {
|
||||||
|
DapMainViewModel* model = _model;
|
||||||
|
UNUSED(model);
|
||||||
|
canvas_clear(canvas);
|
||||||
|
elements_button_left(canvas, "Config");
|
||||||
|
|
||||||
|
canvas_set_color(canvas, ColorBlack);
|
||||||
|
canvas_draw_box(canvas, 0, 0, 127, 11);
|
||||||
|
canvas_set_color(canvas, ColorWhite);
|
||||||
|
|
||||||
|
const char* header_string;
|
||||||
|
if(model->usb_connected) {
|
||||||
|
if(model->version == DapMainViewVersionV1) {
|
||||||
|
header_string = "DAP Link V1 Connected";
|
||||||
|
} else if(model->version == DapMainViewVersionV2) {
|
||||||
|
header_string = "DAP Link V2 Connected";
|
||||||
|
} else {
|
||||||
|
header_string = "DAP Link Connected";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
header_string = "DAP Link";
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas_draw_str_aligned(canvas, 64, 9, AlignCenter, AlignBottom, header_string);
|
||||||
|
|
||||||
|
canvas_set_color(canvas, ColorBlack);
|
||||||
|
if(model->dap_active) {
|
||||||
|
canvas_draw_icon(canvas, 14, 16, &I_ArrowUpFilled_12x18);
|
||||||
|
canvas_draw_icon(canvas, 28, 16, &I_ArrowDownFilled_12x18);
|
||||||
|
} else {
|
||||||
|
canvas_draw_icon(canvas, 14, 16, &I_ArrowUpEmpty_12x18);
|
||||||
|
canvas_draw_icon(canvas, 28, 16, &I_ArrowDownEmpty_12x18);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(model->mode) {
|
||||||
|
case DapMainViewModeDisconnected:
|
||||||
|
canvas_draw_str_aligned(canvas, 26, 38, AlignCenter, AlignTop, "----");
|
||||||
|
break;
|
||||||
|
case DapMainViewModeSWD:
|
||||||
|
canvas_draw_str_aligned(canvas, 26, 38, AlignCenter, AlignTop, "SWD");
|
||||||
|
break;
|
||||||
|
case DapMainViewModeJTAG:
|
||||||
|
canvas_draw_str_aligned(canvas, 26, 38, AlignCenter, AlignTop, "JTAG");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(model->tx_active) {
|
||||||
|
canvas_draw_icon(canvas, 87, 16, &I_ArrowUpFilled_12x18);
|
||||||
|
} else {
|
||||||
|
canvas_draw_icon(canvas, 87, 16, &I_ArrowUpEmpty_12x18);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(model->rx_active) {
|
||||||
|
canvas_draw_icon(canvas, 101, 16, &I_ArrowDownFilled_12x18);
|
||||||
|
} else {
|
||||||
|
canvas_draw_icon(canvas, 101, 16, &I_ArrowDownEmpty_12x18);
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas_draw_str_aligned(canvas, 100, 38, AlignCenter, AlignTop, "UART");
|
||||||
|
|
||||||
|
canvas_draw_line(canvas, 44, 52, 123, 52);
|
||||||
|
if(model->baudrate == 0) {
|
||||||
|
canvas_draw_str(canvas, 45, 62, "Baud: ????");
|
||||||
|
} else {
|
||||||
|
char baudrate_str[18];
|
||||||
|
snprintf(baudrate_str, 18, "Baud: %lu", model->baudrate);
|
||||||
|
canvas_draw_str(canvas, 45, 62, baudrate_str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool dap_main_view_input_callback(InputEvent* event, void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
DapMainView* dap_main_view = context;
|
||||||
|
bool consumed = false;
|
||||||
|
|
||||||
|
if(event->type == InputTypeShort) {
|
||||||
|
if(event->key == InputKeyLeft) {
|
||||||
|
if(dap_main_view->cb_left) {
|
||||||
|
dap_main_view->cb_left(dap_main_view->cb_context);
|
||||||
|
}
|
||||||
|
consumed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return consumed;
|
||||||
|
}
|
||||||
|
|
||||||
|
DapMainView* dap_main_view_alloc() {
|
||||||
|
DapMainView* dap_main_view = malloc(sizeof(DapMainView));
|
||||||
|
|
||||||
|
dap_main_view->view = view_alloc();
|
||||||
|
view_allocate_model(dap_main_view->view, ViewModelTypeLocking, sizeof(DapMainViewModel));
|
||||||
|
view_set_context(dap_main_view->view, dap_main_view);
|
||||||
|
view_set_draw_callback(dap_main_view->view, dap_main_view_draw_callback);
|
||||||
|
view_set_input_callback(dap_main_view->view, dap_main_view_input_callback);
|
||||||
|
return dap_main_view;
|
||||||
|
}
|
||||||
|
|
||||||
|
void dap_main_view_free(DapMainView* dap_main_view) {
|
||||||
|
view_free(dap_main_view->view);
|
||||||
|
free(dap_main_view);
|
||||||
|
}
|
||||||
|
|
||||||
|
View* dap_main_view_get_view(DapMainView* dap_main_view) {
|
||||||
|
return dap_main_view->view;
|
||||||
|
}
|
||||||
|
|
||||||
|
void dap_main_view_set_left_callback(
|
||||||
|
DapMainView* dap_main_view,
|
||||||
|
DapMainViewButtonCallback callback,
|
||||||
|
void* context) {
|
||||||
|
with_view_model(
|
||||||
|
dap_main_view->view,
|
||||||
|
DapMainViewModel * model,
|
||||||
|
{
|
||||||
|
UNUSED(model);
|
||||||
|
dap_main_view->cb_left = callback;
|
||||||
|
dap_main_view->cb_context = context;
|
||||||
|
},
|
||||||
|
true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void dap_main_view_set_mode(DapMainView* dap_main_view, DapMainViewMode mode) {
|
||||||
|
with_view_model(
|
||||||
|
dap_main_view->view, DapMainViewModel * model, { model->mode = mode; }, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void dap_main_view_set_dap(DapMainView* dap_main_view, bool active) {
|
||||||
|
with_view_model(
|
||||||
|
dap_main_view->view, DapMainViewModel * model, { model->dap_active = active; }, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void dap_main_view_set_tx(DapMainView* dap_main_view, bool active) {
|
||||||
|
with_view_model(
|
||||||
|
dap_main_view->view, DapMainViewModel * model, { model->tx_active = active; }, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void dap_main_view_set_rx(DapMainView* dap_main_view, bool active) {
|
||||||
|
with_view_model(
|
||||||
|
dap_main_view->view, DapMainViewModel * model, { model->rx_active = active; }, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void dap_main_view_set_baudrate(DapMainView* dap_main_view, uint32_t baudrate) {
|
||||||
|
with_view_model(
|
||||||
|
dap_main_view->view, DapMainViewModel * model, { model->baudrate = baudrate; }, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void dap_main_view_update(DapMainView* dap_main_view) {
|
||||||
|
with_view_model(
|
||||||
|
dap_main_view->view, DapMainViewModel * model, { UNUSED(model); }, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void dap_main_view_set_version(DapMainView* dap_main_view, DapMainViewVersion version) {
|
||||||
|
with_view_model(
|
||||||
|
dap_main_view->view, DapMainViewModel * model, { model->version = version; }, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void dap_main_view_set_usb_connected(DapMainView* dap_main_view, bool connected) {
|
||||||
|
with_view_model(
|
||||||
|
dap_main_view->view,
|
||||||
|
DapMainViewModel * model,
|
||||||
|
{ model->usb_connected = connected; },
|
||||||
|
false);
|
||||||
|
}
|
||||||
45
applications/plugins/dap_link/gui/views/dap_main_view.h
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <gui/view.h>
|
||||||
|
|
||||||
|
typedef struct DapMainView DapMainView;
|
||||||
|
|
||||||
|
typedef void (*DapMainViewButtonCallback)(void* context);
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
DapMainViewVersionUnknown,
|
||||||
|
DapMainViewVersionV1,
|
||||||
|
DapMainViewVersionV2,
|
||||||
|
} DapMainViewVersion;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
DapMainViewModeDisconnected,
|
||||||
|
DapMainViewModeSWD,
|
||||||
|
DapMainViewModeJTAG,
|
||||||
|
} DapMainViewMode;
|
||||||
|
|
||||||
|
DapMainView* dap_main_view_alloc();
|
||||||
|
|
||||||
|
void dap_main_view_free(DapMainView* dap_main_view);
|
||||||
|
|
||||||
|
View* dap_main_view_get_view(DapMainView* dap_main_view);
|
||||||
|
|
||||||
|
void dap_main_view_set_left_callback(
|
||||||
|
DapMainView* dap_main_view,
|
||||||
|
DapMainViewButtonCallback callback,
|
||||||
|
void* context);
|
||||||
|
|
||||||
|
void dap_main_view_set_mode(DapMainView* dap_main_view, DapMainViewMode mode);
|
||||||
|
|
||||||
|
void dap_main_view_set_version(DapMainView* dap_main_view, DapMainViewVersion version);
|
||||||
|
|
||||||
|
void dap_main_view_set_dap(DapMainView* dap_main_view, bool active);
|
||||||
|
|
||||||
|
void dap_main_view_set_tx(DapMainView* dap_main_view, bool active);
|
||||||
|
|
||||||
|
void dap_main_view_set_rx(DapMainView* dap_main_view, bool active);
|
||||||
|
|
||||||
|
void dap_main_view_set_usb_connected(DapMainView* dap_main_view, bool connected);
|
||||||
|
|
||||||
|
void dap_main_view_set_baudrate(DapMainView* dap_main_view, uint32_t baudrate);
|
||||||
|
|
||||||
|
void dap_main_view_update(DapMainView* dap_main_view);
|
||||||
BIN
applications/plugins/dap_link/icons/ArrowDownEmpty_12x18.png
Normal file
|
After Width: | Height: | Size: 160 B |
BIN
applications/plugins/dap_link/icons/ArrowDownFilled_12x18.png
Normal file
|
After Width: | Height: | Size: 168 B |
BIN
applications/plugins/dap_link/icons/ArrowUpEmpty_12x18.png
Normal file
|
After Width: | Height: | Size: 159 B |
BIN
applications/plugins/dap_link/icons/ArrowUpFilled_12x18.png
Normal file
|
After Width: | Height: | Size: 173 B |
1
applications/plugins/dap_link/lib/free-dap
Submodule
994
applications/plugins/dap_link/usb/dap_v2_usb.c
Normal file
@@ -0,0 +1,994 @@
|
|||||||
|
#include <furi.h>
|
||||||
|
#include <usb.h>
|
||||||
|
#include <usb_std.h>
|
||||||
|
#include <usb_hid.h>
|
||||||
|
#include <usb_cdc.h>
|
||||||
|
#include <furi_hal_console.h>
|
||||||
|
|
||||||
|
#include "dap_v2_usb.h"
|
||||||
|
|
||||||
|
// #define DAP_USB_LOG
|
||||||
|
|
||||||
|
#define HID_EP_IN 0x80
|
||||||
|
#define HID_EP_OUT 0x00
|
||||||
|
|
||||||
|
#define DAP_HID_EP_SEND 1
|
||||||
|
#define DAP_HID_EP_RECV 2
|
||||||
|
#define DAP_HID_EP_BULK_RECV 3
|
||||||
|
#define DAP_HID_EP_BULK_SEND 4
|
||||||
|
#define DAP_CDC_EP_COMM 5
|
||||||
|
#define DAP_CDC_EP_SEND 6
|
||||||
|
#define DAP_CDC_EP_RECV 7
|
||||||
|
|
||||||
|
#define DAP_HID_EP_IN (HID_EP_IN | DAP_HID_EP_SEND)
|
||||||
|
#define DAP_HID_EP_OUT (HID_EP_OUT | DAP_HID_EP_RECV)
|
||||||
|
#define DAP_HID_EP_BULK_IN (HID_EP_IN | DAP_HID_EP_BULK_SEND)
|
||||||
|
#define DAP_HID_EP_BULK_OUT (HID_EP_OUT | DAP_HID_EP_BULK_RECV)
|
||||||
|
|
||||||
|
#define DAP_HID_EP_SIZE 64
|
||||||
|
#define DAP_CDC_COMM_EP_SIZE 8
|
||||||
|
#define DAP_CDC_EP_SIZE 64
|
||||||
|
|
||||||
|
#define DAP_BULK_INTERVAL 0
|
||||||
|
#define DAP_HID_INTERVAL 1
|
||||||
|
#define DAP_CDC_INTERVAL 0
|
||||||
|
#define DAP_CDC_COMM_INTERVAL 1
|
||||||
|
|
||||||
|
#define DAP_HID_VID 0x0483
|
||||||
|
#define DAP_HID_PID 0x5740
|
||||||
|
|
||||||
|
#define DAP_USB_EP0_SIZE 8
|
||||||
|
|
||||||
|
#define EP_CFG_DECONFIGURE 0
|
||||||
|
#define EP_CFG_CONFIGURE 1
|
||||||
|
|
||||||
|
enum {
|
||||||
|
USB_INTF_HID,
|
||||||
|
USB_INTF_BULK,
|
||||||
|
USB_INTF_CDC_COMM,
|
||||||
|
USB_INTF_CDC_DATA,
|
||||||
|
USB_INTF_COUNT,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum {
|
||||||
|
USB_STR_ZERO,
|
||||||
|
USB_STR_MANUFACTURER,
|
||||||
|
USB_STR_PRODUCT,
|
||||||
|
USB_STR_SERIAL_NUMBER,
|
||||||
|
USB_STR_CMSIS_DAP_V1,
|
||||||
|
USB_STR_CMSIS_DAP_V2,
|
||||||
|
USB_STR_COM_PORT,
|
||||||
|
USB_STR_COUNT,
|
||||||
|
};
|
||||||
|
|
||||||
|
// static const char* usb_str[] = {
|
||||||
|
// [USB_STR_MANUFACTURER] = "Flipper Devices Inc.",
|
||||||
|
// [USB_STR_PRODUCT] = "Combined VCP and CMSIS-DAP Adapter",
|
||||||
|
// [USB_STR_COM_PORT] = "Virtual COM-Port",
|
||||||
|
// [USB_STR_CMSIS_DAP_V1] = "CMSIS-DAP v1 Adapter",
|
||||||
|
// [USB_STR_CMSIS_DAP_V2] = "CMSIS-DAP v2 Adapter",
|
||||||
|
// [USB_STR_SERIAL_NUMBER] = "01234567890ABCDEF",
|
||||||
|
// };
|
||||||
|
|
||||||
|
static const struct usb_string_descriptor dev_manuf_descr =
|
||||||
|
USB_STRING_DESC("Flipper Devices Inc.");
|
||||||
|
|
||||||
|
static const struct usb_string_descriptor dev_prod_descr =
|
||||||
|
USB_STRING_DESC("Combined VCP and CMSIS-DAP Adapter");
|
||||||
|
|
||||||
|
static struct usb_string_descriptor* dev_serial_descr = NULL;
|
||||||
|
|
||||||
|
static const struct usb_string_descriptor dev_dap_v1_descr =
|
||||||
|
USB_STRING_DESC("CMSIS-DAP v1 Adapter");
|
||||||
|
|
||||||
|
static const struct usb_string_descriptor dev_dap_v2_descr =
|
||||||
|
USB_STRING_DESC("CMSIS-DAP v2 Adapter");
|
||||||
|
|
||||||
|
static const struct usb_string_descriptor dev_com_descr = USB_STRING_DESC("Virtual COM-Port");
|
||||||
|
|
||||||
|
struct HidConfigDescriptor {
|
||||||
|
struct usb_config_descriptor configuration;
|
||||||
|
|
||||||
|
// CMSIS-DAP v1
|
||||||
|
struct usb_interface_descriptor hid_interface;
|
||||||
|
struct usb_hid_descriptor hid;
|
||||||
|
struct usb_endpoint_descriptor hid_ep_in;
|
||||||
|
struct usb_endpoint_descriptor hid_ep_out;
|
||||||
|
|
||||||
|
// CMSIS-DAP v2
|
||||||
|
struct usb_interface_descriptor bulk_interface;
|
||||||
|
struct usb_endpoint_descriptor bulk_ep_out;
|
||||||
|
struct usb_endpoint_descriptor bulk_ep_in;
|
||||||
|
|
||||||
|
// CDC
|
||||||
|
struct usb_iad_descriptor iad;
|
||||||
|
struct usb_interface_descriptor interface_comm;
|
||||||
|
struct usb_cdc_header_desc cdc_header;
|
||||||
|
struct usb_cdc_call_mgmt_desc cdc_acm;
|
||||||
|
struct usb_cdc_acm_desc cdc_call_mgmt;
|
||||||
|
struct usb_cdc_union_desc cdc_union;
|
||||||
|
struct usb_endpoint_descriptor ep_comm;
|
||||||
|
struct usb_interface_descriptor interface_data;
|
||||||
|
struct usb_endpoint_descriptor ep_in;
|
||||||
|
struct usb_endpoint_descriptor ep_out;
|
||||||
|
|
||||||
|
} __attribute__((packed));
|
||||||
|
|
||||||
|
static const struct usb_device_descriptor hid_device_desc = {
|
||||||
|
.bLength = sizeof(struct usb_device_descriptor),
|
||||||
|
.bDescriptorType = USB_DTYPE_DEVICE,
|
||||||
|
.bcdUSB = VERSION_BCD(2, 1, 0),
|
||||||
|
.bDeviceClass = USB_CLASS_MISC,
|
||||||
|
.bDeviceSubClass = USB_SUBCLASS_IAD,
|
||||||
|
.bDeviceProtocol = USB_PROTO_IAD,
|
||||||
|
.bMaxPacketSize0 = DAP_USB_EP0_SIZE,
|
||||||
|
.idVendor = DAP_HID_VID,
|
||||||
|
.idProduct = DAP_HID_PID,
|
||||||
|
.bcdDevice = VERSION_BCD(1, 0, 0),
|
||||||
|
.iManufacturer = USB_STR_MANUFACTURER,
|
||||||
|
.iProduct = USB_STR_PRODUCT,
|
||||||
|
.iSerialNumber = USB_STR_SERIAL_NUMBER,
|
||||||
|
.bNumConfigurations = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const uint8_t hid_report_desc[] = {
|
||||||
|
0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
|
||||||
|
0x09, 0x00, // Usage (Undefined)
|
||||||
|
0xa1, 0x01, // Collection (Application)
|
||||||
|
0x15, 0x00, // Logical Minimum (0)
|
||||||
|
0x26, 0xff, 0x00, // Logical Maximum (255)
|
||||||
|
0x75, 0x08, // Report Size (8)
|
||||||
|
0x95, 0x40, // Report Count (64)
|
||||||
|
0x09, 0x00, // Usage (Undefined)
|
||||||
|
0x81, 0x82, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||||
|
0x75, 0x08, // Report Size (8)
|
||||||
|
0x95, 0x40, // Report Count (64)
|
||||||
|
0x09, 0x00, // Usage (Undefined)
|
||||||
|
0x91, 0x82, // Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Volatile)
|
||||||
|
0xc0, // End Collection
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct HidConfigDescriptor hid_cfg_desc = {
|
||||||
|
.configuration =
|
||||||
|
{
|
||||||
|
.bLength = sizeof(struct usb_config_descriptor),
|
||||||
|
.bDescriptorType = USB_DTYPE_CONFIGURATION,
|
||||||
|
.wTotalLength = sizeof(struct HidConfigDescriptor),
|
||||||
|
.bNumInterfaces = USB_INTF_COUNT,
|
||||||
|
.bConfigurationValue = 1,
|
||||||
|
.iConfiguration = NO_DESCRIPTOR,
|
||||||
|
.bmAttributes = USB_CFG_ATTR_RESERVED,
|
||||||
|
.bMaxPower = USB_CFG_POWER_MA(500),
|
||||||
|
},
|
||||||
|
|
||||||
|
// CMSIS-DAP v1
|
||||||
|
.hid_interface =
|
||||||
|
{
|
||||||
|
.bLength = sizeof(struct usb_interface_descriptor),
|
||||||
|
.bDescriptorType = USB_DTYPE_INTERFACE,
|
||||||
|
.bInterfaceNumber = USB_INTF_HID,
|
||||||
|
.bAlternateSetting = 0,
|
||||||
|
.bNumEndpoints = 2,
|
||||||
|
.bInterfaceClass = USB_CLASS_HID,
|
||||||
|
.bInterfaceSubClass = USB_HID_SUBCLASS_NONBOOT,
|
||||||
|
.bInterfaceProtocol = USB_HID_PROTO_NONBOOT,
|
||||||
|
.iInterface = USB_STR_CMSIS_DAP_V1,
|
||||||
|
},
|
||||||
|
|
||||||
|
.hid =
|
||||||
|
{
|
||||||
|
.bLength = sizeof(struct usb_hid_descriptor),
|
||||||
|
.bDescriptorType = USB_DTYPE_HID,
|
||||||
|
.bcdHID = VERSION_BCD(1, 1, 1),
|
||||||
|
.bCountryCode = USB_HID_COUNTRY_NONE,
|
||||||
|
.bNumDescriptors = 1,
|
||||||
|
.bDescriptorType0 = USB_DTYPE_HID_REPORT,
|
||||||
|
.wDescriptorLength0 = sizeof(hid_report_desc),
|
||||||
|
},
|
||||||
|
|
||||||
|
.hid_ep_in =
|
||||||
|
{
|
||||||
|
.bLength = sizeof(struct usb_endpoint_descriptor),
|
||||||
|
.bDescriptorType = USB_DTYPE_ENDPOINT,
|
||||||
|
.bEndpointAddress = DAP_HID_EP_IN,
|
||||||
|
.bmAttributes = USB_EPTYPE_INTERRUPT,
|
||||||
|
.wMaxPacketSize = DAP_HID_EP_SIZE,
|
||||||
|
.bInterval = DAP_HID_INTERVAL,
|
||||||
|
},
|
||||||
|
|
||||||
|
.hid_ep_out =
|
||||||
|
{
|
||||||
|
.bLength = sizeof(struct usb_endpoint_descriptor),
|
||||||
|
.bDescriptorType = USB_DTYPE_ENDPOINT,
|
||||||
|
.bEndpointAddress = DAP_HID_EP_OUT,
|
||||||
|
.bmAttributes = USB_EPTYPE_INTERRUPT,
|
||||||
|
.wMaxPacketSize = DAP_HID_EP_SIZE,
|
||||||
|
.bInterval = DAP_HID_INTERVAL,
|
||||||
|
},
|
||||||
|
|
||||||
|
// CMSIS-DAP v2
|
||||||
|
.bulk_interface =
|
||||||
|
{
|
||||||
|
.bLength = sizeof(struct usb_interface_descriptor),
|
||||||
|
.bDescriptorType = USB_DTYPE_INTERFACE,
|
||||||
|
.bInterfaceNumber = USB_INTF_BULK,
|
||||||
|
.bAlternateSetting = 0,
|
||||||
|
.bNumEndpoints = 2,
|
||||||
|
.bInterfaceClass = USB_CLASS_VENDOR,
|
||||||
|
.bInterfaceSubClass = 0,
|
||||||
|
.bInterfaceProtocol = 0,
|
||||||
|
.iInterface = USB_STR_CMSIS_DAP_V2,
|
||||||
|
},
|
||||||
|
|
||||||
|
.bulk_ep_out =
|
||||||
|
{
|
||||||
|
.bLength = sizeof(struct usb_endpoint_descriptor),
|
||||||
|
.bDescriptorType = USB_DTYPE_ENDPOINT,
|
||||||
|
.bEndpointAddress = DAP_HID_EP_BULK_OUT,
|
||||||
|
.bmAttributes = USB_EPTYPE_BULK,
|
||||||
|
.wMaxPacketSize = DAP_HID_EP_SIZE,
|
||||||
|
.bInterval = DAP_BULK_INTERVAL,
|
||||||
|
},
|
||||||
|
|
||||||
|
.bulk_ep_in =
|
||||||
|
{
|
||||||
|
.bLength = sizeof(struct usb_endpoint_descriptor),
|
||||||
|
.bDescriptorType = USB_DTYPE_ENDPOINT,
|
||||||
|
.bEndpointAddress = DAP_HID_EP_BULK_IN,
|
||||||
|
.bmAttributes = USB_EPTYPE_BULK,
|
||||||
|
.wMaxPacketSize = DAP_HID_EP_SIZE,
|
||||||
|
.bInterval = DAP_BULK_INTERVAL,
|
||||||
|
},
|
||||||
|
|
||||||
|
// CDC
|
||||||
|
.iad =
|
||||||
|
{
|
||||||
|
.bLength = sizeof(struct usb_iad_descriptor),
|
||||||
|
.bDescriptorType = USB_DTYPE_INTERFASEASSOC,
|
||||||
|
.bFirstInterface = USB_INTF_CDC_COMM,
|
||||||
|
.bInterfaceCount = 2,
|
||||||
|
.bFunctionClass = USB_CLASS_CDC,
|
||||||
|
.bFunctionSubClass = USB_CDC_SUBCLASS_ACM,
|
||||||
|
.bFunctionProtocol = USB_PROTO_NONE,
|
||||||
|
.iFunction = USB_STR_COM_PORT,
|
||||||
|
},
|
||||||
|
.interface_comm =
|
||||||
|
{
|
||||||
|
.bLength = sizeof(struct usb_interface_descriptor),
|
||||||
|
.bDescriptorType = USB_DTYPE_INTERFACE,
|
||||||
|
.bInterfaceNumber = USB_INTF_CDC_COMM,
|
||||||
|
.bAlternateSetting = 0,
|
||||||
|
.bNumEndpoints = 1,
|
||||||
|
.bInterfaceClass = USB_CLASS_CDC,
|
||||||
|
.bInterfaceSubClass = USB_CDC_SUBCLASS_ACM,
|
||||||
|
.bInterfaceProtocol = USB_PROTO_NONE,
|
||||||
|
.iInterface = 0,
|
||||||
|
},
|
||||||
|
|
||||||
|
.cdc_header =
|
||||||
|
{
|
||||||
|
.bFunctionLength = sizeof(struct usb_cdc_header_desc),
|
||||||
|
.bDescriptorType = USB_DTYPE_CS_INTERFACE,
|
||||||
|
.bDescriptorSubType = USB_DTYPE_CDC_HEADER,
|
||||||
|
.bcdCDC = VERSION_BCD(1, 1, 0),
|
||||||
|
},
|
||||||
|
|
||||||
|
.cdc_acm =
|
||||||
|
{
|
||||||
|
.bFunctionLength = sizeof(struct usb_cdc_call_mgmt_desc),
|
||||||
|
.bDescriptorType = USB_DTYPE_CS_INTERFACE,
|
||||||
|
.bDescriptorSubType = USB_DTYPE_CDC_CALL_MANAGEMENT,
|
||||||
|
// .bmCapabilities = USB_CDC_CAP_LINE | USB_CDC_CAP_BRK,
|
||||||
|
.bmCapabilities = 0,
|
||||||
|
},
|
||||||
|
|
||||||
|
.cdc_call_mgmt =
|
||||||
|
{
|
||||||
|
.bFunctionLength = sizeof(struct usb_cdc_acm_desc),
|
||||||
|
.bDescriptorType = USB_DTYPE_CS_INTERFACE,
|
||||||
|
.bDescriptorSubType = USB_DTYPE_CDC_ACM,
|
||||||
|
.bmCapabilities = USB_CDC_CALL_MGMT_CAP_DATA_INTF,
|
||||||
|
// .bDataInterface = USB_INTF_CDC_DATA,
|
||||||
|
},
|
||||||
|
|
||||||
|
.cdc_union =
|
||||||
|
{
|
||||||
|
.bFunctionLength = sizeof(struct usb_cdc_union_desc),
|
||||||
|
.bDescriptorType = USB_DTYPE_CS_INTERFACE,
|
||||||
|
.bDescriptorSubType = USB_DTYPE_CDC_UNION,
|
||||||
|
.bMasterInterface0 = USB_INTF_CDC_COMM,
|
||||||
|
.bSlaveInterface0 = USB_INTF_CDC_DATA,
|
||||||
|
},
|
||||||
|
|
||||||
|
.ep_comm =
|
||||||
|
{
|
||||||
|
.bLength = sizeof(struct usb_endpoint_descriptor),
|
||||||
|
.bDescriptorType = USB_DTYPE_ENDPOINT,
|
||||||
|
.bEndpointAddress = HID_EP_IN | DAP_CDC_EP_COMM,
|
||||||
|
.bmAttributes = USB_EPTYPE_INTERRUPT,
|
||||||
|
.wMaxPacketSize = DAP_CDC_COMM_EP_SIZE,
|
||||||
|
.bInterval = DAP_CDC_COMM_INTERVAL,
|
||||||
|
},
|
||||||
|
|
||||||
|
.interface_data =
|
||||||
|
{
|
||||||
|
.bLength = sizeof(struct usb_interface_descriptor),
|
||||||
|
.bDescriptorType = USB_DTYPE_INTERFACE,
|
||||||
|
.bInterfaceNumber = USB_INTF_CDC_DATA,
|
||||||
|
.bAlternateSetting = 0,
|
||||||
|
.bNumEndpoints = 2,
|
||||||
|
.bInterfaceClass = USB_CLASS_CDC_DATA,
|
||||||
|
.bInterfaceSubClass = USB_SUBCLASS_NONE,
|
||||||
|
.bInterfaceProtocol = USB_PROTO_NONE,
|
||||||
|
.iInterface = NO_DESCRIPTOR,
|
||||||
|
},
|
||||||
|
|
||||||
|
.ep_in =
|
||||||
|
{
|
||||||
|
.bLength = sizeof(struct usb_endpoint_descriptor),
|
||||||
|
.bDescriptorType = USB_DTYPE_ENDPOINT,
|
||||||
|
.bEndpointAddress = HID_EP_IN | DAP_CDC_EP_SEND,
|
||||||
|
.bmAttributes = USB_EPTYPE_BULK,
|
||||||
|
.wMaxPacketSize = DAP_CDC_EP_SIZE,
|
||||||
|
.bInterval = DAP_CDC_INTERVAL,
|
||||||
|
},
|
||||||
|
|
||||||
|
.ep_out =
|
||||||
|
{
|
||||||
|
.bLength = sizeof(struct usb_endpoint_descriptor),
|
||||||
|
.bDescriptorType = USB_DTYPE_ENDPOINT,
|
||||||
|
.bEndpointAddress = HID_EP_OUT | DAP_CDC_EP_RECV,
|
||||||
|
.bmAttributes = USB_EPTYPE_BULK,
|
||||||
|
.wMaxPacketSize = DAP_CDC_EP_SIZE,
|
||||||
|
.bInterval = DAP_CDC_INTERVAL,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// WinUSB
|
||||||
|
#include "usb_winusb.h"
|
||||||
|
|
||||||
|
typedef struct USB_PACK {
|
||||||
|
usb_binary_object_store_descriptor_t bos;
|
||||||
|
usb_winusb_capability_descriptor_t winusb;
|
||||||
|
} usb_bos_hierarchy_t;
|
||||||
|
|
||||||
|
typedef struct USB_PACK {
|
||||||
|
usb_winusb_subset_header_function_t header;
|
||||||
|
usb_winusb_feature_compatble_id_t comp_id;
|
||||||
|
usb_winusb_feature_reg_property_guids_t property;
|
||||||
|
} usb_msos_descriptor_subset_t;
|
||||||
|
|
||||||
|
typedef struct USB_PACK {
|
||||||
|
usb_winusb_set_header_descriptor_t header;
|
||||||
|
usb_msos_descriptor_subset_t subset;
|
||||||
|
} usb_msos_descriptor_set_t;
|
||||||
|
|
||||||
|
#define USB_DTYPE_BINARY_OBJECT_STORE 15
|
||||||
|
#define USB_DTYPE_DEVICE_CAPABILITY_DESCRIPTOR 16
|
||||||
|
#define USB_DC_TYPE_PLATFORM 5
|
||||||
|
|
||||||
|
const usb_bos_hierarchy_t usb_bos_hierarchy = {
|
||||||
|
.bos =
|
||||||
|
{
|
||||||
|
.bLength = sizeof(usb_binary_object_store_descriptor_t),
|
||||||
|
.bDescriptorType = USB_DTYPE_BINARY_OBJECT_STORE,
|
||||||
|
.wTotalLength = sizeof(usb_bos_hierarchy_t),
|
||||||
|
.bNumDeviceCaps = 1,
|
||||||
|
},
|
||||||
|
.winusb =
|
||||||
|
{
|
||||||
|
.bLength = sizeof(usb_winusb_capability_descriptor_t),
|
||||||
|
.bDescriptorType = USB_DTYPE_DEVICE_CAPABILITY_DESCRIPTOR,
|
||||||
|
.bDevCapabilityType = USB_DC_TYPE_PLATFORM,
|
||||||
|
.bReserved = 0,
|
||||||
|
.PlatformCapabilityUUID = USB_WINUSB_PLATFORM_CAPABILITY_ID,
|
||||||
|
.dwWindowsVersion = USB_WINUSB_WINDOWS_VERSION,
|
||||||
|
.wMSOSDescriptorSetTotalLength = sizeof(usb_msos_descriptor_set_t),
|
||||||
|
.bMS_VendorCode = USB_WINUSB_VENDOR_CODE,
|
||||||
|
.bAltEnumCode = 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const usb_msos_descriptor_set_t usb_msos_descriptor_set = {
|
||||||
|
.header =
|
||||||
|
{
|
||||||
|
.wLength = sizeof(usb_winusb_set_header_descriptor_t),
|
||||||
|
.wDescriptorType = USB_WINUSB_SET_HEADER_DESCRIPTOR,
|
||||||
|
.dwWindowsVersion = USB_WINUSB_WINDOWS_VERSION,
|
||||||
|
.wDescriptorSetTotalLength = sizeof(usb_msos_descriptor_set_t),
|
||||||
|
},
|
||||||
|
|
||||||
|
.subset =
|
||||||
|
{
|
||||||
|
.header =
|
||||||
|
{
|
||||||
|
.wLength = sizeof(usb_winusb_subset_header_function_t),
|
||||||
|
.wDescriptorType = USB_WINUSB_SUBSET_HEADER_FUNCTION,
|
||||||
|
.bFirstInterface = USB_INTF_BULK,
|
||||||
|
.bReserved = 0,
|
||||||
|
.wSubsetLength = sizeof(usb_msos_descriptor_subset_t),
|
||||||
|
},
|
||||||
|
|
||||||
|
.comp_id =
|
||||||
|
{
|
||||||
|
.wLength = sizeof(usb_winusb_feature_compatble_id_t),
|
||||||
|
.wDescriptorType = USB_WINUSB_FEATURE_COMPATBLE_ID,
|
||||||
|
.CompatibleID = "WINUSB\0\0",
|
||||||
|
.SubCompatibleID = {0},
|
||||||
|
},
|
||||||
|
|
||||||
|
.property =
|
||||||
|
{
|
||||||
|
.wLength = sizeof(usb_winusb_feature_reg_property_guids_t),
|
||||||
|
.wDescriptorType = USB_WINUSB_FEATURE_REG_PROPERTY,
|
||||||
|
.wPropertyDataType = USB_WINUSB_PROPERTY_DATA_TYPE_MULTI_SZ,
|
||||||
|
.wPropertyNameLength =
|
||||||
|
sizeof(usb_msos_descriptor_set.subset.property.PropertyName),
|
||||||
|
.PropertyName = {'D', 0, 'e', 0, 'v', 0, 'i', 0, 'c', 0, 'e', 0, 'I', 0,
|
||||||
|
'n', 0, 't', 0, 'e', 0, 'r', 0, 'f', 0, 'a', 0, 'c', 0,
|
||||||
|
'e', 0, 'G', 0, 'U', 0, 'I', 0, 'D', 0, 's', 0, 0, 0},
|
||||||
|
.wPropertyDataLength =
|
||||||
|
sizeof(usb_msos_descriptor_set.subset.property.PropertyData),
|
||||||
|
.PropertyData = {'{', 0, 'C', 0, 'D', 0, 'B', 0, '3', 0, 'B', 0, '5', 0,
|
||||||
|
'A', 0, 'D', 0, '-', 0, '2', 0, '9', 0, '3', 0, 'B', 0,
|
||||||
|
'-', 0, '4', 0, '6', 0, '6', 0, '3', 0, '-', 0, 'A', 0,
|
||||||
|
'A', 0, '3', 0, '6', 0, '-', 0, '1', 0, 'A', 0, 'A', 0,
|
||||||
|
'E', 0, '4', 0, '6', 0, '4', 0, '6', 0, '3', 0, '7', 0,
|
||||||
|
'7', 0, '6', 0, '}', 0, 0, 0, 0, 0},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
FuriSemaphore* semaphore_v1;
|
||||||
|
FuriSemaphore* semaphore_v2;
|
||||||
|
FuriSemaphore* semaphore_cdc;
|
||||||
|
bool connected;
|
||||||
|
usbd_device* usb_dev;
|
||||||
|
DapStateCallback state_callback;
|
||||||
|
DapRxCallback rx_callback_v1;
|
||||||
|
DapRxCallback rx_callback_v2;
|
||||||
|
DapRxCallback rx_callback_cdc;
|
||||||
|
DapCDCControlLineCallback control_line_callback_cdc;
|
||||||
|
DapCDCConfigCallback config_callback_cdc;
|
||||||
|
void* context;
|
||||||
|
void* context_cdc;
|
||||||
|
} DAPState;
|
||||||
|
|
||||||
|
static DAPState dap_state = {
|
||||||
|
.semaphore_v1 = NULL,
|
||||||
|
.semaphore_v2 = NULL,
|
||||||
|
.semaphore_cdc = NULL,
|
||||||
|
.connected = false,
|
||||||
|
.usb_dev = NULL,
|
||||||
|
.state_callback = NULL,
|
||||||
|
.rx_callback_v1 = NULL,
|
||||||
|
.rx_callback_v2 = NULL,
|
||||||
|
.rx_callback_cdc = NULL,
|
||||||
|
.control_line_callback_cdc = NULL,
|
||||||
|
.config_callback_cdc = NULL,
|
||||||
|
.context = NULL,
|
||||||
|
.context_cdc = NULL,
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct usb_cdc_line_coding cdc_config = {0};
|
||||||
|
static uint8_t cdc_ctrl_line_state = 0;
|
||||||
|
|
||||||
|
#ifdef DAP_USB_LOG
|
||||||
|
void furi_console_log_printf(const char* format, ...) _ATTRIBUTE((__format__(__printf__, 1, 2)));
|
||||||
|
|
||||||
|
void furi_console_log_printf(const char* format, ...) {
|
||||||
|
char buffer[256];
|
||||||
|
va_list args;
|
||||||
|
va_start(args, format);
|
||||||
|
vsnprintf(buffer, sizeof(buffer), format, args);
|
||||||
|
va_end(args);
|
||||||
|
furi_hal_console_puts(buffer);
|
||||||
|
furi_hal_console_puts("\r\n");
|
||||||
|
UNUSED(format);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
#define furi_console_log_printf(...)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int32_t dap_v1_usb_tx(uint8_t* buffer, uint8_t size) {
|
||||||
|
if((dap_state.semaphore_v1 == NULL) || (dap_state.connected == false)) return 0;
|
||||||
|
|
||||||
|
furi_check(furi_semaphore_acquire(dap_state.semaphore_v1, FuriWaitForever) == FuriStatusOk);
|
||||||
|
|
||||||
|
if(dap_state.connected) {
|
||||||
|
int32_t len = usbd_ep_write(dap_state.usb_dev, DAP_HID_EP_IN, buffer, size);
|
||||||
|
furi_console_log_printf("v1 tx %ld", len);
|
||||||
|
return len;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t dap_v2_usb_tx(uint8_t* buffer, uint8_t size) {
|
||||||
|
if((dap_state.semaphore_v2 == NULL) || (dap_state.connected == false)) return 0;
|
||||||
|
|
||||||
|
furi_check(furi_semaphore_acquire(dap_state.semaphore_v2, FuriWaitForever) == FuriStatusOk);
|
||||||
|
|
||||||
|
if(dap_state.connected) {
|
||||||
|
int32_t len = usbd_ep_write(dap_state.usb_dev, DAP_HID_EP_BULK_IN, buffer, size);
|
||||||
|
furi_console_log_printf("v2 tx %ld", len);
|
||||||
|
return len;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t dap_cdc_usb_tx(uint8_t* buffer, uint8_t size) {
|
||||||
|
if((dap_state.semaphore_cdc == NULL) || (dap_state.connected == false)) return 0;
|
||||||
|
|
||||||
|
furi_check(furi_semaphore_acquire(dap_state.semaphore_cdc, FuriWaitForever) == FuriStatusOk);
|
||||||
|
|
||||||
|
if(dap_state.connected) {
|
||||||
|
int32_t len = usbd_ep_write(dap_state.usb_dev, HID_EP_IN | DAP_CDC_EP_SEND, buffer, size);
|
||||||
|
furi_console_log_printf("cdc tx %ld", len);
|
||||||
|
return len;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void dap_v1_usb_set_rx_callback(DapRxCallback callback) {
|
||||||
|
dap_state.rx_callback_v1 = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
void dap_v2_usb_set_rx_callback(DapRxCallback callback) {
|
||||||
|
dap_state.rx_callback_v2 = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
void dap_cdc_usb_set_rx_callback(DapRxCallback callback) {
|
||||||
|
dap_state.rx_callback_cdc = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
void dap_cdc_usb_set_control_line_callback(DapCDCControlLineCallback callback) {
|
||||||
|
dap_state.control_line_callback_cdc = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
void dap_cdc_usb_set_config_callback(DapCDCConfigCallback callback) {
|
||||||
|
dap_state.config_callback_cdc = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
void dap_cdc_usb_set_context(void* context) {
|
||||||
|
dap_state.context_cdc = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
void dap_common_usb_set_context(void* context) {
|
||||||
|
dap_state.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
void dap_common_usb_set_state_callback(DapStateCallback callback) {
|
||||||
|
dap_state.state_callback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void* dap_usb_alloc_string_descr(const char* str) {
|
||||||
|
furi_assert(str);
|
||||||
|
|
||||||
|
uint8_t len = strlen(str);
|
||||||
|
uint8_t wlen = (len + 1) * sizeof(uint16_t);
|
||||||
|
struct usb_string_descriptor* dev_str_desc = malloc(wlen);
|
||||||
|
dev_str_desc->bLength = wlen;
|
||||||
|
dev_str_desc->bDescriptorType = USB_DTYPE_STRING;
|
||||||
|
for(uint8_t i = 0; i < len; i++) {
|
||||||
|
dev_str_desc->wString[i] = str[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return dev_str_desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
void dap_common_usb_alloc_name(const char* name) {
|
||||||
|
dev_serial_descr = dap_usb_alloc_string_descr(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
void dap_common_usb_free_name() {
|
||||||
|
free(dev_serial_descr);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void hid_init(usbd_device* dev, FuriHalUsbInterface* intf, void* ctx);
|
||||||
|
static void hid_deinit(usbd_device* dev);
|
||||||
|
static void hid_on_wakeup(usbd_device* dev);
|
||||||
|
static void hid_on_suspend(usbd_device* dev);
|
||||||
|
|
||||||
|
static usbd_respond hid_ep_config(usbd_device* dev, uint8_t cfg);
|
||||||
|
static usbd_respond hid_control(usbd_device* dev, usbd_ctlreq* req, usbd_rqc_callback* callback);
|
||||||
|
|
||||||
|
FuriHalUsbInterface dap_v2_usb_hid = {
|
||||||
|
.init = hid_init,
|
||||||
|
.deinit = hid_deinit,
|
||||||
|
.wakeup = hid_on_wakeup,
|
||||||
|
.suspend = hid_on_suspend,
|
||||||
|
.dev_descr = (struct usb_device_descriptor*)&hid_device_desc,
|
||||||
|
.cfg_descr = (void*)&hid_cfg_desc,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void hid_init(usbd_device* dev, FuriHalUsbInterface* intf, void* ctx) {
|
||||||
|
UNUSED(intf);
|
||||||
|
UNUSED(ctx);
|
||||||
|
|
||||||
|
dap_v2_usb_hid.str_manuf_descr = (void*)&dev_manuf_descr;
|
||||||
|
dap_v2_usb_hid.str_prod_descr = (void*)&dev_prod_descr;
|
||||||
|
dap_v2_usb_hid.str_serial_descr = (void*)dev_serial_descr;
|
||||||
|
|
||||||
|
dap_state.usb_dev = dev;
|
||||||
|
if(dap_state.semaphore_v1 == NULL) dap_state.semaphore_v1 = furi_semaphore_alloc(1, 1);
|
||||||
|
if(dap_state.semaphore_v2 == NULL) dap_state.semaphore_v2 = furi_semaphore_alloc(1, 1);
|
||||||
|
if(dap_state.semaphore_cdc == NULL) dap_state.semaphore_cdc = furi_semaphore_alloc(1, 1);
|
||||||
|
|
||||||
|
usb_hid.dev_descr->idVendor = DAP_HID_VID;
|
||||||
|
usb_hid.dev_descr->idProduct = DAP_HID_PID;
|
||||||
|
|
||||||
|
usbd_reg_config(dev, hid_ep_config);
|
||||||
|
usbd_reg_control(dev, hid_control);
|
||||||
|
|
||||||
|
usbd_connect(dev, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool deinit_flag = false;
|
||||||
|
|
||||||
|
void dap_common_wait_for_deinit() {
|
||||||
|
while(!deinit_flag) {
|
||||||
|
furi_delay_ms(50);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void hid_deinit(usbd_device* dev) {
|
||||||
|
dap_state.usb_dev = NULL;
|
||||||
|
|
||||||
|
furi_semaphore_free(dap_state.semaphore_v1);
|
||||||
|
furi_semaphore_free(dap_state.semaphore_v2);
|
||||||
|
furi_semaphore_free(dap_state.semaphore_cdc);
|
||||||
|
dap_state.semaphore_v1 = NULL;
|
||||||
|
dap_state.semaphore_v2 = NULL;
|
||||||
|
dap_state.semaphore_cdc = NULL;
|
||||||
|
|
||||||
|
usbd_reg_config(dev, NULL);
|
||||||
|
usbd_reg_control(dev, NULL);
|
||||||
|
|
||||||
|
free(usb_hid.str_manuf_descr);
|
||||||
|
free(usb_hid.str_prod_descr);
|
||||||
|
|
||||||
|
FURI_SW_MEMBARRIER();
|
||||||
|
deinit_flag = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void hid_on_wakeup(usbd_device* dev) {
|
||||||
|
UNUSED(dev);
|
||||||
|
if(!dap_state.connected) {
|
||||||
|
dap_state.connected = true;
|
||||||
|
if(dap_state.state_callback != NULL) {
|
||||||
|
dap_state.state_callback(dap_state.connected, dap_state.context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void hid_on_suspend(usbd_device* dev) {
|
||||||
|
UNUSED(dev);
|
||||||
|
if(dap_state.connected) {
|
||||||
|
dap_state.connected = false;
|
||||||
|
if(dap_state.state_callback != NULL) {
|
||||||
|
dap_state.state_callback(dap_state.connected, dap_state.context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t dap_v1_usb_rx(uint8_t* buffer, size_t size) {
|
||||||
|
size_t len = 0;
|
||||||
|
|
||||||
|
if(dap_state.connected) {
|
||||||
|
len = usbd_ep_read(dap_state.usb_dev, DAP_HID_EP_OUT, buffer, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t dap_v2_usb_rx(uint8_t* buffer, size_t size) {
|
||||||
|
size_t len = 0;
|
||||||
|
|
||||||
|
if(dap_state.connected) {
|
||||||
|
len = usbd_ep_read(dap_state.usb_dev, DAP_HID_EP_BULK_OUT, buffer, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t dap_cdc_usb_rx(uint8_t* buffer, size_t size) {
|
||||||
|
size_t len = 0;
|
||||||
|
|
||||||
|
if(dap_state.connected) {
|
||||||
|
len = usbd_ep_read(dap_state.usb_dev, HID_EP_OUT | DAP_CDC_EP_RECV, buffer, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void hid_txrx_ep_callback(usbd_device* dev, uint8_t event, uint8_t ep) {
|
||||||
|
UNUSED(dev);
|
||||||
|
UNUSED(ep);
|
||||||
|
|
||||||
|
switch(event) {
|
||||||
|
case usbd_evt_eptx:
|
||||||
|
furi_semaphore_release(dap_state.semaphore_v1);
|
||||||
|
furi_console_log_printf("hid tx complete");
|
||||||
|
break;
|
||||||
|
case usbd_evt_eprx:
|
||||||
|
if(dap_state.rx_callback_v1 != NULL) {
|
||||||
|
dap_state.rx_callback_v1(dap_state.context);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
furi_console_log_printf("hid %d, %d", event, ep);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void hid_txrx_ep_bulk_callback(usbd_device* dev, uint8_t event, uint8_t ep) {
|
||||||
|
UNUSED(dev);
|
||||||
|
UNUSED(ep);
|
||||||
|
|
||||||
|
switch(event) {
|
||||||
|
case usbd_evt_eptx:
|
||||||
|
furi_semaphore_release(dap_state.semaphore_v2);
|
||||||
|
furi_console_log_printf("bulk tx complete");
|
||||||
|
break;
|
||||||
|
case usbd_evt_eprx:
|
||||||
|
if(dap_state.rx_callback_v2 != NULL) {
|
||||||
|
dap_state.rx_callback_v2(dap_state.context);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
furi_console_log_printf("bulk %d, %d", event, ep);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cdc_txrx_ep_callback(usbd_device* dev, uint8_t event, uint8_t ep) {
|
||||||
|
UNUSED(dev);
|
||||||
|
UNUSED(ep);
|
||||||
|
|
||||||
|
switch(event) {
|
||||||
|
case usbd_evt_eptx:
|
||||||
|
furi_semaphore_release(dap_state.semaphore_cdc);
|
||||||
|
furi_console_log_printf("cdc tx complete");
|
||||||
|
break;
|
||||||
|
case usbd_evt_eprx:
|
||||||
|
if(dap_state.rx_callback_cdc != NULL) {
|
||||||
|
dap_state.rx_callback_cdc(dap_state.context_cdc);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
furi_console_log_printf("cdc %d, %d", event, ep);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static usbd_respond hid_ep_config(usbd_device* dev, uint8_t cfg) {
|
||||||
|
switch(cfg) {
|
||||||
|
case EP_CFG_DECONFIGURE:
|
||||||
|
usbd_ep_deconfig(dev, DAP_HID_EP_OUT);
|
||||||
|
usbd_ep_deconfig(dev, DAP_HID_EP_IN);
|
||||||
|
usbd_ep_deconfig(dev, DAP_HID_EP_BULK_IN);
|
||||||
|
usbd_ep_deconfig(dev, DAP_HID_EP_BULK_OUT);
|
||||||
|
usbd_ep_deconfig(dev, HID_EP_IN | DAP_CDC_EP_COMM);
|
||||||
|
usbd_ep_deconfig(dev, HID_EP_IN | DAP_CDC_EP_SEND);
|
||||||
|
usbd_ep_deconfig(dev, HID_EP_OUT | DAP_CDC_EP_RECV);
|
||||||
|
usbd_reg_endpoint(dev, DAP_HID_EP_OUT, NULL);
|
||||||
|
usbd_reg_endpoint(dev, DAP_HID_EP_IN, NULL);
|
||||||
|
usbd_reg_endpoint(dev, DAP_HID_EP_BULK_IN, NULL);
|
||||||
|
usbd_reg_endpoint(dev, DAP_HID_EP_BULK_OUT, NULL);
|
||||||
|
usbd_reg_endpoint(dev, HID_EP_IN | DAP_CDC_EP_SEND, 0);
|
||||||
|
usbd_reg_endpoint(dev, HID_EP_OUT | DAP_CDC_EP_RECV, 0);
|
||||||
|
return usbd_ack;
|
||||||
|
case EP_CFG_CONFIGURE:
|
||||||
|
usbd_ep_config(dev, DAP_HID_EP_IN, USB_EPTYPE_INTERRUPT, DAP_HID_EP_SIZE);
|
||||||
|
usbd_ep_config(dev, DAP_HID_EP_OUT, USB_EPTYPE_INTERRUPT, DAP_HID_EP_SIZE);
|
||||||
|
usbd_ep_config(dev, DAP_HID_EP_BULK_OUT, USB_EPTYPE_BULK, DAP_HID_EP_SIZE);
|
||||||
|
usbd_ep_config(dev, DAP_HID_EP_BULK_IN, USB_EPTYPE_BULK, DAP_HID_EP_SIZE);
|
||||||
|
usbd_ep_config(dev, HID_EP_OUT | DAP_CDC_EP_RECV, USB_EPTYPE_BULK, DAP_CDC_EP_SIZE);
|
||||||
|
usbd_ep_config(dev, HID_EP_IN | DAP_CDC_EP_SEND, USB_EPTYPE_BULK, DAP_CDC_EP_SIZE);
|
||||||
|
usbd_ep_config(dev, HID_EP_IN | DAP_CDC_EP_COMM, USB_EPTYPE_INTERRUPT, DAP_CDC_EP_SIZE);
|
||||||
|
usbd_reg_endpoint(dev, DAP_HID_EP_IN, hid_txrx_ep_callback);
|
||||||
|
usbd_reg_endpoint(dev, DAP_HID_EP_OUT, hid_txrx_ep_callback);
|
||||||
|
usbd_reg_endpoint(dev, DAP_HID_EP_BULK_OUT, hid_txrx_ep_bulk_callback);
|
||||||
|
usbd_reg_endpoint(dev, DAP_HID_EP_BULK_IN, hid_txrx_ep_bulk_callback);
|
||||||
|
usbd_reg_endpoint(dev, HID_EP_OUT | DAP_CDC_EP_RECV, cdc_txrx_ep_callback);
|
||||||
|
usbd_reg_endpoint(dev, HID_EP_IN | DAP_CDC_EP_SEND, cdc_txrx_ep_callback);
|
||||||
|
// usbd_ep_write(dev, DAP_HID_EP_IN, NULL, 0);
|
||||||
|
// usbd_ep_write(dev, DAP_HID_EP_BULK_IN, NULL, 0);
|
||||||
|
// usbd_ep_write(dev, HID_EP_IN | DAP_CDC_EP_SEND, NULL, 0);
|
||||||
|
return usbd_ack;
|
||||||
|
default:
|
||||||
|
return usbd_fail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef DAP_USB_LOG
|
||||||
|
static void dump_request_type(uint8_t type) {
|
||||||
|
switch(type & USB_REQ_DIRECTION) {
|
||||||
|
case USB_REQ_HOSTTODEV:
|
||||||
|
furi_hal_console_puts("host to dev, ");
|
||||||
|
break;
|
||||||
|
case USB_REQ_DEVTOHOST:
|
||||||
|
furi_hal_console_puts("dev to host, ");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(type & USB_REQ_TYPE) {
|
||||||
|
case USB_REQ_STANDARD:
|
||||||
|
furi_hal_console_puts("standard, ");
|
||||||
|
break;
|
||||||
|
case USB_REQ_CLASS:
|
||||||
|
furi_hal_console_puts("class, ");
|
||||||
|
break;
|
||||||
|
case USB_REQ_VENDOR:
|
||||||
|
furi_hal_console_puts("vendor, ");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(type & USB_REQ_RECIPIENT) {
|
||||||
|
case USB_REQ_DEVICE:
|
||||||
|
furi_hal_console_puts("device");
|
||||||
|
break;
|
||||||
|
case USB_REQ_INTERFACE:
|
||||||
|
furi_hal_console_puts("interface");
|
||||||
|
break;
|
||||||
|
case USB_REQ_ENDPOINT:
|
||||||
|
furi_hal_console_puts("endpoint");
|
||||||
|
break;
|
||||||
|
case USB_REQ_OTHER:
|
||||||
|
furi_hal_console_puts("other");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
furi_hal_console_puts("\r\n");
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
#define dump_request_type(...)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static usbd_respond hid_control(usbd_device* dev, usbd_ctlreq* req, usbd_rqc_callback* callback) {
|
||||||
|
UNUSED(callback);
|
||||||
|
|
||||||
|
dump_request_type(req->bmRequestType);
|
||||||
|
furi_console_log_printf(
|
||||||
|
"control: RT %02x, R %02x, V %04x, I %04x, L %04x",
|
||||||
|
req->bmRequestType,
|
||||||
|
req->bRequest,
|
||||||
|
req->wValue,
|
||||||
|
req->wIndex,
|
||||||
|
req->wLength);
|
||||||
|
|
||||||
|
if(((USB_REQ_RECIPIENT | USB_REQ_TYPE | USB_REQ_DIRECTION) & req->bmRequestType) ==
|
||||||
|
(USB_REQ_STANDARD | USB_REQ_VENDOR | USB_REQ_DEVTOHOST)) {
|
||||||
|
// vendor request, device to host
|
||||||
|
furi_console_log_printf("vendor request");
|
||||||
|
if(USB_WINUSB_VENDOR_CODE == req->bRequest) {
|
||||||
|
// WINUSB request
|
||||||
|
if(USB_WINUSB_DESCRIPTOR_INDEX == req->wIndex) {
|
||||||
|
furi_console_log_printf("WINUSB descriptor");
|
||||||
|
uint16_t length = req->wLength;
|
||||||
|
if(length > sizeof(usb_msos_descriptor_set_t)) {
|
||||||
|
length = sizeof(usb_msos_descriptor_set_t);
|
||||||
|
}
|
||||||
|
|
||||||
|
dev->status.data_ptr = (uint8_t*)&usb_msos_descriptor_set;
|
||||||
|
dev->status.data_count = length;
|
||||||
|
return usbd_ack;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(((USB_REQ_RECIPIENT | USB_REQ_TYPE) & req->bmRequestType) ==
|
||||||
|
(USB_REQ_STANDARD | USB_REQ_DEVICE)) {
|
||||||
|
// device request
|
||||||
|
if(req->bRequest == USB_STD_GET_DESCRIPTOR) {
|
||||||
|
const uint8_t dtype = req->wValue >> 8;
|
||||||
|
const uint8_t dnumber = req->wValue & 0xFF;
|
||||||
|
// get string descriptor
|
||||||
|
if(USB_DTYPE_STRING == dtype) {
|
||||||
|
if(dnumber == USB_STR_CMSIS_DAP_V1) {
|
||||||
|
furi_console_log_printf("str CMSIS-DAP v1");
|
||||||
|
dev->status.data_ptr = (uint8_t*)&dev_dap_v1_descr;
|
||||||
|
dev->status.data_count = dev_dap_v1_descr.bLength;
|
||||||
|
return usbd_ack;
|
||||||
|
} else if(dnumber == USB_STR_CMSIS_DAP_V2) {
|
||||||
|
furi_console_log_printf("str CMSIS-DAP v2");
|
||||||
|
dev->status.data_ptr = (uint8_t*)&dev_dap_v2_descr;
|
||||||
|
dev->status.data_count = dev_dap_v2_descr.bLength;
|
||||||
|
return usbd_ack;
|
||||||
|
} else if(dnumber == USB_STR_COM_PORT) {
|
||||||
|
furi_console_log_printf("str COM port");
|
||||||
|
dev->status.data_ptr = (uint8_t*)&dev_com_descr;
|
||||||
|
dev->status.data_count = dev_com_descr.bLength;
|
||||||
|
return usbd_ack;
|
||||||
|
}
|
||||||
|
} else if(USB_DTYPE_BINARY_OBJECT_STORE == dtype) {
|
||||||
|
furi_console_log_printf("BOS descriptor");
|
||||||
|
uint16_t length = req->wLength;
|
||||||
|
if(length > sizeof(usb_bos_hierarchy_t)) {
|
||||||
|
length = sizeof(usb_bos_hierarchy_t);
|
||||||
|
}
|
||||||
|
dev->status.data_ptr = (uint8_t*)&usb_bos_hierarchy;
|
||||||
|
dev->status.data_count = length;
|
||||||
|
return usbd_ack;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(((USB_REQ_RECIPIENT | USB_REQ_TYPE) & req->bmRequestType) ==
|
||||||
|
(USB_REQ_INTERFACE | USB_REQ_CLASS) &&
|
||||||
|
req->wIndex == 0) {
|
||||||
|
// class request
|
||||||
|
switch(req->bRequest) {
|
||||||
|
// get hid descriptor
|
||||||
|
case USB_HID_GETREPORT:
|
||||||
|
furi_console_log_printf("get report");
|
||||||
|
return usbd_fail;
|
||||||
|
// set hid idle
|
||||||
|
case USB_HID_SETIDLE:
|
||||||
|
furi_console_log_printf("set idle");
|
||||||
|
return usbd_ack;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(((USB_REQ_RECIPIENT | USB_REQ_TYPE) & req->bmRequestType) ==
|
||||||
|
(USB_REQ_INTERFACE | USB_REQ_CLASS) &&
|
||||||
|
req->wIndex == 2) {
|
||||||
|
// class request
|
||||||
|
switch(req->bRequest) {
|
||||||
|
// control line state
|
||||||
|
case USB_CDC_SET_CONTROL_LINE_STATE:
|
||||||
|
furi_console_log_printf("set control line state");
|
||||||
|
cdc_ctrl_line_state = req->wValue;
|
||||||
|
if(dap_state.control_line_callback_cdc != NULL) {
|
||||||
|
dap_state.control_line_callback_cdc(cdc_ctrl_line_state, dap_state.context_cdc);
|
||||||
|
}
|
||||||
|
return usbd_ack;
|
||||||
|
// set cdc line coding
|
||||||
|
case USB_CDC_SET_LINE_CODING:
|
||||||
|
furi_console_log_printf("set line coding");
|
||||||
|
memcpy(&cdc_config, req->data, sizeof(cdc_config));
|
||||||
|
if(dap_state.config_callback_cdc != NULL) {
|
||||||
|
dap_state.config_callback_cdc(&cdc_config, dap_state.context_cdc);
|
||||||
|
}
|
||||||
|
return usbd_ack;
|
||||||
|
// get cdc line coding
|
||||||
|
case USB_CDC_GET_LINE_CODING:
|
||||||
|
furi_console_log_printf("get line coding");
|
||||||
|
dev->status.data_ptr = &cdc_config;
|
||||||
|
dev->status.data_count = sizeof(cdc_config);
|
||||||
|
return usbd_ack;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(((USB_REQ_RECIPIENT | USB_REQ_TYPE) & req->bmRequestType) ==
|
||||||
|
(USB_REQ_INTERFACE | USB_REQ_STANDARD) &&
|
||||||
|
req->wIndex == 0 && req->bRequest == USB_STD_GET_DESCRIPTOR) {
|
||||||
|
// standard request
|
||||||
|
switch(req->wValue >> 8) {
|
||||||
|
// get hid descriptor
|
||||||
|
case USB_DTYPE_HID:
|
||||||
|
furi_console_log_printf("get hid descriptor");
|
||||||
|
dev->status.data_ptr = (uint8_t*)&(hid_cfg_desc.hid);
|
||||||
|
dev->status.data_count = sizeof(hid_cfg_desc.hid);
|
||||||
|
return usbd_ack;
|
||||||
|
// get hid report descriptor
|
||||||
|
case USB_DTYPE_HID_REPORT:
|
||||||
|
furi_console_log_printf("get hid report descriptor");
|
||||||
|
dev->status.data_ptr = (uint8_t*)hid_report_desc;
|
||||||
|
dev->status.data_count = sizeof(hid_report_desc);
|
||||||
|
return usbd_ack;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return usbd_fail;
|
||||||
|
}
|
||||||
55
applications/plugins/dap_link/usb/dap_v2_usb.h
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <furi_hal_usb.h>
|
||||||
|
#include <usb_cdc.h>
|
||||||
|
|
||||||
|
extern FuriHalUsbInterface dap_v2_usb_hid;
|
||||||
|
|
||||||
|
// receive callback type
|
||||||
|
typedef void (*DapRxCallback)(void* context);
|
||||||
|
|
||||||
|
typedef void (*DapStateCallback)(bool state, void* context);
|
||||||
|
|
||||||
|
/************************************ V1 ***************************************/
|
||||||
|
|
||||||
|
int32_t dap_v1_usb_tx(uint8_t* buffer, uint8_t size);
|
||||||
|
|
||||||
|
size_t dap_v1_usb_rx(uint8_t* buffer, size_t size);
|
||||||
|
|
||||||
|
void dap_v1_usb_set_rx_callback(DapRxCallback callback);
|
||||||
|
|
||||||
|
/************************************ V2 ***************************************/
|
||||||
|
|
||||||
|
int32_t dap_v2_usb_tx(uint8_t* buffer, uint8_t size);
|
||||||
|
|
||||||
|
size_t dap_v2_usb_rx(uint8_t* buffer, size_t size);
|
||||||
|
|
||||||
|
void dap_v2_usb_set_rx_callback(DapRxCallback callback);
|
||||||
|
|
||||||
|
/************************************ CDC **************************************/
|
||||||
|
|
||||||
|
typedef void (*DapCDCControlLineCallback)(uint8_t state, void* context);
|
||||||
|
typedef void (*DapCDCConfigCallback)(struct usb_cdc_line_coding* config, void* context);
|
||||||
|
|
||||||
|
int32_t dap_cdc_usb_tx(uint8_t* buffer, uint8_t size);
|
||||||
|
|
||||||
|
size_t dap_cdc_usb_rx(uint8_t* buffer, size_t size);
|
||||||
|
|
||||||
|
void dap_cdc_usb_set_rx_callback(DapRxCallback callback);
|
||||||
|
|
||||||
|
void dap_cdc_usb_set_control_line_callback(DapCDCControlLineCallback callback);
|
||||||
|
|
||||||
|
void dap_cdc_usb_set_config_callback(DapCDCConfigCallback callback);
|
||||||
|
|
||||||
|
void dap_cdc_usb_set_context(void* context);
|
||||||
|
|
||||||
|
/*********************************** Common ************************************/
|
||||||
|
|
||||||
|
void dap_common_usb_set_context(void* context);
|
||||||
|
|
||||||
|
void dap_common_usb_set_state_callback(DapStateCallback callback);
|
||||||
|
|
||||||
|
void dap_common_usb_alloc_name(const char* name);
|
||||||
|
|
||||||
|
void dap_common_usb_free_name();
|
||||||
|
|
||||||
|
void dap_common_wait_for_deinit();
|
||||||
143
applications/plugins/dap_link/usb/usb_winusb.h
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/*- Definitions -------------------------------------------------------------*/
|
||||||
|
|
||||||
|
#define USB_PACK __attribute__((packed))
|
||||||
|
|
||||||
|
#define USB_WINUSB_VENDOR_CODE 0x20
|
||||||
|
|
||||||
|
#define USB_WINUSB_WINDOWS_VERSION 0x06030000 // Windows 8.1
|
||||||
|
|
||||||
|
#define USB_WINUSB_PLATFORM_CAPABILITY_ID \
|
||||||
|
{ \
|
||||||
|
0xdf, 0x60, 0xdd, 0xd8, 0x89, 0x45, 0xc7, 0x4c, 0x9c, 0xd2, 0x65, 0x9d, 0x9e, 0x64, 0x8a, \
|
||||||
|
0x9f \
|
||||||
|
}
|
||||||
|
|
||||||
|
enum // WinUSB Microsoft OS 2.0 descriptor request codes
|
||||||
|
{
|
||||||
|
USB_WINUSB_DESCRIPTOR_INDEX = 0x07,
|
||||||
|
USB_WINUSB_SET_ALT_ENUMERATION = 0x08,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum // wDescriptorType
|
||||||
|
{
|
||||||
|
USB_WINUSB_SET_HEADER_DESCRIPTOR = 0x00,
|
||||||
|
USB_WINUSB_SUBSET_HEADER_CONFIGURATION = 0x01,
|
||||||
|
USB_WINUSB_SUBSET_HEADER_FUNCTION = 0x02,
|
||||||
|
USB_WINUSB_FEATURE_COMPATBLE_ID = 0x03,
|
||||||
|
USB_WINUSB_FEATURE_REG_PROPERTY = 0x04,
|
||||||
|
USB_WINUSB_FEATURE_MIN_RESUME_TIME = 0x05,
|
||||||
|
USB_WINUSB_FEATURE_MODEL_ID = 0x06,
|
||||||
|
USB_WINUSB_FEATURE_CCGP_DEVICE = 0x07,
|
||||||
|
USB_WINUSB_FEATURE_VENDOR_REVISION = 0x08,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum // wPropertyDataType
|
||||||
|
{
|
||||||
|
USB_WINUSB_PROPERTY_DATA_TYPE_SZ = 1,
|
||||||
|
USB_WINUSB_PROPERTY_DATA_TYPE_EXPAND_SZ = 2,
|
||||||
|
USB_WINUSB_PROPERTY_DATA_TYPE_BINARY = 3,
|
||||||
|
USB_WINUSB_PROPERTY_DATA_TYPE_DWORD_LITTLE_ENDIAN = 4,
|
||||||
|
USB_WINUSB_PROPERTY_DATA_TYPE_DWORD_BIG_ENDIAN = 5,
|
||||||
|
USB_WINUSB_PROPERTY_DATA_TYPE_LINK = 6,
|
||||||
|
USB_WINUSB_PROPERTY_DATA_TYPE_MULTI_SZ = 7,
|
||||||
|
};
|
||||||
|
|
||||||
|
/*- Types BOS -------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
typedef struct USB_PACK {
|
||||||
|
uint8_t bLength;
|
||||||
|
uint8_t bDescriptorType;
|
||||||
|
uint16_t wTotalLength;
|
||||||
|
uint8_t bNumDeviceCaps;
|
||||||
|
} usb_binary_object_store_descriptor_t;
|
||||||
|
|
||||||
|
/*- Types WinUSB -------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
typedef struct USB_PACK {
|
||||||
|
uint8_t bLength;
|
||||||
|
uint8_t bDescriptorType;
|
||||||
|
uint8_t bDevCapabilityType;
|
||||||
|
uint8_t bReserved;
|
||||||
|
uint8_t PlatformCapabilityUUID[16];
|
||||||
|
uint32_t dwWindowsVersion;
|
||||||
|
uint16_t wMSOSDescriptorSetTotalLength;
|
||||||
|
uint8_t bMS_VendorCode;
|
||||||
|
uint8_t bAltEnumCode;
|
||||||
|
} usb_winusb_capability_descriptor_t;
|
||||||
|
|
||||||
|
typedef struct USB_PACK {
|
||||||
|
uint16_t wLength;
|
||||||
|
uint16_t wDescriptorType;
|
||||||
|
uint32_t dwWindowsVersion;
|
||||||
|
uint16_t wDescriptorSetTotalLength;
|
||||||
|
} usb_winusb_set_header_descriptor_t;
|
||||||
|
|
||||||
|
typedef struct USB_PACK {
|
||||||
|
uint16_t wLength;
|
||||||
|
uint16_t wDescriptorType;
|
||||||
|
uint8_t bConfigurationValue;
|
||||||
|
uint8_t bReserved;
|
||||||
|
uint16_t wTotalLength;
|
||||||
|
} usb_winusb_subset_header_configuration_t;
|
||||||
|
|
||||||
|
typedef struct USB_PACK {
|
||||||
|
uint16_t wLength;
|
||||||
|
uint16_t wDescriptorType;
|
||||||
|
uint8_t bFirstInterface;
|
||||||
|
uint8_t bReserved;
|
||||||
|
uint16_t wSubsetLength;
|
||||||
|
} usb_winusb_subset_header_function_t;
|
||||||
|
|
||||||
|
typedef struct USB_PACK {
|
||||||
|
uint16_t wLength;
|
||||||
|
uint16_t wDescriptorType;
|
||||||
|
uint8_t CompatibleID[8];
|
||||||
|
uint8_t SubCompatibleID[8];
|
||||||
|
} usb_winusb_feature_compatble_id_t;
|
||||||
|
|
||||||
|
typedef struct USB_PACK {
|
||||||
|
uint16_t wLength;
|
||||||
|
uint16_t wDescriptorType;
|
||||||
|
uint16_t wPropertyDataType;
|
||||||
|
//uint16_t wPropertyNameLength;
|
||||||
|
//uint8_t PropertyName[...];
|
||||||
|
//uint16_t wPropertyDataLength
|
||||||
|
//uint8_t PropertyData[...];
|
||||||
|
} usb_winusb_feature_reg_property_t;
|
||||||
|
|
||||||
|
typedef struct USB_PACK {
|
||||||
|
uint16_t wLength;
|
||||||
|
uint16_t wDescriptorType;
|
||||||
|
uint16_t wPropertyDataType;
|
||||||
|
uint16_t wPropertyNameLength;
|
||||||
|
uint8_t PropertyName[42];
|
||||||
|
uint16_t wPropertyDataLength;
|
||||||
|
uint8_t PropertyData[80];
|
||||||
|
} usb_winusb_feature_reg_property_guids_t;
|
||||||
|
|
||||||
|
typedef struct USB_PACK {
|
||||||
|
uint16_t wLength;
|
||||||
|
uint16_t wDescriptorType;
|
||||||
|
uint8_t bResumeRecoveryTime;
|
||||||
|
uint8_t bResumeSignalingTime;
|
||||||
|
} usb_winusb_feature_min_resume_time_t;
|
||||||
|
|
||||||
|
typedef struct USB_PACK {
|
||||||
|
uint16_t wLength;
|
||||||
|
uint16_t wDescriptorType;
|
||||||
|
uint8_t ModelID[16];
|
||||||
|
} usb_winusb_feature_model_id_t;
|
||||||
|
|
||||||
|
typedef struct USB_PACK {
|
||||||
|
uint16_t wLength;
|
||||||
|
uint16_t wDescriptorType;
|
||||||
|
} usb_winusb_feature_ccgp_device_t;
|
||||||
|
|
||||||
|
typedef struct USB_PACK {
|
||||||
|
uint16_t wLength;
|
||||||
|
uint16_t wDescriptorType;
|
||||||
|
uint16_t VendorRevision;
|
||||||
|
} usb_winusb_feature_vendor_revision_t;
|
||||||
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 loclass_hash1 loclass_hash1
|
||||||
* @param key_sel output key_sel=h[loclass_hash1[i]]
|
* @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:
|
*Expected:
|
||||||
* High Security Key Table
|
* High Security Key Table
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include "rfal_picopass.h"
|
#include "rfal_picopass.h"
|
||||||
#include <optimized_ikeys.h>
|
#include <optimized_ikeys.h>
|
||||||
#include <optimized_cipher.h>
|
#include <optimized_cipher.h>
|
||||||
|
#include "helpers/iclass_elite_dict.h"
|
||||||
|
|
||||||
#define PICOPASS_DEV_NAME_MAX_LEN 22
|
#define PICOPASS_DEV_NAME_MAX_LEN 22
|
||||||
#define PICOPASS_READER_DATA_MAX_SIZE 64
|
#define PICOPASS_READER_DATA_MAX_SIZE 64
|
||||||
@@ -49,6 +50,7 @@ typedef struct {
|
|||||||
bool se_enabled;
|
bool se_enabled;
|
||||||
bool sio;
|
bool sio;
|
||||||
bool biometrics;
|
bool biometrics;
|
||||||
|
uint8_t key[8];
|
||||||
uint8_t pin_length;
|
uint8_t pin_length;
|
||||||
PicopassEncryption encryption;
|
PicopassEncryption encryption;
|
||||||
uint8_t credential[8];
|
uint8_t credential[8];
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
#include "picopass_worker_i.h"
|
#include "picopass_worker_i.h"
|
||||||
|
|
||||||
|
#include <flipper_format/flipper_format.h>
|
||||||
|
|
||||||
#define TAG "PicopassWorker"
|
#define TAG "PicopassWorker"
|
||||||
|
|
||||||
const uint8_t picopass_iclass_key[] = {0xaf, 0xa7, 0x85, 0xa7, 0xda, 0xb3, 0x33, 0x78};
|
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;
|
return ERR_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
ReturnCode picopass_read_card(PicopassBlock* AA1) {
|
ReturnCode picopass_auth(PicopassBlock* AA1, PicopassPacs* pacs) {
|
||||||
rfalPicoPassReadCheckRes rcRes;
|
rfalPicoPassReadCheckRes rcRes;
|
||||||
rfalPicoPassCheckRes chkRes;
|
rfalPicoPassCheckRes chkRes;
|
||||||
|
|
||||||
@@ -197,10 +199,68 @@ ReturnCode picopass_read_card(PicopassBlock* AA1) {
|
|||||||
loclass_opt_doReaderMAC(ccnr, div_key, mac);
|
loclass_opt_doReaderMAC(ccnr, div_key, mac);
|
||||||
|
|
||||||
err = rfalPicoPassPollerCheck(mac, &chkRes);
|
err = rfalPicoPassPollerCheck(mac, &chkRes);
|
||||||
if(err != ERR_NONE) {
|
if(err == ERR_NONE) {
|
||||||
FURI_LOG_E(TAG, "rfalPicoPassPollerCheck error %d", err);
|
return ERR_NONE;
|
||||||
return err;
|
|
||||||
}
|
}
|
||||||
|
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 ?
|
size_t app_limit = AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[0] < PICOPASS_MAX_APP_LIMIT ?
|
||||||
AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[0] :
|
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);
|
pacs->se_enabled = (memcmp(AA1[5].data, "\xff\xff\xff\x00\x06\xff\xff\xff", 8) == 0);
|
||||||
if(pacs->se_enabled) {
|
if(pacs->se_enabled) {
|
||||||
FURI_LOG_D(TAG, "SE enabled");
|
FURI_LOG_D(TAG, "SE enabled");
|
||||||
|
nextState = PicopassWorkerEventFail;
|
||||||
}
|
}
|
||||||
|
|
||||||
err = picopass_read_card(AA1);
|
if(nextState == PicopassWorkerEventSuccess) {
|
||||||
if(err != ERR_NONE) {
|
err = picopass_auth(AA1, pacs);
|
||||||
FURI_LOG_E(TAG, "picopass_read_card error %d", err);
|
if(err != ERR_NONE) {
|
||||||
nextState = PicopassWorkerEventFail;
|
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) {
|
if(nextState == PicopassWorkerEventSuccess) {
|
||||||
err = picopass_device_parse_credential(AA1, pacs);
|
err = picopass_device_parse_credential(AA1, pacs);
|
||||||
}
|
if(err != ERR_NONE) {
|
||||||
if(err != ERR_NONE) {
|
FURI_LOG_E(TAG, "picopass_device_parse_credential error %d", err);
|
||||||
FURI_LOG_E(TAG, "picopass_device_parse_credential error %d", err);
|
nextState = PicopassWorkerEventFail;
|
||||||
nextState = PicopassWorkerEventFail;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(nextState == PicopassWorkerEventSuccess) {
|
if(nextState == PicopassWorkerEventSuccess) {
|
||||||
err = picopass_device_parse_wiegand(pacs->credential, &pacs->record);
|
err = picopass_device_parse_wiegand(pacs->credential, &pacs->record);
|
||||||
}
|
if(err != ERR_NONE) {
|
||||||
if(err != ERR_NONE) {
|
FURI_LOG_E(TAG, "picopass_device_parse_wiegand error %d", err);
|
||||||
FURI_LOG_E(TAG, "picopass_device_parse_wiegand error %d", err);
|
nextState = PicopassWorkerEventFail;
|
||||||
nextState = PicopassWorkerEventFail;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notify caller and exit
|
// Notify caller and exit
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
struct PicopassWorker {
|
struct PicopassWorker {
|
||||||
FuriThread* thread;
|
FuriThread* thread;
|
||||||
Storage* storage;
|
Storage* storage;
|
||||||
|
Stream* dict_stream;
|
||||||
|
|
||||||
PicopassDeviceData* dev_data;
|
PicopassDeviceData* dev_data;
|
||||||
PicopassWorkerCallback callback;
|
PicopassWorkerCallback callback;
|
||||||
|
|||||||
@@ -15,12 +15,10 @@ void picopass_scene_read_card_success_widget_callback(
|
|||||||
|
|
||||||
void picopass_scene_read_card_success_on_enter(void* context) {
|
void picopass_scene_read_card_success_on_enter(void* context) {
|
||||||
Picopass* picopass = context;
|
Picopass* picopass = context;
|
||||||
FuriString* credential_str;
|
FuriString* csn_str = furi_string_alloc_set("CSN:");
|
||||||
FuriString* wiegand_str;
|
FuriString* credential_str = furi_string_alloc();
|
||||||
FuriString* sio_str;
|
FuriString* wiegand_str = furi_string_alloc();
|
||||||
credential_str = furi_string_alloc();
|
FuriString* sio_str = furi_string_alloc();
|
||||||
wiegand_str = furi_string_alloc();
|
|
||||||
sio_str = furi_string_alloc();
|
|
||||||
|
|
||||||
DOLPHIN_DEED(DolphinDeedNfcReadSuccess);
|
DOLPHIN_DEED(DolphinDeedNfcReadSuccess);
|
||||||
|
|
||||||
@@ -28,10 +26,18 @@ void picopass_scene_read_card_success_on_enter(void* context) {
|
|||||||
notification_message(picopass->notifications, &sequence_success);
|
notification_message(picopass->notifications, &sequence_success);
|
||||||
|
|
||||||
// Setup view
|
// Setup view
|
||||||
|
PicopassBlock* AA1 = picopass->dev->dev_data.AA1;
|
||||||
PicopassPacs* pacs = &picopass->dev->dev_data.pacs;
|
PicopassPacs* pacs = &picopass->dev->dev_data.pacs;
|
||||||
Widget* widget = picopass->widget;
|
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");
|
furi_string_cat_printf(wiegand_str, "Read Failed");
|
||||||
|
|
||||||
if(pacs->se_enabled) {
|
if(pacs->se_enabled) {
|
||||||
@@ -79,18 +85,21 @@ void picopass_scene_read_card_success_on_enter(void* context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
widget_add_string_element(
|
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_add_string_element(
|
||||||
widget,
|
widget,
|
||||||
64,
|
64,
|
||||||
32,
|
36,
|
||||||
AlignCenter,
|
AlignCenter,
|
||||||
AlignCenter,
|
AlignCenter,
|
||||||
FontSecondary,
|
FontSecondary,
|
||||||
furi_string_get_cstr(credential_str));
|
furi_string_get_cstr(credential_str));
|
||||||
widget_add_string_element(
|
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(credential_str);
|
||||||
furi_string_free(wiegand_str);
|
furi_string_free(wiegand_str);
|
||||||
furi_string_free(sio_str);
|
furi_string_free(sio_str);
|
||||||
|
|||||||
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.3"
|
||||||
|
#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 |
307
applications/plugins/weather_station/protocols/acurite_592txr.c
Normal file
@@ -0,0 +1,307 @@
|
|||||||
|
#include "acurite_592txr.h"
|
||||||
|
|
||||||
|
#define TAG "WSProtocolAcurite_592TXR"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Help
|
||||||
|
* https://github.com/merbanan/rtl_433/blob/5bef4e43133ac4c0e2d18d36f87c52b4f9458453/src/devices/acurite.c
|
||||||
|
*
|
||||||
|
* Acurite 592TXR Temperature Humidity sensor decoder
|
||||||
|
* Message Type 0x04, 7 bytes
|
||||||
|
* | Byte 0 | Byte 1 | Byte 2 | Byte 3 | Byte 4 | Byte 5 | Byte 6 |
|
||||||
|
* | --------- | --------- | --------- | --------- | --------- | --------- | --------- |
|
||||||
|
* | CCII IIII | IIII IIII | pB00 0100 | pHHH HHHH | p??T TTTT | pTTT TTTT | KKKK KKKK |
|
||||||
|
* - C: Channel 00: C, 10: B, 11: A, (01 is invalid)
|
||||||
|
* - I: Device ID (14 bits)
|
||||||
|
* - B: Battery, 1 is battery OK, 0 is battery low
|
||||||
|
* - M: Message type (6 bits), 0x04
|
||||||
|
* - T: Temperature Celsius (11 - 14 bits?), + 1000 * 10
|
||||||
|
* - H: Relative Humidity (%) (7 bits)
|
||||||
|
* - K: Checksum (8 bits)
|
||||||
|
* - p: Parity bit
|
||||||
|
* Notes:
|
||||||
|
* - Temperature
|
||||||
|
* - Encoded as Celsius + 1000 * 10
|
||||||
|
* - only 11 bits needed for specified range -40 C to 70 C (-40 F - 158 F)
|
||||||
|
* - However 14 bits available for temperature, giving possible range of -100 C to 1538.4 C
|
||||||
|
* - @todo - check if high 3 bits ever used for anything else
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
static const SubGhzBlockConst ws_protocol_acurite_592txr_const = {
|
||||||
|
.te_short = 200,
|
||||||
|
.te_long = 400,
|
||||||
|
.te_delta = 90,
|
||||||
|
.min_count_bit_for_found = 56,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct WSProtocolDecoderAcurite_592TXR {
|
||||||
|
SubGhzProtocolDecoderBase base;
|
||||||
|
|
||||||
|
SubGhzBlockDecoder decoder;
|
||||||
|
WSBlockGeneric generic;
|
||||||
|
|
||||||
|
uint16_t header_count;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct WSProtocolEncoderAcurite_592TXR {
|
||||||
|
SubGhzProtocolEncoderBase base;
|
||||||
|
|
||||||
|
SubGhzProtocolBlockEncoder encoder;
|
||||||
|
WSBlockGeneric generic;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
Acurite_592TXRDecoderStepReset = 0,
|
||||||
|
Acurite_592TXRDecoderStepCheckPreambule,
|
||||||
|
Acurite_592TXRDecoderStepSaveDuration,
|
||||||
|
Acurite_592TXRDecoderStepCheckDuration,
|
||||||
|
} Acurite_592TXRDecoderStep;
|
||||||
|
|
||||||
|
const SubGhzProtocolDecoder ws_protocol_acurite_592txr_decoder = {
|
||||||
|
.alloc = ws_protocol_decoder_acurite_592txr_alloc,
|
||||||
|
.free = ws_protocol_decoder_acurite_592txr_free,
|
||||||
|
|
||||||
|
.feed = ws_protocol_decoder_acurite_592txr_feed,
|
||||||
|
.reset = ws_protocol_decoder_acurite_592txr_reset,
|
||||||
|
|
||||||
|
.get_hash_data = ws_protocol_decoder_acurite_592txr_get_hash_data,
|
||||||
|
.serialize = ws_protocol_decoder_acurite_592txr_serialize,
|
||||||
|
.deserialize = ws_protocol_decoder_acurite_592txr_deserialize,
|
||||||
|
.get_string = ws_protocol_decoder_acurite_592txr_get_string,
|
||||||
|
};
|
||||||
|
|
||||||
|
const SubGhzProtocolEncoder ws_protocol_acurite_592txr_encoder = {
|
||||||
|
.alloc = NULL,
|
||||||
|
.free = NULL,
|
||||||
|
|
||||||
|
.deserialize = NULL,
|
||||||
|
.stop = NULL,
|
||||||
|
.yield = NULL,
|
||||||
|
};
|
||||||
|
|
||||||
|
const SubGhzProtocol ws_protocol_acurite_592txr = {
|
||||||
|
.name = WS_PROTOCOL_ACURITE_592TXR_NAME,
|
||||||
|
.type = SubGhzProtocolWeatherStation,
|
||||||
|
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 |
|
||||||
|
SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable,
|
||||||
|
|
||||||
|
.decoder = &ws_protocol_acurite_592txr_decoder,
|
||||||
|
.encoder = &ws_protocol_acurite_592txr_encoder,
|
||||||
|
};
|
||||||
|
|
||||||
|
void* ws_protocol_decoder_acurite_592txr_alloc(SubGhzEnvironment* environment) {
|
||||||
|
UNUSED(environment);
|
||||||
|
WSProtocolDecoderAcurite_592TXR* instance = malloc(sizeof(WSProtocolDecoderAcurite_592TXR));
|
||||||
|
instance->base.protocol = &ws_protocol_acurite_592txr;
|
||||||
|
instance->generic.protocol_name = instance->base.protocol->name;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ws_protocol_decoder_acurite_592txr_free(void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
WSProtocolDecoderAcurite_592TXR* instance = context;
|
||||||
|
free(instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ws_protocol_decoder_acurite_592txr_reset(void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
WSProtocolDecoderAcurite_592TXR* instance = context;
|
||||||
|
instance->decoder.parser_step = Acurite_592TXRDecoderStepReset;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ws_protocol_acurite_592txr_check_crc(WSProtocolDecoderAcurite_592TXR* instance) {
|
||||||
|
uint8_t msg[] = {
|
||||||
|
instance->decoder.decode_data >> 48,
|
||||||
|
instance->decoder.decode_data >> 40,
|
||||||
|
instance->decoder.decode_data >> 32,
|
||||||
|
instance->decoder.decode_data >> 24,
|
||||||
|
instance->decoder.decode_data >> 16,
|
||||||
|
instance->decoder.decode_data >> 8};
|
||||||
|
|
||||||
|
if((subghz_protocol_blocks_add_bytes(msg, 6) ==
|
||||||
|
(uint8_t)(instance->decoder.decode_data & 0xFF)) &&
|
||||||
|
(!subghz_protocol_blocks_parity_bytes(&msg[2], 4))) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Analysis of received data
|
||||||
|
* @param instance Pointer to a WSBlockGeneric* instance
|
||||||
|
*/
|
||||||
|
static void ws_protocol_acurite_592txr_remote_controller(WSBlockGeneric* instance) {
|
||||||
|
uint8_t channel[] = {3, 0, 2, 1};
|
||||||
|
uint8_t channel_raw = ((instance->data >> 54) & 0x03);
|
||||||
|
instance->channel = channel[channel_raw];
|
||||||
|
instance->id = (instance->data >> 40) & 0x3FFF;
|
||||||
|
instance->battery_low = !((instance->data >> 38) & 1);
|
||||||
|
instance->humidity = (instance->data >> 24) & 0x7F;
|
||||||
|
|
||||||
|
uint16_t temp_raw = ((instance->data >> 9) & 0xF80) | ((instance->data >> 8) & 0x7F);
|
||||||
|
instance->temp = ((float)(temp_raw)-1000) / 10.0f;
|
||||||
|
|
||||||
|
instance->btn = WS_NO_BTN;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ws_protocol_decoder_acurite_592txr_feed(void* context, bool level, uint32_t duration) {
|
||||||
|
furi_assert(context);
|
||||||
|
WSProtocolDecoderAcurite_592TXR* instance = context;
|
||||||
|
|
||||||
|
switch(instance->decoder.parser_step) {
|
||||||
|
case Acurite_592TXRDecoderStepReset:
|
||||||
|
if((level) && (DURATION_DIFF(duration, ws_protocol_acurite_592txr_const.te_short * 3) <
|
||||||
|
ws_protocol_acurite_592txr_const.te_delta * 2)) {
|
||||||
|
instance->decoder.parser_step = Acurite_592TXRDecoderStepCheckPreambule;
|
||||||
|
instance->decoder.te_last = duration;
|
||||||
|
instance->header_count = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Acurite_592TXRDecoderStepCheckPreambule:
|
||||||
|
if(level) {
|
||||||
|
instance->decoder.te_last = duration;
|
||||||
|
} else {
|
||||||
|
if((DURATION_DIFF(
|
||||||
|
instance->decoder.te_last, ws_protocol_acurite_592txr_const.te_short * 3) <
|
||||||
|
ws_protocol_acurite_592txr_const.te_delta * 2) &&
|
||||||
|
(DURATION_DIFF(duration, ws_protocol_acurite_592txr_const.te_short * 3) <
|
||||||
|
ws_protocol_acurite_592txr_const.te_delta * 2)) {
|
||||||
|
//Found preambule
|
||||||
|
instance->header_count++;
|
||||||
|
} else if((instance->header_count > 2) && (instance->header_count < 5)) {
|
||||||
|
if((DURATION_DIFF(
|
||||||
|
instance->decoder.te_last, ws_protocol_acurite_592txr_const.te_short) <
|
||||||
|
ws_protocol_acurite_592txr_const.te_delta) &&
|
||||||
|
(DURATION_DIFF(duration, ws_protocol_acurite_592txr_const.te_long) <
|
||||||
|
ws_protocol_acurite_592txr_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 = Acurite_592TXRDecoderStepSaveDuration;
|
||||||
|
} else if(
|
||||||
|
(DURATION_DIFF(
|
||||||
|
instance->decoder.te_last, ws_protocol_acurite_592txr_const.te_long) <
|
||||||
|
ws_protocol_acurite_592txr_const.te_delta) &&
|
||||||
|
(DURATION_DIFF(duration, ws_protocol_acurite_592txr_const.te_short) <
|
||||||
|
ws_protocol_acurite_592txr_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 = Acurite_592TXRDecoderStepSaveDuration;
|
||||||
|
} else {
|
||||||
|
instance->decoder.parser_step = Acurite_592TXRDecoderStepReset;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
instance->decoder.parser_step = Acurite_592TXRDecoderStepReset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Acurite_592TXRDecoderStepSaveDuration:
|
||||||
|
if(level) {
|
||||||
|
instance->decoder.te_last = duration;
|
||||||
|
instance->decoder.parser_step = Acurite_592TXRDecoderStepCheckDuration;
|
||||||
|
} else {
|
||||||
|
instance->decoder.parser_step = Acurite_592TXRDecoderStepReset;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Acurite_592TXRDecoderStepCheckDuration:
|
||||||
|
if(!level) {
|
||||||
|
if(duration >= ((uint32_t)ws_protocol_acurite_592txr_const.te_short * 5)) {
|
||||||
|
if((instance->decoder.decode_count_bit ==
|
||||||
|
ws_protocol_acurite_592txr_const.min_count_bit_for_found) &&
|
||||||
|
ws_protocol_acurite_592txr_check_crc(instance)) {
|
||||||
|
instance->generic.data = instance->decoder.decode_data;
|
||||||
|
instance->generic.data_count_bit = instance->decoder.decode_count_bit;
|
||||||
|
ws_protocol_acurite_592txr_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 = Acurite_592TXRDecoderStepReset;
|
||||||
|
break;
|
||||||
|
} else if(
|
||||||
|
(DURATION_DIFF(
|
||||||
|
instance->decoder.te_last, ws_protocol_acurite_592txr_const.te_short) <
|
||||||
|
ws_protocol_acurite_592txr_const.te_delta) &&
|
||||||
|
(DURATION_DIFF(duration, ws_protocol_acurite_592txr_const.te_long) <
|
||||||
|
ws_protocol_acurite_592txr_const.te_delta)) {
|
||||||
|
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
|
||||||
|
instance->decoder.parser_step = Acurite_592TXRDecoderStepSaveDuration;
|
||||||
|
} else if(
|
||||||
|
(DURATION_DIFF(
|
||||||
|
instance->decoder.te_last, ws_protocol_acurite_592txr_const.te_long) <
|
||||||
|
ws_protocol_acurite_592txr_const.te_delta) &&
|
||||||
|
(DURATION_DIFF(duration, ws_protocol_acurite_592txr_const.te_short) <
|
||||||
|
ws_protocol_acurite_592txr_const.te_delta)) {
|
||||||
|
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
|
||||||
|
instance->decoder.parser_step = Acurite_592TXRDecoderStepSaveDuration;
|
||||||
|
} else {
|
||||||
|
instance->decoder.parser_step = Acurite_592TXRDecoderStepReset;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
instance->decoder.parser_step = Acurite_592TXRDecoderStepReset;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t ws_protocol_decoder_acurite_592txr_get_hash_data(void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
WSProtocolDecoderAcurite_592TXR* instance = context;
|
||||||
|
return subghz_protocol_blocks_get_hash_data(
|
||||||
|
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ws_protocol_decoder_acurite_592txr_serialize(
|
||||||
|
void* context,
|
||||||
|
FlipperFormat* flipper_format,
|
||||||
|
SubGhzRadioPreset* preset) {
|
||||||
|
furi_assert(context);
|
||||||
|
WSProtocolDecoderAcurite_592TXR* instance = context;
|
||||||
|
return ws_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ws_protocol_decoder_acurite_592txr_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||||
|
furi_assert(context);
|
||||||
|
WSProtocolDecoderAcurite_592TXR* instance = context;
|
||||||
|
bool ret = false;
|
||||||
|
do {
|
||||||
|
if(!ws_block_generic_deserialize(&instance->generic, flipper_format)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if(instance->generic.data_count_bit !=
|
||||||
|
ws_protocol_acurite_592txr_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_acurite_592txr_get_string(void* context, FuriString* output) {
|
||||||
|
furi_assert(context);
|
||||||
|
WSProtocolDecoderAcurite_592TXR* 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_ACURITE_592TXR_NAME "Acurite 592TXR"
|
||||||
|
|
||||||
|
typedef struct WSProtocolDecoderAcurite_592TXR WSProtocolDecoderAcurite_592TXR;
|
||||||
|
typedef struct WSProtocolEncoderAcurite_592TXR WSProtocolEncoderAcurite_592TXR;
|
||||||
|
|
||||||
|
extern const SubGhzProtocolDecoder ws_protocol_acurite_592txr_decoder;
|
||||||
|
extern const SubGhzProtocolEncoder ws_protocol_acurite_592txr_encoder;
|
||||||
|
extern const SubGhzProtocol ws_protocol_acurite_592txr;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allocate WSProtocolDecoderAcurite_592TXR.
|
||||||
|
* @param environment Pointer to a SubGhzEnvironment instance
|
||||||
|
* @return WSProtocolDecoderAcurite_592TXR* pointer to a WSProtocolDecoderAcurite_592TXR instance
|
||||||
|
*/
|
||||||
|
void* ws_protocol_decoder_acurite_592txr_alloc(SubGhzEnvironment* environment);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Free WSProtocolDecoderAcurite_592TXR.
|
||||||
|
* @param context Pointer to a WSProtocolDecoderAcurite_592TXR instance
|
||||||
|
*/
|
||||||
|
void ws_protocol_decoder_acurite_592txr_free(void* context);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset decoder WSProtocolDecoderAcurite_592TXR.
|
||||||
|
* @param context Pointer to a WSProtocolDecoderAcurite_592TXR instance
|
||||||
|
*/
|
||||||
|
void ws_protocol_decoder_acurite_592txr_reset(void* context);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a raw sequence of levels and durations received from the air.
|
||||||
|
* @param context Pointer to a WSProtocolDecoderAcurite_592TXR instance
|
||||||
|
* @param level Signal level true-high false-low
|
||||||
|
* @param duration Duration of this level in, us
|
||||||
|
*/
|
||||||
|
void ws_protocol_decoder_acurite_592txr_feed(void* context, bool level, uint32_t duration);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getting the hash sum of the last randomly received parcel.
|
||||||
|
* @param context Pointer to a WSProtocolDecoderAcurite_592TXR instance
|
||||||
|
* @return hash Hash sum
|
||||||
|
*/
|
||||||
|
uint8_t ws_protocol_decoder_acurite_592txr_get_hash_data(void* context);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serialize data WSProtocolDecoderAcurite_592TXR.
|
||||||
|
* @param context Pointer to a WSProtocolDecoderAcurite_592TXR instance
|
||||||
|
* @param flipper_format Pointer to a FlipperFormat instance
|
||||||
|
* @param preset The modulation on which the signal was received, SubGhzRadioPreset
|
||||||
|
* @return true On success
|
||||||
|
*/
|
||||||
|
bool ws_protocol_decoder_acurite_592txr_serialize(
|
||||||
|
void* context,
|
||||||
|
FlipperFormat* flipper_format,
|
||||||
|
SubGhzRadioPreset* preset);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deserialize data WSProtocolDecoderAcurite_592TXR.
|
||||||
|
* @param context Pointer to a WSProtocolDecoderAcurite_592TXR instance
|
||||||
|
* @param flipper_format Pointer to a FlipperFormat instance
|
||||||
|
* @return true On success
|
||||||
|
*/
|
||||||
|
bool ws_protocol_decoder_acurite_592txr_deserialize(void* context, FlipperFormat* flipper_format);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getting a textual representation of the received data.
|
||||||
|
* @param context Pointer to a WSProtocolDecoderAcurite_592TXR instance
|
||||||
|
* @param output Resulting text
|
||||||
|
*/
|
||||||
|
void ws_protocol_decoder_acurite_592txr_get_string(void* context, FuriString* output);
|
||||||
252
applications/plugins/weather_station/protocols/acurite_606tx.c
Normal file
@@ -0,0 +1,252 @@
|
|||||||
|
#include "acurite_606tx.h"
|
||||||
|
|
||||||
|
#define TAG "WSProtocolAcurite_606TX"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Help
|
||||||
|
* https://github.com/merbanan/rtl_433/blob/5bef4e43133ac4c0e2d18d36f87c52b4f9458453/src/devices/acurite.c#L1644
|
||||||
|
*
|
||||||
|
* 0000 1111 | 0011 0000 | 0101 1100 | 1110 0111
|
||||||
|
* iiii iiii | buuu tttt | tttt tttt | cccc cccc
|
||||||
|
* - i: identification; changes on battery switch
|
||||||
|
* - c: lfsr_digest8;
|
||||||
|
* - u: unknown;
|
||||||
|
* - b: battery low; flag to indicate low battery voltage
|
||||||
|
* - t: Temperature; in °C
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
static const SubGhzBlockConst ws_protocol_acurite_606tx_const = {
|
||||||
|
.te_short = 500,
|
||||||
|
.te_long = 2000,
|
||||||
|
.te_delta = 150,
|
||||||
|
.min_count_bit_for_found = 32,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct WSProtocolDecoderAcurite_606TX {
|
||||||
|
SubGhzProtocolDecoderBase base;
|
||||||
|
|
||||||
|
SubGhzBlockDecoder decoder;
|
||||||
|
WSBlockGeneric generic;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct WSProtocolEncoderAcurite_606TX {
|
||||||
|
SubGhzProtocolEncoderBase base;
|
||||||
|
|
||||||
|
SubGhzProtocolBlockEncoder encoder;
|
||||||
|
WSBlockGeneric generic;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
Acurite_606TXDecoderStepReset = 0,
|
||||||
|
Acurite_606TXDecoderStepSaveDuration,
|
||||||
|
Acurite_606TXDecoderStepCheckDuration,
|
||||||
|
} Acurite_606TXDecoderStep;
|
||||||
|
|
||||||
|
const SubGhzProtocolDecoder ws_protocol_acurite_606tx_decoder = {
|
||||||
|
.alloc = ws_protocol_decoder_acurite_606tx_alloc,
|
||||||
|
.free = ws_protocol_decoder_acurite_606tx_free,
|
||||||
|
|
||||||
|
.feed = ws_protocol_decoder_acurite_606tx_feed,
|
||||||
|
.reset = ws_protocol_decoder_acurite_606tx_reset,
|
||||||
|
|
||||||
|
.get_hash_data = ws_protocol_decoder_acurite_606tx_get_hash_data,
|
||||||
|
.serialize = ws_protocol_decoder_acurite_606tx_serialize,
|
||||||
|
.deserialize = ws_protocol_decoder_acurite_606tx_deserialize,
|
||||||
|
.get_string = ws_protocol_decoder_acurite_606tx_get_string,
|
||||||
|
};
|
||||||
|
|
||||||
|
const SubGhzProtocolEncoder ws_protocol_acurite_606tx_encoder = {
|
||||||
|
.alloc = NULL,
|
||||||
|
.free = NULL,
|
||||||
|
|
||||||
|
.deserialize = NULL,
|
||||||
|
.stop = NULL,
|
||||||
|
.yield = NULL,
|
||||||
|
};
|
||||||
|
|
||||||
|
const SubGhzProtocol ws_protocol_acurite_606tx = {
|
||||||
|
.name = WS_PROTOCOL_ACURITE_606TX_NAME,
|
||||||
|
.type = SubGhzProtocolWeatherStation,
|
||||||
|
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 |
|
||||||
|
SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable,
|
||||||
|
|
||||||
|
.decoder = &ws_protocol_acurite_606tx_decoder,
|
||||||
|
.encoder = &ws_protocol_acurite_606tx_encoder,
|
||||||
|
};
|
||||||
|
|
||||||
|
void* ws_protocol_decoder_acurite_606tx_alloc(SubGhzEnvironment* environment) {
|
||||||
|
UNUSED(environment);
|
||||||
|
WSProtocolDecoderAcurite_606TX* instance = malloc(sizeof(WSProtocolDecoderAcurite_606TX));
|
||||||
|
instance->base.protocol = &ws_protocol_acurite_606tx;
|
||||||
|
instance->generic.protocol_name = instance->base.protocol->name;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ws_protocol_decoder_acurite_606tx_free(void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
WSProtocolDecoderAcurite_606TX* instance = context;
|
||||||
|
free(instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ws_protocol_decoder_acurite_606tx_reset(void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
WSProtocolDecoderAcurite_606TX* instance = context;
|
||||||
|
instance->decoder.parser_step = Acurite_606TXDecoderStepReset;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ws_protocol_acurite_606tx_check(WSProtocolDecoderAcurite_606TX* instance) {
|
||||||
|
if(!instance->decoder.decode_data) return false;
|
||||||
|
uint8_t msg[] = {
|
||||||
|
instance->decoder.decode_data >> 24,
|
||||||
|
instance->decoder.decode_data >> 16,
|
||||||
|
instance->decoder.decode_data >> 8};
|
||||||
|
|
||||||
|
uint8_t crc = subghz_protocol_blocks_lfsr_digest8(msg, 3, 0x98, 0xF1);
|
||||||
|
return (crc == (instance->decoder.decode_data & 0xFF));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Analysis of received data
|
||||||
|
* @param instance Pointer to a WSBlockGeneric* instance
|
||||||
|
*/
|
||||||
|
static void ws_protocol_acurite_606tx_remote_controller(WSBlockGeneric* instance) {
|
||||||
|
instance->id = (instance->data >> 24) & 0xFF;
|
||||||
|
instance->battery_low = (instance->data >> 23) & 1;
|
||||||
|
|
||||||
|
instance->channel = WS_NO_CHANNEL;
|
||||||
|
|
||||||
|
if(!((instance->data >> 19) & 1)) {
|
||||||
|
instance->temp = (float)((instance->data >> 8) & 0x07FF) / 10.0f;
|
||||||
|
} else {
|
||||||
|
instance->temp = (float)((~(instance->data >> 8) & 0x07FF) + 1) / -10.0f;
|
||||||
|
}
|
||||||
|
instance->btn = WS_NO_BTN;
|
||||||
|
instance->humidity = WS_NO_HUMIDITY;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ws_protocol_decoder_acurite_606tx_feed(void* context, bool level, uint32_t duration) {
|
||||||
|
furi_assert(context);
|
||||||
|
WSProtocolDecoderAcurite_606TX* instance = context;
|
||||||
|
|
||||||
|
switch(instance->decoder.parser_step) {
|
||||||
|
case Acurite_606TXDecoderStepReset:
|
||||||
|
if((!level) && (DURATION_DIFF(duration, ws_protocol_acurite_606tx_const.te_short * 17) <
|
||||||
|
ws_protocol_acurite_606tx_const.te_delta * 8)) {
|
||||||
|
//Found syncPrefix
|
||||||
|
instance->decoder.parser_step = Acurite_606TXDecoderStepSaveDuration;
|
||||||
|
instance->decoder.decode_data = 0;
|
||||||
|
instance->decoder.decode_count_bit = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Acurite_606TXDecoderStepSaveDuration:
|
||||||
|
if(level) {
|
||||||
|
instance->decoder.te_last = duration;
|
||||||
|
instance->decoder.parser_step = Acurite_606TXDecoderStepCheckDuration;
|
||||||
|
} else {
|
||||||
|
instance->decoder.parser_step = Acurite_606TXDecoderStepReset;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Acurite_606TXDecoderStepCheckDuration:
|
||||||
|
if(!level) {
|
||||||
|
if((DURATION_DIFF(instance->decoder.te_last, ws_protocol_acurite_606tx_const.te_short) <
|
||||||
|
ws_protocol_acurite_606tx_const.te_delta) &&
|
||||||
|
(DURATION_DIFF(duration, ws_protocol_acurite_606tx_const.te_short) <
|
||||||
|
ws_protocol_acurite_606tx_const.te_delta)) {
|
||||||
|
//Found syncPostfix
|
||||||
|
instance->decoder.parser_step = Acurite_606TXDecoderStepReset;
|
||||||
|
if((instance->decoder.decode_count_bit ==
|
||||||
|
ws_protocol_acurite_606tx_const.min_count_bit_for_found) &&
|
||||||
|
ws_protocol_acurite_606tx_check(instance)) {
|
||||||
|
instance->generic.data = instance->decoder.decode_data;
|
||||||
|
instance->generic.data_count_bit = instance->decoder.decode_count_bit;
|
||||||
|
ws_protocol_acurite_606tx_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;
|
||||||
|
|
||||||
|
break;
|
||||||
|
} else if(
|
||||||
|
(DURATION_DIFF(
|
||||||
|
instance->decoder.te_last, ws_protocol_acurite_606tx_const.te_short) <
|
||||||
|
ws_protocol_acurite_606tx_const.te_delta) &&
|
||||||
|
(DURATION_DIFF(duration, ws_protocol_acurite_606tx_const.te_long) <
|
||||||
|
ws_protocol_acurite_606tx_const.te_delta * 2)) {
|
||||||
|
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
|
||||||
|
instance->decoder.parser_step = Acurite_606TXDecoderStepSaveDuration;
|
||||||
|
} else if(
|
||||||
|
(DURATION_DIFF(
|
||||||
|
instance->decoder.te_last, ws_protocol_acurite_606tx_const.te_short) <
|
||||||
|
ws_protocol_acurite_606tx_const.te_delta) &&
|
||||||
|
(DURATION_DIFF(duration, ws_protocol_acurite_606tx_const.te_long * 2) <
|
||||||
|
ws_protocol_acurite_606tx_const.te_delta * 4)) {
|
||||||
|
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
|
||||||
|
instance->decoder.parser_step = Acurite_606TXDecoderStepSaveDuration;
|
||||||
|
} else {
|
||||||
|
instance->decoder.parser_step = Acurite_606TXDecoderStepReset;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
instance->decoder.parser_step = Acurite_606TXDecoderStepReset;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t ws_protocol_decoder_acurite_606tx_get_hash_data(void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
WSProtocolDecoderAcurite_606TX* instance = context;
|
||||||
|
return subghz_protocol_blocks_get_hash_data(
|
||||||
|
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ws_protocol_decoder_acurite_606tx_serialize(
|
||||||
|
void* context,
|
||||||
|
FlipperFormat* flipper_format,
|
||||||
|
SubGhzRadioPreset* preset) {
|
||||||
|
furi_assert(context);
|
||||||
|
WSProtocolDecoderAcurite_606TX* instance = context;
|
||||||
|
return ws_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ws_protocol_decoder_acurite_606tx_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||||
|
furi_assert(context);
|
||||||
|
WSProtocolDecoderAcurite_606TX* instance = context;
|
||||||
|
bool ret = false;
|
||||||
|
do {
|
||||||
|
if(!ws_block_generic_deserialize(&instance->generic, flipper_format)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if(instance->generic.data_count_bit !=
|
||||||
|
ws_protocol_acurite_606tx_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_acurite_606tx_get_string(void* context, FuriString* output) {
|
||||||
|
furi_assert(context);
|
||||||
|
WSProtocolDecoderAcurite_606TX* 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_ACURITE_606TX_NAME "Acurite-606TX"
|
||||||
|
|
||||||
|
typedef struct WSProtocolDecoderAcurite_606TX WSProtocolDecoderAcurite_606TX;
|
||||||
|
typedef struct WSProtocolEncoderAcurite_606TX WSProtocolEncoderAcurite_606TX;
|
||||||
|
|
||||||
|
extern const SubGhzProtocolDecoder ws_protocol_acurite_606tx_decoder;
|
||||||
|
extern const SubGhzProtocolEncoder ws_protocol_acurite_606tx_encoder;
|
||||||
|
extern const SubGhzProtocol ws_protocol_acurite_606tx;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allocate WSProtocolDecoderAcurite_606TX.
|
||||||
|
* @param environment Pointer to a SubGhzEnvironment instance
|
||||||
|
* @return WSProtocolDecoderAcurite_606TX* pointer to a WSProtocolDecoderAcurite_606TX instance
|
||||||
|
*/
|
||||||
|
void* ws_protocol_decoder_acurite_606tx_alloc(SubGhzEnvironment* environment);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Free WSProtocolDecoderAcurite_606TX.
|
||||||
|
* @param context Pointer to a WSProtocolDecoderAcurite_606TX instance
|
||||||
|
*/
|
||||||
|
void ws_protocol_decoder_acurite_606tx_free(void* context);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset decoder WSProtocolDecoderAcurite_606TX.
|
||||||
|
* @param context Pointer to a WSProtocolDecoderAcurite_606TX instance
|
||||||
|
*/
|
||||||
|
void ws_protocol_decoder_acurite_606tx_reset(void* context);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a raw sequence of levels and durations received from the air.
|
||||||
|
* @param context Pointer to a WSProtocolDecoderAcurite_606TX instance
|
||||||
|
* @param level Signal level true-high false-low
|
||||||
|
* @param duration Duration of this level in, us
|
||||||
|
*/
|
||||||
|
void ws_protocol_decoder_acurite_606tx_feed(void* context, bool level, uint32_t duration);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getting the hash sum of the last randomly received parcel.
|
||||||
|
* @param context Pointer to a WSProtocolDecoderAcurite_606TX instance
|
||||||
|
* @return hash Hash sum
|
||||||
|
*/
|
||||||
|
uint8_t ws_protocol_decoder_acurite_606tx_get_hash_data(void* context);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serialize data WSProtocolDecoderAcurite_606TX.
|
||||||
|
* @param context Pointer to a WSProtocolDecoderAcurite_606TX instance
|
||||||
|
* @param flipper_format Pointer to a FlipperFormat instance
|
||||||
|
* @param preset The modulation on which the signal was received, SubGhzRadioPreset
|
||||||
|
* @return true On success
|
||||||
|
*/
|
||||||
|
bool ws_protocol_decoder_acurite_606tx_serialize(
|
||||||
|
void* context,
|
||||||
|
FlipperFormat* flipper_format,
|
||||||
|
SubGhzRadioPreset* preset);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deserialize data WSProtocolDecoderAcurite_606TX.
|
||||||
|
* @param context Pointer to a WSProtocolDecoderAcurite_606TX instance
|
||||||
|
* @param flipper_format Pointer to a FlipperFormat instance
|
||||||
|
* @return true On success
|
||||||
|
*/
|
||||||
|
bool ws_protocol_decoder_acurite_606tx_deserialize(void* context, FlipperFormat* flipper_format);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getting a textual representation of the received data.
|
||||||
|
* @param context Pointer to a WSProtocolDecoderAcurite_606TX instance
|
||||||
|
* @param output Resulting text
|
||||||
|
*/
|
||||||
|
void ws_protocol_decoder_acurite_606tx_get_string(void* context, FuriString* output);
|
||||||
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,
|
||||||
|
SubGhzRadioPreset* 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, SubGhzRadioPreset
|
||||||
|
* @return true On success
|
||||||
|
*/
|
||||||
|
bool ws_protocol_decoder_gt_wt_03_serialize(
|
||||||
|
void* context,
|
||||||
|
FlipperFormat* flipper_format,
|
||||||
|
SubGhzRadioPreset* 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);
|
||||||
297
applications/plugins/weather_station/protocols/infactory.c
Normal file
@@ -0,0 +1,297 @@
|
|||||||
|
#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->btn = WS_NO_BTN;
|
||||||
|
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,
|
||||||
|
SubGhzRadioPreset* 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, SubGhzRadioPreset
|
||||||
|
* @return true On success
|
||||||
|
*/
|
||||||
|
bool ws_protocol_decoder_infactory_serialize(
|
||||||
|
void* context,
|
||||||
|
FlipperFormat* flipper_format,
|
||||||
|
SubGhzRadioPreset* 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);
|
||||||
@@ -0,0 +1,298 @@
|
|||||||
|
#include "lacrosse_tx141thbv2.h"
|
||||||
|
|
||||||
|
#define TAG "WSProtocolLaCrosse_TX141THBv2"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Help
|
||||||
|
* https://github.com/merbanan/rtl_433/blob/7e83cfd27d14247b6c3c81732bfe4a4f9a974d30/src/devices/lacrosse_tx141x.c
|
||||||
|
*
|
||||||
|
* iiii iiii | bkcc tttt | tttt tttt | hhhh hhhh | cccc cccc | u
|
||||||
|
* - i: identification; changes on battery switch
|
||||||
|
* - c: lfsr_digest8_reflect;
|
||||||
|
* - u: unknown;
|
||||||
|
* - b: battery low; flag to indicate low battery voltage
|
||||||
|
* - h: Humidity;
|
||||||
|
* - t: Temperature; in °F as binary number with one decimal place + 50 °F offset
|
||||||
|
* - n: Channel; Channel number 1 - 3
|
||||||
|
*/
|
||||||
|
|
||||||
|
static const SubGhzBlockConst ws_protocol_lacrosse_tx141thbv2_const = {
|
||||||
|
.te_short = 250,
|
||||||
|
.te_long = 500,
|
||||||
|
.te_delta = 120,
|
||||||
|
.min_count_bit_for_found = 41,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct WSProtocolDecoderLaCrosse_TX141THBv2 {
|
||||||
|
SubGhzProtocolDecoderBase base;
|
||||||
|
|
||||||
|
SubGhzBlockDecoder decoder;
|
||||||
|
WSBlockGeneric generic;
|
||||||
|
|
||||||
|
uint16_t header_count;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct WSProtocolEncoderLaCrosse_TX141THBv2 {
|
||||||
|
SubGhzProtocolEncoderBase base;
|
||||||
|
|
||||||
|
SubGhzProtocolBlockEncoder encoder;
|
||||||
|
WSBlockGeneric generic;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
LaCrosse_TX141THBv2DecoderStepReset = 0,
|
||||||
|
LaCrosse_TX141THBv2DecoderStepCheckPreambule,
|
||||||
|
LaCrosse_TX141THBv2DecoderStepSaveDuration,
|
||||||
|
LaCrosse_TX141THBv2DecoderStepCheckDuration,
|
||||||
|
} LaCrosse_TX141THBv2DecoderStep;
|
||||||
|
|
||||||
|
const SubGhzProtocolDecoder ws_protocol_lacrosse_tx141thbv2_decoder = {
|
||||||
|
.alloc = ws_protocol_decoder_lacrosse_tx141thbv2_alloc,
|
||||||
|
.free = ws_protocol_decoder_lacrosse_tx141thbv2_free,
|
||||||
|
|
||||||
|
.feed = ws_protocol_decoder_lacrosse_tx141thbv2_feed,
|
||||||
|
.reset = ws_protocol_decoder_lacrosse_tx141thbv2_reset,
|
||||||
|
|
||||||
|
.get_hash_data = ws_protocol_decoder_lacrosse_tx141thbv2_get_hash_data,
|
||||||
|
.serialize = ws_protocol_decoder_lacrosse_tx141thbv2_serialize,
|
||||||
|
.deserialize = ws_protocol_decoder_lacrosse_tx141thbv2_deserialize,
|
||||||
|
.get_string = ws_protocol_decoder_lacrosse_tx141thbv2_get_string,
|
||||||
|
};
|
||||||
|
|
||||||
|
const SubGhzProtocolEncoder ws_protocol_lacrosse_tx141thbv2_encoder = {
|
||||||
|
.alloc = NULL,
|
||||||
|
.free = NULL,
|
||||||
|
|
||||||
|
.deserialize = NULL,
|
||||||
|
.stop = NULL,
|
||||||
|
.yield = NULL,
|
||||||
|
};
|
||||||
|
|
||||||
|
const SubGhzProtocol ws_protocol_lacrosse_tx141thbv2 = {
|
||||||
|
.name = WS_PROTOCOL_LACROSSE_TX141THBV2_NAME,
|
||||||
|
.type = SubGhzProtocolWeatherStation,
|
||||||
|
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 |
|
||||||
|
SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable,
|
||||||
|
|
||||||
|
.decoder = &ws_protocol_lacrosse_tx141thbv2_decoder,
|
||||||
|
.encoder = &ws_protocol_lacrosse_tx141thbv2_encoder,
|
||||||
|
};
|
||||||
|
|
||||||
|
void* ws_protocol_decoder_lacrosse_tx141thbv2_alloc(SubGhzEnvironment* environment) {
|
||||||
|
UNUSED(environment);
|
||||||
|
WSProtocolDecoderLaCrosse_TX141THBv2* instance =
|
||||||
|
malloc(sizeof(WSProtocolDecoderLaCrosse_TX141THBv2));
|
||||||
|
instance->base.protocol = &ws_protocol_lacrosse_tx141thbv2;
|
||||||
|
instance->generic.protocol_name = instance->base.protocol->name;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ws_protocol_decoder_lacrosse_tx141thbv2_free(void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
WSProtocolDecoderLaCrosse_TX141THBv2* instance = context;
|
||||||
|
free(instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ws_protocol_decoder_lacrosse_tx141thbv2_reset(void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
WSProtocolDecoderLaCrosse_TX141THBv2* instance = context;
|
||||||
|
instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepReset;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
ws_protocol_lacrosse_tx141thbv2_check_crc(WSProtocolDecoderLaCrosse_TX141THBv2* instance) {
|
||||||
|
if(!instance->decoder.decode_data) return false;
|
||||||
|
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 crc = subghz_protocol_blocks_lfsr_digest8_reflect(msg, 4, 0x31, 0xF4);
|
||||||
|
return (crc == ((instance->decoder.decode_data >> 1) & 0xFF));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Analysis of received data
|
||||||
|
* @param instance Pointer to a WSBlockGeneric* instance
|
||||||
|
*/
|
||||||
|
static void ws_protocol_lacrosse_tx141thbv2_remote_controller(WSBlockGeneric* instance) {
|
||||||
|
instance->id = instance->data >> 33;
|
||||||
|
instance->battery_low = (instance->data >> 32) & 1;
|
||||||
|
instance->btn = (instance->data >> 31) & 1;
|
||||||
|
instance->channel = ((instance->data >> 29) & 0x03) + 1;
|
||||||
|
instance->temp = ((float)((instance->data >> 17) & 0x0FFF) - 500.0f) / 10.0f;
|
||||||
|
instance->humidity = (instance->data >> 9) & 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ws_protocol_decoder_lacrosse_tx141thbv2_feed(void* context, bool level, uint32_t duration) {
|
||||||
|
furi_assert(context);
|
||||||
|
WSProtocolDecoderLaCrosse_TX141THBv2* instance = context;
|
||||||
|
|
||||||
|
switch(instance->decoder.parser_step) {
|
||||||
|
case LaCrosse_TX141THBv2DecoderStepReset:
|
||||||
|
if((level) &&
|
||||||
|
(DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_short * 3) <
|
||||||
|
ws_protocol_lacrosse_tx141thbv2_const.te_delta * 2)) {
|
||||||
|
instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepCheckPreambule;
|
||||||
|
instance->decoder.te_last = duration;
|
||||||
|
instance->header_count = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LaCrosse_TX141THBv2DecoderStepCheckPreambule:
|
||||||
|
if(level) {
|
||||||
|
instance->decoder.te_last = duration;
|
||||||
|
} else {
|
||||||
|
if((DURATION_DIFF(
|
||||||
|
instance->decoder.te_last,
|
||||||
|
ws_protocol_lacrosse_tx141thbv2_const.te_short * 3) <
|
||||||
|
ws_protocol_lacrosse_tx141thbv2_const.te_delta * 2) &&
|
||||||
|
(DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_short * 3) <
|
||||||
|
ws_protocol_lacrosse_tx141thbv2_const.te_delta * 2)) {
|
||||||
|
//Found preambule
|
||||||
|
instance->header_count++;
|
||||||
|
} else if(instance->header_count == 4) {
|
||||||
|
if((DURATION_DIFF(
|
||||||
|
instance->decoder.te_last,
|
||||||
|
ws_protocol_lacrosse_tx141thbv2_const.te_short) <
|
||||||
|
ws_protocol_lacrosse_tx141thbv2_const.te_delta) &&
|
||||||
|
(DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_long) <
|
||||||
|
ws_protocol_lacrosse_tx141thbv2_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 = LaCrosse_TX141THBv2DecoderStepSaveDuration;
|
||||||
|
} else if(
|
||||||
|
(DURATION_DIFF(
|
||||||
|
instance->decoder.te_last,
|
||||||
|
ws_protocol_lacrosse_tx141thbv2_const.te_long) <
|
||||||
|
ws_protocol_lacrosse_tx141thbv2_const.te_delta) &&
|
||||||
|
(DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_short) <
|
||||||
|
ws_protocol_lacrosse_tx141thbv2_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 = LaCrosse_TX141THBv2DecoderStepSaveDuration;
|
||||||
|
} else {
|
||||||
|
instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepReset;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepReset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LaCrosse_TX141THBv2DecoderStepSaveDuration:
|
||||||
|
if(level) {
|
||||||
|
instance->decoder.te_last = duration;
|
||||||
|
instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepCheckDuration;
|
||||||
|
} else {
|
||||||
|
instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepReset;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LaCrosse_TX141THBv2DecoderStepCheckDuration:
|
||||||
|
if(!level) {
|
||||||
|
if(((DURATION_DIFF(
|
||||||
|
instance->decoder.te_last,
|
||||||
|
ws_protocol_lacrosse_tx141thbv2_const.te_short * 3) <
|
||||||
|
ws_protocol_lacrosse_tx141thbv2_const.te_delta * 2) &&
|
||||||
|
(DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_short * 3) <
|
||||||
|
ws_protocol_lacrosse_tx141thbv2_const.te_delta * 2))) {
|
||||||
|
if((instance->decoder.decode_count_bit ==
|
||||||
|
ws_protocol_lacrosse_tx141thbv2_const.min_count_bit_for_found) &&
|
||||||
|
ws_protocol_lacrosse_tx141thbv2_check_crc(instance)) {
|
||||||
|
instance->generic.data = instance->decoder.decode_data;
|
||||||
|
instance->generic.data_count_bit = instance->decoder.decode_count_bit;
|
||||||
|
ws_protocol_lacrosse_tx141thbv2_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 = LaCrosse_TX141THBv2DecoderStepCheckPreambule;
|
||||||
|
break;
|
||||||
|
} else if(
|
||||||
|
(DURATION_DIFF(
|
||||||
|
instance->decoder.te_last, ws_protocol_lacrosse_tx141thbv2_const.te_short) <
|
||||||
|
ws_protocol_lacrosse_tx141thbv2_const.te_delta) &&
|
||||||
|
(DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_long) <
|
||||||
|
ws_protocol_lacrosse_tx141thbv2_const.te_delta)) {
|
||||||
|
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
|
||||||
|
instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepSaveDuration;
|
||||||
|
} else if(
|
||||||
|
(DURATION_DIFF(
|
||||||
|
instance->decoder.te_last, ws_protocol_lacrosse_tx141thbv2_const.te_long) <
|
||||||
|
ws_protocol_lacrosse_tx141thbv2_const.te_delta) &&
|
||||||
|
(DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_short) <
|
||||||
|
ws_protocol_lacrosse_tx141thbv2_const.te_delta)) {
|
||||||
|
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
|
||||||
|
instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepSaveDuration;
|
||||||
|
} else {
|
||||||
|
instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepReset;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepReset;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t ws_protocol_decoder_lacrosse_tx141thbv2_get_hash_data(void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
WSProtocolDecoderLaCrosse_TX141THBv2* instance = context;
|
||||||
|
return subghz_protocol_blocks_get_hash_data(
|
||||||
|
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ws_protocol_decoder_lacrosse_tx141thbv2_serialize(
|
||||||
|
void* context,
|
||||||
|
FlipperFormat* flipper_format,
|
||||||
|
SubGhzRadioPreset* preset) {
|
||||||
|
furi_assert(context);
|
||||||
|
WSProtocolDecoderLaCrosse_TX141THBv2* instance = context;
|
||||||
|
return ws_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ws_protocol_decoder_lacrosse_tx141thbv2_deserialize(
|
||||||
|
void* context,
|
||||||
|
FlipperFormat* flipper_format) {
|
||||||
|
furi_assert(context);
|
||||||
|
WSProtocolDecoderLaCrosse_TX141THBv2* instance = context;
|
||||||
|
bool ret = false;
|
||||||
|
do {
|
||||||
|
if(!ws_block_generic_deserialize(&instance->generic, flipper_format)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if(instance->generic.data_count_bit !=
|
||||||
|
ws_protocol_lacrosse_tx141thbv2_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_lacrosse_tx141thbv2_get_string(void* context, FuriString* output) {
|
||||||
|
furi_assert(context);
|
||||||
|
WSProtocolDecoderLaCrosse_TX141THBv2* 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,81 @@
|
|||||||
|
#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_LACROSSE_TX141THBV2_NAME "TX141THBv2"
|
||||||
|
|
||||||
|
typedef struct WSProtocolDecoderLaCrosse_TX141THBv2 WSProtocolDecoderLaCrosse_TX141THBv2;
|
||||||
|
typedef struct WSProtocolEncoderLaCrosse_TX141THBv2 WSProtocolEncoderLaCrosse_TX141THBv2;
|
||||||
|
|
||||||
|
extern const SubGhzProtocolDecoder ws_protocol_lacrosse_tx141thbv2_decoder;
|
||||||
|
extern const SubGhzProtocolEncoder ws_protocol_lacrosse_tx141thbv2_encoder;
|
||||||
|
extern const SubGhzProtocol ws_protocol_lacrosse_tx141thbv2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allocate WSProtocolDecoderLaCrosse_TX141THBv2.
|
||||||
|
* @param environment Pointer to a SubGhzEnvironment instance
|
||||||
|
* @return WSProtocolDecoderLaCrosse_TX141THBv2* pointer to a WSProtocolDecoderLaCrosse_TX141THBv2 instance
|
||||||
|
*/
|
||||||
|
void* ws_protocol_decoder_lacrosse_tx141thbv2_alloc(SubGhzEnvironment* environment);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Free WSProtocolDecoderLaCrosse_TX141THBv2.
|
||||||
|
* @param context Pointer to a WSProtocolDecoderLaCrosse_TX141THBv2 instance
|
||||||
|
*/
|
||||||
|
void ws_protocol_decoder_lacrosse_tx141thbv2_free(void* context);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset decoder WSProtocolDecoderLaCrosse_TX141THBv2.
|
||||||
|
* @param context Pointer to a WSProtocolDecoderLaCrosse_TX141THBv2 instance
|
||||||
|
*/
|
||||||
|
void ws_protocol_decoder_lacrosse_tx141thbv2_reset(void* context);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a raw sequence of levels and durations received from the air.
|
||||||
|
* @param context Pointer to a WSProtocolDecoderLaCrosse_TX141THBv2 instance
|
||||||
|
* @param level Signal level true-high false-low
|
||||||
|
* @param duration Duration of this level in, us
|
||||||
|
*/
|
||||||
|
void ws_protocol_decoder_lacrosse_tx141thbv2_feed(void* context, bool level, uint32_t duration);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getting the hash sum of the last randomly received parcel.
|
||||||
|
* @param context Pointer to a WSProtocolDecoderLaCrosse_TX141THBv2 instance
|
||||||
|
* @return hash Hash sum
|
||||||
|
*/
|
||||||
|
uint8_t ws_protocol_decoder_lacrosse_tx141thbv2_get_hash_data(void* context);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serialize data WSProtocolDecoderLaCrosse_TX141THBv2.
|
||||||
|
* @param context Pointer to a WSProtocolDecoderLaCrosse_TX141THBv2 instance
|
||||||
|
* @param flipper_format Pointer to a FlipperFormat instance
|
||||||
|
* @param preset The modulation on which the signal was received, SubGhzRadioPreset
|
||||||
|
* @return true On success
|
||||||
|
*/
|
||||||
|
bool ws_protocol_decoder_lacrosse_tx141thbv2_serialize(
|
||||||
|
void* context,
|
||||||
|
FlipperFormat* flipper_format,
|
||||||
|
SubGhzRadioPreset* preset);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deserialize data WSProtocolDecoderLaCrosse_TX141THBv2.
|
||||||
|
* @param context Pointer to a WSProtocolDecoderLaCrosse_TX141THBv2 instance
|
||||||
|
* @param flipper_format Pointer to a FlipperFormat instance
|
||||||
|
* @return true On success
|
||||||
|
*/
|
||||||
|
bool ws_protocol_decoder_lacrosse_tx141thbv2_deserialize(
|
||||||
|
void* context,
|
||||||
|
FlipperFormat* flipper_format);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getting a textual representation of the received data.
|
||||||
|
* @param context Pointer to a WSProtocolDecoderLaCrosse_TX141THBv2 instance
|
||||||
|
* @param output Resulting text
|
||||||
|
*/
|
||||||
|
void ws_protocol_decoder_lacrosse_tx141thbv2_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;
|
||||||
|
instance->btn = WS_NO_BTN;
|
||||||
|
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,
|
||||||
|
SubGhzRadioPreset* 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, SubGhzRadioPreset
|
||||||
|
* @return true On success
|
||||||
|
*/
|
||||||
|
bool ws_protocol_decoder_nexus_th_serialize(
|
||||||
|
void* context,
|
||||||
|
FlipperFormat* flipper_format,
|
||||||
|
SubGhzRadioPreset* 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);
|
||||||
@@ -1,14 +1,17 @@
|
|||||||
#include "oregon2.h"
|
#include "oregon2.h"
|
||||||
#include "../blocks/const.h"
|
|
||||||
#include "../blocks/decoder.h"
|
#include <lib/subghz/blocks/const.h>
|
||||||
#include "../blocks/generic.h"
|
#include <lib/subghz/blocks/decoder.h>
|
||||||
#include "../blocks/math.h"
|
#include <lib/subghz/blocks/encoder.h>
|
||||||
|
#include <lib/subghz/blocks/math.h>
|
||||||
|
#include "ws_generic.h"
|
||||||
|
|
||||||
#include <lib/toolbox/manchester_decoder.h>
|
#include <lib/toolbox/manchester_decoder.h>
|
||||||
#include <lib/flipper_format/flipper_format_i.h>
|
#include <lib/flipper_format/flipper_format_i.h>
|
||||||
|
|
||||||
#define TAG "SubGhzProtocolOregon2"
|
#define TAG "WSProtocolOregon2"
|
||||||
|
|
||||||
static const SubGhzBlockConst oregon2_const = {
|
static const SubGhzBlockConst ws_oregon2_const = {
|
||||||
.te_long = 1000,
|
.te_long = 1000,
|
||||||
.te_short = 500,
|
.te_short = 500,
|
||||||
.te_delta = 200,
|
.te_delta = 200,
|
||||||
@@ -16,7 +19,7 @@ static const SubGhzBlockConst oregon2_const = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
#define OREGON2_PREAMBLE_BITS 19
|
#define OREGON2_PREAMBLE_BITS 19
|
||||||
#define OREGON2_PREAMBLE_MASK ((1 << (OREGON2_PREAMBLE_BITS + 1)) - 1)
|
#define OREGON2_PREAMBLE_MASK 0b1111111111111111111
|
||||||
#define OREGON2_SENSOR_ID(d) (((d) >> 16) & 0xFFFF)
|
#define OREGON2_SENSOR_ID(d) (((d) >> 16) & 0xFFFF)
|
||||||
#define OREGON2_CHECKSUM_BITS 8
|
#define OREGON2_CHECKSUM_BITS 8
|
||||||
|
|
||||||
@@ -26,11 +29,11 @@ static const SubGhzBlockConst oregon2_const = {
|
|||||||
// bit indicating the low battery
|
// bit indicating the low battery
|
||||||
#define OREGON2_FLAG_BAT_LOW 0x4
|
#define OREGON2_FLAG_BAT_LOW 0x4
|
||||||
|
|
||||||
struct SubGhzProtocolDecoderOregon2 {
|
struct WSProtocolDecoderOregon2 {
|
||||||
SubGhzProtocolDecoderBase base;
|
SubGhzProtocolDecoderBase base;
|
||||||
|
|
||||||
SubGhzBlockDecoder decoder;
|
SubGhzBlockDecoder decoder;
|
||||||
SubGhzBlockGeneric generic;
|
WSBlockGeneric generic;
|
||||||
ManchesterState manchester_state;
|
ManchesterState manchester_state;
|
||||||
bool prev_bit;
|
bool prev_bit;
|
||||||
bool have_bit;
|
bool have_bit;
|
||||||
@@ -39,7 +42,7 @@ struct SubGhzProtocolDecoderOregon2 {
|
|||||||
uint32_t var_data;
|
uint32_t var_data;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct SubGhzProtocolDecoderOregon2 SubGhzProtocolDecoderOregon2;
|
typedef struct WSProtocolDecoderOregon2 WSProtocolDecoderOregon2;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
Oregon2DecoderStepReset = 0,
|
Oregon2DecoderStepReset = 0,
|
||||||
@@ -47,23 +50,29 @@ typedef enum {
|
|||||||
Oregon2DecoderStepVarData,
|
Oregon2DecoderStepVarData,
|
||||||
} Oregon2DecoderStep;
|
} Oregon2DecoderStep;
|
||||||
|
|
||||||
void* subghz_protocol_decoder_oregon2_alloc(SubGhzEnvironment* environment) {
|
void* ws_protocol_decoder_oregon2_alloc(SubGhzEnvironment* environment) {
|
||||||
UNUSED(environment);
|
UNUSED(environment);
|
||||||
SubGhzProtocolDecoderOregon2* instance = malloc(sizeof(SubGhzProtocolDecoderOregon2));
|
WSProtocolDecoderOregon2* instance = malloc(sizeof(WSProtocolDecoderOregon2));
|
||||||
instance->base.protocol = &subghz_protocol_oregon2;
|
instance->base.protocol = &ws_protocol_oregon2;
|
||||||
instance->generic.protocol_name = instance->base.protocol->name;
|
instance->generic.protocol_name = instance->base.protocol->name;
|
||||||
|
instance->generic.humidity = WS_NO_HUMIDITY;
|
||||||
|
instance->generic.temp = WS_NO_TEMPERATURE;
|
||||||
|
instance->generic.btn = WS_NO_BTN;
|
||||||
|
instance->generic.channel = WS_NO_CHANNEL;
|
||||||
|
instance->generic.battery_low = WS_NO_BATT;
|
||||||
|
instance->generic.id = WS_NO_ID;
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
void subghz_protocol_decoder_oregon2_free(void* context) {
|
void ws_protocol_decoder_oregon2_free(void* context) {
|
||||||
furi_assert(context);
|
furi_assert(context);
|
||||||
SubGhzProtocolDecoderOregon2* instance = context;
|
WSProtocolDecoderOregon2* instance = context;
|
||||||
free(instance);
|
free(instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
void subghz_protocol_decoder_oregon2_reset(void* context) {
|
void ws_protocol_decoder_oregon2_reset(void* context) {
|
||||||
furi_assert(context);
|
furi_assert(context);
|
||||||
SubGhzProtocolDecoderOregon2* instance = context;
|
WSProtocolDecoderOregon2* instance = context;
|
||||||
instance->decoder.parser_step = Oregon2DecoderStepReset;
|
instance->decoder.parser_step = Oregon2DecoderStepReset;
|
||||||
instance->decoder.decode_data = 0UL;
|
instance->decoder.decode_data = 0UL;
|
||||||
instance->decoder.decode_count_bit = 0;
|
instance->decoder.decode_count_bit = 0;
|
||||||
@@ -77,9 +86,9 @@ void subghz_protocol_decoder_oregon2_reset(void* context) {
|
|||||||
static ManchesterEvent level_and_duration_to_event(bool level, uint32_t duration) {
|
static ManchesterEvent level_and_duration_to_event(bool level, uint32_t duration) {
|
||||||
bool is_long = false;
|
bool is_long = false;
|
||||||
|
|
||||||
if(DURATION_DIFF(duration, oregon2_const.te_long) < oregon2_const.te_delta) {
|
if(DURATION_DIFF(duration, ws_oregon2_const.te_long) < ws_oregon2_const.te_delta) {
|
||||||
is_long = true;
|
is_long = true;
|
||||||
} else if(DURATION_DIFF(duration, oregon2_const.te_short) < oregon2_const.te_delta) {
|
} else if(DURATION_DIFF(duration, ws_oregon2_const.te_short) < ws_oregon2_const.te_delta) {
|
||||||
is_long = false;
|
is_long = false;
|
||||||
} else {
|
} else {
|
||||||
return ManchesterEventReset;
|
return ManchesterEventReset;
|
||||||
@@ -94,12 +103,52 @@ static ManchesterEvent level_and_duration_to_event(bool level, uint32_t duration
|
|||||||
// From sensor id code return amount of bits in variable section
|
// From sensor id code return amount of bits in variable section
|
||||||
static uint8_t oregon2_sensor_id_var_bits(uint16_t sensor_id) {
|
static uint8_t oregon2_sensor_id_var_bits(uint16_t sensor_id) {
|
||||||
if(sensor_id == 0xEC40) return 16;
|
if(sensor_id == 0xEC40) return 16;
|
||||||
|
if(sensor_id == 0x1D20) return 24;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void subghz_protocol_decoder_oregon2_feed(void* context, bool level, uint32_t duration) {
|
static void ws_oregon2_decode_const_data(WSBlockGeneric* ws_block) {
|
||||||
|
ws_block->id = OREGON2_SENSOR_ID(ws_block->data);
|
||||||
|
|
||||||
|
uint8_t ch_bits = (ws_block->data >> 12) & 0xF;
|
||||||
|
ws_block->channel = 1;
|
||||||
|
while(ch_bits > 1) {
|
||||||
|
ws_block->channel++;
|
||||||
|
ch_bits >>= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ws_block->battery_low = (ws_block->data & OREGON2_FLAG_BAT_LOW) ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t bcd_decode_short(uint32_t data) {
|
||||||
|
return (data & 0xF) * 10 + ((data >> 4) & 0xF);
|
||||||
|
}
|
||||||
|
|
||||||
|
static float ws_oregon2_decode_temp(uint32_t data) {
|
||||||
|
int32_t temp_val;
|
||||||
|
temp_val = bcd_decode_short(data >> 4);
|
||||||
|
temp_val *= 10;
|
||||||
|
temp_val += (data >> 12) & 0xF;
|
||||||
|
if(data & 0xF) temp_val = -temp_val;
|
||||||
|
return (float)temp_val / 10.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ws_oregon2_decode_var_data(WSBlockGeneric* ws_b, uint16_t sensor_id, uint32_t data) {
|
||||||
|
switch(sensor_id) {
|
||||||
|
case 0xEC40:
|
||||||
|
ws_b->temp = ws_oregon2_decode_temp(data);
|
||||||
|
ws_b->humidity = WS_NO_HUMIDITY;
|
||||||
|
break;
|
||||||
|
case 0x1D20:
|
||||||
|
ws_b->humidity = bcd_decode_short(data);
|
||||||
|
ws_b->temp = ws_oregon2_decode_temp(data >> 8);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ws_protocol_decoder_oregon2_feed(void* context, bool level, uint32_t duration) {
|
||||||
furi_assert(context);
|
furi_assert(context);
|
||||||
SubGhzProtocolDecoderOregon2* instance = context;
|
WSProtocolDecoderOregon2* instance = context;
|
||||||
// oregon v2.1 signal is inverted
|
// oregon v2.1 signal is inverted
|
||||||
ManchesterEvent event = level_and_duration_to_event(!level, duration);
|
ManchesterEvent event = level_and_duration_to_event(!level, duration);
|
||||||
bool data;
|
bool data;
|
||||||
@@ -118,7 +167,7 @@ void subghz_protocol_decoder_oregon2_feed(void* context, bool level, uint32_t du
|
|||||||
} else if(instance->prev_bit && !data) {
|
} else if(instance->prev_bit && !data) {
|
||||||
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
|
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
|
||||||
} else {
|
} else {
|
||||||
subghz_protocol_decoder_oregon2_reset(context);
|
ws_protocol_decoder_oregon2_reset(context);
|
||||||
}
|
}
|
||||||
instance->have_bit = false;
|
instance->have_bit = false;
|
||||||
} else {
|
} else {
|
||||||
@@ -151,6 +200,7 @@ void subghz_protocol_decoder_oregon2_feed(void* context, bool level, uint32_t du
|
|||||||
instance->generic.data = (instance->generic.data & 0x33333333) << 2 |
|
instance->generic.data = (instance->generic.data & 0x33333333) << 2 |
|
||||||
(instance->generic.data & 0xCCCCCCCC) >> 2;
|
(instance->generic.data & 0xCCCCCCCC) >> 2;
|
||||||
|
|
||||||
|
ws_oregon2_decode_const_data(&instance->generic);
|
||||||
instance->var_bits =
|
instance->var_bits =
|
||||||
oregon2_sensor_id_var_bits(OREGON2_SENSOR_ID(instance->generic.data));
|
oregon2_sensor_id_var_bits(OREGON2_SENSOR_ID(instance->generic.data));
|
||||||
|
|
||||||
@@ -175,6 +225,11 @@ void subghz_protocol_decoder_oregon2_feed(void* context, bool level, uint32_t du
|
|||||||
instance->var_data = (instance->var_data & 0x33333333) << 2 |
|
instance->var_data = (instance->var_data & 0x33333333) << 2 |
|
||||||
(instance->var_data & 0xCCCCCCCC) >> 2;
|
(instance->var_data & 0xCCCCCCCC) >> 2;
|
||||||
|
|
||||||
|
ws_oregon2_decode_var_data(
|
||||||
|
&instance->generic,
|
||||||
|
OREGON2_SENSOR_ID(instance->generic.data),
|
||||||
|
instance->var_data >> OREGON2_CHECKSUM_BITS);
|
||||||
|
|
||||||
instance->decoder.parser_step = Oregon2DecoderStepReset;
|
instance->decoder.parser_step = Oregon2DecoderStepReset;
|
||||||
if(instance->base.callback)
|
if(instance->base.callback)
|
||||||
instance->base.callback(&instance->base, instance->base.context);
|
instance->base.callback(&instance->base, instance->base.context);
|
||||||
@@ -183,20 +238,20 @@ void subghz_protocol_decoder_oregon2_feed(void* context, bool level, uint32_t du
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t subghz_protocol_decoder_oregon2_get_hash_data(void* context) {
|
uint8_t ws_protocol_decoder_oregon2_get_hash_data(void* context) {
|
||||||
furi_assert(context);
|
furi_assert(context);
|
||||||
SubGhzProtocolDecoderOregon2* instance = context;
|
WSProtocolDecoderOregon2* instance = context;
|
||||||
return subghz_protocol_blocks_get_hash_data(
|
return subghz_protocol_blocks_get_hash_data(
|
||||||
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
|
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool subghz_protocol_decoder_oregon2_serialize(
|
bool ws_protocol_decoder_oregon2_serialize(
|
||||||
void* context,
|
void* context,
|
||||||
FlipperFormat* flipper_format,
|
FlipperFormat* flipper_format,
|
||||||
SubGhzPresetDefinition* preset) {
|
SubGhzRadioPreset* preset) {
|
||||||
furi_assert(context);
|
furi_assert(context);
|
||||||
SubGhzProtocolDecoderOregon2* instance = context;
|
WSProtocolDecoderOregon2* instance = context;
|
||||||
if(!subghz_block_generic_serialize(&instance->generic, flipper_format, preset)) return false;
|
if(!ws_block_generic_serialize(&instance->generic, flipper_format, preset)) return false;
|
||||||
uint32_t temp = instance->var_bits;
|
uint32_t temp = instance->var_bits;
|
||||||
if(!flipper_format_write_uint32(flipper_format, "VarBits", &temp, 1)) {
|
if(!flipper_format_write_uint32(flipper_format, "VarBits", &temp, 1)) {
|
||||||
FURI_LOG_E(TAG, "Error adding VarBits");
|
FURI_LOG_E(TAG, "Error adding VarBits");
|
||||||
@@ -213,13 +268,13 @@ bool subghz_protocol_decoder_oregon2_serialize(
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool subghz_protocol_decoder_oregon2_deserialize(void* context, FlipperFormat* flipper_format) {
|
bool ws_protocol_decoder_oregon2_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||||
furi_assert(context);
|
furi_assert(context);
|
||||||
SubGhzProtocolDecoderOregon2* instance = context;
|
WSProtocolDecoderOregon2* instance = context;
|
||||||
bool ret = false;
|
bool ret = false;
|
||||||
uint32_t temp_data;
|
uint32_t temp_data;
|
||||||
do {
|
do {
|
||||||
if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) {
|
if(!ws_block_generic_deserialize(&instance->generic, flipper_format)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if(!flipper_format_read_uint32(flipper_format, "VarBits", &temp_data, 1)) {
|
if(!flipper_format_read_uint32(flipper_format, "VarBits", &temp_data, 1)) {
|
||||||
@@ -235,7 +290,7 @@ bool subghz_protocol_decoder_oregon2_deserialize(void* context, FlipperFormat* f
|
|||||||
FURI_LOG_E(TAG, "Missing VarData");
|
FURI_LOG_E(TAG, "Missing VarData");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if(instance->generic.data_count_bit != oregon2_const.min_count_bit_for_found) {
|
if(instance->generic.data_count_bit != ws_oregon2_const.min_count_bit_for_found) {
|
||||||
FURI_LOG_E(TAG, "Wrong number of bits in key: %d", instance->generic.data_count_bit);
|
FURI_LOG_E(TAG, "Wrong number of bits in key: %d", instance->generic.data_count_bit);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -244,22 +299,6 @@ bool subghz_protocol_decoder_oregon2_deserialize(void* context, FlipperFormat* f
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
// append string of the variable data
|
|
||||||
static void
|
|
||||||
oregon2_var_data_append_string(uint16_t sensor_id, uint32_t var_data, FuriString* output) {
|
|
||||||
uint32_t val;
|
|
||||||
|
|
||||||
if(sensor_id == 0xEC40) {
|
|
||||||
val = ((var_data >> 4) & 0xF) * 10 + ((var_data >> 8) & 0xF);
|
|
||||||
furi_string_cat_printf(
|
|
||||||
output,
|
|
||||||
"Temp: %s%ld.%ld C\r\n",
|
|
||||||
(var_data & 0xF) ? "-" : "+",
|
|
||||||
val,
|
|
||||||
(uint32_t)(var_data >> 12) & 0xF);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void oregon2_append_check_sum(uint32_t fix_data, uint32_t var_data, FuriString* output) {
|
static void oregon2_append_check_sum(uint32_t fix_data, uint32_t var_data, FuriString* output) {
|
||||||
uint8_t sum = fix_data & 0xF;
|
uint8_t sum = fix_data & 0xF;
|
||||||
uint8_t ref_sum = var_data & 0xFF;
|
uint8_t ref_sum = var_data & 0xFF;
|
||||||
@@ -279,45 +318,49 @@ static void oregon2_append_check_sum(uint32_t fix_data, uint32_t var_data, FuriS
|
|||||||
furi_string_cat_printf(output, "Sum err: 0x%hhX vs 0x%hhX", ref_sum, sum);
|
furi_string_cat_printf(output, "Sum err: 0x%hhX vs 0x%hhX", ref_sum, sum);
|
||||||
}
|
}
|
||||||
|
|
||||||
void subghz_protocol_decoder_oregon2_get_string(void* context, FuriString* output) {
|
void ws_protocol_decoder_oregon2_get_string(void* context, FuriString* output) {
|
||||||
furi_assert(context);
|
furi_assert(context);
|
||||||
SubGhzProtocolDecoderOregon2* instance = context;
|
WSProtocolDecoderOregon2* instance = context;
|
||||||
uint16_t sensor_id = OREGON2_SENSOR_ID(instance->generic.data);
|
|
||||||
furi_string_cat_printf(
|
furi_string_cat_printf(
|
||||||
output,
|
output,
|
||||||
"%s\r\n"
|
"%s\r\n"
|
||||||
"ID: 0x%04lX, ch: %ld%s, rc: 0x%02lX\r\n",
|
"ID: 0x%04lX, ch: %d, bat: %d, rc: 0x%02lX\r\n",
|
||||||
instance->generic.protocol_name,
|
instance->generic.protocol_name,
|
||||||
(uint32_t)sensor_id,
|
instance->generic.id,
|
||||||
(uint32_t)(instance->generic.data >> 12) & 0xF,
|
instance->generic.channel,
|
||||||
((instance->generic.data & OREGON2_FLAG_BAT_LOW) ? ", low bat" : ""),
|
instance->generic.battery_low,
|
||||||
(uint32_t)(instance->generic.data >> 4) & 0xFF);
|
(uint32_t)(instance->generic.data >> 4) & 0xFF);
|
||||||
|
|
||||||
if(instance->var_bits > 0) {
|
if(instance->var_bits > 0) {
|
||||||
oregon2_var_data_append_string(
|
furi_string_cat_printf(
|
||||||
sensor_id, instance->var_data >> OREGON2_CHECKSUM_BITS, output);
|
output,
|
||||||
|
"Temp:%d.%d C Hum:%d%%",
|
||||||
|
(int16_t)instance->generic.temp,
|
||||||
|
abs(
|
||||||
|
((int16_t)(instance->generic.temp * 10) -
|
||||||
|
(((int16_t)instance->generic.temp) * 10))),
|
||||||
|
instance->generic.humidity);
|
||||||
oregon2_append_check_sum((uint32_t)instance->generic.data, instance->var_data, output);
|
oregon2_append_check_sum((uint32_t)instance->generic.data, instance->var_data, output);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const SubGhzProtocolDecoder subghz_protocol_oregon2_decoder = {
|
const SubGhzProtocolDecoder ws_protocol_oregon2_decoder = {
|
||||||
.alloc = subghz_protocol_decoder_oregon2_alloc,
|
.alloc = ws_protocol_decoder_oregon2_alloc,
|
||||||
.free = subghz_protocol_decoder_oregon2_free,
|
.free = ws_protocol_decoder_oregon2_free,
|
||||||
|
|
||||||
.feed = subghz_protocol_decoder_oregon2_feed,
|
.feed = ws_protocol_decoder_oregon2_feed,
|
||||||
.reset = subghz_protocol_decoder_oregon2_reset,
|
.reset = ws_protocol_decoder_oregon2_reset,
|
||||||
|
|
||||||
.get_hash_data = subghz_protocol_decoder_oregon2_get_hash_data,
|
.get_hash_data = ws_protocol_decoder_oregon2_get_hash_data,
|
||||||
.serialize = subghz_protocol_decoder_oregon2_serialize,
|
.serialize = ws_protocol_decoder_oregon2_serialize,
|
||||||
.deserialize = subghz_protocol_decoder_oregon2_deserialize,
|
.deserialize = ws_protocol_decoder_oregon2_deserialize,
|
||||||
.get_string = subghz_protocol_decoder_oregon2_get_string,
|
.get_string = ws_protocol_decoder_oregon2_get_string,
|
||||||
};
|
};
|
||||||
|
|
||||||
const SubGhzProtocol subghz_protocol_oregon2 = {
|
const SubGhzProtocol ws_protocol_oregon2 = {
|
||||||
.name = SUBGHZ_PROTOCOL_OREGON2_NAME,
|
.name = WS_PROTOCOL_OREGON2_NAME,
|
||||||
.type = SubGhzProtocolTypeStatic,
|
.type = SubGhzProtocolWeatherStation,
|
||||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable |
|
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable,
|
||||||
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save,
|
|
||||||
|
|
||||||
.decoder = &subghz_protocol_oregon2_decoder,
|
.decoder = &ws_protocol_oregon2_decoder,
|
||||||
};
|
};
|
||||||
6
applications/plugins/weather_station/protocols/oregon2.h
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <lib/subghz/protocols/base.h>
|
||||||
|
|
||||||
|
#define WS_PROTOCOL_OREGON2_NAME "Oregon2"
|
||||||
|
extern const SubGhzProtocol ws_protocol_oregon2;
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
#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,
|
||||||
|
&ws_protocol_acurite_606tx,
|
||||||
|
&ws_protocol_lacrosse_tx141thbv2,
|
||||||
|
&ws_protocol_oregon2,
|
||||||
|
&ws_protocol_acurite_592txr,
|
||||||
|
};
|
||||||
|
|
||||||
|
const SubGhzProtocolRegistry weather_station_protocol_registry = {
|
||||||
|
.items = weather_station_protocol_registry_items,
|
||||||
|
.size = COUNT_OF(weather_station_protocol_registry_items)};
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "../weather_station_app_i.h"
|
||||||
|
|
||||||
|
#include "infactory.h"
|
||||||
|
#include "thermopro_tx4.h"
|
||||||
|
#include "nexus_th.h"
|
||||||
|
#include "gt_wt_03.h"
|
||||||
|
#include "acurite_606tx.h"
|
||||||
|
#include "lacrosse_tx141thbv2.h"
|
||||||
|
#include "oregon2.h"
|
||||||
|
#include "acurite_592txr.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,
|
||||||
|
SubGhzRadioPreset* 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, SubGhzRadioPreset
|
||||||
|
* @return true On success
|
||||||
|
*/
|
||||||
|
bool ws_protocol_decoder_thermopro_tx4_serialize(
|
||||||
|
void* context,
|
||||||
|
FlipperFormat* flipper_format,
|
||||||
|
SubGhzRadioPreset* 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,
|
||||||
|
SubGhzRadioPreset* preset) {
|
||||||
|
furi_assert(instance);
|
||||||
|
bool res = false;
|
||||||
|
FuriString* temp_str;
|
||||||
|
temp_str = furi_string_alloc();
|
||||||
|
do {
|
||||||
|
stream_clean(flipper_format_get_raw_stream(flipper_format));
|
||||||
|
if(!flipper_format_write_header_cstr(
|
||||||
|
flipper_format, 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;
|
||||||
|
}
|
||||||
68
applications/plugins/weather_station/protocols/ws_generic.h
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
#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
|
||||||
|
|
||||||
|
#define WS_NO_ID 0xFFFFFFFF
|
||||||
|
#define WS_NO_BATT 0xFF
|
||||||
|
#define WS_NO_HUMIDITY 0xFF
|
||||||
|
#define WS_NO_CHANNEL 0xFF
|
||||||
|
#define WS_NO_BTN 0xFF
|
||||||
|
#define WS_NO_TEMPERATURE -273.0f
|
||||||
|
|
||||||
|
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, SubGhzRadioPreset
|
||||||
|
* @return true On success
|
||||||
|
*/
|
||||||
|
bool ws_block_generic_serialize(
|
||||||
|
WSBlockGeneric* instance,
|
||||||
|
FlipperFormat* flipper_format,
|
||||||
|
SubGhzRadioPreset* 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);
|
||||||
|
}
|
||||||
@@ -0,0 +1,437 @@
|
|||||||
|
#include "weather_station_receiver.h"
|
||||||
|
#include "../weather_station_app_i.h"
|
||||||
|
#include "weather_station_icons.h"
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
#include <input/input.h>
|
||||||
|
#include <gui/elements.h>
|
||||||
|
#include <assets_icons.h>
|
||||||
|
#include <m-array.h>
|
||||||
|
|
||||||
|
#define FRAME_HEIGHT 12
|
||||||
|
#define MAX_LEN_PX 100
|
||||||
|
#define MENU_ITEMS 4u
|
||||||
|
#define UNLOCK_CNT 3
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
FuriString* item_str;
|
||||||
|
uint8_t type;
|
||||||
|
} WSReceiverMenuItem;
|
||||||
|
|
||||||
|
ARRAY_DEF(WSReceiverMenuItemArray, WSReceiverMenuItem, M_POD_OPLIST)
|
||||||
|
|
||||||
|
#define M_OPL_WSReceiverMenuItemArray_t() ARRAY_OPLIST(WSReceiverMenuItemArray, M_POD_OPLIST)
|
||||||
|
|
||||||
|
struct WSReceiverHistory {
|
||||||
|
WSReceiverMenuItemArray_t data;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct WSReceiverHistory WSReceiverHistory;
|
||||||
|
|
||||||
|
static const Icon* ReceiverItemIcons[] = {
|
||||||
|
[SubGhzProtocolTypeUnknown] = &I_Quest_7x8,
|
||||||
|
[SubGhzProtocolTypeStatic] = &I_Unlock_7x8,
|
||||||
|
[SubGhzProtocolTypeDynamic] = &I_Lock_7x8,
|
||||||
|
[SubGhzProtocolWeatherStation] = &I_station_icon,
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
WSReceiverBarShowDefault,
|
||||||
|
WSReceiverBarShowLock,
|
||||||
|
WSReceiverBarShowToUnlockPress,
|
||||||
|
WSReceiverBarShowUnlock,
|
||||||
|
} WSReceiverBarShow;
|
||||||
|
|
||||||
|
struct WSReceiver {
|
||||||
|
WSLock lock;
|
||||||
|
uint8_t lock_count;
|
||||||
|
FuriTimer* timer;
|
||||||
|
View* view;
|
||||||
|
WSReceiverCallback callback;
|
||||||
|
void* context;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
FuriString* frequency_str;
|
||||||
|
FuriString* preset_str;
|
||||||
|
FuriString* history_stat_str;
|
||||||
|
WSReceiverHistory* history;
|
||||||
|
uint16_t idx;
|
||||||
|
uint16_t list_offset;
|
||||||
|
uint16_t history_item;
|
||||||
|
WSReceiverBarShow bar_show;
|
||||||
|
} WSReceiverModel;
|
||||||
|
|
||||||
|
void ws_view_receiver_set_lock(WSReceiver* ws_receiver, WSLock lock) {
|
||||||
|
furi_assert(ws_receiver);
|
||||||
|
ws_receiver->lock_count = 0;
|
||||||
|
if(lock == WSLockOn) {
|
||||||
|
ws_receiver->lock = lock;
|
||||||
|
with_view_model(
|
||||||
|
ws_receiver->view,
|
||||||
|
WSReceiverModel * model,
|
||||||
|
{ model->bar_show = WSReceiverBarShowLock; },
|
||||||
|
true);
|
||||||
|
furi_timer_start(ws_receiver->timer, pdMS_TO_TICKS(1000));
|
||||||
|
} else {
|
||||||
|
with_view_model(
|
||||||
|
ws_receiver->view,
|
||||||
|
WSReceiverModel * model,
|
||||||
|
{ model->bar_show = WSReceiverBarShowDefault; },
|
||||||
|
true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ws_view_receiver_set_callback(
|
||||||
|
WSReceiver* ws_receiver,
|
||||||
|
WSReceiverCallback callback,
|
||||||
|
void* context) {
|
||||||
|
furi_assert(ws_receiver);
|
||||||
|
furi_assert(callback);
|
||||||
|
ws_receiver->callback = callback;
|
||||||
|
ws_receiver->context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ws_view_receiver_update_offset(WSReceiver* ws_receiver) {
|
||||||
|
furi_assert(ws_receiver);
|
||||||
|
|
||||||
|
with_view_model(
|
||||||
|
ws_receiver->view,
|
||||||
|
WSReceiverModel * model,
|
||||||
|
{
|
||||||
|
size_t history_item = model->history_item;
|
||||||
|
uint16_t bounds = history_item > 3 ? 2 : history_item;
|
||||||
|
|
||||||
|
if(history_item > 3 && model->idx >= (int16_t)(history_item - 1)) {
|
||||||
|
model->list_offset = model->idx - 3;
|
||||||
|
} else if(model->list_offset < model->idx - bounds) {
|
||||||
|
model->list_offset =
|
||||||
|
CLAMP(model->list_offset + 1, (int16_t)(history_item - bounds), 0);
|
||||||
|
} else if(model->list_offset > model->idx - bounds) {
|
||||||
|
model->list_offset = CLAMP(model->idx - 1, (int16_t)(history_item - bounds), 0);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ws_view_receiver_add_item_to_menu(WSReceiver* ws_receiver, const char* name, uint8_t type) {
|
||||||
|
furi_assert(ws_receiver);
|
||||||
|
with_view_model(
|
||||||
|
ws_receiver->view,
|
||||||
|
WSReceiverModel * model,
|
||||||
|
{
|
||||||
|
WSReceiverMenuItem* item_menu = WSReceiverMenuItemArray_push_raw(model->history->data);
|
||||||
|
item_menu->item_str = furi_string_alloc_set(name);
|
||||||
|
item_menu->type = type;
|
||||||
|
if((model->idx == model->history_item - 1)) {
|
||||||
|
model->history_item++;
|
||||||
|
model->idx++;
|
||||||
|
} else {
|
||||||
|
model->history_item++;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
true);
|
||||||
|
ws_view_receiver_update_offset(ws_receiver);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ws_view_receiver_add_data_statusbar(
|
||||||
|
WSReceiver* ws_receiver,
|
||||||
|
const char* frequency_str,
|
||||||
|
const char* preset_str,
|
||||||
|
const char* history_stat_str) {
|
||||||
|
furi_assert(ws_receiver);
|
||||||
|
with_view_model(
|
||||||
|
ws_receiver->view,
|
||||||
|
WSReceiverModel * model,
|
||||||
|
{
|
||||||
|
furi_string_set_str(model->frequency_str, frequency_str);
|
||||||
|
furi_string_set_str(model->preset_str, preset_str);
|
||||||
|
furi_string_set_str(model->history_stat_str, history_stat_str);
|
||||||
|
},
|
||||||
|
true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ws_view_receiver_draw_frame(Canvas* canvas, uint16_t idx, bool scrollbar) {
|
||||||
|
canvas_set_color(canvas, ColorBlack);
|
||||||
|
canvas_draw_box(canvas, 0, 0 + idx * FRAME_HEIGHT, scrollbar ? 122 : 127, FRAME_HEIGHT);
|
||||||
|
|
||||||
|
canvas_set_color(canvas, ColorWhite);
|
||||||
|
canvas_draw_dot(canvas, 0, 0 + idx * FRAME_HEIGHT);
|
||||||
|
canvas_draw_dot(canvas, 1, 0 + idx * FRAME_HEIGHT);
|
||||||
|
canvas_draw_dot(canvas, 0, (0 + idx * FRAME_HEIGHT) + 1);
|
||||||
|
|
||||||
|
canvas_draw_dot(canvas, 0, (0 + idx * FRAME_HEIGHT) + 11);
|
||||||
|
canvas_draw_dot(canvas, scrollbar ? 121 : 126, 0 + idx * FRAME_HEIGHT);
|
||||||
|
canvas_draw_dot(canvas, scrollbar ? 121 : 126, (0 + idx * FRAME_HEIGHT) + 11);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ws_view_receiver_draw(Canvas* canvas, WSReceiverModel* model) {
|
||||||
|
canvas_clear(canvas);
|
||||||
|
canvas_set_color(canvas, ColorBlack);
|
||||||
|
canvas_set_font(canvas, FontSecondary);
|
||||||
|
|
||||||
|
elements_button_left(canvas, "Config");
|
||||||
|
canvas_draw_line(canvas, 46, 51, 125, 51);
|
||||||
|
|
||||||
|
bool scrollbar = model->history_item > 4;
|
||||||
|
FuriString* str_buff;
|
||||||
|
str_buff = furi_string_alloc();
|
||||||
|
|
||||||
|
WSReceiverMenuItem* item_menu;
|
||||||
|
|
||||||
|
for(size_t i = 0; i < MIN(model->history_item, MENU_ITEMS); ++i) {
|
||||||
|
size_t idx = CLAMP((uint16_t)(i + model->list_offset), model->history_item, 0);
|
||||||
|
item_menu = WSReceiverMenuItemArray_get(model->history->data, idx);
|
||||||
|
furi_string_set(str_buff, item_menu->item_str);
|
||||||
|
elements_string_fit_width(canvas, str_buff, scrollbar ? MAX_LEN_PX - 6 : MAX_LEN_PX);
|
||||||
|
if(model->idx == idx) {
|
||||||
|
ws_view_receiver_draw_frame(canvas, i, scrollbar);
|
||||||
|
} else {
|
||||||
|
canvas_set_color(canvas, ColorBlack);
|
||||||
|
}
|
||||||
|
canvas_draw_icon(canvas, 4, 2 + i * FRAME_HEIGHT, ReceiverItemIcons[item_menu->type]);
|
||||||
|
canvas_draw_str(canvas, 15, 9 + i * FRAME_HEIGHT, furi_string_get_cstr(str_buff));
|
||||||
|
furi_string_reset(str_buff);
|
||||||
|
}
|
||||||
|
if(scrollbar) {
|
||||||
|
elements_scrollbar_pos(canvas, 128, 0, 49, model->idx, model->history_item);
|
||||||
|
}
|
||||||
|
furi_string_free(str_buff);
|
||||||
|
|
||||||
|
canvas_set_color(canvas, ColorBlack);
|
||||||
|
|
||||||
|
if(model->history_item == 0) {
|
||||||
|
canvas_draw_icon(canvas, 0, 0, &I_Scanning_123x52);
|
||||||
|
canvas_set_font(canvas, FontPrimary);
|
||||||
|
canvas_draw_str(canvas, 63, 46, "Scanning...");
|
||||||
|
canvas_draw_line(canvas, 46, 51, 125, 51);
|
||||||
|
canvas_set_font(canvas, FontSecondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(model->bar_show) {
|
||||||
|
case WSReceiverBarShowLock:
|
||||||
|
canvas_draw_icon(canvas, 64, 55, &I_Lock_7x8);
|
||||||
|
canvas_draw_str(canvas, 74, 62, "Locked");
|
||||||
|
break;
|
||||||
|
case WSReceiverBarShowToUnlockPress:
|
||||||
|
canvas_draw_str(canvas, 44, 62, furi_string_get_cstr(model->frequency_str));
|
||||||
|
canvas_draw_str(canvas, 79, 62, furi_string_get_cstr(model->preset_str));
|
||||||
|
canvas_draw_str(canvas, 96, 62, furi_string_get_cstr(model->history_stat_str));
|
||||||
|
canvas_set_font(canvas, FontSecondary);
|
||||||
|
elements_bold_rounded_frame(canvas, 14, 8, 99, 48);
|
||||||
|
elements_multiline_text(canvas, 65, 26, "To unlock\npress:");
|
||||||
|
canvas_draw_icon(canvas, 65, 42, &I_Pin_back_arrow_10x8);
|
||||||
|
canvas_draw_icon(canvas, 80, 42, &I_Pin_back_arrow_10x8);
|
||||||
|
canvas_draw_icon(canvas, 95, 42, &I_Pin_back_arrow_10x8);
|
||||||
|
canvas_draw_icon(canvas, 16, 13, &I_WarningDolphin_45x42);
|
||||||
|
canvas_draw_dot(canvas, 17, 61);
|
||||||
|
break;
|
||||||
|
case WSReceiverBarShowUnlock:
|
||||||
|
canvas_draw_icon(canvas, 64, 55, &I_Unlock_7x8);
|
||||||
|
canvas_draw_str(canvas, 74, 62, "Unlocked");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
canvas_draw_str(canvas, 44, 62, furi_string_get_cstr(model->frequency_str));
|
||||||
|
canvas_draw_str(canvas, 79, 62, furi_string_get_cstr(model->preset_str));
|
||||||
|
canvas_draw_str(canvas, 96, 62, furi_string_get_cstr(model->history_stat_str));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ws_view_receiver_timer_callback(void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
WSReceiver* ws_receiver = context;
|
||||||
|
with_view_model(
|
||||||
|
ws_receiver->view,
|
||||||
|
WSReceiverModel * model,
|
||||||
|
{ model->bar_show = WSReceiverBarShowDefault; },
|
||||||
|
true);
|
||||||
|
if(ws_receiver->lock_count < UNLOCK_CNT) {
|
||||||
|
ws_receiver->callback(WSCustomEventViewReceiverOffDisplay, ws_receiver->context);
|
||||||
|
} else {
|
||||||
|
ws_receiver->lock = WSLockOff;
|
||||||
|
ws_receiver->callback(WSCustomEventViewReceiverUnlock, ws_receiver->context);
|
||||||
|
}
|
||||||
|
ws_receiver->lock_count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ws_view_receiver_input(InputEvent* event, void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
WSReceiver* ws_receiver = context;
|
||||||
|
|
||||||
|
if(ws_receiver->lock == WSLockOn) {
|
||||||
|
with_view_model(
|
||||||
|
ws_receiver->view,
|
||||||
|
WSReceiverModel * model,
|
||||||
|
{ model->bar_show = WSReceiverBarShowToUnlockPress; },
|
||||||
|
true);
|
||||||
|
if(ws_receiver->lock_count == 0) {
|
||||||
|
furi_timer_start(ws_receiver->timer, pdMS_TO_TICKS(1000));
|
||||||
|
}
|
||||||
|
if(event->key == InputKeyBack && event->type == InputTypeShort) {
|
||||||
|
ws_receiver->lock_count++;
|
||||||
|
}
|
||||||
|
if(ws_receiver->lock_count >= UNLOCK_CNT) {
|
||||||
|
ws_receiver->callback(WSCustomEventViewReceiverUnlock, ws_receiver->context);
|
||||||
|
with_view_model(
|
||||||
|
ws_receiver->view,
|
||||||
|
WSReceiverModel * model,
|
||||||
|
{ model->bar_show = WSReceiverBarShowUnlock; },
|
||||||
|
true);
|
||||||
|
ws_receiver->lock = WSLockOff;
|
||||||
|
furi_timer_start(ws_receiver->timer, pdMS_TO_TICKS(650));
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(event->key == InputKeyBack && event->type == InputTypeShort) {
|
||||||
|
ws_receiver->callback(WSCustomEventViewReceiverBack, ws_receiver->context);
|
||||||
|
} else if(
|
||||||
|
event->key == InputKeyUp &&
|
||||||
|
(event->type == InputTypeShort || event->type == InputTypeRepeat)) {
|
||||||
|
with_view_model(
|
||||||
|
ws_receiver->view,
|
||||||
|
WSReceiverModel * model,
|
||||||
|
{
|
||||||
|
if(model->idx != 0) model->idx--;
|
||||||
|
},
|
||||||
|
true);
|
||||||
|
} else if(
|
||||||
|
event->key == InputKeyDown &&
|
||||||
|
(event->type == InputTypeShort || event->type == InputTypeRepeat)) {
|
||||||
|
with_view_model(
|
||||||
|
ws_receiver->view,
|
||||||
|
WSReceiverModel * model,
|
||||||
|
{
|
||||||
|
if(model->idx != model->history_item - 1) model->idx++;
|
||||||
|
},
|
||||||
|
true);
|
||||||
|
} else if(event->key == InputKeyLeft && event->type == InputTypeShort) {
|
||||||
|
ws_receiver->callback(WSCustomEventViewReceiverConfig, ws_receiver->context);
|
||||||
|
} else if(event->key == InputKeyOk && event->type == InputTypeShort) {
|
||||||
|
with_view_model(
|
||||||
|
ws_receiver->view,
|
||||||
|
WSReceiverModel * model,
|
||||||
|
{
|
||||||
|
if(model->history_item != 0) {
|
||||||
|
ws_receiver->callback(WSCustomEventViewReceiverOK, ws_receiver->context);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
false);
|
||||||
|
}
|
||||||
|
|
||||||
|
ws_view_receiver_update_offset(ws_receiver);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ws_view_receiver_enter(void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ws_view_receiver_exit(void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
WSReceiver* ws_receiver = context;
|
||||||
|
with_view_model(
|
||||||
|
ws_receiver->view,
|
||||||
|
WSReceiverModel * model,
|
||||||
|
{
|
||||||
|
furi_string_reset(model->frequency_str);
|
||||||
|
furi_string_reset(model->preset_str);
|
||||||
|
furi_string_reset(model->history_stat_str);
|
||||||
|
for
|
||||||
|
M_EACH(item_menu, model->history->data, WSReceiverMenuItemArray_t) {
|
||||||
|
furi_string_free(item_menu->item_str);
|
||||||
|
item_menu->type = 0;
|
||||||
|
}
|
||||||
|
WSReceiverMenuItemArray_reset(model->history->data);
|
||||||
|
model->idx = 0;
|
||||||
|
model->list_offset = 0;
|
||||||
|
model->history_item = 0;
|
||||||
|
},
|
||||||
|
false);
|
||||||
|
furi_timer_stop(ws_receiver->timer);
|
||||||
|
}
|
||||||
|
|
||||||
|
WSReceiver* ws_view_receiver_alloc() {
|
||||||
|
WSReceiver* ws_receiver = malloc(sizeof(WSReceiver));
|
||||||
|
|
||||||
|
// View allocation and configuration
|
||||||
|
ws_receiver->view = view_alloc();
|
||||||
|
|
||||||
|
ws_receiver->lock = WSLockOff;
|
||||||
|
ws_receiver->lock_count = 0;
|
||||||
|
view_allocate_model(ws_receiver->view, ViewModelTypeLocking, sizeof(WSReceiverModel));
|
||||||
|
view_set_context(ws_receiver->view, ws_receiver);
|
||||||
|
view_set_draw_callback(ws_receiver->view, (ViewDrawCallback)ws_view_receiver_draw);
|
||||||
|
view_set_input_callback(ws_receiver->view, ws_view_receiver_input);
|
||||||
|
view_set_enter_callback(ws_receiver->view, ws_view_receiver_enter);
|
||||||
|
view_set_exit_callback(ws_receiver->view, ws_view_receiver_exit);
|
||||||
|
|
||||||
|
with_view_model(
|
||||||
|
ws_receiver->view,
|
||||||
|
WSReceiverModel * model,
|
||||||
|
{
|
||||||
|
model->frequency_str = furi_string_alloc();
|
||||||
|
model->preset_str = furi_string_alloc();
|
||||||
|
model->history_stat_str = furi_string_alloc();
|
||||||
|
model->bar_show = WSReceiverBarShowDefault;
|
||||||
|
model->history = malloc(sizeof(WSReceiverHistory));
|
||||||
|
WSReceiverMenuItemArray_init(model->history->data);
|
||||||
|
},
|
||||||
|
true);
|
||||||
|
ws_receiver->timer =
|
||||||
|
furi_timer_alloc(ws_view_receiver_timer_callback, FuriTimerTypeOnce, ws_receiver);
|
||||||
|
return ws_receiver;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ws_view_receiver_free(WSReceiver* ws_receiver) {
|
||||||
|
furi_assert(ws_receiver);
|
||||||
|
|
||||||
|
with_view_model(
|
||||||
|
ws_receiver->view,
|
||||||
|
WSReceiverModel * model,
|
||||||
|
{
|
||||||
|
furi_string_free(model->frequency_str);
|
||||||
|
furi_string_free(model->preset_str);
|
||||||
|
furi_string_free(model->history_stat_str);
|
||||||
|
for
|
||||||
|
M_EACH(item_menu, model->history->data, WSReceiverMenuItemArray_t) {
|
||||||
|
furi_string_free(item_menu->item_str);
|
||||||
|
item_menu->type = 0;
|
||||||
|
}
|
||||||
|
WSReceiverMenuItemArray_clear(model->history->data);
|
||||||
|
free(model->history);
|
||||||
|
},
|
||||||
|
false);
|
||||||
|
furi_timer_free(ws_receiver->timer);
|
||||||
|
view_free(ws_receiver->view);
|
||||||
|
free(ws_receiver);
|
||||||
|
}
|
||||||
|
|
||||||
|
View* ws_view_receiver_get_view(WSReceiver* ws_receiver) {
|
||||||
|
furi_assert(ws_receiver);
|
||||||
|
return ws_receiver->view;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t ws_view_receiver_get_idx_menu(WSReceiver* ws_receiver) {
|
||||||
|
furi_assert(ws_receiver);
|
||||||
|
uint32_t idx = 0;
|
||||||
|
with_view_model(
|
||||||
|
ws_receiver->view, WSReceiverModel * model, { idx = model->idx; }, false);
|
||||||
|
return idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ws_view_receiver_set_idx_menu(WSReceiver* ws_receiver, uint16_t idx) {
|
||||||
|
furi_assert(ws_receiver);
|
||||||
|
with_view_model(
|
||||||
|
ws_receiver->view,
|
||||||
|
WSReceiverModel * model,
|
||||||
|
{
|
||||||
|
model->idx = idx;
|
||||||
|
if(model->idx > 2) model->list_offset = idx - 2;
|
||||||
|
},
|
||||||
|
true);
|
||||||
|
ws_view_receiver_update_offset(ws_receiver);
|
||||||
|
}
|
||||||