/*
https://randomnerdtutorials.com/esp32-mpu-6050-web-server/
https://grok.com/share/bGVnYWN5_ce464cbc-392f-4693-bed5-5fdbe3099cf7
https://grok.com/share/bGVnYWN5_ce464cbc-392f-4693-bed5-5fdbe3099cf7
use these libraries, not the ones in wokwi
https://github.com/ESP32Async/AsyncTCP
https://github.com/ESP32Async/ESPAsyncWebServer
To test, you need the Wokwi IoT Gateway, as explained here:
https://docs.wokwi.com/guides/esp32-wifi#the-private-gateway
Then start the simulation, and open
http://localhost:9080/
in another browser tab.
*/
#define batteryVoltageHigh 8.4
#define batteryVoltageLow 6.4
const char* compileDateTime = __DATE__ " " __TIME__;
const char* fileInfo = __FILE__;
#include <Adafruit_NeoPixel.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Arduino.h>
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <Adafruit_MPU6050.h>
#include <Adafruit_Sensor.h>
#include <Arduino_JSON.h>
#include "page.h"
#include "OneButton.h"
// Pins
#define batteryVoltagePin 33
#define upButtonPin 18
#define downButtonPin 19
// Setup a new OneButtons
OneButton button1(upButtonPin, true);
OneButton button2(downButtonPin, true);
const char* ssid = "Wokwi-GUEST";
const char* password = "";
AsyncWebServer server(80);
AsyncEventSource events("/events");
JSONVar readings;
unsigned long lastTime = 0, lastTimeTemperature = 0, lastTimeAcc = 0, lastTimeVoltage = 0;
unsigned long gyroDelay = 10, temperatureDelay = 1000, accelerometerDelay = 200, voltageDelay = 2000;
Adafruit_MPU6050 mpu;
sensors_event_t a, g, temp;
float gyroX = 0, gyroY = 0, gyroZ = 0; // Roll, Pitch, Yaw in degrees
float prevGyroX = 0, prevGyroY = 0, prevGyroZ = 0; // Roll, Pitch, Yaw in degrees
float accX, accY, accZ;
float temperature;
unsigned long previousTime = 0;
//------------------------------------------------------------------------
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin)
#define SCREEN_ADDRESS 0x3D ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
//------------------------------------------------------------------------
#define PIN 25 // On Trinket or Gemma, suggest changing this to 1
#define NUMPIXELS 1 // Popular NeoPixel ring size
Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);
//------------------------------------------------------------------------
// Global Variables
// accel of gravity
const float G = 9.80665;
long currentMilli = 0;
long currentSec = 0;
long lastLevel = 0;
long lastSlow = 0;
float batteryVoltage = 0;
//========================================================================
void initMPU() {
if (!mpu.begin()) {
Serial.println("Failed to find MPU6050 chip");
setPixel(200, 0, 0);
while (1) delay(10);
}
setPixel(0, 0, 0);
Serial.println("MPU6050 Found!");
mpu.setGyroRange(MPU6050_RANGE_250_DEG);
mpu.setFilterBandwidth(MPU6050_BAND_44_HZ);
}
//========================================================================
void initWiFi() {
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
Serial.print("Connecting to WiFi...");
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
setPixel(200, 0, 0);
delay(1000);
}
setPixel(0, 0, 0);
Serial.println("\nConnected! IP: " + WiFi.localIP().toString());
}
//========================================================================
String getGyroReadings() {
mpu.getEvent(&a, &g, &temp);
unsigned long currentTime = millis();
float dt = (currentTime - previousTime) / 1000.0;
previousTime = currentTime;
gyroX += (g.gyro.x * 180.0 / PI) * dt; // Roll
gyroY += (g.gyro.y * 180.0 / PI) * dt; // Pitch
gyroZ += (g.gyro.z * 180.0 / PI) * dt; // Yaw
readings["roll"] = String(gyroX, 1);
readings["pitch"] = String(gyroY, 1);
readings["yaw"] = String(gyroZ, 1);
return JSON.stringify(readings);
}
//========================================================================
String getAccReadings() {
mpu.getEvent(&a, &g, &temp);
readings["accX"] = String(a.acceleration.x, 2);
readings["accY"] = String(a.acceleration.y, 2);
readings["accZ"] = String(a.acceleration.z, 2);
return JSON.stringify(readings);
}
//========================================================================
String getTemperature() {
mpu.getEvent(&a, &g, &temp);
float tempTemp = (temp.temperature * 9 / 5) + 32;
return String(tempTemp, 2);
}
//========================================================================
String getVoltage() {
int rawVoltage = analogRead(batteryVoltagePin);
Serial.print("raw analog: ");Serial.print(rawVoltage); Serial.print("\t");
batteryVoltage = mapf(rawVoltage, 0.0, 4095.0, 0.0, 12.0);
Serial.print(batteryVoltage); Serial.print("\t");
int voltWheel = map(batteryVoltage, batteryVoltageLow, batteryVoltageHigh, 0, 85);
if (voltWheel < 0) voltWheel = 0;
if (voltWheel > 85) voltWheel = 85;
pixelWheel(voltWheel);
Serial.println(voltWheel);
return String(batteryVoltage, 1);
}
//========================================================================
void zeroAll() {
}
//========================================================================
// _____ _
// / ___| | |
// \ `--. ___ | |_ _ _ _ __
// `--. \ / _ \| __|| | | || '_ \
// /\__/ /| __/| |_ | |_| || |_) |
// \____/ \___| \__| \__,_|| .__/
// | |
// |_|
//========================================================================
void setup() {
Serial.begin(115200);
Serial.print("Compiled on: "); Serial.println(compileDateTime);
Serial.print("file: "); Serial.println(fileInfo);
//------------------------------------------------------------------------
pinMode(upButtonPin, INPUT_PULLUP);
pinMode(downButtonPin, INPUT_PULLUP);
// link the button 1 functions.
button1.attachClick(click1);
button1.attachDoubleClick(doubleclick1);
button1.attachLongPressStart(longPressStart1);
button1.attachLongPressStop(longPressStop1);
button1.attachDuringLongPress(longPress1);
// link the button 2 functions.
button2.attachClick(click2);
button2.attachDoubleClick(doubleclick2);
button2.attachLongPressStart(longPressStart2);
button2.attachLongPressStop(longPressStop2);
button2.attachDuringLongPress(longPress2);
//------------------------------------------------------------------------
delay(500);
pixels.begin(); // INITIALIZE NeoPixel strip object (REQUIRED)
pixels.clear(); // Set all pixel colors to 'off'
pixels.show(); // Send the updated pixel colors to the hardware.
setPixel(200, 200, 200);
//------------------------------------------------------------------------
delay(500);
// SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
Serial.println(F("SSD1306 allocation failed"));
for (;;); // Don't proceed, loop forever
} else {
Serial.println(F("SSD1306 Connected"));
setSplash();
delay(5000); // Pause for 2 seconds
}
// Clear the buffer
display.clearDisplay();
display.display();
//------------------------------------------------------------------------
initWiFi();
initMPU();
previousTime = millis();
server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) {
request->send_P(200, "text/html", index_html);
});
server.on("/reset", HTTP_GET, [](AsyncWebServerRequest * request) {
prevGyroX = gyroX + prevGyroX;
prevGyroY = gyroY + prevGyroY;
prevGyroZ = gyroZ + prevGyroZ;
gyroX = gyroY = gyroZ = 0; request->send(200, "text/plain", "OK");
});
server.on("/resetX", HTTP_GET, [](AsyncWebServerRequest * request) {
prevGyroX = gyroX + prevGyroX;
gyroX = 0;
request->send(200, "text/plain", "OK");
});
server.on("/resetY", HTTP_GET, [](AsyncWebServerRequest * request) {
prevGyroY = gyroY + prevGyroY;
gyroY = 0;
request->send(200, "text/plain", "OK");
});
server.on("/resetZ", HTTP_GET, [](AsyncWebServerRequest * request) {
prevGyroZ = gyroZ + prevGyroZ;
gyroZ = 0;
request->send(200, "text/plain", "OK");
});
server.on("/zeroAll", HTTP_GET, [](AsyncWebServerRequest * request) {
zeroAll();
request->send(200, "text/plain", "OK");
});
events.onConnect([](AsyncEventSourceClient * client) {
client->send("hello!", NULL, millis(), 10000);
});
server.addHandler(&events);
server.begin();
Serial.println("Web Server started!");
setPixel(0, 150, 0);
setDisplay();
}
//========================================================================
// _ _____ _____ ______
// | | | _ || _ || ___ \
// | | | | | || | | || |_/ /
// | | | | | || | | || __/
// | |____\ \_/ /\ \_/ /| |
// \_____/ \___/ \___/ \_|
//
//========================================================================
void loop() {
button1.tick();
button2.tick();
currentMilli = millis();
currentSec = currentMilli / 1000.0;
if ((millis() - lastTime) > gyroDelay) {
events.send(getGyroReadings().c_str(), "gyro_readings", millis());
lastTime = millis();
}
if ((millis() - lastTimeAcc) > accelerometerDelay) {
events.send(getAccReadings().c_str(), "accelerometer_readings", millis());
lastTimeAcc = millis();
}
if ((millis() - lastTimeTemperature) > temperatureDelay) {
events.send(getTemperature().c_str(), "temperature_reading", millis());
lastTimeTemperature = millis();
}
if ((millis() - lastTimeVoltage) > voltageDelay) {
events.send(getVoltage().c_str(), "voltage_reading", millis());
lastTimeVoltage = millis();
}
if (currentMilli >= lastLevel + 100) {
bubbleLevel();
lastLevel = currentMilli;
}
if (currentSec >= lastSlow + 5) {
lastSlow = currentSec;
}
}
//========================================================================
// ______ _ _
// | ___| | | (_)
// | |_ _ _ _ __ ___ | |_ _ ___ _ __ ___
// | _|| | | || '_ \ / __|| __|| | / _ \ | '_ \ / __|
// | | | |_| || | | || (__ | |_ | || (_) || | | |\__ \
// \_| \__,_||_| |_| \___| \__||_| \___/ |_| |_||___/
//
//========================================================================
void readIO() {
int rawVoltage = analogRead(batteryVoltagePin);
//Serial.println(rawVoltage);
batteryVoltage = mapf(rawVoltage, 00.0, 4095.0, 0.0, 12.0);
}
//========================================================================
void pixelWheel(byte C ) {
//for (int i = 0; i < NUMPIXELS; i++) { // For each pixel...
// pixels.Color() takes RGB values, from 0,0,0 up to 255,255,255
//pixels.setPixelColor(i, pixels.Color(Wheel(C)));
pixels.setPixelColor(0, Wheel(C));
pixels.show(); // Send the updated pixel colors to the hardware.
//}
}
//========================================================================
void setPixel(byte R, byte G, byte B ) {
for (int i = 0; i < NUMPIXELS; i++) { // For each pixel...
// pixels.Color() takes RGB values, from 0,0,0 up to 255,255,255
pixels.setPixelColor(i, pixels.Color(R, G, B));
pixels.show(); // Send the updated pixel colors to the hardware.
}
}
//========================================================================
// Input a value 0 to 255 to get a color value.
// The colours are a transition r - g - b - back to r.
uint32_t Wheel(byte WheelPos) {
WheelPos = 255 - WheelPos;
if (WheelPos < 85) { // Red to green
return pixels.Color(255 - WheelPos * 3, 0, WheelPos * 3);
}
if (WheelPos < 170) { // Green to Blue
WheelPos -= 85;
return pixels.Color(0, WheelPos * 3, 255 - WheelPos * 3);
}
WheelPos -= 170; // Blue to red
return pixels.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
}
//========================================================================
void setSplash() {
display.clearDisplay();
display.setTextSize(2); // Normal 1:1 pixel scale
display.setTextColor(SSD1306_WHITE); // Draw white text
display.setCursor(0, 0); // Start at top-left corner
printCentered("Wireless", 0, 2);
printCentered("Level", 15, 2);
display.setTextSize(1); // Normal 1:1 pixel scale
printCentered("imanMaker", SCREEN_HEIGHT - 24, 1);
printCentered(compileDateTime, SCREEN_HEIGHT - 16, 1);
printCentered(fileInfo, SCREEN_HEIGHT - 8, 1);
display.display();
}
//========================================================================
void setDisplay() {
display.clearDisplay();
display.setTextSize(1); // Normal 1:1 pixel scale
display.setTextColor(SSD1306_WHITE); // Draw white text
display.setCursor(0, SCREEN_HEIGHT - 7); // Start at top-left corner
display.print("IP: ");
display.print(WiFi.localIP());
display.display();
}
//========================================================================
// https://prealpharelease.wordpress.com/2015/01/29/arduino-digital-bubble-level/
void bubbleLevel() {
sensors_event_t event;
float x, y, bx, by;
// get the latest event:
//tilt.getEvent(&event);
// get the accel readings from the event:
//x = event.acceleration.x; // m/s^2
//y = event.acceleration.y; // m/s^2
x = a.acceleration.x;
y = a.acceleration.y;
display.clearDisplay();
//------------------------------------------------------------------------
// print x and y degrees to screen buffer:
display.setCursor(0, 0);
display.print("X:");
//display.setCursor(15, 0);
display.print(mapf(x, 0.0, G, 0.0, 180.0), 0); // g acceleration == 180deg
display.write(247); // deg symbol
//display.setCursor((SCREEN_WIDTH / 2)+19, 0);
//display.print("Y:");
int yDeg = mapf(y, 0.0, G, 0.0, 180.0);
String printY = "Y:" + yDeg;
display.setCursor(0, SCREEN_HEIGHT - 7);
//display.print(mapf(y, 0.0, G, 0.0, 180.0),0); // g acceleration == 180deg
display.print(printY);
display.write(247); // deg symbol
//------------------------------------------------------------------------
bool filled;
// bubble location (90 deg [g/2] is edge of bubble screen):
bx = mapf(x, G / 2, -G / 2, (SCREEN_WIDTH / 4), (SCREEN_WIDTH * 0.75)); // map +/- G/2 x to middle 64 cols of screen
by = mapf(y, -G / 2, G / 2, 0, SCREEN_HEIGHT); // map -/+ G/2 y to 64 rows of screen
// Draw the middle circle with crosshair:
display.drawCircle((SCREEN_WIDTH / 2), SCREEN_HEIGHT / 2, 31.5, WHITE);
if (map(x, 0.0, G, 0.0, 180.0) == 0 && map(y, 0.0, G, 0.0, 180.0) == 0) {
display.fillCircle((SCREEN_WIDTH / 2), SCREEN_HEIGHT / 2, 12, WHITE);
filled = true;
} else {
display.drawCircle((SCREEN_WIDTH / 2), SCREEN_HEIGHT / 2, 12, WHITE);
filled = false;
}
display.drawLine((SCREEN_WIDTH / 2) - 16, SCREEN_HEIGHT / 2, (SCREEN_WIDTH / 2) + 16, (SCREEN_HEIGHT / 2), WHITE);
display.drawLine((SCREEN_WIDTH / 2), (SCREEN_HEIGHT / 2) - 16, (SCREEN_WIDTH / 2), (SCREEN_HEIGHT / 2) + 16, WHITE);
// if within this pixel tolerance, bubble circle turns white to indicate
// levelness otherwise bubble circle is empty:
int tol = 2;
/*
if (bx < (SCREEN_WIDTH / 2) + tol && bx > (SCREEN_WIDTH / 2) - tol && by > (SCREEN_HEIGHT / 2) + tol && by < (SCREEN_HEIGHT / 2) - tol) {
display.fillCircle(bx, by, 8, WHITE);
} else {
display.drawCircle(bx, by, 8, WHITE);
}
*/
if (filled) {
display.drawCircle(bx, by, 8, BLACK);
display.drawLine((SCREEN_WIDTH / 2) - 11, SCREEN_HEIGHT / 2, (SCREEN_WIDTH / 2) + 11, (SCREEN_HEIGHT / 2), BLACK);
display.drawLine((SCREEN_WIDTH / 2), (SCREEN_HEIGHT / 2) - 11, (SCREEN_WIDTH / 2), (SCREEN_HEIGHT / 2) + 11, BLACK);
} else {
display.drawCircle(bx, by, 8, WHITE);
}
//------------------------------------------------------------------------
display.setCursor(SCREEN_WIDTH - 30, 0);
display.print(batteryVoltage, 1); display.print("v");
// draw the stuff in the buffer:
display.display();
// update 10 times a second
delay(100);
}
//========================================================================
// float version of standard map function:
float mapf(float x, float in_min, float in_max, float out_min, float out_max) {
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
//========================================================================
// Function to center and print text
void printCentered(String text, int y, byte size) {
int16_t x1, y1;
uint16_t w, h;
display.setTextSize(size);
// Calculate bounding box for the text
display.getTextBounds(text, 0, y, &x1, &y1, &w, &h);
// Calculate X position to center
int16_t x = (SCREEN_WIDTH - w) / 2;
display.setCursor(x, y);
display.print(text);
}
//========================================================================
// This function will be called when the button1 was pressed 1 time (and no 2. button press followed).
void click1() {
Serial.println("Button 1 click.");
} // click1
// This function will be called when the button1 was pressed 2 times in a short timeframe.
void doubleclick1() {
Serial.println("Button 1 doubleclick.");
} // doubleclick1
// This function will be called once, when the button1 is pressed for a long time.
void longPressStart1() {
Serial.println("Button 1 longPress start");
} // longPressStart1
// This function will be called often, while the button1 is pressed for a long time.
void longPress1() {
Serial.println("Button 1 longPress...");
} // longPress1
// This function will be called once, when the button1 is released after beeing pressed for a long time.
void longPressStop1() {
Serial.println("Button 1 longPress stop");
} // longPressStop1
// ... and the same for button 2:
void click2() {
Serial.println("Button 2 click.");
} // click2
void doubleclick2() {
Serial.println("Button 2 doubleclick.");
} // doubleclick2
void longPressStart2() {
Serial.println("Button 2 longPress start");
} // longPressStart2
void longPress2() {
Serial.println("Button 2 longPress...");
} // longPress2
void longPressStop2() {
Serial.println("Button 2 longPress stop");
} // longPressStop2
// End