diff --git a/device-config.json b/device-config.json index eef6036bcf3..898e84ea87a 100644 --- a/device-config.json +++ b/device-config.json @@ -16,7 +16,7 @@ ], "btle": { "names": [ - "Flipper Zero" + "Flipper *" ], "services": { "8fe5b3d5-2e7f-4a98-2a48-7acc60fe0000": { @@ -41,4 +41,4 @@ } } } -} \ No newline at end of file +} diff --git a/src/fbp.c b/src/fbp.c index 1478f614b7a..60083a4a69e 100644 --- a/src/fbp.c +++ b/src/fbp.c @@ -2,6 +2,7 @@ enum FBPSubmenuIndex { FBPSubmenuIndexInternal, + FBPSubmenuIndexGPIOSimpleMotor, }; uint32_t fbp_start_view(void* context) { @@ -19,6 +20,8 @@ void fbp_submenu_callback(void* context, uint32_t index) { 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); } } @@ -46,6 +49,12 @@ FBP* fbp_alloc() { 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; } @@ -57,9 +66,15 @@ void fbs_free(FBP* app) { 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); diff --git a/src/fbp.h b/src/fbp.h index 08d2d10611e..bd669ad0332 100644 --- a/src/fbp.h +++ b/src/fbp.h @@ -10,6 +10,7 @@ #include "tcode.h" #include "views/internal.h" +#include "views/gpio_simple_motor.h" #define TAG "Flipper BP" @@ -27,9 +28,11 @@ struct FBP { FuriMessageQueue* event_queue; FlipperVibrator* flipper_vibrator; + GPIOSimpleMotor* gpio_simple_motor; }; typedef enum { FBPAppViewSubmenu, FBPAppViewInternal, + FBPAppViewGPIOSimpleMotor, } FBPAppView; \ No newline at end of file diff --git a/src/tcode.h b/src/tcode.h index e5fc04d510e..7174965fe0c 100644 --- a/src/tcode.h +++ b/src/tcode.h @@ -1,3 +1,4 @@ +#pragma once #include typedef enum { diff --git a/src/views/gpio_simple_motor.c b/src/views/gpio_simple_motor.c new file mode 100644 index 00000000000..5fef098dc9a --- /dev/null +++ b/src/views/gpio_simple_motor.c @@ -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; +} \ No newline at end of file diff --git a/src/views/gpio_simple_motor.h b/src/views/gpio_simple_motor.h new file mode 100644 index 00000000000..e2668a45df9 --- /dev/null +++ b/src/views/gpio_simple_motor.h @@ -0,0 +1,13 @@ +#pragma once +#include +#include +#include + +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); diff --git a/src/views/internal.c b/src/views/internal.c index def09764d37..8d458d92094 100644 --- a/src/views/internal.c +++ b/src/views/internal.c @@ -1,7 +1,7 @@ #include "internal.h" #include "../fbp.h" -const uint16_t BT_SERIAL_BUFFER_SIZE = 128; +static const uint16_t BT_SERIAL_BUFFER_SIZE = 128; struct FlipperVibrator { View* view; @@ -96,7 +96,7 @@ static bool input_callback(InputEvent* event, void* ctx) { return true; } -void draw_callback(Canvas* canvas, void* ctx) { +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);