[FL-3097] fbt, faploader: minimal app module implementation (#2420)

* fbt, faploader: minimal app module implementation
* faploader, libs: moved API hashtable core to flipper_application
* example: compound api
* lib: flipper_application: naming fixes, doxygen comments
* fbt: changed `requires` manifest field behavior for app extensions
* examples: refactored plugin apps; faploader: changed new API naming; fbt: changed PLUGIN app type meaning
* loader: dropped support for debug apps & plugin menus
* moved applications/plugins -> applications/external
* Restored x bit on chiplist_convert.py
* git: fixed free-dap submodule path
* pvs: updated submodule paths
* examples: example_advanced_plugins.c: removed potential memory leak on errors
* examples: example_plugins: refined requires
* fbt: not deploying app modules for debug/sample apps; extra validation for .PLUGIN-type apps
* apps: removed cdefines for external apps
* fbt: moved ext app path definition
* fbt: reworked fap_dist handling; f18: synced api_symbols.csv
* fbt: removed resources_paths for extapps
* scripts: reworked storage
* scripts: reworked runfap.py & selfupdate.py to use new api
* wip: fal runner
* fbt: moved file packaging into separate module
* scripts: storage: fixes
* scripts: storage: minor fixes for new api
* fbt: changed internal artifact storage details for external apps
* scripts: storage: additional fixes and better error reporting; examples: using APP_DATA_PATH()
* fbt, scripts: reworked launch_app to deploy plugins; moved old runfap.py to distfap.py
* fbt: extra check for plugins descriptors
* fbt: additional checks in emitter
* fbt: better info message on SDK rebuild
* scripts: removed requirements.txt
* loader: removed remnants of plugins & debug menus
* post-review fixes
This commit is contained in:
hedger
2023-03-14 18:29:28 +04:00
committed by GitHub
parent 4bd3dca16f
commit 53435579b3
376 changed files with 2041 additions and 1036 deletions

6
applications/external/application.fam vendored Normal file
View File

@@ -0,0 +1,6 @@
# Placeholder
App(
appid="external_apps",
name="External apps bundle",
apptype=FlipperAppType.METAPACKAGE,
)

View File

@@ -0,0 +1,10 @@
App(
appid="clock",
name="Clock",
apptype=FlipperAppType.EXTERNAL,
entry_point="clock_app",
requires=["gui"],
stack_size=2 * 1024,
fap_icon="clock.png",
fap_category="Tools",
)

BIN
applications/external/clock/clock.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

136
applications/external/clock/clock_app.c vendored Normal file
View File

@@ -0,0 +1,136 @@
#include <furi.h>
#include <furi_hal.h>
#include <gui/gui.h>
#include <locale/locale.h>
typedef enum {
ClockEventTypeTick,
ClockEventTypeKey,
} ClockEventType;
typedef struct {
ClockEventType type;
InputEvent input;
} ClockEvent;
typedef struct {
FuriString* buffer;
FuriHalRtcDateTime datetime;
LocaleTimeFormat timeformat;
LocaleDateFormat dateformat;
} ClockData;
typedef struct {
FuriMutex* mutex;
FuriMessageQueue* queue;
ClockData* data;
} Clock;
static void clock_input_callback(InputEvent* input_event, FuriMessageQueue* queue) {
furi_assert(queue);
ClockEvent event = {.type = ClockEventTypeKey, .input = *input_event};
furi_message_queue_put(queue, &event, FuriWaitForever);
}
static void clock_render_callback(Canvas* canvas, void* ctx) {
Clock* clock = ctx;
if(furi_mutex_acquire(clock->mutex, 200) != FuriStatusOk) {
return;
}
ClockData* data = clock->data;
canvas_set_font(canvas, FontBigNumbers);
locale_format_time(data->buffer, &data->datetime, data->timeformat, true);
canvas_draw_str_aligned(
canvas, 64, 28, AlignCenter, AlignCenter, furi_string_get_cstr(data->buffer));
// Special case to cover missing glyphs in FontBigNumbers
if(data->timeformat == LocaleTimeFormat12h) {
size_t time_width = canvas_string_width(canvas, furi_string_get_cstr(data->buffer));
canvas_set_font(canvas, FontPrimary);
canvas_draw_str_aligned(
canvas,
64 + (time_width / 2) - 10,
31,
AlignLeft,
AlignCenter,
(data->datetime.hour > 11) ? "PM" : "AM");
}
canvas_set_font(canvas, FontSecondary);
locale_format_date(data->buffer, &data->datetime, data->dateformat, "/");
canvas_draw_str_aligned(
canvas, 64, 42, AlignCenter, AlignTop, furi_string_get_cstr(data->buffer));
furi_mutex_release(clock->mutex);
}
static void clock_tick(void* ctx) {
furi_assert(ctx);
FuriMessageQueue* queue = ctx;
ClockEvent event = {.type = ClockEventTypeTick};
// It's OK to loose this event if system overloaded
furi_message_queue_put(queue, &event, 0);
}
int32_t clock_app(void* p) {
UNUSED(p);
Clock* clock = malloc(sizeof(Clock));
clock->data = malloc(sizeof(ClockData));
clock->data->buffer = furi_string_alloc();
clock->queue = furi_message_queue_alloc(8, sizeof(ClockEvent));
clock->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
furi_hal_rtc_get_datetime(&clock->data->datetime);
clock->data->timeformat = locale_get_time_format();
clock->data->dateformat = locale_get_date_format();
// Set ViewPort callbacks
ViewPort* view_port = view_port_alloc();
view_port_draw_callback_set(view_port, clock_render_callback, clock);
view_port_input_callback_set(view_port, clock_input_callback, clock->queue);
FuriTimer* timer = furi_timer_alloc(clock_tick, FuriTimerTypePeriodic, clock->queue);
// Open GUI and register view_port
Gui* gui = furi_record_open(RECORD_GUI);
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
furi_timer_start(timer, 100);
// Main loop
ClockEvent event;
for(bool processing = true; processing;) {
furi_check(furi_message_queue_get(clock->queue, &event, FuriWaitForever) == FuriStatusOk);
furi_mutex_acquire(clock->mutex, FuriWaitForever);
if(event.type == ClockEventTypeKey) {
if(event.input.type == InputTypeShort && event.input.key == InputKeyBack) {
processing = false;
}
} else if(event.type == ClockEventTypeTick) {
furi_hal_rtc_get_datetime(&clock->data->datetime);
}
furi_mutex_release(clock->mutex);
view_port_update(view_port);
}
furi_timer_free(timer);
view_port_enabled_set(view_port, false);
gui_remove_view_port(gui, view_port);
view_port_free(view_port);
furi_record_close(RECORD_GUI);
furi_message_queue_free(clock->queue);
furi_mutex_free(clock->mutex);
furi_string_free(clock->data->buffer);
free(clock->data);
free(clock);
return 0;
}

105
applications/external/dap_link/README.md vendored Normal file
View File

@@ -0,0 +1,105 @@
# Flipper Zero as CMSIS DAP/DAP Link
Flipper Zero as a [Free-DAP](https://github.com/ataradov/free-dap) based SWD\JTAG debugger. Free-DAP is a free and open source firmware implementation of the [CMSIS-DAP](https://www.keil.com/pack/doc/CMSIS_Dev/DAP/html/index.html) debugger.
## Protocols
SWD, JTAG , CMSIS-DAP v1 (18 KiB/s), CMSIS-DAP v2 (46 KiB/s), VCP (USB-UART).
WinUSB for driverless installation for Windows 8 and above.
## Usage
### VSCode + Cortex-Debug
Set `"device": "cmsis-dap"`
<details>
<summary>BluePill configuration example</summary>
```json
{
"name": "Attach (DAP)",
"cwd": "${workspaceFolder}",
"executable": "./build/firmware.elf",
"request": "attach",
"type": "cortex-debug",
"servertype": "openocd",
"device": "cmsis-dap",
"configFiles": [
"interface/cmsis-dap.cfg",
"target/stm32f1x.cfg",
],
},
```
</details>
<details>
<summary>Flipper Zero configuration example</summary>
```json
{
"name": "Attach (DAP)",
"cwd": "${workspaceFolder}",
"executable": "./build/latest/firmware.elf",
"request": "attach",
"type": "cortex-debug",
"servertype": "openocd",
"device": "cmsis-dap",
"svdFile": "./debug/STM32WB55_CM4.svd",
"rtos": "FreeRTOS",
"configFiles": [
"interface/cmsis-dap.cfg",
"./debug/stm32wbx.cfg",
],
"postAttachCommands": [
"source debug/flipperapps.py",
],
},
```
</details>
### OpenOCD
Use `interface/cmsis-dap.cfg`. You will need OpenOCD v0.11.0.
Additional commands:
* `cmsis_dap_backend hid` for CMSIS-DAP v1 protocol.
* `cmsis_dap_backend usb_bulk` for CMSIS-DAP v2 protocol.
* `cmsis_dap_serial DAP_Oyevoxo` use DAP-Link running on Flipper named `Oyevoxo`.
* `cmsis-dap cmd 81` - reboot connected DAP-Link.
<details>
<summary>Flash BluePill</summary>
```
openocd -f interface/cmsis-dap.cfg -f target/stm32f1x.cfg -c init -c "program build/firmware.bin reset exit 0x8000000"
```
</details>
<details>
<summary>Flash Flipper Zero using DAP v2 protocol</summary>
```
openocd -f interface/cmsis-dap.cfg -c "cmsis_dap_backend usb_bulk" -f debug/stm32wbx.cfg -c init -c "program build/latest/firmware.bin reset exit 0x8000000"
```
</details>
<details>
<summary>Reboot connected DAP-Link on Flipper named Oyevoxo</summary>
```
openocd -f interface/cmsis-dap.cfg -c "cmsis_dap_serial DAP_Oyevoxo" -c "transport select swd" -c "adapter speed 4000000" -c init -c "cmsis-dap cmd 81" -c "exit"
```
</details>
### PlatformIO
Use `debug_tool = cmsis-dap` and `upload_protocol = cmsis-dap`. [Documentation](https://docs.platformio.org/en/latest/plus/debug-tools/cmsis-dap.html#debugging-tool-cmsis-dap). Remember that Windows 8 and above do not require drivers.
<details>
<summary>BluePill platformio.ini example</summary>
```
[env:bluepill_f103c8]
platform = ststm32
board = bluepill_f103c8
debug_tool = cmsis-dap
upload_protocol = cmsis-dap
```
</details>

View File

@@ -0,0 +1,24 @@
App(
appid="dap_link",
name="DAP Link",
apptype=FlipperAppType.EXTERNAL,
entry_point="dap_link_app",
requires=[
"gui",
"dialogs",
],
stack_size=4 * 1024,
order=20,
fap_icon="dap_link.png",
fap_category="GPIO",
fap_private_libs=[
Lib(
name="free-dap",
cincludes=["."],
sources=[
"dap.c",
],
),
],
fap_icon_assets="icons",
)

View File

@@ -0,0 +1,234 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2022, Alex Taradov <alex@taradov.com>. All rights reserved.
#ifndef _DAP_CONFIG_H_
#define _DAP_CONFIG_H_
/*- Includes ----------------------------------------------------------------*/
#include <furi_hal_gpio.h>
/*- Definitions -------------------------------------------------------------*/
#define DAP_CONFIG_ENABLE_JTAG
#define DAP_CONFIG_DEFAULT_PORT DAP_PORT_SWD
#define DAP_CONFIG_DEFAULT_CLOCK 4200000 // Hz
#define DAP_CONFIG_PACKET_SIZE 64
#define DAP_CONFIG_PACKET_COUNT 1
#define DAP_CONFIG_JTAG_DEV_COUNT 8
// DAP_CONFIG_PRODUCT_STR must contain "CMSIS-DAP" to be compatible with the standard
#define DAP_CONFIG_VENDOR_STR "Flipper Zero"
#define DAP_CONFIG_PRODUCT_STR "Generic CMSIS-DAP Adapter"
#define DAP_CONFIG_SER_NUM_STR usb_serial_number
#define DAP_CONFIG_CMSIS_DAP_VER_STR "2.0.0"
#define DAP_CONFIG_RESET_TARGET_FN dap_app_target_reset
#define DAP_CONFIG_VENDOR_FN dap_app_vendor_cmd
// Attribute to use for performance-critical functions
#define DAP_CONFIG_PERFORMANCE_ATTR
// A value at which dap_clock_test() produces 1 kHz output on the SWCLK pin
// #define DAP_CONFIG_DELAY_CONSTANT 19000
#define DAP_CONFIG_DELAY_CONSTANT 6290
// A threshold for switching to fast clock (no added delays)
// This is the frequency produced by dap_clock_test(1) on the SWCLK pin
#define DAP_CONFIG_FAST_CLOCK 2400000 // Hz
/*- Prototypes --------------------------------------------------------------*/
extern char usb_serial_number[16];
/*- Implementations ---------------------------------------------------------*/
extern GpioPin flipper_dap_swclk_pin;
extern GpioPin flipper_dap_swdio_pin;
extern GpioPin flipper_dap_reset_pin;
extern GpioPin flipper_dap_tdo_pin;
extern GpioPin flipper_dap_tdi_pin;
extern void dap_app_vendor_cmd(uint8_t cmd);
extern void dap_app_target_reset();
extern void dap_app_disconnect();
extern void dap_app_connect_swd();
extern void dap_app_connect_jtag();
//-----------------------------------------------------------------------------
static inline void DAP_CONFIG_SWCLK_TCK_write(int value) {
furi_hal_gpio_write(&flipper_dap_swclk_pin, value);
}
//-----------------------------------------------------------------------------
static inline void DAP_CONFIG_SWDIO_TMS_write(int value) {
furi_hal_gpio_write(&flipper_dap_swdio_pin, value);
}
//-----------------------------------------------------------------------------
static inline void DAP_CONFIG_TDI_write(int value) {
#ifdef DAP_CONFIG_ENABLE_JTAG
furi_hal_gpio_write(&flipper_dap_tdi_pin, value);
#else
(void)value;
#endif
}
//-----------------------------------------------------------------------------
static inline void DAP_CONFIG_TDO_write(int value) {
#ifdef DAP_CONFIG_ENABLE_JTAG
furi_hal_gpio_write(&flipper_dap_tdo_pin, value);
#else
(void)value;
#endif
}
//-----------------------------------------------------------------------------
static inline void DAP_CONFIG_nTRST_write(int value) {
(void)value;
}
//-----------------------------------------------------------------------------
static inline void DAP_CONFIG_nRESET_write(int value) {
furi_hal_gpio_write(&flipper_dap_reset_pin, value);
}
//-----------------------------------------------------------------------------
static inline int DAP_CONFIG_SWCLK_TCK_read(void) {
return furi_hal_gpio_read(&flipper_dap_swclk_pin);
}
//-----------------------------------------------------------------------------
static inline int DAP_CONFIG_SWDIO_TMS_read(void) {
return furi_hal_gpio_read(&flipper_dap_swdio_pin);
}
//-----------------------------------------------------------------------------
static inline int DAP_CONFIG_TDO_read(void) {
#ifdef DAP_CONFIG_ENABLE_JTAG
return furi_hal_gpio_read(&flipper_dap_tdo_pin);
#else
return 0;
#endif
}
//-----------------------------------------------------------------------------
static inline int DAP_CONFIG_TDI_read(void) {
#ifdef DAP_CONFIG_ENABLE_JTAG
return furi_hal_gpio_read(&flipper_dap_tdi_pin);
#else
return 0;
#endif
}
//-----------------------------------------------------------------------------
static inline int DAP_CONFIG_nTRST_read(void) {
return 0;
}
//-----------------------------------------------------------------------------
static inline int DAP_CONFIG_nRESET_read(void) {
return furi_hal_gpio_read(&flipper_dap_reset_pin);
}
//-----------------------------------------------------------------------------
static inline void DAP_CONFIG_SWCLK_TCK_set(void) {
LL_GPIO_SetOutputPin(flipper_dap_swclk_pin.port, flipper_dap_swclk_pin.pin);
}
//-----------------------------------------------------------------------------
static inline void DAP_CONFIG_SWCLK_TCK_clr(void) {
LL_GPIO_ResetOutputPin(flipper_dap_swclk_pin.port, flipper_dap_swclk_pin.pin);
}
//-----------------------------------------------------------------------------
static inline void DAP_CONFIG_SWDIO_TMS_in(void) {
LL_GPIO_SetPinMode(flipper_dap_swdio_pin.port, flipper_dap_swdio_pin.pin, LL_GPIO_MODE_INPUT);
}
//-----------------------------------------------------------------------------
static inline void DAP_CONFIG_SWDIO_TMS_out(void) {
LL_GPIO_SetPinMode(flipper_dap_swdio_pin.port, flipper_dap_swdio_pin.pin, LL_GPIO_MODE_OUTPUT);
}
//-----------------------------------------------------------------------------
static inline void DAP_CONFIG_SETUP(void) {
furi_hal_gpio_init(&flipper_dap_swdio_pin, GpioModeInput, GpioPullNo, GpioSpeedVeryHigh);
furi_hal_gpio_init(&flipper_dap_swclk_pin, GpioModeInput, GpioPullNo, GpioSpeedVeryHigh);
furi_hal_gpio_init(&flipper_dap_reset_pin, GpioModeInput, GpioPullNo, GpioSpeedVeryHigh);
#ifdef DAP_CONFIG_ENABLE_JTAG
furi_hal_gpio_init(&flipper_dap_tdo_pin, GpioModeInput, GpioPullNo, GpioSpeedVeryHigh);
furi_hal_gpio_init(&flipper_dap_tdi_pin, GpioModeInput, GpioPullNo, GpioSpeedVeryHigh);
#endif
}
//-----------------------------------------------------------------------------
static inline void DAP_CONFIG_DISCONNECT(void) {
furi_hal_gpio_init(&flipper_dap_swdio_pin, GpioModeInput, GpioPullNo, GpioSpeedVeryHigh);
furi_hal_gpio_init(&flipper_dap_swclk_pin, GpioModeInput, GpioPullNo, GpioSpeedVeryHigh);
furi_hal_gpio_init(&flipper_dap_reset_pin, GpioModeInput, GpioPullNo, GpioSpeedVeryHigh);
#ifdef DAP_CONFIG_ENABLE_JTAG
furi_hal_gpio_init(&flipper_dap_tdo_pin, GpioModeInput, GpioPullNo, GpioSpeedVeryHigh);
furi_hal_gpio_init(&flipper_dap_tdi_pin, GpioModeInput, GpioPullNo, GpioSpeedVeryHigh);
#endif
dap_app_disconnect();
}
//-----------------------------------------------------------------------------
static inline void DAP_CONFIG_CONNECT_SWD(void) {
furi_hal_gpio_init(
&flipper_dap_swdio_pin, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
furi_hal_gpio_write(&flipper_dap_swdio_pin, true);
furi_hal_gpio_init(
&flipper_dap_swclk_pin, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
furi_hal_gpio_write(&flipper_dap_swclk_pin, true);
furi_hal_gpio_init(
&flipper_dap_reset_pin, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
furi_hal_gpio_write(&flipper_dap_reset_pin, true);
#ifdef DAP_CONFIG_ENABLE_JTAG
furi_hal_gpio_init(&flipper_dap_tdo_pin, GpioModeInput, GpioPullNo, GpioSpeedVeryHigh);
furi_hal_gpio_init(&flipper_dap_tdi_pin, GpioModeInput, GpioPullNo, GpioSpeedVeryHigh);
#endif
dap_app_connect_swd();
}
//-----------------------------------------------------------------------------
static inline void DAP_CONFIG_CONNECT_JTAG(void) {
furi_hal_gpio_init(
&flipper_dap_swdio_pin, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
furi_hal_gpio_write(&flipper_dap_swdio_pin, true);
furi_hal_gpio_init(
&flipper_dap_swclk_pin, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
furi_hal_gpio_write(&flipper_dap_swclk_pin, true);
furi_hal_gpio_init(
&flipper_dap_reset_pin, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
furi_hal_gpio_write(&flipper_dap_reset_pin, true);
#ifdef DAP_CONFIG_ENABLE_JTAG
furi_hal_gpio_init(&flipper_dap_tdo_pin, GpioModeInput, GpioPullNo, GpioSpeedVeryHigh);
furi_hal_gpio_init(
&flipper_dap_tdi_pin, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
furi_hal_gpio_write(&flipper_dap_tdi_pin, true);
#endif
dap_app_connect_jtag();
}
//-----------------------------------------------------------------------------
static inline void DAP_CONFIG_LED(int index, int state) {
(void)index;
(void)state;
}
//-----------------------------------------------------------------------------
__attribute__((always_inline)) static inline void DAP_CONFIG_DELAY(uint32_t cycles) {
asm volatile("1: subs %[cycles], %[cycles], #1 \n"
" bne 1b \n"
: [cycles] "+l"(cycles));
}
#endif // _DAP_CONFIG_H_

View File

@@ -0,0 +1,527 @@
#include <dap.h>
#include <furi.h>
#include <furi_hal_version.h>
#include <furi_hal_gpio.h>
#include <furi_hal_uart.h>
#include <furi_hal_console.h>
#include <furi_hal_resources.h>
#include <furi_hal_power.h>
#include <stm32wbxx_ll_usart.h>
#include <stm32wbxx_ll_lpuart.h>
#include "dap_link.h"
#include "dap_config.h"
#include "gui/dap_gui.h"
#include "usb/dap_v2_usb.h"
#include <dialogs/dialogs.h>
#include "dap_link_icons.h"
/***************************************************************************/
/****************************** DAP COMMON *********************************/
/***************************************************************************/
struct DapApp {
FuriThread* dap_thread;
FuriThread* cdc_thread;
FuriThread* gui_thread;
DapState state;
DapConfig config;
};
void dap_app_get_state(DapApp* app, DapState* state) {
*state = app->state;
}
#define DAP_PROCESS_THREAD_TICK 500
typedef enum {
DapThreadEventStop = (1 << 0),
} DapThreadEvent;
void dap_thread_send_stop(FuriThread* thread) {
furi_thread_flags_set(furi_thread_get_id(thread), DapThreadEventStop);
}
GpioPin flipper_dap_swclk_pin;
GpioPin flipper_dap_swdio_pin;
GpioPin flipper_dap_reset_pin;
GpioPin flipper_dap_tdo_pin;
GpioPin flipper_dap_tdi_pin;
/***************************************************************************/
/****************************** DAP PROCESS ********************************/
/***************************************************************************/
typedef struct {
uint8_t data[DAP_CONFIG_PACKET_SIZE];
uint8_t size;
} DapPacket;
typedef enum {
DAPThreadEventStop = DapThreadEventStop,
DAPThreadEventRxV1 = (1 << 1),
DAPThreadEventRxV2 = (1 << 2),
DAPThreadEventUSBConnect = (1 << 3),
DAPThreadEventUSBDisconnect = (1 << 4),
DAPThreadEventApplyConfig = (1 << 5),
DAPThreadEventAll = DAPThreadEventStop | DAPThreadEventRxV1 | DAPThreadEventRxV2 |
DAPThreadEventUSBConnect | DAPThreadEventUSBDisconnect |
DAPThreadEventApplyConfig,
} DAPThreadEvent;
#define USB_SERIAL_NUMBER_LEN 16
char usb_serial_number[USB_SERIAL_NUMBER_LEN] = {0};
const char* dap_app_get_serial(DapApp* app) {
UNUSED(app);
return usb_serial_number;
}
static void dap_app_rx1_callback(void* context) {
furi_assert(context);
FuriThreadId thread_id = (FuriThreadId)context;
furi_thread_flags_set(thread_id, DAPThreadEventRxV1);
}
static void dap_app_rx2_callback(void* context) {
furi_assert(context);
FuriThreadId thread_id = (FuriThreadId)context;
furi_thread_flags_set(thread_id, DAPThreadEventRxV2);
}
static void dap_app_usb_state_callback(bool state, void* context) {
furi_assert(context);
FuriThreadId thread_id = (FuriThreadId)context;
if(state) {
furi_thread_flags_set(thread_id, DAPThreadEventUSBConnect);
} else {
furi_thread_flags_set(thread_id, DAPThreadEventUSBDisconnect);
}
}
static void dap_app_process_v1() {
DapPacket tx_packet;
DapPacket rx_packet;
memset(&tx_packet, 0, sizeof(DapPacket));
rx_packet.size = dap_v1_usb_rx(rx_packet.data, DAP_CONFIG_PACKET_SIZE);
dap_process_request(rx_packet.data, rx_packet.size, tx_packet.data, DAP_CONFIG_PACKET_SIZE);
dap_v1_usb_tx(tx_packet.data, DAP_CONFIG_PACKET_SIZE);
}
static void dap_app_process_v2() {
DapPacket tx_packet;
DapPacket rx_packet;
memset(&tx_packet, 0, sizeof(DapPacket));
rx_packet.size = dap_v2_usb_rx(rx_packet.data, DAP_CONFIG_PACKET_SIZE);
size_t len = dap_process_request(
rx_packet.data, rx_packet.size, tx_packet.data, DAP_CONFIG_PACKET_SIZE);
dap_v2_usb_tx(tx_packet.data, len);
}
void dap_app_vendor_cmd(uint8_t cmd) {
// openocd -c "cmsis-dap cmd 81"
if(cmd == 0x01) {
furi_hal_power_reset();
}
}
void dap_app_target_reset() {
FURI_LOG_I("DAP", "Target reset");
}
static void dap_init_gpio(DapSwdPins swd_pins) {
switch(swd_pins) {
case DapSwdPinsPA7PA6:
flipper_dap_swclk_pin = gpio_ext_pa7;
flipper_dap_swdio_pin = gpio_ext_pa6;
break;
case DapSwdPinsPA14PA13:
flipper_dap_swclk_pin = (GpioPin){.port = GPIOA, .pin = LL_GPIO_PIN_14};
flipper_dap_swdio_pin = (GpioPin){.port = GPIOA, .pin = LL_GPIO_PIN_13};
break;
}
flipper_dap_reset_pin = gpio_ext_pa4;
flipper_dap_tdo_pin = gpio_ext_pb3;
flipper_dap_tdi_pin = gpio_ext_pb2;
}
static void dap_deinit_gpio(DapSwdPins swd_pins) {
// setup gpio pins to default state
furi_hal_gpio_init(&flipper_dap_reset_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
furi_hal_gpio_init(&flipper_dap_tdo_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
furi_hal_gpio_init(&flipper_dap_tdi_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
if(DapSwdPinsPA14PA13 == swd_pins) {
// PA14 and PA13 are used by SWD
furi_hal_gpio_init_ex(
&flipper_dap_swclk_pin,
GpioModeAltFunctionPushPull,
GpioPullDown,
GpioSpeedLow,
GpioAltFn0JTCK_SWCLK);
furi_hal_gpio_init_ex(
&flipper_dap_swdio_pin,
GpioModeAltFunctionPushPull,
GpioPullUp,
GpioSpeedVeryHigh,
GpioAltFn0JTMS_SWDIO);
} else {
furi_hal_gpio_init(&flipper_dap_swclk_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
furi_hal_gpio_init(&flipper_dap_swdio_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
}
}
static int32_t dap_process(void* p) {
DapApp* app = p;
DapState* dap_state = &(app->state);
// allocate resources
FuriHalUsbInterface* usb_config_prev;
app->config.swd_pins = DapSwdPinsPA7PA6;
DapSwdPins swd_pins_prev = app->config.swd_pins;
// init pins
dap_init_gpio(swd_pins_prev);
// init dap
dap_init();
// get name
const char* name = furi_hal_version_get_name_ptr();
if(!name) {
name = "Flipper";
}
snprintf(usb_serial_number, USB_SERIAL_NUMBER_LEN, "DAP_%s", name);
// init usb
usb_config_prev = furi_hal_usb_get_config();
dap_common_usb_alloc_name(usb_serial_number);
dap_common_usb_set_context(furi_thread_get_id(furi_thread_get_current()));
dap_v1_usb_set_rx_callback(dap_app_rx1_callback);
dap_v2_usb_set_rx_callback(dap_app_rx2_callback);
dap_common_usb_set_state_callback(dap_app_usb_state_callback);
furi_hal_usb_set_config(&dap_v2_usb_hid, NULL);
// work
uint32_t events;
while(1) {
events = furi_thread_flags_wait(DAPThreadEventAll, FuriFlagWaitAny, FuriWaitForever);
if(!(events & FuriFlagError)) {
if(events & DAPThreadEventRxV1) {
dap_app_process_v1();
dap_state->dap_counter++;
dap_state->dap_version = DapVersionV1;
}
if(events & DAPThreadEventRxV2) {
dap_app_process_v2();
dap_state->dap_counter++;
dap_state->dap_version = DapVersionV2;
}
if(events & DAPThreadEventUSBConnect) {
dap_state->usb_connected = true;
}
if(events & DAPThreadEventUSBDisconnect) {
dap_state->usb_connected = false;
dap_state->dap_version = DapVersionUnknown;
}
if(events & DAPThreadEventApplyConfig) {
if(swd_pins_prev != app->config.swd_pins) {
dap_deinit_gpio(swd_pins_prev);
swd_pins_prev = app->config.swd_pins;
dap_init_gpio(swd_pins_prev);
}
}
if(events & DAPThreadEventStop) {
break;
}
}
}
// deinit usb
furi_hal_usb_set_config(usb_config_prev, NULL);
dap_common_usb_free_name();
dap_deinit_gpio(swd_pins_prev);
return 0;
}
/***************************************************************************/
/****************************** CDC PROCESS ********************************/
/***************************************************************************/
typedef enum {
CDCThreadEventStop = DapThreadEventStop,
CDCThreadEventUARTRx = (1 << 1),
CDCThreadEventCDCRx = (1 << 2),
CDCThreadEventCDCConfig = (1 << 3),
CDCThreadEventApplyConfig = (1 << 4),
CDCThreadEventAll = CDCThreadEventStop | CDCThreadEventUARTRx | CDCThreadEventCDCRx |
CDCThreadEventCDCConfig | CDCThreadEventApplyConfig,
} CDCThreadEvent;
typedef struct {
FuriStreamBuffer* rx_stream;
FuriThreadId thread_id;
FuriHalUartId uart_id;
struct usb_cdc_line_coding line_coding;
} CDCProcess;
static void cdc_uart_irq_cb(UartIrqEvent ev, uint8_t data, void* ctx) {
CDCProcess* app = ctx;
if(ev == UartIrqEventRXNE) {
furi_stream_buffer_send(app->rx_stream, &data, 1, 0);
furi_thread_flags_set(app->thread_id, CDCThreadEventUARTRx);
}
}
static void cdc_usb_rx_callback(void* context) {
CDCProcess* app = context;
furi_thread_flags_set(app->thread_id, CDCThreadEventCDCRx);
}
static void cdc_usb_control_line_callback(uint8_t state, void* context) {
UNUSED(context);
UNUSED(state);
}
static void cdc_usb_config_callback(struct usb_cdc_line_coding* config, void* context) {
CDCProcess* app = context;
app->line_coding = *config;
furi_thread_flags_set(app->thread_id, CDCThreadEventCDCConfig);
}
static FuriHalUartId cdc_init_uart(
DapUartType type,
DapUartTXRX swap,
uint32_t baudrate,
void (*cb)(UartIrqEvent ev, uint8_t data, void* ctx),
void* ctx) {
FuriHalUartId uart_id = FuriHalUartIdUSART1;
if(baudrate == 0) baudrate = 115200;
switch(type) {
case DapUartTypeUSART1:
uart_id = FuriHalUartIdUSART1;
furi_hal_console_disable();
furi_hal_uart_deinit(uart_id);
if(swap == DapUartTXRXSwap) {
LL_USART_SetTXRXSwap(USART1, LL_USART_TXRX_SWAPPED);
} else {
LL_USART_SetTXRXSwap(USART1, LL_USART_TXRX_STANDARD);
}
furi_hal_uart_init(uart_id, baudrate);
furi_hal_uart_set_irq_cb(uart_id, cb, ctx);
break;
case DapUartTypeLPUART1:
uart_id = FuriHalUartIdLPUART1;
furi_hal_uart_deinit(uart_id);
if(swap == DapUartTXRXSwap) {
LL_LPUART_SetTXRXSwap(LPUART1, LL_LPUART_TXRX_SWAPPED);
} else {
LL_LPUART_SetTXRXSwap(LPUART1, LL_LPUART_TXRX_STANDARD);
}
furi_hal_uart_init(uart_id, baudrate);
furi_hal_uart_set_irq_cb(uart_id, cb, ctx);
break;
}
return uart_id;
}
static void cdc_deinit_uart(DapUartType type) {
switch(type) {
case DapUartTypeUSART1:
furi_hal_uart_deinit(FuriHalUartIdUSART1);
LL_USART_SetTXRXSwap(USART1, LL_USART_TXRX_STANDARD);
furi_hal_console_init();
break;
case DapUartTypeLPUART1:
furi_hal_uart_deinit(FuriHalUartIdLPUART1);
LL_LPUART_SetTXRXSwap(LPUART1, LL_LPUART_TXRX_STANDARD);
break;
}
}
static int32_t cdc_process(void* p) {
DapApp* dap_app = p;
DapState* dap_state = &(dap_app->state);
dap_app->config.uart_pins = DapUartTypeLPUART1;
dap_app->config.uart_swap = DapUartTXRXNormal;
DapUartType uart_pins_prev = dap_app->config.uart_pins;
DapUartTXRX uart_swap_prev = dap_app->config.uart_swap;
CDCProcess* app = malloc(sizeof(CDCProcess));
app->thread_id = furi_thread_get_id(furi_thread_get_current());
app->rx_stream = furi_stream_buffer_alloc(512, 1);
const uint8_t rx_buffer_size = 64;
uint8_t* rx_buffer = malloc(rx_buffer_size);
app->uart_id = cdc_init_uart(
uart_pins_prev, uart_swap_prev, dap_state->cdc_baudrate, cdc_uart_irq_cb, app);
dap_cdc_usb_set_context(app);
dap_cdc_usb_set_rx_callback(cdc_usb_rx_callback);
dap_cdc_usb_set_control_line_callback(cdc_usb_control_line_callback);
dap_cdc_usb_set_config_callback(cdc_usb_config_callback);
uint32_t events;
while(1) {
events = furi_thread_flags_wait(CDCThreadEventAll, FuriFlagWaitAny, FuriWaitForever);
if(!(events & FuriFlagError)) {
if(events & CDCThreadEventCDCConfig) {
if(dap_state->cdc_baudrate != app->line_coding.dwDTERate) {
dap_state->cdc_baudrate = app->line_coding.dwDTERate;
if(dap_state->cdc_baudrate > 0) {
furi_hal_uart_set_br(app->uart_id, dap_state->cdc_baudrate);
}
}
}
if(events & CDCThreadEventUARTRx) {
size_t len =
furi_stream_buffer_receive(app->rx_stream, rx_buffer, rx_buffer_size, 0);
if(len > 0) {
dap_cdc_usb_tx(rx_buffer, len);
}
dap_state->cdc_rx_counter += len;
}
if(events & CDCThreadEventCDCRx) {
size_t len = dap_cdc_usb_rx(rx_buffer, rx_buffer_size);
if(len > 0) {
furi_hal_uart_tx(app->uart_id, rx_buffer, len);
}
dap_state->cdc_tx_counter += len;
}
if(events & CDCThreadEventApplyConfig) {
if(uart_pins_prev != dap_app->config.uart_pins ||
uart_swap_prev != dap_app->config.uart_swap) {
cdc_deinit_uart(uart_pins_prev);
uart_pins_prev = dap_app->config.uart_pins;
uart_swap_prev = dap_app->config.uart_swap;
app->uart_id = cdc_init_uart(
uart_pins_prev,
uart_swap_prev,
dap_state->cdc_baudrate,
cdc_uart_irq_cb,
app);
}
}
if(events & CDCThreadEventStop) {
break;
}
}
}
cdc_deinit_uart(uart_pins_prev);
free(rx_buffer);
furi_stream_buffer_free(app->rx_stream);
free(app);
return 0;
}
/***************************************************************************/
/******************************* MAIN APP **********************************/
/***************************************************************************/
static DapApp* dap_app_alloc() {
DapApp* dap_app = malloc(sizeof(DapApp));
dap_app->dap_thread = furi_thread_alloc_ex("DAP Process", 1024, dap_process, dap_app);
dap_app->cdc_thread = furi_thread_alloc_ex("DAP CDC", 1024, cdc_process, dap_app);
dap_app->gui_thread = furi_thread_alloc_ex("DAP GUI", 1024, dap_gui_thread, dap_app);
return dap_app;
}
static void dap_app_free(DapApp* dap_app) {
furi_assert(dap_app);
furi_thread_free(dap_app->dap_thread);
furi_thread_free(dap_app->cdc_thread);
furi_thread_free(dap_app->gui_thread);
free(dap_app);
}
static DapApp* app_handle = NULL;
void dap_app_disconnect() {
app_handle->state.dap_mode = DapModeDisconnected;
}
void dap_app_connect_swd() {
app_handle->state.dap_mode = DapModeSWD;
}
void dap_app_connect_jtag() {
app_handle->state.dap_mode = DapModeJTAG;
}
void dap_app_set_config(DapApp* app, DapConfig* config) {
app->config = *config;
furi_thread_flags_set(furi_thread_get_id(app->dap_thread), DAPThreadEventApplyConfig);
furi_thread_flags_set(furi_thread_get_id(app->cdc_thread), CDCThreadEventApplyConfig);
}
DapConfig* dap_app_get_config(DapApp* app) {
return &app->config;
}
int32_t dap_link_app(void* p) {
UNUSED(p);
if(furi_hal_usb_is_locked()) {
DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
DialogMessage* message = dialog_message_alloc();
dialog_message_set_header(message, "Connection\nis active!", 3, 2, AlignLeft, AlignTop);
dialog_message_set_text(
message,
"Disconnect from\nPC or phone to\nuse this function.",
3,
30,
AlignLeft,
AlignTop);
dialog_message_set_icon(message, &I_ActiveConnection_50x64, 78, 0);
dialog_message_show(dialogs, message);
dialog_message_free(message);
furi_record_close(RECORD_DIALOGS);
return -1;
}
// alloc app
DapApp* app = dap_app_alloc();
app_handle = app;
furi_thread_start(app->dap_thread);
furi_thread_start(app->cdc_thread);
furi_thread_start(app->gui_thread);
// wait until gui thread is finished
furi_thread_join(app->gui_thread);
// send stop event to threads
dap_thread_send_stop(app->dap_thread);
dap_thread_send_stop(app->cdc_thread);
// wait for threads to stop
furi_thread_join(app->dap_thread);
furi_thread_join(app->cdc_thread);
// free app
dap_app_free(app);
return 0;
}

View File

@@ -0,0 +1,55 @@
#pragma once
#include <stdint.h>
typedef enum {
DapModeDisconnected,
DapModeSWD,
DapModeJTAG,
} DapMode;
typedef enum {
DapVersionUnknown,
DapVersionV1,
DapVersionV2,
} DapVersion;
typedef struct {
bool usb_connected;
DapMode dap_mode;
DapVersion dap_version;
uint32_t dap_counter;
uint32_t cdc_baudrate;
uint32_t cdc_tx_counter;
uint32_t cdc_rx_counter;
} DapState;
typedef enum {
DapSwdPinsPA7PA6, // Pins 2, 3
DapSwdPinsPA14PA13, // Pins 10, 12
} DapSwdPins;
typedef enum {
DapUartTypeUSART1, // Pins 13, 14
DapUartTypeLPUART1, // Pins 15, 16
} DapUartType;
typedef enum {
DapUartTXRXNormal,
DapUartTXRXSwap,
} DapUartTXRX;
typedef struct {
DapSwdPins swd_pins;
DapUartType uart_pins;
DapUartTXRX uart_swap;
} DapConfig;
typedef struct DapApp DapApp;
void dap_app_get_state(DapApp* app, DapState* state);
const char* dap_app_get_serial(DapApp* app);
void dap_app_set_config(DapApp* app, DapConfig* config);
DapConfig* dap_app_get_config(DapApp* app);

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 B

View File

@@ -0,0 +1,92 @@
#include "dap_gui.h"
#include "dap_gui_i.h"
#define DAP_GUI_TICK 250
static bool dap_gui_custom_event_callback(void* context, uint32_t event) {
furi_assert(context);
DapGuiApp* app = context;
return scene_manager_handle_custom_event(app->scene_manager, event);
}
static bool dap_gui_back_event_callback(void* context) {
furi_assert(context);
DapGuiApp* app = context;
return scene_manager_handle_back_event(app->scene_manager);
}
static void dap_gui_tick_event_callback(void* context) {
furi_assert(context);
DapGuiApp* app = context;
scene_manager_handle_tick_event(app->scene_manager);
}
DapGuiApp* dap_gui_alloc() {
DapGuiApp* app = malloc(sizeof(DapGuiApp));
app->gui = furi_record_open(RECORD_GUI);
app->view_dispatcher = view_dispatcher_alloc();
app->scene_manager = scene_manager_alloc(&dap_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, dap_gui_custom_event_callback);
view_dispatcher_set_navigation_event_callback(
app->view_dispatcher, dap_gui_back_event_callback);
view_dispatcher_set_tick_event_callback(
app->view_dispatcher, dap_gui_tick_event_callback, DAP_GUI_TICK);
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
app->notifications = furi_record_open(RECORD_NOTIFICATION);
app->var_item_list = variable_item_list_alloc();
view_dispatcher_add_view(
app->view_dispatcher,
DapGuiAppViewVarItemList,
variable_item_list_get_view(app->var_item_list));
app->main_view = dap_main_view_alloc();
view_dispatcher_add_view(
app->view_dispatcher, DapGuiAppViewMainView, dap_main_view_get_view(app->main_view));
app->widget = widget_alloc();
view_dispatcher_add_view(
app->view_dispatcher, DapGuiAppViewWidget, widget_get_view(app->widget));
scene_manager_next_scene(app->scene_manager, DapSceneMain);
return app;
}
void dap_gui_free(DapGuiApp* app) {
view_dispatcher_remove_view(app->view_dispatcher, DapGuiAppViewVarItemList);
variable_item_list_free(app->var_item_list);
view_dispatcher_remove_view(app->view_dispatcher, DapGuiAppViewMainView);
dap_main_view_free(app->main_view);
view_dispatcher_remove_view(app->view_dispatcher, DapGuiAppViewWidget);
widget_free(app->widget);
// View dispatcher
view_dispatcher_free(app->view_dispatcher);
scene_manager_free(app->scene_manager);
// Close records
furi_record_close(RECORD_GUI);
furi_record_close(RECORD_NOTIFICATION);
free(app);
}
int32_t dap_gui_thread(void* arg) {
DapGuiApp* app = dap_gui_alloc();
app->dap_app = arg;
notification_message_block(app->notifications, &sequence_display_backlight_enforce_on);
view_dispatcher_run(app->view_dispatcher);
notification_message_block(app->notifications, &sequence_display_backlight_enforce_auto);
dap_gui_free(app);
return 0;
}

View File

@@ -0,0 +1,4 @@
#pragma once
#include <stdint.h>
int32_t dap_gui_thread(void* arg);

View File

@@ -0,0 +1,7 @@
#pragma once
typedef enum {
DapAppCustomEventConfig,
DapAppCustomEventHelp,
DapAppCustomEventAbout,
} DapAppCustomEvent;

View File

@@ -0,0 +1,34 @@
#pragma once
#include <gui/gui.h>
#include <gui/view_dispatcher.h>
#include <gui/scene_manager.h>
#include <gui/modules/submenu.h>
#include <notification/notification_messages.h>
#include <gui/modules/variable_item_list.h>
#include <gui/modules/widget.h>
#include "dap_gui.h"
#include "../dap_link.h"
#include "scenes/config/dap_scene.h"
#include "dap_gui_custom_event.h"
#include "views/dap_main_view.h"
typedef struct {
DapApp* dap_app;
Gui* gui;
NotificationApp* notifications;
ViewDispatcher* view_dispatcher;
SceneManager* scene_manager;
VariableItemList* var_item_list;
DapMainView* main_view;
Widget* widget;
} DapGuiApp;
typedef enum {
DapGuiAppViewVarItemList,
DapGuiAppViewMainView,
DapGuiAppViewWidget,
} DapGuiAppView;

View File

@@ -0,0 +1,30 @@
#include "dap_scene.h"
// Generate scene on_enter handlers array
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
void (*const dap_scene_on_enter_handlers[])(void*) = {
#include "dap_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 dap_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = {
#include "dap_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 dap_scene_on_exit_handlers[])(void* context) = {
#include "dap_scene_config.h"
};
#undef ADD_SCENE
// Initialize scene handlers configuration structure
const SceneManagerHandlers dap_scene_handlers = {
.on_enter_handlers = dap_scene_on_enter_handlers,
.on_event_handlers = dap_scene_on_event_handlers,
.on_exit_handlers = dap_scene_on_exit_handlers,
.scene_num = DapSceneNum,
};

View File

@@ -0,0 +1,29 @@
#pragma once
#include <gui/scene_manager.h>
// Generate scene id and total number
#define ADD_SCENE(prefix, name, id) DapScene##id,
typedef enum {
#include "dap_scene_config.h"
DapSceneNum,
} DapScene;
#undef ADD_SCENE
extern const SceneManagerHandlers dap_scene_handlers;
// Generate scene on_enter handlers declaration
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*);
#include "dap_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 "dap_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 "dap_scene_config.h"
#undef ADD_SCENE

View File

@@ -0,0 +1,4 @@
ADD_SCENE(dap, main, Main)
ADD_SCENE(dap, config, Config)
ADD_SCENE(dap, help, Help)
ADD_SCENE(dap, about, About)

View File

@@ -0,0 +1,68 @@
#include "../dap_gui_i.h"
#define DAP_VERSION_APP "0.1.0"
#define DAP_DEVELOPED "Dr_Zlo"
#define DAP_GITHUB "https://github.com/flipperdevices/flipperzero-firmware"
void dap_scene_about_on_enter(void* context) {
DapGuiApp* app = context;
FuriString* temp_str;
temp_str = furi_string_alloc();
furi_string_printf(temp_str, "\e#%s\n", "Information");
furi_string_cat_printf(temp_str, "Version: %s\n", DAP_VERSION_APP);
furi_string_cat_printf(temp_str, "Developed by: %s\n", DAP_DEVELOPED);
furi_string_cat_printf(temp_str, "Github: %s\n\n", DAP_GITHUB);
furi_string_cat_printf(temp_str, "\e#%s\n", "Description");
furi_string_cat_printf(
temp_str, "CMSIS-DAP debugger\nbased on Free-DAP\nThanks to Alex Taradov\n\n");
furi_string_cat_printf(
temp_str,
"Supported protocols:\n"
"SWD, JTAG, UART\n"
"DAP v1 (cmsis_backend hid), DAP v2 (cmsis_backend usb_bulk), VCP\n");
widget_add_text_box_element(
app->widget,
0,
0,
128,
14,
AlignCenter,
AlignBottom,
"\e#\e! \e!\n",
false);
widget_add_text_box_element(
app->widget,
0,
2,
128,
14,
AlignCenter,
AlignBottom,
"\e#\e! DAP Link \e!\n",
false);
widget_add_text_scroll_element(app->widget, 0, 16, 128, 50, furi_string_get_cstr(temp_str));
furi_string_free(temp_str);
view_dispatcher_switch_to_view(app->view_dispatcher, DapGuiAppViewWidget);
}
bool dap_scene_about_on_event(void* context, SceneManagerEvent event) {
DapGuiApp* app = context;
bool consumed = false;
UNUSED(app);
UNUSED(event);
return consumed;
}
void dap_scene_about_on_exit(void* context) {
DapGuiApp* app = context;
// Clear views
widget_reset(app->widget);
}

View File

@@ -0,0 +1,107 @@
#include "../dap_gui_i.h"
static const char* swd_pins[] = {[DapSwdPinsPA7PA6] = "2,3", [DapSwdPinsPA14PA13] = "10,12"};
static const char* uart_pins[] = {[DapUartTypeUSART1] = "13,14", [DapUartTypeLPUART1] = "15,16"};
static const char* uart_swap[] = {[DapUartTXRXNormal] = "No", [DapUartTXRXSwap] = "Yes"};
static void swd_pins_cb(VariableItem* item) {
DapGuiApp* app = variable_item_get_context(item);
uint8_t index = variable_item_get_current_value_index(item);
variable_item_set_current_value_text(item, swd_pins[index]);
DapConfig* config = dap_app_get_config(app->dap_app);
config->swd_pins = index;
dap_app_set_config(app->dap_app, config);
}
static void uart_pins_cb(VariableItem* item) {
DapGuiApp* app = variable_item_get_context(item);
uint8_t index = variable_item_get_current_value_index(item);
variable_item_set_current_value_text(item, uart_pins[index]);
DapConfig* config = dap_app_get_config(app->dap_app);
config->uart_pins = index;
dap_app_set_config(app->dap_app, config);
}
static void uart_swap_cb(VariableItem* item) {
DapGuiApp* app = variable_item_get_context(item);
uint8_t index = variable_item_get_current_value_index(item);
variable_item_set_current_value_text(item, uart_swap[index]);
DapConfig* config = dap_app_get_config(app->dap_app);
config->uart_swap = index;
dap_app_set_config(app->dap_app, config);
}
static void ok_cb(void* context, uint32_t index) {
DapGuiApp* app = context;
switch(index) {
case 3:
view_dispatcher_send_custom_event(app->view_dispatcher, DapAppCustomEventHelp);
break;
case 4:
view_dispatcher_send_custom_event(app->view_dispatcher, DapAppCustomEventAbout);
break;
default:
break;
}
}
void dap_scene_config_on_enter(void* context) {
DapGuiApp* app = context;
VariableItemList* var_item_list = app->var_item_list;
VariableItem* item;
DapConfig* config = dap_app_get_config(app->dap_app);
item = variable_item_list_add(
var_item_list, "SWC SWD Pins", COUNT_OF(swd_pins), swd_pins_cb, app);
variable_item_set_current_value_index(item, config->swd_pins);
variable_item_set_current_value_text(item, swd_pins[config->swd_pins]);
item =
variable_item_list_add(var_item_list, "UART Pins", COUNT_OF(uart_pins), uart_pins_cb, app);
variable_item_set_current_value_index(item, config->uart_pins);
variable_item_set_current_value_text(item, uart_pins[config->uart_pins]);
item = variable_item_list_add(
var_item_list, "Swap TX RX", COUNT_OF(uart_swap), uart_swap_cb, app);
variable_item_set_current_value_index(item, config->uart_swap);
variable_item_set_current_value_text(item, uart_swap[config->uart_swap]);
variable_item_list_add(var_item_list, "Help and Pinout", 0, NULL, NULL);
variable_item_list_add(var_item_list, "About", 0, NULL, NULL);
variable_item_list_set_selected_item(
var_item_list, scene_manager_get_scene_state(app->scene_manager, DapSceneConfig));
variable_item_list_set_enter_callback(var_item_list, ok_cb, app);
view_dispatcher_switch_to_view(app->view_dispatcher, DapGuiAppViewVarItemList);
}
bool dap_scene_config_on_event(void* context, SceneManagerEvent event) {
DapGuiApp* app = context;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == DapAppCustomEventHelp) {
scene_manager_next_scene(app->scene_manager, DapSceneHelp);
return true;
} else if(event.event == DapAppCustomEventAbout) {
scene_manager_next_scene(app->scene_manager, DapSceneAbout);
return true;
}
}
return false;
}
void dap_scene_config_on_exit(void* context) {
DapGuiApp* app = context;
scene_manager_set_scene_state(
app->scene_manager,
DapSceneConfig,
variable_item_list_get_selected_item_index(app->var_item_list));
variable_item_list_reset(app->var_item_list);
}

View File

@@ -0,0 +1,102 @@
#include "../dap_gui_i.h"
void dap_scene_help_on_enter(void* context) {
DapGuiApp* app = context;
DapConfig* config = dap_app_get_config(app->dap_app);
FuriString* string = furi_string_alloc();
furi_string_cat(string, "CMSIS DAP/DAP Link v2\r\n");
furi_string_cat_printf(string, "Serial: %s\r\n", dap_app_get_serial(app->dap_app));
furi_string_cat(
string,
"Pinout:\r\n"
"\e#SWD:\r\n");
switch(config->swd_pins) {
case DapSwdPinsPA7PA6:
furi_string_cat(
string,
" SWC: 2 [A7]\r\n"
" SWD: 3 [A6]\r\n");
break;
case DapSwdPinsPA14PA13:
furi_string_cat(
string,
" SWC: 10 [SWC]\r\n"
" SWD: 12 [SIO]\r\n");
break;
default:
break;
}
furi_string_cat(string, "\e#JTAG:\r\n");
switch(config->swd_pins) {
case DapSwdPinsPA7PA6:
furi_string_cat(
string,
" TCK: 2 [A7]\r\n"
" TMS: 3 [A6]\r\n"
" RST: 4 [A4]\r\n"
" TDO: 5 [B3]\r\n"
" TDI: 6 [B2]\r\n");
break;
case DapSwdPinsPA14PA13:
furi_string_cat(
string,
" RST: 4 [A4]\r\n"
" TDO: 5 [B3]\r\n"
" TDI: 6 [B2]\r\n"
" TCK: 10 [SWC]\r\n"
" TMS: 12 [SIO]\r\n");
break;
default:
break;
}
furi_string_cat(string, "\e#UART:\r\n");
switch(config->uart_pins) {
case DapUartTypeUSART1:
if(config->uart_swap == DapUartTXRXNormal) {
furi_string_cat(
string,
" TX: 13 [TX]\r\n"
" RX: 14 [RX]\r\n");
} else {
furi_string_cat(
string,
" RX: 13 [TX]\r\n"
" TX: 14 [RX]\r\n");
}
break;
case DapUartTypeLPUART1:
if(config->uart_swap == DapUartTXRXNormal) {
furi_string_cat(
string,
" TX: 15 [C1]\r\n"
" RX: 16 [C0]\r\n");
} else {
furi_string_cat(
string,
" RX: 15 [C1]\r\n"
" TX: 16 [C0]\r\n");
}
break;
default:
break;
}
widget_add_text_scroll_element(app->widget, 0, 0, 128, 64, furi_string_get_cstr(string));
furi_string_free(string);
view_dispatcher_switch_to_view(app->view_dispatcher, DapGuiAppViewWidget);
}
bool dap_scene_help_on_event(void* context, SceneManagerEvent event) {
UNUSED(context);
UNUSED(event);
return false;
}
void dap_scene_help_on_exit(void* context) {
DapGuiApp* app = context;
widget_reset(app->widget);
}

View File

@@ -0,0 +1,154 @@
#include "../dap_gui_i.h"
#include "../../dap_link.h"
typedef struct {
DapState dap_state;
bool dap_active;
bool tx_active;
bool rx_active;
} DapSceneMainState;
static bool process_dap_state(DapGuiApp* app) {
DapSceneMainState* state =
(DapSceneMainState*)scene_manager_get_scene_state(app->scene_manager, DapSceneMain);
if(state == NULL) return true;
DapState* prev_state = &state->dap_state;
DapState next_state;
dap_app_get_state(app->dap_app, &next_state);
bool need_to_update = false;
if(prev_state->dap_mode != next_state.dap_mode) {
switch(next_state.dap_mode) {
case DapModeDisconnected:
dap_main_view_set_mode(app->main_view, DapMainViewModeDisconnected);
notification_message(app->notifications, &sequence_blink_stop);
break;
case DapModeSWD:
dap_main_view_set_mode(app->main_view, DapMainViewModeSWD);
notification_message(app->notifications, &sequence_blink_start_blue);
break;
case DapModeJTAG:
dap_main_view_set_mode(app->main_view, DapMainViewModeJTAG);
notification_message(app->notifications, &sequence_blink_start_magenta);
break;
}
need_to_update = true;
}
if(prev_state->dap_version != next_state.dap_version) {
switch(next_state.dap_version) {
case DapVersionUnknown:
dap_main_view_set_version(app->main_view, DapMainViewVersionUnknown);
break;
case DapVersionV1:
dap_main_view_set_version(app->main_view, DapMainViewVersionV1);
break;
case DapVersionV2:
dap_main_view_set_version(app->main_view, DapMainViewVersionV2);
break;
}
need_to_update = true;
}
if(prev_state->usb_connected != next_state.usb_connected) {
dap_main_view_set_usb_connected(app->main_view, next_state.usb_connected);
need_to_update = true;
}
if(prev_state->dap_counter != next_state.dap_counter) {
if(!state->dap_active) {
state->dap_active = true;
dap_main_view_set_dap(app->main_view, state->dap_active);
need_to_update = true;
}
} else {
if(state->dap_active) {
state->dap_active = false;
dap_main_view_set_dap(app->main_view, state->dap_active);
need_to_update = true;
}
}
if(prev_state->cdc_baudrate != next_state.cdc_baudrate) {
dap_main_view_set_baudrate(app->main_view, next_state.cdc_baudrate);
need_to_update = true;
}
if(prev_state->cdc_tx_counter != next_state.cdc_tx_counter) {
if(!state->tx_active) {
state->tx_active = true;
dap_main_view_set_tx(app->main_view, state->tx_active);
need_to_update = true;
notification_message(app->notifications, &sequence_blink_start_red);
}
} else {
if(state->tx_active) {
state->tx_active = false;
dap_main_view_set_tx(app->main_view, state->tx_active);
need_to_update = true;
notification_message(app->notifications, &sequence_blink_stop);
}
}
if(prev_state->cdc_rx_counter != next_state.cdc_rx_counter) {
if(!state->rx_active) {
state->rx_active = true;
dap_main_view_set_rx(app->main_view, state->rx_active);
need_to_update = true;
notification_message(app->notifications, &sequence_blink_start_green);
}
} else {
if(state->rx_active) {
state->rx_active = false;
dap_main_view_set_rx(app->main_view, state->rx_active);
need_to_update = true;
notification_message(app->notifications, &sequence_blink_stop);
}
}
if(need_to_update) {
dap_main_view_update(app->main_view);
}
*prev_state = next_state;
return true;
}
static void dap_scene_main_on_left(void* context) {
DapGuiApp* app = (DapGuiApp*)context;
view_dispatcher_send_custom_event(app->view_dispatcher, DapAppCustomEventConfig);
}
void dap_scene_main_on_enter(void* context) {
DapGuiApp* app = context;
DapSceneMainState* state = malloc(sizeof(DapSceneMainState));
dap_main_view_set_left_callback(app->main_view, dap_scene_main_on_left, app);
view_dispatcher_switch_to_view(app->view_dispatcher, DapGuiAppViewMainView);
scene_manager_set_scene_state(app->scene_manager, DapSceneMain, (uint32_t)state);
}
bool dap_scene_main_on_event(void* context, SceneManagerEvent event) {
DapGuiApp* app = context;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == DapAppCustomEventConfig) {
scene_manager_next_scene(app->scene_manager, DapSceneConfig);
return true;
}
} else if(event.type == SceneManagerEventTypeTick) {
return process_dap_state(app);
}
return false;
}
void dap_scene_main_on_exit(void* context) {
DapGuiApp* app = context;
DapSceneMainState* state =
(DapSceneMainState*)scene_manager_get_scene_state(app->scene_manager, DapSceneMain);
scene_manager_set_scene_state(app->scene_manager, DapSceneMain, (uint32_t)NULL);
FURI_SW_MEMBARRIER();
free(state);
notification_message(app->notifications, &sequence_blink_stop);
}

View File

@@ -0,0 +1,189 @@
#include "dap_main_view.h"
#include "dap_link_icons.h"
#include <gui/elements.h>
// extern const Icon I_ArrowDownEmpty_12x18;
// extern const Icon I_ArrowDownFilled_12x18;
// extern const Icon I_ArrowUpEmpty_12x18;
// extern const Icon I_ArrowUpFilled_12x18;
struct DapMainView {
View* view;
DapMainViewButtonCallback cb_left;
void* cb_context;
};
typedef struct {
DapMainViewMode mode;
DapMainViewVersion version;
bool usb_connected;
uint32_t baudrate;
bool dap_active;
bool tx_active;
bool rx_active;
} DapMainViewModel;
static void dap_main_view_draw_callback(Canvas* canvas, void* _model) {
DapMainViewModel* model = _model;
UNUSED(model);
canvas_clear(canvas);
elements_button_left(canvas, "Config");
canvas_set_color(canvas, ColorBlack);
canvas_draw_box(canvas, 0, 0, 127, 11);
canvas_set_color(canvas, ColorWhite);
const char* header_string;
if(model->usb_connected) {
if(model->version == DapMainViewVersionV1) {
header_string = "DAP Link V1 Connected";
} else if(model->version == DapMainViewVersionV2) {
header_string = "DAP Link V2 Connected";
} else {
header_string = "DAP Link Connected";
}
} else {
header_string = "DAP Link";
}
canvas_draw_str_aligned(canvas, 64, 9, AlignCenter, AlignBottom, header_string);
canvas_set_color(canvas, ColorBlack);
if(model->dap_active) {
canvas_draw_icon(canvas, 14, 16, &I_ArrowUpFilled_12x18);
canvas_draw_icon(canvas, 28, 16, &I_ArrowDownFilled_12x18);
} else {
canvas_draw_icon(canvas, 14, 16, &I_ArrowUpEmpty_12x18);
canvas_draw_icon(canvas, 28, 16, &I_ArrowDownEmpty_12x18);
}
switch(model->mode) {
case DapMainViewModeDisconnected:
canvas_draw_str_aligned(canvas, 26, 38, AlignCenter, AlignTop, "----");
break;
case DapMainViewModeSWD:
canvas_draw_str_aligned(canvas, 26, 38, AlignCenter, AlignTop, "SWD");
break;
case DapMainViewModeJTAG:
canvas_draw_str_aligned(canvas, 26, 38, AlignCenter, AlignTop, "JTAG");
break;
}
if(model->tx_active) {
canvas_draw_icon(canvas, 87, 16, &I_ArrowUpFilled_12x18);
} else {
canvas_draw_icon(canvas, 87, 16, &I_ArrowUpEmpty_12x18);
}
if(model->rx_active) {
canvas_draw_icon(canvas, 101, 16, &I_ArrowDownFilled_12x18);
} else {
canvas_draw_icon(canvas, 101, 16, &I_ArrowDownEmpty_12x18);
}
canvas_draw_str_aligned(canvas, 100, 38, AlignCenter, AlignTop, "UART");
canvas_draw_line(canvas, 44, 52, 123, 52);
if(model->baudrate == 0) {
canvas_draw_str(canvas, 45, 62, "Baud: ????");
} else {
char baudrate_str[18];
snprintf(baudrate_str, 18, "Baud: %lu", model->baudrate);
canvas_draw_str(canvas, 45, 62, baudrate_str);
}
}
static bool dap_main_view_input_callback(InputEvent* event, void* context) {
furi_assert(context);
DapMainView* dap_main_view = context;
bool consumed = false;
if(event->type == InputTypeShort) {
if(event->key == InputKeyLeft) {
if(dap_main_view->cb_left) {
dap_main_view->cb_left(dap_main_view->cb_context);
}
consumed = true;
}
}
return consumed;
}
DapMainView* dap_main_view_alloc() {
DapMainView* dap_main_view = malloc(sizeof(DapMainView));
dap_main_view->view = view_alloc();
view_allocate_model(dap_main_view->view, ViewModelTypeLocking, sizeof(DapMainViewModel));
view_set_context(dap_main_view->view, dap_main_view);
view_set_draw_callback(dap_main_view->view, dap_main_view_draw_callback);
view_set_input_callback(dap_main_view->view, dap_main_view_input_callback);
return dap_main_view;
}
void dap_main_view_free(DapMainView* dap_main_view) {
view_free(dap_main_view->view);
free(dap_main_view);
}
View* dap_main_view_get_view(DapMainView* dap_main_view) {
return dap_main_view->view;
}
void dap_main_view_set_left_callback(
DapMainView* dap_main_view,
DapMainViewButtonCallback callback,
void* context) {
with_view_model(
dap_main_view->view,
DapMainViewModel * model,
{
UNUSED(model);
dap_main_view->cb_left = callback;
dap_main_view->cb_context = context;
},
true);
}
void dap_main_view_set_mode(DapMainView* dap_main_view, DapMainViewMode mode) {
with_view_model(
dap_main_view->view, DapMainViewModel * model, { model->mode = mode; }, false);
}
void dap_main_view_set_dap(DapMainView* dap_main_view, bool active) {
with_view_model(
dap_main_view->view, DapMainViewModel * model, { model->dap_active = active; }, false);
}
void dap_main_view_set_tx(DapMainView* dap_main_view, bool active) {
with_view_model(
dap_main_view->view, DapMainViewModel * model, { model->tx_active = active; }, false);
}
void dap_main_view_set_rx(DapMainView* dap_main_view, bool active) {
with_view_model(
dap_main_view->view, DapMainViewModel * model, { model->rx_active = active; }, false);
}
void dap_main_view_set_baudrate(DapMainView* dap_main_view, uint32_t baudrate) {
with_view_model(
dap_main_view->view, DapMainViewModel * model, { model->baudrate = baudrate; }, false);
}
void dap_main_view_update(DapMainView* dap_main_view) {
with_view_model(
dap_main_view->view, DapMainViewModel * model, { UNUSED(model); }, true);
}
void dap_main_view_set_version(DapMainView* dap_main_view, DapMainViewVersion version) {
with_view_model(
dap_main_view->view, DapMainViewModel * model, { model->version = version; }, false);
}
void dap_main_view_set_usb_connected(DapMainView* dap_main_view, bool connected) {
with_view_model(
dap_main_view->view,
DapMainViewModel * model,
{ model->usb_connected = connected; },
false);
}

View File

@@ -0,0 +1,45 @@
#pragma once
#include <gui/view.h>
typedef struct DapMainView DapMainView;
typedef void (*DapMainViewButtonCallback)(void* context);
typedef enum {
DapMainViewVersionUnknown,
DapMainViewVersionV1,
DapMainViewVersionV2,
} DapMainViewVersion;
typedef enum {
DapMainViewModeDisconnected,
DapMainViewModeSWD,
DapMainViewModeJTAG,
} DapMainViewMode;
DapMainView* dap_main_view_alloc();
void dap_main_view_free(DapMainView* dap_main_view);
View* dap_main_view_get_view(DapMainView* dap_main_view);
void dap_main_view_set_left_callback(
DapMainView* dap_main_view,
DapMainViewButtonCallback callback,
void* context);
void dap_main_view_set_mode(DapMainView* dap_main_view, DapMainViewMode mode);
void dap_main_view_set_version(DapMainView* dap_main_view, DapMainViewVersion version);
void dap_main_view_set_dap(DapMainView* dap_main_view, bool active);
void dap_main_view_set_tx(DapMainView* dap_main_view, bool active);
void dap_main_view_set_rx(DapMainView* dap_main_view, bool active);
void dap_main_view_set_usb_connected(DapMainView* dap_main_view, bool connected);
void dap_main_view_set_baudrate(DapMainView* dap_main_view, uint32_t baudrate);
void dap_main_view_update(DapMainView* dap_main_view);

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 B

View File

@@ -0,0 +1,977 @@
#include <furi.h>
#include <usb.h>
#include <usb_std.h>
#include <usb_hid.h>
#include <usb_cdc.h>
#include <furi_hal_console.h>
#include "dap_v2_usb.h"
// #define DAP_USB_LOG
#define HID_EP_IN 0x80
#define HID_EP_OUT 0x00
#define DAP_HID_EP_SEND 1
#define DAP_HID_EP_RECV 2
#define DAP_HID_EP_BULK_RECV 3
#define DAP_HID_EP_BULK_SEND 4
#define DAP_CDC_EP_COMM 5
#define DAP_CDC_EP_SEND 6
#define DAP_CDC_EP_RECV 7
#define DAP_HID_EP_IN (HID_EP_IN | DAP_HID_EP_SEND)
#define DAP_HID_EP_OUT (HID_EP_OUT | DAP_HID_EP_RECV)
#define DAP_HID_EP_BULK_IN (HID_EP_IN | DAP_HID_EP_BULK_SEND)
#define DAP_HID_EP_BULK_OUT (HID_EP_OUT | DAP_HID_EP_BULK_RECV)
#define DAP_HID_EP_SIZE 64
#define DAP_CDC_COMM_EP_SIZE 8
#define DAP_CDC_EP_SIZE 64
#define DAP_BULK_INTERVAL 0
#define DAP_HID_INTERVAL 1
#define DAP_CDC_INTERVAL 0
#define DAP_CDC_COMM_INTERVAL 1
#define DAP_HID_VID 0x0483
#define DAP_HID_PID 0x5740
#define DAP_USB_EP0_SIZE 8
#define EP_CFG_DECONFIGURE 0
#define EP_CFG_CONFIGURE 1
enum {
USB_INTF_HID,
USB_INTF_BULK,
USB_INTF_CDC_COMM,
USB_INTF_CDC_DATA,
USB_INTF_COUNT,
};
enum {
USB_STR_ZERO,
USB_STR_MANUFACTURER,
USB_STR_PRODUCT,
USB_STR_SERIAL_NUMBER,
USB_STR_CMSIS_DAP_V1,
USB_STR_CMSIS_DAP_V2,
USB_STR_COM_PORT,
USB_STR_COUNT,
};
// static const char* usb_str[] = {
// [USB_STR_MANUFACTURER] = "Flipper Devices Inc.",
// [USB_STR_PRODUCT] = "Combined VCP and CMSIS-DAP Adapter",
// [USB_STR_COM_PORT] = "Virtual COM-Port",
// [USB_STR_CMSIS_DAP_V1] = "CMSIS-DAP v1 Adapter",
// [USB_STR_CMSIS_DAP_V2] = "CMSIS-DAP v2 Adapter",
// [USB_STR_SERIAL_NUMBER] = "01234567890ABCDEF",
// };
static const struct usb_string_descriptor dev_manuf_descr =
USB_STRING_DESC("Flipper Devices Inc.");
static const struct usb_string_descriptor dev_prod_descr =
USB_STRING_DESC("Combined VCP and CMSIS-DAP Adapter");
static struct usb_string_descriptor* dev_serial_descr = NULL;
static const struct usb_string_descriptor dev_dap_v1_descr =
USB_STRING_DESC("CMSIS-DAP v1 Adapter");
static const struct usb_string_descriptor dev_dap_v2_descr =
USB_STRING_DESC("CMSIS-DAP v2 Adapter");
static const struct usb_string_descriptor dev_com_descr = USB_STRING_DESC("Virtual COM-Port");
struct HidConfigDescriptor {
struct usb_config_descriptor configuration;
// CMSIS-DAP v1
struct usb_interface_descriptor hid_interface;
struct usb_hid_descriptor hid;
struct usb_endpoint_descriptor hid_ep_in;
struct usb_endpoint_descriptor hid_ep_out;
// CMSIS-DAP v2
struct usb_interface_descriptor bulk_interface;
struct usb_endpoint_descriptor bulk_ep_out;
struct usb_endpoint_descriptor bulk_ep_in;
// CDC
struct usb_iad_descriptor iad;
struct usb_interface_descriptor interface_comm;
struct usb_cdc_header_desc cdc_header;
struct usb_cdc_call_mgmt_desc cdc_acm;
struct usb_cdc_acm_desc cdc_call_mgmt;
struct usb_cdc_union_desc cdc_union;
struct usb_endpoint_descriptor ep_comm;
struct usb_interface_descriptor interface_data;
struct usb_endpoint_descriptor ep_in;
struct usb_endpoint_descriptor ep_out;
} __attribute__((packed));
static const struct usb_device_descriptor hid_device_desc = {
.bLength = sizeof(struct usb_device_descriptor),
.bDescriptorType = USB_DTYPE_DEVICE,
.bcdUSB = VERSION_BCD(2, 1, 0),
.bDeviceClass = USB_CLASS_MISC,
.bDeviceSubClass = USB_SUBCLASS_IAD,
.bDeviceProtocol = USB_PROTO_IAD,
.bMaxPacketSize0 = DAP_USB_EP0_SIZE,
.idVendor = DAP_HID_VID,
.idProduct = DAP_HID_PID,
.bcdDevice = VERSION_BCD(1, 0, 0),
.iManufacturer = USB_STR_MANUFACTURER,
.iProduct = USB_STR_PRODUCT,
.iSerialNumber = USB_STR_SERIAL_NUMBER,
.bNumConfigurations = 1,
};
static const uint8_t hid_report_desc[] = {
0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
0x09, 0x00, // Usage (Undefined)
0xa1, 0x01, // Collection (Application)
0x15, 0x00, // Logical Minimum (0)
0x26, 0xff, 0x00, // Logical Maximum (255)
0x75, 0x08, // Report Size (8)
0x95, 0x40, // Report Count (64)
0x09, 0x00, // Usage (Undefined)
0x81, 0x82, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x75, 0x08, // Report Size (8)
0x95, 0x40, // Report Count (64)
0x09, 0x00, // Usage (Undefined)
0x91, 0x82, // Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Volatile)
0xc0, // End Collection
};
static const struct HidConfigDescriptor hid_cfg_desc = {
.configuration =
{
.bLength = sizeof(struct usb_config_descriptor),
.bDescriptorType = USB_DTYPE_CONFIGURATION,
.wTotalLength = sizeof(struct HidConfigDescriptor),
.bNumInterfaces = USB_INTF_COUNT,
.bConfigurationValue = 1,
.iConfiguration = NO_DESCRIPTOR,
.bmAttributes = USB_CFG_ATTR_RESERVED,
.bMaxPower = USB_CFG_POWER_MA(500),
},
// CMSIS-DAP v1
.hid_interface =
{
.bLength = sizeof(struct usb_interface_descriptor),
.bDescriptorType = USB_DTYPE_INTERFACE,
.bInterfaceNumber = USB_INTF_HID,
.bAlternateSetting = 0,
.bNumEndpoints = 2,
.bInterfaceClass = USB_CLASS_HID,
.bInterfaceSubClass = USB_HID_SUBCLASS_NONBOOT,
.bInterfaceProtocol = USB_HID_PROTO_NONBOOT,
.iInterface = USB_STR_CMSIS_DAP_V1,
},
.hid =
{
.bLength = sizeof(struct usb_hid_descriptor),
.bDescriptorType = USB_DTYPE_HID,
.bcdHID = VERSION_BCD(1, 1, 1),
.bCountryCode = USB_HID_COUNTRY_NONE,
.bNumDescriptors = 1,
.bDescriptorType0 = USB_DTYPE_HID_REPORT,
.wDescriptorLength0 = sizeof(hid_report_desc),
},
.hid_ep_in =
{
.bLength = sizeof(struct usb_endpoint_descriptor),
.bDescriptorType = USB_DTYPE_ENDPOINT,
.bEndpointAddress = DAP_HID_EP_IN,
.bmAttributes = USB_EPTYPE_INTERRUPT,
.wMaxPacketSize = DAP_HID_EP_SIZE,
.bInterval = DAP_HID_INTERVAL,
},
.hid_ep_out =
{
.bLength = sizeof(struct usb_endpoint_descriptor),
.bDescriptorType = USB_DTYPE_ENDPOINT,
.bEndpointAddress = DAP_HID_EP_OUT,
.bmAttributes = USB_EPTYPE_INTERRUPT,
.wMaxPacketSize = DAP_HID_EP_SIZE,
.bInterval = DAP_HID_INTERVAL,
},
// CMSIS-DAP v2
.bulk_interface =
{
.bLength = sizeof(struct usb_interface_descriptor),
.bDescriptorType = USB_DTYPE_INTERFACE,
.bInterfaceNumber = USB_INTF_BULK,
.bAlternateSetting = 0,
.bNumEndpoints = 2,
.bInterfaceClass = USB_CLASS_VENDOR,
.bInterfaceSubClass = 0,
.bInterfaceProtocol = 0,
.iInterface = USB_STR_CMSIS_DAP_V2,
},
.bulk_ep_out =
{
.bLength = sizeof(struct usb_endpoint_descriptor),
.bDescriptorType = USB_DTYPE_ENDPOINT,
.bEndpointAddress = DAP_HID_EP_BULK_OUT,
.bmAttributes = USB_EPTYPE_BULK,
.wMaxPacketSize = DAP_HID_EP_SIZE,
.bInterval = DAP_BULK_INTERVAL,
},
.bulk_ep_in =
{
.bLength = sizeof(struct usb_endpoint_descriptor),
.bDescriptorType = USB_DTYPE_ENDPOINT,
.bEndpointAddress = DAP_HID_EP_BULK_IN,
.bmAttributes = USB_EPTYPE_BULK,
.wMaxPacketSize = DAP_HID_EP_SIZE,
.bInterval = DAP_BULK_INTERVAL,
},
// CDC
.iad =
{
.bLength = sizeof(struct usb_iad_descriptor),
.bDescriptorType = USB_DTYPE_INTERFASEASSOC,
.bFirstInterface = USB_INTF_CDC_COMM,
.bInterfaceCount = 2,
.bFunctionClass = USB_CLASS_CDC,
.bFunctionSubClass = USB_CDC_SUBCLASS_ACM,
.bFunctionProtocol = USB_PROTO_NONE,
.iFunction = USB_STR_COM_PORT,
},
.interface_comm =
{
.bLength = sizeof(struct usb_interface_descriptor),
.bDescriptorType = USB_DTYPE_INTERFACE,
.bInterfaceNumber = USB_INTF_CDC_COMM,
.bAlternateSetting = 0,
.bNumEndpoints = 1,
.bInterfaceClass = USB_CLASS_CDC,
.bInterfaceSubClass = USB_CDC_SUBCLASS_ACM,
.bInterfaceProtocol = USB_PROTO_NONE,
.iInterface = 0,
},
.cdc_header =
{
.bFunctionLength = sizeof(struct usb_cdc_header_desc),
.bDescriptorType = USB_DTYPE_CS_INTERFACE,
.bDescriptorSubType = USB_DTYPE_CDC_HEADER,
.bcdCDC = VERSION_BCD(1, 1, 0),
},
.cdc_acm =
{
.bFunctionLength = sizeof(struct usb_cdc_call_mgmt_desc),
.bDescriptorType = USB_DTYPE_CS_INTERFACE,
.bDescriptorSubType = USB_DTYPE_CDC_CALL_MANAGEMENT,
// .bmCapabilities = USB_CDC_CAP_LINE | USB_CDC_CAP_BRK,
.bmCapabilities = 0,
},
.cdc_call_mgmt =
{
.bFunctionLength = sizeof(struct usb_cdc_acm_desc),
.bDescriptorType = USB_DTYPE_CS_INTERFACE,
.bDescriptorSubType = USB_DTYPE_CDC_ACM,
.bmCapabilities = USB_CDC_CALL_MGMT_CAP_DATA_INTF,
// .bDataInterface = USB_INTF_CDC_DATA,
},
.cdc_union =
{
.bFunctionLength = sizeof(struct usb_cdc_union_desc),
.bDescriptorType = USB_DTYPE_CS_INTERFACE,
.bDescriptorSubType = USB_DTYPE_CDC_UNION,
.bMasterInterface0 = USB_INTF_CDC_COMM,
.bSlaveInterface0 = USB_INTF_CDC_DATA,
},
.ep_comm =
{
.bLength = sizeof(struct usb_endpoint_descriptor),
.bDescriptorType = USB_DTYPE_ENDPOINT,
.bEndpointAddress = HID_EP_IN | DAP_CDC_EP_COMM,
.bmAttributes = USB_EPTYPE_INTERRUPT,
.wMaxPacketSize = DAP_CDC_COMM_EP_SIZE,
.bInterval = DAP_CDC_COMM_INTERVAL,
},
.interface_data =
{
.bLength = sizeof(struct usb_interface_descriptor),
.bDescriptorType = USB_DTYPE_INTERFACE,
.bInterfaceNumber = USB_INTF_CDC_DATA,
.bAlternateSetting = 0,
.bNumEndpoints = 2,
.bInterfaceClass = USB_CLASS_CDC_DATA,
.bInterfaceSubClass = USB_SUBCLASS_NONE,
.bInterfaceProtocol = USB_PROTO_NONE,
.iInterface = NO_DESCRIPTOR,
},
.ep_in =
{
.bLength = sizeof(struct usb_endpoint_descriptor),
.bDescriptorType = USB_DTYPE_ENDPOINT,
.bEndpointAddress = HID_EP_IN | DAP_CDC_EP_SEND,
.bmAttributes = USB_EPTYPE_BULK,
.wMaxPacketSize = DAP_CDC_EP_SIZE,
.bInterval = DAP_CDC_INTERVAL,
},
.ep_out =
{
.bLength = sizeof(struct usb_endpoint_descriptor),
.bDescriptorType = USB_DTYPE_ENDPOINT,
.bEndpointAddress = HID_EP_OUT | DAP_CDC_EP_RECV,
.bmAttributes = USB_EPTYPE_BULK,
.wMaxPacketSize = DAP_CDC_EP_SIZE,
.bInterval = DAP_CDC_INTERVAL,
},
};
// WinUSB
#include "usb_winusb.h"
typedef struct USB_PACK {
usb_binary_object_store_descriptor_t bos;
usb_winusb_capability_descriptor_t winusb;
} usb_bos_hierarchy_t;
typedef struct USB_PACK {
usb_winusb_subset_header_function_t header;
usb_winusb_feature_compatble_id_t comp_id;
usb_winusb_feature_reg_property_guids_t property;
} usb_msos_descriptor_subset_t;
typedef struct USB_PACK {
usb_winusb_set_header_descriptor_t header;
usb_msos_descriptor_subset_t subset;
} usb_msos_descriptor_set_t;
#define USB_DTYPE_BINARY_OBJECT_STORE 15
#define USB_DTYPE_DEVICE_CAPABILITY_DESCRIPTOR 16
#define USB_DC_TYPE_PLATFORM 5
const usb_bos_hierarchy_t usb_bos_hierarchy = {
.bos =
{
.bLength = sizeof(usb_binary_object_store_descriptor_t),
.bDescriptorType = USB_DTYPE_BINARY_OBJECT_STORE,
.wTotalLength = sizeof(usb_bos_hierarchy_t),
.bNumDeviceCaps = 1,
},
.winusb =
{
.bLength = sizeof(usb_winusb_capability_descriptor_t),
.bDescriptorType = USB_DTYPE_DEVICE_CAPABILITY_DESCRIPTOR,
.bDevCapabilityType = USB_DC_TYPE_PLATFORM,
.bReserved = 0,
.PlatformCapabilityUUID = USB_WINUSB_PLATFORM_CAPABILITY_ID,
.dwWindowsVersion = USB_WINUSB_WINDOWS_VERSION,
.wMSOSDescriptorSetTotalLength = sizeof(usb_msos_descriptor_set_t),
.bMS_VendorCode = USB_WINUSB_VENDOR_CODE,
.bAltEnumCode = 0,
},
};
const usb_msos_descriptor_set_t usb_msos_descriptor_set = {
.header =
{
.wLength = sizeof(usb_winusb_set_header_descriptor_t),
.wDescriptorType = USB_WINUSB_SET_HEADER_DESCRIPTOR,
.dwWindowsVersion = USB_WINUSB_WINDOWS_VERSION,
.wDescriptorSetTotalLength = sizeof(usb_msos_descriptor_set_t),
},
.subset =
{
.header =
{
.wLength = sizeof(usb_winusb_subset_header_function_t),
.wDescriptorType = USB_WINUSB_SUBSET_HEADER_FUNCTION,
.bFirstInterface = USB_INTF_BULK,
.bReserved = 0,
.wSubsetLength = sizeof(usb_msos_descriptor_subset_t),
},
.comp_id =
{
.wLength = sizeof(usb_winusb_feature_compatble_id_t),
.wDescriptorType = USB_WINUSB_FEATURE_COMPATBLE_ID,
.CompatibleID = "WINUSB\0\0",
.SubCompatibleID = {0},
},
.property =
{
.wLength = sizeof(usb_winusb_feature_reg_property_guids_t),
.wDescriptorType = USB_WINUSB_FEATURE_REG_PROPERTY,
.wPropertyDataType = USB_WINUSB_PROPERTY_DATA_TYPE_MULTI_SZ,
.wPropertyNameLength =
sizeof(usb_msos_descriptor_set.subset.property.PropertyName),
.PropertyName = {'D', 0, 'e', 0, 'v', 0, 'i', 0, 'c', 0, 'e', 0, 'I', 0,
'n', 0, 't', 0, 'e', 0, 'r', 0, 'f', 0, 'a', 0, 'c', 0,
'e', 0, 'G', 0, 'U', 0, 'I', 0, 'D', 0, 's', 0, 0, 0},
.wPropertyDataLength =
sizeof(usb_msos_descriptor_set.subset.property.PropertyData),
.PropertyData = {'{', 0, 'C', 0, 'D', 0, 'B', 0, '3', 0, 'B', 0, '5', 0,
'A', 0, 'D', 0, '-', 0, '2', 0, '9', 0, '3', 0, 'B', 0,
'-', 0, '4', 0, '6', 0, '6', 0, '3', 0, '-', 0, 'A', 0,
'A', 0, '3', 0, '6', 0, '-', 0, '1', 0, 'A', 0, 'A', 0,
'E', 0, '4', 0, '6', 0, '4', 0, '6', 0, '3', 0, '7', 0,
'7', 0, '6', 0, '}', 0, 0, 0, 0, 0},
},
},
};
typedef struct {
FuriSemaphore* semaphore_v1;
FuriSemaphore* semaphore_v2;
FuriSemaphore* semaphore_cdc;
bool connected;
usbd_device* usb_dev;
DapStateCallback state_callback;
DapRxCallback rx_callback_v1;
DapRxCallback rx_callback_v2;
DapRxCallback rx_callback_cdc;
DapCDCControlLineCallback control_line_callback_cdc;
DapCDCConfigCallback config_callback_cdc;
void* context;
void* context_cdc;
} DAPState;
static DAPState dap_state = {
.semaphore_v1 = NULL,
.semaphore_v2 = NULL,
.semaphore_cdc = NULL,
.connected = false,
.usb_dev = NULL,
.state_callback = NULL,
.rx_callback_v1 = NULL,
.rx_callback_v2 = NULL,
.rx_callback_cdc = NULL,
.control_line_callback_cdc = NULL,
.config_callback_cdc = NULL,
.context = NULL,
.context_cdc = NULL,
};
static struct usb_cdc_line_coding cdc_config = {0};
static uint8_t cdc_ctrl_line_state = 0;
#ifdef DAP_USB_LOG
void furi_console_log_printf(const char* format, ...) _ATTRIBUTE((__format__(__printf__, 1, 2)));
void furi_console_log_printf(const char* format, ...) {
char buffer[256];
va_list args;
va_start(args, format);
vsnprintf(buffer, sizeof(buffer), format, args);
va_end(args);
furi_hal_console_puts(buffer);
furi_hal_console_puts("\r\n");
UNUSED(format);
}
#else
#define furi_console_log_printf(...)
#endif
int32_t dap_v1_usb_tx(uint8_t* buffer, uint8_t size) {
if((dap_state.semaphore_v1 == NULL) || (dap_state.connected == false)) return 0;
furi_check(furi_semaphore_acquire(dap_state.semaphore_v1, FuriWaitForever) == FuriStatusOk);
if(dap_state.connected) {
int32_t len = usbd_ep_write(dap_state.usb_dev, DAP_HID_EP_IN, buffer, size);
furi_console_log_printf("v1 tx %ld", len);
return len;
} else {
return 0;
}
}
int32_t dap_v2_usb_tx(uint8_t* buffer, uint8_t size) {
if((dap_state.semaphore_v2 == NULL) || (dap_state.connected == false)) return 0;
furi_check(furi_semaphore_acquire(dap_state.semaphore_v2, FuriWaitForever) == FuriStatusOk);
if(dap_state.connected) {
int32_t len = usbd_ep_write(dap_state.usb_dev, DAP_HID_EP_BULK_IN, buffer, size);
furi_console_log_printf("v2 tx %ld", len);
return len;
} else {
return 0;
}
}
int32_t dap_cdc_usb_tx(uint8_t* buffer, uint8_t size) {
if((dap_state.semaphore_cdc == NULL) || (dap_state.connected == false)) return 0;
furi_check(furi_semaphore_acquire(dap_state.semaphore_cdc, FuriWaitForever) == FuriStatusOk);
if(dap_state.connected) {
int32_t len = usbd_ep_write(dap_state.usb_dev, HID_EP_IN | DAP_CDC_EP_SEND, buffer, size);
furi_console_log_printf("cdc tx %ld", len);
return len;
} else {
return 0;
}
}
void dap_v1_usb_set_rx_callback(DapRxCallback callback) {
dap_state.rx_callback_v1 = callback;
}
void dap_v2_usb_set_rx_callback(DapRxCallback callback) {
dap_state.rx_callback_v2 = callback;
}
void dap_cdc_usb_set_rx_callback(DapRxCallback callback) {
dap_state.rx_callback_cdc = callback;
}
void dap_cdc_usb_set_control_line_callback(DapCDCControlLineCallback callback) {
dap_state.control_line_callback_cdc = callback;
}
void dap_cdc_usb_set_config_callback(DapCDCConfigCallback callback) {
dap_state.config_callback_cdc = callback;
}
void dap_cdc_usb_set_context(void* context) {
dap_state.context_cdc = context;
}
void dap_common_usb_set_context(void* context) {
dap_state.context = context;
}
void dap_common_usb_set_state_callback(DapStateCallback callback) {
dap_state.state_callback = callback;
}
static void* dap_usb_alloc_string_descr(const char* str) {
furi_assert(str);
size_t len = strlen(str);
size_t wlen = (len + 1) * sizeof(uint16_t);
struct usb_string_descriptor* dev_str_desc = malloc(wlen);
dev_str_desc->bLength = wlen;
dev_str_desc->bDescriptorType = USB_DTYPE_STRING;
for(size_t i = 0; i < len; i++) {
dev_str_desc->wString[i] = str[i];
}
return dev_str_desc;
}
void dap_common_usb_alloc_name(const char* name) {
dev_serial_descr = dap_usb_alloc_string_descr(name);
}
void dap_common_usb_free_name() {
free(dev_serial_descr);
}
static void hid_init(usbd_device* dev, FuriHalUsbInterface* intf, void* ctx);
static void hid_deinit(usbd_device* dev);
static void hid_on_wakeup(usbd_device* dev);
static void hid_on_suspend(usbd_device* dev);
static usbd_respond hid_ep_config(usbd_device* dev, uint8_t cfg);
static usbd_respond hid_control(usbd_device* dev, usbd_ctlreq* req, usbd_rqc_callback* callback);
FuriHalUsbInterface dap_v2_usb_hid = {
.init = hid_init,
.deinit = hid_deinit,
.wakeup = hid_on_wakeup,
.suspend = hid_on_suspend,
.dev_descr = (struct usb_device_descriptor*)&hid_device_desc,
.cfg_descr = (void*)&hid_cfg_desc,
};
static void hid_init(usbd_device* dev, FuriHalUsbInterface* intf, void* ctx) {
UNUSED(intf);
UNUSED(ctx);
dap_v2_usb_hid.str_manuf_descr = (void*)&dev_manuf_descr;
dap_v2_usb_hid.str_prod_descr = (void*)&dev_prod_descr;
dap_v2_usb_hid.str_serial_descr = (void*)dev_serial_descr;
dap_state.usb_dev = dev;
if(dap_state.semaphore_v1 == NULL) dap_state.semaphore_v1 = furi_semaphore_alloc(1, 1);
if(dap_state.semaphore_v2 == NULL) dap_state.semaphore_v2 = furi_semaphore_alloc(1, 1);
if(dap_state.semaphore_cdc == NULL) dap_state.semaphore_cdc = furi_semaphore_alloc(1, 1);
usbd_reg_config(dev, hid_ep_config);
usbd_reg_control(dev, hid_control);
usbd_connect(dev, true);
}
static void hid_deinit(usbd_device* dev) {
dap_state.usb_dev = NULL;
furi_semaphore_free(dap_state.semaphore_v1);
furi_semaphore_free(dap_state.semaphore_v2);
furi_semaphore_free(dap_state.semaphore_cdc);
dap_state.semaphore_v1 = NULL;
dap_state.semaphore_v2 = NULL;
dap_state.semaphore_cdc = NULL;
usbd_reg_config(dev, NULL);
usbd_reg_control(dev, NULL);
}
static void hid_on_wakeup(usbd_device* dev) {
UNUSED(dev);
if(!dap_state.connected) {
dap_state.connected = true;
if(dap_state.state_callback != NULL) {
dap_state.state_callback(dap_state.connected, dap_state.context);
}
}
}
static void hid_on_suspend(usbd_device* dev) {
UNUSED(dev);
if(dap_state.connected) {
dap_state.connected = false;
if(dap_state.state_callback != NULL) {
dap_state.state_callback(dap_state.connected, dap_state.context);
}
}
}
size_t dap_v1_usb_rx(uint8_t* buffer, size_t size) {
size_t len = 0;
if(dap_state.connected) {
len = usbd_ep_read(dap_state.usb_dev, DAP_HID_EP_OUT, buffer, size);
}
return len;
}
size_t dap_v2_usb_rx(uint8_t* buffer, size_t size) {
size_t len = 0;
if(dap_state.connected) {
len = usbd_ep_read(dap_state.usb_dev, DAP_HID_EP_BULK_OUT, buffer, size);
}
return len;
}
size_t dap_cdc_usb_rx(uint8_t* buffer, size_t size) {
size_t len = 0;
if(dap_state.connected) {
len = usbd_ep_read(dap_state.usb_dev, HID_EP_OUT | DAP_CDC_EP_RECV, buffer, size);
}
return len;
}
static void hid_txrx_ep_callback(usbd_device* dev, uint8_t event, uint8_t ep) {
UNUSED(dev);
UNUSED(ep);
switch(event) {
case usbd_evt_eptx:
furi_semaphore_release(dap_state.semaphore_v1);
furi_console_log_printf("hid tx complete");
break;
case usbd_evt_eprx:
if(dap_state.rx_callback_v1 != NULL) {
dap_state.rx_callback_v1(dap_state.context);
}
break;
default:
furi_console_log_printf("hid %d, %d", event, ep);
break;
}
}
static void hid_txrx_ep_bulk_callback(usbd_device* dev, uint8_t event, uint8_t ep) {
UNUSED(dev);
UNUSED(ep);
switch(event) {
case usbd_evt_eptx:
furi_semaphore_release(dap_state.semaphore_v2);
furi_console_log_printf("bulk tx complete");
break;
case usbd_evt_eprx:
if(dap_state.rx_callback_v2 != NULL) {
dap_state.rx_callback_v2(dap_state.context);
}
break;
default:
furi_console_log_printf("bulk %d, %d", event, ep);
break;
}
}
static void cdc_txrx_ep_callback(usbd_device* dev, uint8_t event, uint8_t ep) {
UNUSED(dev);
UNUSED(ep);
switch(event) {
case usbd_evt_eptx:
furi_semaphore_release(dap_state.semaphore_cdc);
furi_console_log_printf("cdc tx complete");
break;
case usbd_evt_eprx:
if(dap_state.rx_callback_cdc != NULL) {
dap_state.rx_callback_cdc(dap_state.context_cdc);
}
break;
default:
furi_console_log_printf("cdc %d, %d", event, ep);
break;
}
}
static usbd_respond hid_ep_config(usbd_device* dev, uint8_t cfg) {
switch(cfg) {
case EP_CFG_DECONFIGURE:
usbd_ep_deconfig(dev, DAP_HID_EP_OUT);
usbd_ep_deconfig(dev, DAP_HID_EP_IN);
usbd_ep_deconfig(dev, DAP_HID_EP_BULK_IN);
usbd_ep_deconfig(dev, DAP_HID_EP_BULK_OUT);
usbd_ep_deconfig(dev, HID_EP_IN | DAP_CDC_EP_COMM);
usbd_ep_deconfig(dev, HID_EP_IN | DAP_CDC_EP_SEND);
usbd_ep_deconfig(dev, HID_EP_OUT | DAP_CDC_EP_RECV);
usbd_reg_endpoint(dev, DAP_HID_EP_OUT, NULL);
usbd_reg_endpoint(dev, DAP_HID_EP_IN, NULL);
usbd_reg_endpoint(dev, DAP_HID_EP_BULK_IN, NULL);
usbd_reg_endpoint(dev, DAP_HID_EP_BULK_OUT, NULL);
usbd_reg_endpoint(dev, HID_EP_IN | DAP_CDC_EP_SEND, 0);
usbd_reg_endpoint(dev, HID_EP_OUT | DAP_CDC_EP_RECV, 0);
return usbd_ack;
case EP_CFG_CONFIGURE:
usbd_ep_config(dev, DAP_HID_EP_IN, USB_EPTYPE_INTERRUPT, DAP_HID_EP_SIZE);
usbd_ep_config(dev, DAP_HID_EP_OUT, USB_EPTYPE_INTERRUPT, DAP_HID_EP_SIZE);
usbd_ep_config(dev, DAP_HID_EP_BULK_OUT, USB_EPTYPE_BULK, DAP_HID_EP_SIZE);
usbd_ep_config(dev, DAP_HID_EP_BULK_IN, USB_EPTYPE_BULK, DAP_HID_EP_SIZE);
usbd_ep_config(dev, HID_EP_OUT | DAP_CDC_EP_RECV, USB_EPTYPE_BULK, DAP_CDC_EP_SIZE);
usbd_ep_config(dev, HID_EP_IN | DAP_CDC_EP_SEND, USB_EPTYPE_BULK, DAP_CDC_EP_SIZE);
usbd_ep_config(dev, HID_EP_IN | DAP_CDC_EP_COMM, USB_EPTYPE_INTERRUPT, DAP_CDC_EP_SIZE);
usbd_reg_endpoint(dev, DAP_HID_EP_IN, hid_txrx_ep_callback);
usbd_reg_endpoint(dev, DAP_HID_EP_OUT, hid_txrx_ep_callback);
usbd_reg_endpoint(dev, DAP_HID_EP_BULK_OUT, hid_txrx_ep_bulk_callback);
usbd_reg_endpoint(dev, DAP_HID_EP_BULK_IN, hid_txrx_ep_bulk_callback);
usbd_reg_endpoint(dev, HID_EP_OUT | DAP_CDC_EP_RECV, cdc_txrx_ep_callback);
usbd_reg_endpoint(dev, HID_EP_IN | DAP_CDC_EP_SEND, cdc_txrx_ep_callback);
// usbd_ep_write(dev, DAP_HID_EP_IN, NULL, 0);
// usbd_ep_write(dev, DAP_HID_EP_BULK_IN, NULL, 0);
// usbd_ep_write(dev, HID_EP_IN | DAP_CDC_EP_SEND, NULL, 0);
return usbd_ack;
default:
return usbd_fail;
}
}
#ifdef DAP_USB_LOG
static void dump_request_type(uint8_t type) {
switch(type & USB_REQ_DIRECTION) {
case USB_REQ_HOSTTODEV:
furi_hal_console_puts("host to dev, ");
break;
case USB_REQ_DEVTOHOST:
furi_hal_console_puts("dev to host, ");
break;
}
switch(type & USB_REQ_TYPE) {
case USB_REQ_STANDARD:
furi_hal_console_puts("standard, ");
break;
case USB_REQ_CLASS:
furi_hal_console_puts("class, ");
break;
case USB_REQ_VENDOR:
furi_hal_console_puts("vendor, ");
break;
}
switch(type & USB_REQ_RECIPIENT) {
case USB_REQ_DEVICE:
furi_hal_console_puts("device");
break;
case USB_REQ_INTERFACE:
furi_hal_console_puts("interface");
break;
case USB_REQ_ENDPOINT:
furi_hal_console_puts("endpoint");
break;
case USB_REQ_OTHER:
furi_hal_console_puts("other");
break;
}
furi_hal_console_puts("\r\n");
}
#else
#define dump_request_type(...)
#endif
static usbd_respond hid_control(usbd_device* dev, usbd_ctlreq* req, usbd_rqc_callback* callback) {
UNUSED(callback);
dump_request_type(req->bmRequestType);
furi_console_log_printf(
"control: RT %02x, R %02x, V %04x, I %04x, L %04x",
req->bmRequestType,
req->bRequest,
req->wValue,
req->wIndex,
req->wLength);
if(((USB_REQ_RECIPIENT | USB_REQ_TYPE | USB_REQ_DIRECTION) & req->bmRequestType) ==
(USB_REQ_STANDARD | USB_REQ_VENDOR | USB_REQ_DEVTOHOST)) {
// vendor request, device to host
furi_console_log_printf("vendor request");
if(USB_WINUSB_VENDOR_CODE == req->bRequest) {
// WINUSB request
if(USB_WINUSB_DESCRIPTOR_INDEX == req->wIndex) {
furi_console_log_printf("WINUSB descriptor");
uint16_t length = req->wLength;
if(length > sizeof(usb_msos_descriptor_set_t)) {
length = sizeof(usb_msos_descriptor_set_t);
}
dev->status.data_ptr = (uint8_t*)&usb_msos_descriptor_set;
dev->status.data_count = length;
return usbd_ack;
}
}
}
if(((USB_REQ_RECIPIENT | USB_REQ_TYPE) & req->bmRequestType) ==
(USB_REQ_STANDARD | USB_REQ_DEVICE)) {
// device request
if(req->bRequest == USB_STD_GET_DESCRIPTOR) {
const uint8_t dtype = req->wValue >> 8;
const uint8_t dnumber = req->wValue & 0xFF;
// get string descriptor
if(USB_DTYPE_STRING == dtype) {
if(dnumber == USB_STR_CMSIS_DAP_V1) {
furi_console_log_printf("str CMSIS-DAP v1");
dev->status.data_ptr = (uint8_t*)&dev_dap_v1_descr;
dev->status.data_count = dev_dap_v1_descr.bLength;
return usbd_ack;
} else if(dnumber == USB_STR_CMSIS_DAP_V2) {
furi_console_log_printf("str CMSIS-DAP v2");
dev->status.data_ptr = (uint8_t*)&dev_dap_v2_descr;
dev->status.data_count = dev_dap_v2_descr.bLength;
return usbd_ack;
} else if(dnumber == USB_STR_COM_PORT) {
furi_console_log_printf("str COM port");
dev->status.data_ptr = (uint8_t*)&dev_com_descr;
dev->status.data_count = dev_com_descr.bLength;
return usbd_ack;
}
} else if(USB_DTYPE_BINARY_OBJECT_STORE == dtype) {
furi_console_log_printf("BOS descriptor");
uint16_t length = req->wLength;
if(length > sizeof(usb_bos_hierarchy_t)) {
length = sizeof(usb_bos_hierarchy_t);
}
dev->status.data_ptr = (uint8_t*)&usb_bos_hierarchy;
dev->status.data_count = length;
return usbd_ack;
}
}
}
if(((USB_REQ_RECIPIENT | USB_REQ_TYPE) & req->bmRequestType) ==
(USB_REQ_INTERFACE | USB_REQ_CLASS) &&
req->wIndex == 0) {
// class request
switch(req->bRequest) {
// get hid descriptor
case USB_HID_GETREPORT:
furi_console_log_printf("get report");
return usbd_fail;
// set hid idle
case USB_HID_SETIDLE:
furi_console_log_printf("set idle");
return usbd_ack;
default:
break;
}
}
if(((USB_REQ_RECIPIENT | USB_REQ_TYPE) & req->bmRequestType) ==
(USB_REQ_INTERFACE | USB_REQ_CLASS) &&
req->wIndex == 2) {
// class request
switch(req->bRequest) {
// control line state
case USB_CDC_SET_CONTROL_LINE_STATE:
furi_console_log_printf("set control line state");
cdc_ctrl_line_state = req->wValue;
if(dap_state.control_line_callback_cdc != NULL) {
dap_state.control_line_callback_cdc(cdc_ctrl_line_state, dap_state.context_cdc);
}
return usbd_ack;
// set cdc line coding
case USB_CDC_SET_LINE_CODING:
furi_console_log_printf("set line coding");
memcpy(&cdc_config, req->data, sizeof(cdc_config));
if(dap_state.config_callback_cdc != NULL) {
dap_state.config_callback_cdc(&cdc_config, dap_state.context_cdc);
}
return usbd_ack;
// get cdc line coding
case USB_CDC_GET_LINE_CODING:
furi_console_log_printf("get line coding");
dev->status.data_ptr = &cdc_config;
dev->status.data_count = sizeof(cdc_config);
return usbd_ack;
default:
break;
}
}
if(((USB_REQ_RECIPIENT | USB_REQ_TYPE) & req->bmRequestType) ==
(USB_REQ_INTERFACE | USB_REQ_STANDARD) &&
req->wIndex == 0 && req->bRequest == USB_STD_GET_DESCRIPTOR) {
// standard request
switch(req->wValue >> 8) {
// get hid descriptor
case USB_DTYPE_HID:
furi_console_log_printf("get hid descriptor");
dev->status.data_ptr = (uint8_t*)&(hid_cfg_desc.hid);
dev->status.data_count = sizeof(hid_cfg_desc.hid);
return usbd_ack;
// get hid report descriptor
case USB_DTYPE_HID_REPORT:
furi_console_log_printf("get hid report descriptor");
dev->status.data_ptr = (uint8_t*)hid_report_desc;
dev->status.data_count = sizeof(hid_report_desc);
return usbd_ack;
default:
break;
}
}
return usbd_fail;
}

View File

@@ -0,0 +1,53 @@
#pragma once
#include <furi_hal_usb.h>
#include <usb_cdc.h>
extern FuriHalUsbInterface dap_v2_usb_hid;
// receive callback type
typedef void (*DapRxCallback)(void* context);
typedef void (*DapStateCallback)(bool state, void* context);
/************************************ V1 ***************************************/
int32_t dap_v1_usb_tx(uint8_t* buffer, uint8_t size);
size_t dap_v1_usb_rx(uint8_t* buffer, size_t size);
void dap_v1_usb_set_rx_callback(DapRxCallback callback);
/************************************ V2 ***************************************/
int32_t dap_v2_usb_tx(uint8_t* buffer, uint8_t size);
size_t dap_v2_usb_rx(uint8_t* buffer, size_t size);
void dap_v2_usb_set_rx_callback(DapRxCallback callback);
/************************************ CDC **************************************/
typedef void (*DapCDCControlLineCallback)(uint8_t state, void* context);
typedef void (*DapCDCConfigCallback)(struct usb_cdc_line_coding* config, void* context);
int32_t dap_cdc_usb_tx(uint8_t* buffer, uint8_t size);
size_t dap_cdc_usb_rx(uint8_t* buffer, size_t size);
void dap_cdc_usb_set_rx_callback(DapRxCallback callback);
void dap_cdc_usb_set_control_line_callback(DapCDCControlLineCallback callback);
void dap_cdc_usb_set_config_callback(DapCDCConfigCallback callback);
void dap_cdc_usb_set_context(void* context);
/*********************************** Common ************************************/
void dap_common_usb_set_context(void* context);
void dap_common_usb_set_state_callback(DapStateCallback callback);
void dap_common_usb_alloc_name(const char* name);
void dap_common_usb_free_name();

View File

@@ -0,0 +1,143 @@
#pragma once
#include <stdint.h>
/*- Definitions -------------------------------------------------------------*/
#define USB_PACK __attribute__((packed))
#define USB_WINUSB_VENDOR_CODE 0x20
#define USB_WINUSB_WINDOWS_VERSION 0x06030000 // Windows 8.1
#define USB_WINUSB_PLATFORM_CAPABILITY_ID \
{ \
0xdf, 0x60, 0xdd, 0xd8, 0x89, 0x45, 0xc7, 0x4c, 0x9c, 0xd2, 0x65, 0x9d, 0x9e, 0x64, 0x8a, \
0x9f \
}
enum // WinUSB Microsoft OS 2.0 descriptor request codes
{
USB_WINUSB_DESCRIPTOR_INDEX = 0x07,
USB_WINUSB_SET_ALT_ENUMERATION = 0x08,
};
enum // wDescriptorType
{
USB_WINUSB_SET_HEADER_DESCRIPTOR = 0x00,
USB_WINUSB_SUBSET_HEADER_CONFIGURATION = 0x01,
USB_WINUSB_SUBSET_HEADER_FUNCTION = 0x02,
USB_WINUSB_FEATURE_COMPATBLE_ID = 0x03,
USB_WINUSB_FEATURE_REG_PROPERTY = 0x04,
USB_WINUSB_FEATURE_MIN_RESUME_TIME = 0x05,
USB_WINUSB_FEATURE_MODEL_ID = 0x06,
USB_WINUSB_FEATURE_CCGP_DEVICE = 0x07,
USB_WINUSB_FEATURE_VENDOR_REVISION = 0x08,
};
enum // wPropertyDataType
{
USB_WINUSB_PROPERTY_DATA_TYPE_SZ = 1,
USB_WINUSB_PROPERTY_DATA_TYPE_EXPAND_SZ = 2,
USB_WINUSB_PROPERTY_DATA_TYPE_BINARY = 3,
USB_WINUSB_PROPERTY_DATA_TYPE_DWORD_LITTLE_ENDIAN = 4,
USB_WINUSB_PROPERTY_DATA_TYPE_DWORD_BIG_ENDIAN = 5,
USB_WINUSB_PROPERTY_DATA_TYPE_LINK = 6,
USB_WINUSB_PROPERTY_DATA_TYPE_MULTI_SZ = 7,
};
/*- Types BOS -------------------------------------------------------------------*/
typedef struct USB_PACK {
uint8_t bLength;
uint8_t bDescriptorType;
uint16_t wTotalLength;
uint8_t bNumDeviceCaps;
} usb_binary_object_store_descriptor_t;
/*- Types WinUSB -------------------------------------------------------------------*/
typedef struct USB_PACK {
uint8_t bLength;
uint8_t bDescriptorType;
uint8_t bDevCapabilityType;
uint8_t bReserved;
uint8_t PlatformCapabilityUUID[16];
uint32_t dwWindowsVersion;
uint16_t wMSOSDescriptorSetTotalLength;
uint8_t bMS_VendorCode;
uint8_t bAltEnumCode;
} usb_winusb_capability_descriptor_t;
typedef struct USB_PACK {
uint16_t wLength;
uint16_t wDescriptorType;
uint32_t dwWindowsVersion;
uint16_t wDescriptorSetTotalLength;
} usb_winusb_set_header_descriptor_t;
typedef struct USB_PACK {
uint16_t wLength;
uint16_t wDescriptorType;
uint8_t bConfigurationValue;
uint8_t bReserved;
uint16_t wTotalLength;
} usb_winusb_subset_header_configuration_t;
typedef struct USB_PACK {
uint16_t wLength;
uint16_t wDescriptorType;
uint8_t bFirstInterface;
uint8_t bReserved;
uint16_t wSubsetLength;
} usb_winusb_subset_header_function_t;
typedef struct USB_PACK {
uint16_t wLength;
uint16_t wDescriptorType;
uint8_t CompatibleID[8];
uint8_t SubCompatibleID[8];
} usb_winusb_feature_compatble_id_t;
typedef struct USB_PACK {
uint16_t wLength;
uint16_t wDescriptorType;
uint16_t wPropertyDataType;
//uint16_t wPropertyNameLength;
//uint8_t PropertyName[...];
//uint16_t wPropertyDataLength
//uint8_t PropertyData[...];
} usb_winusb_feature_reg_property_t;
typedef struct USB_PACK {
uint16_t wLength;
uint16_t wDescriptorType;
uint16_t wPropertyDataType;
uint16_t wPropertyNameLength;
uint8_t PropertyName[42];
uint16_t wPropertyDataLength;
uint8_t PropertyData[80];
} usb_winusb_feature_reg_property_guids_t;
typedef struct USB_PACK {
uint16_t wLength;
uint16_t wDescriptorType;
uint8_t bResumeRecoveryTime;
uint8_t bResumeSignalingTime;
} usb_winusb_feature_min_resume_time_t;
typedef struct USB_PACK {
uint16_t wLength;
uint16_t wDescriptorType;
uint8_t ModelID[16];
} usb_winusb_feature_model_id_t;
typedef struct USB_PACK {
uint16_t wLength;
uint16_t wDescriptorType;
} usb_winusb_feature_ccgp_device_t;
typedef struct USB_PACK {
uint16_t wLength;
uint16_t wDescriptorType;
uint16_t VendorRevision;
} usb_winusb_feature_vendor_revision_t;

View File

@@ -0,0 +1,24 @@
App(
appid="hid_usb",
name="Remote",
apptype=FlipperAppType.EXTERNAL,
entry_point="hid_usb_app",
stack_size=1 * 1024,
fap_category="USB",
fap_icon="hid_usb_10px.png",
fap_icon_assets="assets",
fap_icon_assets_symbol="hid",
)
App(
appid="hid_ble",
name="Remote",
apptype=FlipperAppType.EXTERNAL,
entry_point="hid_ble_app",
stack_size=1 * 1024,
fap_category="Bluetooth",
fap_icon="hid_ble_10px.png",
fap_icon_assets="assets",
fap_icon_assets_symbol="hid",
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 657 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

418
applications/external/hid_app/hid.c vendored Normal file
View File

@@ -0,0 +1,418 @@
#include "hid.h"
#include "views.h"
#include <notification/notification_messages.h>
#include <dolphin/dolphin.h>
#define TAG "HidApp"
enum HidDebugSubmenuIndex {
HidSubmenuIndexKeynote,
HidSubmenuIndexKeyboard,
HidSubmenuIndexMedia,
HidSubmenuIndexTikTok,
HidSubmenuIndexMouse,
HidSubmenuIndexMouseJiggler,
};
static void hid_submenu_callback(void* context, uint32_t index) {
furi_assert(context);
Hid* app = context;
if(index == HidSubmenuIndexKeynote) {
app->view_id = HidViewKeynote;
view_dispatcher_switch_to_view(app->view_dispatcher, HidViewKeynote);
} else if(index == HidSubmenuIndexKeyboard) {
app->view_id = HidViewKeyboard;
view_dispatcher_switch_to_view(app->view_dispatcher, HidViewKeyboard);
} else if(index == HidSubmenuIndexMedia) {
app->view_id = HidViewMedia;
view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMedia);
} else if(index == HidSubmenuIndexMouse) {
app->view_id = HidViewMouse;
view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMouse);
} else if(index == HidSubmenuIndexTikTok) {
app->view_id = BtHidViewTikTok;
view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewTikTok);
} else if(index == HidSubmenuIndexMouseJiggler) {
app->view_id = HidViewMouseJiggler;
view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMouseJiggler);
}
}
static void bt_hid_connection_status_changed_callback(BtStatus status, void* context) {
furi_assert(context);
Hid* hid = context;
bool connected = (status == BtStatusConnected);
if(hid->transport == HidTransportBle) {
if(connected) {
notification_internal_message(hid->notifications, &sequence_set_blue_255);
} else {
notification_internal_message(hid->notifications, &sequence_reset_blue);
}
}
hid_keynote_set_connected_status(hid->hid_keynote, connected);
hid_keyboard_set_connected_status(hid->hid_keyboard, connected);
hid_media_set_connected_status(hid->hid_media, connected);
hid_mouse_set_connected_status(hid->hid_mouse, connected);
hid_mouse_jiggler_set_connected_status(hid->hid_mouse_jiggler, connected);
hid_tiktok_set_connected_status(hid->hid_tiktok, connected);
}
static void hid_dialog_callback(DialogExResult result, void* context) {
furi_assert(context);
Hid* app = context;
if(result == DialogExResultLeft) {
view_dispatcher_stop(app->view_dispatcher);
} else if(result == DialogExResultRight) {
view_dispatcher_switch_to_view(app->view_dispatcher, app->view_id); // Show last view
} else if(result == DialogExResultCenter) {
view_dispatcher_switch_to_view(app->view_dispatcher, HidViewSubmenu);
}
}
static uint32_t hid_exit_confirm_view(void* context) {
UNUSED(context);
return HidViewExitConfirm;
}
static uint32_t hid_exit(void* context) {
UNUSED(context);
return VIEW_NONE;
}
Hid* hid_alloc(HidTransport transport) {
Hid* app = malloc(sizeof(Hid));
app->transport = transport;
// Gui
app->gui = furi_record_open(RECORD_GUI);
// Bt
app->bt = furi_record_open(RECORD_BT);
// Notifications
app->notifications = furi_record_open(RECORD_NOTIFICATION);
// View dispatcher
app->view_dispatcher = view_dispatcher_alloc();
view_dispatcher_enable_queue(app->view_dispatcher);
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
// Device Type Submenu view
app->device_type_submenu = submenu_alloc();
submenu_add_item(
app->device_type_submenu, "Keynote", HidSubmenuIndexKeynote, hid_submenu_callback, app);
submenu_add_item(
app->device_type_submenu, "Keyboard", HidSubmenuIndexKeyboard, hid_submenu_callback, app);
submenu_add_item(
app->device_type_submenu, "Media", HidSubmenuIndexMedia, hid_submenu_callback, app);
submenu_add_item(
app->device_type_submenu, "Mouse", HidSubmenuIndexMouse, hid_submenu_callback, app);
if(app->transport == HidTransportBle) {
submenu_add_item(
app->device_type_submenu,
"TikTok Controller",
HidSubmenuIndexTikTok,
hid_submenu_callback,
app);
}
submenu_add_item(
app->device_type_submenu,
"Mouse Jiggler",
HidSubmenuIndexMouseJiggler,
hid_submenu_callback,
app);
view_set_previous_callback(submenu_get_view(app->device_type_submenu), hid_exit);
view_dispatcher_add_view(
app->view_dispatcher, HidViewSubmenu, submenu_get_view(app->device_type_submenu));
app->view_id = HidViewSubmenu;
view_dispatcher_switch_to_view(app->view_dispatcher, app->view_id);
return app;
}
Hid* hid_app_alloc_view(void* context) {
furi_assert(context);
Hid* app = context;
// Dialog view
app->dialog = dialog_ex_alloc();
dialog_ex_set_result_callback(app->dialog, hid_dialog_callback);
dialog_ex_set_context(app->dialog, app);
dialog_ex_set_left_button_text(app->dialog, "Exit");
dialog_ex_set_right_button_text(app->dialog, "Stay");
dialog_ex_set_center_button_text(app->dialog, "Menu");
dialog_ex_set_header(app->dialog, "Close Current App?", 16, 12, AlignLeft, AlignTop);
view_dispatcher_add_view(
app->view_dispatcher, HidViewExitConfirm, dialog_ex_get_view(app->dialog));
// Keynote view
app->hid_keynote = hid_keynote_alloc(app);
view_set_previous_callback(hid_keynote_get_view(app->hid_keynote), hid_exit_confirm_view);
view_dispatcher_add_view(
app->view_dispatcher, HidViewKeynote, hid_keynote_get_view(app->hid_keynote));
// Keyboard view
app->hid_keyboard = hid_keyboard_alloc(app);
view_set_previous_callback(hid_keyboard_get_view(app->hid_keyboard), hid_exit_confirm_view);
view_dispatcher_add_view(
app->view_dispatcher, HidViewKeyboard, hid_keyboard_get_view(app->hid_keyboard));
// Media view
app->hid_media = hid_media_alloc(app);
view_set_previous_callback(hid_media_get_view(app->hid_media), hid_exit_confirm_view);
view_dispatcher_add_view(
app->view_dispatcher, HidViewMedia, hid_media_get_view(app->hid_media));
// TikTok view
app->hid_tiktok = hid_tiktok_alloc(app);
view_set_previous_callback(hid_tiktok_get_view(app->hid_tiktok), hid_exit_confirm_view);
view_dispatcher_add_view(
app->view_dispatcher, BtHidViewTikTok, hid_tiktok_get_view(app->hid_tiktok));
// Mouse view
app->hid_mouse = hid_mouse_alloc(app);
view_set_previous_callback(hid_mouse_get_view(app->hid_mouse), hid_exit_confirm_view);
view_dispatcher_add_view(
app->view_dispatcher, HidViewMouse, hid_mouse_get_view(app->hid_mouse));
// Mouse jiggler view
app->hid_mouse_jiggler = hid_mouse_jiggler_alloc(app);
view_set_previous_callback(
hid_mouse_jiggler_get_view(app->hid_mouse_jiggler), hid_exit_confirm_view);
view_dispatcher_add_view(
app->view_dispatcher,
HidViewMouseJiggler,
hid_mouse_jiggler_get_view(app->hid_mouse_jiggler));
return app;
}
void hid_free(Hid* app) {
furi_assert(app);
// Reset notification
if(app->transport == HidTransportBle) {
notification_internal_message(app->notifications, &sequence_reset_blue);
}
// Free views
view_dispatcher_remove_view(app->view_dispatcher, HidViewSubmenu);
submenu_free(app->device_type_submenu);
view_dispatcher_remove_view(app->view_dispatcher, HidViewExitConfirm);
dialog_ex_free(app->dialog);
view_dispatcher_remove_view(app->view_dispatcher, HidViewKeynote);
hid_keynote_free(app->hid_keynote);
view_dispatcher_remove_view(app->view_dispatcher, HidViewKeyboard);
hid_keyboard_free(app->hid_keyboard);
view_dispatcher_remove_view(app->view_dispatcher, HidViewMedia);
hid_media_free(app->hid_media);
view_dispatcher_remove_view(app->view_dispatcher, HidViewMouse);
hid_mouse_free(app->hid_mouse);
view_dispatcher_remove_view(app->view_dispatcher, HidViewMouseJiggler);
hid_mouse_jiggler_free(app->hid_mouse_jiggler);
view_dispatcher_remove_view(app->view_dispatcher, BtHidViewTikTok);
hid_tiktok_free(app->hid_tiktok);
view_dispatcher_free(app->view_dispatcher);
// Close records
furi_record_close(RECORD_GUI);
app->gui = NULL;
furi_record_close(RECORD_NOTIFICATION);
app->notifications = NULL;
furi_record_close(RECORD_BT);
app->bt = NULL;
// Free rest
free(app);
}
void hid_hal_keyboard_press(Hid* instance, uint16_t event) {
furi_assert(instance);
if(instance->transport == HidTransportBle) {
furi_hal_bt_hid_kb_press(event);
} else if(instance->transport == HidTransportUsb) {
furi_hal_hid_kb_press(event);
} else {
furi_crash(NULL);
}
}
void hid_hal_keyboard_release(Hid* instance, uint16_t event) {
furi_assert(instance);
if(instance->transport == HidTransportBle) {
furi_hal_bt_hid_kb_release(event);
} else if(instance->transport == HidTransportUsb) {
furi_hal_hid_kb_release(event);
} else {
furi_crash(NULL);
}
}
void hid_hal_keyboard_release_all(Hid* instance) {
furi_assert(instance);
if(instance->transport == HidTransportBle) {
furi_hal_bt_hid_kb_release_all();
} else if(instance->transport == HidTransportUsb) {
furi_hal_hid_kb_release_all();
} else {
furi_crash(NULL);
}
}
void hid_hal_consumer_key_press(Hid* instance, uint16_t event) {
furi_assert(instance);
if(instance->transport == HidTransportBle) {
furi_hal_bt_hid_consumer_key_press(event);
} else if(instance->transport == HidTransportUsb) {
furi_hal_hid_consumer_key_press(event);
} else {
furi_crash(NULL);
}
}
void hid_hal_consumer_key_release(Hid* instance, uint16_t event) {
furi_assert(instance);
if(instance->transport == HidTransportBle) {
furi_hal_bt_hid_consumer_key_release(event);
} else if(instance->transport == HidTransportUsb) {
furi_hal_hid_consumer_key_release(event);
} else {
furi_crash(NULL);
}
}
void hid_hal_consumer_key_release_all(Hid* instance) {
furi_assert(instance);
if(instance->transport == HidTransportBle) {
furi_hal_bt_hid_consumer_key_release_all();
} else if(instance->transport == HidTransportUsb) {
furi_hal_hid_kb_release_all();
} else {
furi_crash(NULL);
}
}
void hid_hal_mouse_move(Hid* instance, int8_t dx, int8_t dy) {
furi_assert(instance);
if(instance->transport == HidTransportBle) {
furi_hal_bt_hid_mouse_move(dx, dy);
} else if(instance->transport == HidTransportUsb) {
furi_hal_hid_mouse_move(dx, dy);
} else {
furi_crash(NULL);
}
}
void hid_hal_mouse_scroll(Hid* instance, int8_t delta) {
furi_assert(instance);
if(instance->transport == HidTransportBle) {
furi_hal_bt_hid_mouse_scroll(delta);
} else if(instance->transport == HidTransportUsb) {
furi_hal_hid_mouse_scroll(delta);
} else {
furi_crash(NULL);
}
}
void hid_hal_mouse_press(Hid* instance, uint16_t event) {
furi_assert(instance);
if(instance->transport == HidTransportBle) {
furi_hal_bt_hid_mouse_press(event);
} else if(instance->transport == HidTransportUsb) {
furi_hal_hid_mouse_press(event);
} else {
furi_crash(NULL);
}
}
void hid_hal_mouse_release(Hid* instance, uint16_t event) {
furi_assert(instance);
if(instance->transport == HidTransportBle) {
furi_hal_bt_hid_mouse_release(event);
} else if(instance->transport == HidTransportUsb) {
furi_hal_hid_mouse_release(event);
} else {
furi_crash(NULL);
}
}
void hid_hal_mouse_release_all(Hid* instance) {
furi_assert(instance);
if(instance->transport == HidTransportBle) {
furi_hal_bt_hid_mouse_release_all();
} else if(instance->transport == HidTransportUsb) {
furi_hal_hid_mouse_release(HID_MOUSE_BTN_LEFT);
furi_hal_hid_mouse_release(HID_MOUSE_BTN_RIGHT);
} else {
furi_crash(NULL);
}
}
int32_t hid_usb_app(void* p) {
UNUSED(p);
Hid* app = hid_alloc(HidTransportUsb);
app = hid_app_alloc_view(app);
FuriHalUsbInterface* usb_mode_prev = furi_hal_usb_get_config();
furi_hal_usb_unlock();
furi_check(furi_hal_usb_set_config(&usb_hid, NULL) == true);
bt_hid_connection_status_changed_callback(BtStatusConnected, app);
DOLPHIN_DEED(DolphinDeedPluginStart);
view_dispatcher_run(app->view_dispatcher);
furi_hal_usb_set_config(usb_mode_prev, NULL);
hid_free(app);
return 0;
}
int32_t hid_ble_app(void* p) {
UNUSED(p);
Hid* app = hid_alloc(HidTransportBle);
app = hid_app_alloc_view(app);
bt_disconnect(app->bt);
// Wait 2nd core to update nvm storage
furi_delay_ms(200);
// Migrate data from old sd-card folder
Storage* storage = furi_record_open(RECORD_STORAGE);
storage_common_migrate(
storage,
EXT_PATH("apps/Tools/" HID_BT_KEYS_STORAGE_NAME),
APP_DATA_PATH(HID_BT_KEYS_STORAGE_NAME));
bt_keys_storage_set_storage_path(app->bt, APP_DATA_PATH(HID_BT_KEYS_STORAGE_NAME));
furi_record_close(RECORD_STORAGE);
if(!bt_set_profile(app->bt, BtProfileHidKeyboard)) {
FURI_LOG_E(TAG, "Failed to switch to HID profile");
}
furi_hal_bt_start_advertising();
bt_set_status_changed_callback(app->bt, bt_hid_connection_status_changed_callback, app);
DOLPHIN_DEED(DolphinDeedPluginStart);
view_dispatcher_run(app->view_dispatcher);
bt_set_status_changed_callback(app->bt, NULL, NULL);
bt_disconnect(app->bt);
// Wait 2nd core to update nvm storage
furi_delay_ms(200);
bt_keys_storage_set_default_path(app->bt);
if(!bt_set_profile(app->bt, BtProfileSerial)) {
FURI_LOG_E(TAG, "Failed to switch to Serial profile");
}
hid_free(app);
return 0;
}

65
applications/external/hid_app/hid.h vendored Normal file
View File

@@ -0,0 +1,65 @@
#pragma once
#include <furi.h>
#include <furi_hal_bt.h>
#include <furi_hal_bt_hid.h>
#include <furi_hal_usb.h>
#include <furi_hal_usb_hid.h>
#include <bt/bt_service/bt.h>
#include <gui/gui.h>
#include <gui/view.h>
#include <gui/view_dispatcher.h>
#include <notification/notification.h>
#include <storage/storage.h>
#include <gui/modules/submenu.h>
#include <gui/modules/dialog_ex.h>
#include <gui/modules/popup.h>
#include "views/hid_keynote.h"
#include "views/hid_keyboard.h"
#include "views/hid_media.h"
#include "views/hid_mouse.h"
#include "views/hid_mouse_jiggler.h"
#include "views/hid_tiktok.h"
#define HID_BT_KEYS_STORAGE_NAME ".bt_hid.keys"
typedef enum {
HidTransportUsb,
HidTransportBle,
} HidTransport;
typedef struct Hid Hid;
struct Hid {
Bt* bt;
Gui* gui;
NotificationApp* notifications;
ViewDispatcher* view_dispatcher;
Submenu* device_type_submenu;
DialogEx* dialog;
HidKeynote* hid_keynote;
HidKeyboard* hid_keyboard;
HidMedia* hid_media;
HidMouse* hid_mouse;
HidMouseJiggler* hid_mouse_jiggler;
HidTikTok* hid_tiktok;
HidTransport transport;
uint32_t view_id;
};
void hid_hal_keyboard_press(Hid* instance, uint16_t event);
void hid_hal_keyboard_release(Hid* instance, uint16_t event);
void hid_hal_keyboard_release_all(Hid* instance);
void hid_hal_consumer_key_press(Hid* instance, uint16_t event);
void hid_hal_consumer_key_release(Hid* instance, uint16_t event);
void hid_hal_consumer_key_release_all(Hid* instance);
void hid_hal_mouse_move(Hid* instance, int8_t dx, int8_t dy);
void hid_hal_mouse_scroll(Hid* instance, int8_t delta);
void hid_hal_mouse_press(Hid* instance, uint16_t event);
void hid_hal_mouse_release(Hid* instance, uint16_t event);
void hid_hal_mouse_release_all(Hid* instance);

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 969 B

10
applications/external/hid_app/views.h vendored Normal file
View File

@@ -0,0 +1,10 @@
typedef enum {
HidViewSubmenu,
HidViewKeynote,
HidViewKeyboard,
HidViewMedia,
HidViewMouse,
HidViewMouseJiggler,
BtHidViewTikTok,
HidViewExitConfirm,
} HidView;

View File

@@ -0,0 +1,411 @@
#include "hid_keyboard.h"
#include <furi.h>
#include <gui/elements.h>
#include <gui/icon_i.h>
#include "../hid.h"
#include "hid_icons.h"
#define TAG "HidKeyboard"
struct HidKeyboard {
View* view;
Hid* hid;
};
typedef struct {
bool shift;
bool alt;
bool ctrl;
bool gui;
uint8_t x;
uint8_t y;
uint8_t last_key_code;
uint16_t modifier_code;
bool ok_pressed;
bool back_pressed;
bool connected;
char key_string[5];
HidTransport transport;
} HidKeyboardModel;
typedef struct {
uint8_t width;
char* key;
const Icon* icon;
char* shift_key;
uint8_t value;
} HidKeyboardKey;
typedef struct {
int8_t x;
int8_t y;
} HidKeyboardPoint;
// 4 BY 12
#define MARGIN_TOP 0
#define MARGIN_LEFT 4
#define KEY_WIDTH 9
#define KEY_HEIGHT 12
#define KEY_PADDING 1
#define ROW_COUNT 7
#define COLUMN_COUNT 12
// 0 width items are not drawn, but there value is used
const HidKeyboardKey hid_keyboard_keyset[ROW_COUNT][COLUMN_COUNT] = {
{
{.width = 1, .icon = &I_ButtonF1_5x8, .value = HID_KEYBOARD_F1},
{.width = 1, .icon = &I_ButtonF2_5x8, .value = HID_KEYBOARD_F2},
{.width = 1, .icon = &I_ButtonF3_5x8, .value = HID_KEYBOARD_F3},
{.width = 1, .icon = &I_ButtonF4_5x8, .value = HID_KEYBOARD_F4},
{.width = 1, .icon = &I_ButtonF5_5x8, .value = HID_KEYBOARD_F5},
{.width = 1, .icon = &I_ButtonF6_5x8, .value = HID_KEYBOARD_F6},
{.width = 1, .icon = &I_ButtonF7_5x8, .value = HID_KEYBOARD_F7},
{.width = 1, .icon = &I_ButtonF8_5x8, .value = HID_KEYBOARD_F8},
{.width = 1, .icon = &I_ButtonF9_5x8, .value = HID_KEYBOARD_F9},
{.width = 1, .icon = &I_ButtonF10_5x8, .value = HID_KEYBOARD_F10},
{.width = 1, .icon = &I_ButtonF11_5x8, .value = HID_KEYBOARD_F11},
{.width = 1, .icon = &I_ButtonF12_5x8, .value = HID_KEYBOARD_F12},
},
{
{.width = 1, .icon = NULL, .key = "1", .shift_key = "!", .value = HID_KEYBOARD_1},
{.width = 1, .icon = NULL, .key = "2", .shift_key = "@", .value = HID_KEYBOARD_2},
{.width = 1, .icon = NULL, .key = "3", .shift_key = "#", .value = HID_KEYBOARD_3},
{.width = 1, .icon = NULL, .key = "4", .shift_key = "$", .value = HID_KEYBOARD_4},
{.width = 1, .icon = NULL, .key = "5", .shift_key = "%", .value = HID_KEYBOARD_5},
{.width = 1, .icon = NULL, .key = "6", .shift_key = "^", .value = HID_KEYBOARD_6},
{.width = 1, .icon = NULL, .key = "7", .shift_key = "&", .value = HID_KEYBOARD_7},
{.width = 1, .icon = NULL, .key = "8", .shift_key = "*", .value = HID_KEYBOARD_8},
{.width = 1, .icon = NULL, .key = "9", .shift_key = "(", .value = HID_KEYBOARD_9},
{.width = 1, .icon = NULL, .key = "0", .shift_key = ")", .value = HID_KEYBOARD_0},
{.width = 2, .icon = &I_Pin_arrow_left_9x7, .value = HID_KEYBOARD_DELETE},
{.width = 0, .value = HID_KEYBOARD_DELETE},
},
{
{.width = 1, .icon = NULL, .key = "q", .shift_key = "Q", .value = HID_KEYBOARD_Q},
{.width = 1, .icon = NULL, .key = "w", .shift_key = "W", .value = HID_KEYBOARD_W},
{.width = 1, .icon = NULL, .key = "e", .shift_key = "E", .value = HID_KEYBOARD_E},
{.width = 1, .icon = NULL, .key = "r", .shift_key = "R", .value = HID_KEYBOARD_R},
{.width = 1, .icon = NULL, .key = "t", .shift_key = "T", .value = HID_KEYBOARD_T},
{.width = 1, .icon = NULL, .key = "y", .shift_key = "Y", .value = HID_KEYBOARD_Y},
{.width = 1, .icon = NULL, .key = "u", .shift_key = "U", .value = HID_KEYBOARD_U},
{.width = 1, .icon = NULL, .key = "i", .shift_key = "I", .value = HID_KEYBOARD_I},
{.width = 1, .icon = NULL, .key = "o", .shift_key = "O", .value = HID_KEYBOARD_O},
{.width = 1, .icon = NULL, .key = "p", .shift_key = "P", .value = HID_KEYBOARD_P},
{.width = 1, .icon = NULL, .key = "[", .shift_key = "{", .value = HID_KEYBOARD_OPEN_BRACKET},
{.width = 1,
.icon = NULL,
.key = "]",
.shift_key = "}",
.value = HID_KEYBOARD_CLOSE_BRACKET},
},
{
{.width = 1, .icon = NULL, .key = "a", .shift_key = "A", .value = HID_KEYBOARD_A},
{.width = 1, .icon = NULL, .key = "s", .shift_key = "S", .value = HID_KEYBOARD_S},
{.width = 1, .icon = NULL, .key = "d", .shift_key = "D", .value = HID_KEYBOARD_D},
{.width = 1, .icon = NULL, .key = "f", .shift_key = "F", .value = HID_KEYBOARD_F},
{.width = 1, .icon = NULL, .key = "g", .shift_key = "G", .value = HID_KEYBOARD_G},
{.width = 1, .icon = NULL, .key = "h", .shift_key = "H", .value = HID_KEYBOARD_H},
{.width = 1, .icon = NULL, .key = "j", .shift_key = "J", .value = HID_KEYBOARD_J},
{.width = 1, .icon = NULL, .key = "k", .shift_key = "K", .value = HID_KEYBOARD_K},
{.width = 1, .icon = NULL, .key = "l", .shift_key = "L", .value = HID_KEYBOARD_L},
{.width = 1, .icon = NULL, .key = ";", .shift_key = ":", .value = HID_KEYBOARD_SEMICOLON},
{.width = 2, .icon = &I_Pin_arrow_right_9x7, .value = HID_KEYBOARD_RETURN},
{.width = 0, .value = HID_KEYBOARD_RETURN},
},
{
{.width = 1, .icon = NULL, .key = "z", .shift_key = "Z", .value = HID_KEYBOARD_Z},
{.width = 1, .icon = NULL, .key = "x", .shift_key = "X", .value = HID_KEYBOARD_X},
{.width = 1, .icon = NULL, .key = "c", .shift_key = "C", .value = HID_KEYBOARD_C},
{.width = 1, .icon = NULL, .key = "v", .shift_key = "V", .value = HID_KEYBOARD_V},
{.width = 1, .icon = NULL, .key = "b", .shift_key = "B", .value = HID_KEYBOARD_B},
{.width = 1, .icon = NULL, .key = "n", .shift_key = "N", .value = HID_KEYBOARD_N},
{.width = 1, .icon = NULL, .key = "m", .shift_key = "M", .value = HID_KEYBOARD_M},
{.width = 1, .icon = NULL, .key = "/", .shift_key = "?", .value = HID_KEYBOARD_SLASH},
{.width = 1, .icon = NULL, .key = "\\", .shift_key = "|", .value = HID_KEYBOARD_BACKSLASH},
{.width = 1, .icon = NULL, .key = "`", .shift_key = "~", .value = HID_KEYBOARD_GRAVE_ACCENT},
{.width = 1, .icon = &I_ButtonUp_7x4, .value = HID_KEYBOARD_UP_ARROW},
{.width = 1, .icon = NULL, .key = "-", .shift_key = "_", .value = HID_KEYBOARD_MINUS},
},
{
{.width = 1, .icon = &I_Pin_arrow_up_7x9, .value = HID_KEYBOARD_L_SHIFT},
{.width = 1, .icon = NULL, .key = ",", .shift_key = "<", .value = HID_KEYBOARD_COMMA},
{.width = 1, .icon = NULL, .key = ".", .shift_key = ">", .value = HID_KEYBOARD_DOT},
{.width = 4, .icon = NULL, .key = " ", .value = HID_KEYBOARD_SPACEBAR},
{.width = 0, .value = HID_KEYBOARD_SPACEBAR},
{.width = 0, .value = HID_KEYBOARD_SPACEBAR},
{.width = 0, .value = HID_KEYBOARD_SPACEBAR},
{.width = 1, .icon = NULL, .key = "'", .shift_key = "\"", .value = HID_KEYBOARD_APOSTROPHE},
{.width = 1, .icon = NULL, .key = "=", .shift_key = "+", .value = HID_KEYBOARD_EQUAL_SIGN},
{.width = 1, .icon = &I_ButtonLeft_4x7, .value = HID_KEYBOARD_LEFT_ARROW},
{.width = 1, .icon = &I_ButtonDown_7x4, .value = HID_KEYBOARD_DOWN_ARROW},
{.width = 1, .icon = &I_ButtonRight_4x7, .value = HID_KEYBOARD_RIGHT_ARROW},
},
{
{.width = 3, .icon = NULL, .key = "Ctrl", .value = HID_KEYBOARD_L_CTRL},
{.width = 0, .icon = NULL, .value = HID_KEYBOARD_L_CTRL},
{.width = 0, .icon = NULL, .value = HID_KEYBOARD_L_CTRL},
{.width = 3, .icon = NULL, .key = "Alt", .value = HID_KEYBOARD_L_ALT},
{.width = 0, .icon = NULL, .value = HID_KEYBOARD_L_ALT},
{.width = 0, .icon = NULL, .value = HID_KEYBOARD_L_ALT},
{.width = 3, .icon = NULL, .key = "Cmd", .value = HID_KEYBOARD_L_GUI},
{.width = 0, .icon = NULL, .value = HID_KEYBOARD_L_GUI},
{.width = 0, .icon = NULL, .value = HID_KEYBOARD_L_GUI},
{.width = 3, .icon = NULL, .key = "Tab", .value = HID_KEYBOARD_TAB},
{.width = 0, .icon = NULL, .value = HID_KEYBOARD_TAB},
{.width = 0, .icon = NULL, .value = HID_KEYBOARD_TAB},
},
};
static void hid_keyboard_to_upper(char* str) {
while(*str) {
*str = toupper((unsigned char)*str);
str++;
}
}
static void hid_keyboard_draw_key(
Canvas* canvas,
HidKeyboardModel* model,
uint8_t x,
uint8_t y,
HidKeyboardKey key,
bool selected) {
if(!key.width) return;
canvas_set_color(canvas, ColorBlack);
uint8_t keyWidth = KEY_WIDTH * key.width + KEY_PADDING * (key.width - 1);
if(selected) {
// Draw a filled box
elements_slightly_rounded_box(
canvas,
MARGIN_LEFT + x * (KEY_WIDTH + KEY_PADDING),
MARGIN_TOP + y * (KEY_HEIGHT + KEY_PADDING),
keyWidth,
KEY_HEIGHT);
canvas_set_color(canvas, ColorWhite);
} else {
// Draw a framed box
elements_slightly_rounded_frame(
canvas,
MARGIN_LEFT + x * (KEY_WIDTH + KEY_PADDING),
MARGIN_TOP + y * (KEY_HEIGHT + KEY_PADDING),
keyWidth,
KEY_HEIGHT);
}
if(key.icon != NULL) {
// Draw the icon centered on the button
canvas_draw_icon(
canvas,
MARGIN_LEFT + x * (KEY_WIDTH + KEY_PADDING) + keyWidth / 2 - key.icon->width / 2,
MARGIN_TOP + y * (KEY_HEIGHT + KEY_PADDING) + KEY_HEIGHT / 2 - key.icon->height / 2,
key.icon);
} else {
// If shift is toggled use the shift key when available
strcpy(model->key_string, (model->shift && key.shift_key != 0) ? key.shift_key : key.key);
// Upper case if ctrl or alt was toggled true
if((model->ctrl && key.value == HID_KEYBOARD_L_CTRL) ||
(model->alt && key.value == HID_KEYBOARD_L_ALT) ||
(model->gui && key.value == HID_KEYBOARD_L_GUI)) {
hid_keyboard_to_upper(model->key_string);
}
canvas_draw_str_aligned(
canvas,
MARGIN_LEFT + x * (KEY_WIDTH + KEY_PADDING) + keyWidth / 2 + 1,
MARGIN_TOP + y * (KEY_HEIGHT + KEY_PADDING) + KEY_HEIGHT / 2,
AlignCenter,
AlignCenter,
model->key_string);
}
}
static void hid_keyboard_draw_callback(Canvas* canvas, void* context) {
furi_assert(context);
HidKeyboardModel* model = context;
// Header
if((!model->connected) && (model->transport == HidTransportBle)) {
canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15);
canvas_set_font(canvas, FontPrimary);
elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Keyboard");
canvas_draw_icon(canvas, 68, 3, &I_Pin_back_arrow_10x8);
canvas_set_font(canvas, FontSecondary);
elements_multiline_text_aligned(canvas, 127, 4, AlignRight, AlignTop, "Hold to exit");
elements_multiline_text_aligned(
canvas, 4, 60, AlignLeft, AlignBottom, "Waiting for Connection...");
return; // Dont render the keyboard if we are not yet connected
}
canvas_set_font(canvas, FontKeyboard);
// Start shifting the all keys up if on the next row (Scrolling)
uint8_t initY = model->y == 0 ? 0 : 1;
if(model->y > 5) {
initY = model->y - 4;
}
for(uint8_t y = initY; y < ROW_COUNT; y++) {
const HidKeyboardKey* keyboardKeyRow = hid_keyboard_keyset[y];
uint8_t x = 0;
for(uint8_t i = 0; i < COLUMN_COUNT; i++) {
HidKeyboardKey key = keyboardKeyRow[i];
// Select when the button is hovered
// Select if the button is hovered within its width
// Select if back is clicked and its the backspace key
// Deselect when the button clicked or not hovered
bool keySelected = (x <= model->x && model->x < (x + key.width)) && y == model->y;
bool backSelected = model->back_pressed && key.value == HID_KEYBOARD_DELETE;
hid_keyboard_draw_key(
canvas,
model,
x,
y - initY,
key,
(!model->ok_pressed && keySelected) || backSelected);
x += key.width;
}
}
}
static uint8_t hid_keyboard_get_selected_key(HidKeyboardModel* model) {
HidKeyboardKey key = hid_keyboard_keyset[model->y][model->x];
return key.value;
}
static void hid_keyboard_get_select_key(HidKeyboardModel* model, HidKeyboardPoint delta) {
// Keep going until a valid spot is found, this allows for nulls and zero width keys in the map
do {
const int delta_sum = model->y + delta.y;
model->y = delta_sum < 0 ? ROW_COUNT - 1 : delta_sum % ROW_COUNT;
} while(delta.y != 0 && hid_keyboard_keyset[model->y][model->x].value == 0);
do {
const int delta_sum = model->x + delta.x;
model->x = delta_sum < 0 ? COLUMN_COUNT - 1 : delta_sum % COLUMN_COUNT;
} while(delta.x != 0 && hid_keyboard_keyset[model->y][model->x].width ==
0); // Skip zero width keys, pretend they are one key
}
static void hid_keyboard_process(HidKeyboard* hid_keyboard, InputEvent* event) {
with_view_model(
hid_keyboard->view,
HidKeyboardModel * model,
{
if(event->key == InputKeyOk) {
if(event->type == InputTypePress) {
model->ok_pressed = true;
} else if(event->type == InputTypeLong || event->type == InputTypeShort) {
model->last_key_code = hid_keyboard_get_selected_key(model);
// Toggle the modifier key when clicked, and click the key
if(model->last_key_code == HID_KEYBOARD_L_SHIFT) {
model->shift = !model->shift;
if(model->shift)
model->modifier_code |= KEY_MOD_LEFT_SHIFT;
else
model->modifier_code &= ~KEY_MOD_LEFT_SHIFT;
} else if(model->last_key_code == HID_KEYBOARD_L_ALT) {
model->alt = !model->alt;
if(model->alt)
model->modifier_code |= KEY_MOD_LEFT_ALT;
else
model->modifier_code &= ~KEY_MOD_LEFT_ALT;
} else if(model->last_key_code == HID_KEYBOARD_L_CTRL) {
model->ctrl = !model->ctrl;
if(model->ctrl)
model->modifier_code |= KEY_MOD_LEFT_CTRL;
else
model->modifier_code &= ~KEY_MOD_LEFT_CTRL;
} else if(model->last_key_code == HID_KEYBOARD_L_GUI) {
model->gui = !model->gui;
if(model->gui)
model->modifier_code |= KEY_MOD_LEFT_GUI;
else
model->modifier_code &= ~KEY_MOD_LEFT_GUI;
}
hid_hal_keyboard_press(
hid_keyboard->hid, model->modifier_code | model->last_key_code);
} else if(event->type == InputTypeRelease) {
// Release happens after short and long presses
hid_hal_keyboard_release(
hid_keyboard->hid, model->modifier_code | model->last_key_code);
model->ok_pressed = false;
}
} else if(event->key == InputKeyBack) {
// If back is pressed for a short time, backspace
if(event->type == InputTypePress) {
model->back_pressed = true;
} else if(event->type == InputTypeShort) {
hid_hal_keyboard_press(hid_keyboard->hid, HID_KEYBOARD_DELETE);
hid_hal_keyboard_release(hid_keyboard->hid, HID_KEYBOARD_DELETE);
} else if(event->type == InputTypeRelease) {
model->back_pressed = false;
}
} else if(event->type == InputTypePress || event->type == InputTypeRepeat) {
// Cycle the selected keys
if(event->key == InputKeyUp) {
hid_keyboard_get_select_key(model, (HidKeyboardPoint){.x = 0, .y = -1});
} else if(event->key == InputKeyDown) {
hid_keyboard_get_select_key(model, (HidKeyboardPoint){.x = 0, .y = 1});
} else if(event->key == InputKeyLeft) {
hid_keyboard_get_select_key(model, (HidKeyboardPoint){.x = -1, .y = 0});
} else if(event->key == InputKeyRight) {
hid_keyboard_get_select_key(model, (HidKeyboardPoint){.x = 1, .y = 0});
}
}
},
true);
}
static bool hid_keyboard_input_callback(InputEvent* event, void* context) {
furi_assert(context);
HidKeyboard* hid_keyboard = context;
bool consumed = false;
if(event->type == InputTypeLong && event->key == InputKeyBack) {
hid_hal_keyboard_release_all(hid_keyboard->hid);
} else {
hid_keyboard_process(hid_keyboard, event);
consumed = true;
}
return consumed;
}
HidKeyboard* hid_keyboard_alloc(Hid* bt_hid) {
HidKeyboard* hid_keyboard = malloc(sizeof(HidKeyboard));
hid_keyboard->view = view_alloc();
hid_keyboard->hid = bt_hid;
view_set_context(hid_keyboard->view, hid_keyboard);
view_allocate_model(hid_keyboard->view, ViewModelTypeLocking, sizeof(HidKeyboardModel));
view_set_draw_callback(hid_keyboard->view, hid_keyboard_draw_callback);
view_set_input_callback(hid_keyboard->view, hid_keyboard_input_callback);
with_view_model(
hid_keyboard->view,
HidKeyboardModel * model,
{
model->transport = bt_hid->transport;
model->y = 1;
},
true);
return hid_keyboard;
}
void hid_keyboard_free(HidKeyboard* hid_keyboard) {
furi_assert(hid_keyboard);
view_free(hid_keyboard->view);
free(hid_keyboard);
}
View* hid_keyboard_get_view(HidKeyboard* hid_keyboard) {
furi_assert(hid_keyboard);
return hid_keyboard->view;
}
void hid_keyboard_set_connected_status(HidKeyboard* hid_keyboard, bool connected) {
furi_assert(hid_keyboard);
with_view_model(
hid_keyboard->view, HidKeyboardModel * model, { model->connected = connected; }, true);
}

View File

@@ -0,0 +1,14 @@
#pragma once
#include <gui/view.h>
typedef struct Hid Hid;
typedef struct HidKeyboard HidKeyboard;
HidKeyboard* hid_keyboard_alloc(Hid* bt_hid);
void hid_keyboard_free(HidKeyboard* hid_keyboard);
View* hid_keyboard_get_view(HidKeyboard* hid_keyboard);
void hid_keyboard_set_connected_status(HidKeyboard* hid_keyboard, bool connected);

View File

@@ -0,0 +1,214 @@
#include "hid_keynote.h"
#include <gui/elements.h>
#include "../hid.h"
#include "hid_icons.h"
#define TAG "HidKeynote"
struct HidKeynote {
View* view;
Hid* hid;
};
typedef struct {
bool left_pressed;
bool up_pressed;
bool right_pressed;
bool down_pressed;
bool ok_pressed;
bool back_pressed;
bool connected;
HidTransport transport;
} HidKeynoteModel;
static void hid_keynote_draw_arrow(Canvas* canvas, uint8_t x, uint8_t y, CanvasDirection dir) {
canvas_draw_triangle(canvas, x, y, 5, 3, dir);
if(dir == CanvasDirectionBottomToTop) {
canvas_draw_line(canvas, x, y + 6, x, y - 1);
} else if(dir == CanvasDirectionTopToBottom) {
canvas_draw_line(canvas, x, y - 6, x, y + 1);
} else if(dir == CanvasDirectionRightToLeft) {
canvas_draw_line(canvas, x + 6, y, x - 1, y);
} else if(dir == CanvasDirectionLeftToRight) {
canvas_draw_line(canvas, x - 6, y, x + 1, y);
}
}
static void hid_keynote_draw_callback(Canvas* canvas, void* context) {
furi_assert(context);
HidKeynoteModel* model = context;
// Header
if(model->transport == HidTransportBle) {
if(model->connected) {
canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15);
} else {
canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15);
}
}
canvas_set_font(canvas, FontPrimary);
elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Keynote");
canvas_draw_icon(canvas, 68, 2, &I_Pin_back_arrow_10x8);
canvas_set_font(canvas, FontSecondary);
elements_multiline_text_aligned(canvas, 127, 3, AlignRight, AlignTop, "Hold to exit");
// Up
canvas_draw_icon(canvas, 21, 24, &I_Button_18x18);
if(model->up_pressed) {
elements_slightly_rounded_box(canvas, 24, 26, 13, 13);
canvas_set_color(canvas, ColorWhite);
}
hid_keynote_draw_arrow(canvas, 30, 30, CanvasDirectionBottomToTop);
canvas_set_color(canvas, ColorBlack);
// Down
canvas_draw_icon(canvas, 21, 45, &I_Button_18x18);
if(model->down_pressed) {
elements_slightly_rounded_box(canvas, 24, 47, 13, 13);
canvas_set_color(canvas, ColorWhite);
}
hid_keynote_draw_arrow(canvas, 30, 55, CanvasDirectionTopToBottom);
canvas_set_color(canvas, ColorBlack);
// Left
canvas_draw_icon(canvas, 0, 45, &I_Button_18x18);
if(model->left_pressed) {
elements_slightly_rounded_box(canvas, 3, 47, 13, 13);
canvas_set_color(canvas, ColorWhite);
}
hid_keynote_draw_arrow(canvas, 7, 53, CanvasDirectionRightToLeft);
canvas_set_color(canvas, ColorBlack);
// Right
canvas_draw_icon(canvas, 42, 45, &I_Button_18x18);
if(model->right_pressed) {
elements_slightly_rounded_box(canvas, 45, 47, 13, 13);
canvas_set_color(canvas, ColorWhite);
}
hid_keynote_draw_arrow(canvas, 53, 53, CanvasDirectionLeftToRight);
canvas_set_color(canvas, ColorBlack);
// Ok
canvas_draw_icon(canvas, 63, 25, &I_Space_65x18);
if(model->ok_pressed) {
elements_slightly_rounded_box(canvas, 66, 27, 60, 13);
canvas_set_color(canvas, ColorWhite);
}
canvas_draw_icon(canvas, 74, 29, &I_Ok_btn_9x9);
elements_multiline_text_aligned(canvas, 91, 36, AlignLeft, AlignBottom, "Space");
canvas_set_color(canvas, ColorBlack);
// Back
canvas_draw_icon(canvas, 63, 45, &I_Space_65x18);
if(model->back_pressed) {
elements_slightly_rounded_box(canvas, 66, 47, 60, 13);
canvas_set_color(canvas, ColorWhite);
}
canvas_draw_icon(canvas, 74, 49, &I_Pin_back_arrow_10x8);
elements_multiline_text_aligned(canvas, 91, 57, AlignLeft, AlignBottom, "Back");
}
static void hid_keynote_process(HidKeynote* hid_keynote, InputEvent* event) {
with_view_model(
hid_keynote->view,
HidKeynoteModel * model,
{
if(event->type == InputTypePress) {
if(event->key == InputKeyUp) {
model->up_pressed = true;
hid_hal_keyboard_press(hid_keynote->hid, HID_KEYBOARD_UP_ARROW);
} else if(event->key == InputKeyDown) {
model->down_pressed = true;
hid_hal_keyboard_press(hid_keynote->hid, HID_KEYBOARD_DOWN_ARROW);
} else if(event->key == InputKeyLeft) {
model->left_pressed = true;
hid_hal_keyboard_press(hid_keynote->hid, HID_KEYBOARD_LEFT_ARROW);
} else if(event->key == InputKeyRight) {
model->right_pressed = true;
hid_hal_keyboard_press(hid_keynote->hid, HID_KEYBOARD_RIGHT_ARROW);
} else if(event->key == InputKeyOk) {
model->ok_pressed = true;
hid_hal_keyboard_press(hid_keynote->hid, HID_KEYBOARD_SPACEBAR);
} else if(event->key == InputKeyBack) {
model->back_pressed = true;
}
} else if(event->type == InputTypeRelease) {
if(event->key == InputKeyUp) {
model->up_pressed = false;
hid_hal_keyboard_release(hid_keynote->hid, HID_KEYBOARD_UP_ARROW);
} else if(event->key == InputKeyDown) {
model->down_pressed = false;
hid_hal_keyboard_release(hid_keynote->hid, HID_KEYBOARD_DOWN_ARROW);
} else if(event->key == InputKeyLeft) {
model->left_pressed = false;
hid_hal_keyboard_release(hid_keynote->hid, HID_KEYBOARD_LEFT_ARROW);
} else if(event->key == InputKeyRight) {
model->right_pressed = false;
hid_hal_keyboard_release(hid_keynote->hid, HID_KEYBOARD_RIGHT_ARROW);
} else if(event->key == InputKeyOk) {
model->ok_pressed = false;
hid_hal_keyboard_release(hid_keynote->hid, HID_KEYBOARD_SPACEBAR);
} else if(event->key == InputKeyBack) {
model->back_pressed = false;
}
} else if(event->type == InputTypeShort) {
if(event->key == InputKeyBack) {
hid_hal_keyboard_press(hid_keynote->hid, HID_KEYBOARD_DELETE);
hid_hal_keyboard_release(hid_keynote->hid, HID_KEYBOARD_DELETE);
hid_hal_consumer_key_press(hid_keynote->hid, HID_CONSUMER_AC_BACK);
hid_hal_consumer_key_release(hid_keynote->hid, HID_CONSUMER_AC_BACK);
}
}
},
true);
}
static bool hid_keynote_input_callback(InputEvent* event, void* context) {
furi_assert(context);
HidKeynote* hid_keynote = context;
bool consumed = false;
if(event->type == InputTypeLong && event->key == InputKeyBack) {
hid_hal_keyboard_release_all(hid_keynote->hid);
} else {
hid_keynote_process(hid_keynote, event);
consumed = true;
}
return consumed;
}
HidKeynote* hid_keynote_alloc(Hid* hid) {
HidKeynote* hid_keynote = malloc(sizeof(HidKeynote));
hid_keynote->view = view_alloc();
hid_keynote->hid = hid;
view_set_context(hid_keynote->view, hid_keynote);
view_allocate_model(hid_keynote->view, ViewModelTypeLocking, sizeof(HidKeynoteModel));
view_set_draw_callback(hid_keynote->view, hid_keynote_draw_callback);
view_set_input_callback(hid_keynote->view, hid_keynote_input_callback);
with_view_model(
hid_keynote->view, HidKeynoteModel * model, { model->transport = hid->transport; }, true);
return hid_keynote;
}
void hid_keynote_free(HidKeynote* hid_keynote) {
furi_assert(hid_keynote);
view_free(hid_keynote->view);
free(hid_keynote);
}
View* hid_keynote_get_view(HidKeynote* hid_keynote) {
furi_assert(hid_keynote);
return hid_keynote->view;
}
void hid_keynote_set_connected_status(HidKeynote* hid_keynote, bool connected) {
furi_assert(hid_keynote);
with_view_model(
hid_keynote->view, HidKeynoteModel * model, { model->connected = connected; }, true);
}

View File

@@ -0,0 +1,14 @@
#pragma once
#include <gui/view.h>
typedef struct Hid Hid;
typedef struct HidKeynote HidKeynote;
HidKeynote* hid_keynote_alloc(Hid* bt_hid);
void hid_keynote_free(HidKeynote* hid_keynote);
View* hid_keynote_get_view(HidKeynote* hid_keynote);
void hid_keynote_set_connected_status(HidKeynote* hid_keynote, bool connected);

View File

@@ -0,0 +1,218 @@
#include "hid_media.h"
#include <furi.h>
#include <furi_hal_bt_hid.h>
#include <furi_hal_usb_hid.h>
#include <gui/elements.h>
#include "../hid.h"
#include "hid_icons.h"
#define TAG "HidMedia"
struct HidMedia {
View* view;
Hid* hid;
};
typedef struct {
bool left_pressed;
bool up_pressed;
bool right_pressed;
bool down_pressed;
bool ok_pressed;
bool connected;
HidTransport transport;
} HidMediaModel;
static void hid_media_draw_arrow(Canvas* canvas, uint8_t x, uint8_t y, CanvasDirection dir) {
canvas_draw_triangle(canvas, x, y, 5, 3, dir);
if(dir == CanvasDirectionBottomToTop) {
canvas_draw_dot(canvas, x, y - 1);
} else if(dir == CanvasDirectionTopToBottom) {
canvas_draw_dot(canvas, x, y + 1);
} else if(dir == CanvasDirectionRightToLeft) {
canvas_draw_dot(canvas, x - 1, y);
} else if(dir == CanvasDirectionLeftToRight) {
canvas_draw_dot(canvas, x + 1, y);
}
}
static void hid_media_draw_callback(Canvas* canvas, void* context) {
furi_assert(context);
HidMediaModel* model = context;
// Header
if(model->transport == HidTransportBle) {
if(model->connected) {
canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15);
} else {
canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15);
}
}
canvas_set_font(canvas, FontPrimary);
elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Media");
canvas_set_font(canvas, FontSecondary);
// Keypad circles
canvas_draw_icon(canvas, 76, 8, &I_Circles_47x47);
// Up
if(model->up_pressed) {
canvas_set_bitmap_mode(canvas, 1);
canvas_draw_icon(canvas, 93, 9, &I_Pressed_Button_13x13);
canvas_set_bitmap_mode(canvas, 0);
canvas_set_color(canvas, ColorWhite);
}
canvas_draw_icon(canvas, 96, 12, &I_Volup_8x6);
canvas_set_color(canvas, ColorBlack);
// Down
if(model->down_pressed) {
canvas_set_bitmap_mode(canvas, 1);
canvas_draw_icon(canvas, 93, 41, &I_Pressed_Button_13x13);
canvas_set_bitmap_mode(canvas, 0);
canvas_set_color(canvas, ColorWhite);
}
canvas_draw_icon(canvas, 96, 45, &I_Voldwn_6x6);
canvas_set_color(canvas, ColorBlack);
// Left
if(model->left_pressed) {
canvas_set_bitmap_mode(canvas, 1);
canvas_draw_icon(canvas, 77, 25, &I_Pressed_Button_13x13);
canvas_set_bitmap_mode(canvas, 0);
canvas_set_color(canvas, ColorWhite);
}
hid_media_draw_arrow(canvas, 82, 31, CanvasDirectionRightToLeft);
hid_media_draw_arrow(canvas, 86, 31, CanvasDirectionRightToLeft);
canvas_set_color(canvas, ColorBlack);
// Right
if(model->right_pressed) {
canvas_set_bitmap_mode(canvas, 1);
canvas_draw_icon(canvas, 109, 25, &I_Pressed_Button_13x13);
canvas_set_bitmap_mode(canvas, 0);
canvas_set_color(canvas, ColorWhite);
}
hid_media_draw_arrow(canvas, 112, 31, CanvasDirectionLeftToRight);
hid_media_draw_arrow(canvas, 116, 31, CanvasDirectionLeftToRight);
canvas_set_color(canvas, ColorBlack);
// Ok
if(model->ok_pressed) {
canvas_draw_icon(canvas, 93, 25, &I_Pressed_Button_13x13);
canvas_set_color(canvas, ColorWhite);
}
hid_media_draw_arrow(canvas, 96, 31, CanvasDirectionLeftToRight);
canvas_draw_line(canvas, 100, 29, 100, 33);
canvas_draw_line(canvas, 102, 29, 102, 33);
canvas_set_color(canvas, ColorBlack);
// Exit
canvas_draw_icon(canvas, 0, 54, &I_Pin_back_arrow_10x8);
canvas_set_font(canvas, FontSecondary);
elements_multiline_text_aligned(canvas, 13, 62, AlignLeft, AlignBottom, "Hold to exit");
}
static void hid_media_process_press(HidMedia* hid_media, InputEvent* event) {
with_view_model(
hid_media->view,
HidMediaModel * model,
{
if(event->key == InputKeyUp) {
model->up_pressed = true;
hid_hal_consumer_key_press(hid_media->hid, HID_CONSUMER_VOLUME_INCREMENT);
} else if(event->key == InputKeyDown) {
model->down_pressed = true;
hid_hal_consumer_key_press(hid_media->hid, HID_CONSUMER_VOLUME_DECREMENT);
} else if(event->key == InputKeyLeft) {
model->left_pressed = true;
hid_hal_consumer_key_press(hid_media->hid, HID_CONSUMER_SCAN_PREVIOUS_TRACK);
} else if(event->key == InputKeyRight) {
model->right_pressed = true;
hid_hal_consumer_key_press(hid_media->hid, HID_CONSUMER_SCAN_NEXT_TRACK);
} else if(event->key == InputKeyOk) {
model->ok_pressed = true;
hid_hal_consumer_key_press(hid_media->hid, HID_CONSUMER_PLAY_PAUSE);
}
},
true);
}
static void hid_media_process_release(HidMedia* hid_media, InputEvent* event) {
with_view_model(
hid_media->view,
HidMediaModel * model,
{
if(event->key == InputKeyUp) {
model->up_pressed = false;
hid_hal_consumer_key_release(hid_media->hid, HID_CONSUMER_VOLUME_INCREMENT);
} else if(event->key == InputKeyDown) {
model->down_pressed = false;
hid_hal_consumer_key_release(hid_media->hid, HID_CONSUMER_VOLUME_DECREMENT);
} else if(event->key == InputKeyLeft) {
model->left_pressed = false;
hid_hal_consumer_key_release(hid_media->hid, HID_CONSUMER_SCAN_PREVIOUS_TRACK);
} else if(event->key == InputKeyRight) {
model->right_pressed = false;
hid_hal_consumer_key_release(hid_media->hid, HID_CONSUMER_SCAN_NEXT_TRACK);
} else if(event->key == InputKeyOk) {
model->ok_pressed = false;
hid_hal_consumer_key_release(hid_media->hid, HID_CONSUMER_PLAY_PAUSE);
}
},
true);
}
static bool hid_media_input_callback(InputEvent* event, void* context) {
furi_assert(context);
HidMedia* hid_media = context;
bool consumed = false;
if(event->type == InputTypePress) {
hid_media_process_press(hid_media, event);
consumed = true;
} else if(event->type == InputTypeRelease) {
hid_media_process_release(hid_media, event);
consumed = true;
} else if(event->type == InputTypeShort) {
if(event->key == InputKeyBack) {
hid_hal_consumer_key_release_all(hid_media->hid);
}
}
return consumed;
}
HidMedia* hid_media_alloc(Hid* hid) {
HidMedia* hid_media = malloc(sizeof(HidMedia));
hid_media->view = view_alloc();
hid_media->hid = hid;
view_set_context(hid_media->view, hid_media);
view_allocate_model(hid_media->view, ViewModelTypeLocking, sizeof(HidMediaModel));
view_set_draw_callback(hid_media->view, hid_media_draw_callback);
view_set_input_callback(hid_media->view, hid_media_input_callback);
with_view_model(
hid_media->view, HidMediaModel * model, { model->transport = hid->transport; }, true);
return hid_media;
}
void hid_media_free(HidMedia* hid_media) {
furi_assert(hid_media);
view_free(hid_media->view);
free(hid_media);
}
View* hid_media_get_view(HidMedia* hid_media) {
furi_assert(hid_media);
return hid_media->view;
}
void hid_media_set_connected_status(HidMedia* hid_media, bool connected) {
furi_assert(hid_media);
with_view_model(
hid_media->view, HidMediaModel * model, { model->connected = connected; }, true);
}

View File

@@ -0,0 +1,13 @@
#pragma once
#include <gui/view.h>
typedef struct HidMedia HidMedia;
HidMedia* hid_media_alloc();
void hid_media_free(HidMedia* hid_media);
View* hid_media_get_view(HidMedia* hid_media);
void hid_media_set_connected_status(HidMedia* hid_media, bool connected);

View File

@@ -0,0 +1,226 @@
#include "hid_mouse.h"
#include <gui/elements.h>
#include "../hid.h"
#include "hid_icons.h"
#define TAG "HidMouse"
struct HidMouse {
View* view;
Hid* hid;
};
typedef struct {
bool left_pressed;
bool up_pressed;
bool right_pressed;
bool down_pressed;
bool left_mouse_pressed;
bool left_mouse_held;
bool right_mouse_pressed;
bool connected;
HidTransport transport;
} HidMouseModel;
static void hid_mouse_draw_callback(Canvas* canvas, void* context) {
furi_assert(context);
HidMouseModel* model = context;
// Header
if(model->transport == HidTransportBle) {
if(model->connected) {
canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15);
} else {
canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15);
}
}
canvas_set_font(canvas, FontPrimary);
elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Mouse");
canvas_set_font(canvas, FontSecondary);
if(model->left_mouse_held == true) {
elements_multiline_text_aligned(canvas, 0, 62, AlignLeft, AlignBottom, "Selecting...");
} else {
canvas_draw_icon(canvas, 0, 54, &I_Pin_back_arrow_10x8);
canvas_set_font(canvas, FontSecondary);
elements_multiline_text_aligned(canvas, 13, 62, AlignLeft, AlignBottom, "Hold to exit");
}
// Keypad circles
canvas_draw_icon(canvas, 64, 8, &I_Circles_47x47);
// Up
if(model->up_pressed) {
canvas_set_bitmap_mode(canvas, 1);
canvas_draw_icon(canvas, 81, 9, &I_Pressed_Button_13x13);
canvas_set_bitmap_mode(canvas, 0);
canvas_set_color(canvas, ColorWhite);
}
canvas_draw_icon(canvas, 84, 10, &I_Pin_arrow_up_7x9);
canvas_set_color(canvas, ColorBlack);
// Down
if(model->down_pressed) {
canvas_set_bitmap_mode(canvas, 1);
canvas_draw_icon(canvas, 81, 41, &I_Pressed_Button_13x13);
canvas_set_bitmap_mode(canvas, 0);
canvas_set_color(canvas, ColorWhite);
}
canvas_draw_icon(canvas, 84, 43, &I_Pin_arrow_down_7x9);
canvas_set_color(canvas, ColorBlack);
// Left
if(model->left_pressed) {
canvas_set_bitmap_mode(canvas, 1);
canvas_draw_icon(canvas, 65, 25, &I_Pressed_Button_13x13);
canvas_set_bitmap_mode(canvas, 0);
canvas_set_color(canvas, ColorWhite);
}
canvas_draw_icon(canvas, 67, 28, &I_Pin_arrow_left_9x7);
canvas_set_color(canvas, ColorBlack);
// Right
if(model->right_pressed) {
canvas_set_bitmap_mode(canvas, 1);
canvas_draw_icon(canvas, 97, 25, &I_Pressed_Button_13x13);
canvas_set_bitmap_mode(canvas, 0);
canvas_set_color(canvas, ColorWhite);
}
canvas_draw_icon(canvas, 99, 28, &I_Pin_arrow_right_9x7);
canvas_set_color(canvas, ColorBlack);
// Ok
if(model->left_mouse_pressed) {
canvas_draw_icon(canvas, 81, 25, &I_Ok_btn_pressed_13x13);
} else {
canvas_draw_icon(canvas, 83, 27, &I_Left_mouse_icon_9x9);
}
// Back
if(model->right_mouse_pressed) {
canvas_draw_icon(canvas, 108, 48, &I_Ok_btn_pressed_13x13);
} else {
canvas_draw_icon(canvas, 110, 50, &I_Right_mouse_icon_9x9);
}
}
static void hid_mouse_process(HidMouse* hid_mouse, InputEvent* event) {
with_view_model(
hid_mouse->view,
HidMouseModel * model,
{
if(event->key == InputKeyBack) {
if(event->type == InputTypeShort) {
hid_hal_mouse_press(hid_mouse->hid, HID_MOUSE_BTN_RIGHT);
hid_hal_mouse_release(hid_mouse->hid, HID_MOUSE_BTN_RIGHT);
} else if(event->type == InputTypePress) {
model->right_mouse_pressed = true;
} else if(event->type == InputTypeRelease) {
model->right_mouse_pressed = false;
}
} else if(event->key == InputKeyOk) {
if(event->type == InputTypeShort) {
// Just release if it was being held before
if(!model->left_mouse_held)
hid_hal_mouse_press(hid_mouse->hid, HID_MOUSE_BTN_LEFT);
hid_hal_mouse_release(hid_mouse->hid, HID_MOUSE_BTN_LEFT);
model->left_mouse_held = false;
} else if(event->type == InputTypeLong) {
hid_hal_mouse_press(hid_mouse->hid, HID_MOUSE_BTN_LEFT);
model->left_mouse_held = true;
model->left_mouse_pressed = true;
} else if(event->type == InputTypePress) {
model->left_mouse_pressed = true;
} else if(event->type == InputTypeRelease) {
// Only release if it wasn't a long press
if(!model->left_mouse_held) model->left_mouse_pressed = false;
}
} else if(event->key == InputKeyRight) {
if(event->type == InputTypePress) {
model->right_pressed = true;
hid_hal_mouse_move(hid_mouse->hid, MOUSE_MOVE_SHORT, 0);
} else if(event->type == InputTypeRepeat) {
hid_hal_mouse_move(hid_mouse->hid, MOUSE_MOVE_LONG, 0);
} else if(event->type == InputTypeRelease) {
model->right_pressed = false;
}
} else if(event->key == InputKeyLeft) {
if(event->type == InputTypePress) {
model->left_pressed = true;
hid_hal_mouse_move(hid_mouse->hid, -MOUSE_MOVE_SHORT, 0);
} else if(event->type == InputTypeRepeat) {
hid_hal_mouse_move(hid_mouse->hid, -MOUSE_MOVE_LONG, 0);
} else if(event->type == InputTypeRelease) {
model->left_pressed = false;
}
} else if(event->key == InputKeyDown) {
if(event->type == InputTypePress) {
model->down_pressed = true;
hid_hal_mouse_move(hid_mouse->hid, 0, MOUSE_MOVE_SHORT);
} else if(event->type == InputTypeRepeat) {
hid_hal_mouse_move(hid_mouse->hid, 0, MOUSE_MOVE_LONG);
} else if(event->type == InputTypeRelease) {
model->down_pressed = false;
}
} else if(event->key == InputKeyUp) {
if(event->type == InputTypePress) {
model->up_pressed = true;
hid_hal_mouse_move(hid_mouse->hid, 0, -MOUSE_MOVE_SHORT);
} else if(event->type == InputTypeRepeat) {
hid_hal_mouse_move(hid_mouse->hid, 0, -MOUSE_MOVE_LONG);
} else if(event->type == InputTypeRelease) {
model->up_pressed = false;
}
}
},
true);
}
static bool hid_mouse_input_callback(InputEvent* event, void* context) {
furi_assert(context);
HidMouse* hid_mouse = context;
bool consumed = false;
if(event->type == InputTypeLong && event->key == InputKeyBack) {
hid_hal_mouse_release_all(hid_mouse->hid);
} else {
hid_mouse_process(hid_mouse, event);
consumed = true;
}
return consumed;
}
HidMouse* hid_mouse_alloc(Hid* hid) {
HidMouse* hid_mouse = malloc(sizeof(HidMouse));
hid_mouse->view = view_alloc();
hid_mouse->hid = hid;
view_set_context(hid_mouse->view, hid_mouse);
view_allocate_model(hid_mouse->view, ViewModelTypeLocking, sizeof(HidMouseModel));
view_set_draw_callback(hid_mouse->view, hid_mouse_draw_callback);
view_set_input_callback(hid_mouse->view, hid_mouse_input_callback);
with_view_model(
hid_mouse->view, HidMouseModel * model, { model->transport = hid->transport; }, true);
return hid_mouse;
}
void hid_mouse_free(HidMouse* hid_mouse) {
furi_assert(hid_mouse);
view_free(hid_mouse->view);
free(hid_mouse);
}
View* hid_mouse_get_view(HidMouse* hid_mouse) {
furi_assert(hid_mouse);
return hid_mouse->view;
}
void hid_mouse_set_connected_status(HidMouse* hid_mouse, bool connected) {
furi_assert(hid_mouse);
with_view_model(
hid_mouse->view, HidMouseModel * model, { model->connected = connected; }, true);
}

