mirror of
https://github.com/UberGuidoZ/Flipper.git
synced 2026-09-18 19:21:30 +00:00
Added xMasterX API v11.x source
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
# Flipper BP
|
||||
|
||||
Custom implementation of T-Code protocol for Flipper Zero devices.
|
||||
@@ -0,0 +1,13 @@
|
||||
App(
|
||||
appid="fbp",
|
||||
name="Flipper BP",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="fbp_app",
|
||||
stack_size=1 * 1024,
|
||||
requires=[
|
||||
"bt",
|
||||
"gui",
|
||||
],
|
||||
fap_category="Misc_Extra",
|
||||
fap_icon="uart_10px.png",
|
||||
)
|
||||
@@ -0,0 +1,103 @@
|
||||
#include "fbp.h"
|
||||
|
||||
enum FBPSubmenuIndex {
|
||||
FBPSubmenuIndexInternal,
|
||||
FBPSubmenuIndexGPIOSimpleMotor,
|
||||
};
|
||||
|
||||
uint32_t fbp_start_view(void* context) {
|
||||
UNUSED(context);
|
||||
return FBPAppViewSubmenu;
|
||||
}
|
||||
|
||||
uint32_t fbp_exit(void* context) {
|
||||
UNUSED(context);
|
||||
return VIEW_NONE;
|
||||
}
|
||||
|
||||
void fbp_submenu_callback(void* context, uint32_t index) {
|
||||
furi_assert(context);
|
||||
FBP* app = context;
|
||||
if(index == FBPSubmenuIndexInternal) {
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, FBPAppViewInternal);
|
||||
} else if (index == FBPSubmenuIndexGPIOSimpleMotor) {
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, FBPAppViewGPIOSimpleMotor);
|
||||
}
|
||||
}
|
||||
|
||||
FBP* fbp_alloc() {
|
||||
FBP* app = malloc(sizeof(FBP));
|
||||
app->app_mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
||||
app->event_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
|
||||
|
||||
app->bt_connected = false;
|
||||
app->bt = furi_record_open(RECORD_BT);
|
||||
|
||||
app->gui = furi_record_open(RECORD_GUI);
|
||||
app->view_dispatcher = view_dispatcher_alloc();
|
||||
view_dispatcher_enable_queue(app->view_dispatcher);
|
||||
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
||||
|
||||
// set submenu
|
||||
app->submenu = submenu_alloc();
|
||||
view_set_previous_callback(submenu_get_view(app->submenu), fbp_exit);
|
||||
view_dispatcher_add_view(app->view_dispatcher, FBPAppViewSubmenu, submenu_get_view(app->submenu));
|
||||
|
||||
// add Flipper Internal View
|
||||
app->flipper_vibrator = flipper_vibrator_alloc(app);
|
||||
submenu_add_item(app->submenu, "Flipper Internal Vibrator", FBPSubmenuIndexInternal, fbp_submenu_callback, app);
|
||||
view_set_previous_callback(flipper_vibrator_get_view(app->flipper_vibrator), fbp_start_view);
|
||||
view_dispatcher_add_view(app->view_dispatcher, FBPAppViewInternal, flipper_vibrator_get_view(app->flipper_vibrator));
|
||||
|
||||
// add GPIO Simple Motor View
|
||||
app->gpio_simple_motor = gpio_simple_motor_alloc(app);
|
||||
submenu_add_item(app->submenu, "Flipper GPIO Simple Motor", FBPSubmenuIndexGPIOSimpleMotor, fbp_submenu_callback, app);
|
||||
view_set_previous_callback(gpio_simple_motor_get_view(app->gpio_simple_motor), fbp_start_view);
|
||||
view_dispatcher_add_view(app->view_dispatcher, FBPAppViewGPIOSimpleMotor, gpio_simple_motor_get_view(app->gpio_simple_motor));
|
||||
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, FBPAppViewSubmenu);
|
||||
return app;
|
||||
}
|
||||
|
||||
void fbs_free(FBP* app) {
|
||||
furi_assert(app);
|
||||
|
||||
// Free views
|
||||
view_dispatcher_remove_view(app->view_dispatcher, FBPAppViewSubmenu);
|
||||
submenu_free(app->submenu);
|
||||
|
||||
// free Flipper Internal Vibrator
|
||||
view_dispatcher_remove_view(app->view_dispatcher, FBPAppViewInternal);
|
||||
flipper_vibrator_free(app->flipper_vibrator);
|
||||
|
||||
// free GPIO Simple Motor
|
||||
view_dispatcher_remove_view(app->view_dispatcher, FBPAppViewGPIOSimpleMotor);
|
||||
gpio_simple_motor_free(app->gpio_simple_motor);
|
||||
|
||||
// Other deallocations
|
||||
view_dispatcher_free(app->view_dispatcher);
|
||||
|
||||
|
||||
furi_record_close(RECORD_GUI);
|
||||
app->gui = NULL;
|
||||
|
||||
furi_mutex_free(app->app_mutex);
|
||||
furi_message_queue_free(app->event_queue);
|
||||
|
||||
furi_record_close(RECORD_BT);
|
||||
app->bt = NULL;
|
||||
|
||||
free(app);
|
||||
}
|
||||
|
||||
int32_t fbp_app(void* p) {
|
||||
UNUSED(p);
|
||||
FBP* app = fbp_alloc();
|
||||
|
||||
view_dispatcher_run(app->view_dispatcher);
|
||||
|
||||
furi_hal_bt_serial_set_event_callback(0, NULL, NULL);
|
||||
|
||||
fbs_free(app);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include <furi.h>
|
||||
#include <furi_hal_bt.h>
|
||||
#include <furi_hal_bt_serial.h>
|
||||
#include <bt/bt_service/bt.h>
|
||||
#include <gui/gui.h>
|
||||
#include <gui/view_dispatcher.h>
|
||||
#include <gui/modules/submenu.h>
|
||||
|
||||
#include "tcode.h"
|
||||
#include "views/internal.h"
|
||||
#include "views/gpio_simple_motor.h"
|
||||
|
||||
#define TAG "Flipper BP"
|
||||
|
||||
typedef struct FBP FBP;
|
||||
|
||||
struct FBP {
|
||||
Submenu* submenu;
|
||||
ViewDispatcher* view_dispatcher;
|
||||
Gui* gui;
|
||||
|
||||
Bt* bt;
|
||||
bool bt_connected;
|
||||
|
||||
FuriMutex* app_mutex;
|
||||
FuriMessageQueue* event_queue;
|
||||
|
||||
FlipperVibrator* flipper_vibrator;
|
||||
GPIOSimpleMotor* gpio_simple_motor;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
FBPAppViewSubmenu,
|
||||
FBPAppViewInternal,
|
||||
FBPAppViewGPIOSimpleMotor,
|
||||
} FBPAppView;
|
||||
@@ -0,0 +1,183 @@
|
||||
#include "tcode.h"
|
||||
|
||||
static TCodeCommandArray decode_device_command(const uint8_t *buffer, uint16_t size) {
|
||||
TCodeCommandArray command_array;
|
||||
if (size < 2) {
|
||||
command_array.size = 0;
|
||||
FURI_LOG_W("TCode Parser", "Unexpected code length for device command");
|
||||
return command_array;
|
||||
}
|
||||
|
||||
command_array.size = 1;
|
||||
command_array.commands = malloc(sizeof(TCodeCommand));
|
||||
command_array.commands[0].command_type = Device;
|
||||
|
||||
switch (buffer[1]) {
|
||||
case '0':
|
||||
FURI_LOG_T("TCode Parser", "Device Identification requested");
|
||||
command_array.commands[0].data.device_command = DeviceIdentification;
|
||||
break;
|
||||
case '1':
|
||||
FURI_LOG_T("TCode Parser", "TCode version requested");
|
||||
command_array.commands[0].data.device_command = TCodeVersion;
|
||||
break;
|
||||
case '2':
|
||||
FURI_LOG_T("TCode Parser", "Preferences list requested");
|
||||
command_array.commands[0].data.device_command = ListAxesAndUserRangePreferences;
|
||||
break;
|
||||
case 'S':
|
||||
FURI_LOG_T("TCode Parser", "Stop requested");
|
||||
command_array.commands[0].data.device_command = Stop;
|
||||
break;
|
||||
default:
|
||||
command_array.size = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
return command_array;
|
||||
}
|
||||
|
||||
static TCodeCommandArray decode_general_command(const uint8_t *buffer, uint16_t size) {
|
||||
// size of the array = amount of spaces + 1
|
||||
uint16_t counter = 1;
|
||||
for (uint16_t i = 0; i < size; i++) {
|
||||
if (buffer[i] == 32) {
|
||||
counter++;
|
||||
}
|
||||
}
|
||||
|
||||
FURI_LOG_T("TCode Parser", "Found %u commands in the message", counter);
|
||||
|
||||
TCodeCommandArray commands_array;
|
||||
commands_array.size = counter;
|
||||
commands_array.commands = malloc(commands_array.size * sizeof(TCodeCommand));
|
||||
|
||||
uint16_t position = 0;
|
||||
for (uint16_t i = 0; i < counter; i++) {
|
||||
TCodeCommand command;
|
||||
command.command_type = Unknown;
|
||||
|
||||
TCodeCommandMotionType motion_type;
|
||||
switch (buffer[position]) {
|
||||
case 'L':
|
||||
case 'l':
|
||||
motion_type = Linear;
|
||||
break;
|
||||
case 'R':
|
||||
case 'r':
|
||||
motion_type = Rotate;
|
||||
break;
|
||||
case 'V':
|
||||
case 'v':
|
||||
motion_type = Vibrate;
|
||||
break;
|
||||
case 'A':
|
||||
case 'a':
|
||||
motion_type = Auxiliary;
|
||||
break;
|
||||
default: // error
|
||||
FURI_LOG_W("TCode Parser", "Unexpected motion type: %u", buffer[position]);
|
||||
return commands_array;
|
||||
}
|
||||
FURI_LOG_T("TCode Parser", "Parsed motion_type: %u", motion_type);
|
||||
position++;
|
||||
|
||||
uint16_t channel = buffer[position] - 48; // single ascii character 0-9
|
||||
FURI_LOG_T("TCode Parser", "Parsed channel: %u", channel);
|
||||
position++;
|
||||
|
||||
// X characters that are digits
|
||||
uint16_t current_position = position;
|
||||
while (buffer[position] >= 48 && buffer[position] <= 57 && position < size) {
|
||||
position++;
|
||||
}
|
||||
|
||||
uint8_t *magnitude = malloc(2 + (position - current_position) + 1); // "0.XXXX\0"
|
||||
magnitude[0] = '0';
|
||||
magnitude[1] = '.';
|
||||
for (uint16_t x = 0; x < (position - current_position); x++) {
|
||||
magnitude[x + 2] = buffer[current_position + x];
|
||||
}
|
||||
magnitude[position - current_position + 2] = '\0';
|
||||
float magnitude_float = strtof((char *) magnitude, NULL);
|
||||
free(magnitude);
|
||||
FURI_LOG_T("TCode Parser", "Parsed magnitude: %f", (double) magnitude_float);
|
||||
|
||||
FURI_LOG_T("TCode Parser REMOVE ME", "Current position: %u, size: %u", position, size);
|
||||
FURI_LOG_T("TCode Parser REMOVE ME", "%u", buffer[position]);
|
||||
if (position == size || buffer[position] == ' ' || buffer[position] == '\n') {
|
||||
FURI_LOG_T("TCode Parser", "Command type: Magnitude");
|
||||
command.command_type = Magnitude;
|
||||
command.data.magnitude_command.motion_type = motion_type;
|
||||
command.data.magnitude_command.channel_id = channel;
|
||||
command.data.magnitude_command.magnitude = magnitude_float;
|
||||
commands_array.commands[i] = command;
|
||||
position++;
|
||||
continue;
|
||||
}
|
||||
|
||||
uint8_t current_step = buffer[position];
|
||||
position++;
|
||||
|
||||
uint16_t int_value = 0;
|
||||
while (buffer[position] >= 48 && buffer[position] <= 57 && position < size) {
|
||||
int_value *= 10;
|
||||
int_value += buffer[position] - 48;
|
||||
position++;
|
||||
}
|
||||
|
||||
command.data.magnitude_time_interval_command.motion_type = motion_type;
|
||||
command.data.magnitude_time_interval_command.channel_id = channel;
|
||||
command.data.magnitude_time_interval_command.magnitude = magnitude_float;
|
||||
|
||||
if (current_step == 'I' || current_step == 'i') {
|
||||
FURI_LOG_T("TCode Parser", "Command type: MagnitudeTimeInterval");
|
||||
command.command_type = MagnitudeTimeInterval;
|
||||
command.data.magnitude_time_interval_command.time_interval_milliseconds = int_value;
|
||||
}
|
||||
|
||||
if (current_step == 'S' || current_step == 's') {
|
||||
FURI_LOG_T("TCode Parser", "Command type: MagnitudeSpeed");
|
||||
command.command_type = MagnitudeSpeed;
|
||||
command.data.magnitude_speed_command.speed_per_hundred_milliseconds = int_value;
|
||||
}
|
||||
|
||||
if (command.command_type == Unknown) {
|
||||
FURI_LOG_W("TCode Parser", "Unknown command type!");
|
||||
}
|
||||
|
||||
commands_array.commands[i] = command;
|
||||
position++;
|
||||
if (position >= size) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return commands_array;
|
||||
}
|
||||
|
||||
TCodeCommandArray tcode_decode(uint8_t *buffer, uint16_t size) {
|
||||
switch (buffer[0]) {
|
||||
case 'd':
|
||||
case 'D':
|
||||
FURI_LOG_T("TCode Parser", "Parsing device command...");
|
||||
return decode_device_command(buffer, size);
|
||||
case 'l':
|
||||
case 'L':
|
||||
case 'r':
|
||||
case 'R':
|
||||
case 'v':
|
||||
case 'V':
|
||||
case 'a':
|
||||
case 'A':
|
||||
FURI_LOG_T("TCode Parser", "Parsing general command...");
|
||||
return decode_general_command(buffer, size);
|
||||
default: // error
|
||||
{
|
||||
TCodeCommandArray error;
|
||||
error.size = 0;
|
||||
error.commands = NULL;
|
||||
return error;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
#pragma once
|
||||
#include <furi.h>
|
||||
|
||||
typedef enum {
|
||||
Device,
|
||||
Magnitude,
|
||||
MagnitudeTimeInterval,
|
||||
MagnitudeSpeed,
|
||||
Unknown,
|
||||
} TCodeCommandType;
|
||||
|
||||
typedef enum {
|
||||
DeviceIdentification,
|
||||
TCodeVersion,
|
||||
ListAxesAndUserRangePreferences,
|
||||
Stop,
|
||||
} DeviceCommand;
|
||||
|
||||
typedef enum {
|
||||
Linear,
|
||||
Rotate,
|
||||
Vibrate,
|
||||
Auxiliary,
|
||||
} TCodeCommandMotionType;
|
||||
|
||||
typedef struct {
|
||||
TCodeCommandMotionType motion_type;
|
||||
uint8_t channel_id;
|
||||
float magnitude;
|
||||
} TCodeMagnitudeCommand;
|
||||
|
||||
typedef struct {
|
||||
TCodeCommandMotionType motion_type;
|
||||
uint8_t channel_id;
|
||||
float magnitude;
|
||||
uint16_t time_interval_milliseconds;
|
||||
} TCodeMagnitudeTimeIntervalCommand;
|
||||
|
||||
typedef struct {
|
||||
TCodeCommandMotionType motion_type;
|
||||
uint8_t channel_id;
|
||||
float magnitude;
|
||||
uint16_t speed_per_hundred_milliseconds;
|
||||
} TCodeMagnitudeSpeedCommand;
|
||||
|
||||
|
||||
typedef struct {
|
||||
TCodeCommandType command_type;
|
||||
union {
|
||||
DeviceCommand device_command;
|
||||
TCodeMagnitudeCommand magnitude_command;
|
||||
TCodeMagnitudeTimeIntervalCommand magnitude_time_interval_command;
|
||||
TCodeMagnitudeSpeedCommand magnitude_speed_command;
|
||||
} data;
|
||||
} TCodeCommand;
|
||||
|
||||
typedef struct {
|
||||
uint16_t size;
|
||||
TCodeCommand *commands;
|
||||
} TCodeCommandArray;
|
||||
|
||||
|
||||
TCodeCommandArray tcode_decode(uint8_t *buffer, uint16_t size);
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1,149 @@
|
||||
#include "gpio_simple_motor.h"
|
||||
#include "../fbp.h"
|
||||
|
||||
static const uint16_t BT_SERIAL_BUFFER_SIZE = 128;
|
||||
static const uint32_t DEFAULT_FREQ = 1000;
|
||||
static const FuriHalPwmOutputId DEFAULT_PWM_OUTPUT_ID = FuriHalPwmOutputIdTim1PA7;
|
||||
|
||||
struct GPIOSimpleMotor {
|
||||
View* view;
|
||||
FBP* fbp;
|
||||
|
||||
uint8_t current_pwm_duty;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
char* display_text_1;
|
||||
char* display_text_2;
|
||||
char* display_text_3;
|
||||
} GPIOSimpleMotorModel;
|
||||
|
||||
|
||||
static void process_general_command(TCodeCommand command, GPIOSimpleMotor* motor) {
|
||||
if (command.command_type == Magnitude && command.data.magnitude_command.motion_type == Vibrate && command.data.magnitude_command.channel_id == 0) {
|
||||
// just enable vibration on X
|
||||
uint8_t new_duty = (uint8_t) (command.data.magnitude_command.magnitude * 100);
|
||||
if (new_duty > 100) {
|
||||
new_duty = 100;
|
||||
}
|
||||
FURI_LOG_D(TAG, "Setting vibration power on %u", new_duty);
|
||||
|
||||
// using Pulse-Widht Modulation to control a motor via a transistor
|
||||
// just google for a typical arduino + PWM + motor scheme
|
||||
if (new_duty == 0) {
|
||||
furi_hal_pwm_stop(DEFAULT_PWM_OUTPUT_ID);
|
||||
} else if (motor->current_pwm_duty == 0) {
|
||||
furi_hal_pwm_start(DEFAULT_PWM_OUTPUT_ID, DEFAULT_FREQ, new_duty);
|
||||
} else {
|
||||
furi_hal_pwm_set_params(DEFAULT_PWM_OUTPUT_ID, DEFAULT_FREQ, new_duty);
|
||||
}
|
||||
motor->current_pwm_duty = new_duty;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static uint16_t bt_serial_event_callback(SerialServiceEvent event, void* context) {
|
||||
furi_assert(context);
|
||||
GPIOSimpleMotor* motor = context;
|
||||
|
||||
if(event.event == SerialServiceEventTypeDataReceived) {
|
||||
TCodeCommandArray commands = tcode_decode(event.data.buffer, event.data.size);
|
||||
FURI_LOG_D(TAG, "Decoded commands array size: %u", commands.size);
|
||||
for (uint16_t i = 0; i < commands.size; i++) {
|
||||
FURI_LOG_D(TAG, "Command #%u, type: %u\n", i, commands.commands[i].command_type);
|
||||
}
|
||||
for (uint16_t i = 0; i < commands.size; i++) {
|
||||
// looking for first vibro command to execute
|
||||
TCodeCommand current_command = commands.commands[i];
|
||||
TCodeCommandType type = current_command.command_type;
|
||||
if ((type == Magnitude || type == MagnitudeSpeed || type == MagnitudeTimeInterval)) {
|
||||
process_general_command(current_command, motor);
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool input_callback(InputEvent* event, void* ctx) {
|
||||
furi_assert(ctx);
|
||||
GPIOSimpleMotor* motor = ctx;
|
||||
if(event->key == InputKeyBack) {
|
||||
furi_hal_bt_serial_set_event_callback(0, NULL, NULL);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event->key == InputKeyOk) {
|
||||
if (furi_hal_bt_is_active()) {
|
||||
FURI_LOG_D(TAG, "BT is working, hijacking the serial connection...");
|
||||
furi_hal_bt_start_advertising();
|
||||
furi_hal_bt_serial_set_event_callback(BT_SERIAL_BUFFER_SIZE, bt_serial_event_callback, motor);
|
||||
|
||||
with_view_model(
|
||||
motor->view,
|
||||
GPIOSimpleMotorModel * model,
|
||||
{
|
||||
model->display_text_1 = "";
|
||||
model->display_text_2 = "Ready ^_^";
|
||||
model->display_text_3 = "";
|
||||
},
|
||||
true);
|
||||
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Please, enable the Bluetooth and restart the app");
|
||||
|
||||
with_view_model(
|
||||
motor->view,
|
||||
GPIOSimpleMotorModel * model,
|
||||
{
|
||||
model->display_text_1 = "Error:";
|
||||
model->display_text_2 = "Bluetooth is not enabled";
|
||||
model->display_text_3 = "";
|
||||
},
|
||||
true);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void draw_callback(Canvas* canvas, void* ctx) {
|
||||
furi_assert(ctx);
|
||||
GPIOSimpleMotorModel* app = ctx;
|
||||
canvas_draw_str_aligned(canvas, 64, 24, AlignCenter, AlignCenter, (char*)app->display_text_1);
|
||||
canvas_draw_str_aligned(canvas, 64, 32, AlignCenter, AlignCenter, (char*)app->display_text_2);
|
||||
canvas_draw_str_aligned(canvas, 64, 40, AlignCenter, AlignCenter, (char*)app->display_text_3);
|
||||
}
|
||||
|
||||
GPIOSimpleMotor* gpio_simple_motor_alloc(FBP* fbp) {
|
||||
furi_assert(fbp);
|
||||
GPIOSimpleMotor* motor = malloc(sizeof(GPIOSimpleMotor));
|
||||
motor->view = view_alloc();
|
||||
motor->fbp = fbp;
|
||||
view_set_context(motor->view, motor);
|
||||
view_allocate_model(motor->view, ViewModelTypeLocking, sizeof(GPIOSimpleMotorModel));
|
||||
view_set_draw_callback(motor->view, draw_callback);
|
||||
view_set_input_callback(motor->view, input_callback);
|
||||
|
||||
with_view_model(
|
||||
motor->view,
|
||||
GPIOSimpleMotorModel * model,
|
||||
{
|
||||
model->display_text_1 = "Please, connect the";
|
||||
model->display_text_2 = "transistor base to pin A7!";
|
||||
model->display_text_3 = "Press OK to start";
|
||||
},
|
||||
true);
|
||||
|
||||
return motor;
|
||||
}
|
||||
|
||||
void gpio_simple_motor_free(GPIOSimpleMotor* motor) {
|
||||
furi_assert(motor);
|
||||
furi_hal_pwm_stop(DEFAULT_PWM_OUTPUT_ID);
|
||||
view_free(motor->view);
|
||||
free(motor);
|
||||
}
|
||||
|
||||
View* gpio_simple_motor_get_view(GPIOSimpleMotor* motor) {
|
||||
furi_assert(motor);
|
||||
return motor->view;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
#include <gui/view.h>
|
||||
#include <furi_hal_gpio.h>
|
||||
#include <furi_hal_pwm.h>
|
||||
|
||||
typedef struct FBP FBP;
|
||||
typedef struct GPIOSimpleMotor GPIOSimpleMotor;
|
||||
|
||||
GPIOSimpleMotor* gpio_simple_motor_alloc(FBP* fbp);
|
||||
|
||||
void gpio_simple_motor_free(GPIOSimpleMotor* motor_app);
|
||||
|
||||
View* gpio_simple_motor_get_view(GPIOSimpleMotor* motor_app);
|
||||
@@ -0,0 +1,133 @@
|
||||
#include "internal.h"
|
||||
#include "../fbp.h"
|
||||
|
||||
static const uint16_t BT_SERIAL_BUFFER_SIZE = 128;
|
||||
|
||||
struct FlipperVibrator {
|
||||
View* view;
|
||||
FBP* fbp;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
char* display_text;
|
||||
} FlipperVibratorModel;
|
||||
|
||||
|
||||
static void process_general_command(TCodeCommand command) {
|
||||
if (command.command_type == Magnitude && command.data.magnitude_command.motion_type == Vibrate && command.data.magnitude_command.channel_id == 0) {
|
||||
furi_hal_vibro_on(command.data.magnitude_command.magnitude > 0.1f);
|
||||
return;
|
||||
}
|
||||
|
||||
if (command.command_type == MagnitudeSpeed && command.data.magnitude_speed_command.motion_type == Vibrate && command.data.magnitude_speed_command.channel_id == 0) {
|
||||
furi_hal_vibro_on(command.data.magnitude_speed_command.magnitude > 0.1f);
|
||||
return;
|
||||
}
|
||||
|
||||
if (command.command_type == MagnitudeTimeInterval && command.data.magnitude_time_interval_command.motion_type == Vibrate && command.data.magnitude_time_interval_command.channel_id == 0) {
|
||||
furi_hal_vibro_on(command.data.magnitude_time_interval_command.magnitude > 0.1f);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static uint16_t bt_serial_event_callback(SerialServiceEvent event, void* context) {
|
||||
furi_assert(context);
|
||||
FlipperVibrator* flipper_vibrator = context;
|
||||
UNUSED(flipper_vibrator);
|
||||
|
||||
if(event.event == SerialServiceEventTypeDataReceived) {
|
||||
FURI_LOG_D(TAG, "SerialServiceEventTypeDataReceived");
|
||||
FURI_LOG_D(TAG, "Size: %u", event.data.size);
|
||||
FURI_LOG_D(TAG, "Data: ");
|
||||
for (size_t i = 0; i < event.data.size; i++)
|
||||
{
|
||||
printf("%X ", event.data.buffer[i]);
|
||||
}
|
||||
printf("\r\n");
|
||||
|
||||
TCodeCommandArray commands = tcode_decode(event.data.buffer, event.data.size);
|
||||
FURI_LOG_D(TAG, "Decoded commands array size: %u", commands.size);
|
||||
for (uint16_t i = 0; i < commands.size; i++) {
|
||||
FURI_LOG_D(TAG, "Command #%u, type: %u\n", i, commands.commands[i].command_type);
|
||||
}
|
||||
for (uint16_t i = 0; i < commands.size; i++) {
|
||||
// looking for first vibro command to execute
|
||||
TCodeCommand current_command = commands.commands[i];
|
||||
TCodeCommandType type = current_command.command_type;
|
||||
if ((type == Magnitude || type == MagnitudeSpeed || type == MagnitudeTimeInterval)) {
|
||||
process_general_command(current_command);
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool input_callback(InputEvent* event, void* ctx) {
|
||||
furi_assert(ctx);
|
||||
FlipperVibrator* flipper_vibrator = ctx;
|
||||
if(event->key == InputKeyBack) {
|
||||
furi_hal_bt_serial_set_event_callback(0, NULL, NULL);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event->key == InputKeyOk) {
|
||||
if (furi_hal_bt_is_active()) {
|
||||
FURI_LOG_D(TAG, "BT is working, hijacking the serial connection...");
|
||||
furi_hal_bt_start_advertising();
|
||||
furi_hal_bt_serial_set_event_callback(BT_SERIAL_BUFFER_SIZE, bt_serial_event_callback, flipper_vibrator);
|
||||
|
||||
with_view_model(
|
||||
flipper_vibrator->view,
|
||||
FlipperVibratorModel * model,
|
||||
{ model->display_text = "Ready ^_^"; },
|
||||
true);
|
||||
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Please, enable the Bluetooth and restart the app");
|
||||
|
||||
with_view_model(
|
||||
flipper_vibrator->view,
|
||||
FlipperVibratorModel * model,
|
||||
{ model->display_text = "Error: Bluetooth not enabled"; },
|
||||
true);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void draw_callback(Canvas* canvas, void* ctx) {
|
||||
furi_assert(ctx);
|
||||
FlipperVibratorModel* app = ctx;
|
||||
canvas_draw_str_aligned(canvas, 64, 32, AlignCenter, AlignCenter, (char*)app->display_text);
|
||||
}
|
||||
|
||||
FlipperVibrator* flipper_vibrator_alloc(FBP* fbp) {
|
||||
furi_assert(fbp);
|
||||
FlipperVibrator* flipper_vibrator = malloc(sizeof(FlipperVibrator));
|
||||
flipper_vibrator->view = view_alloc();
|
||||
flipper_vibrator->fbp = fbp;
|
||||
view_set_context(flipper_vibrator->view, flipper_vibrator);
|
||||
view_allocate_model(flipper_vibrator->view, ViewModelTypeLocking, sizeof(FlipperVibratorModel));
|
||||
view_set_draw_callback(flipper_vibrator->view, draw_callback);
|
||||
view_set_input_callback(flipper_vibrator->view, input_callback);
|
||||
|
||||
with_view_model(
|
||||
flipper_vibrator->view,
|
||||
FlipperVibratorModel * model,
|
||||
{ model->display_text = "Press OK to start"; },
|
||||
true);
|
||||
|
||||
return flipper_vibrator;
|
||||
}
|
||||
|
||||
void flipper_vibrator_free(FlipperVibrator* flipper_vibrator) {
|
||||
furi_assert(flipper_vibrator);
|
||||
view_free(flipper_vibrator->view);
|
||||
free(flipper_vibrator);
|
||||
}
|
||||
|
||||
View* flipper_vibrator_get_view(FlipperVibrator* flipper_vibrator) {
|
||||
furi_assert(flipper_vibrator);
|
||||
return flipper_vibrator->view;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
#include <gui/view.h>
|
||||
#include <furi_hal_vibro.h>
|
||||
|
||||
typedef struct FBP FBP;
|
||||
typedef struct FlipperVibrator FlipperVibrator;
|
||||
|
||||
FlipperVibrator* flipper_vibrator_alloc(FBP* fbp);
|
||||
|
||||
void flipper_vibrator_free(FlipperVibrator* flipper_vibrator);
|
||||
|
||||
View* flipper_vibrator_get_view(FlipperVibrator* flipper_vibrator);
|
||||
Reference in New Issue
Block a user