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:
MX
2022-08-23 19:14:30 +03:00
parent 56907f0c9f
commit baffcc5cf4
42 changed files with 1175 additions and 1566 deletions

View File

@@ -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**

View File

@@ -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;
}

View File

@@ -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,
)

View File

@@ -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;
}

View File

@@ -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;
};

View File

@@ -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);
}

View File

@@ -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;
};

View File

@@ -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();
}

View File

@@ -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;
};

View File

@@ -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);

View File

@@ -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;
};

View File

@@ -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(){};
};

View File

@@ -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;
}

View File

@@ -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);
};

View File

@@ -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];
};

View File

@@ -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];
};

View File

@@ -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);
};

View File

@@ -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();
};

View File

@@ -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();
};

View File

@@ -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);
}

View File

@@ -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;
};

View File

@@ -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;
}

View File

@@ -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();
}

View File

@@ -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;
};

View File

@@ -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);
}

View File

@@ -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];
};

View File

@@ -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);
};

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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",
], ],
) )

View File

@@ -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,

View 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;
}

View 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!)
};

View 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;
}

View 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);

View 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++;
}
}

View 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);

View 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);
}

View 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,
};

View 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)
![Img 1](http://albertgonzalez.coffee/projects/flipperzero/multi_converter/img/1_small.png) ![Img 2](http://albertgonzalez.coffee/projects/flipperzero/multi_converter/img/2_small.png)
## 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)