View File

@@ -0,0 +1,17 @@
#pragma once
#include <gui/view.h>
#define MOUSE_MOVE_SHORT 5
#define MOUSE_MOVE_LONG 20
typedef struct Hid Hid;
typedef struct HidMouse HidMouse;
HidMouse* hid_mouse_alloc(Hid* bt_hid);
void hid_mouse_free(HidMouse* hid_mouse);
View* hid_mouse_get_view(HidMouse* hid_mouse);
void hid_mouse_set_connected_status(HidMouse* hid_mouse, bool connected);

View File

@@ -0,0 +1,159 @@
#include "hid_mouse_jiggler.h"
#include <gui/elements.h>
#include "../hid.h"
#include "hid_icons.h"
#define TAG "HidMouseJiggler"
struct HidMouseJiggler {
View* view;
Hid* hid;
FuriTimer* timer;
};
typedef struct {
bool connected;
bool running;
uint8_t counter;
HidTransport transport;
} HidMouseJigglerModel;
static void hid_mouse_jiggler_draw_callback(Canvas* canvas, void* context) {
furi_assert(context);
HidMouseJigglerModel* model = context;
// Header
if(model->transport == HidTransportBle) {
if(model->connected) {
canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15);
} else {
canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15);
}
}
canvas_set_font(canvas, FontPrimary);
elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Mouse Jiggler");
canvas_set_font(canvas, FontPrimary);
elements_multiline_text(canvas, AlignLeft, 35, "Press Start\nto jiggle");
canvas_set_font(canvas, FontSecondary);
// Ok
canvas_draw_icon(canvas, 63, 25, &I_Space_65x18);
if(model->running) {
elements_slightly_rounded_box(canvas, 66, 27, 60, 13);
canvas_set_color(canvas, ColorWhite);
}
canvas_draw_icon(canvas, 74, 29, &I_Ok_btn_9x9);
if(model->running) {
elements_multiline_text_aligned(canvas, 91, 36, AlignLeft, AlignBottom, "Stop");
} else {
elements_multiline_text_aligned(canvas, 91, 36, AlignLeft, AlignBottom, "Start");
}
canvas_set_color(canvas, ColorBlack);
// Back
canvas_draw_icon(canvas, 74, 49, &I_Pin_back_arrow_10x8);
elements_multiline_text_aligned(canvas, 91, 57, AlignLeft, AlignBottom, "Quit");
}
static void hid_mouse_jiggler_timer_callback(void* context) {
furi_assert(context);
HidMouseJiggler* hid_mouse_jiggler = context;
with_view_model(
hid_mouse_jiggler->view,
HidMouseJigglerModel * model,
{
if(model->running) {
model->counter++;
hid_hal_mouse_move(
hid_mouse_jiggler->hid,
(model->counter % 2 == 0) ? MOUSE_MOVE_SHORT : -MOUSE_MOVE_SHORT,
0);
}
},
false);
}
static void hid_mouse_jiggler_enter_callback(void* context) {
furi_assert(context);
HidMouseJiggler* hid_mouse_jiggler = context;
furi_timer_start(hid_mouse_jiggler->timer, 500);
}
static void hid_mouse_jiggler_exit_callback(void* context) {
furi_assert(context);
HidMouseJiggler* hid_mouse_jiggler = context;
furi_timer_stop(hid_mouse_jiggler->timer);
}
static bool hid_mouse_jiggler_input_callback(InputEvent* event, void* context) {
furi_assert(context);
HidMouseJiggler* hid_mouse_jiggler = context;
bool consumed = false;
if(event->key == InputKeyOk) {
with_view_model(
hid_mouse_jiggler->view,
HidMouseJigglerModel * model,
{ model->running = !model->running; },
true);
consumed = true;
}
return consumed;
}
HidMouseJiggler* hid_mouse_jiggler_alloc(Hid* hid) {
HidMouseJiggler* hid_mouse_jiggler = malloc(sizeof(HidMouseJiggler));
hid_mouse_jiggler->view = view_alloc();
view_set_context(hid_mouse_jiggler->view, hid_mouse_jiggler);
view_allocate_model(
hid_mouse_jiggler->view, ViewModelTypeLocking, sizeof(HidMouseJigglerModel));
view_set_draw_callback(hid_mouse_jiggler->view, hid_mouse_jiggler_draw_callback);
view_set_input_callback(hid_mouse_jiggler->view, hid_mouse_jiggler_input_callback);
view_set_enter_callback(hid_mouse_jiggler->view, hid_mouse_jiggler_enter_callback);
view_set_exit_callback(hid_mouse_jiggler->view, hid_mouse_jiggler_exit_callback);
hid_mouse_jiggler->hid = hid;
hid_mouse_jiggler->timer = furi_timer_alloc(
hid_mouse_jiggler_timer_callback, FuriTimerTypePeriodic, hid_mouse_jiggler);
with_view_model(
hid_mouse_jiggler->view,
HidMouseJigglerModel * model,
{ model->transport = hid->transport; },
true);
return hid_mouse_jiggler;
}
void hid_mouse_jiggler_free(HidMouseJiggler* hid_mouse_jiggler) {
furi_assert(hid_mouse_jiggler);
furi_timer_stop(hid_mouse_jiggler->timer);
furi_timer_free(hid_mouse_jiggler->timer);
view_free(hid_mouse_jiggler->view);
free(hid_mouse_jiggler);
}
View* hid_mouse_jiggler_get_view(HidMouseJiggler* hid_mouse_jiggler) {
furi_assert(hid_mouse_jiggler);
return hid_mouse_jiggler->view;
}
void hid_mouse_jiggler_set_connected_status(HidMouseJiggler* hid_mouse_jiggler, bool connected) {
furi_assert(hid_mouse_jiggler);
with_view_model(
hid_mouse_jiggler->view,
HidMouseJigglerModel * model,
{ model->connected = connected; },
true);
}

