feat(library): Add percent throttle, improve usability & fix bugs
- Adds a new `sendThrottlePercent()` function for more intuitive throttle control. - Allows initializing the motor magnet count directly in the constructor. - Updates all examples to use the new features. - Adds a new `throttle_percent` example sketch. - Fixes a critical resource leak in `begin()` on partial init failure. - Hardens web examples with security warnings and improves the UI. - Updates README to reflect all changes.
This commit is contained in:
parent
7ee5c83133
commit
76896312db
20
README.md
20
README.md
|
|
@ -11,7 +11,7 @@ This library is a rewrite using the modern ESP-IDF 5 RMT encoder API (`rmt_tx.h`
|
|||
- **Multiple DShot Modes:** Supports DSHOT150, DSHOT300, DSHOT600, and DSHOT1200.
|
||||
- **Bidirectional DShot:** Full support for RPM telemetry feedback.
|
||||
- **Hardware-Timed Signals:** Precise signal generation using the ESP32 RMT peripheral, ensuring stable and reliable motor control.
|
||||
- **Simple API:** Easy-to-use C++ class for sending throttle commands and receiving telemetry data.
|
||||
- **Simple API:** Easy-to-use C++ class with intuitive methods like `sendThrottlePercent()`.
|
||||
- **Efficient and Lightweight:** The core library has no external dependencies.
|
||||
- **Arduino and ESP-IDF Compatible:** Can be used in both Arduino and ESP-IDF projects.
|
||||
|
||||
|
|
@ -52,16 +52,16 @@ void setup() {
|
|||
// Initialize the DShot motor
|
||||
motor.begin();
|
||||
|
||||
Serial.println("Motor initialized. Sending low throttle for 5 seconds...");
|
||||
Serial.println("Motor initialized. Ramping up to 25% throttle...");
|
||||
|
||||
// Send a low throttle command for 5 seconds
|
||||
for (int i = 0; i < 500; i++) {
|
||||
motor.sendThrottle(100);
|
||||
delay(10);
|
||||
// Ramp up to 25% throttle over 2.5 seconds
|
||||
for (int i = 0; i <= 25; i++) {
|
||||
motor.sendThrottlePercent(i);
|
||||
delay(100);
|
||||
}
|
||||
|
||||
Serial.println("Stopping motor.");
|
||||
motor.sendThrottle(0);
|
||||
motor.sendThrottlePercent(0);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
|
|
@ -73,7 +73,8 @@ void loop() {
|
|||
|
||||
The `examples` folder contains more advanced examples:
|
||||
|
||||
- **`dshot300`:** A simple example demonstrating how to send DShot commands and receive telemetry via the serial monitor.
|
||||
- **`throttle_percent`:** A focused example showing how to control motor speed using percentage values (0-100) via the serial monitor.
|
||||
- **`dshot300`:** A more advanced example demonstrating how to send raw DShot commands and receive telemetry via the serial monitor.
|
||||
- **`web_control`:** A full-featured web application for controlling a motor from a web browser. It creates a WiFi access point and serves a web page with a throttle slider and arming switch.
|
||||
- **`web_client`:** A variation of the `web_control` example that connects to an existing WiFi network instead of creating its own access point.
|
||||
|
||||
|
|
@ -101,7 +102,8 @@ The main class is `DShotRMT`. Here are the most important methods:
|
|||
|
||||
- `DShotRMT(gpio_num_t gpio, dshot_mode_t mode, bool is_bidirectional = false)`: Constructor to create a new DShotRMT instance.
|
||||
- `begin()`: Initializes the RMT peripheral and the DShot encoder.
|
||||
- `sendThrottle(uint16_t throttle)`: Sends a throttle value (48-2047) to the motor.
|
||||
- `sendThrottlePercent(float percent)`: Sends a throttle value as a percentage (0.0-100.0).
|
||||
- `sendThrottle(uint16_t throttle)`: Sends a raw throttle value (48-2047) to the motor.
|
||||
- `sendCommand(uint16_t command)`: Sends a DShot command (0-47) to the motor.
|
||||
- `getTelemetry(uint16_t magnet_count)`: Receives and parses telemetry data from the motor (for bidirectional DShot).
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ static constexpr auto IS_BIDIRECTIONAL = true;
|
|||
static constexpr auto MOTOR01_MAGNET_COUNT = 14;
|
||||
|
||||
// Creates the motor instance
|
||||
DShotRMT motor01(MOTOR01_PIN, DSHOT_MODE, IS_BIDIRECTIONAL);
|
||||
DShotRMT motor01(MOTOR01_PIN, DSHOT_MODE, IS_BIDIRECTIONAL, MOTOR01_MAGNET_COUNT);
|
||||
|
||||
//
|
||||
void setup()
|
||||
|
|
@ -85,7 +85,7 @@ void loop()
|
|||
// Get Motor RPM if bidirectional
|
||||
if (IS_BIDIRECTIONAL)
|
||||
{
|
||||
dshot_result_t telem_result = motor01.getTelemetry(MOTOR01_MAGNET_COUNT);
|
||||
dshot_result_t telem_result = motor01.getTelemetry();
|
||||
printDShotResult(telem_result);
|
||||
}
|
||||
|
||||
|
|
@ -134,7 +134,7 @@ void handleSerialInput(const String &input, uint16_t &throttle, bool &continuous
|
|||
}
|
||||
else if (input == "rpm" && IS_BIDIRECTIONAL)
|
||||
{
|
||||
dshot_result_t result = motor01.getTelemetry(MOTOR01_MAGNET_COUNT);
|
||||
dshot_result_t result = motor01.getTelemetry();
|
||||
printDShotResult(result);
|
||||
}
|
||||
else if (input.startsWith("cmd "))
|
||||
|
|
|
|||
|
|
@ -0,0 +1,142 @@
|
|||
/**
|
||||
* @file throttle_percent.ino
|
||||
* @brief Demo sketch for DShotRMT library using percentage throttle.
|
||||
* @author Wastl Kraus
|
||||
* @date 2025-09-20
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <DShotRMT.h>
|
||||
|
||||
// USB serial port settings
|
||||
static constexpr auto &USB_SERIAL = Serial0;
|
||||
static constexpr auto USB_SERIAL_BAUD = 115200;
|
||||
|
||||
// Motor configuration - Pin number or GPIO_PIN
|
||||
static constexpr gpio_num_t MOTOR01_PIN = GPIO_NUM_27;
|
||||
|
||||
// Supported: DSHOT150, DSHOT300, DSHOT600, (DSHOT1200)
|
||||
static constexpr dshot_mode_t DSHOT_MODE = DSHOT300;
|
||||
|
||||
// BiDirectional DShot Support (default: false)
|
||||
static constexpr auto IS_BIDIRECTIONAL = true;
|
||||
|
||||
// Motor magnet count for RPM calculation
|
||||
static constexpr auto MOTOR01_MAGNET_COUNT = 14;
|
||||
|
||||
// Creates the motor instance
|
||||
DShotRMT motor01(MOTOR01_PIN, DSHOT_MODE, IS_BIDIRECTIONAL, MOTOR01_MAGNET_COUNT);
|
||||
|
||||
// Forward declaration
|
||||
void handleSerialInput(const String &input);
|
||||
void printMenu();
|
||||
|
||||
//
|
||||
void setup()
|
||||
{
|
||||
// Starts the USB Serial Port
|
||||
USB_SERIAL.begin(USB_SERIAL_BAUD);
|
||||
|
||||
// Initialize DShot Signal
|
||||
motor01.begin();
|
||||
|
||||
// Print CPU Info
|
||||
motor01.printCpuInfo();
|
||||
|
||||
//
|
||||
printMenu();
|
||||
}
|
||||
|
||||
//
|
||||
void loop()
|
||||
{
|
||||
// Handle serial input
|
||||
if (USB_SERIAL.available() > 0)
|
||||
{
|
||||
String input = USB_SERIAL.readStringUntil('\n');
|
||||
input.trim();
|
||||
|
||||
if (input.length() > 0)
|
||||
{
|
||||
handleSerialInput(input);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
void printMenu()
|
||||
{
|
||||
USB_SERIAL.println(" ");
|
||||
USB_SERIAL.println("*******************************************");
|
||||
USB_SERIAL.println(" DShotRMT Percent Demo ");
|
||||
USB_SERIAL.println("*******************************************");
|
||||
USB_SERIAL.println(" <value> - Set throttle (0 - 100)");
|
||||
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("*******************************************");
|
||||
}
|
||||
|
||||
//
|
||||
void handleSerialInput(const String &input)
|
||||
{
|
||||
if (input == "0")
|
||||
{
|
||||
// Stop motor
|
||||
dshot_result_t result = motor01.sendThrottlePercent(0.0f);
|
||||
printDShotResult(result);
|
||||
}
|
||||
else if (input == "info")
|
||||
{
|
||||
motor01.printDShotInfo();
|
||||
}
|
||||
else if (input == "rpm" && IS_BIDIRECTIONAL)
|
||||
{
|
||||
dshot_result_t result = motor01.getTelemetry();
|
||||
printDShotResult(result);
|
||||
}
|
||||
else if (input.startsWith("cmd "))
|
||||
{
|
||||
// Send DShot command
|
||||
int cmd_num = input.substring(4).toInt();
|
||||
|
||||
if (cmd_num >= DSHOT_CMD_MOTOR_STOP && cmd_num <= DSHOT_CMD_MAX)
|
||||
{
|
||||
dshot_result_t result = motor01.sendCommand(cmd_num);
|
||||
printDShotResult(result);
|
||||
}
|
||||
else
|
||||
{
|
||||
USB_SERIAL.printf("Invalid command: %d (valid range: 0 - %d)\n", cmd_num, DSHOT_CMD_MAX);
|
||||
}
|
||||
}
|
||||
else if (input == "h" || input == "help")
|
||||
{
|
||||
printMenu();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Parse input throttle value as a percentage
|
||||
float throttle_percent = input.toFloat();
|
||||
|
||||
if (throttle_percent >= 0.0f && throttle_percent <= 100.0f)
|
||||
{
|
||||
dshot_result_t result = motor01.sendThrottlePercent(throttle_percent);
|
||||
printDShotResult(result);
|
||||
}
|
||||
else
|
||||
{
|
||||
USB_SERIAL.println(" ");
|
||||
USB_SERIAL.printf("Invalid input: '%s'\n", input.c_str());
|
||||
USB_SERIAL.printf("Valid throttle range: 0.0 - 100.0\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,15 @@
|
|||
* @license MIT
|
||||
*/
|
||||
|
||||
/******************************************************************
|
||||
* 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.
|
||||
******************************************************************/
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <Update.h>
|
||||
#include <WiFi.h>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,15 @@
|
|||
* @license MIT
|
||||
*/
|
||||
|
||||
/******************************************************************
|
||||
* 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.
|
||||
******************************************************************/
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <WiFi.h>
|
||||
|
||||
|
|
|
|||
|
|
@ -33,10 +33,11 @@ void printDShotResult(dshot_result_t &result, Stream &output)
|
|||
|
||||
// Constructors & Destructor
|
||||
// Constructor with GPIO number
|
||||
DShotRMT::DShotRMT(gpio_num_t gpio, dshot_mode_t mode, bool is_bidirectional)
|
||||
DShotRMT::DShotRMT(gpio_num_t gpio, dshot_mode_t mode, bool is_bidirectional, uint16_t magnet_count)
|
||||
: _gpio(gpio),
|
||||
_mode(mode),
|
||||
_is_bidirectional(is_bidirectional),
|
||||
_motor_magnet_count(magnet_count),
|
||||
_dshot_timing(DSHOT_TIMING_US[mode]),
|
||||
_frame_timer_us(0),
|
||||
_rmt_ticks{0},
|
||||
|
|
@ -68,8 +69,8 @@ DShotRMT::DShotRMT(gpio_num_t gpio, dshot_mode_t mode, bool is_bidirectional)
|
|||
}
|
||||
|
||||
// Constructor using pin number
|
||||
DShotRMT::DShotRMT(uint16_t pin_nr, dshot_mode_t mode, bool is_bidirectional)
|
||||
: DShotRMT(static_cast<gpio_num_t>(pin_nr), mode, is_bidirectional)
|
||||
DShotRMT::DShotRMT(uint16_t pin_nr, dshot_mode_t mode, bool is_bidirectional, uint16_t magnet_count)
|
||||
: DShotRMT(static_cast<gpio_num_t>(pin_nr), mode, is_bidirectional, magnet_count)
|
||||
{
|
||||
// Delegates to primary constructor with type cast
|
||||
}
|
||||
|
|
@ -118,12 +119,25 @@ dshot_result_t DShotRMT::begin()
|
|||
{
|
||||
if (!_initRXChannel().success)
|
||||
{
|
||||
// Cleanup previously allocated TX channel on failure
|
||||
rmt_disable(_rmt_tx_channel);
|
||||
rmt_del_channel(_rmt_tx_channel);
|
||||
_rmt_tx_channel = nullptr;
|
||||
return {false, RX_INIT_FAILED};
|
||||
}
|
||||
}
|
||||
|
||||
if (!_initDShotEncoder().success)
|
||||
{
|
||||
// Cleanup previously allocated channels on failure
|
||||
rmt_disable(_rmt_tx_channel);
|
||||
rmt_del_channel(_rmt_tx_channel);
|
||||
_rmt_tx_channel = nullptr;
|
||||
if (_rmt_rx_channel) {
|
||||
rmt_disable(_rmt_rx_channel);
|
||||
rmt_del_channel(_rmt_rx_channel);
|
||||
_rmt_rx_channel = nullptr;
|
||||
}
|
||||
return {false, ENCODER_INIT_FAILED};
|
||||
}
|
||||
|
||||
|
|
@ -146,6 +160,20 @@ dshot_result_t DShotRMT::sendThrottle(uint16_t throttle)
|
|||
return _sendDShotFrame(_packet);
|
||||
}
|
||||
|
||||
// Send throttle value as a percentage
|
||||
dshot_result_t DShotRMT::sendThrottlePercent(float percent)
|
||||
{
|
||||
if (percent < 0.0f || percent > 100.0f)
|
||||
{
|
||||
return {false, PERCENT_NOT_IN_RANGE};
|
||||
}
|
||||
|
||||
// Map percent to DShot throttle range
|
||||
uint16_t throttle = static_cast<uint16_t>(DSHOT_THROTTLE_MIN + ((DSHOT_THROTTLE_MAX - DSHOT_THROTTLE_MIN) / 100.0f) * percent);
|
||||
|
||||
return sendThrottle(throttle);
|
||||
}
|
||||
|
||||
// Send DShot command to ESC
|
||||
dshot_result_t DShotRMT::sendCommand(uint16_t command)
|
||||
{
|
||||
|
|
@ -212,16 +240,19 @@ dshot_result_t DShotRMT::getTelemetry(uint16_t magnet_count)
|
|||
return result;
|
||||
}
|
||||
|
||||
// Use stored magnet count if parameter is 0 (default)
|
||||
uint16_t final_magnet_count = (magnet_count == 0) ? _motor_magnet_count : magnet_count;
|
||||
|
||||
// Check if the callback has set the flag for new data
|
||||
if (_telemetry_ready_flag_atomic)
|
||||
{
|
||||
_telemetry_ready_flag_atomic = false; // Reset the flag
|
||||
uint16_t erpm = _last_erpm_atomic; // Read the atomic variable
|
||||
|
||||
if (erpm != DSHOT_NULL_PACKET && magnet_count >= MAGNETS_PER_POLE_PAIR)
|
||||
if (erpm != DSHOT_NULL_PACKET && final_magnet_count >= MAGNETS_PER_POLE_PAIR)
|
||||
{
|
||||
// Calculate motor RPM from eRPM and magnet count
|
||||
uint8_t pole_pairs = magnet_count / MAGNETS_PER_POLE_PAIR;
|
||||
uint8_t pole_pairs = final_magnet_count / MAGNETS_PER_POLE_PAIR;
|
||||
uint32_t motor_rpm = (erpm / pole_pairs);
|
||||
|
||||
result.success = true;
|
||||
|
|
@ -255,6 +286,11 @@ dshot_result_t DShotRMT::saveESCSettings()
|
|||
}
|
||||
|
||||
// Public Info & Debug Functions
|
||||
void DShotRMT::setMotorMagnetCount(uint16_t magnet_count)
|
||||
{
|
||||
_motor_magnet_count = magnet_count;
|
||||
}
|
||||
|
||||
void DShotRMT::printDShotInfo(Stream &output) const
|
||||
{
|
||||
output.println("\n === DShot Signal Info === ");
|
||||
|
|
|
|||
|
|
@ -82,22 +82,29 @@ class DShotRMT
|
|||
{
|
||||
public:
|
||||
// Constructors & Destructor
|
||||
explicit DShotRMT(gpio_num_t gpio = GPIO_NUM_16, dshot_mode_t mode = DSHOT300, bool is_bidirectional = false);
|
||||
DShotRMT(uint16_t pin_nr, dshot_mode_t mode, bool is_bidirectional);
|
||||
explicit DShotRMT(gpio_num_t gpio = GPIO_NUM_16, dshot_mode_t mode = DSHOT300, bool is_bidirectional = false, uint16_t magnet_count = DEFAULT_MOTOR_MAGNET_COUNT);
|
||||
DShotRMT(uint16_t pin_nr, dshot_mode_t mode, bool is_bidirectional, uint16_t magnet_count = DEFAULT_MOTOR_MAGNET_COUNT);
|
||||
|
||||
~DShotRMT();
|
||||
|
||||
// Public Core Functions
|
||||
dshot_result_t begin();
|
||||
dshot_result_t sendThrottle(uint16_t throttle);
|
||||
dshot_result_t sendThrottlePercent(float percent);
|
||||
dshot_result_t sendCommand(uint16_t command);
|
||||
dshot_result_t sendCommand(dshot_commands_t dshot_command, uint16_t repeat_count = DEFAULT_CMD_REPEAT_COUNT, uint16_t delay_us = DEFAULT_CMD_DELAY_US);
|
||||
dshot_result_t getTelemetry(uint16_t magnet_count = DEFAULT_MOTOR_MAGNET_COUNT);
|
||||
/**
|
||||
* @brief Gets telemetry data from the ESC.
|
||||
* @param magnet_count Optional. Number of motor magnets. If 0 or omitted, uses the value set by setMotorMagnetCount().
|
||||
* @return dshot_result_t Result containing success status, message, and telemetry data.
|
||||
*/
|
||||
dshot_result_t getTelemetry(uint16_t magnet_count = 0);
|
||||
dshot_result_t getESCInfo();
|
||||
dshot_result_t setMotorSpinDirection(bool reversed);
|
||||
dshot_result_t saveESCSettings();
|
||||
|
||||
// Public Utility & Info Functions
|
||||
void setMotorMagnetCount(uint16_t magnet_count);
|
||||
void printDShotInfo(Stream &output = Serial) const;
|
||||
void printCpuInfo(Stream &output = Serial) const;
|
||||
|
||||
|
|
@ -159,6 +166,7 @@ private:
|
|||
static constexpr char const *TRANSMISSION_FAILED = "Transmission failed!";
|
||||
static constexpr char const *RECEIVER_FAILED = "RMT receiver failed!";
|
||||
static constexpr char const *THROTTLE_NOT_IN_RANGE = "Throttle not in range! (48 - 2047)";
|
||||
static constexpr char const *PERCENT_NOT_IN_RANGE = "Percent not in range! (0.0 - 100.0)";
|
||||
static constexpr char const *COMMAND_NOT_VALID = "Command not valid! (0 - 47)";
|
||||
static constexpr char const *BIDIR_NOT_ENABLED = "Bidirectional DShot not enabled!";
|
||||
static constexpr char const *TELEMETRY_SUCCESS = "Valid Telemetric Frame received!";
|
||||
|
|
@ -177,6 +185,7 @@ private:
|
|||
gpio_num_t _gpio;
|
||||
dshot_mode_t _mode;
|
||||
bool _is_bidirectional;
|
||||
uint16_t _motor_magnet_count;
|
||||
const dshot_timing_us_t &_dshot_timing;
|
||||
uint64_t _frame_timer_us;
|
||||
|
||||
|
|
|
|||
|
|
@ -217,6 +217,7 @@ static constexpr char index_html[] = R"rawliteral(
|
|||
<!-- Throttle Section -->
|
||||
<div class="throttle-section" id="throttleSection">
|
||||
<div id="throttleValue">0</div>
|
||||
<div id="throttlePercent" style="font-size: 1.2rem; color: #bdc3c7; margin-top: -15px; margin-bottom: 15px;">0%</div>
|
||||
<input type="range" min="48" max="2047" value="0" id="throttleSlider" disabled>
|
||||
</div>
|
||||
|
||||
|
|
@ -253,6 +254,19 @@ static constexpr char index_html[] = R"rawliteral(
|
|||
setTimeout(initWebSocket, 2000);
|
||||
}
|
||||
|
||||
function updateThrottleDisplays(rawValue) {
|
||||
const DSHOT_MIN = 48;
|
||||
const DSHOT_MAX = 2047;
|
||||
const clampedValue = Math.max(0, Math.min(DSHOT_MAX, rawValue));
|
||||
document.getElementById('throttleValue').innerText = clampedValue;
|
||||
let percent = 0;
|
||||
if (clampedValue > 0) {
|
||||
percent = (clampedValue - DSHOT_MIN) / (DSHOT_MAX - DSHOT_MIN) * 100;
|
||||
}
|
||||
document.getElementById('throttlePercent').innerText = Math.round(percent) + '%';
|
||||
document.getElementById('throttleSlider').value = clampedValue;
|
||||
}
|
||||
|
||||
// Getting data from sketch
|
||||
function onMessage(event) {
|
||||
try {
|
||||
|
|
@ -265,8 +279,7 @@ static constexpr char index_html[] = R"rawliteral(
|
|||
// Sync web and serial throttle inputs
|
||||
if (data.throttle !== undefined) {
|
||||
if (isArmed) {
|
||||
document.getElementById('throttleSlider').value = data.throttle;
|
||||
document.getElementById('throttleValue').innerText = data.throttle;
|
||||
updateThrottleDisplays(data.throttle);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -304,8 +317,7 @@ static constexpr char index_html[] = R"rawliteral(
|
|||
|
||||
// If disarmed, set throttle to 0
|
||||
if (!isArmed) {
|
||||
slider.value = 0;
|
||||
sliderValue.innerText = 0;
|
||||
updateThrottleDisplays(0);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -325,8 +337,7 @@ static constexpr char index_html[] = R"rawliteral(
|
|||
armingStatus.className = 'status-disarmed';
|
||||
throttleSection.classList.remove('armed');
|
||||
slider.disabled = true;
|
||||
slider.value = 0;
|
||||
sliderValue.innerText = 0;
|
||||
updateThrottleDisplays(0);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -334,13 +345,12 @@ static constexpr char index_html[] = R"rawliteral(
|
|||
slider.addEventListener('input', () => {
|
||||
if (!isArmed) {
|
||||
slider.disabled = true;
|
||||
slider.value = 0;
|
||||
sliderValue.innerText = 0;
|
||||
updateThrottleDisplays(0);
|
||||
return;
|
||||
}
|
||||
|
||||
const throttle = slider.value;
|
||||
sliderValue.innerText = throttle;
|
||||
updateThrottleDisplays(throttle);
|
||||
|
||||
const message = JSON.stringify({
|
||||
"throttle": parseInt(throttle),
|
||||
|
|
|
|||
Loading…
Reference in New Issue