#include "SSD1306Wire.h"
#include <ESPAsyncWebServer.h>
//#include <WebSocketsServer.h>
#include <WiFi.h>
#include <vector>
#include <math.h>
#define VERSION "0.1.0"
#define CLIENT_DELAY "1000"
#define SERIAL_DELAY 100
#define HTTP_PORT 80
#define WS_PORT 81
#define QUEUE_DIMENSION 30
char *valid_states[] = {"IDDLE","START","STOP","RUNNING","DATA_READY"};
//void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length);
SSD1306Wire display(0x3c, SDA, SCL);
const char* ssid = "Webserver";
const char* password = "mybestpassword";
const char* PARAM_INPUT_1 = "output";
const char* PARAM_INPUT_2 = "state";
//queue
std::vector<float> values;
float x = 1;
String theState = "NOCOM";
//Valid state check
bool isValidState(String state) {
int found = 0;
for (int i = 0; i < 4; i++) {
if (strcmp(valid_states[i], state.c_str()) == 0) {
found = 1;
break;
}
}
return found;
}
std::vector<String> split(String str, char delim) {
std::vector<String> tokens;
int start = 0;
for (int i = 0; i < str.length(); i++) {
if (str[i] == delim) {
tokens.push_back(str.substring(start, i));
start = i + 1;
}
}
// Aggiungi l'ultimo token se la stringa non termina con un delimitatore
if (start < str.length()) {
tokens.push_back(str.substring(start));
}
return tokens;
}
void enqueue(float num) {
values.push_back(num);
}
String getValuesList(int start_index = 0) {
String json = "[";
// Controllo dei parametri
if (start_index < 0 || start_index >= values.size()) {
return "[]"; // Stringa vuota se l'indice è fuori dai limiti
}
String s = "";
for (int i = start_index; i < values.size(); i++) {
json += s + String(values[i]);
s = ",";
}
json += "]";
return json;
}
AsyncWebServer server(HTTP_PORT);
//WebSocketsServer webSocket(WS_PORT);
const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" href="data:,">
<title>Drill Monitor</title>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<header class="bg-primary text-white p-3">
<h1>Test UI</h1>
</header>
<div class="container mt-3">
<div class="row">
<div class="col-md-3">
<label class="btn btn-primary"><input type="checkbox" id='32' onchange="UI.toggle(this)" autocomplete="off">START</label>
</div>
<div class="col-md-3">
<label class="btn btn-primary"><input type="checkbox" id='33' onchange="UI.toggle(this)" autocomplete="off">ZERO</label>
</div>
<div class="col-md-3">
<label class="btn btn-primary"><input type="checkbox" id='12' onchange="UI.toggle(this)" autocomplete="off">STOP</label>
</div>
<div class="col-md-3">
<label class="btn btn-primary"><input type="checkbox" id='13' onchange="UI.toggle(this)" autocomplete="off">TRIGGER</label>
</div>
</div>
<div class="row mt-3">
<div class="col-md-12">
<canvas id="Chart1" width="400" height="200"></canvas>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
//Polling version
const UI = {
ctx: null,
chart: null,
ts: 0,
chart_num_values: 0,
init: function() {
this.ctx = document.getElementById('Chart1').getContext('2d');
this.chart = new Chart(this.ctx, {
type: 'line',
data: {
labels: [],
datasets: [{
label: 'Profondità',
data: [],
backgroundColor: 'rgba(255, 99, 132, 0.2)',
borderColor: 'rgba(255, 99, 132, 1)',
borderWidth: 1,
pointStyle: false
}]
},
options: {}
});
},
update: function() {
const self = this;
var xhr = new XMLHttpRequest();
xhr.open("GET", "/chart?ts="+self.ts, true);
xhr.send();
xhr.onload = function() {
if (xhr.status === 200) {
var data = JSON.parse(xhr.responseText);
var values = data.values;
var update = (data.values.length > 0);
values.forEach(function(value) {
self.chart.data.labels.push('');
self.chart.data.datasets[0].data.push(value);
self.chart.update(); //redraw
//console.log("push: "+value);
//self.chart_num_values++;
});
if (update) {
self.chart.update();
}
self.ts = data.ts;
} else {
console.error("xhr error:", xhr.status);
}
};
},
setButtons: function(status) {
if(status === "IDLE") {
document.getElementById("32").classList.add('active');
document.getElementById("33").classList.add('active');
document.getElementById("32").classList.remove('active');
document.getElementById("32").classList.remove('active');
}
if(status === "START") {
document.getElementById("32").classList.add('active');
document.getElementById("33").classList.add('active');
document.getElementById("12").classList.remove('active');
document.getElementById("13").classList.remove('active');
}
if(status === "STOP") {
document.getElementById("32").classList.remove('active');
document.getElementById("33").classList.remove('active');
document.getElementById("12").classList.remove('active');
document.getElementById("13").classList.remove('active');
}
if(status === "RUNNING") {
document.getElementById("32").classList.remove('active');
document.getElementById("33").classList.remove('active');
document.getElementById("12").classList.add('active');
document.getElementById("13").classList.remove('active');
}
if(status === "DATAREADY") {
document.getElementById("32").classList.add('active');
document.getElementById("33").classList.add('active');
document.getElementById("12").classList.add('active');
document.getElementById("13").classList.add('active');
}
},
toggle: function(e) {
var xhr = new XMLHttpRequest();
if(e.checked){
xhr.open("GET", "/update?output="+e.id+"&state=1", true);
}
else {
xhr.open("GET", "/update?output="+e.id+"&state=0", true);
}
xhr.send();
},
sleep: function(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
},
loop: async function() {
while (true) {
this.update();
this.setButtons();
await this.sleep(%CLIENT_DELAY%);
}
}
};
UI.init();
UI.loop();
//end polling version
/*
// WebSocket Version
const socket = new WebSocket('ws://localhost:9081');
// Gestione degli eventi WebSocket
socket.onmessage = function(event) {
const data = JSON.parse(event.data);
const randomNumber = data.randomNumber;
//chart update
Chart1.data.labels.push('');
Chart1.data.datasets[0].data.push(randomNumber);
// point limit view
const maxDataPoints = 10;
if (Chart1.data.labels.length > maxDataPoints) {
Chart1.data.labels.shift();
Chart1.data.datasets[0].data.shift();
}
// redraw
Chart1.update();
};
// Gestione dell'apertura della connessione WebSocket
socket.onopen = function(event) {
console.log('Connessione WebSocket stabilita');
};
// Gestione della chiusura della connessione WebSocket
socket.onclose = function(event) {
console.log('Connessione WebSocket chiusa');
};
// Gestione degli errori della connessione WebSocket
socket.onerror = function(error) {
console.error('Errore nella connessione WebSocket:', error);
};*/
</script>
</body>
</html>
)rawliteral";
// Replaces placeholder with button section in your web page
String processor(const String& var){
/*
if(var == "BUTTONPLACEHOLDER"){
String buttons = "";
buttons += "<tr><td><h4>START 32</h4><label class=\"switch\"><input type=\"checkbox\" onchange=\"toggleCheckbox(this)\" id=\"32\" " + outputState(32) + "><span class=\"slider\"></span></label></td>";
buttons += "<td><h4>ZERO 33</h4><label class=\"switch\"><input type=\"checkbox\" onchange=\"toggleCheckbox(this)\" id=\"33\" " + outputState(33) + "><span class=\"slider\"></span></label></td></tr>";
buttons += "<tr><td><h4>STOP 12</h4><label class=\"switch\"><input type=\"checkbox\" onchange=\"toggleCheckbox(this)\" id=\"12\" " + outputState(12) + "><span class=\"slider\"></span></label></td>";
buttons += "<td><h4>TRIGGER 13</h4><label class=\"switch\"><input type=\"checkbox\" onchange=\"toggleCheckbox(this)\" id=\"13\" " + outputState(13) + "><span class=\"slider\"></span></label></td></tr>";
return buttons;
}
*/
if (var == "CLIENT_DELAY") {
return CLIENT_DELAY;
}
return String();
}
void setup() {
Serial.begin(115200);
//Serial.println(F("Hello, ESP32!"));
display.init();
display.flipScreenVertically();
//display.setFont(DejaVu_LGC_Sans_12);
display.drawString(0, 0, F("Starting .."));
display.display();
pinMode(32, OUTPUT);
digitalWrite(32, LOW);
pinMode(33, OUTPUT);
digitalWrite(33, LOW);
pinMode(12, OUTPUT);
digitalWrite(12, LOW);
pinMode(13, OUTPUT);
digitalWrite(13, LOW);
//WiFi.begin(ssid, password);
WiFi.begin("Wokwi-GUEST", "", 6);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.println("Connecting to WiFi..");
}
// Print ESP Local IP Address
display.clear();
display.setFont(ArialMT_Plain_10);
display.setTextAlignment(TEXT_ALIGN_RIGHT);
display.drawString(128, 44, "CLlocalIP " + WiFi.localIP().toString());
//display.drawString(128, 44, "APlocalIP " + WiFi.softAPIP().toString());
//display.drawString(128, 44, "Connected!");
display.display();
Serial.println(" localIP " + WiFi.localIP().toString());
//Serial.println("localIP " + WiFi.softAPIP().toString());
// Route for root / web page
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
request->send_P(200, "text/html", index_html, processor);
});
server.on("/chart", HTTP_GET, [] (AsyncWebServerRequest *request) {
int start_index = 0;
if (request->hasParam("ts")) {
start_index = request->getParam("ts")->value().toInt();
}
String json = "{";
json += "\"values\": "+getValuesList(start_index)+",";
json += "\"status\": \""+theState+"\",";
json += "\"ts\": \""+String(values.size())+"\"";
json += "}";
request->send(200, "application/json", json);
});
// Send a GET request to <ESP_IP>/update?output=<inputMessage1>&state=<inputMessage2>
server.on("/update", HTTP_GET, [] (AsyncWebServerRequest *request) {
String inputMessage1;
String inputMessage2;
// GET input1 value on <ESP_IP>/update?output=<inputMessage1>&state=<inputMessage2>
//Serial.println(request->hasParam(PARAM_INPUT_1));
//Serial.println(request->hasParam(PARAM_INPUT_2));
if (request->hasParam(PARAM_INPUT_1) && request->hasParam(PARAM_INPUT_2)) {
inputMessage1 = request->getParam(PARAM_INPUT_1)->value();
inputMessage2 = request->getParam(PARAM_INPUT_2)->value();
digitalWrite(inputMessage1.toInt(), inputMessage2.toInt());
}
else {
inputMessage1 = "No message sent";
inputMessage2 = "No message sent";
}
Serial.print("GPIO: ");
Serial.print(inputMessage1);
Serial.print(" - Set to: ");
Serial.println(inputMessage2);
//Serial.println(WiFi.localIP());
//Serial.println(WiFi.softAPIP());
request->send(200, "text/plain", "OK");
});
server.on("/check", HTTP_GET, [] (AsyncWebServerRequest *request) {
request->send(200, "text/plain", theState);
});
// Start server
server.begin();
// Configurazione del WebSocket
//webSocket.begin();
//webSocket.onEvent(webSocketEvent);
}
/*void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length) {
Serial.println(type);
switch(type) {
case WStype_DISCONNECTED:
Serial.printf("[%u] Disconnected!\n", num);
break;
case WStype_CONNECTED:
{
IPAddress ip = webSocket.remoteIP(num);
Serial.printf("[%u] Connected from %d.%d.%d.%d url: %s\n", num, ip[0], ip[1], ip[2], ip[3], payload);
}
break;
case WStype_TEXT:
Serial.printf("[%u] get Text: %s\n", num, payload);
// Esegui le azioni necessarie quando viene ricevuto un messaggio di testo
break;
}
}*/
void loop() {
if (Serial.available() > 0) {
// Read serial port message
String inputString = Serial.readStringUntil('\n');
//split serial port message
char delim = ',';
std::vector<String> tokens = split(inputString, delim);
//check if the first token (the state) is a valid state
if (isValidState(tokens[0])) {
Serial.println("State from serial: "+tokens[0]);
theState = tokens[0];
if (theState == "START") {
//enqueue the value for the chart ...
enqueue(tokens[1].toFloat());
/*
*
webSocket.broadcastTXT(tokens[1]); //send websocket broadcast message
*
*/
}
if (theState == "STOP") {
x = 1;
//reset x to initial value and clear the values queue
values.clear();
}
}
}
else {
//Dummy data generation
float y = log(x);
if (theState == "START") {
enqueue(y);
x = x + 1;
}
}
delay(SERIAL_DELAY);
}