Skip to content

Commit

Permalink
[FL-3889] 5V on GPIO control for ext. modules (#3830)
Browse files Browse the repository at this point in the history
* Make file extensions case-insensitive
* Bump protobuf version
* Add support for 5V control via RPC
* Add support for 5V control via Expansion protocol
* Update running instructions
* Update expansion module documentation
* Prettify condition
* Test RPC OTG control as well
* Assets: bump protobuf version
* Disable PVS license expiration check, fix PVS warnings

Co-authored-by: あく <alleteam@gmail.com>
  • Loading branch information
gsurkov and skotopes authored Sep 5, 2024
1 parent b040db0 commit fa2d611
Show file tree
Hide file tree
Showing 8 changed files with 160 additions and 21 deletions.
59 changes: 55 additions & 4 deletions applications/debug/expansion_test/expansion_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,27 @@
* 13 -> 16 (USART TX to LPUART RX)
* 14 -> 15 (USART RX to LPUART TX)
*
* Optional: Connect an LED with an appropriate series resistor
* between pins 1 and 8. It will always be on if the device is
* connected to USB power, so unplug it before running the app.
*
* What this application does:
*
* - Enables module support and emulates the module on a single device
* (hence the above connection),
* - Connects to the expansion module service, sets baud rate,
* - Enables OTG (5V) on GPIO via plain expansion protocol,
* - Waits 5 cycles of idle loop (1 second),
* - Starts the RPC session,
* - Disables OTG (5V) on GPIO via RPC messages,
* - Waits 5 cycles of idle loop (1 second),
* - Creates a directory at `/ext/ExpansionTest` and writes a file
* named `test.txt` under it,
* - Plays an audiovisual alert (sound and blinking display),
* - Waits 10 cycles of idle loop,
* - Enables OTG (5V) on GPIO via RPC messages,
* - Waits 5 cycles of idle loop (1 second),
* - Stops the RPC session,
* - Waits another 10 cycles of idle loop,
* - Disables OTG (5V) on GPIO via plain expansion protocol,
* - Exits (plays a sound if any of the above steps failed).
*/
#include <furi.h>
Expand Down Expand Up @@ -302,6 +311,22 @@ static bool expansion_test_app_handshake(ExpansionTestApp* instance) {
return success;
}

static bool expansion_test_app_enable_otg(ExpansionTestApp* instance, bool enable) {
bool success = false;

do {
const ExpansionFrameControlCommand command = enable ?
ExpansionFrameControlCommandEnableOtg :
ExpansionFrameControlCommandDisableOtg;
if(!expansion_test_app_send_control_request(instance, command)) break;
if(!expansion_test_app_receive_frame(instance, &instance->frame)) break;
if(!expansion_test_app_is_success_response(&instance->frame)) break;
success = true;
} while(false);

return success;
}

static bool expansion_test_app_start_rpc(ExpansionTestApp* instance) {
bool success = false;

Expand Down Expand Up @@ -396,6 +421,27 @@ static bool expansion_test_app_rpc_alert(ExpansionTestApp* instance) {
return success;
}

static bool expansion_test_app_rpc_enable_otg(ExpansionTestApp* instance, bool enable) {
bool success = false;

instance->msg.command_id++;
instance->msg.command_status = PB_CommandStatus_OK;
instance->msg.which_content = PB_Main_gpio_set_otg_mode_tag;
instance->msg.content.gpio_set_otg_mode.mode = enable ? PB_Gpio_GpioOtgMode_ON :
PB_Gpio_GpioOtgMode_OFF;
instance->msg.has_next = false;

do {
if(!expansion_test_app_send_rpc_request(instance, &instance->msg)) break;
if(!expansion_test_app_receive_rpc_request(instance, &instance->msg)) break;
if(instance->msg.which_content != PB_Main_empty_tag) break;
if(instance->msg.command_status != PB_CommandStatus_OK) break;
success = true;
} while(false);

return success;
}

static bool expansion_test_app_idle(ExpansionTestApp* instance, uint32_t num_cycles) {
uint32_t num_cycles_done;
for(num_cycles_done = 0; num_cycles_done < num_cycles; ++num_cycles_done) {
Expand Down Expand Up @@ -434,13 +480,18 @@ int32_t expansion_test_app(void* p) {
if(!expansion_test_app_send_presence(instance)) break;
if(!expansion_test_app_wait_ready(instance)) break;
if(!expansion_test_app_handshake(instance)) break;
if(!expansion_test_app_enable_otg(instance, true)) break;
if(!expansion_test_app_idle(instance, 5)) break;
if(!expansion_test_app_start_rpc(instance)) break;
if(!expansion_test_app_rpc_enable_otg(instance, false)) break;
if(!expansion_test_app_idle(instance, 5)) break;
if(!expansion_test_app_rpc_mkdir(instance)) break;
if(!expansion_test_app_rpc_write(instance)) break;
if(!expansion_test_app_rpc_alert(instance)) break;
if(!expansion_test_app_idle(instance, 10)) break;
if(!expansion_test_app_rpc_enable_otg(instance, true)) break;
if(!expansion_test_app_idle(instance, 5)) break;
if(!expansion_test_app_stop_rpc(instance)) break;
if(!expansion_test_app_idle(instance, 10)) break;
if(!expansion_test_app_enable_otg(instance, false)) break;
success = true;
} while(false);

Expand Down
24 changes: 22 additions & 2 deletions applications/services/expansion/expansion_protocol.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,28 @@ typedef enum {
* @brief Enumeration of suported control commands.
*/
typedef enum {
ExpansionFrameControlCommandStartRpc = 0x00, /**< Start an RPC session. */
ExpansionFrameControlCommandStopRpc = 0x01, /**< Stop an open RPC session. */
/** @brief Start an RPC session.
*
* Must only be used while the RPC session is NOT active.
*/
ExpansionFrameControlCommandStartRpc = 0x00,
/** @brief Stop an open RPC session.
*
* Must only be used while the RPC session IS active.
*/
ExpansionFrameControlCommandStopRpc = 0x01,
/** @brief Enable OTG (5V) on external GPIO.
*
* Must only be used while the RPC session is NOT active,
* otherwise OTG is to be controlled via RPC messages.
*/
ExpansionFrameControlCommandEnableOtg = 0x02,
/** @brief Disable OTG (5V) on external GPIO.
*
* Must only be used while the RPC session is NOT active,
* otherwise OTG is to be controlled via RPC messages.
*/
ExpansionFrameControlCommandDisableOtg = 0x03,
} ExpansionFrameControlCommand;

#pragma pack(push, 1)
Expand Down
26 changes: 20 additions & 6 deletions applications/services/expansion/expansion_worker.c
Original file line number Diff line number Diff line change
Expand Up @@ -245,9 +245,18 @@ static bool expansion_worker_handle_state_connected(

do {
if(rx_frame->header.type == ExpansionFrameTypeControl) {
if(rx_frame->content.control.command != ExpansionFrameControlCommandStartRpc) break;
instance->state = ExpansionWorkerStateRpcActive;
if(!expansion_worker_rpc_session_open(instance)) break;
const uint8_t command = rx_frame->content.control.command;
if(command == ExpansionFrameControlCommandStartRpc) {
if(!expansion_worker_rpc_session_open(instance)) break;
instance->state = ExpansionWorkerStateRpcActive;
} else if(command == ExpansionFrameControlCommandEnableOtg) {
furi_hal_power_enable_otg();
} else if(command == ExpansionFrameControlCommandDisableOtg) {
furi_hal_power_disable_otg();
} else {
break;
}

if(!expansion_worker_send_status_response(instance, ExpansionFrameErrorNone)) break;

} else if(rx_frame->header.type == ExpansionFrameTypeHeartbeat) {
Expand Down Expand Up @@ -279,9 +288,14 @@ static bool expansion_worker_handle_state_rpc_active(
if(size_consumed != rx_frame->content.data.size) break;

} else if(rx_frame->header.type == ExpansionFrameTypeControl) {
if(rx_frame->content.control.command != ExpansionFrameControlCommandStopRpc) break;
instance->state = ExpansionWorkerStateConnected;
expansion_worker_rpc_session_close(instance);
const uint8_t command = rx_frame->content.control.command;
if(command == ExpansionFrameControlCommandStopRpc) {
instance->state = ExpansionWorkerStateConnected;
expansion_worker_rpc_session_close(instance);
} else {
break;
}

if(!expansion_worker_send_status_response(instance, ExpansionFrameErrorNone)) break;

} else if(rx_frame->header.type == ExpansionFrameTypeStatus) {
Expand Down
6 changes: 3 additions & 3 deletions applications/services/gui/modules/byte_input.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ typedef struct {

static const uint8_t keyboard_origin_x = 7;
static const uint8_t keyboard_origin_y = 31;
static const uint8_t keyboard_row_count = 2;
static const int8_t keyboard_row_count = 2;
static const uint8_t enter_symbol = '\r';
static const uint8_t backspace_symbol = '\b';
static const uint8_t max_drawable_bytes = 8;
Expand Down Expand Up @@ -649,11 +649,11 @@ static void byte_input_view_draw_callback(Canvas* canvas, void* _model) {
}
canvas_set_font(canvas, FontKeyboard);
// Draw keyboard
for(uint8_t row = 0; row < keyboard_row_count; row++) {
for(int8_t row = 0; row < keyboard_row_count; row++) {
const uint8_t column_count = byte_input_get_row_size(row);
const ByteInputKey* keys = byte_input_get_row(row);

for(size_t column = 0; column < column_count; column++) {
for(uint8_t column = 0; column < column_count; column++) {
if(keys[column].value == enter_symbol) {
canvas_set_color(canvas, ColorBlack);
if(model->selected_row == row && model->selected_column == column) {
Expand Down
45 changes: 45 additions & 0 deletions applications/services/rpc/rpc_gpio.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include "rpc_i.h"
#include "gpio.pb.h"
#include <furi_hal_gpio.h>
#include <furi_hal_power.h>
#include <furi_hal_resources.h>

static const GpioPin* rpc_pin_to_hal_pin(PB_Gpio_GpioPin rpc_pin) {
Expand Down Expand Up @@ -188,6 +189,44 @@ void rpc_system_gpio_set_input_pull(const PB_Main* request, void* context) {
free(response);
}

void rpc_system_gpio_get_otg_mode(const PB_Main* request, void* context) {
furi_assert(request);
furi_assert(context);
furi_assert(request->which_content == PB_Main_gpio_get_otg_mode_tag);

RpcSession* session = context;

const bool otg_enabled = furi_hal_power_is_otg_enabled();

PB_Main* response = malloc(sizeof(PB_Main));
response->command_id = request->command_id;
response->which_content = PB_Main_gpio_get_otg_mode_response_tag;
response->content.gpio_get_otg_mode_response.mode = otg_enabled ? PB_Gpio_GpioOtgMode_ON :
PB_Gpio_GpioOtgMode_OFF;

rpc_send_and_release(session, response);

free(response);
}

void rpc_system_gpio_set_otg_mode(const PB_Main* request, void* context) {
furi_assert(request);
furi_assert(context);
furi_assert(request->which_content == PB_Main_gpio_set_otg_mode_tag);

RpcSession* session = context;

const PB_Gpio_GpioOtgMode mode = request->content.gpio_set_otg_mode.mode;

if(mode == PB_Gpio_GpioOtgMode_OFF) {
furi_hal_power_disable_otg();
} else {
furi_hal_power_enable_otg();
}

rpc_send_and_release_empty(session, request->command_id, PB_CommandStatus_OK);
}

void* rpc_system_gpio_alloc(RpcSession* session) {
furi_assert(session);

Expand All @@ -212,5 +251,11 @@ void* rpc_system_gpio_alloc(RpcSession* session) {
rpc_handler.message_handler = rpc_system_gpio_set_input_pull;
rpc_add_handler(session, PB_Main_gpio_set_input_pull_tag, &rpc_handler);

rpc_handler.message_handler = rpc_system_gpio_get_otg_mode;
rpc_add_handler(session, PB_Main_gpio_get_otg_mode_tag, &rpc_handler);

rpc_handler.message_handler = rpc_system_gpio_set_otg_mode;
rpc_add_handler(session, PB_Main_gpio_set_otg_mode_tag, &rpc_handler);

return NULL;
}
2 changes: 1 addition & 1 deletion assets/protobuf
Submodule protobuf updated 3 files
+4 −0 Changelog
+3 −0 flipper.proto
+16 −0 gpio.proto
18 changes: 13 additions & 5 deletions documentation/ExpansionModules.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,18 +73,26 @@ If the requested baud rate is supported by the host, it SHALL respond with a STA

### Control frame

CONTROL frames are used to control various aspects of the communication. As of now, the sole purpose of CONTROL frames is to start and stop the RPC session.
CONTROL frames are used to control various aspects of the communication and enable/disable various device features.

| Header (1 byte) | Contents (1 byte) | Checksum (1 byte) |
|-----------------|-------------------|-------------------|
| 0x04 | Command | XOR checksum |

The `Command` field SHALL have one of the followind values:

| Command | Meaning |
|---------|-------------------|
| 0x00 | Start RPC session |
| 0x01 | Stop RPC session |
| Command | Meaning | Note |
|---------|--------------------------|:----:|
| 0x00 | Start RPC session | 1 |
| 0x01 | Stop RPC session | 2 |
| 0x02 | Enable OTG (5V) on GPIO | 3 |
| 0x03 | Disable OTG (5V) on GPIO | 3 |

Notes:

1. Must only be used while the RPC session NOT active.
2. Must only be used while the RPC session IS active.
3. See 1, otherwise OTG is to be controlled via RPC messages.

### Data frame

Expand Down
1 change: 1 addition & 0 deletions scripts/fbt_tools/pvsstudio.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ def generate(env):
PVSOPTIONS=[
"@.pvsoptions",
"-j${PVSNCORES}",
"--disableLicenseExpirationCheck",
# "--incremental", # kinda broken on PVS side
],
PVSCONVOPTIONS=[
Expand Down

0 comments on commit fa2d611

Please sign in to comment.