Merge branch 'v40-merge' into badusb-ble

This commit is contained in:
yocvito
2023-01-26 00:22:28 +01:00
1033 changed files with 5889 additions and 2061 deletions

55
.github/workflows/sonarqube.yaml vendored Normal file
View File

@@ -0,0 +1,55 @@
name: SonarCloud
on:
workflow_dispatch:
pull_request:
types: [opened, synchronize, reopened]
jobs:
build:
name: Build and analyze
runs-on: ubuntu-latest
env:
SONAR_SCANNER_VERSION: 4.7.0.2747
SONAR_SERVER_URL: "https://sonarcloud.io"
BUILD_WRAPPER_OUT_DIR: "$HOME/.sonar/build_wrapper_output" # Directory where build-wrapper output will be placed
FBT_NO_SYNC: "true"
TARGETS: f7
DEFAULT_TARGET: f7
steps:
- name: 'Decontaminate previous build leftovers'
run: |
if [ -d .git ]; then
git submodule status || git checkout "$(git rev-list --max-parents=0 HEAD | tail -n 1)"
fi
- uses: actions/checkout@v2
with:
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
submodules: 'recursive'
- name: Set up JDK 11
uses: actions/setup-java@v1
with:
java-version: 11
- name: Download and set up sonar-scanner
env:
SONAR_SCANNER_DOWNLOAD_URL: https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-${{ env.SONAR_SCANNER_VERSION }}-linux.zip
run: |
mkdir -p $HOME/.sonar
curl -sSLo $HOME/.sonar/sonar-scanner.zip ${{ env.SONAR_SCANNER_DOWNLOAD_URL }}
unzip -o $HOME/.sonar/sonar-scanner.zip -d $HOME/.sonar/
echo "$HOME/.sonar/sonar-scanner-${{ env.SONAR_SCANNER_VERSION }}-linux/bin" >> $GITHUB_PATH
- name: Download and set up build-wrapper
env:
BUILD_WRAPPER_DOWNLOAD_URL: ${{ env.SONAR_SERVER_URL }}/static/cpp/build-wrapper-linux-x86.zip
run: |
curl -sSLo $HOME/.sonar/build-wrapper-linux-x86.zip ${{ env.BUILD_WRAPPER_DOWNLOAD_URL }}
unzip -o $HOME/.sonar/build-wrapper-linux-x86.zip -d $HOME/.sonar/
echo "$HOME/.sonar/build-wrapper-linux-x86" >> $GITHUB_PATH
- name: Run build-wrapper
run: |
mkdir $HOME/.sonar/build_wrapper_output
build-wrapper-linux-x86-64 --out-dir ${{ env.BUILD_WRAPPER_OUT_DIR }} ./sonar-build
- name: Run sonar-scanner
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: |
sonar-scanner --define sonar.host.url="${{ env.SONAR_SERVER_URL }}" --define sonar.cfamily.build-wrapper-output="${{ env.BUILD_WRAPPER_OUT_DIR }}"

2
.gitignore vendored
View File

@@ -67,6 +67,8 @@ PVS-Studio.log
# Automate files, etc # Automate files, etc
automate.py automate.py
deployments/ deployments/
assets/dolphin/custom/
assets/resources/dolphin_custom/
fbt_options.py fbt_options.py
commitnotes.md commitnotes.md
lib/STM32CubeWB lib/STM32CubeWB

View File

@@ -1 +1 @@
--rules-config .pvsconfig -e lib/fatfs -e lib/fnv1a-hash -e lib/FreeRTOS-Kernel -e lib/heatshrink -e lib/libusb_stm32 -e lib/littlefs -e lib/mbedtls -e lib/micro-ecc -e lib/microtar -e lib/mlib -e lib/qrcode -e lib/ST25RFAL002 -e lib/STM32CubeWB -e lib/u8g2 -e lib/nanopb -e */arm-none-eabi/* -e applications/plugins/dap_link/lib/free-dap --ignore-ccache -C gccarm --rules-config .pvsconfig -e lib/fatfs -e lib/fnv1a-hash -e lib/FreeRTOS-Kernel -e lib/heatshrink -e lib/libusb_stm32 -e lib/littlefs -e lib/mbedtls -e lib/micro-ecc -e lib/microtar -e lib/mlib -e lib/qrcode -e lib/ST25RFAL002 -e lib/STM32CubeWB -e lib/u8g2 -e lib/nanopb -e */arm-none-eabi/* -e applications/plugins/dap_link/lib/free-dap

View File

@@ -105,6 +105,12 @@
"type": "shell", "type": "shell",
"command": "./fbt COMPACT=1 DEBUG=0 FORCE=1 flash_usb_full" "command": "./fbt COMPACT=1 DEBUG=0 FORCE=1 flash_usb_full"
}, },
{
"label": "[Debug] Create PVS-Studio report",
"group": "build",
"type": "shell",
"command": "./fbt firmware_pvs"
},
{ {
"label": "[Debug] Build FAPs", "label": "[Debug] Build FAPs",
"group": "build", "group": "build",

View File

@@ -4,7 +4,7 @@
<img src="https://user-images.githubusercontent.com/55334727/210267703-bf9a4181-30a6-4735-a288-431d5d2d66db.png"> <img src="https://user-images.githubusercontent.com/55334727/210267703-bf9a4181-30a6-4735-a288-431d5d2d66db.png">
</p> </p>
[Intro](https://github.com/ClaraCrazy/Flipper-Xtreme#What-makes-it-special) | [Animations](https://github.com/ClaraCrazy/Flipper-Xtreme#Animations) | [Docs](https://github.com/ClaraCrazy/Flipper-Xtreme/wiki) | [Changelog](https://github.com/ClaraCrazy/Flipper-Xtreme#list-of-changes) | [Known bugs](https://github.com/ClaraCrazy/Flipper-Xtreme#Known-bugs) | [Install](https://github.com/ClaraCrazy/Flipper-Xtreme#Install) | [Build](https://github.com/ClaraCrazy/Flipper-Xtreme#build-it-yourself) | [Discord](https://discord.gg/flipper-xtreme) [Intro](https://github.com/ClaraCrazy/Flipper-Xtreme#What-makes-it-special) | [Animations](https://github.com/ClaraCrazy/Flipper-Xtreme#Animations--Asset-Packs) | [Docs](https://github.com/ClaraCrazy/Flipper-Xtreme/wiki) | [Changelog](https://github.com/ClaraCrazy/Flipper-Xtreme#list-of-changes) | [Known bugs](https://github.com/ClaraCrazy/Flipper-Xtreme#Known-bugs) | [Install](https://github.com/ClaraCrazy/Flipper-Xtreme#Install) | [Build](https://github.com/ClaraCrazy/Flipper-Xtreme#build-it-yourself) | [Discord](https://discord.gg/flipper-xtreme)
----- -----
This firmware is a complete overhaul of the [Official Firmware](https://github.com/flipperdevices/flipperzero-firmware), it also features some of the badly implemented ideas from RogueMaster, and lots of awesome code-bits from [Unleashed](https://github.com/DarkFlippers/unleashed-firmware). This firmware is a complete overhaul of the [Official Firmware](https://github.com/flipperdevices/flipperzero-firmware), it also features some of the badly implemented ideas from RogueMaster, and lots of awesome code-bits from [Unleashed](https://github.com/DarkFlippers/unleashed-firmware).
@@ -28,11 +28,33 @@ The goal of this Firmware is to regularly bring out amazing updates based on wha
----- -----
<br> <br>
<h2 align="center">Animations:</h2> <h2 align="center">Animations / Asset Packs:</h2>
This firmware contains NSFW animations and uses these in stock (NSFW) mode. We created our own, new & improved Animation / Asset system, that we can finally reveal. It lets you to create and cycle through your own `Asset Packs` with only a few button presses, allowing you to easily load custom Animations and Icons like never before.
The animations are tied to the level system. Each level you reach, unlocks a new animation. The higher your level, the more lewd it will become. Rumors have it, I'm to be found in at least one of those too <img src="https://user-images.githubusercontent.com/55334727/214010675-9eddb8f5-1dd6-4cf4-a0ee-e37af8b6c933.PNG" align="left" width="200px"/>
You can easily create your own pack, or find some user made ones in the discord channel. Check <a href="https://github.com/ClaraCrazy/Flipper-Xtreme/wiki/1.-File-Formats">here</a> for a tutorial on creating your own. Essentially, we got our own <code>Anims</code> & <code>Icons</code> folders, inside each <code>Asset Pack</code>.
<br clear="left"/>
<img src="https://user-images.githubusercontent.com/55334727/214016338-95a619c7-88d2-4db5-bb7a-75282d9082b8.png" align="left" width="200px"/>
Once you have some packs, upload them to your Flipper in <code>SD/dolphin_custom</code> (if you did this right you should see <code>SD/dolphin_custom/PackName/Anims</code> and/or <code>SD/dolphin_custom/PackName/Icons</code>).
<br clear="left"/>
<img src="https://user-images.githubusercontent.com/55334727/214013624-25dad48e-72ea-4a90-9060-66e137e0d61a.png" align="left" width="200px"/>
After installing the packs to Flipper, hit the <code>Arrow UP</code> button on the main menu and go to <code>Xtreme Settings</code>. Here choose which pack you want and tweak the other settings how you prefer, then press back to reboot and enjoy your new assets & animations!
<br clear="left"/>
-----
<br>
<h2 align="center">Levels:</h2>
This firmware contains some NSFW animations to bring a twist to the boring community, and added a fun leveling-system around them, that you can easily add to your own `Asset Packs`.
The idle_animations are tied to the level system. Each level you reach, unlocks a new animation. The higher your level, the more lewd it will become. Rumors have it, I'm to be found in at least one of those too
| Level | Animations | | Level | Animations |
| ------------- | ------------- | | ------------- | ------------- |
@@ -40,7 +62,7 @@ The animations are tied to the level system. Each level you reach, unlocks a new
| 11-20 | Some tits, maybe an ass | | 11-20 | Some tits, maybe an ass |
| 21-30 | Fully NSFW, graphic scenes | | 21-30 | Fully NSFW, graphic scenes |
If you dont like that, we added an SFW mode to the Firmware. From the main menu, hit `Arrow UP` and select SFW mode and now all assets will be stock. By default, SFW mode is selected, but if you want to enable all of the above simply hit `Arrow UP` from the main menu, select `Xtreme Settings` and change to NSFW graphics.
----- -----
<br> <br>
@@ -51,30 +73,31 @@ Note: This repo is always updated with OFW & Unleashed. No need to mention all t
```txt ```txt
[Added] [Added]
- SFW Mode - Xtreme App
- Asset Packs
- More UI options
- A new battery display-type
- Scrolling view for long file names in browser
- NSFW Animations tied to the level system. Read more above
- Folder handling for empty ones (Now indicate they are empty)
- Jamming Files - Jamming Files
- Custom subghz presets - Custom subghz presets
- Added new Battery display-type - Multiple NFC protocols
- Subghz and IR signal replication via gpio | Credits to @ankris812 - Subghz and IR signal replication via gpio | Credits to @ankris812
- Honda Keys (CVE-2022-27254) & Ford blockers - Honda Keys (CVE-2022-27254) & Ford blockers
- NSFW Animations tied to the level system. Read more above
- New API Routes for Locale settings - New API Routes for Locale settings
- Scrolling view for long file names in browser
- Tamagotchi rom
``` ```
```txt ```txt
[Updated] [Updated]
- All graphics - All Assets
- Tons of apps
- Massive compiler re-do
- About 1k files to speed things up a lot - About 1k files to speed things up a lot
- Folder handling for empty ones (Now indicate they are empty) - Applications to now use the new Locale setting
- Applications now use the new Locale setting
- Compiler now handles all non-compiled faps during build
- Compiler now accepts WIP SDK
- Compiler just stfu about non-fatal problems
- Some further NFC stuff
- Weather App
- Applications now use above mentioned API Routes
``` ```
```txt ```txt
[Fixed] [Fixed]
@@ -89,7 +112,7 @@ Note: This repo is always updated with OFW & Unleashed. No need to mention all t
- Unused Dummy Mode - Unused Dummy Mode
- Broken apps (bad apple, chess, etc.) - Broken apps (bad apple, chess, etc.)
- Unused code from FAPs and system calls - Tons of unused code from FAPs and system calls
``` ```
---- ----

View File

@@ -148,9 +148,12 @@ fap_dist = [
for app_artifact in firmware_env["FW_EXTAPPS"].applications.values() for app_artifact in firmware_env["FW_EXTAPPS"].applications.values()
), ),
), ),
distenv.Install( *(
f"#/dist/{dist_dir}/apps", distenv.Install(
"#/assets/resources/apps", f"#/dist/{dist_dir}/apps/{app_artifact.app.fap_category}",
app_artifact.compact[0],
)
for app_artifact in firmware_env["FW_EXTAPPS"].applications.values()
), ),
] ]
Depends( Depends(

View File

@@ -31,7 +31,8 @@ void AccessorApp::run(void) {
onewire_host_stop(onewire_host); onewire_host_stop(onewire_host);
} }
AccessorApp::AccessorApp() { AccessorApp::AccessorApp()
: text_store{0} {
notification = static_cast<NotificationApp*>(furi_record_open(RECORD_NOTIFICATION)); notification = static_cast<NotificationApp*>(furi_record_open(RECORD_NOTIFICATION));
onewire_host = onewire_host_alloc(); onewire_host = onewire_host_alloc();
furi_hal_power_enable_otg(); furi_hal_power_enable_otg();

View File

@@ -171,9 +171,6 @@ bool WIEGAND::DoWiegandConversion() {
return true; return true;
} else { } else {
_lastWiegand = sysTick; _lastWiegand = sysTick;
_bitCount = 0;
_cardTemp = 0;
_cardTempHigh = 0;
return false; return false;
} }

View File

@@ -2,8 +2,11 @@
#include <gui/canvas.h> #include <gui/canvas.h>
#include <gui/elements.h> #include <gui/elements.h>
#include <lib/toolbox/float_tools.h>
#include <m-array.h> #include <m-array.h>
#include <furi.h> #include <furi.h>
#include <inttypes.h>
#include <stdint.h> #include <stdint.h>
struct BtTestParam { struct BtTestParam {
@@ -98,16 +101,16 @@ static void bt_test_draw_callback(Canvas* canvas, void* _model) {
elements_scrollbar(canvas, model->position, BtTestParamArray_size(model->params)); elements_scrollbar(canvas, model->position, BtTestParamArray_size(model->params));
canvas_draw_str(canvas, 6, 60, model->message); canvas_draw_str(canvas, 6, 60, model->message);
if(model->state == BtTestStateStarted) { if(model->state == BtTestStateStarted) {
if(model->rssi != 0.0f) { if(!float_is_equal(model->rssi, 0.0f)) {
snprintf(info_str, sizeof(info_str), "RSSI:%3.1f dB", (double)model->rssi); snprintf(info_str, sizeof(info_str), "RSSI:%3.1f dB", (double)model->rssi);
canvas_draw_str_aligned(canvas, 124, 60, AlignRight, AlignBottom, info_str); canvas_draw_str_aligned(canvas, 124, 60, AlignRight, AlignBottom, info_str);
} }
} else if(model->state == BtTestStateStopped) { } else if(model->state == BtTestStateStopped) {
if(model->packets_num_rx) { if(model->packets_num_rx) {
snprintf(info_str, sizeof(info_str), "%ld pack rcv", model->packets_num_rx); snprintf(info_str, sizeof(info_str), "%" PRIu32 " pack rcv", model->packets_num_rx);
canvas_draw_str_aligned(canvas, 124, 60, AlignRight, AlignBottom, info_str); canvas_draw_str_aligned(canvas, 124, 60, AlignRight, AlignBottom, info_str);
} else if(model->packets_num_tx) { } else if(model->packets_num_tx) {
snprintf(info_str, sizeof(info_str), "%ld pack sent", model->packets_num_tx); snprintf(info_str, sizeof(info_str), "%" PRIu32 " pack sent", model->packets_num_tx);
canvas_draw_str_aligned(canvas, 124, 60, AlignRight, AlignBottom, info_str); canvas_draw_str_aligned(canvas, 124, 60, AlignRight, AlignBottom, info_str);
} }
} }
@@ -153,7 +156,7 @@ static bool bt_test_input_callback(InputEvent* event, void* context) {
} }
void bt_test_process_up(BtTest* bt_test) { void bt_test_process_up(BtTest* bt_test) {
with_view_model( with_view_model( // -V658
bt_test->view, bt_test->view,
BtTestModel * model, BtTestModel * model,
{ {

View File

@@ -48,7 +48,7 @@ FileBrowserApp* file_browser_app_alloc(char* arg) {
app->file_path = furi_string_alloc(); app->file_path = furi_string_alloc();
app->file_browser = file_browser_alloc(app->file_path); app->file_browser = file_browser_alloc(app->file_path);
file_browser_configure(app->file_browser, "*", NULL, true, &I_badusb_10px, true); file_browser_configure(app->file_browser, "*", NULL, true, false, &I_badusb_10px, true);
view_dispatcher_add_view( view_dispatcher_add_view(
app->view_dispatcher, FileBrowserAppViewStart, widget_get_view(app->widget)); app->view_dispatcher, FileBrowserAppViewStart, widget_get_view(app->widget));

View File

@@ -1,5 +1,5 @@
App( App(
appid="sample_apps", appid="example_apps",
name="Sample apps bundle", name="Example apps bundle",
apptype=FlipperAppType.METAPACKAGE, apptype=FlipperAppType.METAPACKAGE,
) )

View File

@@ -1,5 +1,5 @@
#include "../bad_usb_app_i.h" #include "../bad_usb_app_i.h"
#include "../../../settings/desktop_settings/desktop_settings_app.h" #include "../../../settings/xtreme_settings/xtreme_settings.h"
typedef enum { typedef enum {
BadUsbCustomEventErrorBack, BadUsbCustomEventErrorBack,
@@ -17,8 +17,6 @@ static void
void bad_usb_scene_error_on_enter(void* context) { void bad_usb_scene_error_on_enter(void* context) {
BadUsbApp* app = context; BadUsbApp* app = context;
DesktopSettings* settings = malloc(sizeof(DesktopSettings));
DESKTOP_SETTINGS_LOAD(settings);
if(app->error == BadUsbAppErrorNoFiles) { if(app->error == BadUsbAppErrorNoFiles) {
widget_add_icon_element(app->widget, 0, 0, &I_SDQuestion_35x43); widget_add_icon_element(app->widget, 0, 0, &I_SDQuestion_35x43);
@@ -34,18 +32,7 @@ void bad_usb_scene_error_on_enter(void* context) {
app->widget, GuiButtonTypeLeft, "Back", bad_usb_scene_error_event_callback, app); app->widget, GuiButtonTypeLeft, "Back", bad_usb_scene_error_event_callback, app);
} else if(app->error == BadUsbAppErrorCloseRpc) { } else if(app->error == BadUsbAppErrorCloseRpc) {
widget_add_icon_element(app->widget, 78, 0, &I_ActiveConnection_50x64); widget_add_icon_element(app->widget, 78, 0, &I_ActiveConnection_50x64);
if(settings->sfw_mode) { if(XTREME_SETTINGS()->nsfw_mode) {
widget_add_string_multiline_element(
app->widget, 3, 2, AlignLeft, AlignTop, FontPrimary, "Connection\nis active!");
widget_add_string_multiline_element(
app->widget,
3,
30,
AlignLeft,
AlignTop,
FontSecondary,
"Disconnect from\nPC or phone to\nuse this function.");
} else {
widget_add_string_multiline_element( widget_add_string_multiline_element(
app->widget, 3, 2, AlignLeft, AlignTop, FontPrimary, "I am not\na whore!"); app->widget, 3, 2, AlignLeft, AlignTop, FontPrimary, "I am not\na whore!");
widget_add_string_multiline_element( widget_add_string_multiline_element(
@@ -56,11 +43,21 @@ void bad_usb_scene_error_on_enter(void* context) {
AlignTop, AlignTop,
FontSecondary, FontSecondary,
"Pull out from\nPC or phone to\nuse me like this."); "Pull out from\nPC or phone to\nuse me like this.");
} else {
widget_add_string_multiline_element(
app->widget, 3, 2, AlignLeft, AlignTop, FontPrimary, "Connection\nis active!");
widget_add_string_multiline_element(
app->widget,
3,
30,
AlignLeft,
AlignTop,
FontSecondary,
"Disconnect from\nPC or phone to\nuse this function.");
} }
} }
view_dispatcher_switch_to_view(app->view_dispatcher, BadUsbAppViewError); view_dispatcher_switch_to_view(app->view_dispatcher, BadUsbAppViewError);
free(settings);
} }
bool bad_usb_scene_error_on_event(void* context, SceneManagerEvent event) { bool bad_usb_scene_error_on_event(void* context, SceneManagerEvent event) {

View File

@@ -3,7 +3,7 @@
#include <toolbox/path.h> #include <toolbox/path.h>
#include <gui/elements.h> #include <gui/elements.h>
#include <assets_icons.h> #include <assets_icons.h>
#include "../../../settings/desktop_settings/desktop_settings_app.h" #include "../../../settings/xtreme_settings/xtreme_settings.h"
#define MAX_NAME_LEN 64 #define MAX_NAME_LEN 64
@@ -28,8 +28,7 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) {
elements_string_fit_width(canvas, disp_str, 128 - 2); elements_string_fit_width(canvas, disp_str, 128 - 2);
canvas_set_font(canvas, FontSecondary); canvas_set_font(canvas, FontSecondary);
canvas_draw_str(canvas, 2, 8, furi_string_get_cstr(disp_str)); canvas_draw_str(canvas, 2, 8, furi_string_get_cstr(disp_str));
DesktopSettings* settings = malloc(sizeof(DesktopSettings)); XtremeSettings* xtreme_settings = XTREME_SETTINGS();
DESKTOP_SETTINGS_LOAD(settings);
if(strlen(model->layout) == 0) { if(strlen(model->layout) == 0) {
furi_string_set(disp_str, "(default)"); furi_string_set(disp_str, "(default)");
@@ -50,10 +49,10 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) {
if((model->state.state == BadUsbStateIdle) || (model->state.state == BadUsbStateDone) || if((model->state.state == BadUsbStateIdle) || (model->state.state == BadUsbStateDone) ||
(model->state.state == BadUsbStateNotConnected)) { (model->state.state == BadUsbStateNotConnected)) {
if(settings->sfw_mode) { if(xtreme_settings->nsfw_mode) {
elements_button_center(canvas, "Start");
} else {
elements_button_center(canvas, "Cum"); elements_button_center(canvas, "Cum");
} else {
elements_button_center(canvas, "Start");
} }
} else if((model->state.state == BadUsbStateRunning) || (model->state.state == BadUsbStateDelay)) { } else if((model->state.state == BadUsbStateRunning) || (model->state.state == BadUsbStateDelay)) {
elements_button_center(canvas, "Stop"); elements_button_center(canvas, "Stop");
@@ -69,20 +68,20 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) {
if(model->state.state == BadUsbStateNotConnected) { if(model->state.state == BadUsbStateNotConnected) {
canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18); canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18);
canvas_set_font(canvas, FontPrimary); canvas_set_font(canvas, FontPrimary);
if(settings->sfw_mode) { if(xtreme_settings->nsfw_mode) {
canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "Connect to");
canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "a device");
} else {
canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "Plug me"); canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "Plug me");
canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "in, Daddy"); canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "in, Daddy");
} else {
canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "Connect to");
canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "a device");
} }
} else if(model->state.state == BadUsbStateWillRun) { } else if(model->state.state == BadUsbStateWillRun) {
canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18); canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18);
canvas_set_font(canvas, FontPrimary); canvas_set_font(canvas, FontPrimary);
if(settings->sfw_mode) { if(xtreme_settings->nsfw_mode) {
canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "Will run");
} else {
canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "Will cum"); canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "Will cum");
} else {
canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "Will run");
} }
canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "on connect"); canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "on connect");
} else if(model->state.state == BadUsbStateFileError) { } else if(model->state.state == BadUsbStateFileError) {
@@ -147,7 +146,6 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) {
} }
furi_string_free(disp_str); furi_string_free(disp_str);
free(settings);
} }
static bool bad_usb_input_callback(InputEvent* event, void* context) { static bool bad_usb_input_callback(InputEvent* event, void* context) {

View File

@@ -1,5 +1,5 @@
#include "../ibutton_i.h" #include "../ibutton_i.h"
#include "../../../settings/desktop_settings/desktop_settings_app.h" #include "../../../settings/xtreme_settings/xtreme_assets.h"
static void ibutton_scene_delete_success_popup_callback(void* context) { static void ibutton_scene_delete_success_popup_callback(void* context) {
iButton* ibutton = context; iButton* ibutton = context;
@@ -9,14 +9,8 @@ static void ibutton_scene_delete_success_popup_callback(void* context) {
void ibutton_scene_delete_success_on_enter(void* context) { void ibutton_scene_delete_success_on_enter(void* context) {
iButton* ibutton = context; iButton* ibutton = context;
Popup* popup = ibutton->popup; Popup* popup = ibutton->popup;
DesktopSettings* settings = malloc(sizeof(DesktopSettings));
DESKTOP_SETTINGS_LOAD(settings);
if(settings->sfw_mode) { popup_set_icon(popup, 0, 2, XTREME_ASSETS()->I_DolphinMafia_115x62);
popup_set_icon(popup, 0, 2, &I_DolphinMafia_115x62_sfw);
} else {
popup_set_icon(popup, 0, 2, &I_DolphinMafia_115x62);
}
popup_set_header(popup, "Deleted", 83, 19, AlignLeft, AlignBottom); popup_set_header(popup, "Deleted", 83, 19, AlignLeft, AlignBottom);
popup_set_callback(popup, ibutton_scene_delete_success_popup_callback); popup_set_callback(popup, ibutton_scene_delete_success_popup_callback);
@@ -25,7 +19,6 @@ void ibutton_scene_delete_success_on_enter(void* context) {
popup_enable_timeout(popup); popup_enable_timeout(popup);
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewPopup); view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewPopup);
free(settings);
} }
bool ibutton_scene_delete_success_on_event(void* context, SceneManagerEvent event) { bool ibutton_scene_delete_success_on_event(void* context, SceneManagerEvent event) {

View File

@@ -1,6 +1,6 @@
#include "../ibutton_i.h" #include "../ibutton_i.h"
#include <dolphin/dolphin.h> #include <dolphin/dolphin.h>
#include "../../../settings/desktop_settings/desktop_settings_app.h" #include "../../../settings/xtreme_settings/xtreme_assets.h"
static void ibutton_scene_read_callback(void* context) { static void ibutton_scene_read_callback(void* context) {
iButton* ibutton = context; iButton* ibutton = context;
@@ -12,16 +12,10 @@ void ibutton_scene_read_on_enter(void* context) {
Popup* popup = ibutton->popup; Popup* popup = ibutton->popup;
iButtonKey* key = ibutton->key; iButtonKey* key = ibutton->key;
iButtonWorker* worker = ibutton->key_worker; iButtonWorker* worker = ibutton->key_worker;
DesktopSettings* settings = malloc(sizeof(DesktopSettings));
DESKTOP_SETTINGS_LOAD(settings);
popup_set_header(popup, "iButton", 95, 26, AlignCenter, AlignBottom); popup_set_header(popup, "iButton", 95, 26, AlignCenter, AlignBottom);
popup_set_text(popup, "Waiting\nfor key ...", 95, 30, AlignCenter, AlignTop); popup_set_text(popup, "Waiting\nfor key ...", 95, 30, AlignCenter, AlignTop);
if(settings->sfw_mode) { popup_set_icon(popup, 0, 5, XTREME_ASSETS()->I_DolphinWait_61x59);
popup_set_icon(popup, 0, 5, &I_DolphinWait_61x59_sfw);
} else {
popup_set_icon(popup, 0, 5, &I_DolphinWait_61x59);
}
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewPopup); view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewPopup);
furi_string_set(ibutton->file_path, IBUTTON_APP_FOLDER); furi_string_set(ibutton->file_path, IBUTTON_APP_FOLDER);
@@ -30,7 +24,6 @@ void ibutton_scene_read_on_enter(void* context) {
ibutton_worker_read_start(worker, key); ibutton_worker_read_start(worker, key);
ibutton_notification_message(ibutton, iButtonNotificationMessageReadStart); ibutton_notification_message(ibutton, iButtonNotificationMessageReadStart);
free(settings);
} }
bool ibutton_scene_read_on_event(void* context, SceneManagerEvent event) { bool ibutton_scene_read_on_event(void* context, SceneManagerEvent event) {

View File

@@ -1,5 +1,5 @@
#include "../ibutton_i.h" #include "../ibutton_i.h"
#include "../../../settings/desktop_settings/desktop_settings_app.h" #include "../../../settings/xtreme_settings/xtreme_assets.h"
static void ibutton_scene_save_success_popup_callback(void* context) { static void ibutton_scene_save_success_popup_callback(void* context) {
iButton* ibutton = context; iButton* ibutton = context;
@@ -9,14 +9,8 @@ static void ibutton_scene_save_success_popup_callback(void* context) {
void ibutton_scene_save_success_on_enter(void* context) { void ibutton_scene_save_success_on_enter(void* context) {
iButton* ibutton = context; iButton* ibutton = context;
Popup* popup = ibutton->popup; Popup* popup = ibutton->popup;
DesktopSettings* settings = malloc(sizeof(DesktopSettings));
DESKTOP_SETTINGS_LOAD(settings);
if(settings->sfw_mode) { popup_set_icon(popup, 32, 5, XTREME_ASSETS()->I_DolphinNice_96x59);
popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59_sfw);
} else {
popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59);
}
popup_set_header(popup, "Saved!", 5, 7, AlignLeft, AlignTop); popup_set_header(popup, "Saved!", 5, 7, AlignLeft, AlignTop);
popup_set_callback(popup, ibutton_scene_save_success_popup_callback); popup_set_callback(popup, ibutton_scene_save_success_popup_callback);
@@ -25,7 +19,6 @@ void ibutton_scene_save_success_on_enter(void* context) {
popup_enable_timeout(popup); popup_enable_timeout(popup);
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewPopup); view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewPopup);
free(settings);
} }
bool ibutton_scene_save_success_on_event(void* context, SceneManagerEvent event) { bool ibutton_scene_save_success_on_event(void* context, SceneManagerEvent event) {

View File

@@ -1,5 +1,5 @@
#include "../ibutton_i.h" #include "../ibutton_i.h"
#include "../../../settings/desktop_settings/desktop_settings_app.h" #include "../../../settings/xtreme_settings/xtreme_assets.h"
static void ibutton_scene_write_success_popup_callback(void* context) { static void ibutton_scene_write_success_popup_callback(void* context) {
iButton* ibutton = context; iButton* ibutton = context;
@@ -10,14 +10,8 @@ static void ibutton_scene_write_success_popup_callback(void* context) {
void ibutton_scene_write_success_on_enter(void* context) { void ibutton_scene_write_success_on_enter(void* context) {
iButton* ibutton = context; iButton* ibutton = context;
Popup* popup = ibutton->popup; Popup* popup = ibutton->popup;
DesktopSettings* settings = malloc(sizeof(DesktopSettings));
DESKTOP_SETTINGS_LOAD(settings);
if(settings->sfw_mode) { popup_set_icon(popup, 0, 12, XTREME_ASSETS()->I_iButtonDolphinVerySuccess_108x52);
popup_set_icon(popup, 0, 12, &I_iButtonDolphinVerySuccess_108x52_sfw);
} else {
popup_set_icon(popup, 0, 12, &I_iButtonDolphinVerySuccess_108x52);
}
popup_set_text(popup, "Successfully written!", 40, 12, AlignLeft, AlignBottom); popup_set_text(popup, "Successfully written!", 40, 12, AlignLeft, AlignBottom);
popup_set_callback(popup, ibutton_scene_write_success_popup_callback); popup_set_callback(popup, ibutton_scene_write_success_popup_callback);
@@ -28,7 +22,6 @@ void ibutton_scene_write_success_on_enter(void* context) {
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewPopup); view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewPopup);
ibutton_notification_message(ibutton, iButtonNotificationMessageSuccess); ibutton_notification_message(ibutton, iButtonNotificationMessageSuccess);
ibutton_notification_message(ibutton, iButtonNotificationMessageGreenOn); ibutton_notification_message(ibutton, iButtonNotificationMessageGreenOn);
free(settings);
} }
bool ibutton_scene_write_success_on_event(void* context, SceneManagerEvent event) { bool ibutton_scene_write_success_on_event(void* context, SceneManagerEvent event) {

View File

@@ -1,17 +1,11 @@
#include "../infrared_i.h" #include "../infrared_i.h"
#include "../../../settings/desktop_settings/desktop_settings_app.h" #include "../../../settings/xtreme_settings/xtreme_assets.h"
void infrared_scene_edit_rename_done_on_enter(void* context) { void infrared_scene_edit_rename_done_on_enter(void* context) {
Infrared* infrared = context; Infrared* infrared = context;
Popup* popup = infrared->popup; Popup* popup = infrared->popup;
DesktopSettings* settings = malloc(sizeof(DesktopSettings));
DESKTOP_SETTINGS_LOAD(settings);
if(settings->sfw_mode) { popup_set_icon(popup, 32, 5, XTREME_ASSETS()->I_DolphinNice_96x59);
popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59_sfw);
} else {
popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59);
}
popup_set_header(popup, "Saved!", 5, 7, AlignLeft, AlignTop); popup_set_header(popup, "Saved!", 5, 7, AlignLeft, AlignTop);
popup_set_callback(popup, infrared_popup_closed_callback); popup_set_callback(popup, infrared_popup_closed_callback);
@@ -20,7 +14,6 @@ void infrared_scene_edit_rename_done_on_enter(void* context) {
popup_enable_timeout(popup); popup_enable_timeout(popup);
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewPopup); view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewPopup);
free(settings);
} }
bool infrared_scene_edit_rename_done_on_event(void* context, SceneManagerEvent event) { bool infrared_scene_edit_rename_done_on_event(void* context, SceneManagerEvent event) {

View File

@@ -1,21 +1,15 @@
#include "../infrared_i.h" #include "../infrared_i.h"
#include "../../../settings/desktop_settings/desktop_settings_app.h" #include "../../../settings/xtreme_settings/xtreme_assets.h"
void infrared_scene_learn_done_on_enter(void* context) { void infrared_scene_learn_done_on_enter(void* context) {
Infrared* infrared = context; Infrared* infrared = context;
Popup* popup = infrared->popup; Popup* popup = infrared->popup;
DesktopSettings* settings = malloc(sizeof(DesktopSettings));
DESKTOP_SETTINGS_LOAD(settings);
if(settings->sfw_mode) { popup_set_icon(popup, 32, 5, XTREME_ASSETS()->I_DolphinNice_96x59);
popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59_sfw); if(infrared->app_state.is_learning_new_remote) {
if(infrared->app_state.is_learning_new_remote) { popup_set_header(popup, "New remote\ncreated!", 0, 0, AlignLeft, AlignTop);
popup_set_header(popup, "New remote\ncreated!", 0, 0, AlignLeft, AlignTop);
} else {
popup_set_header(popup, "Saved!", 5, 7, AlignLeft, AlignTop);
}
} else { } else {
popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); popup_set_header(popup, "Saved!", 5, 7, AlignLeft, AlignTop);
} }
popup_set_callback(popup, infrared_popup_closed_callback); popup_set_callback(popup, infrared_popup_closed_callback);
@@ -24,7 +18,6 @@ void infrared_scene_learn_done_on_enter(void* context) {
popup_enable_timeout(popup); popup_enable_timeout(popup);
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewPopup); view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewPopup);
free(settings);
} }
bool infrared_scene_learn_done_on_event(void* context, SceneManagerEvent event) { bool infrared_scene_learn_done_on_event(void* context, SceneManagerEvent event) {

View File

@@ -1,5 +1,5 @@
#include "../infrared_i.h" #include "../infrared_i.h"
#include "../../../settings/desktop_settings/desktop_settings_app.h" #include "../../../settings/xtreme_settings/xtreme_assets.h"
static void static void
infrared_scene_learn_success_dialog_result_callback(DialogExResult result, void* context) { infrared_scene_learn_success_dialog_result_callback(DialogExResult result, void* context) {
@@ -11,8 +11,6 @@ void infrared_scene_learn_success_on_enter(void* context) {
Infrared* infrared = context; Infrared* infrared = context;
DialogEx* dialog_ex = infrared->dialog_ex; DialogEx* dialog_ex = infrared->dialog_ex;
InfraredSignal* signal = infrared->received_signal; InfraredSignal* signal = infrared->received_signal;
DesktopSettings* settings = malloc(sizeof(DesktopSettings));
DESKTOP_SETTINGS_LOAD(settings);
infrared_play_notification_message(infrared, InfraredNotificationMessageGreenOn); infrared_play_notification_message(infrared, InfraredNotificationMessageGreenOn);
@@ -50,17 +48,12 @@ void infrared_scene_learn_success_on_enter(void* context) {
dialog_ex_set_left_button_text(dialog_ex, "Retry"); dialog_ex_set_left_button_text(dialog_ex, "Retry");
dialog_ex_set_right_button_text(dialog_ex, "Save"); dialog_ex_set_right_button_text(dialog_ex, "Save");
dialog_ex_set_center_button_text(dialog_ex, "Send"); dialog_ex_set_center_button_text(dialog_ex, "Send");
if(settings->sfw_mode) { dialog_ex_set_icon(dialog_ex, 0, 1, XTREME_ASSETS()->I_DolphinReadingSuccess_59x63);
dialog_ex_set_icon(dialog_ex, 0, 1, &I_DolphinReadingSuccess_59x63_sfw);
} else {
dialog_ex_set_icon(dialog_ex, 0, 1, &I_DolphinReadingSuccess_59x63);
}
dialog_ex_set_result_callback(dialog_ex, infrared_scene_learn_success_dialog_result_callback); dialog_ex_set_result_callback(dialog_ex, infrared_scene_learn_success_dialog_result_callback);
dialog_ex_set_context(dialog_ex, context); dialog_ex_set_context(dialog_ex, context);
dialog_ex_enable_extended_events(dialog_ex); dialog_ex_enable_extended_events(dialog_ex);
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewDialogEx); view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewDialogEx);
free(settings);
} }
bool infrared_scene_learn_success_on_event(void* context, SceneManagerEvent event) { bool infrared_scene_learn_success_on_event(void* context, SceneManagerEvent event) {

View File

@@ -1,6 +1,6 @@
#include "../lfrfid_i.h" #include "../lfrfid_i.h"
#include "../helpers/rfid_writer.h" #include "../helpers/rfid_writer.h"
#include "../../../settings/desktop_settings/desktop_settings_app.h" #include "../../../settings/xtreme_settings/xtreme_assets.h"
static void writer_initialize(T55xxTiming* t55xxtiming) { static void writer_initialize(T55xxTiming* t55xxtiming) {
t55xxtiming->wait_time = 400; t55xxtiming->wait_time = 400;
@@ -15,8 +15,6 @@ static void lfrfid_clear_t5577_password_and_config_to_EM(LfRfid* app) {
T55xxTiming* t55xxtiming = malloc(sizeof(T55xxTiming)); T55xxTiming* t55xxtiming = malloc(sizeof(T55xxTiming));
Popup* popup = app->popup; Popup* popup = app->popup;
char curr_buf[32] = {}; char curr_buf[32] = {};
DesktopSettings* settings = malloc(sizeof(DesktopSettings));
DESKTOP_SETTINGS_LOAD(settings);
//TODO: use .txt file in resourses for passwords. //TODO: use .txt file in resourses for passwords.
const uint32_t default_passwords[] = { const uint32_t default_passwords[] = {
@@ -45,11 +43,7 @@ static void lfrfid_clear_t5577_password_and_config_to_EM(LfRfid* app) {
writer_initialize(t55xxtiming); writer_initialize(t55xxtiming);
popup_set_header(popup, "Removing\npassword", 90, 36, AlignCenter, AlignCenter); popup_set_header(popup, "Removing\npassword", 90, 36, AlignCenter, AlignCenter);
if(settings->sfw_mode) { popup_set_icon(popup, 0, 3, XTREME_ASSETS()->I_RFIDDolphinSend_97x61);
popup_set_icon(popup, 0, 3, &I_RFIDDolphinSend_97x61_sfw);
} else {
popup_set_icon(popup, 0, 3, &I_RFIDDolphinSend_97x61);
}
popup_set_text(popup, curr_buf, 90, 56, AlignCenter, AlignCenter); popup_set_text(popup, curr_buf, 90, 56, AlignCenter, AlignCenter);
notification_message(app->notifications, &sequence_blink_start_magenta); notification_message(app->notifications, &sequence_blink_start_magenta);
@@ -67,24 +61,17 @@ static void lfrfid_clear_t5577_password_and_config_to_EM(LfRfid* app) {
notification_message(app->notifications, &sequence_blink_stop); notification_message(app->notifications, &sequence_blink_stop);
popup_reset(app->popup); popup_reset(app->popup);
free(t55xxtiming); free(t55xxtiming);
free(settings);
} }
void lfrfid_scene_clear_t5577_on_enter(void* context) { void lfrfid_scene_clear_t5577_on_enter(void* context) {
LfRfid* app = context; LfRfid* app = context;
Popup* popup = app->popup; Popup* popup = app->popup;
DesktopSettings* settings = malloc(sizeof(DesktopSettings));
DESKTOP_SETTINGS_LOAD(settings);
lfrfid_clear_t5577_password_and_config_to_EM(app); lfrfid_clear_t5577_password_and_config_to_EM(app);
notification_message(app->notifications, &sequence_success); notification_message(app->notifications, &sequence_success);
popup_set_header(popup, "Done!", 94, 10, AlignCenter, AlignTop); popup_set_header(popup, "Done!", 94, 10, AlignCenter, AlignTop);
if(settings->sfw_mode) { popup_set_icon(popup, 0, 7, XTREME_ASSETS()->I_RFIDDolphinSuccess_108x57);
popup_set_icon(popup, 0, 7, &I_RFIDDolphinSuccess_108x57_sfw);
} else {
popup_set_icon(popup, 0, 7, &I_RFIDDolphinSuccess_108x57);
}
popup_set_context(popup, app); popup_set_context(popup, app);
popup_set_callback(popup, lfrfid_popup_timeout_callback); popup_set_callback(popup, lfrfid_popup_timeout_callback);
popup_set_timeout(popup, 1500); popup_set_timeout(popup, 1500);
@@ -92,7 +79,6 @@ void lfrfid_scene_clear_t5577_on_enter(void* context) {
view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewPopup); view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewPopup);
notification_message_block(app->notifications, &sequence_set_green_255); notification_message_block(app->notifications, &sequence_set_green_255);
free(settings);
} }
bool lfrfid_scene_clear_t5577_on_event(void* context, SceneManagerEvent event) { bool lfrfid_scene_clear_t5577_on_event(void* context, SceneManagerEvent event) {

View File

@@ -1,17 +1,11 @@
#include "../lfrfid_i.h" #include "../lfrfid_i.h"
#include "../../../settings/desktop_settings/desktop_settings_app.h" #include "../../../settings/xtreme_settings/xtreme_assets.h"
void lfrfid_scene_delete_success_on_enter(void* context) { void lfrfid_scene_delete_success_on_enter(void* context) {
LfRfid* app = context; LfRfid* app = context;
Popup* popup = app->popup; Popup* popup = app->popup;
DesktopSettings* settings = malloc(sizeof(DesktopSettings));
DESKTOP_SETTINGS_LOAD(settings);
if(settings->sfw_mode) { popup_set_icon(popup, 0, 2, XTREME_ASSETS()->I_DolphinMafia_115x62);
popup_set_icon(popup, 0, 2, &I_DolphinMafia_115x62_sfw);
} else {
popup_set_icon(popup, 0, 2, &I_DolphinMafia_115x62);
}
popup_set_header(popup, "Deleted", 83, 19, AlignLeft, AlignBottom); popup_set_header(popup, "Deleted", 83, 19, AlignLeft, AlignBottom);
popup_set_context(popup, app); popup_set_context(popup, app);
popup_set_callback(popup, lfrfid_popup_timeout_callback); popup_set_callback(popup, lfrfid_popup_timeout_callback);
@@ -19,7 +13,6 @@ void lfrfid_scene_delete_success_on_enter(void* context) {
popup_enable_timeout(popup); popup_enable_timeout(popup);
view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewPopup); view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewPopup);
free(settings);
} }
bool lfrfid_scene_delete_success_on_event(void* context, SceneManagerEvent event) { bool lfrfid_scene_delete_success_on_event(void* context, SceneManagerEvent event) {

View File

@@ -1,11 +1,9 @@
#include "../lfrfid_i.h" #include "../lfrfid_i.h"
#include "../../../settings/desktop_settings/desktop_settings_app.h" #include "../../../settings/xtreme_settings/xtreme_assets.h"
void lfrfid_scene_emulate_on_enter(void* context) { void lfrfid_scene_emulate_on_enter(void* context) {
LfRfid* app = context; LfRfid* app = context;
Popup* popup = app->popup; Popup* popup = app->popup;
DesktopSettings* settings = malloc(sizeof(DesktopSettings));
DESKTOP_SETTINGS_LOAD(settings);
popup_set_header(popup, "Emulating", 89, 30, AlignCenter, AlignTop); popup_set_header(popup, "Emulating", 89, 30, AlignCenter, AlignTop);
if(!furi_string_empty(app->file_name)) { if(!furi_string_empty(app->file_name)) {
@@ -19,18 +17,13 @@ void lfrfid_scene_emulate_on_enter(void* context) {
AlignCenter, AlignCenter,
AlignTop); AlignTop);
} }
if(settings->sfw_mode) { popup_set_icon(popup, 0, 3, XTREME_ASSETS()->I_RFIDDolphinSend_97x61);
popup_set_icon(popup, 0, 3, &I_RFIDDolphinSend_97x61_sfw);
} else {
popup_set_icon(popup, 0, 3, &I_RFIDDolphinSend_97x61);
}
lfrfid_worker_start_thread(app->lfworker); lfrfid_worker_start_thread(app->lfworker);
lfrfid_worker_emulate_start(app->lfworker, (LFRFIDProtocol)app->protocol_id); lfrfid_worker_emulate_start(app->lfworker, (LFRFIDProtocol)app->protocol_id);
notification_message(app->notifications, &sequence_blink_start_magenta); notification_message(app->notifications, &sequence_blink_start_magenta);
view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewPopup); view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewPopup);
free(settings);
} }
bool lfrfid_scene_emulate_on_event(void* context, SceneManagerEvent event) { bool lfrfid_scene_emulate_on_event(void* context, SceneManagerEvent event) {

View File

@@ -1,5 +1,5 @@
#include "../lfrfid_i.h" #include "../lfrfid_i.h"
#include "../../../settings/desktop_settings/desktop_settings_app.h" #include "../../../settings/xtreme_settings/xtreme_assets.h"
#define RAW_READ_TIME 5000 #define RAW_READ_TIME 5000
@@ -29,17 +29,11 @@ static void timer_callback(void* context) {
void lfrfid_scene_raw_read_on_enter(void* context) { void lfrfid_scene_raw_read_on_enter(void* context) {
LfRfid* app = context; LfRfid* app = context;
Popup* popup = app->popup; Popup* popup = app->popup;
DesktopSettings* settings = malloc(sizeof(DesktopSettings));
DESKTOP_SETTINGS_LOAD(settings);
LfRfidReadRawState* state = malloc(sizeof(LfRfidReadRawState)); LfRfidReadRawState* state = malloc(sizeof(LfRfidReadRawState));
scene_manager_set_scene_state(app->scene_manager, LfRfidSceneRawRead, (uint32_t)state); scene_manager_set_scene_state(app->scene_manager, LfRfidSceneRawRead, (uint32_t)state);
state->string_file_name = furi_string_alloc(); state->string_file_name = furi_string_alloc();
if(settings->sfw_mode) { popup_set_icon(popup, 0, 3, XTREME_ASSETS()->I_RFIDDolphinReceive_97x61);
popup_set_icon(popup, 0, 3, &I_RFIDDolphinReceive_97x61_sfw);
} else {
popup_set_icon(popup, 0, 3, &I_RFIDDolphinReceive_97x61);
}
view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewPopup); view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewPopup);
lfrfid_worker_start_thread(app->lfworker); lfrfid_worker_start_thread(app->lfworker);
lfrfid_make_app_folder(app); lfrfid_make_app_folder(app);
@@ -64,7 +58,6 @@ void lfrfid_scene_raw_read_on_enter(void* context) {
state->is_psk = false; state->is_psk = false;
state->error = false; state->error = false;
free(settings);
} }
bool lfrfid_scene_raw_read_on_event(void* context, SceneManagerEvent event) { bool lfrfid_scene_raw_read_on_event(void* context, SceneManagerEvent event) {

View File

@@ -1,26 +1,19 @@
#include "../lfrfid_i.h" #include "../lfrfid_i.h"
#include "../../../settings/desktop_settings/desktop_settings_app.h" #include "../../../settings/xtreme_settings/xtreme_assets.h"
void lfrfid_scene_rpc_on_enter(void* context) { void lfrfid_scene_rpc_on_enter(void* context) {
LfRfid* app = context; LfRfid* app = context;
Popup* popup = app->popup; Popup* popup = app->popup;
DesktopSettings* settings = malloc(sizeof(DesktopSettings));
DESKTOP_SETTINGS_LOAD(settings);
popup_set_header(popup, "LF RFID", 89, 42, AlignCenter, AlignBottom); popup_set_header(popup, "LF RFID", 89, 42, AlignCenter, AlignBottom);
popup_set_text(popup, "RPC mode", 89, 44, AlignCenter, AlignTop); popup_set_text(popup, "RPC mode", 89, 44, AlignCenter, AlignTop);
if(settings->sfw_mode) { popup_set_icon(popup, 0, 12, XTREME_ASSETS()->I_RFIDDolphinSend_97x61);
popup_set_icon(popup, 0, 12, &I_RFIDDolphinSend_97x61_sfw);
} else {
popup_set_icon(popup, 0, 12, &I_RFIDDolphinSend_97x61);
}
view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewPopup); view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewPopup);
notification_message(app->notifications, &sequence_display_backlight_on); notification_message(app->notifications, &sequence_display_backlight_on);
app->rpc_state = LfRfidRpcStateIdle; app->rpc_state = LfRfidRpcStateIdle;
free(settings);
} }
bool lfrfid_scene_rpc_on_event(void* context, SceneManagerEvent event) { bool lfrfid_scene_rpc_on_event(void* context, SceneManagerEvent event) {

View File

@@ -1,19 +1,13 @@
#include "../lfrfid_i.h" #include "../lfrfid_i.h"
#include "../../../settings/desktop_settings/desktop_settings_app.h" #include "../../../settings/xtreme_settings/xtreme_assets.h"
void lfrfid_scene_save_success_on_enter(void* context) { void lfrfid_scene_save_success_on_enter(void* context) {
LfRfid* app = context; LfRfid* app = context;
Popup* popup = app->popup; Popup* popup = app->popup;
DesktopSettings* settings = malloc(sizeof(DesktopSettings));
DESKTOP_SETTINGS_LOAD(settings);
// Clear state of data enter scene // Clear state of data enter scene
scene_manager_set_scene_state(app->scene_manager, LfRfidSceneSaveData, 0); scene_manager_set_scene_state(app->scene_manager, LfRfidSceneSaveData, 0);
if(settings->sfw_mode) { popup_set_icon(popup, 32, 5, XTREME_ASSETS()->I_DolphinNice_96x59);
popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59_sfw);
} else {
popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59);
}
popup_set_header(popup, "Saved!", 5, 7, AlignLeft, AlignTop); popup_set_header(popup, "Saved!", 5, 7, AlignLeft, AlignTop);
popup_set_context(popup, app); popup_set_context(popup, app);
popup_set_callback(popup, lfrfid_popup_timeout_callback); popup_set_callback(popup, lfrfid_popup_timeout_callback);
@@ -21,7 +15,6 @@ void lfrfid_scene_save_success_on_enter(void* context) {
popup_enable_timeout(popup); popup_enable_timeout(popup);
view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewPopup); view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewPopup);
free(settings);
} }
bool lfrfid_scene_save_success_on_event(void* context, SceneManagerEvent event) { bool lfrfid_scene_save_success_on_event(void* context, SceneManagerEvent event) {

View File

@@ -1,5 +1,5 @@
#include "../lfrfid_i.h" #include "../lfrfid_i.h"
#include "../../../settings/desktop_settings/desktop_settings_app.h" #include "../../../settings/xtreme_settings/xtreme_assets.h"
static void lfrfid_write_callback(LFRFIDWorkerWriteResult result, void* context) { static void lfrfid_write_callback(LFRFIDWorkerWriteResult result, void* context) {
LfRfid* app = context; LfRfid* app = context;
@@ -21,8 +21,6 @@ static void lfrfid_write_callback(LFRFIDWorkerWriteResult result, void* context)
void lfrfid_scene_write_on_enter(void* context) { void lfrfid_scene_write_on_enter(void* context) {
LfRfid* app = context; LfRfid* app = context;
Popup* popup = app->popup; Popup* popup = app->popup;
DesktopSettings* settings = malloc(sizeof(DesktopSettings));
DESKTOP_SETTINGS_LOAD(settings);
popup_set_header(popup, "Writing", 89, 30, AlignCenter, AlignTop); popup_set_header(popup, "Writing", 89, 30, AlignCenter, AlignTop);
if(!furi_string_empty(app->file_name)) { if(!furi_string_empty(app->file_name)) {
@@ -36,11 +34,7 @@ void lfrfid_scene_write_on_enter(void* context) {
AlignCenter, AlignCenter,
AlignTop); AlignTop);
} }
if(settings->sfw_mode) { popup_set_icon(popup, 0, 3, XTREME_ASSETS()->I_RFIDDolphinSend_97x61);
popup_set_icon(popup, 0, 3, &I_RFIDDolphinSend_97x61_sfw);
} else {
popup_set_icon(popup, 0, 3, &I_RFIDDolphinSend_97x61);
}
view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewPopup); view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewPopup);
@@ -51,7 +45,6 @@ void lfrfid_scene_write_on_enter(void* context) {
lfrfid_worker_write_start( lfrfid_worker_write_start(
app->lfworker, (LFRFIDProtocol)app->protocol_id, lfrfid_write_callback, app); app->lfworker, (LFRFIDProtocol)app->protocol_id, lfrfid_write_callback, app);
notification_message(app->notifications, &sequence_blink_start_magenta); notification_message(app->notifications, &sequence_blink_start_magenta);
free(settings);
} }
bool lfrfid_scene_write_on_event(void* context, SceneManagerEvent event) { bool lfrfid_scene_write_on_event(void* context, SceneManagerEvent event) {

View File

@@ -1,18 +1,12 @@
#include "../lfrfid_i.h" #include "../lfrfid_i.h"
#include "../../../settings/desktop_settings/desktop_settings_app.h" #include "../../../settings/xtreme_settings/xtreme_assets.h"
void lfrfid_scene_write_success_on_enter(void* context) { void lfrfid_scene_write_success_on_enter(void* context) {
LfRfid* app = context; LfRfid* app = context;
Popup* popup = app->popup; Popup* popup = app->popup;
DesktopSettings* settings = malloc(sizeof(DesktopSettings));
DESKTOP_SETTINGS_LOAD(settings);
popup_set_header(popup, "Successfully\nwritten!", 94, 3, AlignCenter, AlignTop); popup_set_header(popup, "Successfully\nwritten!", 94, 3, AlignCenter, AlignTop);
if(settings->sfw_mode) { popup_set_icon(popup, 0, 6, XTREME_ASSETS()->I_RFIDDolphinSuccess_108x57);
popup_set_icon(popup, 0, 6, &I_RFIDDolphinSuccess_108x57_sfw);
} else {
popup_set_icon(popup, 0, 6, &I_RFIDDolphinSuccess_108x57);
}
popup_set_context(popup, app); popup_set_context(popup, app);
popup_set_callback(popup, lfrfid_popup_timeout_callback); popup_set_callback(popup, lfrfid_popup_timeout_callback);
popup_set_timeout(popup, 1500); popup_set_timeout(popup, 1500);
@@ -20,7 +14,6 @@ void lfrfid_scene_write_success_on_enter(void* context) {
view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewPopup); view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewPopup);
notification_message_block(app->notifications, &sequence_set_green_255); notification_message_block(app->notifications, &sequence_set_green_255);
free(settings);
} }
bool lfrfid_scene_write_success_on_event(void* context, SceneManagerEvent event) { bool lfrfid_scene_write_success_on_event(void* context, SceneManagerEvent event) {

View File

@@ -1,5 +1,5 @@
#include "../nfc_i.h" #include "../nfc_i.h"
#include "../../../settings/desktop_settings/desktop_settings_app.h" #include "../../../settings/xtreme_settings/xtreme_assets.h"
void nfc_scene_delete_success_popup_callback(void* context) { void nfc_scene_delete_success_popup_callback(void* context) {
Nfc* nfc = context; Nfc* nfc = context;
@@ -8,23 +8,16 @@ void nfc_scene_delete_success_popup_callback(void* context) {
void nfc_scene_delete_success_on_enter(void* context) { void nfc_scene_delete_success_on_enter(void* context) {
Nfc* nfc = context; Nfc* nfc = context;
DesktopSettings* settings = malloc(sizeof(DesktopSettings));
DESKTOP_SETTINGS_LOAD(settings);
// Setup view // Setup view
Popup* popup = nfc->popup; Popup* popup = nfc->popup;
if(settings->sfw_mode) { popup_set_icon(popup, 0, 2, XTREME_ASSETS()->I_DolphinMafia_115x62);
popup_set_icon(popup, 0, 2, &I_DolphinMafia_115x62_sfw);
} else {
popup_set_icon(popup, 0, 2, &I_DolphinMafia_115x62);
}
popup_set_header(popup, "Deleted", 83, 19, AlignLeft, AlignBottom); popup_set_header(popup, "Deleted", 83, 19, AlignLeft, AlignBottom);
popup_set_timeout(popup, 1500); popup_set_timeout(popup, 1500);
popup_set_context(popup, nfc); popup_set_context(popup, nfc);
popup_set_callback(popup, nfc_scene_delete_success_popup_callback); popup_set_callback(popup, nfc_scene_delete_success_popup_callback);
popup_enable_timeout(popup); popup_enable_timeout(popup);
view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup);
free(settings);
} }
bool nfc_scene_delete_success_on_event(void* context, SceneManagerEvent event) { bool nfc_scene_delete_success_on_event(void* context, SceneManagerEvent event) {

View File

@@ -1,5 +1,5 @@
#include "../nfc_i.h" #include "../nfc_i.h"
#include "../../../settings/desktop_settings/desktop_settings_app.h" #include "../../../settings/xtreme_settings/xtreme_assets.h"
#define NFC_SCENE_EMULATE_NFCV_LOG_SIZE_MAX (100) #define NFC_SCENE_EMULATE_NFCV_LOG_SIZE_MAX (100)
@@ -38,14 +38,7 @@ static void nfc_scene_emulate_nfcv_widget_config(Nfc* nfc, bool data_received) {
FuriString* info_str; FuriString* info_str;
info_str = furi_string_alloc(); info_str = furi_string_alloc();
DesktopSettings* settings = malloc(sizeof(DesktopSettings)); widget_add_icon_element(widget, 0, 3, XTREME_ASSETS()->I_RFIDDolphinSend_97x61);
DESKTOP_SETTINGS_LOAD(settings);
if(settings->sfw_mode) {
widget_add_icon_element(widget, 0, 3, &I_RFIDDolphinSend_97x61_sfw);
} else {
widget_add_icon_element(widget, 0, 3, &I_RFIDDolphinSend_97x61);
}
widget_add_string_element( widget_add_string_element(
widget, 89, 32, AlignCenter, AlignTop, FontPrimary, "Emulating NfcV"); widget, 89, 32, AlignCenter, AlignTop, FontPrimary, "Emulating NfcV");
@@ -64,7 +57,6 @@ static void nfc_scene_emulate_nfcv_widget_config(Nfc* nfc, bool data_received) {
widget_add_button_element( widget_add_button_element(
widget, GuiButtonTypeCenter, "Log", nfc_scene_emulate_nfcv_widget_callback, nfc); widget, GuiButtonTypeCenter, "Log", nfc_scene_emulate_nfcv_widget_callback, nfc);
} }
free(settings);
} }
void nfc_scene_emulate_nfcv_on_enter(void* context) { void nfc_scene_emulate_nfcv_on_enter(void* context) {

View File

@@ -1,5 +1,5 @@
#include "../nfc_i.h" #include "../nfc_i.h"
#include "../../../settings/desktop_settings/desktop_settings_app.h" #include "../../../settings/xtreme_settings/xtreme_assets.h"
#define NFC_SCENE_EMULATE_UID_LOG_SIZE_MAX (200) #define NFC_SCENE_EMULATE_UID_LOG_SIZE_MAX (200)
@@ -37,14 +37,8 @@ static void nfc_scene_emulate_uid_widget_config(Nfc* nfc, bool data_received) {
widget_reset(widget); widget_reset(widget);
FuriString* info_str; FuriString* info_str;
info_str = furi_string_alloc(); info_str = furi_string_alloc();
DesktopSettings* settings = malloc(sizeof(DesktopSettings));
DESKTOP_SETTINGS_LOAD(settings);
if(settings->sfw_mode) { widget_add_icon_element(widget, 0, 3, XTREME_ASSETS()->I_NFC_dolphin_emulation_47x61);
widget_add_icon_element(widget, 0, 3, &I_NFC_dolphin_emulation_47x61_sfw);
} else {
widget_add_icon_element(widget, 0, 3, &I_NFC_dolphin_emulation_47x61);
}
widget_add_string_element(widget, 57, 13, AlignLeft, AlignTop, FontPrimary, "Emulating UID"); widget_add_string_element(widget, 57, 13, AlignLeft, AlignTop, FontPrimary, "Emulating UID");
if(strcmp(nfc->dev->dev_name, "") != 0) { if(strcmp(nfc->dev->dev_name, "") != 0) {
furi_string_printf(info_str, "%s", nfc->dev->dev_name); furi_string_printf(info_str, "%s", nfc->dev->dev_name);
@@ -61,7 +55,6 @@ static void nfc_scene_emulate_uid_widget_config(Nfc* nfc, bool data_received) {
widget_add_button_element( widget_add_button_element(
widget, GuiButtonTypeCenter, "Log", nfc_scene_emulate_uid_widget_callback, nfc); widget, GuiButtonTypeCenter, "Log", nfc_scene_emulate_uid_widget_callback, nfc);
} }
free(settings);
} }
void nfc_scene_emulate_uid_on_enter(void* context) { void nfc_scene_emulate_uid_on_enter(void* context) {

View File

@@ -1,5 +1,5 @@
#include "../nfc_i.h" #include "../nfc_i.h"
#include "../../../settings/desktop_settings/desktop_settings_app.h" #include "../../../settings/xtreme_settings/xtreme_assets.h"
#define NFC_MF_CLASSIC_DATA_NOT_CHANGED (0UL) #define NFC_MF_CLASSIC_DATA_NOT_CHANGED (0UL)
#define NFC_MF_CLASSIC_DATA_CHANGED (1UL) #define NFC_MF_CLASSIC_DATA_CHANGED (1UL)
@@ -15,8 +15,6 @@ bool nfc_mf_classic_emulate_worker_callback(NfcWorkerEvent event, void* context)
void nfc_scene_mf_classic_emulate_on_enter(void* context) { void nfc_scene_mf_classic_emulate_on_enter(void* context) {
Nfc* nfc = context; Nfc* nfc = context;
DesktopSettings* settings = malloc(sizeof(DesktopSettings));
DESKTOP_SETTINGS_LOAD(settings);
// Setup view // Setup view
Popup* popup = nfc->popup; Popup* popup = nfc->popup;
@@ -26,11 +24,7 @@ void nfc_scene_mf_classic_emulate_on_enter(void* context) {
} else { } else {
nfc_text_store_set(nfc, "MIFARE\nClassic"); nfc_text_store_set(nfc, "MIFARE\nClassic");
} }
if(settings->sfw_mode) { popup_set_icon(popup, 0, 3, XTREME_ASSETS()->I_NFC_dolphin_emulation_47x61);
popup_set_icon(popup, 0, 3, &I_NFC_dolphin_emulation_47x61_sfw);
} else {
popup_set_icon(popup, 0, 3, &I_NFC_dolphin_emulation_47x61);
}
popup_set_text(popup, nfc->text_store, 90, 28, AlignCenter, AlignTop); popup_set_text(popup, nfc->text_store, 90, 28, AlignCenter, AlignTop);
// Setup and start worker // Setup and start worker
@@ -42,7 +36,6 @@ void nfc_scene_mf_classic_emulate_on_enter(void* context) {
nfc_mf_classic_emulate_worker_callback, nfc_mf_classic_emulate_worker_callback,
nfc); nfc);
nfc_blink_emulate_start(nfc); nfc_blink_emulate_start(nfc);
free(settings);
} }
bool nfc_scene_mf_classic_emulate_on_event(void* context, SceneManagerEvent event) { bool nfc_scene_mf_classic_emulate_on_event(void* context, SceneManagerEvent event) {

View File

@@ -1,6 +1,6 @@
#include "../nfc_i.h" #include "../nfc_i.h"
#include <dolphin/dolphin.h> #include <dolphin/dolphin.h>
#include "../../../settings/desktop_settings/desktop_settings_app.h" #include "../../../settings/xtreme_settings/xtreme_assets.h"
void nfc_scene_mf_classic_update_success_popup_callback(void* context) { void nfc_scene_mf_classic_update_success_popup_callback(void* context) {
Nfc* nfc = context; Nfc* nfc = context;
@@ -10,17 +10,11 @@ void nfc_scene_mf_classic_update_success_popup_callback(void* context) {
void nfc_scene_mf_classic_update_success_on_enter(void* context) { void nfc_scene_mf_classic_update_success_on_enter(void* context) {
Nfc* nfc = context; Nfc* nfc = context;
DOLPHIN_DEED(DolphinDeedNfcSave); DOLPHIN_DEED(DolphinDeedNfcSave);
DesktopSettings* settings = malloc(sizeof(DesktopSettings));
DESKTOP_SETTINGS_LOAD(settings);
notification_message(nfc->notifications, &sequence_success); notification_message(nfc->notifications, &sequence_success);
Popup* popup = nfc->popup; Popup* popup = nfc->popup;
if(settings->sfw_mode) { popup_set_icon(popup, 32, 5, XTREME_ASSETS()->I_DolphinNice_96x59);
popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59_sfw);
} else {
popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59);
}
popup_set_header(popup, "Updated!", 11, 20, AlignLeft, AlignBottom); popup_set_header(popup, "Updated!", 11, 20, AlignLeft, AlignBottom);
popup_set_timeout(popup, 1500); popup_set_timeout(popup, 1500);
popup_set_context(popup, nfc); popup_set_context(popup, nfc);
@@ -28,7 +22,6 @@ void nfc_scene_mf_classic_update_success_on_enter(void* context) {
popup_enable_timeout(popup); popup_enable_timeout(popup);
view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup);
free(settings);
} }
bool nfc_scene_mf_classic_update_success_on_event(void* context, SceneManagerEvent event) { bool nfc_scene_mf_classic_update_success_on_event(void* context, SceneManagerEvent event) {

View File

@@ -1,6 +1,6 @@
#include "../nfc_i.h" #include "../nfc_i.h"
#include <dolphin/dolphin.h> #include <dolphin/dolphin.h>
#include "../../../settings/desktop_settings/desktop_settings_app.h" #include "../../../settings/xtreme_settings/xtreme_assets.h"
void nfc_scene_mf_classic_write_success_popup_callback(void* context) { void nfc_scene_mf_classic_write_success_popup_callback(void* context) {
Nfc* nfc = context; Nfc* nfc = context;
@@ -10,17 +10,11 @@ void nfc_scene_mf_classic_write_success_popup_callback(void* context) {
void nfc_scene_mf_classic_write_success_on_enter(void* context) { void nfc_scene_mf_classic_write_success_on_enter(void* context) {
Nfc* nfc = context; Nfc* nfc = context;
DOLPHIN_DEED(DolphinDeedNfcSave); DOLPHIN_DEED(DolphinDeedNfcSave);
DesktopSettings* settings = malloc(sizeof(DesktopSettings));
DESKTOP_SETTINGS_LOAD(settings);
notification_message(nfc->notifications, &sequence_success); notification_message(nfc->notifications, &sequence_success);
Popup* popup = nfc->popup; Popup* popup = nfc->popup;
if(settings->sfw_mode) { popup_set_icon(popup, 32, 5, XTREME_ASSETS()->I_DolphinNice_96x59);
popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59_sfw);
} else {
popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59);
}
popup_set_header(popup, "Successfully\nwritten", 13, 22, AlignLeft, AlignBottom); popup_set_header(popup, "Successfully\nwritten", 13, 22, AlignLeft, AlignBottom);
popup_set_timeout(popup, 1500); popup_set_timeout(popup, 1500);
popup_set_context(popup, nfc); popup_set_context(popup, nfc);
@@ -28,7 +22,6 @@ void nfc_scene_mf_classic_write_success_on_enter(void* context) {
popup_enable_timeout(popup); popup_enable_timeout(popup);
view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup);
free(settings);
} }
bool nfc_scene_mf_classic_write_success_on_event(void* context, SceneManagerEvent event) { bool nfc_scene_mf_classic_write_success_on_event(void* context, SceneManagerEvent event) {

View File

@@ -1,6 +1,6 @@
#include "../nfc_i.h" #include "../nfc_i.h"
#include <lib/nfc/protocols/mifare_ultralight.h> #include <lib/nfc/protocols/mifare_ultralight.h>
#include "../../../settings/desktop_settings/desktop_settings_app.h" #include "../../../settings/xtreme_settings/xtreme_assets.h"
#define NFC_SCENE_MF_ULTRALIGHT_EMULATE_LOG_SIZE_MAX (200) #define NFC_SCENE_MF_ULTRALIGHT_EMULATE_LOG_SIZE_MAX (200)
@@ -66,14 +66,7 @@ void nfc_scene_mf_ultralight_emulate_widget_config(Nfc* nfc, bool auth_attempted
FuriString* info_str; FuriString* info_str;
info_str = furi_string_alloc(); info_str = furi_string_alloc();
DesktopSettings* settings = malloc(sizeof(DesktopSettings)); widget_add_icon_element(widget, 0, 3, XTREME_ASSETS()->I_NFC_dolphin_emulation_47x61);
DESKTOP_SETTINGS_LOAD(settings);
if(settings->sfw_mode) {
widget_add_icon_element(widget, 0, 3, &I_NFC_dolphin_emulation_47x61_sfw);
} else {
widget_add_icon_element(widget, 0, 3, &I_NFC_dolphin_emulation_47x61);
}
if(strcmp(nfc->dev->dev_name, "")) { if(strcmp(nfc->dev->dev_name, "")) {
furi_string_printf(info_str, "Emulating\n%s", nfc->dev->dev_name); furi_string_printf(info_str, "Emulating\n%s", nfc->dev->dev_name);
@@ -92,15 +85,12 @@ void nfc_scene_mf_ultralight_emulate_widget_config(Nfc* nfc, bool auth_attempted
nfc_scene_mf_ultralight_emulate_widget_callback, nfc_scene_mf_ultralight_emulate_widget_callback,
nfc); nfc);
} }
free(settings);
} }
void nfc_scene_mf_ultralight_emulate_on_enter(void* context) { void nfc_scene_mf_ultralight_emulate_on_enter(void* context) {
Nfc* nfc = context; Nfc* nfc = context;
uint32_t state = uint32_t state =
scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfUltralightEmulate); scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfUltralightEmulate);
DesktopSettings* settings = malloc(sizeof(DesktopSettings));
DESKTOP_SETTINGS_LOAD(settings);
// Setup Widget // Setup Widget
nfc_scene_mf_ultralight_emulate_widget_config(nfc, false); nfc_scene_mf_ultralight_emulate_widget_config(nfc, false);
@@ -123,11 +113,7 @@ void nfc_scene_mf_ultralight_emulate_on_enter(void* context) {
} else { } else {
nfc_text_store_set(nfc, "MIFARE\nNTAG"); nfc_text_store_set(nfc, "MIFARE\nNTAG");
} }
if(settings->sfw_mode) { popup_set_icon(popup, 0, 3, XTREME_ASSETS()->I_NFC_dolphin_emulation_47x61);
popup_set_icon(popup, 0, 3, &I_NFC_dolphin_emulation_47x61_sfw);
} else {
popup_set_icon(popup, 0, 3, &I_NFC_dolphin_emulation_47x61);
}
popup_set_text(popup, nfc->text_store, 90, 28, AlignCenter, AlignTop); popup_set_text(popup, nfc->text_store, 90, 28, AlignCenter, AlignTop);
// Set Widget state and view // Set Widget state and view
@@ -143,7 +129,6 @@ void nfc_scene_mf_ultralight_emulate_on_enter(void* context) {
nfc_mf_ultralight_emulate_worker_callback, nfc_mf_ultralight_emulate_worker_callback,
nfc); nfc);
nfc_blink_emulate_start(nfc); nfc_blink_emulate_start(nfc);
free(settings);
} }
bool nfc_scene_mf_ultralight_emulate_on_event(void* context, SceneManagerEvent event) { bool nfc_scene_mf_ultralight_emulate_on_event(void* context, SceneManagerEvent event) {

View File

@@ -1,5 +1,5 @@
#include "../nfc_i.h" #include "../nfc_i.h"
#include "../../../settings/desktop_settings/desktop_settings_app.h" #include "../../../settings/xtreme_settings/xtreme_assets.h"
void nfc_scene_restore_original_popup_callback(void* context) { void nfc_scene_restore_original_popup_callback(void* context) {
Nfc* nfc = context; Nfc* nfc = context;
@@ -8,23 +8,16 @@ void nfc_scene_restore_original_popup_callback(void* context) {
void nfc_scene_restore_original_on_enter(void* context) { void nfc_scene_restore_original_on_enter(void* context) {
Nfc* nfc = context; Nfc* nfc = context;
DesktopSettings* settings = malloc(sizeof(DesktopSettings));
DESKTOP_SETTINGS_LOAD(settings);
// Setup view // Setup view
Popup* popup = nfc->popup; Popup* popup = nfc->popup;
if(settings->sfw_mode) { popup_set_icon(popup, 32, 5, XTREME_ASSETS()->I_DolphinNice_96x59);
popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59_sfw);
} else {
popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59);
}
popup_set_header(popup, "Original file\nrestored", 13, 22, AlignLeft, AlignBottom); popup_set_header(popup, "Original file\nrestored", 13, 22, AlignLeft, AlignBottom);
popup_set_timeout(popup, 1500); popup_set_timeout(popup, 1500);
popup_set_context(popup, nfc); popup_set_context(popup, nfc);
popup_set_callback(popup, nfc_scene_restore_original_popup_callback); popup_set_callback(popup, nfc_scene_restore_original_popup_callback);
popup_enable_timeout(popup); popup_enable_timeout(popup);
view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup);
free(settings);
} }
bool nfc_scene_restore_original_on_event(void* context, SceneManagerEvent event) { bool nfc_scene_restore_original_on_event(void* context, SceneManagerEvent event) {

View File

@@ -1,25 +1,18 @@
#include "../nfc_i.h" #include "../nfc_i.h"
#include "../../../settings/desktop_settings/desktop_settings_app.h" #include "../../../settings/xtreme_settings/xtreme_assets.h"
void nfc_scene_rpc_on_enter(void* context) { void nfc_scene_rpc_on_enter(void* context) {
Nfc* nfc = context; Nfc* nfc = context;
Popup* popup = nfc->popup; Popup* popup = nfc->popup;
DesktopSettings* settings = malloc(sizeof(DesktopSettings));
DESKTOP_SETTINGS_LOAD(settings);
popup_set_header(popup, "NFC", 89, 42, AlignCenter, AlignBottom); popup_set_header(popup, "NFC", 89, 42, AlignCenter, AlignBottom);
popup_set_text(popup, "RPC mode", 89, 44, AlignCenter, AlignTop); popup_set_text(popup, "RPC mode", 89, 44, AlignCenter, AlignTop);
if(settings->sfw_mode) { popup_set_icon(popup, 0, 12, XTREME_ASSETS()->I_NFC_dolphin_emulation_47x61);
popup_set_icon(popup, 0, 12, &I_NFC_dolphin_emulation_47x61_sfw);
} else {
popup_set_icon(popup, 0, 12, &I_NFC_dolphin_emulation_47x61);
}
view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup);
notification_message(nfc->notifications, &sequence_display_backlight_on); notification_message(nfc->notifications, &sequence_display_backlight_on);
free(settings);
} }
static bool nfc_scene_rpc_emulate_callback(NfcWorkerEvent event, void* context) { static bool nfc_scene_rpc_emulate_callback(NfcWorkerEvent event, void* context) {

View File

@@ -1,5 +1,5 @@
#include "../nfc_i.h" #include "../nfc_i.h"
#include "../../../settings/desktop_settings/desktop_settings_app.h" #include "../../../settings/xtreme_settings/xtreme_assets.h"
void nfc_scene_save_success_popup_callback(void* context) { void nfc_scene_save_success_popup_callback(void* context) {
Nfc* nfc = context; Nfc* nfc = context;
@@ -8,23 +8,16 @@ void nfc_scene_save_success_popup_callback(void* context) {
void nfc_scene_save_success_on_enter(void* context) { void nfc_scene_save_success_on_enter(void* context) {
Nfc* nfc = context; Nfc* nfc = context;
DesktopSettings* settings = malloc(sizeof(DesktopSettings));
DESKTOP_SETTINGS_LOAD(settings);
// Setup view // Setup view
Popup* popup = nfc->popup; Popup* popup = nfc->popup;
if(settings->sfw_mode) { popup_set_icon(popup, 32, 5, XTREME_ASSETS()->I_DolphinNice_96x59);
popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59_sfw);
} else {
popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59);
}
popup_set_header(popup, "Saved!", 13, 22, AlignLeft, AlignBottom); popup_set_header(popup, "Saved!", 13, 22, AlignLeft, AlignBottom);
popup_set_timeout(popup, 1500); popup_set_timeout(popup, 1500);
popup_set_context(popup, nfc); popup_set_context(popup, nfc);
popup_set_callback(popup, nfc_scene_save_success_popup_callback); popup_set_callback(popup, nfc_scene_save_success_popup_callback);
popup_enable_timeout(popup); popup_enable_timeout(popup);
view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup);
free(settings);
} }
bool nfc_scene_save_success_on_event(void* context, SceneManagerEvent event) { bool nfc_scene_save_success_on_event(void* context, SceneManagerEvent event) {

View File

@@ -1,6 +1,6 @@
#include "../subghz_i.h" #include "../subghz_i.h"
#include "../helpers/subghz_custom_event.h" #include "../helpers/subghz_custom_event.h"
#include "../../../settings/desktop_settings/desktop_settings_app.h" #include "../../../settings/xtreme_settings/xtreme_assets.h"
void subghz_scene_delete_success_popup_callback(void* context) { void subghz_scene_delete_success_popup_callback(void* context) {
SubGhz* subghz = context; SubGhz* subghz = context;
@@ -10,23 +10,16 @@ void subghz_scene_delete_success_popup_callback(void* context) {
void subghz_scene_delete_success_on_enter(void* context) { void subghz_scene_delete_success_on_enter(void* context) {
SubGhz* subghz = context; SubGhz* subghz = context;
DesktopSettings* settings = malloc(sizeof(DesktopSettings));
DESKTOP_SETTINGS_LOAD(settings);
// Setup view // Setup view
Popup* popup = subghz->popup; Popup* popup = subghz->popup;
if(settings->sfw_mode) { popup_set_icon(popup, 0, 2, XTREME_ASSETS()->I_DolphinMafia_115x62);
popup_set_icon(popup, 0, 2, &I_DolphinMafia_115x62_sfw);
} else {
popup_set_icon(popup, 0, 2, &I_DolphinMafia_115x62);
}
popup_set_header(popup, "Deleted", 83, 19, AlignLeft, AlignBottom); popup_set_header(popup, "Deleted", 83, 19, AlignLeft, AlignBottom);
popup_set_timeout(popup, 1500); popup_set_timeout(popup, 1500);
popup_set_context(popup, subghz); popup_set_context(popup, subghz);
popup_set_callback(popup, subghz_scene_delete_success_popup_callback); popup_set_callback(popup, subghz_scene_delete_success_popup_callback);
popup_enable_timeout(popup); popup_enable_timeout(popup);
view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdPopup); view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdPopup);
free(settings);
} }
bool subghz_scene_delete_success_on_event(void* context, SceneManagerEvent event) { bool subghz_scene_delete_success_on_event(void* context, SceneManagerEvent event) {

View File

@@ -1,7 +1,7 @@
#include "../subghz_i.h" #include "../subghz_i.h"
#include <lib/subghz/protocols/keeloq.h> #include <lib/subghz/protocols/keeloq.h>
#include <lib/subghz/protocols/star_line.h> #include <lib/subghz/protocols/star_line.h>
#include "../../../settings/desktop_settings/desktop_settings_app.h" #include "../../../settings/xtreme_settings/xtreme_assets.h"
typedef enum { typedef enum {
SubGhzRpcStateIdle, SubGhzRpcStateIdle,
@@ -11,23 +11,16 @@ typedef enum {
void subghz_scene_rpc_on_enter(void* context) { void subghz_scene_rpc_on_enter(void* context) {
SubGhz* subghz = context; SubGhz* subghz = context;
Popup* popup = subghz->popup; Popup* popup = subghz->popup;
DesktopSettings* settings = malloc(sizeof(DesktopSettings));
DESKTOP_SETTINGS_LOAD(settings);
popup_set_header(popup, "Sub-GHz", 89, 42, AlignCenter, AlignBottom); popup_set_header(popup, "Sub-GHz", 89, 42, AlignCenter, AlignBottom);
popup_set_text(popup, "RPC mode", 89, 44, AlignCenter, AlignTop); popup_set_text(popup, "RPC mode", 89, 44, AlignCenter, AlignTop);
if(settings->sfw_mode) { popup_set_icon(popup, 0, 12, XTREME_ASSETS()->I_RFIDDolphinSend_97x61);
popup_set_icon(popup, 0, 12, &I_RFIDDolphinSend_97x61_sfw);
} else {
popup_set_icon(popup, 0, 12, &I_RFIDDolphinSend_97x61);
}
view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdPopup); view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdPopup);
scene_manager_set_scene_state(subghz->scene_manager, SubGhzSceneRpc, SubGhzRpcStateIdle); scene_manager_set_scene_state(subghz->scene_manager, SubGhzSceneRpc, SubGhzRpcStateIdle);
notification_message(subghz->notifications, &sequence_display_backlight_on); notification_message(subghz->notifications, &sequence_display_backlight_on);
free(settings);
} }
bool subghz_scene_rpc_on_event(void* context, SceneManagerEvent event) { bool subghz_scene_rpc_on_event(void* context, SceneManagerEvent event) {

View File

@@ -1,6 +1,6 @@
#include "../subghz_i.h" #include "../subghz_i.h"
#include "../helpers/subghz_custom_event.h" #include "../helpers/subghz_custom_event.h"
#include "../../../settings/desktop_settings/desktop_settings_app.h" #include "../../../settings/xtreme_settings/xtreme_assets.h"
void subghz_scene_save_success_popup_callback(void* context) { void subghz_scene_save_success_popup_callback(void* context) {
SubGhz* subghz = context; SubGhz* subghz = context;
@@ -9,23 +9,16 @@ void subghz_scene_save_success_popup_callback(void* context) {
void subghz_scene_save_success_on_enter(void* context) { void subghz_scene_save_success_on_enter(void* context) {
SubGhz* subghz = context; SubGhz* subghz = context;
DesktopSettings* settings = malloc(sizeof(DesktopSettings));
DESKTOP_SETTINGS_LOAD(settings);
// Setup view // Setup view
Popup* popup = subghz->popup; Popup* popup = subghz->popup;
if(settings->sfw_mode) { popup_set_icon(popup, 32, 5, XTREME_ASSETS()->I_DolphinNice_96x59);
popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59_sfw);
} else {
popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59);
}
popup_set_header(popup, "Saved!", 13, 22, AlignLeft, AlignBottom); popup_set_header(popup, "Saved!", 13, 22, AlignLeft, AlignBottom);
popup_set_timeout(popup, 1500); popup_set_timeout(popup, 1500);
popup_set_context(popup, subghz); popup_set_context(popup, subghz);
popup_set_callback(popup, subghz_scene_save_success_popup_callback); popup_set_callback(popup, subghz_scene_save_success_popup_callback);
popup_enable_timeout(popup); popup_enable_timeout(popup);
view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdPopup); view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdPopup);
free(settings);
} }
bool subghz_scene_save_success_on_event(void* context, SceneManagerEvent event) { bool subghz_scene_save_success_on_event(void* context, SceneManagerEvent event) {

View File

@@ -7,7 +7,7 @@
#include <assets_icons.h> #include <assets_icons.h>
#include <m-array.h> #include <m-array.h>
#include "../../../settings/desktop_settings/desktop_settings_app.h" #include "../../../settings/xtreme_settings/xtreme_assets.h"
#define FRAME_HEIGHT 12 #define FRAME_HEIGHT 12
#define MAX_LEN_PX 111 #define MAX_LEN_PX 111
@@ -195,8 +195,6 @@ void subghz_view_receiver_draw(Canvas* canvas, SubGhzViewReceiverModel* model) {
canvas_clear(canvas); canvas_clear(canvas);
canvas_set_color(canvas, ColorBlack); canvas_set_color(canvas, ColorBlack);
canvas_set_font(canvas, FontSecondary); canvas_set_font(canvas, FontSecondary);
DesktopSettings* settings = malloc(sizeof(DesktopSettings));
DESKTOP_SETTINGS_LOAD(settings);
if(model->mode == SubGhzViewReceiverModeLive) { if(model->mode == SubGhzViewReceiverModeLive) {
elements_button_left(canvas, "Config"); elements_button_left(canvas, "Config");
@@ -234,21 +232,13 @@ void subghz_view_receiver_draw(Canvas* canvas, SubGhzViewReceiverModel* model) {
if(model->history_item == 0) { if(model->history_item == 0) {
if(model->mode == SubGhzViewReceiverModeLive) { if(model->mode == SubGhzViewReceiverModeLive) {
if(settings->sfw_mode) { canvas_draw_icon(canvas, 0, 0, XTREME_ASSETS()->I_Scanning_123x52);
canvas_draw_icon(canvas, 0, 0, &I_Scanning_123x52_sfw);
} else {
canvas_draw_icon(canvas, 0, 0, &I_Scanning_123x52);
}
canvas_set_font(canvas, FontPrimary); canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 63, 46, "Scanning..."); canvas_draw_str(canvas, 63, 46, "Scanning...");
canvas_draw_line(canvas, 46, 51, 125, 51); canvas_draw_line(canvas, 46, 51, 125, 51);
canvas_set_font(canvas, FontSecondary); canvas_set_font(canvas, FontSecondary);
} else { } else {
if(settings->sfw_mode) { canvas_draw_icon(canvas, 0, 0, XTREME_ASSETS()->I_Scanning_123x52);
canvas_draw_icon(canvas, 0, 0, &I_Scanning_123x52_sfw);
} else {
canvas_draw_icon(canvas, 0, 0, &I_Scanning_123x52);
}
canvas_set_font(canvas, FontPrimary); canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 63, 46, "Decoding..."); canvas_draw_str(canvas, 63, 46, "Decoding...");
canvas_set_font(canvas, FontSecondary); canvas_set_font(canvas, FontSecondary);
@@ -302,7 +292,6 @@ void subghz_view_receiver_draw(Canvas* canvas, SubGhzViewReceiverModel* model) {
canvas_draw_str(canvas, 96, 62, furi_string_get_cstr(model->history_stat_str)); canvas_draw_str(canvas, 96, 62, furi_string_get_cstr(model->history_stat_str));
} break; } break;
} }
free(settings);
} }
static void subghz_view_receiver_timer_callback(void* context) { static void subghz_view_receiver_timer_callback(void* context) {

View File

@@ -1,5 +1,5 @@
#include "../u2f_app_i.h" #include "../u2f_app_i.h"
#include "../../../settings/desktop_settings/desktop_settings_app.h" #include "../../../settings/xtreme_settings/xtreme_settings.h"
static void u2f_scene_error_event_callback(GuiButtonType result, InputType type, void* context) { static void u2f_scene_error_event_callback(GuiButtonType result, InputType type, void* context) {
furi_assert(context); furi_assert(context);
@@ -12,8 +12,6 @@ static void u2f_scene_error_event_callback(GuiButtonType result, InputType type,
void u2f_scene_error_on_enter(void* context) { void u2f_scene_error_on_enter(void* context) {
U2fApp* app = context; U2fApp* app = context;
DesktopSettings* settings = malloc(sizeof(DesktopSettings));
DESKTOP_SETTINGS_LOAD(settings);
if(app->error == U2fAppErrorNoFiles) { if(app->error == U2fAppErrorNoFiles) {
widget_add_icon_element(app->widget, 0, 0, &I_SDQuestion_35x43); widget_add_icon_element(app->widget, 0, 0, &I_SDQuestion_35x43);
@@ -29,18 +27,7 @@ void u2f_scene_error_on_enter(void* context) {
app->widget, GuiButtonTypeLeft, "Back", u2f_scene_error_event_callback, app); app->widget, GuiButtonTypeLeft, "Back", u2f_scene_error_event_callback, app);
} else if(app->error == U2fAppErrorCloseRpc) { } else if(app->error == U2fAppErrorCloseRpc) {
widget_add_icon_element(app->widget, 78, 0, &I_ActiveConnection_50x64); widget_add_icon_element(app->widget, 78, 0, &I_ActiveConnection_50x64);
if(settings->sfw_mode) { if(XTREME_SETTINGS()->nsfw_mode) {
widget_add_string_multiline_element(
app->widget, 3, 2, AlignLeft, AlignTop, FontPrimary, "Connection\nis active!");
widget_add_string_multiline_element(
app->widget,
3,
30,
AlignLeft,
AlignTop,
FontSecondary,
"Disconnect from\nPC or phone to\nuse this function.");
} else {
widget_add_string_multiline_element( widget_add_string_multiline_element(
app->widget, 3, 2, AlignLeft, AlignTop, FontPrimary, "I am not\na whore!"); app->widget, 3, 2, AlignLeft, AlignTop, FontPrimary, "I am not\na whore!");
widget_add_string_multiline_element( widget_add_string_multiline_element(
@@ -51,11 +38,21 @@ void u2f_scene_error_on_enter(void* context) {
AlignTop, AlignTop,
FontSecondary, FontSecondary,
"Pull out from\nPC or phone to\nuse me like this."); "Pull out from\nPC or phone to\nuse me like this.");
} else {
widget_add_string_multiline_element(
app->widget, 3, 2, AlignLeft, AlignTop, FontPrimary, "Connection\nis active!");
widget_add_string_multiline_element(
app->widget,
3,
30,
AlignLeft,
AlignTop,
FontSecondary,
"Disconnect from\nPC or phone to\nuse this function.");
} }
} }
view_dispatcher_switch_to_view(app->view_dispatcher, U2fAppViewError); view_dispatcher_switch_to_view(app->view_dispatcher, U2fAppViewError);
free(settings);
} }
bool u2f_scene_error_on_event(void* context, SceneManagerEvent event) { bool u2f_scene_error_on_event(void* context, SceneManagerEvent event) {

View File

@@ -1,7 +1,7 @@
#include "u2f_view.h" #include "u2f_view.h"
#include <gui/elements.h> #include <gui/elements.h>
#include <assets_icons.h> #include <assets_icons.h>
#include "../../../settings/desktop_settings/desktop_settings_app.h" #include "../../../settings/xtreme_settings/xtreme_assets.h"
struct U2fView { struct U2fView {
View* view; View* view;
@@ -15,74 +15,63 @@ typedef struct {
static void u2f_view_draw_callback(Canvas* canvas, void* _model) { static void u2f_view_draw_callback(Canvas* canvas, void* _model) {
U2fModel* model = _model; U2fModel* model = _model;
DesktopSettings* settings = malloc(sizeof(DesktopSettings));
DESKTOP_SETTINGS_LOAD(settings);
canvas_draw_icon(canvas, 8, 14, &I_Drive_112x35); canvas_draw_icon(canvas, 8, 14, &I_Drive_112x35);
canvas_set_font(canvas, FontSecondary); canvas_set_font(canvas, FontSecondary);
if(model->display_msg == U2fMsgNotConnected) { if(model->display_msg == U2fMsgNotConnected) {
if(settings->sfw_mode) { canvas_draw_icon(canvas, 22, 15, XTREME_ASSETS()->I_Connect_me_62x31);
canvas_draw_icon(canvas, 22, 15, &I_Connect_me_62x31_sfw); if(XTREME_SETTINGS()->nsfw_mode) {
canvas_draw_str_aligned(
canvas, 128 / 2, 3, AlignCenter, AlignTop, "Connect to a device");
} else {
canvas_draw_icon(canvas, 22, 15, &I_Connect_me_62x31);
canvas_draw_str_aligned( canvas_draw_str_aligned(
canvas, 128 / 2, 3, AlignCenter, AlignTop, "Plug me in d-daddy"); canvas, 128 / 2, 3, AlignCenter, AlignTop, "Plug me in d-daddy");
} else {
canvas_draw_str_aligned(
canvas, 128 / 2, 3, AlignCenter, AlignTop, "Connect to a device");
} }
} else if(model->display_msg == U2fMsgIdle) { } else if(model->display_msg == U2fMsgIdle) {
if(settings->sfw_mode) { canvas_draw_icon(canvas, 22, 15, XTREME_ASSETS()->I_Connected_62x31);
canvas_draw_icon(canvas, 22, 15, &I_Connected_62x31_sfw); canvas_draw_str_aligned(canvas, 128 / 2, 3, AlignCenter, AlignTop, "Connected!");
canvas_draw_str_aligned(canvas, 128 / 2, 3, AlignCenter, AlignTop, "Connected!");
} else {
canvas_draw_icon(canvas, 22, 15, &I_Connected_62x31);
canvas_draw_str_aligned(canvas, 128 / 2, 3, AlignCenter, AlignTop, "Connected!");
}
} else if(model->display_msg == U2fMsgRegister) { } else if(model->display_msg == U2fMsgRegister) {
if(settings->sfw_mode) { if(XTREME_SETTINGS()->nsfw_mode) {
elements_button_center(canvas, "OK");
canvas_draw_icon(canvas, 22, 15, &I_Auth_62x31_sfw);
canvas_draw_str_aligned(
canvas, 128 / 2, 3, AlignCenter, AlignTop, "Press OK to register");
} else {
elements_button_center(canvas, "CUM"); elements_button_center(canvas, "CUM");
canvas_draw_icon(canvas, 22, 15, &I_Auth_62x31); canvas_draw_icon(canvas, 22, 15, XTREME_ASSETS()->I_Auth_62x31);
canvas_draw_str_aligned( canvas_draw_str_aligned(
canvas, 128 / 2, 3, AlignCenter, AlignTop, "Press CUM to register"); canvas, 128 / 2, 3, AlignCenter, AlignTop, "Press CUM to register");
} else {
elements_button_center(canvas, "OK");
canvas_draw_icon(canvas, 22, 15, XTREME_ASSETS()->I_Auth_62x31);
canvas_draw_str_aligned(
canvas, 128 / 2, 3, AlignCenter, AlignTop, "Press OK to register");
} }
} else if(model->display_msg == U2fMsgAuth) { } else if(model->display_msg == U2fMsgAuth) {
if(settings->sfw_mode) { if(XTREME_SETTINGS()->nsfw_mode) {
elements_button_center(canvas, "OK");
canvas_draw_icon(canvas, 22, 15, &I_Auth_62x31_sfw);
canvas_draw_str_aligned(
canvas, 128 / 2, 3, AlignCenter, AlignTop, "Press OK to authenticate");
} else {
elements_button_center(canvas, "CUM"); elements_button_center(canvas, "CUM");
canvas_draw_icon(canvas, 22, 15, &I_Auth_62x31); canvas_draw_icon(canvas, 22, 15, XTREME_ASSETS()->I_Auth_62x31);
canvas_draw_str_aligned( canvas_draw_str_aligned(
canvas, 128 / 2, 3, AlignCenter, AlignTop, "Press CUM to authenticate"); canvas, 128 / 2, 3, AlignCenter, AlignTop, "Press CUM to authenticate");
} else {
elements_button_center(canvas, "OK");
canvas_draw_icon(canvas, 22, 15, XTREME_ASSETS()->I_Auth_62x31);
canvas_draw_str_aligned(
canvas, 128 / 2, 3, AlignCenter, AlignTop, "Press OK to authenticate");
} }
} else if(model->display_msg == U2fMsgSuccess) { } else if(model->display_msg == U2fMsgSuccess) {
if(settings->sfw_mode) { canvas_draw_icon(canvas, 22, 15, XTREME_ASSETS()->I_Connected_62x31);
canvas_draw_icon(canvas, 22, 15, &I_Connected_62x31_sfw); if(XTREME_SETTINGS()->nsfw_mode) {
canvas_draw_str_aligned(canvas, 128 / 2, 3, AlignCenter, AlignTop, "Cum released~");
} else {
canvas_draw_str_aligned( canvas_draw_str_aligned(
canvas, 128 / 2, 3, AlignCenter, AlignTop, "Authentication successful!"); canvas, 128 / 2, 3, AlignCenter, AlignTop, "Authentication successful!");
} else {
canvas_draw_icon(canvas, 22, 15, &I_Connected_62x31);
canvas_draw_str_aligned(canvas, 128 / 2, 3, AlignCenter, AlignTop, "Cum released~");
} }
} else if(model->display_msg == U2fMsgError) { } else if(model->display_msg == U2fMsgError) {
if(settings->sfw_mode) { canvas_draw_icon(canvas, 22, 15, XTREME_ASSETS()->I_Error_62x31);
canvas_draw_icon(canvas, 22, 15, &I_Error_62x31_sfw); if(XTREME_SETTINGS()->nsfw_mode) {
canvas_draw_str_aligned(canvas, 128 / 2, 3, AlignCenter, AlignTop, "Unable to cum");
} else {
canvas_draw_str_aligned( canvas_draw_str_aligned(
canvas, 128 / 2, 3, AlignCenter, AlignTop, "Certificate error"); canvas, 128 / 2, 3, AlignCenter, AlignTop, "Certificate error");
} else {
canvas_draw_icon(canvas, 22, 15, &I_Error_62x31);
canvas_draw_str_aligned(canvas, 128 / 2, 3, AlignCenter, AlignTop, "Unable to cum");
} }
} }
free(settings);
} }
static bool u2f_view_input_callback(InputEvent* event, void* context) { static bool u2f_view_input_callback(InputEvent* event, void* context) {

View File

@@ -3,6 +3,7 @@
#include <furi.h> #include <furi.h>
#include <furi_hal.h> #include <furi_hal.h>
#include <storage/storage.h>
#include <input/input.h> #include <input/input.h>
#include <gui/gui.h> #include <gui/gui.h>
#include <stdlib.h> #include <stdlib.h>
@@ -10,24 +11,23 @@
#include <gui/view_dispatcher.h> #include <gui/view_dispatcher.h>
#include <gui/scene_manager.h> #include <gui/scene_manager.h>
#include <math.h> #include <math.h>
#include <notification/notification.h>
#ifndef PI #include <notification/notification_messages.h>
#define PI 3.14159265358979f
#endif
#define TAG "Asteroids" // Used for logging #define TAG "Asteroids" // Used for logging
#define DEBUG_MSG 1 #define DEBUG_MSG 1
#define SCREEN_XRES 128 #define SCREEN_XRES 128
#define SCREEN_YRES 64 #define SCREEN_YRES 64
#define GAME_START_LIVES 3 #define GAME_START_LIVES 3
#define TTLBUL 30 /* Bullet time to live, in ticks. */
/* The game uses the OK button both to fire and to accelerate the ship. #define MAXBUL 5 /* Max bullets on the screen. */
* This makes it a lot more playable since the finger does not have to #define MAXAST 32 /* Max asteroids on the screen. */
* move between two keys. However it is important that the extra time the #define SHIP_HIT_ANIMATION_LEN 15
* player needs to press the button to accelerate instead of just firing #define SAVING_DIRECTORY "/ext/apps_data/asteroids"
* is precisely selected to provide a smooth experience. After a few #define SAVING_FILENAME SAVING_DIRECTORY "/game_asteroids.save"
* attempts, it looks like 70 milliseconds is the right spot. */ #ifndef PI
#define SHIP_ACCELERATION_KEYPRESS_TIME 70 #define PI 3.14159265358979f
#endif
/* ============================ Data structures ============================= */ /* ============================ Data structures ============================= */
@@ -36,7 +36,7 @@ typedef struct Ship {
y, /* Ship y position. */ y, /* Ship y position. */
vx, /* x velocity. */ vx, /* x velocity. */
vy, /* y velocity. */ vy, /* y velocity. */
rot; /* Current rotation. 2*PI full rotation. */ rot; /* Current rotation. 2*PI full ortation. */
} Ship; } Ship;
typedef struct Bullet { typedef struct Bullet {
@@ -51,21 +51,20 @@ typedef struct Asteroid {
uint8_t shape_seed; /* Seed to give random shape. */ uint8_t shape_seed; /* Seed to give random shape. */
} Asteroid; } Asteroid;
#define MAXBUL 10 /* Max bullets on the screen. */
#define MAXAST 32 /* Max asteroids on the screen. */
#define SHIP_HIT_ANIMATION_LEN 15
typedef struct AsteroidsApp { typedef struct AsteroidsApp {
/* GUI */ /* GUI */
Gui* gui; Gui* gui;
ViewPort* view_port; /* We just use a raw viewport and we render ViewPort* view_port; /* We just use a raw viewport and we render
everything into the low level canvas. */ everything into the low level canvas. */
FuriMessageQueue* event_queue; /* Key press events go here. */ FuriMessageQueue* event_queue; /* Keypress events go here. */
/* Game state. */ /* Game state. */
int running; /* Once false exists the app. */ int running; /* Once false exists the app. */
bool gameover; /* Game over status. */ bool gameover; /* Gameover status. */
uint32_t ticks; /* Game ticks. Increments at each refresh. */ uint32_t ticks; /* Game ticks. Increments at each refresh. */
uint32_t score; /* Game score. */ uint32_t score; /* Game score. */
uint32_t highscore; /* Highscore. Shown on Game Over Screen */
bool is_new_highscore; /* Is the last score a new highscore? */
uint32_t lives; /* Number of lives in the current game. */ uint32_t lives; /* Number of lives in the current game. */
uint32_t ship_hit; /* When non zero, the ship was hit by an asteroid uint32_t ship_hit; /* When non zero, the ship was hit by an asteroid
and we need to show an animation as long as and we need to show an animation as long as
@@ -90,10 +89,55 @@ typedef struct AsteroidsApp {
bool fire; /* Short press detected: fire a bullet. */ bool fire; /* Short press detected: fire a bullet. */
} AsteroidsApp; } AsteroidsApp;
/* ============================== Prototypes ================================ */ const NotificationSequence sequence_thrusters = {
&message_vibro_on,
&message_delay_10,
&message_vibro_off,
NULL,
};
const NotificationSequence sequence_brake = {
&message_vibro_on,
&message_delay_10,
&message_delay_1,
&message_delay_1,
&message_vibro_off,
NULL,
};
const NotificationSequence sequence_crash = {
&message_red_255,
&message_vibro_on,
// &message_note_g5, // Play sound but currently disabled
&message_delay_25,
// &message_note_e5,
&message_vibro_off,
&message_sound_off,
NULL,
};
const NotificationSequence sequence_bullet_fired = {
&message_vibro_on,
// &message_note_g5, // Play sound but currently disabled. Need On/Off menu setting
&message_delay_10,
&message_delay_1,
&message_delay_1,
&message_delay_1,
&message_delay_1,
&message_delay_1,
// &message_note_e5,
&message_vibro_off,
&message_sound_off,
NULL,
};
/* ============================== Prototyeps ================================ */
// Only functions called before their definition are here. // Only functions called before their definition are here.
bool load_game(AsteroidsApp* app);
void save_game(AsteroidsApp* app);
void restart_game_after_gameover(AsteroidsApp* app); void restart_game_after_gameover(AsteroidsApp* app);
uint32_t key_pressed_time(AsteroidsApp* app, InputKey key); uint32_t key_pressed_time(AsteroidsApp* app, InputKey key);
@@ -114,7 +158,7 @@ Poly ShipPoly = {{-3, 0, 3}, {-3, 6, -3}, 3};
Poly ShipFirePoly = {{-1.5, 0, 1.5}, {-3, -6, -3}, 3}; Poly ShipFirePoly = {{-1.5, 0, 1.5}, {-3, -6, -3}, 3};
/* Rotate the point of the polygon 'poly' and store the new rotated /* Rotate the point of the poligon 'poly' and store the new rotated
* polygon in 'rot'. The polygon is rotated by an angle 'a', with * polygon in 'rot'. The polygon is rotated by an angle 'a', with
* center at 0,0. */ * center at 0,0. */
void rotate_poly(Poly* rot, Poly* poly, float a) { void rotate_poly(Poly* rot, Poly* poly, float a) {
@@ -165,7 +209,7 @@ void draw_bullet(Canvas* const canvas, Bullet* b) {
/* Draw an asteroid. The asteroid shapes is computed on the fly and /* Draw an asteroid. The asteroid shapes is computed on the fly and
* is not stored in a permanent shape structure. In order to generate * is not stored in a permanent shape structure. In order to generate
* the shape, we use an initial fixed shape that we resize according * the shape, we use an initial fixed shape that we resize according
* to the asteroid size, perturbed according to the asteroid shape * to the asteroid size, perturbate according to the asteroid shape
* seed, and finally draw it rotated of the right amount. */ * seed, and finally draw it rotated of the right amount. */
void draw_asteroid(Canvas* const canvas, Asteroid* ast) { void draw_asteroid(Canvas* const canvas, Asteroid* ast) {
Poly ap; Poly ap;
@@ -241,8 +285,11 @@ void render_callback(Canvas* const canvas, void* ctx) {
/* Draw ship, asteroids, bullets. */ /* Draw ship, asteroids, bullets. */
draw_poly(canvas, &ShipPoly, app->ship.x, app->ship.y, app->ship.rot); draw_poly(canvas, &ShipPoly, app->ship.x, app->ship.y, app->ship.rot);
if(key_pressed_time(app, InputKeyOk) > SHIP_ACCELERATION_KEYPRESS_TIME)
if(key_pressed_time(app, InputKeyUp) > 0) {
notification_message(furi_record_open(RECORD_NOTIFICATION), &sequence_thrusters);
draw_poly(canvas, &ShipFirePoly, app->ship.x, app->ship.y, app->ship.rot); draw_poly(canvas, &ShipFirePoly, app->ship.x, app->ship.y, app->ship.rot);
}
for(int j = 0; j < app->bullets_num; j++) draw_bullet(canvas, &app->bullets[j]); for(int j = 0; j < app->bullets_num; j++) draw_bullet(canvas, &app->bullets[j]);
@@ -252,6 +299,30 @@ void render_callback(Canvas* const canvas, void* ctx) {
if(app->gameover) { if(app->gameover) {
canvas_set_color(canvas, ColorBlack); canvas_set_color(canvas, ColorBlack);
canvas_set_font(canvas, FontPrimary); canvas_set_font(canvas, FontPrimary);
// TODO: if new highscore, display blinking "New High Score"
// Display High Score
if(app->is_new_highscore) {
canvas_draw_str(canvas, 22, 9, "New High Score!");
} else {
canvas_draw_str(canvas, 36, 9, "High Score");
}
// Convert highscore to string
int length = snprintf(NULL, 0, "%lu", app->highscore);
char* str_high_score = malloc(length + 1);
snprintf(str_high_score, length + 1, "%lu", app->highscore);
// Get length to center on screen
int nDigits = 0;
if(app->highscore > 0) {
nDigits = floor(log10(app->highscore)) + 1;
}
// Draw highscore centered
canvas_draw_str(canvas, (SCREEN_XRES / 2) - (nDigits * 2), 20, str_high_score);
free(str_high_score);
canvas_draw_str(canvas, 28, 35, "GAME OVER"); canvas_draw_str(canvas, 28, 35, "GAME OVER");
canvas_set_font(canvas, FontSecondary); canvas_set_font(canvas, FontSecondary);
canvas_draw_str(canvas, 25, 50, "Press OK to restart"); canvas_draw_str(canvas, 25, 50, "Press OK to restart");
@@ -295,6 +366,7 @@ bool objects_are_colliding(float x1, float y1, float r1, float x2, float y2, flo
/* Create a new bullet headed in the same direction of the ship. */ /* Create a new bullet headed in the same direction of the ship. */
void ship_fire_bullet(AsteroidsApp* app) { void ship_fire_bullet(AsteroidsApp* app) {
if(app->bullets_num == MAXBUL) return; if(app->bullets_num == MAXBUL) return;
notification_message(furi_record_open(RECORD_NOTIFICATION), &sequence_bullet_fired);
Bullet* b = &app->bullets[app->bullets_num]; Bullet* b = &app->bullets[app->bullets_num];
b->x = app->ship.x; b->x = app->ship.x;
b->y = app->ship.y; b->y = app->ship.y;
@@ -316,7 +388,7 @@ void ship_fire_bullet(AsteroidsApp* app) {
b->vx += app->ship.vx; b->vx += app->ship.vx;
b->vy += app->ship.vy; b->vy += app->ship.vy;
b->ttl = 50; /* The bullet will disappear after N ticks. */ b->ttl = TTLBUL; /* The bullet will disappear after N ticks. */
app->bullets_num++; app->bullets_num++;
} }
@@ -366,7 +438,7 @@ void remove_asteroid(AsteroidsApp* app, int id) {
/* Called when an asteroid was reached by a bullet. The asteroid /* Called when an asteroid was reached by a bullet. The asteroid
* hit is the one with the specified 'id'. */ * hit is the one with the specified 'id'. */
void asteroid_was_hit(AsteroidsApp* app, int id) { void asteroid_was_hit(AsteroidsApp* app, int id) {
float sizelimit = 6; // Smaller than that, they disappear in one shot. float sizelimit = 6; // Smaller than that polverize in one shot.
Asteroid* a = &app->asteroids[id]; Asteroid* a = &app->asteroids[id];
/* Asteroid is large enough to break into fragments. */ /* Asteroid is large enough to break into fragments. */
@@ -387,17 +459,19 @@ void asteroid_was_hit(AsteroidsApp* app, int id) {
} }
} else { } else {
app->score++; app->score++;
if(app->score > app->highscore) {
app->is_new_highscore = true;
app->highscore = app->score; // Show on Game Over Screen and future main menu
}
} }
} }
/* Set game over state. When in game-over mode, the game displays a /* Set gameover state. When in game-over mode, the game displays a gameover
* game over text with a background of many asteroids floating around. */ * text with a background of many asteroids floating around. */
void game_over(AsteroidsApp* app) { void game_over(AsteroidsApp* app) {
restart_game_after_gameover(app); if(app->is_new_highscore) save_game(app); // Save highscore but only on change
app->gameover = true; app->gameover = true;
int asteroids = 8; app->lives = GAME_START_LIVES; // Show 3 lives in game over screen to match new game start
while(asteroids-- && add_asteroid(app) != NULL)
;
} }
/* Function called when a collision between the asteroid and the /* Function called when a collision between the asteroid and the
@@ -422,16 +496,17 @@ void restart_game(AsteroidsApp* app) {
app->bullets_num = 0; app->bullets_num = 0;
app->last_bullet_tick = 0; app->last_bullet_tick = 0;
app->asteroids_num = 0; app->asteroids_num = 0;
app->ship_hit = 0;
} }
/* Called after game over to restart the game. This function /* Called after gameover to restart the game. This function
* also calls restart_game(). */ * also calls restart_game(). */
void restart_game_after_gameover(AsteroidsApp* app) { void restart_game_after_gameover(AsteroidsApp* app) {
app->gameover = false; app->gameover = false;
app->ticks = 0; app->ticks = 0;
app->score = 0; app->score = 0;
app->ship_hit = 0; app->is_new_highscore = false;
app->lives = GAME_START_LIVES - 1; /* -1 to account for current one. */ app->lives = GAME_START_LIVES - 1;
restart_game(app); restart_game(app);
} }
@@ -505,6 +580,7 @@ void game_tick(void* ctx) {
* 1. Ship was hit, we frozen the game as long as ship_hit isn't zero * 1. Ship was hit, we frozen the game as long as ship_hit isn't zero
* again, and show an animation of a rotating ship. */ * again, and show an animation of a rotating ship. */
if(app->ship_hit) { if(app->ship_hit) {
notification_message(furi_record_open(RECORD_NOTIFICATION), &sequence_crash);
app->ship.rot += 0.5; app->ship.rot += 0.5;
app->ship_hit--; app->ship_hit--;
view_port_update(app->view_port); view_port_update(app->view_port);
@@ -515,7 +591,8 @@ void game_tick(void* ctx) {
} else if(app->gameover) { } else if(app->gameover) {
/* 2. Game over. We need to update only background asteroids. In this /* 2. Game over. We need to update only background asteroids. In this
* state the game just displays a GAME OVER text with the floating * state the game just displays a GAME OVER text with the floating
* asteroids in background. */ * asteroids in backgroud. */
if(key_pressed_time(app, InputKeyOk) > 100) { if(key_pressed_time(app, InputKeyOk) > 100) {
restart_game_after_gameover(app); restart_game_after_gameover(app);
} }
@@ -524,13 +601,14 @@ void game_tick(void* ctx) {
return; return;
} }
/* Handle key presses. */ /* Handle keypresses. */
if(app->pressed[InputKeyLeft]) app->ship.rot -= .35; if(app->pressed[InputKeyLeft]) app->ship.rot -= .35;
if(app->pressed[InputKeyRight]) app->ship.rot += .35; if(app->pressed[InputKeyRight]) app->ship.rot += .35;
if(key_pressed_time(app, InputKeyOk) > SHIP_ACCELERATION_KEYPRESS_TIME) { if(app->pressed[InputKeyUp]) {
app->ship.vx -= 0.5 * (float)sin(app->ship.rot); app->ship.vx -= 0.5 * (float)sin(app->ship.rot);
app->ship.vy += 0.5 * (float)cos(app->ship.rot); app->ship.vy += 0.5 * (float)cos(app->ship.rot);
} else if(app->pressed[InputKeyDown]) { } else if(app->pressed[InputKeyDown]) {
notification_message(furi_record_open(RECORD_NOTIFICATION), &sequence_brake);
app->ship.vx *= 0.75; app->ship.vx *= 0.75;
app->ship.vy *= 0.75; app->ship.vy *= 0.75;
} }
@@ -567,6 +645,41 @@ void game_tick(void* ctx) {
/* ======================== Flipper specific code =========================== */ /* ======================== Flipper specific code =========================== */
bool load_game(AsteroidsApp* app) {
Storage* storage = furi_record_open(RECORD_STORAGE);
File* file = storage_file_alloc(storage);
uint16_t bytes_readed = 0;
if(storage_file_open(file, SAVING_FILENAME, FSAM_READ, FSOM_OPEN_EXISTING)) {
bytes_readed = storage_file_read(file, app, sizeof(AsteroidsApp));
}
storage_file_close(file);
storage_file_free(file);
furi_record_close(RECORD_STORAGE);
return bytes_readed == sizeof(AsteroidsApp);
}
void save_game(AsteroidsApp* app) {
Storage* storage = furi_record_open(RECORD_STORAGE);
if(storage_common_stat(storage, SAVING_DIRECTORY, NULL) == FSE_NOT_EXIST) {
if(!storage_simply_mkdir(storage, SAVING_DIRECTORY)) {
return;
}
}
File* file = storage_file_alloc(storage);
if(storage_file_open(file, SAVING_FILENAME, FSAM_WRITE, FSOM_CREATE_ALWAYS)) {
storage_file_write(file, app, sizeof(AsteroidsApp));
}
storage_file_close(file);
storage_file_free(file);
furi_record_close(RECORD_STORAGE);
}
/* Here all we do is putting the events into the queue that will be handled /* Here all we do is putting the events into the queue that will be handled
* in the while() loop of the app entry point function. */ * in the while() loop of the app entry point function. */
void input_callback(InputEvent* input_event, void* ctx) { void input_callback(InputEvent* input_event, void* ctx) {
@@ -579,6 +692,8 @@ void input_callback(InputEvent* input_event, void* ctx) {
AsteroidsApp* asteroids_app_alloc() { AsteroidsApp* asteroids_app_alloc() {
AsteroidsApp* app = malloc(sizeof(AsteroidsApp)); AsteroidsApp* app = malloc(sizeof(AsteroidsApp));
load_game(app);
app->gui = furi_record_open(RECORD_GUI); app->gui = furi_record_open(RECORD_GUI);
app->view_port = view_port_alloc(); app->view_port = view_port_alloc();
view_port_draw_callback_set(app->view_port, render_callback, app); view_port_draw_callback_set(app->view_port, render_callback, app);
@@ -587,6 +702,7 @@ AsteroidsApp* asteroids_app_alloc() {
app->event_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); app->event_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
app->running = 1; /* Turns 0 when back is pressed. */ app->running = 1; /* Turns 0 when back is pressed. */
restart_game_after_gameover(app); restart_game_after_gameover(app);
memset(app->pressed, 0, sizeof(app->pressed)); memset(app->pressed, 0, sizeof(app->pressed));
return app; return app;
@@ -617,12 +733,15 @@ uint32_t key_pressed_time(AsteroidsApp* app, InputKey key) {
/* Handle keys interaction. */ /* Handle keys interaction. */
void asteroids_update_keypress_state(AsteroidsApp* app, InputEvent input) { void asteroids_update_keypress_state(AsteroidsApp* app, InputEvent input) {
// Allow Rapid fire
if(input.key == InputKeyOk) {
app->fire = true;
}
if(input.type == InputTypePress) { if(input.type == InputTypePress) {
app->pressed[input.key] = furi_get_tick(); app->pressed[input.key] = furi_get_tick();
} else if(input.type == InputTypeRelease) { } else if(input.type == InputTypeRelease) {
uint32_t dur = key_pressed_time(app, input.key);
app->pressed[input.key] = 0; app->pressed[input.key] = 0;
if(dur < 200 && input.key == InputKeyOk) app->fire = true;
} }
} }
@@ -646,7 +765,9 @@ int32_t asteroids_app_entry(void* p) {
/* Handle navigation here. Then handle view-specific inputs /* Handle navigation here. Then handle view-specific inputs
* in the view specific handling function. */ * in the view specific handling function. */
if(input.type == InputTypeShort && input.key == InputKeyBack) { if(input.type == InputTypeLong && input.key == InputKeyBack) {
// Save High Score even if player didn't finish game
if(app->is_new_highscore) save_game(app); // Save highscore but only on change
app->running = 0; app->running = 0;
} else { } else {
asteroids_update_keypress_state(app, input); asteroids_update_keypress_state(app, input);

View File

@@ -39,11 +39,12 @@ static void clock_render_callback(Canvas* const canvas, void* ctx) {
} else { } else {
bool pm = curr_dt.hour > 12; bool pm = curr_dt.hour > 12;
bool pm12 = curr_dt.hour >= 12; bool pm12 = curr_dt.hour >= 12;
bool am12 = curr_dt.hour == 0;
snprintf( snprintf(
time_string, time_string,
TIME_LEN, TIME_LEN,
CLOCK_TIME_FORMAT, CLOCK_TIME_FORMAT,
pm ? curr_dt.hour - 12 : curr_dt.hour, pm ? curr_dt.hour - 12 : (am12 ? 12 : curr_dt.hour),
curr_dt.minute, curr_dt.minute,
curr_dt.second); curr_dt.second);

View File

@@ -16,8 +16,6 @@
#include <dialogs/dialogs.h> #include <dialogs/dialogs.h>
#include "DAP_Link_icons.h" #include "DAP_Link_icons.h"
#include "../../settings/desktop_settings/desktop_settings_app.h"
/***************************************************************************/ /***************************************************************************/
/****************************** DAP COMMON *********************************/ /****************************** DAP COMMON *********************************/
/***************************************************************************/ /***************************************************************************/
@@ -484,37 +482,23 @@ DapConfig* dap_app_get_config(DapApp* app) {
int32_t dap_link_app(void* p) { int32_t dap_link_app(void* p) {
UNUSED(p); UNUSED(p);
DesktopSettings* settings = malloc(sizeof(DesktopSettings));
DESKTOP_SETTINGS_LOAD(settings);
if(furi_hal_usb_is_locked()) { if(furi_hal_usb_is_locked()) {
DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
DialogMessage* message = dialog_message_alloc(); DialogMessage* message = dialog_message_alloc();
if(settings->sfw_mode) { dialog_message_set_header(
dialog_message_set_header( message, "Connection\nis active!", 3, 2, AlignLeft, AlignTop);
message, "Connection\nis active!", 3, 2, AlignLeft, AlignTop); dialog_message_set_text(
dialog_message_set_text( message,
message, "Disconnect from\nPC or phone to\nuse this function.",
"Disconnect from\nPC or phone to\nuse this function.", 3,
3, 30,
30, AlignLeft,
AlignLeft, AlignTop);
AlignTop);
} else {
dialog_message_set_header(message, "I am not\na whore!", 3, 2, AlignLeft, AlignTop);
dialog_message_set_text(
message,
"Pull out from\nPC or phone to\nuse me like this.",
3,
30,
AlignLeft,
AlignTop);
}
dialog_message_set_icon(message, &I_ActiveConnection_50x64, 78, 0); dialog_message_set_icon(message, &I_ActiveConnection_50x64, 78, 0);
dialog_message_show(dialogs, message); dialog_message_show(dialogs, message);
dialog_message_free(message); dialog_message_free(message);
furi_record_close(RECORD_DIALOGS); furi_record_close(RECORD_DIALOGS);
free(settings);
return -1; return -1;
} }

View File

@@ -5,7 +5,6 @@
#include <gui/gui.h> #include <gui/gui.h>
#include <input/input.h> #include <input/input.h>
#include <dolphin/dolphin.h> #include <dolphin/dolphin.h>
#include "applications/settings/desktop_settings/desktop_settings_app.h"
#define TAG "Dice Roller" #define TAG "Dice Roller"
@@ -22,7 +21,6 @@ typedef struct {
typedef struct { typedef struct {
FuriMutex* mutex; FuriMutex* mutex;
FuriMessageQueue* event_queue; FuriMessageQueue* event_queue;
DesktopSettings* desktop_settings;
FuriHalRtcDateTime datetime; FuriHalRtcDateTime datetime;
uint8_t diceSelect; uint8_t diceSelect;
uint8_t diceQty; uint8_t diceQty;
@@ -430,7 +428,6 @@ static void dice_state_init(DiceState* const state) {
state->playerOneScore = 0; state->playerOneScore = 0;
state->playerTwoScore = 0; state->playerTwoScore = 0;
state->letsRoll = false; state->letsRoll = false;
state->desktop_settings = malloc(sizeof(DesktopSettings));
} }
static void dice_tick(void* ctx) { static void dice_tick(void* ctx) {
@@ -470,7 +467,6 @@ int32_t dice_app(void* p) {
return 255; return 255;
} }
DESKTOP_SETTINGS_LOAD(plugin_state->desktop_settings);
ViewPort* view_port = view_port_alloc(); ViewPort* view_port = view_port_alloc();
view_port_draw_callback_set(view_port, dice_render_callback, plugin_state); view_port_draw_callback_set(view_port, dice_render_callback, plugin_state);
@@ -510,11 +506,7 @@ int32_t dice_app(void* p) {
} else if(plugin_state->diceSelect == 20) { } else if(plugin_state->diceSelect == 20) {
plugin_state->diceSelect = 100; plugin_state->diceSelect = 100;
} else if(plugin_state->diceSelect == 100) { } else if(plugin_state->diceSelect == 100) {
if(plugin_state->desktop_settings->is_sfwmode) { plugin_state->diceSelect = 230;
plugin_state->diceSelect = 231;
} else {
plugin_state->diceSelect = 230;
}
} else if(plugin_state->diceSelect == 230) { } else if(plugin_state->diceSelect == 230) {
plugin_state->playerOneScore = 0; plugin_state->playerOneScore = 0;
plugin_state->playerTwoScore = 0; plugin_state->playerTwoScore = 0;
@@ -524,11 +516,7 @@ int32_t dice_app(void* p) {
} else if(plugin_state->diceSelect == 229) { } else if(plugin_state->diceSelect == 229) {
plugin_state->diceSelect = 228; plugin_state->diceSelect = 228;
} else if(plugin_state->diceSelect == 228) { } else if(plugin_state->diceSelect == 228) {
if(plugin_state->desktop_settings->is_sfwmode) { plugin_state->diceSelect = 232;
plugin_state->diceSelect = 59;
} else {
plugin_state->diceSelect = 232;
}
} else if(plugin_state->diceSelect == 232) { } else if(plugin_state->diceSelect == 232) {
plugin_state->diceSelect = 59; plugin_state->diceSelect = 59;
} else if(plugin_state->diceSelect == 59) { } else if(plugin_state->diceSelect == 59) {
@@ -571,7 +559,6 @@ int32_t dice_app(void* p) {
view_port_free(view_port); view_port_free(view_port);
furi_message_queue_free(plugin_state->event_queue); furi_message_queue_free(plugin_state->event_queue);
furi_mutex_free(plugin_state->mutex); furi_mutex_free(plugin_state->mutex);
free(plugin_state->desktop_settings);
free(plugin_state); free(plugin_state);
return 0; return 0;
} }

View File

@@ -5,15 +5,23 @@
#include <input/input.h> #include <input/input.h>
#include <notification/notification_messages.h> #include <notification/notification_messages.h>
typedef struct {
int mode;
} PluginState;
void vibro_test_draw_callback(Canvas* canvas, void* ctx) { void vibro_test_draw_callback(Canvas* canvas, void* ctx) {
UNUSED(ctx); UNUSED(ctx);
canvas_clear(canvas); canvas_clear(canvas);
canvas_set_font(canvas, FontPrimary); canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 2, 10, "Vibro application"); canvas_draw_str(canvas, 2, 10, "Vibro Modes");
canvas_set_font(canvas, FontSecondary); canvas_set_font(canvas, FontSecondary);
canvas_draw_str(canvas, 2, 22, "Press OK turns on vibro"); canvas_draw_str(canvas, 2, 22, "LEFT: strong / RIGHT: Soft");
canvas_set_font(canvas, FontSecondary); canvas_set_font(canvas, FontSecondary);
canvas_draw_str(canvas, 2, 34, "Press LEFT turns off vibro"); canvas_draw_str(canvas, 2, 34, "UP: Pulsed");
canvas_set_font(canvas, FontSecondary);
canvas_draw_str(canvas, 2, 46, "DOWN Pleasure combo");
canvas_set_font(canvas, FontSecondary);
canvas_draw_str(canvas, 2, 58, "OK: Pause");
} }
void vibro_test_input_callback(InputEvent* input_event, void* ctx) { void vibro_test_input_callback(InputEvent* input_event, void* ctx) {
@@ -22,10 +30,22 @@ void vibro_test_input_callback(InputEvent* input_event, void* ctx) {
furi_message_queue_put(event_queue, input_event, FuriWaitForever); furi_message_queue_put(event_queue, input_event, FuriWaitForever);
} }
void delay(int milliseconds) {
furi_thread_flags_wait(0, FuriFlagWaitAny, milliseconds);
}
int32_t orgasmotron_app(void* p) { int32_t orgasmotron_app(void* p) {
UNUSED(p); UNUSED(p);
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
PluginState* plugin_state = malloc(sizeof(PluginState));
ValueMutex state_mutex;
if (!init_mutex(&state_mutex, plugin_state, sizeof(PluginState))) {
FURI_LOG_E("Orgasmatron", "cannot create mutex\r\n");
free(plugin_state);
return 255;
}
// Configure view port // Configure view port
ViewPort* view_port = view_port_alloc(); ViewPort* view_port = view_port_alloc();
view_port_draw_callback_set(view_port, vibro_test_draw_callback, NULL); view_port_draw_callback_set(view_port, vibro_test_draw_callback, NULL);
@@ -38,31 +58,76 @@ int32_t orgasmotron_app(void* p) {
NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
InputEvent event; InputEvent event;
//int mode = 0;
bool processing = true;
//while(furi_message_queue_get(event_queue, &event, FuriWaitForever) == FuriStatusOk) {
while (processing) {
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
PluginState* plugin_state = (PluginState*)acquire_mutex_block(&state_mutex);
if (event_status == FuriStatusOk) {
if(event.key == InputKeyBack && event.type == InputTypeShort) {
//Exit Application
notification_message(notification, &sequence_reset_vibro);
notification_message(notification, &sequence_reset_green);
plugin_state->mode = 0;
processing = false;
//break;
}
if(event.key == InputKeyOk && (event.type == InputTypePress || event.type == InputTypeRelease)) {
plugin_state->mode = 0;
}
if(event.key == InputKeyLeft && (event.type == InputTypePress || event.type == InputTypeRelease)) {
plugin_state->mode = 1;
}
if(event.key == InputKeyRight && (event.type == InputTypePress || event.type == InputTypeRelease)) {
plugin_state->mode = 3;
}
if(event.key == InputKeyUp && (event.type == InputTypePress || event.type == InputTypeRelease)) {
plugin_state->mode = 2;
}
if(event.key == InputKeyDown && (event.type == InputTypePress || event.type == InputTypeRelease)) {
plugin_state->mode = 4;
}
}
while(furi_message_queue_get(event_queue, &event, FuriWaitForever) == FuriStatusOk) { if (plugin_state->mode == 0) {
if(event.type == InputTypeShort && event.key == InputKeyBack) { //Stop Vibration
notification_message(notification, &sequence_reset_vibro); notification_message(notification, &sequence_reset_vibro);
notification_message(notification, &sequence_reset_green); notification_message(notification, &sequence_reset_green);
break; } else if (plugin_state->mode == 1) {
} //Full power
if(event.key == InputKeyOk) { notification_message(notification, &sequence_set_vibro_on);
if(event.type == InputTypePress) { notification_message(notification, &sequence_set_green_255);
} else if (plugin_state->mode == 2) {
//Pulsed Vibration
notification_message(notification, &sequence_set_vibro_on);
notification_message(notification, &sequence_set_green_255);
delay(100);
notification_message(notification, &sequence_reset_vibro);
} else if (plugin_state->mode == 3) {
//Soft power
notification_message(notification, &sequence_set_vibro_on);
notification_message(notification, &sequence_set_green_255);
delay(50);
notification_message(notification, &sequence_reset_vibro);
} else if (plugin_state->mode == 4) {
//Special Sequence
for (int i = 0;i < 15;i++) {
notification_message(notification, &sequence_set_vibro_on); notification_message(notification, &sequence_set_vibro_on);
notification_message(notification, &sequence_set_green_255); notification_message(notification, &sequence_set_green_255);
} else if(event.type == InputTypeRelease) { delay(50);
notification_message(notification, &sequence_reset_vibro);
delay(50);
}
for (int i = 0;i < 2;i++) {
notification_message(notification, &sequence_set_vibro_on); notification_message(notification, &sequence_set_vibro_on);
notification_message(notification, &sequence_set_green_255); notification_message(notification, &sequence_set_green_255);
delay(400);
notification_message(notification, &sequence_reset_vibro);
delay(50);
} }
} }
if(event.key == InputKeyLeft) { release_mutex(&state_mutex, plugin_state);
if(event.type == InputTypePress) {
notification_message(notification, &sequence_reset_vibro);
notification_message(notification, &sequence_reset_green);
} else if(event.type == InputTypeRelease) {
notification_message(notification, &sequence_reset_vibro);
notification_message(notification, &sequence_reset_green);
}
}
} }
gui_remove_view_port(gui, view_port); gui_remove_view_port(gui, view_port);
view_port_free(view_port); view_port_free(view_port);
@@ -70,6 +135,7 @@ int32_t orgasmotron_app(void* p) {
furi_record_close(RECORD_NOTIFICATION); furi_record_close(RECORD_NOTIFICATION);
furi_record_close(RECORD_GUI); furi_record_close(RECORD_GUI);
delete_mutex(&state_mutex);
return 0; return 0;
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1012 B

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -7,8 +7,6 @@
#include <gui/elements.h> #include <gui/elements.h>
#include <m-array.h> #include <m-array.h>
#include "../../../settings/desktop_settings/desktop_settings_app.h"
#define FRAME_HEIGHT 12 #define FRAME_HEIGHT 12
#define MAX_LEN_PX 112 #define MAX_LEN_PX 112
#define MENU_ITEMS 4u #define MENU_ITEMS 4u
@@ -181,9 +179,6 @@ void pcsg_view_receiver_draw(Canvas* canvas, PCSGReceiverModel* model) {
FuriString* str_buff; FuriString* str_buff;
str_buff = furi_string_alloc(); str_buff = furi_string_alloc();
DesktopSettings* settings = malloc(sizeof(DesktopSettings));
DESKTOP_SETTINGS_LOAD(settings);
PCSGReceiverMenuItem* item_menu; PCSGReceiverMenuItem* item_menu;
for(size_t i = 0; i < MIN(model->history_item, MENU_ITEMS); ++i) { for(size_t i = 0; i < MIN(model->history_item, MENU_ITEMS); ++i) {
@@ -209,11 +204,7 @@ void pcsg_view_receiver_draw(Canvas* canvas, PCSGReceiverModel* model) {
canvas_set_color(canvas, ColorBlack); canvas_set_color(canvas, ColorBlack);
if(model->history_item == 0) { if(model->history_item == 0) {
if(settings->sfw_mode) { canvas_draw_icon(canvas, 0, 0, &I_Scanning_123x52);
canvas_draw_icon(canvas, 0, 0, &I_Scanning_123x52_sfw);
} else {
canvas_draw_icon(canvas, 0, 0, &I_Scanning_123x52);
}
canvas_set_font(canvas, FontPrimary); canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 63, 46, "Scanning..."); canvas_draw_str(canvas, 63, 46, "Scanning...");
canvas_draw_line(canvas, 46, 51, 125, 51); canvas_draw_line(canvas, 46, 51, 125, 51);
@@ -235,11 +226,7 @@ void pcsg_view_receiver_draw(Canvas* canvas, PCSGReceiverModel* model) {
canvas_draw_icon(canvas, 65, 42, &I_Pin_back_arrow_10x8); 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, 80, 42, &I_Pin_back_arrow_10x8);
canvas_draw_icon(canvas, 95, 42, &I_Pin_back_arrow_10x8); canvas_draw_icon(canvas, 95, 42, &I_Pin_back_arrow_10x8);
if(settings->sfw_mode) { canvas_draw_icon(canvas, 16, 13, &I_WarningDolphin_45x42);
canvas_draw_icon(canvas, 16, 13, &I_WarningDolphin_45x42_sfw);
} else {
canvas_draw_icon(canvas, 16, 13, &I_WarningDolphin_45x42);
}
canvas_draw_dot(canvas, 17, 61); canvas_draw_dot(canvas, 17, 61);
break; break;
case PCSGReceiverBarShowUnlock: case PCSGReceiverBarShowUnlock:
@@ -252,7 +239,6 @@ void pcsg_view_receiver_draw(Canvas* canvas, PCSGReceiverModel* model) {
canvas_draw_str(canvas, 96, 62, furi_string_get_cstr(model->history_stat_str)); canvas_draw_str(canvas, 96, 62, furi_string_get_cstr(model->history_stat_str));
break; break;
} }
free(settings);
} }
static void pcsg_view_receiver_timer_callback(void* context) { static void pcsg_view_receiver_timer_callback(void* context) {

View File

@@ -1,17 +1,15 @@
ProtoView is a digital signal detection and visualization tool for the ProtoView is a digital signal detection, visualization, editing and reply tool for the [Flipper Zero](https://flipperzero.one/). The Flipper default application, called Subghz, is able to identify certain RF protocols, but when the exact protocol is not implemented (and there are many undocumented and unimplemented ones, such as the ones in use in TPMS systems, car keys and many others), the curious person is left wondering what the device is sending at all. Using ProtoView she or he can visualize the high and low pulses like in the example image below (showing a TPMS signal produced by a Renault tire):
[Flipper Zero](https://flipperzero.one/). The Flipper is able to identify
a great deal of RF protocols, however when the exact protocol is not
implemented (and there are many proprietary ones, such as the ones of
the car keys), the curious person is left wondering what the device is
sending at all. Using ProtoView she or he can visualize the high and low pulses
like in the example image below (showing a Volkswagen key in 2FSK):
![ProtoView screenshot raw signal](/images/protoview_1.jpg) ![ProtoView screenshot raw signal](/images/protoview_1.jpg)
This is often enough to make an initial idea about the encoding used This is often enough to make an initial idea about the encoding used
and if the selected modulation is correct. and if the selected modulation is correct. For example, in the signal above
you can see a set of regular pulses and gaps used for synchronization, and then
a sequence of bits encoded in [Manchester](https://en.wikipedia.org/wiki/Manchester_code) line code. If you study these things for five minutes, you'll find yourself able to decode the bits with naked eyes.
Other than that, ProtoView is able to decode a few interesting protocols: ## Decoding capabilities
Other than showing the raw signal, ProtoView is able to decode a few interesting protocols:
* TPMS sensors: Renault, Toyota, Schrader, Citroen, Ford. * TPMS sensors: Renault, Toyota, Schrader, Citroen, Ford.
* Microchip HSC200/300/301 Keeloq protocol. * Microchip HSC200/300/301 Keeloq protocol.
@@ -23,25 +21,39 @@ Other than that, ProtoView is able to decode a few interesting protocols:
The app implements a framework that makes adding and experimenting with new The app implements a framework that makes adding and experimenting with new
protocols very simple. Check the `protocols` directory to see how the protocols very simple. Check the `protocols` directory to see how the
API works. API works, or read the full documentation at the end of this `README` file.
The gist of it is that the decoder receives the signal already converted into
a bitmap, where each bit represents a short pulse duration. Then there are
functions to seek specific sync/preamble sequences inside the bitmap, to decode
from different line codes, to compute checksums and so forth.
The secondary goal of ProtoView is to provide a somewhat-documented application ## Signals transmission capabilities
for the Flipper (even if ProtoView is a pretty atypical application: doesn't make use of the standard widgets and other abstractions provded by the framework).
Many apps dealing with the *subghz subsystem* (the Flipper Once ProtoView decodes a given message, it is able to *resample* it
abstraction to work with the [CC1101 chip](https://www.ti.com/product/CC1101)) in pulses and gaps of the theoretical duration, and send the signal again
tend to be complicated and completely undocumented. This is unfortunately via the Flipper TX capabilities. The captured signal can be sent
true for the firmware of the device itself. It's a shame because especially to different frequencies and modulations than the ones it was captured
in the case of code that talks with hardware peripherals there are tons from.
of assumptions and hard-gained lessons that can [only be captured by comments and are in the code only implicitly](http://antirez.com/news/124).
For selected protocols, that implement the message creation methods,
ProtoView is also able to edit the message received, modify fields,
and finally re-detect the new produced signal and resend it. Signals
can also be produced from scratch, by setting all the fields to appropriate
values.
## A well-documented app for the Flipper
The secondary goal of ProtoView is to provide a well-documented application for the Flipper (even if ProtoView is a pretty atypical application: it doesn't make use of the standard widgets and other abstractions provided by the framework).
Most apps dealing with the *subghz subsystem* of the Flipper (the abstraction used to work with the [CC1101 chip](https://www.ti.com/product/CC1101)) tend to be complicated and completely undocumented.
Unfortunately, this is also true for the firmware of the device.
It's a shame, because especially in the case of code that talks with hardware peripherals there are tons of assumptions and hard-gained lessons that can [only be captured by comments and are in the code only implicitly](http://antirez.com/news/124).
However, the Flipper firmware source code is well written even if it However, the Flipper firmware source code is well written even if it
lacks comments and documentation, so it is possible to make some ideas of lacks comments and documentation (and sometimes makes use of abstractions more convoluted than needed), so it is possible to make some ideas of how things work just grepping inside. In order to develop this application, I ended reading most parts of the firmware of the device.
how things work just grepping inside.
# Detection algorithm ## Detection algorithm
In order to show unknown signals, the application attempts to understand if In order to detect and show unknown signals, the application attempts to understand if the samples obtained by the Flipper API (a series of pulses that are high
the samples obtained by the Flipper API (a series of pulses that are high
or low, and with different duration in microseconds) look like belonging to or low, and with different duration in microseconds) look like belonging to
a legitimate signal, and aren't just noise. a legitimate signal, and aren't just noise.
@@ -49,25 +61,34 @@ We can't make assumptions about
the encoding and the data rate of the communication, so we use a simple the encoding and the data rate of the communication, so we use a simple
but relatively effective algorithm. As we check the signal, we try to detect but relatively effective algorithm. As we check the signal, we try to detect
long parts of it that are composed of pulses roughly classifiable into long parts of it that are composed of pulses roughly classifiable into
a maximum of three different classes of lengths, plus or minus 10%. Most a maximum of three different duration classes, plus or minus a given percentage.
encodings are somewhat self-clocked, so they tend to have just two or Most encodings are somewhat self-clocked, so they tend to have just two or
three classes of pulse lengths. three classes of pulse lengths.
However often pulses of the same theoretical However, often, pulses of the same theoretical
length have slightly different lengths in the case of high and low level length have slightly different lengths in the case of high and low level
(RF on or off), so we classify them separately for robustness. (RF on or off), so the detector classifies them separately for robustness.
Once the raw signal is detected, the registered protocol decoders are called
against it, in the hope some of the decoders will make sense of the signal.
# Usage # Usage
The application shows the longest coherent signal detected so far. In the main screen, the application shows the longest coherent signal detected so far. The user can switch to other views pressing the LEFT and RIGHT keys. The BACK key will return back to the main screen. Long pressing BACK will quit the application.
* The OK button resets the current signal. ## Main raw signal screen
* The UP and DOWN buttons change the scale. Default is 100us per pixel.
* The LEFT and RIGHT buttons switch to settings. * A long press of the OK button resets the current signal, in order to capture a new one.
* The UP and DOWN buttons change the scale. Default is 100us per pixel, but it will be adapted to the signal just captured.
* A long press of the LEFT and RIGHT keys will pan the signal, to see what was transmitted before/after the current shown range.
* A short press to OK will recenter the signal and set the scale back to the default for the specific pulse duration detected.
Under the detected sequence, you will see a small triangle marking a Under the detected sequence, you will see a small triangle marking a
specific sample. This mark means that the sequence looked coherent up specific sample. This mark means that the sequence looked coherent up
to that point, and starting from there it could be just noise. to that point, and starting from there it could be just noise. However the
signal decoders will not get just up to this point, but will get more:
sometimes the low level detector can't make sense of a signal that the
protocol-specific decoder can understand fully.
If the protocol is decoded, the bottom-left corner of the screen If the protocol is decoded, the bottom-left corner of the screen
will show the name of the protocol, and going in the next screen will show the name of the protocol, and going in the next screen
@@ -81,12 +102,47 @@ and could actually be `1000000/this-number*N` with `N > 2` (here 1000000
is the number of microseconds in one second, and N is the number of clock is the number of microseconds in one second, and N is the number of clock
cycles needed to represent a bit). cycles needed to represent a bit).
Things to investigate: ## Info screen
If a signal was detected, the info view will show the details about the signal. If the signal has more data that a single screen can fit, pressing OK will show the next fields. Pressing DOWN will go to a sub-view with an oscilloscope-alike representation of the signal, from there you can:
1. Resend the signal, by pressing OK.
2. Save the signal as `.sub` file, by long pressing OK.
When resending, you can select a different frequency and modulation if you
wish.
## Frequency and modulation screen
In this view you can just set the frequency and modulation you want to use.
There are special modulations for TPMS signals: they need an higher data
rate.
* Many cheap remotes (gate openers, remotes, ...) are on the 433.92Mhz or nearby and use OOK modulation. * Many cheap remotes (gate openers, remotes, ...) are on the 433.92Mhz or nearby and use OOK modulation.
* Weather stations are often too in the 433.92Mhz OOK. * Weather stations are often too in the 433.92Mhz OOK.
* For car keys, try 433.92 OOK650 and 868.35 Mhz in OOK or 2FSK. * For car keys, try 433.92 OOK650 and 868.35 Mhz in OOK or 2FSK.
* For TPMS try 433.92 in TPMS modulation (FSK optimized for these signals). * For TPMS try 433.92 in TPMS1 or TPMS2 modulations (FSK and OOK custom modulations optimized for these signals, that have a relatively high data rate).
## Signal creator
In this view, you can do two things:
1. Select one of the protocols supporting message creation, and create a signal from scratch.
2. If there is already a detected signal, you can modify the signal.
This is how it works:
1. Select one of the protocols (the one of the currently detected signal will be already provided as default, if any, and if it supports message creation).
2. Fill the fields. Use LEFT and RIGHT to change the values of integers, or just press OK and enter the new value with the Fliper keyboard widget.
3. When you are done, long press OK to build the message. Then press BACK in order to see it.
4. Go to the INFO view, and then DOWN to the signal sending/saving subview in order to send or save it.
## Direct sampling screen
This final screen shows in real time the high and low level that the Flipper
RF chip, the CC1101, is receiving. This will makes very easy to understand
if a given frequency is targeted by something other than noise. This mode is
fun to watch, resembling an old CRT TV set.
# Installing the app from source # Installing the app from source
@@ -113,6 +169,107 @@ to use the Android (or other) application to upload the file,
you can just take out the SD card, insert it in your computer, you can just take out the SD card, insert it in your computer,
copy the fine into `apps/Tools`, and that's it. copy the fine into `apps/Tools`, and that's it.
# Protocols decoders API
Writing a protocol decoder is not hard, and requires to write three
different methods.
1. `decode()`. This is mandatory, and is used in order to turn a known signal into a set of fields containing certain informations. For instance for a thermometer sending data via RF, a raw message will be decoded into fields like temperature, humidity, station ID and so forth.
2. `get_fields()`. Optional, only needed if the protocol supports creating and editing signals. This method just returns the fields names, types and defaults. The app will use this list in order to allow the user to set values. The populated fields will be passed to the `build_message()` method later.
3. `build_message()`. This method gets a set of fields representing the parameters of the protocol, as set by the user, and will create a low level signal composed of pulses and gaps of specific durations.
## `decode()` method
bool decode(uint8_t *bits, uint32_t numbytes, uint32_t numbits, ProtoViewMsgInfo *info);
The method gets a bitmap `bits` long `numbytes` bytes but actually containing `bumbits` valid bits. Each bit represents a pulse of gap of the duration of the shortest time detected in the protocol (this is often called *te*, in the RF protocols jargon). So, for instance, if a signal is composed of pulses and gaps of around 500 and 1000 microseconds, each bit in the bitmap will represent 500 microseconds.
Continuing with the example above, if the received signal was composed of a 1000 microseconds gap, then a 500 microsecond pulse, then a 500 microsecond gap and finally a 1000 microseconds pulse, its bitmap representation will be:
001011
To access the bitmap, the decoder can use the following API:
bool bitmap_get(uint8_t *b, uint32_t blen, uint32_t bitpos);
The `blen` parameter will be set to what the decode method gets
as `numbytes`, and is used to prevent overflows. This way if `bitpos`
is out of range, nothing bad happens.
There are function to match and seek specific patterns inside the signal:
bool bitmap_match_bits(uint8_t *b, uint32_t blen, uint32_t bitpos, const char *bits);
uint32_t bitmap_seek_bits(uint8_t *b, uint32_t blen, uint32_t startpos, uint32_t maxbits, const char *bits);
Finally, there are functions to convert from different line codes:
uint32_t convert_from_line_code(uint8_t *buf, uint64_t buflen, uint8_t *bits, uint32_t len, uint32_t offset, const char *zero_pattern, const char *one_pattern);
uint32_t convert_from_diff_manchester(uint8_t *buf, uint64_t buflen, uint8_t *bits, uint32_t len, uint32_t off, bool previous);
This method can also access the short pulse duration by inspecting the
`info->short_pulse_dur` field (in microseconds).
Please check the `b4b1.c` file for an easy to understand example of the decoder implementation.
If the decoder actually detected a message, it will return `true` and will return a set of fields, like thata:
fieldset_add_bytes(info->fieldset,"id",d,5);
fieldset_add_uint(info->fieldset,"button",d[2]&0xf,4);
## `get_fields()` method.
static void get_fields(ProtoViewFieldSet *fieldset);
This method will be basically a copy of the final part of `decode()`, as
it also needs to return the set of fields this protocol is composed of.
However instead of returning the values of an actual decoded message, it
will just provide their default values for the signal creator view.
Note that the `build_message()` method is guaranteed to receive the
same exact fields in the same exact order.
## `build_message()` method.
void build_message(RawSamplesBuffer *samples, ProtoViewFieldSet *fs);
This method is responsible of creating a signal from scratch, by
appending gaps and pulses of the specific duration into `samples`
using the following API:
raw_samples_add(RawSamplesBuffer *samples, bool level, uint32_t duration);
Level can be true (pules) or false (gap). Duration is in microseconds.
The method receives a set of fields in `fs`. Each field is accessible
directly accessing `fs->fields[... field index ...]`, where the field
index is 0, 1, 2, ... in the same order as `get_fields()` returned the
fields.
For now, you can access the fields in the raw way, by getting the
values directly from the data structure representing each field:
```
typedef struct {
ProtoViewFieldType type;
uint32_t len; // Depends on type:
// Bits for integers (signed,unsigned,binary,hex).
// Number of characters for strings.
// Number of nibbles for bytes (1 for each 4 bits).
// Number of digits after dot for floats.
char *name; // Field name.
union {
char *str; // String type.
int64_t value; // Signed integer type.
uint64_t uvalue; // Unsigned integer type.
uint8_t *bytes; // Raw bytes type.
float fvalue; // Float type.
};
} ProtoViewField;
```
However later the app will likely provide a set of macros to do it
in a more future-proof way.
# License # License
The code is released under the BSD license. The code is released under the BSD license.

View File

@@ -1,14 +0,0 @@
Core improvements
=================
- Decoders should declare the short pulse duration range, so that
only matching decoders will be called. This may also be useful for
modulations. If a signal is only OOK, does not make much sense to
call it for samples obtained in FSK.
- More protocols, especially TPMS and other stuff not supported right now
by the Flipper.
- CC1101 synchronous mode with protocol hopping?
- Protocols decoded can register actions, for instance to generate
sub files with modified signal and so forth.
- Optimize memory usage storing raw samples in a bitfield: 15 bits
duration, 1 bit level.

View File

@@ -57,8 +57,12 @@ static void render_callback(Canvas *const canvas, void *ctx) {
case ViewModulationSettings: case ViewModulationSettings:
render_view_settings(canvas,app); break; render_view_settings(canvas,app); break;
case ViewDirectSampling: render_view_direct_sampling(canvas,app); break; case ViewDirectSampling: render_view_direct_sampling(canvas,app); break;
case ViewLast: furi_crash(TAG " ViewLast selected"); break; case ViewBuildMessage: render_view_build_message(canvas,app); break;
default: furi_crash(TAG "Invalid view selected"); break;
} }
/* Draw the alert box if set. */
ui_draw_alert_if_needed(canvas, app);
} }
/* Here all we do is putting the events into the queue that will be handled /* Here all we do is putting the events into the queue that will be handled
@@ -69,32 +73,53 @@ static void input_callback(InputEvent* input_event, void* ctx)
furi_message_queue_put(app->event_queue,input_event,FuriWaitForever); furi_message_queue_put(app->event_queue,input_event,FuriWaitForever);
} }
/* Called to switch view (when left/right is pressed). Handles /* Called to switch view (when left/right is pressed). Handles
* changing the current view ID and calling the enter/exit view * changing the current view ID and calling the enter/exit view
* callbacks if needed. */ * callbacks if needed.
static void app_switch_view(ProtoViewApp *app, SwitchViewDirection dir) { *
* The 'switchto' parameter can be the identifier of a view, or the
* special views ViewGoNext and ViewGoPrev in order to move to
* the logical next/prev view. */
static void app_switch_view(ProtoViewApp *app, ProtoViewCurrentView switchto) {
/* Switch to the specified view. */
ProtoViewCurrentView old = app->current_view; ProtoViewCurrentView old = app->current_view;
if (dir == AppNextView) { if (switchto == ViewGoNext) {
app->current_view++; app->current_view++;
if (app->current_view == ViewLast) app->current_view = 0; if (app->current_view == ViewLast) app->current_view = 0;
} else if (dir == AppPrevView) { } else if (switchto == ViewGoPrev) {
if (app->current_view == 0) if (app->current_view == 0)
app->current_view = ViewLast-1; app->current_view = ViewLast-1;
else else
app->current_view--; app->current_view--;
} else {
app->current_view = switchto;
} }
ProtoViewCurrentView new = app->current_view; ProtoViewCurrentView new = app->current_view;
/* Set the current subview of the view we just left to zero. This is
* the main subview of the old view. When re re-enter the view we are
* lefting, we want to see the main thing again. */
app->current_subview[old] = 0;
/* Reset the view private data each time, before calling the enter/exit
* callbacks that may want to setup some state. */
memset(app->view_privdata,0,PROTOVIEW_VIEW_PRIVDATA_LEN);
/* Call the enter/exit view callbacks if needed. */ /* Call the enter/exit view callbacks if needed. */
if (old == ViewDirectSampling) view_exit_direct_sampling(app); if (old == ViewDirectSampling) view_exit_direct_sampling(app);
if (new == ViewDirectSampling) view_enter_direct_sampling(app); if (new == ViewDirectSampling) view_enter_direct_sampling(app);
if (old == ViewBuildMessage) view_exit_build_message(app);
if (new == ViewBuildMessage) view_enter_build_message(app);
if (old == ViewInfo) view_exit_info(app);
/* The frequency/modulation settings are actually a single view: /* The frequency/modulation settings are actually a single view:
* as long as the user stays between the two modes of this view we * as long as the user stays between the two modes of this view we
* don't need to call the exit-view callback. */ * don't need to call the exit-view callback. */
if ((old == ViewFrequencySettings && new != ViewModulationSettings) || if ((old == ViewFrequencySettings && new != ViewModulationSettings) ||
(old == ViewModulationSettings && new != ViewFrequencySettings)) (old == ViewModulationSettings && new != ViewFrequencySettings))
view_exit_settings(app); view_exit_settings(app);
ui_dismiss_alert(app);
} }
/* Allocate the application state and initialize a number of stuff. /* Allocate the application state and initialize a number of stuff.
@@ -112,13 +137,21 @@ ProtoViewApp* protoview_app_alloc() {
// GUI // GUI
app->gui = furi_record_open(RECORD_GUI); app->gui = furi_record_open(RECORD_GUI);
app->notification = furi_record_open(RECORD_NOTIFICATION);
app->view_port = view_port_alloc(); app->view_port = view_port_alloc();
view_port_draw_callback_set(app->view_port, render_callback, app); view_port_draw_callback_set(app->view_port, render_callback, app);
view_port_input_callback_set(app->view_port, input_callback, app); view_port_input_callback_set(app->view_port, input_callback, app);
gui_add_view_port(app->gui, app->view_port, GuiLayerFullscreen); gui_add_view_port(app->gui, app->view_port, GuiLayerFullscreen);
app->event_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); app->event_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
app->view_dispatcher = NULL;
app->text_input = NULL;
app->show_text_input = false;
app->alert_dismiss_time = 0;
app->current_view = ViewRawPulses; app->current_view = ViewRawPulses;
for (int j = 0; j < ViewLast; j++) app->current_subview[j] = 0;
app->direct_sampling_enabled = false; app->direct_sampling_enabled = false;
app->view_privdata = malloc(PROTOVIEW_VIEW_PRIVDATA_LEN);
memset(app->view_privdata,0,PROTOVIEW_VIEW_PRIVDATA_LEN);
// Signal found and visualization defaults // Signal found and visualization defaults
app->signal_bestlen = 0; app->signal_bestlen = 0;
@@ -126,8 +159,9 @@ ProtoViewApp* protoview_app_alloc() {
app->signal_decoded = false; app->signal_decoded = false;
app->us_scale = PROTOVIEW_RAW_VIEW_DEFAULT_SCALE; app->us_scale = PROTOVIEW_RAW_VIEW_DEFAULT_SCALE;
app->signal_offset = 0; app->signal_offset = 0;
app->msg_info = NULL;
//init Worker & Protocol // Init Worker & Protocol
app->txrx = malloc(sizeof(ProtoViewTxRx)); app->txrx = malloc(sizeof(ProtoViewTxRx));
/* Setup rx worker and environment. */ /* Setup rx worker and environment. */
@@ -168,7 +202,7 @@ ProtoViewApp* protoview_app_alloc() {
void protoview_app_free(ProtoViewApp *app) { void protoview_app_free(ProtoViewApp *app) {
furi_assert(app); furi_assert(app);
// Put CC1101 on sleep. // Put CC1101 on sleep, this also restores charging.
radio_sleep(app); radio_sleep(app);
// View related. // View related.
@@ -176,6 +210,7 @@ void protoview_app_free(ProtoViewApp *app) {
gui_remove_view_port(app->gui, app->view_port); gui_remove_view_port(app->gui, app->view_port);
view_port_free(app->view_port); view_port_free(app->view_port);
furi_record_close(RECORD_GUI); furi_record_close(RECORD_GUI);
furi_record_close(RECORD_NOTIFICATION);
furi_message_queue_free(app->event_queue); furi_message_queue_free(app->event_queue);
app->gui = NULL; app->gui = NULL;
@@ -217,9 +252,25 @@ static void timer_callback(void *ctx) {
} }
if (delta < RawSamples->total/2) return; if (delta < RawSamples->total/2) return;
app->signal_last_scan_idx = RawSamples->idx; app->signal_last_scan_idx = RawSamples->idx;
scan_for_signal(app); scan_for_signal(app,RawSamples);
} }
/* This is the navigation callback we use in the view dispatcher used
* to display the "text input" widget, that is the keyboard to get text.
* The text input view is implemented to ignore the "back" short press,
* so the event is not consumed and is handled by the view dispatcher.
* However the view dispatcher implementation has the strange behavior that
* if no navigation callback is set, it will not stop when handling back.
*
* We just need a dummy callback returning false. We believe the
* implementation should be changed and if no callback is set, it should be
* the same as returning false. */
static bool keyboard_view_dispatcher_navigation_callback(void *ctx) {
UNUSED(ctx);
return false;
}
/* App entry point, as specified in application.fam. */
int32_t protoview_app_entry(void* p) { int32_t protoview_app_entry(void* p) {
UNUSED(p); UNUSED(p);
ProtoViewApp *app = protoview_app_alloc(); ProtoViewApp *app = protoview_app_alloc();
@@ -249,18 +300,30 @@ int32_t protoview_app_entry(void* p) {
if (input.type == InputTypeShort && if (input.type == InputTypeShort &&
input.key == InputKeyBack) input.key == InputKeyBack)
{ {
/* Exit the app. */ if (app->current_view != ViewRawPulses) {
/* If this is not the main app view, go there. */
app_switch_view(app,ViewRawPulses);
} else {
/* If we are in the main app view, warn the user
* they needs to long press to really quit. */
ui_show_alert(app,"Long press to exit",1000);
}
} else if (input.type == InputTypeLong &&
input.key == InputKeyBack)
{
app->running = 0; app->running = 0;
} else if (input.type == InputTypeShort && } else if (input.type == InputTypeShort &&
input.key == InputKeyRight) input.key == InputKeyRight &&
ui_get_current_subview(app) == 0)
{ {
/* Go to the next view. */ /* Go to the next view. */
app_switch_view(app,AppNextView); app_switch_view(app,ViewGoNext);
} else if (input.type == InputTypeShort && } else if (input.type == InputTypeShort &&
input.key == InputKeyLeft) input.key == InputKeyLeft &&
ui_get_current_subview(app) == 0)
{ {
/* Go to the previous view. */ /* Go to the previous view. */
app_switch_view(app,AppPrevView); app_switch_view(app,ViewGoPrev);
} else { } else {
/* This is where we pass the control to the currently /* This is where we pass the control to the currently
* active view input processing. */ * active view input processing. */
@@ -278,7 +341,10 @@ int32_t protoview_app_entry(void* p) {
case ViewDirectSampling: case ViewDirectSampling:
process_input_direct_sampling(app,input); process_input_direct_sampling(app,input);
break; break;
case ViewLast: furi_crash(TAG " ViewLast selected"); break; case ViewBuildMessage:
process_input_build_message(app,input);
break;
default: furi_crash(TAG "Invalid view selected"); break;
} }
} }
} else { } else {
@@ -289,7 +355,54 @@ int32_t protoview_app_entry(void* p) {
if (!(c % 20)) FURI_LOG_E(TAG, "Loop timeout"); if (!(c % 20)) FURI_LOG_E(TAG, "Loop timeout");
} }
} }
view_port_update(app->view_port); if (app->show_text_input) {
/* Remove our viewport: we need to use a view dispatcher
* in order to show the standard Flipper keyboard. */
gui_remove_view_port(app->gui, app->view_port);
/* Allocate a view dispatcher, add a text input view to it,
* and activate it. */
app->view_dispatcher = view_dispatcher_alloc();
view_dispatcher_enable_queue(app->view_dispatcher);
/* We need to set a navigation callback for the view dispatcher
* otherwise when the user presses back on the keyboard to
* abort, the dispatcher will not stop. */
view_dispatcher_set_navigation_event_callback(
app->view_dispatcher,
keyboard_view_dispatcher_navigation_callback);
app->text_input = text_input_alloc();
view_dispatcher_set_event_callback_context(app->view_dispatcher,app);
view_dispatcher_add_view(app->view_dispatcher, 0, text_input_get_view(app->text_input));
view_dispatcher_switch_to_view(app->view_dispatcher, 0);
/* Setup the text input view. The different parameters are set
* in the app structure by the view that wanted to show the
* input text. The callback, buffer and buffer len must be set. */
text_input_set_header_text(app->text_input, "Save signal filename");
text_input_set_result_callback(
app->text_input,
app->text_input_done_callback,
app,
app->text_input_buffer,
app->text_input_buffer_len,
false);
/* Run the dispatcher with the keyboard. */
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
view_dispatcher_run(app->view_dispatcher);
/* Undo all it: remove the view from the dispatcher, free it
* so that it removes itself from the current gui, finally
* restore our viewport. */
view_dispatcher_remove_view(app->view_dispatcher, 0);
text_input_free(app->text_input);
view_dispatcher_free(app->view_dispatcher);
app->view_dispatcher = NULL;
gui_add_view_port(app->gui, app->view_port, GuiLayerFullscreen);
app->show_text_input = false;
} else {
view_port_update(app->view_port);
}
} }
/* App no longer running. Shut down and free. */ /* App no longer running. Shut down and free. */

View File

@@ -14,26 +14,36 @@
#include <gui/modules/submenu.h> #include <gui/modules/submenu.h>
#include <gui/modules/variable_item_list.h> #include <gui/modules/variable_item_list.h>
#include <gui/modules/widget.h> #include <gui/modules/widget.h>
#include <gui/modules/text_input.h>
#include <notification/notification_messages.h> #include <notification/notification_messages.h>
#include <lib/subghz/subghz_setting.h> #include <lib/subghz/subghz_setting.h>
#include <lib/subghz/subghz_worker.h> #include <lib/subghz/subghz_worker.h>
#include <lib/subghz/receiver.h> #include <lib/subghz/receiver.h>
#include <lib/subghz/transmitter.h> #include <lib/subghz/transmitter.h>
#include <lib/subghz/registry.h> #include <lib/subghz/registry.h>
#include "app_buffer.h" #include "raw_samples.h"
#define TAG "ProtoView" #define TAG "ProtoView"
#define PROTOVIEW_RAW_VIEW_DEFAULT_SCALE 100 #define PROTOVIEW_RAW_VIEW_DEFAULT_SCALE 100 // 100us is 1 pixel by default
#define BITMAP_SEEK_NOT_FOUND UINT32_MAX #define BITMAP_SEEK_NOT_FOUND UINT32_MAX // Returned by function as sentinel
#define PROTOVIEW_VIEW_PRIVDATA_LEN 64 // View specific private data len
#define DEBUG_MSG 1 #define DEBUG_MSG 1
/* Forward declarations. */
typedef struct ProtoViewApp ProtoViewApp; typedef struct ProtoViewApp ProtoViewApp;
typedef struct ProtoViewMsgInfo ProtoViewMsgInfo;
typedef struct ProtoViewFieldSet ProtoViewFieldSet;
typedef struct ProtoViewDecoder ProtoViewDecoder;
/* ============================== enumerations ============================== */
/* Subghz system state */ /* Subghz system state */
typedef enum { typedef enum {
TxRxStateIDLE, TxRxStateIDLE,
TxRxStateRx, TxRxStateRx,
TxRxStateTx,
TxRxStateSleep, TxRxStateSleep,
} TxRxState; } TxRxState;
@@ -43,20 +53,24 @@ typedef enum {
ViewInfo, ViewInfo,
ViewFrequencySettings, ViewFrequencySettings,
ViewModulationSettings, ViewModulationSettings,
ViewBuildMessage,
ViewDirectSampling, ViewDirectSampling,
ViewLast, /* Just a sentinel to wrap around. */ ViewLast, /* Just a sentinel to wrap around. */
/* The following are special views that are not iterated, but
* have meaning for the API. */
ViewGoNext,
ViewGoPrev,
} ProtoViewCurrentView; } ProtoViewCurrentView;
/* Used by app_switch_view() */ /* ================================== RX/TX ================================= */
typedef enum {
AppNextView,
AppPrevView
} SwitchViewDirection;
typedef struct { typedef struct {
const char *name; const char *name; // Name to show to the user.
FuriHalSubGhzPreset preset; const char *id; // Identifier in the Flipper API/file.
uint8_t *custom; FuriHalSubGhzPreset preset; // The preset ID.
uint8_t *custom; // If not null, a set of registers for
// the CC1101, specifying a custom preset.
} ProtoViewModulation; } ProtoViewModulation;
extern ProtoViewModulation ProtoViewModulations[]; /* In app_subghz.c */ extern ProtoViewModulation ProtoViewModulations[]; /* In app_subghz.c */
@@ -84,30 +98,35 @@ struct ProtoViewTxRx {
typedef struct ProtoViewTxRx ProtoViewTxRx; typedef struct ProtoViewTxRx ProtoViewTxRx;
/* This stucture is filled by the decoder for specific protocols with the /* ============================== Main app state ============================ */
* informations about the message. ProtoView will display such information
* in the message info view. */
#define PROTOVIEW_MSG_STR_LEN 32
typedef struct ProtoViewMsgInfo {
char name[PROTOVIEW_MSG_STR_LEN]; /* Protocol name and version. */
char raw[PROTOVIEW_MSG_STR_LEN]; /* Protocol specific raw representation.*/
/* The following is what the decoder wants to show to user. Each decoder
* can use the number of fileds it needs. */
char info1[PROTOVIEW_MSG_STR_LEN]; /* Protocol specific info line 1. */
char info2[PROTOVIEW_MSG_STR_LEN]; /* Protocol specific info line 2. */
char info3[PROTOVIEW_MSG_STR_LEN]; /* Protocol specific info line 3. */
char info4[PROTOVIEW_MSG_STR_LEN]; /* Protocol specific info line 4. */
uint64_t len; /* Bits consumed from the stream. */
} ProtoViewMsgInfo;
#define ALERT_MAX_LEN 32
struct ProtoViewApp { struct ProtoViewApp {
/* GUI */ /* GUI */
Gui *gui; Gui *gui;
NotificationApp *notification;
ViewPort *view_port; /* We just use a raw viewport and we render ViewPort *view_port; /* We just use a raw viewport and we render
everything into the low level canvas. */ everything into the low level canvas. */
ProtoViewCurrentView current_view; /* Active view ID. */ ProtoViewCurrentView current_view; /* Active left-right view ID. */
int current_subview[ViewLast]; /* Active up-down subview ID. */
FuriMessageQueue *event_queue; /* Keypress events go here. */ FuriMessageQueue *event_queue; /* Keypress events go here. */
/* Input text state. */
ViewDispatcher *view_dispatcher; /* Used only when we want to show
the text_input view for a moment.
Otherwise it is set to null. */
TextInput *text_input;
bool show_text_input;
char *text_input_buffer;
uint32_t text_input_buffer_len;
void (*text_input_done_callback)(void*);
/* Alert state. */
uint32_t alert_dismiss_time; /* Millisecond when the alert will be
no longer shown. Or zero if the alert
is currently not set at all. */
char alert_text[ALERT_MAX_LEN]; /* Alert content. */
/* Radio related. */ /* Radio related. */
ProtoViewTxRx *txrx; /* Radio state. */ ProtoViewTxRx *txrx; /* Radio state. */
SubGhzSetting *setting; /* A list of valid frequencies. */ SubGhzSetting *setting; /* A list of valid frequencies. */
@@ -118,9 +137,16 @@ struct ProtoViewApp {
uint32_t signal_last_scan_idx; /* Index of the buffer last time we uint32_t signal_last_scan_idx; /* Index of the buffer last time we
performed the scan. */ performed the scan. */
bool signal_decoded; /* Was the current signal decoded? */ bool signal_decoded; /* Was the current signal decoded? */
ProtoViewMsgInfo signal_info; /* Decoded message, if signal_decoded true. */ ProtoViewMsgInfo *msg_info; /* Decoded message info if not NULL. */
bool direct_sampling_enabled; /* This special view needs an explicit bool direct_sampling_enabled; /* This special view needs an explicit
acknowledge to work. */ acknowledge to work. */
void *view_privdata; /* This is a piece of memory of total size
PROTOVIEW_VIEW_PRIVDATA_LEN that it is
initialized to zero when we switch to
a a new view. While the view we are using
is the same, it can be used by the view to
store any kind of info inside, just casting
the pointer to a few specific-data structure. */
/* Raw view apps state. */ /* Raw view apps state. */
uint32_t us_scale; /* microseconds per pixel. */ uint32_t us_scale; /* microseconds per pixel. */
@@ -132,6 +158,64 @@ struct ProtoViewApp {
ProtoViewModulations table. */ ProtoViewModulations table. */
}; };
/* =========================== Protocols decoders =========================== */
/* This stucture is filled by the decoder for specific protocols with the
* informations about the message. ProtoView will display such information
* in the message info view. */
#define PROTOVIEW_MSG_STR_LEN 32
typedef struct ProtoViewMsgInfo {
ProtoViewDecoder *decoder; /* The decoder that decoded the message. */
ProtoViewFieldSet *fieldset; /* Decoded fields. */
/* Low level information of the detected signal: the following are filled
* by the protocol decoding function: */
uint32_t start_off; /* Pulses start offset in the bitmap. */
uint32_t pulses_count; /* Number of pulses of the full message. */
/* The following are passed already filled to the decoder. */
uint32_t short_pulse_dur; /* Microseconds duration of the short pulse. */
/* The following are filled by ProtoView core after the decoder returned
* success. */
uint8_t *bits; /* Bitmap with the signal. */
uint32_t bits_bytes; /* Number of full bytes in the bitmap, that
is 'pulses_count/8' rounded to the next
integer. */
} ProtoViewMsgInfo;
/* This structures describe a set of protocol fields. It is used by decoders
* supporting message building to receive and return information about the
* protocol. */
typedef enum {
FieldTypeStr,
FieldTypeSignedInt,
FieldTypeUnsignedInt,
FieldTypeBinary,
FieldTypeHex,
FieldTypeBytes,
FieldTypeFloat,
} ProtoViewFieldType;
typedef struct {
ProtoViewFieldType type;
uint32_t len; // Depends on type:
// Bits for integers (signed,unsigned,binary,hex).
// Number of characters for strings.
// Number of nibbles for bytes (1 for each 4 bits).
// Number of digits after dot for floats.
char *name; // Field name.
union {
char *str; // String type.
int64_t value; // Signed integer type.
uint64_t uvalue; // Unsigned integer type.
uint8_t *bytes; // Raw bytes type.
float fvalue; // Float type.
};
} ProtoViewField;
typedef struct ProtoViewFieldSet {
ProtoViewField **fields;
uint32_t numfields;
} ProtoViewFieldSet;
typedef struct ProtoViewDecoder { typedef struct ProtoViewDecoder {
const char *name; /* Protocol name. */ const char *name; /* Protocol name. */
/* The decode function takes a buffer that is actually a bitmap, with /* The decode function takes a buffer that is actually a bitmap, with
@@ -142,6 +226,14 @@ typedef struct ProtoViewDecoder {
* functions that perform bit extraction with bound checking, such as * functions that perform bit extraction with bound checking, such as
* bitmap_get() and so forth. */ * bitmap_get() and so forth. */
bool (*decode)(uint8_t *bits, uint32_t numbytes, uint32_t numbits, ProtoViewMsgInfo *info); bool (*decode)(uint8_t *bits, uint32_t numbytes, uint32_t numbits, ProtoViewMsgInfo *info);
/* This method is used by the decoder to return the fields it needs
* in order to build a new message. This way the message builder view
* can ask the user to fill the right set of fields of the specified
* type. */
void (*get_fields)(ProtoViewFieldSet *fields);
/* This method takes the fields supported by the decoder, and
* renders a message in 'samples'. */
void (*build_message)(RawSamplesBuffer *samples, ProtoViewFieldSet *fields);
} ProtoViewDecoder; } ProtoViewDecoder;
extern RawSamplesBuffer *RawSamples, *DetectedSamples; extern RawSamplesBuffer *RawSamples, *DetectedSamples;
@@ -154,19 +246,26 @@ void radio_rx_end(ProtoViewApp* app);
void radio_sleep(ProtoViewApp* app); void radio_sleep(ProtoViewApp* app);
void raw_sampling_worker_start(ProtoViewApp *app); void raw_sampling_worker_start(ProtoViewApp *app);
void raw_sampling_worker_stop(ProtoViewApp *app); void raw_sampling_worker_stop(ProtoViewApp *app);
void radio_tx_signal(ProtoViewApp *app, FuriHalSubGhzAsyncTxCallback data_feeder, void *ctx);
/* signal.c */ /* signal.c */
uint32_t duration_delta(uint32_t a, uint32_t b); uint32_t duration_delta(uint32_t a, uint32_t b);
void reset_current_signal(ProtoViewApp *app); void reset_current_signal(ProtoViewApp *app);
void scan_for_signal(ProtoViewApp *app); void scan_for_signal(ProtoViewApp *app,RawSamplesBuffer *source);
bool bitmap_get(uint8_t *b, uint32_t blen, uint32_t bitpos); bool bitmap_get(uint8_t *b, uint32_t blen, uint32_t bitpos);
void bitmap_set(uint8_t *b, uint32_t blen, uint32_t bitpos, bool val); void bitmap_set(uint8_t *b, uint32_t blen, uint32_t bitpos, bool val);
void bitmap_set_pattern(uint8_t *b, uint32_t blen, const char *pat); void bitmap_copy(uint8_t *d, uint32_t dlen, uint32_t doff, uint8_t *s, uint32_t slen, uint32_t soff, uint32_t count);
void bitmap_reverse_bytes(uint8_t *p, uint32_t len); void bitmap_set_pattern(uint8_t *b, uint32_t blen, uint32_t off, const char *pat);
void bitmap_reverse_bytes_bits(uint8_t *p, uint32_t len);
bool bitmap_match_bits(uint8_t *b, uint32_t blen, uint32_t bitpos, const char *bits); bool bitmap_match_bits(uint8_t *b, uint32_t blen, uint32_t bitpos, const char *bits);
uint32_t bitmap_seek_bits(uint8_t *b, uint32_t blen, uint32_t startpos, uint32_t maxbits, const char *bits); uint32_t bitmap_seek_bits(uint8_t *b, uint32_t blen, uint32_t startpos, uint32_t maxbits, const char *bits);
uint32_t convert_from_line_code(uint8_t *buf, uint64_t buflen, uint8_t *bits, uint32_t len, uint32_t offset, const char *zero_pattern, const char *one_pattern); uint32_t convert_from_line_code(uint8_t *buf, uint64_t buflen, uint8_t *bits, uint32_t len, uint32_t offset, const char *zero_pattern, const char *one_pattern);
uint32_t convert_from_diff_manchester(uint8_t *buf, uint64_t buflen, uint8_t *bits, uint32_t len, uint32_t off, bool previous); uint32_t convert_from_diff_manchester(uint8_t *buf, uint64_t buflen, uint8_t *bits, uint32_t len, uint32_t off, bool previous);
void init_msg_info(ProtoViewMsgInfo *i, ProtoViewApp *app);
void free_msg_info(ProtoViewMsgInfo *i);
/* signal_file.c */
bool save_signal(ProtoViewApp *app, const char *filename);
/* view_*.c */ /* view_*.c */
void render_view_raw_pulses(Canvas *const canvas, ProtoViewApp *app); void render_view_raw_pulses(Canvas *const canvas, ProtoViewApp *app);
@@ -177,12 +276,44 @@ void render_view_info(Canvas *const canvas, ProtoViewApp *app);
void process_input_info(ProtoViewApp *app, InputEvent input); void process_input_info(ProtoViewApp *app, InputEvent input);
void render_view_direct_sampling(Canvas *const canvas, ProtoViewApp *app); void render_view_direct_sampling(Canvas *const canvas, ProtoViewApp *app);
void process_input_direct_sampling(ProtoViewApp *app, InputEvent input); void process_input_direct_sampling(ProtoViewApp *app, InputEvent input);
void render_view_build_message(Canvas *const canvas, ProtoViewApp *app);
void process_input_build_message(ProtoViewApp *app, InputEvent input);
void view_enter_build_message(ProtoViewApp *app);
void view_exit_build_message(ProtoViewApp *app);
void view_enter_direct_sampling(ProtoViewApp *app); void view_enter_direct_sampling(ProtoViewApp *app);
void view_exit_direct_sampling(ProtoViewApp *app); void view_exit_direct_sampling(ProtoViewApp *app);
void view_exit_settings(ProtoViewApp *app); void view_exit_settings(ProtoViewApp *app);
void view_exit_info(ProtoViewApp *app);
void adjust_raw_view_scale(ProtoViewApp *app, uint32_t short_pulse_dur);
/* ui.c */ /* ui.c */
int ui_get_current_subview(ProtoViewApp *app);
void ui_show_available_subviews(Canvas *canvas, ProtoViewApp *app, int last_subview);
bool ui_process_subview_updown(ProtoViewApp *app, InputEvent input, int last_subview);
void ui_show_keyboard(ProtoViewApp *app, char *buffer, uint32_t buflen,
void (*done_callback)(void*));
void ui_dismiss_keyboard(ProtoViewApp *app);
void ui_show_alert(ProtoViewApp *app, const char *text, uint32_t ttl);
void ui_dismiss_alert(ProtoViewApp *app);
void ui_draw_alert_if_needed(Canvas *canvas, ProtoViewApp *app);
void canvas_draw_str_with_border(Canvas* canvas, uint8_t x, uint8_t y, const char* str, Color text_color, Color border_color); void canvas_draw_str_with_border(Canvas* canvas, uint8_t x, uint8_t y, const char* str, Color text_color, Color border_color);
/* fields.c */
void fieldset_free(ProtoViewFieldSet *fs);
ProtoViewFieldSet *fieldset_new(void);
void fieldset_add_int(ProtoViewFieldSet *fs, const char *name, int64_t val, uint8_t bits);
void fieldset_add_uint(ProtoViewFieldSet *fs, const char *name, uint64_t uval, uint8_t bits);
void fieldset_add_hex(ProtoViewFieldSet *fs, const char *name, uint64_t uval, uint8_t bits);
void fieldset_add_bin(ProtoViewFieldSet *fs, const char *name, uint64_t uval, uint8_t bits);
void fieldset_add_str(ProtoViewFieldSet *fs, const char *name, const char *s);
void fieldset_add_bytes(ProtoViewFieldSet *fs, const char *name, const uint8_t *bytes, uint32_t count);
void fieldset_add_float(ProtoViewFieldSet *fs, const char *name, float val, uint32_t digits_after_dot);
const char *field_get_type_name(ProtoViewField *f);
int field_to_string(char *buf, size_t len, ProtoViewField *f);
bool field_set_from_string(ProtoViewField *f, char *buf, size_t len);
bool field_incr_value(ProtoViewField *f, int incr);
void fieldset_copy_matching_fields(ProtoViewFieldSet *dst, ProtoViewFieldSet *src);
void field_set_from_field(ProtoViewField *dst, ProtoViewField *src);
/* crc.c */ /* crc.c */
uint8_t crc8(const uint8_t *data, size_t len, uint8_t init, uint8_t poly); uint8_t crc8(const uint8_t *data, size_t len, uint8_t init, uint8_t poly);

View File

@@ -13,17 +13,19 @@ void raw_sampling_worker_start(ProtoViewApp *app);
void raw_sampling_worker_stop(ProtoViewApp *app); void raw_sampling_worker_stop(ProtoViewApp *app);
ProtoViewModulation ProtoViewModulations[] = { ProtoViewModulation ProtoViewModulations[] = {
{"OOK 650Khz", FuriHalSubGhzPresetOok650Async, NULL}, {"OOK 650Khz", "FuriHalSubGhzPresetOok650Async",
{"OOK 270Khz", FuriHalSubGhzPresetOok270Async, NULL}, FuriHalSubGhzPresetOok650Async, NULL},
{"2FSK 2.38Khz", FuriHalSubGhzPreset2FSKDev238Async, NULL}, {"OOK 270Khz", "FuriHalSubGhzPresetOok270Async",
{"2FSK 47.6Khz", FuriHalSubGhzPreset2FSKDev476Async, NULL}, FuriHalSubGhzPresetOok270Async, NULL},
{"MSK", FuriHalSubGhzPresetMSK99_97KbAsync, NULL}, {"2FSK 2.38Khz", "FuriHalSubGhzPreset2FSKDev238Async",
{"GFSK", FuriHalSubGhzPresetGFSK9_99KbAsync, NULL}, FuriHalSubGhzPreset2FSKDev238Async, NULL},
{"TPMS 1 (FSK)", 0, (uint8_t*)protoview_subghz_tpms1_fsk_async_regs}, {"2FSK 47.6Khz", "FuriHalSubGhzPreset2FSKDev476Async",
{"TPMS 2 (OOK)", 0, (uint8_t*)protoview_subghz_tpms2_ook_async_regs}, FuriHalSubGhzPreset2FSKDev476Async, NULL},
{"TPMS 3 (FSK)", 0, (uint8_t*)protoview_subghz_tpms3_fsk_async_regs}, {"TPMS 1 (FSK)", NULL, 0, (uint8_t*)protoview_subghz_tpms1_fsk_async_regs},
{"TPMS 4 (FSK)", 0, (uint8_t*)protoview_subghz_tpms4_fsk_async_regs}, {"TPMS 2 (OOK)", NULL, 0, (uint8_t*)protoview_subghz_tpms2_ook_async_regs},
{NULL, 0, NULL} /* End of list sentinel. */ {"TPMS 3 (FSK)", NULL, 0, (uint8_t*)protoview_subghz_tpms3_fsk_async_regs},
{"TPMS 4 (FSK)", NULL, 0, (uint8_t*)protoview_subghz_tpms4_fsk_async_regs},
{NULL, NULL, 0, NULL} /* End of list sentinel. */
}; };
/* Called after the application initialization in order to setup the /* Called after the application initialization in order to setup the
@@ -35,17 +37,26 @@ void radio_begin(ProtoViewApp* app) {
furi_hal_subghz_reset(); furi_hal_subghz_reset();
furi_hal_subghz_idle(); furi_hal_subghz_idle();
/* Power circuits are noisy. Suppressing the charge while we use
* ProtoView will improve the RF performances. */
furi_hal_power_suppress_charge_enter();
/* The CC1101 preset can be either one of the standard presets, if /* The CC1101 preset can be either one of the standard presets, if
* the modulation "custom" field is NULL, or a custom preset we * the modulation "custom" field is NULL, or a custom preset we
* defined in custom_presets.h. */ * defined in custom_presets.h. */
if (ProtoViewModulations[app->modulation].custom == NULL) if (ProtoViewModulations[app->modulation].custom == NULL) {
furi_hal_subghz_load_preset(ProtoViewModulations[app->modulation].preset); furi_hal_subghz_load_preset(
else ProtoViewModulations[app->modulation].preset);
furi_hal_subghz_load_custom_preset(ProtoViewModulations[app->modulation].custom); } else {
furi_hal_subghz_load_custom_preset(
ProtoViewModulations[app->modulation].custom);
}
furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow); furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow);
app->txrx->txrx_state = TxRxStateIDLE; app->txrx->txrx_state = TxRxStateIDLE;
} }
/* ================================= Reception ============================== */
/* Setup subghz to start receiving using a background worker. */ /* Setup subghz to start receiving using a background worker. */
uint32_t radio_rx(ProtoViewApp* app) { uint32_t radio_rx(ProtoViewApp* app) {
furi_assert(app); furi_assert(app);
@@ -76,6 +87,7 @@ uint32_t radio_rx(ProtoViewApp* app) {
/* Stop subghz worker (if active), put radio on idle state. */ /* Stop subghz worker (if active), put radio on idle state. */
void radio_rx_end(ProtoViewApp* app) { void radio_rx_end(ProtoViewApp* app) {
furi_assert(app); furi_assert(app);
if (app->txrx->txrx_state == TxRxStateRx) { if (app->txrx->txrx_state == TxRxStateRx) {
if (!app->txrx->debug_timer_sampling) { if (!app->txrx->debug_timer_sampling) {
if(subghz_worker_is_running(app->txrx->worker)) { if(subghz_worker_is_running(app->txrx->worker)) {
@@ -100,6 +112,33 @@ void radio_sleep(ProtoViewApp* app) {
} }
furi_hal_subghz_sleep(); furi_hal_subghz_sleep();
app->txrx->txrx_state = TxRxStateSleep; app->txrx->txrx_state = TxRxStateSleep;
furi_hal_power_suppress_charge_exit();
}
/* =============================== Transmission ============================= */
/* This function suspends the current RX state, switches to TX mode,
* transmits the signal provided by the callback data_feeder, and later
* restores the RX state if there was one. */
void radio_tx_signal(ProtoViewApp *app, FuriHalSubGhzAsyncTxCallback data_feeder, void *ctx) {
TxRxState oldstate = app->txrx->txrx_state;
if (oldstate == TxRxStateRx) radio_rx_end(app);
radio_begin(app);
furi_hal_subghz_idle();
uint32_t value = furi_hal_subghz_set_frequency_and_path(app->frequency);
FURI_LOG_E(TAG, "Switched to frequency: %lu", value);
furi_hal_gpio_write(&gpio_cc1101_g0, false);
furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow);
furi_hal_subghz_start_async_tx(data_feeder, ctx);
while(!furi_hal_subghz_is_async_tx_complete()) furi_delay_ms(10);
furi_hal_subghz_stop_async_tx();
furi_hal_subghz_idle();
radio_begin(app);
if (oldstate == TxRxStateRx) radio_rx(app);
} }
/* ============================= Raw sampling mode ============================= /* ============================= Raw sampling mode =============================

View File

@@ -1,10 +0,0 @@
This is the binary distribution of the application. If you don't want
to build it from source, just take `protoview.fap` file and drop it into the
following Flipper Zero location:
/ext/apps/Tools
The `ext` part means that we are in the SD card. So if you don't want
to use the Android (or other) application to upload the file,
you can just take out the SD card, insert it in your computer,
copy the fine into `apps/Tools`, and that's it.

View File

@@ -1,4 +0,0 @@
#!/bin/sh
BINPATH="/Users/antirez/hack/flipper/official/build/f7-firmware-D/.extapps/protoview.fap"
cp $BINPATH .
git commit -a -m 'Binary file updated.'

View File

@@ -104,6 +104,9 @@ static uint8_t protoview_subghz_tpms1_fsk_async_regs[][2] = {
/* End */ /* End */
{0, 0}, {0, 0},
/* CC1101 2FSK PATABLE. */
{0xC0, 0}, {0,0}, {0,0}, {0,0}
}; };
/* This is like the default Flipper OOK 640Khz bandwidth preset, but /* This is like the default Flipper OOK 640Khz bandwidth preset, but
@@ -125,8 +128,8 @@ static const uint8_t protoview_subghz_tpms2_ook_async_regs[][2] = {
{CC1101_MDMCFG0, 0x00}, // Channel spacing is 25kHz {CC1101_MDMCFG0, 0x00}, // Channel spacing is 25kHz
{CC1101_MDMCFG1, 0x00}, // Channel spacing is 25kHz {CC1101_MDMCFG1, 0x00}, // Channel spacing is 25kHz
{CC1101_MDMCFG2, 0x30}, // Format ASK/OOK, No preamble/sync {CC1101_MDMCFG2, 0x30}, // Format ASK/OOK, No preamble/sync
{CC1101_MDMCFG3, 0x93}, // Data rate is 10kBaud {CC1101_MDMCFG3, /*0x93*/ 0x32}, // Data rate is 10kBaud
{CC1101_MDMCFG4, 0x18}, // Rx BW filter is 650.000kHz {CC1101_MDMCFG4, /*0x18*/ 0x17}, // Rx BW filter is 650.000kHz
/* Main Radio Control State Machine */ /* Main Radio Control State Machine */
{CC1101_MCSM0, 0x18}, // Autocalibrate on idle-to-rx/tx, PO_TIMEOUT is 64 cycles(149-155us) {CC1101_MCSM0, 0x18}, // Autocalibrate on idle-to-rx/tx, PO_TIMEOUT is 64 cycles(149-155us)
@@ -151,6 +154,9 @@ static const uint8_t protoview_subghz_tpms2_ook_async_regs[][2] = {
/* End */ /* End */
{0, 0}, {0, 0},
/* CC1101 OOK PATABLE. */
{0, 0xC0}, {0,0}, {0,0}, {0,0}
}; };
/* 40 KBaud, 2FSK, 28 kHz deviation, 270 Khz bandwidth filter. */ /* 40 KBaud, 2FSK, 28 kHz deviation, 270 Khz bandwidth filter. */
@@ -196,6 +202,9 @@ static uint8_t protoview_subghz_tpms3_fsk_async_regs[][2] = {
/* End */ /* End */
{0, 0}, {0, 0},
/* CC1101 2FSK PATABLE. */
{0xC0, 0}, {0,0}, {0,0}, {0,0}
}; };
/* FSK 19k dev, 325 Khz filter, 20kBaud. Works well with Toyota. */ /* FSK 19k dev, 325 Khz filter, 20kBaud. Works well with Toyota. */
@@ -239,6 +248,8 @@ static uint8_t protoview_subghz_tpms4_fsk_async_regs[][2] = {
/* End */ /* End */
{0, 0}, {0, 0},
/* CC1101 2FSK PATABLE. */
{0xC0, 0}, {0,0}, {0,0}, {0,0}
}; };

View File

@@ -6,7 +6,7 @@
#include <furi/core/string.h> #include <furi/core/string.h>
#include <lib/subghz/registry.h> #include <lib/subghz/registry.h>
#include <lib/subghz/protocols/base.h> #include <lib/subghz/protocols/base.h>
#include "app_buffer.h" #include "raw_samples.h"
#define TAG "PROTOVIEW-protocol" #define TAG "PROTOVIEW-protocol"

View File

@@ -0,0 +1,358 @@
/* Copyright (C) 2022-2023 Salvatore Sanfilippo -- All Rights Reserved
* See the LICENSE file for information about the license.
*
* Protocol fields implementation. */
#include "app.h"
/* Create a new field of the specified type. Without populating its
* type-specific value. */
static ProtoViewField *field_new(ProtoViewFieldType type, const char *name) {
ProtoViewField *f = malloc(sizeof(*f));
f->type = type;
f->name = strdup(name);
return f;
}
/* Free only the auxiliary data of a field, used to represent the
* current type. Name and type are not touched. */
static void field_free_aux_data(ProtoViewField *f) {
switch(f->type) {
case FieldTypeStr: free(f->str); break;
case FieldTypeBytes: free(f->bytes); break;
default: break; // Nothing to free for other types.
}
}
/* Free a field an associated data. */
static void field_free(ProtoViewField *f) {
field_free_aux_data(f);
free(f->name);
free(f);
}
/* Return the type of the field as string. */
const char *field_get_type_name(ProtoViewField *f) {
switch(f->type) {
case FieldTypeStr: return "str";
case FieldTypeSignedInt: return "int";
case FieldTypeUnsignedInt: return "uint";
case FieldTypeBinary: return "bin";
case FieldTypeHex: return "hex";
case FieldTypeBytes: return "bytes";
case FieldTypeFloat: return "float";
}
return "unknown";
}
/* Set a string representation of the specified field in buf. */
int field_to_string(char *buf, size_t len, ProtoViewField *f) {
switch(f->type) {
case FieldTypeStr:
return snprintf(buf,len,"%s", f->str);
case FieldTypeSignedInt:
return snprintf(buf,len,"%lld", (long long) f->value);
case FieldTypeUnsignedInt:
return snprintf(buf,len,"%llu", (unsigned long long) f->uvalue);
case FieldTypeBinary:
{
uint64_t test_bit = (1 << (f->len-1));
uint64_t idx = 0;
while(idx < len-1 && test_bit) {
buf[idx++] = (f->uvalue & test_bit) ? '1' : '0';
test_bit >>= 1;
}
buf[idx] = 0;
return idx;
}
case FieldTypeHex:
return snprintf(buf, len, "%*llX", (int)(f->len+7)/8, f->uvalue);
case FieldTypeFloat:
return snprintf(buf, len, "%.*f", (int)f->len, (double)f->fvalue);
case FieldTypeBytes:
{
uint64_t idx = 0;
while(idx < len-1 && idx < f->len) {
const char *charset = "0123456789ABCDEF";
uint32_t nibble = idx & 1 ?
(f->bytes[idx/2] & 0xf) :
(f->bytes[idx/2] >> 4);
buf[idx++] = charset[nibble];
}
buf[idx] = 0;
return idx;
}
}
return 0;
}
/* Set the field value from its string representation in 'buf'.
* The field type must already be set and the field should be valid.
* The string represenation 'buf' must be null termianted. Note that
* even when representing binary values containing zero, this values
* are taken as representations, so that would be the string "00" as
* the Bytes type representation.
*
* The function returns true if the filed was successfully set to the
* new value, otherwise if the specified value is invalid for the
* field type, false is returned. */
bool field_set_from_string(ProtoViewField *f, char *buf, size_t len) {
// Initialize values to zero since the Flipper sscanf() implementation
// is fuzzy... may populate only part of the value.
long long val = 0;
unsigned long long uval = 0;
float fval = 0;
switch(f->type) {
case FieldTypeStr:
free(f->str);
f->len = len;
f->str = malloc(len+1);
memcpy(f->str,buf,len+1);
break;
case FieldTypeSignedInt:
if (!sscanf(buf,"%lld",&val)) return false;
f->value = val;
break;
case FieldTypeUnsignedInt:
if (!sscanf(buf,"%llu",&uval)) return false;
f->uvalue = uval;
break;
case FieldTypeBinary:
{
uint64_t bit_to_set = (1 << (len-1));
uint64_t idx = 0;
uval = 0;
while(buf[idx]) {
if (buf[idx] == '1') uval |= bit_to_set;
else if (buf[idx] != '0') return false;
bit_to_set >>= 1;
idx++;
}
f->uvalue = uval;
}
break;
case FieldTypeHex:
if (!sscanf(buf,"%llx",&uval) &&
!sscanf(buf,"%llX",&uval)) return false;
f->uvalue = uval;
break;
case FieldTypeFloat:
if (!sscanf(buf,"%f",&fval)) return false;
f->fvalue = fval;
break;
case FieldTypeBytes:
{
if (len > f->len) return false;
uint64_t idx = 0;
while(buf[idx]) {
uint8_t nibble = 0;
char c = toupper(buf[idx]);
if (c >= '0' && c <= '9') nibble = c-'0';
else if (c >= 'A' && c <= 'F') nibble = 10+(c-'A');
else return false;
if (idx & 1) {
f->bytes[idx/2] =
(f->bytes[idx/2] & 0xF0) | nibble;
} else {
f->bytes[idx/2] =
(f->bytes[idx/2] & 0x0F) | (nibble<<4);
}
idx++;
}
buf[idx] = 0;
}
break;
}
return true;
}
/* Set the 'dst' field to contain a copy of the value of the 'src'
* field. The field name is not modified. */
void field_set_from_field(ProtoViewField *dst, ProtoViewField *src) {
field_free_aux_data(dst);
dst->type = src->type;
dst->len = src->len;
switch(src->type) {
case FieldTypeStr:
dst->str = strdup(src->str);
break;
case FieldTypeBytes:
dst->bytes = malloc(src->len);
memcpy(dst->bytes,src->bytes,dst->len);
break;
case FieldTypeSignedInt:
dst->value = src->value;
break;
case FieldTypeUnsignedInt:
case FieldTypeBinary:
case FieldTypeHex:
dst->uvalue = src->uvalue;
break;
case FieldTypeFloat:
dst->fvalue = src->fvalue;
break;
}
}
/* Increment the specified field value of 'incr'. If the field type
* does not support increments false is returned, otherwise the
* action is performed. */
bool field_incr_value(ProtoViewField *f, int incr) {
switch(f->type) {
case FieldTypeStr: return false;
case FieldTypeSignedInt: {
/* Wrap around depending on the number of bits (f->len)
* the integer was declared to have. */
int64_t max = (1ULL << (f->len-1))-1;
int64_t min = -max-1;
int64_t v = (int64_t)f->value + incr;
if (v > max) v = min+(v-max-1);
if (v < min) v = max+(v-min+1);
f->value = v;
break;
}
case FieldTypeBinary:
case FieldTypeHex:
case FieldTypeUnsignedInt: {
/* Wrap around like for the unsigned case, but here
* is simpler. */
uint64_t max = (1ULL << f->len)-1; // Broken for 64 bits.
uint64_t uv = (uint64_t)f->value + incr;
if (uv > max) uv = uv & max;
f->uvalue = uv;
break;
}
case FieldTypeFloat:
f->fvalue += incr;
break;
case FieldTypeBytes: {
// For bytes we only support single unit increments.
if (incr != -1 && incr != 1) return false;
for (int j = f->len-1; j >= 0; j--) {
uint8_t nibble = (j&1) ? (f->bytes[j/2] & 0x0F) :
((f->bytes[j/2] & 0xF0) >> 4);
nibble += incr;
nibble &= 0x0F;
f->bytes[j/2] = (j&1) ? ((f->bytes[j/2] & 0xF0) | nibble) :
((f->bytes[j/2] & 0x0F) | (nibble<<4));
/* Propagate the operation on overflow of this nibble. */
if ((incr == 1 && nibble == 0) ||
(incr == -1 && nibble == 0xf))
{
continue;
}
break; // Otherwise stop the loop here.
}
break;
}
}
return true;
}
/* Free a field set and its contained fields. */
void fieldset_free(ProtoViewFieldSet *fs) {
for (uint32_t j = 0; j < fs->numfields; j++)
field_free(fs->fields[j]);
free(fs->fields);
free(fs);
}
/* Allocate and init an empty field set. */
ProtoViewFieldSet *fieldset_new(void) {
ProtoViewFieldSet *fs = malloc(sizeof(*fs));
fs->numfields = 0;
fs->fields = NULL;
return fs;
}
/* Append an already allocated field at the end of the specified field set. */
static void fieldset_add_field(ProtoViewFieldSet *fs, ProtoViewField *field) {
fs->numfields++;
fs->fields = realloc(fs->fields,sizeof(ProtoViewField*)*fs->numfields);
fs->fields[fs->numfields-1] = field;
}
/* Allocate and append an integer field. */
void fieldset_add_int(ProtoViewFieldSet *fs, const char *name, int64_t val, uint8_t bits) {
ProtoViewField *f = field_new(FieldTypeSignedInt,name);
f->value = val;
f->len = bits;
fieldset_add_field(fs,f);
}
/* Allocate and append an unsigned field. */
void fieldset_add_uint(ProtoViewFieldSet *fs, const char *name, uint64_t uval, uint8_t bits) {
ProtoViewField *f = field_new(FieldTypeUnsignedInt,name);
f->uvalue = uval;
f->len = bits;
fieldset_add_field(fs,f);
}
/* Allocate and append a hex field. This is an unsigned number but
* with an hex representation. */
void fieldset_add_hex(ProtoViewFieldSet *fs, const char *name, uint64_t uval, uint8_t bits) {
ProtoViewField *f = field_new(FieldTypeHex,name);
f->uvalue = uval;
f->len = bits;
fieldset_add_field(fs,f);
}
/* Allocate and append a bin field. This is an unsigned number but
* with a binary representation. */
void fieldset_add_bin(ProtoViewFieldSet *fs, const char *name, uint64_t uval, uint8_t bits) {
ProtoViewField *f = field_new(FieldTypeBinary,name);
f->uvalue = uval;
f->len = bits;
fieldset_add_field(fs,f);
}
/* Allocate and append a string field. */
void fieldset_add_str(ProtoViewFieldSet *fs, const char *name, const char *s) {
ProtoViewField *f = field_new(FieldTypeStr,name);
f->str = strdup(s);
f->len = strlen(s);
fieldset_add_field(fs,f);
}
/* Allocate and append a bytes field. Note that 'count' is specified in
* nibbles (bytes*2). */
void fieldset_add_bytes(ProtoViewFieldSet *fs, const char *name, const uint8_t *bytes, uint32_t count_nibbles) {
uint32_t numbytes = (count_nibbles+count_nibbles%2)/2;
ProtoViewField *f = field_new(FieldTypeBytes,name);
f->bytes = malloc(numbytes);
memcpy(f->bytes,bytes,numbytes);
f->len = count_nibbles;
fieldset_add_field(fs,f);
}
/* Allocate and append a float field. */
void fieldset_add_float(ProtoViewFieldSet *fs, const char *name, float val, uint32_t digits_after_dot) {
ProtoViewField *f = field_new(FieldTypeFloat,name);
f->fvalue = val;
f->len = digits_after_dot;
fieldset_add_field(fs,f);
}
/* For each field of the destination filedset 'dst', look for a matching
* field name/type in the source fieldset 'src', and if one is found copy
* its value into the 'dst' field. */
void fieldset_copy_matching_fields(ProtoViewFieldSet *dst,
ProtoViewFieldSet *src)
{
for (uint32_t j = 0; j < dst->numfields; j++) {
for (uint32_t i = 0; i < src->numfields; i++) {
if (dst->fields[j]->type == src->fields[i]->type &&
!strcmp(dst->fields[j]->name,src->fields[i]->name))
{
field_set_from_field(dst->fields[j],
src->fields[i]);
}
}
}
}

View File

@@ -25,6 +25,9 @@ static bool decode(uint8_t *bits, uint32_t numbytes, uint32_t numbits, ProtoView
} }
if (off == BITMAP_SEEK_NOT_FOUND) return false; if (off == BITMAP_SEEK_NOT_FOUND) return false;
if (DEBUG_MSG) FURI_LOG_E(TAG, "B4B1 preamble at: %lu",off); if (DEBUG_MSG) FURI_LOG_E(TAG, "B4B1 preamble at: %lu",off);
info->start_off = off;
// Seek data setction. Why -1? Last bit is data.
off += strlen(sync_patterns[j])-1; off += strlen(sync_patterns[j])-1;
uint8_t d[3]; /* 24 bits of data. */ uint8_t d[3]; /* 24 bits of data. */
@@ -32,13 +35,54 @@ static bool decode(uint8_t *bits, uint32_t numbytes, uint32_t numbits, ProtoView
convert_from_line_code(d,sizeof(d),bits,numbytes,off,"1000","1110"); convert_from_line_code(d,sizeof(d),bits,numbytes,off,"1000","1110");
if (DEBUG_MSG) FURI_LOG_E(TAG, "B4B1 decoded: %lu",decoded); if (DEBUG_MSG) FURI_LOG_E(TAG, "B4B1 decoded: %lu",decoded);
if (decoded != 24) return false; if (decoded < 24) return false;
snprintf(info->name,PROTOVIEW_MSG_STR_LEN,"PT/SC remote");
snprintf(info->raw,PROTOVIEW_MSG_STR_LEN,"%02X%02X%02X",d[0],d[1],d[2]); off += 24*4; // seek to end symbol offset to calculate the length.
info->len = off+(4*24); off++; // In this protocol there is a final pulse as terminator.
info->pulses_count = off - info->start_off;
fieldset_add_bytes(info->fieldset,"id",d,5);
fieldset_add_uint(info->fieldset,"button",d[2]&0xf,4);
return true; return true;
} }
/* Give fields and defaults for the signal creator. */
static void get_fields(ProtoViewFieldSet *fieldset) {
uint8_t default_id[3]= {0xAB, 0xCD, 0xE0};
fieldset_add_bytes(fieldset,"id",default_id,5);
fieldset_add_uint(fieldset,"button",1,4);
}
/* Create a signal. */
static void build_message(RawSamplesBuffer *samples, ProtoViewFieldSet *fs)
{
uint32_t te = 334; // Short pulse duration in microseconds.
// Sync: 1 te pulse, 31 te gap.
raw_samples_add(samples,true,te);
raw_samples_add(samples,false,te*31);
// ID + button state
uint8_t data[3];
memcpy(data,fs->fields[0]->bytes,3);
data[2] = (data[2]&0xF0) | (fs->fields[1]->uvalue & 0xF);
for (uint32_t j = 0; j < 24; j++) {
if (bitmap_get(data,sizeof(data),j)) {
raw_samples_add(samples,true,te*3);
raw_samples_add(samples,false,te);
} else {
raw_samples_add(samples,true,te);
raw_samples_add(samples,false,te*3);
}
}
// Signal terminator. Just a single short pulse.
raw_samples_add(samples,true,te);
}
ProtoViewDecoder B4B1Decoder = { ProtoViewDecoder B4B1Decoder = {
"B4B1", decode .name = "PT/SC remote",
.decode = decode,
.get_fields = get_fields,
.build_message = build_message
}; };

View File

@@ -32,9 +32,11 @@ static bool decode(uint8_t *bits, uint32_t numbytes, uint32_t numbits, ProtoView
const char *sync_pattern = "101010101010101010101010" "0000"; const char *sync_pattern = "101010101010101010101010" "0000";
uint8_t sync_len = 24+4; uint8_t sync_len = 24+4;
if (numbits-sync_len+sync_len < 3*66) return false; if (numbits-sync_len+sync_len < 3*66) return false;
uint64_t off = bitmap_seek_bits(bits,numbytes,0,numbits,sync_pattern); uint32_t off = bitmap_seek_bits(bits,numbytes,0,numbits,sync_pattern);
if (off == BITMAP_SEEK_NOT_FOUND) return false; if (off == BITMAP_SEEK_NOT_FOUND) return false;
off += sync_len;
info->start_off = off;
off += sync_len; // Seek start of message.
/* Now there is half the gap left, but we allow from 3 to 7, instead of 5 /* Now there is half the gap left, but we allow from 3 to 7, instead of 5
* symbols of gap, to avoid missing the signal for a matter of wrong * symbols of gap, to avoid missing the signal for a matter of wrong
@@ -52,36 +54,72 @@ static bool decode(uint8_t *bits, uint32_t numbytes, uint32_t numbits, ProtoView
convert_from_line_code(raw,sizeof(raw),bits,numbytes,off, convert_from_line_code(raw,sizeof(raw),bits,numbytes,off,
"110","100"); /* Pulse width modulation. */ "110","100"); /* Pulse width modulation. */
FURI_LOG_E(TAG, "Keeloq decoded bits: %lu", decoded); FURI_LOG_E(TAG, "Keeloq decoded bits: %lu", decoded);
if (decoded < 66) return false; /* Require the full 66 bits. */ if (decoded < 66) return false; /* Require the full 66 bits. */
bitmap_reverse_bytes(raw,sizeof(raw)); /* Keeloq is LSB first. */
info->pulses_count = (off+66*3) - info->start_off;
bitmap_reverse_bytes_bits(raw,sizeof(raw)); /* Keeloq is LSB first. */
int buttons = raw[7]>>4; int buttons = raw[7]>>4;
int s3 = (buttons&1) != 0; int lowbat = (raw[8]&0x1) == 0; // Actual bit meaning: good battery level
int s0 = (buttons&2) != 0; int alwaysone = (raw[8]&0x2) != 0;
int s1 = (buttons&4) != 0;
int s2 = (buttons&8) != 0;
int remote_id = ((raw[7]&0x0f) << 24) | fieldset_add_bytes(info->fieldset,"encr",raw,8);
(raw[6] << 16) | raw[7] = raw[7]<<4; // Make ID bits contiguous
(raw[5] << 8) | fieldset_add_bytes(info->fieldset,"id",raw+4,7); // 28 bits, 7 nibbles
(raw[4] << 0); fieldset_add_bin(info->fieldset,"s[2,1,0,3]",buttons,4);
int lowbat = raw[8]&0x80; fieldset_add_bin(info->fieldset,"low battery",lowbat,1);
fieldset_add_bin(info->fieldset,"always one",alwaysone,1);
snprintf(info->name,sizeof(info->name),"%s","Keeloq remote");
snprintf(info->raw,sizeof(info->raw),"%02X%02X%02X%02X%02X%02X%02X%02X%02X",
raw[0],raw[1],raw[2],raw[3],raw[4],raw[5],
raw[6],raw[7],raw[8]);
snprintf(info->info1,sizeof(info->info1),"Encrpyted %02X%02X%02X%02X",
raw[3],raw[2],raw[1],raw[0]);
snprintf(info->info2,sizeof(info->info2),"ID %08X", remote_id);
snprintf(info->info3,sizeof(info->info3),"s0-s3: %d%d%d%d",
s0,s1,s2,s3);
snprintf(info->info4,sizeof(info->info4),"Low battery? %s",
lowbat ? "yes" : "no");
return true; return true;
} }
static void get_fields(ProtoViewFieldSet *fieldset) {
uint8_t remote_id[4] = {0xab, 0xcd, 0xef, 0xa0};
uint8_t encr[4] = {0xab, 0xab, 0xab, 0xab};
fieldset_add_bytes(fieldset,"encr",encr,8);
fieldset_add_bytes(fieldset,"id",remote_id,7);
fieldset_add_bin(fieldset,"s[2,1,0,3]",2,4);
fieldset_add_bin(fieldset,"low battery",0,1);
fieldset_add_bin(fieldset,"always one",1,1);
}
static void build_message(RawSamplesBuffer *samples, ProtoViewFieldSet *fieldset)
{
uint32_t te = 380; // Short pulse duration in microseconds.
// Sync: 12 pairs of pulse/gap + 9 times gap
for (int j = 0; j < 12; j++) {
raw_samples_add(samples,true,te);
raw_samples_add(samples,false,te);
}
raw_samples_add(samples,false,te*9);
// Data, 66 bits.
uint8_t data[9] = {0};
memcpy(data,fieldset->fields[0]->bytes,4); // Encrypted part.
memcpy(data+4,fieldset->fields[1]->bytes,4); // ID.
data[7] = data[7]>>4 | fieldset->fields[2]->uvalue << 4; // s[2,1,0,3]
int low_battery = fieldset->fields[3] != 0;
int always_one = fieldset->fields[4] != 0;
low_battery = !low_battery; // Bit real meaning is good battery level.
data[8] |= low_battery;
data[8] |= (always_one << 1);
bitmap_reverse_bytes_bits(data,sizeof(data)); /* Keeloq is LSB first. */
for (int j = 0; j < 66; j++) {
if (bitmap_get(data,9,j)) {
raw_samples_add(samples,true,te);
raw_samples_add(samples,false,te*2);
} else {
raw_samples_add(samples,true,te*2);
raw_samples_add(samples,false,te);
}
}
}
ProtoViewDecoder KeeloqDecoder = { ProtoViewDecoder KeeloqDecoder = {
"Keeloq", decode .name = "Keeloq",
.decode = decode,
.get_fields = get_fields,
.build_message = build_message
}; };

View File

@@ -13,6 +13,7 @@ static bool decode(uint8_t *bits, uint32_t numbytes, uint32_t numbits, ProtoView
if (off == BITMAP_SEEK_NOT_FOUND) return false; if (off == BITMAP_SEEK_NOT_FOUND) return false;
FURI_LOG_E(TAG, "Oregon2 preamble+sync found"); FURI_LOG_E(TAG, "Oregon2 preamble+sync found");
info->start_off = off;
off += 32; /* Skip preamble. */ off += 32; /* Skip preamble. */
uint8_t buffer[8], raw[8] = {0}; uint8_t buffer[8], raw[8] = {0};
@@ -21,8 +22,10 @@ static bool decode(uint8_t *bits, uint32_t numbytes, uint32_t numbits, ProtoView
FURI_LOG_E(TAG, "Oregon2 decoded bits: %lu", decoded); FURI_LOG_E(TAG, "Oregon2 decoded bits: %lu", decoded);
if (decoded < 11*4) return false; /* Minimum len to extract some data. */ if (decoded < 11*4) return false; /* Minimum len to extract some data. */
info->pulses_count = (off+11*4*4) - info->start_off;
char temp[3] = {0}, deviceid[2] = {0}, hum[2] = {0}; char temp[3] = {0}, hum[2] = {0};
uint8_t deviceid[2];
for (int j = 0; j < 64; j += 4) { for (int j = 0; j < 64; j += 4) {
uint8_t nib[1]; uint8_t nib[1];
nib[0] = (bitmap_get(buffer,8,j+0) | nib[0] = (bitmap_get(buffer,8,j+0) |
@@ -45,21 +48,20 @@ static bool decode(uint8_t *bits, uint32_t numbytes, uint32_t numbits, ProtoView
} }
} }
snprintf(info->name,sizeof(info->name),"%s","Oregon v2.1"); float tempval = ((temp[0]-'0')*10) +
/* The following line crashes the Flipper because of broken (temp[1]-'0') +
* snprintf() implementation. */ ((float)(temp[2]-'0')*0.1);
snprintf(info->raw,sizeof(info->raw),"%02X%02X%02X%02X%02X%02X%02X%02X", int humval = (hum[0]-'0')*10 + (hum[1]-'0');
raw[0],raw[1],raw[2],raw[3],raw[4],raw[5],
raw[6],raw[7]); fieldset_add_bytes(info->fieldset,"Sensor ID",deviceid,4);
snprintf(info->info1,sizeof(info->info1),"Sensor ID %02X%02X", fieldset_add_float(info->fieldset,"Temperature",tempval,1);
deviceid[0], deviceid[1]); fieldset_add_uint(info->fieldset,"Humidity",humval,7);
snprintf(info->info2,sizeof(info->info2),"Temperature %d%d.%d",
temp[0],temp[1],temp[2]);
snprintf(info->info3,sizeof(info->info3),"Humidity %d%d",
hum[0],hum[1]);
return true; return true;
} }
ProtoViewDecoder Oregon2Decoder = { ProtoViewDecoder Oregon2Decoder = {
"Oregon2", decode .name = "Oregon2",
.decode = decode,
.get_fields = NULL,
.build_message = NULL
}; };

View File

@@ -20,6 +20,7 @@ static bool decode(uint8_t *bits, uint32_t numbytes, uint32_t numbits, ProtoView
if (off == BITMAP_SEEK_NOT_FOUND) return false; if (off == BITMAP_SEEK_NOT_FOUND) return false;
FURI_LOG_E(TAG, "Renault TPMS preamble+sync found"); FURI_LOG_E(TAG, "Renault TPMS preamble+sync found");
info->start_off = off;
off += sync_len; /* Skip preamble + sync. */ off += sync_len; /* Skip preamble + sync. */
uint8_t raw[10]; uint8_t raw[10];
@@ -37,24 +38,24 @@ static bool decode(uint8_t *bits, uint32_t numbytes, uint32_t numbits, ProtoView
for (int j = 1; j < 10; j++) crc ^= raw[j]; for (int j = 1; j < 10; j++) crc ^= raw[j];
if (crc != 0) return false; /* Require sane checksum. */ if (crc != 0) return false; /* Require sane checksum. */
info->pulses_count = (off+8*10*2) - info->start_off;
int repeat = raw[5] & 0xf; int repeat = raw[5] & 0xf;
float kpa = (float)raw[6]*1.364; float kpa = (float)raw[6]*1.364;
int temp = raw[7]-50; int temp = raw[7]-50;
int battery = raw[8]; /* This may be the battery. It's not clear. */ int battery = raw[8]; /* This may be the battery. It's not clear. */
snprintf(info->name,sizeof(info->name),"%s","Citroen TPMS"); fieldset_add_bytes(info->fieldset,"Tire ID",raw+1,4*2);
snprintf(info->raw,sizeof(info->raw), fieldset_add_float(info->fieldset,"Pressure kpa",kpa,2);
"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", fieldset_add_int(info->fieldset,"Temperature C",temp,8);
raw[0],raw[1],raw[2],raw[3],raw[4],raw[5], fieldset_add_uint(info->fieldset,"Repeat",repeat,4);
raw[6],raw[7],raw[8],raw[9]); fieldset_add_uint(info->fieldset,"Battery",battery,8);
snprintf(info->info1,sizeof(info->info1),"Tire ID %02X%02X%02X%02X",
raw[1],raw[2],raw[3],raw[4]);
snprintf(info->info2,sizeof(info->info2),"Pressure %.2f kpa", (double)kpa);
snprintf(info->info3,sizeof(info->info3),"Temperature %d C", temp);
snprintf(info->info4,sizeof(info->info4),"Repeat %d, Bat %d", repeat, battery);
return true; return true;
} }
ProtoViewDecoder CitroenTPMSDecoder = { ProtoViewDecoder CitroenTPMSDecoder = {
"Citroen TPMS", decode .name = "Citroen TPMS",
.decode = decode,
.get_fields = NULL,
.build_message = NULL
}; };

View File

@@ -20,6 +20,7 @@ static bool decode(uint8_t *bits, uint32_t numbytes, uint32_t numbits, ProtoView
if (off == BITMAP_SEEK_NOT_FOUND) return false; if (off == BITMAP_SEEK_NOT_FOUND) return false;
FURI_LOG_E(TAG, "Fort TPMS preamble+sync found"); FURI_LOG_E(TAG, "Fort TPMS preamble+sync found");
info->start_off = off;
off += sync_len; /* Skip preamble and sync. */ off += sync_len; /* Skip preamble and sync. */
uint8_t raw[8]; uint8_t raw[8];
@@ -35,6 +36,8 @@ static bool decode(uint8_t *bits, uint32_t numbytes, uint32_t numbits, ProtoView
for (int j = 0; j < 7; j++) crc += raw[j]; for (int j = 0; j < 7; j++) crc += raw[j];
if (crc != raw[7]) return false; /* Require sane CRC. */ if (crc != raw[7]) return false; /* Require sane CRC. */
info->pulses_count = (off+8*8*2) - info->start_off;
float psi = 0.25 * (((raw[6]&0x20)<<3)|raw[4]); float psi = 0.25 * (((raw[6]&0x20)<<3)|raw[4]);
/* Temperature apperas to be valid only if the most significant /* Temperature apperas to be valid only if the most significant
@@ -44,21 +47,17 @@ static bool decode(uint8_t *bits, uint32_t numbytes, uint32_t numbits, ProtoView
int flags = raw[5] & 0x7f; int flags = raw[5] & 0x7f;
int car_moving = (raw[6] & 0x44) == 0x44; int car_moving = (raw[6] & 0x44) == 0x44;
snprintf(info->name,sizeof(info->name),"%s","Ford TPMS"); fieldset_add_bytes(info->fieldset,"Tire ID",raw,4*2);
snprintf(info->raw,sizeof(info->raw),"%02X%02X%02X%02X%02X%02X%02X%02X", fieldset_add_float(info->fieldset,"Pressure psi",psi,2);
raw[0],raw[1],raw[2],raw[3],raw[4],raw[5], fieldset_add_int(info->fieldset,"Temperature C",temp,8);
raw[6],raw[7]); fieldset_add_hex(info->fieldset,"Flags",flags,7);
snprintf(info->info1,sizeof(info->info1),"Tire ID %02X%02X%02X%02X", fieldset_add_uint(info->fieldset,"Moving",car_moving,1);
raw[0],raw[1],raw[2],raw[3]);
snprintf(info->info2,sizeof(info->info2),"Pressure %.2f psi", (double)psi);
if (temp)
snprintf(info->info3,sizeof(info->info3),"Temperature %d C", temp);
else
snprintf(info->info3,sizeof(info->info3),"Flags %d", flags);
snprintf(info->info4,sizeof(info->info4),"Moving %s", car_moving ? "yes" : "no");
return true; return true;
} }
ProtoViewDecoder FordTPMSDecoder = { ProtoViewDecoder FordTPMSDecoder = {
"Ford TPMS", decode .name = "Ford TPMS",
.decode = decode,
.get_fields = NULL,
.build_message = NULL
}; };

View File

@@ -25,7 +25,7 @@ static const char *test_vector =
static bool decode(uint8_t *bits, uint32_t numbytes, uint32_t numbits, ProtoViewMsgInfo *info) { static bool decode(uint8_t *bits, uint32_t numbytes, uint32_t numbits, ProtoViewMsgInfo *info) {
if (USE_TEST_VECTOR) { /* Test vector to check that decoding works. */ if (USE_TEST_VECTOR) { /* Test vector to check that decoding works. */
bitmap_set_pattern(bits,numbytes,test_vector); bitmap_set_pattern(bits,numbytes,0,test_vector);
numbits = strlen(test_vector); numbits = strlen(test_vector);
} }
@@ -36,6 +36,7 @@ static bool decode(uint8_t *bits, uint32_t numbytes, uint32_t numbits, ProtoView
if (off == BITMAP_SEEK_NOT_FOUND) return false; if (off == BITMAP_SEEK_NOT_FOUND) return false;
FURI_LOG_E(TAG, "Renault TPMS preamble+sync found"); FURI_LOG_E(TAG, "Renault TPMS preamble+sync found");
info->start_off = off;
off += 20; /* Skip preamble. */ off += 20; /* Skip preamble. */
uint8_t raw[9]; uint8_t raw[9];
@@ -47,20 +48,73 @@ static bool decode(uint8_t *bits, uint32_t numbytes, uint32_t numbits, ProtoView
if (decoded < 8*9) return false; /* Require the full 9 bytes. */ if (decoded < 8*9) return false; /* Require the full 9 bytes. */
if (crc8(raw,8,0,7) != raw[8]) return false; /* Require sane CRC. */ if (crc8(raw,8,0,7) != raw[8]) return false; /* Require sane CRC. */
float kpa = 0.75 *((uint32_t)((raw[0]&3)<<8) | raw[1]); info->pulses_count = (off+8*9*2) - info->start_off;
uint8_t flags = raw[0]>>2;
float kpa = 0.75 * ((uint32_t)((raw[0]&3)<<8) | raw[1]);
int temp = raw[2]-30; int temp = raw[2]-30;
snprintf(info->name,sizeof(info->name),"%s","Renault TPMS"); fieldset_add_bytes(info->fieldset,"Tire ID",raw+3,3*2);
snprintf(info->raw,sizeof(info->raw),"%02X%02X%02X%02X%02X%02X%02X%02X%02X", fieldset_add_float(info->fieldset,"Pressure kpa",kpa,2);
raw[0],raw[1],raw[2],raw[3],raw[4],raw[5], fieldset_add_int(info->fieldset,"Temperature C",temp,8);
raw[6],raw[7],raw[8]); fieldset_add_hex(info->fieldset,"Flags",flags,6);
snprintf(info->info1,sizeof(info->info1),"Tire ID %02X%02X%02X", fieldset_add_bytes(info->fieldset,"Unknown1",raw+6,2);
raw[3],raw[4],raw[5]); fieldset_add_bytes(info->fieldset,"Unknown2",raw+7,2);
snprintf(info->info2,sizeof(info->info2),"Pressure %.2f kpa", (double)kpa);
snprintf(info->info3,sizeof(info->info3),"Temperature %d C", temp);
return true; return true;
} }
/* Give fields and defaults for the signal creator. */
static void get_fields(ProtoViewFieldSet *fieldset) {
uint8_t default_id[3]= {0xAB, 0xCD, 0xEF};
fieldset_add_bytes(fieldset,"Tire ID",default_id,3*2);
fieldset_add_float(fieldset,"Pressure kpa",123,2);
fieldset_add_int(fieldset,"Temperature C",20,8);
// We don't know what flags are, but 1B is a common value.
fieldset_add_hex(fieldset,"Flags",0x1b,6);
fieldset_add_bytes(fieldset,"Unknown1",(uint8_t*)"\xff",2);
fieldset_add_bytes(fieldset,"Unknown2",(uint8_t*)"\xff",2);
}
/* Create a Renault TPMS signal, according to the fields provided. */
static void build_message(RawSamplesBuffer *samples, ProtoViewFieldSet *fieldset)
{
uint32_t te = 50; // Short pulse duration in microseconds.
// Preamble + sync
const char *psync = "01010101010101010101010101010110";
const char *p = psync;
while(*p) {
raw_samples_add_or_update(samples,*p == '1',te);
p++;
}
// Data, 9 bytes
uint8_t data[9] = {0};
unsigned int raw_pressure = fieldset->fields[1]->fvalue * 4 / 3;
data[0] = fieldset->fields[3]->uvalue << 2; // Flags
data[0] |= (raw_pressure >> 8) & 3; // Pressure kpa high 2 bits
data[1] = raw_pressure & 0xff; // Pressure kpa low 8 bits
data[2] = fieldset->fields[2]->value + 30; // Temperature C
memcpy(data+3,fieldset->fields[0]->bytes,6); // ID, 24 bits.
data[6] = fieldset->fields[4]->bytes[0]; // Unknown 1
data[7] = fieldset->fields[5]->bytes[0]; // Unknown 2
data[8] = crc8(data,8,0,7);
// Generate Manchester code for each bit
for (uint32_t j = 0; j < 9*8; j++) {
if (bitmap_get(data,sizeof(data),j)) {
raw_samples_add_or_update(samples,true,te);
raw_samples_add_or_update(samples,false,te);
} else {
raw_samples_add_or_update(samples,false,te);
raw_samples_add_or_update(samples,true,te);
}
}
}
ProtoViewDecoder RenaultTPMSDecoder = { ProtoViewDecoder RenaultTPMSDecoder = {
"Renault TPMS", decode .name = "Renault TPMS",
.decode = decode,
.get_fields = get_fields,
.build_message = build_message
}; };

View File

@@ -16,7 +16,7 @@ static const char *test_vector = "0000001111010101010110100101100101101010010101
static bool decode(uint8_t *bits, uint32_t numbytes, uint32_t numbits, ProtoViewMsgInfo *info) { static bool decode(uint8_t *bits, uint32_t numbytes, uint32_t numbits, ProtoViewMsgInfo *info) {
if (USE_TEST_VECTOR) { /* Test vector to check that decoding works. */ if (USE_TEST_VECTOR) { /* Test vector to check that decoding works. */
bitmap_set_pattern(bits,numbytes,test_vector); bitmap_set_pattern(bits,numbytes,0,test_vector);
numbits = strlen(test_vector); numbits = strlen(test_vector);
} }
@@ -27,11 +27,13 @@ static bool decode(uint8_t *bits, uint32_t numbytes, uint32_t numbits, ProtoView
if (off == BITMAP_SEEK_NOT_FOUND) return false; if (off == BITMAP_SEEK_NOT_FOUND) return false;
FURI_LOG_E(TAG, "Schrader TPMS gap+preamble found"); FURI_LOG_E(TAG, "Schrader TPMS gap+preamble found");
info->start_off = off;
off += 10; /* Skip just the long pulse and the first 3 bits of sync, so off += 10; /* Skip just the long pulse and the first 3 bits of sync, so
that we have the first byte of data with the sync nibble that we have the first byte of data with the sync nibble
0011 = 0x3. */ 0011 = 0x3. */
uint8_t raw[8]; uint8_t raw[8];
uint8_t id[4];
uint32_t decoded = uint32_t decoded =
convert_from_line_code(raw,sizeof(raw),bits,numbytes,off, convert_from_line_code(raw,sizeof(raw),bits,numbytes,off,
"01","10"); /* Manchester code. */ "01","10"); /* Manchester code. */
@@ -46,20 +48,24 @@ static bool decode(uint8_t *bits, uint32_t numbytes, uint32_t numbits, ProtoView
return false; return false;
} }
info->pulses_count = (off+8*8*2) - info->start_off;
float kpa = (float)raw[5]*2.5; float kpa = (float)raw[5]*2.5;
int temp = raw[6]-50; int temp = raw[6]-50;
id[0] = raw[1]&7;
id[1] = raw[2];
id[2] = raw[3];
id[3] = raw[4];
snprintf(info->name,sizeof(info->name),"%s","Schrader TPMS"); fieldset_add_bytes(info->fieldset,"Tire ID",id,4*2);
snprintf(info->raw,sizeof(info->raw),"%02X%02X%02X%02X%02X%02X%02X%02X", fieldset_add_float(info->fieldset,"Pressure kpa",kpa,2);
raw[0],raw[1],raw[2],raw[3],raw[4],raw[5], fieldset_add_int(info->fieldset,"Temperature C",temp,8);
raw[6],raw[7]);
snprintf(info->info1,sizeof(info->info1),"Tire ID %01X%02X%02X%02X",
raw[1]&7,raw[2],raw[3],raw[4]); /* Only 28 bits of ID, not 32. */
snprintf(info->info2,sizeof(info->info2),"Pressure %.2f kpa", (double)kpa);
snprintf(info->info3,sizeof(info->info3),"Temperature %d C", temp);
return true; return true;
} }
ProtoViewDecoder SchraderTPMSDecoder = { ProtoViewDecoder SchraderTPMSDecoder = {
"Schrader TPMS", decode .name = "Schrader TPMS",
.decode = decode,
.get_fields = NULL,
.build_message = NULL
}; };

View File

@@ -25,6 +25,7 @@ static bool decode(uint8_t *bits, uint32_t numbytes, uint32_t numbits, ProtoView
if (off == BITMAP_SEEK_NOT_FOUND) return false; if (off == BITMAP_SEEK_NOT_FOUND) return false;
FURI_LOG_E(TAG, "Schrader EG53MA4 TPMS preamble+sync found"); FURI_LOG_E(TAG, "Schrader EG53MA4 TPMS preamble+sync found");
info->start_off = off;
off += sync_len-8; /* Skip preamble, not sync that is part of the data. */ off += sync_len-8; /* Skip preamble, not sync that is part of the data. */
uint8_t raw[10]; uint8_t raw[10];
@@ -40,6 +41,8 @@ static bool decode(uint8_t *bits, uint32_t numbytes, uint32_t numbits, ProtoView
for (int j = 0; j < 9; j++) crc += raw[j]; for (int j = 0; j < 9; j++) crc += raw[j];
if (crc != raw[9]) return false; /* Require sane CRC. */ if (crc != raw[9]) return false; /* Require sane CRC. */
info->pulses_count = (off+10*8*2) - info->start_off;
/* To convert the raw pressure to kPa, RTL433 uses 2.5, but is likely /* To convert the raw pressure to kPa, RTL433 uses 2.5, but is likely
* wrong. Searching on Google for users experimenting with the value * wrong. Searching on Google for users experimenting with the value
* reported, the value appears to be 2.75. */ * reported, the value appears to be 2.75. */
@@ -47,17 +50,15 @@ static bool decode(uint8_t *bits, uint32_t numbytes, uint32_t numbits, ProtoView
int temp_f = raw[8]; int temp_f = raw[8];
int temp_c = (temp_f-32)*5/9; /* Convert Fahrenheit to Celsius. */ int temp_c = (temp_f-32)*5/9; /* Convert Fahrenheit to Celsius. */
snprintf(info->name,sizeof(info->name),"%s","Schrader EG53MA4 TPMS"); fieldset_add_bytes(info->fieldset,"Tire ID",raw+4,3*2);
snprintf(info->raw,sizeof(info->raw),"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", fieldset_add_float(info->fieldset,"Pressure kpa",kpa,2);
raw[0],raw[1],raw[2],raw[3],raw[4],raw[5], fieldset_add_int(info->fieldset,"Temperature C",temp_c,8);
raw[6],raw[7],raw[8],raw[9]);
snprintf(info->info1,sizeof(info->info1),"Tire ID %02X%02X%02X",
raw[4],raw[5],raw[6]); /* Only 28 bits of ID, not 32. */
snprintf(info->info2,sizeof(info->info2),"Pressure %.2f kpa", (double)kpa);
snprintf(info->info3,sizeof(info->info3),"Temperature %d C", temp_c);
return true; return true;
} }
ProtoViewDecoder SchraderEG53MA4TPMSDecoder = { ProtoViewDecoder SchraderEG53MA4TPMSDecoder = {
"Schrader EG53MA4 TPMS", decode .name = "Schrader EG53MA4 TPMS",
.decode = decode,
.get_fields = NULL,
.build_message = NULL
}; };

View File

@@ -42,6 +42,7 @@ static bool decode(uint8_t *bits, uint32_t numbytes, uint32_t numbits, ProtoView
for (j = 0; sync[j]; j++) { for (j = 0; sync[j]; j++) {
off = bitmap_seek_bits(bits,numbytes,0,numbits,sync[j]); off = bitmap_seek_bits(bits,numbytes,0,numbits,sync[j]);
if (off != BITMAP_SEEK_NOT_FOUND) { if (off != BITMAP_SEEK_NOT_FOUND) {
info->start_off = off;
off += strlen(sync[j])-2; off += strlen(sync[j])-2;
break; break;
} }
@@ -58,20 +59,31 @@ static bool decode(uint8_t *bits, uint32_t numbytes, uint32_t numbits, ProtoView
if (decoded < 8*9) return false; /* Require the full 8 bytes. */ if (decoded < 8*9) return false; /* Require the full 8 bytes. */
if (crc8(raw,8,0x80,7) != raw[8]) return false; /* Require sane CRC. */ if (crc8(raw,8,0x80,7) != raw[8]) return false; /* Require sane CRC. */
float kpa = (float)((raw[4]&0x7f)<<1 | raw[5]>>7) * 0.25 - 7; /* We detected a valid signal. However now info->start_off is actually
* pointing to the sync part, not the preamble of alternating 0 and 1.
* Protoview decoders get called with some space to the left, in order
* for the decoder itself to fix the signal if neeeded, so that its
* logical representation will be more accurate and better to save
* and retransmit. */
if (info->start_off >= 12) {
info->start_off -= 12;
bitmap_set_pattern(bits,numbytes,info->start_off,"010101010101");
}
info->pulses_count = (off+8*9*2) - info->start_off;
float psi = (float)((raw[4]&0x7f)<<1 | raw[5]>>7) * 0.25 - 7;
int temp = ((raw[5]&0x7f)<<1 | raw[6]>>7) - 40; int temp = ((raw[5]&0x7f)<<1 | raw[6]>>7) - 40;
snprintf(info->name,sizeof(info->name),"%s","Toyota TPMS"); fieldset_add_bytes(info->fieldset,"Tire ID",raw,4*2);
snprintf(info->raw,sizeof(info->raw),"%02X%02X%02X%02X%02X%02X%02X%02X%02X", fieldset_add_float(info->fieldset,"Pressure psi",psi,2);
raw[0],raw[1],raw[2],raw[3],raw[4],raw[5], fieldset_add_int(info->fieldset,"Temperature C",temp,8);
raw[6],raw[7],raw[8]);
snprintf(info->info1,sizeof(info->info1),"Tire ID %02X%02X%02X%02X",
raw[0],raw[1],raw[2],raw[3]);
snprintf(info->info1,sizeof(info->info1),"Pressure %.2f psi", (double)kpa);
snprintf(info->info2,sizeof(info->info2),"Temperature %d C", temp);
return true; return true;
} }
ProtoViewDecoder ToyotaTPMSDecoder = { ProtoViewDecoder ToyotaTPMSDecoder = {
"Toyota TPMS", decode .name = "Toyota TPMS",
.decode = decode,
.get_fields = NULL,
.build_message = NULL
}; };

View File

@@ -5,7 +5,7 @@
#include <furi/core/string.h> #include <furi/core/string.h>
#include <furi.h> #include <furi.h>
#include <furi_hal.h> #include <furi_hal.h>
#include "app_buffer.h" #include "raw_samples.h"
/* Allocate and initialize a samples buffer. */ /* Allocate and initialize a samples buffer. */
RawSamplesBuffer *raw_samples_alloc(void) { RawSamplesBuffer *raw_samples_alloc(void) {
@@ -48,6 +48,31 @@ void raw_samples_add(RawSamplesBuffer *s, bool level, uint32_t dur) {
furi_mutex_release(s->mutex); furi_mutex_release(s->mutex);
} }
/* This is like raw_samples_add(), however in case a sample of the
* same level of the previous one is added, the duration of the last
* sample is updated instead. Needed mainly for the decoders build_message()
* methods: it is simpler to write an encoder of a signal like that,
* just creating messages piece by piece.
*
* This function is a bit slower so the internal data sampling should
* be performed with raw_samples_add(). */
void raw_samples_add_or_update(RawSamplesBuffer *s, bool level, uint32_t dur) {
furi_mutex_acquire(s->mutex,FuriWaitForever);
uint32_t previdx = (s->idx-1) % RAW_SAMPLES_NUM;
if (s->samples[previdx].level == level &&
s->samples[previdx].dur != 0)
{
/* Update the last sample: it has the same level. */
s->samples[previdx].dur += dur;
} else {
/* Add a new sample. */
s->samples[s->idx].level = level;
s->samples[s->idx].dur = dur;
s->idx = (s->idx+1) % RAW_SAMPLES_NUM;
}
furi_mutex_release(s->mutex);
}
/* Get the sample from the buffer. It is possible to use out of range indexes /* Get the sample from the buffer. It is possible to use out of range indexes
* as 'idx' because the modulo operation will rewind back from the start. */ * as 'idx' because the modulo operation will rewind back from the start. */
void raw_samples_get(RawSamplesBuffer *s, uint32_t idx, bool *level, uint32_t *dur) void raw_samples_get(RawSamplesBuffer *s, uint32_t idx, bool *level, uint32_t *dur)

View File

@@ -26,6 +26,7 @@ RawSamplesBuffer *raw_samples_alloc(void);
void raw_samples_reset(RawSamplesBuffer *s); void raw_samples_reset(RawSamplesBuffer *s);
void raw_samples_center(RawSamplesBuffer *s, uint32_t offset); void raw_samples_center(RawSamplesBuffer *s, uint32_t offset);
void raw_samples_add(RawSamplesBuffer *s, bool level, uint32_t dur); void raw_samples_add(RawSamplesBuffer *s, bool level, uint32_t dur);
void raw_samples_add_or_update(RawSamplesBuffer *s, bool level, uint32_t dur);
void raw_samples_get(RawSamplesBuffer *s, uint32_t idx, bool *level, uint32_t *dur); void raw_samples_get(RawSamplesBuffer *s, uint32_t idx, bool *level, uint32_t *dur);
void raw_samples_copy(RawSamplesBuffer *dst, RawSamplesBuffer *src); void raw_samples_copy(RawSamplesBuffer *dst, RawSamplesBuffer *src);
void raw_samples_free(RawSamplesBuffer *s); void raw_samples_free(RawSamplesBuffer *s);

View File

@@ -4,7 +4,6 @@
#include "app.h" #include "app.h"
bool decode_signal(RawSamplesBuffer *s, uint64_t len, ProtoViewMsgInfo *info); bool decode_signal(RawSamplesBuffer *s, uint64_t len, ProtoViewMsgInfo *info);
void initialize_msg_info(ProtoViewMsgInfo *i);
/* ============================================================================= /* =============================================================================
* Raw signal detection * Raw signal detection
@@ -23,6 +22,8 @@ void reset_current_signal(ProtoViewApp *app) {
app->signal_decoded = false; app->signal_decoded = false;
raw_samples_reset(DetectedSamples); raw_samples_reset(DetectedSamples);
raw_samples_reset(RawSamples); raw_samples_reset(RawSamples);
free_msg_info(app->msg_info);
app->msg_info = NULL;
} }
/* This function starts scanning samples at offset idx looking for the /* This function starts scanning samples at offset idx looking for the
@@ -119,15 +120,40 @@ uint32_t search_coherent_signal(RawSamplesBuffer *s, uint32_t idx) {
return len; return len;
} }
/* Search the buffer with the stored signal (last N samples received) /* Called when we detect a message. Just blinks when the message was
* not decoded. Vibrates, too, when the message was correctly decoded. */
void notify_signal_detected(ProtoViewApp *app, bool decoded) {
static const NotificationSequence decoded_seq = {
&message_vibro_on,
&message_green_255,
&message_delay_50,
&message_green_0,
&message_vibro_off,
NULL
};
static const NotificationSequence unknown_seq = {
&message_red_255,
&message_delay_50,
&message_red_0,
NULL
};
if (decoded)
notification_message(app->notification, &decoded_seq);
else
notification_message(app->notification, &unknown_seq);
}
/* Search the source buffer with the stored signal (last N samples received)
* in order to find a coherent signal. If a signal that does not appear to * in order to find a coherent signal. If a signal that does not appear to
* be just noise is found, it is set in DetectedSamples global signal * be just noise is found, it is set in DetectedSamples global signal
* buffer, that is what is rendered on the screen. */ * buffer, that is what is rendered on the screen. */
void scan_for_signal(ProtoViewApp *app) { void scan_for_signal(ProtoViewApp *app, RawSamplesBuffer *source) {
/* We need to work on a copy: the RawSamples buffer is populated /* We need to work on a copy: the source buffer may be populated
* by the background thread receiving data. */ * by the background thread receiving data. */
RawSamplesBuffer *copy = raw_samples_alloc(); RawSamplesBuffer *copy = raw_samples_alloc();
raw_samples_copy(copy,RawSamples); raw_samples_copy(copy,source);
/* Try to seek on data that looks to have a regular high low high low /* Try to seek on data that looks to have a regular high low high low
* pattern. */ * pattern. */
@@ -135,7 +161,6 @@ void scan_for_signal(ProtoViewApp *app) {
than a few samples it's very easy to than a few samples it's very easy to
mistake noise for signal. */ mistake noise for signal. */
ProtoViewMsgInfo *info = malloc(sizeof(ProtoViewMsgInfo));
uint32_t i = 0; uint32_t i = 0;
while (i < copy->total-1) { while (i < copy->total-1) {
@@ -143,10 +168,16 @@ void scan_for_signal(ProtoViewApp *app) {
/* For messages that are long enough, attempt decoding. */ /* For messages that are long enough, attempt decoding. */
if (thislen > minlen) { if (thislen > minlen) {
initialize_msg_info(info); /* Allocate the message information that some decoder may
* fill, in case it is able to decode a message. */
ProtoViewMsgInfo *info = malloc(sizeof(ProtoViewMsgInfo));
init_msg_info(info,app);
info->short_pulse_dur = copy->short_pulse_dur;
uint32_t saved_idx = copy->idx; /* Save index, see later. */ uint32_t saved_idx = copy->idx; /* Save index, see later. */
/* decode_signal() expects the detected signal to start /* decode_signal() expects the detected signal to start
* from index .*/ * from index zero .*/
raw_samples_center(copy,i); raw_samples_center(copy,i);
bool decoded = decode_signal(copy,thislen,info); bool decoded = decode_signal(copy,thislen,info);
copy->idx = saved_idx; /* Restore the index as we are scanning copy->idx = saved_idx; /* Restore the index as we are scanning
@@ -158,7 +189,8 @@ void scan_for_signal(ProtoViewApp *app) {
if ((thislen > app->signal_bestlen && app->signal_decoded == false) if ((thislen > app->signal_bestlen && app->signal_decoded == false)
|| (app->signal_decoded == false && decoded)) || (app->signal_decoded == false && decoded))
{ {
app->signal_info = *info; free_msg_info(app->msg_info);
app->msg_info = info;
app->signal_bestlen = thislen; app->signal_bestlen = thislen;
app->signal_decoded = decoded; app->signal_decoded = decoded;
raw_samples_copy(DetectedSamples,copy); raw_samples_copy(DetectedSamples,copy);
@@ -166,18 +198,17 @@ void scan_for_signal(ProtoViewApp *app) {
FURI_LOG_E(TAG, "===> Displayed sample updated (%d samples %lu us)", FURI_LOG_E(TAG, "===> Displayed sample updated (%d samples %lu us)",
(int)thislen, DetectedSamples->short_pulse_dur); (int)thislen, DetectedSamples->short_pulse_dur);
/* Adjust raw view scale if the signal has an high adjust_raw_view_scale(app,DetectedSamples->short_pulse_dur);
* data rate. */ notify_signal_detected(app,decoded);
if (DetectedSamples->short_pulse_dur < 75) } else {
app->us_scale = 10; /* If the structure was not filled, discard it. Otherwise
else if (DetectedSamples->short_pulse_dur < 145) * now the owner is app->msg_info. */
app->us_scale = 30; free_msg_info(info);
} }
} }
i += thislen ? thislen : 1; i += thislen ? thislen : 1;
} }
raw_samples_free(copy); raw_samples_free(copy);
free(info);
} }
/* ============================================================================= /* =============================================================================
@@ -215,10 +246,104 @@ bool bitmap_get(uint8_t *b, uint32_t blen, uint32_t bitpos) {
return (b[byte] & (1<<bit)) != 0; return (b[byte] & (1<<bit)) != 0;
} }
/* Copy 'count' bits from the bitmap 's' of 'slen' total bytes, to the
* bitmap 'd' of 'dlen' total bytes. The bits are copied starting from
* offset 'soff' of the source bitmap to the offset 'doff' of the
* destination bitmap. */
void bitmap_copy(uint8_t *d, uint32_t dlen, uint32_t doff,
uint8_t *s, uint32_t slen, uint32_t soff,
uint32_t count)
{
/* If we are byte-aligned in both source and destination, use a fast
* path for the number of bytes we can consume this way. */
if ((doff & 7) == 0 && (soff & 7) == 0) {
uint32_t didx = doff/8;
uint32_t sidx = soff/8;
while(count > 8 && didx < dlen && sidx < slen) {
d[didx++] = s[sidx++];
count -= 8;
}
doff = didx * 8;
soff = sidx * 8;
/* Note that if we entered this path, the count at the end
* of the loop will be < 8. */
}
/* Copy the bits needed to reach an offset where we can copy
* two half bytes of src to a full byte of destination. */
while(count > 8 && (doff&7) != 0) {
bool bit = bitmap_get(s,slen,soff++);
bitmap_set(d,dlen,doff++,bit);
count--;
}
/* If we are here and count > 8, we have an offset that is byte aligned
* to the destination bitmap, but not aligned to the source bitmap.
* We can copy fast enough by shifting each two bytes of the original
* bitmap.
*
* This is how it works:
*
* dst:
* +--------+--------+--------+
* | 0 | 1 | 2 |
* | | | | <- data to fill
* +--------+--------+--------+
* ^
* |
* doff = 8
*
* src:
* +--------+--------+--------+
* | 0 | 1 | 2 |
* |hellowor|ld!HELLO|WORLDS!!| <- data to copy
* +--------+--------+--------+
* ^
* |
* soff = 11
*
* skew = 11%8 = 3
* each destination byte in dst will receive:
*
* dst[doff/8] = (src[soff/8] << skew) | (src[soff/8+1] >> (8-skew))
*
* dstbyte = doff/8 = 8/8 = 1
* srcbyte = soff/8 = 11/8 = 1
*
* so dst[1] will get:
* src[1] << 3, that is "ld!HELLO" << 3 = "HELLO..."
* xored with
* src[2] << 5, that is "WORLDS!!" >> 5 = ".....WOR"
* That is "HELLOWOR"
*/
if (count > 8) {
uint8_t skew = soff % 8; /* Don't worry, compiler will optimize. */
uint32_t didx = doff/8;
uint32_t sidx = soff/8;
while(count > 8 && didx < dlen && sidx < slen) {
d[didx] = ((s[sidx] << skew) |
(s[sidx+1] >> (8-skew)));
sidx++;
didx++;
soff += 8;
doff += 8;
count -= 8;
}
}
/* Here count is guaranteed to be < 8.
* Copy the final bits bit by bit. */
while(count) {
bool bit = bitmap_get(s,slen,soff++);
bitmap_set(d,dlen,doff++,bit);
count--;
}
}
/* We decode bits assuming the first bit we receive is the MSB /* We decode bits assuming the first bit we receive is the MSB
* (see bitmap_set/get functions). Certain devices send data * (see bitmap_set/get functions). Certain devices send data
* encoded in the reverse way. */ * encoded in the reverse way. */
void bitmap_reverse_bytes(uint8_t *p, uint32_t len) { void bitmap_reverse_bytes_bits(uint8_t *p, uint32_t len) {
for (uint32_t j = 0; j < len; j++) { for (uint32_t j = 0; j < len; j++) {
uint32_t b = p[j]; uint32_t b = p[j];
/* Step 1: swap the two nibbles: 12345678 -> 56781234 */ /* Step 1: swap the two nibbles: 12345678 -> 56781234 */
@@ -259,15 +384,17 @@ uint32_t bitmap_seek_bits(uint8_t *b, uint32_t blen, uint32_t startpos, uint32_t
return BITMAP_SEEK_NOT_FOUND; return BITMAP_SEEK_NOT_FOUND;
} }
/* Set the pattern 'pat' into the bitmap 'b' of max length 'blen' bytes. /* Set the pattern 'pat' into the bitmap 'b' of max length 'blen' bytes,
* starting from the specified offset.
*
* The pattern is given as a string of 0s and 1s characters, like "01101001". * The pattern is given as a string of 0s and 1s characters, like "01101001".
* This function is useful in order to set the test vectors in the protocol * This function is useful in order to set the test vectors in the protocol
* decoders, to see if the decoding works regardless of the fact we are able * decoders, to see if the decoding works regardless of the fact we are able
* to actually receive a given signal. */ * to actually receive a given signal. */
void bitmap_set_pattern(uint8_t *b, uint32_t blen, const char *pat) { void bitmap_set_pattern(uint8_t *b, uint32_t blen, uint32_t off, const char *pat) {
uint32_t i = 0; uint32_t i = 0;
while(pat[i]) { while(pat[i]) {
bitmap_set(b,blen,i,pat[i] == '1'); bitmap_set(b,blen,i+off,pat[i] == '1');
i++; i++;
} }
} }
@@ -408,10 +535,21 @@ ProtoViewDecoder *Decoders[] = {
NULL NULL
}; };
/* Free the message info and allocated data. */
void free_msg_info(ProtoViewMsgInfo *i) {
if (i == NULL) return;
fieldset_free(i->fieldset);
free(i->bits);
free(i);
}
/* Reset the message info structure before passing it to the decoding /* Reset the message info structure before passing it to the decoding
* functions. */ * functions. */
void initialize_msg_info(ProtoViewMsgInfo *i) { void init_msg_info(ProtoViewMsgInfo *i, ProtoViewApp *app) {
UNUSED(app);
memset(i,0,sizeof(ProtoViewMsgInfo)); memset(i,0,sizeof(ProtoViewMsgInfo));
i->bits = NULL;
i->fieldset = fieldset_new();
} }
/* This function is called when a new signal is detected. It converts it /* This function is called when a new signal is detected. It converts it
@@ -424,7 +562,7 @@ bool decode_signal(RawSamplesBuffer *s, uint64_t len, ProtoViewMsgInfo *info) {
/* We call the decoders with an offset a few samples before the actual /* We call the decoders with an offset a few samples before the actual
* signal detected and for a len of a few bits after its end. */ * signal detected and for a len of a few bits after its end. */
uint32_t before_samples = 20; uint32_t before_samples = 32;
uint32_t after_samples = 100; uint32_t after_samples = 100;
uint8_t *bitmap = malloc(bitmap_size); uint8_t *bitmap = malloc(bitmap_size);
@@ -451,14 +589,28 @@ bool decode_signal(RawSamplesBuffer *s, uint64_t len, ProtoViewMsgInfo *info) {
uint32_t delta = furi_get_tick() - start_time; uint32_t delta = furi_get_tick() - start_time;
FURI_LOG_E(TAG, "Decoder %s took %lu ms", FURI_LOG_E(TAG, "Decoder %s took %lu ms",
Decoders[j]->name, (unsigned long)delta); Decoders[j]->name, (unsigned long)delta);
if (decoded) break; if (decoded) {
info->decoder = Decoders[j];
break;
}
j++; j++;
} }
if (!decoded) { if (!decoded) {
FURI_LOG_E(TAG, "No decoding possible"); FURI_LOG_E(TAG, "No decoding possible");
} else { } else {
FURI_LOG_E(TAG, "Decoded %s, raw=%s info=[%s,%s,%s,%s]", info->name, info->raw, info->info1, info->info2, info->info3, info->info4); FURI_LOG_E(TAG, "+++ Decoded %s", info->decoder->name);
/* The message was correctly decoded: fill the info structure
* with the decoded signal. The decoder may not implement offset/len
* filling of the structure. In such case we have no info and
* pulses_count will be set to zero. */
if (info->pulses_count) {
info->bits_bytes = (info->pulses_count+7)/8; // Round to full byte.
info->bits = malloc(info->bits_bytes);
bitmap_copy(info->bits,info->bits_bytes,0,
bitmap,bitmap_size,info->start_off,
info->pulses_count);
}
} }
free(bitmap); free(bitmap);
return decoded; return decoded;

View File

@@ -0,0 +1,141 @@
/* Copyright (C) 2023 Salvatore Sanfilippo -- All Rights Reserved
* Copyright (C) 2023 Maciej Wojtasik -- All Rights Reserved
* See the LICENSE file for information about the license. */
#include "app.h"
#include <stream/stream.h>
#include <flipper_format/flipper_format_i.h>
/* ========================= Signal file operations ========================= */
/* This function saves the current logical signal on disk. What is saved here
* is not the signal as level and duration as we received it from CC1101,
* but it's logical representation stored in the app->msg_info bitmap, where
* each 1 or 0 means a puls or gap for the specified short pulse duration time
* (te). */
bool save_signal(ProtoViewApp *app, const char *filename) {
/* We have a message at all? */
if (app->msg_info == NULL || app->msg_info->pulses_count == 0) return false;
Storage *storage = furi_record_open(RECORD_STORAGE);
FlipperFormat *file = flipper_format_file_alloc(storage);
Stream *stream = flipper_format_get_raw_stream(file);
FuriString *file_content = NULL;
bool success = true;
if (flipper_format_file_open_always(file, filename)) {
/* Write the file header. */
FuriString *file_content = furi_string_alloc();
const char *preset_id = ProtoViewModulations[app->modulation].id;
furi_string_printf(file_content,
"Filetype: Flipper SubGhz RAW File\n"
"Version: 1\n"
"Frequency: %ld\n"
"Preset: %s\n",
app->frequency,
preset_id ? preset_id : "FuriHalSubGhzPresetCustom");
/* For custom modulations, we need to emit a set of registers. */
if (preset_id == NULL) {
FuriString *custom = furi_string_alloc();
uint8_t *regs = ProtoViewModulations[app->modulation].custom;
furi_string_printf(custom,
"Custom_preset_module: CC1101\n"
"Custom_preset_data: ");
for (int j = 0; regs[j]; j += 2) {
furi_string_cat_printf(custom, "%02X %02X ",
(int)regs[j], (int)regs[j+1]);
}
size_t len = furi_string_size(file_content);
furi_string_set_char(custom,len-1,'\n');
furi_string_cat(file_content,custom);
furi_string_free(custom);
}
/* We always save raw files. */
furi_string_cat_printf(file_content,
"Protocol: RAW\n"
"RAW_Data: -10000\n"); // Start with 10 ms of gap
/* Write header. */
size_t len = furi_string_size(file_content);
if (stream_write(stream,
(uint8_t*) furi_string_get_cstr(file_content), len)
!= len)
{
FURI_LOG_W(TAG, "Short write to file");
success = false;
goto write_err;
}
furi_string_reset(file_content);
/* Write raw data sections. The Flipper subghz parser can't handle
* too much data on a single line, so we generate a new one
* every few samples. */
uint32_t this_line_samples = 0;
uint32_t max_line_samples = 100;
uint32_t idx = 0; // Iindex in the signal bitmap.
ProtoViewMsgInfo *i = app->msg_info;
while(idx < i->pulses_count) {
bool level = bitmap_get(i->bits,i->bits_bytes,idx);
uint32_t te_times = 1;
idx++;
/* Count the duration of the current pulse/gap. */
while(idx < i->pulses_count &&
bitmap_get(i->bits,i->bits_bytes,idx) == level)
{
te_times++;
idx++;
}
// Invariant: after the loop 'idx' is at the start of the
// next gap or pulse.
int32_t dur = (int32_t)i->short_pulse_dur * te_times;
if (level == 0) dur = -dur; /* Negative is gap in raw files. */
/* Emit the sample. If this is the first sample of the line,
* also emit the RAW_Data: field. */
if (this_line_samples == 0)
furi_string_cat_printf(file_content,"RAW_Data: ");
furi_string_cat_printf(file_content,"%d ",(int)dur);
this_line_samples++;
/* Store the current set of samples on disk, when we reach a
* given number or the end of the signal. */
bool end_reached = (idx == i->pulses_count);
if (this_line_samples == max_line_samples || end_reached) {
/* If that's the end, terminate the signal with a long
* gap. */
if (end_reached) furi_string_cat_printf(file_content,"-10000 ");
/* We always have a trailing space in the last sample. Make it
* a newline. */
size_t len = furi_string_size(file_content);
furi_string_set_char(file_content,len-1,'\n');
if (stream_write(stream,
(uint8_t*) furi_string_get_cstr(file_content),
len) != len)
{
FURI_LOG_W(TAG, "Short write to file");
success = false;
goto write_err;
}
/* Prepare for next line. */
furi_string_reset(file_content);
this_line_samples = 0;
}
}
} else {
success = false;
FURI_LOG_W(TAG, "Unable to open file");
}
write_err:
furi_record_close(RECORD_STORAGE);
flipper_format_free(file);
if (file_content != NULL) furi_string_free(file_content);
return success;
}

View File

@@ -3,6 +3,122 @@
#include "app.h" #include "app.h"
/* =========================== Subview handling ================================
* Note that these are not the Flipper subviews, but the subview system
* implemented inside ProtoView.
* ========================================================================== */
/* Return the ID of the currently selected subview, of the current
* view. */
int ui_get_current_subview(ProtoViewApp *app) {
return app->current_subview[app->current_view];
}
/* Called by view rendering callback that has subviews, to show small triangles
* facing down/up if there are other subviews the user can access with up
* and down. */
void ui_show_available_subviews(Canvas *canvas, ProtoViewApp *app,
int last_subview)
{
int subview = ui_get_current_subview(app);
if (subview != 0)
canvas_draw_triangle(canvas,120,5,8,5,CanvasDirectionBottomToTop);
if (subview != last_subview-1)
canvas_draw_triangle(canvas,120,59,8,5,CanvasDirectionTopToBottom);
}
/* Handle up/down keys when we are in a subview. If the function catched
* such keypress, it returns true, so that the actual view input callback
* knows it can just return ASAP without doing anything. */
bool ui_process_subview_updown(ProtoViewApp *app, InputEvent input, int last_subview) {
int subview = ui_get_current_subview(app);
if (input.type == InputTypePress) {
if (input.key == InputKeyUp) {
if (subview != 0)
app->current_subview[app->current_view]--;
return true;
} else if (input.key == InputKeyDown) {
if (subview != last_subview-1)
app->current_subview[app->current_view]++;
return true;
}
}
return false;
}
/* ============================= Text input ====================================
* Normally we just use our own private UI widgets. However for the text input
* widget, that is quite complex, visualizes a keyboard and must be standardized
* for user coherent experience, we use the one provided by the Flipper
* framework. The following two functions allow to show the keyboard to get
* text and later dismiss it.
* ========================================================================== */
/* Show the keyboard, take the user input and store it into the specified
* 'buffer' of 'buflen' total bytes. When the user is done, the done_callback
* is called passing the application context to it. Such callback needs
* to do whatever it wants with the input buffer and dismissi the keyboard
* calling: dismiss_keyboard(app);
*
* Note: if the buffer is not a null-termined zero string, what it contains will
* be used as initial input for the user. */
void ui_show_keyboard(ProtoViewApp *app, char *buffer, uint32_t buflen,
void (*done_callback)(void*))
{
app->show_text_input = true;
app->text_input_buffer = buffer;
app->text_input_buffer_len = buflen;
app->text_input_done_callback = done_callback;
}
void ui_dismiss_keyboard(ProtoViewApp *app) {
view_dispatcher_stop(app->view_dispatcher);
}
/* ================================= Alert ================================== */
/* Set an alert message to be shown over any currently active view, for
* the specified amount of time of 'ttl' milliseconds. */
void ui_show_alert(ProtoViewApp *app, const char *text, uint32_t ttl) {
app->alert_dismiss_time = furi_get_tick() + furi_ms_to_ticks(ttl);
snprintf(app->alert_text,ALERT_MAX_LEN,"%s",text);
}
/* Cancel the alert before its time has elapsed. */
void ui_dismiss_alert(ProtoViewApp *app) {
app->alert_dismiss_time = 0;
}
/* Show the alert if an alert is set. This is called after the currently
* active view displayed its stuff, so we overwrite the screen with the
* alert message. */
void ui_draw_alert_if_needed(Canvas *canvas, ProtoViewApp *app) {
if (app->alert_dismiss_time == 0) {
/* No active alert. */
return;
} else if (app->alert_dismiss_time < furi_get_tick()) {
/* Alert just expired. */
ui_dismiss_alert(app);
return;
}
/* Show the alert. A box with black border and a text inside. */
canvas_set_font(canvas, FontPrimary);
uint8_t w = canvas_string_width(canvas, app->alert_text);
uint8_t h = 8; // Font height.
uint8_t text_x = 64-(w/2);
uint8_t text_y = 32+4;
uint8_t padding = 3;
canvas_set_color(canvas,ColorBlack);
canvas_draw_box(canvas,text_x-padding,text_y-padding-h,w+padding*2,h+padding*2);
canvas_set_color(canvas,ColorWhite);
canvas_draw_box(canvas,text_x-padding+1,text_y-padding-h+1,w+padding*2-2,h+padding*2-2);
canvas_set_color(canvas,ColorBlack);
canvas_draw_str(canvas,text_x,text_y,app->alert_text);
}
/* =========================== Canvas extensions ============================ */
void canvas_draw_str_with_border(Canvas* canvas, uint8_t x, uint8_t y, const char* str, Color text_color, Color border_color) void canvas_draw_str_with_border(Canvas* canvas, uint8_t x, uint8_t y, const char* str, Color text_color, Color border_color)
{ {
struct { struct {

View File

@@ -0,0 +1,253 @@
/* Copyright (C) 2022-2023 Salvatore Sanfilippo -- All Rights Reserved
* See the LICENSE file for information about the license. */
#include "app.h"
extern ProtoViewDecoder *Decoders[]; // Defined in signal.c.
/* Our view private data. */
#define USER_VALUE_LEN 64
typedef struct {
ProtoViewDecoder *decoder; /* Decoder we are using to create a
message. */
uint32_t cur_decoder; /* Decoder index when we are yet selecting
a decoder. Used when decoder is NULL. */
ProtoViewFieldSet *fieldset; /* The fields to populate. */
uint32_t cur_field; /* Field we are editing right now. This
is the index inside the 'fieldset'
fields. */
char *user_value; /* Keyboard input to replace the current
field value goes here. */
} BuildViewPrivData;
/* Not all the decoders support message bulding, so we can't just
* increment / decrement the cur_decoder index here. */
static void select_next_decoder(ProtoViewApp *app) {
BuildViewPrivData *privdata = app->view_privdata;
do {
privdata->cur_decoder++;
if (Decoders[privdata->cur_decoder] == NULL)
privdata->cur_decoder = 0;
} while(Decoders[privdata->cur_decoder]->get_fields == NULL);
}
/* Like select_next_decoder() but goes backward. */
static void select_prev_decoder(ProtoViewApp *app) {
BuildViewPrivData *privdata = app->view_privdata;
do {
if (privdata->cur_decoder == 0) {
/* Go one after the last one to wrap around. */
while(Decoders[privdata->cur_decoder]) privdata->cur_decoder++;
}
privdata->cur_decoder--;
} while(Decoders[privdata->cur_decoder]->get_fields == NULL);
}
/* Render the view to select the decoder, among the ones that
* support message building. */
static void render_view_select_decoder(Canvas *const canvas, ProtoViewApp *app) {
BuildViewPrivData *privdata = app->view_privdata;
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 0, 9, "Signal creator");
canvas_set_font(canvas, FontSecondary);
canvas_draw_str(canvas, 0, 19, "up/down: select, ok: choose");
canvas_set_font(canvas, FontPrimary);
canvas_draw_str_aligned(canvas,64,38,AlignCenter,AlignCenter,
Decoders[privdata->cur_decoder]->name);
}
/* Render the view that allows the user to populate the fields needed
* for the selected decoder to build a message. */
static void render_view_set_fields(Canvas *const canvas, ProtoViewApp *app) {
BuildViewPrivData *privdata = app->view_privdata;
char buf[32];
snprintf(buf,sizeof(buf), "%s field %d/%d",
privdata->decoder->name, (int)privdata->cur_field+1,
(int)privdata->fieldset->numfields);
canvas_set_color(canvas,ColorBlack);
canvas_draw_box(canvas,0,0,128,21);
canvas_set_color(canvas,ColorWhite);
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 1, 9, buf);
canvas_set_font(canvas, FontSecondary);
canvas_draw_str(canvas, 1, 19, "up/down: next field, ok: edit");
/* Write the field name, type, current content. */
canvas_set_color(canvas,ColorBlack);
ProtoViewField *field = privdata->fieldset->fields[privdata->cur_field];
snprintf(buf,sizeof(buf), "%s %s:%d", field->name,
field_get_type_name(field), (int)field->len);
buf[0] = toupper(buf[0]);
canvas_set_font(canvas, FontPrimary);
canvas_draw_str_aligned(canvas,64,30,AlignCenter,AlignCenter,buf);
canvas_set_font(canvas, FontSecondary);
/* Render the current value between "" */
unsigned int written = (unsigned int) field_to_string(buf+1,sizeof(buf)-1,field);
buf[0] = '"';
if (written+3 < sizeof(buf)) memcpy(buf+written+1,"\"\x00",2);
canvas_draw_str_aligned(canvas,63,45,AlignCenter,AlignCenter,buf);
/* Footer instructions. */
canvas_draw_str(canvas, 0, 62, "Long ok: create, < > incr/decr");
}
/* Render the build message view. */
void render_view_build_message(Canvas *const canvas, ProtoViewApp *app) {
BuildViewPrivData *privdata = app->view_privdata;
if (privdata->decoder)
render_view_set_fields(canvas,app);
else
render_view_select_decoder(canvas,app);
}
/* Handle input for the decoder selection. */
static void process_input_select_decoder(ProtoViewApp *app, InputEvent input) {
BuildViewPrivData *privdata = app->view_privdata;
if (input.type == InputTypeShort) {
if (input.key == InputKeyOk) {
privdata->decoder = Decoders[privdata->cur_decoder];
privdata->fieldset = fieldset_new();
privdata->decoder->get_fields(privdata->fieldset);
/* If the currently decoded message was produced with the
* same decoder the user selected, let's populate the
* defaults with the current values. So the user will
* actaully edit the current message. */
if (app->signal_decoded &&
app->msg_info->decoder == privdata->decoder)
{
fieldset_copy_matching_fields(privdata->fieldset,
app->msg_info->fieldset);
}
/* Now we use the subview system in order to protect the
message editing mode from accidental < or > presses.
Since we are technically into a subview now, we'll have
control of < and >. */
InputEvent ii = {.type = InputTypePress, .key = InputKeyDown};
ui_process_subview_updown(app,ii,2);
} else if (input.key == InputKeyDown) {
select_next_decoder(app);
} else if (input.key == InputKeyUp) {
select_prev_decoder(app);
}
}
}
/* Called after the user typed the new field value in the keyboard.
* Let's save it and remove the keyboard view. */
static void text_input_done_callback(void* context) {
ProtoViewApp *app = context;
BuildViewPrivData *privdata = app->view_privdata;
if (field_set_from_string(privdata->fieldset->fields[privdata->cur_field],
privdata->user_value, strlen(privdata->user_value)) == false)
{
ui_show_alert(app, "Invalid value", 1500);
}
free(privdata->user_value);
privdata->user_value = NULL;
ui_dismiss_keyboard(app);
}
/* Handles the effects of < and > keys in field editing mode.
* Instead of force the user to enter the text input mode, delete
* the old value, enter the one, we allow to increment and
* decrement the current field in a much simpler way.
*
* The current filed is changed by 'incr' amount. */
static bool increment_current_field(ProtoViewApp *app, int incr) {
BuildViewPrivData *privdata = app->view_privdata;
ProtoViewFieldSet *fs = privdata->fieldset;
ProtoViewField *f = fs->fields[privdata->cur_field];
return field_incr_value(f,incr);
}
/* Handle input for fields editing mode. */
static void process_input_set_fields(ProtoViewApp *app, InputEvent input) {
BuildViewPrivData *privdata = app->view_privdata;
ProtoViewFieldSet *fs = privdata->fieldset;
if (input.type == InputTypeShort && input.key == InputKeyOk) {
/* Show the keyboard to let the user type the new
* value. */
if (privdata->user_value == NULL)
privdata->user_value = malloc(USER_VALUE_LEN);
field_to_string(privdata->user_value, USER_VALUE_LEN,
fs->fields[privdata->cur_field]);
ui_show_keyboard(app, privdata->user_value, USER_VALUE_LEN,
text_input_done_callback);
} else if (input.type == InputTypeShort && input.key == InputKeyDown) {
privdata->cur_field = (privdata->cur_field+1) % fs->numfields;
} else if (input.type == InputTypeShort && input.key == InputKeyUp) {
if (privdata->cur_field == 0)
privdata->cur_field = fs->numfields-1;
else
privdata->cur_field--;
} else if (input.type == InputTypeShort && input.key == InputKeyRight) {
increment_current_field(app,1);
} else if (input.type == InputTypeShort && input.key == InputKeyLeft) {
increment_current_field(app,-1);
} else if (input.type == InputTypeRepeat && input.key == InputKeyRight) {
// The reason why we don't use a large increment directly
// is that certain field types only support +1 -1 increments.
int times = 10;
while(times--) increment_current_field(app,1);
} else if (input.type == InputTypeRepeat && input.key == InputKeyLeft) {
int times = 10;
while(times--) increment_current_field(app,-1);
} else if (input.type == InputTypeLong && input.key == InputKeyOk) {
// Build the message in a fresh raw buffer.
if (privdata->decoder->build_message) {
RawSamplesBuffer *rs = raw_samples_alloc();
privdata->decoder->build_message(rs,privdata->fieldset);
app->signal_decoded = false; // So that the new signal will be
// accepted as the current signal.
scan_for_signal(app,rs);
raw_samples_free(rs);
ui_show_alert(app,"Done: press back key",3000);
}
}
}
/* Handle input for the build message view. */
void process_input_build_message(ProtoViewApp *app, InputEvent input) {
BuildViewPrivData *privdata = app->view_privdata;
if (privdata->decoder)
process_input_set_fields(app,input);
else
process_input_select_decoder(app,input);
}
/* Enter view callback. */
void view_enter_build_message(ProtoViewApp *app) {
BuildViewPrivData *privdata = app->view_privdata;
// When we enter the view, the current decoder is just set to zero.
// Seek the next valid if needed.
if (Decoders[privdata->cur_decoder]->get_fields == NULL) {
select_next_decoder(app);
}
// However if there is currently a decoded message, and the
// decoder of such message supports message building, let's
// select it.
if (app->signal_decoded &&
app->msg_info->decoder->get_fields &&
app->msg_info->decoder->build_message)
{
while(Decoders[privdata->cur_decoder] != app->msg_info->decoder)
select_next_decoder(app);
}
}
/* Called on exit for cleanup. */
void view_exit_build_message(ProtoViewApp *app) {
BuildViewPrivData *privdata = app->view_privdata;
if (privdata->fieldset) fieldset_free(privdata->fieldset);
if (privdata->user_value) free(privdata->user_value);
}

View File

@@ -2,8 +2,122 @@
* See the LICENSE file for information about the license. */ * See the LICENSE file for information about the license. */
#include "app.h" #include "app.h"
#include <gui/view.h>
#include <lib/toolbox/random_name.h>
/* Renders the view with the detected message information. */ /* This view has subviews accessible navigating up/down. This
* enumaration is used to track the currently active subview. */
enum {
SubViewInfoMain,
SubViewInfoSave,
SubViewInfoLast, /* Just a sentinel. */
};
/* Our view private data. */
#define SAVE_FILENAME_LEN 64
typedef struct {
/* Our save view displays an oscilloscope-alike resampled signal,
* so that the user can see what they are saving. With left/right
* you can move to next rows. Here we store where we are. */
uint32_t signal_display_start_row;
char *filename;
uint8_t cur_info_page; // Info page to display. Useful when there are
// too many fields populated by the decoder that
// a single page is not enough.
} InfoViewPrivData;
/* Draw the text label and value of the specified info field at x,y. */
static void render_info_field(Canvas *const canvas,
ProtoViewField *f, uint8_t x, uint8_t y)
{
char buf[64];
char strval[32];
field_to_string(strval,sizeof(strval),f);
snprintf(buf,sizeof(buf),"%s: %s", f->name, strval);
canvas_set_font(canvas, FontSecondary);
canvas_draw_str(canvas, x, y, buf);
}
/* Render the view with the detected message information. */
#define INFO_LINES_PER_PAGE 5
static void render_subview_main(Canvas *const canvas, ProtoViewApp *app) {
InfoViewPrivData *privdata = app->view_privdata;
uint8_t pages = (app->msg_info->fieldset->numfields
+(INFO_LINES_PER_PAGE-1)) / INFO_LINES_PER_PAGE;
privdata->cur_info_page %= pages;
uint8_t current_page = privdata->cur_info_page;
char buf[32];
/* Protocol name as title. */
canvas_set_font(canvas, FontPrimary);
uint8_t y = 8, lineheight = 10;
if (pages > 1) {
snprintf(buf,sizeof(buf),"%s %u/%u", app->msg_info->decoder->name,
current_page+1, pages);
canvas_draw_str(canvas, 0, y, buf);
} else {
canvas_draw_str(canvas, 0, y, app->msg_info->decoder->name);
}
y += lineheight;
/* Draw the info fields. */
uint8_t max_lines = INFO_LINES_PER_PAGE;
uint32_t j = current_page*max_lines;
while (j < app->msg_info->fieldset->numfields) {
render_info_field(canvas,app->msg_info->fieldset->fields[j++],0,y);
y += lineheight;
if (--max_lines == 0) break;
}
/* Draw a vertical "save" label. Temporary solution, to switch to
* something better ASAP. */
y = 37;
lineheight = 7;
canvas_draw_str(canvas, 119, y, "s"); y += lineheight;
canvas_draw_str(canvas, 119, y, "a"); y += lineheight;
canvas_draw_str(canvas, 119, y, "v"); y += lineheight;
canvas_draw_str(canvas, 119, y, "e"); y += lineheight;
}
/* Render view with save option. */
static void render_subview_save(Canvas *const canvas, ProtoViewApp *app) {
InfoViewPrivData *privdata = app->view_privdata;
/* Display our signal in digital form: here we don't show the
* signal with the exact timing of the received samples, but as it
* is in its logic form, in exact multiples of the short pulse length. */
uint8_t rows = 6;
uint8_t rowheight = 11;
uint8_t bitwidth = 4;
uint8_t bitheight = 5;
uint32_t idx = privdata->signal_display_start_row * (128/4);
bool prevbit = false;
for (uint8_t y = bitheight+12; y <= rows*rowheight; y += rowheight) {
for (uint8_t x = 0; x < 128; x += 4) {
bool bit = bitmap_get(app->msg_info->bits,
app->msg_info->bits_bytes,idx);
uint8_t prevy = y + prevbit*(bitheight*-1) - 1;
uint8_t thisy = y + bit*(bitheight*-1) - 1;
canvas_draw_line(canvas,x,prevy,x,thisy);
canvas_draw_line(canvas,x,thisy,x+bitwidth-1,thisy);
prevbit = bit;
if (idx >= app->msg_info->pulses_count) {
canvas_set_color(canvas, ColorWhite);
canvas_draw_dot(canvas, x+1,thisy);
canvas_draw_dot(canvas, x+3,thisy);
canvas_set_color(canvas, ColorBlack);
}
idx++; // Draw next bit
}
}
canvas_set_font(canvas, FontSecondary);
canvas_draw_str(canvas, 0, 6, "ok: send, long ok: save");
}
/* Render the selected subview of this view. */
void render_view_info(Canvas *const canvas, ProtoViewApp *app) { void render_view_info(Canvas *const canvas, ProtoViewApp *app) {
if (app->signal_decoded == false) { if (app->signal_decoded == false) {
canvas_set_font(canvas, FontSecondary); canvas_set_font(canvas, FontSecondary);
@@ -11,32 +125,211 @@ void render_view_info(Canvas *const canvas, ProtoViewApp *app) {
return; return;
} }
/* Protocol name as title. */ ui_show_available_subviews(canvas,app,SubViewInfoLast);
canvas_set_font(canvas, FontPrimary); switch(app->current_subview[app->current_view]) {
uint8_t y = 8, lineheight = 10; case SubViewInfoMain: render_subview_main(canvas,app); break;
canvas_draw_str(canvas, 0, y, app->signal_info.name); case SubViewInfoSave: render_subview_save(canvas,app); break;
y += lineheight;
/* Info fields. */
char buf[128];
canvas_set_font(canvas, FontSecondary);
if (app->signal_info.raw[0]) {
snprintf(buf,sizeof(buf),"Raw: %s", app->signal_info.raw);
canvas_draw_str(canvas, 0, y, buf);
y += lineheight;
} }
canvas_draw_str(canvas, 0, y, app->signal_info.info1); y += lineheight; }
canvas_draw_str(canvas, 0, y, app->signal_info.info2); y += lineheight;
canvas_draw_str(canvas, 0, y, app->signal_info.info3); y += lineheight; /* The user typed the file name. Let's save it and remove the keyboard
canvas_draw_str(canvas, 0, y, app->signal_info.info4); y += lineheight; * view. */
static void text_input_done_callback(void* context) {
ProtoViewApp *app = context;
InfoViewPrivData *privdata = app->view_privdata;
FuriString *save_path = furi_string_alloc_printf(
"%s/%s.sub", EXT_PATH("subghz"), privdata->filename);
save_signal(app, furi_string_get_cstr(save_path));
furi_string_free(save_path);
free(privdata->filename);
privdata->filename = NULL; // Don't free it again on view exit
ui_dismiss_keyboard(app);
ui_show_alert(app, "Signal saved", 1500);
}
/* Replace all the occurrences of character c1 with c2 in the specified
* string. */
void str_replace(char *buf, char c1, char c2) {
char *p = buf;
while(*p) {
if (*p == c1) *p = c2;
p++;
}
}
/* Set a random filename the user can edit. */
void set_signal_random_filename(ProtoViewApp *app, char *buf, size_t buflen) {
char suffix[6];
set_random_name(suffix,sizeof(suffix));
snprintf(buf,buflen,"%.10s-%s-%d",app->msg_info->decoder->name,suffix,rand()%1000);
str_replace(buf,' ','_');
str_replace(buf,'-','_');
str_replace(buf,'/','_');
}
/* ========================== Signal transmission =========================== */
/* This is the context we pass to the data yield callback for
* asynchronous tx. */
typedef enum {
SendSignalSendStartGap,
SendSignalSendBits,
SendSignalSendEndGap,
SendSignalEndTransmission
} SendSignalState;
#define PROTOVIEW_SENDSIGNAL_START_GAP 10000 /* microseconds. */
#define PROTOVIEW_SENDSIGNAL_END_GAP 10000 /* microseconds. */
typedef struct {
SendSignalState state; // Current state.
uint32_t curpos; // Current bit position of data to send.
ProtoViewApp *app; // App reference.
uint32_t start_gap_dur; // Gap to send at the start.
uint32_t end_gap_dur; // Gap to send at the end.
} SendSignalCtx;
/* Setup the state context for the callback responsible to feed data
* to the subghz async tx system. */
static void send_signal_init(SendSignalCtx *ss, ProtoViewApp *app) {
ss->state = SendSignalSendStartGap;
ss->curpos = 0;
ss->app = app;
ss->start_gap_dur = PROTOVIEW_SENDSIGNAL_START_GAP;
ss->end_gap_dur = PROTOVIEW_SENDSIGNAL_END_GAP;
}
/* Send signal data feeder callback. When the asynchronous transmission is
* active, this function is called to return new samples from the currently
* decoded signal in app->msg_info. The subghz subsystem aspects this function,
* that is the data feeder, to return LevelDuration types (that is a structure
* with level, that is pulse or gap, and duration in microseconds).
*
* The position into the transmission is stored in the context 'ctx', that
* references a SendSignalCtx structure.
*
* In the SendSignalCtx structure 'ss' we remember at which bit of the
* message we are, in ss->curoff. We also send a start and end gap in order
* to make sure the transmission is clear.
*/
LevelDuration radio_tx_feed_data(void *ctx) {
SendSignalCtx *ss = ctx;
/* Send start gap. */
if (ss->state == SendSignalSendStartGap) {
ss->state = SendSignalSendBits;
return level_duration_make(0,ss->start_gap_dur);
}
/* Send data. */
if (ss->state == SendSignalSendBits) {
uint32_t dur = 0, j;
uint32_t level = 0;
/* Let's see how many consecutive bits we have with the same
* level. */
for (j = 0; ss->curpos+j < ss->app->msg_info->pulses_count; j++) {
uint32_t l = bitmap_get(ss->app->msg_info->bits,
ss->app->msg_info->bits_bytes,
ss->curpos+j);
if (j == 0) {
/* At the first bit of this sequence, we store the
* level of the sequence. */
level = l;
dur += ss->app->msg_info->short_pulse_dur;
continue;
}
/* As long as the level is the same, we update the duration.
* Otherwise stop the loop and return this sample. */
if (l != level) break;
dur += ss->app->msg_info->short_pulse_dur;
}
ss->curpos += j;
/* If this was the last set of bits, change the state to
* send the final gap. */
if (ss->curpos >= ss->app->msg_info->pulses_count)
ss->state = SendSignalSendEndGap;
return level_duration_make(level, dur);
}
/* Send end gap. */
if (ss->state == SendSignalSendEndGap) {
ss->state = SendSignalEndTransmission;
return level_duration_make(0,ss->end_gap_dur);
}
/* End transmission. Here state is guaranteed
* to be SendSignalEndTransmission */
return level_duration_reset();
}
/* Vibrate and produce a click sound when a signal is sent. */
void notify_signal_sent(ProtoViewApp *app) {
static const NotificationSequence sent_seq = {
&message_blue_255,
&message_vibro_on,
&message_note_g1,
&message_delay_10,
&message_sound_off,
&message_vibro_off,
&message_blue_0,
NULL
};
notification_message(app->notification, &sent_seq);
} }
/* Handle input for the info view. */ /* Handle input for the info view. */
void process_input_info(ProtoViewApp *app, InputEvent input) { void process_input_info(ProtoViewApp *app, InputEvent input) {
if (input.type == InputTypeShort) { /* If we don't have a decoded signal, we don't allow to go up/down
if (input.key == InputKeyOk) { * in the subviews: they are only useful when a loaded signal. */
if (app->signal_decoded &&
ui_process_subview_updown(app,input,SubViewInfoLast)) return;
InfoViewPrivData *privdata = app->view_privdata;
int subview = ui_get_current_subview(app);
/* Main subview. */
if (subview == SubViewInfoMain) {
if (input.type == InputTypeLong && input.key == InputKeyOk) {
/* Reset the current sample to capture the next. */ /* Reset the current sample to capture the next. */
reset_current_signal(app); reset_current_signal(app);
} else if (input.type == InputTypeShort && input.key == InputKeyOk) {
/* Show next info page. */
privdata->cur_info_page++;
}
} else if (subview == SubViewInfoSave) {
/* Save subview. */
if (input.type == InputTypePress && input.key == InputKeyRight) {
privdata->signal_display_start_row++;
} else if (input.type == InputTypePress && input.key == InputKeyLeft) {
if (privdata->signal_display_start_row != 0)
privdata->signal_display_start_row--;
} else if (input.type == InputTypeLong && input.key == InputKeyOk)
{
// We have have the buffer already allocated, in case the
// user aborted with BACK a previous saving.
if (privdata->filename == NULL)
privdata->filename = malloc(SAVE_FILENAME_LEN);
set_signal_random_filename(app,privdata->filename,SAVE_FILENAME_LEN);
ui_show_keyboard(app, privdata->filename, SAVE_FILENAME_LEN,
text_input_done_callback);
} else if (input.type == InputTypeShort && input.key == InputKeyOk) {
SendSignalCtx send_state;
send_signal_init(&send_state,app);
radio_tx_signal(app,radio_tx_feed_data,&send_state);
notify_signal_sent(app);
} }
} }
} }
/* Called on view exit. */
void view_exit_info(ProtoViewApp *app) {
InfoViewPrivData *privdata = app->view_privdata;
// When the user aborts the keyboard input, we are left with the
// filename buffer allocated.
if (privdata->filename) free(privdata->filename);
}

View File

@@ -65,7 +65,7 @@ void render_view_raw_pulses(Canvas *const canvas, ProtoViewApp *app) {
canvas_draw_str_with_border(canvas, 97, 63, buf, ColorWhite, ColorBlack); canvas_draw_str_with_border(canvas, 97, 63, buf, ColorWhite, ColorBlack);
if (app->signal_decoded) { if (app->signal_decoded) {
canvas_set_font(canvas, FontPrimary); canvas_set_font(canvas, FontPrimary);
canvas_draw_str_with_border(canvas, 1, 61, app->signal_info.name, ColorWhite, ColorBlack); canvas_draw_str_with_border(canvas, 1, 61, app->msg_info->decoder->name, ColorWhite, ColorBlack);
} }
} }
@@ -77,14 +77,15 @@ void process_input_raw_pulses(ProtoViewApp *app, InputEvent input) {
* previous samples. */ * previous samples. */
if (input.key == InputKeyRight) app->signal_offset++; if (input.key == InputKeyRight) app->signal_offset++;
else if (input.key == InputKeyLeft) app->signal_offset--; else if (input.key == InputKeyLeft) app->signal_offset--;
else if (input.key == InputKeyOk) { } else if (input.type == InputTypeLong) {
app->signal_offset = 0;
app->us_scale = PROTOVIEW_RAW_VIEW_DEFAULT_SCALE;
}
} else if (input.type == InputTypeShort) {
if (input.key == InputKeyOk) { if (input.key == InputKeyOk) {
/* Reset the current sample to capture the next. */ /* Reset the current sample to capture the next. */
reset_current_signal(app); reset_current_signal(app);
}
} else if (input.type == InputTypeShort) {
if (input.key == InputKeyOk) {
app->signal_offset = 0;
adjust_raw_view_scale(app,DetectedSamples->short_pulse_dur);
} else if (input.key == InputKeyDown) { } else if (input.key == InputKeyDown) {
/* Rescaling. The set becomes finer under 50us per pixel. */ /* Rescaling. The set becomes finer under 50us per pixel. */
uint32_t scale_step = app->us_scale >= 50 ? 50 : 10; uint32_t scale_step = app->us_scale >= 50 ? 50 : 10;
@@ -95,3 +96,19 @@ void process_input_raw_pulses(ProtoViewApp *app, InputEvent input) {
} }
} }
} }
/* Adjust raw view scale depending on short pulse duration. */
void adjust_raw_view_scale(ProtoViewApp *app, uint32_t short_pulse_dur) {
if (short_pulse_dur == 0)
app->us_scale = PROTOVIEW_RAW_VIEW_DEFAULT_SCALE;
else if (short_pulse_dur < 75)
app->us_scale = 10;
else if (short_pulse_dur < 145)
app->us_scale = 30;
else if (short_pulse_dur < 400)
app->us_scale = 100;
else if (short_pulse_dur < 1000)
app->us_scale = 200;
else
app->us_scale = PROTOVIEW_RAW_VIEW_DEFAULT_SCALE;
}

View File

@@ -1,6 +1,3 @@
#include "helpers/snake_file_handler.h"
#include "helpers/snake_types.h"
#include <furi.h> #include <furi.h>
#include <gui/gui.h> #include <gui/gui.h>
#include <input/input.h> #include <input/input.h>
@@ -8,7 +5,52 @@
#include <dolphin/dolphin.h> #include <dolphin/dolphin.h>
#include <notification/notification.h> #include <notification/notification.h>
#include <notification/notification_messages.h> #include <notification/notification_messages.h>
#include <dolphin/dolphin.h>
typedef struct {
// +-----x
// |
// |
// y
uint8_t x;
uint8_t y;
} Point;
typedef enum {
GameStateLife,
// https://melmagazine.com/en-us/story/snake-nokia-6110-oral-history-taneli-armanto
// Armanto: While testing the early versions of the game, I noticed it was hard
// to control the snake upon getting close to and edge but not crashing — especially
// in the highest speed levels. I wanted the highest level to be as fast as I could
// possibly make the device "run," but on the other hand, I wanted to be friendly
// and help the player manage that level. Otherwise it might not be fun to play. So
// I implemented a little delay. A few milliseconds of extra time right before
// the player crashes, during which she can still change the directions. And if
// she does, the game continues.
GameStateLastChance,
GameStateGameOver,
} GameState;
// Note: do not change without purpose. Current values are used in smart
// orthogonality calculation in `snake_game_get_turn_snake`.
typedef enum {
DirectionUp,
DirectionRight,
DirectionDown,
DirectionLeft,
} Direction;
#define MAX_SNAKE_LEN 253
typedef struct {
Point points[MAX_SNAKE_LEN];
uint16_t len;
Direction currentMovement;
Direction nextMovement; // if backward of currentMovement, ignore
Point fruit;
GameState state;
} SnakeState;
typedef enum { typedef enum {
EventTypeTick, EventTypeTick,
@@ -74,35 +116,22 @@ static void snake_game_render_callback(Canvas* const canvas, void* ctx) {
canvas_draw_box(canvas, p.x, p.y, 4, 4); canvas_draw_box(canvas, p.x, p.y, 4, 4);
} }
// Show score on the game field
if(snake_state->state != GameStateGameOver) {
char buffer2[6];
canvas_set_font(canvas, FontBatteryPercent);
snprintf(buffer2, sizeof(buffer2), "%u", (snake_state->len - 7));
canvas_draw_str_aligned(canvas, 124, 10, AlignRight, AlignBottom, buffer2);
}
// Game Over banner // Game Over banner
if(snake_state->state == GameStateGameOver) { if(snake_state->state == GameStateGameOver) {
// Screen is 128x64 px // Screen is 128x64 px
canvas_set_color(canvas, ColorWhite); canvas_set_color(canvas, ColorWhite);
canvas_draw_box(canvas, 32, 20, 64, 34); canvas_draw_box(canvas, 34, 20, 62, 24);
canvas_set_color(canvas, ColorBlack); canvas_set_color(canvas, ColorBlack);
canvas_draw_frame(canvas, 32, 20, 64, 34); canvas_draw_frame(canvas, 34, 20, 62, 24);
canvas_set_font(canvas, FontPrimary); canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 37, 31, "Game Over"); canvas_draw_str(canvas, 37, 31, "Game Over");
char buffer[18];
canvas_set_font(canvas, FontSecondary); canvas_set_font(canvas, FontSecondary);
snprintf(buffer, sizeof(buffer), "Score: %u", snake_state->len); char buffer[12];
snprintf(buffer, sizeof(buffer), "Score: %u", snake_state->len - 7U);
canvas_draw_str_aligned(canvas, 64, 41, AlignCenter, AlignBottom, buffer); canvas_draw_str_aligned(canvas, 64, 41, AlignCenter, AlignBottom, buffer);
snprintf(buffer, sizeof(buffer), "Highscore: %d", snake_state->highscore);
canvas_draw_str_aligned(canvas, 64, 51, AlignCenter, AlignBottom, buffer);
}
if((snake_state->len - 7) % 20 == 0 && (snake_state->len - 7) != 0) {
DOLPHIN_DEED(getRandomDeed());
} }
release_mutex((ValueMutex*)ctx, snake_state); release_mutex((ValueMutex*)ctx, snake_state);
@@ -233,24 +262,16 @@ static void snake_game_move_snake(SnakeState* const snake_state, Point const nex
snake_state->points[0] = next_step; snake_state->points[0] = next_step;
} }
static void snake_game_game_over(SnakeState* const snake_state, NotificationApp* notification) {
snake_state->state = GameStateGameOver;
snake_state->len = snake_state->len - 7;
if(snake_state->len > snake_state->highscore) {
snake_state->isNewHighscore = true;
snake_state->highscore = snake_state->len;
}
notification_message_block(notification, &sequence_fail);
}
static void static void
snake_game_process_game_step(SnakeState* const snake_state, NotificationApp* notification) { snake_game_process_game_step(SnakeState* const snake_state, NotificationApp* notification) {
if(snake_state->state == GameStateGameOver) { if(snake_state->state == GameStateGameOver) {
return; return;
} }
snake_state->currentMovement = snake_game_get_turn_snake(snake_state); bool can_turn = (snake_state->points[0].x % 2 == 0) && (snake_state->points[0].y % 2 == 0);
if(can_turn) {
snake_state->currentMovement = snake_game_get_turn_snake(snake_state);
}
Point next_step = snake_game_get_next_step(snake_state); Point next_step = snake_game_get_next_step(snake_state);
@@ -260,7 +281,8 @@ static void
snake_state->state = GameStateLastChance; snake_state->state = GameStateLastChance;
return; return;
} else if(snake_state->state == GameStateLastChance) { } else if(snake_state->state == GameStateLastChance) {
snake_game_game_over(snake_state, notification); snake_state->state = GameStateGameOver;
notification_message_block(notification, &sequence_fail);
return; return;
} }
} else { } else {
@@ -271,7 +293,8 @@ static void
crush = snake_game_collision_with_tail(snake_state, next_step); crush = snake_game_collision_with_tail(snake_state, next_step);
if(crush) { if(crush) {
snake_game_game_over(snake_state, notification); snake_state->state = GameStateGameOver;
notification_message_block(notification, &sequence_fail);
return; return;
} }
@@ -279,7 +302,8 @@ static void
if(eatFruit) { if(eatFruit) {
snake_state->len++; snake_state->len++;
if(snake_state->len >= MAX_SNAKE_LEN) { if(snake_state->len >= MAX_SNAKE_LEN) {
snake_game_game_over(snake_state, notification); snake_state->state = GameStateGameOver;
notification_message_block(notification, &sequence_fail);
return; return;
} }
} }
@@ -298,16 +322,11 @@ int32_t snake_game_app(void* p) {
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(SnakeEvent)); FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(SnakeEvent));
SnakeState* snake_state = malloc(sizeof(SnakeState)); SnakeState* snake_state = malloc(sizeof(SnakeState));
snake_state->isNewHighscore = false; snake_game_init_game(snake_state);
snake_state->highscore = 0;
if(!snake_game_init_game_from_file(snake_state)) {
snake_game_init_game(snake_state);
}
ValueMutex state_mutex; ValueMutex state_mutex;
if(!init_mutex(&state_mutex, snake_state, sizeof(SnakeState))) { if(!init_mutex(&state_mutex, snake_state, sizeof(SnakeState))) {
FURI_LOG_E("SnakeGame", "cannot create mutex\r\n"); FURI_LOG_E("SnakeGame", "cannot create mutex\r\n");
furi_message_queue_free(event_queue);
free(snake_state); free(snake_state);
return 255; return 255;
} }
@@ -358,9 +377,6 @@ int32_t snake_game_app(void* p) {
} }
break; break;
case InputKeyBack: case InputKeyBack:
if(snake_state->state == GameStateLife) {
snake_game_save_game_to_file(snake_state);
}
processing = false; processing = false;
break; break;
default: default:
@@ -378,11 +394,8 @@ int32_t snake_game_app(void* p) {
release_mutex(&state_mutex, snake_state); release_mutex(&state_mutex, snake_state);
} }
if(snake_state->isNewHighscore) { // Return backlight to normal state
snake_game_save_score_to_file(snake_state->highscore); notification_message(notification, &sequence_display_backlight_enforce_auto);
}
// Wait for all notifications to be played and return backlight to normal state
notification_message_block(notification, &sequence_display_backlight_enforce_auto);
furi_timer_free(timer); furi_timer_free(timer);
view_port_enabled_set(view_port, false); view_port_enabled_set(view_port, false);
@@ -396,3 +409,26 @@ int32_t snake_game_app(void* p) {
return 0; return 0;
} }
// Screen is 128x64 px
// (4 + 4) * 16 - 4 + 2 + 2border == 128
// (4 + 4) * 8 - 4 + 2 + 2border == 64
// Game field from point{x: 0, y: 0} to point{x: 30, y: 14}.
// The snake turns only in even cells - intersections.
// ┌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┐
// ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎
// ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎
// ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎
// ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎
// ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎
// ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎
// ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎
// ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎
// ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎
// ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎
// ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎
// ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎
// ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎
// ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎
// ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎
// └╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘

View File

@@ -3,15 +3,29 @@ Tama P1 Emulator for Flipper Zero
This is a tama P1 Emulator app for Flipper Zero, based on [TamaLIB](https://github.com/jcrona/tamalib/). This is a tama P1 Emulator app for Flipper Zero, based on [TamaLIB](https://github.com/jcrona/tamalib/).
![Alt Text](tama.gif)
How to play How to play
----------- -----------
Create a `tama_p1` folder in your microSD card, and put the ROM as `rom.bin`. Create a `tama_p1` folder in your microSD card, and put the ROM as `rom.bin`.
Left button is A, OK is B, and right button is C. Hold the back button to exit. Use a search engine to find the Tamagotchi ROM. There is a file named `a`.
There is currently no saving, so your progress will be reset when you exit the Rename this to `rom.bin`.
app.
- Left button is A.
- OK is B.
- Right button is C.
- Holding the Up button functions the same as press both A and C, which mutes the volume.
- Hold the Back button to save and exit.
Building Building
-------- --------
Move this folder into flippers applications/plugins/tama_p1.
Launching the app, directly from console to flipper:
`./fbt launch_app APPSRC=applications\plugins\tama_p1`
Run the following to compile icons: Run the following to compile icons:
``` ```
scripts/assets.py icons applications/tama_p1/icons applications/tama_p1/compiled scripts/assets.py icons applications/tama_p1/icons applications/tama_p1/compiled
@@ -20,16 +34,26 @@ scripts/assets.py icons applications/tama_p1/icons applications/tama_p1/compiled
Note: you may also need to add `-Wno-unused-parameter` to `CCFLAGS` in Note: you may also need to add `-Wno-unused-parameter` to `CCFLAGS` in
`site_cons/cc.scons` to suppress unused parameter errors in TamaLIB. `site_cons/cc.scons` to suppress unused parameter errors in TamaLIB.
Debugging
---------
Using the serial script from [FlipperScripts](https://github.com/DroomOne/FlipperScripts/blob/main/serial_logger.py)
it is easy to add direct logging after running the application:
`python .\serial_logger.py`
`./fbt launch_app APPSRC=applications\plugins\tama_p1; python .\serial_logger.py`
Implemented Implemented
----------- -----------
- Basic emulation - Basic emulation
- Input - Input
- Sound - Sound
- Saving/Loading emulator state (stored in `/ext/tama_p1/save.bin`)
- Mute button combo shortcut (Up = A+C)
To-do To-do
----- -----
- Saving/loading - more than one save slot
- Multiple slots?
- In-game reset - In-game reset
- Test mode? - Test mode?
- Volume adjustment - Volume adjustment

View File

@@ -36,7 +36,7 @@ static bool_t tama_p1_hal_is_log_enabled(log_level_t level) {
static void tama_p1_hal_log(log_level_t level, char* buff, ...) { static void tama_p1_hal_log(log_level_t level, char* buff, ...) {
if(!tama_p1_hal_is_log_enabled(level)) return; if(!tama_p1_hal_is_log_enabled(level)) return;
FuriString* string = NULL; FuriString* string = furi_string_alloc();
va_list args; va_list args;
va_start(args, buff); va_start(args, buff);
furi_string_cat_vprintf(string, buff, args); furi_string_cat_vprintf(string, buff, args);
@@ -50,7 +50,10 @@ static void tama_p1_hal_log(log_level_t level, char* buff, ...) {
FURI_LOG_I(TAG_HAL, "%s", furi_string_get_cstr(string)); FURI_LOG_I(TAG_HAL, "%s", furi_string_get_cstr(string));
break; break;
case LOG_MEMORY: case LOG_MEMORY:
break;
case LOG_CPU: case LOG_CPU:
FURI_LOG_D(TAG_HAL, "%s", furi_string_get_cstr(string));
break;
default: default:
FURI_LOG_D(TAG_HAL, "%s", furi_string_get_cstr(string)); FURI_LOG_D(TAG_HAL, "%s", furi_string_get_cstr(string));
break; break;

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

View File

@@ -9,6 +9,11 @@
#define TAMA_LCD_ICON_SIZE 14 #define TAMA_LCD_ICON_SIZE 14
#define TAMA_LCD_ICON_MARGIN 1 #define TAMA_LCD_ICON_MARGIN 1
#define STATE_FILE_MAGIC "TLST"
#define STATE_FILE_VERSION 2
#define TAMA_SAVE_PATH EXT_PATH("tama_p1/save.bin")
typedef struct { typedef struct {
FuriThread* thread; FuriThread* thread;
hal_t hal; hal_t hal;

View File

@@ -41,8 +41,10 @@ static void tama_p1_draw_callback(Canvas* const canvas, void* cb_ctx) {
uint16_t canv_height = canvas_height(canvas); uint16_t canv_height = canvas_height(canvas);
uint16_t lcd_matrix_scaled_width = 32 * TAMA_SCREEN_SCALE_FACTOR; uint16_t lcd_matrix_scaled_width = 32 * TAMA_SCREEN_SCALE_FACTOR;
uint16_t lcd_matrix_scaled_height = 16 * TAMA_SCREEN_SCALE_FACTOR; uint16_t lcd_matrix_scaled_height = 16 * TAMA_SCREEN_SCALE_FACTOR;
// uint16_t lcd_matrix_top = 0;
uint16_t lcd_matrix_top = (canv_height - lcd_matrix_scaled_height) / 2; uint16_t lcd_matrix_top = (canv_height - lcd_matrix_scaled_height) / 2;
uint16_t lcd_matrix_left = (canv_width - lcd_matrix_scaled_width) / 2; uint16_t lcd_matrix_left = (canv_width - lcd_matrix_scaled_width) / 2;
uint16_t lcd_icon_upper_top = lcd_matrix_top - TAMA_LCD_ICON_SIZE - TAMA_LCD_ICON_MARGIN; uint16_t lcd_icon_upper_top = lcd_matrix_top - TAMA_LCD_ICON_SIZE - TAMA_LCD_ICON_MARGIN;
uint16_t lcd_icon_upper_left = lcd_matrix_left; uint16_t lcd_icon_upper_left = lcd_matrix_left;
uint16_t lcd_icon_lower_top = uint16_t lcd_icon_lower_top =
@@ -51,13 +53,6 @@ static void tama_p1_draw_callback(Canvas* const canvas, void* cb_ctx) {
uint16_t lcd_icon_spacing_horiz = uint16_t lcd_icon_spacing_horiz =
(lcd_matrix_scaled_width - (4 * TAMA_LCD_ICON_SIZE)) / 3 + TAMA_LCD_ICON_SIZE; (lcd_matrix_scaled_width - (4 * TAMA_LCD_ICON_SIZE)) / 3 + TAMA_LCD_ICON_SIZE;
// Draw pixels
// canvas_draw_frame(
// canvas,
// lcd_matrix_left,
// lcd_matrix_top,
// lcd_matrix_scaled_width,
// lcd_matrix_scaled_height);
uint16_t y = lcd_matrix_top; uint16_t y = lcd_matrix_top;
for(uint8_t row = 0; row < 16; ++row) { for(uint8_t row = 0; row < 16; ++row) {
@@ -74,21 +69,23 @@ static void tama_p1_draw_callback(Canvas* const canvas, void* cb_ctx) {
y += TAMA_SCREEN_SCALE_FACTOR; y += TAMA_SCREEN_SCALE_FACTOR;
} }
// Draw icons // Start drawing icons
uint8_t lcd_icons = g_ctx->icons; uint8_t lcd_icons = g_ctx->icons;
// Top
// Draw top icons
y = lcd_icon_upper_top; y = lcd_icon_upper_top;
// y = 64 - TAMA_LCD_ICON_SIZE;
uint16_t x_ic = lcd_icon_upper_left; uint16_t x_ic = lcd_icon_upper_left;
for(uint8_t i = 0; i < 4; ++i) { for(uint8_t i = 0; i < 4; ++i) {
// canvas_draw_frame(canvas, x_ic, y, TAMA_LCD_ICON_SIZE, TAMA_LCD_ICON_SIZE);
if(lcd_icons & 1) { if(lcd_icons & 1) {
canvas_draw_icon(canvas, x_ic, y, icons_list[i]); canvas_draw_icon(canvas, x_ic, y, icons_list[i]);
} }
// x_ic += TAMA_LCD_ICON_SIZE + 4;
x_ic += lcd_icon_spacing_horiz; x_ic += lcd_icon_spacing_horiz;
lcd_icons >>= 1; lcd_icons >>= 1;
} }
// Bottom // Draw bottom icons
y = lcd_icon_lower_top; y = lcd_icon_lower_top;
x_ic = lcd_icon_lower_left; x_ic = lcd_icon_lower_left;
for(uint8_t i = 4; i < 8; ++i) { for(uint8_t i = 4; i < 8; ++i) {
@@ -118,6 +115,225 @@ static void tama_p1_update_timer_callback(FuriMessageQueue* event_queue) {
furi_message_queue_put(event_queue, &event, 0); furi_message_queue_put(event_queue, &event, 0);
} }
static void tama_p1_load_state() {
state_t *state;
uint8_t buf[4];
bool error = false;
state = tamalib_get_state();
Storage* storage = furi_record_open(RECORD_STORAGE);
File* file = storage_file_alloc(storage);
if(storage_file_open(file, TAMA_SAVE_PATH, FSAM_READ, FSOM_OPEN_EXISTING)) {
storage_file_read(file, &buf, 4);
if (buf[0] != (uint8_t) STATE_FILE_MAGIC[0] || buf[1] != (uint8_t) STATE_FILE_MAGIC[1] ||
buf[2] != (uint8_t) STATE_FILE_MAGIC[2] || buf[3] != (uint8_t) STATE_FILE_MAGIC[3]) {
FURI_LOG_E(TAG, "FATAL: Wrong state file magic in \"%s\" !\n", TAMA_SAVE_PATH);
error = true;
}
storage_file_read(file, &buf, 1);
if (buf[0] != STATE_FILE_VERSION) {
FURI_LOG_E(TAG, "FATAL: Unsupported version");
error = true;
}
if (!error) {
FURI_LOG_D(TAG, "Reading save.bin");
storage_file_read(file, &buf, 2);
*(state->pc) = buf[0] | ((buf[1] & 0x1F) << 8);
storage_file_read(file, &buf, 2);
*(state->x) = buf[0] | ((buf[1] & 0xF) << 8);
storage_file_read(file, &buf, 2);
*(state->y) = buf[0] | ((buf[1] & 0xF) << 8);
storage_file_read(file, &buf, 1);
*(state->a) = buf[0] & 0xF;
storage_file_read(file, &buf, 1);
*(state->b) = buf[0] & 0xF;
storage_file_read(file, &buf, 1);
*(state->np) = buf[0] & 0x1F;
storage_file_read(file, &buf, 1);
*(state->sp) = buf[0];
storage_file_read(file, &buf, 1);
*(state->flags) = buf[0] & 0xF;
storage_file_read(file, &buf, 4);
*(state->tick_counter) = buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24);
storage_file_read(file, &buf, 4);
*(state->clk_timer_timestamp) = buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24);
storage_file_read(file, &buf, 4);
*(state->prog_timer_timestamp) = buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24);
storage_file_read(file, &buf, 1);
*(state->prog_timer_enabled) = buf[0] & 0x1;
storage_file_read(file, &buf, 1);
*(state->prog_timer_data) = buf[0];
storage_file_read(file, &buf, 1);
*(state->prog_timer_rld) = buf[0];
storage_file_read(file, &buf, 4);
*(state->call_depth) = buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24);
FURI_LOG_D(TAG, "Restoring Interupts");
for (uint32_t i = 0; i < INT_SLOT_NUM; i++) {
storage_file_read(file, &buf, 1);
state->interrupts[i].factor_flag_reg = buf[0] & 0xF;
storage_file_read(file, &buf, 1);
state->interrupts[i].mask_reg = buf[0] & 0xF;
storage_file_read(file, &buf, 1);
state->interrupts[i].triggered = buf[0] & 0x1;
}
/* First 640 half bytes correspond to the RAM */
FURI_LOG_D(TAG, "Restoring RAM");
for (uint32_t i = 0; i < MEM_RAM_SIZE; i++) {
storage_file_read(file, &buf, 1);
SET_RAM_MEMORY(state->memory, i + MEM_RAM_ADDR, buf[0] & 0xF);
}
/* I/Os are from 0xF00 to 0xF7F */
FURI_LOG_D(TAG, "Restoring I/O");
for (uint32_t i = 0; i < MEM_IO_SIZE; i++) {
storage_file_read(file, &buf, 1);
SET_IO_MEMORY(state->memory, i + MEM_IO_ADDR, buf[0] & 0xF);
}
FURI_LOG_D(TAG, "Refreshing Hardware");
tamalib_refresh_hw();
}
}
storage_file_close(file);
storage_file_free(file);
furi_record_close(RECORD_STORAGE);
}
static void tama_p1_save_state() {
// Saving state
FURI_LOG_D(TAG, "Saving Gamestate");
uint8_t buf[4];
state_t *state;
uint32_t offset = 0;
state = tamalib_get_state();
Storage* storage = furi_record_open(RECORD_STORAGE);
File* file = storage_file_alloc(storage);
if(storage_file_open(file, TAMA_SAVE_PATH, FSAM_WRITE, FSOM_CREATE_ALWAYS)) {
buf[0] = (uint8_t) STATE_FILE_MAGIC[0];
buf[1] = (uint8_t) STATE_FILE_MAGIC[1];
buf[2] = (uint8_t) STATE_FILE_MAGIC[2];
buf[3] = (uint8_t) STATE_FILE_MAGIC[3];
offset += storage_file_write(file, &buf, sizeof(buf));
buf[0] = STATE_FILE_VERSION & 0xFF;
offset += storage_file_write(file, &buf, 1);
buf[0] = *(state->pc) & 0xFF;
buf[1] = (*(state->pc) >> 8) & 0x1F;
offset += storage_file_write(file, &buf, 2);
buf[0] = *(state->x) & 0xFF;
buf[1] = (*(state->x) >> 8) & 0xF;
offset += storage_file_write(file, &buf, 2);
buf[0] = *(state->y) & 0xFF;
buf[1] = (*(state->y) >> 8) & 0xF;
offset += storage_file_write(file, &buf, 2);
buf[0] = *(state->a) & 0xF;
offset += storage_file_write(file, &buf, 1);
buf[0] = *(state->b) & 0xF;
offset += storage_file_write(file, &buf, 1);
buf[0] = *(state->np) & 0x1F;
offset += storage_file_write(file, &buf, 1);
buf[0] = *(state->sp) & 0xFF;
offset += storage_file_write(file, &buf, 1);
buf[0] = *(state->flags) & 0xF;
offset += storage_file_write(file, &buf, 1);
buf[0] = *(state->tick_counter) & 0xFF;
buf[1] = (*(state->tick_counter) >> 8) & 0xFF;
buf[2] = (*(state->tick_counter) >> 16) & 0xFF;
buf[3] = (*(state->tick_counter) >> 24) & 0xFF;
offset += storage_file_write(file, &buf, sizeof(buf));
buf[0] = *(state->clk_timer_timestamp) & 0xFF;
buf[1] = (*(state->clk_timer_timestamp) >> 8) & 0xFF;
buf[2] = (*(state->clk_timer_timestamp) >> 16) & 0xFF;
buf[3] = (*(state->clk_timer_timestamp) >> 24) & 0xFF;
offset += storage_file_write(file, &buf, sizeof(buf));
buf[0] = *(state->prog_timer_timestamp) & 0xFF;
buf[1] = (*(state->prog_timer_timestamp) >> 8) & 0xFF;
buf[2] = (*(state->prog_timer_timestamp) >> 16) & 0xFF;
buf[3] = (*(state->prog_timer_timestamp) >> 24) & 0xFF;
offset += storage_file_write(file, &buf, sizeof(buf));
buf[0] = *(state->prog_timer_enabled) & 0x1;
offset += storage_file_write(file, &buf, 1);
buf[0] = *(state->prog_timer_data) & 0xFF;
offset += storage_file_write(file, &buf, 1);
buf[0] = *(state->prog_timer_rld) & 0xFF;
offset += storage_file_write(file, &buf, 1);
buf[0] = *(state->call_depth) & 0xFF;
buf[1] = (*(state->call_depth) >> 8) & 0xFF;
buf[2] = (*(state->call_depth) >> 16) & 0xFF;
buf[3] = (*(state->call_depth) >> 24) & 0xFF;
offset += storage_file_write(file, &buf, sizeof(buf));
for (uint32_t i = 0; i < INT_SLOT_NUM; i++) {
buf[0] = state->interrupts[i].factor_flag_reg & 0xF;
offset += storage_file_write(file, &buf, 1);
buf[0] = state->interrupts[i].mask_reg & 0xF;
offset += storage_file_write(file, &buf, 1);
buf[0] = state->interrupts[i].triggered & 0x1;
offset += storage_file_write(file, &buf, 1);
}
/* First 640 half bytes correspond to the RAM */
for (uint32_t i = 0; i < MEM_RAM_SIZE; i++) {
buf[0] = GET_RAM_MEMORY(state->memory, i + MEM_RAM_ADDR) & 0xF;
offset += storage_file_write(file, &buf, 1);
}
/* I/Os are from 0xF00 to 0xF7F */
for (uint32_t i = 0; i < MEM_IO_SIZE; i++) {
buf[0] = GET_IO_MEMORY(state->memory, i + MEM_IO_ADDR) & 0xF;
offset += storage_file_write(file, &buf, 1);
}
}
storage_file_close(file);
storage_file_free(file);
furi_record_close(RECORD_STORAGE);
FURI_LOG_D(TAG, "Finished Writing %lu", offset);
}
static int32_t tama_p1_worker(void* context) { static int32_t tama_p1_worker(void* context) {
bool running = true; bool running = true;
FuriMutex* mutex = context; FuriMutex* mutex = context;
@@ -125,6 +341,9 @@ static int32_t tama_p1_worker(void* context) {
cpu_sync_ref_timestamp(); cpu_sync_ref_timestamp();
LL_TIM_EnableCounter(TIM2); LL_TIM_EnableCounter(TIM2);
tama_p1_load_state();
while(running) { while(running) {
if(furi_thread_flags_get()) { if(furi_thread_flags_get()) {
running = false; running = false;
@@ -139,6 +358,8 @@ static int32_t tama_p1_worker(void* context) {
return 0; return 0;
} }
static void tama_p1_init(TamaApp* const ctx) { static void tama_p1_init(TamaApp* const ctx) {
g_ctx = ctx; g_ctx = ctx;
memset(ctx, 0, sizeof(TamaApp)); memset(ctx, 0, sizeof(TamaApp));
@@ -264,12 +485,22 @@ int32_t tama_p1_app(void* p) {
tamalib_set_button(BTN_MIDDLE, tama_btn_state); tamalib_set_button(BTN_MIDDLE, tama_btn_state);
} else if(event.input.key == InputKeyRight) { } else if(event.input.key == InputKeyRight) {
tamalib_set_button(BTN_RIGHT, tama_btn_state); tamalib_set_button(BTN_RIGHT, tama_btn_state);
} else if(event.input.key == InputKeyDown && event.input.type == InputTypeShort) {
// TODO: pause or fast-forward tamagotchi
tama_p1_save_state();
} else if(event.input.key == InputKeyUp) { // mute tamagotchi
tamalib_set_button(BTN_LEFT, tama_btn_state);
tamalib_set_button(BTN_RIGHT, tama_btn_state);
} else if(event.input.key == InputKeyBack && event.input.type == InputTypeShort) {
tama_p1_save_state();
} }
} }
if(event.input.key == InputKeyBack && event.input.type == InputTypeLong) { if(event.input.key == InputKeyBack && event.input.type == InputTypeLong) {
furi_timer_stop(timer); furi_timer_stop(timer);
running = false; running = false;
tama_p1_save_state();
} }
} }

View File

@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2022 Alexander Kopachov
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -14,6 +14,8 @@
#define TOTP_CLI_COMMAND_ADD_ARG_DIGITS "digits" #define TOTP_CLI_COMMAND_ADD_ARG_DIGITS "digits"
#define TOTP_CLI_COMMAND_ADD_ARG_DIGITS_PREFIX "-d" #define TOTP_CLI_COMMAND_ADD_ARG_DIGITS_PREFIX "-d"
#define TOTP_CLI_COMMAND_ADD_ARG_UNSECURE_PREFIX "-u" #define TOTP_CLI_COMMAND_ADD_ARG_UNSECURE_PREFIX "-u"
#define TOTP_CLI_COMMAND_ADD_ARG_DURATION "duration"
#define TOTP_CLI_COMMAND_ADD_ARG_DURATION_PREFIX "-l"
static bool token_info_set_algo_from_str(TokenInfo* token_info, const FuriString* str) { static bool token_info_set_algo_from_str(TokenInfo* token_info, const FuriString* str) {
if(furi_string_cmpi_str(str, TOTP_CONFIG_TOKEN_ALGO_SHA1_NAME) == 0) { if(furi_string_cmpi_str(str, TOTP_CONFIG_TOKEN_ALGO_SHA1_NAME) == 0) {
@@ -34,6 +36,16 @@ static bool token_info_set_algo_from_str(TokenInfo* token_info, const FuriString
return false; return false;
} }
static bool args_read_uint8_and_trim(FuriString* args, uint8_t* value) {
int int_value;
if(!args_read_int_and_trim(args, &int_value) || int_value < 0 || int_value > UINT8_MAX) {
return false;
}
*value = (uint8_t)int_value;
return true;
}
void totp_cli_command_add_docopt_commands() { void totp_cli_command_add_docopt_commands() {
TOTP_CLI_PRINTF(" " TOTP_CLI_COMMAND_ADD ", " TOTP_CLI_COMMAND_ADD_ALT TOTP_CLI_PRINTF(" " TOTP_CLI_COMMAND_ADD ", " TOTP_CLI_COMMAND_ADD_ALT
", " TOTP_CLI_COMMAND_ADD_ALT2 " Add new token\r\n"); ", " TOTP_CLI_COMMAND_ADD_ALT2 " Add new token\r\n");
@@ -42,11 +54,11 @@ void totp_cli_command_add_docopt_commands() {
void totp_cli_command_add_docopt_usage() { void totp_cli_command_add_docopt_usage() {
TOTP_CLI_PRINTF( TOTP_CLI_PRINTF(
" " TOTP_CLI_COMMAND_NAME " " TOTP_CLI_COMMAND_NAME
" " DOCOPT_REQUIRED(TOTP_CLI_COMMAND_ADD " | " TOTP_CLI_COMMAND_ADD_ALT " | " TOTP_CLI_COMMAND_ADD_ALT2) " " DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ADD_ARG_NAME) " " DOCOPT_OPTIONAL( " " DOCOPT_REQUIRED(TOTP_CLI_COMMAND_ADD " | " TOTP_CLI_COMMAND_ADD_ALT " | " TOTP_CLI_COMMAND_ADD_ALT2) " " DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ADD_ARG_NAME) " " DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_ADD_ARG_ALGO_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ADD_ARG_ALGO))) " " DOCOPT_OPTIONAL(
DOCOPT_OPTION( DOCOPT_OPTION(
TOTP_CLI_COMMAND_ADD_ARG_ALGO_PREFIX, TOTP_CLI_COMMAND_ADD_ARG_DIGITS_PREFIX,
DOCOPT_ARGUMENT( DOCOPT_ARGUMENT(
TOTP_CLI_COMMAND_ADD_ARG_ALGO))) " " DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_ADD_ARG_DIGITS_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ADD_ARG_DIGITS))) " " DOCOPT_OPTIONAL(DOCOPT_SWITCH(TOTP_CLI_COMMAND_ADD_ARG_UNSECURE_PREFIX)) "\r\n"); TOTP_CLI_COMMAND_ADD_ARG_DIGITS))) " " DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_ADD_ARG_DURATION_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ADD_ARG_DURATION))) " " DOCOPT_OPTIONAL(DOCOPT_SWITCH(TOTP_CLI_COMMAND_ADD_ARG_UNSECURE_PREFIX)) "\r\n");
} }
void totp_cli_command_add_docopt_arguments() { void totp_cli_command_add_docopt_arguments() {
@@ -64,6 +76,10 @@ void totp_cli_command_add_docopt_options() {
TOTP_CLI_COMMAND_ADD_ARG_DIGITS_PREFIX, TOTP_CLI_COMMAND_ADD_ARG_DIGITS_PREFIX,
DOCOPT_ARGUMENT( DOCOPT_ARGUMENT(
TOTP_CLI_COMMAND_ADD_ARG_DIGITS)) " Number of digits to generate, one of: 6, 8 " DOCOPT_DEFAULT("6") "\r\n"); TOTP_CLI_COMMAND_ADD_ARG_DIGITS)) " Number of digits to generate, one of: 6, 8 " DOCOPT_DEFAULT("6") "\r\n");
TOTP_CLI_PRINTF(" " DOCOPT_OPTION(
TOTP_CLI_COMMAND_ADD_ARG_DURATION_PREFIX,
DOCOPT_ARGUMENT(
TOTP_CLI_COMMAND_ADD_ARG_DURATION)) " Token lifetime duration in seconds, between: 15 and 255 " DOCOPT_DEFAULT("30") "\r\n");
TOTP_CLI_PRINTF(" " DOCOPT_SWITCH( TOTP_CLI_PRINTF(" " DOCOPT_SWITCH(
TOTP_CLI_COMMAND_ADD_ARG_UNSECURE_PREFIX) " Show console user input as-is without masking\r\n"); TOTP_CLI_COMMAND_ADD_ARG_UNSECURE_PREFIX) " Show console user input as-is without masking\r\n");
} }
@@ -110,16 +126,32 @@ void totp_cli_command_add_handle(PluginState* plugin_state, FuriString* args, Cl
parsed = true; parsed = true;
} }
} else if(furi_string_cmpi_str(temp_str, TOTP_CLI_COMMAND_ADD_ARG_DIGITS_PREFIX) == 0) { } else if(furi_string_cmpi_str(temp_str, TOTP_CLI_COMMAND_ADD_ARG_DIGITS_PREFIX) == 0) {
if(!args_read_string_and_trim(args, temp_str)) { uint8_t digit_value;
if(!args_read_uint8_and_trim(args, &digit_value)) {
TOTP_CLI_PRINTF( TOTP_CLI_PRINTF(
"Missed value for argument \"" TOTP_CLI_COMMAND_ADD_ARG_DIGITS_PREFIX "Missed or incorrect value for argument \"" TOTP_CLI_COMMAND_ADD_ARG_DIGITS_PREFIX
"\"\r\n"); "\"\r\n");
} else if(!token_info_set_digits_from_int( } else if(!token_info_set_digits_from_int(token_info, digit_value)) {
token_info, CONVERT_CHAR_TO_DIGIT(furi_string_get_char(temp_str, 0)))) {
TOTP_CLI_PRINTF( TOTP_CLI_PRINTF(
"\"%s\" is incorrect value for argument \"" TOTP_CLI_COMMAND_ADD_ARG_DIGITS_PREFIX "\"%" PRIu8
"\" is incorrect value for argument \"" TOTP_CLI_COMMAND_ADD_ARG_DIGITS_PREFIX
"\"\r\n", "\"\r\n",
furi_string_get_cstr(temp_str)); digit_value);
} else {
parsed = true;
}
} else if(furi_string_cmpi_str(temp_str, TOTP_CLI_COMMAND_ADD_ARG_DURATION_PREFIX) == 0) {
uint8_t duration_value;
if(!args_read_uint8_and_trim(args, &duration_value)) {
TOTP_CLI_PRINTF(
"Missed or incorrect value for argument \"" TOTP_CLI_COMMAND_ADD_ARG_DURATION_PREFIX
"\"\r\n");
} else if(!token_info_set_duration_from_int(token_info, duration_value)) {
TOTP_CLI_PRINTF(
"\"%" PRIu8
"\" is incorrect value for argument \"" TOTP_CLI_COMMAND_ADD_ARG_DURATION_PREFIX
"\"\r\n",
duration_value);
} else { } else {
parsed = true; parsed = true;
} }

View File

@@ -40,19 +40,21 @@ void totp_cli_command_list_handle(PluginState* plugin_state, Cli* cli) {
return; return;
} }
TOTP_CLI_PRINTF("+-----+-----------------------------+--------+--------+\r\n"); TOTP_CLI_PRINTF("+-----+---------------------------+--------+----+-----+\r\n");
TOTP_CLI_PRINTF("| %-*s | %-*s | %-*s | %-s |\r\n", 3, "#", 27, "Name", 6, "Algo", "Digits"); TOTP_CLI_PRINTF(
TOTP_CLI_PRINTF("+-----+-----------------------------+--------+--------+\r\n"); "| %-*s | %-*s | %-*s | %-s | %-s |\r\n", 3, "#", 25, "Name", 6, "Algo", "Ln", "Dur");
TOTP_CLI_PRINTF("+-----+---------------------------+--------+----+-----+\r\n");
uint16_t index = 1; uint16_t index = 1;
TOTP_LIST_FOREACH(plugin_state->tokens_list, node, { TOTP_LIST_FOREACH(plugin_state->tokens_list, node, {
TokenInfo* token_info = (TokenInfo*)node->data; TokenInfo* token_info = (TokenInfo*)node->data;
TOTP_CLI_PRINTF( TOTP_CLI_PRINTF(
"| %-3" PRIu16 " | %-27.27s | %-6s | %-6" PRIu8 " |\r\n", "| %-3" PRIu16 " | %-25.25s | %-6s | %-2" PRIu8 " | %-3" PRIu8 " |\r\n",
index, index,
token_info->name, token_info->name,
get_algo_as_cstr(token_info->algo), get_algo_as_cstr(token_info->algo),
token_info->digits); token_info->digits,
token_info->duration);
index++; index++;
}); });
TOTP_CLI_PRINTF("+-----+-----------------------------+--------+--------+\r\n"); TOTP_CLI_PRINTF("+-----+---------------------------+--------+----+-----+\r\n");
} }

View File

@@ -5,6 +5,7 @@
#include "../../types/common.h" #include "../../types/common.h"
#include "../../types/token_info.h" #include "../../types/token_info.h"
#include "migrations/config_migration_v1_to_v2.h" #include "migrations/config_migration_v1_to_v2.h"
#include "migrations/config_migration_v2_to_v3.h"
#define CONFIG_FILE_DIRECTORY_PATH EXT_PATH("apps_data/authenticator") #define CONFIG_FILE_DIRECTORY_PATH EXT_PATH("apps_data/authenticator")
#define CONFIG_FILE_PATH CONFIG_FILE_DIRECTORY_PATH "/totp.conf" #define CONFIG_FILE_PATH CONFIG_FILE_DIRECTORY_PATH "/totp.conf"
@@ -173,6 +174,13 @@ static TotpConfigFileOpenResult totp_open_config_file(Storage* storage, FlipperF
flipper_format_write_comment(fff_data_file, temp_str); flipper_format_write_comment(fff_data_file, temp_str);
flipper_format_write_comment_cstr(fff_data_file, " "); flipper_format_write_comment_cstr(fff_data_file, " ");
flipper_format_write_comment_cstr(
fff_data_file,
"# Token lifetime duration in seconds. Should be between 15 and 255. Majority websites requires 30, however some rare websites may require custom lifetime. If you are not sure which one to use - use 30");
furi_string_printf(temp_str, "%s: 30", TOTP_CONFIG_KEY_TOKEN_DURATION);
flipper_format_write_comment(fff_data_file, temp_str);
flipper_format_write_comment_cstr(fff_data_file, " ");
flipper_format_write_comment_cstr(fff_data_file, "=== TOKEN SAMPLE END ==="); flipper_format_write_comment_cstr(fff_data_file, "=== TOKEN SAMPLE END ===");
flipper_format_write_comment_cstr(fff_data_file, " "); flipper_format_write_comment_cstr(fff_data_file, " ");
@@ -232,6 +240,12 @@ TotpConfigFileUpdateResult
break; break;
} }
tmp_uint32 = token_info->duration;
if(!flipper_format_write_uint32(file, TOTP_CONFIG_KEY_TOKEN_DURATION, &tmp_uint32, 1)) {
update_result = TotpConfigFileUpdateError;
break;
}
update_result = TotpConfigFileUpdateSuccess; update_result = TotpConfigFileUpdateSuccess;
} while(false); } while(false);
@@ -483,6 +497,7 @@ TotpConfigFileOpenResult totp_config_file_load_base(PluginState* const plugin_st
if(file_version == 1) { if(file_version == 1) {
if(totp_config_migrate_v1_to_v2(fff_data_file, fff_backup_data_file)) { if(totp_config_migrate_v1_to_v2(fff_data_file, fff_backup_data_file)) {
FURI_LOG_I(LOGGING_TAG, "Applied migration from v1 to v2"); FURI_LOG_I(LOGGING_TAG, "Applied migration from v1 to v2");
file_version = 2;
} else { } else {
FURI_LOG_W( FURI_LOG_W(
LOGGING_TAG, "An error occurred during migration from v1 to v2"); LOGGING_TAG, "An error occurred during migration from v1 to v2");
@@ -491,6 +506,18 @@ TotpConfigFileOpenResult totp_config_file_load_base(PluginState* const plugin_st
} }
} }
if(file_version == 2) {
if(totp_config_migrate_v2_to_v3(fff_data_file, fff_backup_data_file)) {
FURI_LOG_I(LOGGING_TAG, "Applied migration from v2 to v3");
file_version = 3;
} else {
FURI_LOG_W(
LOGGING_TAG, "An error occurred during migration from v2 to v3");
result = TotpConfigFileOpenError;
break;
}
}
flipper_format_file_close(fff_backup_data_file); flipper_format_file_close(fff_backup_data_file);
flipper_format_free(fff_backup_data_file); flipper_format_free(fff_backup_data_file);
flipper_format_rewind(fff_data_file); flipper_format_rewind(fff_data_file);
@@ -669,6 +696,12 @@ TokenLoadingResult totp_config_file_load_tokens(PluginState* const plugin_state)
tokenInfo->digits = TOTP_6_DIGITS; tokenInfo->digits = TOTP_6_DIGITS;
} }
if(!flipper_format_read_uint32(
fff_data_file, TOTP_CONFIG_KEY_TOKEN_DURATION, &temp_data32, 1) ||
!token_info_set_duration_from_int(tokenInfo, temp_data32)) {
tokenInfo->duration = TOTP_TOKEN_DURATION_DEFAULT;
}
FURI_LOG_D(LOGGING_TAG, "Found token \"%s\"", tokenInfo->name); FURI_LOG_D(LOGGING_TAG, "Found token \"%s\"", tokenInfo->name);
TOTP_LIST_INIT_OR_ADD(plugin_state->tokens_list, tokenInfo, furi_check); TOTP_LIST_INIT_OR_ADD(plugin_state->tokens_list, tokenInfo, furi_check);

View File

@@ -1,13 +1,14 @@
#pragma once #pragma once
#define CONFIG_FILE_HEADER "Flipper TOTP plugin config file" #define CONFIG_FILE_HEADER "Flipper TOTP plugin config file"
#define CONFIG_FILE_ACTUAL_VERSION 2 #define CONFIG_FILE_ACTUAL_VERSION 3
#define TOTP_CONFIG_KEY_TIMEZONE "Timezone" #define TOTP_CONFIG_KEY_TIMEZONE "Timezone"
#define TOTP_CONFIG_KEY_TOKEN_NAME "TokenName" #define TOTP_CONFIG_KEY_TOKEN_NAME "TokenName"
#define TOTP_CONFIG_KEY_TOKEN_SECRET "TokenSecret" #define TOTP_CONFIG_KEY_TOKEN_SECRET "TokenSecret"
#define TOTP_CONFIG_KEY_TOKEN_ALGO "TokenAlgo" #define TOTP_CONFIG_KEY_TOKEN_ALGO "TokenAlgo"
#define TOTP_CONFIG_KEY_TOKEN_DIGITS "TokenDigits" #define TOTP_CONFIG_KEY_TOKEN_DIGITS "TokenDigits"
#define TOTP_CONFIG_KEY_TOKEN_DURATION "TokenDuration"
#define TOTP_CONFIG_KEY_CRYPTO_VERIFY "Crypto" #define TOTP_CONFIG_KEY_CRYPTO_VERIFY "Crypto"
#define TOTP_CONFIG_KEY_BASE_IV "BaseIV" #define TOTP_CONFIG_KEY_BASE_IV "BaseIV"
#define TOTP_CONFIG_KEY_PINSET "PinIsSet" #define TOTP_CONFIG_KEY_PINSET "PinIsSet"

View File

@@ -1,6 +1,7 @@
#include "config_migration_v1_to_v2.h" #include "config_migration_v1_to_v2.h"
#include <flipper_format/flipper_format.h> #include <flipper_format/flipper_format.h>
#include "../constants.h" #include "../constants.h"
#include "../../../types/token_info.h"
#define NEW_VERSION 2 #define NEW_VERSION 2
@@ -36,7 +37,7 @@ bool totp_config_migrate_v1_to_v2(
flipper_format_write_string_cstr( flipper_format_write_string_cstr(
fff_data_file, TOTP_CONFIG_KEY_TOKEN_ALGO, TOTP_CONFIG_TOKEN_ALGO_SHA1_NAME); fff_data_file, TOTP_CONFIG_KEY_TOKEN_ALGO, TOTP_CONFIG_TOKEN_ALGO_SHA1_NAME);
uint32_t default_digits = 6; const uint32_t default_digits = TOTP_6_DIGITS;
flipper_format_write_uint32( flipper_format_write_uint32(
fff_data_file, TOTP_CONFIG_KEY_TOKEN_DIGITS, &default_digits, 1); fff_data_file, TOTP_CONFIG_KEY_TOKEN_DIGITS, &default_digits, 1);
} }

View File

@@ -0,0 +1,70 @@
#include "config_migration_v2_to_v3.h"
#include <flipper_format/flipper_format.h>
#include "../constants.h"
#include "../../../types/token_info.h"
#define NEW_VERSION 3
bool totp_config_migrate_v2_to_v3(
FlipperFormat* fff_data_file,
FlipperFormat* fff_backup_data_file) {
flipper_format_write_header_cstr(fff_data_file, CONFIG_FILE_HEADER, NEW_VERSION);
FuriString* temp_str = furi_string_alloc();
if(flipper_format_read_string(fff_backup_data_file, TOTP_CONFIG_KEY_BASE_IV, temp_str)) {
flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_BASE_IV, temp_str);
}
flipper_format_rewind(fff_backup_data_file);
if(flipper_format_read_string(fff_backup_data_file, TOTP_CONFIG_KEY_CRYPTO_VERIFY, temp_str)) {
flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_CRYPTO_VERIFY, temp_str);
}
flipper_format_rewind(fff_backup_data_file);
if(flipper_format_read_string(fff_backup_data_file, TOTP_CONFIG_KEY_TIMEZONE, temp_str)) {
flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_TIMEZONE, temp_str);
}
flipper_format_rewind(fff_backup_data_file);
if(flipper_format_read_string(fff_backup_data_file, TOTP_CONFIG_KEY_PINSET, temp_str)) {
flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_PINSET, temp_str);
}
flipper_format_rewind(fff_backup_data_file);
if(flipper_format_read_string(
fff_backup_data_file, TOTP_CONFIG_KEY_NOTIFICATION_METHOD, temp_str)) {
flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_NOTIFICATION_METHOD, temp_str);
}
flipper_format_rewind(fff_backup_data_file);
while(true) {
if(!flipper_format_read_string(
fff_backup_data_file, TOTP_CONFIG_KEY_TOKEN_NAME, temp_str)) {
break;
}
flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_TOKEN_NAME, temp_str);
flipper_format_read_string(fff_backup_data_file, TOTP_CONFIG_KEY_TOKEN_SECRET, temp_str);
flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_TOKEN_SECRET, temp_str);
flipper_format_read_string(fff_backup_data_file, TOTP_CONFIG_KEY_TOKEN_ALGO, temp_str);
flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_TOKEN_ALGO, temp_str);
flipper_format_read_string(fff_backup_data_file, TOTP_CONFIG_KEY_TOKEN_DIGITS, temp_str);
flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_TOKEN_DIGITS, temp_str);
const uint32_t default_duration = TOTP_TOKEN_DURATION_DEFAULT;
flipper_format_write_uint32(
fff_data_file, TOTP_CONFIG_KEY_TOKEN_DURATION, &default_duration, 1);
}
furi_string_free(temp_str);
return true;
}

View File

@@ -0,0 +1,7 @@
#pragma once
#include <flipper_format/flipper_format.h>
bool totp_config_migrate_v2_to_v3(
FlipperFormat* fff_data_file,
FlipperFormat* fff_backup_data_file);

View File

@@ -61,3 +61,12 @@ bool token_info_set_digits_from_int(TokenInfo* token_info, uint8_t digits) {
return false; return false;
} }
bool token_info_set_duration_from_int(TokenInfo* token_info, uint8_t duration) {
if(duration >= 15) {
token_info->duration = duration;
return true;
}
return false;
}

View File

@@ -2,6 +2,8 @@
#include <inttypes.h> #include <inttypes.h>
#define TOTP_TOKEN_DURATION_DEFAULT 30
typedef uint8_t TokenHashAlgo; typedef uint8_t TokenHashAlgo;
typedef uint8_t TokenDigitsCount; typedef uint8_t TokenDigitsCount;
@@ -70,6 +72,11 @@ typedef struct {
* @brief Desired TOTP token length * @brief Desired TOTP token length
*/ */
TokenDigitsCount digits; TokenDigitsCount digits;
/**
* @brief Desired TOTP token duration in seconds
*/
uint8_t duration;
} TokenInfo; } TokenInfo;
/** /**
@@ -102,6 +109,14 @@ bool token_info_set_secret(
* @brief Sets token digits count from \c uint8_t value * @brief Sets token digits count from \c uint8_t value
* @param token_info instance whichs token digits count length should be updated * @param token_info instance whichs token digits count length should be updated
* @param digits desired token digits count length * @param digits desired token digits count length
* @return \c true if token digits count length has been updated; \c false p * @return \c true if token digits count length has been updated; \c false otherwise
*/ */
bool token_info_set_digits_from_int(TokenInfo* token_info, uint8_t digits); bool token_info_set_digits_from_int(TokenInfo* token_info, uint8_t digits);
/**
* @brief Sets token duration from \c uint8_t value
* @param token_info instance whichs token digits count length should be updated
* @param duration desired token duration in seconds
* @return \c true if token duration has been updated; \c false otherwise
*/
bool token_info_set_duration_from_int(TokenInfo* token_info, uint8_t duration);

View File

@@ -21,6 +21,7 @@ typedef enum {
TokenSecretTextBox, TokenSecretTextBox,
TokenAlgoSelect, TokenAlgoSelect,
TokenLengthSelect, TokenLengthSelect,
TokenDurationSelect,
ConfirmButton, ConfirmButton,
} Control; } Control;
@@ -39,6 +40,8 @@ typedef struct {
int16_t screen_y_offset; int16_t screen_y_offset;
TokenHashAlgo algo; TokenHashAlgo algo;
uint8_t digits_count_index; uint8_t digits_count_index;
uint8_t duration;
FuriString* duration_text;
} SceneState; } SceneState;
void totp_scene_add_new_token_init(const PluginState* plugin_state) { void totp_scene_add_new_token_init(const PluginState* plugin_state) {
@@ -63,6 +66,10 @@ static void on_token_secret_user_comitted(InputTextSceneCallbackResult* result)
free(result); free(result);
} }
static void update_duration_text(SceneState* scene_state) {
furi_string_printf(scene_state->duration_text, "%d sec.", scene_state->duration);
}
void totp_scene_add_new_token_activate( void totp_scene_add_new_token_activate(
PluginState* plugin_state, PluginState* plugin_state,
const TokenAddEditSceneContext* context) { const TokenAddEditSceneContext* context) {
@@ -89,6 +96,9 @@ void totp_scene_add_new_token_activate(
scene_state->screen_y_offset = 0; scene_state->screen_y_offset = 0;
scene_state->input_state = NULL; scene_state->input_state = NULL;
scene_state->duration = TOTP_TOKEN_DURATION_DEFAULT;
scene_state->duration_text = furi_string_alloc();
update_duration_text(scene_state);
if(context == NULL) { if(context == NULL) {
TOTP_NULLABLE_NULL(scene_state->current_token_index); TOTP_NULLABLE_NULL(scene_state->current_token_index);
@@ -124,14 +134,23 @@ void totp_scene_add_new_token_render(Canvas* const canvas, PluginState* plugin_s
ui_control_select_render( ui_control_select_render(
canvas, canvas,
0, 0,
63 - scene_state->screen_y_offset, 61 - scene_state->screen_y_offset,
SCREEN_WIDTH, SCREEN_WIDTH,
TOKEN_DIGITS_TEXT_LIST[scene_state->digits_count_index], TOKEN_DIGITS_TEXT_LIST[scene_state->digits_count_index],
scene_state->selected_control == TokenLengthSelect); scene_state->selected_control == TokenLengthSelect);
ui_control_select_render(
canvas,
0,
78 - scene_state->screen_y_offset,
SCREEN_WIDTH,
furi_string_get_cstr(scene_state->duration_text),
scene_state->selected_control == TokenDurationSelect);
ui_control_button_render( ui_control_button_render(
canvas, canvas,
SCREEN_WIDTH_CENTER - 24, SCREEN_WIDTH_CENTER - 24,
85 - scene_state->screen_y_offset, 101 - scene_state->screen_y_offset,
48, 48,
13, 13,
"Confirm", "Confirm",
@@ -146,8 +165,12 @@ void totp_scene_add_new_token_render(Canvas* const canvas, PluginState* plugin_s
} }
void update_screen_y_offset(SceneState* scene_state) { void update_screen_y_offset(SceneState* scene_state) {
if(scene_state->selected_control > TokenAlgoSelect) { if(scene_state->selected_control > TokenLengthSelect) {
scene_state->screen_y_offset = 35; scene_state->screen_y_offset = 51;
} else if(scene_state->selected_control > TokenAlgoSelect) {
scene_state->screen_y_offset = 34;
} else if(scene_state->selected_control > TokenSecretTextBox) {
scene_state->screen_y_offset = 17;
} else { } else {
scene_state->screen_y_offset = 0; scene_state->screen_y_offset = 0;
} }
@@ -197,6 +220,9 @@ bool totp_scene_add_new_token_handle_event(PluginEvent* const event, PluginState
} else if(scene_state->selected_control == TokenLengthSelect) { } else if(scene_state->selected_control == TokenLengthSelect) {
totp_roll_value_uint8_t( totp_roll_value_uint8_t(
&scene_state->digits_count_index, 1, 0, 1, RollOverflowBehaviorRoll); &scene_state->digits_count_index, 1, 0, 1, RollOverflowBehaviorRoll);
} else if(scene_state->selected_control == TokenDurationSelect) {
totp_roll_value_uint8_t(&scene_state->duration, 15, 15, 255, RollOverflowBehaviorStop);
update_duration_text(scene_state);
} }
break; break;
case InputKeyLeft: case InputKeyLeft:
@@ -206,6 +232,10 @@ bool totp_scene_add_new_token_handle_event(PluginEvent* const event, PluginState
} else if(scene_state->selected_control == TokenLengthSelect) { } else if(scene_state->selected_control == TokenLengthSelect) {
totp_roll_value_uint8_t( totp_roll_value_uint8_t(
&scene_state->digits_count_index, -1, 0, 1, RollOverflowBehaviorRoll); &scene_state->digits_count_index, -1, 0, 1, RollOverflowBehaviorRoll);
} else if(scene_state->selected_control == TokenDurationSelect) {
totp_roll_value_uint8_t(
&scene_state->duration, -15, 15, 255, RollOverflowBehaviorStop);
update_duration_text(scene_state);
} }
break; break;
case InputKeyOk: case InputKeyOk:
@@ -230,6 +260,8 @@ bool totp_scene_add_new_token_handle_event(PluginEvent* const event, PluginState
break; break;
case TokenLengthSelect: case TokenLengthSelect:
break; break;
case TokenDurationSelect:
break;
case ConfirmButton: { case ConfirmButton: {
TokenInfo* tokenInfo = token_info_alloc(); TokenInfo* tokenInfo = token_info_alloc();
bool token_secret_set = token_info_set_secret( bool token_secret_set = token_info_set_secret(
@@ -245,6 +277,7 @@ bool totp_scene_add_new_token_handle_event(PluginEvent* const event, PluginState
tokenInfo->name, scene_state->token_name, scene_state->token_name_length + 1); tokenInfo->name, scene_state->token_name, scene_state->token_name_length + 1);
tokenInfo->algo = scene_state->algo; tokenInfo->algo = scene_state->algo;
tokenInfo->digits = TOKEN_DIGITS_VALUE_LIST[scene_state->digits_count_index]; tokenInfo->digits = TOKEN_DIGITS_VALUE_LIST[scene_state->digits_count_index];
tokenInfo->duration = scene_state->duration;
TOTP_LIST_INIT_OR_ADD(plugin_state->tokens_list, tokenInfo, furi_check); TOTP_LIST_INIT_OR_ADD(plugin_state->tokens_list, tokenInfo, furi_check);
plugin_state->tokens_count++; plugin_state->tokens_count++;
@@ -310,6 +343,8 @@ void totp_scene_add_new_token_deactivate(PluginState* plugin_state) {
free(scene_state->token_secret_input_context->header_text); free(scene_state->token_secret_input_context->header_text);
free(scene_state->token_secret_input_context); free(scene_state->token_secret_input_context);
furi_string_free(scene_state->duration_text);
if(scene_state->input_state != NULL) { if(scene_state->input_state != NULL) {
totp_input_text_free(scene_state->input_state); totp_input_text_free(scene_state->input_state);
} }

View File

@@ -16,13 +16,11 @@
#include "../token_menu/totp_scene_token_menu.h" #include "../token_menu/totp_scene_token_menu.h"
#include "../../../workers/type_code/type_code.h" #include "../../../workers/type_code/type_code.h"
#define TOKEN_LIFETIME 30
typedef struct { typedef struct {
uint16_t current_token_index; uint16_t current_token_index;
char last_code[TOTP_TOKEN_DIGITS_MAX_COUNT + 1]; char last_code[TOTP_TOKEN_DIGITS_MAX_COUNT + 1];
char* last_code_name;
bool need_token_update; bool need_token_update;
TokenInfo* current_token;
uint32_t last_token_gen_time; uint32_t last_token_gen_time;
TotpTypeCodeWorkerContext* type_code_worker_context; TotpTypeCodeWorkerContext* type_code_worker_context;
NotificationMessage const** notification_sequence_new_token; NotificationMessage const** notification_sequence_new_token;
@@ -151,7 +149,7 @@ static void update_totp_params(PluginState* const plugin_state) {
list_element_at(plugin_state->tokens_list, scene_state->current_token_index)->data; list_element_at(plugin_state->tokens_list, scene_state->current_token_index)->data;
scene_state->need_token_update = true; scene_state->need_token_update = true;
scene_state->last_code_name = tokenInfo->name; scene_state->current_token = tokenInfo;
} }
} }
@@ -229,7 +227,7 @@ void totp_scene_generate_token_render(Canvas* const canvas, PluginState* plugin_
furi_hal_rtc_get_datetime(&curr_dt); furi_hal_rtc_get_datetime(&curr_dt);
uint32_t curr_ts = furi_hal_rtc_datetime_to_timestamp(&curr_dt); uint32_t curr_ts = furi_hal_rtc_datetime_to_timestamp(&curr_dt);
bool is_new_token_time = curr_ts % TOKEN_LIFETIME == 0; bool is_new_token_time = curr_ts % scene_state->current_token->duration == 0;
if(is_new_token_time && scene_state->last_token_gen_time != curr_ts) { if(is_new_token_time && scene_state->last_token_gen_time != curr_ts) {
scene_state->need_token_update = true; scene_state->need_token_update = true;
} }
@@ -238,10 +236,7 @@ void totp_scene_generate_token_render(Canvas* const canvas, PluginState* plugin_
scene_state->need_token_update = false; scene_state->need_token_update = false;
scene_state->last_token_gen_time = curr_ts; scene_state->last_token_gen_time = curr_ts;
const TokenInfo* tokenInfo = const TokenInfo* tokenInfo = scene_state->current_token;
(TokenInfo*)(list_element_at(
plugin_state->tokens_list, scene_state->current_token_index)
->data);
if(tokenInfo->token != NULL && tokenInfo->token_length > 0) { if(tokenInfo->token != NULL && tokenInfo->token_length > 0) {
furi_mutex_acquire( furi_mutex_acquire(
@@ -258,7 +253,7 @@ void totp_scene_generate_token_render(Canvas* const canvas, PluginState* plugin_
key_length, key_length,
curr_ts, curr_ts,
plugin_state->timezone_offset, plugin_state->timezone_offset,
TOKEN_LIFETIME), tokenInfo->duration),
scene_state->last_code, scene_state->last_code,
tokenInfo->digits); tokenInfo->digits);
memset_s(key, key_length, 0, key_length); memset_s(key, key_length, 0, key_length);
@@ -279,7 +274,7 @@ void totp_scene_generate_token_render(Canvas* const canvas, PluginState* plugin_
} }
canvas_set_font(canvas, FontPrimary); canvas_set_font(canvas, FontPrimary);
uint16_t token_name_width = canvas_string_width(canvas, scene_state->last_code_name); uint16_t token_name_width = canvas_string_width(canvas, scene_state->current_token->name);
if(SCREEN_WIDTH - token_name_width > 18) { if(SCREEN_WIDTH - token_name_width > 18) {
canvas_draw_str_aligned( canvas_draw_str_aligned(
canvas, canvas,
@@ -287,7 +282,7 @@ void totp_scene_generate_token_render(Canvas* const canvas, PluginState* plugin_
SCREEN_HEIGHT_CENTER - 20, SCREEN_HEIGHT_CENTER - 20,
AlignCenter, AlignCenter,
AlignCenter, AlignCenter,
scene_state->last_code_name); scene_state->current_token->name);
} else { } else {
canvas_draw_str_aligned( canvas_draw_str_aligned(
canvas, canvas,
@@ -295,7 +290,7 @@ void totp_scene_generate_token_render(Canvas* const canvas, PluginState* plugin_
SCREEN_HEIGHT_CENTER - 20, SCREEN_HEIGHT_CENTER - 20,
AlignLeft, AlignLeft,
AlignCenter, AlignCenter,
scene_state->last_code_name); scene_state->current_token->name);
canvas_set_color(canvas, ColorWhite); canvas_set_color(canvas, ColorWhite);
canvas_draw_box(canvas, 0, SCREEN_HEIGHT_CENTER - 24, 9, 9); canvas_draw_box(canvas, 0, SCREEN_HEIGHT_CENTER - 24, 9, 9);
canvas_draw_box(canvas, SCREEN_WIDTH - 10, SCREEN_HEIGHT_CENTER - 24, 9, 9); canvas_draw_box(canvas, SCREEN_WIDTH - 10, SCREEN_HEIGHT_CENTER - 24, 9, 9);
@@ -313,6 +308,7 @@ void totp_scene_generate_token_render(Canvas* const canvas, PluginState* plugin_
const uint8_t BAR_MARGIN = 3; const uint8_t BAR_MARGIN = 3;
const uint8_t BAR_HEIGHT = 4; const uint8_t BAR_HEIGHT = 4;
const uint8_t TOKEN_LIFETIME = scene_state->current_token->duration;
float percentDone = (float)(TOKEN_LIFETIME - curr_ts % TOKEN_LIFETIME) / (float)TOKEN_LIFETIME; float percentDone = (float)(TOKEN_LIFETIME - curr_ts % TOKEN_LIFETIME) / (float)TOKEN_LIFETIME;
uint8_t barWidth = (uint8_t)((float)(SCREEN_WIDTH - (BAR_MARGIN << 1)) * percentDone); uint8_t barWidth = (uint8_t)((float)(SCREEN_WIDTH - (BAR_MARGIN << 1)) * percentDone);
uint8_t barX = ((SCREEN_WIDTH - (BAR_MARGIN << 1) - barWidth) >> 1) + BAR_MARGIN; uint8_t barX = ((SCREEN_WIDTH - (BAR_MARGIN << 1) - barWidth) >> 1) + BAR_MARGIN;

Some files were not shown because too many files have changed in this diff Show More