mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-05-20 04:54:45 -07:00
added multiconverter / removed hid analyzer
hid analyzer won't needed anymore because of new lfrfid system adds support for multiple hid protocols
This commit is contained in:
12
ReadMe.md
12
ReadMe.md
@@ -1,6 +1,8 @@
|
|||||||
# Flipper Zero Unleashed Firmware
|
<h3 align="center">
|
||||||
|
<a href="https://github.com/Eng1n33r/flipperzero-firmware">
|
||||||
<img src="https://i.ibb.co/wQ12PVc/fzCUSTOM.png" alt="fzCUSTOM" border="0">
|
<img src="https://user-images.githubusercontent.com/10697207/186202043-26947e28-b1cc-459a-8f20-ffcc7fc0c71c.png" align="center" alt="fzCUSTOM" border="0">
|
||||||
|
</a>
|
||||||
|
</h3>
|
||||||
|
|
||||||
Welcome to Flipper Zero's Custom Firmware repo!
|
Welcome to Flipper Zero's Custom Firmware repo!
|
||||||
Our goal is to make any features possible in this device without any limitations!
|
Our goal is to make any features possible in this device without any limitations!
|
||||||
@@ -57,7 +59,7 @@ See changelog in releases for latest updates!
|
|||||||
|
|
||||||
- ESP8266 Deauther plugin [(by SequoiaSan)](https://github.com/SequoiaSan/FlipperZero-Wifi-ESP8266-Deauther-Module)
|
- ESP8266 Deauther plugin [(by SequoiaSan)](https://github.com/SequoiaSan/FlipperZero-Wifi-ESP8266-Deauther-Module)
|
||||||
- WiFi Scanner plugin [(by SequoiaSan)](https://github.com/SequoiaSan/FlipperZero-WiFi-Scanner_Module)
|
- WiFi Scanner plugin [(by SequoiaSan)](https://github.com/SequoiaSan/FlipperZero-WiFi-Scanner_Module)
|
||||||
- Dec/Hex Converter plugin [(by theisolinearchip)](https://github.com/theisolinearchip/flipperzero_stuff/tree/main/applications/dec_hex_converter)
|
- MultiConverter plugin [(by theisolinearchip)](https://github.com/theisolinearchip/flipperzero_stuff)
|
||||||
- WAV player plugin (fixed) [(OFW: DrZlo13)](https://github.com/flipperdevices/flipperzero-firmware/tree/zlo/wav-player)
|
- WAV player plugin (fixed) [(OFW: DrZlo13)](https://github.com/flipperdevices/flipperzero-firmware/tree/zlo/wav-player)
|
||||||
- UPC-A Barcode generator plugin [(by McAzzaMan)](https://github.com/McAzzaMan/flipperzero-firmware/tree/UPC-A_Barcode_Generator/applications/barcode_generator)
|
- UPC-A Barcode generator plugin [(by McAzzaMan)](https://github.com/McAzzaMan/flipperzero-firmware/tree/UPC-A_Barcode_Generator/applications/barcode_generator)
|
||||||
- GPIO: Sentry Safe plugin [(by H4ckd4ddy)](https://github.com/H4ckd4ddy/flipperzero-sentry-safe-plugin)
|
- GPIO: Sentry Safe plugin [(by H4ckd4ddy)](https://github.com/H4ckd4ddy/flipperzero-sentry-safe-plugin)
|
||||||
@@ -91,6 +93,8 @@ See changelog in releases for latest updates!
|
|||||||
|
|
||||||
## [- Barcode Generator](https://github.com/Eng1n33r/flipperzero-firmware/blob/dev/documentation/BarcodeGenerator.md)
|
## [- Barcode Generator](https://github.com/Eng1n33r/flipperzero-firmware/blob/dev/documentation/BarcodeGenerator.md)
|
||||||
|
|
||||||
|
## [- Multi Converter](https://github.com/Eng1n33r/flipperzero-firmware/blob/dev/documentation/MultiConverter.md)
|
||||||
|
|
||||||
## [- WAV Player sample files & how to convert](https://github.com/UberGuidoZ/Flipper/tree/main/Wav_Player#readme)
|
## [- WAV Player sample files & how to convert](https://github.com/UberGuidoZ/Flipper/tree/main/Wav_Player#readme)
|
||||||
|
|
||||||
### **Plugins that works with external hardware**
|
### **Plugins that works with external hardware**
|
||||||
|
|||||||
@@ -1,404 +0,0 @@
|
|||||||
#include <furi.h>
|
|
||||||
#include <gui/gui.h>
|
|
||||||
#include <input/input.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
|
|
||||||
#define DEC_HEX_CONVERTER_NUMBER_DIGITS 9
|
|
||||||
#define DEC_HEX_CONVERTER_KEYS 18
|
|
||||||
#define DEC_HEX_CONVERTER_KEY_DEL 16
|
|
||||||
// #define DEC_HEX_CONVERTER_KEY_SWAP 17 // actually not used...
|
|
||||||
|
|
||||||
#define DEC_HEX_CONVERTER_CHAR_DEL '<'
|
|
||||||
#define DEC_HEX_CONVERTER_CHAR_SWAP 's'
|
|
||||||
#define DEC_HEX_CONVERTER_CHAR_MODE '#'
|
|
||||||
#define DEC_HEX_CONVERTER_CHAR_OVERFLOW '#'
|
|
||||||
|
|
||||||
#define DEC_HEX_CONVERTER_KEY_FRAME_MARGIN 3
|
|
||||||
#define DEC_HEX_CONVERTER_KEY_CHAR_HEIGHT 8
|
|
||||||
|
|
||||||
#define DEC_HEX_MAX_SUPORTED_DEC_INT 999999999
|
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
EventTypeKey,
|
|
||||||
} EventType;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
InputEvent input;
|
|
||||||
EventType type;
|
|
||||||
} DecHexConverterEvent;
|
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
ModeDec,
|
|
||||||
ModeHex,
|
|
||||||
} Mode;
|
|
||||||
|
|
||||||
// setting up one char array next to the other one causes the canvas_draw_str to display both of them
|
|
||||||
// when addressing the first one if there's no string terminator or similar indicator. Adding a \0 seems
|
|
||||||
// to work fine to prevent that, so add a final last char outside the size constants (added on init
|
|
||||||
// and NEVER changed nor referenced again)
|
|
||||||
//
|
|
||||||
// (as a reference, canvas_draw_str ends up calling u8g2_DrawStr from u8g2_font.c,
|
|
||||||
// that finally ends up calling u8g2_draw_string)
|
|
||||||
typedef struct {
|
|
||||||
char dec_number[DEC_HEX_CONVERTER_NUMBER_DIGITS + 1];
|
|
||||||
char hex_number[DEC_HEX_CONVERTER_NUMBER_DIGITS + 1];
|
|
||||||
Mode mode; // dec / hex
|
|
||||||
int8_t cursor; // position on keyboard (includes digit letters and other options)
|
|
||||||
int8_t digit_pos; // current digit on selected mode
|
|
||||||
} DecHexConverterState;
|
|
||||||
|
|
||||||
// move cursor left / right (TODO: implement menu nav in a more "standard" and reusable way?)
|
|
||||||
void dec_hex_converter_logic_move_cursor_lr(
|
|
||||||
DecHexConverterState* const dec_hex_converter_state,
|
|
||||||
int8_t d) {
|
|
||||||
dec_hex_converter_state->cursor += d;
|
|
||||||
|
|
||||||
if(dec_hex_converter_state->cursor > DEC_HEX_CONVERTER_KEYS - 1)
|
|
||||||
dec_hex_converter_state->cursor = 0;
|
|
||||||
else if(dec_hex_converter_state->cursor < 0)
|
|
||||||
dec_hex_converter_state->cursor = DEC_HEX_CONVERTER_KEYS - 1;
|
|
||||||
|
|
||||||
// if we're moving left / right to the letters keys on ModeDec just go to the closest available key
|
|
||||||
if(dec_hex_converter_state->mode == ModeDec) {
|
|
||||||
if(dec_hex_converter_state->cursor == 10)
|
|
||||||
dec_hex_converter_state->cursor = 16;
|
|
||||||
else if(dec_hex_converter_state->cursor == 15)
|
|
||||||
dec_hex_converter_state->cursor = 9;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// move cursor up / down; there're two lines, so we basically toggle
|
|
||||||
void dec_hex_converter_logic_move_cursor_ud(DecHexConverterState* const dec_hex_converter_state) {
|
|
||||||
if(dec_hex_converter_state->cursor < 9) {
|
|
||||||
// move to second line ("down")
|
|
||||||
dec_hex_converter_state->cursor += 9;
|
|
||||||
|
|
||||||
// if we're reaching the letter keys while ModeDec, just move left / right for the first available key
|
|
||||||
if(dec_hex_converter_state->mode == ModeDec &&
|
|
||||||
(dec_hex_converter_state->cursor >= 10 && dec_hex_converter_state->cursor <= 15)) {
|
|
||||||
if(dec_hex_converter_state->cursor <= 12)
|
|
||||||
dec_hex_converter_state->cursor = 9;
|
|
||||||
else
|
|
||||||
dec_hex_converter_state->cursor = 16;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// move to first line ("up")
|
|
||||||
dec_hex_converter_state->cursor -= 9;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// fetch number from current mode and modifies the destination one, RM dnt stel pls
|
|
||||||
// (if destination is shorter than the output value, overried with "-" chars or something similar)
|
|
||||||
void dec_hex_converter_logic_convert_number(DecHexConverterState* const dec_hex_converter_state) {
|
|
||||||
char* s_ptr;
|
|
||||||
char* d_ptr;
|
|
||||||
|
|
||||||
char dest[DEC_HEX_CONVERTER_NUMBER_DIGITS];
|
|
||||||
int i = 0; // current index on destination array
|
|
||||||
|
|
||||||
if(dec_hex_converter_state->mode == ModeDec) {
|
|
||||||
// DEC to HEX cannot overflow if they're, at least, the same size
|
|
||||||
|
|
||||||
s_ptr = dec_hex_converter_state->dec_number;
|
|
||||||
d_ptr = dec_hex_converter_state->hex_number;
|
|
||||||
|
|
||||||
int a = atoi(s_ptr);
|
|
||||||
int r;
|
|
||||||
while(a != 0) {
|
|
||||||
r = a % 16;
|
|
||||||
dest[i] = r + (r < 10 ? '0' : ('A' - 10));
|
|
||||||
a /= 16;
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
s_ptr = dec_hex_converter_state->hex_number;
|
|
||||||
d_ptr = dec_hex_converter_state->dec_number;
|
|
||||||
|
|
||||||
int a = strtol(s_ptr, NULL, 16);
|
|
||||||
if(a > DEC_HEX_MAX_SUPORTED_DEC_INT) {
|
|
||||||
// draw all "###" if there's an overflow
|
|
||||||
for(int j = 0; j < DEC_HEX_CONVERTER_NUMBER_DIGITS; j++) {
|
|
||||||
d_ptr[j] = DEC_HEX_CONVERTER_CHAR_OVERFLOW;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
while(a > 0) {
|
|
||||||
dest[i++] = (a % 10) + '0';
|
|
||||||
a /= 10;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// dest is reversed, copy to destination pointer and append empty chars at the end
|
|
||||||
for(int j = 0; j < DEC_HEX_CONVERTER_NUMBER_DIGITS; j++) {
|
|
||||||
if(i >= 1)
|
|
||||||
d_ptr[j] = dest[--i];
|
|
||||||
else
|
|
||||||
d_ptr[j] = ' ';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// change from DEC to HEX or HEX to DEC, set the digit_pos to the last position not empty on the destination mode
|
|
||||||
void dec_hex_converter_logic_swap_mode(DecHexConverterState* const dec_hex_converter_state) {
|
|
||||||
char* n_ptr;
|
|
||||||
if(dec_hex_converter_state->mode == ModeDec) {
|
|
||||||
dec_hex_converter_state->mode = ModeHex;
|
|
||||||
n_ptr = dec_hex_converter_state->hex_number;
|
|
||||||
} else {
|
|
||||||
dec_hex_converter_state->mode = ModeDec;
|
|
||||||
n_ptr = dec_hex_converter_state->dec_number;
|
|
||||||
}
|
|
||||||
|
|
||||||
dec_hex_converter_state->digit_pos = DEC_HEX_CONVERTER_NUMBER_DIGITS;
|
|
||||||
for(int i = 0; i < DEC_HEX_CONVERTER_NUMBER_DIGITS; i++) {
|
|
||||||
if(n_ptr[i] == ' ') {
|
|
||||||
dec_hex_converter_state->digit_pos = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// removes the number on current digit on current mode
|
|
||||||
static void
|
|
||||||
dec_hex_converter_logic_del_number(DecHexConverterState* const dec_hex_converter_state) {
|
|
||||||
if(dec_hex_converter_state->digit_pos > 0) dec_hex_converter_state->digit_pos--;
|
|
||||||
|
|
||||||
if(dec_hex_converter_state->mode == ModeDec) {
|
|
||||||
dec_hex_converter_state->dec_number[dec_hex_converter_state->digit_pos] = ' ';
|
|
||||||
} else {
|
|
||||||
dec_hex_converter_state->hex_number[dec_hex_converter_state->digit_pos] = ' ';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// append a number to the digit on the current mode
|
|
||||||
static void dec_hex_converter_logic_add_number(
|
|
||||||
DecHexConverterState* const dec_hex_converter_state,
|
|
||||||
int8_t number) {
|
|
||||||
// ignore HEX values on DEC mode (probably button nav will be disabled too, so cannot reach);
|
|
||||||
// also do not add anything if we're out of bound
|
|
||||||
if((number > 9 && dec_hex_converter_state->mode == ModeDec) ||
|
|
||||||
dec_hex_converter_state->digit_pos >= DEC_HEX_CONVERTER_NUMBER_DIGITS)
|
|
||||||
return;
|
|
||||||
|
|
||||||
char* s_ptr;
|
|
||||||
|
|
||||||
if(dec_hex_converter_state->mode == ModeDec) {
|
|
||||||
s_ptr = dec_hex_converter_state->dec_number;
|
|
||||||
} else {
|
|
||||||
s_ptr = dec_hex_converter_state->hex_number;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(number < 10) {
|
|
||||||
s_ptr[dec_hex_converter_state->digit_pos] = number + '0';
|
|
||||||
} else {
|
|
||||||
s_ptr[dec_hex_converter_state->digit_pos] = (number - 10) + 'A'; // A-F (HEX only)
|
|
||||||
}
|
|
||||||
|
|
||||||
dec_hex_converter_state->digit_pos++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------
|
|
||||||
|
|
||||||
static void dec_hex_converter_render_callback(Canvas* const canvas, void* ctx) {
|
|
||||||
const DecHexConverterState* dec_hex_converter_state = acquire_mutex((ValueMutex*)ctx, 25);
|
|
||||||
if(dec_hex_converter_state == NULL) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
canvas_set_color(canvas, ColorBlack);
|
|
||||||
|
|
||||||
// DEC
|
|
||||||
canvas_set_font(canvas, FontPrimary);
|
|
||||||
canvas_draw_str(canvas, 2, 10, "DEC: ");
|
|
||||||
|
|
||||||
canvas_set_font(canvas, FontPrimary);
|
|
||||||
canvas_draw_str(canvas, 2 + 30, 10, dec_hex_converter_state->dec_number);
|
|
||||||
|
|
||||||
// HEX
|
|
||||||
canvas_set_font(canvas, FontPrimary);
|
|
||||||
canvas_draw_str(canvas, 2, 10 + 12, "HEX: ");
|
|
||||||
|
|
||||||
canvas_set_font(canvas, FontPrimary);
|
|
||||||
canvas_draw_str(canvas, 2 + 30, 10 + 12, dec_hex_converter_state->hex_number);
|
|
||||||
|
|
||||||
// current mode indicator
|
|
||||||
// char buffer[4];
|
|
||||||
// snprintf(buffer, sizeof(buffer), "%u", dec_hex_converter_state->digit_pos); // debug: show digit position instead of selected mode
|
|
||||||
if(dec_hex_converter_state->mode == ModeDec) {
|
|
||||||
canvas_draw_glyph(canvas, 128 - 10, 10, DEC_HEX_CONVERTER_CHAR_MODE);
|
|
||||||
} else {
|
|
||||||
canvas_draw_glyph(canvas, 128 - 10, 10 + 12, DEC_HEX_CONVERTER_CHAR_MODE);
|
|
||||||
}
|
|
||||||
|
|
||||||
// draw the line
|
|
||||||
canvas_draw_line(canvas, 2, 25, 128 - 3, 25);
|
|
||||||
|
|
||||||
// draw the keyboard
|
|
||||||
uint8_t _x = 5;
|
|
||||||
uint8_t _y = 25 + 15; // line + 10
|
|
||||||
|
|
||||||
for(int i = 0; i < DEC_HEX_CONVERTER_KEYS; i++) {
|
|
||||||
char g;
|
|
||||||
if(i < 10)
|
|
||||||
g = (i + '0');
|
|
||||||
else if(i < 16)
|
|
||||||
g = ((i - 10) + 'A');
|
|
||||||
else if(i == 16)
|
|
||||||
g = DEC_HEX_CONVERTER_CHAR_DEL; // '<'
|
|
||||||
else
|
|
||||||
g = DEC_HEX_CONVERTER_CHAR_SWAP; // 's'
|
|
||||||
|
|
||||||
uint8_t g_w = canvas_glyph_width(canvas, g);
|
|
||||||
|
|
||||||
// disable letters on DEC mode (but keep the previous width for visual purposes - show "blank keys")
|
|
||||||
if(dec_hex_converter_state->mode == ModeDec && i > 9 && i < 16) g = ' ';
|
|
||||||
|
|
||||||
if(dec_hex_converter_state->cursor == i) {
|
|
||||||
canvas_draw_box(
|
|
||||||
canvas,
|
|
||||||
_x - DEC_HEX_CONVERTER_KEY_FRAME_MARGIN,
|
|
||||||
_y - (DEC_HEX_CONVERTER_KEY_CHAR_HEIGHT + DEC_HEX_CONVERTER_KEY_FRAME_MARGIN),
|
|
||||||
DEC_HEX_CONVERTER_KEY_FRAME_MARGIN + g_w + DEC_HEX_CONVERTER_KEY_FRAME_MARGIN,
|
|
||||||
DEC_HEX_CONVERTER_KEY_CHAR_HEIGHT + DEC_HEX_CONVERTER_KEY_FRAME_MARGIN * 2);
|
|
||||||
canvas_set_color(canvas, ColorWhite);
|
|
||||||
canvas_draw_glyph(canvas, _x, _y, g);
|
|
||||||
canvas_set_color(canvas, ColorBlack);
|
|
||||||
} else {
|
|
||||||
canvas_draw_frame(
|
|
||||||
canvas,
|
|
||||||
_x - DEC_HEX_CONVERTER_KEY_FRAME_MARGIN,
|
|
||||||
_y - (DEC_HEX_CONVERTER_KEY_CHAR_HEIGHT + DEC_HEX_CONVERTER_KEY_FRAME_MARGIN),
|
|
||||||
DEC_HEX_CONVERTER_KEY_FRAME_MARGIN + g_w + DEC_HEX_CONVERTER_KEY_FRAME_MARGIN,
|
|
||||||
DEC_HEX_CONVERTER_KEY_CHAR_HEIGHT + DEC_HEX_CONVERTER_KEY_FRAME_MARGIN * 2);
|
|
||||||
canvas_draw_glyph(canvas, _x, _y, g);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(i < 8) {
|
|
||||||
_x += g_w + DEC_HEX_CONVERTER_KEY_FRAME_MARGIN * 2 + 2;
|
|
||||||
} else if(i == 8) {
|
|
||||||
_y += (DEC_HEX_CONVERTER_KEY_CHAR_HEIGHT + DEC_HEX_CONVERTER_KEY_FRAME_MARGIN * 2) + 3;
|
|
||||||
_x = 7; // some padding at the beginning on second line
|
|
||||||
} else {
|
|
||||||
_x += g_w + DEC_HEX_CONVERTER_KEY_FRAME_MARGIN * 2 + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
release_mutex((ValueMutex*)ctx, dec_hex_converter_state);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
dec_hex_converter_input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
|
|
||||||
furi_assert(event_queue);
|
|
||||||
|
|
||||||
DecHexConverterEvent event = {.type = EventTypeKey, .input = *input_event};
|
|
||||||
furi_message_queue_put(event_queue, &event, FuriWaitForever);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void dec_hex_converter_init(DecHexConverterState* const dec_hex_converter_state) {
|
|
||||||
dec_hex_converter_state->mode = ModeDec;
|
|
||||||
dec_hex_converter_state->digit_pos = 0;
|
|
||||||
|
|
||||||
dec_hex_converter_state->dec_number[DEC_HEX_CONVERTER_NUMBER_DIGITS] = '\0'; // null terminator
|
|
||||||
dec_hex_converter_state->hex_number[DEC_HEX_CONVERTER_NUMBER_DIGITS] = '\0'; // null terminator
|
|
||||||
|
|
||||||
for(int i = 0; i < DEC_HEX_CONVERTER_NUMBER_DIGITS; i++) {
|
|
||||||
dec_hex_converter_state->dec_number[i] = ' ';
|
|
||||||
dec_hex_converter_state->hex_number[i] = ' ';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// main entry point
|
|
||||||
int32_t dec_hex_converter_app(void* p) {
|
|
||||||
UNUSED(p);
|
|
||||||
|
|
||||||
// get event queue
|
|
||||||
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(DecHexConverterEvent));
|
|
||||||
|
|
||||||
// allocate state
|
|
||||||
DecHexConverterState* dec_hex_converter_state = malloc(sizeof(DecHexConverterState));
|
|
||||||
|
|
||||||
// set mutex for plugin state (different threads can access it)
|
|
||||||
ValueMutex state_mutex;
|
|
||||||
if(!init_mutex(&state_mutex, dec_hex_converter_state, sizeof(dec_hex_converter_state))) {
|
|
||||||
FURI_LOG_E("DecHexConverter", "cannot create mutex\r\n");
|
|
||||||
furi_message_queue_free(event_queue);
|
|
||||||
free(dec_hex_converter_state);
|
|
||||||
return 255;
|
|
||||||
}
|
|
||||||
|
|
||||||
// register callbacks for drawing and input processing
|
|
||||||
ViewPort* view_port = view_port_alloc();
|
|
||||||
view_port_draw_callback_set(view_port, dec_hex_converter_render_callback, &state_mutex);
|
|
||||||
view_port_input_callback_set(view_port, dec_hex_converter_input_callback, event_queue);
|
|
||||||
|
|
||||||
// open GUI and register view_port
|
|
||||||
Gui* gui = furi_record_open(RECORD_GUI);
|
|
||||||
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
|
|
||||||
|
|
||||||
dec_hex_converter_init(dec_hex_converter_state);
|
|
||||||
|
|
||||||
// main loop
|
|
||||||
DecHexConverterEvent event;
|
|
||||||
for(bool processing = true; processing;) {
|
|
||||||
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
|
|
||||||
DecHexConverterState* dec_hex_converter_state =
|
|
||||||
(DecHexConverterState*)acquire_mutex_block(&state_mutex);
|
|
||||||
|
|
||||||
if(event_status == FuriStatusOk) {
|
|
||||||
// press events
|
|
||||||
if(event.type == EventTypeKey) {
|
|
||||||
if(event.input.type == InputTypePress) {
|
|
||||||
switch(event.input.key) {
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
case InputKeyUp:
|
|
||||||
case InputKeyDown:
|
|
||||||
dec_hex_converter_logic_move_cursor_ud(dec_hex_converter_state);
|
|
||||||
break;
|
|
||||||
case InputKeyRight:
|
|
||||||
dec_hex_converter_logic_move_cursor_lr(dec_hex_converter_state, 1);
|
|
||||||
break;
|
|
||||||
case InputKeyLeft:
|
|
||||||
dec_hex_converter_logic_move_cursor_lr(dec_hex_converter_state, -1);
|
|
||||||
break;
|
|
||||||
case InputKeyOk:
|
|
||||||
if(dec_hex_converter_state->cursor < DEC_HEX_CONVERTER_KEY_DEL) {
|
|
||||||
// positions from 0 to 15 works as regular numbers (DEC / HEX where applicable)
|
|
||||||
// (logic won't allow add numbers > 9 on ModeDec)
|
|
||||||
dec_hex_converter_logic_add_number(
|
|
||||||
dec_hex_converter_state, dec_hex_converter_state->cursor);
|
|
||||||
} else if(dec_hex_converter_state->cursor == DEC_HEX_CONVERTER_KEY_DEL) {
|
|
||||||
// del
|
|
||||||
dec_hex_converter_logic_del_number(dec_hex_converter_state);
|
|
||||||
} else {
|
|
||||||
// swap
|
|
||||||
dec_hex_converter_logic_swap_mode(dec_hex_converter_state);
|
|
||||||
}
|
|
||||||
|
|
||||||
dec_hex_converter_logic_convert_number(dec_hex_converter_state);
|
|
||||||
break;
|
|
||||||
case InputKeyBack:
|
|
||||||
processing = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// event timeout
|
|
||||||
}
|
|
||||||
|
|
||||||
view_port_update(view_port);
|
|
||||||
release_mutex(&state_mutex, dec_hex_converter_state);
|
|
||||||
}
|
|
||||||
|
|
||||||
view_port_enabled_set(view_port, false);
|
|
||||||
gui_remove_view_port(gui, view_port);
|
|
||||||
furi_record_close(RECORD_GUI);
|
|
||||||
view_port_free(view_port);
|
|
||||||
furi_message_queue_free(event_queue);
|
|
||||||
delete_mutex(&state_mutex);
|
|
||||||
free(dec_hex_converter_state);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
App(
|
|
||||||
appid="hid_analyzer",
|
|
||||||
name="HID Analyzer",
|
|
||||||
apptype=FlipperAppType.PLUGIN,
|
|
||||||
entry_point="hid_analyzer_app",
|
|
||||||
cdefines=["APP_HID_ANALYZER"],
|
|
||||||
stack_size=2 * 1024,
|
|
||||||
order=40,
|
|
||||||
)
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
#include "decoder_hid.h"
|
|
||||||
#include <furi_hal.h>
|
|
||||||
|
|
||||||
constexpr uint32_t clocks_in_us = 64;
|
|
||||||
|
|
||||||
constexpr uint32_t jitter_time_us = 20;
|
|
||||||
constexpr uint32_t min_time_us = 64;
|
|
||||||
constexpr uint32_t max_time_us = 80;
|
|
||||||
|
|
||||||
constexpr uint32_t min_time = (min_time_us - jitter_time_us) * clocks_in_us;
|
|
||||||
constexpr uint32_t mid_time = ((max_time_us - min_time_us) / 2 + min_time_us) * clocks_in_us;
|
|
||||||
constexpr uint32_t max_time = (max_time_us + jitter_time_us) * clocks_in_us;
|
|
||||||
|
|
||||||
bool DecoderHID::read(uint8_t* data, uint8_t data_size) {
|
|
||||||
bool result = false;
|
|
||||||
furi_assert(data_size >= 3);
|
|
||||||
|
|
||||||
if(ready) {
|
|
||||||
result = true;
|
|
||||||
hid.decode(
|
|
||||||
reinterpret_cast<const uint8_t*>(&stored_data), sizeof(uint32_t) * 3, data, data_size);
|
|
||||||
ready = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DecoderHID::process_front(bool polarity, uint32_t time) {
|
|
||||||
if(ready) return;
|
|
||||||
|
|
||||||
if(polarity == true) {
|
|
||||||
last_pulse_time = time;
|
|
||||||
} else {
|
|
||||||
last_pulse_time += time;
|
|
||||||
|
|
||||||
if(last_pulse_time > min_time && last_pulse_time < max_time) {
|
|
||||||
bool pulse;
|
|
||||||
|
|
||||||
if(last_pulse_time < mid_time) {
|
|
||||||
// 6 pulses
|
|
||||||
pulse = false;
|
|
||||||
} else {
|
|
||||||
// 5 pulses
|
|
||||||
pulse = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(last_pulse == pulse) {
|
|
||||||
pulse_count++;
|
|
||||||
|
|
||||||
if(pulse) {
|
|
||||||
if(pulse_count > 4) {
|
|
||||||
pulse_count = 0;
|
|
||||||
store_data(1);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if(pulse_count > 5) {
|
|
||||||
pulse_count = 0;
|
|
||||||
store_data(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if(last_pulse) {
|
|
||||||
if(pulse_count > 2) {
|
|
||||||
store_data(1);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if(pulse_count > 3) {
|
|
||||||
store_data(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pulse_count = 0;
|
|
||||||
last_pulse = pulse;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DecoderHID::DecoderHID() {
|
|
||||||
reset_state();
|
|
||||||
}
|
|
||||||
|
|
||||||
void DecoderHID::store_data(bool data) {
|
|
||||||
stored_data[0] = (stored_data[0] << 1) | ((stored_data[1] >> 31) & 1);
|
|
||||||
stored_data[1] = (stored_data[1] << 1) | ((stored_data[2] >> 31) & 1);
|
|
||||||
stored_data[2] = (stored_data[2] << 1) | data;
|
|
||||||
|
|
||||||
if(hid.can_be_decoded(reinterpret_cast<const uint8_t*>(&stored_data), sizeof(uint32_t) * 3)) {
|
|
||||||
ready = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void DecoderHID::reset_state() {
|
|
||||||
last_pulse = false;
|
|
||||||
pulse_count = 0;
|
|
||||||
ready = false;
|
|
||||||
last_pulse_time = 0;
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <atomic>
|
|
||||||
#include "protocols/protocol_hid.h"
|
|
||||||
|
|
||||||
class DecoderHID {
|
|
||||||
public:
|
|
||||||
bool read(uint8_t* data, uint8_t data_size);
|
|
||||||
void process_front(bool polarity, uint32_t time);
|
|
||||||
DecoderHID();
|
|
||||||
|
|
||||||
private:
|
|
||||||
uint32_t last_pulse_time = 0;
|
|
||||||
bool last_pulse;
|
|
||||||
uint8_t pulse_count;
|
|
||||||
|
|
||||||
uint32_t stored_data[3] = {0, 0, 0};
|
|
||||||
void store_data(bool data);
|
|
||||||
|
|
||||||
std::atomic<bool> ready;
|
|
||||||
|
|
||||||
void reset_state();
|
|
||||||
ProtocolHID hid;
|
|
||||||
};
|
|
||||||
@@ -1,143 +0,0 @@
|
|||||||
#include "hid_reader.h"
|
|
||||||
#include <furi.h>
|
|
||||||
#include <furi_hal.h>
|
|
||||||
#include <stm32wbxx_ll_cortex.h>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief private violation assistant for HIDReader
|
|
||||||
*/
|
|
||||||
struct HIDReaderAccessor {
|
|
||||||
static void decode(HIDReader& hid_reader, bool polarity) {
|
|
||||||
hid_reader.decode(polarity);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
void HIDReader::decode(bool polarity) {
|
|
||||||
uint32_t current_dwt_value = DWT->CYCCNT;
|
|
||||||
uint32_t period = current_dwt_value - last_dwt_value;
|
|
||||||
last_dwt_value = current_dwt_value;
|
|
||||||
|
|
||||||
decoder_hid.process_front(polarity, period);
|
|
||||||
|
|
||||||
detect_ticks++;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool HIDReader::switch_timer_elapsed() {
|
|
||||||
const uint32_t seconds_to_switch = furi_kernel_get_tick_frequency() * 2.0f;
|
|
||||||
return (furi_get_tick() - switch_os_tick_last) > seconds_to_switch;
|
|
||||||
}
|
|
||||||
|
|
||||||
void HIDReader::switch_timer_reset() {
|
|
||||||
switch_os_tick_last = furi_get_tick();
|
|
||||||
}
|
|
||||||
|
|
||||||
void HIDReader::switch_mode() {
|
|
||||||
switch(type) {
|
|
||||||
case Type::Normal:
|
|
||||||
type = Type::Indala;
|
|
||||||
furi_hal_rfid_change_read_config(62500.0f, 0.25f);
|
|
||||||
break;
|
|
||||||
case Type::Indala:
|
|
||||||
type = Type::Normal;
|
|
||||||
furi_hal_rfid_change_read_config(125000.0f, 0.5f);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch_timer_reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
static void comparator_trigger_callback(bool level, void* comp_ctx) {
|
|
||||||
HIDReader* _this = static_cast<HIDReader*>(comp_ctx);
|
|
||||||
|
|
||||||
HIDReaderAccessor::decode(*_this, !level);
|
|
||||||
}
|
|
||||||
|
|
||||||
HIDReader::HIDReader() {
|
|
||||||
}
|
|
||||||
|
|
||||||
void HIDReader::start() {
|
|
||||||
type = Type::Normal;
|
|
||||||
|
|
||||||
furi_hal_rfid_pins_read();
|
|
||||||
furi_hal_rfid_tim_read(125000, 0.5);
|
|
||||||
furi_hal_rfid_tim_read_start();
|
|
||||||
start_comparator();
|
|
||||||
|
|
||||||
switch_timer_reset();
|
|
||||||
last_read_count = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void HIDReader::start_forced(HIDReader::Type _type) {
|
|
||||||
start();
|
|
||||||
if(_type == Type::Indala) {
|
|
||||||
switch_mode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void HIDReader::stop() {
|
|
||||||
furi_hal_rfid_pins_reset();
|
|
||||||
furi_hal_rfid_tim_read_stop();
|
|
||||||
furi_hal_rfid_tim_reset();
|
|
||||||
stop_comparator();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool HIDReader::read(LfrfidKeyType* _type, uint8_t* data, uint8_t data_size, bool switch_enable) {
|
|
||||||
bool result = false;
|
|
||||||
bool something_read = false;
|
|
||||||
|
|
||||||
if(decoder_hid.read(data, data_size)) {
|
|
||||||
*_type = LfrfidKeyType::KeyH10301; // should be an OK temp
|
|
||||||
something_read = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// validation
|
|
||||||
if(something_read) {
|
|
||||||
switch_timer_reset();
|
|
||||||
|
|
||||||
if(last_read_type == *_type && memcmp(last_read_data, data, data_size) == 0) {
|
|
||||||
last_read_count = last_read_count + 1;
|
|
||||||
|
|
||||||
if(last_read_count > 2) {
|
|
||||||
result = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
last_read_type = *_type;
|
|
||||||
memcpy(last_read_data, data, data_size);
|
|
||||||
last_read_count = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// mode switching
|
|
||||||
if(switch_enable && switch_timer_elapsed()) {
|
|
||||||
switch_mode();
|
|
||||||
last_read_count = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool HIDReader::detect() {
|
|
||||||
bool detected = false;
|
|
||||||
if(detect_ticks > 10) {
|
|
||||||
detected = true;
|
|
||||||
}
|
|
||||||
detect_ticks = 0;
|
|
||||||
|
|
||||||
return detected;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool HIDReader::any_read() {
|
|
||||||
return last_read_count > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void HIDReader::start_comparator(void) {
|
|
||||||
furi_hal_rfid_comp_set_callback(comparator_trigger_callback, this);
|
|
||||||
last_dwt_value = DWT->CYCCNT;
|
|
||||||
|
|
||||||
furi_hal_rfid_comp_start();
|
|
||||||
}
|
|
||||||
|
|
||||||
void HIDReader::stop_comparator(void) {
|
|
||||||
furi_hal_rfid_comp_stop();
|
|
||||||
furi_hal_rfid_comp_set_callback(NULL, NULL);
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include "decoder_hid.h"
|
|
||||||
#include "key_info.h"
|
|
||||||
|
|
||||||
//#define RFID_GPIO_DEBUG 1
|
|
||||||
|
|
||||||
class HIDReader {
|
|
||||||
public:
|
|
||||||
enum class Type : uint8_t {
|
|
||||||
Normal,
|
|
||||||
Indala,
|
|
||||||
};
|
|
||||||
|
|
||||||
HIDReader();
|
|
||||||
void start();
|
|
||||||
void start_forced(HIDReader::Type type);
|
|
||||||
void stop();
|
|
||||||
bool read(LfrfidKeyType* _type, uint8_t* data, uint8_t data_size, bool switch_enable = true);
|
|
||||||
|
|
||||||
bool detect();
|
|
||||||
bool any_read();
|
|
||||||
|
|
||||||
private:
|
|
||||||
friend struct HIDReaderAccessor;
|
|
||||||
|
|
||||||
DecoderHID decoder_hid;
|
|
||||||
|
|
||||||
uint32_t last_dwt_value;
|
|
||||||
|
|
||||||
void start_comparator(void);
|
|
||||||
void stop_comparator(void);
|
|
||||||
|
|
||||||
void decode(bool polarity);
|
|
||||||
|
|
||||||
uint32_t detect_ticks;
|
|
||||||
|
|
||||||
uint32_t switch_os_tick_last;
|
|
||||||
bool switch_timer_elapsed();
|
|
||||||
void switch_timer_reset();
|
|
||||||
void switch_mode();
|
|
||||||
|
|
||||||
LfrfidKeyType last_read_type;
|
|
||||||
uint8_t last_read_data[LFRFID_KEY_SIZE];
|
|
||||||
uint8_t last_read_count;
|
|
||||||
|
|
||||||
Type type = Type::Normal;
|
|
||||||
};
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
#include "hid_worker.h"
|
|
||||||
|
|
||||||
HIDWorker::HIDWorker() {
|
|
||||||
}
|
|
||||||
|
|
||||||
HIDWorker::~HIDWorker() {
|
|
||||||
}
|
|
||||||
|
|
||||||
void HIDWorker::start_read() {
|
|
||||||
reader.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool HIDWorker::read() {
|
|
||||||
static const uint8_t data_size = LFRFID_KEY_SIZE;
|
|
||||||
uint8_t data[data_size] = {0};
|
|
||||||
LfrfidKeyType type;
|
|
||||||
|
|
||||||
bool result = reader.read(&type, data, data_size);
|
|
||||||
|
|
||||||
if(result) {
|
|
||||||
key.set_type(type);
|
|
||||||
key.set_data(data, data_size);
|
|
||||||
};
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool HIDWorker::detect() {
|
|
||||||
return reader.detect();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool HIDWorker::any_read() {
|
|
||||||
return reader.any_read();
|
|
||||||
}
|
|
||||||
|
|
||||||
void HIDWorker::stop_read() {
|
|
||||||
reader.stop();
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include "key_info.h"
|
|
||||||
#include "rfid_key.h"
|
|
||||||
#include "hid_reader.h"
|
|
||||||
|
|
||||||
class HIDWorker {
|
|
||||||
public:
|
|
||||||
HIDWorker();
|
|
||||||
~HIDWorker();
|
|
||||||
|
|
||||||
void start_read();
|
|
||||||
bool read();
|
|
||||||
bool detect();
|
|
||||||
bool any_read();
|
|
||||||
void stop_read();
|
|
||||||
|
|
||||||
RfidKey key;
|
|
||||||
|
|
||||||
private:
|
|
||||||
HIDReader reader;
|
|
||||||
};
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
static const uint8_t LFRFID_KEY_SIZE = 8;
|
|
||||||
static const uint8_t LFRFID_KEY_NAME_SIZE = 22;
|
|
||||||
|
|
||||||
enum class LfrfidKeyType : uint8_t {
|
|
||||||
KeyEM4100,
|
|
||||||
KeyH10301,
|
|
||||||
KeyI40134,
|
|
||||||
};
|
|
||||||
|
|
||||||
const char* lfrfid_key_get_type_string(LfrfidKeyType type);
|
|
||||||
const char* lfrfid_key_get_manufacturer_string(LfrfidKeyType type);
|
|
||||||
bool lfrfid_key_get_string_type(const char* string, LfrfidKeyType* type);
|
|
||||||
uint8_t lfrfid_key_get_type_data_count(LfrfidKeyType type);
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This code tries to fit the periods into a given number of cycles (phases) by taking cycles from the next cycle of periods.
|
|
||||||
*/
|
|
||||||
class OscFSK {
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* Get next period
|
|
||||||
* @param bit bit value
|
|
||||||
* @param period return period
|
|
||||||
* @return bool whether to advance to the next bit
|
|
||||||
*/
|
|
||||||
bool next(bool bit, uint16_t* period);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* FSK ocillator constructor
|
|
||||||
*
|
|
||||||
* @param freq_low bit 0 freq
|
|
||||||
* @param freq_hi bit 1 freq
|
|
||||||
* @param osc_phase_max max oscillator phase
|
|
||||||
*/
|
|
||||||
OscFSK(uint16_t freq_low, uint16_t freq_hi, uint16_t osc_phase_max);
|
|
||||||
|
|
||||||
private:
|
|
||||||
const uint16_t freq[2];
|
|
||||||
const uint16_t osc_phase_max;
|
|
||||||
int32_t osc_phase_current;
|
|
||||||
};
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include "stdint.h"
|
|
||||||
#include "stdbool.h"
|
|
||||||
|
|
||||||
class ProtocolGeneric {
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Get the encoded data size
|
|
||||||
*
|
|
||||||
* @return uint8_t size of encoded data in bytes
|
|
||||||
*/
|
|
||||||
virtual uint8_t get_encoded_data_size() = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Get the decoded data size
|
|
||||||
*
|
|
||||||
* @return uint8_t size of decoded data in bytes
|
|
||||||
*/
|
|
||||||
virtual uint8_t get_decoded_data_size() = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief encode decoded data
|
|
||||||
*
|
|
||||||
* @param decoded_data
|
|
||||||
* @param decoded_data_size
|
|
||||||
* @param encoded_data
|
|
||||||
* @param encoded_data_size
|
|
||||||
*/
|
|
||||||
virtual void encode(
|
|
||||||
const uint8_t* decoded_data,
|
|
||||||
const uint8_t decoded_data_size,
|
|
||||||
uint8_t* encoded_data,
|
|
||||||
const uint8_t encoded_data_size) = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief decode encoded data
|
|
||||||
*
|
|
||||||
* @param encoded_data
|
|
||||||
* @param encoded_data_size
|
|
||||||
* @param decoded_data
|
|
||||||
* @param decoded_data_size
|
|
||||||
*/
|
|
||||||
virtual void decode(
|
|
||||||
const uint8_t* encoded_data,
|
|
||||||
const uint8_t encoded_data_size,
|
|
||||||
uint8_t* decoded_data,
|
|
||||||
const uint8_t decoded_data_size) = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief fast check that data can be correctly decoded
|
|
||||||
*
|
|
||||||
* @param encoded_data
|
|
||||||
* @param encoded_data_size
|
|
||||||
* @return true - can be correctly decoded
|
|
||||||
* @return false - cannot be correctly decoded
|
|
||||||
*/
|
|
||||||
virtual bool can_be_decoded(const uint8_t* encoded_data, const uint8_t encoded_data_size) = 0;
|
|
||||||
|
|
||||||
virtual ~ProtocolGeneric(){};
|
|
||||||
};
|
|
||||||
@@ -1,155 +0,0 @@
|
|||||||
#include "protocol_hid.h"
|
|
||||||
#include <furi.h>
|
|
||||||
|
|
||||||
typedef uint32_t HIDCardData;
|
|
||||||
constexpr uint8_t HIDCount = 3;
|
|
||||||
constexpr uint8_t HIDBitSize = sizeof(HIDCardData) * 8;
|
|
||||||
|
|
||||||
uint8_t ProtocolHID::get_encoded_data_size() {
|
|
||||||
return sizeof(HIDCardData) * HIDCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t ProtocolHID::get_decoded_data_size() {
|
|
||||||
return 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProtocolHID::encode(
|
|
||||||
const uint8_t* decoded_data,
|
|
||||||
const uint8_t decoded_data_size,
|
|
||||||
uint8_t* encoded_data,
|
|
||||||
const uint8_t encoded_data_size) {
|
|
||||||
UNUSED(decoded_data);
|
|
||||||
UNUSED(decoded_data_size);
|
|
||||||
UNUSED(encoded_data);
|
|
||||||
UNUSED(encoded_data_size);
|
|
||||||
// bob!
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProtocolHID::decode(
|
|
||||||
const uint8_t* encoded_data,
|
|
||||||
const uint8_t encoded_data_size,
|
|
||||||
uint8_t* decoded_data,
|
|
||||||
const uint8_t decoded_data_size) {
|
|
||||||
furi_check(decoded_data_size >= get_decoded_data_size());
|
|
||||||
furi_check(encoded_data_size >= get_encoded_data_size());
|
|
||||||
|
|
||||||
// header check
|
|
||||||
int16_t second1pos = find_second_1(encoded_data);
|
|
||||||
|
|
||||||
if((*(encoded_data + 1) & 0b1100) != 0x08) {
|
|
||||||
*decoded_data = 37;
|
|
||||||
} else {
|
|
||||||
*decoded_data = (36 - (second1pos - 8));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int16_t ProtocolHID::find_second_1(const uint8_t* encoded_data) {
|
|
||||||
if((*(encoded_data + 1) & 0b11) == 0b10) {
|
|
||||||
return 8;
|
|
||||||
} else {
|
|
||||||
for(int8_t i = 3; i >= 0; i--) {
|
|
||||||
if(((*(encoded_data + 0) >> (2 * i)) & 0b11) == 0b10) {
|
|
||||||
return (12 - i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for(int8_t i = 3; i >= 0; i--) {
|
|
||||||
if(((*(encoded_data + 7) >> (2 * i)) & 0b11) == 0b10) {
|
|
||||||
return (16 - i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for(int8_t i = 3; i >= 2; i--) {
|
|
||||||
if(((*(encoded_data + 6) >> (2 * i)) & 0b11) == 0b10) {
|
|
||||||
return (20 - i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ProtocolHID::can_be_decoded(const uint8_t* encoded_data, const uint8_t encoded_data_size) {
|
|
||||||
furi_check(encoded_data_size >= get_encoded_data_size());
|
|
||||||
|
|
||||||
const HIDCardData* card_data = reinterpret_cast<const HIDCardData*>(encoded_data);
|
|
||||||
|
|
||||||
// header check
|
|
||||||
int16_t second1pos = -1;
|
|
||||||
// packet pre-preamble
|
|
||||||
if(*(encoded_data + 3) != 0x1D) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// packet preamble
|
|
||||||
if(*(encoded_data + 2) != 0x55) { // first four 0s mandatory in preamble
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if((*(encoded_data + 1) & 0xF0) != 0x50) { // next two 0s mandatory in preamble
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if((*(encoded_data + 1) & 0b1100) != 0x08) { // if it's not a 1...
|
|
||||||
// either it's a 37-bit or invalid
|
|
||||||
// so just continue with the manchester encoding checks
|
|
||||||
} else { // it is a 1. so it could be anywhere between 26 and 36 bit encoding. or invalid.
|
|
||||||
// we need to find the location of the second 1
|
|
||||||
second1pos = find_second_1(encoded_data);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(second1pos == -1) {
|
|
||||||
// we're 37 bit or invalid
|
|
||||||
}
|
|
||||||
|
|
||||||
// data decoding. ensure all is properly manchester encoded
|
|
||||||
uint32_t result = 0;
|
|
||||||
|
|
||||||
// decode from word 0
|
|
||||||
// coded with 01 = 0, 10 = 1 transitions
|
|
||||||
for(int8_t i = 11; i >= 0; i--) {
|
|
||||||
switch((*(card_data + 0) >> (2 * i)) & 0b11) {
|
|
||||||
case 0b01:
|
|
||||||
result = (result << 1) | 0;
|
|
||||||
break;
|
|
||||||
case 0b10:
|
|
||||||
result = (result << 1) | 1;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// decode from word 1
|
|
||||||
// coded with 01 = 0, 10 = 1 transitions
|
|
||||||
for(int8_t i = 15; i >= 0; i--) {
|
|
||||||
switch((*(card_data + 1) >> (2 * i)) & 0b11) {
|
|
||||||
case 0b01:
|
|
||||||
result = (result << 1) | 0;
|
|
||||||
break;
|
|
||||||
case 0b10:
|
|
||||||
result = (result << 1) | 1;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// decode from word 2
|
|
||||||
// coded with 01 = 0, 10 = 1 transitions
|
|
||||||
for(int8_t i = 15; i >= 0; i--) {
|
|
||||||
switch((*(card_data + 2) >> (2 * i)) & 0b11) {
|
|
||||||
case 0b01:
|
|
||||||
result = (result << 1) | 0;
|
|
||||||
break;
|
|
||||||
case 0b10:
|
|
||||||
result = (result << 1) | 1;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include "protocol_generic.h"
|
|
||||||
|
|
||||||
class ProtocolHID : public ProtocolGeneric {
|
|
||||||
public:
|
|
||||||
uint8_t get_encoded_data_size() final;
|
|
||||||
uint8_t get_decoded_data_size() final;
|
|
||||||
|
|
||||||
void encode(
|
|
||||||
const uint8_t* decoded_data,
|
|
||||||
const uint8_t decoded_data_size,
|
|
||||||
uint8_t* encoded_data,
|
|
||||||
const uint8_t encoded_data_size) final;
|
|
||||||
|
|
||||||
void decode(
|
|
||||||
const uint8_t* encoded_data,
|
|
||||||
const uint8_t encoded_data_size,
|
|
||||||
uint8_t* decoded_data,
|
|
||||||
const uint8_t decoded_data_size) final;
|
|
||||||
|
|
||||||
bool can_be_decoded(const uint8_t* encoded_data, const uint8_t encoded_data_size) final;
|
|
||||||
|
|
||||||
private:
|
|
||||||
int16_t find_second_1(const uint8_t* encoded_data);
|
|
||||||
};
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include "stdint.h"
|
|
||||||
|
|
||||||
class PulseJoiner {
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Push timer pulse. First negative pulse is ommited.
|
|
||||||
*
|
|
||||||
* @param polarity pulse polarity: true = high2low, false = low2high
|
|
||||||
* @param period overall period time in timer clicks
|
|
||||||
* @param pulse pulse time in timer clicks
|
|
||||||
*
|
|
||||||
* @return true - next pulse can and must be popped immediatly
|
|
||||||
*/
|
|
||||||
bool push_pulse(bool polarity, uint16_t period, uint16_t pulse);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Get the next timer pulse. Call only if push_pulse returns true.
|
|
||||||
*
|
|
||||||
* @param period overall period time in timer clicks
|
|
||||||
* @param pulse pulse time in timer clicks
|
|
||||||
*/
|
|
||||||
void pop_pulse(uint16_t* period, uint16_t* pulse);
|
|
||||||
|
|
||||||
PulseJoiner();
|
|
||||||
|
|
||||||
private:
|
|
||||||
struct Pulse {
|
|
||||||
bool polarity;
|
|
||||||
uint16_t time;
|
|
||||||
};
|
|
||||||
|
|
||||||
uint8_t pulse_index = 0;
|
|
||||||
static const uint8_t pulse_max = 6;
|
|
||||||
Pulse pulses[pulse_max];
|
|
||||||
};
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include "key_info.h"
|
|
||||||
#include <array>
|
|
||||||
|
|
||||||
class RfidKey {
|
|
||||||
public:
|
|
||||||
RfidKey();
|
|
||||||
~RfidKey();
|
|
||||||
|
|
||||||
void set_type(LfrfidKeyType type);
|
|
||||||
void set_data(const uint8_t* data, const uint8_t data_size);
|
|
||||||
void set_name(const char* name);
|
|
||||||
|
|
||||||
LfrfidKeyType get_type();
|
|
||||||
const uint8_t* get_data();
|
|
||||||
const char* get_type_text();
|
|
||||||
uint8_t get_type_data_count() const;
|
|
||||||
char* get_name();
|
|
||||||
uint8_t get_name_length();
|
|
||||||
void clear();
|
|
||||||
RfidKey& operator=(const RfidKey& rhs);
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::array<uint8_t, LFRFID_KEY_SIZE> data;
|
|
||||||
LfrfidKeyType type;
|
|
||||||
char name[LFRFID_KEY_NAME_SIZE + 1];
|
|
||||||
};
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include <furi_hal.h>
|
|
||||||
#include "key_info.h"
|
|
||||||
#include "encoder_generic.h"
|
|
||||||
#include "encoder_emmarin.h"
|
|
||||||
#include "encoder_hid_h10301.h"
|
|
||||||
#include "encoder_indala_40134.h"
|
|
||||||
#include "pulse_joiner.h"
|
|
||||||
#include <map>
|
|
||||||
|
|
||||||
class RfidTimerEmulator {
|
|
||||||
public:
|
|
||||||
RfidTimerEmulator();
|
|
||||||
~RfidTimerEmulator();
|
|
||||||
void start(LfrfidKeyType type, const uint8_t* data, uint8_t data_size);
|
|
||||||
void stop();
|
|
||||||
|
|
||||||
private:
|
|
||||||
EncoderGeneric* current_encoder = nullptr;
|
|
||||||
|
|
||||||
std::map<LfrfidKeyType, EncoderGeneric*> encoders = {
|
|
||||||
{LfrfidKeyType::KeyEM4100, new EncoderEM()},
|
|
||||||
{LfrfidKeyType::KeyH10301, new EncoderHID_H10301()},
|
|
||||||
{LfrfidKeyType::KeyI40134, new EncoderIndala_40134()},
|
|
||||||
};
|
|
||||||
|
|
||||||
PulseJoiner pulse_joiner;
|
|
||||||
static void timer_update_callback(void* ctx);
|
|
||||||
};
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include "stdint.h"
|
|
||||||
|
|
||||||
class RfidWriter {
|
|
||||||
public:
|
|
||||||
RfidWriter();
|
|
||||||
~RfidWriter();
|
|
||||||
void start();
|
|
||||||
void stop();
|
|
||||||
void write_em(const uint8_t em_data[5]);
|
|
||||||
void write_hid(const uint8_t hid_data[3]);
|
|
||||||
void write_indala(const uint8_t indala_data[3]);
|
|
||||||
|
|
||||||
private:
|
|
||||||
void write_gap(uint32_t gap_time);
|
|
||||||
void write_bit(bool value);
|
|
||||||
void write_byte(uint8_t value);
|
|
||||||
void write_block(uint8_t page, uint8_t block, bool lock_bit, uint32_t data);
|
|
||||||
void write_reset();
|
|
||||||
};
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include "stdint.h"
|
|
||||||
#include <list>
|
|
||||||
#include <functional>
|
|
||||||
|
|
||||||
class TickSequencer {
|
|
||||||
public:
|
|
||||||
TickSequencer();
|
|
||||||
~TickSequencer();
|
|
||||||
|
|
||||||
void tick();
|
|
||||||
void reset();
|
|
||||||
void clear();
|
|
||||||
|
|
||||||
void do_every_tick(uint32_t tick_count, std::function<void(void)> fn);
|
|
||||||
void do_after_tick(uint32_t tick_count, std::function<void(void)> fn);
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::list<std::pair<uint32_t, std::function<void(void)> > > list;
|
|
||||||
std::list<std::pair<uint32_t, std::function<void(void)> > >::iterator list_it;
|
|
||||||
|
|
||||||
uint32_t tick_count;
|
|
||||||
|
|
||||||
void do_nothing();
|
|
||||||
};
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
#include "hid_analyzer_app.h"
|
|
||||||
#include "scene/hid_analyzer_app_scene_read.h"
|
|
||||||
#include "scene/hid_analyzer_app_scene_read_success.h"
|
|
||||||
|
|
||||||
HIDApp::HIDApp()
|
|
||||||
: scene_controller{this}
|
|
||||||
, notification{"notification"}
|
|
||||||
, storage{"storage"}
|
|
||||||
, dialogs{"dialogs"}
|
|
||||||
, text_store(40) {
|
|
||||||
}
|
|
||||||
|
|
||||||
HIDApp::~HIDApp() {
|
|
||||||
}
|
|
||||||
|
|
||||||
void HIDApp::run(void* _args) {
|
|
||||||
UNUSED(_args);
|
|
||||||
|
|
||||||
view_controller.attach_to_gui(ViewDispatcherTypeFullscreen);
|
|
||||||
scene_controller.add_scene(SceneType::Read, new HIDAppSceneRead());
|
|
||||||
scene_controller.add_scene(SceneType::ReadSuccess, new HIDAppSceneReadSuccess());
|
|
||||||
scene_controller.process(100, SceneType::Read);
|
|
||||||
}
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <furi.h>
|
|
||||||
#include <furi_hal.h>
|
|
||||||
|
|
||||||
#include <generic_scene.hpp>
|
|
||||||
#include <scene_controller.hpp>
|
|
||||||
#include <view_controller.hpp>
|
|
||||||
#include <record_controller.hpp>
|
|
||||||
#include <text_store.h>
|
|
||||||
|
|
||||||
#include <view_modules/submenu_vm.h>
|
|
||||||
#include <view_modules/popup_vm.h>
|
|
||||||
#include <view_modules/dialog_ex_vm.h>
|
|
||||||
#include <view_modules/text_input_vm.h>
|
|
||||||
#include <view_modules/byte_input_vm.h>
|
|
||||||
#include "view/container_vm.h"
|
|
||||||
|
|
||||||
#include <notification/notification_messages.h>
|
|
||||||
#include <storage/storage.h>
|
|
||||||
#include <dialogs/dialogs.h>
|
|
||||||
|
|
||||||
#include "helpers/hid_worker.h"
|
|
||||||
|
|
||||||
class HIDApp {
|
|
||||||
public:
|
|
||||||
enum class EventType : uint8_t {
|
|
||||||
GENERIC_EVENT_ENUM_VALUES,
|
|
||||||
Next,
|
|
||||||
MenuSelected,
|
|
||||||
Stay,
|
|
||||||
Retry,
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class SceneType : uint8_t {
|
|
||||||
GENERIC_SCENE_ENUM_VALUES,
|
|
||||||
Read,
|
|
||||||
ReadSuccess,
|
|
||||||
};
|
|
||||||
|
|
||||||
class Event {
|
|
||||||
public:
|
|
||||||
union {
|
|
||||||
int32_t menu_index;
|
|
||||||
} payload;
|
|
||||||
|
|
||||||
EventType type;
|
|
||||||
};
|
|
||||||
|
|
||||||
HIDApp();
|
|
||||||
~HIDApp();
|
|
||||||
|
|
||||||
void run(void* args);
|
|
||||||
|
|
||||||
// private:
|
|
||||||
SceneController<GenericScene<HIDApp>, HIDApp> scene_controller;
|
|
||||||
ViewController<HIDApp, SubmenuVM, PopupVM, DialogExVM, TextInputVM, ByteInputVM, ContainerVM>
|
|
||||||
view_controller;
|
|
||||||
RecordController<NotificationApp> notification;
|
|
||||||
RecordController<Storage> storage;
|
|
||||||
RecordController<DialogsApp> dialogs;
|
|
||||||
TextStore text_store;
|
|
||||||
|
|
||||||
HIDWorker worker;
|
|
||||||
};
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
#include "hid_analyzer_app.h"
|
|
||||||
|
|
||||||
// app enter function
|
|
||||||
extern "C" int32_t hid_analyzer_app(void* args) {
|
|
||||||
HIDApp* app = new HIDApp();
|
|
||||||
app->run(args);
|
|
||||||
delete app;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
#include "hid_analyzer_app_scene_read.h"
|
|
||||||
#include <dolphin/dolphin.h>
|
|
||||||
|
|
||||||
void HIDAppSceneRead::on_enter(HIDApp* app, bool /* need_restore */) {
|
|
||||||
auto popup = app->view_controller.get<PopupVM>();
|
|
||||||
|
|
||||||
DOLPHIN_DEED(DolphinDeedRfidRead);
|
|
||||||
popup->set_header("Searching for\nLF HID RFID", 89, 34, AlignCenter, AlignTop);
|
|
||||||
popup->set_icon(0, 3, &I_RFIDDolphinReceive_97x61);
|
|
||||||
|
|
||||||
app->view_controller.switch_to<PopupVM>();
|
|
||||||
app->worker.start_read();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool HIDAppSceneRead::on_event(HIDApp* app, HIDApp::Event* event) {
|
|
||||||
bool consumed = false;
|
|
||||||
|
|
||||||
if(event->type == HIDApp::EventType::Tick) {
|
|
||||||
if(app->worker.read()) {
|
|
||||||
DOLPHIN_DEED(DolphinDeedRfidReadSuccess);
|
|
||||||
notification_message(app->notification, &sequence_success);
|
|
||||||
app->scene_controller.switch_to_next_scene(HIDApp::SceneType::ReadSuccess);
|
|
||||||
} else {
|
|
||||||
if(app->worker.any_read()) {
|
|
||||||
notification_message(app->notification, &sequence_blink_green_10);
|
|
||||||
} else if(app->worker.detect()) {
|
|
||||||
notification_message(app->notification, &sequence_blink_cyan_10);
|
|
||||||
} else {
|
|
||||||
notification_message(app->notification, &sequence_blink_cyan_10);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return consumed;
|
|
||||||
}
|
|
||||||
|
|
||||||
void HIDAppSceneRead::on_exit(HIDApp* app) {
|
|
||||||
app->view_controller.get<PopupVM>()->clean();
|
|
||||||
app->worker.stop_read();
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include "../hid_analyzer_app.h"
|
|
||||||
|
|
||||||
class HIDAppSceneRead : public GenericScene<HIDApp> {
|
|
||||||
public:
|
|
||||||
void on_enter(HIDApp* app, bool need_restore) final;
|
|
||||||
bool on_event(HIDApp* app, HIDApp::Event* event) final;
|
|
||||||
void on_exit(HIDApp* app) final;
|
|
||||||
};
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
#include "hid_analyzer_app_scene_read_success.h"
|
|
||||||
#include "../view/elements/button_element.h"
|
|
||||||
#include "../view/elements/icon_element.h"
|
|
||||||
#include "../view/elements/string_element.h"
|
|
||||||
|
|
||||||
void HIDAppSceneReadSuccess::on_enter(HIDApp* app, bool /* need_restore */) {
|
|
||||||
string_init(string[0]);
|
|
||||||
string_init(string[1]);
|
|
||||||
string_init(string[2]);
|
|
||||||
|
|
||||||
auto container = app->view_controller.get<ContainerVM>();
|
|
||||||
|
|
||||||
auto button = container->add<ButtonElement>();
|
|
||||||
button->set_type(ButtonElement::Type::Left, "Retry");
|
|
||||||
button->set_callback(app, HIDAppSceneReadSuccess::back_callback);
|
|
||||||
|
|
||||||
auto icon = container->add<IconElement>();
|
|
||||||
icon->set_icon(3, 12, &I_RFIDBigChip_37x36);
|
|
||||||
|
|
||||||
auto header = container->add<StringElement>();
|
|
||||||
header->set_text("HID", 89, 3, 0, AlignCenter);
|
|
||||||
|
|
||||||
// auto line_1_text = container->add<StringElement>();
|
|
||||||
auto line_2_text = container->add<StringElement>();
|
|
||||||
// auto line_3_text = container->add<StringElement>();
|
|
||||||
|
|
||||||
// auto line_1_value = container->add<StringElement>();
|
|
||||||
auto line_2_value = container->add<StringElement>();
|
|
||||||
// auto line_3_value = container->add<StringElement>();
|
|
||||||
|
|
||||||
const uint8_t* data = app->worker.key.get_data();
|
|
||||||
|
|
||||||
// line_1_text->set_text("Hi:", 65, 23, 0, AlignRight, AlignBottom, FontSecondary);
|
|
||||||
line_2_text->set_text("Bit:", 65, 35, 0, AlignRight, AlignBottom, FontSecondary);
|
|
||||||
// line_3_text->set_text("Bye:", 65, 47, 0, AlignRight, AlignBottom, FontSecondary);
|
|
||||||
|
|
||||||
string_printf(string[1], "%u", data[0]);
|
|
||||||
|
|
||||||
// line_1_value->set_text(
|
|
||||||
// string_get_cstr(string[0]), 68, 23, 0, AlignLeft, AlignBottom, FontSecondary);
|
|
||||||
line_2_value->set_text(
|
|
||||||
string_get_cstr(string[1]), 68, 35, 0, AlignLeft, AlignBottom, FontSecondary);
|
|
||||||
// line_3_value->set_text(
|
|
||||||
// string_get_cstr(string[2]), 68, 47, 0, AlignLeft, AlignBottom, FontSecondary);
|
|
||||||
|
|
||||||
app->view_controller.switch_to<ContainerVM>();
|
|
||||||
|
|
||||||
notification_message_block(app->notification, &sequence_set_green_255);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool HIDAppSceneReadSuccess::on_event(HIDApp* app, HIDApp::Event* event) {
|
|
||||||
bool consumed = false;
|
|
||||||
|
|
||||||
if(event->type == HIDApp::EventType::Retry) {
|
|
||||||
app->scene_controller.search_and_switch_to_previous_scene({HIDApp::SceneType::Read});
|
|
||||||
consumed = true;
|
|
||||||
} else if(event->type == HIDApp::EventType::Back) {
|
|
||||||
app->scene_controller.search_and_switch_to_previous_scene({HIDApp::SceneType::Read});
|
|
||||||
consumed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return consumed;
|
|
||||||
}
|
|
||||||
|
|
||||||
void HIDAppSceneReadSuccess::on_exit(HIDApp* app) {
|
|
||||||
notification_message_block(app->notification, &sequence_reset_green);
|
|
||||||
app->view_controller.get<ContainerVM>()->clean();
|
|
||||||
string_clear(string[0]);
|
|
||||||
string_clear(string[1]);
|
|
||||||
string_clear(string[2]);
|
|
||||||
}
|
|
||||||
|
|
||||||
void HIDAppSceneReadSuccess::back_callback(void* context) {
|
|
||||||
HIDApp* app = static_cast<HIDApp*>(context);
|
|
||||||
HIDApp::Event event;
|
|
||||||
event.type = HIDApp::EventType::Retry;
|
|
||||||
app->view_controller.send_event(&event);
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include "../hid_analyzer_app.h"
|
|
||||||
|
|
||||||
class HIDAppSceneReadSuccess : public GenericScene<HIDApp> {
|
|
||||||
public:
|
|
||||||
void on_enter(HIDApp* app, bool need_restore) final;
|
|
||||||
bool on_event(HIDApp* app, HIDApp::Event* event) final;
|
|
||||||
void on_exit(HIDApp* app) final;
|
|
||||||
|
|
||||||
private:
|
|
||||||
static void back_callback(void* context);
|
|
||||||
|
|
||||||
string_t string[3];
|
|
||||||
};
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include <view_modules/generic_view_module.h>
|
|
||||||
|
|
||||||
class ContainerVM : public GenericViewModule {
|
|
||||||
public:
|
|
||||||
ContainerVM();
|
|
||||||
~ContainerVM() final;
|
|
||||||
View* get_view() final;
|
|
||||||
void clean() final;
|
|
||||||
|
|
||||||
template <typename T> T* add();
|
|
||||||
|
|
||||||
private:
|
|
||||||
View* view;
|
|
||||||
static void view_draw_callback(Canvas* canvas, void* model);
|
|
||||||
static bool view_input_callback(InputEvent* event, void* context);
|
|
||||||
};
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include "generic_element.h"
|
|
||||||
|
|
||||||
typedef void (*ButtonElementCallback)(void* context);
|
|
||||||
|
|
||||||
class ButtonElement : public GenericElement {
|
|
||||||
public:
|
|
||||||
ButtonElement();
|
|
||||||
~ButtonElement() final;
|
|
||||||
void draw(Canvas* canvas) final;
|
|
||||||
bool input(InputEvent* event) final;
|
|
||||||
|
|
||||||
enum class Type : uint8_t {
|
|
||||||
Left,
|
|
||||||
Center,
|
|
||||||
Right,
|
|
||||||
};
|
|
||||||
|
|
||||||
void set_type(Type type, const char* text);
|
|
||||||
void set_callback(void* context, ButtonElementCallback callback);
|
|
||||||
|
|
||||||
private:
|
|
||||||
Type type = Type::Left;
|
|
||||||
const char* text = nullptr;
|
|
||||||
|
|
||||||
void* context = nullptr;
|
|
||||||
ButtonElementCallback callback = nullptr;
|
|
||||||
};
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include <gui/gui.h>
|
|
||||||
#include <gui/view.h>
|
|
||||||
|
|
||||||
class GenericElement {
|
|
||||||
public:
|
|
||||||
GenericElement(){};
|
|
||||||
virtual ~GenericElement(){};
|
|
||||||
virtual void draw(Canvas* canvas) = 0;
|
|
||||||
virtual bool input(InputEvent* event) = 0;
|
|
||||||
|
|
||||||
// TODO that must be accessible only to ContainerVMData
|
|
||||||
void set_parent_view(View* view);
|
|
||||||
|
|
||||||
// TODO that must be accessible only to inheritors
|
|
||||||
void lock_model();
|
|
||||||
void unlock_model(bool need_redraw);
|
|
||||||
|
|
||||||
private:
|
|
||||||
View* view = nullptr;
|
|
||||||
};
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include "generic_element.h"
|
|
||||||
|
|
||||||
class IconElement : public GenericElement {
|
|
||||||
public:
|
|
||||||
IconElement();
|
|
||||||
~IconElement() final;
|
|
||||||
void draw(Canvas* canvas) final;
|
|
||||||
bool input(InputEvent* event) final;
|
|
||||||
|
|
||||||
void set_icon(uint8_t x = 0, uint8_t y = 0, const Icon* icon = NULL);
|
|
||||||
|
|
||||||
private:
|
|
||||||
const Icon* icon = NULL;
|
|
||||||
uint8_t x = 0;
|
|
||||||
uint8_t y = 0;
|
|
||||||
};
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include "generic_element.h"
|
|
||||||
|
|
||||||
class StringElement : public GenericElement {
|
|
||||||
public:
|
|
||||||
StringElement();
|
|
||||||
~StringElement() final;
|
|
||||||
void draw(Canvas* canvas) final;
|
|
||||||
bool input(InputEvent* event) final;
|
|
||||||
|
|
||||||
void set_text(
|
|
||||||
const char* text = NULL,
|
|
||||||
uint8_t x = 0,
|
|
||||||
uint8_t y = 0,
|
|
||||||
uint8_t fit_width = 0,
|
|
||||||
Align horizontal = AlignLeft,
|
|
||||||
Align vertical = AlignTop,
|
|
||||||
Font font = FontPrimary);
|
|
||||||
|
|
||||||
private:
|
|
||||||
const char* text = NULL;
|
|
||||||
uint8_t x = 0;
|
|
||||||
uint8_t y = 0;
|
|
||||||
uint8_t fit_width = 0;
|
|
||||||
Align horizontal = AlignLeft;
|
|
||||||
Align vertical = AlignTop;
|
|
||||||
Font font = FontPrimary;
|
|
||||||
};
|
|
||||||
@@ -68,7 +68,6 @@ App(
|
|||||||
apptype=FlipperAppType.METAPACKAGE,
|
apptype=FlipperAppType.METAPACKAGE,
|
||||||
provides=[
|
provides=[
|
||||||
"picopass",
|
"picopass",
|
||||||
"hid_analyzer",
|
|
||||||
"barcode_generator",
|
"barcode_generator",
|
||||||
"mouse_jacker",
|
"mouse_jacker",
|
||||||
"nrf_sniff",
|
"nrf_sniff",
|
||||||
@@ -77,6 +76,6 @@ App(
|
|||||||
"esp8266_deauth",
|
"esp8266_deauth",
|
||||||
"wifi_scanner",
|
"wifi_scanner",
|
||||||
"wav_player",
|
"wav_player",
|
||||||
"dec_hex_converter",
|
"multi_converter",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
App(
|
App(
|
||||||
appid="dec_hex_converter",
|
appid="multi_converter",
|
||||||
name="Dec/Hex Converter",
|
name="Multi Converter",
|
||||||
apptype=FlipperAppType.PLUGIN,
|
apptype=FlipperAppType.PLUGIN,
|
||||||
entry_point="dec_hex_converter_app",
|
entry_point="multi_converter_app",
|
||||||
cdefines=["APP_DEC_HEX_CONVERTER"],
|
cdefines=["APP_DEC_HEX_CONVERTER"],
|
||||||
requires=["gui"],
|
requires=["gui"],
|
||||||
stack_size=1 * 1024,
|
stack_size=1 * 1024,
|
||||||
162
applications/multi_converter/multi_converter.c
Normal file
162
applications/multi_converter/multi_converter.c
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
#include <furi.h>
|
||||||
|
#include <gui/gui.h>
|
||||||
|
#include <input/input.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#include "multi_converter_definitions.h"
|
||||||
|
#include "multi_converter_mode_display.h"
|
||||||
|
#include "multi_converter_mode_select.h"
|
||||||
|
|
||||||
|
static void multi_converter_render_callback(Canvas* const canvas, void* ctx) {
|
||||||
|
|
||||||
|
const MultiConverterState* multi_converter_state = acquire_mutex((ValueMutex*)ctx, 25);
|
||||||
|
if(multi_converter_state == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (multi_converter_state->mode == ModeDisplay) {
|
||||||
|
multi_converter_mode_display_draw(canvas, multi_converter_state);
|
||||||
|
} else {
|
||||||
|
multi_converter_mode_select_draw(canvas, multi_converter_state);
|
||||||
|
}
|
||||||
|
|
||||||
|
release_mutex((ValueMutex*)ctx, multi_converter_state);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void multi_converter_input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
|
||||||
|
furi_assert(event_queue);
|
||||||
|
|
||||||
|
MultiConverterEvent event = {.type = EventTypeKey, .input = *input_event};
|
||||||
|
furi_message_queue_put(event_queue, &event, FuriWaitForever);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void multi_converter_init(MultiConverterState* const multi_converter_state) {
|
||||||
|
// initial default values
|
||||||
|
|
||||||
|
multi_converter_state->buffer_orig[MULTI_CONVERTER_NUMBER_DIGITS] = '\0';
|
||||||
|
multi_converter_state->buffer_dest[MULTI_CONVERTER_NUMBER_DIGITS] = '\0'; // null terminators
|
||||||
|
|
||||||
|
multi_converter_state->unit_type_orig = UnitTypeDec;
|
||||||
|
multi_converter_state->unit_type_dest = UnitTypeHex;
|
||||||
|
|
||||||
|
multi_converter_state->keyboard_lock = 0;
|
||||||
|
|
||||||
|
// init the display view
|
||||||
|
multi_converter_mode_display_reset(multi_converter_state);
|
||||||
|
|
||||||
|
// init the select view
|
||||||
|
multi_converter_mode_select_reset(multi_converter_state);
|
||||||
|
|
||||||
|
// set ModeDisplay as the current mode
|
||||||
|
multi_converter_state->mode = ModeDisplay;
|
||||||
|
}
|
||||||
|
|
||||||
|
// main entry point
|
||||||
|
int32_t multi_converter_app(void* p) {
|
||||||
|
UNUSED(p);
|
||||||
|
|
||||||
|
// get event queue
|
||||||
|
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(MultiConverterEvent));
|
||||||
|
|
||||||
|
// allocate state
|
||||||
|
MultiConverterState* multi_converter_state = malloc(sizeof(MultiConverterState));
|
||||||
|
|
||||||
|
// set mutex for plugin state (different threads can access it)
|
||||||
|
ValueMutex state_mutex;
|
||||||
|
if(!init_mutex(&state_mutex, multi_converter_state, sizeof(multi_converter_state))) {
|
||||||
|
FURI_LOG_E("MultiConverter", "cannot create mutex\r\n");
|
||||||
|
free(multi_converter_state);
|
||||||
|
return 255;
|
||||||
|
}
|
||||||
|
|
||||||
|
// register callbacks for drawing and input processing
|
||||||
|
ViewPort* view_port = view_port_alloc();
|
||||||
|
view_port_draw_callback_set(view_port, multi_converter_render_callback, &state_mutex);
|
||||||
|
view_port_input_callback_set(view_port, multi_converter_input_callback, event_queue);
|
||||||
|
|
||||||
|
// open GUI and register view_port
|
||||||
|
Gui* gui = furi_record_open("gui");
|
||||||
|
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
|
||||||
|
|
||||||
|
multi_converter_init(multi_converter_state);
|
||||||
|
|
||||||
|
// main loop
|
||||||
|
MultiConverterEvent event;
|
||||||
|
for (bool processing = true; processing;) {
|
||||||
|
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
|
||||||
|
MultiConverterState* multi_converter_state = (MultiConverterState*)acquire_mutex_block(&state_mutex);
|
||||||
|
|
||||||
|
if (event_status == FuriStatusOk) {
|
||||||
|
// press events
|
||||||
|
if (event.type == EventTypeKey && !multi_converter_state->keyboard_lock) {
|
||||||
|
if (multi_converter_state->mode == ModeDisplay) {
|
||||||
|
|
||||||
|
if (event.input.key == InputKeyBack) {
|
||||||
|
if (event.input.type == InputTypePress) processing = false;
|
||||||
|
} else if (event.input.key == InputKeyOk) { // the "ok" press can be short or long
|
||||||
|
MultiConverterModeTrigger t = None;
|
||||||
|
|
||||||
|
if (event.input.type == InputTypeLong) t = multi_converter_mode_display_ok(1, multi_converter_state);
|
||||||
|
else if (event.input.type == InputTypeShort) t = multi_converter_mode_display_ok(0, multi_converter_state);
|
||||||
|
|
||||||
|
if (t == Reset) {
|
||||||
|
multi_converter_mode_select_reset(multi_converter_state);
|
||||||
|
multi_converter_state->mode = ModeSelector;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (event.input.type == InputTypePress) multi_converter_mode_display_navigation(event.input.key, multi_converter_state);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else { // ModeSelect
|
||||||
|
if (event.input.type == InputTypePress) {
|
||||||
|
switch (event.input.key) {
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
case InputKeyBack:
|
||||||
|
case InputKeyOk: {
|
||||||
|
MultiConverterModeTrigger t = multi_converter_mode_select_exit(event.input.key == InputKeyOk ? 1 : 0, multi_converter_state);
|
||||||
|
|
||||||
|
if (t == Reset) {
|
||||||
|
multi_converter_mode_display_reset(multi_converter_state);
|
||||||
|
} else if (t == Convert) {
|
||||||
|
multi_converter_mode_display_convert(multi_converter_state);
|
||||||
|
}
|
||||||
|
|
||||||
|
multi_converter_state->keyboard_lock = 1;
|
||||||
|
multi_converter_state->mode = ModeDisplay;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case InputKeyLeft:
|
||||||
|
case InputKeyRight:
|
||||||
|
multi_converter_mode_select_switch(multi_converter_state);
|
||||||
|
break;
|
||||||
|
case InputKeyUp:
|
||||||
|
multi_converter_mode_select_change_unit(-1, multi_converter_state);
|
||||||
|
break;
|
||||||
|
case InputKeyDown:
|
||||||
|
multi_converter_mode_select_change_unit(1, multi_converter_state);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (multi_converter_state->keyboard_lock) {
|
||||||
|
multi_converter_state->keyboard_lock = 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// event timeout
|
||||||
|
}
|
||||||
|
|
||||||
|
view_port_update(view_port);
|
||||||
|
release_mutex(&state_mutex, multi_converter_state);
|
||||||
|
}
|
||||||
|
|
||||||
|
view_port_enabled_set(view_port, false);
|
||||||
|
gui_remove_view_port(gui, view_port);
|
||||||
|
furi_record_close("gui");
|
||||||
|
view_port_free(view_port);
|
||||||
|
furi_message_queue_free(event_queue);
|
||||||
|
delete_mutex(&state_mutex);
|
||||||
|
free(multi_converter_state);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
82
applications/multi_converter/multi_converter_definitions.h
Normal file
82
applications/multi_converter/multi_converter_definitions.h
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define MULTI_CONVERTER_NUMBER_DIGITS 9
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
EventTypeKey,
|
||||||
|
} EventType;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
InputEvent input;
|
||||||
|
EventType type;
|
||||||
|
} MultiConverterEvent;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
ModeDisplay,
|
||||||
|
ModeSelector,
|
||||||
|
} MultiConverterMode;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
None,
|
||||||
|
Reset,
|
||||||
|
Convert,
|
||||||
|
} MultiConverterModeTrigger;
|
||||||
|
|
||||||
|
// new units goes here, used as index to the main multi_converter_available_units array (multi_converter_units.h)
|
||||||
|
typedef enum {
|
||||||
|
UnitTypeDec,
|
||||||
|
UnitTypeHex,
|
||||||
|
UnitTypeBin,
|
||||||
|
|
||||||
|
UnitTypeCelsius,
|
||||||
|
UnitTypeFahernheit,
|
||||||
|
UnitTypeKelvin,
|
||||||
|
|
||||||
|
UnitTypeKilometers,
|
||||||
|
UnitTypeMeters,
|
||||||
|
UnitTypeCentimeters,
|
||||||
|
UnitTypeMiles,
|
||||||
|
UnitTypeFeet,
|
||||||
|
UnitTypeInches,
|
||||||
|
|
||||||
|
UnitTypeDegree,
|
||||||
|
UnitTypeRadian,
|
||||||
|
} MultiConverterUnitType;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
MultiConverterUnitType selected_unit_type_orig;
|
||||||
|
MultiConverterUnitType selected_unit_type_dest;
|
||||||
|
uint8_t select_orig;
|
||||||
|
} MultiConverterModeSelect;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8_t cursor; // cursor position when typing
|
||||||
|
int8_t key; // hover key
|
||||||
|
uint8_t comma; // comma already added? (only one comma allowed)
|
||||||
|
uint8_t negative; // is negative?
|
||||||
|
} MultiConverterModeDisplay;
|
||||||
|
|
||||||
|
typedef struct MultiConverterUnit MultiConverterUnit;
|
||||||
|
typedef struct MultiConverterState MultiConverterState;
|
||||||
|
|
||||||
|
struct MultiConverterUnit {
|
||||||
|
uint8_t allow_comma;
|
||||||
|
uint8_t allow_negative;
|
||||||
|
uint8_t max_number_keys;
|
||||||
|
char mini_name[4];
|
||||||
|
char name[12];
|
||||||
|
void (*convert_function)(MultiConverterState * const);
|
||||||
|
uint8_t (*allowed_function)(MultiConverterUnitType);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MultiConverterState {
|
||||||
|
char buffer_orig[MULTI_CONVERTER_NUMBER_DIGITS + 1];
|
||||||
|
char buffer_dest[MULTI_CONVERTER_NUMBER_DIGITS + 1];
|
||||||
|
MultiConverterUnitType unit_type_orig;
|
||||||
|
MultiConverterUnitType unit_type_dest;
|
||||||
|
MultiConverterMode mode;
|
||||||
|
MultiConverterModeDisplay display;
|
||||||
|
MultiConverterModeSelect select;
|
||||||
|
uint8_t keyboard_lock; // used to create a small lock when switching from SELECT to DISPLAY modes
|
||||||
|
// (debouncing, basically; otherwise it switch modes twice 'cause it's too fast!)
|
||||||
|
};
|
||||||
284
applications/multi_converter/multi_converter_mode_display.c
Normal file
284
applications/multi_converter/multi_converter_mode_display.c
Normal file
@@ -0,0 +1,284 @@
|
|||||||
|
#include "multi_converter_mode_display.h"
|
||||||
|
|
||||||
|
#define MULTI_CONVERTER_DISPLAY_KEYS 18 // [0] to [F] + [BACK] + [SELECT]
|
||||||
|
|
||||||
|
#define MULTI_CONVERTER_DISPLAY_KEY_NEGATIVE 0 // long press
|
||||||
|
#define MULTI_CONVERTER_DISPLAY_KEY_COMMA 1 // long press
|
||||||
|
#define MULTI_CONVERTER_DISPLAY_KEY_DEL 16
|
||||||
|
#define MULTI_CONVERTER_DISPLAY_KEY_SELECT 17
|
||||||
|
|
||||||
|
#define MULTI_CONVERTER_DISPLAY_CHAR_COMMA '.'
|
||||||
|
#define MULTI_CONVERTER_DISPLAY_CHAR_NEGATIVE '-'
|
||||||
|
#define MULTI_CONVERTER_DISPLAY_CHAR_DEL '<'
|
||||||
|
#define MULTI_CONVERTER_DISPLAY_CHAR_SELECT '#'
|
||||||
|
#define MULTI_CONVERTER_DISPLAY_CHAR_BLANK ' '
|
||||||
|
|
||||||
|
#define MULTI_CONVERTER_DISPLAY_KEY_FRAME_MARGIN 3
|
||||||
|
#define MULTI_CONVERTER_DISPLAY_KEY_CHAR_HEIGHT 8
|
||||||
|
|
||||||
|
|
||||||
|
void multi_converter_mode_display_convert(MultiConverterState* const multi_converter_state) {
|
||||||
|
|
||||||
|
// 1.- if origin == destination (in theory user won't be allowed to choose the same options, but it's kinda "valid"...)
|
||||||
|
// just copy buffer_orig to buffer_dest and that's it
|
||||||
|
|
||||||
|
if (multi_converter_state->unit_type_orig == multi_converter_state->unit_type_dest) {
|
||||||
|
memcpy(multi_converter_state->buffer_dest, multi_converter_state->buffer_orig, MULTI_CONVERTER_NUMBER_DIGITS);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2.- origin_buffer has not null functions
|
||||||
|
if (multi_converter_get_unit(multi_converter_state->unit_type_orig).convert_function == NULL || multi_converter_get_unit(multi_converter_state->unit_type_orig).allowed_function == NULL) return;
|
||||||
|
|
||||||
|
// 3.- valid destination type (using allowed_destinations function)
|
||||||
|
if (!multi_converter_get_unit(multi_converter_state->unit_type_orig).allowed_function(multi_converter_state->unit_type_dest)) return;
|
||||||
|
|
||||||
|
multi_converter_get_unit(multi_converter_state->unit_type_orig).convert_function(multi_converter_state);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void multi_converter_mode_display_draw(Canvas* const canvas, const MultiConverterState* multi_converter_state) {
|
||||||
|
|
||||||
|
canvas_set_color(canvas, ColorBlack);
|
||||||
|
|
||||||
|
// ORIGIN
|
||||||
|
canvas_set_font(canvas, FontPrimary);
|
||||||
|
canvas_draw_str(canvas, 2, 10, multi_converter_get_unit(multi_converter_state->unit_type_orig).mini_name);
|
||||||
|
|
||||||
|
canvas_set_font(canvas, FontPrimary);
|
||||||
|
canvas_draw_str(canvas, 2 + 30, 10, multi_converter_state->buffer_orig);
|
||||||
|
|
||||||
|
// DESTINATION
|
||||||
|
canvas_set_font(canvas, FontPrimary);
|
||||||
|
canvas_draw_str(canvas, 2, 10 + 12, multi_converter_get_unit(multi_converter_state->unit_type_dest).mini_name);
|
||||||
|
|
||||||
|
canvas_set_font(canvas, FontPrimary);
|
||||||
|
canvas_draw_str(canvas, 2 + 30, 10 + 12, multi_converter_state->buffer_dest);
|
||||||
|
|
||||||
|
// SEPARATOR_LINE
|
||||||
|
canvas_draw_line(canvas, 2, 25, 128 - 3, 25);
|
||||||
|
|
||||||
|
// KEYBOARD
|
||||||
|
uint8_t _x = 5;
|
||||||
|
uint8_t _y = 25 + 15; // line + 10
|
||||||
|
|
||||||
|
for (int i = 0; i < MULTI_CONVERTER_DISPLAY_KEYS; i++) {
|
||||||
|
|
||||||
|
char g;
|
||||||
|
if (i < 10) g = (i + '0');
|
||||||
|
else if (i < 16) g = ((i - 10) + 'A');
|
||||||
|
else if (i == MULTI_CONVERTER_DISPLAY_KEY_DEL) g = MULTI_CONVERTER_DISPLAY_CHAR_DEL;
|
||||||
|
else g = MULTI_CONVERTER_DISPLAY_CHAR_SELECT;
|
||||||
|
|
||||||
|
uint8_t g_w = canvas_glyph_width(canvas, g);
|
||||||
|
|
||||||
|
if (i < 16 && i > multi_converter_get_unit(multi_converter_state->unit_type_orig).max_number_keys-1) {
|
||||||
|
// some units won't use the full [0] - [F] keyboard, in those situations just hide the char
|
||||||
|
// (won't be selectable anyway, so no worries here; this is just about drawing stuff)
|
||||||
|
g = MULTI_CONVERTER_DISPLAY_CHAR_BLANK;
|
||||||
|
}
|
||||||
|
|
||||||
|
// currently hover key is highlighted
|
||||||
|
if ((multi_converter_state->display).key == i) {
|
||||||
|
canvas_draw_box(canvas,
|
||||||
|
_x - MULTI_CONVERTER_DISPLAY_KEY_FRAME_MARGIN,
|
||||||
|
_y - (MULTI_CONVERTER_DISPLAY_KEY_CHAR_HEIGHT + MULTI_CONVERTER_DISPLAY_KEY_FRAME_MARGIN),
|
||||||
|
MULTI_CONVERTER_DISPLAY_KEY_FRAME_MARGIN + g_w + MULTI_CONVERTER_DISPLAY_KEY_FRAME_MARGIN,
|
||||||
|
MULTI_CONVERTER_DISPLAY_KEY_CHAR_HEIGHT + MULTI_CONVERTER_DISPLAY_KEY_FRAME_MARGIN * 2
|
||||||
|
);
|
||||||
|
canvas_set_color(canvas, ColorWhite);
|
||||||
|
} else {
|
||||||
|
canvas_draw_frame(canvas,
|
||||||
|
_x - MULTI_CONVERTER_DISPLAY_KEY_FRAME_MARGIN,
|
||||||
|
_y - (MULTI_CONVERTER_DISPLAY_KEY_CHAR_HEIGHT + MULTI_CONVERTER_DISPLAY_KEY_FRAME_MARGIN),
|
||||||
|
MULTI_CONVERTER_DISPLAY_KEY_FRAME_MARGIN + g_w + MULTI_CONVERTER_DISPLAY_KEY_FRAME_MARGIN,
|
||||||
|
MULTI_CONVERTER_DISPLAY_KEY_CHAR_HEIGHT + MULTI_CONVERTER_DISPLAY_KEY_FRAME_MARGIN * 2
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// draw key
|
||||||
|
canvas_draw_glyph(canvas, _x, _y, g);
|
||||||
|
|
||||||
|
// certain keys have long_press features, draw whatever they're using there too
|
||||||
|
if (i == MULTI_CONVERTER_DISPLAY_KEY_NEGATIVE) {
|
||||||
|
canvas_draw_box(canvas,
|
||||||
|
_x + MULTI_CONVERTER_DISPLAY_KEY_FRAME_MARGIN + g_w - 4,
|
||||||
|
_y + MULTI_CONVERTER_DISPLAY_KEY_FRAME_MARGIN - 2,
|
||||||
|
4,
|
||||||
|
2
|
||||||
|
);
|
||||||
|
} else if (i == MULTI_CONVERTER_DISPLAY_KEY_COMMA) {
|
||||||
|
canvas_draw_box(canvas,
|
||||||
|
_x + MULTI_CONVERTER_DISPLAY_KEY_FRAME_MARGIN + g_w - 2,
|
||||||
|
_y + MULTI_CONVERTER_DISPLAY_KEY_FRAME_MARGIN - 2,
|
||||||
|
2,
|
||||||
|
2
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// back to black
|
||||||
|
canvas_set_color(canvas, ColorBlack);
|
||||||
|
|
||||||
|
if (i < 8) {
|
||||||
|
_x += g_w + MULTI_CONVERTER_DISPLAY_KEY_FRAME_MARGIN * 2 + 2;
|
||||||
|
} else if (i == 8) {
|
||||||
|
_y += (MULTI_CONVERTER_DISPLAY_KEY_CHAR_HEIGHT + MULTI_CONVERTER_DISPLAY_KEY_FRAME_MARGIN * 2) + 3;
|
||||||
|
_x = 8; // some padding at the beginning on second line
|
||||||
|
} else {
|
||||||
|
_x += g_w + MULTI_CONVERTER_DISPLAY_KEY_FRAME_MARGIN * 2 + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void multi_converter_mode_display_navigation(InputKey key, MultiConverterState* const multi_converter_state) {
|
||||||
|
|
||||||
|
// first move to keyboard position, then check if the ORIGIN allows that specific key, if not jump to the "closest one"
|
||||||
|
switch (key) {
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case InputKeyUp:
|
||||||
|
case InputKeyDown:
|
||||||
|
if ((multi_converter_state->display).key >= 9) (multi_converter_state->display).key -= 9;
|
||||||
|
else (multi_converter_state->display).key += 9;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case InputKeyLeft:
|
||||||
|
case InputKeyRight:
|
||||||
|
|
||||||
|
(multi_converter_state->display).key += (key == InputKeyLeft ? -1 : 1);
|
||||||
|
|
||||||
|
if ((multi_converter_state->display).key > MULTI_CONVERTER_DISPLAY_KEYS-1) (multi_converter_state->display).key = 0;
|
||||||
|
else if ((multi_converter_state->display).key < 0) (multi_converter_state->display).key = MULTI_CONVERTER_DISPLAY_KEYS-1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if destination key is disabled by max_number_keys, move to the closest one
|
||||||
|
// (this could be improved with more accurate keys movements, probably...)
|
||||||
|
if (multi_converter_get_unit(multi_converter_state->unit_type_orig).max_number_keys >= 16) return; // weird, since this means "do not show any number on the keyboard, but just in case..."
|
||||||
|
|
||||||
|
int8_t i = -1;
|
||||||
|
if (key == InputKeyRight || key == InputKeyDown) i = 1;
|
||||||
|
|
||||||
|
while ((multi_converter_state->display).key < 16 && (multi_converter_state->display).key > multi_converter_get_unit(multi_converter_state->unit_type_orig).max_number_keys-1) {
|
||||||
|
(multi_converter_state->display).key += i;
|
||||||
|
if ((multi_converter_state->display).key > MULTI_CONVERTER_DISPLAY_KEYS-1) (multi_converter_state->display).key = 0;
|
||||||
|
else if ((multi_converter_state->display).key < 0) (multi_converter_state->display).key = MULTI_CONVERTER_DISPLAY_KEYS-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void multi_converter_mode_display_reset(MultiConverterState* const multi_converter_state) {
|
||||||
|
|
||||||
|
// clean the buffers
|
||||||
|
for (int i = 0; i < MULTI_CONVERTER_NUMBER_DIGITS; i++) {
|
||||||
|
multi_converter_state->buffer_orig[i] = MULTI_CONVERTER_DISPLAY_CHAR_BLANK;
|
||||||
|
multi_converter_state->buffer_dest[i] = MULTI_CONVERTER_DISPLAY_CHAR_BLANK;
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset the display flags and index
|
||||||
|
multi_converter_state->display.cursor = 0;
|
||||||
|
multi_converter_state->display.key = 0;
|
||||||
|
multi_converter_state->display.comma = 0;
|
||||||
|
multi_converter_state->display.negative = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void multi_converter_mode_display_toggle_negative(MultiConverterState* const multi_converter_state) {
|
||||||
|
if (multi_converter_get_unit(multi_converter_state->unit_type_orig).allow_negative) {
|
||||||
|
|
||||||
|
if (!(multi_converter_state->display).negative) {
|
||||||
|
// shift origin buffer one to right + add the "-" sign (last digit will be lost)
|
||||||
|
for (int i = MULTI_CONVERTER_NUMBER_DIGITS-1; i > 0; i--) {
|
||||||
|
// we could avoid the blanks, but nevermind
|
||||||
|
multi_converter_state->buffer_orig[i] = multi_converter_state->buffer_orig[i-1];
|
||||||
|
}
|
||||||
|
multi_converter_state->buffer_orig[0] = MULTI_CONVERTER_DISPLAY_CHAR_NEGATIVE;
|
||||||
|
|
||||||
|
// only increment cursor if we're not out of bound
|
||||||
|
if ((multi_converter_state->display).cursor < MULTI_CONVERTER_NUMBER_DIGITS) (multi_converter_state->display).cursor++;
|
||||||
|
} else {
|
||||||
|
// shift origin buffer one to left, append ' ' on the end
|
||||||
|
for (int i = 0; i < MULTI_CONVERTER_NUMBER_DIGITS-1; i++) {
|
||||||
|
if (multi_converter_state->buffer_orig[i] == MULTI_CONVERTER_DISPLAY_CHAR_BLANK) break;
|
||||||
|
|
||||||
|
multi_converter_state->buffer_orig[i] = multi_converter_state->buffer_orig[i+1];
|
||||||
|
}
|
||||||
|
multi_converter_state->buffer_orig[MULTI_CONVERTER_NUMBER_DIGITS-1] = MULTI_CONVERTER_DISPLAY_CHAR_BLANK;
|
||||||
|
|
||||||
|
(multi_converter_state->display).cursor--;
|
||||||
|
}
|
||||||
|
|
||||||
|
// toggle flag
|
||||||
|
(multi_converter_state->display).negative ^= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void multi_converter_mode_display_add_comma(MultiConverterState* const multi_converter_state) {
|
||||||
|
if (
|
||||||
|
!multi_converter_get_unit(multi_converter_state->unit_type_orig).allow_comma ||
|
||||||
|
(multi_converter_state->display).comma ||
|
||||||
|
!(multi_converter_state->display).cursor ||
|
||||||
|
((multi_converter_state->display).cursor == (MULTI_CONVERTER_NUMBER_DIGITS - 1))
|
||||||
|
) return; // maybe not allowerd; or one comma already in place; also cannot add commas as first or last chars
|
||||||
|
|
||||||
|
// set flag to one
|
||||||
|
(multi_converter_state->display).comma = 1;
|
||||||
|
|
||||||
|
multi_converter_state->buffer_orig[(multi_converter_state->display).cursor] = MULTI_CONVERTER_DISPLAY_CHAR_COMMA;
|
||||||
|
(multi_converter_state->display).cursor++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void multi_converter_mode_display_add_number(MultiConverterState* const multi_converter_state) {
|
||||||
|
if ((multi_converter_state->display).key > multi_converter_get_unit(multi_converter_state->unit_type_orig).max_number_keys-1) return;
|
||||||
|
|
||||||
|
if ((multi_converter_state->display).key < 10) {
|
||||||
|
multi_converter_state->buffer_orig[(multi_converter_state->display).cursor] = (multi_converter_state->display).key + '0';
|
||||||
|
} else {
|
||||||
|
multi_converter_state->buffer_orig[(multi_converter_state->display).cursor] = ((multi_converter_state->display).key - 10) + 'A';
|
||||||
|
}
|
||||||
|
|
||||||
|
(multi_converter_state->display).cursor++;
|
||||||
|
}
|
||||||
|
|
||||||
|
MultiConverterModeTrigger multi_converter_mode_display_ok(uint8_t long_press, MultiConverterState* const multi_converter_state) {
|
||||||
|
|
||||||
|
if ((multi_converter_state->display).key < MULTI_CONVERTER_DISPLAY_KEY_DEL) {
|
||||||
|
if ((multi_converter_state->display).cursor >= MULTI_CONVERTER_NUMBER_DIGITS) return None; // limit reached, ignore
|
||||||
|
|
||||||
|
// long press on 0 toggle NEGATIVE if allowed, on 1 adds COMMA if allowed
|
||||||
|
if (long_press) {
|
||||||
|
|
||||||
|
if ((multi_converter_state->display).key == MULTI_CONVERTER_DISPLAY_KEY_NEGATIVE) {
|
||||||
|
// toggle negative
|
||||||
|
multi_converter_mode_display_toggle_negative(multi_converter_state);
|
||||||
|
} else if ((multi_converter_state->display).key == MULTI_CONVERTER_DISPLAY_KEY_COMMA) {
|
||||||
|
// add comma
|
||||||
|
multi_converter_mode_display_add_comma(multi_converter_state);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// regular keys
|
||||||
|
multi_converter_mode_display_add_number(multi_converter_state);
|
||||||
|
}
|
||||||
|
|
||||||
|
multi_converter_mode_display_convert(multi_converter_state);
|
||||||
|
|
||||||
|
} else if ((multi_converter_state->display).key == MULTI_CONVERTER_DISPLAY_KEY_DEL) {
|
||||||
|
if ((multi_converter_state->display).cursor > 0) (multi_converter_state->display).cursor--;
|
||||||
|
|
||||||
|
if (multi_converter_state->buffer_orig[(multi_converter_state->display).cursor] == MULTI_CONVERTER_DISPLAY_CHAR_COMMA) (multi_converter_state->display).comma = 0;
|
||||||
|
if (multi_converter_state->buffer_orig[(multi_converter_state->display).cursor] == MULTI_CONVERTER_DISPLAY_CHAR_NEGATIVE) (multi_converter_state->display).negative = 0;
|
||||||
|
|
||||||
|
multi_converter_state->buffer_orig[(multi_converter_state->display).cursor] = MULTI_CONVERTER_DISPLAY_CHAR_BLANK;
|
||||||
|
|
||||||
|
multi_converter_mode_display_convert(multi_converter_state);
|
||||||
|
|
||||||
|
} else { // MULTI_CONVERTER_DISPLAY_KEY_SELECT
|
||||||
|
return Reset;
|
||||||
|
}
|
||||||
|
|
||||||
|
return None;
|
||||||
|
|
||||||
|
}
|
||||||
55
applications/multi_converter/multi_converter_mode_display.h
Normal file
55
applications/multi_converter/multi_converter_mode_display.h
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <input/input.h>
|
||||||
|
#include <gui/gui.h>
|
||||||
|
|
||||||
|
#include "multi_converter_definitions.h"
|
||||||
|
#include "multi_converter_units.h"
|
||||||
|
|
||||||
|
//
|
||||||
|
// performs a unit conversion from origin to source buffers, if there's any error, overflow or
|
||||||
|
// non-compatible format (which shouldn't happen, but just in case) abort conversion and outputs
|
||||||
|
// some "?" strings on the buffer or something similar
|
||||||
|
//
|
||||||
|
void multi_converter_mode_display_convert(MultiConverterState* const multi_converter_state);
|
||||||
|
|
||||||
|
//
|
||||||
|
// draw the main DISPLAY view with the current multi_converter_state values
|
||||||
|
//
|
||||||
|
void multi_converter_mode_display_draw(Canvas* const canvas, const MultiConverterState* multi_converter_state);
|
||||||
|
|
||||||
|
//
|
||||||
|
// keyboard navigation on DISPLAY mode (NAVIGATION only, no BACK nor OK - InputKey guaranteed to be left/right/up/down)
|
||||||
|
//
|
||||||
|
void multi_converter_mode_display_navigation(InputKey key, MultiConverterState* const multi_converter_state);
|
||||||
|
|
||||||
|
//
|
||||||
|
// reset the DISPLAY mode with the current units, cleaning the buffers and different flags;
|
||||||
|
// call this when exiting the SELECT mode / changing the units
|
||||||
|
//
|
||||||
|
void multi_converter_mode_display_reset(MultiConverterState* const multi_converter_state);
|
||||||
|
|
||||||
|
//
|
||||||
|
// toggle the negative flag on current selected buffer ONLY if the unit allows negative numbers
|
||||||
|
// (adding negative number may crop the last char on the buffer; it cannot be recovered)
|
||||||
|
//
|
||||||
|
void multi_converter_mode_display_toggle_negative(MultiConverterState* const multi_converter_state);
|
||||||
|
|
||||||
|
//
|
||||||
|
// add a comma/dot/decimal separator/whatever on current selected buffer ONLY if the unit allows it
|
||||||
|
// (only ONE comma allowed, not in the beginning nor end)
|
||||||
|
//
|
||||||
|
void multi_converter_mode_display_add_comma(MultiConverterState* const multi_converter_state);
|
||||||
|
|
||||||
|
//
|
||||||
|
// add a regular number to the buffer if it's <= the max_number_keys from the unit (not necessary
|
||||||
|
// since the draw and navigation functions won't allow a trigger for an invalid number, but still
|
||||||
|
// to keep the "checks" policy on each "add key" function...)
|
||||||
|
//
|
||||||
|
void multi_converter_mode_display_add_number(MultiConverterState* const multi_converter_state);
|
||||||
|
|
||||||
|
//
|
||||||
|
// handle the OK action when selecting a specific key on the keyboard (add a number, a symbol, change mode...)
|
||||||
|
// returns a ModeTrigger enum value: may or may not let to a mode change on the main loop (WON'T change the mode here)
|
||||||
|
//
|
||||||
|
MultiConverterModeTrigger multi_converter_mode_display_ok(uint8_t long_press, MultiConverterState* const multi_converter_state);
|
||||||
160
applications/multi_converter/multi_converter_mode_select.c
Normal file
160
applications/multi_converter/multi_converter_mode_select.c
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
#include "multi_converter_mode_select.h"
|
||||||
|
|
||||||
|
#define MULTI_CONVERTER_LIST_ENTRIES_COUNT 3
|
||||||
|
|
||||||
|
#define MULTI_CONVERTER_INFO_STRING_FROM "FROM:"
|
||||||
|
#define MULTI_CONVERTER_INFO_STRING_TO "TO:"
|
||||||
|
#define MULTI_CONVERTER_INFO_STRING_OK "OK: Change"
|
||||||
|
#define MULTI_CONVERTER_INFO_STRING_BACK "BACK: Cancel"
|
||||||
|
|
||||||
|
void multi_converter_mode_select_draw_destination_offset(uint8_t x, uint8_t y, int8_t d, Canvas* const canvas, const MultiConverterState* multi_converter_state) {
|
||||||
|
int i = 1;
|
||||||
|
while (i < MULTI_CONVERTER_AVAILABLE_UNITS) { // in case there's no match, to avoid an endless loop (in theory shouldn't happen, but...)
|
||||||
|
int ut = multi_converter_get_unit_type_offset((multi_converter_state->select).selected_unit_type_dest, i * d);
|
||||||
|
if (
|
||||||
|
multi_converter_available_units[(multi_converter_state->select).selected_unit_type_orig].allowed_function(ut) &&
|
||||||
|
(multi_converter_state->select).selected_unit_type_orig != ut
|
||||||
|
) {
|
||||||
|
canvas_draw_str(canvas, x, y, multi_converter_available_units[ut].name);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void multi_converter_mode_select_draw_selected_unit(uint8_t x, uint8_t y, MultiConverterUnitType unit_type, Canvas* const canvas) {
|
||||||
|
canvas_draw_box(canvas, x - 2 , y - 10, canvas_string_width(canvas, multi_converter_available_units[unit_type].name) + 4, 13);
|
||||||
|
canvas_set_color(canvas, ColorWhite);
|
||||||
|
canvas_draw_str(canvas, x, y, multi_converter_available_units[unit_type].name);
|
||||||
|
canvas_set_color(canvas, ColorBlack);
|
||||||
|
}
|
||||||
|
|
||||||
|
void multi_converter_mode_select_draw(Canvas* const canvas, const MultiConverterState* multi_converter_state) {
|
||||||
|
|
||||||
|
int y = 10;
|
||||||
|
int x = 10;
|
||||||
|
|
||||||
|
canvas_set_color(canvas, ColorBlack);
|
||||||
|
|
||||||
|
// FROM
|
||||||
|
canvas_set_font(canvas, FontPrimary);
|
||||||
|
canvas_draw_str(canvas, x, y, MULTI_CONVERTER_INFO_STRING_FROM);
|
||||||
|
|
||||||
|
canvas_set_font(canvas, FontSecondary);
|
||||||
|
|
||||||
|
// offset -1
|
||||||
|
y += 12;
|
||||||
|
|
||||||
|
canvas_draw_str(canvas, x, y, multi_converter_available_units[ multi_converter_get_unit_type_offset((multi_converter_state->select).selected_unit_type_orig, -1) ].name);
|
||||||
|
|
||||||
|
// current selected element
|
||||||
|
y += 12;
|
||||||
|
|
||||||
|
multi_converter_mode_select_draw_selected_unit(x, y, (multi_converter_state->select).selected_unit_type_orig, canvas);
|
||||||
|
|
||||||
|
if ((multi_converter_state->select).select_orig) canvas_draw_str(canvas, x - 6, y, ">");
|
||||||
|
|
||||||
|
// offset +1
|
||||||
|
y += 12;
|
||||||
|
|
||||||
|
canvas_draw_str(canvas, x, y, multi_converter_available_units[ multi_converter_get_unit_type_offset((multi_converter_state->select).selected_unit_type_orig, 1) ].name);
|
||||||
|
|
||||||
|
// TO
|
||||||
|
y = 10;
|
||||||
|
x = 70;
|
||||||
|
|
||||||
|
canvas_set_font(canvas, FontPrimary);
|
||||||
|
canvas_draw_str(canvas, x, y, MULTI_CONVERTER_INFO_STRING_TO);
|
||||||
|
|
||||||
|
canvas_set_font(canvas, FontSecondary);
|
||||||
|
|
||||||
|
// offset -1: go back from current selected destination and find the first one valid (even if it's itself)
|
||||||
|
y += 12;
|
||||||
|
|
||||||
|
multi_converter_mode_select_draw_destination_offset(x, y, -1, canvas, multi_converter_state);
|
||||||
|
|
||||||
|
// current selected element
|
||||||
|
y += 12;
|
||||||
|
|
||||||
|
multi_converter_mode_select_draw_selected_unit(x, y, (multi_converter_state->select).selected_unit_type_dest, canvas);
|
||||||
|
|
||||||
|
if (!(multi_converter_state->select).select_orig) canvas_draw_str(canvas, x - 6, y, ">");
|
||||||
|
|
||||||
|
// offset +1: same but on the opposite direction
|
||||||
|
y += 12;
|
||||||
|
|
||||||
|
multi_converter_mode_select_draw_destination_offset(x, y, 1, canvas, multi_converter_state);
|
||||||
|
|
||||||
|
// OK / CANCEL
|
||||||
|
|
||||||
|
canvas_set_color(canvas, ColorBlack);
|
||||||
|
canvas_draw_box(canvas, 0, 64 - 12, canvas_string_width(canvas, MULTI_CONVERTER_INFO_STRING_OK) + 4, 12);
|
||||||
|
canvas_draw_box(canvas, 128 - 4 - canvas_string_width(canvas, MULTI_CONVERTER_INFO_STRING_BACK), 64 - 12, canvas_string_width(canvas, "BACK: Cancel") + 4, 12);
|
||||||
|
|
||||||
|
canvas_set_color(canvas, ColorWhite);
|
||||||
|
canvas_draw_str(canvas, 2, 64 - 3, MULTI_CONVERTER_INFO_STRING_OK);
|
||||||
|
canvas_draw_str(canvas, 128 - 2 - canvas_string_width(canvas, MULTI_CONVERTER_INFO_STRING_BACK), 64 - 3, MULTI_CONVERTER_INFO_STRING_BACK);
|
||||||
|
}
|
||||||
|
|
||||||
|
void multi_converter_mode_select_reset(MultiConverterState* const multi_converter_state) {
|
||||||
|
|
||||||
|
// initial pre-selected values are equal to the current selected values
|
||||||
|
(multi_converter_state->select).selected_unit_type_orig = multi_converter_state->unit_type_orig;
|
||||||
|
(multi_converter_state->select).selected_unit_type_dest = multi_converter_state->unit_type_dest;
|
||||||
|
|
||||||
|
(multi_converter_state->select).select_orig = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
MultiConverterModeTrigger multi_converter_mode_select_exit(uint8_t save_changes, MultiConverterState* const multi_converter_state) {
|
||||||
|
if (save_changes) {
|
||||||
|
|
||||||
|
multi_converter_state->unit_type_dest = (multi_converter_state->select).selected_unit_type_dest;
|
||||||
|
|
||||||
|
if (multi_converter_state->unit_type_orig == (multi_converter_state->select).selected_unit_type_orig) {
|
||||||
|
// if the ORIGIN unit didn't changed, just trigger the convert
|
||||||
|
|
||||||
|
return Convert;
|
||||||
|
} else {
|
||||||
|
multi_converter_state->unit_type_orig = (multi_converter_state->select).selected_unit_type_orig;
|
||||||
|
multi_converter_state->unit_type_dest = (multi_converter_state->select).selected_unit_type_dest;
|
||||||
|
|
||||||
|
return Reset;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
void multi_converter_mode_select_switch(MultiConverterState* const multi_converter_state) {
|
||||||
|
(multi_converter_state->select).select_orig ^= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void multi_converter_mode_select_change_unit(int8_t direction, MultiConverterState* const multi_converter_state) {
|
||||||
|
|
||||||
|
MultiConverterUnitType d;
|
||||||
|
if ((multi_converter_state->select).select_orig) {
|
||||||
|
(multi_converter_state->select).selected_unit_type_orig = multi_converter_get_unit_type_offset((multi_converter_state->select).selected_unit_type_orig, direction);
|
||||||
|
d = (multi_converter_state->select).selected_unit_type_dest;
|
||||||
|
} else {
|
||||||
|
d = ((multi_converter_state->select).selected_unit_type_dest + direction) % MULTI_CONVERTER_AVAILABLE_UNITS;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check each unit with the ORIGIN allowed_function() to make sure we're selecting a valid DESTINATION
|
||||||
|
// (when changing the ORIGIN unit the DIRECTION in which we'll switch the DESTINATION will be the SAME);
|
||||||
|
// also notice that ORIGIN must be DIFFERENT than DESTINATION
|
||||||
|
int i = 0;
|
||||||
|
while (i < MULTI_CONVERTER_AVAILABLE_UNITS) {
|
||||||
|
if (
|
||||||
|
multi_converter_available_units[(multi_converter_state->select).selected_unit_type_orig].allowed_function(d) &&
|
||||||
|
(multi_converter_state->select).selected_unit_type_orig != d
|
||||||
|
) {
|
||||||
|
(multi_converter_state->select).selected_unit_type_dest = d;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
d = multi_converter_get_unit_type_offset(d, direction);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
58
applications/multi_converter/multi_converter_mode_select.h
Normal file
58
applications/multi_converter/multi_converter_mode_select.h
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <input/input.h>
|
||||||
|
#include <gui/gui.h>
|
||||||
|
|
||||||
|
#include "multi_converter_definitions.h"
|
||||||
|
#include "multi_converter_units.h"
|
||||||
|
|
||||||
|
//
|
||||||
|
// aux draw function for units offsets and draw stuff
|
||||||
|
//
|
||||||
|
void multi_converter_mode_select_draw_destination_offset(uint8_t x, uint8_t y, int8_t d, Canvas* const canvas, const MultiConverterState* multi_converter_state);
|
||||||
|
|
||||||
|
void multi_converter_mode_select_draw_selected_unit(uint8_t x, uint8_t y, MultiConverterUnitType unit_type, Canvas* const canvas);
|
||||||
|
|
||||||
|
//
|
||||||
|
// draw the main SELECT view with the current multi_converter_state values
|
||||||
|
//
|
||||||
|
void multi_converter_mode_select_draw(Canvas* const canvas, const MultiConverterState* multi_converter_state);
|
||||||
|
|
||||||
|
//
|
||||||
|
// reset the SELECT mode view, showing as "pre-selected" the current working units
|
||||||
|
//
|
||||||
|
void multi_converter_mode_select_reset(MultiConverterState* const multi_converter_state);
|
||||||
|
|
||||||
|
//
|
||||||
|
// exit from SELECT mode and go back to display view, if save_changes == 1 use the current SELECT view info
|
||||||
|
// to modify the current selected units and reset the views properly (usually if the ORIGIN unit has been
|
||||||
|
// changed, reset everything; otherwise just trigger the convert function with a new DESTINATION)
|
||||||
|
//
|
||||||
|
// currently this function DON'T CHECK invalid unit relations (the navigation and display functions will
|
||||||
|
// prevent weird behaviours, so for now we're trusting the selected_unit_orig/dest_type values)
|
||||||
|
//
|
||||||
|
// returns an enum code MultiConverterDisplayTrigger based on doing nothing (cancel), triggering the display
|
||||||
|
// convert method or reseting the whole display mode (when fully changing the units)
|
||||||
|
//
|
||||||
|
// notice the MODE CHANGE itself is not done here but in the main loop (outside the call) via the ModeTrigger enum element
|
||||||
|
//
|
||||||
|
MultiConverterModeTrigger multi_converter_mode_select_exit(uint8_t save_changes, MultiConverterState* const multi_converter_state);
|
||||||
|
|
||||||
|
//
|
||||||
|
// switch between selecting the ORIGIN or the DESTINATION unit on DISPLAY mode (since there're only
|
||||||
|
// two options, both left/right arrow keys acts as toggles, no "direction" required)
|
||||||
|
//
|
||||||
|
void multi_converter_mode_select_switch(MultiConverterState* const multi_converter_state);
|
||||||
|
|
||||||
|
//
|
||||||
|
// change the selected unit on SELECTED mode, using the select_orig flag to check if we're switching the
|
||||||
|
// ORIGIN or the DESTINATION unit; the DIRECTION (up or down to travel the array) is set as a param
|
||||||
|
//
|
||||||
|
// when switching the ORIGIN one, reset the DESTINATION to the first valid unit (if the current one is not
|
||||||
|
// valid anymore); when switching the DESTINATION one, an allowed_function() check is performed in order to
|
||||||
|
// properly set a valid destination unit.
|
||||||
|
//
|
||||||
|
// (notice the draw step also perform which units are valid to display, so no worries about that here)
|
||||||
|
//
|
||||||
|
void multi_converter_mode_select_change_unit(int8_t direction, MultiConverterState* const multi_converter_state);
|
||||||
230
applications/multi_converter/multi_converter_units.c
Normal file
230
applications/multi_converter/multi_converter_units.c
Normal file
@@ -0,0 +1,230 @@
|
|||||||
|
#include "multi_converter_units.h"
|
||||||
|
|
||||||
|
#define MULTI_CONVERTER_CHAR_OVERFLOW '#'
|
||||||
|
#define MULTI_CONVERTER_MAX_SUPORTED_INT 999999999
|
||||||
|
|
||||||
|
#define multi_converter_unit_set_overflow(b) for (int _i = 0; _i < MULTI_CONVERTER_NUMBER_DIGITS; _i++) b[_i] = MULTI_CONVERTER_CHAR_OVERFLOW;
|
||||||
|
|
||||||
|
//
|
||||||
|
// DEC / HEX / BIN conversion
|
||||||
|
//
|
||||||
|
void multi_converter_unit_dec_hex_bin_convert(MultiConverterState* const multi_converter_state) {
|
||||||
|
|
||||||
|
char dest[MULTI_CONVERTER_NUMBER_DIGITS];
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
uint8_t overflow = 0;
|
||||||
|
|
||||||
|
int a = 0;
|
||||||
|
int r = 0;
|
||||||
|
uint8_t f = 1;
|
||||||
|
|
||||||
|
switch(multi_converter_state->unit_type_orig) {
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
case UnitTypeDec: {
|
||||||
|
a = atoi(multi_converter_state->buffer_orig);
|
||||||
|
f = (multi_converter_state->unit_type_dest == UnitTypeHex ? 16 : 2);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case UnitTypeHex:
|
||||||
|
a = strtol(multi_converter_state->buffer_orig, NULL, 16);
|
||||||
|
f = (multi_converter_state->unit_type_dest == UnitTypeDec ? 10 : 2);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case UnitTypeBin:
|
||||||
|
a = strtol(multi_converter_state->buffer_orig, NULL, 2);
|
||||||
|
f = (multi_converter_state->unit_type_dest == UnitTypeDec ? 10 : 16);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (a > 0) {
|
||||||
|
r = a % f;
|
||||||
|
dest[i] = r + (r < 10 ? '0' : ('A' - 10) );
|
||||||
|
a /= f;
|
||||||
|
if (i++ >= MULTI_CONVERTER_NUMBER_DIGITS) {
|
||||||
|
overflow = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (overflow) {
|
||||||
|
multi_converter_unit_set_overflow(multi_converter_state->buffer_dest);
|
||||||
|
} else {
|
||||||
|
// copy DEST (reversed) to destination and append empty chars at the end
|
||||||
|
for (int j = 0; j < MULTI_CONVERTER_NUMBER_DIGITS; j++) {
|
||||||
|
if (i >= 1) multi_converter_state->buffer_dest[j] = dest[--i];
|
||||||
|
else multi_converter_state->buffer_dest[j] = ' ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t multi_converter_unit_dec_hex_bin_allowed(MultiConverterUnitType unit_type) {
|
||||||
|
return (unit_type == UnitTypeDec || unit_type == UnitTypeHex || unit_type == UnitTypeBin);
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// CEL / FAR / KEL
|
||||||
|
//
|
||||||
|
void multi_converter_unit_temperature_convert(MultiConverterState* const multi_converter_state) {
|
||||||
|
|
||||||
|
double a = strtof(multi_converter_state->buffer_orig, NULL);
|
||||||
|
uint8_t overflow = 0;
|
||||||
|
|
||||||
|
switch(multi_converter_state->unit_type_orig) {
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
case UnitTypeCelsius:
|
||||||
|
if (multi_converter_state->unit_type_dest == UnitTypeFahernheit) {
|
||||||
|
// celsius to fahrenheit
|
||||||
|
a = (a * ((double) 1.8)) + 32;
|
||||||
|
} else { // UnitTypeKelvin
|
||||||
|
a += ((double) 273.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case UnitTypeFahernheit:
|
||||||
|
// fahrenheit to celsius, always
|
||||||
|
a = (a - 32) / ((double) 1.8);
|
||||||
|
if (multi_converter_state->unit_type_dest == UnitTypeKelvin) {
|
||||||
|
// if kelvin, add
|
||||||
|
a += ((double) 273.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case UnitTypeKelvin:
|
||||||
|
// kelvin to celsius, always
|
||||||
|
a -= ((double) 273.15);
|
||||||
|
if (multi_converter_state->unit_type_dest == UnitTypeFahernheit) {
|
||||||
|
// if fahernheit, convert
|
||||||
|
a = (a * ((double) 1.8)) + 32;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (overflow) {
|
||||||
|
multi_converter_unit_set_overflow(multi_converter_state->buffer_dest);
|
||||||
|
} else {
|
||||||
|
|
||||||
|
int ret = snprintf(multi_converter_state->buffer_dest, MULTI_CONVERTER_NUMBER_DIGITS + 1, "%.3lf", a);
|
||||||
|
|
||||||
|
if (ret < 0) multi_converter_unit_set_overflow(multi_converter_state->buffer_dest);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t multi_converter_unit_temperature_allowed(MultiConverterUnitType unit_type) {
|
||||||
|
return (unit_type == UnitTypeCelsius || unit_type == UnitTypeFahernheit || unit_type == UnitTypeKelvin);
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// KM / M / CM / MILES / FEET / INCHES
|
||||||
|
//
|
||||||
|
|
||||||
|
void multi_converter_unit_distance_convert(MultiConverterState* const multi_converter_state) {
|
||||||
|
double a = strtof(multi_converter_state->buffer_orig, NULL);
|
||||||
|
uint8_t overflow = 0;
|
||||||
|
|
||||||
|
switch(multi_converter_state->unit_type_orig) {
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
case UnitTypeKilometers:
|
||||||
|
if (multi_converter_state->unit_type_dest == UnitTypeMeters) a *= ((double) 1000);
|
||||||
|
else if (multi_converter_state->unit_type_dest == UnitTypeCentimeters) a *= ((double) 100000);
|
||||||
|
else if (multi_converter_state->unit_type_dest == UnitTypeMiles) a *= ((double) 0.6213711);
|
||||||
|
else if (multi_converter_state->unit_type_dest == UnitTypeFeet) a *= ((double) 3280.839895013);
|
||||||
|
else if (multi_converter_state->unit_type_dest == UnitTypeInches) a *= ((double) 39370.078740157);
|
||||||
|
break;
|
||||||
|
case UnitTypeMeters:
|
||||||
|
if (multi_converter_state->unit_type_dest == UnitTypeKilometers) a /= ((double) 1000);
|
||||||
|
else if (multi_converter_state->unit_type_dest == UnitTypeCentimeters) a *= ((double) 100);
|
||||||
|
else if (multi_converter_state->unit_type_dest == UnitTypeMiles) a *= ((double) 0.0006213711);
|
||||||
|
else if (multi_converter_state->unit_type_dest == UnitTypeFeet) a *= ((double) 3.280839895013);
|
||||||
|
else if (multi_converter_state->unit_type_dest == UnitTypeInches) a *= ((double) 39.370078740157);
|
||||||
|
break;
|
||||||
|
case UnitTypeCentimeters:
|
||||||
|
if (multi_converter_state->unit_type_dest == UnitTypeKilometers) a /= ((double) 100000);
|
||||||
|
else if (multi_converter_state->unit_type_dest == UnitTypeMeters) a /= ((double) 100);
|
||||||
|
else if (multi_converter_state->unit_type_dest == UnitTypeMiles) a *= ((double) 0.000006213711);
|
||||||
|
else if (multi_converter_state->unit_type_dest == UnitTypeFeet) a *= ((double) 0.03280839895013);
|
||||||
|
else if (multi_converter_state->unit_type_dest == UnitTypeInches) a *= ((double) 0.39370078740157);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case UnitTypeMiles:
|
||||||
|
if (multi_converter_state->unit_type_dest == UnitTypeKilometers) a *= ((double) 1.609344);
|
||||||
|
else if (multi_converter_state->unit_type_dest == UnitTypeMeters) a *= ((double) 1609.344);
|
||||||
|
else if (multi_converter_state->unit_type_dest == UnitTypeCentimeters) a *= ((double) 160934.4);
|
||||||
|
else if (multi_converter_state->unit_type_dest == UnitTypeFeet) a *= ((double) 5280);
|
||||||
|
else if (multi_converter_state->unit_type_dest == UnitTypeInches) a *= ((double) 63360);
|
||||||
|
break;
|
||||||
|
case UnitTypeFeet:
|
||||||
|
if (multi_converter_state->unit_type_dest == UnitTypeKilometers) a *= ((double) 0.0003048);
|
||||||
|
else if (multi_converter_state->unit_type_dest == UnitTypeMeters) a *= ((double) 0.3048);
|
||||||
|
else if (multi_converter_state->unit_type_dest == UnitTypeCentimeters) a *= ((double) 30.48);
|
||||||
|
else if (multi_converter_state->unit_type_dest == UnitTypeMiles) a *= ((double) 0.000189393939394);
|
||||||
|
else if (multi_converter_state->unit_type_dest == UnitTypeInches) a *= ((double) 12);
|
||||||
|
break;
|
||||||
|
case UnitTypeInches:
|
||||||
|
if (multi_converter_state->unit_type_dest == UnitTypeKilometers) a *= ((double) 0.0000254);
|
||||||
|
else if (multi_converter_state->unit_type_dest == UnitTypeMeters) a *= ((double) 0.0254);
|
||||||
|
else if (multi_converter_state->unit_type_dest == UnitTypeCentimeters) a *= ((double) 2.54);
|
||||||
|
else if (multi_converter_state->unit_type_dest == UnitTypeMiles) a *= ((double) 0.0000157828282828);
|
||||||
|
else if (multi_converter_state->unit_type_dest == UnitTypeFeet) a *= ((double) 0.0833333333333);
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (overflow) {
|
||||||
|
multi_converter_unit_set_overflow(multi_converter_state->buffer_dest);
|
||||||
|
} else {
|
||||||
|
|
||||||
|
int ret = snprintf(multi_converter_state->buffer_dest, MULTI_CONVERTER_NUMBER_DIGITS + 1, "%lf", a);
|
||||||
|
|
||||||
|
if (ret < 0) multi_converter_unit_set_overflow(multi_converter_state->buffer_dest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t multi_converter_unit_distance_allowed(MultiConverterUnitType unit_type) {
|
||||||
|
return (
|
||||||
|
unit_type == UnitTypeKilometers || unit_type == UnitTypeMeters || unit_type == UnitTypeCentimeters ||
|
||||||
|
unit_type == UnitTypeMiles || unit_type == UnitTypeFeet || unit_type == UnitTypeInches
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// DEG / RAD
|
||||||
|
//
|
||||||
|
|
||||||
|
void multi_converter_unit_angle_convert(MultiConverterState* const multi_converter_state) {
|
||||||
|
double a = strtof(multi_converter_state->buffer_orig, NULL);
|
||||||
|
uint8_t overflow = 0;
|
||||||
|
|
||||||
|
switch(multi_converter_state->unit_type_orig) {
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
case UnitTypeDegree:
|
||||||
|
if (multi_converter_state->unit_type_dest == UnitTypeRadian) a *= ((double) 0.0174532925199);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case UnitTypeRadian:
|
||||||
|
if (multi_converter_state->unit_type_dest == UnitTypeDegree) a *= ((double) 57.2957795131);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (overflow) {
|
||||||
|
multi_converter_unit_set_overflow(multi_converter_state->buffer_dest);
|
||||||
|
} else {
|
||||||
|
|
||||||
|
int ret = snprintf(multi_converter_state->buffer_dest, MULTI_CONVERTER_NUMBER_DIGITS + 1, "%lf", a);
|
||||||
|
|
||||||
|
if (ret < 0) multi_converter_unit_set_overflow(multi_converter_state->buffer_dest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t multi_converter_unit_angle_allowed(MultiConverterUnitType unit_type) {
|
||||||
|
return (unit_type == UnitTypeDegree || unit_type == UnitTypeRadian);
|
||||||
|
}
|
||||||
71
applications/multi_converter/multi_converter_units.h
Normal file
71
applications/multi_converter/multi_converter_units.h
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <input/input.h>
|
||||||
|
#include <gui/gui.h>
|
||||||
|
|
||||||
|
#include "multi_converter_definitions.h"
|
||||||
|
|
||||||
|
#define MULTI_CONVERTER_AVAILABLE_UNITS 14
|
||||||
|
|
||||||
|
#define multi_converter_get_unit(unit_type) multi_converter_available_units[unit_type]
|
||||||
|
#define multi_converter_get_unit_type_offset(unit_type, offset) (((unit_type + offset) % MULTI_CONVERTER_AVAILABLE_UNITS + MULTI_CONVERTER_AVAILABLE_UNITS) % MULTI_CONVERTER_AVAILABLE_UNITS)
|
||||||
|
// the modulo operation will fail with extremely large values on the units array
|
||||||
|
|
||||||
|
// DEC / HEX / BIN
|
||||||
|
void multi_converter_unit_dec_hex_bin_convert(MultiConverterState* const multi_converter_state);
|
||||||
|
uint8_t multi_converter_unit_dec_hex_bin_allowed(MultiConverterUnitType);
|
||||||
|
|
||||||
|
// CEL / FAR / KEL
|
||||||
|
void multi_converter_unit_temperature_convert(MultiConverterState* const multi_converter_state);
|
||||||
|
uint8_t multi_converter_unit_temperature_allowed(MultiConverterUnitType);
|
||||||
|
|
||||||
|
// KM / M / CM / MILES / FEET / INCHES
|
||||||
|
void multi_converter_unit_distance_convert(MultiConverterState* const multi_converter_state);
|
||||||
|
uint8_t multi_converter_unit_distance_allowed(MultiConverterUnitType);
|
||||||
|
|
||||||
|
// DEG / RAD
|
||||||
|
void multi_converter_unit_angle_convert(MultiConverterState* const multi_converter_state);
|
||||||
|
uint8_t multi_converter_unit_angle_allowed(MultiConverterUnitType unit_type);
|
||||||
|
|
||||||
|
//
|
||||||
|
// each unit is made of comma? + negative? + keyboard_length + mini_name + name + convert function + allowed function
|
||||||
|
// (setting functions as NULL will cause convert / select options to be ignored)
|
||||||
|
//
|
||||||
|
static const MultiConverterUnit multi_converter_unit_dec = { 0, 0, 10, "DEC\0", "Decimal\0", multi_converter_unit_dec_hex_bin_convert, multi_converter_unit_dec_hex_bin_allowed };
|
||||||
|
static const MultiConverterUnit multi_converter_unit_hex = { 0, 0, 16, "HEX\0", "Hexadecimal\0", multi_converter_unit_dec_hex_bin_convert, multi_converter_unit_dec_hex_bin_allowed };
|
||||||
|
static const MultiConverterUnit multi_converter_unit_bin = { 0, 0, 2, "BIN\0", "Binary\0", multi_converter_unit_dec_hex_bin_convert, multi_converter_unit_dec_hex_bin_allowed };
|
||||||
|
|
||||||
|
static const MultiConverterUnit multi_converter_unit_cel = { 1, 1, 10, "CEL\0", "Celsius\0", multi_converter_unit_temperature_convert, multi_converter_unit_temperature_allowed };
|
||||||
|
static const MultiConverterUnit multi_converter_unit_far = { 1, 1, 10, "FAR\0", "Fahernheit\0", multi_converter_unit_temperature_convert, multi_converter_unit_temperature_allowed };
|
||||||
|
static const MultiConverterUnit multi_converter_unit_kel = { 1, 1, 10, "KEL\0", "Kelvin\0", multi_converter_unit_temperature_convert, multi_converter_unit_temperature_allowed };
|
||||||
|
|
||||||
|
static const MultiConverterUnit multi_converter_unit_km = { 1, 0, 10, "KM\0", "Kilometers\0", multi_converter_unit_distance_convert, multi_converter_unit_distance_allowed };
|
||||||
|
static const MultiConverterUnit multi_converter_unit_m = { 1, 0, 10, "M\0", "Meters\0", multi_converter_unit_distance_convert, multi_converter_unit_distance_allowed };
|
||||||
|
static const MultiConverterUnit multi_converter_unit_cm = { 1, 0, 10, "CM\0", "Centimeters\0", multi_converter_unit_distance_convert, multi_converter_unit_distance_allowed };
|
||||||
|
static const MultiConverterUnit multi_converter_unit_mi = { 1, 0, 10, "MI\0", "Miles\0", multi_converter_unit_distance_convert, multi_converter_unit_distance_allowed };
|
||||||
|
static const MultiConverterUnit multi_converter_unit_ft = { 1, 0, 10, "FT\0", "Feet\0", multi_converter_unit_distance_convert, multi_converter_unit_distance_allowed };
|
||||||
|
static const MultiConverterUnit multi_converter_unit_in = { 1, 0, 10, " \"\0", "Inches\0", multi_converter_unit_distance_convert, multi_converter_unit_distance_allowed };
|
||||||
|
|
||||||
|
static const MultiConverterUnit multi_converter_unit_deg = { 1, 0, 10, "DEG\0", "Degree\0", multi_converter_unit_angle_convert, multi_converter_unit_angle_allowed };
|
||||||
|
static const MultiConverterUnit multi_converter_unit_rad = { 1, 0, 10, "RAD\0", "Radian\0", multi_converter_unit_angle_convert, multi_converter_unit_angle_allowed };
|
||||||
|
|
||||||
|
// index order set by the MultiConverterUnitType enum element (multi_converter_definitions.h)
|
||||||
|
static const MultiConverterUnit multi_converter_available_units[MULTI_CONVERTER_AVAILABLE_UNITS] = {
|
||||||
|
[UnitTypeDec] = multi_converter_unit_dec,
|
||||||
|
[UnitTypeHex] = multi_converter_unit_hex,
|
||||||
|
[UnitTypeBin] = multi_converter_unit_bin,
|
||||||
|
|
||||||
|
[UnitTypeCelsius] = multi_converter_unit_cel,
|
||||||
|
[UnitTypeFahernheit] = multi_converter_unit_far,
|
||||||
|
[UnitTypeKelvin] = multi_converter_unit_kel,
|
||||||
|
|
||||||
|
[UnitTypeKilometers] = multi_converter_unit_km,
|
||||||
|
[UnitTypeMeters] = multi_converter_unit_m,
|
||||||
|
[UnitTypeCentimeters] = multi_converter_unit_cm,
|
||||||
|
[UnitTypeMiles] = multi_converter_unit_mi,
|
||||||
|
[UnitTypeFeet] = multi_converter_unit_ft,
|
||||||
|
[UnitTypeInches] = multi_converter_unit_in,
|
||||||
|
|
||||||
|
[UnitTypeDegree] = multi_converter_unit_deg,
|
||||||
|
[UnitTypeRadian] = multi_converter_unit_rad,
|
||||||
|
};
|
||||||
61
documentation/MultiConverter.md
Normal file
61
documentation/MultiConverter.md
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
# MultiConverter
|
||||||
|
|
||||||
|
## Author: [theisolinearchip](https://github.com/theisolinearchip/flipperzero_stuff/tree/main/applications/multi_converter)
|
||||||
|
|
||||||
|
An expanded version of my previous __Dec/Hex Converter__, this time allowing more units and a _(probably poorly made from a design-point-of-view)_ selector mode
|
||||||
|
to swap between different unit groups.
|
||||||
|
|
||||||
|
I wrote it with the idea of _expanding the unit list_ on mind, so adding new ones it's a matter of increasing an array of constants + defining the proper conversion functions.
|
||||||
|
|
||||||
|
(Actually the whole project is more about "making the framework" rather than providing _ALL_ of the possible units : D)
|
||||||
|
|
||||||
|
 
|
||||||
|
|
||||||
|
## Current conversions
|
||||||
|
|
||||||
|
- `Decimal / Hexadecimal / Binary`
|
||||||
|
- `Celsius / Fahernheit / Kelvin`
|
||||||
|
- `Kilometers / Meters / Centimeters / Miles / Feet / Inches`
|
||||||
|
- `Degree / Radian`
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Base keyboard allows numbers from `0` to `F`, being disabled (or not) according to the current selected unit.
|
||||||
|
|
||||||
|
Long press on `0` toggles a __negative__ value; long press on `1` sets a __decimal point__ (only if allowed by the current selected unit).
|
||||||
|
|
||||||
|
`<` removes the last character; `#` changes to __Unit Select Mode__.
|
||||||
|
|
||||||
|
### Unit Select Mode
|
||||||
|
|
||||||
|
`Left` and `Right` to swap between __origin unit__ and __destination unit__ (notice the _destination_ will change according to the current selected _origin_).
|
||||||
|
|
||||||
|
`Ok` to save the changes and go back to the __Display Mode__; `Back` to go back without changing any unit.
|
||||||
|
|
||||||
|
## Adding new units
|
||||||
|
|
||||||
|
1. Add the new units in the `MultiConverterUnitType` enum on `multi_converter_definitions.h` (basic definitions header). Notice each enum element will be used as an array index later.
|
||||||
|
|
||||||
|
2. Increase the `MULTI_CONVERTER_AVAILABLE_UNITS` constant on `multi_converter_units.h` (units main header file).
|
||||||
|
|
||||||
|
3. Set a pair of functions for __converting__ units and to __check__ if a target unit is allowed to work with the destination unit (both on `multi_converter_units.h`
|
||||||
|
and `multi_converter_units.c`; follow the already built-in units for more info).
|
||||||
|
|
||||||
|
4. Add the proper `MultiConverterUnit` structs for each new unit.
|
||||||
|
|
||||||
|
5. Add each new struct to the main `multi_converter_available_units` array.
|
||||||
|
|
||||||
|
And that's it! The system will fetch the new units and display it!
|
||||||
|
|
||||||
|
## Known issues, TODO-list, etc.
|
||||||
|
|
||||||
|
This is an initial release, so expect some bugs and issues (also I don't work with C that much, so there're probably lots of things that can be improved and/or changed!).
|
||||||
|
|
||||||
|
- I've noticed some small decimal variations when "going deep" with some units (like converting __miles__ to __centimeters__ and things like that); probably due to the precision-level required. Need to check that.
|
||||||
|
- Pending: improve overflow checks.
|
||||||
|
- The way some long numbers are shown could probably be improved to look fancier.
|
||||||
|
- Both _origin_ and _destination buffers_ are the same. The destination one could probably be longer in order to avoid certain _overflow scenarios_.
|
||||||
|
- The GUI needs improvement too: there's a whole __widget/views system__ built in the Flipper that allows things like setting up keys, showing "Save/Back/Cancel" messages with
|
||||||
|
callbacks and stuff like that. Didn't know anything about them, so I moved on with something more basic (which is probably fince since it's not a "very big project"); but
|
||||||
|
a more "standard" way with the regular GUI stuff provided by the firmware will be interesting...
|
||||||
|
- More GUI stuff: the _long click buttons_ for adding a decimal point / negative number aren't very clear on the view itself (I tried to add a small dot / dash symbol, but I think those are small enough to be a little bit confusing)
|
||||||
Reference in New Issue
Block a user