View File

@@ -0,0 +1,17 @@
#pragma once
#include <gui/view.h>
#define MOUSE_MOVE_SHORT 5
#define MOUSE_MOVE_LONG 20
typedef struct Hid Hid;
typedef struct HidMouseJiggler HidMouseJiggler;
HidMouseJiggler* hid_mouse_jiggler_alloc(Hid* bt_hid);
void hid_mouse_jiggler_free(HidMouseJiggler* hid_mouse_jiggler);
View* hid_mouse_jiggler_get_view(HidMouseJiggler* hid_mouse_jiggler);
void hid_mouse_jiggler_set_connected_status(HidMouseJiggler* hid_mouse_jiggler, bool connected);

View File

@@ -0,0 +1,241 @@
#include "hid_tiktok.h"
#include "../hid.h"
#include <gui/elements.h>
#include "hid_icons.h"
#define TAG "HidTikTok"
struct HidTikTok {
View* view;
Hid* hid;
};
typedef struct {
bool left_pressed;
bool up_pressed;
bool right_pressed;
bool down_pressed;
bool ok_pressed;
bool connected;
bool is_cursor_set;
HidTransport transport;
} HidTikTokModel;
static void hid_tiktok_draw_callback(Canvas* canvas, void* context) {
furi_assert(context);
HidTikTokModel* model = context;
// Header
if(model->transport == HidTransportBle) {
if(model->connected) {
canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15);
} else {
canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15);
}
}
canvas_set_font(canvas, FontPrimary);
elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "TikTok");
canvas_set_font(canvas, FontSecondary);
// Keypad circles
canvas_draw_icon(canvas, 76, 8, &I_Circles_47x47);
// Up
if(model->up_pressed) {
canvas_set_bitmap_mode(canvas, 1);
canvas_draw_icon(canvas, 93, 9, &I_Pressed_Button_13x13);
canvas_set_bitmap_mode(canvas, 0);
canvas_set_color(canvas, ColorWhite);
}
canvas_draw_icon(canvas, 96, 11, &I_Arr_up_7x9);
canvas_set_color(canvas, ColorBlack);
// Down
if(model->down_pressed) {
canvas_set_bitmap_mode(canvas, 1);
canvas_draw_icon(canvas, 93, 41, &I_Pressed_Button_13x13);
canvas_set_bitmap_mode(canvas, 0);
canvas_set_color(canvas, ColorWhite);
}
canvas_draw_icon(canvas, 96, 44, &I_Arr_dwn_7x9);
canvas_set_color(canvas, ColorBlack);
// Left
if(model->left_pressed) {
canvas_set_bitmap_mode(canvas, 1);
canvas_draw_icon(canvas, 77, 25, &I_Pressed_Button_13x13);
canvas_set_bitmap_mode(canvas, 0);
canvas_set_color(canvas, ColorWhite);
}
canvas_draw_icon(canvas, 81, 29, &I_Voldwn_6x6);
canvas_set_color(canvas, ColorBlack);
// Right
if(model->right_pressed) {
canvas_set_bitmap_mode(canvas, 1);
canvas_draw_icon(canvas, 109, 25, &I_Pressed_Button_13x13);
canvas_set_bitmap_mode(canvas, 0);
canvas_set_color(canvas, ColorWhite);
}
canvas_draw_icon(canvas, 111, 29, &I_Volup_8x6);
canvas_set_color(canvas, ColorBlack);
// Ok
if(model->ok_pressed) {
canvas_draw_icon(canvas, 91, 23, &I_Like_pressed_17x17);
} else {
canvas_draw_icon(canvas, 94, 27, &I_Like_def_11x9);
}
// Exit
canvas_draw_icon(canvas, 0, 54, &I_Pin_back_arrow_10x8);
canvas_set_font(canvas, FontSecondary);
elements_multiline_text_aligned(canvas, 13, 62, AlignLeft, AlignBottom, "Hold to exit");
}
static void hid_tiktok_reset_cursor(HidTikTok* hid_tiktok) {
// Set cursor to the phone's left up corner
// Delays to guarantee one packet per connection interval
for(size_t i = 0; i < 8; i++) {
hid_hal_mouse_move(hid_tiktok->hid, -127, -127);
furi_delay_ms(50);
}
// Move cursor from the corner
hid_hal_mouse_move(hid_tiktok->hid, 20, 120);
furi_delay_ms(50);
}
static void
hid_tiktok_process_press(HidTikTok* hid_tiktok, HidTikTokModel* model, InputEvent* event) {
if(event->key == InputKeyUp) {
model->up_pressed = true;
} else if(event->key == InputKeyDown) {
model->down_pressed = true;
} else if(event->key == InputKeyLeft) {
model->left_pressed = true;
hid_hal_consumer_key_press(hid_tiktok->hid, HID_CONSUMER_VOLUME_DECREMENT);
} else if(event->key == InputKeyRight) {
model->right_pressed = true;
hid_hal_consumer_key_press(hid_tiktok->hid, HID_CONSUMER_VOLUME_INCREMENT);
} else if(event->key == InputKeyOk) {
model->ok_pressed = true;
}
}
static void
hid_tiktok_process_release(HidTikTok* hid_tiktok, HidTikTokModel* model, InputEvent* event) {
if(event->key == InputKeyUp) {
model->up_pressed = false;
} else if(event->key == InputKeyDown) {
model->down_pressed = false;
} else if(event->key == InputKeyLeft) {
model->left_pressed = false;
hid_hal_consumer_key_release(hid_tiktok->hid, HID_CONSUMER_VOLUME_DECREMENT);
} else if(event->key == InputKeyRight) {
model->right_pressed = false;
hid_hal_consumer_key_release(hid_tiktok->hid, HID_CONSUMER_VOLUME_INCREMENT);
} else if(event->key == InputKeyOk) {
model->ok_pressed = false;
}
}
static bool hid_tiktok_input_callback(InputEvent* event, void* context) {
furi_assert(context);
HidTikTok* hid_tiktok = context;
bool consumed = false;
with_view_model(
hid_tiktok->view,
HidTikTokModel * model,
{
if(event->type == InputTypePress) {
hid_tiktok_process_press(hid_tiktok, model, event);
if(model->connected && !model->is_cursor_set) {
hid_tiktok_reset_cursor(hid_tiktok);
model->is_cursor_set = true;
}
consumed = true;
} else if(event->type == InputTypeRelease) {
hid_tiktok_process_release(hid_tiktok, model, event);
consumed = true;
} else if(event->type == InputTypeShort) {
if(event->key == InputKeyOk) {
hid_hal_mouse_press(hid_tiktok->hid, HID_MOUSE_BTN_LEFT);
furi_delay_ms(50);
hid_hal_mouse_release(hid_tiktok->hid, HID_MOUSE_BTN_LEFT);
furi_delay_ms(50);
hid_hal_mouse_press(hid_tiktok->hid, HID_MOUSE_BTN_LEFT);
furi_delay_ms(50);
hid_hal_mouse_release(hid_tiktok->hid, HID_MOUSE_BTN_LEFT);
consumed = true;
} else if(event->key == InputKeyUp) {
// Emulate up swipe
hid_hal_mouse_scroll(hid_tiktok->hid, -6);
hid_hal_mouse_scroll(hid_tiktok->hid, -12);
hid_hal_mouse_scroll(hid_tiktok->hid, -19);
hid_hal_mouse_scroll(hid_tiktok->hid, -12);
hid_hal_mouse_scroll(hid_tiktok->hid, -6);
consumed = true;
} else if(event->key == InputKeyDown) {
// Emulate down swipe
hid_hal_mouse_scroll(hid_tiktok->hid, 6);
hid_hal_mouse_scroll(hid_tiktok->hid, 12);
hid_hal_mouse_scroll(hid_tiktok->hid, 19);
hid_hal_mouse_scroll(hid_tiktok->hid, 12);
hid_hal_mouse_scroll(hid_tiktok->hid, 6);
consumed = true;
} else if(event->key == InputKeyBack) {
hid_hal_consumer_key_release_all(hid_tiktok->hid);
consumed = true;
}
} else if(event->type == InputTypeLong) {
if(event->key == InputKeyBack) {
hid_hal_consumer_key_release_all(hid_tiktok->hid);
model->is_cursor_set = false;
consumed = false;
}
}
},
true);
return consumed;
}
HidTikTok* hid_tiktok_alloc(Hid* bt_hid) {
HidTikTok* hid_tiktok = malloc(sizeof(HidTikTok));
hid_tiktok->hid = bt_hid;
hid_tiktok->view = view_alloc();
view_set_context(hid_tiktok->view, hid_tiktok);
view_allocate_model(hid_tiktok->view, ViewModelTypeLocking, sizeof(HidTikTokModel));
view_set_draw_callback(hid_tiktok->view, hid_tiktok_draw_callback);
view_set_input_callback(hid_tiktok->view, hid_tiktok_input_callback);
with_view_model(
hid_tiktok->view, HidTikTokModel * model, { model->transport = bt_hid->transport; }, true);
return hid_tiktok;
}
void hid_tiktok_free(HidTikTok* hid_tiktok) {
furi_assert(hid_tiktok);
view_free(hid_tiktok->view);
free(hid_tiktok);
}
View* hid_tiktok_get_view(HidTikTok* hid_tiktok) {
furi_assert(hid_tiktok);
return hid_tiktok->view;
}
void hid_tiktok_set_connected_status(HidTikTok* hid_tiktok, bool connected) {
furi_assert(hid_tiktok);
with_view_model(
hid_tiktok->view,
HidTikTokModel * model,
{
model->connected = connected;
model->is_cursor_set = false;
},
true);
}

