feat: add ESP32 BTS7960 motor controller with web interface
- Implement MotorController class with PWM speed control (0-100%) - Add bidirectional control (forward/reverse) via LEDC PWM at 20kHz - Include optional current sensing and stall detection - Create responsive web UI with speed slider and direction buttons - Configure WiFi with static IP (10.81.2.185) - Use built-in WebServer library for ESP32 Arduino 3.x compatibility
This commit is contained in:
7
.agents/rules-architect/AGENTS.md
Normal file
7
.agents/rules-architect/AGENTS.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# Architect Mode Rules
|
||||
|
||||
## Project Structure
|
||||
|
||||
- PlatformIO standard structure: `src/main.cpp`, `include/`, `lib/`
|
||||
- Consider separate motor control library in `lib/` for reusability
|
||||
- BTS7960 can drive two motors independently - plan for dual motor control interface
|
||||
7
.agents/rules-ask/AGENTS.md
Normal file
7
.agents/rules-ask/AGENTS.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# Ask Mode Rules
|
||||
|
||||
## Documentation Context
|
||||
|
||||
- Main hardware reference: https://deepbluembedded.com/arduino-bts7960-dc-motor-driver/
|
||||
- ESP32 LOLIN32 pinout differs from standard ESP32 DevKit - verify pin assignments
|
||||
- BTS7960 datasheet needed for current sensing calculations
|
||||
8
.agents/rules-code/AGENTS.md
Normal file
8
.agents/rules-code/AGENTS.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# Code Mode Rules
|
||||
|
||||
## ESP32/PlatformIO Specifics
|
||||
|
||||
- Use `LEDC` peripheral for PWM on ESP32 (not analogWrite)
|
||||
- BTS7960 PWM frequency: 1-25kHz optimal, 20kHz recommended to reduce motor noise
|
||||
- Enable pins (R_EN, L_EN) must be HIGH before PWM signals work
|
||||
- Current sense pins (R_IS, L_IS) output analog voltage proportional to motor current
|
||||
7
.agents/rules-debug/AGENTS.md
Normal file
7
.agents/rules-debug/AGENTS.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# Debug Mode Rules
|
||||
|
||||
## ESP32 Debugging
|
||||
|
||||
- Use `pio device monitor -b 115200` for serial debugging
|
||||
- BTS7960 current sense pins can diagnose motor stall/overload conditions
|
||||
- Check R_IS/L_IS for overcurrent (>43A causes fault)
|
||||
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
.pio
|
||||
.vscode/.browse.c_cpp.db*
|
||||
.vscode/c_cpp_properties.json
|
||||
.vscode/launch.json
|
||||
.vscode/ipch
|
||||
10
.vscode/extensions.json
vendored
Normal file
10
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
// See http://go.microsoft.com/fwlink/?LinkId=827846
|
||||
// for the documentation about the extensions.json format
|
||||
"recommendations": [
|
||||
"platformio.platformio-ide"
|
||||
],
|
||||
"unwantedRecommendations": [
|
||||
"ms-vscode.cpptools-extension-pack"
|
||||
]
|
||||
}
|
||||
31
AGENTS.md
Normal file
31
AGENTS.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# AGENTS.md
|
||||
|
||||
This file provides guidance to agents when working with code in this repository.
|
||||
|
||||
## Project Overview
|
||||
|
||||
PlatformIO project for ESP32 LOLIN32 Rev1 controlling BTS7960 dual H-bridge motor driver (12V DC).
|
||||
|
||||
## Build Commands
|
||||
|
||||
```bash
|
||||
pio run # Build
|
||||
pio run -t upload # Build and upload
|
||||
pio device monitor # Serial monitor (115200 baud)
|
||||
```
|
||||
|
||||
## Hardware-Specific Notes
|
||||
|
||||
- **Board**: ESP32 LOLIN32 Rev1 (use `lolin32` in platformio.ini)
|
||||
- **Motor Driver**: BTS7960 dual H-bridge - requires 4 GPIO pins (RPWM=25, LPWM=26, R_EN=27, L_EN=14)
|
||||
- **Power**: 12V DC input to motor driver - logic level is 3.3V compatible with ESP32
|
||||
- **WiFi**: Connects to SSID 'tami' with static IP 10.81.2.185
|
||||
- **Reference**: https://deepbluembedded.com/arduino-bts7960-dc-motor-driver/
|
||||
|
||||
## Non-Obvious Notes
|
||||
|
||||
- Use built-in `WebServer` library (NOT ESPAsyncWebServer - has enum compatibility issues with ESP32 Arduino 3.x)
|
||||
- WebServer requires `handleClient()` call in loop() - unlike async version
|
||||
- LEDC PWM at 20kHz reduces motor noise
|
||||
- Enable pins (R_EN, L_EN) must be HIGH before PWM works
|
||||
- **BTS7960 is 3.3V compatible**: Inputs are TTL/CMOS compatible (VIL<0.8V, VIH>2.0V) - ESP32 GPIOs work directly without level shifters (per BTN7960 datasheet section 5.4.1)
|
||||
42
include/config.h
Normal file
42
include/config.h
Normal file
@@ -0,0 +1,42 @@
|
||||
#ifndef CONFIG_H
|
||||
#define CONFIG_H
|
||||
|
||||
// WiFi Configuration
|
||||
#define WIFI_SSID "tami"
|
||||
#define WIFI_PASSWORD ""
|
||||
|
||||
// Static IP Configuration
|
||||
#define STATIC_IP IPAddress(10, 81, 2, 185)
|
||||
#define GATEWAY IPAddress(10, 81, 2, 1)
|
||||
#define SUBNET IPAddress(255, 255, 255, 0)
|
||||
#define DNS IPAddress(10, 81, 2, 1)
|
||||
|
||||
// BTS7960 Pin Definitions
|
||||
#define RPWM_PIN 25 // Right PWM (Forward)
|
||||
#define LPWM_PIN 26 // Left PWM (Reverse)
|
||||
#define R_EN_PIN 27 // Right Enable
|
||||
#define L_EN_PIN 14 // Left Enable
|
||||
#define R_IS_PIN 34 // Right Current Sense (ADC input only)
|
||||
#define L_IS_PIN 35 // Left Current Sense (ADC input only)
|
||||
|
||||
// PWM Configuration
|
||||
#define PWM_FREQ 20000 // 20kHz - reduces motor noise
|
||||
#define PWM_RESOLUTION 8 // 8-bit resolution (0-255)
|
||||
#define PWM_CHANNEL_R 0 // LEDC channel for RPWM
|
||||
#define PWM_CHANNEL_L 1 // LEDC channel for LPWM
|
||||
|
||||
// Current Sense Configuration
|
||||
// BTS7960 current sense ratio: 8500:1 (kilo-amps)
|
||||
// With 1kΩ resistor on IS pin: V = I_motor / 8500
|
||||
// ESP32 ADC: 12-bit (0-4095), 0-3.3V
|
||||
#define CURRENT_SENSE_RATIO 8500.0f // Amps to sense current ratio
|
||||
#define SENSE_RESISTOR 1000.0f // 1kΩ sense resistor (ohms)
|
||||
#define ADC_MAX 4095.0f // 12-bit ADC max value
|
||||
#define ADC_VREF 3.3f // ADC reference voltage
|
||||
#define STALL_CURRENT_THRESHOLD 5.0f // Current (amps) that indicates stall
|
||||
#define STALL_DETECT_TIME_MS 500 // Time to confirm stall (ms)
|
||||
|
||||
// Web Server
|
||||
#define HTTP_PORT 80
|
||||
|
||||
#endif
|
||||
41
include/motor.h
Normal file
41
include/motor.h
Normal file
@@ -0,0 +1,41 @@
|
||||
#ifndef MOTOR_H
|
||||
#define MOTOR_H
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "config.h"
|
||||
|
||||
class MotorController {
|
||||
public:
|
||||
void begin();
|
||||
void setSpeed(int speed); // 0-100 percentage
|
||||
void setDirection(int dir); // -1=reverse, 0=stop, 1=forward
|
||||
void stop();
|
||||
void update(); // Call in loop() for stall detection
|
||||
|
||||
int getSpeed();
|
||||
int getDirection();
|
||||
float getCurrentRight(); // Current in amps (forward direction)
|
||||
float getCurrentLeft(); // Current in amps (reverse direction)
|
||||
float getCurrentActive(); // Current from active direction
|
||||
bool isStalled(); // True if stall detected
|
||||
|
||||
// Callback for stall events
|
||||
void setStallCallback(void (*callback)(float current));
|
||||
|
||||
private:
|
||||
int _speed = 0;
|
||||
int _direction = 0;
|
||||
float _currentRight = 0;
|
||||
float _currentLeft = 0;
|
||||
bool _stalled = false;
|
||||
unsigned long _stallStartTime = 0;
|
||||
void (*_stallCallback)(float current) = nullptr;
|
||||
|
||||
void applyMotorState();
|
||||
float readCurrentSense(int pin);
|
||||
void checkStall();
|
||||
};
|
||||
|
||||
extern MotorController motor;
|
||||
|
||||
#endif
|
||||
7
include/webserver.h
Normal file
7
include/webserver.h
Normal file
@@ -0,0 +1,7 @@
|
||||
#ifndef WEBSERVER_H
|
||||
#define WEBSERVER_H
|
||||
|
||||
void setupWebServer();
|
||||
void handleWebServer();
|
||||
|
||||
#endif
|
||||
14
platformio.ini
Normal file
14
platformio.ini
Normal file
@@ -0,0 +1,14 @@
|
||||
; PlatformIO Project Configuration File
|
||||
; ESP32 LOLIN32 Rev1 with BTS7960 Motor Driver
|
||||
|
||||
[env:lolin32]
|
||||
platform = espressif32
|
||||
board = lolin32
|
||||
framework = arduino
|
||||
monitor_speed = 115200
|
||||
|
||||
; No external libraries needed - using built-in WebServer
|
||||
|
||||
; Build flags
|
||||
build_flags =
|
||||
-D CORE_DEBUG_LEVEL=0
|
||||
8
readme.md
Normal file
8
readme.md
Normal file
@@ -0,0 +1,8 @@
|
||||
### inital prompt
|
||||
|
||||
start platformio project.
|
||||
using esp32 lolin32 rev1.
|
||||
we will connect a dual h-bdrige module based on BTS7960 module.
|
||||
12v dc input.
|
||||
|
||||
module guide - https://deepbluembedded.com/arduino-bts7960-dc-motor-driver/
|
||||
62
src/main.cpp
Normal file
62
src/main.cpp
Normal file
@@ -0,0 +1,62 @@
|
||||
#include <Arduino.h>
|
||||
#include <WiFi.h>
|
||||
#include "config.h"
|
||||
#include "motor.h"
|
||||
#include "webserver.h"
|
||||
|
||||
void setupWiFi() {
|
||||
Serial.println("Connecting to WiFi...");
|
||||
|
||||
// Configure static IP
|
||||
if (!WiFi.config(STATIC_IP, GATEWAY, SUBNET, DNS)) {
|
||||
Serial.println("Static IP configuration failed!");
|
||||
}
|
||||
|
||||
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
|
||||
|
||||
int attempts = 0;
|
||||
while (WiFi.status() != WL_CONNECTED && attempts < 30) {
|
||||
delay(500);
|
||||
Serial.print(".");
|
||||
attempts++;
|
||||
}
|
||||
|
||||
if (WiFi.status() == WL_CONNECTED) {
|
||||
Serial.println("\nWiFi connected!");
|
||||
Serial.print("IP Address: ");
|
||||
Serial.println(WiFi.localIP());
|
||||
} else {
|
||||
Serial.println("\nWiFi connection failed!");
|
||||
Serial.println("Restarting in 5 seconds...");
|
||||
delay(5000);
|
||||
ESP.restart();
|
||||
}
|
||||
}
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
delay(1000);
|
||||
|
||||
Serial.println("\n=============================");
|
||||
Serial.println(" BTS7960 Motor Controller");
|
||||
Serial.println("=============================\n");
|
||||
|
||||
// Initialize motor controller
|
||||
motor.begin();
|
||||
|
||||
// Connect to WiFi
|
||||
setupWiFi();
|
||||
|
||||
// Start web server
|
||||
setupWebServer();
|
||||
|
||||
Serial.println("\nReady! Access the control panel at:");
|
||||
Serial.print("http://");
|
||||
Serial.println(WiFi.localIP());
|
||||
}
|
||||
|
||||
void loop() {
|
||||
handleWebServer();
|
||||
motor.update(); // Update current sensing and stall detection
|
||||
delay(1);
|
||||
}
|
||||
182
src/motor.cpp
Normal file
182
src/motor.cpp
Normal file
@@ -0,0 +1,182 @@
|
||||
#include "motor.h"
|
||||
|
||||
// Set to true to enable current sensing (requires R_IS and L_IS connected)
|
||||
#define CURRENT_SENSING_ENABLED false
|
||||
|
||||
MotorController motor;
|
||||
|
||||
void MotorController::begin() {
|
||||
// Configure enable pins as outputs
|
||||
pinMode(R_EN_PIN, OUTPUT);
|
||||
pinMode(L_EN_PIN, OUTPUT);
|
||||
|
||||
// Enable the H-bridge
|
||||
digitalWrite(R_EN_PIN, HIGH);
|
||||
digitalWrite(L_EN_PIN, HIGH);
|
||||
|
||||
// Configure LEDC PWM channels for ESP32
|
||||
ledcSetup(PWM_CHANNEL_R, PWM_FREQ, PWM_RESOLUTION);
|
||||
ledcSetup(PWM_CHANNEL_L, PWM_FREQ, PWM_RESOLUTION);
|
||||
|
||||
// Attach PWM channels to GPIO pins
|
||||
ledcAttachPin(RPWM_PIN, PWM_CHANNEL_R);
|
||||
ledcAttachPin(LPWM_PIN, PWM_CHANNEL_L);
|
||||
|
||||
#if CURRENT_SENSING_ENABLED
|
||||
// Configure current sense pins as analog inputs
|
||||
// GPIO34 and GPIO35 are input-only pins, perfect for ADC
|
||||
analogSetAttenuation(ADC_11db); // Full range 0-3.3V
|
||||
pinMode(R_IS_PIN, INPUT);
|
||||
pinMode(L_IS_PIN, INPUT);
|
||||
Serial.println("Current sensing enabled");
|
||||
#endif
|
||||
|
||||
// Start stopped
|
||||
stop();
|
||||
|
||||
Serial.println("Motor controller initialized");
|
||||
}
|
||||
|
||||
void MotorController::setSpeed(int speed) {
|
||||
_speed = constrain(speed, 0, 100);
|
||||
_stalled = false; // Reset stall state on new command
|
||||
_stallStartTime = 0;
|
||||
applyMotorState();
|
||||
}
|
||||
|
||||
void MotorController::setDirection(int dir) {
|
||||
_direction = constrain(dir, -1, 1);
|
||||
_stalled = false; // Reset stall state on new command
|
||||
_stallStartTime = 0;
|
||||
applyMotorState();
|
||||
}
|
||||
|
||||
void MotorController::stop() {
|
||||
_speed = 0;
|
||||
_direction = 0;
|
||||
_stalled = false;
|
||||
_stallStartTime = 0;
|
||||
ledcWrite(PWM_CHANNEL_R, 0);
|
||||
ledcWrite(PWM_CHANNEL_L, 0);
|
||||
}
|
||||
|
||||
void MotorController::update() {
|
||||
#if CURRENT_SENSING_ENABLED
|
||||
// Read current sensors
|
||||
_currentRight = readCurrentSense(R_IS_PIN);
|
||||
_currentLeft = readCurrentSense(L_IS_PIN);
|
||||
|
||||
// Check for stall condition
|
||||
checkStall();
|
||||
#endif
|
||||
}
|
||||
|
||||
int MotorController::getSpeed() {
|
||||
return _speed;
|
||||
}
|
||||
|
||||
int MotorController::getDirection() {
|
||||
return _direction;
|
||||
}
|
||||
|
||||
float MotorController::getCurrentRight() {
|
||||
return _currentRight;
|
||||
}
|
||||
|
||||
float MotorController::getCurrentLeft() {
|
||||
return _currentLeft;
|
||||
}
|
||||
|
||||
float MotorController::getCurrentActive() {
|
||||
if (_direction > 0) {
|
||||
return _currentRight;
|
||||
} else if (_direction < 0) {
|
||||
return _currentLeft;
|
||||
}
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
bool MotorController::isStalled() {
|
||||
return _stalled;
|
||||
}
|
||||
|
||||
void MotorController::setStallCallback(void (*callback)(float current)) {
|
||||
_stallCallback = callback;
|
||||
}
|
||||
|
||||
void MotorController::applyMotorState() {
|
||||
// Convert percentage to 8-bit PWM value
|
||||
int pwmValue = map(_speed, 0, 100, 0, 255);
|
||||
|
||||
if (_direction == 0 || _speed == 0) {
|
||||
// Stop - both PWM outputs off
|
||||
ledcWrite(PWM_CHANNEL_R, 0);
|
||||
ledcWrite(PWM_CHANNEL_L, 0);
|
||||
} else if (_direction > 0) {
|
||||
// Forward - RPWM active, LPWM off
|
||||
ledcWrite(PWM_CHANNEL_R, pwmValue);
|
||||
ledcWrite(PWM_CHANNEL_L, 0);
|
||||
} else {
|
||||
// Reverse - LPWM active, RPWM off
|
||||
ledcWrite(PWM_CHANNEL_R, 0);
|
||||
ledcWrite(PWM_CHANNEL_L, pwmValue);
|
||||
}
|
||||
|
||||
Serial.printf("Motor: dir=%d, speed=%d%%, pwm=%d\n", _direction, _speed, pwmValue);
|
||||
}
|
||||
|
||||
float MotorController::readCurrentSense(int pin) {
|
||||
#if CURRENT_SENSING_ENABLED
|
||||
// Read ADC value (12-bit, 0-4095)
|
||||
int adcValue = analogRead(pin);
|
||||
|
||||
// Convert to voltage (0-3.3V)
|
||||
float voltage = (adcValue / ADC_MAX) * ADC_VREF;
|
||||
|
||||
// Calculate current
|
||||
// IS pin outputs: I_sense = I_load / CURRENT_SENSE_RATIO
|
||||
// With sense resistor: V = I_sense * R_sense
|
||||
// Therefore: I_load = V * CURRENT_SENSE_RATIO / R_sense
|
||||
float current = (voltage * CURRENT_SENSE_RATIO) / SENSE_RESISTOR;
|
||||
|
||||
return current;
|
||||
#else
|
||||
return 0.0f;
|
||||
#endif
|
||||
}
|
||||
|
||||
void MotorController::checkStall() {
|
||||
#if CURRENT_SENSING_ENABLED
|
||||
// Only check stall when motor should be running
|
||||
if (_direction == 0 || _speed == 0) {
|
||||
_stalled = false;
|
||||
_stallStartTime = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
float activeCurrent = getCurrentActive();
|
||||
|
||||
// Check if current exceeds stall threshold
|
||||
if (activeCurrent > STALL_CURRENT_THRESHOLD) {
|
||||
if (_stallStartTime == 0) {
|
||||
// Start timing potential stall
|
||||
_stallStartTime = millis();
|
||||
} else if ((millis() - _stallStartTime) > STALL_DETECT_TIME_MS) {
|
||||
// Stall confirmed
|
||||
if (!_stalled) {
|
||||
_stalled = true;
|
||||
Serial.printf("STALL DETECTED! Current: %.2fA\n", activeCurrent);
|
||||
|
||||
// Call callback if registered
|
||||
if (_stallCallback != nullptr) {
|
||||
_stallCallback(activeCurrent);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Current normal, reset stall detection
|
||||
_stallStartTime = 0;
|
||||
_stalled = false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
215
src/webserver.cpp
Normal file
215
src/webserver.cpp
Normal file
@@ -0,0 +1,215 @@
|
||||
#include <Arduino.h>
|
||||
#include <WebServer.h>
|
||||
#include "motor.h"
|
||||
#include "config.h"
|
||||
|
||||
WebServer server(HTTP_PORT);
|
||||
|
||||
// HTML page for motor control
|
||||
const char index_html[] PROGMEM = R"rawliteral(
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Motor Control</title>
|
||||
<style>
|
||||
* { box-sizing: border-box; }
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
background: #1a1a2e;
|
||||
color: #eee;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
.container {
|
||||
max-width: 400px;
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
}
|
||||
h1 { color: #00d9ff; margin-bottom: 30px; }
|
||||
.status {
|
||||
background: #16213e;
|
||||
padding: 15px;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.status span {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: #00d9ff;
|
||||
}
|
||||
.slider-container {
|
||||
background: #16213e;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.slider {
|
||||
width: 100%;
|
||||
height: 25px;
|
||||
-webkit-appearance: none;
|
||||
background: #0f3460;
|
||||
border-radius: 12px;
|
||||
outline: none;
|
||||
}
|
||||
.slider::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
background: #00d9ff;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
}
|
||||
.buttons {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.btn {
|
||||
padding: 20px 30px;
|
||||
font-size: 18px;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
transition: transform 0.1s, background 0.2s;
|
||||
min-width: 100px;
|
||||
}
|
||||
.btn:active { transform: scale(0.95); }
|
||||
.btn-forward { background: #00c853; color: white; }
|
||||
.btn-reverse { background: #ff5252; color: white; }
|
||||
.btn-stop { background: #ff9100; color: white; flex-basis: 100%; }
|
||||
.btn:hover { filter: brightness(1.1); }
|
||||
.speed-value {
|
||||
font-size: 48px;
|
||||
color: #00d9ff;
|
||||
margin: 10px 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Motor Control</h1>
|
||||
|
||||
<div class="status">
|
||||
Direction: <span id="dirStatus">STOPPED</span>
|
||||
</div>
|
||||
|
||||
<div class="slider-container">
|
||||
<label>Speed</label>
|
||||
<div class="speed-value"><span id="speedValue">0</span>%</div>
|
||||
<input type="range" class="slider" id="speedSlider" min="0" max="100" value="0">
|
||||
</div>
|
||||
|
||||
<div class="buttons">
|
||||
<button class="btn btn-forward" onclick="setDir(1)">FORWARD</button>
|
||||
<button class="btn btn-reverse" onclick="setDir(-1)">REVERSE</button>
|
||||
<button class="btn btn-stop" onclick="stopMotor()">STOP</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const slider = document.getElementById('speedSlider');
|
||||
const speedVal = document.getElementById('speedValue');
|
||||
const dirStatus = document.getElementById('dirStatus');
|
||||
let currentDir = 0;
|
||||
|
||||
slider.oninput = function() {
|
||||
speedVal.textContent = this.value;
|
||||
};
|
||||
|
||||
slider.onchange = function() {
|
||||
fetch('/speed?value=' + this.value)
|
||||
.then(r => r.text())
|
||||
.then(console.log);
|
||||
};
|
||||
|
||||
function setDir(dir) {
|
||||
currentDir = dir;
|
||||
fetch('/direction?value=' + dir)
|
||||
.then(r => r.text())
|
||||
.then(() => updateStatus());
|
||||
}
|
||||
|
||||
function stopMotor() {
|
||||
currentDir = 0;
|
||||
slider.value = 0;
|
||||
speedVal.textContent = '0';
|
||||
fetch('/stop')
|
||||
.then(r => r.text())
|
||||
.then(() => updateStatus());
|
||||
}
|
||||
|
||||
function updateStatus() {
|
||||
if (currentDir > 0) dirStatus.textContent = 'FORWARD';
|
||||
else if (currentDir < 0) dirStatus.textContent = 'REVERSE';
|
||||
else dirStatus.textContent = 'STOPPED';
|
||||
}
|
||||
|
||||
// Get initial state
|
||||
fetch('/status')
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
slider.value = data.speed;
|
||||
speedVal.textContent = data.speed;
|
||||
currentDir = data.direction;
|
||||
updateStatus();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
)rawliteral";
|
||||
|
||||
void handleRoot() {
|
||||
server.send(200, "text/html", index_html);
|
||||
}
|
||||
|
||||
void handleSpeed() {
|
||||
if (server.hasArg("value")) {
|
||||
int speed = server.arg("value").toInt();
|
||||
motor.setSpeed(speed);
|
||||
server.send(200, "text/plain", "OK");
|
||||
} else {
|
||||
server.send(400, "text/plain", "Missing value");
|
||||
}
|
||||
}
|
||||
|
||||
void handleDirection() {
|
||||
if (server.hasArg("value")) {
|
||||
int dir = server.arg("value").toInt();
|
||||
motor.setDirection(dir);
|
||||
server.send(200, "text/plain", "OK");
|
||||
} else {
|
||||
server.send(400, "text/plain", "Missing value");
|
||||
}
|
||||
}
|
||||
|
||||
void handleStop() {
|
||||
motor.stop();
|
||||
server.send(200, "text/plain", "OK");
|
||||
}
|
||||
|
||||
void handleStatus() {
|
||||
String json = "{\"speed\":" + String(motor.getSpeed()) +
|
||||
",\"direction\":" + String(motor.getDirection()) +
|
||||
",\"currentR\":" + String(motor.getCurrentRight(), 2) +
|
||||
",\"currentL\":" + String(motor.getCurrentLeft(), 2) +
|
||||
",\"stalled\":" + (motor.isStalled() ? "true" : "false") + "}";
|
||||
server.send(200, "application/json", json);
|
||||
}
|
||||
|
||||
void setupWebServer() {
|
||||
server.on("/", handleRoot);
|
||||
server.on("/speed", handleSpeed);
|
||||
server.on("/direction", handleDirection);
|
||||
server.on("/stop", handleStop);
|
||||
server.on("/status", handleStatus);
|
||||
|
||||
server.begin();
|
||||
Serial.println("Web server started on port 80");
|
||||
}
|
||||
|
||||
void handleWebServer() {
|
||||
server.handleClient();
|
||||
}
|
||||
Reference in New Issue
Block a user