Last active
April 19, 2024 13:45
-
-
Save harrkout/42659e5b7e8aa9e975b3a3124674b145 to your computer and use it in GitHub Desktop.
IoT rover, friday push
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <Arduino.h> | |
#include <WiFi.h> | |
#include <AsyncTCP.h> | |
#include <ESPAsyncWebServer.h> | |
#include <AsyncElegantOTA.h> | |
#include <SPIFFS.h> | |
#include <ArduinoJson.h> | |
#include <RunningMedian.h> | |
TaskHandle_t looptask; | |
RunningMedian samples = RunningMedian(5); | |
#define INTERRUPT_PIN1 18 // Pin connected to the OUT of the optical sensor | |
#define INTERRUPT_PIN2 19 // Pin connected to the OUT of the optical sensor | |
AsyncWebServer server(80); | |
String header; | |
/* Set static IP configuration */ | |
IPAddress staticIP(192, 168, 1, 4); // static IP address | |
IPAddress gateway(192, 168, 1, 1); // gateway/router IP address | |
String rightWheelReverse_PIO26 = "off"; | |
String rightWheelForward_PIO27 = "off"; | |
String leftWheelReverse_PIO32 = "off"; | |
String leftWheelForward_PIO33 = "off"; | |
const int rightWheelForward_OUT27 = 27; | |
const int leftWheelForward_OUT33 = 33; | |
const int rightWheelReverse_OUT26 = 26; | |
const int leftWheelReverse_OUT32 = 32; | |
/* optical */ | |
unsigned long lastTime = 0; | |
int tick = 0; | |
float wheelDiameter = 0.1; // in meters | |
float ticksPerRevolution = 2; // one tick is one revolution of a wheel | |
float distancePerTick = PI * wheelDiameter / ticksPerRevolution; // in meters | |
void forward() { | |
Serial.println("Moving forward"); | |
digitalWrite(rightWheelForward_OUT27, HIGH); | |
digitalWrite(leftWheelForward_OUT33, HIGH); | |
} | |
void backward() { | |
Serial.println("Moving backward"); | |
digitalWrite(rightWheelReverse_OUT26, HIGH); | |
digitalWrite(leftWheelReverse_OUT32, HIGH); | |
} | |
void left() { | |
Serial.println("Turning left"); | |
digitalWrite(rightWheelForward_OUT27, HIGH); | |
digitalWrite(leftWheelForward_OUT33, LOW); | |
} | |
void right() { | |
Serial.println("Turning right"); | |
digitalWrite(rightWheelForward_OUT27, LOW); | |
digitalWrite(leftWheelForward_OUT33, HIGH); | |
} | |
void stop() { | |
Serial.println("Stopping"); | |
digitalWrite(rightWheelForward_OUT27, LOW); | |
digitalWrite(leftWheelForward_OUT33, LOW); | |
digitalWrite(rightWheelReverse_OUT26, LOW); | |
digitalWrite(leftWheelReverse_OUT32, LOW); | |
} | |
void setup() { | |
Serial.begin(115200); | |
Serial.println(); | |
/* ====================================================== */ | |
// Mount SPIFFS file system | |
if (!SPIFFS.begin(true)) { | |
Serial.println("An Error has occurred while mounting SPIFFS"); | |
return; | |
} | |
// SPIFFS.format(); | |
// Get flash chip size | |
uint32_t flashSize = ESP.getFlashChipSize(); | |
Serial.print("Flash Chip Size: "); | |
Serial.print(flashSize); | |
Serial.println(" bytes"); | |
// Calculate SPIFFS flash offset | |
uint32_t spiffsSize = SPIFFS.totalBytes(); | |
uint32_t spiffsOffset = flashSize - spiffsSize; | |
Serial.print("SPIFFS Flash Offset: 0x"); | |
Serial.println(spiffsOffset, HEX); | |
// List files in SPIFFS and print their contents | |
File root = SPIFFS.open("/"); | |
File file = root.openNextFile(); | |
// Check if the file "/commands.txt" exists | |
if (SPIFFS.exists("/commands.txt")) { | |
Serial.println("File '/commands.txt' exists in SPIFFS."); | |
} else { | |
Serial.println("File '/commands.txt' does not exist in SPIFFS."); | |
} | |
while (file) { | |
Serial.print("File: "); | |
Serial.println(file.name()); | |
while (file.available()) { | |
Serial.write(file.read()); | |
} | |
file.close(); | |
file = root.openNextFile(); | |
} | |
// Open config.json file | |
File configFile = SPIFFS.open("/config.json", "r"); | |
if (!configFile) { | |
Serial.println("Failed to open config file"); | |
return; | |
} | |
// Parse JSON object | |
StaticJsonDocument<256> jsonConfig; | |
DeserializationError error = deserializeJson(jsonConfig, configFile); | |
if (error) { | |
Serial.println("Failed to parse config file"); | |
return; | |
} | |
// Retrieve SSID and password from JSON | |
const char* ssid = jsonConfig["ssid"]; | |
const char* password = jsonConfig["password"]; | |
//========================================= | |
/* optical */ | |
pinMode(INTERRUPT_PIN1, INPUT); // Set the interrupt pin as input | |
pinMode(INTERRUPT_PIN2, INPUT); // Set the interrupt pin as input | |
attachInterrupt(digitalPinToInterrupt(INTERRUPT_PIN1), speedInterrupt, FALLING); // Attach the interrupt | |
attachInterrupt(digitalPinToInterrupt(INTERRUPT_PIN2), speedInterrupt, FALLING); // Attach the interrupt | |
pinMode(rightWheelForward_OUT27, OUTPUT); | |
pinMode(leftWheelForward_OUT33, OUTPUT); | |
pinMode(rightWheelReverse_OUT26, OUTPUT); | |
pinMode(leftWheelReverse_OUT32, OUTPUT); | |
/* other core */ | |
// Create a task to run the loop code on Core 0 | |
xTaskCreatePinnedToCore( | |
loopTask1, // Function to execute | |
"loopTask", // Name of the task | |
10000, // Stack size (bytes) | |
NULL, // Task parameter | |
1, // Priority | |
NULL, // Task handle | |
0 // Core to run the task on (Core 0) | |
); | |
WiFi.mode(WIFI_STA); | |
WiFi.softAPConfig(staticIP, staticIP, gateway); | |
if (!WiFi.softAP(ssid, password)) { | |
Serial.println("Soft AP creation failed."); | |
while (1) | |
; | |
} | |
WiFi.mode(WIFI_STA); | |
WiFi.begin(ssid, password); | |
Serial.println(""); | |
while (WiFi.status() != WL_CONNECTED) { | |
delay(500); | |
Serial.print("."); | |
} | |
Serial.println(""); | |
Serial.print("Connected to "); | |
Serial.println(ssid); | |
Serial.print("IP address: "); | |
Serial.println(WiFi.localIP()); | |
server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) { | |
AsyncResponseStream *response = request->beginResponseStream("text/html"); | |
AsyncElegantOTA.begin(&server); | |
response->print("<!DOCTYPE html><html>"); | |
response->print("<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">"); | |
response->print("<link rel=\"icon\" href=\"data:,\">"); | |
response->print("<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}"); | |
response->print(".button { background-color: #4CAF50; border: none; color: white; padding: 16px 40px;"); | |
response->print("text-decoration: none; font-size: 30px; margin: 2px; cursor: pointer;}"); | |
response->print(".button:active { background-color: #7F7F7F; }"); | |
response->print(".slider { width: 300px; }</style>"); | |
response->print("<script>"); | |
response->print("document.addEventListener('keydown', function(event) {"); | |
response->print(" switch(event.key) {"); | |
response->print(" case 'ArrowUp':"); | |
response->print(" sendRequest('/forward');"); | |
response->print(" break;"); | |
response->print(" case 'ArrowDown':"); | |
response->print(" sendRequest('/backward');"); | |
response->print(" break;"); | |
response->print(" case 'ArrowLeft':"); | |
response->print(" sendRequest('/left');"); | |
response->print(" break;"); | |
response->print(" case 'ArrowRight':"); | |
response->print(" sendRequest('/right');"); | |
response->print(" break;"); | |
response->print(" }"); | |
response->print("});"); | |
response->print("document.addEventListener('keyup', function(event) {"); | |
response->print(" sendRequest('/stop');"); | |
response->print("});"); | |
response->print("function sendRequest(url) {"); | |
response->print(" var xhttp = new XMLHttpRequest();"); | |
response->print(" xhttp.open('GET', url, true);"); | |
response->print(" xhttp.send();"); | |
response->print("}"); | |
response->print("</script>"); | |
response->print("<body><h1>Rover Pad v1.1</h1>"); | |
response->print("<div style=\"display: flex; flex-direction: column; align-items: center;\">"); | |
response->print("<div style=\"display: flex;\">"); | |
response->print("<p></p>"); | |
response->print("<p><button class=\"button\" onmousedown=\"sendRequest('/forward')\" onmouseup=\"sendRequest('/stop')\" ontouchstart=\"sendRequest('/forward')\" ontouchend=\"sendRequest('/stop')\">↑</button></p>"); | |
response->print("<p></p>"); | |
response->print("</div>"); | |
response->print("<div style=\"display: flex;\">"); | |
response->print("<p><button class=\"button\" onmousedown=\"sendRequest('/left')\" onmouseup=\"sendRequest('/stop')\" ontouchstart=\"sendRequest('/left')\" ontouchend=\"sendRequest('/stop')\">←</button></p>"); | |
response->print("<p></p>"); | |
response->print("<p><button class=\"button\" onmousedown=\"sendRequest('/right')\" onmouseup=\"sendRequest('/stop')\" ontouchstart=\"sendRequest('/right')\" ontouchend=\"sendRequest('/stop')\">→</button></p>"); | |
response->print("</div>"); | |
response->print("<div style=\"display: flex;\">"); | |
response->print("<p></p>"); | |
response->print("<p><button class=\"button\" onmousedown=\"sendRequest('/backward')\" onmouseup=\"sendRequest('/stop')\" ontouchstart=\"sendRequest('/backward')\" ontouchend=\"sendRequest('/stop')\">↓</button></p>"); | |
response->print("<p></p>"); | |
response->print("</div>"); | |
/**"speed" slider */ | |
response->print("<p>Speed: <span id=\"speedValue\">0</span></p>"); | |
response->print("<input type=\"range\" min=\"0\" max=\"255\" value=\"0\" class=\"slider\" id=\"speedSlider\">"); | |
/**Js for updating speed value */ | |
response->print("<script>"); | |
response->print("var slider = document.getElementById('speedSlider');"); | |
response->print("var output = document.getElementById('speedValue');"); | |
response->print("output.innerHTML = slider.value;"); | |
response->print("slider.oninput = function() {"); | |
response->print(" output.innerHTML = this.value;"); | |
response->print(" sendRequest('/speed/' + this.value);"); | |
response->print("}"); | |
response->print("</script>"); | |
/* Ticks per second */ | |
response->print("<p id=\"ticksPerSecond\"></p>"); | |
response->print("<script>"); | |
response->print("function updateTicksPerSecond() {"); | |
response->print(" var xhttp = new XMLHttpRequest();"); | |
response->print(" xhttp.onreadystatechange = function() {"); | |
response->print(" if (this.readyState == 4 && this.status == 200) {"); | |
response->print(" document.getElementById('ticksPerSecond').innerText = this.responseText;"); | |
response->print(" setTimeout(updateTicksPerSecond, 1000);"); // Update every 1 second | |
response->print(" }"); | |
response->print(" };"); | |
response->print(" xhttp.open('GET', '/tickpersecond', true);"); | |
response->print(" xhttp.send();"); | |
response->print("}"); | |
// Call the function initially | |
response->print("updateTicksPerSecond();"); | |
response->print("</script>"); | |
/* Speedometer */ | |
response->print("<p id=\"speedometer\"></p>"); | |
response->print("<script>"); | |
response->print("function updateSpeedometer() {"); | |
response->print(" var xhttp = new XMLHttpRequest();"); | |
response->print(" xhttp.onreadystatechange = function() {"); | |
response->print(" if (this.readyState == 4 && this.status == 200) {"); | |
response->print(" var ticksPerSecond = parseFloat(this.responseText);"); | |
response->print(" var speed = ticksPerSecond * " + String(distancePerTick * 60 / 1000) + ";"); // Convert ticks per second to km/h | |
response->print(" document.getElementById('speedometer').innerText = 'Speed: ' + speed.toFixed(2) + ' km/h';"); | |
response->print(" setTimeout(updateSpeedometer, 1000);"); // Update every 1 second | |
response->print(" }"); | |
response->print(" };"); | |
response->print(" xhttp.open('GET', '/tickpersecond', true);"); | |
response->print(" xhttp.send();"); | |
response->print("}"); | |
// Call the function initially | |
response->print("updateSpeedometer();"); | |
response->print("</script>"); | |
// Circular buttons to set ESP32 to different sleep states | |
response->print("<div style=\"display: flex; justify-content: space-around;\">"); | |
response->print("<button style=\"width: 100px; height: 100px; border-radius: 50%;\" onclick=\"setSleepState('light');\">Light Sleep</button>"); | |
response->print("<button style=\"width: 100px; height: 100px; border-radius: 50%;\" onclick=\"setSleepState('deep');\">Deep Sleep</button>"); | |
response->print("</div>"); | |
// Script to send requests to ESP32 | |
response->print("<script>"); | |
response->print("function setSleepState(state) {"); | |
response->print(" var xhttp = new XMLHttpRequest();"); | |
response->print(" xhttp.onreadystatechange = function() {"); | |
response->print(" if (this.readyState == 4 && this.status == 200) {"); | |
response->print(" console.log('ESP32 set to ' + state + ' sleep state for 30 seconds.');"); | |
response->print(" }"); | |
response->print(" };"); | |
response->print(" xhttp.open('GET', '/setSleepState?state=' + state, true);"); | |
response->print(" xhttp.send();"); | |
response->print("}"); | |
response->print("</script>"); | |
/* button for updating IP */ | |
response->print("<button class=\"button\" onclick=\"window.location.href='http://"); | |
response->print(WiFi.localIP().toString()); | |
response->print("/update';\">Update IP</button>"); | |
// Plotly.js integration for serial plotter | |
response->print("<div>"); | |
response->print("<div id=\"plot\"></div>"); | |
response->print("<script src=\"https://cdn.plot.ly/plotly-latest.min.js\"></script>"); | |
response->print("<script>"); | |
response->print("var xValues = [];"); | |
response->print("var yValues = [];"); | |
response->print("var plotDiv = document.getElementById('plot');"); | |
response->print("var data = [{"); | |
response->print(" x: xValues,"); | |
response->print(" y: yValues,"); | |
response->print(" type: 'scatter'"); | |
response->print("}];"); | |
response->print("Plotly.newPlot(plotDiv, data);"); | |
response->print("var cnt = 0;"); | |
response->print("setInterval(function() {"); | |
response->print(" xValues.push(cnt);"); | |
response->print(" yValues.push(Math.random());"); | |
response->print(" cnt++;"); | |
response->print(" if (xValues.length > 30) {"); | |
response->print(" xValues.shift();"); | |
response->print(" yValues.shift();"); | |
response->print(" }"); | |
response->print(" Plotly.newPlot(plotDiv, data);"); | |
response->print("}, 1000);"); | |
response->print("</script>"); | |
response->print("</div>"); | |
response->print("</body></html>"); | |
request->send(response); | |
}); | |
server.on("/forward", HTTP_GET, [](AsyncWebServerRequest * request) { | |
forward(); | |
request->send(200, "text/plain", "Moving forward"); | |
}); | |
server.on("/backward", HTTP_GET, [](AsyncWebServerRequest * request) { | |
backward(); | |
request->send(200, "text/plain", "Moving backward"); | |
}); | |
server.on("/left", HTTP_GET, [](AsyncWebServerRequest * request) { | |
left(); | |
request->send(200, "text/plain", "Turning left"); | |
}); | |
server.on("/right", HTTP_GET, [](AsyncWebServerRequest * request) { | |
right(); | |
request->send(200, "text/plain", "Turning right"); | |
}); | |
server.on("/stop", HTTP_GET, [](AsyncWebServerRequest * request) { | |
stop(); | |
request->send(200, "text/plain", "Stopping"); | |
}); | |
server.on("/speed", HTTP_GET, [](AsyncWebServerRequest * request) { | |
if (request->hasParam("value")) { | |
String value = request->getParam("value")->value(); | |
int newSpeed = value.toInt(); | |
// Ensure the speed value is within the valid range (0 - 255) | |
newSpeed = constrain(newSpeed, 0, 255); | |
// Update the speed of both motors | |
analogWrite(rightWheelForward_OUT27, newSpeed); | |
analogWrite(leftWheelForward_OUT33, newSpeed); | |
request->send(200, "text/plain", "Speed updated to: " + String(newSpeed)); | |
} else { | |
request->send(400, "text/plain", "Missing speed parameter"); | |
} | |
}); | |
server.on("/tickpersecond", HTTP_GET, [](AsyncWebServerRequest * request) { | |
request->send(200, "text/plain", String(tick)); | |
}); | |
// Handler to set ESP32 to different sleep states | |
server.on("/setSleepState", HTTP_GET, [](AsyncWebServerRequest * request) { | |
if (request->hasParam("state")) { | |
String state = request->getParam("state")->value(); | |
if (state == "light") { | |
// Set ESP32 to light sleep for 30 seconds | |
esp_sleep_enable_timer_wakeup(5 * 1000000); // 30 seconds | |
esp_light_sleep_start(); | |
request->send(200, "text/plain", "ESP32 set to light sleep for 30 seconds."); | |
} else if (state == "deep") { | |
// Set ESP32 to deep sleep for 30 seconds | |
esp_sleep_enable_timer_wakeup(5 * 1000000); // 30 seconds | |
esp_deep_sleep_start(); | |
request->send(200, "text/plain", "ESP32 set to deep sleep for 30 seconds."); | |
} else { | |
request->send(400, "text/plain", "Invalid sleep state"); | |
} | |
} else { | |
request->send(400, "text/plain", "Missing sleep state parameter"); | |
} | |
}); | |
server.onNotFound([](AsyncWebServerRequest * request) { | |
request->send(404, "text/plain", "Not found"); | |
}); | |
AsyncElegantOTA.begin(&server); | |
server.begin(); | |
Serial.println("HTTP server started"); | |
} | |
/* ========================================================= */ | |
/* other core */ | |
portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED; | |
void speedInterrupt() { | |
portENTER_CRITICAL_ISR(&mux); | |
unsigned long currentTime = micros(); | |
if (currentTime - lastTime > 1000) { // 1ms debounce time | |
tick++; | |
lastTime = currentTime; | |
} | |
portEXIT_CRITICAL_ISR(&mux); | |
} | |
// Define variables to store the previous tick count and the time of the previous tick | |
unsigned long prevTickCount = 0; | |
unsigned long prevTickTime = 0; | |
void loopTask1(void *pvParameters) { | |
Serial.print("Task1 running on core "); | |
Serial.println(xPortGetCoreID()); | |
for (;;) { | |
delay(1000); | |
portENTER_CRITICAL(&mux); | |
int tickCopy = tick; | |
tick = 0; // reset the tick count | |
portEXIT_CRITICAL(&mux); | |
Serial.println(tickCopy); | |
// Calculate time elapsed since the last tick in seconds | |
unsigned long currentTime = millis(); | |
float timeElapsed = (currentTime - prevTickTime) / 1000.0; // convert milliseconds to seconds | |
// Calculate ticks per second | |
float ticksPerSecond = tickCopy / timeElapsed; | |
// Update previous tick count and time | |
prevTickCount = tickCopy; | |
prevTickTime = currentTime; | |
// Calculate revolutions per minute (RPM) | |
float rpm = ticksPerSecond / 2 * 60; // Divide by 2 to account for 2 ticks per revolution | |
// Calculate speed in km/h | |
float speed = rpm * distancePerTick * 60 / 1000; // Convert distance to kilometers | |
Serial.print("Ticks per second: "); | |
Serial.print(ticksPerSecond); | |
Serial.print("\t"); | |
Serial.print("RPM: "); | |
Serial.print(rpm); | |
Serial.print("\t"); | |
Serial.print("Speed: "); | |
Serial.print(speed); | |
Serial.println(" km/h"); | |
} | |
} | |
/* ====================================================== */ | |
void loop() { | |
delay(1000); | |
tick = samples.getCount(); | |
Serial.println(tick); | |
samples.clear(); | |
/* =========================== */ | |
touchSetCycles(0x0, 0x0); // Example of setting both measurement and sleep cycles to 0x2000 | |
long total1 = touchRead(12); // Assuming the touch pin is connected to GPIO 12 | |
samples.add(total1); | |
long m = samples.getMedian(); | |
long a = samples.getAverage(); | |
// Map the touch sensor value (0-4095) to the motor speed (0-255) | |
// int motorSpeed = map(touchRead(12), 0, 4095, 0, 255); | |
// | |
// // Set the motor speed | |
// analogWrite(rightWheelForward_OUT27, motorSpeed); | |
// analogWrite(leftWheelForward_OUT33, motorSpeed); | |
Serial.print("median - "); | |
Serial.print(m); | |
Serial.print("\t"); | |
Serial.print("average - "); | |
Serial.print(a); | |
Serial.print("\n"); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment