All apps migrated!!!
@@ -1,7 +0,0 @@
|
||||
#define WIFI_MODULE_INIT_VERSION "WFDM_0.1"
|
||||
|
||||
#define MODULE_CONTEXT_INITIALIZATION WIFI_MODULE_INIT_VERSION
|
||||
|
||||
#define FLIPPERZERO_SERIAL_BAUD 230400
|
||||
|
||||
#define NA 0
|
||||
@@ -1,13 +0,0 @@
|
||||
App(
|
||||
appid="esp8266_deauther",
|
||||
name="[ESP8266] Deauther",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="esp8266_deauth_app",
|
||||
requires=["gui"],
|
||||
stack_size=2 * 1024,
|
||||
fap_icon="wifi_10px.png",
|
||||
fap_category="WiFi",
|
||||
fap_author="@SequoiaSan & @xMasterX",
|
||||
fap_version="1.0",
|
||||
fap_description="DSTIKE Deauther module interface, based on ESP8266",
|
||||
)
|
||||
@@ -1,560 +0,0 @@
|
||||
#include <furi.h>
|
||||
#include <furi_hal_console.h>
|
||||
#include <furi_hal_gpio.h>
|
||||
#include <furi_hal_power.h>
|
||||
#include <furi_hal_uart.h>
|
||||
#include <gui/canvas_i.h>
|
||||
#include <gui/gui.h>
|
||||
#include <input/input.h>
|
||||
//#include <math.h>
|
||||
//#include <notification/notification.h>
|
||||
//#include <notification/notification_messages.h>
|
||||
//#include <stdlib.h>
|
||||
#include <xtreme.h>
|
||||
|
||||
#include "FlipperZeroWiFiDeauthModuleDefines.h"
|
||||
|
||||
#define UART_CH \
|
||||
(xtreme_settings.uart_esp_channel == UARTDefault ? FuriHalUartIdUSART1 : FuriHalUartIdLPUART1)
|
||||
|
||||
#define DEAUTH_APP_DEBUG 0
|
||||
|
||||
#if DEAUTH_APP_DEBUG
|
||||
#define APP_NAME_TAG "WiFi_Deauther"
|
||||
#define DEAUTH_APP_LOG_I(format, ...) FURI_LOG_I(APP_NAME_TAG, format, ##__VA_ARGS__)
|
||||
#define DEAUTH_APP_LOG_D(format, ...) FURI_LOG_D(APP_NAME_TAG, format, ##__VA_ARGS__)
|
||||
#define DEAUTH_APP_LOG_E(format, ...) FURI_LOG_E(APP_NAME_TAG, format, ##__VA_ARGS__)
|
||||
#else
|
||||
#define DEAUTH_APP_LOG_I(format, ...)
|
||||
#define DEAUTH_APP_LOG_D(format, ...)
|
||||
#define DEAUTH_APP_LOG_E(format, ...)
|
||||
#endif // WIFI_APP_DEBUG
|
||||
|
||||
#define DISABLE_CONSOLE !DEAUTH_APP_DEBUG
|
||||
#define ENABLE_MODULE_POWER 1
|
||||
#define ENABLE_MODULE_DETECTION 1
|
||||
|
||||
typedef enum EEventType // app internally defined event types
|
||||
{ EventTypeKey // flipper input.h type
|
||||
} EEventType;
|
||||
|
||||
typedef struct SPluginEvent {
|
||||
EEventType m_type;
|
||||
InputEvent m_input;
|
||||
} SPluginEvent;
|
||||
|
||||
typedef enum EAppContext {
|
||||
Undefined,
|
||||
WaitingForModule,
|
||||
Initializing,
|
||||
ModuleActive,
|
||||
} EAppContext;
|
||||
|
||||
typedef enum EWorkerEventFlags {
|
||||
WorkerEventReserved = (1 << 0), // Reserved for StreamBuffer internal event
|
||||
WorkerEventStop = (1 << 1),
|
||||
WorkerEventRx = (1 << 2),
|
||||
} EWorkerEventFlags;
|
||||
|
||||
typedef struct SGpioButtons {
|
||||
GpioPin const* pinButtonUp;
|
||||
GpioPin const* pinButtonDown;
|
||||
GpioPin const* pinButtonOK;
|
||||
GpioPin const* pinButtonBack;
|
||||
} SGpioButtons;
|
||||
|
||||
typedef struct SWiFiDeauthApp {
|
||||
FuriMutex* mutex;
|
||||
Gui* m_gui;
|
||||
FuriThread* m_worker_thread;
|
||||
//NotificationApp* m_notification;
|
||||
FuriStreamBuffer* m_rx_stream;
|
||||
SGpioButtons m_GpioButtons;
|
||||
|
||||
bool m_wifiDeauthModuleInitialized;
|
||||
bool m_wifiDeauthModuleAttached;
|
||||
|
||||
EAppContext m_context;
|
||||
|
||||
uint8_t m_backBuffer[128 * 8 * 8];
|
||||
//uint8_t m_renderBuffer[128 * 8 * 8];
|
||||
|
||||
uint8_t* m_backBufferPtr;
|
||||
//uint8_t* m_m_renderBufferPtr;
|
||||
|
||||
//uint8_t* m_originalBuffer;
|
||||
//uint8_t** m_originalBufferLocation;
|
||||
size_t m_canvasSize;
|
||||
|
||||
bool m_needUpdateGUI;
|
||||
} SWiFiDeauthApp;
|
||||
|
||||
/////// INIT STATE ///////
|
||||
static void esp8266_deauth_app_init(SWiFiDeauthApp* const app) {
|
||||
app->m_context = Undefined;
|
||||
|
||||
app->m_canvasSize = 128 * 8 * 8;
|
||||
memset(app->m_backBuffer, DEAUTH_APP_DEBUG ? 0xFF : 0x00, app->m_canvasSize);
|
||||
//memset(app->m_renderBuffer, DEAUTH_APP_DEBUG ? 0xFF : 0x00, app->m_canvasSize);
|
||||
|
||||
//app->m_originalBuffer = NULL;
|
||||
//app->m_originalBufferLocation = NULL;
|
||||
|
||||
//app->m_m_renderBufferPtr = app->m_renderBuffer;
|
||||
app->m_backBufferPtr = app->m_backBuffer;
|
||||
|
||||
app->m_GpioButtons.pinButtonUp = &gpio_ext_pc3;
|
||||
app->m_GpioButtons.pinButtonDown = &gpio_ext_pb2;
|
||||
app->m_GpioButtons.pinButtonOK = &gpio_ext_pb3;
|
||||
app->m_GpioButtons.pinButtonBack = &gpio_ext_pa4;
|
||||
|
||||
app->m_needUpdateGUI = false;
|
||||
|
||||
#if ENABLE_MODULE_POWER
|
||||
app->m_wifiDeauthModuleInitialized = false;
|
||||
#else
|
||||
app->m_wifiDeauthModuleInitialized = true;
|
||||
#endif // ENABLE_MODULE_POWER
|
||||
|
||||
#if ENABLE_MODULE_DETECTION
|
||||
app->m_wifiDeauthModuleAttached = false;
|
||||
#else
|
||||
app->m_wifiDeauthModuleAttached = true;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void esp8266_deauth_module_render_callback(Canvas* const canvas, void* ctx) {
|
||||
furi_assert(ctx);
|
||||
SWiFiDeauthApp* app = ctx;
|
||||
furi_mutex_acquire(app->mutex, FuriWaitForever);
|
||||
|
||||
//if(app->m_needUpdateGUI)
|
||||
//{
|
||||
// app->m_needUpdateGUI = false;
|
||||
|
||||
// //app->m_canvasSize = canvas_get_buffer_size(canvas);
|
||||
// //app->m_originalBuffer = canvas_get_buffer(canvas);
|
||||
// //app->m_originalBufferLocation = &u8g2_GetBufferPtr(&canvas->fb);
|
||||
// //u8g2_GetBufferPtr(&canvas->fb) = app->m_m_renderBufferPtr;
|
||||
//}
|
||||
|
||||
//uint8_t* exchangeBuffers = app->m_m_renderBufferPtr;
|
||||
//app->m_m_renderBufferPtr = app->m_backBufferPtr;
|
||||
//app->m_backBufferPtr = exchangeBuffers;
|
||||
|
||||
//if(app->m_needUpdateGUI)
|
||||
//{
|
||||
// //memcpy(app->m_renderBuffer, app->m_backBuffer, app->m_canvasSize);
|
||||
// app->m_needUpdateGUI = false;
|
||||
//}
|
||||
|
||||
switch(app->m_context) {
|
||||
case Undefined: {
|
||||
canvas_clear(canvas);
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
|
||||
const char* strInitializing = "Something wrong";
|
||||
canvas_draw_str(
|
||||
canvas,
|
||||
(128 / 2) - (canvas_string_width(canvas, strInitializing) / 2),
|
||||
(64 / 2) /* - (canvas_current_font_height(canvas) / 2)*/,
|
||||
strInitializing);
|
||||
} break;
|
||||
case WaitingForModule:
|
||||
#if ENABLE_MODULE_DETECTION
|
||||
furi_assert(!app->m_wifiDeauthModuleAttached);
|
||||
if(!app->m_wifiDeauthModuleAttached) {
|
||||
canvas_clear(canvas);
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
|
||||
const char* strInitializing = "Attach WiFi Deauther module";
|
||||
canvas_draw_str(
|
||||
canvas,
|
||||
(128 / 2) - (canvas_string_width(canvas, strInitializing) / 2),
|
||||
(64 / 2) /* - (canvas_current_font_height(canvas) / 2)*/,
|
||||
strInitializing);
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
case Initializing:
|
||||
#if ENABLE_MODULE_POWER
|
||||
{
|
||||
furi_assert(!app->m_wifiDeauthModuleInitialized);
|
||||
if(!app->m_wifiDeauthModuleInitialized) {
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
|
||||
const char* strInitializing = "Initializing...";
|
||||
canvas_draw_str(
|
||||
canvas,
|
||||
(128 / 2) - (canvas_string_width(canvas, strInitializing) / 2),
|
||||
(64 / 2) - (canvas_current_font_height(canvas) / 2),
|
||||
strInitializing);
|
||||
}
|
||||
}
|
||||
#endif // ENABLE_MODULE_POWER
|
||||
break;
|
||||
case ModuleActive: {
|
||||
uint8_t* buffer = canvas->fb.tile_buf_ptr;
|
||||
app->m_canvasSize = gui_get_framebuffer_size(app->m_gui);
|
||||
memcpy(buffer, app->m_backBuffer, app->m_canvasSize);
|
||||
} break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
furi_mutex_release(app->mutex);
|
||||
}
|
||||
|
||||
static void
|
||||
esp8266_deauth_module_input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
|
||||
furi_assert(event_queue);
|
||||
|
||||
SPluginEvent event = {.m_type = EventTypeKey, .m_input = *input_event};
|
||||
furi_message_queue_put(event_queue, &event, FuriWaitForever);
|
||||
}
|
||||
|
||||
static void uart_on_irq_cb(UartIrqEvent ev, uint8_t data, void* context) {
|
||||
furi_assert(context);
|
||||
|
||||
SWiFiDeauthApp* app = context;
|
||||
|
||||
DEAUTH_APP_LOG_I("uart_echo_on_irq_cb");
|
||||
|
||||
if(ev == UartIrqEventRXNE) {
|
||||
DEAUTH_APP_LOG_I("ev == UartIrqEventRXNE");
|
||||
furi_stream_buffer_send(app->m_rx_stream, &data, 1, 0);
|
||||
furi_thread_flags_set(furi_thread_get_id(app->m_worker_thread), WorkerEventRx);
|
||||
}
|
||||
}
|
||||
|
||||
static int32_t uart_worker(void* context) {
|
||||
furi_assert(context);
|
||||
DEAUTH_APP_LOG_I("[UART] Worker thread init");
|
||||
|
||||
SWiFiDeauthApp* app = context;
|
||||
furi_mutex_acquire(app->mutex, FuriWaitForever);
|
||||
if(app == NULL) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
FuriStreamBuffer* rx_stream = app->m_rx_stream;
|
||||
|
||||
furi_mutex_release(app->mutex);
|
||||
|
||||
#if ENABLE_MODULE_POWER
|
||||
bool initialized = false;
|
||||
|
||||
FuriString* receivedString;
|
||||
receivedString = furi_string_alloc();
|
||||
#endif // ENABLE_MODULE_POWER
|
||||
|
||||
while(true) {
|
||||
uint32_t events = furi_thread_flags_wait(
|
||||
WorkerEventStop | WorkerEventRx, FuriFlagWaitAny, FuriWaitForever);
|
||||
furi_check((events & FuriFlagError) == 0);
|
||||
|
||||
if(events & WorkerEventStop) break;
|
||||
if(events & WorkerEventRx) {
|
||||
DEAUTH_APP_LOG_I("[UART] Received data");
|
||||
SWiFiDeauthApp* app = context;
|
||||
furi_mutex_acquire(app->mutex, FuriWaitForever);
|
||||
if(app == NULL) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
size_t dataReceivedLength = 0;
|
||||
int index = 0;
|
||||
do {
|
||||
const uint8_t dataBufferSize = 64;
|
||||
uint8_t dataBuffer[dataBufferSize];
|
||||
dataReceivedLength =
|
||||
furi_stream_buffer_receive(rx_stream, dataBuffer, dataBufferSize, 25);
|
||||
if(dataReceivedLength > 0) {
|
||||
#if ENABLE_MODULE_POWER
|
||||
if(!initialized) {
|
||||
if(!(dataReceivedLength > strlen(MODULE_CONTEXT_INITIALIZATION))) {
|
||||
DEAUTH_APP_LOG_I("[UART] Found possible init candidate");
|
||||
for(uint16_t i = 0; i < dataReceivedLength; i++) {
|
||||
furi_string_push_back(receivedString, dataBuffer[i]);
|
||||
}
|
||||
}
|
||||
} else
|
||||
#endif // ENABLE_MODULE_POWER
|
||||
{
|
||||
DEAUTH_APP_LOG_I("[UART] Data copied to backbuffer");
|
||||
memcpy(app->m_backBuffer + index, dataBuffer, dataReceivedLength);
|
||||
index += dataReceivedLength;
|
||||
app->m_needUpdateGUI = true;
|
||||
}
|
||||
}
|
||||
|
||||
} while(dataReceivedLength > 0);
|
||||
|
||||
#if ENABLE_MODULE_POWER
|
||||
if(!app->m_wifiDeauthModuleInitialized) {
|
||||
if(furi_string_cmp_str(receivedString, MODULE_CONTEXT_INITIALIZATION) == 0) {
|
||||
DEAUTH_APP_LOG_I("[UART] Initialized");
|
||||
initialized = true;
|
||||
app->m_wifiDeauthModuleInitialized = true;
|
||||
app->m_context = ModuleActive;
|
||||
furi_string_free(receivedString);
|
||||
} else {
|
||||
DEAUTH_APP_LOG_I("[UART] Not an initialization command");
|
||||
furi_string_reset(receivedString);
|
||||
}
|
||||
}
|
||||
#endif // ENABLE_MODULE_POWER
|
||||
|
||||
furi_mutex_release(app->mutex);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t esp8266_deauth_app(void* p) {
|
||||
UNUSED(p);
|
||||
|
||||
DEAUTH_APP_LOG_I("Init");
|
||||
|
||||
// FuriTimer* timer = furi_timer_alloc(blink_test_update, FuriTimerTypePeriodic, event_queue);
|
||||
// furi_timer_start(timer, furi_kernel_get_tick_frequency());
|
||||
|
||||
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(SPluginEvent));
|
||||
|
||||
SWiFiDeauthApp* app = malloc(sizeof(SWiFiDeauthApp));
|
||||
|
||||
esp8266_deauth_app_init(app);
|
||||
|
||||
furi_hal_gpio_init_simple(app->m_GpioButtons.pinButtonUp, GpioModeOutputPushPull);
|
||||
furi_hal_gpio_init_simple(app->m_GpioButtons.pinButtonDown, GpioModeOutputPushPull);
|
||||
furi_hal_gpio_init_simple(app->m_GpioButtons.pinButtonOK, GpioModeOutputPushPull);
|
||||
furi_hal_gpio_init_simple(app->m_GpioButtons.pinButtonBack, GpioModeOutputPushPull);
|
||||
|
||||
furi_hal_gpio_write(app->m_GpioButtons.pinButtonUp, true);
|
||||
furi_hal_gpio_write(app->m_GpioButtons.pinButtonDown, true);
|
||||
furi_hal_gpio_write(app->m_GpioButtons.pinButtonOK, true);
|
||||
furi_hal_gpio_write(
|
||||
app->m_GpioButtons.pinButtonBack, false); // GPIO15 - Boot fails if pulled HIGH
|
||||
|
||||
#if ENABLE_MODULE_DETECTION
|
||||
furi_hal_gpio_init(
|
||||
&gpio_ext_pc0,
|
||||
GpioModeInput,
|
||||
GpioPullUp,
|
||||
GpioSpeedLow); // Connect to the Flipper's ground just to be sure
|
||||
//furi_hal_gpio_add_int_callback(pinD0, input_isr_d0, this);
|
||||
app->m_context = WaitingForModule;
|
||||
#else
|
||||
#if ENABLE_MODULE_POWER
|
||||
app->m_context = Initializing;
|
||||
uint8_t attempts = 0;
|
||||
while(!furi_hal_power_is_otg_enabled() && attempts++ < 5) {
|
||||
furi_hal_power_enable_otg();
|
||||
furi_delay_ms(10);
|
||||
}
|
||||
furi_delay_ms(200);
|
||||
#else
|
||||
app->m_context = ModuleActive;
|
||||
#endif
|
||||
#endif // ENABLE_MODULE_DETECTION
|
||||
|
||||
app->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
||||
if(!app->mutex) {
|
||||
DEAUTH_APP_LOG_E("cannot create mutex\r\n");
|
||||
free(app);
|
||||
return 255;
|
||||
}
|
||||
|
||||
DEAUTH_APP_LOG_I("Mutex created");
|
||||
|
||||
//app->m_notification = furi_record_open(RECORD_NOTIFICATION);
|
||||
|
||||
ViewPort* view_port = view_port_alloc();
|
||||
view_port_draw_callback_set(view_port, esp8266_deauth_module_render_callback, app);
|
||||
view_port_input_callback_set(view_port, esp8266_deauth_module_input_callback, event_queue);
|
||||
|
||||
// Open GUI and register view_port
|
||||
app->m_gui = furi_record_open(RECORD_GUI);
|
||||
gui_add_view_port(app->m_gui, view_port, GuiLayerFullscreen);
|
||||
|
||||
//notification_message(app->notification, &sequence_set_only_blue_255);
|
||||
|
||||
app->m_rx_stream = furi_stream_buffer_alloc(1 * 1024, 1);
|
||||
|
||||
app->m_worker_thread = furi_thread_alloc();
|
||||
furi_thread_set_name(app->m_worker_thread, "WiFiDeauthModuleUARTWorker");
|
||||
furi_thread_set_stack_size(app->m_worker_thread, 1 * 1024);
|
||||
furi_thread_set_context(app->m_worker_thread, app);
|
||||
furi_thread_set_callback(app->m_worker_thread, uart_worker);
|
||||
furi_thread_start(app->m_worker_thread);
|
||||
DEAUTH_APP_LOG_I("UART thread allocated");
|
||||
|
||||
// Enable uart listener
|
||||
#if DISABLE_CONSOLE
|
||||
furi_hal_console_disable();
|
||||
#endif
|
||||
|
||||
if(UART_CH == FuriHalUartIdUSART1) {
|
||||
furi_hal_console_disable();
|
||||
} else if(UART_CH == FuriHalUartIdLPUART1) {
|
||||
furi_hal_uart_init(UART_CH, FLIPPERZERO_SERIAL_BAUD);
|
||||
}
|
||||
|
||||
furi_hal_uart_set_br(FuriHalUartIdUSART1, FLIPPERZERO_SERIAL_BAUD);
|
||||
furi_hal_uart_set_irq_cb(FuriHalUartIdUSART1, uart_on_irq_cb, app);
|
||||
DEAUTH_APP_LOG_I("UART Listener created");
|
||||
|
||||
SPluginEvent event;
|
||||
for(bool processing = true; processing;) {
|
||||
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
|
||||
furi_mutex_acquire(app->mutex, FuriWaitForever);
|
||||
|
||||
#if ENABLE_MODULE_DETECTION
|
||||
if(!app->m_wifiDeauthModuleAttached) {
|
||||
if(furi_hal_gpio_read(&gpio_ext_pc0) == false) {
|
||||
DEAUTH_APP_LOG_I("Module Attached");
|
||||
app->m_wifiDeauthModuleAttached = true;
|
||||
#if ENABLE_MODULE_POWER
|
||||
app->m_context = Initializing;
|
||||
uint8_t attempts2 = 0;
|
||||
while(!furi_hal_power_is_otg_enabled() && attempts2++ < 3) {
|
||||
furi_hal_power_enable_otg();
|
||||
furi_delay_ms(10);
|
||||
}
|
||||
#else
|
||||
app->m_context = ModuleActive;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#endif // ENABLE_MODULE_DETECTION
|
||||
|
||||
if(event_status == FuriStatusOk) {
|
||||
if(event.m_type == EventTypeKey) {
|
||||
if(app->m_wifiDeauthModuleInitialized) {
|
||||
if(app->m_context == ModuleActive) {
|
||||
switch(event.m_input.key) {
|
||||
case InputKeyUp:
|
||||
if(event.m_input.type == InputTypePress) {
|
||||
DEAUTH_APP_LOG_I("Up Press");
|
||||
furi_hal_gpio_write(app->m_GpioButtons.pinButtonUp, false);
|
||||
} else if(event.m_input.type == InputTypeRelease) {
|
||||
DEAUTH_APP_LOG_I("Up Release");
|
||||
furi_hal_gpio_write(app->m_GpioButtons.pinButtonUp, true);
|
||||
}
|
||||
break;
|
||||
case InputKeyDown:
|
||||
if(event.m_input.type == InputTypePress) {
|
||||
DEAUTH_APP_LOG_I("Down Press");
|
||||
furi_hal_gpio_write(app->m_GpioButtons.pinButtonDown, false);
|
||||
} else if(event.m_input.type == InputTypeRelease) {
|
||||
DEAUTH_APP_LOG_I("Down Release");
|
||||
furi_hal_gpio_write(app->m_GpioButtons.pinButtonDown, true);
|
||||
}
|
||||
break;
|
||||
case InputKeyOk:
|
||||
if(event.m_input.type == InputTypePress) {
|
||||
DEAUTH_APP_LOG_I("OK Press");
|
||||
furi_hal_gpio_write(app->m_GpioButtons.pinButtonOK, false);
|
||||
} else if(event.m_input.type == InputTypeRelease) {
|
||||
DEAUTH_APP_LOG_I("OK Release");
|
||||
furi_hal_gpio_write(app->m_GpioButtons.pinButtonOK, true);
|
||||
}
|
||||
break;
|
||||
case InputKeyBack:
|
||||
if(event.m_input.type == InputTypePress) {
|
||||
DEAUTH_APP_LOG_I("Back Press");
|
||||
furi_hal_gpio_write(app->m_GpioButtons.pinButtonBack, false);
|
||||
} else if(event.m_input.type == InputTypeRelease) {
|
||||
DEAUTH_APP_LOG_I("Back Release");
|
||||
furi_hal_gpio_write(app->m_GpioButtons.pinButtonBack, true);
|
||||
} else if(event.m_input.type == InputTypeLong) {
|
||||
DEAUTH_APP_LOG_I("Back Long");
|
||||
processing = false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if(event.m_input.key == InputKeyBack) {
|
||||
if(event.m_input.type == InputTypeShort ||
|
||||
event.m_input.type == InputTypeLong) {
|
||||
processing = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if ENABLE_MODULE_DETECTION
|
||||
if(app->m_wifiDeauthModuleAttached && furi_hal_gpio_read(&gpio_ext_pc0) == true) {
|
||||
DEAUTH_APP_LOG_D("Module Disconnected - Exit");
|
||||
processing = false;
|
||||
app->m_wifiDeauthModuleAttached = false;
|
||||
app->m_wifiDeauthModuleInitialized = false;
|
||||
}
|
||||
#endif
|
||||
|
||||
view_port_update(view_port);
|
||||
furi_mutex_release(app->mutex);
|
||||
}
|
||||
|
||||
DEAUTH_APP_LOG_I("Start exit app");
|
||||
|
||||
furi_thread_flags_set(furi_thread_get_id(app->m_worker_thread), WorkerEventStop);
|
||||
furi_thread_join(app->m_worker_thread);
|
||||
furi_thread_free(app->m_worker_thread);
|
||||
|
||||
DEAUTH_APP_LOG_I("Thread Deleted");
|
||||
|
||||
// Reset GPIO pins to default state
|
||||
furi_hal_gpio_init(&gpio_ext_pc0, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
|
||||
furi_hal_gpio_init(&gpio_ext_pc3, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
|
||||
furi_hal_gpio_init(&gpio_ext_pb2, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
|
||||
furi_hal_gpio_init(&gpio_ext_pb3, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
|
||||
furi_hal_gpio_init(&gpio_ext_pa4, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
|
||||
|
||||
#if DISABLE_CONSOLE
|
||||
furi_hal_console_enable();
|
||||
#endif
|
||||
|
||||
if(UART_CH == FuriHalUartIdLPUART1) {
|
||||
furi_hal_uart_deinit(UART_CH);
|
||||
} else {
|
||||
furi_hal_console_enable();
|
||||
}
|
||||
|
||||
//*app->m_originalBufferLocation = app->m_originalBuffer;
|
||||
|
||||
view_port_enabled_set(view_port, false);
|
||||
|
||||
gui_remove_view_port(app->m_gui, view_port);
|
||||
|
||||
// Close gui record
|
||||
furi_record_close(RECORD_GUI);
|
||||
//furi_record_close(RECORD_NOTIFICATION);
|
||||
app->m_gui = NULL;
|
||||
|
||||
view_port_free(view_port);
|
||||
|
||||
furi_message_queue_free(event_queue);
|
||||
|
||||
furi_stream_buffer_free(app->m_rx_stream);
|
||||
|
||||
furi_mutex_free(app->mutex);
|
||||
|
||||
// Free rest
|
||||
free(app);
|
||||
|
||||
DEAUTH_APP_LOG_I("App freed");
|
||||
|
||||
#if ENABLE_MODULE_POWER
|
||||
if(furi_hal_power_is_otg_enabled()) {
|
||||
furi_hal_power_disable_otg();
|
||||
}
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
Before Width: | Height: | Size: 1.7 KiB |
@@ -1,13 +0,0 @@
|
||||
App(
|
||||
appid="esp8266_ifttt_virtual_button",
|
||||
name="[ESP8266] IFTTT Btn",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="ifttt_virtual_button_app",
|
||||
cdefines=["APP_IFTTT_VIRTUAL_BUTTON"],
|
||||
requires=[
|
||||
"gui",
|
||||
],
|
||||
stack_size=2 * 1024,
|
||||
fap_icon="icon.png",
|
||||
fap_category="WiFi",
|
||||
)
|
||||
|
Before Width: | Height: | Size: 1.7 KiB |
@@ -1,236 +0,0 @@
|
||||
#include "ifttt_virtual_button.h"
|
||||
|
||||
#define IFTTT_FOLDER "/ext/apps_data/ifttt"
|
||||
#define IFTTT_CONFIG_FOLDER "/ext/apps_data/ifttt/config"
|
||||
const char* CONFIG_FILE_PATH = "/ext/apps_data/ifttt/config/config.settings";
|
||||
|
||||
#define FLIPPERZERO_SERIAL_BAUD 115200
|
||||
typedef enum ESerialCommand { ESerialCommand_Config } ESerialCommand;
|
||||
|
||||
Settings save_settings(Settings settings) {
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
FlipperFormat* file = flipper_format_file_alloc(storage);
|
||||
if(flipper_format_file_open_existing(file, CONFIG_FILE_PATH)) {
|
||||
flipper_format_update_string_cstr(file, CONF_SSID, settings.save_ssid);
|
||||
flipper_format_update_string_cstr(file, CONF_PASSWORD, settings.save_password);
|
||||
flipper_format_update_string_cstr(file, CONF_KEY, settings.save_key);
|
||||
flipper_format_update_string_cstr(file, CONF_EVENT, settings.save_event);
|
||||
} else {
|
||||
}
|
||||
flipper_format_file_close(file);
|
||||
flipper_format_free(file);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
return settings;
|
||||
}
|
||||
|
||||
void save_settings_file(FlipperFormat* file, Settings* settings) {
|
||||
flipper_format_write_header_cstr(file, CONFIG_FILE_HEADER, CONFIG_FILE_VERSION);
|
||||
flipper_format_write_comment_cstr(file, "Enter here the SSID of the wifi network");
|
||||
flipper_format_write_string_cstr(file, CONF_SSID, settings->save_ssid);
|
||||
flipper_format_write_comment_cstr(file, "Enter here the PASSWORD of the wifi network");
|
||||
flipper_format_write_string_cstr(file, CONF_PASSWORD, settings->save_password);
|
||||
flipper_format_write_comment_cstr(file, "Enter here the WEBHOOKS of your IFTTT account");
|
||||
flipper_format_write_string_cstr(file, CONF_KEY, settings->save_key);
|
||||
flipper_format_write_comment_cstr(file, "Enter here the EVENT name of your trigger");
|
||||
flipper_format_write_string_cstr(file, CONF_EVENT, settings->save_event);
|
||||
}
|
||||
|
||||
Settings* load_settings() {
|
||||
Settings* settings = malloc(sizeof(Settings));
|
||||
|
||||
settings->save_ssid = "";
|
||||
settings->save_password = "";
|
||||
settings->save_key = "";
|
||||
settings->save_event = "";
|
||||
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
FlipperFormat* file = flipper_format_file_alloc(storage);
|
||||
|
||||
FuriString* string_value = furi_string_alloc();
|
||||
FuriString* text_ssid_value = furi_string_alloc();
|
||||
FuriString* text_password_value = furi_string_alloc();
|
||||
FuriString* text_key_value = furi_string_alloc();
|
||||
FuriString* text_event_value = furi_string_alloc();
|
||||
|
||||
if(storage_common_stat(storage, CONFIG_FILE_PATH, NULL) != FSE_OK) {
|
||||
if(flipper_format_file_open_new(file, CONFIG_FILE_PATH)) {
|
||||
save_settings_file(file, settings);
|
||||
}
|
||||
flipper_format_file_close(file);
|
||||
} else {
|
||||
if(flipper_format_file_open_existing(file, CONFIG_FILE_PATH)) {
|
||||
uint32_t value;
|
||||
if(flipper_format_read_header(file, string_value, &value)) {
|
||||
if(flipper_format_read_string(file, CONF_SSID, text_ssid_value)) {
|
||||
settings->save_ssid = malloc(furi_string_size(text_ssid_value) + 1);
|
||||
strcpy(settings->save_ssid, furi_string_get_cstr(text_ssid_value));
|
||||
}
|
||||
if(flipper_format_read_string(file, CONF_PASSWORD, text_password_value)) {
|
||||
settings->save_password = malloc(furi_string_size(text_password_value) + 1);
|
||||
strcpy(settings->save_password, furi_string_get_cstr(text_password_value));
|
||||
}
|
||||
if(flipper_format_read_string(file, CONF_KEY, text_key_value)) {
|
||||
settings->save_key = malloc(furi_string_size(text_key_value) + 1);
|
||||
strcpy(settings->save_key, furi_string_get_cstr(text_key_value));
|
||||
}
|
||||
if(flipper_format_read_string(file, CONF_EVENT, text_event_value)) {
|
||||
settings->save_event = malloc(furi_string_size(text_event_value) + 1);
|
||||
strcpy(settings->save_event, furi_string_get_cstr(text_event_value));
|
||||
}
|
||||
}
|
||||
}
|
||||
flipper_format_file_close(file);
|
||||
}
|
||||
|
||||
furi_string_free(text_ssid_value);
|
||||
furi_string_free(text_password_value);
|
||||
furi_string_free(text_key_value);
|
||||
furi_string_free(text_event_value);
|
||||
flipper_format_free(file);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
return settings;
|
||||
}
|
||||
|
||||
void send_serial_command_config(ESerialCommand command, Settings* settings) {
|
||||
uint8_t data[1] = {0};
|
||||
|
||||
char config_tmp[100];
|
||||
strcpy(config_tmp, "config,");
|
||||
strcat(config_tmp, settings->save_key);
|
||||
char config_tmp2[5];
|
||||
strcpy(config_tmp2, config_tmp);
|
||||
strcat(config_tmp2, ",");
|
||||
char config_tmp3[100];
|
||||
strcpy(config_tmp3, config_tmp2);
|
||||
strcat(config_tmp3, settings->save_ssid);
|
||||
char config_tmp4[5];
|
||||
strcpy(config_tmp4, config_tmp3);
|
||||
strcat(config_tmp4, ",");
|
||||
char config_tmp5[100];
|
||||
strcpy(config_tmp5, config_tmp4);
|
||||
strcat(config_tmp5, settings->save_password);
|
||||
char config_tmp6[5];
|
||||
strcpy(config_tmp6, config_tmp5);
|
||||
strcat(config_tmp6, ",");
|
||||
char config[350];
|
||||
strcpy(config, config_tmp6);
|
||||
strcat(config, settings->save_event);
|
||||
|
||||
int length = strlen(config);
|
||||
for(int i = 0; i < length; i++) {
|
||||
switch(command) {
|
||||
case ESerialCommand_Config:
|
||||
data[0] = config[i];
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
furi_hal_uart_tx(FuriHalUartIdUSART1, data, 1);
|
||||
}
|
||||
}
|
||||
|
||||
static bool ifttt_virtual_button_custom_event_callback(void* context, uint32_t event) {
|
||||
furi_assert(context);
|
||||
VirtualButtonApp* app = context;
|
||||
return scene_manager_handle_custom_event(app->scene_manager, event);
|
||||
}
|
||||
|
||||
static bool ifttt_virtual_button_back_event_callback(void* context) {
|
||||
furi_assert(context);
|
||||
VirtualButtonApp* app = context;
|
||||
return scene_manager_handle_back_event(app->scene_manager);
|
||||
}
|
||||
|
||||
static void ifttt_virtual_button_tick_event_callback(void* context) {
|
||||
furi_assert(context);
|
||||
VirtualButtonApp* app = context;
|
||||
scene_manager_handle_tick_event(app->scene_manager);
|
||||
}
|
||||
|
||||
VirtualButtonApp* ifttt_virtual_button_app_alloc(uint32_t first_scene) {
|
||||
VirtualButtonApp* app = malloc(sizeof(VirtualButtonApp));
|
||||
|
||||
// Records
|
||||
app->gui = furi_record_open(RECORD_GUI);
|
||||
app->power = furi_record_open(RECORD_POWER);
|
||||
|
||||
// View dispatcher
|
||||
app->view_dispatcher = view_dispatcher_alloc();
|
||||
app->scene_manager = scene_manager_alloc(&virtual_button_scene_handlers, app);
|
||||
view_dispatcher_enable_queue(app->view_dispatcher);
|
||||
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
||||
view_dispatcher_set_custom_event_callback(
|
||||
app->view_dispatcher, ifttt_virtual_button_custom_event_callback);
|
||||
view_dispatcher_set_navigation_event_callback(
|
||||
app->view_dispatcher, ifttt_virtual_button_back_event_callback);
|
||||
view_dispatcher_set_tick_event_callback(
|
||||
app->view_dispatcher, ifttt_virtual_button_tick_event_callback, 2000);
|
||||
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
||||
|
||||
// Views
|
||||
app->sen_view = send_view_alloc();
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, VirtualButtonAppViewSendView, send_view_get_view(app->sen_view));
|
||||
|
||||
app->abou_view = about_view_alloc();
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, VirtualButtonAppViewAboutView, about_view_get_view(app->abou_view));
|
||||
|
||||
app->submenu = submenu_alloc();
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, VirtualButtonAppViewSubmenu, submenu_get_view(app->submenu));
|
||||
app->dialog = dialog_ex_alloc();
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, VirtualButtonAppViewDialog, dialog_ex_get_view(app->dialog));
|
||||
|
||||
// Set first scene
|
||||
scene_manager_next_scene(app->scene_manager, first_scene);
|
||||
return app;
|
||||
}
|
||||
|
||||
void ifttt_virtual_button_app_free(VirtualButtonApp* app) {
|
||||
furi_assert(app);
|
||||
|
||||
free(app->settings.save_ssid);
|
||||
free(app->settings.save_password);
|
||||
free(app->settings.save_key);
|
||||
|
||||
// Views
|
||||
view_dispatcher_remove_view(app->view_dispatcher, VirtualButtonAppViewSendView);
|
||||
send_view_free(app->sen_view);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, VirtualButtonAppViewAboutView);
|
||||
about_view_free(app->abou_view);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, VirtualButtonAppViewSubmenu);
|
||||
submenu_free(app->submenu);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, VirtualButtonAppViewDialog);
|
||||
dialog_ex_free(app->dialog);
|
||||
// View dispatcher
|
||||
view_dispatcher_free(app->view_dispatcher);
|
||||
scene_manager_free(app->scene_manager);
|
||||
// Records
|
||||
furi_record_close(RECORD_POWER);
|
||||
furi_record_close(RECORD_GUI);
|
||||
|
||||
free(app);
|
||||
}
|
||||
|
||||
int32_t ifttt_virtual_button_app(void* p) {
|
||||
UNUSED(p);
|
||||
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
if(!storage_simply_mkdir(storage, IFTTT_FOLDER)) {
|
||||
}
|
||||
if(!storage_simply_mkdir(storage, IFTTT_CONFIG_FOLDER)) {
|
||||
}
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
uint32_t first_scene = VirtualButtonAppSceneStart;
|
||||
VirtualButtonApp* app = ifttt_virtual_button_app_alloc(first_scene);
|
||||
memcpy(&app->settings, load_settings(), sizeof(Settings));
|
||||
send_serial_command_config(ESerialCommand_Config, &(app->settings));
|
||||
|
||||
view_dispatcher_run(app->view_dispatcher);
|
||||
ifttt_virtual_button_app_free(app);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <furi.h>
|
||||
#include <power/power_service/power.h>
|
||||
#include <gui/gui.h>
|
||||
#include <gui/view.h>
|
||||
#include <gui/view_dispatcher.h>
|
||||
#include <gui/scene_manager.h>
|
||||
#include "views/send_view.h"
|
||||
#include "views/about_view.h"
|
||||
#include <gui/modules/submenu.h>
|
||||
#include <gui/modules/dialog_ex.h>
|
||||
#include <flipper_format/flipper_format.h>
|
||||
#include <flipper_format/flipper_format_i.h>
|
||||
#include <storage/storage.h>
|
||||
#include <furi_hal_uart.h>
|
||||
#include "scenes/virtual_button_scene.h"
|
||||
|
||||
#define APP_NAME "[ESP8266] IFTTT Virtual Button"
|
||||
|
||||
#define CONF_SSID "wifi_ssid"
|
||||
#define CONF_PASSWORD "wifi_password"
|
||||
#define CONF_KEY "webhooks_key"
|
||||
#define CONF_EVENT "event"
|
||||
#define CONFIG_FILE_HEADER "IFTTT Virtual Button Config File"
|
||||
#define CONFIG_FILE_VERSION 1
|
||||
|
||||
typedef struct {
|
||||
char* save_ssid;
|
||||
char* save_password;
|
||||
char* save_key;
|
||||
char* save_event;
|
||||
} Settings;
|
||||
|
||||
typedef struct {
|
||||
Power* power;
|
||||
Gui* gui;
|
||||
SceneManager* scene_manager;
|
||||
ViewDispatcher* view_dispatcher;
|
||||
SendView* sen_view;
|
||||
AboutView* abou_view;
|
||||
Submenu* submenu;
|
||||
DialogEx* dialog;
|
||||
PowerInfo info;
|
||||
Settings settings;
|
||||
} VirtualButtonApp;
|
||||
|
||||
typedef enum {
|
||||
VirtualButtonAppViewSendView,
|
||||
VirtualButtonAppViewAboutView,
|
||||
VirtualButtonAppViewSubmenu,
|
||||
VirtualButtonAppViewDialog,
|
||||
} VirtualButtonAppView;
|
||||
|
||||
Settings save_settings(Settings settings);
|
||||
Settings* load_settings();
|
||||
@@ -1,30 +0,0 @@
|
||||
#include "virtual_button_scene.h"
|
||||
|
||||
// Generate scene on_enter handlers array
|
||||
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
|
||||
void (*const virtual_button_on_enter_handlers[])(void*) = {
|
||||
#include "virtual_button_scene_config.h"
|
||||
};
|
||||
#undef ADD_SCENE
|
||||
|
||||
// Generate scene on_event handlers array
|
||||
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event,
|
||||
bool (*const virtual_button_on_event_handlers[])(void* context, SceneManagerEvent event) = {
|
||||
#include "virtual_button_scene_config.h"
|
||||
};
|
||||
#undef ADD_SCENE
|
||||
|
||||
// Generate scene on_exit handlers array
|
||||
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit,
|
||||
void (*const virtual_button_on_exit_handlers[])(void* context) = {
|
||||
#include "virtual_button_scene_config.h"
|
||||
};
|
||||
#undef ADD_SCENE
|
||||
|
||||
// Initialize scene handlers configuration structure
|
||||
const SceneManagerHandlers virtual_button_scene_handlers = {
|
||||
.on_enter_handlers = virtual_button_on_enter_handlers,
|
||||
.on_event_handlers = virtual_button_on_event_handlers,
|
||||
.on_exit_handlers = virtual_button_on_exit_handlers,
|
||||
.scene_num = VirtualButtonAppSceneNum,
|
||||
};
|
||||
@@ -1,29 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <gui/scene_manager.h>
|
||||
|
||||
// Generate scene id and total number
|
||||
#define ADD_SCENE(prefix, name, id) VirtualButtonAppScene##id,
|
||||
typedef enum {
|
||||
#include "virtual_button_scene_config.h"
|
||||
VirtualButtonAppSceneNum,
|
||||
} VirtualButtonAppScene;
|
||||
#undef ADD_SCENE
|
||||
|
||||
extern const SceneManagerHandlers virtual_button_scene_handlers;
|
||||
|
||||
// Generate scene on_enter handlers declaration
|
||||
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*);
|
||||
#include "virtual_button_scene_config.h"
|
||||
#undef ADD_SCENE
|
||||
|
||||
// Generate scene on_event handlers declaration
|
||||
#define ADD_SCENE(prefix, name, id) \
|
||||
bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event);
|
||||
#include "virtual_button_scene_config.h"
|
||||
#undef ADD_SCENE
|
||||
|
||||
// Generate scene on_exit handlers declaration
|
||||
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context);
|
||||
#include "virtual_button_scene_config.h"
|
||||
#undef ADD_SCENE
|
||||
@@ -1,26 +0,0 @@
|
||||
#include "../ifttt_virtual_button.h"
|
||||
|
||||
static void virtual_button_scene_about_view_update_model(VirtualButtonApp* app) {
|
||||
power_get_info(app->power, &app->info);
|
||||
}
|
||||
|
||||
void virtual_button_scene_about_view_on_enter(void* context) {
|
||||
VirtualButtonApp* app = context;
|
||||
virtual_button_scene_about_view_update_model(app);
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, VirtualButtonAppViewAboutView);
|
||||
}
|
||||
|
||||
bool virtual_button_scene_about_view_on_event(void* context, SceneManagerEvent event) {
|
||||
VirtualButtonApp* app = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeTick) {
|
||||
virtual_button_scene_about_view_update_model(app);
|
||||
consumed = true;
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void virtual_button_scene_about_view_on_exit(void* context) {
|
||||
UNUSED(context);
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
ADD_SCENE(virtual_button, start, Start)
|
||||
ADD_SCENE(virtual_button, send_view, SendView)
|
||||
ADD_SCENE(virtual_button, about_view, AboutView)
|
||||
@@ -1,26 +0,0 @@
|
||||
#include "../ifttt_virtual_button.h"
|
||||
|
||||
static void virtual_button_scene_send_view_update_model(VirtualButtonApp* app) {
|
||||
power_get_info(app->power, &app->info);
|
||||
}
|
||||
|
||||
void virtual_button_scene_send_view_on_enter(void* context) {
|
||||
VirtualButtonApp* app = context;
|
||||
virtual_button_scene_send_view_update_model(app);
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, VirtualButtonAppViewSendView);
|
||||
}
|
||||
|
||||
bool virtual_button_scene_send_view_on_event(void* context, SceneManagerEvent event) {
|
||||
VirtualButtonApp* app = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeTick) {
|
||||
virtual_button_scene_send_view_update_model(app);
|
||||
consumed = true;
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void virtual_button_scene_send_view_on_exit(void* context) {
|
||||
UNUSED(context);
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
#include "../ifttt_virtual_button.h"
|
||||
|
||||
enum VirtualButtonSubmenuIndex {
|
||||
VirtualButtonSubmenuIndexSendView,
|
||||
VirtualButtonSubmenuIndexAboutView,
|
||||
};
|
||||
|
||||
static void virtual_button_scene_start_submenu_callback(void* context, uint32_t index) {
|
||||
furi_assert(context);
|
||||
VirtualButtonApp* app = context;
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, index);
|
||||
}
|
||||
|
||||
void virtual_button_scene_start_on_enter(void* context) {
|
||||
VirtualButtonApp* app = context;
|
||||
Submenu* submenu = app->submenu;
|
||||
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
"Send IFTTT command",
|
||||
VirtualButtonSubmenuIndexSendView,
|
||||
virtual_button_scene_start_submenu_callback,
|
||||
app);
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
"About",
|
||||
VirtualButtonSubmenuIndexAboutView,
|
||||
virtual_button_scene_start_submenu_callback,
|
||||
app);
|
||||
submenu_set_selected_item(
|
||||
submenu, scene_manager_get_scene_state(app->scene_manager, VirtualButtonAppSceneStart));
|
||||
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, VirtualButtonAppViewSubmenu);
|
||||
}
|
||||
|
||||
bool virtual_button_scene_start_on_event(void* context, SceneManagerEvent event) {
|
||||
VirtualButtonApp* app = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == VirtualButtonSubmenuIndexSendView) {
|
||||
scene_manager_next_scene(app->scene_manager, VirtualButtonAppSceneSendView);
|
||||
} else if(event.event == VirtualButtonSubmenuIndexAboutView) {
|
||||
scene_manager_next_scene(app->scene_manager, VirtualButtonAppSceneAboutView);
|
||||
}
|
||||
scene_manager_set_scene_state(app->scene_manager, VirtualButtonAppSceneStart, event.event);
|
||||
consumed = true;
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void virtual_button_scene_start_on_exit(void* context) {
|
||||
VirtualButtonApp* app = context;
|
||||
submenu_reset(app->submenu);
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
#include "about_view.h"
|
||||
#include <furi.h>
|
||||
#include <gui/elements.h>
|
||||
#include <notification/notification.h>
|
||||
#include <notification/notification_messages.h>
|
||||
|
||||
struct AboutView {
|
||||
View* view;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
bool connected;
|
||||
} AboutViewModel;
|
||||
|
||||
static void about_view_draw_callback(Canvas* canvas, void* context) {
|
||||
furi_assert(context);
|
||||
canvas_clear(canvas);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_str_aligned(canvas, 0, 0, AlignLeft, AlignTop, "IFTTT Virtual button");
|
||||
canvas_draw_str_aligned(canvas, 0, 15, AlignLeft, AlignTop, "Version 0.2");
|
||||
canvas_draw_str_aligned(canvas, 0, 50, AlignLeft, AlignTop, "press back");
|
||||
}
|
||||
|
||||
AboutView* about_view_alloc() {
|
||||
AboutView* about_view = malloc(sizeof(AboutView));
|
||||
about_view->view = view_alloc();
|
||||
view_set_context(about_view->view, about_view);
|
||||
view_allocate_model(about_view->view, ViewModelTypeLocking, sizeof(AboutViewModel));
|
||||
view_set_draw_callback(about_view->view, about_view_draw_callback);
|
||||
return about_view;
|
||||
}
|
||||
|
||||
void about_view_free(AboutView* about_view) {
|
||||
furi_assert(about_view);
|
||||
view_free(about_view->view);
|
||||
free(about_view);
|
||||
}
|
||||
|
||||
View* about_view_get_view(AboutView* about_view) {
|
||||
furi_assert(about_view);
|
||||
return about_view->view;
|
||||
}
|
||||
|
||||
void about_view_set_data(AboutView* about_view, bool connected) {
|
||||
furi_assert(about_view);
|
||||
with_view_model(
|
||||
about_view->view, AboutViewModel * model, { model->connected = connected; }, true);
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <gui/view.h>
|
||||
|
||||
typedef struct AboutView AboutView;
|
||||
|
||||
AboutView* about_view_alloc();
|
||||
|
||||
void about_view_free(AboutView* about_view);
|
||||
|
||||
View* about_view_get_view(AboutView* about_view);
|
||||
@@ -1,137 +0,0 @@
|
||||
#include "send_view.h"
|
||||
#include <furi.h>
|
||||
#include <gui/elements.h>
|
||||
#include <notification/notification.h>
|
||||
#include <notification/notification_messages.h>
|
||||
#include <furi_hal_uart.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#define FLIPPERZERO_SERIAL_BAUD 115200
|
||||
|
||||
typedef enum ESerialCommand { ESerialCommand_Send } ESerialCommand;
|
||||
|
||||
struct SendView {
|
||||
View* view;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
bool right_pressed;
|
||||
bool connected;
|
||||
} SendViewModel;
|
||||
|
||||
static void Shake(void) {
|
||||
NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
|
||||
notification_message(notification, &sequence_single_vibro);
|
||||
furi_record_close(RECORD_NOTIFICATION);
|
||||
}
|
||||
|
||||
void send_serial_command_send(ESerialCommand command) {
|
||||
uint8_t data[1] = {0};
|
||||
|
||||
char name[10] = "send";
|
||||
int length = strlen(name);
|
||||
for(int i = 0; i < length; i++) {
|
||||
switch(command) {
|
||||
case ESerialCommand_Send:
|
||||
data[0] = name[i];
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
};
|
||||
|
||||
furi_hal_uart_tx(FuriHalUartIdUSART1, data, 1);
|
||||
}
|
||||
}
|
||||
|
||||
static void send_view_draw_callback(Canvas* canvas, void* context) {
|
||||
furi_assert(context);
|
||||
SendViewModel* model = context;
|
||||
canvas_clear(canvas);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_str_aligned(canvas, 64, 0, AlignCenter, AlignTop, "SEND MODULE");
|
||||
canvas_draw_line(canvas, 0, 10, 128, 10);
|
||||
canvas_draw_str_aligned(canvas, 64, 15, AlignCenter, AlignTop, "Press right to send IFTTT");
|
||||
canvas_draw_str_aligned(canvas, 64, 25, AlignCenter, AlignTop, "command or press and hold");
|
||||
canvas_draw_str_aligned(canvas, 64, 35, AlignCenter, AlignTop, "back to return to the menu");
|
||||
|
||||
// Right
|
||||
if(model->right_pressed) {
|
||||
}
|
||||
}
|
||||
|
||||
static void send_view_process(SendView* send_view, InputEvent* event) {
|
||||
with_view_model(
|
||||
send_view->view,
|
||||
SendViewModel * model,
|
||||
{
|
||||
if(event->type == InputTypePress) {
|
||||
if(event->key == InputKeyUp) {
|
||||
} else if(event->key == InputKeyDown) {
|
||||
} else if(event->key == InputKeyLeft) {
|
||||
} else if(event->key == InputKeyRight) {
|
||||
model->right_pressed = true;
|
||||
Shake();
|
||||
send_serial_command_send(ESerialCommand_Send);
|
||||
} else if(event->key == InputKeyOk) {
|
||||
} else if(event->key == InputKeyBack) {
|
||||
}
|
||||
} else if(event->type == InputTypeRelease) {
|
||||
if(event->key == InputKeyUp) {
|
||||
} else if(event->key == InputKeyDown) {
|
||||
} else if(event->key == InputKeyLeft) {
|
||||
} else if(event->key == InputKeyRight) {
|
||||
model->right_pressed = false;
|
||||
} else if(event->key == InputKeyOk) {
|
||||
} else if(event->key == InputKeyBack) {
|
||||
}
|
||||
} else if(event->type == InputTypeShort) {
|
||||
if(event->key == InputKeyBack) {
|
||||
}
|
||||
}
|
||||
},
|
||||
true);
|
||||
}
|
||||
|
||||
static bool send_view_input_callback(InputEvent* event, void* context) {
|
||||
furi_assert(context);
|
||||
SendView* send_view = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event->type == InputTypeLong && event->key == InputKeyBack) {
|
||||
} else {
|
||||
send_view_process(send_view, event);
|
||||
consumed = true;
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
SendView* send_view_alloc() {
|
||||
SendView* send_view = malloc(sizeof(SendView));
|
||||
send_view->view = view_alloc();
|
||||
view_set_context(send_view->view, send_view);
|
||||
view_allocate_model(send_view->view, ViewModelTypeLocking, sizeof(SendViewModel));
|
||||
view_set_draw_callback(send_view->view, send_view_draw_callback);
|
||||
view_set_input_callback(send_view->view, send_view_input_callback);
|
||||
furi_hal_uart_set_br(FuriHalUartIdUSART1, FLIPPERZERO_SERIAL_BAUD);
|
||||
|
||||
return send_view;
|
||||
}
|
||||
|
||||
void send_view_free(SendView* send_view) {
|
||||
furi_assert(send_view);
|
||||
view_free(send_view->view);
|
||||
free(send_view);
|
||||
}
|
||||
|
||||
View* send_view_get_view(SendView* send_view) {
|
||||
furi_assert(send_view);
|
||||
return send_view->view;
|
||||
}
|
||||
|
||||
void send_view_set_data(SendView* send_view, bool connected) {
|
||||
furi_assert(send_view);
|
||||
with_view_model(
|
||||
send_view->view, SendViewModel * model, { model->connected = connected; }, true);
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <gui/view.h>
|
||||
|
||||
typedef struct SendView SendView;
|
||||
|
||||
SendView* send_view_alloc();
|
||||
|
||||
void send_view_free(SendView* send_view);
|
||||
|
||||
View* send_view_get_view(SendView* send_view);
|
||||
@@ -1,15 +0,0 @@
|
||||
App(
|
||||
appid="morse_code",
|
||||
name="Morse Code",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="morse_code_app",
|
||||
requires=[
|
||||
"gui",
|
||||
],
|
||||
stack_size=1 * 1024,
|
||||
fap_icon="morse_code_10px.png",
|
||||
fap_category="Media",
|
||||
fap_author="@wh00hw & @xMasterX",
|
||||
fap_version="1.0",
|
||||
fap_description="Simple Morse Code parser",
|
||||
)
|
||||
@@ -1,166 +0,0 @@
|
||||
#include "morse_code_worker.h"
|
||||
#include <furi.h>
|
||||
#include <gui/gui.h>
|
||||
#include <gui/elements.h>
|
||||
#include <input/input.h>
|
||||
#include <stdlib.h>
|
||||
#include <furi_hal.h>
|
||||
#include <string.h>
|
||||
|
||||
static const float MORSE_CODE_VOLUMES[] = {0, .25, .5, .75, 1};
|
||||
|
||||
typedef struct {
|
||||
FuriString* words;
|
||||
uint8_t volume;
|
||||
uint32_t dit_delta;
|
||||
} MorseCodeModel;
|
||||
|
||||
typedef struct {
|
||||
MorseCodeModel* model;
|
||||
FuriMutex** model_mutex;
|
||||
|
||||
FuriMessageQueue* input_queue;
|
||||
|
||||
ViewPort* view_port;
|
||||
Gui* gui;
|
||||
|
||||
MorseCodeWorker* worker;
|
||||
} MorseCode;
|
||||
|
||||
static void render_callback(Canvas* const canvas, void* ctx) {
|
||||
MorseCode* morse_code = ctx;
|
||||
furi_check(furi_mutex_acquire(morse_code->model_mutex, FuriWaitForever) == FuriStatusOk);
|
||||
// border around the edge of the screen
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
|
||||
//write words
|
||||
elements_multiline_text_aligned(
|
||||
canvas, 64, 30, AlignCenter, AlignCenter, furi_string_get_cstr(morse_code->model->words));
|
||||
|
||||
// volume view_port
|
||||
uint8_t vol_bar_x_pos = 124;
|
||||
uint8_t vol_bar_y_pos = 0;
|
||||
const uint8_t volume_h = (64 / (COUNT_OF(MORSE_CODE_VOLUMES) - 1)) * morse_code->model->volume;
|
||||
canvas_draw_frame(canvas, vol_bar_x_pos, vol_bar_y_pos, 4, 64);
|
||||
canvas_draw_box(canvas, vol_bar_x_pos, vol_bar_y_pos + (64 - volume_h), 4, volume_h);
|
||||
|
||||
//dit bpms
|
||||
FuriString* ditbpm = furi_string_alloc_printf("Dit: %ld ms", morse_code->model->dit_delta);
|
||||
canvas_draw_str_aligned(canvas, 0, 10, AlignLeft, AlignCenter, furi_string_get_cstr(ditbpm));
|
||||
furi_string_free(ditbpm);
|
||||
|
||||
//button info
|
||||
elements_button_center(canvas, "Press/Hold");
|
||||
furi_mutex_release(morse_code->model_mutex);
|
||||
}
|
||||
|
||||
static void input_callback(InputEvent* input_event, void* ctx) {
|
||||
MorseCode* morse_code = ctx;
|
||||
furi_message_queue_put(morse_code->input_queue, input_event, FuriWaitForever);
|
||||
}
|
||||
|
||||
static void morse_code_worker_callback(FuriString* words, void* context) {
|
||||
MorseCode* morse_code = context;
|
||||
furi_check(furi_mutex_acquire(morse_code->model_mutex, FuriWaitForever) == FuriStatusOk);
|
||||
furi_string_set(morse_code->model->words, words);
|
||||
furi_mutex_release(morse_code->model_mutex);
|
||||
view_port_update(morse_code->view_port);
|
||||
}
|
||||
|
||||
MorseCode* morse_code_alloc() {
|
||||
MorseCode* instance = malloc(sizeof(MorseCode));
|
||||
|
||||
instance->model = malloc(sizeof(MorseCodeModel));
|
||||
instance->model->words = furi_string_alloc_set_str("");
|
||||
instance->model->volume = 3;
|
||||
instance->model->dit_delta = 150;
|
||||
instance->model_mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
||||
|
||||
instance->input_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
|
||||
|
||||
instance->worker = morse_code_worker_alloc();
|
||||
|
||||
morse_code_worker_set_callback(instance->worker, morse_code_worker_callback, instance);
|
||||
|
||||
instance->view_port = view_port_alloc();
|
||||
view_port_draw_callback_set(instance->view_port, render_callback, instance);
|
||||
view_port_input_callback_set(instance->view_port, input_callback, instance);
|
||||
|
||||
// Open GUI and register view_port
|
||||
instance->gui = furi_record_open(RECORD_GUI);
|
||||
gui_add_view_port(instance->gui, instance->view_port, GuiLayerFullscreen);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void morse_code_free(MorseCode* instance) {
|
||||
gui_remove_view_port(instance->gui, instance->view_port);
|
||||
furi_record_close(RECORD_GUI);
|
||||
view_port_free(instance->view_port);
|
||||
|
||||
morse_code_worker_free(instance->worker);
|
||||
|
||||
furi_message_queue_free(instance->input_queue);
|
||||
|
||||
furi_mutex_free(instance->model_mutex);
|
||||
|
||||
furi_string_free(instance->model->words);
|
||||
free(instance->model);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
int32_t morse_code_app() {
|
||||
MorseCode* morse_code = morse_code_alloc();
|
||||
InputEvent input;
|
||||
|
||||
morse_code_worker_start(morse_code->worker);
|
||||
morse_code_worker_set_volume(
|
||||
morse_code->worker, MORSE_CODE_VOLUMES[morse_code->model->volume]);
|
||||
morse_code_worker_set_dit_delta(morse_code->worker, morse_code->model->dit_delta);
|
||||
|
||||
while(furi_message_queue_get(morse_code->input_queue, &input, FuriWaitForever) ==
|
||||
FuriStatusOk) {
|
||||
furi_check(furi_mutex_acquire(morse_code->model_mutex, FuriWaitForever) == FuriStatusOk);
|
||||
if(input.key == InputKeyBack && input.type == InputTypeLong) {
|
||||
furi_mutex_release(morse_code->model_mutex);
|
||||
break;
|
||||
} else if(input.key == InputKeyBack && input.type == InputTypeShort) {
|
||||
morse_code_worker_reset_text(morse_code->worker);
|
||||
furi_string_reset(morse_code->model->words);
|
||||
} else if(input.key == InputKeyOk) {
|
||||
if(input.type == InputTypePress)
|
||||
morse_code_worker_play(morse_code->worker, true);
|
||||
else if(input.type == InputTypeRelease)
|
||||
morse_code_worker_play(morse_code->worker, false);
|
||||
} else if(input.key == InputKeyUp && input.type == InputTypePress) {
|
||||
if(morse_code->model->volume < COUNT_OF(MORSE_CODE_VOLUMES) - 1)
|
||||
morse_code->model->volume++;
|
||||
morse_code_worker_set_volume(
|
||||
morse_code->worker, MORSE_CODE_VOLUMES[morse_code->model->volume]);
|
||||
} else if(input.key == InputKeyDown && input.type == InputTypePress) {
|
||||
if(morse_code->model->volume > 0) morse_code->model->volume--;
|
||||
morse_code_worker_set_volume(
|
||||
morse_code->worker, MORSE_CODE_VOLUMES[morse_code->model->volume]);
|
||||
} else if(input.key == InputKeyLeft && input.type == InputTypePress) {
|
||||
if(morse_code->model->dit_delta > 10) morse_code->model->dit_delta -= 10;
|
||||
morse_code_worker_set_dit_delta(morse_code->worker, morse_code->model->dit_delta);
|
||||
} else if(input.key == InputKeyRight && input.type == InputTypePress) {
|
||||
if(morse_code->model->dit_delta >= 10) morse_code->model->dit_delta += 10;
|
||||
morse_code_worker_set_dit_delta(morse_code->worker, morse_code->model->dit_delta);
|
||||
}
|
||||
|
||||
FURI_LOG_D(
|
||||
"Input",
|
||||
"%s %s %ld",
|
||||
input_get_key_name(input.key),
|
||||
input_get_type_name(input.type),
|
||||
input.sequence);
|
||||
|
||||
furi_mutex_release(morse_code->model_mutex);
|
||||
view_port_update(morse_code->view_port);
|
||||
}
|
||||
|
||||
morse_code_worker_stop(morse_code->worker);
|
||||
morse_code_free(morse_code);
|
||||
return 0;
|
||||
}
|
||||
|
Before Width: | Height: | Size: 168 B |
@@ -1,183 +0,0 @@
|
||||
#include "morse_code_worker.h"
|
||||
#include <furi_hal.h>
|
||||
#include <lib/flipper_format/flipper_format.h>
|
||||
|
||||
#define TAG "MorseCodeWorker"
|
||||
|
||||
#define MORSE_CODE_VERSION 0
|
||||
|
||||
//A-Z0-1
|
||||
const char morse_array[36][6] = {".-", "-...", "-.-.", "-..", ".", "..-.",
|
||||
"--.", "....", "..", ".---", "-.-", ".-..",
|
||||
"--", "-.", "---", ".--.", "--.-", ".-.",
|
||||
"...", "-", "..-", "...-", ".--", "-..-",
|
||||
"-.--", "--..", ".----", "..---", "...--", "....-",
|
||||
".....", "-....", "--...", "---..", "----.", "-----"};
|
||||
const char symbol_array[36] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L',
|
||||
'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
|
||||
'Y', 'Z', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0'};
|
||||
|
||||
struct MorseCodeWorker {
|
||||
FuriThread* thread;
|
||||
MorseCodeWorkerCallback callback;
|
||||
void* callback_context;
|
||||
bool is_running;
|
||||
bool play;
|
||||
float volume;
|
||||
uint32_t dit_delta;
|
||||
FuriString* buffer;
|
||||
FuriString* words;
|
||||
};
|
||||
|
||||
void morse_code_worker_fill_buffer(MorseCodeWorker* instance, uint32_t duration) {
|
||||
FURI_LOG_D("MorseCode: Duration", "%ld", duration);
|
||||
if(duration <= instance->dit_delta)
|
||||
furi_string_push_back(instance->buffer, *DOT);
|
||||
else if(duration <= (instance->dit_delta * 3))
|
||||
furi_string_push_back(instance->buffer, *LINE);
|
||||
else
|
||||
furi_string_reset(instance->buffer);
|
||||
if(furi_string_size(instance->buffer) > 5) furi_string_reset(instance->buffer);
|
||||
FURI_LOG_D("MorseCode: Buffer", "%s", furi_string_get_cstr(instance->buffer));
|
||||
}
|
||||
|
||||
void morse_code_worker_fill_letter(MorseCodeWorker* instance) {
|
||||
if(furi_string_size(instance->words) > 63) furi_string_reset(instance->words);
|
||||
for(size_t i = 0; i < sizeof(morse_array); i++) {
|
||||
if(furi_string_cmp_str(instance->buffer, morse_array[i]) == 0) {
|
||||
furi_string_push_back(instance->words, symbol_array[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
furi_string_reset(instance->buffer);
|
||||
FURI_LOG_D("MorseCode: Words", "%s", furi_string_get_cstr(instance->words));
|
||||
}
|
||||
|
||||
static int32_t morse_code_worker_thread_callback(void* context) {
|
||||
furi_assert(context);
|
||||
MorseCodeWorker* instance = context;
|
||||
bool was_playing = false;
|
||||
uint32_t start_tick = 0;
|
||||
uint32_t end_tick = 0;
|
||||
bool pushed = true;
|
||||
bool spaced = true;
|
||||
while(instance->is_running) {
|
||||
furi_delay_ms(SLEEP);
|
||||
|
||||
if(instance->play) {
|
||||
if(!was_playing) {
|
||||
start_tick = furi_get_tick();
|
||||
if(furi_hal_speaker_acquire(1000)) {
|
||||
furi_hal_speaker_start(FREQUENCY, instance->volume);
|
||||
}
|
||||
was_playing = true;
|
||||
}
|
||||
} else {
|
||||
if(was_playing) {
|
||||
pushed = false;
|
||||
spaced = false;
|
||||
if(furi_hal_speaker_is_mine()) {
|
||||
furi_hal_speaker_stop();
|
||||
furi_hal_speaker_release();
|
||||
}
|
||||
end_tick = furi_get_tick();
|
||||
was_playing = false;
|
||||
morse_code_worker_fill_buffer(instance, end_tick - start_tick);
|
||||
start_tick = 0;
|
||||
}
|
||||
}
|
||||
if(!pushed) {
|
||||
if(end_tick + (instance->dit_delta * 3) < furi_get_tick()) {
|
||||
//NEW LETTER
|
||||
if(!furi_string_empty(instance->buffer)) {
|
||||
morse_code_worker_fill_letter(instance);
|
||||
if(instance->callback)
|
||||
instance->callback(instance->words, instance->callback_context);
|
||||
} else {
|
||||
spaced = true;
|
||||
}
|
||||
pushed = true;
|
||||
}
|
||||
}
|
||||
if(!spaced) {
|
||||
if(end_tick + (instance->dit_delta * 7) < furi_get_tick()) {
|
||||
//NEW WORD
|
||||
furi_string_push_back(instance->words, *SPACE);
|
||||
if(instance->callback)
|
||||
instance->callback(instance->words, instance->callback_context);
|
||||
spaced = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
MorseCodeWorker* morse_code_worker_alloc() {
|
||||
MorseCodeWorker* instance = malloc(sizeof(MorseCodeWorker));
|
||||
instance->thread = furi_thread_alloc();
|
||||
furi_thread_set_name(instance->thread, "MorseCodeWorker");
|
||||
furi_thread_set_stack_size(instance->thread, 1024);
|
||||
furi_thread_set_context(instance->thread, instance);
|
||||
furi_thread_set_callback(instance->thread, morse_code_worker_thread_callback);
|
||||
instance->play = false;
|
||||
instance->volume = 1.0f;
|
||||
instance->dit_delta = 150;
|
||||
instance->buffer = furi_string_alloc_set_str("");
|
||||
instance->words = furi_string_alloc_set_str("");
|
||||
return instance;
|
||||
}
|
||||
|
||||
void morse_code_worker_free(MorseCodeWorker* instance) {
|
||||
furi_assert(instance);
|
||||
furi_string_free(instance->buffer);
|
||||
furi_string_free(instance->words);
|
||||
furi_thread_free(instance->thread);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void morse_code_worker_set_callback(
|
||||
MorseCodeWorker* instance,
|
||||
MorseCodeWorkerCallback callback,
|
||||
void* context) {
|
||||
furi_assert(instance);
|
||||
instance->callback = callback;
|
||||
instance->callback_context = context;
|
||||
}
|
||||
|
||||
void morse_code_worker_play(MorseCodeWorker* instance, bool play) {
|
||||
furi_assert(instance);
|
||||
instance->play = play;
|
||||
}
|
||||
|
||||
void morse_code_worker_set_volume(MorseCodeWorker* instance, float level) {
|
||||
furi_assert(instance);
|
||||
instance->volume = level;
|
||||
}
|
||||
|
||||
void morse_code_worker_set_dit_delta(MorseCodeWorker* instance, uint32_t delta) {
|
||||
furi_assert(instance);
|
||||
instance->dit_delta = delta;
|
||||
}
|
||||
|
||||
void morse_code_worker_reset_text(MorseCodeWorker* instance) {
|
||||
furi_assert(instance);
|
||||
furi_string_reset(instance->buffer);
|
||||
furi_string_reset(instance->words);
|
||||
}
|
||||
|
||||
void morse_code_worker_start(MorseCodeWorker* instance) {
|
||||
furi_assert(instance);
|
||||
furi_assert(instance->is_running == false);
|
||||
instance->is_running = true;
|
||||
furi_thread_start(instance->thread);
|
||||
FURI_LOG_D("MorseCode: Start", "is Running");
|
||||
}
|
||||
|
||||
void morse_code_worker_stop(MorseCodeWorker* instance) {
|
||||
furi_assert(instance);
|
||||
furi_assert(instance->is_running == true);
|
||||
instance->play = false;
|
||||
instance->is_running = false;
|
||||
furi_thread_join(instance->thread);
|
||||
FURI_LOG_D("MorseCode: Stop", "Stop");
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <furi.h>
|
||||
|
||||
#define FREQUENCY 261.63f
|
||||
#define SLEEP 10
|
||||
#define DOT "."
|
||||
#define LINE "-"
|
||||
#define SPACE " "
|
||||
|
||||
typedef void (*MorseCodeWorkerCallback)(FuriString* buffer, void* context);
|
||||
|
||||
typedef struct MorseCodeWorker MorseCodeWorker;
|
||||
|
||||
MorseCodeWorker* morse_code_worker_alloc();
|
||||
|
||||
void morse_code_worker_free(MorseCodeWorker* instance);
|
||||
|
||||
void morse_code_worker_set_callback(
|
||||
MorseCodeWorker* instance,
|
||||
MorseCodeWorkerCallback callback,
|
||||
void* context);
|
||||
|
||||
void morse_code_worker_start(MorseCodeWorker* instance);
|
||||
|
||||
void morse_code_worker_stop(MorseCodeWorker* instance);
|
||||
|
||||
void morse_code_worker_play(MorseCodeWorker* instance, bool play);
|
||||
|
||||
void morse_code_worker_reset_text(MorseCodeWorker* instance);
|
||||
|
||||
void morse_code_worker_set_volume(MorseCodeWorker* instance, float level);
|
||||
|
||||
void morse_code_worker_set_dit_delta(MorseCodeWorker* instance, uint32_t delta);
|
||||
@@ -1,13 +0,0 @@
|
||||
App(
|
||||
appid="multi_converter",
|
||||
name="Multi Converter",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="multi_converter_app",
|
||||
requires=["gui"],
|
||||
stack_size=1 * 1024,
|
||||
fap_icon="converter_10px.png",
|
||||
fap_category="Tools",
|
||||
fap_author="@theisolinearchip",
|
||||
fap_version="1.0",
|
||||
fap_description="A multi-unit converter written with an easy and expandable system for adding new units and conversion methods",
|
||||
)
|
||||
|
Before Width: | Height: | Size: 1.6 KiB |
@@ -1,164 +0,0 @@
|
||||
#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) {
|
||||
furi_assert(ctx);
|
||||
const MultiConverterState* multi_converter_state = ctx;
|
||||
furi_mutex_acquire(multi_converter_state->mutex, FuriWaitForever);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
furi_mutex_release(multi_converter_state->mutex);
|
||||
}
|
||||
|
||||
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)
|
||||
multi_converter_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
||||
if(!multi_converter_state->mutex) {
|
||||
FURI_LOG_E("MultiConverter", "cannot create mutex\r\n");
|
||||
furi_message_queue_free(event_queue);
|
||||
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, multi_converter_state);
|
||||
view_port_input_callback_set(view_port, multi_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);
|
||||
|
||||
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);
|
||||
furi_mutex_acquire(multi_converter_state->mutex, FuriWaitForever);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
view_port_update(view_port);
|
||||
furi_mutex_release(multi_converter_state->mutex);
|
||||
}
|
||||
|
||||
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);
|
||||
furi_mutex_free(multi_converter_state->mutex);
|
||||
free(multi_converter_state);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
#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 {
|
||||
FuriMutex* mutex;
|
||||
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!)
|
||||
};
|
||||
@@ -1,326 +0,0 @@
|
||||
#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;
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
#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);
|
||||
@@ -1,210 +0,0 @@
|
||||
#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++;
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
#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);
|
||||
@@ -1,261 +0,0 @@
|
||||
#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);
|
||||
}
|
||||
@@ -1,171 +0,0 @@
|
||||
#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,
|
||||
};
|
||||
@@ -1,11 +0,0 @@
|
||||
App(
|
||||
appid="multi_dice",
|
||||
name="Multi-Dice",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="dice_app",
|
||||
cdefines=["APP_DICE"],
|
||||
requires=["gui"],
|
||||
stack_size=2 * 1024,
|
||||
fap_icon="dice.png",
|
||||
fap_category="Games",
|
||||
)
|
||||
@@ -1,558 +0,0 @@
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include "furi_hal_random.h"
|
||||
#include <gui/elements.h>
|
||||
#include <gui/gui.h>
|
||||
#include <input/input.h>
|
||||
|
||||
#define TAG "Dice Roller"
|
||||
|
||||
typedef enum {
|
||||
EventTypeTick,
|
||||
EventTypeKey,
|
||||
} EventType;
|
||||
|
||||
typedef struct {
|
||||
EventType type;
|
||||
InputEvent input;
|
||||
} PluginEvent;
|
||||
|
||||
typedef struct {
|
||||
FuriMutex* mutex;
|
||||
FuriMessageQueue* event_queue;
|
||||
FuriHalRtcDateTime datetime;
|
||||
uint8_t diceSelect;
|
||||
uint8_t diceQty;
|
||||
uint8_t diceRoll;
|
||||
uint8_t playerOneScore;
|
||||
uint8_t playerTwoScore;
|
||||
char rollTime[1][15];
|
||||
char diceType[1][11];
|
||||
char strings[5][45];
|
||||
char theScores[1][45];
|
||||
bool letsRoll;
|
||||
} DiceState;
|
||||
|
||||
static void dice_input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
|
||||
furi_assert(event_queue);
|
||||
PluginEvent event = {.type = EventTypeKey, .input = *input_event};
|
||||
furi_message_queue_put(event_queue, &event, FuriWaitForever);
|
||||
}
|
||||
|
||||
static void dice_render_callback(Canvas* const canvas, void* ctx) {
|
||||
DiceState* state = ctx;
|
||||
if(furi_mutex_acquire(state->mutex, 200) != FuriStatusOk) {
|
||||
// Can't obtain mutex, requeue render
|
||||
PluginEvent event = {.type = EventTypeTick};
|
||||
furi_message_queue_put(state->event_queue, &event, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
if(state->diceSelect < 220) {
|
||||
if(state->diceQty == 1) {
|
||||
elements_button_left(canvas, "x1");
|
||||
} else if(state->diceQty == 2) {
|
||||
elements_button_left(canvas, "x2");
|
||||
} else if(state->diceQty == 3) {
|
||||
elements_button_left(canvas, "x3");
|
||||
} else if(state->diceQty == 4) {
|
||||
elements_button_left(canvas, "x4");
|
||||
} else if(state->diceQty == 5) {
|
||||
elements_button_left(canvas, "x5");
|
||||
} else if(state->diceQty == 6) {
|
||||
elements_button_left(canvas, "x6");
|
||||
}
|
||||
}
|
||||
if(state->letsRoll) {
|
||||
furi_hal_rtc_get_datetime(&state->datetime);
|
||||
uint8_t hour = state->datetime.hour;
|
||||
char strAMPM[3];
|
||||
snprintf(strAMPM, sizeof(strAMPM), "%s", "AM");
|
||||
if(hour > 12) {
|
||||
hour -= 12;
|
||||
snprintf(strAMPM, sizeof(strAMPM), "%s", "PM");
|
||||
}
|
||||
snprintf(
|
||||
state->rollTime[0],
|
||||
sizeof(state->rollTime[0]),
|
||||
"%.2d:%.2d:%.2d %s",
|
||||
hour,
|
||||
state->datetime.minute,
|
||||
state->datetime.second,
|
||||
strAMPM);
|
||||
if(state->diceSelect == 229) {
|
||||
const char* eightBall[] = {
|
||||
"It is certain",
|
||||
"Without a doubt",
|
||||
"You may rely on it",
|
||||
"Yes definitely",
|
||||
"It is decidedly so",
|
||||
"As I see it, yes",
|
||||
"Most likely",
|
||||
"Yes",
|
||||
"Outlook good",
|
||||
"Signs point to yes",
|
||||
"Reply hazy try again",
|
||||
"Better not tell you now",
|
||||
"Ask again later",
|
||||
"Cannot predict now",
|
||||
"Concentrate and ask again",
|
||||
"Don't count on it",
|
||||
"Outlook not so good",
|
||||
"My sources say no",
|
||||
"Very doubtful",
|
||||
"My reply is no"};
|
||||
state->diceRoll =
|
||||
((rand() % state->diceSelect) + 1); // JUST TO GET IT GOING? AND FIX BUG
|
||||
snprintf(state->diceType[0], sizeof(state->diceType[0]), "%s", "8BALL");
|
||||
snprintf(
|
||||
state->strings[0],
|
||||
sizeof(state->strings[0]),
|
||||
"%s at %s",
|
||||
state->diceType[0],
|
||||
state->rollTime[0]);
|
||||
uint8_t d1_i = rand() % COUNT_OF(eightBall);
|
||||
snprintf(state->strings[1], sizeof(state->strings[1]), "%s", eightBall[d1_i]);
|
||||
} else if(state->diceSelect == 228) {
|
||||
const char* eightBall[] = {
|
||||
"I'd do it.",
|
||||
"Hell, yeah!",
|
||||
"You bet your life!",
|
||||
"What are you waiting for?",
|
||||
"You could do worse things.",
|
||||
"Sure, I won't tell.",
|
||||
"Yeah, you got this. Would I lie to you?",
|
||||
"Looks like fun to me. ",
|
||||
"Yeah, sure, why not?",
|
||||
"DO IT!!!",
|
||||
"Who's it gonna hurt?",
|
||||
"Can you blame someone else?",
|
||||
"Ask me again later.",
|
||||
"Maybe, maybe not, I can't tell right now. ",
|
||||
"Are you the betting type? ",
|
||||
"Don't blame me if you get caught.",
|
||||
"What have you got to lose?",
|
||||
"I wouldn't if I were you.",
|
||||
"My money's on the snowball.",
|
||||
"Oh Hell no!"};
|
||||
state->diceRoll =
|
||||
((rand() % state->diceSelect) + 1); // JUST TO GET IT GOING? AND FIX BUG
|
||||
snprintf(state->diceType[0], sizeof(state->diceType[0]), "%s", "Devil Ball");
|
||||
snprintf(
|
||||
state->strings[0],
|
||||
sizeof(state->strings[0]),
|
||||
"%s at %s",
|
||||
state->diceType[0],
|
||||
state->rollTime[0]);
|
||||
uint8_t d1_i = rand() % COUNT_OF(eightBall);
|
||||
snprintf(state->strings[1], sizeof(state->strings[1]), "%s", eightBall[d1_i]);
|
||||
} else if(state->diceSelect == 230) {
|
||||
const char* diceOne[] = {
|
||||
"Nibble",
|
||||
"Massage",
|
||||
"Touch",
|
||||
"Caress",
|
||||
"Pet",
|
||||
"Fondle",
|
||||
"Suck",
|
||||
"Lick",
|
||||
"Blow",
|
||||
"Kiss",
|
||||
"???"};
|
||||
const char* diceTwo[] = {
|
||||
"Navel",
|
||||
"Ears",
|
||||
"Lips",
|
||||
"Neck",
|
||||
"Hand",
|
||||
"Thigh",
|
||||
"Nipple",
|
||||
"Breasts",
|
||||
"???",
|
||||
"Genitals"};
|
||||
state->diceRoll =
|
||||
((rand() % state->diceSelect) + 1); // JUST TO GET IT GOING? AND FIX BUG
|
||||
snprintf(state->diceType[0], sizeof(state->diceType[0]), "%s", "SEX?");
|
||||
snprintf(
|
||||
state->strings[0],
|
||||
sizeof(state->strings[0]),
|
||||
"%s at %s",
|
||||
state->diceType[0],
|
||||
state->rollTime[0]);
|
||||
uint8_t d1_i = rand() % COUNT_OF(diceOne);
|
||||
uint8_t d2_i = rand() % COUNT_OF(diceTwo);
|
||||
snprintf(
|
||||
state->strings[1],
|
||||
sizeof(state->strings[1]),
|
||||
"%s %s",
|
||||
diceOne[d1_i],
|
||||
diceTwo[d2_i]);
|
||||
} else if(state->diceSelect == 231) {
|
||||
const char* deckOne[] = {"2H", "2C", "2D", "2S", "3H", "3C", "3D", "3S", "4H",
|
||||
"4C", "4D", "4S", "5H", "5C", "5D", "5S", "6H", "6C",
|
||||
"6D", "6S", "7H", "7C", "7D", "7S", "8H", "8C", "8D",
|
||||
"8S", "9H", "9C", "9D", "9S", "10H", "10C", "10D", "10S",
|
||||
"JH", "JC", "JD", "JS", "QH", "QC", "QD", "QS", "KH",
|
||||
"KC", "KD", "KS", "AH", "AC", "AD", "AS"};
|
||||
char* deckTwo[] = {"2H", "2C", "2D", "2S", "3H", "3C", "3D", "3S", "4H",
|
||||
"4C", "4D", "4S", "5H", "5C", "5D", "5S", "6H", "6C",
|
||||
"6D", "6S", "7H", "7C", "7D", "7S", "8H", "8C", "8D",
|
||||
"8S", "9H", "9C", "9D", "9S", "10H", "10C", "10D", "10S",
|
||||
"JH", "JC", "JD", "JS", "QH", "QC", "QD", "QS", "KH",
|
||||
"KC", "KD", "KS", "AH", "AC", "AD"}; // ONE LESS SINCE ONE WILL BE REMOVED
|
||||
state->diceRoll =
|
||||
((rand() % state->diceSelect) + 1); // JUST TO GET IT GOING? AND FIX BUG
|
||||
snprintf(state->diceType[0], sizeof(state->diceType[0]), "%s", "WAR!");
|
||||
snprintf(
|
||||
state->strings[0],
|
||||
sizeof(state->strings[0]),
|
||||
"%s at %s",
|
||||
state->diceType[0],
|
||||
state->rollTime[0]);
|
||||
uint8_t d1_i = rand() % COUNT_OF(deckOne);
|
||||
// INITIALIZE WITH PLACEHOLDERS TO AVOID MAYBE UNINITIALIZED ERROR
|
||||
for(uint8_t i = 0; i < COUNT_OF(deckOne); i++) {
|
||||
if(i < d1_i) {
|
||||
snprintf(deckTwo[i], 8, "%s", deckOne[i]);
|
||||
} else if(i > d1_i) {
|
||||
snprintf(deckTwo[i - 1], 8, "%s", deckOne[i]);
|
||||
}
|
||||
}
|
||||
uint8_t d2_i = rand() % COUNT_OF(deckTwo);
|
||||
if(d1_i > d2_i) {
|
||||
state->playerOneScore++;
|
||||
snprintf(
|
||||
state->strings[1],
|
||||
sizeof(state->strings[1]),
|
||||
"%s > %s",
|
||||
deckOne[d1_i],
|
||||
deckTwo[d2_i]);
|
||||
} else {
|
||||
state->playerTwoScore++;
|
||||
snprintf(
|
||||
state->strings[1],
|
||||
sizeof(state->strings[1]),
|
||||
"%s < %s",
|
||||
deckOne[d1_i],
|
||||
deckTwo[d2_i]);
|
||||
}
|
||||
} else if(state->diceSelect == 232) {
|
||||
const char* diceOne[] = {
|
||||
"You", "You choose", "Nobody", "Everyone", "Nose goes", "Player to your right"};
|
||||
const char* diceTwo[] = {
|
||||
"take a tiny toke",
|
||||
"just chill",
|
||||
"take 2 tokes",
|
||||
"take a huge hit",
|
||||
"bogart it",
|
||||
"take a puff"};
|
||||
const char* diceThree[] = {
|
||||
"while humming a tune",
|
||||
"with your eyes closed",
|
||||
"on your knees",
|
||||
"while holding your nose",
|
||||
"while spinning in a circle",
|
||||
"in slow motion"};
|
||||
const char* diceFour[] = {
|
||||
"twice",
|
||||
"then tell a joke",
|
||||
"then laugh as hard as you can",
|
||||
"with the player to your left",
|
||||
"then sing a song",
|
||||
"then do a dance"};
|
||||
state->diceRoll =
|
||||
((rand() % state->diceSelect) + 1); // JUST TO GET IT GOING? AND FIX BUG
|
||||
snprintf(state->diceType[0], sizeof(state->diceType[0]), "%s", "WEED!");
|
||||
snprintf(
|
||||
state->strings[0],
|
||||
sizeof(state->strings[0]),
|
||||
"%s at %s",
|
||||
state->diceType[0],
|
||||
state->rollTime[0]);
|
||||
uint8_t d1_i = rand() % COUNT_OF(diceOne);
|
||||
uint8_t d2_i = rand() % COUNT_OF(diceTwo);
|
||||
uint8_t d3_i = rand() % COUNT_OF(diceThree);
|
||||
uint8_t d4_i = rand() % COUNT_OF(diceFour);
|
||||
snprintf(state->strings[1], sizeof(state->strings[1]), "%s", diceOne[d1_i]);
|
||||
snprintf(state->strings[2], sizeof(state->strings[2]), "%s", diceTwo[d2_i]);
|
||||
snprintf(state->strings[3], sizeof(state->strings[3]), "%s", diceThree[d3_i]);
|
||||
snprintf(state->strings[4], sizeof(state->strings[4]), "%s", diceFour[d4_i]);
|
||||
} else {
|
||||
state->diceRoll = ((rand() % state->diceSelect) + 1);
|
||||
snprintf(
|
||||
state->diceType[0], sizeof(state->diceType[0]), "%s%d", "d", state->diceSelect);
|
||||
snprintf(
|
||||
state->strings[0],
|
||||
sizeof(state->strings[0]),
|
||||
"%d%s at %s",
|
||||
state->diceQty,
|
||||
state->diceType[0],
|
||||
state->rollTime[0]);
|
||||
if(state->diceQty == 1) {
|
||||
snprintf(state->strings[1], sizeof(state->strings[1]), "%d", state->diceRoll);
|
||||
} else if(state->diceQty == 2) {
|
||||
snprintf(
|
||||
state->strings[1],
|
||||
sizeof(state->strings[1]),
|
||||
"%d %d",
|
||||
state->diceRoll,
|
||||
((rand() % state->diceSelect) + 1));
|
||||
} else if(state->diceQty == 3) {
|
||||
snprintf(
|
||||
state->strings[1],
|
||||
sizeof(state->strings[1]),
|
||||
"%d %d %d",
|
||||
state->diceRoll,
|
||||
((rand() % state->diceSelect) + 1),
|
||||
((rand() % state->diceSelect) + 1));
|
||||
} else if(state->diceQty == 4) {
|
||||
snprintf(
|
||||
state->strings[1],
|
||||
sizeof(state->strings[1]),
|
||||
"%d %d %d %d",
|
||||
state->diceRoll,
|
||||
((rand() % state->diceSelect) + 1),
|
||||
((rand() % state->diceSelect) + 1),
|
||||
((rand() % state->diceSelect) + 1));
|
||||
} else if(state->diceQty == 5) {
|
||||
snprintf(
|
||||
state->strings[1],
|
||||
sizeof(state->strings[1]),
|
||||
"%d %d %d %d %d",
|
||||
state->diceRoll,
|
||||
((rand() % state->diceSelect) + 1),
|
||||
((rand() % state->diceSelect) + 1),
|
||||
((rand() % state->diceSelect) + 1),
|
||||
((rand() % state->diceSelect) + 1));
|
||||
} else if(state->diceQty == 6) {
|
||||
snprintf(
|
||||
state->strings[1],
|
||||
sizeof(state->strings[1]),
|
||||
"%d %d %d %d %d %d",
|
||||
state->diceRoll,
|
||||
((rand() % state->diceSelect) + 1),
|
||||
((rand() % state->diceSelect) + 1),
|
||||
((rand() % state->diceSelect) + 1),
|
||||
((rand() % state->diceSelect) + 1),
|
||||
((rand() % state->diceSelect) + 1));
|
||||
}
|
||||
}
|
||||
state->letsRoll = false;
|
||||
}
|
||||
furi_mutex_release(state->mutex);
|
||||
if(state->diceRoll != 0) {
|
||||
if(state->diceSelect == 232) {
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
canvas_draw_str_aligned(canvas, 64, 8, AlignCenter, AlignCenter, state->strings[0]);
|
||||
canvas_draw_str_aligned(canvas, 64, 18, AlignCenter, AlignCenter, state->strings[1]);
|
||||
canvas_draw_str_aligned(canvas, 64, 26, AlignCenter, AlignCenter, state->strings[2]);
|
||||
canvas_draw_str_aligned(canvas, 64, 34, AlignCenter, AlignCenter, state->strings[3]);
|
||||
canvas_draw_str_aligned(canvas, 64, 42, AlignCenter, AlignCenter, state->strings[4]);
|
||||
} else if(state->diceSelect == 228 || state->diceSelect == 229) {
|
||||
canvas_set_font(canvas, FontBatteryPercent);
|
||||
canvas_draw_str_aligned(canvas, 64, 20, AlignCenter, AlignCenter, state->strings[1]);
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
canvas_draw_str_aligned(canvas, 64, 8, AlignCenter, AlignCenter, state->strings[0]);
|
||||
} else {
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
canvas_draw_str_aligned(canvas, 64, 20, AlignCenter, AlignCenter, state->strings[1]);
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
canvas_draw_str_aligned(canvas, 64, 8, AlignCenter, AlignCenter, state->strings[0]);
|
||||
}
|
||||
if(state->diceSelect == 231 &&
|
||||
!(state->playerOneScore == 0 && state->playerTwoScore == 0)) {
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
snprintf(
|
||||
state->theScores[0],
|
||||
sizeof(state->theScores[0]),
|
||||
"%d %d",
|
||||
state->playerOneScore,
|
||||
state->playerTwoScore);
|
||||
canvas_draw_str_aligned(canvas, 64, 34, AlignCenter, AlignCenter, state->theScores[0]);
|
||||
}
|
||||
}
|
||||
if(state->diceSelect == 229 || state->diceSelect == 228) {
|
||||
elements_button_center(canvas, "Shake");
|
||||
} else if(state->diceSelect == 231) {
|
||||
elements_button_center(canvas, "Draw");
|
||||
} else {
|
||||
elements_button_center(canvas, "Roll");
|
||||
}
|
||||
if(state->diceSelect == 2) {
|
||||
elements_button_right(canvas, "d2");
|
||||
} else if(state->diceSelect == 3) {
|
||||
elements_button_right(canvas, "d3");
|
||||
} else if(state->diceSelect == 4) {
|
||||
elements_button_right(canvas, "d4");
|
||||
} else if(state->diceSelect == 6) {
|
||||
elements_button_right(canvas, "d6");
|
||||
} else if(state->diceSelect == 8) {
|
||||
elements_button_right(canvas, "d8");
|
||||
} else if(state->diceSelect == 10) {
|
||||
elements_button_right(canvas, "d10");
|
||||
} else if(state->diceSelect == 12) {
|
||||
elements_button_right(canvas, "d12");
|
||||
} else if(state->diceSelect == 20) {
|
||||
elements_button_right(canvas, "d20");
|
||||
} else if(state->diceSelect == 59) {
|
||||
elements_button_right(canvas, "d59");
|
||||
} else if(state->diceSelect == 69) {
|
||||
elements_button_right(canvas, "d69");
|
||||
} else if(state->diceSelect == 100) {
|
||||
elements_button_right(canvas, "d100");
|
||||
} else if(state->diceSelect == 229) {
|
||||
elements_button_right(canvas, "8BALL");
|
||||
} else if(state->diceSelect == 228) {
|
||||
elements_button_right(canvas, "DBALL");
|
||||
} else if(state->diceSelect == 230) {
|
||||
elements_button_right(canvas, "SEX");
|
||||
} else if(state->diceSelect == 231) {
|
||||
elements_button_right(canvas, "WAR");
|
||||
} else if(state->diceSelect == 232) {
|
||||
elements_button_right(canvas, "WEED");
|
||||
}
|
||||
}
|
||||
|
||||
static void dice_state_init(DiceState* const state) {
|
||||
memset(state, 0, sizeof(DiceState));
|
||||
furi_hal_rtc_get_datetime(&state->datetime);
|
||||
state->diceSelect = 20;
|
||||
state->diceQty = 1;
|
||||
state->diceRoll = 0;
|
||||
state->playerOneScore = 0;
|
||||
state->playerTwoScore = 0;
|
||||
state->letsRoll = false;
|
||||
}
|
||||
|
||||
static void dice_tick(void* ctx) {
|
||||
furi_assert(ctx);
|
||||
FuriMessageQueue* event_queue = ctx;
|
||||
PluginEvent event = {.type = EventTypeTick};
|
||||
// It's OK to lose this event if system overloaded
|
||||
furi_message_queue_put(event_queue, &event, 0);
|
||||
}
|
||||
|
||||
int32_t dice_app(void* p) {
|
||||
UNUSED(p);
|
||||
DiceState* plugin_state = malloc(sizeof(DiceState));
|
||||
dice_state_init(plugin_state);
|
||||
plugin_state->event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent));
|
||||
if(plugin_state->event_queue == NULL) {
|
||||
FURI_LOG_E(TAG, "cannot create event queue\n");
|
||||
free(plugin_state);
|
||||
return 255;
|
||||
}
|
||||
|
||||
plugin_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
||||
if(plugin_state->mutex == NULL) {
|
||||
FURI_LOG_E(TAG, "cannot create mutex\n");
|
||||
furi_message_queue_free(plugin_state->event_queue);
|
||||
free(plugin_state);
|
||||
return 255;
|
||||
}
|
||||
|
||||
FuriTimer* timer =
|
||||
furi_timer_alloc(dice_tick, FuriTimerTypePeriodic, plugin_state->event_queue);
|
||||
if(timer == NULL) {
|
||||
FURI_LOG_E(TAG, "cannot create timer\n");
|
||||
furi_mutex_free(plugin_state->mutex);
|
||||
furi_message_queue_free(plugin_state->event_queue);
|
||||
free(plugin_state);
|
||||
return 255;
|
||||
}
|
||||
|
||||
ViewPort* view_port = view_port_alloc();
|
||||
view_port_draw_callback_set(view_port, dice_render_callback, plugin_state);
|
||||
view_port_input_callback_set(view_port, dice_input_callback, plugin_state->event_queue);
|
||||
|
||||
Gui* gui = furi_record_open(RECORD_GUI);
|
||||
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
|
||||
furi_timer_start(timer, furi_kernel_get_tick_frequency());
|
||||
|
||||
// Main loop
|
||||
PluginEvent event;
|
||||
for(bool processing = true; processing;) {
|
||||
FuriStatus event_status = furi_message_queue_get(plugin_state->event_queue, &event, 100);
|
||||
if(event_status == FuriStatusOk) {
|
||||
if(event.type == EventTypeKey) {
|
||||
if(event.input.type == InputTypeShort || event.input.type == InputTypeRepeat) {
|
||||
switch(event.input.key) {
|
||||
case InputKeyUp:
|
||||
break;
|
||||
case InputKeyDown:
|
||||
break;
|
||||
case InputKeyRight:
|
||||
if(plugin_state->diceSelect == 2) {
|
||||
plugin_state->diceSelect = 3;
|
||||
} else if(plugin_state->diceSelect == 3) {
|
||||
plugin_state->diceSelect = 4;
|
||||
} else if(plugin_state->diceSelect == 4) {
|
||||
plugin_state->diceSelect = 6;
|
||||
} else if(plugin_state->diceSelect == 6) {
|
||||
plugin_state->diceSelect = 8;
|
||||
} else if(plugin_state->diceSelect == 8) {
|
||||
plugin_state->diceSelect = 10;
|
||||
} else if(plugin_state->diceSelect == 10) {
|
||||
plugin_state->diceSelect = 12;
|
||||
} else if(plugin_state->diceSelect == 12) {
|
||||
plugin_state->diceSelect = 20;
|
||||
} else if(plugin_state->diceSelect == 20) {
|
||||
plugin_state->diceSelect = 100;
|
||||
} else if(plugin_state->diceSelect == 100) {
|
||||
plugin_state->diceSelect = 230;
|
||||
} else if(plugin_state->diceSelect == 230) {
|
||||
plugin_state->playerOneScore = 0;
|
||||
plugin_state->playerTwoScore = 0;
|
||||
plugin_state->diceSelect = 231;
|
||||
} else if(plugin_state->diceSelect == 231) {
|
||||
plugin_state->diceSelect = 229;
|
||||
} else if(plugin_state->diceSelect == 229) {
|
||||
plugin_state->diceSelect = 228;
|
||||
} else if(plugin_state->diceSelect == 228) {
|
||||
plugin_state->diceSelect = 232;
|
||||
} else if(plugin_state->diceSelect == 232) {
|
||||
plugin_state->diceSelect = 59;
|
||||
} else if(plugin_state->diceSelect == 59) {
|
||||
plugin_state->diceSelect = 69;
|
||||
} else {
|
||||
plugin_state->diceSelect = 2;
|
||||
}
|
||||
break;
|
||||
case InputKeyLeft:
|
||||
if(plugin_state->diceQty <= 5) {
|
||||
plugin_state->diceQty = plugin_state->diceQty + 1;
|
||||
} else {
|
||||
plugin_state->diceQty = 1;
|
||||
}
|
||||
break;
|
||||
case InputKeyOk:
|
||||
plugin_state->letsRoll = true;
|
||||
break;
|
||||
case InputKeyBack:
|
||||
processing = false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if(event.type == EventTypeTick) {
|
||||
// furi_hal_rtc_get_datetime(&plugin_state->datetime);
|
||||
}
|
||||
view_port_update(view_port);
|
||||
furi_mutex_release(plugin_state->mutex);
|
||||
} else {
|
||||
// FURI_LOG_D(TAG, "osMessageQueue: event timeout");
|
||||
}
|
||||
}
|
||||
// Cleanup
|
||||
furi_timer_free(timer);
|
||||
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(plugin_state->event_queue);
|
||||
furi_mutex_free(plugin_state->mutex);
|
||||
free(plugin_state);
|
||||
return 0;
|
||||
}
|
||||
|
Before Width: | Height: | Size: 207 B |
@@ -1,14 +0,0 @@
|
||||
App(
|
||||
appid="music_beeper",
|
||||
name="Music Beeper",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="music_beeper_app",
|
||||
requires=[
|
||||
"gui",
|
||||
"dialogs",
|
||||
],
|
||||
stack_size=2 * 1024,
|
||||
fap_icon="music_10px.png",
|
||||
fap_icon_assets="icons",
|
||||
fap_category="Media",
|
||||
)
|
||||
|
Before Width: | Height: | Size: 142 B |
@@ -1,368 +0,0 @@
|
||||
#include "music_beeper_worker.h"
|
||||
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
|
||||
#include "music_beeper_icons.h"
|
||||
#include <assets_icons.h>
|
||||
#include <gui/gui.h>
|
||||
#include <dialogs/dialogs.h>
|
||||
#include <storage/storage.h>
|
||||
|
||||
#define TAG "MusicBeeper"
|
||||
|
||||
#define MUSIC_BEEPER_APP_PATH_FOLDER EXT_PATH("music_player")
|
||||
#define MUSIC_BEEPER_APP_EXTENSION "*"
|
||||
|
||||
#define MUSIC_BEEPER_SEMITONE_HISTORY_SIZE 4
|
||||
|
||||
typedef struct {
|
||||
uint8_t semitone_history[MUSIC_BEEPER_SEMITONE_HISTORY_SIZE];
|
||||
uint8_t duration_history[MUSIC_BEEPER_SEMITONE_HISTORY_SIZE];
|
||||
|
||||
uint8_t volume;
|
||||
uint8_t semitone;
|
||||
uint8_t dots;
|
||||
uint8_t duration;
|
||||
float position;
|
||||
} MusicBeeperModel;
|
||||
|
||||
typedef struct {
|
||||
MusicBeeperModel* model;
|
||||
FuriMutex** model_mutex;
|
||||
|
||||
FuriMessageQueue* input_queue;
|
||||
|
||||
ViewPort* view_port;
|
||||
Gui* gui;
|
||||
|
||||
MusicBeeperWorker* worker;
|
||||
} MusicBeeper;
|
||||
|
||||
static const float MUSIC_BEEPER_VOLUMES[] = {0, .25, .5, .75, 1};
|
||||
|
||||
static const char* semitone_to_note(int8_t semitone) {
|
||||
switch(semitone) {
|
||||
case 0:
|
||||
return "C";
|
||||
case 1:
|
||||
return "C#";
|
||||
case 2:
|
||||
return "D";
|
||||
case 3:
|
||||
return "D#";
|
||||
case 4:
|
||||
return "E";
|
||||
case 5:
|
||||
return "F";
|
||||
case 6:
|
||||
return "F#";
|
||||
case 7:
|
||||
return "G";
|
||||
case 8:
|
||||
return "G#";
|
||||
case 9:
|
||||
return "A";
|
||||
case 10:
|
||||
return "A#";
|
||||
case 11:
|
||||
return "B";
|
||||
default:
|
||||
return "--";
|
||||
}
|
||||
}
|
||||
|
||||
static bool is_white_note(uint8_t semitone, uint8_t id) {
|
||||
switch(semitone) {
|
||||
case 0:
|
||||
if(id == 0) return true;
|
||||
break;
|
||||
case 2:
|
||||
if(id == 1) return true;
|
||||
break;
|
||||
case 4:
|
||||
if(id == 2) return true;
|
||||
break;
|
||||
case 5:
|
||||
if(id == 3) return true;
|
||||
break;
|
||||
case 7:
|
||||
if(id == 4) return true;
|
||||
break;
|
||||
case 9:
|
||||
if(id == 5) return true;
|
||||
break;
|
||||
case 11:
|
||||
if(id == 6) return true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool is_black_note(uint8_t semitone, uint8_t id) {
|
||||
switch(semitone) {
|
||||
case 1:
|
||||
if(id == 0) return true;
|
||||
break;
|
||||
case 3:
|
||||
if(id == 1) return true;
|
||||
break;
|
||||
case 6:
|
||||
if(id == 3) return true;
|
||||
break;
|
||||
case 8:
|
||||
if(id == 4) return true;
|
||||
break;
|
||||
case 10:
|
||||
if(id == 5) return true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void render_callback(Canvas* canvas, void* ctx) {
|
||||
MusicBeeper* music_beeper = ctx;
|
||||
furi_check(furi_mutex_acquire(music_beeper->model_mutex, FuriWaitForever) == FuriStatusOk);
|
||||
|
||||
canvas_clear(canvas);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
canvas_draw_str(canvas, 0, 12, "MusicBeeper");
|
||||
|
||||
uint8_t x_pos = 0;
|
||||
uint8_t y_pos = 24;
|
||||
const uint8_t white_w = 10;
|
||||
const uint8_t white_h = 40;
|
||||
|
||||
const int8_t black_x = 6;
|
||||
const int8_t black_y = -5;
|
||||
const uint8_t black_w = 8;
|
||||
const uint8_t black_h = 32;
|
||||
|
||||
// white keys
|
||||
for(size_t i = 0; i < 7; i++) {
|
||||
if(is_white_note(music_beeper->model->semitone, i)) {
|
||||
canvas_draw_box(canvas, x_pos + white_w * i, y_pos, white_w + 1, white_h);
|
||||
} else {
|
||||
canvas_draw_frame(canvas, x_pos + white_w * i, y_pos, white_w + 1, white_h);
|
||||
}
|
||||
}
|
||||
|
||||
// black keys
|
||||
for(size_t i = 0; i < 7; i++) {
|
||||
if(i != 2 && i != 6) {
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
canvas_draw_box(
|
||||
canvas, x_pos + white_w * i + black_x, y_pos + black_y, black_w + 1, black_h);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
if(is_black_note(music_beeper->model->semitone, i)) {
|
||||
canvas_draw_box(
|
||||
canvas, x_pos + white_w * i + black_x, y_pos + black_y, black_w + 1, black_h);
|
||||
} else {
|
||||
canvas_draw_frame(
|
||||
canvas, x_pos + white_w * i + black_x, y_pos + black_y, black_w + 1, black_h);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// volume view_port
|
||||
x_pos = 124;
|
||||
y_pos = 0;
|
||||
const uint8_t volume_h =
|
||||
(64 / (COUNT_OF(MUSIC_BEEPER_VOLUMES) - 1)) * music_beeper->model->volume;
|
||||
canvas_draw_frame(canvas, x_pos, y_pos, 4, 64);
|
||||
canvas_draw_box(canvas, x_pos, y_pos + (64 - volume_h), 4, volume_h);
|
||||
|
||||
// note stack view_port
|
||||
x_pos = 73;
|
||||
y_pos = 0;
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
canvas_draw_frame(canvas, x_pos, y_pos, 49, 64);
|
||||
canvas_draw_line(canvas, x_pos + 28, 0, x_pos + 28, 64);
|
||||
|
||||
char duration_text[16];
|
||||
for(uint8_t i = 0; i < MUSIC_BEEPER_SEMITONE_HISTORY_SIZE; i++) {
|
||||
if(music_beeper->model->duration_history[i] == 0xFF) {
|
||||
snprintf(duration_text, 15, "--");
|
||||
} else {
|
||||
snprintf(duration_text, 15, "%d", music_beeper->model->duration_history[i]);
|
||||
}
|
||||
|
||||
if(i == 0) {
|
||||
canvas_draw_box(canvas, x_pos, y_pos + 48, 49, 16);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
} else {
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
}
|
||||
canvas_draw_str(
|
||||
canvas,
|
||||
x_pos + 4,
|
||||
64 - 16 * i - 3,
|
||||
semitone_to_note(music_beeper->model->semitone_history[i]));
|
||||
canvas_draw_str(canvas, x_pos + 31, 64 - 16 * i - 3, duration_text);
|
||||
canvas_draw_line(canvas, x_pos, 64 - 16 * i, x_pos + 48, 64 - 16 * i);
|
||||
}
|
||||
|
||||
furi_mutex_release(music_beeper->model_mutex);
|
||||
}
|
||||
|
||||
static void input_callback(InputEvent* input_event, void* ctx) {
|
||||
MusicBeeper* music_beeper = ctx;
|
||||
if(input_event->type == InputTypeShort) {
|
||||
furi_message_queue_put(music_beeper->input_queue, input_event, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static void music_beeper_worker_callback(
|
||||
uint8_t semitone,
|
||||
uint8_t dots,
|
||||
uint8_t duration,
|
||||
float position,
|
||||
void* context) {
|
||||
MusicBeeper* music_beeper = context;
|
||||
furi_check(furi_mutex_acquire(music_beeper->model_mutex, FuriWaitForever) == FuriStatusOk);
|
||||
|
||||
for(size_t i = 0; i < MUSIC_BEEPER_SEMITONE_HISTORY_SIZE - 1; i++) {
|
||||
size_t r = MUSIC_BEEPER_SEMITONE_HISTORY_SIZE - 1 - i;
|
||||
music_beeper->model->duration_history[r] = music_beeper->model->duration_history[r - 1];
|
||||
music_beeper->model->semitone_history[r] = music_beeper->model->semitone_history[r - 1];
|
||||
}
|
||||
|
||||
semitone = (semitone == 0xFF) ? 0xFF : semitone % 12;
|
||||
|
||||
music_beeper->model->semitone = semitone;
|
||||
music_beeper->model->dots = dots;
|
||||
music_beeper->model->duration = duration;
|
||||
music_beeper->model->position = position;
|
||||
|
||||
music_beeper->model->semitone_history[0] = semitone;
|
||||
music_beeper->model->duration_history[0] = duration;
|
||||
|
||||
furi_mutex_release(music_beeper->model_mutex);
|
||||
view_port_update(music_beeper->view_port);
|
||||
}
|
||||
|
||||
void music_beeper_clear(MusicBeeper* instance) {
|
||||
memset(instance->model->duration_history, 0xff, MUSIC_BEEPER_SEMITONE_HISTORY_SIZE);
|
||||
memset(instance->model->semitone_history, 0xff, MUSIC_BEEPER_SEMITONE_HISTORY_SIZE);
|
||||
music_beeper_worker_clear(instance->worker);
|
||||
}
|
||||
|
||||
MusicBeeper* music_beeper_alloc() {
|
||||
MusicBeeper* instance = malloc(sizeof(MusicBeeper));
|
||||
|
||||
instance->model = malloc(sizeof(MusicBeeperModel));
|
||||
instance->model->volume = 4;
|
||||
|
||||
instance->model_mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
||||
|
||||
instance->input_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
|
||||
|
||||
instance->worker = music_beeper_worker_alloc();
|
||||
music_beeper_worker_set_volume(
|
||||
instance->worker, MUSIC_BEEPER_VOLUMES[instance->model->volume]);
|
||||
music_beeper_worker_set_callback(instance->worker, music_beeper_worker_callback, instance);
|
||||
|
||||
music_beeper_clear(instance);
|
||||
|
||||
instance->view_port = view_port_alloc();
|
||||
view_port_draw_callback_set(instance->view_port, render_callback, instance);
|
||||
view_port_input_callback_set(instance->view_port, input_callback, instance);
|
||||
|
||||
// Open GUI and register view_port
|
||||
instance->gui = furi_record_open(RECORD_GUI);
|
||||
gui_add_view_port(instance->gui, instance->view_port, GuiLayerFullscreen);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void music_beeper_free(MusicBeeper* instance) {
|
||||
gui_remove_view_port(instance->gui, instance->view_port);
|
||||
furi_record_close(RECORD_GUI);
|
||||
view_port_free(instance->view_port);
|
||||
|
||||
music_beeper_worker_free(instance->worker);
|
||||
|
||||
furi_message_queue_free(instance->input_queue);
|
||||
|
||||
furi_mutex_free(instance->model_mutex);
|
||||
|
||||
free(instance->model);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
int32_t music_beeper_app(void* p) {
|
||||
MusicBeeper* music_beeper = music_beeper_alloc();
|
||||
|
||||
FuriString* file_path;
|
||||
file_path = furi_string_alloc();
|
||||
|
||||
do {
|
||||
if(p && strlen(p)) {
|
||||
furi_string_set(file_path, (const char*)p);
|
||||
} else {
|
||||
furi_string_set(file_path, MUSIC_BEEPER_APP_PATH_FOLDER);
|
||||
|
||||
DialogsFileBrowserOptions browser_options;
|
||||
dialog_file_browser_set_basic_options(
|
||||
&browser_options, MUSIC_BEEPER_APP_EXTENSION, &I_music_10px);
|
||||
browser_options.base_path = MUSIC_BEEPER_APP_PATH_FOLDER;
|
||||
|
||||
DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
|
||||
bool res = dialog_file_browser_show(dialogs, file_path, file_path, &browser_options);
|
||||
|
||||
furi_record_close(RECORD_DIALOGS);
|
||||
if(!res) {
|
||||
FURI_LOG_E(TAG, "No file selected");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!music_beeper_worker_load(music_beeper->worker, furi_string_get_cstr(file_path))) {
|
||||
FURI_LOG_E(TAG, "Unable to load file");
|
||||
break;
|
||||
}
|
||||
|
||||
music_beeper_worker_start(music_beeper->worker);
|
||||
|
||||
InputEvent input;
|
||||
while(furi_message_queue_get(music_beeper->input_queue, &input, FuriWaitForever) ==
|
||||
FuriStatusOk) {
|
||||
furi_check(
|
||||
furi_mutex_acquire(music_beeper->model_mutex, FuriWaitForever) == FuriStatusOk);
|
||||
|
||||
if(input.key == InputKeyBack) {
|
||||
furi_mutex_release(music_beeper->model_mutex);
|
||||
break;
|
||||
} else if(input.key == InputKeyUp) {
|
||||
if(music_beeper->model->volume < COUNT_OF(MUSIC_BEEPER_VOLUMES) - 1)
|
||||
music_beeper->model->volume++;
|
||||
music_beeper_worker_set_volume(
|
||||
music_beeper->worker, MUSIC_BEEPER_VOLUMES[music_beeper->model->volume]);
|
||||
} else if(input.key == InputKeyDown) {
|
||||
if(music_beeper->model->volume > 0) music_beeper->model->volume--;
|
||||
music_beeper_worker_set_volume(
|
||||
music_beeper->worker, MUSIC_BEEPER_VOLUMES[music_beeper->model->volume]);
|
||||
}
|
||||
|
||||
furi_mutex_release(music_beeper->model_mutex);
|
||||
view_port_update(music_beeper->view_port);
|
||||
}
|
||||
|
||||
music_beeper_worker_stop(music_beeper->worker);
|
||||
if(p && strlen(p)) break; // Exit instead of going to browser if launched with arg
|
||||
music_beeper_clear(music_beeper);
|
||||
} while(1);
|
||||
|
||||
furi_string_free(file_path);
|
||||
music_beeper_free(music_beeper);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
#include <furi.h>
|
||||
#include <cli/cli.h>
|
||||
#include <storage/storage.h>
|
||||
#include "music_beeper_worker.h"
|
||||
|
||||
static void music_beeper_cli(Cli* cli, FuriString* args, void* context) {
|
||||
UNUSED(context);
|
||||
MusicBeeperWorker* music_beeper_worker = music_beeper_worker_alloc();
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
|
||||
do {
|
||||
if(storage_common_stat(storage, furi_string_get_cstr(args), NULL) == FSE_OK) {
|
||||
if(!music_beeper_worker_load(music_beeper_worker, furi_string_get_cstr(args))) {
|
||||
printf("Failed to open file %s\r\n", furi_string_get_cstr(args));
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if(!music_beeper_worker_load_rtttl_from_string(
|
||||
music_beeper_worker, furi_string_get_cstr(args))) {
|
||||
printf("Argument is not a file or RTTTL\r\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
printf("Press CTRL+C to stop\r\n");
|
||||
music_beeper_worker_set_volume(music_beeper_worker, 1.0f);
|
||||
music_beeper_worker_start(music_beeper_worker);
|
||||
while(!cli_cmd_interrupt_received(cli)) {
|
||||
furi_delay_ms(50);
|
||||
}
|
||||
music_beeper_worker_stop(music_beeper_worker);
|
||||
} while(0);
|
||||
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
music_beeper_worker_free(music_beeper_worker);
|
||||
}
|
||||
|
||||
void music_beeper_on_system_start() {
|
||||
#ifdef SRV_CLI
|
||||
Cli* cli = furi_record_open(RECORD_CLI);
|
||||
|
||||
cli_add_command(cli, "music_beeper", CliCommandFlagDefault, music_beeper_cli, NULL);
|
||||
|
||||
furi_record_close(RECORD_CLI);
|
||||
#else
|
||||
UNUSED(music_beeper_cli);
|
||||
#endif
|
||||
}
|
||||
@@ -1,510 +0,0 @@
|
||||
#include "music_beeper_worker.h"
|
||||
|
||||
#include <furi_hal.h>
|
||||
#include <furi.h>
|
||||
|
||||
#include <storage/storage.h>
|
||||
#include <lib/flipper_format/flipper_format.h>
|
||||
|
||||
#include <m-array.h>
|
||||
|
||||
#define TAG "MusicBeeperWorker"
|
||||
|
||||
#define MUSIC_BEEPER_FILETYPE "Flipper Music Format"
|
||||
#define MUSIC_BEEPER_VERSION 0
|
||||
|
||||
#define SEMITONE_PAUSE 0xFF
|
||||
|
||||
#define NOTE_C4 261.63f
|
||||
#define NOTE_C4_SEMITONE (4.0f * 12.0f)
|
||||
#define TWO_POW_TWELTH_ROOT 1.059463094359f
|
||||
|
||||
typedef struct {
|
||||
uint8_t semitone;
|
||||
uint8_t duration;
|
||||
uint8_t dots;
|
||||
} NoteBlock;
|
||||
|
||||
ARRAY_DEF(NoteBlockArray, NoteBlock, M_POD_OPLIST);
|
||||
|
||||
struct MusicBeeperWorker {
|
||||
FuriThread* thread;
|
||||
bool should_work;
|
||||
|
||||
MusicBeeperWorkerCallback callback;
|
||||
void* callback_context;
|
||||
|
||||
float volume;
|
||||
uint32_t bpm;
|
||||
uint32_t duration;
|
||||
uint32_t octave;
|
||||
NoteBlockArray_t notes;
|
||||
};
|
||||
|
||||
static int32_t music_beeper_worker_thread_callback(void* context) {
|
||||
furi_assert(context);
|
||||
MusicBeeperWorker* instance = context;
|
||||
|
||||
NoteBlockArray_it_t it;
|
||||
NoteBlockArray_it(it, instance->notes);
|
||||
if(furi_hal_speaker_acquire(1000)) {
|
||||
while(instance->should_work) {
|
||||
if(NoteBlockArray_end_p(it)) {
|
||||
NoteBlockArray_it(it, instance->notes);
|
||||
furi_delay_ms(10);
|
||||
} else {
|
||||
NoteBlock* note_block = NoteBlockArray_ref(it);
|
||||
|
||||
float note_from_a4 = (float)note_block->semitone - NOTE_C4_SEMITONE;
|
||||
float frequency = NOTE_C4 * powf(TWO_POW_TWELTH_ROOT, note_from_a4);
|
||||
float duration = 60.0 * furi_kernel_get_tick_frequency() * 4 / instance->bpm /
|
||||
note_block->duration;
|
||||
uint32_t dots = note_block->dots;
|
||||
while(dots > 0) {
|
||||
duration += duration / 2;
|
||||
dots--;
|
||||
}
|
||||
uint32_t next_tick = furi_get_tick() + duration;
|
||||
float volume = instance->volume;
|
||||
|
||||
if(instance->callback) {
|
||||
instance->callback(
|
||||
note_block->semitone,
|
||||
note_block->dots,
|
||||
note_block->duration,
|
||||
0.0,
|
||||
instance->callback_context);
|
||||
}
|
||||
|
||||
furi_hal_speaker_stop();
|
||||
furi_hal_speaker_start(frequency, volume);
|
||||
while(instance->should_work && furi_get_tick() < next_tick) {
|
||||
volume *= 1;
|
||||
furi_hal_speaker_set_volume(volume);
|
||||
furi_delay_ms(2);
|
||||
}
|
||||
NoteBlockArray_next(it);
|
||||
}
|
||||
}
|
||||
|
||||
furi_hal_speaker_stop();
|
||||
furi_hal_speaker_release();
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Speaker system is busy with another process.");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
MusicBeeperWorker* music_beeper_worker_alloc() {
|
||||
MusicBeeperWorker* instance = malloc(sizeof(MusicBeeperWorker));
|
||||
|
||||
NoteBlockArray_init(instance->notes);
|
||||
|
||||
instance->thread = furi_thread_alloc();
|
||||
furi_thread_set_name(instance->thread, "MusicBeeperWorker");
|
||||
furi_thread_set_stack_size(instance->thread, 1024);
|
||||
furi_thread_set_context(instance->thread, instance);
|
||||
furi_thread_set_callback(instance->thread, music_beeper_worker_thread_callback);
|
||||
|
||||
instance->volume = 1.0f;
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void music_beeper_worker_clear(MusicBeeperWorker* instance) {
|
||||
NoteBlockArray_reset(instance->notes);
|
||||
}
|
||||
|
||||
void music_beeper_worker_free(MusicBeeperWorker* instance) {
|
||||
furi_assert(instance);
|
||||
furi_thread_free(instance->thread);
|
||||
NoteBlockArray_clear(instance->notes);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
static bool is_digit(const char c) {
|
||||
return isdigit(c) != 0;
|
||||
}
|
||||
|
||||
static bool is_letter(const char c) {
|
||||
return islower(c) != 0 || isupper(c) != 0;
|
||||
}
|
||||
|
||||
static bool is_space(const char c) {
|
||||
return c == ' ' || c == '\t';
|
||||
}
|
||||
|
||||
static size_t extract_number(const char* string, uint32_t* number) {
|
||||
size_t ret = 0;
|
||||
*number = 0;
|
||||
while(is_digit(*string)) {
|
||||
*number *= 10;
|
||||
*number += (*string - '0');
|
||||
string++;
|
||||
ret++;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static size_t extract_dots(const char* string, uint32_t* number) {
|
||||
size_t ret = 0;
|
||||
*number = 0;
|
||||
while(*string == '.') {
|
||||
*number += 1;
|
||||
string++;
|
||||
ret++;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static size_t extract_char(const char* string, char* symbol) {
|
||||
if(is_letter(*string)) {
|
||||
*symbol = *string;
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static size_t extract_sharp(const char* string, char* symbol) {
|
||||
if(*string == '#' || *string == '_') {
|
||||
*symbol = '#';
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static size_t skip_till(const char* string, const char symbol) {
|
||||
size_t ret = 0;
|
||||
while(*string != '\0' && *string != symbol) {
|
||||
string++;
|
||||
ret++;
|
||||
}
|
||||
if(*string != symbol) {
|
||||
ret = 0;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool music_beeper_worker_add_note(
|
||||
MusicBeeperWorker* instance,
|
||||
uint8_t semitone,
|
||||
uint8_t duration,
|
||||
uint8_t dots) {
|
||||
NoteBlock note_block;
|
||||
|
||||
note_block.semitone = semitone;
|
||||
note_block.duration = duration;
|
||||
note_block.dots = dots;
|
||||
|
||||
NoteBlockArray_push_back(instance->notes, note_block);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int8_t note_to_semitone(const char note) {
|
||||
switch(note) {
|
||||
case 'C':
|
||||
return 0;
|
||||
// C#
|
||||
case 'D':
|
||||
return 2;
|
||||
// D#
|
||||
case 'E':
|
||||
return 4;
|
||||
case 'F':
|
||||
return 5;
|
||||
// F#
|
||||
case 'G':
|
||||
return 7;
|
||||
// G#
|
||||
case 'A':
|
||||
return 9;
|
||||
// A#
|
||||
case 'B':
|
||||
return 11;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static bool music_beeper_worker_parse_notes(MusicBeeperWorker* instance, const char* string) {
|
||||
const char* cursor = string;
|
||||
bool result = true;
|
||||
|
||||
while(*cursor != '\0') {
|
||||
if(!is_space(*cursor)) {
|
||||
uint32_t duration = 0;
|
||||
char note_char = '\0';
|
||||
char sharp_char = '\0';
|
||||
uint32_t octave = 0;
|
||||
uint32_t dots = 0;
|
||||
|
||||
// Parsing
|
||||
cursor += extract_number(cursor, &duration);
|
||||
cursor += extract_char(cursor, ¬e_char);
|
||||
cursor += extract_sharp(cursor, &sharp_char);
|
||||
cursor += extract_number(cursor, &octave);
|
||||
cursor += extract_dots(cursor, &dots);
|
||||
|
||||
// Post processing
|
||||
note_char = toupper(note_char);
|
||||
if(!duration) {
|
||||
duration = instance->duration;
|
||||
}
|
||||
if(!octave) {
|
||||
octave = instance->octave;
|
||||
}
|
||||
|
||||
// Validation
|
||||
bool is_valid = true;
|
||||
is_valid &= (duration >= 1 && duration <= 128);
|
||||
is_valid &= ((note_char >= 'A' && note_char <= 'G') || note_char == 'P');
|
||||
is_valid &= (sharp_char == '#' || sharp_char == '\0');
|
||||
is_valid &= (octave <= 16);
|
||||
is_valid &= (dots <= 16);
|
||||
if(!is_valid) {
|
||||
FURI_LOG_E(
|
||||
TAG,
|
||||
"Invalid note: %lu%c%c%lu.%lu",
|
||||
duration,
|
||||
note_char == '\0' ? '_' : note_char,
|
||||
sharp_char == '\0' ? '_' : sharp_char,
|
||||
octave,
|
||||
dots);
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// Note to semitones
|
||||
uint8_t semitone = 0;
|
||||
if(note_char == 'P') {
|
||||
semitone = SEMITONE_PAUSE;
|
||||
} else {
|
||||
semitone += octave * 12;
|
||||
semitone += note_to_semitone(note_char);
|
||||
semitone += sharp_char == '#' ? 1 : 0;
|
||||
}
|
||||
|
||||
if(music_beeper_worker_add_note(instance, semitone, duration, dots)) {
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
"Added note: %c%c%lu.%lu = %u %lu",
|
||||
note_char == '\0' ? '_' : note_char,
|
||||
sharp_char == '\0' ? '_' : sharp_char,
|
||||
octave,
|
||||
dots,
|
||||
semitone,
|
||||
duration);
|
||||
} else {
|
||||
FURI_LOG_E(
|
||||
TAG,
|
||||
"Invalid note: %c%c%lu.%lu = %u %lu",
|
||||
note_char == '\0' ? '_' : note_char,
|
||||
sharp_char == '\0' ? '_' : sharp_char,
|
||||
octave,
|
||||
dots,
|
||||
semitone,
|
||||
duration);
|
||||
}
|
||||
cursor += skip_till(cursor, ',');
|
||||
}
|
||||
|
||||
if(*cursor != '\0') cursor++;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool music_beeper_worker_load(MusicBeeperWorker* instance, const char* file_path) {
|
||||
furi_assert(instance);
|
||||
furi_assert(file_path);
|
||||
|
||||
bool ret = false;
|
||||
if(strcasestr(file_path, ".fmf")) {
|
||||
ret = music_beeper_worker_load_fmf_from_file(instance, file_path);
|
||||
} else {
|
||||
ret = music_beeper_worker_load_rtttl_from_file(instance, file_path);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool music_beeper_worker_load_fmf_from_file(MusicBeeperWorker* instance, const char* file_path) {
|
||||
furi_assert(instance);
|
||||
furi_assert(file_path);
|
||||
|
||||
bool result = false;
|
||||
FuriString* temp_str;
|
||||
temp_str = furi_string_alloc();
|
||||
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
FlipperFormat* file = flipper_format_file_alloc(storage);
|
||||
|
||||
do {
|
||||
if(!flipper_format_file_open_existing(file, file_path)) break;
|
||||
|
||||
uint32_t version = 0;
|
||||
if(!flipper_format_read_header(file, temp_str, &version)) break;
|
||||
if(furi_string_cmp_str(temp_str, MUSIC_BEEPER_FILETYPE) ||
|
||||
(version != MUSIC_BEEPER_VERSION)) {
|
||||
FURI_LOG_E(TAG, "Incorrect file format or version");
|
||||
break;
|
||||
}
|
||||
|
||||
if(!flipper_format_read_uint32(file, "BPM", &instance->bpm, 1)) {
|
||||
FURI_LOG_E(TAG, "BPM is missing");
|
||||
break;
|
||||
}
|
||||
if(!flipper_format_read_uint32(file, "Duration", &instance->duration, 1)) {
|
||||
FURI_LOG_E(TAG, "Duration is missing");
|
||||
break;
|
||||
}
|
||||
if(!flipper_format_read_uint32(file, "Octave", &instance->octave, 1)) {
|
||||
FURI_LOG_E(TAG, "Octave is missing");
|
||||
break;
|
||||
}
|
||||
|
||||
if(!flipper_format_read_string(file, "Notes", temp_str)) {
|
||||
FURI_LOG_E(TAG, "Notes is missing");
|
||||
break;
|
||||
}
|
||||
|
||||
if(!music_beeper_worker_parse_notes(instance, furi_string_get_cstr(temp_str))) {
|
||||
break;
|
||||
}
|
||||
|
||||
result = true;
|
||||
} while(false);
|
||||
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
flipper_format_free(file);
|
||||
furi_string_free(temp_str);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool music_beeper_worker_load_rtttl_from_file(MusicBeeperWorker* instance, const char* file_path) {
|
||||
furi_assert(instance);
|
||||
furi_assert(file_path);
|
||||
|
||||
bool result = false;
|
||||
FuriString* content;
|
||||
content = furi_string_alloc();
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
File* file = storage_file_alloc(storage);
|
||||
|
||||
do {
|
||||
if(!storage_file_open(file, file_path, FSAM_READ, FSOM_OPEN_EXISTING)) {
|
||||
FURI_LOG_E(TAG, "Unable to open file");
|
||||
break;
|
||||
};
|
||||
|
||||
uint16_t ret = 0;
|
||||
do {
|
||||
uint8_t buffer[65] = {0};
|
||||
ret = storage_file_read(file, buffer, sizeof(buffer) - 1);
|
||||
for(size_t i = 0; i < ret; i++) {
|
||||
furi_string_push_back(content, buffer[i]);
|
||||
}
|
||||
} while(ret > 0);
|
||||
|
||||
furi_string_trim(content);
|
||||
if(!furi_string_size(content)) {
|
||||
FURI_LOG_E(TAG, "Empty file");
|
||||
break;
|
||||
}
|
||||
|
||||
if(!music_beeper_worker_load_rtttl_from_string(instance, furi_string_get_cstr(content))) {
|
||||
FURI_LOG_E(TAG, "Invalid file content");
|
||||
break;
|
||||
}
|
||||
|
||||
result = true;
|
||||
} while(0);
|
||||
|
||||
storage_file_free(file);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
furi_string_free(content);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool music_beeper_worker_load_rtttl_from_string(MusicBeeperWorker* instance, const char* string) {
|
||||
furi_assert(instance);
|
||||
|
||||
const char* cursor = string;
|
||||
|
||||
// Skip name
|
||||
cursor += skip_till(cursor, ':');
|
||||
if(*cursor != ':') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Duration
|
||||
cursor += skip_till(cursor, '=');
|
||||
if(*cursor != '=') {
|
||||
return false;
|
||||
}
|
||||
cursor++;
|
||||
cursor += extract_number(cursor, &instance->duration);
|
||||
|
||||
// Octave
|
||||
cursor += skip_till(cursor, '=');
|
||||
if(*cursor != '=') {
|
||||
return false;
|
||||
}
|
||||
cursor++;
|
||||
cursor += extract_number(cursor, &instance->octave);
|
||||
|
||||
// BPM
|
||||
cursor += skip_till(cursor, '=');
|
||||
if(*cursor != '=') {
|
||||
return false;
|
||||
}
|
||||
cursor++;
|
||||
cursor += extract_number(cursor, &instance->bpm);
|
||||
|
||||
// Notes
|
||||
cursor += skip_till(cursor, ':');
|
||||
if(*cursor != ':') {
|
||||
return false;
|
||||
}
|
||||
cursor++;
|
||||
if(!music_beeper_worker_parse_notes(instance, cursor)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void music_beeper_worker_set_callback(
|
||||
MusicBeeperWorker* instance,
|
||||
MusicBeeperWorkerCallback callback,
|
||||
void* context) {
|
||||
furi_assert(instance);
|
||||
instance->callback = callback;
|
||||
instance->callback_context = context;
|
||||
}
|
||||
|
||||
void music_beeper_worker_set_volume(MusicBeeperWorker* instance, float volume) {
|
||||
furi_assert(instance);
|
||||
instance->volume = volume;
|
||||
}
|
||||
|
||||
void music_beeper_worker_start(MusicBeeperWorker* instance) {
|
||||
furi_assert(instance);
|
||||
furi_assert(instance->should_work == false);
|
||||
|
||||
instance->should_work = true;
|
||||
furi_thread_start(instance->thread);
|
||||
}
|
||||
|
||||
void music_beeper_worker_stop(MusicBeeperWorker* instance) {
|
||||
furi_assert(instance);
|
||||
furi_assert(instance->should_work == true);
|
||||
|
||||
instance->should_work = false;
|
||||
furi_thread_join(instance->thread);
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef void (*MusicBeeperWorkerCallback)(
|
||||
uint8_t semitone,
|
||||
uint8_t dots,
|
||||
uint8_t duration,
|
||||
float position,
|
||||
void* context);
|
||||
|
||||
typedef struct MusicBeeperWorker MusicBeeperWorker;
|
||||
|
||||
MusicBeeperWorker* music_beeper_worker_alloc();
|
||||
|
||||
void music_beeper_worker_clear(MusicBeeperWorker* instance);
|
||||
|
||||
void music_beeper_worker_free(MusicBeeperWorker* instance);
|
||||
|
||||
bool music_beeper_worker_load(MusicBeeperWorker* instance, const char* file_path);
|
||||
|
||||
bool music_beeper_worker_load_fmf_from_file(MusicBeeperWorker* instance, const char* file_path);
|
||||
|
||||
bool music_beeper_worker_load_rtttl_from_file(MusicBeeperWorker* instance, const char* file_path);
|
||||
|
||||
bool music_beeper_worker_load_rtttl_from_string(MusicBeeperWorker* instance, const char* string);
|
||||
|
||||
void music_beeper_worker_set_callback(
|
||||
MusicBeeperWorker* instance,
|
||||
MusicBeeperWorkerCallback callback,
|
||||
void* context);
|
||||
|
||||
void music_beeper_worker_set_volume(MusicBeeperWorker* instance, float volume);
|
||||
|
||||
void music_beeper_worker_start(MusicBeeperWorker* instance);
|
||||
|
||||
void music_beeper_worker_stop(MusicBeeperWorker* instance);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,25 +0,0 @@
|
||||
App(
|
||||
appid="nrf24mousejacker",
|
||||
name="[NRF24] Mouse Jacker",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="mousejacker_app",
|
||||
requires=[
|
||||
"gui",
|
||||
"dialogs",
|
||||
],
|
||||
stack_size=2 * 1024,
|
||||
fap_icon="mouse_10px.png",
|
||||
fap_category="GPIO",
|
||||
fap_author="@mothball187 & @xMasterX",
|
||||
fap_version="1.0",
|
||||
fap_description="App works with NRF24 Sniffer app to perform mousejack attacks",
|
||||
fap_icon_assets="images",
|
||||
fap_private_libs=[
|
||||
Lib(
|
||||
name="nrf24",
|
||||
sources=[
|
||||
"nrf24.c",
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -1,546 +0,0 @@
|
||||
#include "nrf24.h"
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <furi_hal_resources.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
void nrf24_init() {
|
||||
// this is needed if multiple SPI devices are connected to the same bus but with different CS pins
|
||||
if(xtreme_settings.spi_nrf24_handle == SpiDefault) {
|
||||
furi_hal_gpio_init_simple(&gpio_ext_pc3, GpioModeOutputPushPull);
|
||||
furi_hal_gpio_write(&gpio_ext_pc3, true);
|
||||
} else if(xtreme_settings.spi_nrf24_handle == SpiExtra) {
|
||||
furi_hal_gpio_init_simple(&gpio_ext_pa4, GpioModeOutputPushPull);
|
||||
furi_hal_gpio_write(&gpio_ext_pa4, true);
|
||||
}
|
||||
|
||||
furi_hal_spi_bus_handle_init(nrf24_HANDLE);
|
||||
furi_hal_spi_acquire(nrf24_HANDLE);
|
||||
furi_hal_gpio_init(nrf24_CE_PIN, GpioModeOutputPushPull, GpioPullUp, GpioSpeedVeryHigh);
|
||||
furi_hal_gpio_write(nrf24_CE_PIN, false);
|
||||
}
|
||||
|
||||
void nrf24_deinit() {
|
||||
furi_hal_spi_release(nrf24_HANDLE);
|
||||
furi_hal_spi_bus_handle_deinit(nrf24_HANDLE);
|
||||
furi_hal_gpio_write(nrf24_CE_PIN, false);
|
||||
furi_hal_gpio_init(nrf24_CE_PIN, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
|
||||
|
||||
// resetting the CS pins to floating
|
||||
if(xtreme_settings.spi_nrf24_handle == SpiDefault) {
|
||||
furi_hal_gpio_init_simple(&gpio_ext_pc3, GpioModeAnalog);
|
||||
} else if(xtreme_settings.spi_nrf24_handle == SpiExtra) {
|
||||
furi_hal_gpio_init_simple(&gpio_ext_pa4, GpioModeAnalog);
|
||||
}
|
||||
}
|
||||
|
||||
void nrf24_spi_trx(
|
||||
FuriHalSpiBusHandle* handle,
|
||||
uint8_t* tx,
|
||||
uint8_t* rx,
|
||||
uint8_t size,
|
||||
uint32_t timeout) {
|
||||
UNUSED(timeout);
|
||||
furi_hal_gpio_write(handle->cs, false);
|
||||
furi_hal_spi_bus_trx(handle, tx, rx, size, nrf24_TIMEOUT);
|
||||
furi_hal_gpio_write(handle->cs, true);
|
||||
}
|
||||
|
||||
uint8_t nrf24_write_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t data) {
|
||||
uint8_t tx[2] = {W_REGISTER | (REGISTER_MASK & reg), data};
|
||||
uint8_t rx[2] = {0};
|
||||
nrf24_spi_trx(handle, tx, rx, 2, nrf24_TIMEOUT);
|
||||
return rx[0];
|
||||
}
|
||||
|
||||
uint8_t
|
||||
nrf24_write_buf_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* data, uint8_t size) {
|
||||
uint8_t tx[size + 1];
|
||||
uint8_t rx[size + 1];
|
||||
memset(rx, 0, size + 1);
|
||||
tx[0] = W_REGISTER | (REGISTER_MASK & reg);
|
||||
memcpy(&tx[1], data, size);
|
||||
nrf24_spi_trx(handle, tx, rx, size + 1, nrf24_TIMEOUT);
|
||||
return rx[0];
|
||||
}
|
||||
|
||||
uint8_t nrf24_read_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* data, uint8_t size) {
|
||||
uint8_t tx[size + 1];
|
||||
uint8_t rx[size + 1];
|
||||
memset(rx, 0, size + 1);
|
||||
tx[0] = R_REGISTER | (REGISTER_MASK & reg);
|
||||
memset(&tx[1], 0, size);
|
||||
nrf24_spi_trx(handle, tx, rx, size + 1, nrf24_TIMEOUT);
|
||||
memcpy(data, &rx[1], size);
|
||||
return rx[0];
|
||||
}
|
||||
|
||||
uint8_t nrf24_flush_rx(FuriHalSpiBusHandle* handle) {
|
||||
uint8_t tx[] = {FLUSH_RX};
|
||||
uint8_t rx[] = {0};
|
||||
nrf24_spi_trx(handle, tx, rx, 1, nrf24_TIMEOUT);
|
||||
return rx[0];
|
||||
}
|
||||
|
||||
uint8_t nrf24_flush_tx(FuriHalSpiBusHandle* handle) {
|
||||
uint8_t tx[] = {FLUSH_TX};
|
||||
uint8_t rx[] = {0};
|
||||
nrf24_spi_trx(handle, tx, rx, 1, nrf24_TIMEOUT);
|
||||
return rx[0];
|
||||
}
|
||||
|
||||
uint8_t nrf24_get_maclen(FuriHalSpiBusHandle* handle) {
|
||||
uint8_t maclen;
|
||||
nrf24_read_reg(handle, REG_SETUP_AW, &maclen, 1);
|
||||
maclen &= 3;
|
||||
return maclen + 2;
|
||||
}
|
||||
|
||||
uint8_t nrf24_set_maclen(FuriHalSpiBusHandle* handle, uint8_t maclen) {
|
||||
assert(maclen > 1 && maclen < 6);
|
||||
uint8_t status = 0;
|
||||
status = nrf24_write_reg(handle, REG_SETUP_AW, maclen - 2);
|
||||
return status;
|
||||
}
|
||||
|
||||
uint8_t nrf24_status(FuriHalSpiBusHandle* handle) {
|
||||
uint8_t status;
|
||||
uint8_t tx[] = {R_REGISTER | (REGISTER_MASK & REG_STATUS)};
|
||||
nrf24_spi_trx(handle, tx, &status, 1, nrf24_TIMEOUT);
|
||||
return status;
|
||||
}
|
||||
|
||||
uint32_t nrf24_get_rate(FuriHalSpiBusHandle* handle) {
|
||||
uint8_t setup = 0;
|
||||
uint32_t rate = 0;
|
||||
nrf24_read_reg(handle, REG_RF_SETUP, &setup, 1);
|
||||
setup &= 0x28;
|
||||
if(setup == 0x20)
|
||||
rate = 250000; // 250kbps
|
||||
else if(setup == 0x08)
|
||||
rate = 2000000; // 2Mbps
|
||||
else if(setup == 0x00)
|
||||
rate = 1000000; // 1Mbps
|
||||
|
||||
return rate;
|
||||
}
|
||||
|
||||
uint8_t nrf24_set_rate(FuriHalSpiBusHandle* handle, uint32_t rate) {
|
||||
uint8_t r6 = 0;
|
||||
uint8_t status = 0;
|
||||
if(!rate) rate = 2000000;
|
||||
|
||||
nrf24_read_reg(handle, REG_RF_SETUP, &r6, 1); // RF_SETUP register
|
||||
r6 = r6 & (~0x28); // Clear rate fields.
|
||||
if(rate == 2000000)
|
||||
r6 = r6 | 0x08;
|
||||
else if(rate == 1000000)
|
||||
r6 = r6;
|
||||
else if(rate == 250000)
|
||||
r6 = r6 | 0x20;
|
||||
|
||||
status = nrf24_write_reg(handle, REG_RF_SETUP, r6); // Write new rate.
|
||||
return status;
|
||||
}
|
||||
|
||||
uint8_t nrf24_get_chan(FuriHalSpiBusHandle* handle) {
|
||||
uint8_t channel = 0;
|
||||
nrf24_read_reg(handle, REG_RF_CH, &channel, 1);
|
||||
return channel;
|
||||
}
|
||||
|
||||
uint8_t nrf24_set_chan(FuriHalSpiBusHandle* handle, uint8_t chan) {
|
||||
uint8_t status;
|
||||
status = nrf24_write_reg(handle, REG_RF_CH, chan);
|
||||
return status;
|
||||
}
|
||||
|
||||
uint8_t nrf24_get_src_mac(FuriHalSpiBusHandle* handle, uint8_t* mac) {
|
||||
uint8_t size = 0;
|
||||
uint8_t status = 0;
|
||||
size = nrf24_get_maclen(handle);
|
||||
status = nrf24_read_reg(handle, REG_RX_ADDR_P0, mac, size);
|
||||
return status;
|
||||
}
|
||||
|
||||
uint8_t nrf24_set_src_mac(FuriHalSpiBusHandle* handle, uint8_t* mac, uint8_t size) {
|
||||
uint8_t status = 0;
|
||||
uint8_t clearmac[] = {0, 0, 0, 0, 0};
|
||||
nrf24_set_maclen(handle, size);
|
||||
nrf24_write_buf_reg(handle, REG_RX_ADDR_P0, clearmac, 5);
|
||||
status = nrf24_write_buf_reg(handle, REG_RX_ADDR_P0, mac, size);
|
||||
return status;
|
||||
}
|
||||
|
||||
uint8_t nrf24_get_dst_mac(FuriHalSpiBusHandle* handle, uint8_t* mac) {
|
||||
uint8_t size = 0;
|
||||
uint8_t status = 0;
|
||||
size = nrf24_get_maclen(handle);
|
||||
status = nrf24_read_reg(handle, REG_TX_ADDR, mac, size);
|
||||
return status;
|
||||
}
|
||||
|
||||
uint8_t nrf24_set_dst_mac(FuriHalSpiBusHandle* handle, uint8_t* mac, uint8_t size) {
|
||||
uint8_t status = 0;
|
||||
uint8_t clearmac[] = {0, 0, 0, 0, 0};
|
||||
nrf24_set_maclen(handle, size);
|
||||
nrf24_write_buf_reg(handle, REG_TX_ADDR, clearmac, 5);
|
||||
status = nrf24_write_buf_reg(handle, REG_TX_ADDR, mac, size);
|
||||
return status;
|
||||
}
|
||||
|
||||
uint8_t nrf24_get_packetlen(FuriHalSpiBusHandle* handle) {
|
||||
uint8_t len = 0;
|
||||
nrf24_read_reg(handle, RX_PW_P0, &len, 1);
|
||||
return len;
|
||||
}
|
||||
|
||||
uint8_t nrf24_set_packetlen(FuriHalSpiBusHandle* handle, uint8_t len) {
|
||||
uint8_t status = 0;
|
||||
status = nrf24_write_reg(handle, RX_PW_P0, len);
|
||||
return status;
|
||||
}
|
||||
|
||||
uint8_t
|
||||
nrf24_rxpacket(FuriHalSpiBusHandle* handle, uint8_t* packet, uint8_t* packetsize, bool full) {
|
||||
uint8_t status = 0;
|
||||
uint8_t size = 0;
|
||||
uint8_t tx_pl_wid[] = {R_RX_PL_WID, 0};
|
||||
uint8_t rx_pl_wid[] = {0, 0};
|
||||
uint8_t tx_cmd[33] = {0}; // 32 max payload size + 1 for command
|
||||
uint8_t tmp_packet[33] = {0};
|
||||
|
||||
status = nrf24_status(handle);
|
||||
|
||||
if(status & 0x40) {
|
||||
if(full)
|
||||
size = nrf24_get_packetlen(handle);
|
||||
else {
|
||||
nrf24_spi_trx(handle, tx_pl_wid, rx_pl_wid, 2, nrf24_TIMEOUT);
|
||||
size = rx_pl_wid[1];
|
||||
}
|
||||
|
||||
tx_cmd[0] = R_RX_PAYLOAD;
|
||||
nrf24_spi_trx(handle, tx_cmd, tmp_packet, size + 1, nrf24_TIMEOUT);
|
||||
nrf24_write_reg(handle, REG_STATUS, 0x40); // clear bit.
|
||||
memcpy(packet, &tmp_packet[1], size);
|
||||
} else if(status == 0) {
|
||||
nrf24_flush_rx(handle);
|
||||
nrf24_write_reg(handle, REG_STATUS, 0x40); // clear bit.
|
||||
}
|
||||
|
||||
*packetsize = size;
|
||||
return status;
|
||||
}
|
||||
|
||||
uint8_t nrf24_txpacket(FuriHalSpiBusHandle* handle, uint8_t* payload, uint8_t size, bool ack) {
|
||||
uint8_t status = 0;
|
||||
uint8_t tx[size + 1];
|
||||
uint8_t rx[size + 1];
|
||||
memset(tx, 0, size + 1);
|
||||
memset(rx, 0, size + 1);
|
||||
|
||||
if(!ack)
|
||||
tx[0] = W_TX_PAYLOAD_NOACK;
|
||||
else
|
||||
tx[0] = W_TX_PAYLOAD;
|
||||
|
||||
memcpy(&tx[1], payload, size);
|
||||
nrf24_spi_trx(handle, tx, rx, size + 1, nrf24_TIMEOUT);
|
||||
nrf24_set_tx_mode(handle);
|
||||
|
||||
while(!(status & (TX_DS | MAX_RT))) status = nrf24_status(handle);
|
||||
|
||||
if(status & MAX_RT) nrf24_flush_tx(handle);
|
||||
|
||||
nrf24_set_idle(handle);
|
||||
nrf24_write_reg(handle, REG_STATUS, TX_DS | MAX_RT);
|
||||
return status & TX_DS;
|
||||
}
|
||||
|
||||
uint8_t nrf24_power_up(FuriHalSpiBusHandle* handle) {
|
||||
uint8_t status = 0;
|
||||
uint8_t cfg = 0;
|
||||
nrf24_read_reg(handle, REG_CONFIG, &cfg, 1);
|
||||
cfg = cfg | 2;
|
||||
status = nrf24_write_reg(handle, REG_CONFIG, cfg);
|
||||
furi_delay_ms(5000);
|
||||
return status;
|
||||
}
|
||||
|
||||
uint8_t nrf24_set_idle(FuriHalSpiBusHandle* handle) {
|
||||
uint8_t status = 0;
|
||||
uint8_t cfg = 0;
|
||||
nrf24_read_reg(handle, REG_CONFIG, &cfg, 1);
|
||||
cfg &= 0xfc; // clear bottom two bits to power down the radio
|
||||
status = nrf24_write_reg(handle, REG_CONFIG, cfg);
|
||||
//nr204_write_reg(handle, REG_EN_RXADDR, 0x0);
|
||||
furi_hal_gpio_write(nrf24_CE_PIN, false);
|
||||
return status;
|
||||
}
|
||||
|
||||
uint8_t nrf24_set_rx_mode(FuriHalSpiBusHandle* handle) {
|
||||
uint8_t status = 0;
|
||||
uint8_t cfg = 0;
|
||||
//status = nrf24_write_reg(handle, REG_CONFIG, 0x0F); // enable 2-byte CRC, PWR_UP, and PRIM_RX
|
||||
nrf24_read_reg(handle, REG_CONFIG, &cfg, 1);
|
||||
cfg |= 0x03; // PWR_UP, and PRIM_RX
|
||||
status = nrf24_write_reg(handle, REG_CONFIG, cfg);
|
||||
//nr204_write_reg(REG_EN_RXADDR, 0x03) // Set RX Pipe 0 and 1
|
||||
furi_hal_gpio_write(nrf24_CE_PIN, true);
|
||||
furi_delay_ms(2000);
|
||||
return status;
|
||||
}
|
||||
|
||||
uint8_t nrf24_set_tx_mode(FuriHalSpiBusHandle* handle) {
|
||||
uint8_t status = 0;
|
||||
uint8_t cfg = 0;
|
||||
furi_hal_gpio_write(nrf24_CE_PIN, false);
|
||||
nrf24_write_reg(handle, REG_STATUS, 0x30);
|
||||
//status = nrf24_write_reg(handle, REG_CONFIG, 0x0E); // enable 2-byte CRC, PWR_UP
|
||||
nrf24_read_reg(handle, REG_CONFIG, &cfg, 1);
|
||||
cfg &= 0xfe; // disable PRIM_RX
|
||||
cfg |= 0x02; // PWR_UP
|
||||
status = nrf24_write_reg(handle, REG_CONFIG, cfg);
|
||||
furi_hal_gpio_write(nrf24_CE_PIN, true);
|
||||
furi_delay_ms(2);
|
||||
return status;
|
||||
}
|
||||
|
||||
void nrf24_configure(
|
||||
FuriHalSpiBusHandle* handle,
|
||||
uint8_t rate,
|
||||
uint8_t* srcmac,
|
||||
uint8_t* dstmac,
|
||||
uint8_t maclen,
|
||||
uint8_t channel,
|
||||
bool noack,
|
||||
bool disable_aa) {
|
||||
assert(channel <= 125);
|
||||
assert(rate == 1 || rate == 2);
|
||||
if(rate == 2)
|
||||
rate = 8; // 2Mbps
|
||||
else
|
||||
rate = 0; // 1Mbps
|
||||
|
||||
nrf24_write_reg(handle, REG_CONFIG, 0x00); // Stop nRF
|
||||
nrf24_set_idle(handle);
|
||||
nrf24_write_reg(handle, REG_STATUS, 0x1c); // clear interrupts
|
||||
if(disable_aa)
|
||||
nrf24_write_reg(handle, REG_EN_AA, 0x00); // Disable Shockburst
|
||||
else
|
||||
nrf24_write_reg(handle, REG_EN_AA, 0x1F); // Enable Shockburst
|
||||
|
||||
nrf24_write_reg(handle, REG_DYNPD, 0x3F); // enable dynamic payload length on all pipes
|
||||
if(noack)
|
||||
nrf24_write_reg(handle, REG_FEATURE, 0x05); // disable payload-with-ack, enable noack
|
||||
else {
|
||||
nrf24_write_reg(handle, REG_CONFIG, 0x0C); // 2 byte CRC
|
||||
nrf24_write_reg(handle, REG_FEATURE, 0x07); // enable dyn payload and ack
|
||||
nrf24_write_reg(
|
||||
handle, REG_SETUP_RETR, 0x1f); // 15 retries for AA, 500us auto retransmit delay
|
||||
}
|
||||
|
||||
nrf24_set_idle(handle);
|
||||
nrf24_flush_rx(handle);
|
||||
nrf24_flush_tx(handle);
|
||||
|
||||
if(maclen) nrf24_set_maclen(handle, maclen);
|
||||
if(srcmac) nrf24_set_src_mac(handle, srcmac, maclen);
|
||||
if(dstmac) nrf24_set_dst_mac(handle, dstmac, maclen);
|
||||
|
||||
nrf24_write_reg(handle, REG_RF_CH, channel);
|
||||
nrf24_write_reg(handle, REG_RF_SETUP, rate);
|
||||
furi_delay_ms(200);
|
||||
}
|
||||
|
||||
void nrf24_init_promisc_mode(FuriHalSpiBusHandle* handle, uint8_t channel, uint8_t rate) {
|
||||
//uint8_t preamble[] = {0x55, 0x00}; // little endian
|
||||
uint8_t preamble[] = {0xAA, 0x00}; // little endian
|
||||
//uint8_t preamble[] = {0x00, 0x55}; // little endian
|
||||
//uint8_t preamble[] = {0x00, 0xAA}; // little endian
|
||||
nrf24_write_reg(handle, REG_CONFIG, 0x00); // Stop nRF
|
||||
nrf24_write_reg(handle, REG_STATUS, 0x1c); // clear interrupts
|
||||
nrf24_write_reg(handle, REG_DYNPD, 0x0); // disable shockburst
|
||||
nrf24_write_reg(handle, REG_EN_AA, 0x00); // Disable Shockburst
|
||||
nrf24_write_reg(handle, REG_FEATURE, 0x05); // disable payload-with-ack, enable noack
|
||||
nrf24_set_maclen(handle, 2); // shortest address
|
||||
nrf24_set_src_mac(handle, preamble, 2); // set src mac to preamble bits to catch everything
|
||||
nrf24_set_packetlen(handle, 32); // set max packet length
|
||||
nrf24_set_idle(handle);
|
||||
nrf24_flush_rx(handle);
|
||||
nrf24_flush_tx(handle);
|
||||
nrf24_write_reg(handle, REG_RF_CH, channel);
|
||||
nrf24_write_reg(handle, REG_RF_SETUP, rate);
|
||||
|
||||
// prime for RX, no checksum
|
||||
nrf24_write_reg(handle, REG_CONFIG, 0x03); // PWR_UP and PRIM_RX, disable AA and CRC
|
||||
furi_hal_gpio_write(nrf24_CE_PIN, true);
|
||||
furi_delay_ms(100);
|
||||
}
|
||||
|
||||
void hexlify(uint8_t* in, uint8_t size, char* out) {
|
||||
memset(out, 0, size * 2);
|
||||
for(int i = 0; i < size; i++)
|
||||
snprintf(out + strlen(out), sizeof(out + strlen(out)), "%02X", in[i]);
|
||||
}
|
||||
|
||||
uint64_t bytes_to_int64(uint8_t* bytes, uint8_t size, bool bigendian) {
|
||||
uint64_t ret = 0;
|
||||
for(int i = 0; i < size; i++)
|
||||
if(bigendian)
|
||||
ret |= bytes[i] << ((size - 1 - i) * 8);
|
||||
else
|
||||
ret |= bytes[i] << (i * 8);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void int64_to_bytes(uint64_t val, uint8_t* out, bool bigendian) {
|
||||
for(int i = 0; i < 8; i++) {
|
||||
if(bigendian)
|
||||
out[i] = (val >> ((7 - i) * 8)) & 0xff;
|
||||
else
|
||||
out[i] = (val >> (i * 8)) & 0xff;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t bytes_to_int32(uint8_t* bytes, bool bigendian) {
|
||||
uint32_t ret = 0;
|
||||
for(int i = 0; i < 4; i++)
|
||||
if(bigendian)
|
||||
ret |= bytes[i] << ((3 - i) * 8);
|
||||
else
|
||||
ret |= bytes[i] << (i * 8);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void int32_to_bytes(uint32_t val, uint8_t* out, bool bigendian) {
|
||||
for(int i = 0; i < 4; i++) {
|
||||
if(bigendian)
|
||||
out[i] = (val >> ((3 - i) * 8)) & 0xff;
|
||||
else
|
||||
out[i] = (val >> (i * 8)) & 0xff;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t bytes_to_int16(uint8_t* bytes, bool bigendian) {
|
||||
uint16_t ret = 0;
|
||||
for(int i = 0; i < 2; i++)
|
||||
if(bigendian)
|
||||
ret |= bytes[i] << ((1 - i) * 8);
|
||||
else
|
||||
ret |= bytes[i] << (i * 8);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void int16_to_bytes(uint16_t val, uint8_t* out, bool bigendian) {
|
||||
for(int i = 0; i < 2; i++) {
|
||||
if(bigendian)
|
||||
out[i] = (val >> ((1 - i) * 8)) & 0xff;
|
||||
else
|
||||
out[i] = (val >> (i * 8)) & 0xff;
|
||||
}
|
||||
}
|
||||
|
||||
// handle iffyness with preamble processing sometimes being a bit (literally) off
|
||||
void alt_address_old(uint8_t* packet, uint8_t* altaddr) {
|
||||
uint8_t macmess_hi_b[4];
|
||||
uint8_t macmess_lo_b[2];
|
||||
uint32_t macmess_hi;
|
||||
uint16_t macmess_lo;
|
||||
uint8_t preserved;
|
||||
|
||||
// get first 6 bytes into 32-bit and 16-bit variables
|
||||
memcpy(macmess_hi_b, packet, 4);
|
||||
memcpy(macmess_lo_b, packet + 4, 2);
|
||||
|
||||
macmess_hi = bytes_to_int32(macmess_hi_b, true);
|
||||
|
||||
//preserve least 7 bits from hi that will be shifted down to lo
|
||||
preserved = macmess_hi & 0x7f;
|
||||
macmess_hi >>= 7;
|
||||
|
||||
macmess_lo = bytes_to_int16(macmess_lo_b, true);
|
||||
macmess_lo >>= 7;
|
||||
macmess_lo = (preserved << 9) | macmess_lo;
|
||||
int32_to_bytes(macmess_hi, macmess_hi_b, true);
|
||||
int16_to_bytes(macmess_lo, macmess_lo_b, true);
|
||||
memcpy(altaddr, &macmess_hi_b[1], 3);
|
||||
memcpy(altaddr + 3, macmess_lo_b, 2);
|
||||
}
|
||||
|
||||
bool validate_address(uint8_t* addr) {
|
||||
uint8_t bad[][3] = {{0x55, 0x55}, {0xAA, 0xAA}, {0x00, 0x00}, {0xFF, 0xFF}};
|
||||
for(int i = 0; i < 4; i++)
|
||||
for(int j = 0; j < 2; j++)
|
||||
if(!memcmp(addr + j * 2, bad[i], 2)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool nrf24_sniff_address(FuriHalSpiBusHandle* handle, uint8_t maclen, uint8_t* address) {
|
||||
bool found = false;
|
||||
uint8_t packet[32] = {0};
|
||||
uint8_t packetsize;
|
||||
//char printit[65];
|
||||
uint8_t status = 0;
|
||||
status = nrf24_rxpacket(handle, packet, &packetsize, true);
|
||||
if(status & 0x40) {
|
||||
if(validate_address(packet)) {
|
||||
for(int i = 0; i < maclen; i++) address[i] = packet[maclen - 1 - i];
|
||||
|
||||
/*
|
||||
alt_address(packet, packet);
|
||||
|
||||
for(i = 0; i < maclen; i++)
|
||||
address[i + 5] = packet[maclen - 1 - i];
|
||||
*/
|
||||
|
||||
//memcpy(address, packet, maclen);
|
||||
//hexlify(packet, packetsize, printit);
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
uint8_t nrf24_find_channel(
|
||||
FuriHalSpiBusHandle* handle,
|
||||
uint8_t* srcmac,
|
||||
uint8_t* dstmac,
|
||||
uint8_t maclen,
|
||||
uint8_t rate,
|
||||
uint8_t min_channel,
|
||||
uint8_t max_channel,
|
||||
bool autoinit) {
|
||||
uint8_t ping_packet[] = {0x0f, 0x0f, 0x0f, 0x0f}; // this can be anything, we just need an ack
|
||||
uint8_t ch = max_channel + 1; // means fail
|
||||
nrf24_configure(handle, rate, srcmac, dstmac, maclen, 2, false, false);
|
||||
for(ch = min_channel; ch <= max_channel + 1; ch++) {
|
||||
nrf24_write_reg(handle, REG_RF_CH, ch);
|
||||
if(nrf24_txpacket(handle, ping_packet, 4, true)) break;
|
||||
}
|
||||
|
||||
if(autoinit) {
|
||||
FURI_LOG_D("nrf24", "initializing radio for channel %d", ch);
|
||||
nrf24_configure(handle, rate, srcmac, dstmac, maclen, ch, false, false);
|
||||
return ch;
|
||||
}
|
||||
|
||||
return ch;
|
||||
}
|
||||
|
||||
bool nrf24_check_connected(FuriHalSpiBusHandle* handle) {
|
||||
uint8_t status = nrf24_status(handle);
|
||||
|
||||
if(status != 0x00) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,376 +0,0 @@
|
||||
#pragma once
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <furi_hal_spi.h>
|
||||
#include <xtreme.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define R_REGISTER 0x00
|
||||
#define W_REGISTER 0x20
|
||||
#define REGISTER_MASK 0x1F
|
||||
#define ACTIVATE 0x50
|
||||
#define R_RX_PL_WID 0x60
|
||||
#define R_RX_PAYLOAD 0x61
|
||||
#define W_TX_PAYLOAD 0xA0
|
||||
#define W_TX_PAYLOAD_NOACK 0xB0
|
||||
#define W_ACK_PAYLOAD 0xA8
|
||||
#define FLUSH_TX 0xE1
|
||||
#define FLUSH_RX 0xE2
|
||||
#define REUSE_TX_PL 0xE3
|
||||
#define RF24_NOP 0xFF
|
||||
|
||||
#define REG_CONFIG 0x00
|
||||
#define REG_EN_AA 0x01
|
||||
#define REG_EN_RXADDR 0x02
|
||||
#define REG_SETUP_AW 0x03
|
||||
#define REG_SETUP_RETR 0x04
|
||||
#define REG_DYNPD 0x1C
|
||||
#define REG_FEATURE 0x1D
|
||||
#define REG_RF_SETUP 0x06
|
||||
#define REG_STATUS 0x07
|
||||
#define REG_RX_ADDR_P0 0x0A
|
||||
#define REG_RF_CH 0x05
|
||||
#define REG_TX_ADDR 0x10
|
||||
|
||||
#define RX_PW_P0 0x11
|
||||
#define TX_DS 0x20
|
||||
#define MAX_RT 0x10
|
||||
|
||||
#define nrf24_TIMEOUT 500
|
||||
#define nrf24_CE_PIN &gpio_ext_pb2
|
||||
#define nrf24_HANDLE \
|
||||
(xtreme_settings.spi_nrf24_handle == SpiDefault ? &furi_hal_spi_bus_handle_external : \
|
||||
&furi_hal_spi_bus_handle_external_extra)
|
||||
|
||||
/* Low level API */
|
||||
|
||||
/** Write device register
|
||||
*
|
||||
* @param handle - pointer to FuriHalSpiHandle
|
||||
* @param reg - register
|
||||
* @param data - data to write
|
||||
*
|
||||
* @return device status
|
||||
*/
|
||||
uint8_t nrf24_write_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t data);
|
||||
|
||||
/** Write buffer to device register
|
||||
*
|
||||
* @param handle - pointer to FuriHalSpiHandle
|
||||
* @param reg - register
|
||||
* @param data - data to write
|
||||
* @param size - size of data to write
|
||||
*
|
||||
* @return device status
|
||||
*/
|
||||
uint8_t nrf24_write_buf_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* data, uint8_t size);
|
||||
|
||||
/** Read device register
|
||||
*
|
||||
* @param handle - pointer to FuriHalSpiHandle
|
||||
* @param reg - register
|
||||
* @param[out] data - pointer to data
|
||||
*
|
||||
* @return device status
|
||||
*/
|
||||
uint8_t nrf24_read_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* data, uint8_t size);
|
||||
|
||||
/** Power up the radio for operation
|
||||
*
|
||||
* @param handle - pointer to FuriHalSpiHandle
|
||||
*
|
||||
* @return device status
|
||||
*/
|
||||
uint8_t nrf24_power_up(FuriHalSpiBusHandle* handle);
|
||||
|
||||
/** Power down the radio
|
||||
*
|
||||
* @param handle - pointer to FuriHalSpiHandle
|
||||
*
|
||||
* @return device status
|
||||
*/
|
||||
uint8_t nrf24_set_idle(FuriHalSpiBusHandle* handle);
|
||||
|
||||
/** Sets the radio to RX mode
|
||||
*
|
||||
* @param handle - pointer to FuriHalSpiHandle
|
||||
*
|
||||
* @return device status
|
||||
*/
|
||||
uint8_t nrf24_set_rx_mode(FuriHalSpiBusHandle* handle);
|
||||
|
||||
/** Sets the radio to TX mode
|
||||
*
|
||||
* @param handle - pointer to FuriHalSpiHandle
|
||||
*
|
||||
* @return device status
|
||||
*/
|
||||
uint8_t nrf24_set_tx_mode(FuriHalSpiBusHandle* handle);
|
||||
|
||||
/*=============================================================================================================*/
|
||||
|
||||
/* High level API */
|
||||
|
||||
/** Must call this before using any other nrf24 API
|
||||
*
|
||||
*/
|
||||
void nrf24_init();
|
||||
|
||||
/** Must call this when we end using nrf24 device
|
||||
*
|
||||
*/
|
||||
void nrf24_deinit();
|
||||
|
||||
/** Send flush rx command
|
||||
*
|
||||
* @param handle - pointer to FuriHalSpiHandle
|
||||
*
|
||||
* @return device status
|
||||
*/
|
||||
uint8_t nrf24_flush_rx(FuriHalSpiBusHandle* handle);
|
||||
|
||||
/** Send flush tx command
|
||||
*
|
||||
* @param handle - pointer to FuriHalSpiHandle
|
||||
*
|
||||
* @return device status
|
||||
*/
|
||||
uint8_t nrf24_flush_tx(FuriHalSpiBusHandle* handle);
|
||||
|
||||
/** Gets the RX packet length in data pipe 0
|
||||
*
|
||||
* @param handle - pointer to FuriHalSpiHandle
|
||||
*
|
||||
* @return packet length in data pipe 0
|
||||
*/
|
||||
uint8_t nrf24_get_packetlen(FuriHalSpiBusHandle* handle);
|
||||
|
||||
/** Sets the RX packet length in data pipe 0
|
||||
*
|
||||
* @param handle - pointer to FuriHalSpiHandle
|
||||
* @param len - length to set
|
||||
*
|
||||
* @return device status
|
||||
*/
|
||||
uint8_t nrf24_set_packetlen(FuriHalSpiBusHandle* handle, uint8_t len);
|
||||
|
||||
/** Gets configured length of MAC address
|
||||
*
|
||||
* @param handle - pointer to FuriHalSpiHandle
|
||||
*
|
||||
* @return MAC address length
|
||||
*/
|
||||
uint8_t nrf24_get_maclen(FuriHalSpiBusHandle* handle);
|
||||
|
||||
/** Sets configured length of MAC address
|
||||
*
|
||||
* @param handle - pointer to FuriHalSpiHandle
|
||||
* @param maclen - length to set MAC address to, must be greater than 1 and less than 6
|
||||
*
|
||||
* @return MAC address length
|
||||
*/
|
||||
uint8_t nrf24_set_maclen(FuriHalSpiBusHandle* handle, uint8_t maclen);
|
||||
|
||||
/** Gets the current status flags from the STATUS register
|
||||
*
|
||||
* @param handle - pointer to FuriHalSpiHandle
|
||||
*
|
||||
* @return status flags
|
||||
*/
|
||||
uint8_t nrf24_status(FuriHalSpiBusHandle* handle);
|
||||
|
||||
/** Gets the current transfer rate
|
||||
*
|
||||
* @param handle - pointer to FuriHalSpiHandle
|
||||
*
|
||||
* @return transfer rate in bps
|
||||
*/
|
||||
uint32_t nrf24_get_rate(FuriHalSpiBusHandle* handle);
|
||||
|
||||
/** Sets the transfer rate
|
||||
*
|
||||
* @param handle - pointer to FuriHalSpiHandle
|
||||
* @param rate - the transfer rate in bps
|
||||
*
|
||||
* @return device status
|
||||
*/
|
||||
uint8_t nrf24_set_rate(FuriHalSpiBusHandle* handle, uint32_t rate);
|
||||
|
||||
/** Gets the current channel
|
||||
* In nrf24, the channel number is multiplied times 1MHz and added to 2400MHz to get the frequency
|
||||
*
|
||||
* @param handle - pointer to FuriHalSpiHandle
|
||||
*
|
||||
* @return channel
|
||||
*/
|
||||
uint8_t nrf24_get_chan(FuriHalSpiBusHandle* handle);
|
||||
|
||||
/** Sets the channel
|
||||
*
|
||||
* @param handle - pointer to FuriHalSpiHandle
|
||||
* @param frequency - the frequency in hertz
|
||||
*
|
||||
* @return device status
|
||||
*/
|
||||
uint8_t nrf24_set_chan(FuriHalSpiBusHandle* handle, uint8_t chan);
|
||||
|
||||
/** Gets the source mac address
|
||||
*
|
||||
* @param handle - pointer to FuriHalSpiHandle
|
||||
* @param[out] mac - the source mac address
|
||||
*
|
||||
* @return device status
|
||||
*/
|
||||
uint8_t nrf24_get_src_mac(FuriHalSpiBusHandle* handle, uint8_t* mac);
|
||||
|
||||
/** Sets the source mac address
|
||||
*
|
||||
* @param handle - pointer to FuriHalSpiHandle
|
||||
* @param mac - the mac address to set
|
||||
* @param size - the size of the mac address (2 to 5)
|
||||
*
|
||||
* @return device status
|
||||
*/
|
||||
uint8_t nrf24_set_src_mac(FuriHalSpiBusHandle* handle, uint8_t* mac, uint8_t size);
|
||||
|
||||
/** Gets the dest mac address
|
||||
*
|
||||
* @param handle - pointer to FuriHalSpiHandle
|
||||
* @param[out] mac - the source mac address
|
||||
*
|
||||
* @return device status
|
||||
*/
|
||||
uint8_t nrf24_get_dst_mac(FuriHalSpiBusHandle* handle, uint8_t* mac);
|
||||
|
||||
/** Sets the dest mac address
|
||||
*
|
||||
* @param handle - pointer to FuriHalSpiHandle
|
||||
* @param mac - the mac address to set
|
||||
* @param size - the size of the mac address (2 to 5)
|
||||
*
|
||||
* @return device status
|
||||
*/
|
||||
uint8_t nrf24_set_dst_mac(FuriHalSpiBusHandle* handle, uint8_t* mac, uint8_t size);
|
||||
|
||||
/** Reads RX packet
|
||||
*
|
||||
* @param handle - pointer to FuriHalSpiHandle
|
||||
* @param[out] packet - the packet contents
|
||||
* @param[out] packetsize - size of the received packet
|
||||
* @param full - boolean set to true, packet length is determined by RX_PW_P0 register, false it is determined by dynamic payload length command
|
||||
*
|
||||
* @return device status
|
||||
*/
|
||||
uint8_t
|
||||
nrf24_rxpacket(FuriHalSpiBusHandle* handle, uint8_t* packet, uint8_t* packetsize, bool full);
|
||||
|
||||
/** Sends TX packet
|
||||
*
|
||||
* @param handle - pointer to FuriHalSpiHandle
|
||||
* @param packet - the packet contents
|
||||
* @param size - packet size
|
||||
* @param ack - boolean to determine whether an ACK is required for the packet or not
|
||||
*
|
||||
* @return device status
|
||||
*/
|
||||
uint8_t nrf24_txpacket(FuriHalSpiBusHandle* handle, uint8_t* payload, uint8_t size, bool ack);
|
||||
|
||||
/** Configure the radio
|
||||
* This is not comprehensive, but covers a lot of the common configuration options that may be changed
|
||||
* @param handle - pointer to FuriHalSpiHandle
|
||||
* @param rate - transfer rate in Mbps (1 or 2)
|
||||
* @param srcmac - source mac address
|
||||
* @param dstmac - destination mac address
|
||||
* @param maclen - length of mac address
|
||||
* @param channel - channel to tune to
|
||||
* @param noack - if true, disable auto-acknowledge
|
||||
* @param disable_aa - if true, disable ShockBurst
|
||||
*
|
||||
*/
|
||||
void nrf24_configure(
|
||||
FuriHalSpiBusHandle* handle,
|
||||
uint8_t rate,
|
||||
uint8_t* srcmac,
|
||||
uint8_t* dstmac,
|
||||
uint8_t maclen,
|
||||
uint8_t channel,
|
||||
bool noack,
|
||||
bool disable_aa);
|
||||
|
||||
/** Configures the radio for "promiscuous mode" and primes it for rx
|
||||
* This is not an actual mode of the nrf24, but this function exploits a few bugs in the chip that allows it to act as if it were.
|
||||
* See http://travisgoodspeed.blogspot.com/2011/02/promiscuity-is-nrf24l01s-duty.html for details.
|
||||
* @param handle - pointer to FuriHalSpiHandle
|
||||
* @param channel - channel to tune to
|
||||
* @param rate - transfer rate in Mbps (1 or 2)
|
||||
*/
|
||||
void nrf24_init_promisc_mode(FuriHalSpiBusHandle* handle, uint8_t channel, uint8_t rate);
|
||||
|
||||
/** Listens for a packet and returns first possible address sniffed
|
||||
* Call this only after calling nrf24_init_promisc_mode
|
||||
* @param handle - pointer to FuriHalSpiHandle
|
||||
* @param maclen - length of target mac address
|
||||
* @param[out] addresses - sniffed address
|
||||
*
|
||||
* @return success
|
||||
*/
|
||||
bool nrf24_sniff_address(FuriHalSpiBusHandle* handle, uint8_t maclen, uint8_t* address);
|
||||
|
||||
/** Sends ping packet on each channel for designated tx mac looking for ack
|
||||
*
|
||||
* @param handle - pointer to FuriHalSpiHandle
|
||||
* @param srcmac - source address
|
||||
* @param dstmac - destination address
|
||||
* @param maclen - length of address
|
||||
* @param rate - transfer rate in Mbps (1 or 2)
|
||||
* @param min_channel - channel to start with
|
||||
* @param max_channel - channel to end at
|
||||
* @param autoinit - if true, automatically configure radio for this channel
|
||||
*
|
||||
* @return channel that the address is listening on, if this value is above the max_channel param, it failed
|
||||
*/
|
||||
uint8_t nrf24_find_channel(
|
||||
FuriHalSpiBusHandle* handle,
|
||||
uint8_t* srcmac,
|
||||
uint8_t* dstmac,
|
||||
uint8_t maclen,
|
||||
uint8_t rate,
|
||||
uint8_t min_channel,
|
||||
uint8_t max_channel,
|
||||
bool autoinit);
|
||||
|
||||
/** Converts 64 bit value into uint8_t array
|
||||
* @param val - 64-bit integer
|
||||
* @param[out] out - bytes out
|
||||
* @param bigendian - if true, convert as big endian, otherwise little endian
|
||||
*/
|
||||
void int64_to_bytes(uint64_t val, uint8_t* out, bool bigendian);
|
||||
|
||||
/** Converts 32 bit value into uint8_t array
|
||||
* @param val - 32-bit integer
|
||||
* @param[out] out - bytes out
|
||||
* @param bigendian - if true, convert as big endian, otherwise little endian
|
||||
*/
|
||||
void int32_to_bytes(uint32_t val, uint8_t* out, bool bigendian);
|
||||
|
||||
/** Converts uint8_t array into 32 bit value
|
||||
* @param bytes - uint8_t array
|
||||
* @param bigendian - if true, convert as big endian, otherwise little endian
|
||||
*
|
||||
* @return 32-bit value
|
||||
*/
|
||||
uint32_t bytes_to_int32(uint8_t* bytes, bool bigendian);
|
||||
|
||||
/** Check if the nrf24 is connected
|
||||
* @param handle - pointer to FuriHalSpiHandle
|
||||
*
|
||||
* @return true if connected, otherwise false
|
||||
*/
|
||||
bool nrf24_check_connected(FuriHalSpiBusHandle* handle);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
Before Width: | Height: | Size: 1.6 KiB |
@@ -1,407 +0,0 @@
|
||||
#include <furi.h>
|
||||
#include <gui/gui.h>
|
||||
#include <dialogs/dialogs.h>
|
||||
#include <input/input.h>
|
||||
#include <stdlib.h>
|
||||
#include <furi_hal.h>
|
||||
#include <furi_hal_gpio.h>
|
||||
#include <furi_hal_spi.h>
|
||||
#include <furi_hal_interrupt.h>
|
||||
#include <furi_hal_resources.h>
|
||||
#include <notification/notification_messages.h>
|
||||
#include <nrf24.h>
|
||||
#include "mousejacker_ducky.h"
|
||||
#include "nrf24mousejacker_icons.h"
|
||||
#include <assets_icons.h>
|
||||
|
||||
#define TAG "mousejacker"
|
||||
#define LOGITECH_MAX_CHANNEL 85
|
||||
#define NRFSNIFF_APP_PATH_FOLDER_ADDRESSES EXT_PATH("apps_data/nrf24sniff/addresses.txt")
|
||||
#define BADKB_FOLDER EXT_PATH("badkb")
|
||||
#define MOUSEJACKER_APP_PATH_EXTENSION ".txt"
|
||||
#define MAX_ADDRS 100
|
||||
|
||||
typedef enum {
|
||||
EventTypeTick,
|
||||
EventTypeKey,
|
||||
} EventType;
|
||||
|
||||
typedef struct {
|
||||
EventType type;
|
||||
InputEvent input;
|
||||
} PluginEvent;
|
||||
|
||||
uint8_t addrs_count = 0;
|
||||
int8_t addr_idx = 0;
|
||||
uint8_t loaded_addrs[MAX_ADDRS][6]; // first byte is rate, the rest are the address
|
||||
|
||||
char target_fmt_text[] = "Target addr: %s";
|
||||
char target_address_str[12] = "None";
|
||||
char target_text[30];
|
||||
char index_text[30];
|
||||
|
||||
static void render_callback(Canvas* const canvas, void* ctx) {
|
||||
furi_assert(ctx);
|
||||
const PluginState* plugin_state = ctx;
|
||||
furi_mutex_acquire(plugin_state->mutex, FuriWaitForever);
|
||||
|
||||
// border around the edge of the screen
|
||||
canvas_draw_frame(canvas, 0, 0, 128, 64);
|
||||
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
if(!plugin_state->addr_err && !plugin_state->ducky_err && !plugin_state->is_thread_running &&
|
||||
!plugin_state->is_ducky_running) {
|
||||
snprintf(target_text, sizeof(target_text), target_fmt_text, target_address_str);
|
||||
canvas_draw_str_aligned(canvas, 7, 10, AlignLeft, AlignBottom, target_text);
|
||||
canvas_draw_str_aligned(canvas, 22, 20, AlignLeft, AlignBottom, "<- select address ->");
|
||||
snprintf(
|
||||
index_text, sizeof(index_text), "Address index: %d/%d", addr_idx + 1, addrs_count);
|
||||
canvas_draw_str_aligned(canvas, 10, 30, AlignLeft, AlignBottom, index_text);
|
||||
canvas_draw_str_aligned(canvas, 10, 40, AlignLeft, AlignBottom, "Press Ok button to ");
|
||||
canvas_draw_str_aligned(canvas, 10, 50, AlignLeft, AlignBottom, "browse for ducky script");
|
||||
if(!plugin_state->is_nrf24_connected) {
|
||||
canvas_draw_str_aligned(
|
||||
canvas, 10, 60, AlignLeft, AlignBottom, "Connect NRF24 to GPIO!");
|
||||
}
|
||||
} else if(plugin_state->addr_err) {
|
||||
canvas_draw_str_aligned(
|
||||
canvas, 10, 10, AlignLeft, AlignBottom, "Error: No nrf24sniff folder");
|
||||
canvas_draw_str_aligned(canvas, 10, 20, AlignLeft, AlignBottom, "or addresses.txt file");
|
||||
canvas_draw_str_aligned(
|
||||
canvas, 10, 30, AlignLeft, AlignBottom, "loading error / empty file");
|
||||
canvas_draw_str_aligned(
|
||||
canvas, 7, 40, AlignLeft, AlignBottom, "Run (NRF24: Sniff) app first!");
|
||||
} else if(plugin_state->ducky_err) {
|
||||
canvas_draw_str_aligned(
|
||||
canvas, 3, 10, AlignLeft, AlignBottom, "Error: No mousejacker folder");
|
||||
canvas_draw_str_aligned(canvas, 3, 20, AlignLeft, AlignBottom, "or duckyscript file");
|
||||
canvas_draw_str_aligned(canvas, 3, 30, AlignLeft, AlignBottom, "loading error");
|
||||
} else if(plugin_state->is_thread_running && !plugin_state->is_ducky_running) {
|
||||
canvas_draw_str_aligned(canvas, 3, 10, AlignLeft, AlignBottom, "Loading...");
|
||||
canvas_draw_str_aligned(canvas, 3, 20, AlignLeft, AlignBottom, "Please wait!");
|
||||
} else if(plugin_state->is_thread_running && plugin_state->is_ducky_running) {
|
||||
canvas_draw_str_aligned(canvas, 3, 10, AlignLeft, AlignBottom, "Running duckyscript");
|
||||
canvas_draw_str_aligned(canvas, 3, 20, AlignLeft, AlignBottom, "Please wait!");
|
||||
canvas_draw_str_aligned(
|
||||
canvas, 3, 30, AlignLeft, AlignBottom, "Press back to exit (if it stuck)");
|
||||
} else {
|
||||
canvas_draw_str_aligned(canvas, 3, 10, AlignLeft, AlignBottom, "Unknown Error");
|
||||
canvas_draw_str_aligned(canvas, 3, 20, AlignLeft, AlignBottom, "press back");
|
||||
canvas_draw_str_aligned(canvas, 3, 30, AlignLeft, AlignBottom, "to exit");
|
||||
}
|
||||
|
||||
furi_mutex_release(plugin_state->mutex);
|
||||
}
|
||||
|
||||
static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
|
||||
furi_assert(event_queue);
|
||||
|
||||
PluginEvent event = {.type = EventTypeKey, .input = *input_event};
|
||||
furi_message_queue_put(event_queue, &event, FuriWaitForever);
|
||||
}
|
||||
|
||||
static void mousejacker_state_init(PluginState* const plugin_state) {
|
||||
plugin_state->is_thread_running = false;
|
||||
plugin_state->is_nrf24_connected = true;
|
||||
}
|
||||
|
||||
static void hexlify(uint8_t* in, uint8_t size, char* out) {
|
||||
memset(out, 0, size * 2);
|
||||
for(int i = 0; i < size; i++)
|
||||
snprintf(out + strlen(out), sizeof(out + strlen(out)), "%02X", in[i]);
|
||||
}
|
||||
|
||||
static bool open_ducky_script(Stream* stream, PluginState* plugin_state) {
|
||||
DialogsApp* dialogs = furi_record_open("dialogs");
|
||||
bool result = false;
|
||||
FuriString* path;
|
||||
path = furi_string_alloc();
|
||||
furi_string_set(path, BADKB_FOLDER);
|
||||
|
||||
DialogsFileBrowserOptions browser_options;
|
||||
dialog_file_browser_set_basic_options(
|
||||
&browser_options, MOUSEJACKER_APP_PATH_EXTENSION, &I_badkb_10px);
|
||||
browser_options.hide_ext = false;
|
||||
|
||||
bool ret = dialog_file_browser_show(dialogs, path, path, &browser_options);
|
||||
|
||||
furi_record_close("dialogs");
|
||||
if(ret) {
|
||||
if(!file_stream_open(stream, furi_string_get_cstr(path), FSAM_READ, FSOM_OPEN_EXISTING)) {
|
||||
FURI_LOG_D(TAG, "Cannot open file \"%s\"", furi_string_get_cstr(path));
|
||||
} else {
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
furi_string_free(path);
|
||||
|
||||
plugin_state->is_ducky_running = true;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool open_addrs_file(Stream* stream) {
|
||||
bool result = false;
|
||||
FuriString* path;
|
||||
path = furi_string_alloc();
|
||||
furi_string_set(path, NRFSNIFF_APP_PATH_FOLDER_ADDRESSES);
|
||||
|
||||
if(!file_stream_open(stream, furi_string_get_cstr(path), FSAM_READ, FSOM_OPEN_EXISTING)) {
|
||||
FURI_LOG_D(TAG, "Cannot open file \"%s\"", furi_string_get_cstr(path));
|
||||
} else {
|
||||
result = true;
|
||||
}
|
||||
|
||||
furi_string_free(path);
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool process_ducky_file(
|
||||
Stream* file_stream,
|
||||
uint8_t* addr,
|
||||
uint8_t addr_size,
|
||||
uint8_t rate,
|
||||
PluginState* plugin_state) {
|
||||
size_t file_size = 0;
|
||||
size_t bytes_read = 0;
|
||||
uint8_t* file_buf;
|
||||
bool loaded = false;
|
||||
FURI_LOG_D(TAG, "opening ducky script");
|
||||
if(open_ducky_script(file_stream, plugin_state)) {
|
||||
file_size = stream_size(file_stream);
|
||||
if(file_size == (size_t)0) {
|
||||
FURI_LOG_D(TAG, "load failed. file_size: %d", file_size);
|
||||
plugin_state->is_ducky_running = false;
|
||||
return loaded;
|
||||
}
|
||||
file_buf = malloc(file_size);
|
||||
memset(file_buf, 0, file_size);
|
||||
bytes_read = stream_read(file_stream, file_buf, file_size);
|
||||
if(bytes_read == file_size) {
|
||||
FURI_LOG_D(TAG, "executing ducky script");
|
||||
mj_process_ducky_script(
|
||||
nrf24_HANDLE, addr, addr_size, rate, (char*)file_buf, plugin_state);
|
||||
FURI_LOG_D(TAG, "finished execution");
|
||||
loaded = true;
|
||||
} else {
|
||||
FURI_LOG_D(TAG, "load failed. file size: %d", file_size);
|
||||
}
|
||||
free(file_buf);
|
||||
}
|
||||
plugin_state->is_ducky_running = false;
|
||||
return loaded;
|
||||
}
|
||||
|
||||
static bool load_addrs_file(Stream* file_stream) {
|
||||
size_t file_size = 0;
|
||||
size_t bytes_read = 0;
|
||||
uint8_t* file_buf;
|
||||
char* line_ptr;
|
||||
uint8_t rate;
|
||||
uint8_t addrlen = 0;
|
||||
uint32_t counter = 0;
|
||||
uint8_t addr[5] = {0};
|
||||
uint32_t i_addr_lo = 0;
|
||||
uint32_t i_addr_hi = 0;
|
||||
bool loaded = false;
|
||||
FURI_LOG_D(TAG, "opening addrs file");
|
||||
addrs_count = 0;
|
||||
if(open_addrs_file(file_stream)) {
|
||||
file_size = stream_size(file_stream);
|
||||
if(file_size == (size_t)0) {
|
||||
FURI_LOG_D(TAG, "load failed. file_size: %d", file_size);
|
||||
return loaded;
|
||||
}
|
||||
file_buf = malloc(file_size);
|
||||
memset(file_buf, 0, file_size);
|
||||
bytes_read = stream_read(file_stream, file_buf, file_size);
|
||||
if(bytes_read == file_size) {
|
||||
FURI_LOG_D(TAG, "loading addrs file");
|
||||
char* line = strtok((char*)file_buf, "\n");
|
||||
|
||||
while(line != NULL) {
|
||||
line_ptr = strstr((char*)line, ",");
|
||||
*line_ptr = 0;
|
||||
rate = atoi(line_ptr + 1);
|
||||
addrlen = (uint8_t)(strlen(line) / 2);
|
||||
i_addr_lo = strtoul(line + 2, NULL, 16);
|
||||
line[2] = (char)0;
|
||||
i_addr_hi = strtoul(line, NULL, 16);
|
||||
int32_to_bytes(i_addr_lo, &addr[1], true);
|
||||
addr[0] = (uint8_t)(i_addr_hi & 0xFF);
|
||||
memset(loaded_addrs[counter], rate, 1);
|
||||
memcpy(&loaded_addrs[counter++][1], addr, addrlen);
|
||||
addrs_count++;
|
||||
line = strtok(NULL, "\n");
|
||||
loaded = true;
|
||||
}
|
||||
} else {
|
||||
FURI_LOG_D(TAG, "load failed. file size: %d", file_size);
|
||||
}
|
||||
free(file_buf);
|
||||
}
|
||||
return loaded;
|
||||
}
|
||||
|
||||
// entrypoint for worker
|
||||
static int32_t mj_worker_thread(void* ctx) {
|
||||
PluginState* plugin_state = ctx;
|
||||
bool ducky_ok = false;
|
||||
if(!plugin_state->addr_err) {
|
||||
plugin_state->is_thread_running = true;
|
||||
plugin_state->file_stream = file_stream_alloc(plugin_state->storage);
|
||||
nrf24_find_channel(
|
||||
nrf24_HANDLE,
|
||||
loaded_addrs[addr_idx] + 1,
|
||||
loaded_addrs[addr_idx] + 1,
|
||||
5,
|
||||
loaded_addrs[addr_idx][0],
|
||||
2,
|
||||
LOGITECH_MAX_CHANNEL,
|
||||
true);
|
||||
ducky_ok = process_ducky_file(
|
||||
plugin_state->file_stream,
|
||||
loaded_addrs[addr_idx] + 1,
|
||||
5,
|
||||
loaded_addrs[addr_idx][0],
|
||||
plugin_state);
|
||||
if(!ducky_ok) {
|
||||
plugin_state->ducky_err = true;
|
||||
} else {
|
||||
plugin_state->ducky_err = false;
|
||||
}
|
||||
stream_free(plugin_state->file_stream);
|
||||
}
|
||||
plugin_state->is_thread_running = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t mousejacker_app(void* p) {
|
||||
UNUSED(p);
|
||||
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent));
|
||||
|
||||
PluginState* plugin_state = malloc(sizeof(PluginState));
|
||||
mousejacker_state_init(plugin_state);
|
||||
plugin_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
||||
if(!plugin_state->mutex) {
|
||||
FURI_LOG_E("mousejacker", "cannot create mutex\r\n");
|
||||
furi_message_queue_free(event_queue);
|
||||
free(plugin_state);
|
||||
return 255;
|
||||
}
|
||||
|
||||
uint8_t attempts = 0;
|
||||
while(!furi_hal_power_is_otg_enabled() && attempts++ < 5) {
|
||||
furi_hal_power_enable_otg();
|
||||
furi_delay_ms(10);
|
||||
}
|
||||
|
||||
NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
|
||||
|
||||
// Set system callbacks
|
||||
ViewPort* view_port = view_port_alloc();
|
||||
view_port_draw_callback_set(view_port, render_callback, plugin_state);
|
||||
view_port_input_callback_set(view_port, 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);
|
||||
|
||||
plugin_state->storage = furi_record_open(RECORD_STORAGE);
|
||||
plugin_state->file_stream = file_stream_alloc(plugin_state->storage);
|
||||
|
||||
plugin_state->mjthread = furi_thread_alloc();
|
||||
furi_thread_set_name(plugin_state->mjthread, "MJ Worker");
|
||||
furi_thread_set_stack_size(plugin_state->mjthread, 2048);
|
||||
furi_thread_set_context(plugin_state->mjthread, plugin_state);
|
||||
furi_thread_set_callback(plugin_state->mjthread, mj_worker_thread);
|
||||
|
||||
// spawn load file dialog to choose sniffed addresses file
|
||||
if(load_addrs_file(plugin_state->file_stream)) {
|
||||
addr_idx = 0;
|
||||
hexlify(&loaded_addrs[addr_idx][1], 5, target_address_str);
|
||||
plugin_state->addr_err = false;
|
||||
} else {
|
||||
plugin_state->addr_err = true;
|
||||
}
|
||||
stream_free(plugin_state->file_stream);
|
||||
nrf24_init();
|
||||
|
||||
PluginEvent event;
|
||||
for(bool processing = true; processing;) {
|
||||
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
|
||||
furi_mutex_acquire(plugin_state->mutex, FuriWaitForever);
|
||||
|
||||
if(event_status == FuriStatusOk) {
|
||||
// press events
|
||||
if(event.type == EventTypeKey) {
|
||||
if(event.input.type == InputTypePress) {
|
||||
switch(event.input.key) {
|
||||
case InputKeyUp:
|
||||
break;
|
||||
case InputKeyDown:
|
||||
break;
|
||||
case InputKeyRight:
|
||||
if(!plugin_state->addr_err) {
|
||||
addr_idx++;
|
||||
if(addr_idx >= addrs_count) addr_idx = 0;
|
||||
hexlify(loaded_addrs[addr_idx] + 1, 5, target_address_str);
|
||||
}
|
||||
break;
|
||||
case InputKeyLeft:
|
||||
if(!plugin_state->addr_err) {
|
||||
addr_idx--;
|
||||
if(addr_idx < 0) addr_idx = addrs_count - 1;
|
||||
hexlify(loaded_addrs[addr_idx] + 1, 5, target_address_str);
|
||||
}
|
||||
break;
|
||||
case InputKeyOk:
|
||||
if(!plugin_state->addr_err) {
|
||||
if(!nrf24_check_connected(nrf24_HANDLE)) {
|
||||
plugin_state->is_nrf24_connected = false;
|
||||
view_port_update(view_port);
|
||||
notification_message(notification, &sequence_error);
|
||||
} else if(!plugin_state->is_thread_running) {
|
||||
furi_thread_start(plugin_state->mjthread);
|
||||
view_port_update(view_port);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case InputKeyBack:
|
||||
plugin_state->close_thread_please = true;
|
||||
if(plugin_state->is_thread_running && plugin_state->mjthread) {
|
||||
furi_thread_join(
|
||||
plugin_state->mjthread); // wait until thread is finished
|
||||
}
|
||||
plugin_state->close_thread_please = false;
|
||||
processing = false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
view_port_update(view_port);
|
||||
furi_mutex_release(plugin_state->mutex);
|
||||
}
|
||||
|
||||
furi_thread_free(plugin_state->mjthread);
|
||||
nrf24_deinit();
|
||||
view_port_enabled_set(view_port, false);
|
||||
gui_remove_view_port(gui, view_port);
|
||||
furi_record_close(RECORD_NOTIFICATION);
|
||||
furi_record_close(RECORD_GUI);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
view_port_free(view_port);
|
||||
furi_message_queue_free(event_queue);
|
||||
furi_mutex_free(plugin_state->mutex);
|
||||
free(plugin_state);
|
||||
|
||||
if(furi_hal_power_is_otg_enabled()) {
|
||||
furi_hal_power_disable_otg();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,460 +0,0 @@
|
||||
#include "mousejacker_ducky.h"
|
||||
|
||||
static const char ducky_cmd_comment[] = {"REM"};
|
||||
static const char ducky_cmd_delay[] = {"DELAY "};
|
||||
static const char ducky_cmd_string[] = {"STRING "};
|
||||
static const char ducky_cmd_altstring[] = {"ALTSTRING "};
|
||||
static const char ducky_cmd_repeat[] = {"REPEAT "};
|
||||
|
||||
static uint8_t LOGITECH_HID_TEMPLATE[] =
|
||||
{0x00, 0xC1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
static uint8_t LOGITECH_HELLO[] = {0x00, 0x4F, 0x00, 0x04, 0xB0, 0x10, 0x00, 0x00, 0x00, 0xED};
|
||||
static uint8_t LOGITECH_KEEPALIVE[] = {0x00, 0x40, 0x00, 0x55, 0x6B};
|
||||
|
||||
uint8_t prev_hid = 0;
|
||||
static bool holding_ctrl = false;
|
||||
static bool holding_shift = false;
|
||||
static bool holding_alt = false;
|
||||
static bool holding_gui = false;
|
||||
|
||||
#define RT_THRESHOLD 50
|
||||
#define LOGITECH_MIN_CHANNEL 2
|
||||
#define LOGITECH_MAX_CHANNEL 83
|
||||
#define LOGITECH_KEEPALIVE_SIZE 5
|
||||
#define LOGITECH_HID_TEMPLATE_SIZE 10
|
||||
#define LOGITECH_HELLO_SIZE 10
|
||||
#define TAG "mousejacker_ducky"
|
||||
|
||||
MJDuckyKey mj_ducky_keys[] = {{" ", 44, 0}, {"!", 30, 2}, {"\"", 52, 2},
|
||||
{"#", 32, 2}, {"$", 33, 2}, {"%", 34, 2},
|
||||
{"&", 36, 2}, {"'", 52, 0}, {"(", 38, 2},
|
||||
{")", 39, 2}, {"*", 37, 2}, {"+", 46, 2},
|
||||
{",", 54, 0}, {"-", 45, 0}, {".", 55, 0},
|
||||
{"/", 56, 0}, {"0", 39, 0}, {"1", 30, 0},
|
||||
{"2", 31, 0}, {"3", 32, 0}, {"4", 33, 0},
|
||||
{"5", 34, 0}, {"6", 35, 0}, {"7", 36, 0},
|
||||
{"8", 37, 0}, {"9", 38, 0}, {":", 51, 2},
|
||||
{";", 51, 0}, {"<", 54, 2}, {"=", 46, 0},
|
||||
{">", 55, 2}, {"?", 56, 2}, {"@", 31, 2},
|
||||
{"A", 4, 2}, {"B", 5, 2}, {"C", 6, 2},
|
||||
{"D", 7, 2}, {"E", 8, 2}, {"F", 9, 2},
|
||||
{"G", 10, 2}, {"H", 11, 2}, {"I", 12, 2},
|
||||
{"J", 13, 2}, {"K", 14, 2}, {"L", 15, 2},
|
||||
{"M", 16, 2}, {"N", 17, 2}, {"O", 18, 2},
|
||||
{"P", 19, 2}, {"Q", 20, 2}, {"R", 21, 2},
|
||||
{"S", 22, 2}, {"T", 23, 2}, {"U", 24, 2},
|
||||
{"V", 25, 2}, {"W", 26, 2}, {"X", 27, 2},
|
||||
{"Y", 28, 2}, {"Z", 29, 2}, {"[", 47, 0},
|
||||
{"\\", 49, 0}, {"]", 48, 0}, {"^", 35, 2},
|
||||
{"_", 45, 2}, {"`", 53, 0}, {"a", 4, 0},
|
||||
{"b", 5, 0}, {"c", 6, 0}, {"d", 7, 0},
|
||||
{"e", 8, 0}, {"f", 9, 0}, {"g", 10, 0},
|
||||
{"h", 11, 0}, {"i", 12, 0}, {"j", 13, 0},
|
||||
{"k", 14, 0}, {"l", 15, 0}, {"m", 16, 0},
|
||||
{"n", 17, 0}, {"o", 18, 0}, {"p", 19, 0},
|
||||
{"q", 20, 0}, {"r", 21, 0}, {"s", 22, 0},
|
||||
{"t", 23, 0}, {"u", 24, 0}, {"v", 25, 0},
|
||||
{"w", 26, 0}, {"x", 27, 0}, {"y", 28, 0},
|
||||
{"z", 29, 0}, {"{", 47, 2}, {"|", 49, 2},
|
||||
{"}", 48, 2}, {"~", 53, 2}, {"BACKSPACE", 42, 0},
|
||||
{"", 0, 0}, {"ALT", 0, 4}, {"SHIFT", 0, 2},
|
||||
{"CTRL", 0, 1}, {"GUI", 0, 8}, {"SCROLLLOCK", 71, 0},
|
||||
{"ENTER", 40, 0}, {"F12", 69, 0}, {"HOME", 74, 0},
|
||||
{"F10", 67, 0}, {"F9", 66, 0}, {"ESCAPE", 41, 0},
|
||||
{"PAGEUP", 75, 0}, {"TAB", 43, 0}, {"PRINTSCREEN", 70, 0},
|
||||
{"F2", 59, 0}, {"CAPSLOCK", 57, 0}, {"F1", 58, 0},
|
||||
{"F4", 61, 0}, {"F6", 63, 0}, {"F8", 65, 0},
|
||||
{"DOWNARROW", 81, 0}, {"DELETE", 42, 0}, {"RIGHT", 79, 0},
|
||||
{"F3", 60, 0}, {"DOWN", 81, 0}, {"DEL", 76, 0},
|
||||
{"END", 77, 0}, {"INSERT", 73, 0}, {"F5", 62, 0},
|
||||
{"LEFTARROW", 80, 0}, {"RIGHTARROW", 79, 0}, {"PAGEDOWN", 78, 0},
|
||||
{"PAUSE", 72, 0}, {"SPACE", 44, 0}, {"UPARROW", 82, 0},
|
||||
{"F11", 68, 0}, {"F7", 64, 0}, {"UP", 82, 0},
|
||||
{"LEFT", 80, 0}, {"NUM 1", 89, 0}, {"NUM 2", 90, 0},
|
||||
{"NUM 3", 91, 0}, {"NUM 4", 92, 0}, {"NUM 5", 93, 0},
|
||||
{"NUM 6", 94, 0}, {"NUM 7", 95, 0}, {"NUM 8", 96, 0},
|
||||
{"NUM 9", 97, 0}, {"NUM 0", 98, 0}};
|
||||
|
||||
/*
|
||||
static bool mj_ducky_get_number(const char* param, uint32_t* val) {
|
||||
uint32_t value = 0;
|
||||
if(sscanf(param, "%lu", &value) == 1) {
|
||||
*val = value;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
*/
|
||||
|
||||
static uint32_t mj_ducky_get_command_len(const char* line) {
|
||||
uint32_t len = strlen(line);
|
||||
for(uint32_t i = 0; i < len; i++) {
|
||||
if(line[i] == ' ') return i;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool mj_get_ducky_key(char* key, size_t keylen, MJDuckyKey* dk) {
|
||||
//FURI_LOG_D(TAG, "looking up key %s with length %d", key, keylen);
|
||||
for(uint i = 0; i < sizeof(mj_ducky_keys) / sizeof(MJDuckyKey); i++) {
|
||||
if(strlen(mj_ducky_keys[i].name) == keylen &&
|
||||
!strncmp(mj_ducky_keys[i].name, key, keylen)) {
|
||||
memcpy(dk, &mj_ducky_keys[i], sizeof(MJDuckyKey));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void checksum(uint8_t* payload, uint len) {
|
||||
// This is also from the KeyKeriki paper
|
||||
// Thanks Thorsten and Max!
|
||||
uint8_t cksum = 0xff;
|
||||
for(uint n = 0; n < len - 2; n++) cksum = (cksum - payload[n]) & 0xff;
|
||||
cksum = (cksum + 1) & 0xff;
|
||||
payload[len - 1] = cksum;
|
||||
}
|
||||
|
||||
static void inject_packet(
|
||||
FuriHalSpiBusHandle* handle,
|
||||
uint8_t* addr,
|
||||
uint8_t addr_size,
|
||||
uint8_t rate,
|
||||
uint8_t* payload,
|
||||
size_t payload_size,
|
||||
PluginState* plugin_state) {
|
||||
uint8_t rt_count = 0;
|
||||
while(1) {
|
||||
if(!plugin_state->is_thread_running || plugin_state->close_thread_please) {
|
||||
return;
|
||||
}
|
||||
if(nrf24_txpacket(handle, payload, payload_size, true)) {
|
||||
break;
|
||||
}
|
||||
|
||||
rt_count++;
|
||||
// retransmit threshold exceeded, scan for new channel
|
||||
if(rt_count > RT_THRESHOLD) {
|
||||
if(nrf24_find_channel(
|
||||
handle,
|
||||
addr,
|
||||
addr,
|
||||
addr_size,
|
||||
rate,
|
||||
LOGITECH_MIN_CHANNEL,
|
||||
LOGITECH_MAX_CHANNEL,
|
||||
true) > LOGITECH_MAX_CHANNEL) {
|
||||
return; // fail
|
||||
}
|
||||
//FURI_LOG_D("mj", "find channel passed, %d", tessst);
|
||||
|
||||
rt_count = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void build_hid_packet(uint8_t mod, uint8_t hid, uint8_t* payload) {
|
||||
memcpy(payload, LOGITECH_HID_TEMPLATE, LOGITECH_HID_TEMPLATE_SIZE);
|
||||
payload[2] = mod;
|
||||
payload[3] = hid;
|
||||
checksum(payload, LOGITECH_HID_TEMPLATE_SIZE);
|
||||
}
|
||||
|
||||
static void release_key(
|
||||
FuriHalSpiBusHandle* handle,
|
||||
uint8_t* addr,
|
||||
uint8_t addr_size,
|
||||
uint8_t rate,
|
||||
PluginState* plugin_state) {
|
||||
// This function release keys currently pressed, but keep pressing special keys
|
||||
// if holding mod keys variable are set to true
|
||||
|
||||
uint8_t hid_payload[LOGITECH_HID_TEMPLATE_SIZE] = {0};
|
||||
build_hid_packet(
|
||||
0 | holding_ctrl | holding_shift << 1 | holding_alt << 2 | holding_gui << 3,
|
||||
0,
|
||||
hid_payload);
|
||||
inject_packet(
|
||||
handle,
|
||||
addr,
|
||||
addr_size,
|
||||
rate,
|
||||
hid_payload,
|
||||
LOGITECH_HID_TEMPLATE_SIZE,
|
||||
plugin_state); // empty hid packet
|
||||
}
|
||||
|
||||
static void send_hid_packet(
|
||||
FuriHalSpiBusHandle* handle,
|
||||
uint8_t* addr,
|
||||
uint8_t addr_size,
|
||||
uint8_t rate,
|
||||
uint8_t mod,
|
||||
uint8_t hid,
|
||||
PluginState* plugin_state) {
|
||||
uint8_t hid_payload[LOGITECH_HID_TEMPLATE_SIZE] = {0};
|
||||
if(hid == prev_hid) release_key(handle, addr, addr_size, rate, plugin_state);
|
||||
|
||||
prev_hid = hid;
|
||||
build_hid_packet(
|
||||
mod | holding_ctrl | holding_shift << 1 | holding_alt << 2 | holding_gui << 3,
|
||||
hid,
|
||||
hid_payload);
|
||||
inject_packet(
|
||||
handle, addr, addr_size, rate, hid_payload, LOGITECH_HID_TEMPLATE_SIZE, plugin_state);
|
||||
furi_delay_ms(12);
|
||||
}
|
||||
|
||||
static bool ducky_end_line(const char chr) {
|
||||
return ((chr == ' ') || (chr == '\0') || (chr == '\r') || (chr == '\n'));
|
||||
}
|
||||
|
||||
// returns false if there was an error processing script line
|
||||
static bool mj_process_ducky_line(
|
||||
FuriHalSpiBusHandle* handle,
|
||||
uint8_t* addr,
|
||||
uint8_t addr_size,
|
||||
uint8_t rate,
|
||||
char* line,
|
||||
char* prev_line,
|
||||
PluginState* plugin_state) {
|
||||
MJDuckyKey dk;
|
||||
uint8_t hid_payload[LOGITECH_HID_TEMPLATE_SIZE] = {0};
|
||||
char* line_tmp = line;
|
||||
uint32_t line_len = strlen(line);
|
||||
if(!plugin_state->is_thread_running || plugin_state->close_thread_please) {
|
||||
return true;
|
||||
}
|
||||
for(uint32_t i = 0; i < line_len; i++) {
|
||||
if((line_tmp[i] != ' ') && (line_tmp[i] != '\t') && (line_tmp[i] != '\n')) {
|
||||
line_tmp = &line_tmp[i];
|
||||
break; // Skip spaces and tabs
|
||||
}
|
||||
if(i == line_len - 1) return true; // Skip empty lines
|
||||
}
|
||||
|
||||
FURI_LOG_D(TAG, "line: %s", line_tmp);
|
||||
|
||||
// General commands
|
||||
if(strncmp(line_tmp, ducky_cmd_comment, strlen(ducky_cmd_comment)) == 0) {
|
||||
// REM - comment line
|
||||
return true;
|
||||
} else if(strncmp(line_tmp, ducky_cmd_delay, strlen(ducky_cmd_delay)) == 0) {
|
||||
// DELAY
|
||||
line_tmp = &line_tmp[mj_ducky_get_command_len(line_tmp) + 1];
|
||||
uint32_t delay_val = 0;
|
||||
delay_val = atoi(line_tmp);
|
||||
if(delay_val > 0) {
|
||||
uint32_t delay_count = delay_val / 10;
|
||||
build_hid_packet(0, 0, hid_payload);
|
||||
inject_packet(
|
||||
handle,
|
||||
addr,
|
||||
addr_size,
|
||||
rate,
|
||||
hid_payload,
|
||||
LOGITECH_HID_TEMPLATE_SIZE,
|
||||
plugin_state); // empty hid packet
|
||||
for(uint32_t i = 0; i < delay_count; i++) {
|
||||
if(!plugin_state->is_thread_running || plugin_state->close_thread_please) {
|
||||
return true;
|
||||
}
|
||||
inject_packet(
|
||||
handle,
|
||||
addr,
|
||||
addr_size,
|
||||
rate,
|
||||
LOGITECH_KEEPALIVE,
|
||||
LOGITECH_KEEPALIVE_SIZE,
|
||||
plugin_state);
|
||||
furi_delay_ms(10);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} else if(strncmp(line_tmp, ducky_cmd_string, strlen(ducky_cmd_string)) == 0) {
|
||||
// STRING
|
||||
line_tmp = &line_tmp[mj_ducky_get_command_len(line_tmp) + 1];
|
||||
for(size_t i = 0; i < strlen(line_tmp); i++) {
|
||||
if(!mj_get_ducky_key(&line_tmp[i], 1, &dk)) return false;
|
||||
|
||||
send_hid_packet(handle, addr, addr_size, rate, dk.mod, dk.hid, plugin_state);
|
||||
}
|
||||
|
||||
return true;
|
||||
} else if(strncmp(line_tmp, ducky_cmd_altstring, strlen(ducky_cmd_altstring)) == 0) {
|
||||
// ALTSTRING
|
||||
line_tmp = &line_tmp[mj_ducky_get_command_len(line_tmp) + 1];
|
||||
for(size_t i = 0; i < strlen(line_tmp); i++) {
|
||||
if((line_tmp[i] < ' ') || (line_tmp[i] > '~')) {
|
||||
continue; // Skip non-printable chars
|
||||
}
|
||||
|
||||
char alt_code[4];
|
||||
// Getting altcode of the char
|
||||
snprintf(alt_code, 4, "%u", line_tmp[i]);
|
||||
|
||||
uint8_t j = 0;
|
||||
while(!ducky_end_line(alt_code[j])) {
|
||||
char pad_num[5] = {'N', 'U', 'M', ' ', alt_code[j]};
|
||||
if(!mj_get_ducky_key(pad_num, 5, &dk)) return false;
|
||||
holding_alt = true;
|
||||
FURI_LOG_D(TAG, "Sending %s", pad_num);
|
||||
send_hid_packet(handle, addr, addr_size, rate, dk.mod, dk.hid, plugin_state);
|
||||
j++;
|
||||
}
|
||||
holding_alt = false;
|
||||
release_key(handle, addr, addr_size, rate, plugin_state);
|
||||
}
|
||||
|
||||
return true;
|
||||
} else if(strncmp(line_tmp, ducky_cmd_repeat, strlen(ducky_cmd_repeat)) == 0) {
|
||||
// REPEAT
|
||||
uint32_t repeat_cnt = 0;
|
||||
if(prev_line == NULL) return false;
|
||||
|
||||
line_tmp = &line_tmp[mj_ducky_get_command_len(line_tmp) + 1];
|
||||
repeat_cnt = atoi(line_tmp);
|
||||
if(repeat_cnt < 2) return false;
|
||||
|
||||
FURI_LOG_D(TAG, "repeating %s %ld times", prev_line, repeat_cnt);
|
||||
for(uint32_t i = 0; i < repeat_cnt; i++)
|
||||
mj_process_ducky_line(handle, addr, addr_size, rate, prev_line, NULL, plugin_state);
|
||||
|
||||
return true;
|
||||
} else if(strncmp(line_tmp, "ALT", strlen("ALT")) == 0) {
|
||||
line_tmp = &line_tmp[mj_ducky_get_command_len(line_tmp) + 1];
|
||||
if(!mj_get_ducky_key(line_tmp, strlen(line_tmp), &dk)) return false;
|
||||
holding_alt = true;
|
||||
send_hid_packet(handle, addr, addr_size, rate, dk.mod, dk.hid, plugin_state);
|
||||
holding_alt = false;
|
||||
return true;
|
||||
} else if(
|
||||
strncmp(line_tmp, "GUI", strlen("GUI")) == 0 ||
|
||||
strncmp(line_tmp, "WINDOWS", strlen("WINDOWS")) == 0 ||
|
||||
strncmp(line_tmp, "COMMAND", strlen("COMMAND")) == 0) {
|
||||
line_tmp = &line_tmp[mj_ducky_get_command_len(line_tmp) + 1];
|
||||
if(!mj_get_ducky_key(line_tmp, strlen(line_tmp), &dk)) return false;
|
||||
holding_gui = true;
|
||||
send_hid_packet(handle, addr, addr_size, rate, dk.mod, dk.hid, plugin_state);
|
||||
holding_gui = false;
|
||||
return true;
|
||||
} else if(
|
||||
strncmp(line_tmp, "CTRL-ALT", strlen("CTRL-ALT")) == 0 ||
|
||||
strncmp(line_tmp, "CONTROL-ALT", strlen("CONTROL-ALT")) == 0) {
|
||||
line_tmp = &line_tmp[mj_ducky_get_command_len(line_tmp) + 1];
|
||||
if(!mj_get_ducky_key(line_tmp, strlen(line_tmp), &dk)) return false;
|
||||
holding_ctrl = true;
|
||||
holding_alt = true;
|
||||
send_hid_packet(handle, addr, addr_size, rate, dk.mod, dk.hid, plugin_state);
|
||||
holding_ctrl = false;
|
||||
holding_alt = false;
|
||||
return true;
|
||||
} else if(
|
||||
strncmp(line_tmp, "CTRL-SHIFT", strlen("CTRL-SHIFT")) == 0 ||
|
||||
strncmp(line_tmp, "CONTROL-SHIFT", strlen("CONTROL-SHIFT")) == 0) {
|
||||
line_tmp = &line_tmp[mj_ducky_get_command_len(line_tmp) + 1];
|
||||
if(!mj_get_ducky_key(line_tmp, strlen(line_tmp), &dk)) return false;
|
||||
holding_ctrl = true;
|
||||
holding_shift = true;
|
||||
send_hid_packet(handle, addr, addr_size, rate, dk.mod, dk.hid, plugin_state);
|
||||
holding_ctrl = false;
|
||||
holding_shift = false;
|
||||
return true;
|
||||
} else if(
|
||||
strncmp(line_tmp, "CTRL", strlen("CTRL")) == 0 ||
|
||||
strncmp(line_tmp, "CONTROL", strlen("CONTROL")) == 0) {
|
||||
line_tmp = &line_tmp[mj_ducky_get_command_len(line_tmp) + 1];
|
||||
if(!mj_get_ducky_key(line_tmp, strlen(line_tmp), &dk)) return false;
|
||||
holding_ctrl = true;
|
||||
send_hid_packet(handle, addr, addr_size, rate, dk.mod, dk.hid, plugin_state);
|
||||
holding_ctrl = false;
|
||||
return true;
|
||||
} else if(strncmp(line_tmp, "SHIFT", strlen("SHIFT")) == 0) {
|
||||
line_tmp = &line_tmp[mj_ducky_get_command_len(line_tmp) + 1];
|
||||
if(!mj_get_ducky_key(line_tmp, strlen(line_tmp), &dk)) return false;
|
||||
holding_shift = true;
|
||||
send_hid_packet(handle, addr, addr_size, rate, dk.mod, dk.hid, plugin_state);
|
||||
holding_shift = false;
|
||||
return true;
|
||||
} else if(
|
||||
strncmp(line_tmp, "ESC", strlen("ESC")) == 0 ||
|
||||
strncmp(line_tmp, "APP", strlen("APP")) == 0 ||
|
||||
strncmp(line_tmp, "ESCAPE", strlen("ESCAPE")) == 0) {
|
||||
if(!mj_get_ducky_key("ESCAPE", 6, &dk)) return false;
|
||||
send_hid_packet(handle, addr, addr_size, rate, dk.mod, dk.hid, plugin_state);
|
||||
return true;
|
||||
} else if(strncmp(line_tmp, "ENTER", strlen("ENTER")) == 0) {
|
||||
if(!mj_get_ducky_key("ENTER", 5, &dk)) return false;
|
||||
send_hid_packet(handle, addr, addr_size, rate, dk.mod, dk.hid, plugin_state);
|
||||
return true;
|
||||
} else if(
|
||||
strncmp(line_tmp, "UP", strlen("UP")) == 0 ||
|
||||
strncmp(line_tmp, "UPARROW", strlen("UPARROW")) == 0) {
|
||||
if(!mj_get_ducky_key("UP", 2, &dk)) return false;
|
||||
send_hid_packet(handle, addr, addr_size, rate, dk.mod, dk.hid, plugin_state);
|
||||
return true;
|
||||
} else if(
|
||||
strncmp(line_tmp, "DOWN", strlen("DOWN")) == 0 ||
|
||||
strncmp(line_tmp, "DOWNARROW", strlen("DOWNARROW")) == 0) {
|
||||
if(!mj_get_ducky_key("DOWN", 4, &dk)) return false;
|
||||
send_hid_packet(handle, addr, addr_size, rate, dk.mod, dk.hid, plugin_state);
|
||||
return true;
|
||||
} else if(
|
||||
strncmp(line_tmp, "LEFT", strlen("LEFT")) == 0 ||
|
||||
strncmp(line_tmp, "LEFTARROW", strlen("LEFTARROW")) == 0) {
|
||||
if(!mj_get_ducky_key("LEFT", 4, &dk)) return false;
|
||||
send_hid_packet(handle, addr, addr_size, rate, dk.mod, dk.hid, plugin_state);
|
||||
return true;
|
||||
} else if(
|
||||
strncmp(line_tmp, "RIGHT", strlen("RIGHT")) == 0 ||
|
||||
strncmp(line_tmp, "RIGHTARROW", strlen("RIGHTARROW")) == 0) {
|
||||
if(!mj_get_ducky_key("RIGHT", 5, &dk)) return false;
|
||||
send_hid_packet(handle, addr, addr_size, rate, dk.mod, dk.hid, plugin_state);
|
||||
return true;
|
||||
} else if(strncmp(line_tmp, "SPACE", strlen("SPACE")) == 0) {
|
||||
if(!mj_get_ducky_key("SPACE", 5, &dk)) return false;
|
||||
send_hid_packet(handle, addr, addr_size, rate, dk.mod, dk.hid, plugin_state);
|
||||
return true;
|
||||
} else if(strncmp(line_tmp, "TAB", strlen("TAB")) == 0) {
|
||||
if(!mj_get_ducky_key("TAB", 3, &dk)) return false;
|
||||
send_hid_packet(handle, addr, addr_size, rate, dk.mod, dk.hid, plugin_state);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void mj_process_ducky_script(
|
||||
FuriHalSpiBusHandle* handle,
|
||||
uint8_t* addr,
|
||||
uint8_t addr_size,
|
||||
uint8_t rate,
|
||||
char* script,
|
||||
PluginState* plugin_state) {
|
||||
uint8_t hid_payload[LOGITECH_HID_TEMPLATE_SIZE] = {0};
|
||||
char* prev_line = NULL;
|
||||
|
||||
inject_packet(
|
||||
handle, addr, addr_size, rate, LOGITECH_HELLO, LOGITECH_HELLO_SIZE, plugin_state);
|
||||
char* line = strtok(script, "\n");
|
||||
while(line != NULL) {
|
||||
if(strcmp(&line[strlen(line) - 1], "\r") == 0) line[strlen(line) - 1] = (char)0;
|
||||
|
||||
if(!mj_process_ducky_line(handle, addr, addr_size, rate, line, prev_line, plugin_state))
|
||||
FURI_LOG_D(TAG, "unable to process ducky script line: %s", line);
|
||||
|
||||
prev_line = line;
|
||||
line = strtok(NULL, "\n");
|
||||
}
|
||||
build_hid_packet(0, 0, hid_payload);
|
||||
inject_packet(
|
||||
handle,
|
||||
addr,
|
||||
addr_size,
|
||||
rate,
|
||||
hid_payload,
|
||||
LOGITECH_HID_TEMPLATE_SIZE,
|
||||
plugin_state); // empty hid packet at end
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
#pragma once
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <furi_hal_spi.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <nrf24.h>
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <toolbox/stream/file_stream.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
char* name;
|
||||
uint8_t hid;
|
||||
uint8_t mod;
|
||||
} MJDuckyKey;
|
||||
|
||||
typedef struct {
|
||||
FuriMutex* mutex;
|
||||
bool ducky_err;
|
||||
bool addr_err;
|
||||
bool is_thread_running;
|
||||
bool is_ducky_running;
|
||||
bool is_nrf24_connected;
|
||||
bool close_thread_please;
|
||||
Storage* storage;
|
||||
FuriThread* mjthread;
|
||||
Stream* file_stream;
|
||||
} PluginState;
|
||||
|
||||
void mj_process_ducky_script(
|
||||
FuriHalSpiBusHandle* handle,
|
||||
uint8_t* addr,
|
||||
uint8_t addr_size,
|
||||
uint8_t rate,
|
||||
char* script,
|
||||
PluginState* plugin_state);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,21 +0,0 @@
|
||||
App(
|
||||
appid="nrf24sniff",
|
||||
name="[NRF24] Sniffer",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="nrfsniff_app",
|
||||
requires=["gui"],
|
||||
stack_size=2 * 1024,
|
||||
fap_icon="nrfsniff_10px.png",
|
||||
fap_category="GPIO",
|
||||
fap_author="@mothball187 & @xMasterX",
|
||||
fap_version="1.0",
|
||||
fap_description="App captures addresses to use with NRF24 Mouse Jacker app to perform mousejack attacks",
|
||||
fap_private_libs=[
|
||||
Lib(
|
||||
name="nrf24",
|
||||
sources=[
|
||||
"nrf24.c",
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -1,546 +0,0 @@
|
||||
#include "nrf24.h"
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <furi_hal_resources.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
void nrf24_init() {
|
||||
// this is needed if multiple SPI devices are connected to the same bus but with different CS pins
|
||||
if(xtreme_settings.spi_nrf24_handle == SpiDefault) {
|
||||
furi_hal_gpio_init_simple(&gpio_ext_pc3, GpioModeOutputPushPull);
|
||||
furi_hal_gpio_write(&gpio_ext_pc3, true);
|
||||
} else if(xtreme_settings.spi_nrf24_handle == SpiExtra) {
|
||||
furi_hal_gpio_init_simple(&gpio_ext_pa4, GpioModeOutputPushPull);
|
||||
furi_hal_gpio_write(&gpio_ext_pa4, true);
|
||||
}
|
||||
|
||||
furi_hal_spi_bus_handle_init(nrf24_HANDLE);
|
||||
furi_hal_spi_acquire(nrf24_HANDLE);
|
||||
furi_hal_gpio_init(nrf24_CE_PIN, GpioModeOutputPushPull, GpioPullUp, GpioSpeedVeryHigh);
|
||||
furi_hal_gpio_write(nrf24_CE_PIN, false);
|
||||
}
|
||||
|
||||
void nrf24_deinit() {
|
||||
furi_hal_spi_release(nrf24_HANDLE);
|
||||
furi_hal_spi_bus_handle_deinit(nrf24_HANDLE);
|
||||
furi_hal_gpio_write(nrf24_CE_PIN, false);
|
||||
furi_hal_gpio_init(nrf24_CE_PIN, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
|
||||
|
||||
// resetting the CS pins to floating
|
||||
if(xtreme_settings.spi_nrf24_handle == SpiDefault) {
|
||||
furi_hal_gpio_init_simple(&gpio_ext_pc3, GpioModeAnalog);
|
||||
} else if(xtreme_settings.spi_nrf24_handle == SpiExtra) {
|
||||
furi_hal_gpio_init_simple(&gpio_ext_pa4, GpioModeAnalog);
|
||||
}
|
||||
}
|
||||
|
||||
void nrf24_spi_trx(
|
||||
FuriHalSpiBusHandle* handle,
|
||||
uint8_t* tx,
|
||||
uint8_t* rx,
|
||||
uint8_t size,
|
||||
uint32_t timeout) {
|
||||
UNUSED(timeout);
|
||||
furi_hal_gpio_write(handle->cs, false);
|
||||
furi_hal_spi_bus_trx(handle, tx, rx, size, nrf24_TIMEOUT);
|
||||
furi_hal_gpio_write(handle->cs, true);
|
||||
}
|
||||
|
||||
uint8_t nrf24_write_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t data) {
|
||||
uint8_t tx[2] = {W_REGISTER | (REGISTER_MASK & reg), data};
|
||||
uint8_t rx[2] = {0};
|
||||
nrf24_spi_trx(handle, tx, rx, 2, nrf24_TIMEOUT);
|
||||
return rx[0];
|
||||
}
|
||||
|
||||
uint8_t
|
||||
nrf24_write_buf_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* data, uint8_t size) {
|
||||
uint8_t tx[size + 1];
|
||||
uint8_t rx[size + 1];
|
||||
memset(rx, 0, size + 1);
|
||||
tx[0] = W_REGISTER | (REGISTER_MASK & reg);
|
||||
memcpy(&tx[1], data, size);
|
||||
nrf24_spi_trx(handle, tx, rx, size + 1, nrf24_TIMEOUT);
|
||||
return rx[0];
|
||||
}
|
||||
|
||||
uint8_t nrf24_read_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* data, uint8_t size) {
|
||||
uint8_t tx[size + 1];
|
||||
uint8_t rx[size + 1];
|
||||
memset(rx, 0, size + 1);
|
||||
tx[0] = R_REGISTER | (REGISTER_MASK & reg);
|
||||
memset(&tx[1], 0, size);
|
||||
nrf24_spi_trx(handle, tx, rx, size + 1, nrf24_TIMEOUT);
|
||||
memcpy(data, &rx[1], size);
|
||||
return rx[0];
|
||||
}
|
||||
|
||||
uint8_t nrf24_flush_rx(FuriHalSpiBusHandle* handle) {
|
||||
uint8_t tx[] = {FLUSH_RX};
|
||||
uint8_t rx[] = {0};
|
||||
nrf24_spi_trx(handle, tx, rx, 1, nrf24_TIMEOUT);
|
||||
return rx[0];
|
||||
}
|
||||
|
||||
uint8_t nrf24_flush_tx(FuriHalSpiBusHandle* handle) {
|
||||
uint8_t tx[] = {FLUSH_TX};
|
||||
uint8_t rx[] = {0};
|
||||
nrf24_spi_trx(handle, tx, rx, 1, nrf24_TIMEOUT);
|
||||
return rx[0];
|
||||
}
|
||||
|
||||
uint8_t nrf24_get_maclen(FuriHalSpiBusHandle* handle) {
|
||||
uint8_t maclen;
|
||||
nrf24_read_reg(handle, REG_SETUP_AW, &maclen, 1);
|
||||
maclen &= 3;
|
||||
return maclen + 2;
|
||||
}
|
||||
|
||||
uint8_t nrf24_set_maclen(FuriHalSpiBusHandle* handle, uint8_t maclen) {
|
||||
assert(maclen > 1 && maclen < 6);
|
||||
uint8_t status = 0;
|
||||
status = nrf24_write_reg(handle, REG_SETUP_AW, maclen - 2);
|
||||
return status;
|
||||
}
|
||||
|
||||
uint8_t nrf24_status(FuriHalSpiBusHandle* handle) {
|
||||
uint8_t status;
|
||||
uint8_t tx[] = {R_REGISTER | (REGISTER_MASK & REG_STATUS)};
|
||||
nrf24_spi_trx(handle, tx, &status, 1, nrf24_TIMEOUT);
|
||||
return status;
|
||||
}
|
||||
|
||||
uint32_t nrf24_get_rate(FuriHalSpiBusHandle* handle) {
|
||||
uint8_t setup = 0;
|
||||
uint32_t rate = 0;
|
||||
nrf24_read_reg(handle, REG_RF_SETUP, &setup, 1);
|
||||
setup &= 0x28;
|
||||
if(setup == 0x20)
|
||||
rate = 250000; // 250kbps
|
||||
else if(setup == 0x08)
|
||||
rate = 2000000; // 2Mbps
|
||||
else if(setup == 0x00)
|
||||
rate = 1000000; // 1Mbps
|
||||
|
||||
return rate;
|
||||
}
|
||||
|
||||
uint8_t nrf24_set_rate(FuriHalSpiBusHandle* handle, uint32_t rate) {
|
||||
uint8_t r6 = 0;
|
||||
uint8_t status = 0;
|
||||
if(!rate) rate = 2000000;
|
||||
|
||||
nrf24_read_reg(handle, REG_RF_SETUP, &r6, 1); // RF_SETUP register
|
||||
r6 = r6 & (~0x28); // Clear rate fields.
|
||||
if(rate == 2000000)
|
||||
r6 = r6 | 0x08;
|
||||
else if(rate == 1000000)
|
||||
r6 = r6;
|
||||
else if(rate == 250000)
|
||||
r6 = r6 | 0x20;
|
||||
|
||||
status = nrf24_write_reg(handle, REG_RF_SETUP, r6); // Write new rate.
|
||||
return status;
|
||||
}
|
||||
|
||||
uint8_t nrf24_get_chan(FuriHalSpiBusHandle* handle) {
|
||||
uint8_t channel = 0;
|
||||
nrf24_read_reg(handle, REG_RF_CH, &channel, 1);
|
||||
return channel;
|
||||
}
|
||||
|
||||
uint8_t nrf24_set_chan(FuriHalSpiBusHandle* handle, uint8_t chan) {
|
||||
uint8_t status;
|
||||
status = nrf24_write_reg(handle, REG_RF_CH, chan);
|
||||
return status;
|
||||
}
|
||||
|
||||
uint8_t nrf24_get_src_mac(FuriHalSpiBusHandle* handle, uint8_t* mac) {
|
||||
uint8_t size = 0;
|
||||
uint8_t status = 0;
|
||||
size = nrf24_get_maclen(handle);
|
||||
status = nrf24_read_reg(handle, REG_RX_ADDR_P0, mac, size);
|
||||
return status;
|
||||
}
|
||||
|
||||
uint8_t nrf24_set_src_mac(FuriHalSpiBusHandle* handle, uint8_t* mac, uint8_t size) {
|
||||
uint8_t status = 0;
|
||||
uint8_t clearmac[] = {0, 0, 0, 0, 0};
|
||||
nrf24_set_maclen(handle, size);
|
||||
nrf24_write_buf_reg(handle, REG_RX_ADDR_P0, clearmac, 5);
|
||||
status = nrf24_write_buf_reg(handle, REG_RX_ADDR_P0, mac, size);
|
||||
return status;
|
||||
}
|
||||
|
||||
uint8_t nrf24_get_dst_mac(FuriHalSpiBusHandle* handle, uint8_t* mac) {
|
||||
uint8_t size = 0;
|
||||
uint8_t status = 0;
|
||||
size = nrf24_get_maclen(handle);
|
||||
status = nrf24_read_reg(handle, REG_TX_ADDR, mac, size);
|
||||
return status;
|
||||
}
|
||||
|
||||
uint8_t nrf24_set_dst_mac(FuriHalSpiBusHandle* handle, uint8_t* mac, uint8_t size) {
|
||||
uint8_t status = 0;
|
||||
uint8_t clearmac[] = {0, 0, 0, 0, 0};
|
||||
nrf24_set_maclen(handle, size);
|
||||
nrf24_write_buf_reg(handle, REG_TX_ADDR, clearmac, 5);
|
||||
status = nrf24_write_buf_reg(handle, REG_TX_ADDR, mac, size);
|
||||
return status;
|
||||
}
|
||||
|
||||
uint8_t nrf24_get_packetlen(FuriHalSpiBusHandle* handle) {
|
||||
uint8_t len = 0;
|
||||
nrf24_read_reg(handle, RX_PW_P0, &len, 1);
|
||||
return len;
|
||||
}
|
||||
|
||||
uint8_t nrf24_set_packetlen(FuriHalSpiBusHandle* handle, uint8_t len) {
|
||||
uint8_t status = 0;
|
||||
status = nrf24_write_reg(handle, RX_PW_P0, len);
|
||||
return status;
|
||||
}
|
||||
|
||||
uint8_t
|
||||
nrf24_rxpacket(FuriHalSpiBusHandle* handle, uint8_t* packet, uint8_t* packetsize, bool full) {
|
||||
uint8_t status = 0;
|
||||
uint8_t size = 0;
|
||||
uint8_t tx_pl_wid[] = {R_RX_PL_WID, 0};
|
||||
uint8_t rx_pl_wid[] = {0, 0};
|
||||
uint8_t tx_cmd[33] = {0}; // 32 max payload size + 1 for command
|
||||
uint8_t tmp_packet[33] = {0};
|
||||
|
||||
status = nrf24_status(handle);
|
||||
|
||||
if(status & 0x40) {
|
||||
if(full)
|
||||
size = nrf24_get_packetlen(handle);
|
||||
else {
|
||||
nrf24_spi_trx(handle, tx_pl_wid, rx_pl_wid, 2, nrf24_TIMEOUT);
|
||||
size = rx_pl_wid[1];
|
||||
}
|
||||
|
||||
tx_cmd[0] = R_RX_PAYLOAD;
|
||||
nrf24_spi_trx(handle, tx_cmd, tmp_packet, size + 1, nrf24_TIMEOUT);
|
||||
nrf24_write_reg(handle, REG_STATUS, 0x40); // clear bit.
|
||||
memcpy(packet, &tmp_packet[1], size);
|
||||
} else if(status == 0) {
|
||||
nrf24_flush_rx(handle);
|
||||
nrf24_write_reg(handle, REG_STATUS, 0x40); // clear bit.
|
||||
}
|
||||
|
||||
*packetsize = size;
|
||||
return status;
|
||||
}
|
||||
|
||||
uint8_t nrf24_txpacket(FuriHalSpiBusHandle* handle, uint8_t* payload, uint8_t size, bool ack) {
|
||||
uint8_t status = 0;
|
||||
uint8_t tx[size + 1];
|
||||
uint8_t rx[size + 1];
|
||||
memset(tx, 0, size + 1);
|
||||
memset(rx, 0, size + 1);
|
||||
|
||||
if(!ack)
|
||||
tx[0] = W_TX_PAYLOAD_NOACK;
|
||||
else
|
||||
tx[0] = W_TX_PAYLOAD;
|
||||
|
||||
memcpy(&tx[1], payload, size);
|
||||
nrf24_spi_trx(handle, tx, rx, size + 1, nrf24_TIMEOUT);
|
||||
nrf24_set_tx_mode(handle);
|
||||
|
||||
while(!(status & (TX_DS | MAX_RT))) status = nrf24_status(handle);
|
||||
|
||||
if(status & MAX_RT) nrf24_flush_tx(handle);
|
||||
|
||||
nrf24_set_idle(handle);
|
||||
nrf24_write_reg(handle, REG_STATUS, TX_DS | MAX_RT);
|
||||
return status & TX_DS;
|
||||
}
|
||||
|
||||
uint8_t nrf24_power_up(FuriHalSpiBusHandle* handle) {
|
||||
uint8_t status = 0;
|
||||
uint8_t cfg = 0;
|
||||
nrf24_read_reg(handle, REG_CONFIG, &cfg, 1);
|
||||
cfg = cfg | 2;
|
||||
status = nrf24_write_reg(handle, REG_CONFIG, cfg);
|
||||
furi_delay_ms(5000);
|
||||
return status;
|
||||
}
|
||||
|
||||
uint8_t nrf24_set_idle(FuriHalSpiBusHandle* handle) {
|
||||
uint8_t status = 0;
|
||||
uint8_t cfg = 0;
|
||||
nrf24_read_reg(handle, REG_CONFIG, &cfg, 1);
|
||||
cfg &= 0xfc; // clear bottom two bits to power down the radio
|
||||
status = nrf24_write_reg(handle, REG_CONFIG, cfg);
|
||||
//nr204_write_reg(handle, REG_EN_RXADDR, 0x0);
|
||||
furi_hal_gpio_write(nrf24_CE_PIN, false);
|
||||
return status;
|
||||
}
|
||||
|
||||
uint8_t nrf24_set_rx_mode(FuriHalSpiBusHandle* handle) {
|
||||
uint8_t status = 0;
|
||||
uint8_t cfg = 0;
|
||||
//status = nrf24_write_reg(handle, REG_CONFIG, 0x0F); // enable 2-byte CRC, PWR_UP, and PRIM_RX
|
||||
nrf24_read_reg(handle, REG_CONFIG, &cfg, 1);
|
||||
cfg |= 0x03; // PWR_UP, and PRIM_RX
|
||||
status = nrf24_write_reg(handle, REG_CONFIG, cfg);
|
||||
//nr204_write_reg(REG_EN_RXADDR, 0x03) // Set RX Pipe 0 and 1
|
||||
furi_hal_gpio_write(nrf24_CE_PIN, true);
|
||||
furi_delay_ms(2000);
|
||||
return status;
|
||||
}
|
||||
|
||||
uint8_t nrf24_set_tx_mode(FuriHalSpiBusHandle* handle) {
|
||||
uint8_t status = 0;
|
||||
uint8_t cfg = 0;
|
||||
furi_hal_gpio_write(nrf24_CE_PIN, false);
|
||||
nrf24_write_reg(handle, REG_STATUS, 0x30);
|
||||
//status = nrf24_write_reg(handle, REG_CONFIG, 0x0E); // enable 2-byte CRC, PWR_UP
|
||||
nrf24_read_reg(handle, REG_CONFIG, &cfg, 1);
|
||||
cfg &= 0xfe; // disable PRIM_RX
|
||||
cfg |= 0x02; // PWR_UP
|
||||
status = nrf24_write_reg(handle, REG_CONFIG, cfg);
|
||||
furi_hal_gpio_write(nrf24_CE_PIN, true);
|
||||
furi_delay_ms(2);
|
||||
return status;
|
||||
}
|
||||
|
||||
void nrf24_configure(
|
||||
FuriHalSpiBusHandle* handle,
|
||||
uint8_t rate,
|
||||
uint8_t* srcmac,
|
||||
uint8_t* dstmac,
|
||||
uint8_t maclen,
|
||||
uint8_t channel,
|
||||
bool noack,
|
||||
bool disable_aa) {
|
||||
assert(channel <= 125);
|
||||
assert(rate == 1 || rate == 2);
|
||||
if(rate == 2)
|
||||
rate = 8; // 2Mbps
|
||||
else
|
||||
rate = 0; // 1Mbps
|
||||
|
||||
nrf24_write_reg(handle, REG_CONFIG, 0x00); // Stop nRF
|
||||
nrf24_set_idle(handle);
|
||||
nrf24_write_reg(handle, REG_STATUS, 0x1c); // clear interrupts
|
||||
if(disable_aa)
|
||||
nrf24_write_reg(handle, REG_EN_AA, 0x00); // Disable Shockburst
|
||||
else
|
||||
nrf24_write_reg(handle, REG_EN_AA, 0x1F); // Enable Shockburst
|
||||
|
||||
nrf24_write_reg(handle, REG_DYNPD, 0x3F); // enable dynamic payload length on all pipes
|
||||
if(noack)
|
||||
nrf24_write_reg(handle, REG_FEATURE, 0x05); // disable payload-with-ack, enable noack
|
||||
else {
|
||||
nrf24_write_reg(handle, REG_CONFIG, 0x0C); // 2 byte CRC
|
||||
nrf24_write_reg(handle, REG_FEATURE, 0x07); // enable dyn payload and ack
|
||||
nrf24_write_reg(
|
||||
handle, REG_SETUP_RETR, 0x1f); // 15 retries for AA, 500us auto retransmit delay
|
||||
}
|
||||
|
||||
nrf24_set_idle(handle);
|
||||
nrf24_flush_rx(handle);
|
||||
nrf24_flush_tx(handle);
|
||||
|
||||
if(maclen) nrf24_set_maclen(handle, maclen);
|
||||
if(srcmac) nrf24_set_src_mac(handle, srcmac, maclen);
|
||||
if(dstmac) nrf24_set_dst_mac(handle, dstmac, maclen);
|
||||
|
||||
nrf24_write_reg(handle, REG_RF_CH, channel);
|
||||
nrf24_write_reg(handle, REG_RF_SETUP, rate);
|
||||
furi_delay_ms(200);
|
||||
}
|
||||
|
||||
void nrf24_init_promisc_mode(FuriHalSpiBusHandle* handle, uint8_t channel, uint8_t rate) {
|
||||
//uint8_t preamble[] = {0x55, 0x00}; // little endian
|
||||
uint8_t preamble[] = {0xAA, 0x00}; // little endian
|
||||
//uint8_t preamble[] = {0x00, 0x55}; // little endian
|
||||
//uint8_t preamble[] = {0x00, 0xAA}; // little endian
|
||||
nrf24_write_reg(handle, REG_CONFIG, 0x00); // Stop nRF
|
||||
nrf24_write_reg(handle, REG_STATUS, 0x1c); // clear interrupts
|
||||
nrf24_write_reg(handle, REG_DYNPD, 0x0); // disable shockburst
|
||||
nrf24_write_reg(handle, REG_EN_AA, 0x00); // Disable Shockburst
|
||||
nrf24_write_reg(handle, REG_FEATURE, 0x05); // disable payload-with-ack, enable noack
|
||||
nrf24_set_maclen(handle, 2); // shortest address
|
||||
nrf24_set_src_mac(handle, preamble, 2); // set src mac to preamble bits to catch everything
|
||||
nrf24_set_packetlen(handle, 32); // set max packet length
|
||||
nrf24_set_idle(handle);
|
||||
nrf24_flush_rx(handle);
|
||||
nrf24_flush_tx(handle);
|
||||
nrf24_write_reg(handle, REG_RF_CH, channel);
|
||||
nrf24_write_reg(handle, REG_RF_SETUP, rate);
|
||||
|
||||
// prime for RX, no checksum
|
||||
nrf24_write_reg(handle, REG_CONFIG, 0x03); // PWR_UP and PRIM_RX, disable AA and CRC
|
||||
furi_hal_gpio_write(nrf24_CE_PIN, true);
|
||||
furi_delay_ms(100);
|
||||
}
|
||||
|
||||
void hexlify(uint8_t* in, uint8_t size, char* out) {
|
||||
memset(out, 0, size * 2);
|
||||
for(int i = 0; i < size; i++)
|
||||
snprintf(out + strlen(out), sizeof(out + strlen(out)), "%02X", in[i]);
|
||||
}
|
||||
|
||||
uint64_t bytes_to_int64(uint8_t* bytes, uint8_t size, bool bigendian) {
|
||||
uint64_t ret = 0;
|
||||
for(int i = 0; i < size; i++)
|
||||
if(bigendian)
|
||||
ret |= bytes[i] << ((size - 1 - i) * 8);
|
||||
else
|
||||
ret |= bytes[i] << (i * 8);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void int64_to_bytes(uint64_t val, uint8_t* out, bool bigendian) {
|
||||
for(int i = 0; i < 8; i++) {
|
||||
if(bigendian)
|
||||
out[i] = (val >> ((7 - i) * 8)) & 0xff;
|
||||
else
|
||||
out[i] = (val >> (i * 8)) & 0xff;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t bytes_to_int32(uint8_t* bytes, bool bigendian) {
|
||||
uint32_t ret = 0;
|
||||
for(int i = 0; i < 4; i++)
|
||||
if(bigendian)
|
||||
ret |= bytes[i] << ((3 - i) * 8);
|
||||
else
|
||||
ret |= bytes[i] << (i * 8);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void int32_to_bytes(uint32_t val, uint8_t* out, bool bigendian) {
|
||||
for(int i = 0; i < 4; i++) {
|
||||
if(bigendian)
|
||||
out[i] = (val >> ((3 - i) * 8)) & 0xff;
|
||||
else
|
||||
out[i] = (val >> (i * 8)) & 0xff;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t bytes_to_int16(uint8_t* bytes, bool bigendian) {
|
||||
uint16_t ret = 0;
|
||||
for(int i = 0; i < 2; i++)
|
||||
if(bigendian)
|
||||
ret |= bytes[i] << ((1 - i) * 8);
|
||||
else
|
||||
ret |= bytes[i] << (i * 8);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void int16_to_bytes(uint16_t val, uint8_t* out, bool bigendian) {
|
||||
for(int i = 0; i < 2; i++) {
|
||||
if(bigendian)
|
||||
out[i] = (val >> ((1 - i) * 8)) & 0xff;
|
||||
else
|
||||
out[i] = (val >> (i * 8)) & 0xff;
|
||||
}
|
||||
}
|
||||
|
||||
// handle iffyness with preamble processing sometimes being a bit (literally) off
|
||||
void alt_address_old(uint8_t* packet, uint8_t* altaddr) {
|
||||
uint8_t macmess_hi_b[4];
|
||||
uint8_t macmess_lo_b[2];
|
||||
uint32_t macmess_hi;
|
||||
uint16_t macmess_lo;
|
||||
uint8_t preserved;
|
||||
|
||||
// get first 6 bytes into 32-bit and 16-bit variables
|
||||
memcpy(macmess_hi_b, packet, 4);
|
||||
memcpy(macmess_lo_b, packet + 4, 2);
|
||||
|
||||
macmess_hi = bytes_to_int32(macmess_hi_b, true);
|
||||
|
||||
//preserve least 7 bits from hi that will be shifted down to lo
|
||||
preserved = macmess_hi & 0x7f;
|
||||
macmess_hi >>= 7;
|
||||
|
||||
macmess_lo = bytes_to_int16(macmess_lo_b, true);
|
||||
macmess_lo >>= 7;
|
||||
macmess_lo = (preserved << 9) | macmess_lo;
|
||||
int32_to_bytes(macmess_hi, macmess_hi_b, true);
|
||||
int16_to_bytes(macmess_lo, macmess_lo_b, true);
|
||||
memcpy(altaddr, &macmess_hi_b[1], 3);
|
||||
memcpy(altaddr + 3, macmess_lo_b, 2);
|
||||
}
|
||||
|
||||
bool validate_address(uint8_t* addr) {
|
||||
uint8_t bad[][3] = {{0x55, 0x55}, {0xAA, 0xAA}, {0x00, 0x00}, {0xFF, 0xFF}};
|
||||
for(int i = 0; i < 4; i++)
|
||||
for(int j = 0; j < 2; j++)
|
||||
if(!memcmp(addr + j * 2, bad[i], 2)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool nrf24_sniff_address(FuriHalSpiBusHandle* handle, uint8_t maclen, uint8_t* address) {
|
||||
bool found = false;
|
||||
uint8_t packet[32] = {0};
|
||||
uint8_t packetsize;
|
||||
//char printit[65];
|
||||
uint8_t status = 0;
|
||||
status = nrf24_rxpacket(handle, packet, &packetsize, true);
|
||||
if(status & 0x40) {
|
||||
if(validate_address(packet)) {
|
||||
for(int i = 0; i < maclen; i++) address[i] = packet[maclen - 1 - i];
|
||||
|
||||
/*
|
||||
alt_address(packet, packet);
|
||||
|
||||
for(i = 0; i < maclen; i++)
|
||||
address[i + 5] = packet[maclen - 1 - i];
|
||||
*/
|
||||
|
||||
//memcpy(address, packet, maclen);
|
||||
//hexlify(packet, packetsize, printit);
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
uint8_t nrf24_find_channel(
|
||||
FuriHalSpiBusHandle* handle,
|
||||
uint8_t* srcmac,
|
||||
uint8_t* dstmac,
|
||||
uint8_t maclen,
|
||||
uint8_t rate,
|
||||
uint8_t min_channel,
|
||||
uint8_t max_channel,
|
||||
bool autoinit) {
|
||||
uint8_t ping_packet[] = {0x0f, 0x0f, 0x0f, 0x0f}; // this can be anything, we just need an ack
|
||||
uint8_t ch = max_channel + 1; // means fail
|
||||
nrf24_configure(handle, rate, srcmac, dstmac, maclen, 2, false, false);
|
||||
for(ch = min_channel; ch <= max_channel + 1; ch++) {
|
||||
nrf24_write_reg(handle, REG_RF_CH, ch);
|
||||
if(nrf24_txpacket(handle, ping_packet, 4, true)) break;
|
||||
}
|
||||
|
||||
if(autoinit) {
|
||||
FURI_LOG_D("nrf24", "initializing radio for channel %d", ch);
|
||||
nrf24_configure(handle, rate, srcmac, dstmac, maclen, ch, false, false);
|
||||
return ch;
|
||||
}
|
||||
|
||||
return ch;
|
||||
}
|
||||
|
||||
bool nrf24_check_connected(FuriHalSpiBusHandle* handle) {
|
||||
uint8_t status = nrf24_status(handle);
|
||||
|
||||
if(status != 0x00) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,376 +0,0 @@
|
||||
#pragma once
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <furi_hal_spi.h>
|
||||
#include <xtreme.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define R_REGISTER 0x00
|
||||
#define W_REGISTER 0x20
|
||||
#define REGISTER_MASK 0x1F
|
||||
#define ACTIVATE 0x50
|
||||
#define R_RX_PL_WID 0x60
|
||||
#define R_RX_PAYLOAD 0x61
|
||||
#define W_TX_PAYLOAD 0xA0
|
||||
#define W_TX_PAYLOAD_NOACK 0xB0
|
||||
#define W_ACK_PAYLOAD 0xA8
|
||||
#define FLUSH_TX 0xE1
|
||||
#define FLUSH_RX 0xE2
|
||||
#define REUSE_TX_PL 0xE3
|
||||
#define RF24_NOP 0xFF
|
||||
|
||||
#define REG_CONFIG 0x00
|
||||
#define REG_EN_AA 0x01
|
||||
#define REG_EN_RXADDR 0x02
|
||||
#define REG_SETUP_AW 0x03
|
||||
#define REG_SETUP_RETR 0x04
|
||||
#define REG_DYNPD 0x1C
|
||||
#define REG_FEATURE 0x1D
|
||||
#define REG_RF_SETUP 0x06
|
||||
#define REG_STATUS 0x07
|
||||
#define REG_RX_ADDR_P0 0x0A
|
||||
#define REG_RF_CH 0x05
|
||||
#define REG_TX_ADDR 0x10
|
||||
|
||||
#define RX_PW_P0 0x11
|
||||
#define TX_DS 0x20
|
||||
#define MAX_RT 0x10
|
||||
|
||||
#define nrf24_TIMEOUT 500
|
||||
#define nrf24_CE_PIN &gpio_ext_pb2
|
||||
#define nrf24_HANDLE \
|
||||
(xtreme_settings.spi_nrf24_handle == SpiDefault ? &furi_hal_spi_bus_handle_external : \
|
||||
&furi_hal_spi_bus_handle_external_extra)
|
||||
|
||||
/* Low level API */
|
||||
|
||||
/** Write device register
|
||||
*
|
||||
* @param handle - pointer to FuriHalSpiHandle
|
||||
* @param reg - register
|
||||
* @param data - data to write
|
||||
*
|
||||
* @return device status
|
||||
*/
|
||||
uint8_t nrf24_write_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t data);
|
||||
|
||||
/** Write buffer to device register
|
||||
*
|
||||
* @param handle - pointer to FuriHalSpiHandle
|
||||
* @param reg - register
|
||||
* @param data - data to write
|
||||
* @param size - size of data to write
|
||||
*
|
||||
* @return device status
|
||||
*/
|
||||
uint8_t nrf24_write_buf_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* data, uint8_t size);
|
||||
|
||||
/** Read device register
|
||||
*
|
||||
* @param handle - pointer to FuriHalSpiHandle
|
||||
* @param reg - register
|
||||
* @param[out] data - pointer to data
|
||||
*
|
||||
* @return device status
|
||||
*/
|
||||
uint8_t nrf24_read_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* data, uint8_t size);
|
||||
|
||||
/** Power up the radio for operation
|
||||
*
|
||||
* @param handle - pointer to FuriHalSpiHandle
|
||||
*
|
||||
* @return device status
|
||||
*/
|
||||
uint8_t nrf24_power_up(FuriHalSpiBusHandle* handle);
|
||||
|
||||
/** Power down the radio
|
||||
*
|
||||
* @param handle - pointer to FuriHalSpiHandle
|
||||
*
|
||||
* @return device status
|
||||
*/
|
||||
uint8_t nrf24_set_idle(FuriHalSpiBusHandle* handle);
|
||||
|
||||
/** Sets the radio to RX mode
|
||||
*
|
||||
* @param handle - pointer to FuriHalSpiHandle
|
||||
*
|
||||
* @return device status
|
||||
*/
|
||||
uint8_t nrf24_set_rx_mode(FuriHalSpiBusHandle* handle);
|
||||
|
||||
/** Sets the radio to TX mode
|
||||
*
|
||||
* @param handle - pointer to FuriHalSpiHandle
|
||||
*
|
||||
* @return device status
|
||||
*/
|
||||
uint8_t nrf24_set_tx_mode(FuriHalSpiBusHandle* handle);
|
||||
|
||||
/*=============================================================================================================*/
|
||||
|
||||
/* High level API */
|
||||
|
||||
/** Must call this before using any other nrf24 API
|
||||
*
|
||||
*/
|
||||
void nrf24_init();
|
||||
|
||||
/** Must call this when we end using nrf24 device
|
||||
*
|
||||
*/
|
||||
void nrf24_deinit();
|
||||
|
||||
/** Send flush rx command
|
||||
*
|
||||
* @param handle - pointer to FuriHalSpiHandle
|
||||
*
|
||||
* @return device status
|
||||
*/
|
||||
uint8_t nrf24_flush_rx(FuriHalSpiBusHandle* handle);
|
||||
|
||||
/** Send flush tx command
|
||||
*
|
||||
* @param handle - pointer to FuriHalSpiHandle
|
||||
*
|
||||
* @return device status
|
||||
*/
|
||||
uint8_t nrf24_flush_tx(FuriHalSpiBusHandle* handle);
|
||||
|
||||
/** Gets the RX packet length in data pipe 0
|
||||
*
|
||||
* @param handle - pointer to FuriHalSpiHandle
|
||||
*
|
||||
* @return packet length in data pipe 0
|
||||
*/
|
||||
uint8_t nrf24_get_packetlen(FuriHalSpiBusHandle* handle);
|
||||
|
||||
/** Sets the RX packet length in data pipe 0
|
||||
*
|
||||
* @param handle - pointer to FuriHalSpiHandle
|
||||
* @param len - length to set
|
||||
*
|
||||
* @return device status
|
||||
*/
|
||||
uint8_t nrf24_set_packetlen(FuriHalSpiBusHandle* handle, uint8_t len);
|
||||
|
||||
/** Gets configured length of MAC address
|
||||
*
|
||||
* @param handle - pointer to FuriHalSpiHandle
|
||||
*
|
||||
* @return MAC address length
|
||||
*/
|
||||
uint8_t nrf24_get_maclen(FuriHalSpiBusHandle* handle);
|
||||
|
||||
/** Sets configured length of MAC address
|
||||
*
|
||||
* @param handle - pointer to FuriHalSpiHandle
|
||||
* @param maclen - length to set MAC address to, must be greater than 1 and less than 6
|
||||
*
|
||||
* @return MAC address length
|
||||
*/
|
||||
uint8_t nrf24_set_maclen(FuriHalSpiBusHandle* handle, uint8_t maclen);
|
||||
|
||||
/** Gets the current status flags from the STATUS register
|
||||
*
|
||||
* @param handle - pointer to FuriHalSpiHandle
|
||||
*
|
||||
* @return status flags
|
||||
*/
|
||||
uint8_t nrf24_status(FuriHalSpiBusHandle* handle);
|
||||
|
||||
/** Gets the current transfer rate
|
||||
*
|
||||
* @param handle - pointer to FuriHalSpiHandle
|
||||
*
|
||||
* @return transfer rate in bps
|
||||
*/
|
||||
uint32_t nrf24_get_rate(FuriHalSpiBusHandle* handle);
|
||||
|
||||
/** Sets the transfer rate
|
||||
*
|
||||
* @param handle - pointer to FuriHalSpiHandle
|
||||
* @param rate - the transfer rate in bps
|
||||
*
|
||||
* @return device status
|
||||
*/
|
||||
uint8_t nrf24_set_rate(FuriHalSpiBusHandle* handle, uint32_t rate);
|
||||
|
||||
/** Gets the current channel
|
||||
* In nrf24, the channel number is multiplied times 1MHz and added to 2400MHz to get the frequency
|
||||
*
|
||||
* @param handle - pointer to FuriHalSpiHandle
|
||||
*
|
||||
* @return channel
|
||||
*/
|
||||
uint8_t nrf24_get_chan(FuriHalSpiBusHandle* handle);
|
||||
|
||||
/** Sets the channel
|
||||
*
|
||||
* @param handle - pointer to FuriHalSpiHandle
|
||||
* @param frequency - the frequency in hertz
|
||||
*
|
||||
* @return device status
|
||||
*/
|
||||
uint8_t nrf24_set_chan(FuriHalSpiBusHandle* handle, uint8_t chan);
|
||||
|
||||
/** Gets the source mac address
|
||||
*
|
||||
* @param handle - pointer to FuriHalSpiHandle
|
||||
* @param[out] mac - the source mac address
|
||||
*
|
||||
* @return device status
|
||||
*/
|
||||
uint8_t nrf24_get_src_mac(FuriHalSpiBusHandle* handle, uint8_t* mac);
|
||||
|
||||
/** Sets the source mac address
|
||||
*
|
||||
* @param handle - pointer to FuriHalSpiHandle
|
||||
* @param mac - the mac address to set
|
||||
* @param size - the size of the mac address (2 to 5)
|
||||
*
|
||||
* @return device status
|
||||
*/
|
||||
uint8_t nrf24_set_src_mac(FuriHalSpiBusHandle* handle, uint8_t* mac, uint8_t size);
|
||||
|
||||
/** Gets the dest mac address
|
||||
*
|
||||
* @param handle - pointer to FuriHalSpiHandle
|
||||
* @param[out] mac - the source mac address
|
||||
*
|
||||
* @return device status
|
||||
*/
|
||||
uint8_t nrf24_get_dst_mac(FuriHalSpiBusHandle* handle, uint8_t* mac);
|
||||
|
||||
/** Sets the dest mac address
|
||||
*
|
||||
* @param handle - pointer to FuriHalSpiHandle
|
||||
* @param mac - the mac address to set
|
||||
* @param size - the size of the mac address (2 to 5)
|
||||
*
|
||||
* @return device status
|
||||
*/
|
||||
uint8_t nrf24_set_dst_mac(FuriHalSpiBusHandle* handle, uint8_t* mac, uint8_t size);
|
||||
|
||||
/** Reads RX packet
|
||||
*
|
||||
* @param handle - pointer to FuriHalSpiHandle
|
||||
* @param[out] packet - the packet contents
|
||||
* @param[out] packetsize - size of the received packet
|
||||
* @param full - boolean set to true, packet length is determined by RX_PW_P0 register, false it is determined by dynamic payload length command
|
||||
*
|
||||
* @return device status
|
||||
*/
|
||||
uint8_t
|
||||
nrf24_rxpacket(FuriHalSpiBusHandle* handle, uint8_t* packet, uint8_t* packetsize, bool full);
|
||||
|
||||
/** Sends TX packet
|
||||
*
|
||||
* @param handle - pointer to FuriHalSpiHandle
|
||||
* @param packet - the packet contents
|
||||
* @param size - packet size
|
||||
* @param ack - boolean to determine whether an ACK is required for the packet or not
|
||||
*
|
||||
* @return device status
|
||||
*/
|
||||
uint8_t nrf24_txpacket(FuriHalSpiBusHandle* handle, uint8_t* payload, uint8_t size, bool ack);
|
||||
|
||||
/** Configure the radio
|
||||
* This is not comprehensive, but covers a lot of the common configuration options that may be changed
|
||||
* @param handle - pointer to FuriHalSpiHandle
|
||||
* @param rate - transfer rate in Mbps (1 or 2)
|
||||
* @param srcmac - source mac address
|
||||
* @param dstmac - destination mac address
|
||||
* @param maclen - length of mac address
|
||||
* @param channel - channel to tune to
|
||||
* @param noack - if true, disable auto-acknowledge
|
||||
* @param disable_aa - if true, disable ShockBurst
|
||||
*
|
||||
*/
|
||||
void nrf24_configure(
|
||||
FuriHalSpiBusHandle* handle,
|
||||
uint8_t rate,
|
||||
uint8_t* srcmac,
|
||||
uint8_t* dstmac,
|
||||
uint8_t maclen,
|
||||
uint8_t channel,
|
||||
bool noack,
|
||||
bool disable_aa);
|
||||
|
||||
/** Configures the radio for "promiscuous mode" and primes it for rx
|
||||
* This is not an actual mode of the nrf24, but this function exploits a few bugs in the chip that allows it to act as if it were.
|
||||
* See http://travisgoodspeed.blogspot.com/2011/02/promiscuity-is-nrf24l01s-duty.html for details.
|
||||
* @param handle - pointer to FuriHalSpiHandle
|
||||
* @param channel - channel to tune to
|
||||
* @param rate - transfer rate in Mbps (1 or 2)
|
||||
*/
|
||||
void nrf24_init_promisc_mode(FuriHalSpiBusHandle* handle, uint8_t channel, uint8_t rate);
|
||||
|
||||
/** Listens for a packet and returns first possible address sniffed
|
||||
* Call this only after calling nrf24_init_promisc_mode
|
||||
* @param handle - pointer to FuriHalSpiHandle
|
||||
* @param maclen - length of target mac address
|
||||
* @param[out] addresses - sniffed address
|
||||
*
|
||||
* @return success
|
||||
*/
|
||||
bool nrf24_sniff_address(FuriHalSpiBusHandle* handle, uint8_t maclen, uint8_t* address);
|
||||
|
||||
/** Sends ping packet on each channel for designated tx mac looking for ack
|
||||
*
|
||||
* @param handle - pointer to FuriHalSpiHandle
|
||||
* @param srcmac - source address
|
||||
* @param dstmac - destination address
|
||||
* @param maclen - length of address
|
||||
* @param rate - transfer rate in Mbps (1 or 2)
|
||||
* @param min_channel - channel to start with
|
||||
* @param max_channel - channel to end at
|
||||
* @param autoinit - if true, automatically configure radio for this channel
|
||||
*
|
||||
* @return channel that the address is listening on, if this value is above the max_channel param, it failed
|
||||
*/
|
||||
uint8_t nrf24_find_channel(
|
||||
FuriHalSpiBusHandle* handle,
|
||||
uint8_t* srcmac,
|
||||
uint8_t* dstmac,
|
||||
uint8_t maclen,
|
||||
uint8_t rate,
|
||||
uint8_t min_channel,
|
||||
uint8_t max_channel,
|
||||
bool autoinit);
|
||||
|
||||
/** Converts 64 bit value into uint8_t array
|
||||
* @param val - 64-bit integer
|
||||
* @param[out] out - bytes out
|
||||
* @param bigendian - if true, convert as big endian, otherwise little endian
|
||||
*/
|
||||
void int64_to_bytes(uint64_t val, uint8_t* out, bool bigendian);
|
||||
|
||||
/** Converts 32 bit value into uint8_t array
|
||||
* @param val - 32-bit integer
|
||||
* @param[out] out - bytes out
|
||||
* @param bigendian - if true, convert as big endian, otherwise little endian
|
||||
*/
|
||||
void int32_to_bytes(uint32_t val, uint8_t* out, bool bigendian);
|
||||
|
||||
/** Converts uint8_t array into 32 bit value
|
||||
* @param bytes - uint8_t array
|
||||
* @param bigendian - if true, convert as big endian, otherwise little endian
|
||||
*
|
||||
* @return 32-bit value
|
||||
*/
|
||||
uint32_t bytes_to_int32(uint8_t* bytes, bool bigendian);
|
||||
|
||||
/** Check if the nrf24 is connected
|
||||
* @param handle - pointer to FuriHalSpiHandle
|
||||
*
|
||||
* @return true if connected, otherwise false
|
||||
*/
|
||||
bool nrf24_check_connected(FuriHalSpiBusHandle* handle);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,473 +0,0 @@
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <gui/gui.h>
|
||||
#include <input/input.h>
|
||||
#include <notification/notification_messages.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <nrf24.h>
|
||||
#include <toolbox/stream/file_stream.h>
|
||||
|
||||
#define LOGITECH_MAX_CHANNEL 85
|
||||
#define COUNT_THRESHOLD 2
|
||||
#define DEFAULT_SAMPLE_TIME 4000
|
||||
#define MAX_ADDRS 100
|
||||
#define MAX_CONFIRMED 32
|
||||
|
||||
#define NRFSNIFF_APP_PATH_FOLDER STORAGE_APP_DATA_PATH_PREFIX
|
||||
#define NRFSNIFF_APP_FILENAME "addresses.txt"
|
||||
#define TAG "nrfsniff"
|
||||
|
||||
typedef enum {
|
||||
EventTypeTick,
|
||||
EventTypeKey,
|
||||
} EventType;
|
||||
|
||||
typedef struct {
|
||||
EventType type;
|
||||
InputEvent input;
|
||||
} PluginEvent;
|
||||
|
||||
typedef struct {
|
||||
FuriMutex* mutex;
|
||||
} PluginState;
|
||||
|
||||
char rate_text_fmt[] = "Transfer rate: %dMbps";
|
||||
char sample_text_fmt[] = "Sample Time: %d ms";
|
||||
char channel_text_fmt[] = "Channel: %d Sniffing: %s";
|
||||
char preamble_text_fmt[] = "Preamble: %02X";
|
||||
char sniff_text_fmt[] = "Found: %d Unique: %u";
|
||||
char addresses_header_text[] = "Address,rate";
|
||||
char sniffed_address_fmt[] = "%s,%d";
|
||||
char rate_text[46];
|
||||
char channel_text[38];
|
||||
char sample_text[32];
|
||||
char preamble_text[14];
|
||||
char sniff_text[38];
|
||||
char sniffed_address[14];
|
||||
|
||||
uint8_t target_channel = 0;
|
||||
uint32_t found_count = 0;
|
||||
uint32_t unique_saved_count = 0;
|
||||
uint32_t sample_time = DEFAULT_SAMPLE_TIME;
|
||||
uint8_t target_rate = 8; // rate can be either 8 (2Mbps) or 0 (1Mbps)
|
||||
uint8_t target_preamble[] = {0xAA, 0x00};
|
||||
uint8_t sniffing_state = false;
|
||||
char top_address[12];
|
||||
|
||||
uint8_t candidates[MAX_ADDRS][5] = {0}; // last 100 sniffed addresses
|
||||
uint32_t counts[MAX_ADDRS];
|
||||
uint8_t confirmed[MAX_CONFIRMED][5] = {0}; // first 32 confirmed addresses
|
||||
uint8_t confirmed_idx = 0;
|
||||
uint32_t total_candidates = 0;
|
||||
uint32_t candidate_idx = 0;
|
||||
|
||||
static int get_addr_index(uint8_t* addr, uint8_t addr_size) {
|
||||
for(uint32_t i = 0; i < total_candidates; i++) {
|
||||
uint8_t* arr_item = candidates[i];
|
||||
if(!memcmp(arr_item, addr, addr_size)) return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int get_highest_idx() {
|
||||
uint32_t highest = 0;
|
||||
int highest_idx = 0;
|
||||
for(uint32_t i = 0; i < total_candidates; i++) {
|
||||
if(counts[i] > highest) {
|
||||
highest = counts[i];
|
||||
highest_idx = i;
|
||||
}
|
||||
}
|
||||
|
||||
return highest_idx;
|
||||
}
|
||||
|
||||
// if array is full, start over from beginning
|
||||
static void insert_addr(uint8_t* addr, uint8_t addr_size) {
|
||||
if(candidate_idx >= MAX_ADDRS) candidate_idx = 0;
|
||||
|
||||
memcpy(candidates[candidate_idx], addr, addr_size);
|
||||
counts[candidate_idx] = 1;
|
||||
if(total_candidates < MAX_ADDRS) total_candidates++;
|
||||
candidate_idx++;
|
||||
}
|
||||
|
||||
static void render_callback(Canvas* const canvas, void* ctx) {
|
||||
furi_assert(ctx);
|
||||
const PluginState* plugin_state = ctx;
|
||||
furi_mutex_acquire(plugin_state->mutex, FuriWaitForever);
|
||||
|
||||
uint8_t rate = 2;
|
||||
char sniffing[] = "Yes";
|
||||
|
||||
// border around the edge of the screen
|
||||
canvas_draw_frame(canvas, 0, 0, 128, 64);
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
|
||||
if(target_rate == 0) rate = 1;
|
||||
|
||||
if(!sniffing_state) strcpy(sniffing, "No");
|
||||
|
||||
snprintf(rate_text, sizeof(rate_text), rate_text_fmt, (int)rate);
|
||||
snprintf(channel_text, sizeof(channel_text), channel_text_fmt, (int)target_channel, sniffing);
|
||||
snprintf(sample_text, sizeof(sample_text), sample_text_fmt, (int)sample_time);
|
||||
//snprintf(preamble_text, sizeof(preamble_text), preamble_text_fmt, target_preamble[0]);
|
||||
snprintf(sniff_text, sizeof(sniff_text), sniff_text_fmt, found_count, unique_saved_count);
|
||||
snprintf(
|
||||
sniffed_address, sizeof(sniffed_address), sniffed_address_fmt, top_address, (int)rate);
|
||||
canvas_draw_str_aligned(canvas, 10, 10, AlignLeft, AlignBottom, rate_text);
|
||||
canvas_draw_str_aligned(canvas, 10, 20, AlignLeft, AlignBottom, sample_text);
|
||||
canvas_draw_str_aligned(canvas, 10, 30, AlignLeft, AlignBottom, channel_text);
|
||||
//canvas_draw_str_aligned(canvas, 10, 30, AlignLeft, AlignBottom, preamble_text);
|
||||
canvas_draw_str_aligned(canvas, 10, 40, AlignLeft, AlignBottom, sniff_text);
|
||||
canvas_draw_str_aligned(canvas, 30, 50, AlignLeft, AlignBottom, addresses_header_text);
|
||||
canvas_draw_str_aligned(canvas, 30, 60, AlignLeft, AlignBottom, sniffed_address);
|
||||
|
||||
furi_mutex_release(plugin_state->mutex);
|
||||
}
|
||||
|
||||
static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
|
||||
furi_assert(event_queue);
|
||||
|
||||
PluginEvent event = {.type = EventTypeKey, .input = *input_event};
|
||||
furi_message_queue_put(event_queue, &event, FuriWaitForever);
|
||||
}
|
||||
|
||||
static void hexlify(uint8_t* in, uint8_t size, char* out) {
|
||||
memset(out, 0, size * 2);
|
||||
for(int i = 0; i < size; i++)
|
||||
snprintf(out + strlen(out), sizeof(out + strlen(out)), "%02X", in[i]);
|
||||
}
|
||||
|
||||
static bool save_addr_to_file(
|
||||
Storage* storage,
|
||||
uint8_t* data,
|
||||
uint8_t size,
|
||||
NotificationApp* notification) {
|
||||
size_t file_size = 0;
|
||||
uint8_t linesize = 0;
|
||||
char filepath[42] = {0};
|
||||
char addrline[14] = {0};
|
||||
char ending[4];
|
||||
uint8_t* file_contents;
|
||||
uint8_t rate = 1;
|
||||
Stream* stream = file_stream_alloc(storage);
|
||||
|
||||
if(target_rate == 8) rate = 2;
|
||||
snprintf(ending, sizeof(ending), ",%d\n", rate);
|
||||
hexlify(data, size, addrline);
|
||||
strcat(addrline, ending);
|
||||
linesize = strlen(addrline);
|
||||
strcpy(filepath, NRFSNIFF_APP_PATH_FOLDER);
|
||||
strcat(filepath, "/");
|
||||
strcat(filepath, NRFSNIFF_APP_FILENAME);
|
||||
stream_seek(stream, 0, StreamOffsetFromStart);
|
||||
|
||||
// check if address already exists in file
|
||||
if(file_stream_open(stream, filepath, FSAM_READ_WRITE, FSOM_OPEN_APPEND)) {
|
||||
bool found = false;
|
||||
file_size = stream_size(stream);
|
||||
stream_seek(stream, 0, StreamOffsetFromStart);
|
||||
if(file_size > 0) {
|
||||
file_contents = malloc(file_size + 1);
|
||||
memset(file_contents, 0, file_size + 1);
|
||||
if(stream_read(stream, file_contents, file_size) > 0) {
|
||||
char* line = strtok((char*)file_contents, "\n");
|
||||
|
||||
while(line != NULL) {
|
||||
if(!memcmp(line, addrline, 12)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
line = strtok(NULL, "\n");
|
||||
}
|
||||
}
|
||||
free(file_contents);
|
||||
}
|
||||
|
||||
if(found) {
|
||||
FURI_LOG_I(TAG, "Address exists in file. Ending save process.");
|
||||
stream_free(stream);
|
||||
return false;
|
||||
} else {
|
||||
if(stream_write(stream, (uint8_t*)addrline, linesize) != linesize) {
|
||||
FURI_LOG_I(TAG, "Failed to write bytes to file stream.");
|
||||
stream_free(stream);
|
||||
return false;
|
||||
} else {
|
||||
FURI_LOG_I(TAG, "Found a new address: %s", addrline);
|
||||
FURI_LOG_I(TAG, "Save successful!");
|
||||
|
||||
notification_message(notification, &sequence_success);
|
||||
|
||||
stream_free(stream);
|
||||
unique_saved_count++;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
FURI_LOG_I(TAG, "Cannot open file \"%s\"", filepath);
|
||||
stream_free(stream);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void alt_address(uint8_t* addr, uint8_t* altaddr) {
|
||||
uint8_t macmess_hi_b[4];
|
||||
uint32_t macmess_hi;
|
||||
uint8_t macmess_lo;
|
||||
uint8_t preserved;
|
||||
uint8_t tmpaddr[5];
|
||||
|
||||
// swap bytes
|
||||
for(int i = 0; i < 5; i++) tmpaddr[i] = addr[4 - i];
|
||||
|
||||
// get address into 32-bit and 8-bit variables
|
||||
memcpy(macmess_hi_b, tmpaddr, 4);
|
||||
macmess_lo = tmpaddr[4];
|
||||
|
||||
macmess_hi = bytes_to_int32(macmess_hi_b, true);
|
||||
|
||||
//preserve lowest bit from hi to shift to low
|
||||
preserved = macmess_hi & 1;
|
||||
macmess_hi >>= 1;
|
||||
macmess_lo >>= 1;
|
||||
macmess_lo = (preserved << 7) | macmess_lo;
|
||||
int32_to_bytes(macmess_hi, macmess_hi_b, true);
|
||||
memcpy(tmpaddr, macmess_hi_b, 4);
|
||||
tmpaddr[4] = macmess_lo;
|
||||
|
||||
// swap bytes back
|
||||
for(int i = 0; i < 5; i++) altaddr[i] = tmpaddr[4 - i];
|
||||
}
|
||||
|
||||
static bool previously_confirmed(uint8_t* addr) {
|
||||
bool found = false;
|
||||
for(int i = 0; i < MAX_CONFIRMED; i++) {
|
||||
if(!memcmp(confirmed[i], addr, 5)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
static void wrap_up(Storage* storage, NotificationApp* notification) {
|
||||
uint8_t ch;
|
||||
uint8_t addr[5];
|
||||
uint8_t altaddr[5];
|
||||
char trying[12];
|
||||
int idx;
|
||||
uint8_t rate = 0;
|
||||
if(target_rate == 8) rate = 2;
|
||||
|
||||
nrf24_set_idle(nrf24_HANDLE);
|
||||
|
||||
while(true) {
|
||||
idx = get_highest_idx();
|
||||
if(counts[idx] < COUNT_THRESHOLD) break;
|
||||
|
||||
counts[idx] = 0;
|
||||
memcpy(addr, candidates[idx], 5);
|
||||
hexlify(addr, 5, trying);
|
||||
FURI_LOG_I(TAG, "trying address %s", trying);
|
||||
ch = nrf24_find_channel(nrf24_HANDLE, addr, addr, 5, rate, 2, LOGITECH_MAX_CHANNEL, false);
|
||||
FURI_LOG_I(TAG, "find_channel returned %d", (int)ch);
|
||||
if(ch > LOGITECH_MAX_CHANNEL) {
|
||||
alt_address(addr, altaddr);
|
||||
hexlify(altaddr, 5, trying);
|
||||
FURI_LOG_I(TAG, "trying alternate address %s", trying);
|
||||
ch = nrf24_find_channel(
|
||||
nrf24_HANDLE, altaddr, altaddr, 5, rate, 2, LOGITECH_MAX_CHANNEL, false);
|
||||
FURI_LOG_I(TAG, "find_channel returned %d", (int)ch);
|
||||
memcpy(addr, altaddr, 5);
|
||||
}
|
||||
|
||||
if(ch <= LOGITECH_MAX_CHANNEL) {
|
||||
hexlify(addr, 5, top_address);
|
||||
found_count++;
|
||||
save_addr_to_file(storage, addr, 5, notification);
|
||||
if(confirmed_idx < MAX_CONFIRMED) memcpy(confirmed[confirmed_idx++], addr, 5);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void clear_cache() {
|
||||
found_count = 0;
|
||||
unique_saved_count = 0;
|
||||
confirmed_idx = 0;
|
||||
candidate_idx = 0;
|
||||
target_channel = 2;
|
||||
total_candidates = 0;
|
||||
memset(candidates, 0, sizeof(candidates));
|
||||
memset(counts, 0, sizeof(counts));
|
||||
memset(confirmed, 0, sizeof(confirmed));
|
||||
}
|
||||
|
||||
static void start_sniffing() {
|
||||
nrf24_init_promisc_mode(nrf24_HANDLE, target_channel, target_rate);
|
||||
}
|
||||
|
||||
int32_t nrfsniff_app(void* p) {
|
||||
UNUSED(p);
|
||||
uint8_t address[5] = {0};
|
||||
uint32_t start = 0;
|
||||
hexlify(address, 5, top_address);
|
||||
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent));
|
||||
PluginState* plugin_state = malloc(sizeof(PluginState));
|
||||
plugin_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
||||
if(!plugin_state->mutex) {
|
||||
furi_message_queue_free(event_queue);
|
||||
FURI_LOG_E(TAG, "cannot create mutex\r\n");
|
||||
free(plugin_state);
|
||||
return 255;
|
||||
}
|
||||
|
||||
uint8_t attempts = 0;
|
||||
while(!furi_hal_power_is_otg_enabled() && attempts++ < 5) {
|
||||
furi_hal_power_enable_otg();
|
||||
furi_delay_ms(10);
|
||||
}
|
||||
|
||||
nrf24_init();
|
||||
|
||||
// Set system callbacks
|
||||
ViewPort* view_port = view_port_alloc();
|
||||
view_port_draw_callback_set(view_port, render_callback, plugin_state);
|
||||
view_port_input_callback_set(view_port, 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);
|
||||
|
||||
NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
|
||||
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
storage_common_migrate(storage, EXT_PATH("nrfsniff"), NRFSNIFF_APP_PATH_FOLDER);
|
||||
storage_common_mkdir(storage, NRFSNIFF_APP_PATH_FOLDER);
|
||||
|
||||
PluginEvent event;
|
||||
for(bool processing = true; processing;) {
|
||||
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
|
||||
furi_mutex_acquire(plugin_state->mutex, FuriWaitForever);
|
||||
|
||||
if(event_status == FuriStatusOk) {
|
||||
// press events
|
||||
if(event.type == EventTypeKey) {
|
||||
if(event.input.type == InputTypePress ||
|
||||
(event.input.type == InputTypeLong && event.input.key == InputKeyBack)) {
|
||||
switch(event.input.key) {
|
||||
case InputKeyUp:
|
||||
// toggle rate 1/2Mbps
|
||||
if(!sniffing_state) {
|
||||
if(target_rate == 0)
|
||||
target_rate = 8;
|
||||
else
|
||||
target_rate = 0;
|
||||
}
|
||||
break;
|
||||
case InputKeyDown:
|
||||
// toggle preamble
|
||||
if(!sniffing_state) {
|
||||
if(target_preamble[0] == 0x55)
|
||||
target_preamble[0] = 0xAA;
|
||||
else
|
||||
target_preamble[0] = 0x55;
|
||||
|
||||
nrf24_set_src_mac(nrf24_HANDLE, target_preamble, 2);
|
||||
}
|
||||
break;
|
||||
case InputKeyRight:
|
||||
// increment channel
|
||||
//if(!sniffing_state && target_channel <= LOGITECH_MAX_CHANNEL)
|
||||
// target_channel++;
|
||||
sample_time += 500;
|
||||
break;
|
||||
case InputKeyLeft:
|
||||
// decrement channel
|
||||
//if(!sniffing_state && target_channel > 0) target_channel--;
|
||||
if(sample_time > 500) sample_time -= 500;
|
||||
break;
|
||||
case InputKeyOk:
|
||||
// toggle sniffing
|
||||
if(nrf24_check_connected(nrf24_HANDLE)) {
|
||||
sniffing_state = !sniffing_state;
|
||||
if(sniffing_state) {
|
||||
clear_cache();
|
||||
start_sniffing();
|
||||
start = furi_get_tick();
|
||||
} else {
|
||||
wrap_up(storage, notification);
|
||||
}
|
||||
} else {
|
||||
notification_message(notification, &sequence_error);
|
||||
}
|
||||
|
||||
break;
|
||||
case InputKeyBack:
|
||||
processing = false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(sniffing_state) {
|
||||
if(nrf24_sniff_address(nrf24_HANDLE, 5, address)) {
|
||||
int idx;
|
||||
uint8_t* top_addr;
|
||||
if(!previously_confirmed(address)) {
|
||||
idx = get_addr_index(address, 5);
|
||||
if(idx == -1)
|
||||
insert_addr(address, 5);
|
||||
else
|
||||
counts[idx]++;
|
||||
|
||||
top_addr = candidates[get_highest_idx()];
|
||||
hexlify(top_addr, 5, top_address);
|
||||
}
|
||||
}
|
||||
|
||||
if(furi_get_tick() - start >= sample_time) {
|
||||
target_channel++;
|
||||
if(target_channel > LOGITECH_MAX_CHANNEL) target_channel = 2;
|
||||
{
|
||||
wrap_up(storage, notification);
|
||||
start_sniffing();
|
||||
}
|
||||
|
||||
start = furi_get_tick();
|
||||
}
|
||||
}
|
||||
|
||||
view_port_update(view_port);
|
||||
furi_mutex_release(plugin_state->mutex);
|
||||
}
|
||||
|
||||
clear_cache();
|
||||
sample_time = DEFAULT_SAMPLE_TIME;
|
||||
target_rate = 8; // rate can be either 8 (2Mbps) or 0 (1Mbps)
|
||||
sniffing_state = false;
|
||||
nrf24_deinit();
|
||||
view_port_enabled_set(view_port, false);
|
||||
gui_remove_view_port(gui, view_port);
|
||||
furi_record_close(RECORD_GUI);
|
||||
furi_record_close(RECORD_NOTIFICATION);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
view_port_free(view_port);
|
||||
furi_message_queue_free(event_queue);
|
||||
furi_mutex_free(plugin_state->mutex);
|
||||
free(plugin_state);
|
||||
|
||||
if(furi_hal_power_is_otg_enabled()) {
|
||||
furi_hal_power_disable_otg();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
Before Width: | Height: | Size: 1.7 KiB |
@@ -1,16 +0,0 @@
|
||||
App(
|
||||
appid="ocarina",
|
||||
name="Ocarina",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="ocarina_app",
|
||||
cdefines=["APP_OCARINA"],
|
||||
requires=["gui"],
|
||||
stack_size=1 * 1024,
|
||||
fap_icon="music_10px.png",
|
||||
fap_category="Media",
|
||||
fap_icon_assets="icons",
|
||||
fap_author="@invalidna-me",
|
||||
fap_weburl="https://github.com/invalidna-me/flipperzero-ocarina",
|
||||
fap_version="1.0",
|
||||
fap_description="A basic Ocarina (of Time), Controls are the same as the N64 version of the Ocarina of Time",
|
||||
)
|
||||
|
Before Width: | Height: | Size: 142 B |
@@ -1,134 +0,0 @@
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <gui/gui.h>
|
||||
#include <input/input.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define NOTE_UP 587.33f
|
||||
#define NOTE_LEFT 493.88f
|
||||
#define NOTE_RIGHT 440.00f
|
||||
#define NOTE_DOWN 349.23
|
||||
#define NOTE_OK 293.66f
|
||||
|
||||
typedef struct {
|
||||
FuriMutex* model_mutex;
|
||||
|
||||
FuriMessageQueue* event_queue;
|
||||
|
||||
ViewPort* view_port;
|
||||
Gui* gui;
|
||||
} Ocarina;
|
||||
|
||||
void draw_callback(Canvas* canvas, void* ctx) {
|
||||
Ocarina* ocarina = ctx;
|
||||
furi_check(furi_mutex_acquire(ocarina->model_mutex, FuriWaitForever) == FuriStatusOk);
|
||||
|
||||
//canvas_draw_box(canvas, ocarina->model->x, ocarina->model->y, 4, 4);
|
||||
canvas_draw_frame(canvas, 0, 0, 128, 64);
|
||||
canvas_draw_str(canvas, 50, 10, "Ocarina");
|
||||
canvas_draw_str(canvas, 30, 20, "OK button for A");
|
||||
|
||||
furi_mutex_release(ocarina->model_mutex);
|
||||
}
|
||||
|
||||
void input_callback(InputEvent* input, void* ctx) {
|
||||
Ocarina* ocarina = ctx;
|
||||
// Puts input onto event queue with priority 0, and waits until completion.
|
||||
furi_message_queue_put(ocarina->event_queue, input, FuriWaitForever);
|
||||
}
|
||||
|
||||
Ocarina* ocarina_alloc() {
|
||||
Ocarina* instance = malloc(sizeof(Ocarina));
|
||||
|
||||
instance->model_mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
||||
|
||||
instance->event_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
|
||||
|
||||
instance->view_port = view_port_alloc();
|
||||
view_port_draw_callback_set(instance->view_port, draw_callback, instance);
|
||||
view_port_input_callback_set(instance->view_port, input_callback, instance);
|
||||
|
||||
instance->gui = furi_record_open("gui");
|
||||
gui_add_view_port(instance->gui, instance->view_port, GuiLayerFullscreen);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void ocarina_free(Ocarina* instance) {
|
||||
view_port_enabled_set(instance->view_port, false); // Disabsles our ViewPort
|
||||
gui_remove_view_port(instance->gui, instance->view_port); // Removes our ViewPort from the Gui
|
||||
furi_record_close("gui"); // Closes the gui record
|
||||
view_port_free(instance->view_port); // Frees memory allocated by view_port_alloc
|
||||
furi_message_queue_free(instance->event_queue);
|
||||
|
||||
furi_mutex_free(instance->model_mutex);
|
||||
|
||||
if(furi_hal_speaker_is_mine()) {
|
||||
furi_hal_speaker_stop();
|
||||
furi_hal_speaker_release();
|
||||
}
|
||||
|
||||
free(instance);
|
||||
}
|
||||
|
||||
int32_t ocarina_app(void* p) {
|
||||
UNUSED(p);
|
||||
|
||||
Ocarina* ocarina = ocarina_alloc();
|
||||
|
||||
InputEvent event;
|
||||
for(bool processing = true; processing;) {
|
||||
// Pops a message off the queue and stores it in `event`.
|
||||
// No message priority denoted by NULL, and 100 ticks of timeout.
|
||||
FuriStatus status = furi_message_queue_get(ocarina->event_queue, &event, 100);
|
||||
furi_check(furi_mutex_acquire(ocarina->model_mutex, FuriWaitForever) == FuriStatusOk);
|
||||
|
||||
float volume = 1.0f;
|
||||
if(status == FuriStatusOk) {
|
||||
if(event.type == InputTypePress) {
|
||||
switch(event.key) {
|
||||
case InputKeyUp:
|
||||
if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(1000)) {
|
||||
furi_hal_speaker_start(NOTE_UP, volume);
|
||||
}
|
||||
break;
|
||||
case InputKeyDown:
|
||||
if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(1000)) {
|
||||
furi_hal_speaker_start(NOTE_DOWN, volume);
|
||||
}
|
||||
break;
|
||||
case InputKeyLeft:
|
||||
if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(1000)) {
|
||||
furi_hal_speaker_start(NOTE_LEFT, volume);
|
||||
}
|
||||
break;
|
||||
case InputKeyRight:
|
||||
if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(1000)) {
|
||||
furi_hal_speaker_start(NOTE_RIGHT, volume);
|
||||
}
|
||||
break;
|
||||
case InputKeyOk:
|
||||
if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(1000)) {
|
||||
furi_hal_speaker_start(NOTE_OK, volume);
|
||||
}
|
||||
break;
|
||||
case InputKeyBack:
|
||||
processing = false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else if(event.type == InputTypeRelease) {
|
||||
if(furi_hal_speaker_is_mine()) {
|
||||
furi_hal_speaker_stop();
|
||||
furi_hal_speaker_release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
furi_mutex_release(ocarina->model_mutex);
|
||||
view_port_update(ocarina->view_port); // signals our draw callback
|
||||
}
|
||||
ocarina_free(ocarina);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
App(
|
||||
appid="orgasmotron",
|
||||
name="Orgasmotron",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="orgasmotron_app",
|
||||
cdefines=["ORGASMOTRON"],
|
||||
requires=["gui"],
|
||||
stack_size=1 * 1024,
|
||||
fap_icon="orgasmotron_10px.png",
|
||||
fap_category="Tools",
|
||||
)
|
||||
@@ -1,163 +0,0 @@
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
|
||||
#include <gui/gui.h>
|
||||
#include <input/input.h>
|
||||
#include <notification/notification_messages.h>
|
||||
|
||||
typedef struct {
|
||||
FuriMutex* mutex;
|
||||
int mode;
|
||||
} PluginState;
|
||||
|
||||
void vibro_test_draw_callback(Canvas* canvas, void* ctx) {
|
||||
UNUSED(ctx);
|
||||
canvas_clear(canvas);
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
canvas_draw_str(canvas, 2, 10, "Vibro Modes");
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
canvas_draw_str(canvas, 2, 22, "UP: Pulsed");
|
||||
canvas_draw_str(canvas, 2, 34, "LEFT: strong / RIGHT: Soft");
|
||||
canvas_draw_str(canvas, 2, 46, "DOWN: Pleasure combo");
|
||||
canvas_draw_str(canvas, 2, 58, "OK: Pause");
|
||||
}
|
||||
|
||||
void vibro_test_input_callback(InputEvent* input_event, void* ctx) {
|
||||
furi_assert(ctx);
|
||||
FuriMessageQueue* event_queue = ctx;
|
||||
furi_message_queue_put(event_queue, input_event, FuriWaitForever);
|
||||
}
|
||||
|
||||
void delay(int milliseconds) {
|
||||
furi_thread_flags_wait(0, FuriFlagWaitAny, milliseconds);
|
||||
}
|
||||
|
||||
int32_t orgasmotron_app(void* p) {
|
||||
UNUSED(p);
|
||||
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
|
||||
|
||||
PluginState* plugin_state = malloc(sizeof(PluginState));
|
||||
plugin_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
||||
if(!plugin_state->mutex) {
|
||||
FURI_LOG_E("Orgasmatron", "cannot create mutex\r\n");
|
||||
free(plugin_state);
|
||||
return 255;
|
||||
}
|
||||
|
||||
// Configure view port
|
||||
ViewPort* view_port = view_port_alloc();
|
||||
view_port_draw_callback_set(view_port, vibro_test_draw_callback, NULL);
|
||||
view_port_input_callback_set(view_port, vibro_test_input_callback, event_queue);
|
||||
|
||||
// Register view port in GUI
|
||||
Gui* gui = furi_record_open(RECORD_GUI);
|
||||
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
|
||||
|
||||
NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
|
||||
|
||||
InputEvent event;
|
||||
bool processing = true;
|
||||
size_t i = 0;
|
||||
while(processing) {
|
||||
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 50);
|
||||
furi_mutex_acquire(plugin_state->mutex, FuriWaitForever);
|
||||
if(event_status == FuriStatusOk) {
|
||||
if(event.key == InputKeyBack && event.type == InputTypeShort) {
|
||||
//Exit Application
|
||||
plugin_state->mode = 0;
|
||||
processing = false;
|
||||
}
|
||||
if(event.key == InputKeyOk &&
|
||||
(event.type == InputTypePress || event.type == InputTypeRelease)) {
|
||||
plugin_state->mode = 0;
|
||||
}
|
||||
if(event.key == InputKeyLeft &&
|
||||
(event.type == InputTypePress || event.type == InputTypeRelease)) {
|
||||
notification_message(notification, &sequence_set_green_255);
|
||||
plugin_state->mode = 1;
|
||||
}
|
||||
if(event.key == InputKeyRight &&
|
||||
(event.type == InputTypePress || event.type == InputTypeRelease)) {
|
||||
notification_message(notification, &sequence_set_green_255);
|
||||
plugin_state->mode = 3;
|
||||
}
|
||||
if(event.key == InputKeyUp &&
|
||||
(event.type == InputTypePress || event.type == InputTypeRelease)) {
|
||||
notification_message(notification, &sequence_set_green_255);
|
||||
plugin_state->mode = 2;
|
||||
}
|
||||
if(event.key == InputKeyDown &&
|
||||
(event.type == InputTypePress || event.type == InputTypeRelease)) {
|
||||
notification_message(notification, &sequence_set_green_255);
|
||||
plugin_state->mode = 4;
|
||||
}
|
||||
i = 0;
|
||||
}
|
||||
|
||||
if(plugin_state->mode == 0) {
|
||||
//Stop Vibration
|
||||
if(i == 0) {
|
||||
notification_message(notification, &sequence_reset_vibro);
|
||||
notification_message(notification, &sequence_reset_green);
|
||||
i++;
|
||||
}
|
||||
} else if(plugin_state->mode == 1) {
|
||||
//Full power
|
||||
if(i == 0) {
|
||||
notification_message(notification, &sequence_set_vibro_on);
|
||||
i++;
|
||||
}
|
||||
} else if(plugin_state->mode == 2) {
|
||||
//Pulsed Vibration
|
||||
i++;
|
||||
if(i == 1) {
|
||||
notification_message(notification, &sequence_set_vibro_on);
|
||||
}
|
||||
if(i == 3) {
|
||||
notification_message(notification, &sequence_reset_vibro);
|
||||
}
|
||||
if(i == 4) {
|
||||
i = 0;
|
||||
}
|
||||
} else if(plugin_state->mode == 3) {
|
||||
//Soft power
|
||||
i++;
|
||||
if(i == 1) {
|
||||
notification_message(notification, &sequence_set_vibro_on);
|
||||
}
|
||||
if(i == 2) {
|
||||
notification_message(notification, &sequence_reset_vibro);
|
||||
i = 0;
|
||||
}
|
||||
} else if(plugin_state->mode == 4) {
|
||||
//Special Sequence
|
||||
i++;
|
||||
if(i < 23) {
|
||||
if(i % 2) {
|
||||
notification_message(notification, &sequence_set_vibro_on);
|
||||
} else {
|
||||
notification_message(notification, &sequence_reset_vibro);
|
||||
}
|
||||
} else if(i < 40) {
|
||||
if(i == 24 || i == 33) {
|
||||
notification_message(notification, &sequence_set_vibro_on);
|
||||
} else if(i == 32) {
|
||||
notification_message(notification, &sequence_reset_vibro);
|
||||
}
|
||||
} else if(i == 41) {
|
||||
notification_message(notification, &sequence_reset_vibro);
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
furi_mutex_release(plugin_state->mutex);
|
||||
}
|
||||
gui_remove_view_port(gui, view_port);
|
||||
view_port_free(view_port);
|
||||
furi_mutex_free(plugin_state->mutex);
|
||||
furi_message_queue_free(event_queue);
|
||||
|
||||
furi_record_close(RECORD_NOTIFICATION);
|
||||
furi_record_close(RECORD_GUI);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
Before Width: | Height: | Size: 162 B |
@@ -1,17 +0,0 @@
|
||||
App(
|
||||
appid="flipper_pong",
|
||||
name="Pong",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="flipper_pong_app",
|
||||
cdefines=["APP_FLIPPER_PONG"],
|
||||
requires=[
|
||||
"gui",
|
||||
],
|
||||
stack_size=1 * 1024,
|
||||
fap_icon="pong.png",
|
||||
fap_category="Games",
|
||||
fap_author="@nmrr & @SimplyMinimal",
|
||||
fap_weburl="https://github.com/nmrr/flipperzero-pong",
|
||||
fap_version="1.1",
|
||||
fap_description="Simple pong game",
|
||||
)
|
||||
@@ -1,293 +0,0 @@
|
||||
// CC0 1.0 Universal (CC0 1.0)
|
||||
// Public Domain Dedication
|
||||
// https://github.com/nmrr
|
||||
|
||||
#include <stdio.h>
|
||||
#include <furi.h>
|
||||
#include <gui/gui.h>
|
||||
#include <input/input.h>
|
||||
#include <notification/notification_messages.h>
|
||||
#include <furi_hal_random.h>
|
||||
|
||||
#define SCREEN_SIZE_X 128
|
||||
#define SCREEN_SIZE_Y 64
|
||||
#define FPS 20
|
||||
|
||||
#define PAD_SIZE_X 3
|
||||
#define PAD_SIZE_Y 8
|
||||
#define PLAYER1_PAD_SPEED 4
|
||||
|
||||
#define PLAYER2_PAD_SPEED 2
|
||||
#define BALL_SIZE 4
|
||||
|
||||
typedef enum {
|
||||
EventTypeInput,
|
||||
ClockEventTypeTick,
|
||||
} EventType;
|
||||
|
||||
typedef struct {
|
||||
EventType type;
|
||||
InputEvent input;
|
||||
} EventApp;
|
||||
|
||||
typedef struct Players {
|
||||
FuriMutex* mutex;
|
||||
uint8_t player1_X, player1_Y, player2_X, player2_Y;
|
||||
uint16_t player1_score, player2_score;
|
||||
uint8_t ball_X, ball_Y, ball_X_speed, ball_Y_speed, ball_X_direction, ball_Y_direction;
|
||||
} Players;
|
||||
|
||||
static void draw_callback(Canvas* canvas, void* ctx) {
|
||||
furi_assert(ctx);
|
||||
Players* playersMutex = ctx;
|
||||
furi_mutex_acquire(playersMutex->mutex, FuriWaitForever);
|
||||
|
||||
canvas_draw_frame(canvas, 0, 0, 128, 64);
|
||||
canvas_draw_box(
|
||||
canvas, playersMutex->player1_X, playersMutex->player1_Y, PAD_SIZE_X, PAD_SIZE_Y);
|
||||
canvas_draw_box(
|
||||
canvas, playersMutex->player2_X, playersMutex->player2_Y, PAD_SIZE_X, PAD_SIZE_Y);
|
||||
canvas_draw_box(canvas, playersMutex->ball_X, playersMutex->ball_Y, BALL_SIZE, BALL_SIZE);
|
||||
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
canvas_set_font_direction(canvas, CanvasDirectionBottomToTop);
|
||||
char buffer[16];
|
||||
snprintf(
|
||||
buffer,
|
||||
sizeof(buffer),
|
||||
"%u - %u",
|
||||
playersMutex->player1_score,
|
||||
playersMutex->player2_score);
|
||||
canvas_draw_str_aligned(
|
||||
canvas, SCREEN_SIZE_X / 2 + 15, SCREEN_SIZE_Y / 2 + 2, AlignCenter, AlignTop, buffer);
|
||||
|
||||
furi_mutex_release(playersMutex->mutex);
|
||||
}
|
||||
|
||||
static void input_callback(InputEvent* input_event, void* ctx) {
|
||||
furi_assert(ctx);
|
||||
FuriMessageQueue* event_queue = ctx;
|
||||
EventApp event = {.type = EventTypeInput, .input = *input_event};
|
||||
furi_message_queue_put(event_queue, &event, FuriWaitForever);
|
||||
}
|
||||
|
||||
static void clock_tick(void* ctx) {
|
||||
furi_assert(ctx);
|
||||
FuriMessageQueue* queue = ctx;
|
||||
EventApp event = {.type = ClockEventTypeTick};
|
||||
furi_message_queue_put(queue, &event, 0);
|
||||
}
|
||||
|
||||
bool insidePad(uint8_t x, uint8_t y, uint8_t playerX, uint8_t playerY) {
|
||||
if(x >= playerX && x <= playerX + PAD_SIZE_X && y >= playerY && y <= playerY + PAD_SIZE_Y)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t changeSpeed() {
|
||||
uint8_t randomuint8[1];
|
||||
while(1) {
|
||||
furi_hal_random_fill_buf(randomuint8, 1);
|
||||
randomuint8[0] &= 0b00000011;
|
||||
if(randomuint8[0] >= 1) break;
|
||||
}
|
||||
return randomuint8[0];
|
||||
}
|
||||
|
||||
uint8_t changeDirection() {
|
||||
uint8_t randomuint8[1];
|
||||
furi_hal_random_fill_buf(randomuint8, 1);
|
||||
randomuint8[0] &= 0b1;
|
||||
return randomuint8[0];
|
||||
}
|
||||
|
||||
int32_t flipper_pong_app(void* p) {
|
||||
UNUSED(p);
|
||||
EventApp event;
|
||||
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(EventApp));
|
||||
|
||||
Players players;
|
||||
players.player1_X = SCREEN_SIZE_X - PAD_SIZE_X - 1;
|
||||
players.player1_Y = SCREEN_SIZE_Y / 2 - PAD_SIZE_Y / 2;
|
||||
players.player1_score = 0;
|
||||
|
||||
players.player2_X = 1;
|
||||
players.player2_Y = SCREEN_SIZE_Y / 2 - PAD_SIZE_Y / 2;
|
||||
players.player2_score = 0;
|
||||
|
||||
players.ball_X = SCREEN_SIZE_X / 2 - BALL_SIZE / 2;
|
||||
players.ball_Y = SCREEN_SIZE_Y / 2 - BALL_SIZE / 2;
|
||||
players.ball_X_speed = 1;
|
||||
players.ball_Y_speed = 1;
|
||||
players.ball_X_direction = changeDirection();
|
||||
players.ball_Y_direction = changeDirection();
|
||||
|
||||
players.mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
||||
if(!players.mutex) {
|
||||
furi_message_queue_free(event_queue);
|
||||
return 255;
|
||||
}
|
||||
|
||||
ViewPort* view_port = view_port_alloc();
|
||||
view_port_draw_callback_set(view_port, draw_callback, &players);
|
||||
view_port_input_callback_set(view_port, input_callback, event_queue);
|
||||
|
||||
Gui* gui = furi_record_open(RECORD_GUI);
|
||||
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
|
||||
|
||||
NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
|
||||
if(players.ball_X_direction == 0)
|
||||
notification_message(notification, &sequence_set_only_red_255);
|
||||
else
|
||||
notification_message(notification, &sequence_set_only_blue_255);
|
||||
|
||||
FuriTimer* timer = furi_timer_alloc(clock_tick, FuriTimerTypePeriodic, event_queue);
|
||||
furi_timer_start(timer, 1000 / FPS);
|
||||
|
||||
while(1) {
|
||||
FuriStatus event_status = furi_message_queue_get(event_queue, &event, FuriWaitForever);
|
||||
furi_mutex_acquire(players.mutex, FuriWaitForever);
|
||||
|
||||
if(event_status == FuriStatusOk) {
|
||||
if(event.type == EventTypeInput) {
|
||||
if(event.input.key == InputKeyBack) {
|
||||
furi_mutex_release(players.mutex);
|
||||
notification_message(notification, &sequence_set_only_green_255);
|
||||
break;
|
||||
} else if(event.input.key == InputKeyUp) {
|
||||
if(players.player1_Y >= 1 + PLAYER1_PAD_SPEED)
|
||||
players.player1_Y -= PLAYER1_PAD_SPEED;
|
||||
else
|
||||
players.player1_Y = 1;
|
||||
} else if(event.input.key == InputKeyDown) {
|
||||
if(players.player1_Y <= SCREEN_SIZE_Y - PAD_SIZE_Y - PLAYER1_PAD_SPEED - 1)
|
||||
players.player1_Y += PLAYER1_PAD_SPEED;
|
||||
else
|
||||
players.player1_Y = SCREEN_SIZE_Y - PAD_SIZE_Y - 1;
|
||||
}
|
||||
} else if(event.type == ClockEventTypeTick) {
|
||||
if(players.ball_X + BALL_SIZE / 2 <= SCREEN_SIZE_X * 0.35 &&
|
||||
players.ball_X_direction == 0) {
|
||||
if(players.ball_Y + BALL_SIZE / 2 < players.player2_Y + PAD_SIZE_Y / 2) {
|
||||
if(players.player2_Y >= 1 + PLAYER2_PAD_SPEED)
|
||||
players.player2_Y -= PLAYER2_PAD_SPEED;
|
||||
else
|
||||
players.player2_Y = 1;
|
||||
} else if(players.ball_Y + BALL_SIZE / 2 > players.player2_Y + PAD_SIZE_Y / 2) {
|
||||
if(players.player2_Y <= SCREEN_SIZE_Y - PAD_SIZE_Y - PLAYER2_PAD_SPEED - 1)
|
||||
players.player2_Y += PLAYER2_PAD_SPEED;
|
||||
else
|
||||
players.player2_Y = SCREEN_SIZE_Y - PAD_SIZE_Y - 1;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t ball_corner_X[4] = {
|
||||
players.ball_X,
|
||||
players.ball_X + BALL_SIZE,
|
||||
players.ball_X + BALL_SIZE,
|
||||
players.ball_X};
|
||||
uint8_t ball_corner_Y[4] = {
|
||||
players.ball_Y,
|
||||
players.ball_Y,
|
||||
players.ball_Y + BALL_SIZE,
|
||||
players.ball_Y + BALL_SIZE};
|
||||
bool insidePlayer1 = false, insidePlayer2 = false;
|
||||
|
||||
for(int i = 0; i < 4; i++) {
|
||||
if(insidePad(
|
||||
ball_corner_X[i],
|
||||
ball_corner_Y[i],
|
||||
players.player1_X,
|
||||
players.player1_Y) == true) {
|
||||
insidePlayer1 = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if(insidePad(
|
||||
ball_corner_X[i],
|
||||
ball_corner_Y[i],
|
||||
players.player2_X,
|
||||
players.player2_Y) == true) {
|
||||
insidePlayer2 = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(insidePlayer1 == true) {
|
||||
players.ball_X_direction = 0;
|
||||
players.ball_X -= players.ball_X_speed;
|
||||
players.ball_X_speed = changeSpeed();
|
||||
players.ball_Y_speed = changeSpeed();
|
||||
notification_message(notification, &sequence_set_only_red_255);
|
||||
} else if(insidePlayer2 == true) {
|
||||
players.ball_X_direction = 1;
|
||||
players.ball_X += players.ball_X_speed;
|
||||
players.ball_X_speed = changeSpeed();
|
||||
players.ball_Y_speed = changeSpeed();
|
||||
notification_message(notification, &sequence_set_only_blue_255);
|
||||
} else {
|
||||
if(players.ball_X_direction == 1) {
|
||||
if(players.ball_X <=
|
||||
SCREEN_SIZE_X - BALL_SIZE - 1 - players.ball_X_speed) {
|
||||
players.ball_X += players.ball_X_speed;
|
||||
} else {
|
||||
players.ball_X = SCREEN_SIZE_X / 2 - BALL_SIZE / 2;
|
||||
players.ball_Y = SCREEN_SIZE_Y / 2 - BALL_SIZE / 2;
|
||||
players.ball_X_speed = 1;
|
||||
players.ball_Y_speed = 1;
|
||||
players.ball_X_direction = 0;
|
||||
players.player2_score++;
|
||||
notification_message(notification, &sequence_set_only_red_255);
|
||||
}
|
||||
} else {
|
||||
if(players.ball_X >= 1 + players.ball_X_speed) {
|
||||
players.ball_X -= players.ball_X_speed;
|
||||
} else {
|
||||
players.ball_X = SCREEN_SIZE_X / 2 - BALL_SIZE / 2;
|
||||
players.ball_Y = SCREEN_SIZE_Y / 2 - BALL_SIZE / 2;
|
||||
players.ball_X_speed = 1;
|
||||
players.ball_Y_speed = 1;
|
||||
players.ball_X_direction = 1;
|
||||
players.player1_score++;
|
||||
notification_message(notification, &sequence_set_only_blue_255);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(players.ball_Y_direction == 1) {
|
||||
if(players.ball_Y <= SCREEN_SIZE_Y - BALL_SIZE - 1 - players.ball_Y_speed) {
|
||||
players.ball_Y += players.ball_Y_speed;
|
||||
} else {
|
||||
players.ball_Y = SCREEN_SIZE_Y - BALL_SIZE - 1;
|
||||
players.ball_X_speed = changeSpeed();
|
||||
players.ball_Y_speed = changeSpeed();
|
||||
players.ball_Y_direction = 0;
|
||||
}
|
||||
} else {
|
||||
if(players.ball_Y >= 1 + players.ball_Y_speed) {
|
||||
players.ball_Y -= players.ball_Y_speed;
|
||||
} else {
|
||||
players.ball_Y = 1;
|
||||
players.ball_X_speed = changeSpeed();
|
||||
players.ball_Y_speed = changeSpeed();
|
||||
players.ball_Y_direction = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
furi_mutex_release(players.mutex);
|
||||
view_port_update(view_port);
|
||||
}
|
||||
|
||||
notification_message(notification, &sequence_reset_rgb);
|
||||
furi_message_queue_free(event_queue);
|
||||
furi_mutex_free(players.mutex);
|
||||
gui_remove_view_port(gui, view_port);
|
||||
view_port_free(view_port);
|
||||
furi_timer_free(timer);
|
||||
furi_record_close(RECORD_GUI);
|
||||
furi_record_close(RECORD_NOTIFICATION);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
Before Width: | Height: | Size: 7.1 KiB |
@@ -1,52 +0,0 @@
|
||||
App(
|
||||
appid="sam",
|
||||
name="SAM AYBABTU",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="sam_app",
|
||||
requires=[
|
||||
"gui",
|
||||
"dialogs",
|
||||
],
|
||||
stack_size=4 * 1024,
|
||||
fap_icon="music_10px.png",
|
||||
fap_category="Media",
|
||||
)
|
||||
App(
|
||||
appid="sam_yes",
|
||||
name="SAM YES",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="sam_app_yes",
|
||||
requires=[
|
||||
"gui",
|
||||
"dialogs",
|
||||
],
|
||||
stack_size=4 * 1024,
|
||||
fap_icon="music_10px.png",
|
||||
fap_category="Media",
|
||||
)
|
||||
App(
|
||||
appid="sam_no",
|
||||
name="SAM NO",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="sam_app_no",
|
||||
requires=[
|
||||
"gui",
|
||||
"dialogs",
|
||||
],
|
||||
stack_size=4 * 1024,
|
||||
fap_icon="music_10px.png",
|
||||
fap_category="Media",
|
||||
)
|
||||
App(
|
||||
appid="sam_wtf",
|
||||
name="SAM WTF",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="sam_app_wtf",
|
||||
requires=[
|
||||
"gui",
|
||||
"dialogs",
|
||||
],
|
||||
stack_size=4 * 1024,
|
||||
fap_icon="music_10px.png",
|
||||
fap_category="Media",
|
||||
)
|
||||
|
Before Width: | Height: | Size: 142 B |
@@ -1,46 +0,0 @@
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include "stm32_sam.h"
|
||||
// WOULD BE COOL IF SOMEONE MADE A TEXT ENTRY SCREEN TO HAVE IT READ WHAT IS ENTERED TO TEXT
|
||||
STM32SAM voice;
|
||||
|
||||
extern "C" int32_t sam_app(void* p) {
|
||||
UNUSED(p);
|
||||
if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(1000)) {
|
||||
voice.begin();
|
||||
voice.say(
|
||||
"All your base are belong to us. You have no chance to survive make your time. ha. ha. ha. GOOD BYE. ");
|
||||
furi_hal_speaker_release();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern "C" int32_t sam_app_yes(void* p) {
|
||||
UNUSED(p);
|
||||
if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(1000)) {
|
||||
voice.begin();
|
||||
voice.say("Yes");
|
||||
furi_hal_speaker_release();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern "C" int32_t sam_app_no(void* p) {
|
||||
UNUSED(p);
|
||||
if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(1000)) {
|
||||
voice.begin();
|
||||
voice.say("No");
|
||||
furi_hal_speaker_release();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern "C" int32_t sam_app_wtf(void* p) {
|
||||
UNUSED(p);
|
||||
if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(1000)) {
|
||||
voice.begin();
|
||||
voice.say("What The Fuck");
|
||||
furi_hal_speaker_release();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
#include <furi.h>
|
||||
|
||||
#ifndef __STM32SAM__
|
||||
#define __STM32SAM__
|
||||
|
||||
// SAM Text-To-Speech (TTS), ported from https://github.com/s-macke/SAM
|
||||
|
||||
class STM32SAM {
|
||||
public:
|
||||
STM32SAM(uint32_t STM32SAM_SPEED);
|
||||
STM32SAM();
|
||||
|
||||
void begin(void);
|
||||
|
||||
void
|
||||
sam(const char* argv,
|
||||
unsigned char phonetic,
|
||||
unsigned char singmode,
|
||||
unsigned char pitch,
|
||||
unsigned char speed,
|
||||
unsigned char mouth,
|
||||
unsigned char throat);
|
||||
void
|
||||
sam(char* argv,
|
||||
unsigned char phonetic,
|
||||
unsigned char singmode,
|
||||
unsigned char pitch,
|
||||
unsigned char speed,
|
||||
unsigned char mouth,
|
||||
unsigned char throat);
|
||||
|
||||
void say(const char* argv);
|
||||
void say(char* argv);
|
||||
void sing(const char* argv);
|
||||
void sing(char* argv);
|
||||
void sayPhonetic(const char* argv);
|
||||
void sayPhonetic(char* argv);
|
||||
void singPhonetic(const char* argv);
|
||||
void singPhonetic(char* argv);
|
||||
void setVoice(
|
||||
unsigned char _pitch = 64,
|
||||
unsigned char _speed = 72,
|
||||
unsigned char _mouth = 128,
|
||||
unsigned char _throat = 128);
|
||||
void setPitch(unsigned char _pitch = 64);
|
||||
void setSpeed(unsigned char _speed = 72);
|
||||
void setMouth(unsigned char _mouth = 128);
|
||||
void setThroat(unsigned char _throat = 128);
|
||||
|
||||
private:
|
||||
void SetAUDIO(unsigned char main_volume);
|
||||
|
||||
void Output8BitAry(int index, unsigned char ary[5]);
|
||||
void Output8Bit(int index, unsigned char A);
|
||||
unsigned char Read(unsigned char p, unsigned char Y);
|
||||
void Write(unsigned char p, unsigned char Y, unsigned char value);
|
||||
void RenderSample(unsigned char* mem66);
|
||||
void Render();
|
||||
void AddInflection(unsigned char mem48, unsigned char phase1);
|
||||
void SetMouthThroat();
|
||||
unsigned char trans(unsigned char mem39212, unsigned char mem39213);
|
||||
void SetInput(char* _input);
|
||||
void Init();
|
||||
int SAMMain();
|
||||
void PrepareOutput();
|
||||
void Insert(
|
||||
unsigned char position /*var57*/,
|
||||
unsigned char mem60,
|
||||
unsigned char mem59,
|
||||
unsigned char mem58);
|
||||
void InsertBreath();
|
||||
void CopyStress();
|
||||
int Parser1();
|
||||
void SetPhonemeLength();
|
||||
void Code41240();
|
||||
void Parser2();
|
||||
void AdjustLengths();
|
||||
void Code47503(unsigned char mem52);
|
||||
void Code37055(unsigned char mem59);
|
||||
void Code37066(unsigned char mem58);
|
||||
unsigned char GetRuleByte(unsigned short mem62, unsigned char Y);
|
||||
int TextToPhonemes(unsigned char* input); // Code36484
|
||||
|
||||
uint32_t _STM32SAM_SPEED;
|
||||
|
||||
unsigned char speed;
|
||||
unsigned char pitch;
|
||||
unsigned char mouth;
|
||||
unsigned char throat;
|
||||
|
||||
unsigned char phonetic;
|
||||
unsigned char singmode;
|
||||
|
||||
}; // STM32SAM class
|
||||
|
||||
#endif
|
||||
@@ -1,17 +0,0 @@
|
||||
App(
|
||||
appid="uhf_rfid",
|
||||
name="[YRM100] UHF RFID",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
targets=["f7"],
|
||||
entry_point="uhf_app_main",
|
||||
requires=[
|
||||
"storage",
|
||||
"gui",
|
||||
],
|
||||
stack_size=8 * 1024,
|
||||
order=30,
|
||||
fap_icon="uhf_10px.png",
|
||||
fap_category="GPIO",
|
||||
fap_icon_assets="icons",
|
||||
fap_icon_assets_symbol="uhf_rfid",
|
||||
)
|
||||
@@ -1,30 +0,0 @@
|
||||
#include "uhf_scene.h"
|
||||
|
||||
// Generate scene on_enter handlers array
|
||||
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
|
||||
void (*const uhf_on_enter_handlers[])(void*) = {
|
||||
#include "uhf_scene_config.h"
|
||||
};
|
||||
#undef ADD_SCENE
|
||||
|
||||
// Generate scene on_event handlers array
|
||||
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event,
|
||||
bool (*const uhf_on_event_handlers[])(void* context, SceneManagerEvent event) = {
|
||||
#include "uhf_scene_config.h"
|
||||
};
|
||||
#undef ADD_SCENE
|
||||
|
||||
// Generate scene on_exit handlers array
|
||||
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit,
|
||||
void (*const uhf_on_exit_handlers[])(void* context) = {
|
||||
#include "uhf_scene_config.h"
|
||||
};
|
||||
#undef ADD_SCENE
|
||||
|
||||
// Initialize scene handlers configuration structure
|
||||
const SceneManagerHandlers uhf_scene_handlers = {
|
||||
.on_enter_handlers = uhf_on_enter_handlers,
|
||||
.on_event_handlers = uhf_on_event_handlers,
|
||||
.on_exit_handlers = uhf_on_exit_handlers,
|
||||
.scene_num = UHFSceneNum,
|
||||
};
|
||||
@@ -1,29 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <gui/scene_manager.h>
|
||||
|
||||
// Generate scene id and total number
|
||||
#define ADD_SCENE(prefix, name, id) UHFScene##id,
|
||||
typedef enum {
|
||||
#include "uhf_scene_config.h"
|
||||
UHFSceneNum,
|
||||
} UHFScene;
|
||||
#undef ADD_SCENE
|
||||
|
||||
extern const SceneManagerHandlers uhf_scene_handlers;
|
||||
|
||||
// Generate scene on_enter handlers declaration
|
||||
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*);
|
||||
#include "uhf_scene_config.h"
|
||||
#undef ADD_SCENE
|
||||
|
||||
// Generate scene on_event handlers declaration
|
||||
#define ADD_SCENE(prefix, name, id) \
|
||||
bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event);
|
||||
#include "uhf_scene_config.h"
|
||||
#undef ADD_SCENE
|
||||
|
||||
// Generate scene on_exit handlers declaration
|
||||
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context);
|
||||
#include "uhf_scene_config.h"
|
||||
#undef ADD_SCENE
|
||||
@@ -1,18 +0,0 @@
|
||||
ADD_SCENE(uhf, verify, Verify)
|
||||
ADD_SCENE(uhf, start, Start)
|
||||
ADD_SCENE(uhf, read_tag, ReadTag)
|
||||
ADD_SCENE(uhf, read_tag_success, ReadTagSuccess)
|
||||
ADD_SCENE(uhf, tag_menu, TagMenu)
|
||||
ADD_SCENE(uhf, save_name, SaveName)
|
||||
ADD_SCENE(uhf, save_success, SaveSuccess)
|
||||
ADD_SCENE(uhf, saved_menu, SavedMenu)
|
||||
ADD_SCENE(uhf, file_select, FileSelect)
|
||||
ADD_SCENE(uhf, device_info, DeviceInfo)
|
||||
ADD_SCENE(uhf, delete, Delete)
|
||||
ADD_SCENE(uhf, delete_success, DeleteSuccess)
|
||||
ADD_SCENE(uhf, write_tag, WriteTag)
|
||||
ADD_SCENE(uhf, write_tag_success, WriteTagSuccess)
|
||||
ADD_SCENE(uhf, settings, Settings)
|
||||
// ADD_SCENE(uhf, read_factory_success, ReadFactorySuccess)
|
||||
// ADD_SCENE(uhf, write_key, WriteKey)
|
||||
// ADD_SCENE(uhf, key_menu, KeyMenu)
|
||||
@@ -1,50 +0,0 @@
|
||||
#include "../uhf_app_i.h"
|
||||
|
||||
void uhf_scene_delete_widget_callback(GuiButtonType result, InputType type, void* context) {
|
||||
UHFApp* uhf_app = context;
|
||||
if(type == InputTypeShort) {
|
||||
view_dispatcher_send_custom_event(uhf_app->view_dispatcher, result);
|
||||
}
|
||||
}
|
||||
|
||||
void uhf_scene_delete_on_enter(void* context) {
|
||||
UHFApp* uhf_app = context;
|
||||
|
||||
// Setup Custom Widget view
|
||||
char temp_str[64];
|
||||
snprintf(temp_str, sizeof(temp_str), "\e#Delete %s?\e#", uhf_app->uhf_device->dev_name);
|
||||
widget_add_text_box_element(
|
||||
uhf_app->widget, 0, 0, 128, 23, AlignCenter, AlignCenter, temp_str, false);
|
||||
widget_add_button_element(
|
||||
uhf_app->widget, GuiButtonTypeLeft, "Back", uhf_scene_delete_widget_callback, uhf_app);
|
||||
widget_add_button_element(
|
||||
uhf_app->widget, GuiButtonTypeRight, "Delete", uhf_scene_delete_widget_callback, uhf_app);
|
||||
|
||||
view_dispatcher_switch_to_view(uhf_app->view_dispatcher, UHFViewWidget);
|
||||
}
|
||||
|
||||
bool uhf_scene_delete_on_event(void* context, SceneManagerEvent event) {
|
||||
UHFApp* uhf_app = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == GuiButtonTypeLeft) {
|
||||
return scene_manager_previous_scene(uhf_app->scene_manager);
|
||||
} else if(event.event == GuiButtonTypeRight) {
|
||||
if(uhf_device_delete(uhf_app->uhf_device, true)) {
|
||||
scene_manager_next_scene(uhf_app->scene_manager, UHFSceneDeleteSuccess);
|
||||
} else {
|
||||
scene_manager_search_and_switch_to_previous_scene(
|
||||
uhf_app->scene_manager, UHFSceneStart);
|
||||
}
|
||||
consumed = true;
|
||||
}
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void uhf_scene_delete_on_exit(void* context) {
|
||||
UHFApp* uhf_app = context;
|
||||
|
||||
widget_reset(uhf_app->widget);
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
#include "../uhf_app_i.h"
|
||||
|
||||
void uhf_scene_delete_success_popup_callback(void* context) {
|
||||
UHFApp* uhf_app = context;
|
||||
view_dispatcher_send_custom_event(uhf_app->view_dispatcher, UHFCustomEventViewExit);
|
||||
}
|
||||
|
||||
void uhf_scene_delete_success_on_enter(void* context) {
|
||||
UHFApp* uhf_app = context;
|
||||
|
||||
// Setup view
|
||||
Popup* popup = uhf_app->popup;
|
||||
popup_set_icon(popup, 0, 2, &I_DolphinMafia_115x62);
|
||||
popup_set_header(popup, "Deleted", 83, 19, AlignLeft, AlignBottom);
|
||||
popup_set_timeout(popup, 1500);
|
||||
popup_set_context(popup, uhf_app);
|
||||
popup_set_callback(popup, uhf_scene_delete_success_popup_callback);
|
||||
popup_enable_timeout(popup);
|
||||
view_dispatcher_switch_to_view(uhf_app->view_dispatcher, UHFViewPopup);
|
||||
}
|
||||
|
||||
bool uhf_scene_delete_success_on_event(void* context, SceneManagerEvent event) {
|
||||
UHFApp* uhf_app = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == UHFCustomEventViewExit) {
|
||||
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||
uhf_app->scene_manager, UHFSceneStart);
|
||||
}
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void uhf_scene_delete_success_on_exit(void* context) {
|
||||
UHFApp* uhf_app = context;
|
||||
|
||||
// Clear view
|
||||
popup_reset(uhf_app->popup);
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
#include "../uhf_app_i.h"
|
||||
#include <dolphin/dolphin.h>
|
||||
|
||||
typedef enum { EPC_INFO, TID_INFO, USER_INFO } UHFTagInfo;
|
||||
|
||||
static UHFTagInfo current_info;
|
||||
|
||||
char* get_current_bank_info_str() {
|
||||
switch(current_info) {
|
||||
case EPC_INFO:
|
||||
return "EPC Bank";
|
||||
case TID_INFO:
|
||||
return "TID Bank";
|
||||
case USER_INFO:
|
||||
return "User Bank";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
char* get_next_bank_info_str() {
|
||||
switch(current_info) {
|
||||
case EPC_INFO:
|
||||
current_info = TID_INFO;
|
||||
return "TID";
|
||||
case TID_INFO:
|
||||
current_info = USER_INFO;
|
||||
return "USER";
|
||||
case USER_INFO:
|
||||
current_info = EPC_INFO;
|
||||
return "EPC";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
void uhf_scene_device_info_widget_callback(GuiButtonType result, InputType type, void* context) {
|
||||
UHFApp* uhf_app = context;
|
||||
if(type == InputTypeShort) {
|
||||
view_dispatcher_send_custom_event(uhf_app->view_dispatcher, result);
|
||||
}
|
||||
}
|
||||
|
||||
void change_view_on_event(UHFApp* uhf_app) {
|
||||
UHFTag* uhf_tag = uhf_app->uhf_device->uhf_tag_wrapper->uhf_tag;
|
||||
FuriString* furi_temp_str;
|
||||
furi_temp_str = furi_string_alloc();
|
||||
char* temp_str;
|
||||
size_t length;
|
||||
|
||||
widget_reset(uhf_app->widget);
|
||||
widget_add_string_element(
|
||||
uhf_app->widget, 64, 5, AlignCenter, AlignCenter, FontPrimary, get_current_bank_info_str());
|
||||
|
||||
switch(current_info) {
|
||||
case EPC_INFO:
|
||||
temp_str = convertToHexString(uhf_tag->epc->data, uhf_tag->epc->size);
|
||||
length = uhf_tag->epc->size;
|
||||
break;
|
||||
case TID_INFO:
|
||||
temp_str = convertToHexString(uhf_tag->tid->data, uhf_tag->tid->size);
|
||||
length = uhf_tag->tid->size;
|
||||
break;
|
||||
case USER_INFO:
|
||||
temp_str = convertToHexString(uhf_tag->user->data, uhf_tag->user->size);
|
||||
length = uhf_tag->user->size;
|
||||
break;
|
||||
default:
|
||||
temp_str = NULL;
|
||||
length = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
furi_string_cat_printf(furi_temp_str, "Length: %d bytes", length);
|
||||
|
||||
widget_add_string_element(
|
||||
uhf_app->widget,
|
||||
3,
|
||||
12,
|
||||
AlignLeft,
|
||||
AlignTop,
|
||||
FontKeyboard,
|
||||
furi_string_get_cstr(furi_temp_str));
|
||||
|
||||
widget_add_string_multiline_element(
|
||||
uhf_app->widget, 3, 24, AlignLeft, AlignTop, FontKeyboard, temp_str);
|
||||
|
||||
widget_add_button_element(
|
||||
uhf_app->widget,
|
||||
GuiButtonTypeRight,
|
||||
get_next_bank_info_str(),
|
||||
uhf_scene_device_info_widget_callback,
|
||||
uhf_app);
|
||||
|
||||
widget_add_button_element(
|
||||
uhf_app->widget, GuiButtonTypeLeft, "Back", uhf_scene_device_info_widget_callback, uhf_app);
|
||||
|
||||
furi_string_free(furi_temp_str);
|
||||
free(temp_str);
|
||||
}
|
||||
|
||||
void uhf_scene_device_info_on_enter(void* context) {
|
||||
UHFApp* uhf_app = context;
|
||||
current_info = EPC_INFO;
|
||||
dolphin_deed(DolphinDeedNfcReadSuccess);
|
||||
change_view_on_event(uhf_app);
|
||||
view_dispatcher_switch_to_view(uhf_app->view_dispatcher, UHFViewWidget);
|
||||
}
|
||||
|
||||
bool uhf_scene_device_info_on_event(void* context, SceneManagerEvent event) {
|
||||
UHFApp* uhf_app = context;
|
||||
bool consumed = false;
|
||||
if(event.type == SceneManagerEventTypeTick) return false;
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == GuiButtonTypeLeft) {
|
||||
consumed = scene_manager_previous_scene(uhf_app->scene_manager);
|
||||
} else if(event.event == GuiButtonTypeRight) {
|
||||
change_view_on_event(uhf_app);
|
||||
} else if(event.event == UHFCustomEventViewExit) {
|
||||
view_dispatcher_switch_to_view(uhf_app->view_dispatcher, UHFViewWidget);
|
||||
consumed = true;
|
||||
}
|
||||
} else if(event.type == SceneManagerEventTypeBack) {
|
||||
scene_manager_previous_scene(uhf_app->scene_manager);
|
||||
consumed = true;
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void uhf_scene_device_info_on_exit(void* context) {
|
||||
UHFApp* uhf_app = context;
|
||||
|
||||
// Clear views
|
||||
widget_reset(uhf_app->widget);
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
#include "../uhf_app_i.h"
|
||||
|
||||
void uhf_scene_file_select_on_enter(void* context) {
|
||||
UHFApp* uhf_app = context;
|
||||
// Process file_select return
|
||||
uhf_device_set_loading_callback(uhf_app->uhf_device, uhf_show_loading_popup, uhf_app);
|
||||
if(uhf_file_select(uhf_app->uhf_device)) {
|
||||
scene_manager_next_scene(uhf_app->scene_manager, UHFSceneSavedMenu);
|
||||
} else {
|
||||
scene_manager_search_and_switch_to_previous_scene(uhf_app->scene_manager, UHFSceneStart);
|
||||
}
|
||||
uhf_device_set_loading_callback(uhf_app->uhf_device, NULL, uhf_app);
|
||||
}
|
||||
|
||||
bool uhf_scene_file_select_on_event(void* context, SceneManagerEvent event) {
|
||||
UNUSED(context);
|
||||
UNUSED(event);
|
||||
return false;
|
||||
}
|
||||
|
||||
void uhf_scene_file_select_on_exit(void* context) {
|
||||
UNUSED(context);
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
#include "../uhf_app_i.h"
|
||||
#include <dolphin/dolphin.h>
|
||||
|
||||
void uhf_read_tag_worker_callback(UHFWorkerEvent event, void* ctx) {
|
||||
UHFApp* uhf_app = ctx;
|
||||
if(event == UHFWorkerEventSuccess) {
|
||||
view_dispatcher_send_custom_event(uhf_app->view_dispatcher, UHFCustomEventWorkerExit);
|
||||
}
|
||||
}
|
||||
|
||||
void uhf_scene_read_tag_on_enter(void* ctx) {
|
||||
UHFApp* uhf_app = ctx;
|
||||
dolphin_deed(DolphinDeedNfcRead);
|
||||
|
||||
// Setup view
|
||||
Popup* popup = uhf_app->popup;
|
||||
popup_set_header(popup, "Detecting\n[UHF] RFID\nTag", 68, 30, AlignLeft, AlignTop);
|
||||
popup_set_icon(popup, 0, 3, &I_RFIDDolphinReceive_97x61);
|
||||
|
||||
// Start worker
|
||||
view_dispatcher_switch_to_view(uhf_app->view_dispatcher, UHFViewPopup);
|
||||
uhf_worker_start(
|
||||
uhf_app->worker, UHFWorkerStateDetectSingle, uhf_read_tag_worker_callback, uhf_app);
|
||||
|
||||
uhf_blink_start(uhf_app);
|
||||
}
|
||||
|
||||
bool uhf_scene_read_tag_on_event(void* ctx, SceneManagerEvent event) {
|
||||
UHFApp* uhf_app = ctx;
|
||||
bool consumed = false;
|
||||
if(event.event == UHFCustomEventWorkerExit) {
|
||||
scene_manager_next_scene(uhf_app->scene_manager, UHFSceneReadTagSuccess);
|
||||
consumed = true;
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void uhf_scene_read_tag_on_exit(void* ctx) {
|
||||
UHFApp* uhf_app = ctx;
|
||||
// Stop worker
|
||||
uhf_worker_stop(uhf_app->worker);
|
||||
// Clear view
|
||||
popup_reset(uhf_app->popup);
|
||||
uhf_blink_stop(uhf_app);
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
#include "../uhf_app_i.h"
|
||||
#include <dolphin/dolphin.h>
|
||||
|
||||
void uhf_read_tag_success_worker_callback(UHFWorkerEvent event, void* ctx) {
|
||||
UNUSED(event);
|
||||
UNUSED(ctx);
|
||||
}
|
||||
|
||||
void uhf_scene_read_card_success_widget_callback(GuiButtonType result, InputType type, void* ctx) {
|
||||
furi_assert(ctx);
|
||||
UHFApp* uhf_app = ctx;
|
||||
|
||||
if(type == InputTypeShort) {
|
||||
view_dispatcher_send_custom_event(uhf_app->view_dispatcher, result);
|
||||
}
|
||||
}
|
||||
|
||||
void uhf_scene_read_tag_success_on_enter(void* ctx) {
|
||||
UHFApp* uhf_app = ctx;
|
||||
UHFTag* uhf_tag = uhf_app->worker->uhf_tag_wrapper->uhf_tag;
|
||||
FuriString* temp_str = furi_string_alloc();
|
||||
|
||||
dolphin_deed(DolphinDeedNfcReadSuccess);
|
||||
|
||||
// Send notification
|
||||
notification_message(uhf_app->notifications, &sequence_success);
|
||||
|
||||
widget_add_string_element(
|
||||
uhf_app->widget, 32, 5, AlignLeft, AlignCenter, FontPrimary, "Read Success");
|
||||
|
||||
widget_add_string_element(uhf_app->widget, 3, 18, AlignLeft, AlignCenter, FontPrimary, "PC :");
|
||||
|
||||
widget_add_string_element(
|
||||
uhf_app->widget, 66, 18, AlignLeft, AlignCenter, FontPrimary, "CRC :");
|
||||
|
||||
widget_add_string_element(
|
||||
uhf_app->widget, 3, 32, AlignLeft, AlignCenter, FontPrimary, "EPC :");
|
||||
|
||||
furi_string_cat_printf(temp_str, "%04X", uhf_tag->epc->pc);
|
||||
widget_add_string_element(
|
||||
uhf_app->widget,
|
||||
26,
|
||||
19,
|
||||
AlignLeft,
|
||||
AlignCenter,
|
||||
FontKeyboard,
|
||||
furi_string_get_cstr(temp_str));
|
||||
furi_string_reset(temp_str);
|
||||
furi_string_cat_printf(temp_str, "%04X", uhf_tag->epc->crc);
|
||||
widget_add_string_element(
|
||||
uhf_app->widget,
|
||||
96,
|
||||
19,
|
||||
AlignLeft,
|
||||
AlignCenter,
|
||||
FontKeyboard,
|
||||
furi_string_get_cstr(temp_str));
|
||||
char* epc = convertToHexString(uhf_tag->epc->data, uhf_tag->epc->size);
|
||||
if(epc != NULL) {
|
||||
widget_add_string_multiline_element(
|
||||
uhf_app->widget, 34, 29, AlignLeft, AlignTop, FontKeyboard, epc);
|
||||
}
|
||||
widget_add_button_element(
|
||||
uhf_app->widget,
|
||||
GuiButtonTypeRight,
|
||||
"More",
|
||||
uhf_scene_read_card_success_widget_callback,
|
||||
uhf_app);
|
||||
widget_add_button_element(
|
||||
uhf_app->widget,
|
||||
GuiButtonTypeLeft,
|
||||
"Exit",
|
||||
uhf_scene_read_card_success_widget_callback,
|
||||
uhf_app);
|
||||
view_dispatcher_switch_to_view(uhf_app->view_dispatcher, UHFViewWidget);
|
||||
free(epc);
|
||||
furi_string_free(temp_str);
|
||||
}
|
||||
|
||||
bool uhf_scene_read_tag_success_on_event(void* ctx, SceneManagerEvent event) {
|
||||
UHFApp* uhf_app = ctx;
|
||||
bool consumed = false;
|
||||
if(event.event == SceneManagerEventTypeBack) {
|
||||
uhf_app->worker->state = UHFWorkerStateStop;
|
||||
}
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
// if 'exit' is pressed go back to home screen
|
||||
if(event.event == GuiButtonTypeLeft) {
|
||||
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||
uhf_app->scene_manager, UHFSceneStart);
|
||||
} else if(event.event == GuiButtonTypeRight) {
|
||||
scene_manager_next_scene(uhf_app->scene_manager, UHFSceneTagMenu);
|
||||
consumed = true;
|
||||
} else if(event.event == GuiButtonTypeCenter) {
|
||||
// consumed = scene_manager_search_and_switch_to_another_scene(
|
||||
// picopass->scene_manager, PicopassSceneStart);
|
||||
}
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void uhf_scene_read_tag_success_on_exit(void* ctx) {
|
||||
UHFApp* uhf_app = ctx;
|
||||
|
||||
// // Stop worker
|
||||
uhf_worker_stop(uhf_app->worker);
|
||||
// Clear view
|
||||
popup_reset(uhf_app->popup);
|
||||
// clear widget
|
||||
widget_reset(uhf_app->widget);
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
#include "../uhf_app_i.h"
|
||||
#include <lib/toolbox/name_generator.h>
|
||||
#include <gui/modules/validators.h>
|
||||
#include <toolbox/path.h>
|
||||
|
||||
void uhf_scene_save_name_text_input_callback(void* context) {
|
||||
UHFApp* uhf_app = context;
|
||||
|
||||
view_dispatcher_send_custom_event(uhf_app->view_dispatcher, UHFCustomEventTextInputDone);
|
||||
}
|
||||
|
||||
void uhf_scene_save_name_on_enter(void* context) {
|
||||
UHFApp* uhf_app = context;
|
||||
|
||||
// Setup view
|
||||
TextInput* text_input = uhf_app->text_input;
|
||||
name_generator_make_random(uhf_app->text_store, sizeof(uhf_app->text_store));
|
||||
text_input_set_header_text(text_input, "Name the tag");
|
||||
text_input_set_result_callback(
|
||||
text_input,
|
||||
uhf_scene_save_name_text_input_callback,
|
||||
uhf_app,
|
||||
uhf_app->text_store,
|
||||
UHF_DEV_NAME_MAX_LEN,
|
||||
true);
|
||||
|
||||
FuriString* folder_path;
|
||||
folder_path = furi_string_alloc_set(STORAGE_APP_DATA_PATH_PREFIX);
|
||||
|
||||
if(furi_string_end_with(uhf_app->uhf_device->load_path, UHF_APP_EXTENSION)) {
|
||||
path_extract_dirname(furi_string_get_cstr(uhf_app->uhf_device->load_path), folder_path);
|
||||
}
|
||||
|
||||
ValidatorIsFile* validator_is_file = validator_is_file_alloc_init(
|
||||
furi_string_get_cstr(folder_path), UHF_APP_EXTENSION, uhf_app->uhf_device->dev_name);
|
||||
text_input_set_validator(text_input, validator_is_file_callback, validator_is_file);
|
||||
|
||||
view_dispatcher_switch_to_view(uhf_app->view_dispatcher, UHFViewTextInput);
|
||||
|
||||
furi_string_free(folder_path);
|
||||
}
|
||||
|
||||
bool uhf_scene_save_name_on_event(void* context, SceneManagerEvent event) {
|
||||
UHFApp* uhf_app = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == UHFCustomEventTextInputDone) {
|
||||
strlcpy(
|
||||
uhf_app->uhf_device->dev_name,
|
||||
uhf_app->text_store,
|
||||
strlen(uhf_app->text_store) + 1);
|
||||
if(uhf_device_save(uhf_app->uhf_device, uhf_app->text_store)) {
|
||||
scene_manager_next_scene(uhf_app->scene_manager, UHFSceneSaveSuccess);
|
||||
consumed = true;
|
||||
} else {
|
||||
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||
uhf_app->scene_manager, UHFSceneStart);
|
||||
}
|
||||
}
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void uhf_scene_save_name_on_exit(void* context) {
|
||||
UHFApp* uhf_app = context;
|
||||
|
||||
// Clear view
|
||||
void* validator_context = text_input_get_validator_callback_context(uhf_app->text_input);
|
||||
text_input_set_validator(uhf_app->text_input, NULL, NULL);
|
||||
validator_is_file_free(validator_context);
|
||||
|
||||
text_input_reset(uhf_app->text_input);
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
#include "../uhf_app_i.h"
|
||||
#include <dolphin/dolphin.h>
|
||||
|
||||
void uhf_scene_save_success_popup_callback(void* context) {
|
||||
UHFApp* uhf_app = context;
|
||||
view_dispatcher_send_custom_event(uhf_app->view_dispatcher, UHFCustomEventViewExit);
|
||||
}
|
||||
|
||||
void uhf_scene_save_success_on_enter(void* context) {
|
||||
UHFApp* uhf_app = context;
|
||||
dolphin_deed(DolphinDeedNfcSave);
|
||||
|
||||
// Setup view
|
||||
Popup* popup = uhf_app->popup;
|
||||
popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59);
|
||||
popup_set_header(popup, "Saved!", 13, 22, AlignLeft, AlignBottom);
|
||||
popup_set_timeout(popup, 1500);
|
||||
popup_set_context(popup, uhf_app);
|
||||
popup_set_callback(popup, uhf_scene_save_success_popup_callback);
|
||||
popup_enable_timeout(popup);
|
||||
view_dispatcher_switch_to_view(uhf_app->view_dispatcher, UHFViewPopup);
|
||||
}
|
||||
|
||||
bool uhf_scene_save_success_on_event(void* context, SceneManagerEvent event) {
|
||||
UHFApp* uhf_app = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == UHFCustomEventViewExit) {
|
||||
if(scene_manager_has_previous_scene(uhf_app->scene_manager, UHFSceneTagMenu)) {
|
||||
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||
uhf_app->scene_manager, UHFSceneTagMenu);
|
||||
} else {
|
||||
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||
uhf_app->scene_manager, UHFSceneStart);
|
||||
}
|
||||
}
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void uhf_scene_save_success_on_exit(void* context) {
|
||||
UHFApp* uhf_app = context;
|
||||
|
||||
// Clear view
|
||||
popup_reset(uhf_app->popup);
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
#include "../uhf_app_i.h"
|
||||
|
||||
enum SubmenuIndex {
|
||||
SubmenuIndexDelete,
|
||||
SubmenuIndexInfo,
|
||||
SubmenuIndexWrite,
|
||||
};
|
||||
|
||||
void uhf_scene_saved_menu_submenu_callback(void* context, uint32_t index) {
|
||||
UHFApp* uhf_app = context;
|
||||
|
||||
view_dispatcher_send_custom_event(uhf_app->view_dispatcher, index);
|
||||
}
|
||||
|
||||
void uhf_scene_saved_menu_on_enter(void* context) {
|
||||
UHFApp* uhf_app = context;
|
||||
Submenu* submenu = uhf_app->submenu;
|
||||
|
||||
submenu_add_item(
|
||||
submenu, "Delete", SubmenuIndexDelete, uhf_scene_saved_menu_submenu_callback, uhf_app);
|
||||
submenu_add_item(
|
||||
submenu, "Info", SubmenuIndexInfo, uhf_scene_saved_menu_submenu_callback, uhf_app);
|
||||
submenu_add_item(
|
||||
submenu, "Write", SubmenuIndexWrite, uhf_scene_saved_menu_submenu_callback, uhf_app);
|
||||
|
||||
submenu_set_selected_item(
|
||||
uhf_app->submenu,
|
||||
scene_manager_get_scene_state(uhf_app->scene_manager, UHFSceneSavedMenu));
|
||||
|
||||
view_dispatcher_switch_to_view(uhf_app->view_dispatcher, UHFViewMenu);
|
||||
}
|
||||
|
||||
bool uhf_scene_saved_menu_on_event(void* context, SceneManagerEvent event) {
|
||||
UHFApp* uhf_app = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
scene_manager_set_scene_state(uhf_app->scene_manager, UHFSceneSavedMenu, event.event);
|
||||
|
||||
if(event.event == SubmenuIndexDelete) {
|
||||
scene_manager_next_scene(uhf_app->scene_manager, UHFSceneDelete);
|
||||
consumed = true;
|
||||
} else if(event.event == SubmenuIndexInfo) {
|
||||
scene_manager_next_scene(uhf_app->scene_manager, UHFSceneDeviceInfo);
|
||||
consumed = true;
|
||||
} else if(event.event == SubmenuIndexWrite) {
|
||||
scene_manager_next_scene(uhf_app->scene_manager, UHFSceneWriteTag);
|
||||
consumed = true;
|
||||
}
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void uhf_scene_saved_menu_on_exit(void* context) {
|
||||
UHFApp* uhf_app = context;
|
||||
|
||||
submenu_reset(uhf_app->submenu);
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
#include "../uhf_app_i.h"
|
||||
#include "../uhf_module.h"
|
||||
|
||||
void uhf_settings_set_module_baudrate(VariableItem* item) {
|
||||
M100Module* uhf_module = variable_item_get_context(item);
|
||||
uint8_t index = variable_item_get_current_value_index(item);
|
||||
if(index >= BAUD_RATES_COUNT) {
|
||||
return;
|
||||
}
|
||||
uint32_t baudrate = BAUD_RATES[index];
|
||||
m100_set_baudrate(uhf_module, baudrate);
|
||||
char text_buf[10];
|
||||
snprintf(text_buf, sizeof(text_buf), "%lu", uhf_module->baudrate);
|
||||
variable_item_set_current_value_text(item, text_buf);
|
||||
}
|
||||
|
||||
void uhf_settings_set_module_powerdb(VariableItem* item) {
|
||||
M100Module* uhf_module = variable_item_get_context(item);
|
||||
uint8_t index = variable_item_get_current_value_index(item);
|
||||
if(index >= POWER_DBM_COUNT) {
|
||||
return;
|
||||
}
|
||||
uint16_t power = POWER_DBM[index];
|
||||
m100_set_transmitting_power(uhf_module, power);
|
||||
char text_buf[10];
|
||||
snprintf(text_buf, sizeof(text_buf), "%ddBm", uhf_module->transmitting_power);
|
||||
variable_item_set_current_value_text(item, text_buf);
|
||||
}
|
||||
|
||||
void uhf_settings_set_module_working_region(VariableItem* item) {
|
||||
M100Module* uhf_module = variable_item_get_context(item);
|
||||
uint8_t index = variable_item_get_current_value_index(item);
|
||||
if(index >= WORKING_REGIONS_COUNT) {
|
||||
return;
|
||||
}
|
||||
WorkingRegion region = WORKING_REGIONS[index];
|
||||
m100_set_working_region(uhf_module, region);
|
||||
variable_item_set_current_value_text(item, WORKING_REGIONS_STR[index]);
|
||||
}
|
||||
|
||||
uint8_t uhf_settings_get_module_baudrate_index(M100Module* module) {
|
||||
for(uint8_t i = 0; i < BAUD_RATES_COUNT; i++) {
|
||||
if(BAUD_RATES[i] == module->baudrate) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8_t uhf_settings_get_module_power_index(M100Module* module) {
|
||||
for(uint8_t i = 0; i < BAUD_RATES_COUNT; i++) {
|
||||
if(POWER_DBM[i] == module->transmitting_power) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8_t uhf_settings_get_module_working_region_index(M100Module* module) {
|
||||
for(uint8_t i = 0; i < WORKING_REGIONS_COUNT; i++) {
|
||||
if(WORKING_REGIONS[i] == module->region) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void uhf_scene_settings_on_enter(void* ctx) {
|
||||
UHFApp* uhf_app = ctx;
|
||||
M100Module* uhf_module = uhf_app->worker->module;
|
||||
VariableItem* item;
|
||||
VariableItemList* variable_item_list = uhf_app->variable_item_list;
|
||||
|
||||
uint8_t value_index = uhf_settings_get_module_baudrate_index(uhf_module);
|
||||
char text_buf[10];
|
||||
snprintf(text_buf, sizeof(text_buf), "%lu", uhf_module->baudrate);
|
||||
item = variable_item_list_add(
|
||||
variable_item_list,
|
||||
"Baudrate:",
|
||||
BAUD_RATES_COUNT,
|
||||
uhf_settings_set_module_baudrate,
|
||||
uhf_module);
|
||||
|
||||
variable_item_set_current_value_text(item, text_buf);
|
||||
variable_item_set_current_value_index(item, value_index);
|
||||
|
||||
value_index = uhf_settings_get_module_power_index(uhf_module);
|
||||
item = variable_item_list_add(
|
||||
variable_item_list,
|
||||
"Power(DBM):",
|
||||
POWER_DBM_COUNT,
|
||||
uhf_settings_set_module_powerdb,
|
||||
uhf_module);
|
||||
snprintf(text_buf, sizeof(text_buf), "%ddBm", uhf_module->transmitting_power);
|
||||
variable_item_set_current_value_text(item, text_buf);
|
||||
variable_item_set_current_value_index(item, value_index);
|
||||
|
||||
value_index = uhf_settings_get_module_working_region_index(uhf_module);
|
||||
item = variable_item_list_add(
|
||||
variable_item_list,
|
||||
"Region:",
|
||||
WORKING_REGIONS_COUNT,
|
||||
uhf_settings_set_module_working_region,
|
||||
uhf_module);
|
||||
variable_item_set_current_value_text(item, WORKING_REGIONS_STR[value_index]);
|
||||
variable_item_set_current_value_index(item, value_index);
|
||||
|
||||
view_dispatcher_switch_to_view(uhf_app->view_dispatcher, UHFViewVariableItemList);
|
||||
}
|
||||
|
||||
bool uhf_scene_settings_on_event(void* ctx, SceneManagerEvent event) {
|
||||
UHFApp* uhf_app = ctx;
|
||||
UNUSED(uhf_app);
|
||||
UNUSED(event);
|
||||
return false;
|
||||
}
|
||||
|
||||
void uhf_scene_settings_on_exit(void* ctx) {
|
||||
UHFApp* uhf_app = ctx;
|
||||
variable_item_list_set_selected_item(uhf_app->variable_item_list, 0);
|
||||
variable_item_list_reset(uhf_app->variable_item_list);
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
#include "../uhf_app_i.h"
|
||||
|
||||
enum SubmenuIndex { SubmenuIndexRead, SubmenuIndexSaved, SubmenuIndexSettings };
|
||||
|
||||
void uhf_scene_start_submenu_callback(void* ctx, uint32_t index) {
|
||||
UHFApp* uhf_app = ctx;
|
||||
view_dispatcher_send_custom_event(uhf_app->view_dispatcher, index);
|
||||
}
|
||||
|
||||
void uhf_scene_start_on_enter(void* ctx) {
|
||||
UHFApp* uhf_app = ctx;
|
||||
|
||||
Submenu* submenu = uhf_app->submenu;
|
||||
submenu_add_item(
|
||||
submenu, "Read Tag", SubmenuIndexRead, uhf_scene_start_submenu_callback, uhf_app);
|
||||
submenu_add_item(
|
||||
submenu, "Saved", SubmenuIndexSaved, uhf_scene_start_submenu_callback, uhf_app);
|
||||
submenu_add_item(
|
||||
submenu, "Settings", SubmenuIndexSettings, uhf_scene_start_submenu_callback, uhf_app);
|
||||
|
||||
submenu_set_selected_item(
|
||||
submenu, scene_manager_get_scene_state(uhf_app->scene_manager, UHFSceneStart));
|
||||
view_dispatcher_switch_to_view(uhf_app->view_dispatcher, UHFViewMenu);
|
||||
}
|
||||
|
||||
bool uhf_scene_start_on_event(void* ctx, SceneManagerEvent event) {
|
||||
UHFApp* uhf_app = ctx;
|
||||
bool consumed = false;
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == SubmenuIndexRead) {
|
||||
scene_manager_set_scene_state(uhf_app->scene_manager, UHFSceneStart, SubmenuIndexRead);
|
||||
scene_manager_next_scene(uhf_app->scene_manager, UHFSceneReadTag);
|
||||
consumed = true;
|
||||
} else if(event.event == SubmenuIndexSaved) {
|
||||
// Explicitly save state so that the correct item is
|
||||
// reselected if the user cancels loading a file.
|
||||
scene_manager_set_scene_state(
|
||||
uhf_app->scene_manager, UHFSceneStart, SubmenuIndexSaved);
|
||||
scene_manager_next_scene(uhf_app->scene_manager, UHFSceneFileSelect);
|
||||
consumed = true;
|
||||
} else if(event.event == SubmenuIndexSettings) {
|
||||
scene_manager_set_scene_state(
|
||||
uhf_app->scene_manager, UHFSceneStart, SubmenuIndexSettings);
|
||||
scene_manager_next_scene(uhf_app->scene_manager, UHFSceneSettings);
|
||||
consumed = true;
|
||||
}
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void uhf_scene_start_on_exit(void* ctx) {
|
||||
UHFApp* uhf_app = ctx;
|
||||
submenu_reset(uhf_app->submenu);
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
#include "../uhf_app_i.h"
|
||||
|
||||
enum SubmenuIndex {
|
||||
SubmenuIndexSave,
|
||||
SubmenuIndexChangeKey,
|
||||
};
|
||||
|
||||
void uhf_scene_tag_menu_submenu_callback(void* ctx, uint32_t index) {
|
||||
UHFApp* uhf_app = ctx;
|
||||
view_dispatcher_send_custom_event(uhf_app->view_dispatcher, index);
|
||||
}
|
||||
|
||||
void uhf_scene_tag_menu_on_enter(void* ctx) {
|
||||
UHFApp* uhf_app = ctx;
|
||||
|
||||
Submenu* submenu = uhf_app->submenu;
|
||||
|
||||
submenu_add_item(
|
||||
submenu, "Save", SubmenuIndexSave, uhf_scene_tag_menu_submenu_callback, uhf_app);
|
||||
submenu_add_item(
|
||||
submenu, "Change Key", SubmenuIndexChangeKey, uhf_scene_tag_menu_submenu_callback, uhf_app);
|
||||
|
||||
submenu_set_selected_item(
|
||||
submenu, scene_manager_get_scene_state(uhf_app->scene_manager, UHFSceneTagMenu));
|
||||
|
||||
view_dispatcher_switch_to_view(uhf_app->view_dispatcher, UHFViewMenu);
|
||||
}
|
||||
|
||||
bool uhf_scene_tag_menu_on_event(void* ctx, SceneManagerEvent event) {
|
||||
UHFApp* uhf_app = ctx;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == SubmenuIndexSave) {
|
||||
scene_manager_set_scene_state(
|
||||
uhf_app->scene_manager, UHFSceneTagMenu, SubmenuIndexSave);
|
||||
scene_manager_next_scene(uhf_app->scene_manager, UHFSceneSaveName);
|
||||
consumed = true;
|
||||
}
|
||||
// else if(event.event == SubmenuIndexChangeKey) {
|
||||
// scene_manager_set_scene_state(
|
||||
// picopass->scene_manager, UHFSceneTagMenu, SubmenuIndexChangeKey);
|
||||
// scene_manager_next_scene(picopass->scene_manager, PicopassSceneKeyMenu);
|
||||
// consumed = true;
|
||||
// }
|
||||
} else if(event.type == SceneManagerEventTypeBack) {
|
||||
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||
uhf_app->scene_manager, UHFSceneStart);
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void uhf_scene_tag_menu_on_exit(void* ctx) {
|
||||
UHFApp* uhf_app = ctx;
|
||||
|
||||
submenu_reset(uhf_app->submenu);
|
||||
}
|
||||
@@ -1,147 +0,0 @@
|
||||
#include "../uhf_app_i.h"
|
||||
|
||||
bool verify_success = false;
|
||||
FuriString* temp_str;
|
||||
|
||||
void uhf_scene_verify_callback_event(UHFWorkerEvent event, void* ctx) {
|
||||
UNUSED(ctx);
|
||||
UHFApp* uhf_app = ctx;
|
||||
if(event == UHFWorkerEventSuccess) verify_success = true;
|
||||
|
||||
view_dispatcher_send_custom_event(uhf_app->view_dispatcher, UHFCustomEventVerifyDone);
|
||||
}
|
||||
|
||||
void uhf_scene_verify_widget_callback(GuiButtonType result, InputType type, void* ctx) {
|
||||
furi_assert(ctx);
|
||||
UHFApp* uhf_app = ctx;
|
||||
|
||||
if(type == InputTypeShort) {
|
||||
view_dispatcher_send_custom_event(uhf_app->view_dispatcher, result);
|
||||
}
|
||||
}
|
||||
|
||||
void uhf_scene_verify_on_enter(void* ctx) {
|
||||
UHFApp* uhf_app = ctx;
|
||||
uhf_worker_start(
|
||||
uhf_app->worker, UHFWorkerStateVerify, uhf_scene_verify_callback_event, uhf_app);
|
||||
temp_str = furi_string_alloc();
|
||||
view_dispatcher_switch_to_view(uhf_app->view_dispatcher, UHFViewWidget);
|
||||
}
|
||||
|
||||
bool uhf_scene_verify_on_event(void* ctx, SceneManagerEvent event) {
|
||||
UHFApp* uhf_app = ctx;
|
||||
bool consumed = false;
|
||||
if(event.event == SceneManagerEventTypeBack) {
|
||||
uhf_app->worker->state = UHFWorkerStateStop;
|
||||
} else if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == GuiButtonTypeRight) {
|
||||
scene_manager_next_scene(uhf_app->scene_manager, UHFSceneStart);
|
||||
consumed = true;
|
||||
} else if(event.event == GuiButtonTypeLeft) {
|
||||
if(!verify_success) {
|
||||
widget_reset(uhf_app->widget);
|
||||
furi_string_reset(temp_str);
|
||||
uhf_worker_stop(uhf_app->worker);
|
||||
// furi_hal_gpio_write(&gpio_ext, false);
|
||||
// furi_delay_ms(50);
|
||||
// furi_hal_gpio_write(&gpio_ext_pa7, true);
|
||||
// furi_delay_ms(50);
|
||||
uhf_worker_start(
|
||||
uhf_app->worker,
|
||||
UHFWorkerStateVerify,
|
||||
uhf_scene_verify_callback_event,
|
||||
uhf_app);
|
||||
}
|
||||
} else if(event.event == UHFCustomEventVerifyDone) {
|
||||
if(verify_success) {
|
||||
widget_reset(uhf_app->widget);
|
||||
furi_string_reset(temp_str);
|
||||
M100Module* module = uhf_app->worker->module;
|
||||
widget_add_string_element(
|
||||
uhf_app->widget, 64, 5, AlignCenter, AlignCenter, FontPrimary, "Module Info");
|
||||
// hardware info
|
||||
furi_string_cat_str(temp_str, "HW Version: ");
|
||||
furi_string_cat_str(temp_str, module->info->hw_version);
|
||||
widget_add_string_element(
|
||||
uhf_app->widget,
|
||||
1,
|
||||
15,
|
||||
AlignLeft,
|
||||
AlignTop,
|
||||
FontSecondary,
|
||||
furi_string_get_cstr(temp_str));
|
||||
furi_string_reset(temp_str);
|
||||
// software info
|
||||
furi_string_cat_str(temp_str, "SW Version: ");
|
||||
furi_string_cat_str(temp_str, module->info->sw_version);
|
||||
widget_add_string_element(
|
||||
uhf_app->widget,
|
||||
1,
|
||||
27,
|
||||
AlignLeft,
|
||||
AlignTop,
|
||||
FontSecondary,
|
||||
furi_string_get_cstr(temp_str));
|
||||
furi_string_reset(temp_str);
|
||||
// manufacturer info
|
||||
furi_string_cat_str(temp_str, "Manufacturer: ");
|
||||
furi_string_cat_str(temp_str, module->info->manufacturer);
|
||||
widget_add_string_element(
|
||||
uhf_app->widget,
|
||||
1,
|
||||
39,
|
||||
AlignLeft,
|
||||
AlignTop,
|
||||
FontSecondary,
|
||||
furi_string_get_cstr(temp_str));
|
||||
|
||||
widget_add_button_element(
|
||||
uhf_app->widget,
|
||||
GuiButtonTypeRight,
|
||||
"Continue",
|
||||
uhf_scene_verify_widget_callback,
|
||||
uhf_app);
|
||||
} else {
|
||||
widget_add_string_element(
|
||||
uhf_app->widget,
|
||||
64,
|
||||
5,
|
||||
AlignCenter,
|
||||
AlignCenter,
|
||||
FontPrimary,
|
||||
"No UHF Module found");
|
||||
widget_add_string_multiline_element(
|
||||
uhf_app->widget,
|
||||
64,
|
||||
30,
|
||||
AlignCenter,
|
||||
AlignCenter,
|
||||
FontSecondary,
|
||||
"Please refer to the git@frux-c/uhf_rfid for help.");
|
||||
widget_add_button_element(
|
||||
uhf_app->widget,
|
||||
GuiButtonTypeLeft,
|
||||
"Retry",
|
||||
uhf_scene_verify_widget_callback,
|
||||
uhf_app);
|
||||
widget_add_button_element(
|
||||
uhf_app->widget,
|
||||
GuiButtonTypeRight,
|
||||
"Skip",
|
||||
uhf_scene_verify_widget_callback,
|
||||
uhf_app);
|
||||
}
|
||||
}
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void uhf_scene_verify_on_exit(void* ctx) {
|
||||
UHFApp* uhf_app = ctx;
|
||||
// Clear string
|
||||
furi_string_free(temp_str);
|
||||
// Stop worker
|
||||
uhf_worker_stop(uhf_app->worker);
|
||||
// clear widget
|
||||
widget_reset(uhf_app->widget);
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
#include "../uhf_app_i.h"
|
||||
#include <dolphin/dolphin.h>
|
||||
|
||||
void uhf_write_tag_worker_callback(UHFWorkerEvent event, void* ctx) {
|
||||
UHFApp* uhf_app = ctx;
|
||||
if(event == UHFWorkerEventSuccess) {
|
||||
view_dispatcher_send_custom_event(uhf_app->view_dispatcher, UHFCustomEventWorkerExit);
|
||||
}
|
||||
// } else if(event == UHFWorkerEventAborted) {
|
||||
// scene_manager_search_and_switch_to_previous_scene(uhf_app->scene_manager, UHFSceneStart);
|
||||
// }
|
||||
}
|
||||
|
||||
void uhf_scene_write_tag_on_enter(void* ctx) {
|
||||
UHFApp* uhf_app = ctx;
|
||||
dolphin_deed(DolphinDeedNfcRead);
|
||||
|
||||
// Setup view
|
||||
Popup* popup = uhf_app->popup;
|
||||
popup_set_header(popup, "Writing\n[UHF] RFID\nTag", 68, 30, AlignLeft, AlignTop);
|
||||
popup_set_icon(popup, 0, 3, &I_RFIDDolphinSend_97x61);
|
||||
|
||||
// Start worker
|
||||
view_dispatcher_switch_to_view(uhf_app->view_dispatcher, UHFViewPopup);
|
||||
uhf_worker_start(
|
||||
uhf_app->worker, UHFWorkerStateWriteSingle, uhf_write_tag_worker_callback, uhf_app);
|
||||
|
||||
uhf_blink_start(uhf_app);
|
||||
}
|
||||
|
||||
bool uhf_scene_write_tag_on_event(void* ctx, SceneManagerEvent event) {
|
||||
UHFApp* uhf_app = ctx;
|
||||
bool consumed = false;
|
||||
if(event.event == UHFCustomEventWorkerExit) {
|
||||
scene_manager_next_scene(uhf_app->scene_manager, UHFSceneWriteTagSuccess);
|
||||
consumed = true;
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void uhf_scene_write_tag_on_exit(void* ctx) {
|
||||
UHFApp* uhf_app = ctx;
|
||||
// Stop worker
|
||||
uhf_worker_stop(uhf_app->worker);
|
||||
// Clear view
|
||||
popup_reset(uhf_app->popup);
|
||||
|
||||
uhf_blink_stop(uhf_app);
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
#include "../uhf_app_i.h"
|
||||
#include <dolphin/dolphin.h>
|
||||
|
||||
void uhf_write_tag_success_worker_callback(UHFWorkerEvent event, void* ctx) {
|
||||
UNUSED(event);
|
||||
UNUSED(ctx);
|
||||
}
|
||||
|
||||
void uhf_scene_write_tag_success_widget_callback(GuiButtonType result, InputType type, void* ctx) {
|
||||
furi_assert(ctx);
|
||||
UHFApp* uhf_app = ctx;
|
||||
|
||||
if(type == InputTypeShort) {
|
||||
view_dispatcher_send_custom_event(uhf_app->view_dispatcher, result);
|
||||
}
|
||||
}
|
||||
|
||||
void uhf_scene_write_tag_success_on_enter(void* ctx) {
|
||||
UHFApp* uhf_app = ctx;
|
||||
|
||||
dolphin_deed(DolphinDeedNfcReadSuccess);
|
||||
|
||||
// Send notification
|
||||
notification_message(uhf_app->notifications, &sequence_success);
|
||||
|
||||
widget_add_string_element(
|
||||
uhf_app->widget, 32, 5, AlignLeft, AlignCenter, FontPrimary, "Write Success");
|
||||
|
||||
// widget_add_string_element(uhf_app->widget, 3, 18, AlignLeft, AlignCenter, FontPrimary, "PC :");
|
||||
|
||||
// widget_add_string_element(
|
||||
// uhf_app->widget, 66, 18, AlignLeft, AlignCenter, FontPrimary, "CRC :");
|
||||
|
||||
// widget_add_string_element(
|
||||
// uhf_app->widget, 3, 32, AlignLeft, AlignCenter, FontPrimary, "EPC :");
|
||||
|
||||
// char* pc = convertToHexString(uhf_tag->pc, 2);
|
||||
// widget_add_string_element(uhf_app->widget, 26, 19, AlignLeft, AlignCenter, FontKeyboard, pc);
|
||||
// char* crc = convertToHexString(uhf_tag->crc, 2);
|
||||
// widget_add_string_element(uhf_app->widget, 96, 19, AlignLeft, AlignCenter, FontKeyboard, crc);
|
||||
// char* epc = convertToHexString(uhf_tag->epc + 2, uhf_tag->epc_length - 2);
|
||||
// widget_add_string_multiline_element(
|
||||
// uhf_app->widget, 34, 29, AlignLeft, AlignTop, FontKeyboard, epc);
|
||||
|
||||
widget_add_button_element(
|
||||
uhf_app->widget,
|
||||
GuiButtonTypeRight,
|
||||
"More",
|
||||
uhf_scene_write_tag_success_widget_callback,
|
||||
uhf_app);
|
||||
widget_add_button_element(
|
||||
uhf_app->widget,
|
||||
GuiButtonTypeLeft,
|
||||
"Exit",
|
||||
uhf_scene_write_tag_success_widget_callback,
|
||||
uhf_app);
|
||||
view_dispatcher_switch_to_view(uhf_app->view_dispatcher, UHFViewWidget);
|
||||
// free(pc);
|
||||
// free(crc);
|
||||
// free(epc);
|
||||
}
|
||||
|
||||
bool uhf_scene_write_tag_success_on_event(void* ctx, SceneManagerEvent event) {
|
||||
UHFApp* uhf_app = ctx;
|
||||
bool consumed = false;
|
||||
if(event.event == SceneManagerEventTypeBack) {
|
||||
uhf_app->worker->state = UHFWorkerStateStop;
|
||||
}
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
// if 'exit' is pressed go back to home screen
|
||||
if(event.event == GuiButtonTypeLeft) {
|
||||
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||
uhf_app->scene_manager, UHFSceneStart);
|
||||
} else if(event.event == GuiButtonTypeRight) {
|
||||
scene_manager_next_scene(uhf_app->scene_manager, UHFSceneTagMenu);
|
||||
consumed = true;
|
||||
} else if(event.event == GuiButtonTypeCenter) {
|
||||
// consumed = scene_manager_search_and_switch_to_another_scene(
|
||||
// picopass->scene_manager, PicopassSceneStart);
|
||||
}
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void uhf_scene_write_tag_success_on_exit(void* ctx) {
|
||||
UHFApp* uhf_app = ctx;
|
||||
|
||||
// // Stop worker
|
||||
uhf_worker_stop(uhf_app->worker);
|
||||
// Clear view
|
||||
popup_reset(uhf_app->popup);
|
||||
// clear widget
|
||||
widget_reset(uhf_app->widget);
|
||||
}
|
||||
|
Before Width: | Height: | Size: 148 B |
@@ -1,214 +0,0 @@
|
||||
#include "uhf_app_i.h"
|
||||
|
||||
char* convertToHexString(uint8_t* array, size_t length) {
|
||||
if(array == NULL || length == 0) {
|
||||
return " ";
|
||||
}
|
||||
FuriString* temp_str = furi_string_alloc();
|
||||
|
||||
for(size_t i = 0; i < length; i++) {
|
||||
furi_string_cat_printf(temp_str, "%02X ", array[i]);
|
||||
}
|
||||
const char* furi_str = furi_string_get_cstr(temp_str);
|
||||
|
||||
size_t str_len = strlen(furi_str);
|
||||
char* str = (char*)malloc(sizeof(char) * str_len);
|
||||
|
||||
memcpy(str, furi_str, str_len);
|
||||
furi_string_free(temp_str);
|
||||
return str;
|
||||
}
|
||||
|
||||
bool uhf_custom_event_callback(void* ctx, uint32_t event) {
|
||||
furi_assert(ctx);
|
||||
UHFApp* uhf_app = ctx;
|
||||
return scene_manager_handle_custom_event(uhf_app->scene_manager, event);
|
||||
}
|
||||
|
||||
bool uhf_back_event_callback(void* ctx) {
|
||||
furi_assert(ctx);
|
||||
UHFApp* uhf_app = ctx;
|
||||
return scene_manager_handle_back_event(uhf_app->scene_manager);
|
||||
}
|
||||
|
||||
void uhf_tick_event_callback(void* ctx) {
|
||||
furi_assert(ctx);
|
||||
UHFApp* uhf_app = ctx;
|
||||
scene_manager_handle_tick_event(uhf_app->scene_manager);
|
||||
}
|
||||
|
||||
UHFApp* uhf_alloc() {
|
||||
UHFApp* uhf_app = (UHFApp*)malloc(sizeof(UHFApp));
|
||||
uhf_app->view_dispatcher = view_dispatcher_alloc();
|
||||
uhf_app->scene_manager = scene_manager_alloc(&uhf_scene_handlers, uhf_app);
|
||||
view_dispatcher_enable_queue(uhf_app->view_dispatcher);
|
||||
view_dispatcher_set_event_callback_context(uhf_app->view_dispatcher, uhf_app);
|
||||
view_dispatcher_set_custom_event_callback(uhf_app->view_dispatcher, uhf_custom_event_callback);
|
||||
view_dispatcher_set_navigation_event_callback(
|
||||
uhf_app->view_dispatcher, uhf_back_event_callback);
|
||||
view_dispatcher_set_tick_event_callback(
|
||||
uhf_app->view_dispatcher, uhf_tick_event_callback, 100);
|
||||
|
||||
// Open GUI record
|
||||
uhf_app->gui = furi_record_open(RECORD_GUI);
|
||||
view_dispatcher_attach_to_gui(
|
||||
uhf_app->view_dispatcher, uhf_app->gui, ViewDispatcherTypeFullscreen);
|
||||
|
||||
// Variable Item List
|
||||
uhf_app->variable_item_list = variable_item_list_alloc();
|
||||
|
||||
//worker
|
||||
uhf_app->worker = uhf_worker_alloc();
|
||||
|
||||
// device
|
||||
uhf_app->uhf_device = uhf_device_alloc();
|
||||
|
||||
UHFTagWrapper* uhf_tag_wrapper = uhf_tag_wrapper_alloc();
|
||||
|
||||
// // point tag object to worker
|
||||
uhf_app->worker->uhf_tag_wrapper = uhf_tag_wrapper;
|
||||
uhf_app->uhf_device->uhf_tag_wrapper = uhf_tag_wrapper;
|
||||
|
||||
// Open Notification record
|
||||
uhf_app->notifications = furi_record_open(RECORD_NOTIFICATION);
|
||||
|
||||
// Variable Item List
|
||||
uhf_app->variable_item_list = variable_item_list_alloc();
|
||||
view_dispatcher_add_view(
|
||||
uhf_app->view_dispatcher,
|
||||
UHFViewVariableItemList,
|
||||
variable_item_list_get_view(uhf_app->variable_item_list));
|
||||
|
||||
// Submenu
|
||||
uhf_app->submenu = submenu_alloc();
|
||||
view_dispatcher_add_view(
|
||||
uhf_app->view_dispatcher, UHFViewMenu, submenu_get_view(uhf_app->submenu));
|
||||
|
||||
// Popup
|
||||
uhf_app->popup = popup_alloc();
|
||||
view_dispatcher_add_view(
|
||||
uhf_app->view_dispatcher, UHFViewPopup, popup_get_view(uhf_app->popup));
|
||||
|
||||
// Loading
|
||||
uhf_app->loading = loading_alloc();
|
||||
view_dispatcher_add_view(
|
||||
uhf_app->view_dispatcher, UHFViewLoading, loading_get_view(uhf_app->loading));
|
||||
|
||||
// Text Input
|
||||
uhf_app->text_input = text_input_alloc();
|
||||
view_dispatcher_add_view(
|
||||
uhf_app->view_dispatcher, UHFViewTextInput, text_input_get_view(uhf_app->text_input));
|
||||
|
||||
// Custom Widget
|
||||
uhf_app->widget = widget_alloc();
|
||||
view_dispatcher_add_view(
|
||||
uhf_app->view_dispatcher, UHFViewWidget, widget_get_view(uhf_app->widget));
|
||||
|
||||
return uhf_app;
|
||||
}
|
||||
|
||||
void uhf_free(UHFApp* uhf_app) {
|
||||
furi_assert(uhf_app);
|
||||
|
||||
// Submenu
|
||||
view_dispatcher_remove_view(uhf_app->view_dispatcher, UHFViewMenu);
|
||||
submenu_free(uhf_app->submenu);
|
||||
|
||||
// Popup
|
||||
view_dispatcher_remove_view(uhf_app->view_dispatcher, UHFViewPopup);
|
||||
popup_free(uhf_app->popup);
|
||||
|
||||
// Loading
|
||||
view_dispatcher_remove_view(uhf_app->view_dispatcher, UHFViewLoading);
|
||||
loading_free(uhf_app->loading);
|
||||
|
||||
// TextInput
|
||||
view_dispatcher_remove_view(uhf_app->view_dispatcher, UHFViewTextInput);
|
||||
text_input_free(uhf_app->text_input);
|
||||
|
||||
// Custom Widget
|
||||
view_dispatcher_remove_view(uhf_app->view_dispatcher, UHFViewWidget);
|
||||
widget_free(uhf_app->widget);
|
||||
|
||||
// Tag
|
||||
uhf_tag_wrapper_free(uhf_app->worker->uhf_tag_wrapper);
|
||||
|
||||
// Worker
|
||||
uhf_worker_stop(uhf_app->worker);
|
||||
uhf_worker_free(uhf_app->worker);
|
||||
|
||||
// Device
|
||||
uhf_device_free(uhf_app->uhf_device);
|
||||
|
||||
// View Dispatcher
|
||||
view_dispatcher_free(uhf_app->view_dispatcher);
|
||||
|
||||
// Scene Manager
|
||||
scene_manager_free(uhf_app->scene_manager);
|
||||
|
||||
// GUI
|
||||
furi_record_close(RECORD_GUI);
|
||||
uhf_app->gui = NULL;
|
||||
|
||||
// Variable Item List
|
||||
variable_item_list_free(uhf_app->variable_item_list);
|
||||
|
||||
// Notifications
|
||||
furi_record_close(RECORD_NOTIFICATION);
|
||||
uhf_app->notifications = NULL;
|
||||
|
||||
free(uhf_app);
|
||||
}
|
||||
|
||||
static const NotificationSequence uhf_sequence_blink_start_cyan = {
|
||||
&message_blink_start_10,
|
||||
&message_blink_set_color_cyan,
|
||||
&message_do_not_reset,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const NotificationSequence uhf_sequence_blink_stop = {
|
||||
&message_blink_stop,
|
||||
NULL,
|
||||
};
|
||||
|
||||
void uhf_blink_start(UHFApp* uhf_app) {
|
||||
notification_message(uhf_app->notifications, &uhf_sequence_blink_start_cyan);
|
||||
}
|
||||
|
||||
void uhf_blink_stop(UHFApp* uhf_app) {
|
||||
notification_message(uhf_app->notifications, &uhf_sequence_blink_stop);
|
||||
}
|
||||
|
||||
void uhf_show_loading_popup(void* ctx, bool show) {
|
||||
UHFApp* uhf_app = ctx;
|
||||
|
||||
if(show) {
|
||||
// Raise timer priority so that animations can play
|
||||
furi_timer_set_thread_priority(FuriTimerThreadPriorityElevated);
|
||||
view_dispatcher_switch_to_view(uhf_app->view_dispatcher, UHFViewLoading);
|
||||
} else {
|
||||
// Restore default timer priority
|
||||
furi_timer_set_thread_priority(FuriTimerThreadPriorityNormal);
|
||||
}
|
||||
}
|
||||
|
||||
int32_t uhf_app_main(void* ctx) {
|
||||
UNUSED(ctx);
|
||||
UHFApp* uhf_app = uhf_alloc();
|
||||
|
||||
// enable 5v pin
|
||||
furi_hal_power_enable_otg();
|
||||
// init pin a2
|
||||
// furi_hal_gpio_init_simple(&gpio_ext_pa7, GpioModeOutputPushPull);
|
||||
furi_hal_uart_set_br(FuriHalUartIdUSART1, DEFAULT_BAUDRATE);
|
||||
scene_manager_next_scene(uhf_app->scene_manager, UHFSceneVerify);
|
||||
view_dispatcher_run(uhf_app->view_dispatcher);
|
||||
|
||||
// disable 5v pin
|
||||
furi_hal_power_disable_otg();
|
||||
// furi_hal_gpio_disable_int_callback()
|
||||
// exit app
|
||||
uhf_free(uhf_app);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
typedef struct UHFApp UHFApp;
|
||||
@@ -1,110 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <furi.h>
|
||||
#include <gui/gui.h>
|
||||
#include <gui/view_dispatcher.h>
|
||||
#include <gui/scene_manager.h>
|
||||
#include <notification/notification_messages.h>
|
||||
|
||||
#include <gui/modules/submenu.h>
|
||||
#include <gui/modules/popup.h>
|
||||
#include <gui/modules/loading.h>
|
||||
#include <gui/modules/text_input.h>
|
||||
#include <gui/modules/widget.h>
|
||||
#include <gui/modules/variable_item_list.h>
|
||||
|
||||
#include <input/input.h>
|
||||
|
||||
#include "uhf_app.h"
|
||||
#include "uhf_worker.h"
|
||||
#include "uhf_device.h"
|
||||
#include "scenes/uhf_scene.h"
|
||||
|
||||
#include <storage/storage.h>
|
||||
#include <lib/toolbox/path.h>
|
||||
#include <toolbox/path.h>
|
||||
#include <flipper_format/flipper_format.h>
|
||||
|
||||
#include <uhf_rfid_icons.h>
|
||||
|
||||
#include <assets_icons.h>
|
||||
|
||||
#define UHF_TEXT_STORE_SIZE 128
|
||||
// #define UHF_APPS_DATA_FOLDER EXT_PATH("apps_data")
|
||||
// #define UHF_APPS_STORAGE_FOLDER
|
||||
// UHF_APPS_DATA_FOLDER "/"
|
||||
// "uhf_rfid"
|
||||
// #define UHF_FILE_EXTENSION ".uhf"
|
||||
|
||||
enum UHFCustomEvent {
|
||||
// Reserve first 100 events for button types and indexes, starting from 0
|
||||
UHFCustomEventReserved = 100,
|
||||
|
||||
UHFCustomEventVerifyDone,
|
||||
UHFCustomEventViewExit,
|
||||
UHFCustomEventWorkerExit,
|
||||
UHFCustomEventByteInputDone,
|
||||
UHFCustomEventTextInputDone,
|
||||
UHFCustomEventSceneSettingLock,
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
EventTypeTick,
|
||||
EventTypeKey,
|
||||
} EventType;
|
||||
|
||||
struct UHFApp {
|
||||
UHFWorker* worker;
|
||||
ViewDispatcher* view_dispatcher;
|
||||
Gui* gui;
|
||||
NotificationApp* notifications;
|
||||
SceneManager* scene_manager;
|
||||
VariableItemList* variable_item_list;
|
||||
// Storage* storage;
|
||||
UHFDevice* uhf_device;
|
||||
char text_store[UHF_TEXT_STORE_SIZE + 1];
|
||||
FuriString* text_box_store;
|
||||
// Common Views
|
||||
Submenu* submenu;
|
||||
Popup* popup;
|
||||
Loading* loading;
|
||||
TextInput* text_input;
|
||||
Widget* widget;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
UHFViewMenu,
|
||||
UHFViewPopup,
|
||||
UHFViewLoading,
|
||||
UHFViewTextInput,
|
||||
UHFViewWidget,
|
||||
UHFViewVariableItemList,
|
||||
} UHFView;
|
||||
|
||||
UHFApp* uhf_app_alloc();
|
||||
|
||||
void uhf_text_store_set(UHFApp* uhf, const char* text, ...);
|
||||
|
||||
void uhf_text_store_clear(UHFApp* uhf);
|
||||
|
||||
void uhf_blink_start(UHFApp* uhf);
|
||||
|
||||
void uhf_blink_stop(UHFApp* uhf);
|
||||
|
||||
void uhf_show_loading_popup(void* context, bool show);
|
||||
|
||||
/** Check if memory is set to pattern
|
||||
*
|
||||
* @warning zero size will return false
|
||||
*
|
||||
* @param[in] data Pointer to the byte array
|
||||
* @param[in] pattern The pattern
|
||||
* @param[in] size The byte array size
|
||||
*
|
||||
* @return True if memory is set to pattern, false otherwise
|
||||
*/
|
||||
bool uhf_is_memset(const uint8_t* data, const uint8_t pattern, size_t size);
|
||||
|
||||
char* convertToHexString(uint8_t* array, size_t length);
|
||||
|
||||
// bool uhf_save_read_data(UHFResponseData* uhf_response_data, Storage* storage, const char* filename);
|
||||
@@ -1,69 +0,0 @@
|
||||
#include "uhf_buffer.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
Buffer* buffer_alloc(size_t initial_capacity) {
|
||||
Buffer* buf = (Buffer*)malloc(sizeof(Buffer));
|
||||
buf->data = (uint8_t*)malloc(sizeof(uint8_t) * initial_capacity);
|
||||
if(!buf->data) {
|
||||
free(buf);
|
||||
return NULL;
|
||||
}
|
||||
buf->size = 0;
|
||||
buf->capacity = initial_capacity;
|
||||
return buf;
|
||||
}
|
||||
|
||||
bool buffer_append_single(Buffer* buf, uint8_t data) {
|
||||
if(buf->closed) return false;
|
||||
if(buf->size + 1 > buf->capacity) {
|
||||
size_t new_capacity = buf->capacity * 2;
|
||||
uint8_t* new_data = (uint8_t*)realloc(buf->data, sizeof(uint8_t) * new_capacity);
|
||||
if(!new_data) return false;
|
||||
buf->data = new_data;
|
||||
buf->capacity = new_capacity;
|
||||
}
|
||||
buf->data[buf->size++] = data;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool buffer_append(Buffer* buf, uint8_t* data, size_t data_size) {
|
||||
if(buf->closed) return false;
|
||||
if(buf->size + data_size > buf->capacity) {
|
||||
size_t new_capacity = buf->capacity * 2;
|
||||
uint8_t* new_data = (uint8_t*)realloc(buf->data, new_capacity);
|
||||
if(!new_data) return false;
|
||||
|
||||
buf->data = new_data;
|
||||
buf->capacity = new_capacity;
|
||||
}
|
||||
|
||||
memcpy((void*)&buf->data[buf->size], data, data_size);
|
||||
buf->size += data_size;
|
||||
return true;
|
||||
}
|
||||
|
||||
uint8_t* buffer_get_data(Buffer* buf) {
|
||||
return buf->data;
|
||||
}
|
||||
|
||||
size_t buffer_get_size(Buffer* buf) {
|
||||
return buf->size;
|
||||
}
|
||||
|
||||
void buffer_close(Buffer* buf) {
|
||||
buf->closed = true;
|
||||
}
|
||||
|
||||
void buffer_reset(Buffer* buf) {
|
||||
for(size_t i = 0; i < MAX_BUFFER_SIZE; i++) {
|
||||
buf->data[i] = 0;
|
||||
}
|
||||
buf->size = 0;
|
||||
buf->closed = false;
|
||||
}
|
||||
|
||||
void buffer_free(Buffer* buf) {
|
||||
free(buf->data);
|
||||
free(buf);
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#define MAX_BUFFER_SIZE 200
|
||||
|
||||
typedef struct Buffer {
|
||||
uint8_t* data;
|
||||
size_t size;
|
||||
size_t capacity;
|
||||
bool closed;
|
||||
} Buffer;
|
||||
|
||||
Buffer* buffer_alloc(size_t inital_capacity);
|
||||
bool buffer_append_single(Buffer* buf, uint8_t value);
|
||||
bool buffer_append(Buffer* buf, uint8_t* data, size_t size);
|
||||
uint8_t* buffer_get_data(Buffer* buf);
|
||||
size_t buffer_get_size(Buffer* buf);
|
||||
void buffer_close(Buffer* buf);
|
||||
void buffer_reset(Buffer* buf);
|
||||
void buffer_free(Buffer* buf);
|
||||
@@ -1,350 +0,0 @@
|
||||
#include "uhf_device.h"
|
||||
#include <toolbox/path.h>
|
||||
#include <flipper_format/flipper_format.h>
|
||||
#include <uhf_rfid_icons.h>
|
||||
|
||||
#include <assets_icons.h>
|
||||
|
||||
#define TAG "UHFDevice"
|
||||
|
||||
static const char* uhf_file_header = "Flipper UHF RFID device";
|
||||
static const uint32_t uhf_file_version = 1;
|
||||
// static const uint8_t bank_data_start = 20;
|
||||
// static const uint8_t bank_data_length = 16;
|
||||
|
||||
UHFDevice* uhf_device_alloc() {
|
||||
UHFDevice* uhf_device = malloc(sizeof(UHFDevice));
|
||||
uhf_device->storage = furi_record_open(RECORD_STORAGE);
|
||||
uhf_device->dialogs = furi_record_open(RECORD_DIALOGS);
|
||||
uhf_device->load_path = furi_string_alloc();
|
||||
return uhf_device;
|
||||
}
|
||||
|
||||
void uhf_device_set_name(UHFDevice* dev, const char* name) {
|
||||
furi_assert(dev);
|
||||
strlcpy(dev->dev_name, name, UHF_DEV_NAME_MAX_LEN);
|
||||
}
|
||||
|
||||
static bool uhf_device_save_file(
|
||||
UHFDevice* dev,
|
||||
const char* dev_name,
|
||||
const char* folder,
|
||||
const char* extension,
|
||||
bool use_load_path) {
|
||||
furi_assert(dev);
|
||||
|
||||
UHFTag* uhf_tag = dev->uhf_tag_wrapper->uhf_tag;
|
||||
bool saved = false;
|
||||
FlipperFormat* file = flipper_format_file_alloc(dev->storage);
|
||||
FuriString* temp_str;
|
||||
temp_str = furi_string_alloc();
|
||||
do {
|
||||
if(use_load_path && !furi_string_empty(dev->load_path)) {
|
||||
// Get directory name
|
||||
path_extract_dirname(furi_string_get_cstr(dev->load_path), temp_str);
|
||||
// Make path to file to save
|
||||
furi_string_cat_printf(temp_str, "/%s%s", dev_name, extension);
|
||||
} else {
|
||||
// First remove uhf device file if it was saved
|
||||
furi_string_printf(temp_str, "%s/%s%s", folder, dev_name, extension);
|
||||
}
|
||||
// Open file
|
||||
if(!flipper_format_file_open_always(file, furi_string_get_cstr(temp_str))) break;
|
||||
|
||||
// Write header
|
||||
if(!flipper_format_write_header_cstr(file, uhf_file_header, uhf_file_version)) break;
|
||||
|
||||
// Reserved bank might be added
|
||||
// todo : maybe
|
||||
uint32_t temp_arr[1];
|
||||
uint8_t temp_arr2[2];
|
||||
// write pc
|
||||
temp_arr2[0] = (uint8_t)(uhf_tag_get_epc_pc(uhf_tag) >> 8) & 0xFF;
|
||||
temp_arr2[1] = (uint8_t)(uhf_tag_get_epc_pc(uhf_tag) & 0xFF);
|
||||
if(!flipper_format_write_hex(file, UHF_EPC_PC_LABEL, temp_arr2, 2)) break;
|
||||
// write crc
|
||||
temp_arr2[0] = (uint8_t)(uhf_tag_get_epc_crc(uhf_tag) >> 8) & 0xFF;
|
||||
temp_arr2[1] = (uint8_t)(uhf_tag_get_epc_crc(uhf_tag) & 0xFF);
|
||||
if(!flipper_format_write_hex(file, UHF_EPC_CRC_LABEL, temp_arr2, 2)) break;
|
||||
// write epc
|
||||
temp_arr[0] = uhf_tag_get_epc_size(uhf_tag);
|
||||
if(!flipper_format_write_uint32(file, UHF_EPC_BANK_LENGTH_LABEL, temp_arr, 1)) break;
|
||||
if(!flipper_format_write_hex(
|
||||
file, UHF_EPC_BANK_LABEL, uhf_tag_get_epc(uhf_tag), uhf_tag_get_epc_size(uhf_tag)))
|
||||
break;
|
||||
// write tid
|
||||
temp_arr[0] = uhf_tag_get_tid_size(uhf_tag);
|
||||
if(!flipper_format_write_uint32(file, UHF_TID_BANK_LENGTH_LABEL, temp_arr, 1)) break;
|
||||
if(!flipper_format_write_hex(
|
||||
file, UHF_TID_BANK_LABEL, uhf_tag_get_tid(uhf_tag), uhf_tag_get_tid_size(uhf_tag)))
|
||||
break;
|
||||
// write user
|
||||
temp_arr[0] = uhf_tag_get_user_size(uhf_tag);
|
||||
if(!flipper_format_write_uint32(file, UHF_USER_BANK_LENGTH_LABEL, temp_arr, 1)) break;
|
||||
if(!flipper_format_write_hex(
|
||||
file,
|
||||
UHF_USER_BANK_LABEL,
|
||||
uhf_tag_get_user(uhf_tag),
|
||||
uhf_tag_get_user_size(uhf_tag)))
|
||||
break;
|
||||
saved = true;
|
||||
} while(0);
|
||||
|
||||
if(!saved) {
|
||||
dialog_message_show_storage_error(dev->dialogs, "Can not save\nfile");
|
||||
}
|
||||
furi_string_free(temp_str);
|
||||
flipper_format_free(file);
|
||||
return saved;
|
||||
}
|
||||
|
||||
bool uhf_device_save(UHFDevice* dev, const char* dev_name) {
|
||||
return uhf_device_save_file(
|
||||
dev, dev_name, STORAGE_APP_DATA_PATH_PREFIX, UHF_APP_EXTENSION, true);
|
||||
|
||||
return false;
|
||||
}
|
||||
// uncomment
|
||||
|
||||
static bool uhf_device_load_data(UHFDevice* dev, FuriString* path, bool show_dialog) {
|
||||
bool parsed = false;
|
||||
FlipperFormat* file = flipper_format_file_alloc(dev->storage);
|
||||
// UHFResponseData* uhf_response_data = dev->dev_data;
|
||||
FuriString* temp_str;
|
||||
temp_str = furi_string_alloc();
|
||||
bool deprecated_version = false;
|
||||
UHFTag* uhf_tag = uhf_tag_alloc();
|
||||
uhf_tag_reset(uhf_tag);
|
||||
uint32_t temp_arr[1];
|
||||
if(dev->loading_cb) {
|
||||
dev->loading_cb(dev->loading_cb_ctx, true);
|
||||
}
|
||||
|
||||
do {
|
||||
if(!flipper_format_file_open_existing(file, furi_string_get_cstr(path))) break;
|
||||
|
||||
// Read and verify file header
|
||||
uint32_t version = 0;
|
||||
if(!flipper_format_read_header(file, temp_str, &version)) break;
|
||||
if(furi_string_cmp_str(temp_str, uhf_file_header) || (version != uhf_file_version)) {
|
||||
deprecated_version = true;
|
||||
break;
|
||||
}
|
||||
// read pc
|
||||
uint8_t temp_arr2[2];
|
||||
if(!flipper_format_read_hex(file, UHF_EPC_PC_LABEL, temp_arr2, 2)) break;
|
||||
uhf_tag_set_epc_pc(uhf_tag, (temp_arr2[0] << 8) + temp_arr2[1]);
|
||||
// read crc
|
||||
if(!flipper_format_read_hex(file, UHF_EPC_CRC_LABEL, temp_arr2, 2)) break;
|
||||
uhf_tag_set_epc_crc(uhf_tag, (temp_arr2[0] << 8) + temp_arr2[1]);
|
||||
// read epc
|
||||
if(!flipper_format_read_uint32(file, UHF_EPC_BANK_LENGTH_LABEL, temp_arr, 1)) break;
|
||||
uhf_tag_set_epc_size(uhf_tag, temp_arr[0]);
|
||||
if(!flipper_format_read_hex(
|
||||
file, UHF_EPC_BANK_LABEL, uhf_tag_get_epc(uhf_tag), uhf_tag_get_epc_size(uhf_tag)))
|
||||
break;
|
||||
|
||||
// read tid
|
||||
if(!flipper_format_read_uint32(file, UHF_TID_BANK_LENGTH_LABEL, temp_arr, 1)) break;
|
||||
uhf_tag_set_tid_size(uhf_tag, temp_arr[0]);
|
||||
if(!flipper_format_read_hex(
|
||||
file, UHF_TID_BANK_LABEL, uhf_tag_get_tid(uhf_tag), uhf_tag_get_tid_size(uhf_tag)))
|
||||
break;
|
||||
// read user
|
||||
if(!flipper_format_read_uint32(file, UHF_USER_BANK_LENGTH_LABEL, temp_arr, 1)) break;
|
||||
uhf_tag_set_user_size(uhf_tag, temp_arr[0]);
|
||||
if(!flipper_format_read_hex(
|
||||
file,
|
||||
UHF_USER_BANK_LABEL,
|
||||
uhf_tag_get_user(uhf_tag),
|
||||
uhf_tag_get_user_size(uhf_tag)))
|
||||
break;
|
||||
|
||||
parsed = true;
|
||||
} while(false);
|
||||
|
||||
if(dev->loading_cb) {
|
||||
dev->loading_cb(dev->loading_cb_ctx, false);
|
||||
}
|
||||
|
||||
if((!parsed) && (show_dialog)) {
|
||||
if(deprecated_version) {
|
||||
dialog_message_show_storage_error(dev->dialogs, "File format deprecated");
|
||||
} else {
|
||||
dialog_message_show_storage_error(dev->dialogs, "Can not parse\nfile");
|
||||
}
|
||||
}
|
||||
uhf_tag_wrapper_set_tag(dev->uhf_tag_wrapper, uhf_tag);
|
||||
furi_string_free(temp_str);
|
||||
flipper_format_free(file);
|
||||
return parsed;
|
||||
}
|
||||
|
||||
// void picopass_device_clear(UHFDevice* dev) {
|
||||
// furi_assert(dev);
|
||||
|
||||
// picopass_device_data_clear(&dev->dev_data);
|
||||
// memset(&dev->dev_data, 0, sizeof(dev->dev_data));
|
||||
// dev->format = PicopassDeviceSaveFormatHF;
|
||||
// furi_string_reset(dev->load_path);
|
||||
// }
|
||||
|
||||
void uhf_device_free(UHFDevice* uhf_dev) {
|
||||
furi_assert(uhf_dev);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
furi_record_close(RECORD_DIALOGS);
|
||||
furi_string_free(uhf_dev->load_path);
|
||||
free(uhf_dev);
|
||||
}
|
||||
|
||||
bool uhf_file_select(UHFDevice* dev) {
|
||||
furi_assert(dev);
|
||||
|
||||
FuriString* uhf_app_folder;
|
||||
uhf_app_folder = furi_string_alloc_set(STORAGE_APP_DATA_PATH_PREFIX);
|
||||
|
||||
DialogsFileBrowserOptions browser_options;
|
||||
dialog_file_browser_set_basic_options(&browser_options, UHF_APP_EXTENSION, &I_Nfc_10px);
|
||||
browser_options.base_path = STORAGE_APP_DATA_PATH_PREFIX;
|
||||
|
||||
bool res =
|
||||
dialog_file_browser_show(dev->dialogs, dev->load_path, uhf_app_folder, &browser_options);
|
||||
|
||||
furi_string_free(uhf_app_folder);
|
||||
if(res) {
|
||||
FuriString* filename;
|
||||
filename = furi_string_alloc();
|
||||
path_extract_filename(dev->load_path, filename, true);
|
||||
strncpy(dev->dev_name, furi_string_get_cstr(filename), UHF_DEV_NAME_MAX_LEN);
|
||||
res = uhf_device_load_data(dev, dev->load_path, true);
|
||||
if(res) {
|
||||
uhf_device_set_name(dev, dev->dev_name);
|
||||
}
|
||||
furi_string_free(filename);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
// void uhf_device_data_clear(UHFDevice* dev_data) {
|
||||
// for(size_t i = 0; i < PICOPASS_MAX_APP_LIMIT; i++) {
|
||||
// memset(dev_data->AA1[i].data, 0, sizeof(dev_data->AA1[i].data));
|
||||
// }
|
||||
// dev_data->pacs.legacy = false;
|
||||
// dev_data->pacs.se_enabled = false;
|
||||
// dev_data->pacs.elite_kdf = false;
|
||||
// dev_data->pacs.pin_length = 0;
|
||||
// }
|
||||
|
||||
bool uhf_device_delete(UHFDevice* dev, bool use_load_path) {
|
||||
furi_assert(dev);
|
||||
|
||||
bool deleted = false;
|
||||
FuriString* file_path;
|
||||
file_path = furi_string_alloc();
|
||||
|
||||
do {
|
||||
// Delete original file
|
||||
if(use_load_path && !furi_string_empty(dev->load_path)) {
|
||||
furi_string_set(file_path, dev->load_path);
|
||||
} else {
|
||||
furi_string_printf(file_path, APP_DATA_PATH("%s%s"), dev->dev_name, UHF_APP_EXTENSION);
|
||||
}
|
||||
if(!storage_simply_remove(dev->storage, furi_string_get_cstr(file_path))) break;
|
||||
deleted = true;
|
||||
} while(0);
|
||||
|
||||
if(!deleted) {
|
||||
dialog_message_show_storage_error(dev->dialogs, "Can not remove file");
|
||||
}
|
||||
|
||||
furi_string_free(file_path);
|
||||
return deleted;
|
||||
}
|
||||
|
||||
void uhf_device_set_loading_callback(UHFDevice* dev, UHFLoadingCallback callback, void* context) {
|
||||
furi_assert(dev);
|
||||
|
||||
dev->loading_cb = callback;
|
||||
dev->loading_cb_ctx = context;
|
||||
}
|
||||
|
||||
// ReturnCode picopass_device_decrypt(uint8_t* enc_data, uint8_t* dec_data) {
|
||||
// uint8_t key[32] = {0};
|
||||
// memcpy(key, picopass_iclass_decryptionkey, sizeof(picopass_iclass_decryptionkey));
|
||||
// mbedtls_des3_context ctx;
|
||||
// mbedtls_des3_init(&ctx);
|
||||
// mbedtls_des3_set2key_dec(&ctx, key);
|
||||
// mbedtls_des3_crypt_ecb(&ctx, enc_data, dec_data);
|
||||
// mbedtls_des3_free(&ctx);
|
||||
// return ERR_NONE;
|
||||
// }
|
||||
|
||||
// ReturnCode picopass_device_parse_credential(PicopassBlock* AA1, PicopassPacs* pacs) {
|
||||
// ReturnCode err;
|
||||
|
||||
// pacs->biometrics = AA1[6].data[4];
|
||||
// pacs->pin_length = AA1[6].data[6] & 0x0F;
|
||||
// pacs->encryption = AA1[6].data[7];
|
||||
|
||||
// if(pacs->encryption == PicopassDeviceEncryption3DES) {
|
||||
// FURI_LOG_D(TAG, "3DES Encrypted");
|
||||
// err = picopass_device_decrypt(AA1[7].data, pacs->credential);
|
||||
// if(err != ERR_NONE) {
|
||||
// FURI_LOG_E(TAG, "decrypt error %d", err);
|
||||
// return err;
|
||||
// }
|
||||
|
||||
// err = picopass_device_decrypt(AA1[8].data, pacs->pin0);
|
||||
// if(err != ERR_NONE) {
|
||||
// FURI_LOG_E(TAG, "decrypt error %d", err);
|
||||
// return err;
|
||||
// }
|
||||
|
||||
// err = picopass_device_decrypt(AA1[9].data, pacs->pin1);
|
||||
// if(err != ERR_NONE) {
|
||||
// FURI_LOG_E(TAG, "decrypt error %d", err);
|
||||
// return err;
|
||||
// }
|
||||
// } else if(pacs->encryption == PicopassDeviceEncryptionNone) {
|
||||
// FURI_LOG_D(TAG, "No Encryption");
|
||||
// memcpy(pacs->credential, AA1[7].data, PICOPASS_BLOCK_LEN);
|
||||
// memcpy(pacs->pin0, AA1[8].data, PICOPASS_BLOCK_LEN);
|
||||
// memcpy(pacs->pin1, AA1[9].data, PICOPASS_BLOCK_LEN);
|
||||
// } else if(pacs->encryption == PicopassDeviceEncryptionDES) {
|
||||
// FURI_LOG_D(TAG, "DES Encrypted");
|
||||
// } else {
|
||||
// FURI_LOG_D(TAG, "Unknown encryption");
|
||||
// }
|
||||
|
||||
// pacs->sio = (AA1[10].data[0] == 0x30); // rough check
|
||||
|
||||
// return ERR_NONE;
|
||||
// }
|
||||
|
||||
// ReturnCode picopass_device_parse_wiegand(uint8_t* data, PicopassWiegandRecord* record) {
|
||||
// uint32_t* halves = (uint32_t*)data;
|
||||
// if(halves[0] == 0) {
|
||||
// uint8_t leading0s = __builtin_clz(REVERSE_BYTES_U32(halves[1]));
|
||||
// record->bitLength = 31 - leading0s;
|
||||
// } else {
|
||||
// uint8_t leading0s = __builtin_clz(REVERSE_BYTES_U32(halves[0]));
|
||||
// record->bitLength = 63 - leading0s;
|
||||
// }
|
||||
// FURI_LOG_D(TAG, "bitLength: %d", record->bitLength);
|
||||
|
||||
// if(record->bitLength == 26) {
|
||||
// uint8_t* v4 = data + 4;
|
||||
// uint32_t bot = v4[3] | (v4[2] << 8) | (v4[1] << 16) | (v4[0] << 24);
|
||||
|
||||
// record->CardNumber = (bot >> 1) & 0xFFFF;
|
||||
// record->FacilityCode = (bot >> 17) & 0xFF;
|
||||
// FURI_LOG_D(TAG, "FC: %u CN: %u", record->FacilityCode, record->CardNumber);
|
||||
// record->valid = true;
|
||||
// } else {
|
||||
// record->CardNumber = 0;
|
||||
// record->FacilityCode = 0;
|
||||
// record->valid = false;
|
||||
// }
|
||||
// return ERR_NONE;
|
||||
// }
|
||||
@@ -1,54 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <storage/storage.h>
|
||||
#include <dialogs/dialogs.h>
|
||||
#include <mbedtls/des.h>
|
||||
#include "uhf_tag.h"
|
||||
|
||||
#define UHF_DEV_NAME_MAX_LEN 22
|
||||
#define UHF_EPC_BANK_LENGTH_LABEL "EPC_LENGTH"
|
||||
#define UHF_TID_BANK_LENGTH_LABEL "TID_LENGTH"
|
||||
#define UHF_USER_BANK_LENGTH_LABEL "USER_LENGTH"
|
||||
#define UHF_EPC_PC_LABEL "PC"
|
||||
#define UHF_EPC_CRC_LABEL "CRC"
|
||||
#define UHF_RFU_BANK_LABEL "RFU"
|
||||
#define UHF_EPC_BANK_LABEL "EPC"
|
||||
#define UHF_TID_BANK_LABEL "TID"
|
||||
#define UHF_USER_BANK_LABEL "USER"
|
||||
|
||||
#define UHF_APP_EXTENSION ".uhf"
|
||||
|
||||
typedef void (*UHFLoadingCallback)(void* context, bool state);
|
||||
|
||||
typedef struct {
|
||||
Storage* storage;
|
||||
DialogsApp* dialogs;
|
||||
UHFTagWrapper* uhf_tag_wrapper;
|
||||
char dev_name[UHF_DEV_NAME_MAX_LEN + 1];
|
||||
FuriString* load_path;
|
||||
UHFLoadingCallback loading_cb;
|
||||
void* loading_cb_ctx;
|
||||
} UHFDevice;
|
||||
|
||||
UHFDevice* uhf_device_alloc();
|
||||
|
||||
void uhf_device_free(UHFDevice* uhf_dev);
|
||||
|
||||
void uhf_device_set_name(UHFDevice* dev, const char* name);
|
||||
|
||||
bool uhf_device_save(UHFDevice* dev, const char* dev_name);
|
||||
|
||||
bool uhf_file_select(UHFDevice* dev);
|
||||
|
||||
// void uhf_device_data_clear(PicopassDeviceData* dev_data);
|
||||
|
||||
void uhf_device_clear(UHFDevice* dev);
|
||||
|
||||
bool uhf_device_delete(UHFDevice* dev, bool use_load_path);
|
||||
|
||||
void uhf_device_set_loading_callback(UHFDevice* dev, UHFLoadingCallback callback, void* context);
|
||||
|
||||
// ReturnCode uhf_device_parse_credential(PicopassBlock* AA1, PicopassPacs* pacs);
|
||||
// ReturnCode uhf_device_parse_wiegand(uint8_t* data, PicopassWiegandRecord* record);
|
||||
@@ -1,388 +0,0 @@
|
||||
#include "uhf_module.h"
|
||||
#include "uhf_module_cmd.h"
|
||||
|
||||
#define DELAY_MS 100
|
||||
#define WAIT_TICK 8000 // max wait time in between each byte
|
||||
|
||||
volatile uint16_t tick = 0;
|
||||
|
||||
void rx_callback(UartIrqEvent event, uint8_t data, void* ctx) {
|
||||
UNUSED(event);
|
||||
Buffer* buffer = ctx;
|
||||
if(buffer->closed) return; // buffer closed
|
||||
buffer_append_single(buffer, data); // append data
|
||||
if(data == FRAME_END) buffer_close(buffer); // end of frame
|
||||
tick = WAIT_TICK; // reset tick
|
||||
}
|
||||
|
||||
static M100ResponseType setup_and_send_rx(M100Module* module, uint8_t* cmd, size_t cmd_length) {
|
||||
buffer_reset(module->buf);
|
||||
tick = WAIT_TICK;
|
||||
furi_hal_uart_tx(FuriHalUartIdUSART1, cmd, cmd_length);
|
||||
while(--tick) {
|
||||
furi_delay_us(5);
|
||||
}
|
||||
buffer_close(module->buf);
|
||||
// Validation Checks
|
||||
uint8_t* data = buffer_get_data(module->buf);
|
||||
size_t length = buffer_get_size(module->buf);
|
||||
// check if size > 0
|
||||
if(!length) return M100EmptyResponse;
|
||||
// check if data is valid
|
||||
if(data[0] != FRAME_START || data[length - 1] != FRAME_END) return M100ValidationFail;
|
||||
// check if checksum is correct
|
||||
if(checksum(data + 1, length - 3) != data[length - 2]) return M100ChecksumFail;
|
||||
return M100SuccessResponse;
|
||||
}
|
||||
|
||||
M100ModuleInfo* m100_module_info_alloc() {
|
||||
M100ModuleInfo* module_info = (M100ModuleInfo*)malloc(sizeof(M100ModuleInfo));
|
||||
return module_info;
|
||||
}
|
||||
|
||||
void m100_module_info_free(M100ModuleInfo* module_info) {
|
||||
free(module_info->hw_version);
|
||||
free(module_info->sw_version);
|
||||
free(module_info->manufacturer);
|
||||
free(module_info);
|
||||
}
|
||||
M100Module* m100_module_alloc() {
|
||||
M100Module* module = (M100Module*)malloc(sizeof(M100Module));
|
||||
module->info = m100_module_info_alloc();
|
||||
module->buf = buffer_alloc(MAX_BUFFER_SIZE);
|
||||
module->baudrate = DEFAULT_BAUDRATE;
|
||||
module->transmitting_power = DEFAULT_TRANSMITTING_POWER;
|
||||
module->region = DEFAULT_WORKING_REGION;
|
||||
furi_hal_uart_set_irq_cb(FuriHalUartIdUSART1, rx_callback, module->buf);
|
||||
return module;
|
||||
}
|
||||
|
||||
void m100_module_free(M100Module* module) {
|
||||
m100_module_info_free(module->info);
|
||||
buffer_free(module->buf);
|
||||
free(module);
|
||||
}
|
||||
|
||||
uint8_t checksum(const uint8_t* data, size_t length) {
|
||||
// CheckSum8 Modulo 256
|
||||
// Sum of Bytes % 256
|
||||
uint64_t sum_val = 0x00;
|
||||
for(size_t i = 0; i < length; i++) {
|
||||
sum_val += data[i];
|
||||
}
|
||||
return (uint8_t)(sum_val % 0x100);
|
||||
}
|
||||
|
||||
uint16_t crc16_genibus(const uint8_t* data, size_t length) {
|
||||
uint16_t crc = 0xFFFF; // Initial value
|
||||
uint16_t polynomial = 0x1021; // CRC-16/GENIBUS polynomial
|
||||
|
||||
for(size_t i = 0; i < length; i++) {
|
||||
crc ^= (data[i] << 8); // Move byte into MSB of 16bit CRC
|
||||
for(int j = 0; j < 8; j++) {
|
||||
if(crc & 0x8000) {
|
||||
crc = (crc << 1) ^ polynomial;
|
||||
} else {
|
||||
crc <<= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return crc ^ 0xFFFF; // Post-inversion
|
||||
}
|
||||
|
||||
char* _m100_info_helper(M100Module* module, char** info) {
|
||||
if(!buffer_get_size(module->buf)) return NULL;
|
||||
uint8_t* data = buffer_get_data(module->buf);
|
||||
uint16_t payload_len = data[3];
|
||||
payload_len = (payload_len << 8) + data[4];
|
||||
FuriString* temp_str = furi_string_alloc();
|
||||
for(int i = 0; i < payload_len; i++) {
|
||||
furi_string_cat_printf(temp_str, "%c", data[6 + i]);
|
||||
}
|
||||
if(*info == NULL) {
|
||||
*info = (char*)malloc(sizeof(char) * payload_len);
|
||||
} else {
|
||||
for(size_t i = 0; i < strlen(*info); i++) {
|
||||
(*info)[i] = 0;
|
||||
}
|
||||
}
|
||||
memcpy(*info, furi_string_get_cstr(temp_str), payload_len);
|
||||
furi_string_free(temp_str);
|
||||
return *info;
|
||||
}
|
||||
|
||||
char* m100_get_hardware_version(M100Module* module) {
|
||||
setup_and_send_rx(module, (uint8_t*)&CMD_HW_VERSION.cmd[0], CMD_HW_VERSION.length);
|
||||
return _m100_info_helper(module, &module->info->hw_version);
|
||||
}
|
||||
char* m100_get_software_version(M100Module* module) {
|
||||
setup_and_send_rx(module, (uint8_t*)&CMD_SW_VERSION.cmd[0], CMD_SW_VERSION.length);
|
||||
return _m100_info_helper(module, &module->info->sw_version);
|
||||
}
|
||||
char* m100_get_manufacturers(M100Module* module) {
|
||||
setup_and_send_rx(module, (uint8_t*)&CMD_MANUFACTURERS.cmd[0], CMD_MANUFACTURERS.length);
|
||||
return _m100_info_helper(module, &module->info->manufacturer);
|
||||
}
|
||||
|
||||
M100ResponseType m100_single_poll(M100Module* module, UHFTag* uhf_tag) {
|
||||
M100ResponseType rp_type =
|
||||
setup_and_send_rx(module, (uint8_t*)&CMD_SINGLE_POLLING.cmd[0], CMD_SINGLE_POLLING.length);
|
||||
if(rp_type != M100SuccessResponse) return rp_type;
|
||||
uint8_t* data = buffer_get_data(module->buf);
|
||||
size_t length = buffer_get_size(module->buf);
|
||||
uint16_t pc = data[6];
|
||||
uint16_t crc = 0;
|
||||
// mask out epc length from protocol control
|
||||
size_t epc_len = pc;
|
||||
epc_len >>= 3;
|
||||
epc_len *= 2;
|
||||
// get protocol control
|
||||
pc <<= 8;
|
||||
pc += data[7];
|
||||
// get cyclic redundency check
|
||||
crc = data[8 + epc_len];
|
||||
crc <<= 8;
|
||||
crc += data[8 + epc_len + 1];
|
||||
// validate checksum
|
||||
if(checksum(data + 1, length - 3) != data[length - 2]) return M100ValidationFail;
|
||||
// validate crc
|
||||
if(crc16_genibus(data + 6, epc_len + 2) != crc) return M100ValidationFail;
|
||||
uhf_tag_set_epc_pc(uhf_tag, pc);
|
||||
uhf_tag_set_epc_crc(uhf_tag, crc);
|
||||
uhf_tag_set_epc(uhf_tag, data + 8, epc_len);
|
||||
return M100SuccessResponse;
|
||||
}
|
||||
|
||||
M100ResponseType m100_set_select(M100Module* module, UHFTag* uhf_tag) {
|
||||
// Set select
|
||||
uint8_t cmd[MAX_BUFFER_SIZE];
|
||||
size_t cmd_length = CMD_SET_SELECT_PARAMETER.length;
|
||||
size_t mask_length_bytes = uhf_tag->epc->size;
|
||||
size_t mask_length_bits = mask_length_bytes * 8;
|
||||
// payload len == sel param len + ptr len + mask len + epc len
|
||||
size_t payload_len = 7 + mask_length_bytes;
|
||||
memcpy(cmd, CMD_SET_SELECT_PARAMETER.cmd, cmd_length);
|
||||
// set new length
|
||||
cmd_length = 12 + mask_length_bytes + 2;
|
||||
// set payload length
|
||||
cmd[3] = (payload_len >> 8) & 0xFF;
|
||||
cmd[4] = payload_len & 0xFF;
|
||||
// set select param
|
||||
cmd[5] = 0x01; // 0x00=rfu, 0x01=epc, 0x10=tid, 0x11=user
|
||||
// set ptr
|
||||
cmd[9] = 0x20; // epc data begins after 0x20
|
||||
// set mask length
|
||||
cmd[10] = mask_length_bits;
|
||||
// truncate
|
||||
cmd[11] = false;
|
||||
// set mask
|
||||
memcpy((void*)&cmd[12], uhf_tag->epc->data, mask_length_bytes);
|
||||
|
||||
// set checksum
|
||||
cmd[cmd_length - 2] = checksum(cmd + 1, 11 + mask_length_bytes);
|
||||
// end frame
|
||||
cmd[cmd_length - 1] = FRAME_END;
|
||||
|
||||
setup_and_send_rx(module, cmd, 12 + mask_length_bytes + 3);
|
||||
|
||||
uint8_t* data = buffer_get_data(module->buf);
|
||||
if(checksum(data + 1, 5) != data[6]) return M100ValidationFail; // error in rx
|
||||
if(data[5] != 0x00) return M100ValidationFail; // error if not 0
|
||||
|
||||
return M100SuccessResponse;
|
||||
}
|
||||
|
||||
UHFTag* m100_get_select_param(M100Module* module) {
|
||||
buffer_reset(module->buf);
|
||||
furi_hal_uart_set_irq_cb(FuriHalUartIdLPUART1, rx_callback, module->buf);
|
||||
furi_hal_uart_tx(
|
||||
FuriHalUartIdUSART1,
|
||||
(uint8_t*)&CMD_GET_SELECT_PARAMETER.cmd,
|
||||
CMD_GET_SELECT_PARAMETER.length);
|
||||
furi_delay_ms(DELAY_MS);
|
||||
// UHFTag* uhf_tag = uhf_tag_alloc();
|
||||
// uint8_t* data = buffer_get_data(module->buf);
|
||||
// size_t mask_length =
|
||||
// uhf_tag_set_epc(uhf_tag, data + 12, )
|
||||
// TODO : implement
|
||||
return NULL;
|
||||
}
|
||||
|
||||
M100ResponseType m100_read_label_data_storage(
|
||||
M100Module* module,
|
||||
UHFTag* uhf_tag,
|
||||
BankType bank,
|
||||
uint32_t access_pwd,
|
||||
uint16_t word_count) {
|
||||
/*
|
||||
Will probably remove UHFTag as param and get it from get selected tag
|
||||
*/
|
||||
if(bank == EPCBank) return M100SuccessResponse;
|
||||
uint8_t cmd[MAX_BUFFER_SIZE];
|
||||
size_t cmd_length = CMD_READ_LABEL_DATA_STORAGE_AREA.length;
|
||||
memcpy(cmd, CMD_READ_LABEL_DATA_STORAGE_AREA.cmd, cmd_length);
|
||||
// set access password
|
||||
cmd[5] = (access_pwd >> 24) & 0xFF;
|
||||
cmd[6] = (access_pwd >> 16) & 0xFF;
|
||||
cmd[7] = (access_pwd >> 8) & 0xFF;
|
||||
cmd[8] = access_pwd & 0xFF;
|
||||
// set mem bank
|
||||
cmd[9] = (uint8_t)bank;
|
||||
// set word counter
|
||||
cmd[12] = (word_count >> 8) & 0xFF;
|
||||
cmd[13] = word_count & 0xFF;
|
||||
// calc checksum
|
||||
cmd[cmd_length - 2] = checksum(cmd + 1, cmd_length - 3);
|
||||
|
||||
M100ResponseType rp_type = setup_and_send_rx(module, cmd, cmd_length);
|
||||
if(rp_type != M100SuccessResponse) return rp_type;
|
||||
|
||||
uint8_t* data = buffer_get_data(module->buf);
|
||||
|
||||
uint8_t rtn_command = data[2];
|
||||
uint16_t payload_len = data[3];
|
||||
payload_len = (payload_len << 8) + data[4];
|
||||
|
||||
if(rtn_command == 0xFF) {
|
||||
if(payload_len == 0x01) return M100NoTagResponse;
|
||||
return M100MemoryOverrun;
|
||||
}
|
||||
|
||||
size_t ptr_offset = 5 /*<-ptr offset*/ + uhf_tag_get_epc_size(uhf_tag) + 3 /*<-pc + ul*/;
|
||||
size_t bank_data_length = payload_len - (ptr_offset - 5 /*dont include the offset*/);
|
||||
|
||||
if(bank == TIDBank) {
|
||||
uhf_tag_set_tid(uhf_tag, data + ptr_offset, bank_data_length);
|
||||
} else if(bank == UserBank) {
|
||||
uhf_tag_set_user(uhf_tag, data + ptr_offset, bank_data_length);
|
||||
}
|
||||
|
||||
return M100SuccessResponse;
|
||||
}
|
||||
|
||||
M100ResponseType m100_write_label_data_storage(
|
||||
M100Module* module,
|
||||
UHFTag* saved_tag,
|
||||
UHFTag* selected_tag,
|
||||
BankType bank,
|
||||
uint16_t source_address,
|
||||
uint32_t access_pwd) {
|
||||
uint8_t cmd[MAX_BUFFER_SIZE];
|
||||
size_t cmd_length = CMD_WRITE_LABEL_DATA_STORE.length;
|
||||
memcpy(cmd, CMD_WRITE_LABEL_DATA_STORE.cmd, cmd_length);
|
||||
uint16_t payload_len = 9;
|
||||
uint16_t data_length = 0;
|
||||
if(bank == ReservedBank) {
|
||||
// access pwd len + kill pwd len
|
||||
payload_len += 4;
|
||||
data_length = 4;
|
||||
} else if(bank == EPCBank) {
|
||||
// epc len + pc len
|
||||
payload_len += 4 + uhf_tag_get_epc_size(saved_tag);
|
||||
data_length = 4 + uhf_tag_get_epc_size(saved_tag);
|
||||
// set data
|
||||
uint8_t tmp_arr[4];
|
||||
tmp_arr[0] = (uint8_t)((uhf_tag_get_epc_crc(selected_tag) >> 8) & 0xFF);
|
||||
tmp_arr[1] = (uint8_t)(uhf_tag_get_epc_crc(selected_tag) & 0xFF);
|
||||
tmp_arr[2] = (uint8_t)((uhf_tag_get_epc_pc(saved_tag) >> 8) & 0xFF);
|
||||
tmp_arr[3] = (uint8_t)(uhf_tag_get_epc_pc(saved_tag) & 0xFF);
|
||||
memcpy(cmd + 14, tmp_arr, 4);
|
||||
memcpy(cmd + 18, uhf_tag_get_epc(saved_tag), uhf_tag_get_epc_size(saved_tag));
|
||||
} else if(bank == UserBank) {
|
||||
payload_len += uhf_tag_get_user_size(saved_tag);
|
||||
data_length = uhf_tag_get_user_size(saved_tag);
|
||||
// set data
|
||||
memcpy(cmd + 14, uhf_tag_get_user(saved_tag), uhf_tag_get_user_size(saved_tag));
|
||||
}
|
||||
// set payload length
|
||||
cmd[3] = (payload_len >> 8) & 0xFF;
|
||||
cmd[4] = payload_len & 0xFF;
|
||||
// set access password
|
||||
cmd[5] = (access_pwd >> 24) & 0xFF;
|
||||
cmd[6] = (access_pwd >> 16) & 0xFF;
|
||||
cmd[7] = (access_pwd >> 8) & 0xFF;
|
||||
cmd[8] = access_pwd & 0xFF;
|
||||
// set membank
|
||||
cmd[9] = (uint8_t)bank;
|
||||
// set source address
|
||||
cmd[10] = (source_address >> 8) & 0xFF;
|
||||
cmd[11] = source_address & 0xFF;
|
||||
// set data length
|
||||
size_t data_length_words = data_length / 2;
|
||||
cmd[12] = (data_length_words >> 8) & 0xFF;
|
||||
cmd[13] = data_length_words & 0xFF;
|
||||
// update cmd len
|
||||
cmd_length = 7 + payload_len;
|
||||
// calculate checksum
|
||||
cmd[cmd_length - 2] = checksum(cmd + 1, cmd_length - 3);
|
||||
cmd[cmd_length - 1] = FRAME_END;
|
||||
// send cmd
|
||||
// furi_hal_uart_set_irq_cb(FuriHalUartIdUSART1, rx_callback, module->buf);
|
||||
// furi_hal_uart_tx(FuriHalUartIdUSART1, cmd, cmd_length);
|
||||
// unsigned int delay = DELAY_MS / 2;
|
||||
// unsigned int timeout = 15;
|
||||
// while(!buffer_get_size(module->buf)) {
|
||||
// furi_delay_ms(delay);
|
||||
// if(!timeout--) break;
|
||||
// }
|
||||
setup_and_send_rx(module, cmd, cmd_length);
|
||||
uint8_t* buff_data = buffer_get_data(module->buf);
|
||||
size_t buff_length = buffer_get_size(module->buf);
|
||||
if(buff_data[2] == 0xFF && buff_length == 8)
|
||||
return M100NoTagResponse;
|
||||
else if(buff_data[2] == 0xFF)
|
||||
return M100ValidationFail;
|
||||
return M100SuccessResponse;
|
||||
}
|
||||
void m100_set_baudrate(M100Module* module, uint32_t baudrate) {
|
||||
size_t length = CMD_SET_COMMUNICATION_BAUD_RATE.length;
|
||||
uint8_t cmd[length];
|
||||
memcpy(cmd, CMD_SET_COMMUNICATION_BAUD_RATE.cmd, length);
|
||||
uint16_t br_mod = baudrate / 100; // module format
|
||||
cmd[6] = 0xFF & br_mod; // pow LSB
|
||||
cmd[5] = 0xFF & (br_mod >> 8); // pow MSB
|
||||
cmd[length - 2] = checksum(cmd + 1, length - 3);
|
||||
furi_hal_uart_tx(FuriHalUartIdUSART1, cmd, length);
|
||||
furi_hal_uart_set_br(FuriHalUartIdUSART1, baudrate);
|
||||
module->baudrate = baudrate;
|
||||
}
|
||||
|
||||
bool m100_set_working_region(M100Module* module, WorkingRegion region) {
|
||||
size_t length = CMD_SET_WORK_AREA.length;
|
||||
uint8_t cmd[length];
|
||||
memcpy(cmd, CMD_SET_WORK_AREA.cmd, length);
|
||||
cmd[5] = (uint8_t)region;
|
||||
cmd[length - 2] = checksum(cmd + 1, length - 3);
|
||||
setup_and_send_rx(module, cmd, length);
|
||||
module->region = region;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool m100_set_transmitting_power(M100Module* module, uint16_t power) {
|
||||
size_t length = CMD_SET_TRANSMITTING_POWER.length;
|
||||
uint8_t cmd[length];
|
||||
memcpy(cmd, CMD_SET_TRANSMITTING_POWER.cmd, length);
|
||||
cmd[5] = (power >> 8) & 0xFF;
|
||||
cmd[6] = power & 0xFF;
|
||||
cmd[length - 2] = checksum(cmd + 1, length - 3);
|
||||
setup_and_send_rx(module, cmd, length);
|
||||
module->transmitting_power = power;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool m100_set_freq_hopping(M100Module* module, bool hopping) {
|
||||
UNUSED(module);
|
||||
UNUSED(hopping);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool m100_set_power(M100Module* module, uint8_t* power) {
|
||||
UNUSED(module);
|
||||
UNUSED(power);
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t m100_get_baudrate(M100Module* module) {
|
||||
return module->baudrate;
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <furi_hal.h>
|
||||
#include "uhf_tag.h"
|
||||
#include "uhf_buffer.h"
|
||||
#include "uhf_tag.h"
|
||||
#include <furi_hal.h>
|
||||
#include "uhf_module_settings.h"
|
||||
|
||||
#define FRAME_START 0xBB
|
||||
#define FRAME_END 0x7E
|
||||
#define DEFAULT_BAUDRATE BAUD_RATES[BAUD_RATES_COUNT - 1]
|
||||
#define DEFAULT_TRANSMITTING_POWER POWER_DBM[POWER_DBM_COUNT - 1]
|
||||
#define DEFAULT_WORKING_REGION WR_US
|
||||
|
||||
typedef struct {
|
||||
char* hw_version;
|
||||
char* sw_version;
|
||||
char* manufacturer;
|
||||
} M100ModuleInfo;
|
||||
|
||||
typedef enum {
|
||||
M100SuccessResponse,
|
||||
M100ValidationFail,
|
||||
M100NoTagResponse,
|
||||
M100MemoryOverrun,
|
||||
M100EmptyResponse,
|
||||
M100ChecksumFail
|
||||
} M100ResponseType;
|
||||
|
||||
typedef struct {
|
||||
M100ModuleInfo* info;
|
||||
uint32_t baudrate;
|
||||
WorkingRegion region;
|
||||
uint16_t region_frequency;
|
||||
uint16_t transmitting_power;
|
||||
bool freq_hopping;
|
||||
Buffer* buf;
|
||||
} M100Module;
|
||||
|
||||
M100ModuleInfo* m100_module_info_alloc();
|
||||
void m100_module_info_free(M100ModuleInfo* module_info);
|
||||
|
||||
M100Module* m100_module_alloc();
|
||||
void m100_module_free(M100Module* module);
|
||||
uint16_t crc16_genibus(const uint8_t* data, size_t length);
|
||||
uint8_t checksum(const uint8_t* data, size_t length);
|
||||
uint8_t get_baudrate_count();
|
||||
|
||||
// Function prototypes
|
||||
char* m100_get_hardware_version(M100Module* module);
|
||||
char* m100_get_software_version(M100Module* module);
|
||||
char* m100_get_manufacturers(M100Module* module);
|
||||
|
||||
void m100_set_baudrate(M100Module* module, uint32_t baudrate);
|
||||
bool m100_set_working_region(M100Module* module, WorkingRegion region);
|
||||
bool m100_set_transmitting_power(M100Module* module, uint16_t power);
|
||||
bool m100_set_freq_hopping(M100Module* module, bool hopping);
|
||||
bool m100_set_power(M100Module* module, uint8_t* power);
|
||||
|
||||
// gen2 cmds
|
||||
M100ResponseType m100_single_poll(M100Module* module, UHFTag* uhf_tag);
|
||||
M100ResponseType m100_set_select(M100Module* module, UHFTag* uhf_tag);
|
||||
M100ResponseType m100_read_label_data_storage(
|
||||
M100Module* module,
|
||||
UHFTag* uhf_tag,
|
||||
BankType bank,
|
||||
uint32_t access_pwd,
|
||||
uint16_t word_count);
|
||||
|
||||
M100ResponseType m100_write_label_data_storage(
|
||||
M100Module* module,
|
||||
UHFTag* saved_tag,
|
||||
UHFTag* selected_tag,
|
||||
BankType bank,
|
||||
uint16_t source_address,
|
||||
uint32_t access_pwd);
|
||||
|
||||
uint32_t m100_get_baudrate(M100Module* module);
|
||||
@@ -1,89 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
typedef struct {
|
||||
const uint8_t* cmd;
|
||||
size_t length;
|
||||
} Command;
|
||||
|
||||
// Define the command data arrays
|
||||
static const uint8_t CMD_HW_VERSION_DATA[] = {0xBB, 0x00, 0x03, 0x00, 0x01, 0x00, 0x04, 0x7E};
|
||||
static const uint8_t CMD_SW_VERSION_DATA[] = {0xBB, 0x00, 0x03, 0x00, 0x01, 0x01, 0x05, 0x7E};
|
||||
static const uint8_t CMD_MANUFACTURERS_DATA[] = {0xBB, 0x00, 0x03, 0x00, 0x01, 0x02, 0x06, 0x7E};
|
||||
static const uint8_t CMD_SINGLE_POLLING_DATA[] = {0xBB, 0x00, 0x22, 0x00, 0x00, 0x22, 0x7E};
|
||||
static const uint8_t CMD_MULTIPLE_POLLING_DATA[] =
|
||||
{0xBB, 0x00, 0x27, 0x00, 0x03, 0x22, 0x27, 0x10, 0x83, 0x7E};
|
||||
static const uint8_t CMD_STOP_MULTIPLE_POLLING_DATA[] = {0xBB, 0x00, 0x28, 0x00, 0x00, 0x28, 0x7E};
|
||||
static const uint8_t CMD_SET_SELECT_PARAMETER_DATA[] = {0xBB, 0x00, 0x0C, 0x00, 0x13, 0x01, 0x00,
|
||||
0x00, 0x00, 0x20, 0x60, 0x00, 0x30, 0x75,
|
||||
0x1F, 0xEB, 0x70, 0x5C, 0x59, 0x04, 0xE3,
|
||||
0xD5, 0x0D, 0x70, 0xAD, 0x7E};
|
||||
static const uint8_t CMD_GET_SELECT_PARAMETER_DATA[] = {0xBB, 0x00, 0x0B, 0x00, 0x00, 0x0B, 0x7E};
|
||||
static const uint8_t CMD_SET_SELECT_MODE_DATA[] = {0xBB, 0x00, 0x12, 0x00, 0x01, 0x01, 0x14, 0x7E};
|
||||
static const uint8_t CMD_READ_LABEL_DATA_STORAGE_AREA_DATA[] =
|
||||
{0xBB, 0x00, 0x39, 0x00, 0x09, 0x00, 0x00, 0xFF, 0xFF, 0x03, 0x00, 0x00, 0x00, 0x02, 0x45, 0x7E};
|
||||
static const uint8_t CMD_WRITE_LABEL_DATA_STORE_DATA[] = {0xBB, 0x00, 0x49, 0x00, 0x0D, 0x00, 0x00,
|
||||
0xFF, 0xFF, 0x03, 0x00, 0x00, 0x00, 0x02,
|
||||
0x12, 0x34, 0x56, 0x78, 0x6D, 0x7E};
|
||||
static const uint8_t CMD_LOCK_LABEL_DATA_STORE_DATA[] =
|
||||
{0xBB, 0x00, 0x82, 0x00, 0x07, 0x00, 0x00, 0xFF, 0xFF, 0x02, 0x00, 0x80, 0x09, 0x7E};
|
||||
static const uint8_t CMD_INACTIVATE_KILL_TAG_DATA[] =
|
||||
{0xBB, 0x00, 0x65, 0x00, 0x04, 0x00, 0x00, 0xFF, 0xFF, 0x67, 0x7E};
|
||||
static const uint8_t CMD_SET_COMMUNICATION_BAUD_RATE_DATA[] =
|
||||
{0xBB, 0x00, 0x11, 0x00, 0x02, 0x00, 0xC0, 0xD3, 0x7E};
|
||||
static const uint8_t CMD_GET_QUERY_PARAMETERS_DATA[] = {0xBB, 0x00, 0x0D, 0x00, 0x00, 0x0D, 0x7E};
|
||||
static const uint8_t CMD_SET_QUERY_PARAMETER_DATA[] =
|
||||
{0xBB, 0x00, 0x0E, 0x00, 0x02, 0x10, 0x20, 0x40, 0x7E};
|
||||
static const uint8_t CMD_SET_WORK_AREA_DATA[] = {0xBB, 0x00, 0x07, 0x00, 0x01, 0x01, 0x09, 0x7E};
|
||||
static const uint8_t CMD_SET_TRANSMITTING_POWER_DATA[] =
|
||||
{0xBB, 0x00, 0xB6, 0x00, 0x02, 0x07, 0xD0, 0x8F, 0x7E};
|
||||
|
||||
// Define the Command structs
|
||||
static const Command CMD_HW_VERSION = {CMD_HW_VERSION_DATA, sizeof(CMD_HW_VERSION_DATA)};
|
||||
static const Command CMD_SW_VERSION = {CMD_SW_VERSION_DATA, sizeof(CMD_SW_VERSION_DATA)};
|
||||
static const Command CMD_MANUFACTURERS = {CMD_MANUFACTURERS_DATA, sizeof(CMD_MANUFACTURERS_DATA)};
|
||||
static const Command CMD_SINGLE_POLLING = {
|
||||
CMD_SINGLE_POLLING_DATA,
|
||||
sizeof(CMD_SINGLE_POLLING_DATA)};
|
||||
static const Command CMD_MULTIPLE_POLLING = {
|
||||
CMD_MULTIPLE_POLLING_DATA,
|
||||
sizeof(CMD_MULTIPLE_POLLING_DATA)};
|
||||
static const Command CMD_STOP_MULTIPLE_POLLING = {
|
||||
CMD_STOP_MULTIPLE_POLLING_DATA,
|
||||
sizeof(CMD_STOP_MULTIPLE_POLLING_DATA)};
|
||||
static const Command CMD_SET_SELECT_PARAMETER = {
|
||||
CMD_SET_SELECT_PARAMETER_DATA,
|
||||
sizeof(CMD_SET_SELECT_PARAMETER_DATA)};
|
||||
static const Command CMD_GET_SELECT_PARAMETER = {
|
||||
CMD_GET_SELECT_PARAMETER_DATA,
|
||||
sizeof(CMD_GET_SELECT_PARAMETER_DATA)};
|
||||
static const Command CMD_SET_SELECT_MODE = {
|
||||
CMD_SET_SELECT_MODE_DATA,
|
||||
sizeof(CMD_SET_SELECT_MODE_DATA)};
|
||||
static const Command CMD_READ_LABEL_DATA_STORAGE_AREA = {
|
||||
CMD_READ_LABEL_DATA_STORAGE_AREA_DATA,
|
||||
sizeof(CMD_READ_LABEL_DATA_STORAGE_AREA_DATA)};
|
||||
static const Command CMD_WRITE_LABEL_DATA_STORE = {
|
||||
CMD_WRITE_LABEL_DATA_STORE_DATA,
|
||||
sizeof(CMD_WRITE_LABEL_DATA_STORE_DATA)};
|
||||
static const Command CMD_LOCK_LABEL_DATA_STORE = {
|
||||
CMD_LOCK_LABEL_DATA_STORE_DATA,
|
||||
sizeof(CMD_LOCK_LABEL_DATA_STORE_DATA)};
|
||||
static const Command CMD_INACTIVATE_KILL_TAG = {
|
||||
CMD_INACTIVATE_KILL_TAG_DATA,
|
||||
sizeof(CMD_INACTIVATE_KILL_TAG_DATA)};
|
||||
static const Command CMD_SET_COMMUNICATION_BAUD_RATE = {
|
||||
CMD_SET_COMMUNICATION_BAUD_RATE_DATA,
|
||||
sizeof(CMD_SET_COMMUNICATION_BAUD_RATE_DATA)};
|
||||
static const Command CMD_GET_QUERY_PARAMETERS = {
|
||||
CMD_GET_QUERY_PARAMETERS_DATA,
|
||||
sizeof(CMD_GET_QUERY_PARAMETERS_DATA)};
|
||||
static const Command CMD_SET_QUERY_PARAMETER = {
|
||||
CMD_SET_QUERY_PARAMETER_DATA,
|
||||
sizeof(CMD_SET_QUERY_PARAMETER_DATA)};
|
||||
static const Command CMD_SET_WORK_AREA = {CMD_SET_WORK_AREA_DATA, sizeof(CMD_SET_WORK_AREA_DATA)};
|
||||
static const Command CMD_SET_TRANSMITTING_POWER = {
|
||||
CMD_SET_TRANSMITTING_POWER_DATA,
|
||||
sizeof(CMD_SET_TRANSMITTING_POWER_DATA)};
|
||||
@@ -1,29 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// UHF module regions
|
||||
typedef enum {
|
||||
WR_CHINA_900 = 1, // Freq_CH-920.125M
|
||||
WR_US, // Freq_CH-902.25M
|
||||
WR_EU, // Freq_CH-865.1M
|
||||
WR_CHINA_800, // Freq_CH-840.125M
|
||||
WR_KOREA = 6 // Freq_CH-917.1M
|
||||
} WorkingRegion;
|
||||
|
||||
// UHF module baudrates
|
||||
static const uint32_t BAUD_RATES[] = {9600, 19200, 115200};
|
||||
static const uint8_t BAUD_RATES_COUNT = sizeof(BAUD_RATES) / sizeof(BAUD_RATES[0]);
|
||||
// RF Power Setting
|
||||
static const uint8_t POWER_DBM[] = {12, 14, 17, 20}; // To be determined ...
|
||||
static const uint8_t POWER_DBM_COUNT = sizeof(POWER_DBM) / sizeof(POWER_DBM[0]);
|
||||
// UHF WorkingArea
|
||||
static const char* WORKING_REGIONS_STR[] = {"CN1", "US", "EU", "CN2", "KR"};
|
||||
static const uint8_t __working_region_str =
|
||||
sizeof(WORKING_REGIONS_STR) / sizeof(WORKING_REGIONS_STR[0]);
|
||||
static const WorkingRegion WORKING_REGIONS[] = {WR_CHINA_900, WR_US, WR_EU, WR_CHINA_800, WR_KOREA};
|
||||
static const uint8_t WORKING_REGIONS_COUNT = sizeof(WORKING_REGIONS) / sizeof(WORKING_REGIONS[0]);
|
||||
// UHF WorkingChannel
|
||||
// static const string WORKING_CHANNELS_STR[] = {"China 900MHz", "US", "EU", "China 800MHz", "Korea"};
|
||||
// static const WorkingChannel WORKING_CHANNELS[] = {WC_CHINA_900, WC_US, WC_EU, WC_CHINA_800, WC_KOREA};
|
||||
// static const uint8_t WORKING_CHANNELS_COUNT = sizeof(WORKING_CHANNELS) / sizeof(WORKING_CHANNELS[0]);
|
||||
@@ -1,116 +0,0 @@
|
||||
#include "uhf_tag.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
UHFTagWrapper* uhf_tag_wrapper_alloc() {
|
||||
UHFTagWrapper* uhf_tag_wrapper = (UHFTagWrapper*)malloc(sizeof(UHFTagWrapper));
|
||||
uhf_tag_wrapper->uhf_tag = NULL;
|
||||
return uhf_tag_wrapper;
|
||||
}
|
||||
|
||||
void uhf_tag_wrapper_set_tag(UHFTagWrapper* uhf_tag_wrapper, UHFTag* uhf_tag) {
|
||||
if(uhf_tag_wrapper->uhf_tag != NULL) {
|
||||
uhf_tag_free(uhf_tag_wrapper->uhf_tag);
|
||||
}
|
||||
uhf_tag_wrapper->uhf_tag = uhf_tag;
|
||||
}
|
||||
|
||||
void uhf_tag_wrapper_free(UHFTagWrapper* uhf_tag_wrapper) {
|
||||
uhf_tag_free(uhf_tag_wrapper->uhf_tag);
|
||||
free(uhf_tag_wrapper);
|
||||
}
|
||||
|
||||
UHFTag* uhf_tag_alloc() {
|
||||
UHFTag* uhf_tag = (UHFTag*)malloc(sizeof(UHFTag));
|
||||
uhf_tag->reserved = (ReservedMemoryBank*)malloc(sizeof(ReservedMemoryBank));
|
||||
uhf_tag->epc = (EPCMemoryBank*)malloc(sizeof(EPCMemoryBank));
|
||||
uhf_tag->tid = (TIDMemoryBank*)malloc(sizeof(TIDMemoryBank));
|
||||
uhf_tag->user = (UserMemoryBank*)malloc(sizeof(UserMemoryBank));
|
||||
return uhf_tag;
|
||||
}
|
||||
|
||||
void uhf_tag_reset(UHFTag* uhf_tag) {
|
||||
uhf_tag->epc->crc = 0;
|
||||
uhf_tag->epc->pc = 0;
|
||||
uhf_tag->epc->size = 0;
|
||||
uhf_tag->tid->size = 0;
|
||||
uhf_tag->user->size = 0;
|
||||
}
|
||||
|
||||
void uhf_tag_free(UHFTag* uhf_tag) {
|
||||
if(uhf_tag == NULL) return;
|
||||
free(uhf_tag->reserved);
|
||||
free(uhf_tag->epc);
|
||||
free(uhf_tag->tid);
|
||||
free(uhf_tag->user);
|
||||
free(uhf_tag);
|
||||
}
|
||||
|
||||
void uhf_tag_set_epc_pc(UHFTag* uhf_tag, uint16_t pc) {
|
||||
uhf_tag->epc->pc = pc;
|
||||
}
|
||||
|
||||
void uhf_tag_set_epc_crc(UHFTag* uhf_tag, uint16_t crc) {
|
||||
uhf_tag->epc->crc = crc;
|
||||
}
|
||||
|
||||
void uhf_tag_set_epc(UHFTag* uhf_tag, uint8_t* data_in, size_t size) {
|
||||
memcpy(uhf_tag->epc->data, data_in, size);
|
||||
uhf_tag->epc->size = size;
|
||||
}
|
||||
|
||||
void uhf_tag_set_epc_size(UHFTag* uhf_tag, size_t size) {
|
||||
uhf_tag->epc->size = size;
|
||||
}
|
||||
|
||||
void uhf_tag_set_tid(UHFTag* uhf_tag, uint8_t* data_in, size_t size) {
|
||||
memcpy(uhf_tag->tid->data, data_in, size);
|
||||
uhf_tag->tid->size = size;
|
||||
}
|
||||
|
||||
void uhf_tag_set_tid_size(UHFTag* uhf_tag, size_t size) {
|
||||
uhf_tag->tid->size = size;
|
||||
}
|
||||
|
||||
void uhf_tag_set_user(UHFTag* uhf_tag, uint8_t* data_in, size_t size) {
|
||||
memcpy(uhf_tag->user->data, data_in, size);
|
||||
uhf_tag->user->size = size;
|
||||
}
|
||||
|
||||
void uhf_tag_set_user_size(UHFTag* uhf_tag, size_t size) {
|
||||
uhf_tag->user->size = size;
|
||||
}
|
||||
|
||||
// getters
|
||||
|
||||
uint8_t* uhf_tag_get_epc(UHFTag* uhf_tag) {
|
||||
return uhf_tag->epc->data;
|
||||
}
|
||||
|
||||
size_t uhf_tag_get_epc_size(UHFTag* uhf_tag) {
|
||||
return uhf_tag->epc->size;
|
||||
}
|
||||
|
||||
uint16_t uhf_tag_get_epc_pc(UHFTag* uhf_tag) {
|
||||
return uhf_tag->epc->pc;
|
||||
}
|
||||
|
||||
uint16_t uhf_tag_get_epc_crc(UHFTag* uhf_tag) {
|
||||
return uhf_tag->epc->crc;
|
||||
}
|
||||
|
||||
uint8_t* uhf_tag_get_tid(UHFTag* uhf_tag) {
|
||||
return uhf_tag->tid->data;
|
||||
}
|
||||
|
||||
size_t uhf_tag_get_tid_size(UHFTag* uhf_tag) {
|
||||
return uhf_tag->tid->size;
|
||||
}
|
||||
|
||||
uint8_t* uhf_tag_get_user(UHFTag* uhf_tag) {
|
||||
return uhf_tag->user->data;
|
||||
}
|
||||
|
||||
size_t uhf_tag_get_user_size(UHFTag* uhf_tag) {
|
||||
return uhf_tag->user->size;
|
||||
}
|
||||