Added xMasterX API v11.x source

This commit is contained in:
UberGuidoZ
2023-01-25 23:52:38 -08:00
parent 33322c0d73
commit e292fbfcd5
1094 changed files with 102160 additions and 0 deletions
@@ -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);