View File

@@ -0,0 +1,14 @@
#pragma once
#include <gui/view.h>
typedef struct Hid Hid;
typedef struct HidTikTok HidTikTok;
HidTikTok* hid_tiktok_alloc(Hid* bt_hid);
void hid_tiktok_free(HidTikTok* hid_tiktok);
View* hid_tiktok_get_view(HidTikTok* hid_tiktok);
void hid_tiktok_set_connected_status(HidTikTok* hid_tiktok, bool connected);

View File

@@ -0,0 +1,24 @@
App(
appid="music_player",
name="Music Player",
apptype=FlipperAppType.EXTERNAL,
entry_point="music_player_app",
requires=[
"gui",
"dialogs",
],
provides=["music_player_start"],
stack_size=2 * 1024,
order=20,
fap_icon="icons/music_10px.png",
fap_category="Media",
fap_icon_assets="icons",
)
App(
appid="music_player_start",
apptype=FlipperAppType.STARTUP,
entry_point="music_player_on_system_start",
requires=["music_player"],
order=30,
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 B

View File

@@ -0,0 +1,373 @@
#include "music_player_worker.h"
#include <furi.h>
#include <furi_hal.h>
#include <music_player_icons.h>
#include <gui/gui.h>
#include <dialogs/dialogs.h>
#include <storage/storage.h>
#define TAG "MusicPlayer"
#define MUSIC_PLAYER_APP_EXTENSION "*"
#define MUSIC_PLAYER_SEMITONE_HISTORY_SIZE 4
typedef struct {
uint8_t semitone_history[MUSIC_PLAYER_SEMITONE_HISTORY_SIZE];
uint8_t duration_history[MUSIC_PLAYER_SEMITONE_HISTORY_SIZE];
uint8_t volume;
uint8_t semitone;
uint8_t dots;
uint8_t duration;
float position;
} MusicPlayerModel;
typedef struct {
MusicPlayerModel* model;
FuriMutex** model_mutex;
FuriMessageQueue* input_queue;
ViewPort* view_port;
Gui* gui;
MusicPlayerWorker* worker;
} MusicPlayer;
static const float MUSIC_PLAYER_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) {
MusicPlayer* music_player = ctx;
furi_check(furi_mutex_acquire(music_player->model_mutex, FuriWaitForever) == FuriStatusOk);
canvas_clear(canvas);
canvas_set_color(canvas, ColorBlack);
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 0, 12, "MusicPlayer");
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_player->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_player->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_PLAYER_VOLUMES) - 1)) * music_player->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; //-V1048
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_PLAYER_SEMITONE_HISTORY_SIZE; i++) {
if(music_player->model->duration_history[i] == 0xFF) {
snprintf(duration_text, 15, "--");
} else {
snprintf(duration_text, 15, "%d", music_player->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_player->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_player->model_mutex);
}
static void input_callback(InputEvent* input_event, void* ctx) {
MusicPlayer* music_player = ctx;
if(input_event->type == InputTypeShort) {
furi_message_queue_put(music_player->input_queue, input_event, 0);
}
}
static void music_player_worker_callback(
uint8_t semitone,
uint8_t dots,
uint8_t duration,
float position,
void* context) {
MusicPlayer* music_player = context;
furi_check(furi_mutex_acquire(music_player->model_mutex, FuriWaitForever) == FuriStatusOk);
for(size_t i = 0; i < MUSIC_PLAYER_SEMITONE_HISTORY_SIZE - 1; i++) {
size_t r = MUSIC_PLAYER_SEMITONE_HISTORY_SIZE - 1 - i;
music_player->model->duration_history[r] = music_player->model->duration_history[r - 1];
music_player->model->semitone_history[r] = music_player->model->semitone_history[r - 1];
}
semitone = (semitone == 0xFF) ? 0xFF : semitone % 12;
music_player->model->semitone = semitone;
music_player->model->dots = dots;
music_player->model->duration = duration;
music_player->model->position = position;
music_player->model->semitone_history[0] = semitone;
music_player->model->duration_history[0] = duration;
furi_mutex_release(music_player->model_mutex);
view_port_update(music_player->view_port);
}
void music_player_clear(MusicPlayer* instance) {
memset(instance->model->duration_history, 0xff, MUSIC_PLAYER_SEMITONE_HISTORY_SIZE);
memset(instance->model->semitone_history, 0xff, MUSIC_PLAYER_SEMITONE_HISTORY_SIZE);
music_player_worker_clear(instance->worker);
}
MusicPlayer* music_player_alloc() {
MusicPlayer* instance = malloc(sizeof(MusicPlayer));
instance->model = malloc(sizeof(MusicPlayerModel));
instance->model->volume = 3;
instance->model_mutex = furi_mutex_alloc(FuriMutexTypeNormal);
instance->input_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
instance->worker = music_player_worker_alloc();
music_player_worker_set_volume(
instance->worker, MUSIC_PLAYER_VOLUMES[instance->model->volume]);
music_player_worker_set_callback(instance->worker, music_player_worker_callback, instance);
music_player_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_player_free(MusicPlayer* instance) {
gui_remove_view_port(instance->gui, instance->view_port);
furi_record_close(RECORD_GUI);
view_port_free(instance->view_port);
music_player_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_player_app(void* p) {
MusicPlayer* music_player = music_player_alloc();
FuriString* file_path;
file_path = furi_string_alloc();
do {
if(p && strlen(p)) {
furi_string_set(file_path, (const char*)p);
} else {
Storage* storage = furi_record_open(RECORD_STORAGE);
storage_common_migrate(
storage, EXT_PATH("music_player"), STORAGE_APP_DATA_PATH_PREFIX);
furi_record_close(RECORD_STORAGE);
furi_string_set(file_path, STORAGE_APP_DATA_PATH_PREFIX);
DialogsFileBrowserOptions browser_options;
dialog_file_browser_set_basic_options(
&browser_options, MUSIC_PLAYER_APP_EXTENSION, &I_music_10px);
browser_options.hide_ext = false;
browser_options.base_path = STORAGE_APP_DATA_PATH_PREFIX;
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_player_worker_load(music_player->worker, furi_string_get_cstr(file_path))) {
FURI_LOG_E(TAG, "Unable to load file");
break;
}
music_player_worker_start(music_player->worker);
InputEvent input;
while(furi_message_queue_get(music_player->input_queue, &input, FuriWaitForever) ==
FuriStatusOk) {
furi_check(
furi_mutex_acquire(music_player->model_mutex, FuriWaitForever) == FuriStatusOk);
if(input.key == InputKeyBack) {
furi_mutex_release(music_player->model_mutex);
break;
} else if(input.key == InputKeyUp) {
if(music_player->model->volume < COUNT_OF(MUSIC_PLAYER_VOLUMES) - 1)
music_player->model->volume++;
music_player_worker_set_volume(
music_player->worker, MUSIC_PLAYER_VOLUMES[music_player->model->volume]);
} else if(input.key == InputKeyDown) {
if(music_player->model->volume > 0) music_player->model->volume--;
music_player_worker_set_volume(
music_player->worker, MUSIC_PLAYER_VOLUMES[music_player->model->volume]);
}
furi_mutex_release(music_player->model_mutex);
view_port_update(music_player->view_port);
}
music_player_worker_stop(music_player->worker);
if(p && strlen(p)) break; // Exit instead of going to browser if launched with arg
music_player_clear(music_player);
} while(1);
furi_string_free(file_path);
music_player_free(music_player);
return 0;
}

View File

@@ -0,0 +1,48 @@
#include <furi.h>
#include <cli/cli.h>
#include <storage/storage.h>
#include "music_player_worker.h"
static void music_player_cli(Cli* cli, FuriString* args, void* context) {
UNUSED(context);
MusicPlayerWorker* music_player_worker = music_player_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_player_worker_load(music_player_worker, furi_string_get_cstr(args))) {
printf("Failed to open file %s\r\n", furi_string_get_cstr(args));
break;
}
} else {
if(!music_player_worker_load_rtttl_from_string(
music_player_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_player_worker_set_volume(music_player_worker, 1.0f);
music_player_worker_start(music_player_worker);
while(!cli_cmd_interrupt_received(cli)) {
furi_delay_ms(50);
}
music_player_worker_stop(music_player_worker);
} while(0);
furi_record_close(RECORD_STORAGE);
music_player_worker_free(music_player_worker);
}
void music_player_on_system_start() {
#ifdef SRV_CLI
Cli* cli = furi_record_open(RECORD_CLI);
cli_add_command(cli, "music_player", CliCommandFlagDefault, music_player_cli, NULL);
furi_record_close(RECORD_CLI);
#else
UNUSED(music_player_cli);
#endif
}

View File

@@ -0,0 +1,508 @@
#include "music_player_worker.h"
#include <furi_hal.h>
#include <furi.h>
#include <storage/storage.h>
#include <lib/flipper_format/flipper_format.h>
#include <math.h>
#include <m-array.h>
#define TAG "MusicPlayerWorker"
#define MUSIC_PLAYER_FILETYPE "Flipper Music Format"
#define MUSIC_PLAYER_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 MusicPlayerWorker {
FuriThread* thread;
bool should_work;
MusicPlayerWorkerCallback callback;
void* callback_context;
float volume;
uint32_t bpm;
uint32_t duration;
uint32_t octave;
NoteBlockArray_t notes;
};
static int32_t music_player_worker_thread_callback(void* context) {
furi_assert(context);
MusicPlayerWorker* 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 *= 0.9945679;
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;
}
MusicPlayerWorker* music_player_worker_alloc() {
MusicPlayerWorker* instance = malloc(sizeof(MusicPlayerWorker));
NoteBlockArray_init(instance->notes);
instance->thread = furi_thread_alloc_ex(
"MusicPlayerWorker", 1024, music_player_worker_thread_callback, instance);
instance->volume = 1.0f;
return instance;
}
void music_player_worker_clear(MusicPlayerWorker* instance) {
NoteBlockArray_reset(instance->notes);
}
void music_player_worker_free(MusicPlayerWorker* 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_player_worker_add_note(
MusicPlayerWorker* 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_player_worker_parse_notes(MusicPlayerWorker* 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, &note_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_player_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_player_worker_load(MusicPlayerWorker* instance, const char* file_path) {
furi_assert(instance);
furi_assert(file_path);
bool ret = false;
if(strcasestr(file_path, ".fmf")) {
ret = music_player_worker_load_fmf_from_file(instance, file_path);
} else {
ret = music_player_worker_load_rtttl_from_file(instance, file_path);
}
return ret;
}
bool music_player_worker_load_fmf_from_file(MusicPlayerWorker* 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_PLAYER_FILETYPE) ||
(version != MUSIC_PLAYER_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_player_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_player_worker_load_rtttl_from_file(MusicPlayerWorker* 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_player_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_player_worker_load_rtttl_from_string(MusicPlayerWorker* 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_player_worker_parse_notes(instance, cursor)) {
return false;
}
return true;
}
void music_player_worker_set_callback(
MusicPlayerWorker* instance,
MusicPlayerWorkerCallback callback,
void* context) {
furi_assert(instance);
instance->callback = callback;
instance->callback_context = context;
}
void music_player_worker_set_volume(MusicPlayerWorker* instance, float volume) {
furi_assert(instance);
instance->volume = volume;
}
void music_player_worker_start(MusicPlayerWorker* instance) {
furi_assert(instance);
furi_assert(instance->should_work == false);
instance->should_work = true;
furi_thread_start(instance->thread);
}
void music_player_worker_stop(MusicPlayerWorker* instance) {
furi_assert(instance);
furi_assert(instance->should_work == true);
instance->should_work = false;
furi_thread_join(instance->thread);
}

View File

@@ -0,0 +1,38 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
typedef void (*MusicPlayerWorkerCallback)(
uint8_t semitone,
uint8_t dots,
uint8_t duration,
float position,
void* context);
typedef struct MusicPlayerWorker MusicPlayerWorker;
MusicPlayerWorker* music_player_worker_alloc();
void music_player_worker_clear(MusicPlayerWorker* instance);
void music_player_worker_free(MusicPlayerWorker* instance);
bool music_player_worker_load(MusicPlayerWorker* instance, const char* file_path);
bool music_player_worker_load_fmf_from_file(MusicPlayerWorker* instance, const char* file_path);
bool music_player_worker_load_rtttl_from_file(MusicPlayerWorker* instance, const char* file_path);
bool music_player_worker_load_rtttl_from_string(MusicPlayerWorker* instance, const char* string);
void music_player_worker_set_callback(
MusicPlayerWorker* instance,
MusicPlayerWorkerCallback callback,
void* context);
void music_player_worker_set_volume(MusicPlayerWorker* instance, float volume);
void music_player_worker_start(MusicPlayerWorker* instance);
void music_player_worker_stop(MusicPlayerWorker* instance);

View File

@@ -0,0 +1,21 @@
App(
appid="nfc_magic",
name="Nfc Magic",
apptype=FlipperAppType.EXTERNAL,
targets=["f7"],
entry_point="nfc_magic_app",
requires=[
"storage",
"gui",
],
stack_size=4 * 1024,
order=30,
fap_icon="../../../assets/icons/Archive/125_10px.png",
fap_category="NFC",
fap_private_libs=[
Lib(
name="magic",
),
],
fap_icon_assets="assets",
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@@ -0,0 +1,214 @@
#include "magic.h"
#include <furi_hal_nfc.h>
#define TAG "Magic"
#define MAGIC_CMD_WUPA (0x40)
#define MAGIC_CMD_WIPE (0x41)
#define MAGIC_CMD_READ (0x43)
#define MAGIC_CMD_WRITE (0x43)
#define MAGIC_MIFARE_READ_CMD (0x30)
#define MAGIC_MIFARE_WRITE_CMD (0xA0)
#define MAGIC_ACK (0x0A)
#define MAGIC_BUFFER_SIZE (32)
bool magic_wupa() {
bool magic_activated = false;
uint8_t tx_data[MAGIC_BUFFER_SIZE] = {};
uint8_t rx_data[MAGIC_BUFFER_SIZE] = {};
uint16_t rx_len = 0;
FuriHalNfcReturn ret = 0;
do {
// Setup nfc poller
furi_hal_nfc_exit_sleep();
furi_hal_nfc_ll_txrx_on();
furi_hal_nfc_ll_poll();
ret = furi_hal_nfc_ll_set_mode(
FuriHalNfcModePollNfca, FuriHalNfcBitrate106, FuriHalNfcBitrate106);
if(ret != FuriHalNfcReturnOk) break;
furi_hal_nfc_ll_set_fdt_listen(FURI_HAL_NFC_LL_FDT_LISTEN_NFCA_POLLER);
furi_hal_nfc_ll_set_fdt_poll(FURI_HAL_NFC_LL_FDT_POLL_NFCA_POLLER);
furi_hal_nfc_ll_set_error_handling(FuriHalNfcErrorHandlingNfc);
furi_hal_nfc_ll_set_guard_time(FURI_HAL_NFC_LL_GT_NFCA);
// Start communication
tx_data[0] = MAGIC_CMD_WUPA;
ret = furi_hal_nfc_ll_txrx_bits(
tx_data,
7,
rx_data,
sizeof(rx_data),
&rx_len,
FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_TX_MANUAL | FURI_HAL_NFC_LL_TXRX_FLAGS_AGC_ON |
FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_RX_KEEP,
furi_hal_nfc_ll_ms2fc(20));
if(ret != FuriHalNfcReturnIncompleteByte) break;
if(rx_len != 4) break;
if(rx_data[0] != MAGIC_ACK) break;
magic_activated = true;
} while(false);
if(!magic_activated) {
furi_hal_nfc_ll_txrx_off();
furi_hal_nfc_start_sleep();
}
return magic_activated;
}
bool magic_data_access_cmd() {
bool write_cmd_success = false;
uint8_t tx_data[MAGIC_BUFFER_SIZE] = {};
uint8_t rx_data[MAGIC_BUFFER_SIZE] = {};
uint16_t rx_len = 0;
FuriHalNfcReturn ret = 0;
do {
tx_data[0] = MAGIC_CMD_WRITE;
ret = furi_hal_nfc_ll_txrx_bits(
tx_data,
8,
rx_data,
sizeof(rx_data),
&rx_len,
FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_TX_MANUAL | FURI_HAL_NFC_LL_TXRX_FLAGS_AGC_ON |
FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_RX_KEEP,
furi_hal_nfc_ll_ms2fc(20));
if(ret != FuriHalNfcReturnIncompleteByte) break;
if(rx_len != 4) break;
if(rx_data[0] != MAGIC_ACK) break;
write_cmd_success = true;
} while(false);
if(!write_cmd_success) {
furi_hal_nfc_ll_txrx_off();
furi_hal_nfc_start_sleep();
}
return write_cmd_success;
}
bool magic_read_block(uint8_t block_num, MfClassicBlock* data) {
furi_assert(data);
bool read_success = false;
uint8_t tx_data[MAGIC_BUFFER_SIZE] = {};
uint8_t rx_data[MAGIC_BUFFER_SIZE] = {};
uint16_t rx_len = 0;
FuriHalNfcReturn ret = 0;
do {
tx_data[0] = MAGIC_MIFARE_READ_CMD;
tx_data[1] = block_num;
ret = furi_hal_nfc_ll_txrx_bits(
tx_data,
2 * 8,
rx_data,
sizeof(rx_data),
&rx_len,
FURI_HAL_NFC_LL_TXRX_FLAGS_AGC_ON,
furi_hal_nfc_ll_ms2fc(20));
if(ret != FuriHalNfcReturnOk) break;
if(rx_len != 16 * 8) break;
memcpy(data->value, rx_data, sizeof(data->value));
read_success = true;
} while(false);
if(!read_success) {
furi_hal_nfc_ll_txrx_off();
furi_hal_nfc_start_sleep();
}
return read_success;
}
bool magic_write_blk(uint8_t block_num, MfClassicBlock* data) {
furi_assert(data);
bool write_success = false;
uint8_t tx_data[MAGIC_BUFFER_SIZE] = {};
uint8_t rx_data[MAGIC_BUFFER_SIZE] = {};
uint16_t rx_len = 0;
FuriHalNfcReturn ret = 0;
do {
tx_data[0] = MAGIC_MIFARE_WRITE_CMD;
tx_data[1] = block_num;
ret = furi_hal_nfc_ll_txrx_bits(
tx_data,
2 * 8,
rx_data,
sizeof(rx_data),
&rx_len,
FURI_HAL_NFC_LL_TXRX_FLAGS_AGC_ON | FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_RX_KEEP,
furi_hal_nfc_ll_ms2fc(20));
if(ret != FuriHalNfcReturnIncompleteByte) break;
if(rx_len != 4) break;
if(rx_data[0] != MAGIC_ACK) break;
memcpy(tx_data, data->value, sizeof(data->value));
ret = furi_hal_nfc_ll_txrx_bits(
tx_data,
16 * 8,
rx_data,
sizeof(rx_data),
&rx_len,
FURI_HAL_NFC_LL_TXRX_FLAGS_AGC_ON | FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_RX_KEEP,
furi_hal_nfc_ll_ms2fc(20));
if(ret != FuriHalNfcReturnIncompleteByte) break;
if(rx_len != 4) break;
if(rx_data[0] != MAGIC_ACK) break;
write_success = true;
} while(false);
if(!write_success) {
furi_hal_nfc_ll_txrx_off();
furi_hal_nfc_start_sleep();
}
return write_success;
}
bool magic_wipe() {
bool wipe_success = false;
uint8_t tx_data[MAGIC_BUFFER_SIZE] = {};
uint8_t rx_data[MAGIC_BUFFER_SIZE] = {};
uint16_t rx_len = 0;
FuriHalNfcReturn ret = 0;
do {
tx_data[0] = MAGIC_CMD_WIPE;
ret = furi_hal_nfc_ll_txrx_bits(
tx_data,
8,
rx_data,
sizeof(rx_data),
&rx_len,
FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_TX_MANUAL | FURI_HAL_NFC_LL_TXRX_FLAGS_AGC_ON |
FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_RX_KEEP,
furi_hal_nfc_ll_ms2fc(2000));
if(ret != FuriHalNfcReturnIncompleteByte) break;
if(rx_len != 4) break;
if(rx_data[0] != MAGIC_ACK) break;
wipe_success = true;
} while(false);
return wipe_success;
}
void magic_deactivate() {
furi_hal_nfc_ll_txrx_off();
furi_hal_nfc_sleep();
}

View File

@@ -0,0 +1,15 @@
#pragma once
#include <lib/nfc/protocols/mifare_classic.h>
bool magic_wupa();
bool magic_read_block(uint8_t block_num, MfClassicBlock* data);
bool magic_data_access_cmd();
bool magic_write_blk(uint8_t block_num, MfClassicBlock* data);
bool magic_wipe();
void magic_deactivate();

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