Skip to content

Instantly share code, notes, and snippets.

@harrkout
Last active April 19, 2024 13:45
Show Gist options
  • Save harrkout/42659e5b7e8aa9e975b3a3124674b145 to your computer and use it in GitHub Desktop.
Save harrkout/42659e5b7e8aa9e975b3a3124674b145 to your computer and use it in GitHub Desktop.
IoT rover, friday push
#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')\">&#8593;</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')\">&#8592;</button></p>");
response->print("<p></p>");
response->print("<p><button class=\"button\" onmousedown=\"sendRequest('/right')\" onmouseup=\"sendRequest('/stop')\" ontouchstart=\"sendRequest('/right')\" ontouchend=\"sendRequest('/stop')\">&#8594;</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')\">&#8595;</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