mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-05-13 16:28:36 -07:00
Merge branch 'dev' of https://github.com/DarkFlippers/unleashed-firmware into xfw-dev
This commit is contained in:
@@ -60,7 +60,9 @@ static int32_t avr_isp_worker_rw_thread(void* context) {
|
|||||||
AvrIspWorkerRW* instance = context;
|
AvrIspWorkerRW* instance = context;
|
||||||
|
|
||||||
/* start PWM on &gpio_ext_pa4 */
|
/* start PWM on &gpio_ext_pa4 */
|
||||||
furi_hal_pwm_start(FuriHalPwmOutputIdLptim2PA4, 4000000, 50);
|
if(!furi_hal_pwm_is_running(FuriHalPwmOutputIdLptim2PA4)) {
|
||||||
|
furi_hal_pwm_start(FuriHalPwmOutputIdLptim2PA4, 4000000, 50);
|
||||||
|
}
|
||||||
|
|
||||||
FURI_LOG_D(TAG, "Start");
|
FURI_LOG_D(TAG, "Start");
|
||||||
|
|
||||||
@@ -122,7 +124,9 @@ static int32_t avr_isp_worker_rw_thread(void* context) {
|
|||||||
}
|
}
|
||||||
FURI_LOG_D(TAG, "Stop");
|
FURI_LOG_D(TAG, "Stop");
|
||||||
|
|
||||||
furi_hal_pwm_stop(FuriHalPwmOutputIdLptim2PA4);
|
if(furi_hal_pwm_is_running(FuriHalPwmOutputIdLptim2PA4)) {
|
||||||
|
furi_hal_pwm_stop(FuriHalPwmOutputIdLptim2PA4);
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -136,7 +140,12 @@ bool avr_isp_worker_rw_detect_chip(AvrIspWorkerRW* instance) {
|
|||||||
instance->chip_arr_ind = avr_isp_chip_arr_size + 1;
|
instance->chip_arr_ind = avr_isp_chip_arr_size + 1;
|
||||||
|
|
||||||
/* start PWM on &gpio_ext_pa4 */
|
/* start PWM on &gpio_ext_pa4 */
|
||||||
furi_hal_pwm_start(FuriHalPwmOutputIdLptim2PA4, 4000000, 50);
|
bool was_pwm_enabled = false;
|
||||||
|
if(!furi_hal_pwm_is_running(FuriHalPwmOutputIdLptim2PA4)) {
|
||||||
|
furi_hal_pwm_start(FuriHalPwmOutputIdLptim2PA4, 4000000, 50);
|
||||||
|
} else {
|
||||||
|
was_pwm_enabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
do {
|
do {
|
||||||
if(!avr_isp_auto_set_spi_speed_start_pmode(instance->avr_isp)) {
|
if(!avr_isp_auto_set_spi_speed_start_pmode(instance->avr_isp)) {
|
||||||
@@ -200,7 +209,9 @@ bool avr_isp_worker_rw_detect_chip(AvrIspWorkerRW* instance) {
|
|||||||
|
|
||||||
} while(0);
|
} while(0);
|
||||||
|
|
||||||
furi_hal_pwm_stop(FuriHalPwmOutputIdLptim2PA4);
|
if(furi_hal_pwm_is_running(FuriHalPwmOutputIdLptim2PA4) && !was_pwm_enabled) {
|
||||||
|
furi_hal_pwm_stop(FuriHalPwmOutputIdLptim2PA4);
|
||||||
|
}
|
||||||
|
|
||||||
if(instance->callback) {
|
if(instance->callback) {
|
||||||
if(instance->chip_arr_ind > avr_isp_chip_arr_size) {
|
if(instance->chip_arr_ind > avr_isp_chip_arr_size) {
|
||||||
|
|||||||
BIN
applications/external/hid_app/assets/Space_60x18.png
vendored
Normal file
BIN
applications/external/hid_app/assets/Space_60x18.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.8 KiB |
18
applications/external/hid_app/hid.c
vendored
18
applications/external/hid_app/hid.c
vendored
@@ -22,10 +22,12 @@ static void hid_submenu_callback(void* context, uint32_t index) {
|
|||||||
Hid* app = context;
|
Hid* app = context;
|
||||||
if(index == HidSubmenuIndexKeynote) {
|
if(index == HidSubmenuIndexKeynote) {
|
||||||
app->view_id = HidViewKeynote;
|
app->view_id = HidViewKeynote;
|
||||||
|
hid_keynote_set_orientation(app->hid_keynote, false);
|
||||||
view_dispatcher_switch_to_view(app->view_dispatcher, HidViewKeynote);
|
view_dispatcher_switch_to_view(app->view_dispatcher, HidViewKeynote);
|
||||||
} else if(index == HidSubmenuIndexKeynoteVertical) {
|
} else if(index == HidSubmenuIndexKeynoteVertical) {
|
||||||
app->view_id = HidViewKeynoteVertical;
|
app->view_id = HidViewKeynote;
|
||||||
view_dispatcher_switch_to_view(app->view_dispatcher, HidViewKeynoteVertical);
|
hid_keynote_set_orientation(app->hid_keynote, true);
|
||||||
|
view_dispatcher_switch_to_view(app->view_dispatcher, HidViewKeynote);
|
||||||
} else if(index == HidSubmenuIndexKeyboard) {
|
} else if(index == HidSubmenuIndexKeyboard) {
|
||||||
app->view_id = HidViewKeyboard;
|
app->view_id = HidViewKeyboard;
|
||||||
view_dispatcher_switch_to_view(app->view_dispatcher, HidViewKeyboard);
|
view_dispatcher_switch_to_view(app->view_dispatcher, HidViewKeyboard);
|
||||||
@@ -62,7 +64,6 @@ static void bt_hid_connection_status_changed_callback(BtStatus status, void* con
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
hid_keynote_set_connected_status(hid->hid_keynote, connected);
|
hid_keynote_set_connected_status(hid->hid_keynote, connected);
|
||||||
hid_keynote_vertical_set_connected_status(hid->hid_keynote_vertical, connected);
|
|
||||||
hid_keyboard_set_connected_status(hid->hid_keyboard, connected);
|
hid_keyboard_set_connected_status(hid->hid_keyboard, connected);
|
||||||
hid_numpad_set_connected_status(hid->hid_numpad, connected);
|
hid_numpad_set_connected_status(hid->hid_numpad, connected);
|
||||||
hid_media_set_connected_status(hid->hid_media, connected);
|
hid_media_set_connected_status(hid->hid_media, connected);
|
||||||
@@ -177,15 +178,6 @@ Hid* hid_app_alloc_view(void* context) {
|
|||||||
view_dispatcher_add_view(
|
view_dispatcher_add_view(
|
||||||
app->view_dispatcher, HidViewKeynote, hid_keynote_get_view(app->hid_keynote));
|
app->view_dispatcher, HidViewKeynote, hid_keynote_get_view(app->hid_keynote));
|
||||||
|
|
||||||
// Keynote Vertical view
|
|
||||||
app->hid_keynote_vertical = hid_keynote_vertical_alloc(app);
|
|
||||||
view_set_previous_callback(
|
|
||||||
hid_keynote_vertical_get_view(app->hid_keynote_vertical), hid_exit_confirm_view);
|
|
||||||
view_dispatcher_add_view(
|
|
||||||
app->view_dispatcher,
|
|
||||||
HidViewKeynoteVertical,
|
|
||||||
hid_keynote_vertical_get_view(app->hid_keynote_vertical));
|
|
||||||
|
|
||||||
// Keyboard view
|
// Keyboard view
|
||||||
app->hid_keyboard = hid_keyboard_alloc(app);
|
app->hid_keyboard = hid_keyboard_alloc(app);
|
||||||
view_set_previous_callback(hid_keyboard_get_view(app->hid_keyboard), hid_exit_confirm_view);
|
view_set_previous_callback(hid_keyboard_get_view(app->hid_keyboard), hid_exit_confirm_view);
|
||||||
@@ -252,8 +244,6 @@ void hid_free(Hid* app) {
|
|||||||
dialog_ex_free(app->dialog);
|
dialog_ex_free(app->dialog);
|
||||||
view_dispatcher_remove_view(app->view_dispatcher, HidViewKeynote);
|
view_dispatcher_remove_view(app->view_dispatcher, HidViewKeynote);
|
||||||
hid_keynote_free(app->hid_keynote);
|
hid_keynote_free(app->hid_keynote);
|
||||||
view_dispatcher_remove_view(app->view_dispatcher, HidViewKeynoteVertical);
|
|
||||||
hid_keynote_vertical_free(app->hid_keynote_vertical);
|
|
||||||
view_dispatcher_remove_view(app->view_dispatcher, HidViewKeyboard);
|
view_dispatcher_remove_view(app->view_dispatcher, HidViewKeyboard);
|
||||||
hid_keyboard_free(app->hid_keyboard);
|
hid_keyboard_free(app->hid_keyboard);
|
||||||
view_dispatcher_remove_view(app->view_dispatcher, HidViewNumpad);
|
view_dispatcher_remove_view(app->view_dispatcher, HidViewNumpad);
|
||||||
|
|||||||
2
applications/external/hid_app/hid.h
vendored
2
applications/external/hid_app/hid.h
vendored
@@ -17,7 +17,6 @@
|
|||||||
#include <gui/modules/dialog_ex.h>
|
#include <gui/modules/dialog_ex.h>
|
||||||
#include <gui/modules/popup.h>
|
#include <gui/modules/popup.h>
|
||||||
#include "views/hid_keynote.h"
|
#include "views/hid_keynote.h"
|
||||||
#include "views/hid_keynote_vertical.h"
|
|
||||||
#include "views/hid_keyboard.h"
|
#include "views/hid_keyboard.h"
|
||||||
#include "views/hid_numpad.h"
|
#include "views/hid_numpad.h"
|
||||||
#include "views/hid_media.h"
|
#include "views/hid_media.h"
|
||||||
@@ -43,7 +42,6 @@ struct Hid {
|
|||||||
Submenu* device_type_submenu;
|
Submenu* device_type_submenu;
|
||||||
DialogEx* dialog;
|
DialogEx* dialog;
|
||||||
HidKeynote* hid_keynote;
|
HidKeynote* hid_keynote;
|
||||||
HidKeynoteVertical* hid_keynote_vertical;
|
|
||||||
HidKeyboard* hid_keyboard;
|
HidKeyboard* hid_keyboard;
|
||||||
HidNumpad* hid_numpad;
|
HidNumpad* hid_numpad;
|
||||||
HidMedia* hid_media;
|
HidMedia* hid_media;
|
||||||
|
|||||||
1
applications/external/hid_app/views.h
vendored
1
applications/external/hid_app/views.h
vendored
@@ -1,7 +1,6 @@
|
|||||||
typedef enum {
|
typedef enum {
|
||||||
HidViewSubmenu,
|
HidViewSubmenu,
|
||||||
HidViewKeynote,
|
HidViewKeynote,
|
||||||
HidViewKeynoteVertical,
|
|
||||||
HidViewKeyboard,
|
HidViewKeyboard,
|
||||||
HidViewNumpad,
|
HidViewNumpad,
|
||||||
HidViewMedia,
|
HidViewMedia,
|
||||||
|
|||||||
@@ -111,6 +111,91 @@ static void hid_keynote_draw_callback(Canvas* canvas, void* context) {
|
|||||||
elements_multiline_text_aligned(canvas, 91, 57, AlignLeft, AlignBottom, "Back");
|
elements_multiline_text_aligned(canvas, 91, 57, AlignLeft, AlignBottom, "Back");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void hid_keynote_draw_vertical_callback(Canvas* canvas, void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
HidKeynoteModel* model = context;
|
||||||
|
|
||||||
|
// Header
|
||||||
|
canvas_set_font(canvas, FontPrimary);
|
||||||
|
if(model->transport == HidTransportBle) {
|
||||||
|
if(model->connected) {
|
||||||
|
canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15);
|
||||||
|
} else {
|
||||||
|
canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15);
|
||||||
|
}
|
||||||
|
|
||||||
|
elements_multiline_text_aligned(canvas, 20, 3, AlignLeft, AlignTop, "Keynote");
|
||||||
|
} else {
|
||||||
|
elements_multiline_text_aligned(canvas, 12, 3, AlignLeft, AlignTop, "Keynote");
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas_draw_icon(canvas, 2, 18, &I_Pin_back_arrow_10x8);
|
||||||
|
canvas_set_font(canvas, FontSecondary);
|
||||||
|
elements_multiline_text_aligned(canvas, 15, 19, AlignLeft, AlignTop, "Hold to exit");
|
||||||
|
|
||||||
|
const uint8_t x_2 = 23;
|
||||||
|
const uint8_t x_1 = 2;
|
||||||
|
const uint8_t x_3 = 44;
|
||||||
|
|
||||||
|
const uint8_t y_1 = 44;
|
||||||
|
const uint8_t y_2 = 65;
|
||||||
|
|
||||||
|
// Up
|
||||||
|
canvas_draw_icon(canvas, x_2, y_1, &I_Button_18x18);
|
||||||
|
if(model->up_pressed) {
|
||||||
|
elements_slightly_rounded_box(canvas, x_2 + 3, y_1 + 2, 13, 13);
|
||||||
|
canvas_set_color(canvas, ColorWhite);
|
||||||
|
}
|
||||||
|
hid_keynote_draw_arrow(canvas, x_2 + 9, y_1 + 6, CanvasDirectionBottomToTop);
|
||||||
|
canvas_set_color(canvas, ColorBlack);
|
||||||
|
|
||||||
|
// Down
|
||||||
|
canvas_draw_icon(canvas, x_2, y_2, &I_Button_18x18);
|
||||||
|
if(model->down_pressed) {
|
||||||
|
elements_slightly_rounded_box(canvas, x_2 + 3, y_2 + 2, 13, 13);
|
||||||
|
canvas_set_color(canvas, ColorWhite);
|
||||||
|
}
|
||||||
|
hid_keynote_draw_arrow(canvas, x_2 + 9, y_2 + 10, CanvasDirectionTopToBottom);
|
||||||
|
canvas_set_color(canvas, ColorBlack);
|
||||||
|
|
||||||
|
// Left
|
||||||
|
canvas_draw_icon(canvas, x_1, y_2, &I_Button_18x18);
|
||||||
|
if(model->left_pressed) {
|
||||||
|
elements_slightly_rounded_box(canvas, x_1 + 3, y_2 + 2, 13, 13);
|
||||||
|
canvas_set_color(canvas, ColorWhite);
|
||||||
|
}
|
||||||
|
hid_keynote_draw_arrow(canvas, x_1 + 7, y_2 + 8, CanvasDirectionRightToLeft);
|
||||||
|
canvas_set_color(canvas, ColorBlack);
|
||||||
|
|
||||||
|
// Right
|
||||||
|
canvas_draw_icon(canvas, x_3, y_2, &I_Button_18x18);
|
||||||
|
if(model->right_pressed) {
|
||||||
|
elements_slightly_rounded_box(canvas, x_3 + 3, y_2 + 2, 13, 13);
|
||||||
|
canvas_set_color(canvas, ColorWhite);
|
||||||
|
}
|
||||||
|
hid_keynote_draw_arrow(canvas, x_3 + 11, y_2 + 8, CanvasDirectionLeftToRight);
|
||||||
|
canvas_set_color(canvas, ColorBlack);
|
||||||
|
|
||||||
|
// Ok
|
||||||
|
canvas_draw_icon(canvas, 2, 86, &I_Space_60x18);
|
||||||
|
if(model->ok_pressed) {
|
||||||
|
elements_slightly_rounded_box(canvas, 5, 88, 55, 13);
|
||||||
|
canvas_set_color(canvas, ColorWhite);
|
||||||
|
}
|
||||||
|
canvas_draw_icon(canvas, 11, 90, &I_Ok_btn_9x9);
|
||||||
|
elements_multiline_text_aligned(canvas, 26, 98, AlignLeft, AlignBottom, "Space");
|
||||||
|
canvas_set_color(canvas, ColorBlack);
|
||||||
|
|
||||||
|
// Back
|
||||||
|
canvas_draw_icon(canvas, 2, 107, &I_Space_60x18);
|
||||||
|
if(model->back_pressed) {
|
||||||
|
elements_slightly_rounded_box(canvas, 5, 109, 55, 13);
|
||||||
|
canvas_set_color(canvas, ColorWhite);
|
||||||
|
}
|
||||||
|
canvas_draw_icon(canvas, 11, 111, &I_Pin_back_arrow_10x8);
|
||||||
|
elements_multiline_text_aligned(canvas, 26, 119, AlignLeft, AlignBottom, "Back");
|
||||||
|
}
|
||||||
|
|
||||||
static void hid_keynote_process(HidKeynote* hid_keynote, InputEvent* event) {
|
static void hid_keynote_process(HidKeynote* hid_keynote, InputEvent* event) {
|
||||||
with_view_model(
|
with_view_model(
|
||||||
hid_keynote->view,
|
hid_keynote->view,
|
||||||
@@ -212,3 +297,16 @@ void hid_keynote_set_connected_status(HidKeynote* hid_keynote, bool connected) {
|
|||||||
with_view_model(
|
with_view_model(
|
||||||
hid_keynote->view, HidKeynoteModel * model, { model->connected = connected; }, true);
|
hid_keynote->view, HidKeynoteModel * model, { model->connected = connected; }, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void hid_keynote_set_orientation(HidKeynote* hid_keynote, bool vertical) {
|
||||||
|
furi_assert(hid_keynote);
|
||||||
|
|
||||||
|
if(vertical) {
|
||||||
|
view_set_draw_callback(hid_keynote->view, hid_keynote_draw_vertical_callback);
|
||||||
|
view_set_orientation(hid_keynote->view, ViewOrientationVerticalFlip);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
view_set_draw_callback(hid_keynote->view, hid_keynote_draw_callback);
|
||||||
|
view_set_orientation(hid_keynote->view, ViewOrientationHorizontal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,3 +12,5 @@ void hid_keynote_free(HidKeynote* hid_keynote);
|
|||||||
View* hid_keynote_get_view(HidKeynote* hid_keynote);
|
View* hid_keynote_get_view(HidKeynote* hid_keynote);
|
||||||
|
|
||||||
void hid_keynote_set_connected_status(HidKeynote* hid_keynote, bool connected);
|
void hid_keynote_set_connected_status(HidKeynote* hid_keynote, bool connected);
|
||||||
|
|
||||||
|
void hid_keynote_set_orientation(HidKeynote* hid_keynote, bool vertical);
|
||||||
@@ -1,228 +0,0 @@
|
|||||||
#include "hid_keynote_vertical.h"
|
|
||||||
#include <gui/elements.h>
|
|
||||||
#include "../hid.h"
|
|
||||||
|
|
||||||
#include "hid_icons.h"
|
|
||||||
|
|
||||||
#define TAG "HidKeynoteVertical"
|
|
||||||
|
|
||||||
struct HidKeynoteVertical {
|
|
||||||
View* view;
|
|
||||||
Hid* hid;
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
bool left_pressed;
|
|
||||||
bool up_pressed;
|
|
||||||
bool right_pressed;
|
|
||||||
bool down_pressed;
|
|
||||||
bool ok_pressed;
|
|
||||||
bool back_pressed;
|
|
||||||
bool connected;
|
|
||||||
HidTransport transport;
|
|
||||||
} HidKeynoteVerticalModel;
|
|
||||||
|
|
||||||
static void
|
|
||||||
hid_keynote_vertical_draw_arrow(Canvas* canvas, uint8_t x, uint8_t y, CanvasDirection dir) {
|
|
||||||
canvas_draw_triangle(canvas, x, y, 5, 3, dir);
|
|
||||||
if(dir == CanvasDirectionBottomToTop) {
|
|
||||||
canvas_draw_line(canvas, x, y + 6, x, y - 1);
|
|
||||||
} else if(dir == CanvasDirectionTopToBottom) {
|
|
||||||
canvas_draw_line(canvas, x, y - 6, x, y + 1);
|
|
||||||
} else if(dir == CanvasDirectionRightToLeft) {
|
|
||||||
canvas_draw_line(canvas, x + 6, y, x - 1, y);
|
|
||||||
} else if(dir == CanvasDirectionLeftToRight) {
|
|
||||||
canvas_draw_line(canvas, x - 6, y, x + 1, y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void hid_keynote_vertical_draw_callback(Canvas* canvas, void* context) {
|
|
||||||
furi_assert(context);
|
|
||||||
HidKeynoteVerticalModel* model = context;
|
|
||||||
|
|
||||||
// Header
|
|
||||||
if(model->transport == HidTransportBle) {
|
|
||||||
if(model->connected) {
|
|
||||||
canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15);
|
|
||||||
} else {
|
|
||||||
canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
canvas_set_font(canvas, FontPrimary);
|
|
||||||
elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Keynote");
|
|
||||||
canvas_set_font(canvas, FontSecondary);
|
|
||||||
elements_multiline_text_aligned(
|
|
||||||
canvas, 24, 14, AlignLeft, AlignTop, "Vertical Up --->");
|
|
||||||
|
|
||||||
canvas_draw_icon(canvas, 68, 2, &I_Pin_back_arrow_10x8);
|
|
||||||
canvas_set_font(canvas, FontSecondary);
|
|
||||||
elements_multiline_text_aligned(canvas, 127, 3, AlignRight, AlignTop, "Hold to exit");
|
|
||||||
|
|
||||||
// Up
|
|
||||||
canvas_draw_icon(canvas, 21, 24, &I_Button_18x18);
|
|
||||||
if(model->up_pressed) {
|
|
||||||
elements_slightly_rounded_box(canvas, 24, 26, 13, 13);
|
|
||||||
canvas_set_color(canvas, ColorWhite);
|
|
||||||
}
|
|
||||||
hid_keynote_vertical_draw_arrow(canvas, 30, 30, CanvasDirectionBottomToTop);
|
|
||||||
canvas_set_color(canvas, ColorBlack);
|
|
||||||
|
|
||||||
// Down
|
|
||||||
canvas_draw_icon(canvas, 21, 45, &I_Button_18x18);
|
|
||||||
if(model->down_pressed) {
|
|
||||||
elements_slightly_rounded_box(canvas, 24, 47, 13, 13);
|
|
||||||
canvas_set_color(canvas, ColorWhite);
|
|
||||||
}
|
|
||||||
hid_keynote_vertical_draw_arrow(canvas, 30, 55, CanvasDirectionTopToBottom);
|
|
||||||
canvas_set_color(canvas, ColorBlack);
|
|
||||||
|
|
||||||
// Left
|
|
||||||
canvas_draw_icon(canvas, 0, 35, &I_Button_18x18);
|
|
||||||
if(model->left_pressed) {
|
|
||||||
elements_slightly_rounded_box(canvas, 3, 37, 13, 13);
|
|
||||||
canvas_set_color(canvas, ColorWhite);
|
|
||||||
}
|
|
||||||
hid_keynote_vertical_draw_arrow(canvas, 7, 43, CanvasDirectionRightToLeft);
|
|
||||||
canvas_set_color(canvas, ColorBlack);
|
|
||||||
|
|
||||||
// Right
|
|
||||||
canvas_draw_icon(canvas, 42, 35, &I_Button_18x18);
|
|
||||||
if(model->right_pressed) {
|
|
||||||
elements_slightly_rounded_box(canvas, 45, 37, 13, 13);
|
|
||||||
canvas_set_color(canvas, ColorWhite);
|
|
||||||
}
|
|
||||||
hid_keynote_vertical_draw_arrow(canvas, 53, 43, CanvasDirectionLeftToRight);
|
|
||||||
canvas_set_color(canvas, ColorBlack);
|
|
||||||
|
|
||||||
// Ok
|
|
||||||
canvas_draw_icon(canvas, 63, 25, &I_Space_65x18);
|
|
||||||
if(model->ok_pressed) {
|
|
||||||
elements_slightly_rounded_box(canvas, 66, 27, 60, 13);
|
|
||||||
canvas_set_color(canvas, ColorWhite);
|
|
||||||
}
|
|
||||||
canvas_draw_icon(canvas, 74, 29, &I_Ok_btn_9x9);
|
|
||||||
elements_multiline_text_aligned(canvas, 91, 36, AlignLeft, AlignBottom, "Space");
|
|
||||||
canvas_set_color(canvas, ColorBlack);
|
|
||||||
|
|
||||||
// Back
|
|
||||||
canvas_draw_icon(canvas, 63, 45, &I_Space_65x18);
|
|
||||||
if(model->back_pressed) {
|
|
||||||
elements_slightly_rounded_box(canvas, 66, 47, 60, 13);
|
|
||||||
canvas_set_color(canvas, ColorWhite);
|
|
||||||
}
|
|
||||||
canvas_draw_icon(canvas, 74, 49, &I_Pin_back_arrow_10x8);
|
|
||||||
elements_multiline_text_aligned(canvas, 91, 57, AlignLeft, AlignBottom, "Back");
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
hid_keynote_vertical_process(HidKeynoteVertical* hid_keynote_vertical, InputEvent* event) {
|
|
||||||
with_view_model(
|
|
||||||
hid_keynote_vertical->view,
|
|
||||||
HidKeynoteVerticalModel * model,
|
|
||||||
{
|
|
||||||
if(event->type == InputTypePress) {
|
|
||||||
if(event->key == InputKeyUp) {
|
|
||||||
model->up_pressed = true;
|
|
||||||
hid_hal_keyboard_press(hid_keynote_vertical->hid, HID_KEYBOARD_LEFT_ARROW);
|
|
||||||
} else if(event->key == InputKeyDown) {
|
|
||||||
model->down_pressed = true;
|
|
||||||
hid_hal_keyboard_press(hid_keynote_vertical->hid, HID_KEYBOARD_RIGHT_ARROW);
|
|
||||||
} else if(event->key == InputKeyLeft) {
|
|
||||||
model->left_pressed = true;
|
|
||||||
hid_hal_keyboard_press(hid_keynote_vertical->hid, HID_KEYBOARD_DOWN_ARROW);
|
|
||||||
} else if(event->key == InputKeyRight) {
|
|
||||||
model->right_pressed = true;
|
|
||||||
hid_hal_keyboard_press(hid_keynote_vertical->hid, HID_KEYBOARD_UP_ARROW);
|
|
||||||
} else if(event->key == InputKeyOk) {
|
|
||||||
model->ok_pressed = true;
|
|
||||||
hid_hal_keyboard_press(hid_keynote_vertical->hid, HID_KEYBOARD_SPACEBAR);
|
|
||||||
} else if(event->key == InputKeyBack) {
|
|
||||||
model->back_pressed = true;
|
|
||||||
}
|
|
||||||
} else if(event->type == InputTypeRelease) {
|
|
||||||
if(event->key == InputKeyUp) {
|
|
||||||
model->up_pressed = false;
|
|
||||||
hid_hal_keyboard_release(hid_keynote_vertical->hid, HID_KEYBOARD_LEFT_ARROW);
|
|
||||||
} else if(event->key == InputKeyDown) {
|
|
||||||
model->down_pressed = false;
|
|
||||||
hid_hal_keyboard_release(hid_keynote_vertical->hid, HID_KEYBOARD_RIGHT_ARROW);
|
|
||||||
} else if(event->key == InputKeyLeft) {
|
|
||||||
model->left_pressed = false;
|
|
||||||
hid_hal_keyboard_release(hid_keynote_vertical->hid, HID_KEYBOARD_DOWN_ARROW);
|
|
||||||
} else if(event->key == InputKeyRight) {
|
|
||||||
model->right_pressed = false;
|
|
||||||
hid_hal_keyboard_release(hid_keynote_vertical->hid, HID_KEYBOARD_UP_ARROW);
|
|
||||||
} else if(event->key == InputKeyOk) {
|
|
||||||
model->ok_pressed = false;
|
|
||||||
hid_hal_keyboard_release(hid_keynote_vertical->hid, HID_KEYBOARD_SPACEBAR);
|
|
||||||
} else if(event->key == InputKeyBack) {
|
|
||||||
model->back_pressed = false;
|
|
||||||
}
|
|
||||||
} else if(event->type == InputTypeShort) {
|
|
||||||
if(event->key == InputKeyBack) {
|
|
||||||
hid_hal_keyboard_press(hid_keynote_vertical->hid, HID_KEYBOARD_DELETE);
|
|
||||||
hid_hal_keyboard_release(hid_keynote_vertical->hid, HID_KEYBOARD_DELETE);
|
|
||||||
hid_hal_consumer_key_press(hid_keynote_vertical->hid, HID_CONSUMER_AC_BACK);
|
|
||||||
hid_hal_consumer_key_release(hid_keynote_vertical->hid, HID_CONSUMER_AC_BACK);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
true);
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool hid_keynote_vertical_input_callback(InputEvent* event, void* context) {
|
|
||||||
furi_assert(context);
|
|
||||||
HidKeynoteVertical* hid_keynote_vertical = context;
|
|
||||||
bool consumed = false;
|
|
||||||
|
|
||||||
if(event->type == InputTypeLong && event->key == InputKeyBack) {
|
|
||||||
hid_hal_keyboard_release_all(hid_keynote_vertical->hid);
|
|
||||||
} else {
|
|
||||||
hid_keynote_vertical_process(hid_keynote_vertical, event);
|
|
||||||
consumed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return consumed;
|
|
||||||
}
|
|
||||||
|
|
||||||
HidKeynoteVertical* hid_keynote_vertical_alloc(Hid* hid) {
|
|
||||||
HidKeynoteVertical* hid_keynote_vertical = malloc(sizeof(HidKeynoteVertical));
|
|
||||||
hid_keynote_vertical->view = view_alloc();
|
|
||||||
hid_keynote_vertical->hid = hid;
|
|
||||||
view_set_context(hid_keynote_vertical->view, hid_keynote_vertical);
|
|
||||||
view_allocate_model(
|
|
||||||
hid_keynote_vertical->view, ViewModelTypeLocking, sizeof(HidKeynoteVerticalModel));
|
|
||||||
view_set_draw_callback(hid_keynote_vertical->view, hid_keynote_vertical_draw_callback);
|
|
||||||
view_set_input_callback(hid_keynote_vertical->view, hid_keynote_vertical_input_callback);
|
|
||||||
|
|
||||||
with_view_model(
|
|
||||||
hid_keynote_vertical->view,
|
|
||||||
HidKeynoteVerticalModel * model,
|
|
||||||
{ model->transport = hid->transport; },
|
|
||||||
true);
|
|
||||||
|
|
||||||
return hid_keynote_vertical;
|
|
||||||
}
|
|
||||||
|
|
||||||
void hid_keynote_vertical_free(HidKeynoteVertical* hid_keynote_vertical) {
|
|
||||||
furi_assert(hid_keynote_vertical);
|
|
||||||
view_free(hid_keynote_vertical->view);
|
|
||||||
free(hid_keynote_vertical);
|
|
||||||
}
|
|
||||||
|
|
||||||
View* hid_keynote_vertical_get_view(HidKeynoteVertical* hid_keynote_vertical) {
|
|
||||||
furi_assert(hid_keynote_vertical);
|
|
||||||
return hid_keynote_vertical->view;
|
|
||||||
}
|
|
||||||
|
|
||||||
void hid_keynote_vertical_set_connected_status(
|
|
||||||
HidKeynoteVertical* hid_keynote_vertical,
|
|
||||||
bool connected) {
|
|
||||||
furi_assert(hid_keynote_vertical);
|
|
||||||
with_view_model(
|
|
||||||
hid_keynote_vertical->view,
|
|
||||||
HidKeynoteVerticalModel * model,
|
|
||||||
{ model->connected = connected; },
|
|
||||||
true);
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <gui/view.h>
|
|
||||||
|
|
||||||
typedef struct Hid Hid;
|
|
||||||
typedef struct HidKeynoteVertical HidKeynoteVertical;
|
|
||||||
|
|
||||||
HidKeynoteVertical* hid_keynote_vertical_alloc(Hid* bt_hid);
|
|
||||||
|
|
||||||
void hid_keynote_vertical_free(HidKeynoteVertical* hid_keynote_vertical);
|
|
||||||
|
|
||||||
View* hid_keynote_vertical_get_view(HidKeynoteVertical* hid_keynote_vertical);
|
|
||||||
|
|
||||||
void hid_keynote_vertical_set_connected_status(
|
|
||||||
HidKeynoteVertical* hid_keynote_vertical,
|
|
||||||
bool connected);
|
|
||||||
56
applications/external/hid_app/views/hid_numpad.c
vendored
56
applications/external/hid_app/views/hid_numpad.c
vendored
@@ -39,26 +39,26 @@ typedef struct {
|
|||||||
int8_t y;
|
int8_t y;
|
||||||
} HidNumpadPoint;
|
} HidNumpadPoint;
|
||||||
|
|
||||||
#define MARGIN_TOP 0
|
#define MARGIN_TOP 32
|
||||||
#define MARGIN_LEFT 24
|
#define MARGIN_LEFT 1
|
||||||
#define KEY_WIDTH 20
|
#define KEY_WIDTH 20
|
||||||
#define KEY_HEIGHT 15
|
#define KEY_HEIGHT 15
|
||||||
#define KEY_PADDING 1
|
#define KEY_PADDING 1
|
||||||
#define ROW_COUNT 5
|
#define ROW_COUNT 6
|
||||||
#define COLUMN_COUNT 4
|
#define COLUMN_COUNT 3
|
||||||
|
|
||||||
const HidNumpadKey hid_numpad_keyset[ROW_COUNT][COLUMN_COUNT] = {
|
const HidNumpadKey hid_numpad_keyset[ROW_COUNT][COLUMN_COUNT] = {
|
||||||
{
|
{
|
||||||
{.width = 1, .height = 1, .icon = NULL, .key = "NL", .value = HID_KEYPAD_NUMLOCK},
|
{.width = 1, .height = 1, .icon = NULL, .key = "NL", .value = HID_KEYPAD_NUMLOCK},
|
||||||
{.width = 1, .height = 1, .icon = NULL, .key = "/", .value = HID_KEYPAD_SLASH},
|
{.width = 1, .height = 1, .icon = NULL, .key = "/", .value = HID_KEYPAD_SLASH},
|
||||||
{.width = 1, .height = 1, .icon = NULL, .key = "*", .value = HID_KEYPAD_ASTERISK},
|
{.width = 1, .height = 1, .icon = NULL, .key = "*", .value = HID_KEYPAD_ASTERISK},
|
||||||
{.width = 1, .height = 1, .icon = NULL, .key = "-", .value = HID_KEYPAD_MINUS},
|
// {.width = 1, .height = 1, .icon = NULL, .key = "-", .value = HID_KEYPAD_MINUS},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
{.width = 1, .height = 1, .icon = NULL, .key = "7", .value = HID_KEYPAD_7},
|
{.width = 1, .height = 1, .icon = NULL, .key = "7", .value = HID_KEYPAD_7},
|
||||||
{.width = 1, .height = 1, .icon = NULL, .key = "8", .value = HID_KEYBOARD_8},
|
{.width = 1, .height = 1, .icon = NULL, .key = "8", .value = HID_KEYBOARD_8},
|
||||||
{.width = 1, .height = 1, .icon = NULL, .key = "9", .value = HID_KEYBOARD_9},
|
{.width = 1, .height = 1, .icon = NULL, .key = "9", .value = HID_KEYBOARD_9},
|
||||||
{.width = 1, .height = 2, .icon = NULL, .key = "+", .value = HID_KEYPAD_PLUS},
|
// {.width = 1, .height = 2, .icon = NULL, .key = "+", .value = HID_KEYPAD_PLUS},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
{.width = 1, .height = 1, .icon = NULL, .key = "4", .value = HID_KEYPAD_4},
|
{.width = 1, .height = 1, .icon = NULL, .key = "4", .value = HID_KEYPAD_4},
|
||||||
@@ -69,13 +69,18 @@ const HidNumpadKey hid_numpad_keyset[ROW_COUNT][COLUMN_COUNT] = {
|
|||||||
{.width = 1, .height = 1, .icon = NULL, .key = "1", .value = HID_KEYPAD_1},
|
{.width = 1, .height = 1, .icon = NULL, .key = "1", .value = HID_KEYPAD_1},
|
||||||
{.width = 1, .height = 1, .icon = NULL, .key = "2", .value = HID_KEYPAD_2},
|
{.width = 1, .height = 1, .icon = NULL, .key = "2", .value = HID_KEYPAD_2},
|
||||||
{.width = 1, .height = 1, .icon = NULL, .key = "3", .value = HID_KEYPAD_3},
|
{.width = 1, .height = 1, .icon = NULL, .key = "3", .value = HID_KEYPAD_3},
|
||||||
{.width = 1, .height = 2, .icon = NULL, .key = "En", .value = HID_KEYPAD_ENTER},
|
// {.width = 1, .height = 2, .icon = NULL, .key = "En", .value = HID_KEYPAD_ENTER},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
{.width = 2, .height = 1, .icon = NULL, .key = "0", .value = HID_KEYBOARD_0},
|
{.width = 2, .height = 1, .icon = NULL, .key = "0", .value = HID_KEYBOARD_0},
|
||||||
{.width = 0, .height = 0, .icon = NULL, .key = "0", .value = HID_KEYBOARD_0},
|
{.width = 0, .height = 0, .icon = NULL, .key = "0", .value = HID_KEYBOARD_0},
|
||||||
{.width = 1, .height = 1, .icon = NULL, .key = ".", .value = HID_KEYPAD_DOT},
|
{.width = 1, .height = 1, .icon = NULL, .key = ".", .value = HID_KEYPAD_DOT},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
{.width = 1, .height = 1, .icon = NULL, .key = "En", .value = HID_KEYPAD_ENTER},
|
||||||
|
{.width = 1, .height = 1, .icon = NULL, .key = "-", .value = HID_KEYPAD_MINUS},
|
||||||
|
{.width = 1, .height = 1, .icon = NULL, .key = "+", .value = HID_KEYPAD_PLUS},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
static void hid_numpad_draw_key(
|
static void hid_numpad_draw_key(
|
||||||
@@ -128,26 +133,36 @@ static void hid_numpad_draw_callback(Canvas* canvas, void* context) {
|
|||||||
furi_assert(context);
|
furi_assert(context);
|
||||||
HidNumpadModel* model = context;
|
HidNumpadModel* model = context;
|
||||||
|
|
||||||
if((!model->connected) && (model->transport == HidTransportBle)) {
|
// Header
|
||||||
canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15);
|
canvas_set_font(canvas, FontPrimary);
|
||||||
canvas_set_font(canvas, FontPrimary);
|
if(model->transport == HidTransportBle) {
|
||||||
elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Numpad");
|
if(model->connected) {
|
||||||
|
canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15);
|
||||||
|
} else {
|
||||||
|
canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15);
|
||||||
|
elements_multiline_text_aligned(
|
||||||
|
canvas, 7, 60, AlignLeft, AlignBottom, "Waiting for\nConnection...");
|
||||||
|
}
|
||||||
|
elements_multiline_text_aligned(canvas, 20, 3, AlignLeft, AlignTop, "Numpad");
|
||||||
|
|
||||||
canvas_draw_icon(canvas, 68, 3, &I_Pin_back_arrow_10x8);
|
} else {
|
||||||
canvas_set_font(canvas, FontSecondary);
|
elements_multiline_text_aligned(canvas, 12, 3, AlignLeft, AlignTop, "Numpad");
|
||||||
elements_multiline_text_aligned(canvas, 127, 4, AlignRight, AlignTop, "Hold to exit");
|
}
|
||||||
|
|
||||||
elements_multiline_text_aligned(
|
canvas_draw_icon(canvas, 3, 18, &I_Pin_back_arrow_10x8);
|
||||||
canvas, 4, 60, AlignLeft, AlignBottom, "Waiting for Connection...");
|
canvas_set_font(canvas, FontSecondary);
|
||||||
|
elements_multiline_text_aligned(canvas, 15, 19, AlignLeft, AlignTop, "Hold to exit");
|
||||||
|
|
||||||
|
if(!model->connected && (model->transport == HidTransportBle)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
canvas_set_font(canvas, FontKeyboard);
|
canvas_set_font(canvas, FontKeyboard);
|
||||||
uint8_t initY = model->y == 0 ? 0 : 1;
|
uint8_t initY = 0; // = model->y == 0 ? 0 : 1;
|
||||||
|
|
||||||
if(model->y > 5) {
|
// if(model->y > ROW_COUNT) {
|
||||||
initY = model->y - 4;
|
// initY = model->y - (ROW_COUNT - 1);
|
||||||
}
|
// }
|
||||||
|
|
||||||
for(uint8_t y = initY; y < ROW_COUNT; y++) {
|
for(uint8_t y = initY; y < ROW_COUNT; y++) {
|
||||||
const HidNumpadKey* numpadKeyRow = hid_numpad_keyset[y];
|
const HidNumpadKey* numpadKeyRow = hid_numpad_keyset[y];
|
||||||
@@ -269,6 +284,7 @@ HidNumpad* hid_numpad_alloc(Hid* bt_hid) {
|
|||||||
hid_numpad->hid = bt_hid;
|
hid_numpad->hid = bt_hid;
|
||||||
view_set_context(hid_numpad->view, hid_numpad);
|
view_set_context(hid_numpad->view, hid_numpad);
|
||||||
view_allocate_model(hid_numpad->view, ViewModelTypeLocking, sizeof(HidNumpadModel));
|
view_allocate_model(hid_numpad->view, ViewModelTypeLocking, sizeof(HidNumpadModel));
|
||||||
|
view_set_orientation(hid_numpad->view, ViewOrientationVertical);
|
||||||
view_set_draw_callback(hid_numpad->view, hid_numpad_draw_callback);
|
view_set_draw_callback(hid_numpad->view, hid_numpad_draw_callback);
|
||||||
view_set_input_callback(hid_numpad->view, hid_numpad_input_callback);
|
view_set_input_callback(hid_numpad->view, hid_numpad_input_callback);
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,13 @@ void signal_gen_scene_pwm_on_enter(void* context) {
|
|||||||
signal_gen_pwm_set_callback(app->pwm_view, signal_gen_pwm_callback, app);
|
signal_gen_pwm_set_callback(app->pwm_view, signal_gen_pwm_callback, app);
|
||||||
|
|
||||||
signal_gen_pwm_set_params(app->pwm_view, 0, DEFAULT_FREQ, DEFAULT_DUTY);
|
signal_gen_pwm_set_params(app->pwm_view, 0, DEFAULT_FREQ, DEFAULT_DUTY);
|
||||||
furi_hal_pwm_start(pwm_ch_id[0], DEFAULT_FREQ, DEFAULT_DUTY);
|
|
||||||
|
if(!furi_hal_pwm_is_running(pwm_ch_id[0])) {
|
||||||
|
furi_hal_pwm_start(pwm_ch_id[0], DEFAULT_FREQ, DEFAULT_DUTY);
|
||||||
|
} else {
|
||||||
|
furi_hal_pwm_stop(pwm_ch_id[0]);
|
||||||
|
furi_hal_pwm_start(pwm_ch_id[0], DEFAULT_FREQ, DEFAULT_DUTY);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool signal_gen_scene_pwm_on_event(void* context, SceneManagerEvent event) {
|
bool signal_gen_scene_pwm_on_event(void* context, SceneManagerEvent event) {
|
||||||
@@ -46,8 +52,18 @@ bool signal_gen_scene_pwm_on_event(void* context, SceneManagerEvent event) {
|
|||||||
furi_hal_pwm_set_params(app->pwm_ch, app->pwm_freq, app->pwm_duty);
|
furi_hal_pwm_set_params(app->pwm_ch, app->pwm_freq, app->pwm_duty);
|
||||||
} else if(event.event == SignalGenPwmEventChannelChange) {
|
} else if(event.event == SignalGenPwmEventChannelChange) {
|
||||||
consumed = true;
|
consumed = true;
|
||||||
furi_hal_pwm_stop(app->pwm_ch_prev);
|
// Stop previous channel PWM
|
||||||
furi_hal_pwm_start(app->pwm_ch, app->pwm_freq, app->pwm_duty);
|
if(furi_hal_pwm_is_running(app->pwm_ch_prev)) {
|
||||||
|
furi_hal_pwm_stop(app->pwm_ch_prev);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start PWM and restart if it was starter already
|
||||||
|
if(furi_hal_pwm_is_running(app->pwm_ch)) {
|
||||||
|
furi_hal_pwm_stop(app->pwm_ch);
|
||||||
|
furi_hal_pwm_start(app->pwm_ch, app->pwm_freq, app->pwm_duty);
|
||||||
|
} else {
|
||||||
|
furi_hal_pwm_start(app->pwm_ch, app->pwm_freq, app->pwm_duty);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return consumed;
|
return consumed;
|
||||||
@@ -56,5 +72,8 @@ bool signal_gen_scene_pwm_on_event(void* context, SceneManagerEvent event) {
|
|||||||
void signal_gen_scene_pwm_on_exit(void* context) {
|
void signal_gen_scene_pwm_on_exit(void* context) {
|
||||||
SignalGenApp* app = context;
|
SignalGenApp* app = context;
|
||||||
variable_item_list_reset(app->var_item_list);
|
variable_item_list_reset(app->var_item_list);
|
||||||
furi_hal_pwm_stop(app->pwm_ch);
|
|
||||||
|
if(furi_hal_pwm_is_running(app->pwm_ch)) {
|
||||||
|
furi_hal_pwm_stop(app->pwm_ch);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,10 +52,10 @@ void totp_type_code_worker_execute_automation(
|
|||||||
while(i < code_buffer_size && (cb_char = code_buffer[i]) != 0) {
|
while(i < code_buffer_size && (cb_char = code_buffer[i]) != 0) {
|
||||||
uint8_t char_index = CONVERT_CHAR_TO_DIGIT(cb_char);
|
uint8_t char_index = CONVERT_CHAR_TO_DIGIT(cb_char);
|
||||||
if(char_index > 9) {
|
if(char_index > 9) {
|
||||||
char_index = cb_char - 0x41 + 10;
|
char_index = cb_char - 'A' + 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(char_index > 35) break;
|
if(char_index >= sizeof(hid_number_keys)) break;
|
||||||
|
|
||||||
uint16_t hid_kb_key = hid_number_keys[char_index];
|
uint16_t hid_kb_key = hid_number_keys[char_index];
|
||||||
if(char_index > 9) {
|
if(char_index > 9) {
|
||||||
|
|||||||
15
applications/external/unitemp/Sensors.c
vendored
15
applications/external/unitemp/Sensors.c
vendored
@@ -79,7 +79,7 @@ static const SensorType* sensorTypes[] = {&DHT11, &DHT12_SW, &DHT20, &DHT
|
|||||||
&Dallas, &AM2320_SW, &AM2320_I2C, &HTU21x, &AHT10,
|
&Dallas, &AM2320_SW, &AM2320_I2C, &HTU21x, &AHT10,
|
||||||
&SHT30, &GXHT30, &LM75, &HDC1080, &BMP180,
|
&SHT30, &GXHT30, &LM75, &HDC1080, &BMP180,
|
||||||
&BMP280, &BME280, &BME680, &MAX31855, &MAX6675,
|
&BMP280, &BME280, &BME680, &MAX31855, &MAX6675,
|
||||||
&SCD30};
|
&SCD30, &SCD40};
|
||||||
|
|
||||||
const SensorType* unitemp_sensors_getTypeFromInt(uint8_t index) {
|
const SensorType* unitemp_sensors_getTypeFromInt(uint8_t index) {
|
||||||
if(index > SENSOR_TYPES_COUNT) return NULL;
|
if(index > SENSOR_TYPES_COUNT) return NULL;
|
||||||
@@ -624,11 +624,16 @@ UnitempStatus unitemp_sensor_updateData(Sensor* sensor) {
|
|||||||
UNITEMP_DEBUG("Sensor %s update status %d", sensor->name, sensor->status);
|
UNITEMP_DEBUG("Sensor %s update status %d", sensor->name, sensor->status);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(app->settings.temp_unit == UT_TEMP_FAHRENHEIT && sensor->status == UT_SENSORSTATUS_OK) {
|
|
||||||
uintemp_celsiumToFarengate(sensor);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(sensor->status == UT_SENSORSTATUS_OK) {
|
if(sensor->status == UT_SENSORSTATUS_OK) {
|
||||||
|
if(app->settings.heat_index &&
|
||||||
|
((sensor->type->datatype & (UT_TEMPERATURE | UT_HUMIDITY)) ==
|
||||||
|
(UT_TEMPERATURE | UT_HUMIDITY))) {
|
||||||
|
unitemp_calculate_heat_index(sensor);
|
||||||
|
}
|
||||||
|
if(app->settings.temp_unit == UT_TEMP_FAHRENHEIT) {
|
||||||
|
uintemp_celsiumToFarengate(sensor);
|
||||||
|
}
|
||||||
|
|
||||||
sensor->temp += sensor->temp_offset / 10.f;
|
sensor->temp += sensor->temp_offset / 10.f;
|
||||||
if(app->settings.pressure_unit == UT_PRESSURE_MM_HG) {
|
if(app->settings.pressure_unit == UT_PRESSURE_MM_HG) {
|
||||||
unitemp_pascalToMmHg(sensor);
|
unitemp_pascalToMmHg(sensor);
|
||||||
|
|||||||
2
applications/external/unitemp/Sensors.h
vendored
2
applications/external/unitemp/Sensors.h
vendored
@@ -119,6 +119,7 @@ typedef struct Sensor {
|
|||||||
char* name;
|
char* name;
|
||||||
//Температура
|
//Температура
|
||||||
float temp;
|
float temp;
|
||||||
|
float heat_index;
|
||||||
//Относительная влажность
|
//Относительная влажность
|
||||||
float hum;
|
float hum;
|
||||||
//Атмосферное давление
|
//Атмосферное давление
|
||||||
@@ -334,4 +335,5 @@ const GPIO*
|
|||||||
#include "./sensors/MAX31855.h"
|
#include "./sensors/MAX31855.h"
|
||||||
#include "./sensors/MAX6675.h"
|
#include "./sensors/MAX6675.h"
|
||||||
#include "./sensors/SCD30.h"
|
#include "./sensors/SCD30.h"
|
||||||
|
#include "./sensors/SCD40.h"
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
BIN
applications/external/unitemp/assets/heat_index_11x14.png
vendored
Normal file
BIN
applications/external/unitemp/assets/heat_index_11x14.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
60
applications/external/unitemp/interfaces/endianness.h
vendored
Normal file
60
applications/external/unitemp/interfaces/endianness.h
vendored
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
//
|
||||||
|
// Created by Avilov Vasily on 10.06.2023.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef FLIPPERZERO_FIRMWARE_ENDIANNESS_H
|
||||||
|
#define FLIPPERZERO_FIRMWARE_ENDIANNESS_H
|
||||||
|
|
||||||
|
inline static void store16(uint8_t* b, uint16_t i) {
|
||||||
|
memcpy(b, &i, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline static void store32(uint8_t* b, uint32_t i) {
|
||||||
|
memcpy(b, &i, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline static uint16_t load16(uint8_t* b) {
|
||||||
|
uint16_t x;
|
||||||
|
memcpy(&x, b, 2);
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline static uint32_t load32(uint8_t* b) {
|
||||||
|
uint32_t x;
|
||||||
|
memcpy(&x, b, 4);
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if BYTE_ORDER == BIG_ENDIAN
|
||||||
|
#define htobe16(x) (x)
|
||||||
|
#define htobe32(x) (x)
|
||||||
|
#define htole16(x) __builtin_bswap16(x)
|
||||||
|
#define htole32(x) __builtin_bswap32(x)
|
||||||
|
#define be16toh(x) (x)
|
||||||
|
#define be32toh(x) (x)
|
||||||
|
#define le16toh(x) __builtin_bswap16(x)
|
||||||
|
#define le32toh(x) __builtin_bswap32(x)
|
||||||
|
#elif BYTE_ORDER == LITTLE_ENDIAN
|
||||||
|
#define htobe16(x) __builtin_bswap16(x)
|
||||||
|
#define htobe32(x) __builtin_bswap32(x)
|
||||||
|
#define htole16(x) (x)
|
||||||
|
#define htole32(x) (x)
|
||||||
|
#define be16toh(x) __builtin_bswap16(x)
|
||||||
|
#define be32toh(x) __builtin_bswap32(x)
|
||||||
|
#define le16toh(x) (x)
|
||||||
|
#define le32toh(x) (x)
|
||||||
|
#else
|
||||||
|
#error "What kind of system is this?"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define load16_le(b) (le16toh(load16(b)))
|
||||||
|
#define load32_le(b) (le32toh(load32(b)))
|
||||||
|
#define store16_le(b, i) (store16(b, htole16(i)))
|
||||||
|
#define store32_le(b, i) (store32(b, htole32(i)))
|
||||||
|
|
||||||
|
#define load16_be(b) (be16toh(load16(b)))
|
||||||
|
#define load32_be(b) (be32toh(load32(b)))
|
||||||
|
#define store16_be(b, i) (store16(b, htobe16(i)))
|
||||||
|
#define store32_be(b, i) (store32(b, htobe32(i)))
|
||||||
|
|
||||||
|
#endif //FLIPPERZERO_FIRMWARE_ENDIANNESS_H
|
||||||
53
applications/external/unitemp/sensors/SCD30.c
vendored
53
applications/external/unitemp/sensors/SCD30.c
vendored
@@ -21,60 +21,9 @@
|
|||||||
|
|
||||||
#include "SCD30.h"
|
#include "SCD30.h"
|
||||||
#include "../interfaces/I2CSensor.h"
|
#include "../interfaces/I2CSensor.h"
|
||||||
|
#include "../interfaces/endianness.h"
|
||||||
//#include <3rdparty/everest/include/everest/kremlin/c_endianness.h>
|
//#include <3rdparty/everest/include/everest/kremlin/c_endianness.h>
|
||||||
|
|
||||||
inline static uint16_t load16(uint8_t* b) {
|
|
||||||
uint16_t x;
|
|
||||||
memcpy(&x, b, 2);
|
|
||||||
return x;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline static uint32_t load32(uint8_t* b) {
|
|
||||||
uint32_t x;
|
|
||||||
memcpy(&x, b, 4);
|
|
||||||
return x;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline static void store16(uint8_t* b, uint16_t i) {
|
|
||||||
memcpy(b, &i, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline static void store32(uint8_t* b, uint32_t i) {
|
|
||||||
memcpy(b, &i, 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
#if BYTE_ORDER == BIG_ENDIAN
|
|
||||||
#define htobe16(x) (x)
|
|
||||||
#define htobe32(x) (x)
|
|
||||||
#define htole16(x) __builtin_bswap16(x)
|
|
||||||
#define htole32(x) __builtin_bswap32(x)
|
|
||||||
#define be16toh(x) (x)
|
|
||||||
#define be32toh(x) (x)
|
|
||||||
#define le16toh(x) __builtin_bswap16(x)
|
|
||||||
#define le32toh(x) __builtin_bswap32(x)
|
|
||||||
#elif BYTE_ORDER == LITTLE_ENDIAN
|
|
||||||
#define htobe16(x) __builtin_bswap16(x)
|
|
||||||
#define htobe32(x) __builtin_bswap32(x)
|
|
||||||
#define htole16(x) (x)
|
|
||||||
#define htole32(x) (x)
|
|
||||||
#define be16toh(x) __builtin_bswap16(x)
|
|
||||||
#define be32toh(x) __builtin_bswap32(x)
|
|
||||||
#define le16toh(x) (x)
|
|
||||||
#define le32toh(x) (x)
|
|
||||||
#else
|
|
||||||
#error "What kind of system is this?"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define load16_le(b) (le16toh(load16(b)))
|
|
||||||
#define load32_le(b) (le32toh(load32(b)))
|
|
||||||
#define store16_le(b, i) (store16(b, htole16(i)))
|
|
||||||
#define store32_le(b, i) (store32(b, htole32(i)))
|
|
||||||
|
|
||||||
#define load16_be(b) (be16toh(load16(b)))
|
|
||||||
#define load32_be(b) (be32toh(load32(b)))
|
|
||||||
#define store16_be(b, i) (store16(b, htobe16(i)))
|
|
||||||
#define store32_be(b, i) (store32(b, htobe32(i)))
|
|
||||||
|
|
||||||
typedef union {
|
typedef union {
|
||||||
uint16_t array16[2];
|
uint16_t array16[2];
|
||||||
uint8_t array8[4];
|
uint8_t array8[4];
|
||||||
|
|||||||
291
applications/external/unitemp/sensors/SCD40.c
vendored
Normal file
291
applications/external/unitemp/sensors/SCD40.c
vendored
Normal file
@@ -0,0 +1,291 @@
|
|||||||
|
/*
|
||||||
|
Unitemp - Universal temperature reader
|
||||||
|
Copyright (C) 2022-2023 Victor Nikitchuk (https://github.com/quen0n)
|
||||||
|
Contributed by divinebird (https://github.com/divinebird)
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Some information may be seen on https://github.com/sparkfun/SparkFun_SCD30_Arduino_Library
|
||||||
|
|
||||||
|
#include "SCD30.h"
|
||||||
|
#include "../interfaces/I2CSensor.h"
|
||||||
|
#include "../interfaces/endianness.h"
|
||||||
|
//#include <3rdparty/everest/include/everest/kremlin/c_endianness.h>
|
||||||
|
|
||||||
|
bool unitemp_SCD40_alloc(Sensor* sensor, char* args);
|
||||||
|
bool unitemp_SCD40_init(Sensor* sensor);
|
||||||
|
bool unitemp_SCD40_deinit(Sensor* sensor);
|
||||||
|
UnitempStatus unitemp_SCD40_update(Sensor* sensor);
|
||||||
|
bool unitemp_SCD40_free(Sensor* sensor);
|
||||||
|
|
||||||
|
const SensorType SCD40 = {
|
||||||
|
.typename = "SCD40",
|
||||||
|
.interface = &I2C,
|
||||||
|
.datatype = UT_DATA_TYPE_TEMP_HUM_CO2,
|
||||||
|
.pollingInterval = 5000,
|
||||||
|
.allocator = unitemp_SCD40_alloc,
|
||||||
|
.mem_releaser = unitemp_SCD40_free,
|
||||||
|
.initializer = unitemp_SCD40_init,
|
||||||
|
.deinitializer = unitemp_SCD40_deinit,
|
||||||
|
.updater = unitemp_SCD40_update};
|
||||||
|
|
||||||
|
#define SCD40_ID 0x62
|
||||||
|
|
||||||
|
#define COMMAND_START_PERIODIC_MEASUREMENT 0X21B1
|
||||||
|
#define COMMAND_READ_MEASUREMENT 0XEC05
|
||||||
|
#define COMMAND_STOP_PERIODIC_MEASUREMENT 0X3F86
|
||||||
|
|
||||||
|
#define COMMAND_PERSIST_SETTINGS 0X3615
|
||||||
|
#define COMMAND_GET_SERIAL_NUMBER 0X3682
|
||||||
|
#define COMMAND_PERFORM_SELF_TEST 0X3639
|
||||||
|
#define COMMAND_PERFORM_FACTORY_RESET 0X3632
|
||||||
|
#define COMMAND_REINIT 0X3646
|
||||||
|
|
||||||
|
#define COMMAND_SET_TEMPERATURE_OFFSET 0X241D
|
||||||
|
#define COMMAND_GET_TEMPERATURE_OFFSET 0X2318
|
||||||
|
#define COMMAND_SET_SENSOR_ALTITUDE 0X2427
|
||||||
|
#define COMMAND_GET_SENSOR_ALTITUDE 0X2322
|
||||||
|
#define COMMAND_SET_AMBIENT_PRESSURE 0XE000
|
||||||
|
#define COMMAND_PERFORM_FORCED_RECALIBRATION 0X362F
|
||||||
|
#define COMMAND_SET_AUTOMATIC_SELF_CALIBRATION_ENABLED 0X2416
|
||||||
|
#define COMMAND_GET_AUTOMATIC_SELF_CALIBRATION_ENABLED 0X2313
|
||||||
|
|
||||||
|
static bool readMeasurement(Sensor* sensor) __attribute__((unused));
|
||||||
|
static void reset(Sensor* sensor) __attribute__((unused));
|
||||||
|
|
||||||
|
static bool setAutoSelfCalibration(Sensor* sensor, bool enable) __attribute__((unused));
|
||||||
|
static bool getAutoSelfCalibration(Sensor* sensor) __attribute__((unused));
|
||||||
|
|
||||||
|
static bool getFirmwareVersion(Sensor* sensor, uint16_t* val) __attribute__((unused));
|
||||||
|
|
||||||
|
static float getTemperatureOffset(Sensor* sensor) __attribute__((unused));
|
||||||
|
static bool setTemperatureOffset(Sensor* sensor, float tempOffset) __attribute__((unused));
|
||||||
|
|
||||||
|
static bool beginMeasuring(Sensor* sensor) __attribute__((unused));
|
||||||
|
static bool stopMeasurement(Sensor* sensor) __attribute__((unused));
|
||||||
|
|
||||||
|
bool unitemp_SCD40_alloc(Sensor* sensor, char* args) {
|
||||||
|
UNUSED(args);
|
||||||
|
I2CSensor* i2c_sensor = (I2CSensor*)sensor->instance;
|
||||||
|
|
||||||
|
i2c_sensor->minI2CAdr = SCD40_ID << 1;
|
||||||
|
i2c_sensor->maxI2CAdr = SCD40_ID << 1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool unitemp_SCD40_free(Sensor* sensor) {
|
||||||
|
//Нечего высвобождать, так как ничего не было выделено
|
||||||
|
UNUSED(sensor);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool unitemp_SCD40_init(Sensor* sensor) {
|
||||||
|
return beginMeasuring(sensor);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool unitemp_SCD40_deinit(Sensor* sensor) {
|
||||||
|
return stopMeasurement(sensor);
|
||||||
|
}
|
||||||
|
|
||||||
|
UnitempStatus unitemp_SCD40_update(Sensor* sensor) {
|
||||||
|
readMeasurement(sensor);
|
||||||
|
return UT_SENSORSTATUS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define CRC8_POLYNOMIAL 0x31
|
||||||
|
#define CRC8_INIT 0xFF
|
||||||
|
|
||||||
|
static uint8_t computeCRC8(uint8_t* message, uint8_t len) {
|
||||||
|
uint8_t crc = CRC8_INIT; // Init with 0xFF
|
||||||
|
for(uint8_t x = 0; x < len; x++) {
|
||||||
|
crc ^= message[x]; // XOR-in the next input byte
|
||||||
|
for(uint8_t i = 0; i < 8; i++) {
|
||||||
|
if((crc & 0x80) != 0)
|
||||||
|
crc = (uint8_t)((crc << 1) ^ CRC8_POLYNOMIAL);
|
||||||
|
else
|
||||||
|
crc <<= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return crc; // No output reflection
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sends a command along with arguments and CRC
|
||||||
|
static bool sendCommandWithCRC(Sensor* sensor, uint16_t command, uint16_t arguments) {
|
||||||
|
static const uint8_t cmdSize = 5;
|
||||||
|
|
||||||
|
uint8_t bytes[cmdSize];
|
||||||
|
uint8_t* pointer = bytes;
|
||||||
|
store16_be(pointer, command);
|
||||||
|
pointer += 2;
|
||||||
|
uint8_t* argPos = pointer;
|
||||||
|
store16_be(pointer, arguments);
|
||||||
|
pointer += 2;
|
||||||
|
*pointer = computeCRC8(argPos, pointer - argPos);
|
||||||
|
|
||||||
|
I2CSensor* i2c_sensor = (I2CSensor*)sensor->instance;
|
||||||
|
return unitemp_i2c_writeArray(i2c_sensor, cmdSize, bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sends just a command, no arguments, no CRC
|
||||||
|
static bool sendCommand(Sensor* sensor, uint16_t command) {
|
||||||
|
static const uint8_t cmdSize = 2;
|
||||||
|
|
||||||
|
uint8_t bytes[cmdSize];
|
||||||
|
store16_be(bytes, command);
|
||||||
|
|
||||||
|
I2CSensor* i2c_sensor = (I2CSensor*)sensor->instance;
|
||||||
|
return unitemp_i2c_writeArray(i2c_sensor, cmdSize, bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint16_t readRegister(Sensor* sensor, uint16_t registerAddress) {
|
||||||
|
static const uint8_t regSize = 2;
|
||||||
|
|
||||||
|
if(!sendCommand(sensor, registerAddress)) return 0; // Sensor did not ACK
|
||||||
|
|
||||||
|
furi_delay_ms(3);
|
||||||
|
|
||||||
|
uint8_t bytes[regSize];
|
||||||
|
I2CSensor* i2c_sensor = (I2CSensor*)sensor->instance;
|
||||||
|
if(!unitemp_i2c_readArray(i2c_sensor, regSize, bytes)) return 0;
|
||||||
|
|
||||||
|
return load16_be(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool loadWord(uint8_t* buff, uint16_t* val) {
|
||||||
|
uint16_t tmp = load16_be(buff);
|
||||||
|
uint8_t expectedCRC = computeCRC8(buff, 2);
|
||||||
|
if(buff[2] != expectedCRC) return false;
|
||||||
|
*val = tmp;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool getSettingValue(Sensor* sensor, uint16_t registerAddress, uint16_t* val) {
|
||||||
|
static const uint8_t respSize = 3;
|
||||||
|
|
||||||
|
if(!sendCommand(sensor, registerAddress)) return false; // Sensor did not ACK
|
||||||
|
|
||||||
|
furi_delay_ms(3);
|
||||||
|
|
||||||
|
uint8_t bytes[respSize];
|
||||||
|
I2CSensor* i2c_sensor = (I2CSensor*)sensor->instance;
|
||||||
|
if(!unitemp_i2c_readArray(i2c_sensor, respSize, bytes)) return false;
|
||||||
|
|
||||||
|
return loadWord(bytes, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get 18 bytes from SCD30
|
||||||
|
// Updates global variables with floats
|
||||||
|
// Returns true if success
|
||||||
|
static bool readMeasurement(Sensor* sensor) {
|
||||||
|
if(!sendCommand(sensor, COMMAND_READ_MEASUREMENT)) {
|
||||||
|
FURI_LOG_E(APP_NAME, "Sensor did not ACK");
|
||||||
|
return false; // Sensor did not ACK
|
||||||
|
}
|
||||||
|
|
||||||
|
furi_delay_ms(3);
|
||||||
|
|
||||||
|
static const uint8_t respSize = 9;
|
||||||
|
uint8_t buff[respSize];
|
||||||
|
uint8_t* bytes = buff;
|
||||||
|
I2CSensor* i2c_sensor = (I2CSensor*)sensor->instance;
|
||||||
|
if(!unitemp_i2c_readArray(i2c_sensor, respSize, bytes)) {
|
||||||
|
FURI_LOG_E(APP_NAME, "Error while read measures");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t tmpValue;
|
||||||
|
|
||||||
|
bool error = false;
|
||||||
|
if(loadWord(bytes, &tmpValue)) {
|
||||||
|
sensor->co2 = tmpValue;
|
||||||
|
} else {
|
||||||
|
FURI_LOG_E(APP_NAME, "Error while parsing CO2");
|
||||||
|
error = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes += 3;
|
||||||
|
if(loadWord(bytes, &tmpValue)) {
|
||||||
|
sensor->temp = (float)tmpValue * 175.0f / 65535.0f - 45.0f;
|
||||||
|
} else {
|
||||||
|
FURI_LOG_E(APP_NAME, "Error while parsing temp");
|
||||||
|
error = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes += 3;
|
||||||
|
if(loadWord(bytes, &tmpValue)) {
|
||||||
|
sensor->hum = (float)tmpValue * 100.0f / 65535.0f;
|
||||||
|
} else {
|
||||||
|
FURI_LOG_E(APP_NAME, "Error while parsing humidity");
|
||||||
|
error = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !error;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void reset(Sensor* sensor) {
|
||||||
|
sendCommand(sensor, COMMAND_REINIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool setAutoSelfCalibration(Sensor* sensor, bool enable) {
|
||||||
|
return sendCommandWithCRC(
|
||||||
|
sensor, COMMAND_SET_AUTOMATIC_SELF_CALIBRATION_ENABLED, enable); // Activate continuous ASC
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the current ASC setting
|
||||||
|
static bool getAutoSelfCalibration(Sensor* sensor) {
|
||||||
|
return 1 == readRegister(sensor, COMMAND_GET_AUTOMATIC_SELF_CALIBRATION_ENABLED);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unfinished
|
||||||
|
static bool getFirmwareVersion(Sensor* sensor, uint16_t* val) {
|
||||||
|
if(!sendCommand(sensor, COMMAND_READ_MEASUREMENT)) {
|
||||||
|
FURI_LOG_E(APP_NAME, "Sensor did not ACK");
|
||||||
|
return false; // Sensor did not ACK
|
||||||
|
}
|
||||||
|
|
||||||
|
static const uint8_t respSize = 9;
|
||||||
|
uint8_t buff[respSize];
|
||||||
|
uint8_t* bytes = buff;
|
||||||
|
I2CSensor* i2c_sensor = (I2CSensor*)sensor->instance;
|
||||||
|
if(!unitemp_i2c_readArray(i2c_sensor, respSize, bytes)) {
|
||||||
|
FURI_LOG_E(APP_NAME, "Error while read measures");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
*val = 0;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool beginMeasuring(Sensor* sensor) {
|
||||||
|
return sendCommand(sensor, COMMAND_START_PERIODIC_MEASUREMENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop continuous measurement
|
||||||
|
static bool stopMeasurement(Sensor* sensor) {
|
||||||
|
return sendCommand(sensor, COMMAND_READ_MEASUREMENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
static float getTemperatureOffset(Sensor* sensor) {
|
||||||
|
uint16_t curOffset;
|
||||||
|
if(!getSettingValue(sensor, COMMAND_GET_TEMPERATURE_OFFSET, &curOffset)) return 0.0;
|
||||||
|
return (float)curOffset * 175.0f / 65536.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool setTemperatureOffset(Sensor* sensor, float tempOffset) {
|
||||||
|
uint16_t newOffset = tempOffset * 65536.0 / 175.0 + 0.5f;
|
||||||
|
return sendCommandWithCRC(
|
||||||
|
sensor, COMMAND_SET_TEMPERATURE_OFFSET, newOffset); // Activate continuous ASC
|
||||||
|
}
|
||||||
59
applications/external/unitemp/sensors/SCD40.h
vendored
Normal file
59
applications/external/unitemp/sensors/SCD40.h
vendored
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
Unitemp - Universal temperature reader
|
||||||
|
Copyright (C) 2022-2023 Victor Nikitchuk (https://github.com/quen0n)
|
||||||
|
Contributed by divinebird (https://github.com/divinebird)
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
#ifndef UNITEMP_SCD40
|
||||||
|
#define UNITEMP_SCD40
|
||||||
|
|
||||||
|
#include "../unitemp.h"
|
||||||
|
#include "../Sensors.h"
|
||||||
|
|
||||||
|
extern const SensorType SCD40;
|
||||||
|
/**
|
||||||
|
* @brief Выделение памяти и установка начальных значений датчика SCD40
|
||||||
|
* @param sensor Указатель на создаваемый датчик
|
||||||
|
* @return Истина при успехе
|
||||||
|
*/
|
||||||
|
bool unitemp_SCD40_alloc(Sensor* sensor, char* args);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Инициализации датчика SCD40
|
||||||
|
* @param sensor Указатель на датчик
|
||||||
|
* @return Истина если инициализация упспешная
|
||||||
|
*/
|
||||||
|
bool unitemp_SCD40_init(Sensor* sensor);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Деинициализация датчика
|
||||||
|
* @param sensor Указатель на датчик
|
||||||
|
*/
|
||||||
|
bool unitemp_SCD40_deinit(Sensor* sensor);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Обновление значений из датчика
|
||||||
|
* @param sensor Указатель на датчик
|
||||||
|
* @return Статус опроса датчика
|
||||||
|
*/
|
||||||
|
UnitempStatus unitemp_SCD40_update(Sensor* sensor);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Высвободить память датчика
|
||||||
|
* @param sensor Указатель на датчик
|
||||||
|
*/
|
||||||
|
bool unitemp_SCD40_free(Sensor* sensor);
|
||||||
|
|
||||||
|
#endif
|
||||||
30
applications/external/unitemp/unitemp.c
vendored
30
applications/external/unitemp/unitemp.c
vendored
@@ -28,8 +28,31 @@ Unitemp* app;
|
|||||||
|
|
||||||
void uintemp_celsiumToFarengate(Sensor* sensor) {
|
void uintemp_celsiumToFarengate(Sensor* sensor) {
|
||||||
sensor->temp = sensor->temp * (9.0 / 5.0) + 32;
|
sensor->temp = sensor->temp * (9.0 / 5.0) + 32;
|
||||||
|
sensor->heat_index = sensor->heat_index * (9.0 / 5.0) + 32;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static float heat_index_consts[9] = {
|
||||||
|
-42.379f,
|
||||||
|
2.04901523f,
|
||||||
|
10.14333127f,
|
||||||
|
-0.22475541f,
|
||||||
|
-0.00683783f,
|
||||||
|
-0.05481717f,
|
||||||
|
0.00122874f,
|
||||||
|
0.00085282f,
|
||||||
|
-0.00000199f};
|
||||||
|
void unitemp_calculate_heat_index(Sensor* sensor) {
|
||||||
|
// temp should be in Celsius, heat index will be in Celsius
|
||||||
|
float temp = sensor->temp * (9.0 / 5.0) + 32.0f;
|
||||||
|
float hum = sensor->hum;
|
||||||
|
sensor->heat_index =
|
||||||
|
(heat_index_consts[0] + heat_index_consts[1] * temp + heat_index_consts[2] * hum +
|
||||||
|
heat_index_consts[3] * temp * hum + heat_index_consts[4] * temp * temp +
|
||||||
|
heat_index_consts[5] * hum * hum + heat_index_consts[6] * temp * temp * hum +
|
||||||
|
heat_index_consts[7] * temp * hum * hum + heat_index_consts[8] * temp * temp * hum * hum -
|
||||||
|
32.0f) *
|
||||||
|
(5.0 / 9.0);
|
||||||
|
}
|
||||||
void unitemp_pascalToMmHg(Sensor* sensor) {
|
void unitemp_pascalToMmHg(Sensor* sensor) {
|
||||||
sensor->pressure = sensor->pressure * 0.007500638;
|
sensor->pressure = sensor->pressure * 0.007500638;
|
||||||
}
|
}
|
||||||
@@ -71,6 +94,7 @@ bool unitemp_saveSettings(void) {
|
|||||||
app->file_stream, "INFINITY_BACKLIGHT %d\n", app->settings.infinityBacklight);
|
app->file_stream, "INFINITY_BACKLIGHT %d\n", app->settings.infinityBacklight);
|
||||||
stream_write_format(app->file_stream, "TEMP_UNIT %d\n", app->settings.temp_unit);
|
stream_write_format(app->file_stream, "TEMP_UNIT %d\n", app->settings.temp_unit);
|
||||||
stream_write_format(app->file_stream, "PRESSURE_UNIT %d\n", app->settings.pressure_unit);
|
stream_write_format(app->file_stream, "PRESSURE_UNIT %d\n", app->settings.pressure_unit);
|
||||||
|
stream_write_format(app->file_stream, "HEAT_INDEX %d\n", app->settings.heat_index);
|
||||||
|
|
||||||
//Закрытие потока и освобождение памяти
|
//Закрытие потока и освобождение памяти
|
||||||
file_stream_close(app->file_stream);
|
file_stream_close(app->file_stream);
|
||||||
@@ -166,6 +190,11 @@ bool unitemp_loadSettings(void) {
|
|||||||
int p = 0;
|
int p = 0;
|
||||||
sscanf(((char*)(file_buf + line_end)), "\nPRESSURE_UNIT %d", &p);
|
sscanf(((char*)(file_buf + line_end)), "\nPRESSURE_UNIT %d", &p);
|
||||||
app->settings.pressure_unit = p;
|
app->settings.pressure_unit = p;
|
||||||
|
} else if(!strcmp(buff, "HEAT_INDEX")) {
|
||||||
|
//Чтение значения параметра
|
||||||
|
int p = 0;
|
||||||
|
sscanf(((char*)(file_buf + line_end)), "\nHEAT_INDEX %d", &p);
|
||||||
|
app->settings.heat_index = p;
|
||||||
} else {
|
} else {
|
||||||
FURI_LOG_W(APP_NAME, "Unknown settings parameter: %s", buff);
|
FURI_LOG_W(APP_NAME, "Unknown settings parameter: %s", buff);
|
||||||
}
|
}
|
||||||
@@ -203,6 +232,7 @@ static bool unitemp_alloc(void) {
|
|||||||
app->settings.infinityBacklight = true; //Подсветка горит всегда
|
app->settings.infinityBacklight = true; //Подсветка горит всегда
|
||||||
app->settings.temp_unit = UT_TEMP_CELSIUS; //Единица измерения температуры - градусы Цельсия
|
app->settings.temp_unit = UT_TEMP_CELSIUS; //Единица измерения температуры - градусы Цельсия
|
||||||
app->settings.pressure_unit = UT_PRESSURE_MM_HG; //Единица измерения давления - мм рт. ст.
|
app->settings.pressure_unit = UT_PRESSURE_MM_HG; //Единица измерения давления - мм рт. ст.
|
||||||
|
app->settings.heat_index = false;
|
||||||
|
|
||||||
app->gui = furi_record_open(RECORD_GUI);
|
app->gui = furi_record_open(RECORD_GUI);
|
||||||
//Диспетчер окон
|
//Диспетчер окон
|
||||||
|
|||||||
11
applications/external/unitemp/unitemp.h
vendored
11
applications/external/unitemp/unitemp.h
vendored
@@ -40,7 +40,7 @@
|
|||||||
//Имя приложения
|
//Имя приложения
|
||||||
#define APP_NAME "Unitemp"
|
#define APP_NAME "Unitemp"
|
||||||
//Версия приложения
|
//Версия приложения
|
||||||
#define UNITEMP_APP_VER "1.3"
|
#define UNITEMP_APP_VER "1.4"
|
||||||
//Путь хранения файлов плагина
|
//Путь хранения файлов плагина
|
||||||
#define APP_PATH_FOLDER "/ext/unitemp"
|
#define APP_PATH_FOLDER "/ext/unitemp"
|
||||||
//Имя файла с настройками
|
//Имя файла с настройками
|
||||||
@@ -80,6 +80,8 @@ typedef struct {
|
|||||||
tempMeasureUnit temp_unit;
|
tempMeasureUnit temp_unit;
|
||||||
//Единица измерения давления
|
//Единица измерения давления
|
||||||
pressureMeasureUnit pressure_unit;
|
pressureMeasureUnit pressure_unit;
|
||||||
|
// Do calculate and show heat index
|
||||||
|
bool heat_index;
|
||||||
//Последнее состояние OTG
|
//Последнее состояние OTG
|
||||||
bool lastOTGState;
|
bool lastOTGState;
|
||||||
} UnitempSettings;
|
} UnitempSettings;
|
||||||
@@ -111,6 +113,13 @@ typedef struct {
|
|||||||
|
|
||||||
/* Объявление прототипов функций */
|
/* Объявление прототипов функций */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Calculates the heat index in Celsius from the temperature and humidity and stores it in the sensor heat_index field
|
||||||
|
*
|
||||||
|
* @param sensor The sensor struct, with temperature in Celcius and humidity in percent
|
||||||
|
*/
|
||||||
|
void unitemp_calculate_heat_index(Sensor* sensor);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Перевод значения температуры датчика из Цельсия в Фаренгейты
|
* @brief Перевод значения температуры датчика из Цельсия в Фаренгейты
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -113,6 +113,33 @@ static void _draw_humidity(Canvas* canvas, Sensor* sensor, const uint8_t pos[2])
|
|||||||
canvas_draw_str(canvas, pos[0] + 27 + int_len / 2 + 4, pos[1] + 10 + 7, "%");
|
canvas_draw_str(canvas, pos[0] + 27 + int_len / 2 + 4, pos[1] + 10 + 7, "%");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void _draw_heat_index(Canvas* canvas, Sensor* sensor, const uint8_t pos[2]) {
|
||||||
|
canvas_draw_rframe(canvas, pos[0], pos[1], 54, 20, 3);
|
||||||
|
canvas_draw_rframe(canvas, pos[0], pos[1], 54, 19, 3);
|
||||||
|
|
||||||
|
canvas_draw_icon(canvas, pos[0] + 3, pos[1] + 3, &I_heat_index_11x14);
|
||||||
|
|
||||||
|
int16_t heat_index_int = sensor->heat_index;
|
||||||
|
int8_t heat_index_dec = abs((int16_t)(sensor->heat_index * 10) % 10);
|
||||||
|
|
||||||
|
snprintf(app->buff, BUFF_SIZE, "%d", heat_index_int);
|
||||||
|
canvas_set_font(canvas, FontBigNumbers);
|
||||||
|
canvas_draw_str_aligned(
|
||||||
|
canvas,
|
||||||
|
pos[0] + 27 + ((sensor->heat_index <= -10 || sensor->heat_index > 99) ? 5 : 0),
|
||||||
|
pos[1] + 10,
|
||||||
|
AlignCenter,
|
||||||
|
AlignCenter,
|
||||||
|
app->buff);
|
||||||
|
|
||||||
|
if(heat_index_int <= 99) {
|
||||||
|
uint8_t int_len = canvas_string_width(canvas, app->buff);
|
||||||
|
snprintf(app->buff, BUFF_SIZE, ".%d", heat_index_dec);
|
||||||
|
canvas_set_font(canvas, FontPrimary);
|
||||||
|
canvas_draw_str(canvas, pos[0] + 27 + int_len / 2 + 2, pos[1] + 10 + 7, app->buff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void _draw_pressure(Canvas* canvas, Sensor* sensor) {
|
static void _draw_pressure(Canvas* canvas, Sensor* sensor) {
|
||||||
const uint8_t x = 29, y = 39;
|
const uint8_t x = 29, y = 39;
|
||||||
//Рисование рамки
|
//Рисование рамки
|
||||||
@@ -320,12 +347,23 @@ static void _draw_carousel_values(Canvas* canvas) {
|
|||||||
ColorWhite);
|
ColorWhite);
|
||||||
break;
|
break;
|
||||||
case UT_DATA_TYPE_TEMP_HUM:
|
case UT_DATA_TYPE_TEMP_HUM:
|
||||||
_draw_temperature(
|
if(!app->settings.heat_index) {
|
||||||
canvas,
|
_draw_temperature(
|
||||||
unitemp_sensor_getActive(generalview_sensor_index),
|
canvas,
|
||||||
temp_positions[1][0],
|
unitemp_sensor_getActive(generalview_sensor_index),
|
||||||
temp_positions[1][1],
|
temp_positions[1][0],
|
||||||
ColorWhite);
|
temp_positions[1][1],
|
||||||
|
ColorWhite);
|
||||||
|
} else {
|
||||||
|
_draw_temperature(
|
||||||
|
canvas,
|
||||||
|
unitemp_sensor_getActive(generalview_sensor_index),
|
||||||
|
temp_positions[2][0],
|
||||||
|
temp_positions[2][1],
|
||||||
|
ColorWhite);
|
||||||
|
_draw_heat_index(
|
||||||
|
canvas, unitemp_sensor_getActive(generalview_sensor_index), hum_positions[1]);
|
||||||
|
}
|
||||||
_draw_humidity(
|
_draw_humidity(
|
||||||
canvas, unitemp_sensor_getActive(generalview_sensor_index), hum_positions[0]);
|
canvas, unitemp_sensor_getActive(generalview_sensor_index), hum_positions[0]);
|
||||||
break;
|
break;
|
||||||
@@ -446,8 +484,8 @@ static void _draw_carousel_info(Canvas* canvas) {
|
|||||||
->currentI2CAdr >>
|
->currentI2CAdr >>
|
||||||
1);
|
1);
|
||||||
canvas_draw_str(canvas, 57, 35, app->buff);
|
canvas_draw_str(canvas, 57, 35, app->buff);
|
||||||
canvas_draw_str(canvas, 54, 46, "15 (C0)");
|
canvas_draw_str(canvas, 54, 46, "15 (C1)");
|
||||||
canvas_draw_str(canvas, 54, 58, "16 (C1)");
|
canvas_draw_str(canvas, 54, 58, "16 (C0)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
static void _draw_view_sensorsCarousel(Canvas* canvas) {
|
static void _draw_view_sensorsCarousel(Canvas* canvas) {
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ static VariableItemList* variable_item_list;
|
|||||||
static const char states[2][9] = {"Auto", "Infinity"};
|
static const char states[2][9] = {"Auto", "Infinity"};
|
||||||
static const char temp_units[UT_TEMP_COUNT][3] = {"*C", "*F"};
|
static const char temp_units[UT_TEMP_COUNT][3] = {"*C", "*F"};
|
||||||
static const char pressure_units[UT_PRESSURE_COUNT][6] = {"mm Hg", "in Hg", "kPa", "hPA"};
|
static const char pressure_units[UT_PRESSURE_COUNT][6] = {"mm Hg", "in Hg", "kPa", "hPA"};
|
||||||
|
static const char heat_index_bool[2][4] = {"OFF", "ON"};
|
||||||
|
|
||||||
//Элемент списка - бесконечная подсветка
|
//Элемент списка - бесконечная подсветка
|
||||||
VariableItem* infinity_backlight_item;
|
VariableItem* infinity_backlight_item;
|
||||||
@@ -33,6 +34,8 @@ VariableItem* infinity_backlight_item;
|
|||||||
VariableItem* temperature_unit_item;
|
VariableItem* temperature_unit_item;
|
||||||
//Единица измерения давления
|
//Единица измерения давления
|
||||||
VariableItem* pressure_unit_item;
|
VariableItem* pressure_unit_item;
|
||||||
|
|
||||||
|
VariableItem* heat_index_item;
|
||||||
#define VIEW_ID UnitempViewSettings
|
#define VIEW_ID UnitempViewSettings
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -57,6 +60,7 @@ static uint32_t _exit_callback(void* context) {
|
|||||||
(bool)variable_item_get_current_value_index(infinity_backlight_item);
|
(bool)variable_item_get_current_value_index(infinity_backlight_item);
|
||||||
app->settings.temp_unit = variable_item_get_current_value_index(temperature_unit_item);
|
app->settings.temp_unit = variable_item_get_current_value_index(temperature_unit_item);
|
||||||
app->settings.pressure_unit = variable_item_get_current_value_index(pressure_unit_item);
|
app->settings.pressure_unit = variable_item_get_current_value_index(pressure_unit_item);
|
||||||
|
app->settings.heat_index = variable_item_get_current_value_index(heat_index_item);
|
||||||
unitemp_saveSettings();
|
unitemp_saveSettings();
|
||||||
unitemp_loadSettings();
|
unitemp_loadSettings();
|
||||||
|
|
||||||
@@ -90,6 +94,11 @@ static void _setting_change_callback(VariableItem* item) {
|
|||||||
pressure_unit_item,
|
pressure_unit_item,
|
||||||
pressure_units[variable_item_get_current_value_index(pressure_unit_item)]);
|
pressure_units[variable_item_get_current_value_index(pressure_unit_item)]);
|
||||||
}
|
}
|
||||||
|
if(item == heat_index_item) {
|
||||||
|
variable_item_set_current_value_text(
|
||||||
|
heat_index_item,
|
||||||
|
heat_index_bool[variable_item_get_current_value_index(heat_index_item)]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -106,6 +115,8 @@ void unitemp_Settings_alloc(void) {
|
|||||||
variable_item_list_add(variable_item_list, "Temp. unit", 2, _setting_change_callback, app);
|
variable_item_list_add(variable_item_list, "Temp. unit", 2, _setting_change_callback, app);
|
||||||
pressure_unit_item = variable_item_list_add(
|
pressure_unit_item = variable_item_list_add(
|
||||||
variable_item_list, "Press. unit", UT_PRESSURE_COUNT, _setting_change_callback, app);
|
variable_item_list, "Press. unit", UT_PRESSURE_COUNT, _setting_change_callback, app);
|
||||||
|
heat_index_item = variable_item_list_add(
|
||||||
|
variable_item_list, "Calc. heat index", 2, _setting_change_callback, app);
|
||||||
|
|
||||||
//Добавление колбека на нажатие средней кнопки
|
//Добавление колбека на нажатие средней кнопки
|
||||||
variable_item_list_set_enter_callback(variable_item_list, _enter_callback, app);
|
variable_item_list_set_enter_callback(variable_item_list, _enter_callback, app);
|
||||||
@@ -139,6 +150,10 @@ void unitemp_Settings_switch(void) {
|
|||||||
pressure_unit_item,
|
pressure_unit_item,
|
||||||
pressure_units[variable_item_get_current_value_index(pressure_unit_item)]);
|
pressure_units[variable_item_get_current_value_index(pressure_unit_item)]);
|
||||||
|
|
||||||
|
variable_item_set_current_value_index(heat_index_item, (uint8_t)app->settings.heat_index);
|
||||||
|
variable_item_set_current_value_text(
|
||||||
|
heat_index_item, heat_index_bool[variable_item_get_current_value_index(heat_index_item)]);
|
||||||
|
|
||||||
view_dispatcher_switch_to_view(app->view_dispatcher, VIEW_ID);
|
view_dispatcher_switch_to_view(app->view_dispatcher, VIEW_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -160,6 +160,10 @@ static void nfc_cli_apdu(Cli* cli, FuriString* args) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
resp_size = (tx_rx.rx_bits / 8) * 2;
|
resp_size = (tx_rx.rx_bits / 8) * 2;
|
||||||
|
if(!resp_size) {
|
||||||
|
printf("No response\r\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
resp_buffer = malloc(resp_size);
|
resp_buffer = malloc(resp_size);
|
||||||
uint8_to_hex_chars(tx_rx.rx_data, resp_buffer, resp_size);
|
uint8_to_hex_chars(tx_rx.rx_data, resp_buffer, resp_size);
|
||||||
resp_buffer[resp_size] = 0;
|
resp_buffer[resp_size] = 0;
|
||||||
|
|||||||
@@ -8,6 +8,83 @@ void nfc_scene_nfc_data_info_widget_callback(GuiButtonType result, InputType typ
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void nfc_scene_slix_build_string(
|
||||||
|
FuriString* temp_str,
|
||||||
|
NfcVData* nfcv_data,
|
||||||
|
SlixTypeFeatures features,
|
||||||
|
const char* type) {
|
||||||
|
furi_string_cat_printf(temp_str, "Type: %s\n", type);
|
||||||
|
furi_string_cat_printf(temp_str, "Keys:\n");
|
||||||
|
if(features & SlixFeatureRead) {
|
||||||
|
furi_string_cat_printf(
|
||||||
|
temp_str,
|
||||||
|
" Read %08llX%s\n",
|
||||||
|
nfc_util_bytes2num(nfcv_data->sub_data.slix.key_read, 4),
|
||||||
|
(nfcv_data->sub_data.slix.flags & NfcVSlixDataFlagsHasKeyRead) ? "" : " (unset)");
|
||||||
|
}
|
||||||
|
if(features & SlixFeatureWrite) {
|
||||||
|
furi_string_cat_printf(
|
||||||
|
temp_str,
|
||||||
|
" Write %08llX%s\n",
|
||||||
|
nfc_util_bytes2num(nfcv_data->sub_data.slix.key_write, 4),
|
||||||
|
(nfcv_data->sub_data.slix.flags & NfcVSlixDataFlagsHasKeyWrite) ? "" : " (unset)");
|
||||||
|
}
|
||||||
|
if(features & SlixFeaturePrivacy) {
|
||||||
|
furi_string_cat_printf(
|
||||||
|
temp_str,
|
||||||
|
" Privacy %08llX%s\n",
|
||||||
|
nfc_util_bytes2num(nfcv_data->sub_data.slix.key_privacy, 4),
|
||||||
|
(nfcv_data->sub_data.slix.flags & NfcVSlixDataFlagsHasKeyPrivacy) ? "" : " (unset)");
|
||||||
|
furi_string_cat_printf(
|
||||||
|
temp_str,
|
||||||
|
" Privacy mode %s\n",
|
||||||
|
(nfcv_data->sub_data.slix.flags & NfcVSlixDataFlagsPrivacy) ? "ENABLED" : "DISABLED");
|
||||||
|
}
|
||||||
|
if(features & SlixFeatureDestroy) {
|
||||||
|
furi_string_cat_printf(
|
||||||
|
temp_str,
|
||||||
|
" Destroy %08llX%s\n",
|
||||||
|
nfc_util_bytes2num(nfcv_data->sub_data.slix.key_destroy, 4),
|
||||||
|
(nfcv_data->sub_data.slix.flags & NfcVSlixDataFlagsHasKeyDestroy) ? "" : " (unset)");
|
||||||
|
}
|
||||||
|
if(features & SlixFeatureEas) {
|
||||||
|
furi_string_cat_printf(
|
||||||
|
temp_str,
|
||||||
|
" EAS %08llX%s\n",
|
||||||
|
nfc_util_bytes2num(nfcv_data->sub_data.slix.key_eas, 4),
|
||||||
|
(nfcv_data->sub_data.slix.flags & NfcVSlixDataFlagsHasKeyEas) ? "" : " (unset)");
|
||||||
|
}
|
||||||
|
if(features & SlixFeatureSignature) {
|
||||||
|
furi_string_cat_printf(
|
||||||
|
temp_str,
|
||||||
|
"Signature %08llX...\n",
|
||||||
|
nfc_util_bytes2num(nfcv_data->sub_data.slix.signature, 4));
|
||||||
|
}
|
||||||
|
furi_string_cat_printf(
|
||||||
|
temp_str,
|
||||||
|
"DSFID: %02X %s\n",
|
||||||
|
nfcv_data->dsfid,
|
||||||
|
(nfcv_data->security_status[0] & NfcVLockBitDsfid) ? "(locked)" : "");
|
||||||
|
furi_string_cat_printf(
|
||||||
|
temp_str,
|
||||||
|
"AFI: %02X %s\n",
|
||||||
|
nfcv_data->afi,
|
||||||
|
(nfcv_data->security_status[0] & NfcVLockBitAfi) ? "(locked)" : "");
|
||||||
|
furi_string_cat_printf(
|
||||||
|
temp_str,
|
||||||
|
"EAS: %s\n",
|
||||||
|
(nfcv_data->security_status[0] & NfcVLockBitEas) ? "locked" : "not locked");
|
||||||
|
|
||||||
|
if(features & SlixFeatureProtection) {
|
||||||
|
furi_string_cat_printf(
|
||||||
|
temp_str,
|
||||||
|
"PPL: %s\n",
|
||||||
|
(nfcv_data->security_status[0] & NfcVLockBitPpl) ? "locked" : "not locked");
|
||||||
|
furi_string_cat_printf(temp_str, "Prot.ptr %02X\n", nfcv_data->sub_data.slix.pp_pointer);
|
||||||
|
furi_string_cat_printf(temp_str, "Prot.con %02X\n", nfcv_data->sub_data.slix.pp_condition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void nfc_scene_nfc_data_info_on_enter(void* context) {
|
void nfc_scene_nfc_data_info_on_enter(void* context) {
|
||||||
Nfc* nfc = context;
|
Nfc* nfc = context;
|
||||||
Widget* widget = nfc->widget;
|
Widget* widget = nfc->widget;
|
||||||
@@ -80,95 +157,25 @@ void nfc_scene_nfc_data_info_on_enter(void* context) {
|
|||||||
}
|
}
|
||||||
furi_string_cat_printf(temp_str, "\n");
|
furi_string_cat_printf(temp_str, "\n");
|
||||||
|
|
||||||
furi_string_cat_printf(
|
furi_string_cat_printf(temp_str, "IC Ref: %d\n", nfcv_data->ic_ref);
|
||||||
temp_str,
|
furi_string_cat_printf(temp_str, "Blocks: %d\n", nfcv_data->block_num);
|
||||||
"DSFID: %02X %s\n",
|
furi_string_cat_printf(temp_str, "Blocksize: %d\n", nfcv_data->block_size);
|
||||||
nfcv_data->dsfid,
|
|
||||||
(nfcv_data->security_status[0] & NfcVLockBitDsfid) ? "(locked)" : "");
|
|
||||||
furi_string_cat_printf(
|
|
||||||
temp_str,
|
|
||||||
"AFI: %02X %s\n",
|
|
||||||
nfcv_data->afi,
|
|
||||||
(nfcv_data->security_status[0] & NfcVLockBitAfi) ? "(locked)" : "");
|
|
||||||
furi_string_cat_printf(temp_str, "IC Ref: %02X\n", nfcv_data->ic_ref);
|
|
||||||
furi_string_cat_printf(temp_str, "Blocks: %02X\n", nfcv_data->block_num);
|
|
||||||
furi_string_cat_printf(temp_str, "Blocksize: %02X\n", nfcv_data->block_size);
|
|
||||||
|
|
||||||
switch(dev_data->nfcv_data.sub_type) {
|
switch(dev_data->nfcv_data.sub_type) {
|
||||||
case NfcVTypePlain:
|
case NfcVTypePlain:
|
||||||
furi_string_cat_printf(temp_str, "Type: Plain\n");
|
furi_string_cat_printf(temp_str, "Type: Plain\n");
|
||||||
break;
|
break;
|
||||||
case NfcVTypeSlix:
|
case NfcVTypeSlix:
|
||||||
furi_string_cat_printf(temp_str, "Type: SLIX\n");
|
nfc_scene_slix_build_string(temp_str, nfcv_data, SlixFeatureSlix, "SLIX");
|
||||||
furi_string_cat_printf(temp_str, "Keys:\n");
|
|
||||||
furi_string_cat_printf(
|
|
||||||
temp_str,
|
|
||||||
" EAS %08llX\n",
|
|
||||||
nfc_util_bytes2num(nfcv_data->sub_data.slix.key_eas, 4));
|
|
||||||
break;
|
break;
|
||||||
case NfcVTypeSlixS:
|
case NfcVTypeSlixS:
|
||||||
furi_string_cat_printf(temp_str, "Type: SLIX-S\n");
|
nfc_scene_slix_build_string(temp_str, nfcv_data, SlixFeatureSlixS, "SLIX-S");
|
||||||
furi_string_cat_printf(temp_str, "Keys:\n");
|
|
||||||
furi_string_cat_printf(
|
|
||||||
temp_str,
|
|
||||||
" Read %08llX\n",
|
|
||||||
nfc_util_bytes2num(nfcv_data->sub_data.slix.key_read, 4));
|
|
||||||
furi_string_cat_printf(
|
|
||||||
temp_str,
|
|
||||||
" Write %08llX\n",
|
|
||||||
nfc_util_bytes2num(nfcv_data->sub_data.slix.key_write, 4));
|
|
||||||
furi_string_cat_printf(
|
|
||||||
temp_str,
|
|
||||||
" Privacy %08llX\n",
|
|
||||||
nfc_util_bytes2num(nfcv_data->sub_data.slix.key_privacy, 4));
|
|
||||||
furi_string_cat_printf(
|
|
||||||
temp_str,
|
|
||||||
" Destroy %08llX\n",
|
|
||||||
nfc_util_bytes2num(nfcv_data->sub_data.slix.key_destroy, 4));
|
|
||||||
furi_string_cat_printf(
|
|
||||||
temp_str,
|
|
||||||
" EAS %08llX\n",
|
|
||||||
nfc_util_bytes2num(nfcv_data->sub_data.slix.key_eas, 4));
|
|
||||||
break;
|
break;
|
||||||
case NfcVTypeSlixL:
|
case NfcVTypeSlixL:
|
||||||
furi_string_cat_printf(temp_str, "Type: SLIX-L\n");
|
nfc_scene_slix_build_string(temp_str, nfcv_data, SlixFeatureSlixL, "SLIX-L");
|
||||||
furi_string_cat_printf(temp_str, "Keys:\n");
|
|
||||||
furi_string_cat_printf(
|
|
||||||
temp_str,
|
|
||||||
" Privacy %08llX\n",
|
|
||||||
nfc_util_bytes2num(nfcv_data->sub_data.slix.key_privacy, 4));
|
|
||||||
furi_string_cat_printf(
|
|
||||||
temp_str,
|
|
||||||
" Destroy %08llX\n",
|
|
||||||
nfc_util_bytes2num(nfcv_data->sub_data.slix.key_destroy, 4));
|
|
||||||
furi_string_cat_printf(
|
|
||||||
temp_str,
|
|
||||||
" EAS %08llX\n",
|
|
||||||
nfc_util_bytes2num(nfcv_data->sub_data.slix.key_eas, 4));
|
|
||||||
break;
|
break;
|
||||||
case NfcVTypeSlix2:
|
case NfcVTypeSlix2:
|
||||||
furi_string_cat_printf(temp_str, "Type: SLIX2\n");
|
nfc_scene_slix_build_string(temp_str, nfcv_data, SlixFeatureSlix2, "SLIX2");
|
||||||
furi_string_cat_printf(temp_str, "Keys:\n");
|
|
||||||
furi_string_cat_printf(
|
|
||||||
temp_str,
|
|
||||||
" Read %08llX\n",
|
|
||||||
nfc_util_bytes2num(nfcv_data->sub_data.slix.key_read, 4));
|
|
||||||
furi_string_cat_printf(
|
|
||||||
temp_str,
|
|
||||||
" Write %08llX\n",
|
|
||||||
nfc_util_bytes2num(nfcv_data->sub_data.slix.key_write, 4));
|
|
||||||
furi_string_cat_printf(
|
|
||||||
temp_str,
|
|
||||||
" Privacy %08llX\n",
|
|
||||||
nfc_util_bytes2num(nfcv_data->sub_data.slix.key_privacy, 4));
|
|
||||||
furi_string_cat_printf(
|
|
||||||
temp_str,
|
|
||||||
" Destroy %08llX\n",
|
|
||||||
nfc_util_bytes2num(nfcv_data->sub_data.slix.key_destroy, 4));
|
|
||||||
furi_string_cat_printf(
|
|
||||||
temp_str,
|
|
||||||
" EAS %08llX\n",
|
|
||||||
nfc_util_bytes2num(nfcv_data->sub_data.slix.key_eas, 4));
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
furi_string_cat_printf(temp_str, "\e#ISO15693 (unknown)\n");
|
furi_string_cat_printf(temp_str, "\e#ISO15693 (unknown)\n");
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ void nfc_scene_nfcv_read_success_on_enter(void* context) {
|
|||||||
Nfc* nfc = context;
|
Nfc* nfc = context;
|
||||||
NfcDeviceData* dev_data = &nfc->dev->dev_data;
|
NfcDeviceData* dev_data = &nfc->dev->dev_data;
|
||||||
FuriHalNfcDevData* nfc_data = &nfc->dev->dev_data.nfc_data;
|
FuriHalNfcDevData* nfc_data = &nfc->dev->dev_data.nfc_data;
|
||||||
NfcVData* nfcv_data = &nfc->dev->dev_data.nfcv_data;
|
|
||||||
// Setup view
|
// Setup view
|
||||||
Widget* widget = nfc->widget;
|
Widget* widget = nfc->widget;
|
||||||
widget_add_button_element(
|
widget_add_button_element(
|
||||||
@@ -46,13 +45,12 @@ void nfc_scene_nfcv_read_success_on_enter(void* context) {
|
|||||||
furi_string_cat_printf(temp_str, "\e#ISO15693 (unknown)\n");
|
furi_string_cat_printf(temp_str, "\e#ISO15693 (unknown)\n");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
furi_string_cat_printf(temp_str, "UID:");
|
furi_string_cat_printf(temp_str, "UID:\n");
|
||||||
for(size_t i = 0; i < nfc_data->uid_len; i++) {
|
for(size_t i = 0; i < nfc_data->uid_len; i++) {
|
||||||
furi_string_cat_printf(temp_str, " %02X", nfc_data->uid[i]);
|
furi_string_cat_printf(temp_str, " %02X", nfc_data->uid[i]);
|
||||||
}
|
}
|
||||||
furi_string_cat_printf(temp_str, "\n");
|
furi_string_cat_printf(temp_str, "\n");
|
||||||
furi_string_cat_printf(temp_str, "Blocks: %02X\n", nfcv_data->block_num);
|
furi_string_cat_printf(temp_str, "(see More->Info for details)\n");
|
||||||
furi_string_cat_printf(temp_str, "Blocksize: %02X\n", nfcv_data->block_size);
|
|
||||||
|
|
||||||
widget_add_text_scroll_element(widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str));
|
widget_add_text_scroll_element(widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str));
|
||||||
furi_string_free(temp_str);
|
furi_string_free(temp_str);
|
||||||
|
|||||||
@@ -1355,6 +1355,7 @@ Function,+,furi_hal_power_sleep,void,
|
|||||||
Function,+,furi_hal_power_sleep_available,_Bool,
|
Function,+,furi_hal_power_sleep_available,_Bool,
|
||||||
Function,+,furi_hal_power_suppress_charge_enter,void,
|
Function,+,furi_hal_power_suppress_charge_enter,void,
|
||||||
Function,+,furi_hal_power_suppress_charge_exit,void,
|
Function,+,furi_hal_power_suppress_charge_exit,void,
|
||||||
|
Function,+,furi_hal_pwm_is_running,_Bool,FuriHalPwmOutputId
|
||||||
Function,+,furi_hal_pwm_set_params,void,"FuriHalPwmOutputId, uint32_t, uint8_t"
|
Function,+,furi_hal_pwm_set_params,void,"FuriHalPwmOutputId, uint32_t, uint8_t"
|
||||||
Function,+,furi_hal_pwm_start,void,"FuriHalPwmOutputId, uint32_t, uint8_t"
|
Function,+,furi_hal_pwm_start,void,"FuriHalPwmOutputId, uint32_t, uint8_t"
|
||||||
Function,+,furi_hal_pwm_stop,void,FuriHalPwmOutputId
|
Function,+,furi_hal_pwm_stop,void,FuriHalPwmOutputId
|
||||||
|
|||||||
|
@@ -82,6 +82,15 @@ void furi_hal_pwm_stop(FuriHalPwmOutputId channel) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool furi_hal_pwm_is_running(FuriHalPwmOutputId channel) {
|
||||||
|
if(channel == FuriHalPwmOutputIdTim1PA7) {
|
||||||
|
return furi_hal_bus_is_enabled(FuriHalBusTIM1);
|
||||||
|
} else if(channel == FuriHalPwmOutputIdLptim2PA4) {
|
||||||
|
return furi_hal_bus_is_enabled(FuriHalBusLPTIM2);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void furi_hal_pwm_set_params(FuriHalPwmOutputId channel, uint32_t freq, uint8_t duty) {
|
void furi_hal_pwm_set_params(FuriHalPwmOutputId channel, uint32_t freq, uint8_t duty) {
|
||||||
furi_assert(freq > 0);
|
furi_assert(freq > 0);
|
||||||
uint32_t freq_div = 64000000LU / freq;
|
uint32_t freq_div = 64000000LU / freq;
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ extern "C" {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
FuriHalPwmOutputIdTim1PA7,
|
FuriHalPwmOutputIdTim1PA7,
|
||||||
@@ -37,6 +38,13 @@ void furi_hal_pwm_stop(FuriHalPwmOutputId channel);
|
|||||||
*/
|
*/
|
||||||
void furi_hal_pwm_set_params(FuriHalPwmOutputId channel, uint32_t freq, uint8_t duty);
|
void furi_hal_pwm_set_params(FuriHalPwmOutputId channel, uint32_t freq, uint8_t duty);
|
||||||
|
|
||||||
|
/** Is PWM channel running?
|
||||||
|
*
|
||||||
|
* @param[in] channel PWM channel (FuriHalPwmOutputId)
|
||||||
|
* @return bool - true if running
|
||||||
|
*/
|
||||||
|
bool furi_hal_pwm_is_running(FuriHalPwmOutputId channel);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -51,8 +51,16 @@ struct DigitalSignalInternals {
|
|||||||
#define T_TIM 1562 /* 15.625 ns *100 */
|
#define T_TIM 1562 /* 15.625 ns *100 */
|
||||||
#define T_TIM_DIV2 781 /* 15.625 ns / 2 *100 */
|
#define T_TIM_DIV2 781 /* 15.625 ns / 2 *100 */
|
||||||
|
|
||||||
|
/* end marker in DMA ringbuffer, will get written into timer register at the end */
|
||||||
|
#define SEQ_TIMER_MAX 0xFFFFFFFF
|
||||||
|
|
||||||
|
/* time to wait in loops before returning */
|
||||||
|
#define SEQ_LOCK_WAIT_MS 10UL
|
||||||
|
#define SEQ_LOCK_WAIT_TICKS (SEQ_LOCK_WAIT_MS * 1000 * 64)
|
||||||
|
|
||||||
/* maximum entry count of the sequence dma ring buffer */
|
/* maximum entry count of the sequence dma ring buffer */
|
||||||
#define SEQUENCE_DMA_RINGBUFFER_SIZE 32
|
#define RINGBUFFER_SIZE 128
|
||||||
|
|
||||||
/* maximum number of DigitalSignals in a sequence */
|
/* maximum number of DigitalSignals in a sequence */
|
||||||
#define SEQUENCE_SIGNALS_SIZE 32
|
#define SEQUENCE_SIGNALS_SIZE 32
|
||||||
/*
|
/*
|
||||||
@@ -214,12 +222,12 @@ void digital_signal_prepare_arr(DigitalSignal* signal) {
|
|||||||
for(size_t pos = 0; pos < signal->edge_cnt; pos++) {
|
for(size_t pos = 0; pos < signal->edge_cnt; pos++) {
|
||||||
uint32_t pulse_duration = signal->edge_timings[pos] + internals->reload_reg_remainder;
|
uint32_t pulse_duration = signal->edge_timings[pos] + internals->reload_reg_remainder;
|
||||||
if(pulse_duration < 10 || pulse_duration > 10000000) {
|
if(pulse_duration < 10 || pulse_duration > 10000000) {
|
||||||
/*FURI_LOG_D(
|
FURI_LOG_D(
|
||||||
TAG,
|
TAG,
|
||||||
"[prepare] pulse_duration out of range: %lu = %lu * %llu",
|
"[prepare] pulse_duration out of range: %lu = %lu * %llu",
|
||||||
pulse_duration,
|
pulse_duration,
|
||||||
signal->edge_timings[pos],
|
signal->edge_timings[pos],
|
||||||
internals->factor);*/
|
internals->factor);
|
||||||
pulse_duration = 100;
|
pulse_duration = 100;
|
||||||
}
|
}
|
||||||
uint32_t pulse_ticks = (pulse_duration + T_TIM_DIV2) / T_TIM;
|
uint32_t pulse_ticks = (pulse_duration + T_TIM_DIV2) / T_TIM;
|
||||||
@@ -243,20 +251,16 @@ static void digital_signal_stop_timer() {
|
|||||||
LL_TIM_DisableUpdateEvent(TIM2);
|
LL_TIM_DisableUpdateEvent(TIM2);
|
||||||
LL_TIM_DisableDMAReq_UPDATE(TIM2);
|
LL_TIM_DisableDMAReq_UPDATE(TIM2);
|
||||||
|
|
||||||
if(furi_hal_bus_is_enabled(FuriHalBusTIM2)) {
|
furi_hal_bus_disable(FuriHalBusTIM2);
|
||||||
furi_hal_bus_disable(FuriHalBusTIM2);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void digital_signal_setup_timer() {
|
static void digital_signal_setup_timer() {
|
||||||
if(!furi_hal_bus_is_enabled(FuriHalBusTIM2)) {
|
furi_hal_bus_enable(FuriHalBusTIM2);
|
||||||
furi_hal_bus_enable(FuriHalBusTIM2);
|
|
||||||
}
|
|
||||||
|
|
||||||
LL_TIM_SetCounterMode(TIM2, LL_TIM_COUNTERMODE_UP);
|
LL_TIM_SetCounterMode(TIM2, LL_TIM_COUNTERMODE_UP);
|
||||||
LL_TIM_SetClockDivision(TIM2, LL_TIM_CLOCKDIVISION_DIV1);
|
LL_TIM_SetClockDivision(TIM2, LL_TIM_CLOCKDIVISION_DIV1);
|
||||||
LL_TIM_SetPrescaler(TIM2, 0);
|
LL_TIM_SetPrescaler(TIM2, 0);
|
||||||
LL_TIM_SetAutoReload(TIM2, 0xFFFFFFFF);
|
LL_TIM_SetAutoReload(TIM2, SEQ_TIMER_MAX);
|
||||||
LL_TIM_SetCounter(TIM2, 0);
|
LL_TIM_SetCounter(TIM2, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -339,7 +343,7 @@ DigitalSequence* digital_sequence_alloc(uint32_t size, const GpioPin* gpio) {
|
|||||||
sequence->bake = false;
|
sequence->bake = false;
|
||||||
|
|
||||||
sequence->dma_buffer = malloc(sizeof(struct ReloadBuffer));
|
sequence->dma_buffer = malloc(sizeof(struct ReloadBuffer));
|
||||||
sequence->dma_buffer->size = SEQUENCE_DMA_RINGBUFFER_SIZE;
|
sequence->dma_buffer->size = RINGBUFFER_SIZE;
|
||||||
sequence->dma_buffer->buffer = malloc(sequence->dma_buffer->size * sizeof(uint32_t));
|
sequence->dma_buffer->buffer = malloc(sequence->dma_buffer->size * sizeof(uint32_t));
|
||||||
|
|
||||||
sequence->dma_config_gpio.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH;
|
sequence->dma_config_gpio.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH;
|
||||||
@@ -458,42 +462,26 @@ static DigitalSignal* digital_sequence_bake(DigitalSequence* sequence) {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void digital_sequence_update_pos(DigitalSequence* sequence) {
|
|
||||||
struct ReloadBuffer* dma_buffer = sequence->dma_buffer;
|
|
||||||
|
|
||||||
dma_buffer->read_pos = dma_buffer->size - LL_DMA_GetDataLength(DMA1, LL_DMA_CHANNEL_2);
|
|
||||||
}
|
|
||||||
|
|
||||||
static const uint32_t wait_ms = 10;
|
|
||||||
static const uint32_t wait_ticks = wait_ms * 1000 * 64;
|
|
||||||
|
|
||||||
static void digital_sequence_finish(DigitalSequence* sequence) {
|
static void digital_sequence_finish(DigitalSequence* sequence) {
|
||||||
struct ReloadBuffer* dma_buffer = sequence->dma_buffer;
|
struct ReloadBuffer* dma_buffer = sequence->dma_buffer;
|
||||||
|
|
||||||
if(dma_buffer->dma_active) {
|
if(dma_buffer->dma_active) {
|
||||||
uint32_t prev_timer = DWT->CYCCNT;
|
uint32_t prev_timer = DWT->CYCCNT;
|
||||||
uint32_t end_pos = (dma_buffer->write_pos + 1) % dma_buffer->size;
|
|
||||||
do {
|
do {
|
||||||
uint32_t last_pos = dma_buffer->read_pos;
|
/* we are finished, when the DMA transferred the SEQ_TIMER_MAX marker */
|
||||||
|
if(TIM2->ARR == SEQ_TIMER_MAX) {
|
||||||
digital_sequence_update_pos(sequence);
|
|
||||||
|
|
||||||
/* we are finished, when the DMA transferred the 0xFFFFFFFF-timer which is the current write_pos */
|
|
||||||
if(dma_buffer->read_pos == end_pos) {
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if(DWT->CYCCNT - prev_timer > SEQ_LOCK_WAIT_TICKS) {
|
||||||
if(last_pos != dma_buffer->read_pos) { //-V547
|
dma_buffer->read_pos =
|
||||||
prev_timer = DWT->CYCCNT;
|
RINGBUFFER_SIZE - LL_DMA_GetDataLength(DMA1, LL_DMA_CHANNEL_2);
|
||||||
}
|
FURI_LOG_D(
|
||||||
if(DWT->CYCCNT - prev_timer > wait_ticks) {
|
|
||||||
/*FURI_LOG_D(
|
|
||||||
TAG,
|
TAG,
|
||||||
"[SEQ] hung %lu ms in finish (ARR 0x%08lx, read %lu, write %lu)",
|
"[SEQ] hung %lu ms in finish (ARR 0x%08lx, read %lu, write %lu)",
|
||||||
wait_ms,
|
SEQ_LOCK_WAIT_MS,
|
||||||
TIM2->ARR,
|
TIM2->ARR,
|
||||||
dma_buffer->read_pos,
|
dma_buffer->read_pos,
|
||||||
dma_buffer->write_pos);*/
|
dma_buffer->write_pos);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} while(1);
|
} while(1);
|
||||||
@@ -508,34 +496,42 @@ static void digital_sequence_queue_pulse(DigitalSequence* sequence, uint32_t len
|
|||||||
|
|
||||||
if(dma_buffer->dma_active) {
|
if(dma_buffer->dma_active) {
|
||||||
uint32_t prev_timer = DWT->CYCCNT;
|
uint32_t prev_timer = DWT->CYCCNT;
|
||||||
uint32_t end_pos = (dma_buffer->write_pos + 1) % dma_buffer->size;
|
|
||||||
do {
|
do {
|
||||||
uint32_t last_pos = dma_buffer->read_pos;
|
dma_buffer->read_pos = RINGBUFFER_SIZE - LL_DMA_GetDataLength(DMA1, LL_DMA_CHANNEL_2);
|
||||||
digital_sequence_update_pos(sequence);
|
|
||||||
|
|
||||||
if(dma_buffer->read_pos != end_pos) {
|
uint32_t free =
|
||||||
|
(RINGBUFFER_SIZE + dma_buffer->read_pos - dma_buffer->write_pos) % RINGBUFFER_SIZE;
|
||||||
|
|
||||||
|
if(free > 2) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(last_pos != dma_buffer->read_pos) { //-V547
|
if(DWT->CYCCNT - prev_timer > SEQ_LOCK_WAIT_TICKS) {
|
||||||
prev_timer = DWT->CYCCNT;
|
FURI_LOG_D(
|
||||||
}
|
|
||||||
if(DWT->CYCCNT - prev_timer > wait_ticks) {
|
|
||||||
/*FURI_LOG_D(
|
|
||||||
TAG,
|
TAG,
|
||||||
"[SEQ] hung %lu ms in queue (ARR 0x%08lx, read %lu, write %lu)",
|
"[SEQ] hung %lu ms in queue (ARR 0x%08lx, read %lu, write %lu)",
|
||||||
wait_ms,
|
SEQ_LOCK_WAIT_MS,
|
||||||
TIM2->ARR,
|
TIM2->ARR,
|
||||||
dma_buffer->read_pos,
|
dma_buffer->read_pos,
|
||||||
dma_buffer->write_pos);*/
|
dma_buffer->write_pos);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if(TIM2->ARR == SEQ_TIMER_MAX) {
|
||||||
|
FURI_LOG_D(
|
||||||
|
TAG,
|
||||||
|
"[SEQ] buffer underrun in queue (ARR 0x%08lx, read %lu, write %lu)",
|
||||||
|
TIM2->ARR,
|
||||||
|
dma_buffer->read_pos,
|
||||||
|
dma_buffer->write_pos);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} while(1);
|
} while(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
dma_buffer->buffer[dma_buffer->write_pos] = length;
|
dma_buffer->buffer[dma_buffer->write_pos] = length;
|
||||||
dma_buffer->write_pos = (dma_buffer->write_pos + 1) % dma_buffer->size;
|
dma_buffer->write_pos++;
|
||||||
dma_buffer->buffer[dma_buffer->write_pos] = 0xFFFFFFFF;
|
dma_buffer->write_pos %= RINGBUFFER_SIZE;
|
||||||
|
dma_buffer->buffer[dma_buffer->write_pos] = SEQ_TIMER_MAX;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool digital_sequence_send(DigitalSequence* sequence) {
|
bool digital_sequence_send(DigitalSequence* sequence) {
|
||||||
@@ -557,90 +553,97 @@ bool digital_sequence_send(DigitalSequence* sequence) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t remainder = 0;
|
if(!sequence->sequence_used) {
|
||||||
bool traded_first = false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
FURI_CRITICAL_ENTER();
|
int32_t remainder = 0;
|
||||||
|
uint32_t trade_for_next = 0;
|
||||||
|
uint32_t seq_pos_next = 1;
|
||||||
|
|
||||||
dma_buffer->dma_active = false;
|
dma_buffer->dma_active = false;
|
||||||
dma_buffer->buffer[0] = 0xFFFFFFFF;
|
dma_buffer->buffer[0] = SEQ_TIMER_MAX;
|
||||||
dma_buffer->read_pos = 0;
|
dma_buffer->read_pos = 0;
|
||||||
dma_buffer->write_pos = 0;
|
dma_buffer->write_pos = 0;
|
||||||
|
|
||||||
for(uint32_t seq_pos = 0; seq_pos < sequence->sequence_used; seq_pos++) {
|
/* already prepare the current signal pointer */
|
||||||
uint8_t signal_index = sequence->sequence[seq_pos];
|
DigitalSignal* sig = sequence->signals[sequence->sequence[0]];
|
||||||
DigitalSignal* sig = sequence->signals[signal_index];
|
DigitalSignal* sig_next = NULL;
|
||||||
bool last_signal = ((seq_pos + 1) == sequence->sequence_used);
|
/* re-use the GPIO buffer from the first signal */
|
||||||
|
sequence->gpio_buff = sig->internals->gpio_buff;
|
||||||
|
|
||||||
/* all signals are prepared and we can re-use the GPIO buffer from the fist signal */
|
FURI_CRITICAL_ENTER();
|
||||||
if(seq_pos == 0) {
|
|
||||||
sequence->gpio_buff = sig->internals->gpio_buff;
|
while(sig) {
|
||||||
|
bool last_signal = (seq_pos_next >= sequence->sequence_used);
|
||||||
|
|
||||||
|
if(!last_signal) {
|
||||||
|
sig_next = sequence->signals[sequence->sequence[seq_pos_next++]];
|
||||||
}
|
}
|
||||||
|
|
||||||
for(uint32_t pulse_pos = 0; pulse_pos < sig->internals->reload_reg_entries; pulse_pos++) {
|
for(uint32_t pulse_pos = 0; pulse_pos < sig->internals->reload_reg_entries; pulse_pos++) {
|
||||||
if(traded_first) {
|
bool last_pulse = ((pulse_pos + 1) >= sig->internals->reload_reg_entries);
|
||||||
traded_first = false;
|
uint32_t pulse_length = sig->reload_reg_buff[pulse_pos] + trade_for_next;
|
||||||
continue;
|
|
||||||
}
|
|
||||||
uint32_t pulse_length = 0;
|
|
||||||
bool last_pulse = ((pulse_pos + 1) == sig->internals->reload_reg_entries);
|
|
||||||
|
|
||||||
pulse_length = sig->reload_reg_buff[pulse_pos];
|
trade_for_next = 0;
|
||||||
|
|
||||||
/* when we are too late more than half a tick, make the first edge temporarily longer */
|
/* when we are too late more than half a tick, make the first edge temporarily longer */
|
||||||
if(remainder >= T_TIM_DIV2) {
|
if(remainder >= T_TIM_DIV2) {
|
||||||
remainder -= T_TIM;
|
remainder -= T_TIM;
|
||||||
pulse_length += 1;
|
pulse_length += 1;
|
||||||
}
|
}
|
||||||
remainder += sig->internals->reload_reg_remainder;
|
|
||||||
|
|
||||||
/* last pulse in that signal and have a next signal? */
|
/* last pulse in current signal and have a next signal? */
|
||||||
if(last_pulse) {
|
if(last_pulse && sig_next) {
|
||||||
if((seq_pos + 1) < sequence->sequence_used) {
|
/* when a signal ends with the same level as the next signal begins, let the next signal generate the whole pulse.
|
||||||
DigitalSignal* sig_next = sequence->signals[sequence->sequence[seq_pos + 1]];
|
beware, we do not want the level after the last edge, but the last level before that edge */
|
||||||
|
bool end_level = sig->start_level ^ ((sig->edge_cnt % 2) == 0);
|
||||||
|
|
||||||
/* when a signal ends with the same level as the next signal begins, let the fist signal generate the whole pulse */
|
/* if they have the same level, pass the duration to the next pulse(s) */
|
||||||
/* beware, we do not want the level after the last edge, but the last level before that edge */
|
if(end_level == sig_next->start_level) {
|
||||||
bool end_level = sig->start_level ^ ((sig->edge_cnt % 2) == 0);
|
trade_for_next = pulse_length;
|
||||||
|
|
||||||
/* take from the next, add it to the current if they have the same level */
|
|
||||||
if(end_level == sig_next->start_level) {
|
|
||||||
pulse_length += sig_next->reload_reg_buff[0];
|
|
||||||
traded_first = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
digital_sequence_queue_pulse(sequence, pulse_length);
|
/* if it was decided, that the next signal's first pulse shall also handle our "length", then do not queue here */
|
||||||
|
if(!trade_for_next) {
|
||||||
|
digital_sequence_queue_pulse(sequence, pulse_length);
|
||||||
|
|
||||||
/* start transmission when buffer was filled enough */
|
if(!dma_buffer->dma_active) {
|
||||||
bool start_send = sequence->dma_buffer->write_pos >= (sequence->dma_buffer->size - 4);
|
/* start transmission when buffer was filled enough */
|
||||||
|
bool start_send = sequence->dma_buffer->write_pos >= (RINGBUFFER_SIZE - 2);
|
||||||
|
|
||||||
/* or it was the last pulse */
|
/* or it was the last pulse */
|
||||||
if(last_pulse && last_signal) {
|
if(last_pulse && last_signal) {
|
||||||
start_send = true;
|
start_send = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* start transmission */
|
/* start transmission */
|
||||||
if(start_send && !dma_buffer->dma_active) {
|
if(start_send) {
|
||||||
digital_sequence_setup_dma(sequence);
|
digital_sequence_setup_dma(sequence);
|
||||||
digital_signal_setup_timer();
|
digital_signal_setup_timer();
|
||||||
|
|
||||||
/* if the send time is specified, wait till the core timer passed beyond that time */
|
/* if the send time is specified, wait till the core timer passed beyond that time */
|
||||||
if(sequence->send_time_active) {
|
if(sequence->send_time_active) {
|
||||||
sequence->send_time_active = false;
|
sequence->send_time_active = false;
|
||||||
while(sequence->send_time - DWT->CYCCNT < 0x80000000) {
|
while(sequence->send_time - DWT->CYCCNT < 0x80000000) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
digital_signal_start_timer();
|
||||||
|
dma_buffer->dma_active = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
digital_signal_start_timer();
|
|
||||||
dma_buffer->dma_active = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
remainder += sig->internals->reload_reg_remainder;
|
||||||
|
sig = sig_next;
|
||||||
|
sig_next = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* wait until last dma transaction was finished */
|
/* wait until last dma transaction was finished */
|
||||||
digital_sequence_finish(sequence);
|
|
||||||
FURI_CRITICAL_EXIT();
|
FURI_CRITICAL_EXIT();
|
||||||
|
digital_sequence_finish(sequence);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -660,178 +660,167 @@ bool nfc_device_load_mifare_df_data(FlipperFormat* file, NfcDevice* dev) {
|
|||||||
return parsed;
|
return parsed;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool nfc_device_save_slix_data(FlipperFormat* file, NfcDevice* dev) {
|
static bool nfc_device_save_slix_data(
|
||||||
|
FlipperFormat* file,
|
||||||
|
NfcDevice* dev,
|
||||||
|
SlixTypeFeatures features,
|
||||||
|
const char* type) {
|
||||||
bool saved = false;
|
bool saved = false;
|
||||||
NfcVSlixData* data = &dev->dev_data.nfcv_data.sub_data.slix;
|
NfcVSlixData* data = &dev->dev_data.nfcv_data.sub_data.slix;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
if(!flipper_format_write_comment_cstr(file, "SLIX specific data")) break;
|
char msg[64];
|
||||||
if(!flipper_format_write_hex(file, "Password EAS", data->key_eas, sizeof(data->key_eas)))
|
snprintf(msg, sizeof(msg), "%s specific data", type);
|
||||||
|
if(!flipper_format_write_comment_cstr(file, msg)) break;
|
||||||
|
if(!flipper_format_write_comment_cstr(
|
||||||
|
file, "Passwords are optional. If password is omitted, any password is accepted"))
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
if(features & SlixFeatureRead) {
|
||||||
|
if(data->flags & NfcVSlixDataFlagsHasKeyRead) {
|
||||||
|
if(!flipper_format_write_hex(
|
||||||
|
file, "Password Read", data->key_read, sizeof(data->key_read)))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(features & SlixFeatureWrite) {
|
||||||
|
if(data->flags & NfcVSlixDataFlagsHasKeyWrite) {
|
||||||
|
if(!flipper_format_write_hex(
|
||||||
|
file, "Password Write", data->key_write, sizeof(data->key_write)))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(features & SlixFeaturePrivacy) {
|
||||||
|
if(data->flags & NfcVSlixDataFlagsHasKeyPrivacy) {
|
||||||
|
if(!flipper_format_write_hex(
|
||||||
|
file, "Password Privacy", data->key_privacy, sizeof(data->key_privacy)))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(features & SlixFeatureDestroy) {
|
||||||
|
if(data->flags & NfcVSlixDataFlagsHasKeyDestroy) {
|
||||||
|
if(!flipper_format_write_hex(
|
||||||
|
file, "Password Destroy", data->key_destroy, sizeof(data->key_destroy)))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(features & SlixFeatureEas) {
|
||||||
|
if(data->flags & NfcVSlixDataFlagsHasKeyEas) {
|
||||||
|
if(!flipper_format_write_hex(
|
||||||
|
file, "Password EAS", data->key_eas, sizeof(data->key_eas)))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(features & SlixFeatureSignature) {
|
||||||
|
if(!flipper_format_write_comment_cstr(
|
||||||
|
file,
|
||||||
|
"This is the card's secp128r1 elliptic curve signature. It can not be calculated without knowing NXP's private key."))
|
||||||
|
break;
|
||||||
|
if(!flipper_format_write_hex(
|
||||||
|
file, "Signature", data->signature, sizeof(data->signature)))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if(features & SlixFeaturePrivacy) {
|
||||||
|
bool privacy = (data->flags & NfcVSlixDataFlagsPrivacy) ? true : false;
|
||||||
|
if(!flipper_format_write_bool(file, "Privacy Mode", &privacy, 1)) break;
|
||||||
|
}
|
||||||
|
if(features & SlixFeatureProtection) {
|
||||||
|
if(!flipper_format_write_comment_cstr(file, "Protection pointer configuration")) break;
|
||||||
|
if(!flipper_format_write_hex(file, "Protection pointer", &data->pp_pointer, 1)) break;
|
||||||
|
if(!flipper_format_write_hex(file, "Protection condition", &data->pp_condition, 1))
|
||||||
|
break;
|
||||||
|
}
|
||||||
saved = true;
|
saved = true;
|
||||||
} while(false);
|
} while(false);
|
||||||
|
|
||||||
return saved;
|
return saved;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool nfc_device_load_slix_data(FlipperFormat* file, NfcDevice* dev) {
|
bool nfc_device_load_slix_data(FlipperFormat* file, NfcDevice* dev, SlixTypeFeatures features) {
|
||||||
bool parsed = false;
|
bool parsed = false;
|
||||||
NfcVSlixData* data = &dev->dev_data.nfcv_data.sub_data.slix;
|
NfcVSlixData* data = &dev->dev_data.nfcv_data.sub_data.slix;
|
||||||
memset(data, 0, sizeof(NfcVSlixData));
|
memset(data, 0, sizeof(NfcVSlixData));
|
||||||
|
|
||||||
do {
|
do {
|
||||||
if(!flipper_format_read_hex(file, "Password EAS", data->key_eas, sizeof(data->key_eas)))
|
data->flags = 0;
|
||||||
break;
|
|
||||||
|
|
||||||
parsed = true;
|
|
||||||
} while(false);
|
|
||||||
|
|
||||||
return parsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool nfc_device_save_slix_s_data(FlipperFormat* file, NfcDevice* dev) {
|
|
||||||
bool saved = false;
|
|
||||||
NfcVSlixData* data = &dev->dev_data.nfcv_data.sub_data.slix;
|
|
||||||
|
|
||||||
do {
|
|
||||||
if(!flipper_format_write_comment_cstr(file, "SLIX-S specific data")) break;
|
|
||||||
if(!flipper_format_write_hex(file, "Password Read", data->key_read, sizeof(data->key_read)))
|
|
||||||
break;
|
|
||||||
if(!flipper_format_write_hex(
|
|
||||||
file, "Password Write", data->key_write, sizeof(data->key_write)))
|
|
||||||
break;
|
|
||||||
if(!flipper_format_write_hex(
|
|
||||||
file, "Password Privacy", data->key_privacy, sizeof(data->key_privacy)))
|
|
||||||
break;
|
|
||||||
if(!flipper_format_write_hex(
|
|
||||||
file, "Password Destroy", data->key_destroy, sizeof(data->key_destroy)))
|
|
||||||
break;
|
|
||||||
if(!flipper_format_write_hex(file, "Password EAS", data->key_eas, sizeof(data->key_eas)))
|
|
||||||
break;
|
|
||||||
if(!flipper_format_write_bool(file, "Privacy Mode", &data->privacy, 1)) break;
|
|
||||||
saved = true;
|
|
||||||
} while(false);
|
|
||||||
|
|
||||||
return saved;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool nfc_device_load_slix_s_data(FlipperFormat* file, NfcDevice* dev) {
|
|
||||||
bool parsed = false;
|
|
||||||
NfcVSlixData* data = &dev->dev_data.nfcv_data.sub_data.slix;
|
|
||||||
memset(data, 0, sizeof(NfcVSlixData));
|
|
||||||
|
|
||||||
do {
|
|
||||||
if(!flipper_format_read_hex(file, "Password Read", data->key_read, sizeof(data->key_read)))
|
|
||||||
break;
|
|
||||||
if(!flipper_format_read_hex(
|
|
||||||
file, "Password Write", data->key_write, sizeof(data->key_write)))
|
|
||||||
break;
|
|
||||||
if(!flipper_format_read_hex(
|
|
||||||
file, "Password Privacy", data->key_privacy, sizeof(data->key_privacy)))
|
|
||||||
break;
|
|
||||||
if(!flipper_format_read_hex(
|
|
||||||
file, "Password Destroy", data->key_destroy, sizeof(data->key_destroy)))
|
|
||||||
break;
|
|
||||||
if(!flipper_format_read_hex(file, "Password EAS", data->key_eas, sizeof(data->key_eas)))
|
|
||||||
break;
|
|
||||||
if(!flipper_format_read_bool(file, "Privacy Mode", &data->privacy, 1)) break;
|
|
||||||
|
|
||||||
parsed = true;
|
|
||||||
} while(false);
|
|
||||||
|
|
||||||
return parsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool nfc_device_save_slix_l_data(FlipperFormat* file, NfcDevice* dev) {
|
|
||||||
bool saved = false;
|
|
||||||
NfcVSlixData* data = &dev->dev_data.nfcv_data.sub_data.slix;
|
|
||||||
|
|
||||||
do {
|
|
||||||
if(!flipper_format_write_comment_cstr(file, "SLIX-L specific data")) break;
|
|
||||||
if(!flipper_format_write_hex(
|
|
||||||
file, "Password Privacy", data->key_privacy, sizeof(data->key_privacy)))
|
|
||||||
break;
|
|
||||||
if(!flipper_format_write_hex(
|
|
||||||
file, "Password Destroy", data->key_destroy, sizeof(data->key_destroy)))
|
|
||||||
break;
|
|
||||||
if(!flipper_format_write_hex(file, "Password EAS", data->key_eas, sizeof(data->key_eas)))
|
|
||||||
break;
|
|
||||||
if(!flipper_format_write_bool(file, "Privacy Mode", &data->privacy, 1)) break;
|
|
||||||
saved = true;
|
|
||||||
} while(false);
|
|
||||||
|
|
||||||
return saved;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool nfc_device_load_slix_l_data(FlipperFormat* file, NfcDevice* dev) {
|
|
||||||
bool parsed = false;
|
|
||||||
NfcVSlixData* data = &dev->dev_data.nfcv_data.sub_data.slix;
|
|
||||||
memset(data, 0, sizeof(NfcVSlixData));
|
|
||||||
|
|
||||||
do {
|
|
||||||
if(!flipper_format_read_hex(
|
|
||||||
file, "Password Privacy", data->key_privacy, sizeof(data->key_privacy)))
|
|
||||||
break;
|
|
||||||
if(!flipper_format_read_hex(
|
|
||||||
file, "Password Destroy", data->key_destroy, sizeof(data->key_destroy)))
|
|
||||||
break;
|
|
||||||
if(!flipper_format_read_hex(file, "Password EAS", data->key_eas, sizeof(data->key_eas)))
|
|
||||||
break;
|
|
||||||
if(!flipper_format_read_bool(file, "Privacy Mode", &data->privacy, 1)) break;
|
|
||||||
|
|
||||||
parsed = true;
|
|
||||||
} while(false);
|
|
||||||
|
|
||||||
return parsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool nfc_device_save_slix2_data(FlipperFormat* file, NfcDevice* dev) {
|
|
||||||
bool saved = false;
|
|
||||||
NfcVSlixData* data = &dev->dev_data.nfcv_data.sub_data.slix;
|
|
||||||
|
|
||||||
do {
|
|
||||||
if(!flipper_format_write_comment_cstr(file, "SLIX2 specific data")) break;
|
|
||||||
if(!flipper_format_write_hex(file, "Password Read", data->key_read, sizeof(data->key_read)))
|
|
||||||
break;
|
|
||||||
if(!flipper_format_write_hex(
|
|
||||||
file, "Password Write", data->key_write, sizeof(data->key_write)))
|
|
||||||
break;
|
|
||||||
if(!flipper_format_write_hex(
|
|
||||||
file, "Password Privacy", data->key_privacy, sizeof(data->key_privacy)))
|
|
||||||
break;
|
|
||||||
if(!flipper_format_write_hex(
|
|
||||||
file, "Password Destroy", data->key_destroy, sizeof(data->key_destroy)))
|
|
||||||
break;
|
|
||||||
if(!flipper_format_write_hex(file, "Password EAS", data->key_eas, sizeof(data->key_eas)))
|
|
||||||
break;
|
|
||||||
if(!flipper_format_write_bool(file, "Privacy Mode", &data->privacy, 1)) break;
|
|
||||||
saved = true;
|
|
||||||
} while(false);
|
|
||||||
|
|
||||||
return saved;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool nfc_device_load_slix2_data(FlipperFormat* file, NfcDevice* dev) { // -V524
|
|
||||||
bool parsed = false;
|
|
||||||
NfcVSlixData* data = &dev->dev_data.nfcv_data.sub_data.slix;
|
|
||||||
memset(data, 0, sizeof(NfcVSlixData));
|
|
||||||
|
|
||||||
do {
|
|
||||||
if(!flipper_format_read_hex(file, "Password Read", data->key_read, sizeof(data->key_read)))
|
|
||||||
break;
|
|
||||||
if(!flipper_format_read_hex(
|
|
||||||
file, "Password Write", data->key_write, sizeof(data->key_write)))
|
|
||||||
break;
|
|
||||||
if(!flipper_format_read_hex(
|
|
||||||
file, "Password Privacy", data->key_privacy, sizeof(data->key_privacy)))
|
|
||||||
break;
|
|
||||||
if(!flipper_format_read_hex(
|
|
||||||
file, "Password Destroy", data->key_destroy, sizeof(data->key_destroy)))
|
|
||||||
break;
|
|
||||||
if(!flipper_format_read_hex(file, "Password EAS", data->key_eas, sizeof(data->key_eas)))
|
|
||||||
break;
|
|
||||||
if(!flipper_format_read_bool(file, "Privacy Mode", &data->privacy, 1)) break;
|
|
||||||
|
|
||||||
|
if(features & SlixFeatureRead) {
|
||||||
|
if(flipper_format_key_exist(file, "Password Read")) {
|
||||||
|
if(!flipper_format_read_hex(
|
||||||
|
file, "Password Read", data->key_read, sizeof(data->key_read))) {
|
||||||
|
FURI_LOG_D(TAG, "Failed reading Password Read");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
data->flags |= NfcVSlixDataFlagsHasKeyRead;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(features & SlixFeatureWrite) {
|
||||||
|
if(flipper_format_key_exist(file, "Password Write")) {
|
||||||
|
if(!flipper_format_read_hex(
|
||||||
|
file, "Password Write", data->key_write, sizeof(data->key_write))) {
|
||||||
|
FURI_LOG_D(TAG, "Failed reading Password Write");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
data->flags |= NfcVSlixDataFlagsHasKeyWrite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(features & SlixFeaturePrivacy) {
|
||||||
|
if(flipper_format_key_exist(file, "Password Privacy")) {
|
||||||
|
if(!flipper_format_read_hex(
|
||||||
|
file, "Password Privacy", data->key_privacy, sizeof(data->key_privacy))) {
|
||||||
|
FURI_LOG_D(TAG, "Failed reading Password Privacy");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
data->flags |= NfcVSlixDataFlagsHasKeyPrivacy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(features & SlixFeatureDestroy) {
|
||||||
|
if(flipper_format_key_exist(file, "Password Destroy")) {
|
||||||
|
if(!flipper_format_read_hex(
|
||||||
|
file, "Password Destroy", data->key_destroy, sizeof(data->key_destroy))) {
|
||||||
|
FURI_LOG_D(TAG, "Failed reading Password Destroy");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
data->flags |= NfcVSlixDataFlagsHasKeyDestroy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(features & SlixFeatureEas) {
|
||||||
|
if(flipper_format_key_exist(file, "Password EAS")) {
|
||||||
|
if(!flipper_format_read_hex(
|
||||||
|
file, "Password EAS", data->key_eas, sizeof(data->key_eas))) {
|
||||||
|
FURI_LOG_D(TAG, "Failed reading Password EAS");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
data->flags |= NfcVSlixDataFlagsHasKeyEas;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(features & SlixFeatureSignature) {
|
||||||
|
if(!flipper_format_read_hex(
|
||||||
|
file, "Signature", data->signature, sizeof(data->signature))) {
|
||||||
|
FURI_LOG_D(TAG, "Failed reading Signature");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(features & SlixFeaturePrivacy) {
|
||||||
|
bool privacy;
|
||||||
|
if(!flipper_format_read_bool(file, "Privacy Mode", &privacy, 1)) {
|
||||||
|
FURI_LOG_D(TAG, "Failed reading Privacy Mode");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if(privacy) {
|
||||||
|
data->flags |= NfcVSlixDataFlagsPrivacy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(features & SlixFeatureProtection) {
|
||||||
|
if(!flipper_format_read_hex(file, "Protection pointer", &(data->pp_pointer), 1)) {
|
||||||
|
FURI_LOG_D(TAG, "Failed reading Protection pointer");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if(!flipper_format_read_hex(file, "Protection condition", &(data->pp_condition), 1)) {
|
||||||
|
FURI_LOG_D(TAG, "Failed reading Protection condition");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
parsed = true;
|
parsed = true;
|
||||||
} while(false);
|
} while(false);
|
||||||
|
|
||||||
@@ -862,7 +851,8 @@ static bool nfc_device_save_nfcv_data(FlipperFormat* file, NfcDevice* dev) {
|
|||||||
file, "Data Content", data->data, data->block_num * data->block_size))
|
file, "Data Content", data->data, data->block_num * data->block_size))
|
||||||
break;
|
break;
|
||||||
if(!flipper_format_write_comment_cstr(
|
if(!flipper_format_write_comment_cstr(
|
||||||
file, "First byte: DSFID (0x01) / AFI (0x02) lock info, others: block lock info"))
|
file,
|
||||||
|
"First byte: DSFID (0x01) / AFI (0x02) / EAS (0x04) / PPL (0x08) lock info, others: block lock info"))
|
||||||
break;
|
break;
|
||||||
if(!flipper_format_write_hex(
|
if(!flipper_format_write_hex(
|
||||||
file, "Security Status", data->security_status, 1 + data->block_num))
|
file, "Security Status", data->security_status, 1 + data->block_num))
|
||||||
@@ -880,16 +870,16 @@ static bool nfc_device_save_nfcv_data(FlipperFormat* file, NfcDevice* dev) {
|
|||||||
saved = true;
|
saved = true;
|
||||||
break;
|
break;
|
||||||
case NfcVTypeSlix:
|
case NfcVTypeSlix:
|
||||||
saved = nfc_device_save_slix_data(file, dev);
|
saved = nfc_device_save_slix_data(file, dev, SlixFeatureSlix, "SLIX");
|
||||||
break;
|
break;
|
||||||
case NfcVTypeSlixS:
|
case NfcVTypeSlixS:
|
||||||
saved = nfc_device_save_slix_s_data(file, dev);
|
saved = nfc_device_save_slix_data(file, dev, SlixFeatureSlixS, "SLIX-S");
|
||||||
break;
|
break;
|
||||||
case NfcVTypeSlixL:
|
case NfcVTypeSlixL:
|
||||||
saved = nfc_device_save_slix_l_data(file, dev);
|
saved = nfc_device_save_slix_data(file, dev, SlixFeatureSlixL, "SLIX-L");
|
||||||
break;
|
break;
|
||||||
case NfcVTypeSlix2:
|
case NfcVTypeSlix2:
|
||||||
saved = nfc_device_save_slix2_data(file, dev);
|
saved = nfc_device_save_slix_data(file, dev, SlixFeatureSlix2, "SLIX2");
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
@@ -909,23 +899,45 @@ bool nfc_device_load_nfcv_data(FlipperFormat* file, NfcDevice* dev) {
|
|||||||
uint32_t temp_uint32 = 0;
|
uint32_t temp_uint32 = 0;
|
||||||
uint8_t temp_value = 0;
|
uint8_t temp_value = 0;
|
||||||
|
|
||||||
if(!flipper_format_read_hex(file, "DSFID", &(data->dsfid), 1)) break;
|
if(!flipper_format_read_hex(file, "DSFID", &(data->dsfid), 1)) {
|
||||||
if(!flipper_format_read_hex(file, "AFI", &(data->afi), 1)) break;
|
FURI_LOG_D(TAG, "Failed reading DSFID");
|
||||||
if(!flipper_format_read_hex(file, "IC Reference", &(data->ic_ref), 1)) break;
|
|
||||||
if(!flipper_format_read_uint32(file, "Block Count", &temp_uint32, 1)) break;
|
|
||||||
data->block_num = temp_uint32;
|
|
||||||
if(!flipper_format_read_hex(file, "Block Size", &(data->block_size), 1)) break;
|
|
||||||
if(!flipper_format_read_hex(
|
|
||||||
file, "Data Content", data->data, data->block_num * data->block_size))
|
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
if(!flipper_format_read_hex(file, "AFI", &(data->afi), 1)) {
|
||||||
|
FURI_LOG_D(TAG, "Failed reading AFI");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if(!flipper_format_read_hex(file, "IC Reference", &(data->ic_ref), 1)) {
|
||||||
|
FURI_LOG_D(TAG, "Failed reading IC Reference");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if(!flipper_format_read_uint32(file, "Block Count", &temp_uint32, 1)) {
|
||||||
|
FURI_LOG_D(TAG, "Failed reading Block Count");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
data->block_num = temp_uint32;
|
||||||
|
if(!flipper_format_read_hex(file, "Block Size", &(data->block_size), 1)) {
|
||||||
|
FURI_LOG_D(TAG, "Failed reading Block Size");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if(!flipper_format_read_hex(
|
||||||
|
file, "Data Content", data->data, data->block_num * data->block_size)) {
|
||||||
|
FURI_LOG_D(TAG, "Failed reading Data Content");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
/* optional, as added later */
|
/* optional, as added later */
|
||||||
if(flipper_format_key_exist(file, "Security Status")) {
|
if(flipper_format_key_exist(file, "Security Status")) {
|
||||||
if(!flipper_format_read_hex(
|
if(!flipper_format_read_hex(
|
||||||
file, "Security Status", data->security_status, 1 + data->block_num))
|
file, "Security Status", data->security_status, 1 + data->block_num)) {
|
||||||
|
FURI_LOG_D(TAG, "Failed reading Security Status");
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!flipper_format_read_hex(file, "Subtype", &temp_value, 1)) {
|
||||||
|
FURI_LOG_D(TAG, "Failed reading Subtype");
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
if(!flipper_format_read_hex(file, "Subtype", &temp_value, 1)) break;
|
|
||||||
data->sub_type = temp_value;
|
data->sub_type = temp_value;
|
||||||
|
|
||||||
switch(data->sub_type) {
|
switch(data->sub_type) {
|
||||||
@@ -933,16 +945,16 @@ bool nfc_device_load_nfcv_data(FlipperFormat* file, NfcDevice* dev) {
|
|||||||
parsed = true;
|
parsed = true;
|
||||||
break;
|
break;
|
||||||
case NfcVTypeSlix:
|
case NfcVTypeSlix:
|
||||||
parsed = nfc_device_load_slix_data(file, dev);
|
parsed = nfc_device_load_slix_data(file, dev, SlixFeatureSlix);
|
||||||
break;
|
break;
|
||||||
case NfcVTypeSlixS:
|
case NfcVTypeSlixS:
|
||||||
parsed = nfc_device_load_slix_s_data(file, dev);
|
parsed = nfc_device_load_slix_data(file, dev, SlixFeatureSlixS);
|
||||||
break;
|
break;
|
||||||
case NfcVTypeSlixL:
|
case NfcVTypeSlixL:
|
||||||
parsed = nfc_device_load_slix_l_data(file, dev);
|
parsed = nfc_device_load_slix_data(file, dev, SlixFeatureSlixL);
|
||||||
break;
|
break;
|
||||||
case NfcVTypeSlix2:
|
case NfcVTypeSlix2:
|
||||||
parsed = nfc_device_load_slix2_data(file, dev);
|
parsed = nfc_device_load_slix_data(file, dev, SlixFeatureSlix2);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -1085,14 +1085,14 @@ void nfc_worker_mf_classic_dict_attack(NfcWorker* nfc_worker) {
|
|||||||
deactivated = true;
|
deactivated = true;
|
||||||
} else {
|
} else {
|
||||||
// If the key A is marked as found and matches the searching key, invalidate it
|
// If the key A is marked as found and matches the searching key, invalidate it
|
||||||
uint8_t found_key[6];
|
MfClassicSectorTrailer* sec_trailer =
|
||||||
memcpy(found_key, data->block[i].value, 6);
|
mf_classic_get_sector_trailer_by_sector(data, i);
|
||||||
|
|
||||||
uint8_t current_key[6];
|
uint8_t current_key[6];
|
||||||
memcpy(current_key, &key, 6);
|
memcpy(current_key, &key, 6);
|
||||||
|
|
||||||
if(mf_classic_is_key_found(data, i, MfClassicKeyA) &&
|
if(mf_classic_is_key_found(data, i, MfClassicKeyA) &&
|
||||||
memcmp(found_key, current_key, 6) == 0) {
|
memcmp(sec_trailer->key_a, current_key, 6) == 0) {
|
||||||
mf_classic_set_key_not_found(data, i, MfClassicKeyA);
|
mf_classic_set_key_not_found(data, i, MfClassicKeyA);
|
||||||
is_key_a_found = false;
|
is_key_a_found = false;
|
||||||
FURI_LOG_D(TAG, "Key %dA not found in attack", i);
|
FURI_LOG_D(TAG, "Key %dA not found in attack", i);
|
||||||
@@ -1111,14 +1111,14 @@ void nfc_worker_mf_classic_dict_attack(NfcWorker* nfc_worker) {
|
|||||||
deactivated = true;
|
deactivated = true;
|
||||||
} else {
|
} else {
|
||||||
// If the key B is marked as found and matches the searching key, invalidate it
|
// If the key B is marked as found and matches the searching key, invalidate it
|
||||||
uint8_t found_key[6];
|
MfClassicSectorTrailer* sec_trailer =
|
||||||
memcpy(found_key, data->block[i].value + 10, 6);
|
mf_classic_get_sector_trailer_by_sector(data, i);
|
||||||
|
|
||||||
uint8_t current_key[6];
|
uint8_t current_key[6];
|
||||||
memcpy(current_key, &key, 6);
|
memcpy(current_key, &key, 6);
|
||||||
|
|
||||||
if(mf_classic_is_key_found(data, i, MfClassicKeyB) &&
|
if(mf_classic_is_key_found(data, i, MfClassicKeyB) &&
|
||||||
memcmp(found_key, current_key, 6) == 0) {
|
memcmp(sec_trailer->key_b, current_key, 6) == 0) {
|
||||||
mf_classic_set_key_not_found(data, i, MfClassicKeyB);
|
mf_classic_set_key_not_found(data, i, MfClassicKeyB);
|
||||||
is_key_b_found = false;
|
is_key_b_found = false;
|
||||||
FURI_LOG_D(TAG, "Key %dB not found in attack", i);
|
FURI_LOG_D(TAG, "Key %dB not found in attack", i);
|
||||||
|
|||||||
@@ -149,12 +149,18 @@ bool nfcv_read_card(NfcVReader* reader, FuriHalNfcDevData* nfc_data, NfcVData* n
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* clear all know sub type data before reading them */
|
||||||
|
memset(&nfcv_data->sub_data, 0x00, sizeof(nfcv_data->sub_data));
|
||||||
|
|
||||||
if(slix_check_card_type(nfc_data)) {
|
if(slix_check_card_type(nfc_data)) {
|
||||||
FURI_LOG_I(TAG, "NXP SLIX detected");
|
FURI_LOG_I(TAG, "NXP SLIX detected");
|
||||||
nfcv_data->sub_type = NfcVTypeSlix;
|
nfcv_data->sub_type = NfcVTypeSlix;
|
||||||
} else if(slix2_check_card_type(nfc_data)) {
|
} else if(slix2_check_card_type(nfc_data)) {
|
||||||
FURI_LOG_I(TAG, "NXP SLIX2 detected");
|
FURI_LOG_I(TAG, "NXP SLIX2 detected");
|
||||||
nfcv_data->sub_type = NfcVTypeSlix2;
|
nfcv_data->sub_type = NfcVTypeSlix2;
|
||||||
|
if(slix2_read_custom(nfc_data, nfcv_data) != ERR_NONE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
} else if(slix_s_check_card_type(nfc_data)) {
|
} else if(slix_s_check_card_type(nfc_data)) {
|
||||||
FURI_LOG_I(TAG, "NXP SLIX-S detected");
|
FURI_LOG_I(TAG, "NXP SLIX-S detected");
|
||||||
nfcv_data->sub_type = NfcVTypeSlixS;
|
nfcv_data->sub_type = NfcVTypeSlixS;
|
||||||
@@ -612,9 +618,34 @@ void nfcv_emu_handle_packet(
|
|||||||
|
|
||||||
if(ctx->flags & NFCV_REQ_FLAG_AFI) {
|
if(ctx->flags & NFCV_REQ_FLAG_AFI) {
|
||||||
uint8_t afi = nfcv_data->frame[ctx->payload_offset];
|
uint8_t afi = nfcv_data->frame[ctx->payload_offset];
|
||||||
if(afi == nfcv_data->afi) {
|
|
||||||
respond = true;
|
uint8_t family = (afi & 0xF0);
|
||||||
|
uint8_t subfamily = (afi & 0x0F);
|
||||||
|
|
||||||
|
if(family) {
|
||||||
|
if(subfamily) {
|
||||||
|
/* selected family and subfamily only */
|
||||||
|
if(afi == nfcv_data->afi) {
|
||||||
|
respond = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* selected family, any subfamily */
|
||||||
|
if(family == (nfcv_data->afi & 0xf0)) {
|
||||||
|
respond = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if(subfamily) {
|
||||||
|
/* proprietary subfamily only */
|
||||||
|
if(afi == nfcv_data->afi) {
|
||||||
|
respond = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* all families and subfamilies */
|
||||||
|
respond = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
respond = true;
|
respond = true;
|
||||||
}
|
}
|
||||||
@@ -740,13 +771,19 @@ void nfcv_emu_handle_packet(
|
|||||||
case NFCV_CMD_READ_MULTI_BLOCK:
|
case NFCV_CMD_READ_MULTI_BLOCK:
|
||||||
case NFCV_CMD_READ_BLOCK: {
|
case NFCV_CMD_READ_BLOCK: {
|
||||||
uint8_t block = nfcv_data->frame[ctx->payload_offset];
|
uint8_t block = nfcv_data->frame[ctx->payload_offset];
|
||||||
uint8_t blocks = 1;
|
int blocks = 1;
|
||||||
|
|
||||||
if(ctx->command == NFCV_CMD_READ_MULTI_BLOCK) {
|
if(ctx->command == NFCV_CMD_READ_MULTI_BLOCK) {
|
||||||
blocks = nfcv_data->frame[ctx->payload_offset + 1] + 1;
|
blocks = nfcv_data->frame[ctx->payload_offset + 1] + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(block + blocks <= nfcv_data->block_num) {
|
/* limit the maximum block count, underflow accepted */
|
||||||
|
if(block + blocks > nfcv_data->block_num) {
|
||||||
|
blocks = nfcv_data->block_num - block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* only respond with the valid blocks, if there are any */
|
||||||
|
if(blocks > 0) {
|
||||||
uint8_t buffer_pos = 0;
|
uint8_t buffer_pos = 0;
|
||||||
|
|
||||||
ctx->response_buffer[buffer_pos++] = NFCV_NOERROR;
|
ctx->response_buffer[buffer_pos++] = NFCV_NOERROR;
|
||||||
@@ -773,10 +810,13 @@ void nfcv_emu_handle_packet(
|
|||||||
ctx->response_flags,
|
ctx->response_flags,
|
||||||
ctx->send_time);
|
ctx->send_time);
|
||||||
} else {
|
} else {
|
||||||
ctx->response_buffer[0] = NFCV_RES_FLAG_ERROR;
|
/* reply with an error only in addressed or selected mode */
|
||||||
ctx->response_buffer[1] = NFCV_ERROR_GENERIC;
|
if(ctx->addressed || ctx->selected) {
|
||||||
nfcv_emu_send(
|
ctx->response_buffer[0] = NFCV_RES_FLAG_ERROR;
|
||||||
tx_rx, nfcv_data, ctx->response_buffer, 2, ctx->response_flags, ctx->send_time);
|
ctx->response_buffer[1] = NFCV_ERROR_GENERIC;
|
||||||
|
nfcv_emu_send(
|
||||||
|
tx_rx, nfcv_data, ctx->response_buffer, 2, ctx->response_flags, ctx->send_time);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "READ BLOCK %d", block);
|
snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "READ BLOCK %d", block);
|
||||||
|
|
||||||
|
|||||||
@@ -139,8 +139,10 @@ typedef enum {
|
|||||||
} NfcVErrorcodes;
|
} NfcVErrorcodes;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
NfcVLockBitDsfid = 1,
|
NfcVLockBitDsfid = 1 << 0,
|
||||||
NfcVLockBitAfi = 2,
|
NfcVLockBitAfi = 1 << 1,
|
||||||
|
NfcVLockBitEas = 1 << 2,
|
||||||
|
NfcVLockBitPpl = 1 << 3,
|
||||||
} NfcVLockBits;
|
} NfcVLockBits;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
@@ -168,14 +170,55 @@ typedef enum {
|
|||||||
NfcVSendFlagsHighRate = 1 << 4
|
NfcVSendFlagsHighRate = 1 << 4
|
||||||
} NfcVSendFlags;
|
} NfcVSendFlags;
|
||||||
|
|
||||||
|
/* SLIX specific config flags */
|
||||||
|
typedef enum {
|
||||||
|
NfcVSlixDataFlagsNone = 0,
|
||||||
|
NfcVSlixDataFlagsHasKeyRead = 1 << 0,
|
||||||
|
NfcVSlixDataFlagsHasKeyWrite = 1 << 1,
|
||||||
|
NfcVSlixDataFlagsHasKeyPrivacy = 1 << 2,
|
||||||
|
NfcVSlixDataFlagsHasKeyDestroy = 1 << 3,
|
||||||
|
NfcVSlixDataFlagsHasKeyEas = 1 << 4,
|
||||||
|
NfcVSlixDataFlagsValidKeyRead = 1 << 8,
|
||||||
|
NfcVSlixDataFlagsValidKeyWrite = 1 << 9,
|
||||||
|
NfcVSlixDataFlagsValidKeyPrivacy = 1 << 10,
|
||||||
|
NfcVSlixDataFlagsValidKeyDestroy = 1 << 11,
|
||||||
|
NfcVSlixDataFlagsValidKeyEas = 1 << 12,
|
||||||
|
NfcVSlixDataFlagsPrivacy = 1 << 16,
|
||||||
|
NfcVSlixDataFlagsDestroyed = 1 << 17
|
||||||
|
} NfcVSlixDataFlags;
|
||||||
|
|
||||||
|
/* abstract the file read/write operations for all SLIX types to reduce duplicated code */
|
||||||
|
typedef enum {
|
||||||
|
SlixFeatureRead = 1 << 0,
|
||||||
|
SlixFeatureWrite = 1 << 1,
|
||||||
|
SlixFeaturePrivacy = 1 << 2,
|
||||||
|
SlixFeatureDestroy = 1 << 3,
|
||||||
|
SlixFeatureEas = 1 << 4,
|
||||||
|
SlixFeatureSignature = 1 << 5,
|
||||||
|
SlixFeatureProtection = 1 << 6,
|
||||||
|
|
||||||
|
SlixFeatureSlix = SlixFeatureEas,
|
||||||
|
SlixFeatureSlixS =
|
||||||
|
(SlixFeatureRead | SlixFeatureWrite | SlixFeaturePrivacy | SlixFeatureDestroy |
|
||||||
|
SlixFeatureEas),
|
||||||
|
SlixFeatureSlixL = (SlixFeaturePrivacy | SlixFeatureDestroy | SlixFeatureEas),
|
||||||
|
SlixFeatureSlix2 =
|
||||||
|
(SlixFeatureRead | SlixFeatureWrite | SlixFeaturePrivacy | SlixFeatureDestroy |
|
||||||
|
SlixFeatureEas | SlixFeatureSignature | SlixFeatureProtection),
|
||||||
|
} SlixTypeFeatures;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
uint32_t flags;
|
||||||
uint8_t key_read[4];
|
uint8_t key_read[4];
|
||||||
uint8_t key_write[4];
|
uint8_t key_write[4];
|
||||||
uint8_t key_privacy[4];
|
uint8_t key_privacy[4];
|
||||||
uint8_t key_destroy[4];
|
uint8_t key_destroy[4];
|
||||||
uint8_t key_eas[4];
|
uint8_t key_eas[4];
|
||||||
uint8_t rand[2];
|
uint8_t rand[2];
|
||||||
bool privacy;
|
uint8_t signature[32];
|
||||||
|
/* SLIX2 options */
|
||||||
|
uint8_t pp_pointer;
|
||||||
|
uint8_t pp_condition;
|
||||||
} NfcVSlixData;
|
} NfcVSlixData;
|
||||||
|
|
||||||
typedef union {
|
typedef union {
|
||||||
|
|||||||
@@ -9,6 +9,120 @@
|
|||||||
|
|
||||||
#define TAG "SLIX"
|
#define TAG "SLIX"
|
||||||
|
|
||||||
|
ReturnCode slix2_read_nxp_sysinfo(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data) {
|
||||||
|
furi_assert(nfc_data);
|
||||||
|
furi_assert(nfcv_data);
|
||||||
|
|
||||||
|
uint8_t rxBuf[32];
|
||||||
|
uint16_t received = 0;
|
||||||
|
ReturnCode ret = ERR_NONE;
|
||||||
|
|
||||||
|
FURI_LOG_D(TAG, "Read NXP SYSTEM INFORMATION...");
|
||||||
|
|
||||||
|
for(int tries = 0; tries < NFCV_COMMAND_RETRIES; tries++) {
|
||||||
|
uint8_t cmd[] = {};
|
||||||
|
uint8_t uid[NFCV_UID_LENGTH];
|
||||||
|
|
||||||
|
/* UID is stored reversed in requests */
|
||||||
|
for(int pos = 0; pos < nfc_data->uid_len; pos++) {
|
||||||
|
uid[pos] = nfc_data->uid[nfc_data->uid_len - 1 - pos];
|
||||||
|
}
|
||||||
|
|
||||||
|
ReturnCode ret = rfalNfcvPollerTransceiveReq(
|
||||||
|
NFCV_CMD_NXP_GET_NXP_SYSTEM_INFORMATION,
|
||||||
|
RFAL_NFCV_REQ_FLAG_DEFAULT,
|
||||||
|
NFCV_MANUFACTURER_NXP,
|
||||||
|
uid,
|
||||||
|
cmd,
|
||||||
|
sizeof(cmd),
|
||||||
|
rxBuf,
|
||||||
|
sizeof(rxBuf),
|
||||||
|
&received);
|
||||||
|
|
||||||
|
if(ret == ERR_NONE) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(ret != ERR_NONE || received != 8) {
|
||||||
|
FURI_LOG_D(TAG, "Failed: %d, %d", ret, received);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
FURI_LOG_D(TAG, "Success...");
|
||||||
|
|
||||||
|
NfcVSlixData* slix = &nfcv_data->sub_data.slix;
|
||||||
|
slix->pp_pointer = rxBuf[1];
|
||||||
|
slix->pp_condition = rxBuf[2];
|
||||||
|
|
||||||
|
/* convert NXP's to our internal lock bits format */
|
||||||
|
nfcv_data->security_status[0] = 0;
|
||||||
|
nfcv_data->security_status[0] |= (rxBuf[3] & SlixLockBitDsfid) ? NfcVLockBitDsfid : 0;
|
||||||
|
nfcv_data->security_status[0] |= (rxBuf[3] & SlixLockBitAfi) ? NfcVLockBitAfi : 0;
|
||||||
|
nfcv_data->security_status[0] |= (rxBuf[3] & SlixLockBitEas) ? NfcVLockBitEas : 0;
|
||||||
|
nfcv_data->security_status[0] |= (rxBuf[3] & SlixLockBitPpl) ? NfcVLockBitPpl : 0;
|
||||||
|
|
||||||
|
return ERR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
ReturnCode slix2_read_signature(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data) {
|
||||||
|
furi_assert(nfc_data);
|
||||||
|
furi_assert(nfcv_data);
|
||||||
|
|
||||||
|
uint8_t rxBuf[64];
|
||||||
|
uint16_t received = 0;
|
||||||
|
ReturnCode ret = ERR_NONE;
|
||||||
|
|
||||||
|
FURI_LOG_D(TAG, "Read SIGNATURE...");
|
||||||
|
|
||||||
|
for(int tries = 0; tries < NFCV_COMMAND_RETRIES; tries++) {
|
||||||
|
uint8_t cmd[] = {};
|
||||||
|
uint8_t uid[NFCV_UID_LENGTH];
|
||||||
|
|
||||||
|
/* UID is stored reversed in requests */
|
||||||
|
for(int pos = 0; pos < nfc_data->uid_len; pos++) {
|
||||||
|
uid[pos] = nfc_data->uid[nfc_data->uid_len - 1 - pos];
|
||||||
|
}
|
||||||
|
|
||||||
|
ReturnCode ret = rfalNfcvPollerTransceiveReq(
|
||||||
|
NFCV_CMD_NXP_READ_SIGNATURE,
|
||||||
|
RFAL_NFCV_REQ_FLAG_DEFAULT,
|
||||||
|
NFCV_MANUFACTURER_NXP,
|
||||||
|
uid,
|
||||||
|
cmd,
|
||||||
|
sizeof(cmd),
|
||||||
|
rxBuf,
|
||||||
|
sizeof(rxBuf),
|
||||||
|
&received);
|
||||||
|
|
||||||
|
if(ret == ERR_NONE) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(ret != ERR_NONE || received != 33) {
|
||||||
|
FURI_LOG_D(TAG, "Failed: %d, %d", ret, received);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
FURI_LOG_D(TAG, "Success...");
|
||||||
|
|
||||||
|
NfcVSlixData* slix = &nfcv_data->sub_data.slix;
|
||||||
|
memcpy(slix->signature, &rxBuf[1], 32);
|
||||||
|
|
||||||
|
return ERR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
ReturnCode slix2_read_custom(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data) {
|
||||||
|
ReturnCode ret = ERR_NONE;
|
||||||
|
|
||||||
|
ret = slix2_read_nxp_sysinfo(nfc_data, nfcv_data);
|
||||||
|
if(ret != ERR_NONE) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
ret = slix2_read_signature(nfc_data, nfcv_data);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
static uint32_t slix_read_be(uint8_t* data, uint32_t length) {
|
static uint32_t slix_read_be(uint8_t* data, uint32_t length) {
|
||||||
uint32_t value = 0;
|
uint32_t value = 0;
|
||||||
|
|
||||||
@@ -137,6 +251,43 @@ ReturnCode slix_unlock(NfcVData* data, uint32_t password_id) {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void slix_generic_pass_infos(
|
||||||
|
uint8_t password_id,
|
||||||
|
NfcVSlixData* slix,
|
||||||
|
uint8_t** password,
|
||||||
|
uint32_t* flag_valid,
|
||||||
|
uint32_t* flag_set) {
|
||||||
|
switch(password_id) {
|
||||||
|
case SLIX_PASS_READ:
|
||||||
|
*password = slix->key_read;
|
||||||
|
*flag_valid = NfcVSlixDataFlagsValidKeyRead;
|
||||||
|
*flag_set = NfcVSlixDataFlagsHasKeyRead;
|
||||||
|
break;
|
||||||
|
case SLIX_PASS_WRITE:
|
||||||
|
*password = slix->key_write;
|
||||||
|
*flag_valid = NfcVSlixDataFlagsValidKeyWrite;
|
||||||
|
*flag_set = NfcVSlixDataFlagsHasKeyWrite;
|
||||||
|
break;
|
||||||
|
case SLIX_PASS_PRIVACY:
|
||||||
|
*password = slix->key_privacy;
|
||||||
|
*flag_valid = NfcVSlixDataFlagsValidKeyPrivacy;
|
||||||
|
*flag_set = NfcVSlixDataFlagsHasKeyPrivacy;
|
||||||
|
break;
|
||||||
|
case SLIX_PASS_DESTROY:
|
||||||
|
*password = slix->key_destroy;
|
||||||
|
*flag_valid = NfcVSlixDataFlagsValidKeyDestroy;
|
||||||
|
*flag_set = NfcVSlixDataFlagsHasKeyDestroy;
|
||||||
|
break;
|
||||||
|
case SLIX_PASS_EASAFI:
|
||||||
|
*password = slix->key_eas;
|
||||||
|
*flag_valid = NfcVSlixDataFlagsValidKeyEas;
|
||||||
|
*flag_set = NfcVSlixDataFlagsHasKeyEas;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool slix_generic_protocol_filter(
|
bool slix_generic_protocol_filter(
|
||||||
FuriHalNfcTxRxContext* tx_rx,
|
FuriHalNfcTxRxContext* tx_rx,
|
||||||
FuriHalNfcDevData* nfc_data,
|
FuriHalNfcDevData* nfc_data,
|
||||||
@@ -150,7 +301,8 @@ bool slix_generic_protocol_filter(
|
|||||||
NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx;
|
NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx;
|
||||||
NfcVSlixData* slix = &nfcv_data->sub_data.slix;
|
NfcVSlixData* slix = &nfcv_data->sub_data.slix;
|
||||||
|
|
||||||
if(slix->privacy && ctx->command != NFCV_CMD_NXP_GET_RANDOM_NUMBER &&
|
if((slix->flags & NfcVSlixDataFlagsPrivacy) &&
|
||||||
|
ctx->command != NFCV_CMD_NXP_GET_RANDOM_NUMBER &&
|
||||||
ctx->command != NFCV_CMD_NXP_SET_PASSWORD) {
|
ctx->command != NFCV_CMD_NXP_SET_PASSWORD) {
|
||||||
snprintf(
|
snprintf(
|
||||||
nfcv_data->last_command,
|
nfcv_data->last_command,
|
||||||
@@ -186,66 +338,73 @@ bool slix_generic_protocol_filter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
case NFCV_CMD_NXP_SET_PASSWORD: {
|
case NFCV_CMD_NXP_SET_PASSWORD: {
|
||||||
|
/* the password to be set is the first parameter */
|
||||||
uint8_t password_id = nfcv_data->frame[ctx->payload_offset];
|
uint8_t password_id = nfcv_data->frame[ctx->payload_offset];
|
||||||
|
/* right after that is the XORed password */
|
||||||
|
uint8_t* password_xored = &nfcv_data->frame[ctx->payload_offset + 1];
|
||||||
|
|
||||||
|
/* only handle if the password type is supported */
|
||||||
if(!(password_id & password_supported)) {
|
if(!(password_id & password_supported)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t* password_xored = &nfcv_data->frame[ctx->payload_offset + 1];
|
/* fetch the last RAND value */
|
||||||
uint8_t* rand = slix->rand;
|
uint8_t* rand = slix->rand;
|
||||||
uint8_t* password = NULL;
|
|
||||||
|
/* first calc the password that has been sent */
|
||||||
uint8_t password_rcv[4];
|
uint8_t password_rcv[4];
|
||||||
|
for(int pos = 0; pos < 4; pos++) {
|
||||||
switch(password_id) {
|
password_rcv[pos] = password_xored[3 - pos] ^ rand[pos % 2];
|
||||||
case SLIX_PASS_READ:
|
|
||||||
password = slix->key_read;
|
|
||||||
break;
|
|
||||||
case SLIX_PASS_WRITE:
|
|
||||||
password = slix->key_write;
|
|
||||||
break;
|
|
||||||
case SLIX_PASS_PRIVACY:
|
|
||||||
password = slix->key_privacy;
|
|
||||||
break;
|
|
||||||
case SLIX_PASS_DESTROY:
|
|
||||||
password = slix->key_destroy;
|
|
||||||
break;
|
|
||||||
case SLIX_PASS_EASAFI:
|
|
||||||
password = slix->key_eas;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
uint32_t pass_received = slix_read_be(password_rcv, 4);
|
||||||
|
|
||||||
|
/* then determine the password type (or even update if not set yet) */
|
||||||
|
uint8_t* password = NULL;
|
||||||
|
uint32_t flag_valid = 0;
|
||||||
|
uint32_t flag_set = 0;
|
||||||
|
|
||||||
|
slix_generic_pass_infos(password_id, slix, &password, &flag_valid, &flag_set);
|
||||||
|
|
||||||
|
/* when the password is not supported, return silently */
|
||||||
if(!password) {
|
if(!password) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
for(int pos = 0; pos < 4; pos++) {
|
/* check if the password is known */
|
||||||
password_rcv[pos] = password_xored[3 - pos] ^ rand[pos % 2];
|
bool pass_valid = false;
|
||||||
}
|
uint32_t pass_expect = 0;
|
||||||
uint32_t pass_expect = slix_read_be(password, 4);
|
|
||||||
uint32_t pass_received = slix_read_be(password_rcv, 4);
|
|
||||||
|
|
||||||
/* if the password is all-zeroes, just accept any password*/
|
if(slix->flags & flag_set) {
|
||||||
if(!pass_expect || pass_expect == pass_received) {
|
/* if so, fetch the stored password and compare */
|
||||||
|
pass_expect = slix_read_be(password, 4);
|
||||||
|
pass_valid = (pass_expect == pass_received);
|
||||||
|
} else {
|
||||||
|
/* if not known, just accept it and store that password */
|
||||||
|
memcpy(password, password_rcv, 4);
|
||||||
|
nfcv_data->modified = true;
|
||||||
|
slix->flags |= flag_set;
|
||||||
|
|
||||||
|
pass_valid = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* if the pass was valid or accepted for other reasons, continue */
|
||||||
|
if(pass_valid) {
|
||||||
|
slix->flags |= flag_valid;
|
||||||
|
|
||||||
|
/* handle actions when a correct password was given, aside of setting the flag */
|
||||||
switch(password_id) {
|
switch(password_id) {
|
||||||
case SLIX_PASS_READ:
|
|
||||||
break;
|
|
||||||
case SLIX_PASS_WRITE:
|
|
||||||
break;
|
|
||||||
case SLIX_PASS_PRIVACY:
|
case SLIX_PASS_PRIVACY:
|
||||||
slix->privacy = false;
|
slix->flags &= ~NfcVSlixDataFlagsPrivacy;
|
||||||
nfcv_data->modified = true;
|
nfcv_data->modified = true;
|
||||||
break;
|
break;
|
||||||
case SLIX_PASS_DESTROY:
|
case SLIX_PASS_DESTROY:
|
||||||
|
slix->flags |= NfcVSlixDataFlagsDestroyed;
|
||||||
FURI_LOG_D(TAG, "Pooof! Got destroyed");
|
FURI_LOG_D(TAG, "Pooof! Got destroyed");
|
||||||
break;
|
break;
|
||||||
case SLIX_PASS_EASAFI:
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx->response_buffer[0] = NFCV_NOERROR;
|
ctx->response_buffer[0] = NFCV_NOERROR;
|
||||||
nfcv_emu_send(
|
nfcv_emu_send(
|
||||||
tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time);
|
tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time);
|
||||||
@@ -268,6 +427,49 @@ bool slix_generic_protocol_filter(
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case NFCV_CMD_NXP_WRITE_PASSWORD: {
|
||||||
|
uint8_t password_id = nfcv_data->frame[ctx->payload_offset];
|
||||||
|
|
||||||
|
if(!(password_id & password_supported)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t* new_password = &nfcv_data->frame[ctx->payload_offset + 1];
|
||||||
|
uint8_t* password = NULL;
|
||||||
|
uint32_t flag_valid = 0;
|
||||||
|
uint32_t flag_set = 0;
|
||||||
|
|
||||||
|
slix_generic_pass_infos(password_id, slix, &password, &flag_valid, &flag_set);
|
||||||
|
|
||||||
|
/* when the password is not supported, return silently */
|
||||||
|
if(!password) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool pass_valid = (slix->flags & flag_valid);
|
||||||
|
if(!(slix->flags & flag_set)) {
|
||||||
|
pass_valid = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(pass_valid) {
|
||||||
|
slix->flags |= flag_valid;
|
||||||
|
slix->flags |= flag_set;
|
||||||
|
|
||||||
|
memcpy(password, new_password, 4);
|
||||||
|
|
||||||
|
ctx->response_buffer[0] = NFCV_NOERROR;
|
||||||
|
nfcv_emu_send(
|
||||||
|
tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time);
|
||||||
|
snprintf(
|
||||||
|
nfcv_data->last_command, sizeof(nfcv_data->last_command), "WRITE_PASSWORD OK");
|
||||||
|
} else {
|
||||||
|
snprintf(
|
||||||
|
nfcv_data->last_command, sizeof(nfcv_data->last_command), "WRITE_PASSWORD FAIL");
|
||||||
|
}
|
||||||
|
handled = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case NFCV_CMD_NXP_ENABLE_PRIVACY: {
|
case NFCV_CMD_NXP_ENABLE_PRIVACY: {
|
||||||
ctx->response_buffer[0] = NFCV_NOERROR;
|
ctx->response_buffer[0] = NFCV_NOERROR;
|
||||||
|
|
||||||
@@ -278,7 +480,7 @@ bool slix_generic_protocol_filter(
|
|||||||
sizeof(nfcv_data->last_command),
|
sizeof(nfcv_data->last_command),
|
||||||
"NFCV_CMD_NXP_ENABLE_PRIVACY");
|
"NFCV_CMD_NXP_ENABLE_PRIVACY");
|
||||||
|
|
||||||
slix->privacy = true;
|
slix->flags |= NfcVSlixDataFlagsPrivacy;
|
||||||
handled = true;
|
handled = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -315,7 +517,10 @@ void slix_l_prepare(NfcVData* nfcv_data) {
|
|||||||
FURI_LOG_D(
|
FURI_LOG_D(
|
||||||
TAG, " Destroy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_destroy, 4));
|
TAG, " Destroy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_destroy, 4));
|
||||||
FURI_LOG_D(TAG, " EAS pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_eas, 4));
|
FURI_LOG_D(TAG, " EAS pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_eas, 4));
|
||||||
FURI_LOG_D(TAG, " Privacy mode: %s", nfcv_data->sub_data.slix.privacy ? "ON" : "OFF");
|
FURI_LOG_D(
|
||||||
|
TAG,
|
||||||
|
" Privacy mode: %s",
|
||||||
|
(nfcv_data->sub_data.slix.flags & NfcVSlixDataFlagsPrivacy) ? "ON" : "OFF");
|
||||||
|
|
||||||
NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx;
|
NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx;
|
||||||
ctx->emu_protocol_filter = &slix_l_protocol_filter;
|
ctx->emu_protocol_filter = &slix_l_protocol_filter;
|
||||||
@@ -345,7 +550,10 @@ void slix_s_prepare(NfcVData* nfcv_data) {
|
|||||||
FURI_LOG_D(
|
FURI_LOG_D(
|
||||||
TAG, " Destroy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_destroy, 4));
|
TAG, " Destroy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_destroy, 4));
|
||||||
FURI_LOG_D(TAG, " EAS pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_eas, 4));
|
FURI_LOG_D(TAG, " EAS pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_eas, 4));
|
||||||
FURI_LOG_D(TAG, " Privacy mode: %s", nfcv_data->sub_data.slix.privacy ? "ON" : "OFF");
|
FURI_LOG_D(
|
||||||
|
TAG,
|
||||||
|
" Privacy mode: %s",
|
||||||
|
(nfcv_data->sub_data.slix.flags & NfcVSlixDataFlagsPrivacy) ? "ON" : "OFF");
|
||||||
|
|
||||||
NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx;
|
NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx;
|
||||||
ctx->emu_protocol_filter = &slix_s_protocol_filter;
|
ctx->emu_protocol_filter = &slix_s_protocol_filter;
|
||||||
@@ -375,7 +583,10 @@ void slix_prepare(NfcVData* nfcv_data) {
|
|||||||
FURI_LOG_D(
|
FURI_LOG_D(
|
||||||
TAG, " Destroy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_destroy, 4));
|
TAG, " Destroy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_destroy, 4));
|
||||||
FURI_LOG_D(TAG, " EAS pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_eas, 4));
|
FURI_LOG_D(TAG, " EAS pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_eas, 4));
|
||||||
FURI_LOG_D(TAG, " Privacy mode: %s", nfcv_data->sub_data.slix.privacy ? "ON" : "OFF");
|
FURI_LOG_D(
|
||||||
|
TAG,
|
||||||
|
" Privacy mode: %s",
|
||||||
|
(nfcv_data->sub_data.slix.flags & NfcVSlixDataFlagsPrivacy) ? "ON" : "OFF");
|
||||||
|
|
||||||
NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx;
|
NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx;
|
||||||
ctx->emu_protocol_filter = &slix_protocol_filter;
|
ctx->emu_protocol_filter = &slix_protocol_filter;
|
||||||
@@ -389,6 +600,10 @@ bool slix2_protocol_filter( // -V524
|
|||||||
furi_assert(nfc_data);
|
furi_assert(nfc_data);
|
||||||
furi_assert(nfcv_data_in);
|
furi_assert(nfcv_data_in);
|
||||||
|
|
||||||
|
NfcVData* nfcv_data = (NfcVData*)nfcv_data_in;
|
||||||
|
NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx;
|
||||||
|
NfcVSlixData* slix = &nfcv_data->sub_data.slix;
|
||||||
|
|
||||||
bool handled = false;
|
bool handled = false;
|
||||||
|
|
||||||
/* many SLIX share some of the functions, place that in a generic handler */
|
/* many SLIX share some of the functions, place that in a generic handler */
|
||||||
@@ -396,6 +611,157 @@ bool slix2_protocol_filter( // -V524
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch(ctx->command) {
|
||||||
|
/* override WRITE BLOCK for block 79 (16 bit counter) */
|
||||||
|
case NFCV_CMD_WRITE_BLOCK:
|
||||||
|
case NFCV_CMD_WRITE_MULTI_BLOCK: {
|
||||||
|
uint8_t resp_len = 1;
|
||||||
|
uint8_t blocks = 1;
|
||||||
|
uint8_t block = nfcv_data->frame[ctx->payload_offset];
|
||||||
|
uint8_t data_pos = ctx->payload_offset + 1;
|
||||||
|
|
||||||
|
if(ctx->command == NFCV_CMD_WRITE_MULTI_BLOCK) {
|
||||||
|
blocks = nfcv_data->frame[data_pos] + 1;
|
||||||
|
data_pos++;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t* data = &nfcv_data->frame[data_pos];
|
||||||
|
uint32_t data_len = nfcv_data->block_size * blocks;
|
||||||
|
|
||||||
|
if((block + blocks) <= nfcv_data->block_num &&
|
||||||
|
(data_pos + data_len + 2) == nfcv_data->frame_length) {
|
||||||
|
ctx->response_buffer[0] = NFCV_NOERROR;
|
||||||
|
|
||||||
|
for(int block_num = block; block_num < block + blocks; block_num++) {
|
||||||
|
/* special case, 16-bit counter */
|
||||||
|
if(block_num == 79) {
|
||||||
|
uint32_t dest;
|
||||||
|
uint32_t ctr_old;
|
||||||
|
|
||||||
|
memcpy(&dest, &nfcv_data->frame[data_pos], 4);
|
||||||
|
memcpy(&ctr_old, &nfcv_data->data[nfcv_data->block_size * block_num], 4);
|
||||||
|
|
||||||
|
uint32_t ctr_new = ctr_old;
|
||||||
|
bool allowed = true;
|
||||||
|
|
||||||
|
/* increment counter */
|
||||||
|
if(dest == 1) {
|
||||||
|
ctr_new = (ctr_old & 0xFFFF0000) | ((ctr_old + 1) & 0xFFFF);
|
||||||
|
|
||||||
|
/* protection flag set? */
|
||||||
|
if(ctr_old & 0x01000000) {
|
||||||
|
allowed = nfcv_data->sub_data.slix.flags &
|
||||||
|
NfcVSlixDataFlagsValidKeyRead;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ctr_new = dest;
|
||||||
|
allowed = nfcv_data->sub_data.slix.flags & NfcVSlixDataFlagsValidKeyWrite;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(allowed) {
|
||||||
|
memcpy(&nfcv_data->data[nfcv_data->block_size * block_num], &ctr_new, 4);
|
||||||
|
} else {
|
||||||
|
/* incorrect read or write password */
|
||||||
|
ctx->response_buffer[0] = NFCV_RES_FLAG_ERROR;
|
||||||
|
ctx->response_buffer[1] = NFCV_ERROR_GENERIC;
|
||||||
|
resp_len = 2;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
memcpy(
|
||||||
|
&nfcv_data->data[nfcv_data->block_size * block_num],
|
||||||
|
&nfcv_data->frame[data_pos],
|
||||||
|
nfcv_data->block_size);
|
||||||
|
}
|
||||||
|
data_pos += nfcv_data->block_size;
|
||||||
|
}
|
||||||
|
nfcv_data->modified = true;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
ctx->response_buffer[0] = NFCV_RES_FLAG_ERROR;
|
||||||
|
ctx->response_buffer[1] = NFCV_ERROR_GENERIC;
|
||||||
|
resp_len = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool respond = (ctx->response_buffer[0] == NFCV_NOERROR) ||
|
||||||
|
(ctx->addressed || ctx->selected);
|
||||||
|
|
||||||
|
if(respond) {
|
||||||
|
nfcv_emu_send(
|
||||||
|
tx_rx,
|
||||||
|
nfcv_data,
|
||||||
|
ctx->response_buffer,
|
||||||
|
resp_len,
|
||||||
|
ctx->response_flags,
|
||||||
|
ctx->send_time);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(ctx->command == NFCV_CMD_WRITE_MULTI_BLOCK) {
|
||||||
|
snprintf(
|
||||||
|
nfcv_data->last_command,
|
||||||
|
sizeof(nfcv_data->last_command),
|
||||||
|
"WRITE MULTI BLOCK %d, %d blocks",
|
||||||
|
block,
|
||||||
|
blocks);
|
||||||
|
} else {
|
||||||
|
snprintf(
|
||||||
|
nfcv_data->last_command,
|
||||||
|
sizeof(nfcv_data->last_command),
|
||||||
|
"WRITE BLOCK %d <- %02X %02X %02X %02X",
|
||||||
|
block,
|
||||||
|
data[0],
|
||||||
|
data[1],
|
||||||
|
data[2],
|
||||||
|
data[3]);
|
||||||
|
}
|
||||||
|
handled = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case NFCV_CMD_NXP_READ_SIGNATURE: {
|
||||||
|
uint32_t len = 0;
|
||||||
|
ctx->response_buffer[len++] = NFCV_NOERROR;
|
||||||
|
memcpy(&ctx->response_buffer[len], slix->signature, sizeof(slix->signature));
|
||||||
|
len += sizeof(slix->signature);
|
||||||
|
|
||||||
|
nfcv_emu_send(
|
||||||
|
tx_rx, nfcv_data, ctx->response_buffer, len, ctx->response_flags, ctx->send_time);
|
||||||
|
snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "READ_SIGNATURE");
|
||||||
|
|
||||||
|
handled = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case NFCV_CMD_NXP_GET_NXP_SYSTEM_INFORMATION: {
|
||||||
|
uint32_t len = 0;
|
||||||
|
uint8_t lock_bits = 0;
|
||||||
|
|
||||||
|
/* convert our internal lock bits format into NXP's */
|
||||||
|
lock_bits |= (nfcv_data->security_status[0] & NfcVLockBitDsfid) ? SlixLockBitDsfid : 0;
|
||||||
|
lock_bits |= (nfcv_data->security_status[0] & NfcVLockBitAfi) ? SlixLockBitAfi : 0;
|
||||||
|
lock_bits |= (nfcv_data->security_status[0] & NfcVLockBitEas) ? SlixLockBitEas : 0;
|
||||||
|
lock_bits |= (nfcv_data->security_status[0] & NfcVLockBitPpl) ? SlixLockBitPpl : 0;
|
||||||
|
|
||||||
|
ctx->response_buffer[len++] = NFCV_NOERROR;
|
||||||
|
ctx->response_buffer[len++] = nfcv_data->sub_data.slix.pp_pointer;
|
||||||
|
ctx->response_buffer[len++] = nfcv_data->sub_data.slix.pp_condition;
|
||||||
|
ctx->response_buffer[len++] = lock_bits;
|
||||||
|
ctx->response_buffer[len++] = 0x7F; /* features LSB */
|
||||||
|
ctx->response_buffer[len++] = 0x35; /* features */
|
||||||
|
ctx->response_buffer[len++] = 0; /* features */
|
||||||
|
ctx->response_buffer[len++] = 0; /* features MSB */
|
||||||
|
|
||||||
|
nfcv_emu_send(
|
||||||
|
tx_rx, nfcv_data, ctx->response_buffer, len, ctx->response_flags, ctx->send_time);
|
||||||
|
snprintf(
|
||||||
|
nfcv_data->last_command,
|
||||||
|
sizeof(nfcv_data->last_command),
|
||||||
|
"GET_NXP_SYSTEM_INFORMATION");
|
||||||
|
|
||||||
|
handled = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return handled;
|
return handled;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -405,7 +771,10 @@ void slix2_prepare(NfcVData* nfcv_data) {
|
|||||||
FURI_LOG_D(
|
FURI_LOG_D(
|
||||||
TAG, " Destroy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_destroy, 4));
|
TAG, " Destroy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_destroy, 4));
|
||||||
FURI_LOG_D(TAG, " EAS pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_eas, 4));
|
FURI_LOG_D(TAG, " EAS pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_eas, 4));
|
||||||
FURI_LOG_D(TAG, " Privacy mode: %s", nfcv_data->sub_data.slix.privacy ? "ON" : "OFF");
|
FURI_LOG_D(
|
||||||
|
TAG,
|
||||||
|
" Privacy mode: %s",
|
||||||
|
(nfcv_data->sub_data.slix.flags & NfcVSlixDataFlagsPrivacy) ? "ON" : "OFF");
|
||||||
|
|
||||||
NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx;
|
NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx;
|
||||||
ctx->emu_protocol_filter = &slix2_protocol_filter;
|
ctx->emu_protocol_filter = &slix2_protocol_filter;
|
||||||
|
|||||||
@@ -8,19 +8,35 @@
|
|||||||
#define NFCV_MANUFACTURER_NXP 0x04
|
#define NFCV_MANUFACTURER_NXP 0x04
|
||||||
|
|
||||||
/* ISO15693-3 CUSTOM NXP COMMANDS */
|
/* ISO15693-3 CUSTOM NXP COMMANDS */
|
||||||
#define NFCV_CMD_NXP_SET_EAS 0xA2
|
typedef enum {
|
||||||
#define NFCV_CMD_NXP_RESET_EAS 0xA3
|
NFCV_CMD_NXP_SET_EAS = 0xA2,
|
||||||
#define NFCV_CMD_NXP_LOCK_EAS 0xA4
|
NFCV_CMD_NXP_RESET_EAS = 0xA3,
|
||||||
#define NFCV_CMD_NXP_EAS_ALARM 0xA5
|
NFCV_CMD_NXP_LOCK_EAS = 0xA4,
|
||||||
#define NFCV_CMD_NXP_PASSWORD_PROTECT_EAS_AFI 0xA6
|
NFCV_CMD_NXP_EAS_ALARM = 0xA5,
|
||||||
#define NFCV_CMD_NXP_WRITE_EAS_ID 0xA7
|
NFCV_CMD_NXP_PASSWORD_PROTECT_EAS_AFI = 0xA6,
|
||||||
#define NFCV_CMD_NXP_INVENTORY_PAGE_READ 0xB0
|
NFCV_CMD_NXP_WRITE_EAS_ID = 0xA7,
|
||||||
#define NFCV_CMD_NXP_INVENTORY_PAGE_READ_FAST 0xB1
|
NFCV_CMD_NXP_GET_NXP_SYSTEM_INFORMATION = 0xAB,
|
||||||
#define NFCV_CMD_NXP_GET_RANDOM_NUMBER 0xB2
|
NFCV_CMD_NXP_INVENTORY_PAGE_READ = 0xB0,
|
||||||
#define NFCV_CMD_NXP_SET_PASSWORD 0xB3
|
NFCV_CMD_NXP_INVENTORY_PAGE_READ_FAST = 0xB1,
|
||||||
#define NFCV_CMD_NXP_WRITE_PASSWORD 0xB4
|
NFCV_CMD_NXP_GET_RANDOM_NUMBER = 0xB2,
|
||||||
#define NFCV_CMD_NXP_DESTROY 0xB9
|
NFCV_CMD_NXP_SET_PASSWORD = 0xB3,
|
||||||
#define NFCV_CMD_NXP_ENABLE_PRIVACY 0xBA
|
NFCV_CMD_NXP_WRITE_PASSWORD = 0xB4,
|
||||||
|
NFCV_CMD_NXP_64_BIT_PASSWORD_PROTECTION = 0xB5,
|
||||||
|
NFCV_CMD_NXP_PROTECT_PAGE = 0xB6,
|
||||||
|
NFCV_CMD_NXP_LOCK_PAGE_PROTECTION_CONDITION = 0xB7,
|
||||||
|
NFCV_CMD_NXP_DESTROY = 0xB9,
|
||||||
|
NFCV_CMD_NXP_ENABLE_PRIVACY = 0xBA,
|
||||||
|
NFCV_CMD_NXP_STAY_QUIET_PERSISTENT = 0xBC,
|
||||||
|
NFCV_CMD_NXP_READ_SIGNATURE = 0xBD
|
||||||
|
} SlixCommands;
|
||||||
|
|
||||||
|
/* lock bit bits used in SLIX's NXP SYSTEM INFORMATION response */
|
||||||
|
typedef enum {
|
||||||
|
SlixLockBitAfi = 1 << 0,
|
||||||
|
SlixLockBitEas = 1 << 1,
|
||||||
|
SlixLockBitDsfid = 1 << 2,
|
||||||
|
SlixLockBitPpl = 1 << 3,
|
||||||
|
} SlixLockBits;
|
||||||
|
|
||||||
/* available passwords */
|
/* available passwords */
|
||||||
#define SLIX_PASS_READ 0x01
|
#define SLIX_PASS_READ 0x01
|
||||||
@@ -37,6 +53,10 @@ bool slix2_check_card_type(FuriHalNfcDevData* nfc_data);
|
|||||||
bool slix_s_check_card_type(FuriHalNfcDevData* nfc_data);
|
bool slix_s_check_card_type(FuriHalNfcDevData* nfc_data);
|
||||||
bool slix_l_check_card_type(FuriHalNfcDevData* nfc_data);
|
bool slix_l_check_card_type(FuriHalNfcDevData* nfc_data);
|
||||||
|
|
||||||
|
ReturnCode slix2_read_custom(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data);
|
||||||
|
ReturnCode slix2_read_signature(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data);
|
||||||
|
ReturnCode slix2_read_nxp_sysinfo(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data);
|
||||||
|
|
||||||
ReturnCode slix_get_random(NfcVData* data);
|
ReturnCode slix_get_random(NfcVData* data);
|
||||||
ReturnCode slix_unlock(NfcVData* data, uint32_t password_id);
|
ReturnCode slix_unlock(NfcVData* data, uint32_t password_id);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user