2025-09-09 16:19:57 +01:00
/**
2025-09-11 12:58:22 +01:00
* @ file web_control . ino
2025-09-09 16:19:57 +01:00
* @ brief Demo sketch for DShotRMT library
* @ author Wastl Kraus
* @ date 2025 - 09 - 09
* @ license MIT
*/
2025-09-20 15:47:39 +01:00
/******************************************************************
* SECURITY WARNING
* This example provides a web interface to control a motor
* without any authentication . It is intended for use on a
* trusted local network only .
*
* DO NOT EXPOSE THIS DEVICE DIRECTLY TO THE INTERNET .
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2025-09-09 16:19:57 +01:00
# include <Arduino.h>
# include <WiFi.h>
# include <DShotRMT.h>
2025-10-01 23:31:24 +01:00
# include "web_utilities/web_content.h"
2025-09-09 16:19:57 +01:00
# include <ArduinoJson.h>
# include <AsyncTCP.h>
# include <ESPAsyncWebServer.h>
// Wifi Configuration
static constexpr auto * ssid = " DShotRMT Control " ;
static constexpr auto * password = " 12345678 " ;
IPAddress local_IP ( 10 , 10 , 10 , 1 ) ;
IPAddress gateway ( 0 , 0 , 0 , 0 ) ;
IPAddress subnet ( 255 , 255 , 255 , 0 ) ;
// USB serial port settings
static constexpr auto & USB_SERIAL = Serial ;
static constexpr auto USB_SERIAL_BAUD = 115200 ;
// Motor configuration - Pin number or GPIO_PIN
2025-10-01 08:48:26 +01:00
static constexpr gpio_num_t MOTOR01_PIN = GPIO_NUM_27 ;
2025-09-09 16:19:57 +01:00
// Supported: DSHOT150, DSHOT300, DSHOT600, (DSHOT1200)
2025-10-01 08:48:26 +01:00
static constexpr dshot_mode_t DSHOT_MODE = DSHOT300 ;
2025-09-09 16:19:57 +01:00
// BiDirectional DShot Support (default: false)
2025-09-23 22:48:18 +01:00
static constexpr auto IS_BIDIRECTIONAL = false ; // Note: Bidirectional DShot is currently not officially supported due to instability and external hardware requirements.
2025-09-09 16:19:57 +01:00
// Motor magnet count for RPM calculation
static constexpr auto MOTOR01_MAGNET_COUNT = 14 ;
// Creates the motor instance
2025-10-01 23:31:24 +01:00
DShotRMT motor01 ( MOTOR01_PIN , DSHOT_MODE , IS_BIDIRECTIONAL , MOTOR01_MAGNET_COUNT ) ;
2025-09-09 16:19:57 +01:00
// Web Server Configuration
AsyncWebServer server ( 80 ) ;
AsyncWebSocket ws ( " /ws " ) ;
// Global variables
2025-09-25 15:32:40 +01:00
static uint16_t throttle = DSHOT_CMD_MOTOR_STOP ;
2025-09-09 16:19:57 +01:00
static bool isArmed = false ;
static bool continuous_throttle = true ;
// Helpers (forward declaration)
void printMenu ( ) ;
void handleSerialInput ( const String & input ) ;
void handleWebSocketMessage ( void * arg , uint8_t * data , size_t len ) ;
void onWsEvent ( AsyncWebSocket * server , AsyncWebSocketClient * client , AwsEventType type , void * arg , uint8_t * data , size_t len ) ;
void setArmingStatus ( bool armed ) ;
//
void setup ( )
{
USB_SERIAL . begin ( USB_SERIAL_BAUD ) ;
motor01 . begin ( ) ;
2025-10-01 08:48:26 +01:00
printCpuInfo ( USB_SERIAL ) ;
2025-09-09 16:19:57 +01:00
// Set IP Address
WiFi . softAPConfig ( local_IP , gateway , subnet ) ;
// Start Wifi Access Point
USB_SERIAL . println ( " \n Starting Access Point... " ) ;
WiFi . softAP ( ssid , password ) ;
IPAddress IP = WiFi . softAPIP ( ) ;
USB_SERIAL . print ( " Access Point IP address: " ) ;
USB_SERIAL . println ( IP ) ;
// Init WebSockets and Webserver
USB_SERIAL . println ( " \n Starting Webserver... " ) ;
ws . onEvent ( onWsEvent ) ;
server . addHandler ( & ws ) ;
server . on ( " / " , HTTP_GET , [ ] ( AsyncWebServerRequest * request )
{ request - > send_P ( 200 , " text/html " , index_html ) ; } ) ;
server . begin ( ) ;
USB_SERIAL . println ( " HTTP server started. " ) ;
// Initialize with disarmed state
setArmingStatus ( false ) ;
printMenu ( ) ;
}
2025-09-11 12:58:22 +01:00
//
2025-09-09 16:19:57 +01:00
void loop ( )
{
static uint64_t last_serial_update = 0 ;
2025-09-25 15:32:40 +01:00
static uint16_t last_sent_throttle = DSHOT_CMD_MOTOR_STOP ;
2025-09-09 16:19:57 +01:00
static bool last_sent_armed = false ;
static String last_sent_rpm = " N/A " ;
// Handle serial input
if ( USB_SERIAL . available ( ) > 0 )
{
String input = USB_SERIAL . readStringUntil ( ' \n ' ) ;
input . trim ( ) ;
if ( input . length ( ) > 0 )
{
handleSerialInput ( input ) ;
}
}
// Send throttle value only if armed and continuous mode is enabled
if ( isArmed & & continuous_throttle & & throttle > 0 )
{
motor01 . sendThrottle ( throttle ) ;
}
else if ( ! isArmed & & continuous_throttle )
{
2025-09-25 15:32:40 +01:00
// Ensure motor is stopped when disarmed
motor01 . sendCommand ( DSHOT_CMD_MOTOR_STOP ) ;
2025-09-09 16:19:57 +01:00
}
// Print motor stats every 3 seconds in continuous mode
if ( ( esp_timer_get_time ( ) - last_serial_update > = 3000000 ) )
{
2025-10-01 08:48:26 +01:00
printDShotInfo ( motor01 , USB_SERIAL ) ;
2025-09-09 16:19:57 +01:00
USB_SERIAL . println ( " " ) ;
// Get Motor RPM if bidirectional and armed
if ( IS_BIDIRECTIONAL & & isArmed )
{
2025-10-01 12:44:25 +01:00
dshot_result_t telem_result = motor01 . getTelemetry ( ) ;
2025-09-26 18:01:55 +01:00
printDShotResult ( telem_result ) ;
2025-09-09 16:19:57 +01:00
}
USB_SERIAL . println ( " Type 'help' to show Menu " ) ;
// Time Stamp
last_serial_update = esp_timer_get_time ( ) ;
}
// Update JSON on data change
String current_rpm = " N/A " ;
if ( IS_BIDIRECTIONAL & & isArmed )
{
2025-10-01 12:44:25 +01:00
dshot_result_t telem_result = motor01 . getTelemetry ( ) ;
2025-09-11 12:58:22 +01:00
if ( telem_result . success & & telem_result . motor_rpm > 0 )
{
current_rpm = String ( telem_result . motor_rpm ) ;
}
2025-09-09 16:19:57 +01:00
}
if ( throttle ! = last_sent_throttle | | isArmed ! = last_sent_armed | | current_rpm ! = last_sent_rpm )
{
// Generate JSON for Webserver
JsonDocument doc ;
doc [ " throttle " ] = isArmed ? throttle : 0 ;
doc [ " armed " ] = isArmed ;
doc [ " rpm " ] = current_rpm ;
String json_output ;
json_output . reserve ( 256 ) ;
serializeJson ( doc , json_output ) ;
if ( ws . count ( ) > 0 )
{
ws . textAll ( json_output ) ;
}
// Update last run
last_sent_throttle = throttle ;
last_sent_armed = isArmed ;
last_sent_rpm = current_rpm ;
}
ws . cleanupClients ( ) ;
}
//
void setArmingStatus ( bool armed )
{
isArmed = armed ;
2025-09-11 12:58:22 +01:00
if ( armed )
2025-09-09 16:19:57 +01:00
{
continuous_throttle = true ;
2025-09-11 12:58:22 +01:00
return ;
2025-09-09 16:19:57 +01:00
}
2025-09-11 12:58:22 +01:00
// Safety: Stop motor and reset throttle when disarming
throttle = 0 ;
continuous_throttle = false ;
2025-09-25 15:32:40 +01:00
motor01 . sendCommand ( DSHOT_CMD_MOTOR_STOP ) ;
2025-09-11 12:58:22 +01:00
USB_SERIAL . println ( " " ) ;
USB_SERIAL . println ( " === MOTOR DISARMED - SAFETY STOP EXECUTED === " ) ;
2025-09-09 16:19:57 +01:00
}
//
void printMenu ( )
{
USB_SERIAL . println ( " " ) ;
USB_SERIAL . println ( " *********************************************** " ) ;
USB_SERIAL . println ( " --- DShotRMT Demo & Web UI --- " ) ;
USB_SERIAL . println ( " *********************************************** " ) ;
USB_SERIAL . println ( " Web Config: http://10.10.10.1 " ) ;
USB_SERIAL . println ( " *********************************************** " ) ;
USB_SERIAL . println ( " arm - Arm motor " ) ;
USB_SERIAL . println ( " disarm - Disarm motor (safety) " ) ;
USB_SERIAL . println ( " <value> - Set throttle (48 – 2047) " ) ;
USB_SERIAL . println ( " 0 - Stop motor " ) ;
USB_SERIAL . println ( " *********************************************** " ) ;
USB_SERIAL . println ( " cmd <number> - Send DShot command (0 - 47) " ) ;
USB_SERIAL . println ( " info - Show motor info " ) ;
if ( IS_BIDIRECTIONAL )
{
USB_SERIAL . println ( " rpm - Get telemetry data " ) ;
}
USB_SERIAL . println ( " *********************************************** " ) ;
USB_SERIAL . println ( " h / help - Show this Menu " ) ;
USB_SERIAL . println ( " *********************************************** " ) ;
USB_SERIAL . printf ( " Current Status: %s \n " , isArmed ? " ARMED " : " DISARMED " ) ;
USB_SERIAL . println ( " *********************************************** " ) ;
}
// Handle serial inputs and updates global variables
void handleSerialInput ( const String & input )
{
if ( input = = " arm " )
{
setArmingStatus ( true ) ;
2025-09-11 12:58:22 +01:00
return ;
2025-09-09 16:19:57 +01:00
}
2025-09-11 12:58:22 +01:00
if ( input = = " 0 " | | input = = " disarm " )
2025-09-09 16:19:57 +01:00
{
setArmingStatus ( false ) ;
2025-09-11 12:58:22 +01:00
return ;
2025-09-09 16:19:57 +01:00
}
2025-09-11 12:58:22 +01:00
if ( input = = " info " )
2025-09-09 16:19:57 +01:00
{
2025-10-01 08:48:26 +01:00
printDShotInfo ( motor01 , USB_SERIAL ) ;
2025-09-09 16:19:57 +01:00
USB_SERIAL . println ( " " ) ;
USB_SERIAL . printf ( " Arming Status: %s \n " , isArmed ? " ARMED " : " DISARMED " ) ;
2025-09-11 12:58:22 +01:00
return ;
2025-09-09 16:19:57 +01:00
}
2025-09-11 12:58:22 +01:00
if ( input = = " rpm " & & IS_BIDIRECTIONAL )
2025-09-09 16:19:57 +01:00
{
if ( isArmed )
{
2025-10-01 12:44:25 +01:00
dshot_result_t result = motor01 . getTelemetry ( ) ;
2025-09-26 18:01:55 +01:00
printDShotResult ( result ) ;
2025-09-09 16:19:57 +01:00
}
else
{
USB_SERIAL . println ( " " ) ;
USB_SERIAL . println ( " Cannot read RPM - Motor is DISARMED " ) ;
}
2025-09-11 12:58:22 +01:00
return ;
2025-09-09 16:19:57 +01:00
}
2025-09-11 12:58:22 +01:00
if ( input . startsWith ( " cmd " ) )
2025-09-09 16:19:57 +01:00
{
if ( ! isArmed )
{
USB_SERIAL . println ( " " ) ;
USB_SERIAL . println ( " Cannot send command - Motor is DISARMED. Use 'arm' command first. " ) ;
return ;
}
continuous_throttle = false ;
int cmd_num = input . substring ( 4 ) . toInt ( ) ;
2025-09-11 12:58:22 +01:00
2025-09-25 15:32:40 +01:00
if ( cmd_num > = DSHOT_CMD_MOTOR_STOP & & cmd_num < = DSHOT_CMD_MAX )
2025-09-09 16:19:57 +01:00
{
dshot_result_t result = motor01 . sendCommand ( cmd_num ) ;
2025-09-26 18:01:55 +01:00
printDShotResult ( result ) ;
2025-09-09 16:19:57 +01:00
}
else
{
USB_SERIAL . println ( " " ) ;
2025-09-25 15:32:40 +01:00
USB_SERIAL . printf ( " Invalid command: %d (valid range: 0 - %d) \n " , cmd_num , DSHOT_CMD_MAX ) ;
2025-09-09 16:19:57 +01:00
}
2025-09-11 12:58:22 +01:00
return ;
2025-09-09 16:19:57 +01:00
}
2025-09-11 12:58:22 +01:00
if ( input = = " h " | | input = = " help " )
2025-09-09 16:19:57 +01:00
{
printMenu ( ) ;
2025-09-11 12:58:22 +01:00
return ;
2025-09-09 16:19:57 +01:00
}
2025-09-11 12:58:22 +01:00
if ( input = = " status " )
2025-09-09 16:19:57 +01:00
{
USB_SERIAL . println ( " " ) ;
USB_SERIAL . printf ( " Arming Status: %s \n " , isArmed ? " ARMED " : " DISARMED " ) ;
USB_SERIAL . printf ( " Current Throttle: %u \n " , throttle ) ;
USB_SERIAL . printf ( " Continuous Mode: %s \n " , continuous_throttle ? " ACTIVE " : " INACTIVE " ) ;
2025-09-11 12:58:22 +01:00
return ;
2025-09-09 16:19:57 +01:00
}
2025-09-11 12:58:22 +01:00
// Handle throttle input
int throttle_value = input . toInt ( ) ;
2025-09-09 16:19:57 +01:00
2025-09-11 12:58:22 +01:00
if ( throttle_value > = DSHOT_THROTTLE_MIN & & throttle_value < = DSHOT_THROTTLE_MAX )
{
if ( ! isArmed )
2025-09-09 16:19:57 +01:00
{
2025-09-11 12:58:22 +01:00
USB_SERIAL . println ( " " ) ;
USB_SERIAL . println ( " Cannot set throttle - Motor is DISARMED. Use 'arm' command first. " ) ;
return ;
2025-09-09 16:19:57 +01:00
}
2025-09-11 12:58:22 +01:00
throttle = throttle_value ;
continuous_throttle = true ;
dshot_result_t result = motor01 . sendThrottle ( throttle ) ;
if ( result . success )
2025-09-09 16:19:57 +01:00
{
USB_SERIAL . println ( " " ) ;
2025-09-11 12:58:22 +01:00
USB_SERIAL . printf ( " Throttle set to %u (continuous mode active) \n " , throttle ) ;
2025-09-09 16:19:57 +01:00
}
2025-09-11 12:58:22 +01:00
return ;
}
if ( throttle_value = = 0 )
{
throttle = 0 ;
continuous_throttle = false ;
2025-09-25 15:32:40 +01:00
dshot_result_t result = motor01 . sendCommand ( DSHOT_CMD_MOTOR_STOP ) ;
2025-09-26 18:01:55 +01:00
printDShotResult ( result ) ;
2025-09-11 12:58:22 +01:00
return ;
2025-09-09 16:19:57 +01:00
}
2025-09-11 12:58:22 +01:00
// Invalid input
USB_SERIAL . println ( " " ) ;
USB_SERIAL . printf ( " Invalid input: '%s' \n " , input . c_str ( ) ) ;
USB_SERIAL . printf ( " Valid throttle range: %d - %d \n " , DSHOT_THROTTLE_MIN , DSHOT_THROTTLE_MAX ) ;
USB_SERIAL . println ( " Use 'arm' to enable motor control " ) ;
2025-09-09 16:19:57 +01:00
}
// Websocket request processing
void handleWebSocketMessage ( void * arg , uint8_t * data , size_t len )
{
JsonDocument doc ;
DeserializationError error = deserializeJson ( doc , data , len ) ;
if ( error )
{
USB_SERIAL . print ( F ( " deserializeJson() failed: " ) ) ;
USB_SERIAL . println ( error . c_str ( ) ) ;
return ;
}
bool armedFromWeb = false ;
// Handle arming status
if ( doc . containsKey ( " armed " ) )
{
bool armed = doc [ " armed " ] ;
setArmingStatus ( armed ) ;
armedFromWeb = true ;
}
// Handle throttle value (only if armed)
2025-09-11 12:58:22 +01:00
if ( doc . containsKey ( " throttle " ) )
2025-09-09 16:19:57 +01:00
{
2025-09-11 12:58:22 +01:00
if ( ! isArmed )
{
throttle = 0 ;
continuous_throttle = false ;
// Ignore throttle commands when disarmed
USB_SERIAL . println ( " " ) ;
USB_SERIAL . println ( " Web throttle command ignored - Motor is DISARMED " ) ;
return ;
}
2025-09-09 16:19:57 +01:00
uint16_t web_throttle = doc [ " throttle " ] ;
if ( web_throttle = = 0 )
{
throttle = 0 ;
continuous_throttle = false ;
2025-09-25 15:32:40 +01:00
motor01 . sendCommand ( DSHOT_CMD_MOTOR_STOP ) ;
2025-09-09 16:19:57 +01:00
}
else if ( web_throttle > = DSHOT_THROTTLE_MIN & & web_throttle < = DSHOT_THROTTLE_MAX )
{
throttle = web_throttle ;
continuous_throttle = true ;
}
}
// Webserver arms with DSHOT_THROTTLE_MIN
if ( armedFromWeb & & isArmed )
{
continuous_throttle = true ;
motor01 . sendThrottle ( throttle ) ;
USB_SERIAL . println ( " " ) ;
2025-09-11 12:58:22 +01:00
USB_SERIAL . printf ( " Motor armed via Web - throttle set to %i \n " , throttle ) ;
2025-09-09 16:19:57 +01:00
}
}
// Websocket request handler
void onWsEvent ( AsyncWebSocket * server , AsyncWebSocketClient * client , AwsEventType type , void * arg , uint8_t * data , size_t len )
{
switch ( type )
{
case WS_EVT_CONNECT :
USB_SERIAL . printf ( " Web Client #%u connected from %s \n " , client - > id ( ) , client - > remoteIP ( ) . toString ( ) . c_str ( ) ) ;
// Send current arming status to new client
{
JsonDocument doc ;
doc [ " armed " ] = isArmed ;
doc [ " throttle " ] = isArmed ? throttle : 0 ;
String json_output ;
serializeJson ( doc , json_output ) ;
client - > text ( json_output ) ;
}
break ;
case WS_EVT_DISCONNECT :
USB_SERIAL . printf ( " Web Client #%u disconnected \n " , client - > id ( ) ) ;
break ;
case WS_EVT_DATA :
handleWebSocketMessage ( arg , data , len ) ;
break ;
case WS_EVT_PONG :
case WS_EVT_ERROR :
break ;
}
}