Giter Site home page Giter Site logo

https---github.com-1achy-simhub-esp32---sunton-screen's Introduction

SimHub firmware for ESP32 sunton display

Alt text Alt text

It is original project:

https://github.com/eCrowneEng/ESP-SimHub

https://github.com/MoemenMostafa/simhub-esp-dashboard

I some Adjust for ESP32-2432S028:

https://macsbug.wordpress.com/2022/08/17/esp32-2432s028/

Display setting to SHCustomProtocol.h

..........

// selezione la configurazione nella cartella lgfx_user

// #include <LGFX_AUTODETECT.hpp> // Preparare la classe "LGFX"

// #include <lgfx_user/LGFX_ESP8266_sample.hpp>

#include <lgfx_user/LGFX_ESP32_esp32-2432s028.hpp>

#include <Arduino.h>

// #include <TFT_eSPI.h> // Hardware-specific library

// #include <SPI.h>

#include

......

SimHub firmware for ESP32 and ESP8266

Head over to the discord server to discuss this firmware: https://discord.gg/zw377qhj9c

What is this?

Firmware source code for ESP32 and ESP8266 that is compatible with SimHub. It's derived from what SimHub lets you setup, but tweaked with a thin compatibility layer for the ESP32 and ESP8266.

Why would you want to do this?

ESP8266 is cheaper and more powerful that most arduinos boards and fairly popular in the IoT Community. The ESP32 is a very handy little monster to use too.

How To

  • Download this code
  • Install Git
  • Install VSCode and Platformio
  • Open with VSCode
  • Adjust it however you need
  • Upload it to your device

Why Platformio?

It's better in almost every way than Arduino IDE, but more generic and more strict. It's worth learning it, trust me.

How does the SimHub WiFi connection works?

SimHub assumes there is a serial port that it can talk to, and a certain protocol that accounts for errors Automatic Repeat Request.

Instead of having a local device connected to the computer directly, we create a "virual com port" that forwards all communication to a certain IP.

On the Microcontroller side, we modify the code to create a socket server receiving data on the right port, we put that data in a buffer (similar to the serial port) and we allow the modified Arduino SimHub client to consume it from the buffer, as if it was a normal seial connection.

How to configure the firmware to use WiFi

Set the preprocessor directive called INCLUDE_WIFI to true (in src/main.cpp). Set DEBUG_TCP_BRIDGE to true as well, and set monitor_speed = 115200 in platformio.ini.

How to connect the ESP to your WiFi

Upload a WiFi enabled firmware. Connect USB to computer and open the serial monitor with 115200 baud rate (to catch debug messages). Power the ESP up.

(If you hardcoded WiFi credentials already, skip this paragraph) It will itself create a new WiFi network (ESP-{a bunch of letters and numbers}) that you can connect to from your phone or computer. Connect to that network and navigate to http://192.168.4.1, observe a captive portal. Configure your WiFi and then disconnect.

Once the ESP is connected to a network, it won't create its own. The device should output the IP address it's using, write it down, you'll need it.

If you have access to your router settings, it's worth setting a static ip address or a DHCP reservation to the ESP.. that way you won't have to reconfigure the virtual port every few reconnections.

How to configure the virtual port

Download Perle TruePort. Install it. Configure a new COM port. Forward the data to the IP address of your ESP. Check the manual on the section called "Configuring the COM Port Connection" > "Access Device Server Serial Port" Follow the instructions to set it up in LITE mode. .. and that should be it.

Alt text Alt text

How to configure SimHub to use the virtual port

SimHub should see the virtual port as a normal COM port, and it should be able to "scan it" and do the normal handshake process to query and use it. After confirming everything works, you can upload a new ESP firmware without debug logging.

How to configure SimHub to use custom protocol

SimHub should config use custom protocol:

  1. go in menu Alt text

  2. copy text :https://github.com/1achy/https---github.com-1achy-SIMHUB-ESP32---SUNTON-screen/blob/main/istruzioni%20simhub%20protocol%20custom/customProtocol-dashBoard.txt

  3. paste

P.S.: More info : https://github.com/SHWotever/SimHub/wiki/Custom-Arduino-hardware-support

ESP32 Support Caveats

Some Arduino APIs for the ESP32 are different, so not everything will work out of the box, but I got the WiFi bridge working and the ShakeIt fans with some effort, as you need to use different includes etc. It's very likely that other features that I haven't tested will need tweaks. But if you're willing to experiment with this, feel free to do so. Checkout this PR eCrowneEng/ESP-SimHub#1 as an example of the things you need to do to achieve compatibility.

How to enable ESP32

  • Open platformio.ini, comment out the ESP8266 env and Uncomment the ESP32 env. Also specify your board in the env, by default is set to something like esp32doit-devkit-v1 because I owned this board, but there are tons of boards for the ESP32, as you can see here. Click on one and it will show you what's its id.
  • Make sure to use the correct pin numbers for the ESP32 as opposed to aliases such as D1, D2, D3.. etc

https---github.com-1achy-simhub-esp32---sunton-screen's People

Contributors

1achy avatar

Stargazers

Marcos avatar

Watchers

 avatar Marcos avatar

https---github.com-1achy-simhub-esp32---sunton-screen's Issues

simhub non riconosce come arduino

Se provo a connetterlo come arduino simhub non lo collega e lo schermo resta bloccato in un loop in cui si continua a riavviare. Se lo collego come dispositivo seriale generico resta acceso senza problemi, si collega, ma non riceve le informazioni dal gioco

improve touch button BLE

#ifndef SHCUSTOMPROTOCOL_H
#define SHCUSTOMPROTOCOL_H
#define LGFX_USE_V1
#include <LovyanGFX.hpp>
// selezione la configurazione nella cartella lgfx_user
// #include <LGFX_AUTODETECT.hpp> // Preparare la classe "LGFX"
// #include <lgfx_user/LGFX_ESP8266_sample.hpp>
#include <lgfx_user/LGFX_ESP32_esp32-2432s028.hpp>

#include <Arduino.h>
#include
#include <BleGamepad.h> // libreria bluetooh

static LGFX tft;

static const int SCREEN_WIDTH = 320;
static const int SCREEN_HEIGHT = 240;
static const int X_CENTER = SCREEN_WIDTH / 2;
static const int Y_CENTER = SCREEN_HEIGHT / 2;
static const int ROWS = 5;
static const int COLS = 5;
static const int CELL_WIDTH = SCREEN_WIDTH / COLS;
static const int HALF_CELL_WIDTH = CELL_WIDTH / 2;
static const int CELL_HIGHT = SCREEN_HEIGHT / ROWS;
static const int HALF_CELL_HIGHT = CELL_HIGHT / 2;
static const int COL[] = {0, CELL_WIDTH, CELL_WIDTH * 2, CELL_WIDTH * 3, CELL_WIDTH * 4, CELL_WIDTH * 6, CELL_WIDTH * 7};
static const int ROW[] = {0, CELL_HIGHT, CELL_HIGHT * 2, CELL_HIGHT * 3, CELL_HIGHT * 4, CELL_HIGHT * 6, CELL_HIGHT * 7};

std::map<String, String> prevData;
std::map<String, int32_t> prevColor;

// configuro ble per gamepad

BleGamepad bleGamepad("ESP32 Touch Gamepad", "YourCompany", 100);
BleGamepadConfiguration bleGamepadConfig;

// configuro ble per gamepad

class SHCustomProtocol
{
private:
// Global variables
int rpmPercent = 50;
int prev_rpmPercent = 50;
int rpmRedLineSetting = 90;
String gear = "N";
String prev_gear;
String speed = "0";
String currentLapTime = "00:00.00";
String lastLapTime = "00:00.00";
String bestLapTime = "00:00.00";
String sessionBestLiveDeltaSeconds = "0.000";
String sessionBestLiveDeltaProgressSeconds = "0.00";
String tyrePressureFrontLeft = "00.0";
String tyrePressureFrontRight = "00.0";
String tyrePressureRearLeft = "00.0";
String tyrePressureRearRight = "00.0";
String tcLevel = "0";
String tcActive = "0";
String absLevel = "0";
String absActive = "0";
String isTCCutNull = "True";
String tcTcCut = "0 0";
String brakeBias = "0";
String brake = "0";
String lapInvalidated = "False";
uint16_t touchX, touchY; // definisce i due interi per gestione touchscreen

public:
/*
CUSTOM PROTOCOL CLASS
SEE https://github.com/zegreatclan/SimHub/wiki/Custom-Arduino-hardware-support

GENERAL RULES :
	- ALWAYS BACKUP THIS FILE, reinstalling/updating SimHub would overwrite it with the default version.
	- Read data AS FAST AS POSSIBLE in the read function
	- NEVER block the arduino (using delay for instance)
	- Make sure the data read in "read()" function READS ALL THE DATA from the serial port matching the custom protocol definition
	- Idle function is called hundreds of times per second, never use it for slow code, arduino performances would fall
	- If you use library suspending interrupts make sure to use it only in the "read" function when ALL data has been read from the serial port.
		It is the only interrupt safe place

COMMON FUNCTIONS :
	- FlowSerialReadStringUntil('\n')
		Read the incoming data up to the end (\n) won't be included
	- FlowSerialReadStringUntil(';')
		Read the incoming data up to the separator (;) separator won't be included
	- FlowSerialDebugPrintLn(string)
		Send a debug message to simhub which will display in the log panel and log file (only use it when debugging, it would slow down arduino in run conditions)

*/

// Called when starting the arduino (setup method in main sketch)
void setup()
{
 //   Serial.begin(115200);   //x debug
 //   Serial.println("Test seriale avviato!"); //x debug
	
	
	tft.init();
	tft.setRotation(1);
	tft.fillScreen(TFT_BLACK);

//Questa parte di 10 righe , dopo il commento ,del codice configura il comportamento del tuo gamepad Bluetooth emulato tramite l'ESP32. Ecco una spiegazione dettagliata di ogni riga:

//Dettagli delle Funzioni
//bleGamepadConfig.setAutoReport(false);
//Questa funzione disabilita la segnalazione automatica dello stato del gamepad.
//Significa che lo stato del gamepad non verrà inviato automaticamente ai dispositivi collegati. Dovrai chiamare manualmente bleGamepad.sendReport() per inviare lo stato del gamepad quando necessario.
//bleGamepadConfig.setAxesMax(32760);

//Questa funzione imposta il valore massimo per gli assi (X, Y, Z, ecc.) del gamepad.
//Anche se non stai usando gli assi nel tuo progetto, è buona pratica impostare un valore massimo sensato.
//bleGamepadConfig.setIncludeSlider1(false);

//Questa funzione specifica che il gamepad non includerà un asse slider1.
//Poiché stai utilizzando solo pulsanti touch, non hai bisogno di assi slider.
//bleGamepadConfig.setIncludeXAxis(false);

//Questa funzione specifica che il gamepad non includerà l'asse X.
//Non stai usando assi nel tuo progetto, quindi li escludi tutti.
//bleGamepadConfig.setIncludeYAxis(false);

//Questa funzione specifica che il gamepad non includerà l'asse Y.
//bleGamepadConfig.setIncludeZAxis(false);

//Questa funzione specifica che il gamepad non includerà l'asse Z.
//bleGamepadConfig.setIncludeRxAxis(false);

//Questa funzione specifica che il gamepad non includerà l'asse Rx (asse di rotazione X).
//bleGamepadConfig.setIncludeRyAxis(false);

//Questa funzione specifica che il gamepad non includerà l'asse Ry (asse di rotazione Y).
//bleGamepadConfig.setIncludeRzAxis(false);

//Questa funzione specifica che il gamepad non includerà l'asse Rz (asse di rotazione Z).
//bleGamepadConfig.setButtonCount(6);

//Questa funzione imposta il numero di pulsanti sul gamepad a 6.
//Questo corrisponde ai 6 pulsanti touch che vuoi configurare sul tuo display.
//bleGamepad.begin(&bleGamepadConfig);

//Questa funzione inizializza il gamepad Bluetooth con la configurazione specificata.
//Dopo questa chiamata, il gamepad è pronto per essere utilizzato e inviato ai dispositivi collegati.

    bleGamepadConfig.setAutoReport(false);
    bleGamepadConfig.setAxesMax(32760);
    bleGamepadConfig.setIncludeSlider1(false);
    bleGamepadConfig.setIncludeXAxis(false);
    bleGamepadConfig.setIncludeYAxis(false);
    bleGamepadConfig.setIncludeZAxis(false);
    bleGamepadConfig.setIncludeRxAxis(false);
    bleGamepadConfig.setIncludeRyAxis(false);
    bleGamepadConfig.setIncludeRzAxis(false);
    bleGamepadConfig.setButtonCount(6);  // 6 touch buttons
    bleGamepad.begin(&bleGamepadConfig);




}




// Called when new data is coming from computer
void read()
{
	String full = "";

	speed = FlowSerialReadStringUntil(';').toInt();
	gear = FlowSerialReadStringUntil(';');
	rpmPercent = FlowSerialReadStringUntil(';').toInt();
	rpmRedLineSetting = FlowSerialReadStringUntil(';').toInt();
	currentLapTime = FlowSerialReadStringUntil(';');
	lastLapTime = FlowSerialReadStringUntil(';');
	bestLapTime = FlowSerialReadStringUntil(';');
	sessionBestLiveDeltaSeconds = FlowSerialReadStringUntil(';');
	sessionBestLiveDeltaProgressSeconds = FlowSerialReadStringUntil(';');
	tyrePressureFrontLeft  = FlowSerialReadStringUntil(';');
	tyrePressureFrontRight  = FlowSerialReadStringUntil(';');
	tyrePressureRearLeft  = FlowSerialReadStringUntil(';');
	tyrePressureRearRight  = FlowSerialReadStringUntil(';');
	tcLevel  = FlowSerialReadStringUntil(';');
	tcActive  = FlowSerialReadStringUntil(';');
	absLevel  = FlowSerialReadStringUntil(';');
	absActive  = FlowSerialReadStringUntil(';');
	isTCCutNull  = FlowSerialReadStringUntil(';');
	tcTcCut  = FlowSerialReadStringUntil(';');
	brakeBias  = FlowSerialReadStringUntil(';');
	brake  = FlowSerialReadStringUntil(';');
	lapInvalidated  = FlowSerialReadStringUntil(';');

	const String rest = FlowSerialReadStringUntil('\n');
}

// Called once per arduino loop, timing can't be predicted,
// but it's called between each command sent to the arduino
void loop()
{
    readTouch(); // Leggi il touchscreen dello schermo
	
	drawRpmMeter(0, 0, SCREEN_WIDTH, HALF_CELL_HIGHT);
	drawGear(COL[2], COL[1]);
			
	// First+Second Column (Lap times)
	drawCell(COL[0], ROW[1], bestLapTime, "bestLapTime", "Best Lap", "left");
	drawCell(COL[0], ROW[2], lastLapTime, "lastLapTime", "Last Lap", "left");
	drawCell(COL[0], ROW[3], currentLapTime, "currenLapTime", "Current Lap", "left", lapInvalidated == "True" ? TFT_RED : TFT_WHITE);


	// Third Column (speed)
	drawCell(COL[2], ROW[3], speed, "speed", "Speed");

	// Fourth+Fifth Column (delta)
	drawCell(SCREEN_WIDTH, ROW[1], sessionBestLiveDeltaSeconds, "sessionBestLiveDeltaSeconds", "Delta", "right", sessionBestLiveDeltaSeconds.indexOf('-') >= 0 ? TFT_GREEN : TFT_RED);
	drawCell(SCREEN_WIDTH, ROW[2], sessionBestLiveDeltaProgressSeconds, "sessionBestLiveDeltaProgressSeconds", "Delta P", "right", sessionBestLiveDeltaProgressSeconds.indexOf('-') >= 0 ? TFT_GREEN : TFT_RED);
	

	// (TC, ABS, BB)
	if (isTCCutNull == "False")
		drawCell(COL[0], ROW[4], tcTcCut, "tcTcCut", "TC TC2", "center", TFT_YELLOW);
	else
		drawCell(COL[0], ROW[4], tcLevel, "tcLevel", "TC", "center", TFT_YELLOW);
	drawCell(COL[1], ROW[4], absLevel, "absLevel", "ABS", "center", TFT_BLUE);
	drawCell(COL[2], ROW[4], brakeBias, "brakeBias", "BB", "center", TFT_MAGENTA);

	// (tyre pressure)
	drawCell(COL[3], ROW[3], tyrePressureFrontLeft, "tyrePressureFrontLeft", "FL", "center", TFT_CYAN);
	drawCell(COL[4], ROW[3], tyrePressureFrontRight, "tyrePressureFrontRight", "FR", "center", TFT_CYAN);
	drawCell(COL[3], ROW[4], tyrePressureRearLeft, "tyrePressureRearLeft", "RL", "center", TFT_CYAN);
	drawCell(COL[4], ROW[4], tyrePressureRearRight, "tyrePressureRearRight", "RR", "center", TFT_CYAN);
}

// Called once between each byte read on arduino,
// THIS IS A CRITICAL PATH :
// AVOID ANY TIME CONSUMING ROUTINES !!!
// PREFER READ OR LOOP METHOS AS MUCH AS POSSIBLE
// AVOID ANY INTERRUPTS DISABLE (serial data would be lost!!!)
void idle()
{
}

void drawGear(int32_t x, int32_t y)
{
	// draw gear only when it changes
	if (gear != prev_gear)
	{
		// tft.loadFont("Formula1_Regular_web_072pt7b", SPIFFS);
		tft.setTextColor(TFT_YELLOW, TFT_BLACK);
		tft.setTextSize(8);
		tft.setTextDatum(MC_DATUM);
		tft.setCursor(x + 12, y + HALF_CELL_HIGHT);
		tft.print(gear);
		tft.setTextSize(1);
		tft.setTextDatum(TL_DATUM);

		prev_gear = gear;
	}
}

boolean isDrawGearRpmRedRec()
{
	if (rpmPercent >= rpmRedLineSetting)
	{
		return true;
	}
	return false;
}

void drawRpmMeter(int32_t x, int32_t y, int width, int height)
{
	int meterWidth = (width * rpmPercent) / 100;

	int yPlusOne = y + 1;
	int innerWidth = width - meterWidth - 1;
	int innerHeight = height - 4;

	if (prev_rpmPercent > rpmPercent)
	{
		tft.fillRect(meterWidth, yPlusOne, innerWidth, innerHeight, TFT_BLACK); // clear the part after the current rect width
	}

	if (rpmPercent >= rpmRedLineSetting)
	{
		tft.fillRect(x, yPlusOne, meterWidth - 2, innerHeight, TFT_RED);
	}
	else if (rpmPercent >= rpmRedLineSetting - 5)
	{
		tft.fillRect(x, yPlusOne, meterWidth - 2, innerHeight, TFT_ORANGE);
	}
	else
	{
		tft.fillRect(x, yPlusOne, meterWidth - 2, innerHeight, TFT_GREEN);
	}

	// draw the frame only if it ont there
	if (prev_rpmPercent == 50) tft.drawRect(x, y, width, height-2, TFT_WHITE);
	
	prev_rpmPercent = rpmPercent;
}

void drawCell(int32_t x, int32_t y, String data, String id, String name = "Data", String align = "center", int32_t color = TFT_WHITE, int fontSize = 4)
{
	const static int titleHeight = 19;
	const static int hPadding = 5;
	const static int vPadding = 1;

	tft.setTextColor(color, TFT_BLACK);

	const bool dataChanged =  (prevData[id] != data);
	const bool colorChanged =  (prevColor[id] != color);

	if (dataChanged) {

		if (align == "left")
		{
			
			if (colorChanged) tft.drawRoundRect(x, y, CELL_WIDTH * 2 - 1, CELL_HIGHT - 2, 5, color);		// Rectangle
			if (colorChanged) tft.drawString(name, x + hPadding, y + vPadding, 2);						// Title
			tft.drawString(data, x + hPadding, y + titleHeight, fontSize); // Data
		}
		else if (align == "right")
		{
			if (colorChanged) tft.drawRoundRect(x - (CELL_WIDTH * 2), y, CELL_WIDTH * 2 - 1, CELL_HIGHT - 2, 5, color); // Rectangle
			if (colorChanged) tft.drawRightString(name, x - hPadding, y + vPadding, 2);						// Title
			tft.drawRightString(data, x - hPadding, y + titleHeight, fontSize);	  // Data
		}
		else // "center"
		{
			if (colorChanged) tft.drawRoundRect(x, y, CELL_WIDTH - 2, CELL_HIGHT - 2, 5, color);	 // Rectangle
			if (colorChanged) tft.drawCentreString(name, x + HALF_CELL_WIDTH, y + vPadding, 2);			 // Title
			tft.drawCentreString(data, x + HALF_CELL_WIDTH, y + titleHeight, fontSize); // Data
		}

		// Clean the previous speed if it was wider
		if (prevData[id].length() > data.length())
		{
			tft.setTextColor(TFT_BLACK, TFT_BLACK);
			if (align == "left")
			{
				tft.drawString(prevData[id], x + hPadding, y + titleHeight, fontSize);
			}
			else if (align == "right")
			{
				tft.drawRightString(prevData[id], x - hPadding, y + titleHeight, fontSize);
			}
			else
			{
				tft.drawCentreString(prevData[id], x + HALF_CELL_WIDTH, y + titleHeight, fontSize);
			}
		}

		prevData[id] = data;
		prevColor[id] = color;
	}

}

void readTouch()
{
    if (tft.getTouch(&touchX, &touchY))
	        

    {
        tft.fillCircle(touchX, touchY, 5, TFT_RED); // Visualizza le coordinate del tocco PER DEBUG	
		  Serial.print("Tocco rilevato alle coordinate: "); //x debug
        Serial.print(touchX); //x debug
        Serial.print(", "); //x debug
        Serial.println(touchY); //x debug

// Questa sezione del codice gestisce il rilevamento del tocco sullo schermo e mappa le coordinate del tocco a un pulsante
// virtuale, inviando quindi un comando tramite Bluetooth utilizzando il BleGamepad. Ecco una spiegazione dettagliata di ciò
// che fa ogni parte del codice:

        int buttonIndex = getTouchButtonIndex(touchX, touchY);

        //  Serial.print("Indice pulsante: ");  //x debug
        //  Serial.println(buttonIndex);       //x debug

        if (buttonIndex != -1)
        {
            bleGamepad.press(buttonIndex + 1);

		//	Serial.print("Pulsante ");          //x debug
     	//	Serial.print(buttonIndex + 1);       //x debug
        //  Serial.println("premuto");   //x debug

            delay(100); // debounce delay
            bleGamepad.release(buttonIndex + 1);
        //  Serial.print("Pulsante ");          //x debug
        //  Serial.print(buttonIndex + 1);     //x debug
        //  Serial.println(" rilasciato.");   //x debug
        }		
    }
}

// La funzione getTouchButtonIndex calcola l'indice di un pulsante touch in base alle coordinate di tocco x e y sullo schermo.
// In pratica, divide lo schermo in una griglia di pulsanti, quindi determina quale pulsante è stato toccato.

int getTouchButtonIndex(int x, int y)
{
    int buttonWidth = SCREEN_WIDTH / 3;
    int buttonHeight = SCREEN_HEIGHT / 2;
    int col = x / buttonWidth;
    int row = y / buttonHeight;
    return col + row * 3; // Restituisce l'indice del pulsante (0 a 5)
}

};

#endif

new idea custom button strem dedk second page

tre passaggi principali:

Aggiungere una pagina web per la personalizzazione: Creare una pagina web che permetta di configurare il numero, i colori e le icone dei pulsanti.
Gestire il caricamento delle immagini: Implementare una funzionalità per caricare le immagini tramite la pagina web e salvarle su SPIFFS.
Modificare il codice per disegnare i pulsanti personalizzati: Aggiornare il codice per utilizzare le configurazioni salvate.

  1. Creazione della pagina web per la personalizzazione
    Aggiungi il codice HTML per la personalizzazione e il caricamento delle immagini:

cpp
Copia codice
#include <WiFi.h>
#include <WebServer.h>
#include <FS.h>
#include <SPIFFS.h>

// Configurazione Wi-Fi
const char* ssid = "your_SSID";
const char* password = "your_PASSWORD";

WebServer server(80);

String htmlPage = R"rawliteral(

<title>ESP32 Button Configuration</title>

Configure Buttons

Number of Buttons:

Button Colors

Button 1:
Button 2:
Button 3:
Button 4:
Button 5:
Button 6:

Button Icons

Button 1:
Button 2:
Button 3:
Button 4:
Button 5:
Button 6:
)rawliteral";

// Funzione per gestire la richiesta della pagina principale
void handleRoot() {
server.send(200, "text/html", htmlPage);
}

// Funzione per salvare la configurazione
void handleSave() {
if (server.hasArg("numButtons")) {
numButtons = server.arg("numButtons").toInt();
// Salva il numero di pulsanti in un file o in una variabile
}
for (int i = 1; i <= 6; i++) {
if (server.hasArg("color" + String(i))) {
String color = server.arg("color" + String(i));
// Salva il colore del pulsante in un file o in una variabile
}
if (server.hasArg("icon" + String(i))) {
HTTPUpload& upload = server.upload();
if (upload.status == UPLOAD_FILE_START) {
// Salva l'icona del pulsante in SPIFFS
File file = SPIFFS.open("/icon" + String(i) + ".bmp", FILE_WRITE);
if (file) {
file.write(upload.buf, upload.currentSize);
file.close();
}
}
}
}
server.send(200, "text/html", "

Configuration Saved!

Go Back");
}

void setup() {
Serial.begin(115200);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.println("Connecting to WiFi...");
}
Serial.println("Connected to WiFi");

if (!SPIFFS.begin(true)) {
Serial.println("An error has occurred while mounting SPIFFS");
return;
}

server.on("/", handleRoot);
server.on("/save", HTTP_POST, handleSave);
server.begin();
}

void loop() {
server.handleClient();
}
2. Modifica del codice per gestire la personalizzazione
Aggiungi la logica per leggere i dati salvati e aggiornare l'aspetto dei pulsanti:

cpp
Copia codice
void drawColoredButton(int x, int y, int width, int height, String label, uint16_t color, String iconPath = "") {
// Draw the dark background for the button
tft.fillRoundRect(x, y, width, height, 10, TFT_BLACK);

// Draw the top and bottom color edges
tft.fillRoundRect(x + 3, y + 14, width - 6, height / 5, 10, color); // Top edge
tft.fillRoundRect(x + 3, y + height - height / 5 - 14, width - 6, height / 5, 10, color); // Bottom edge

// Draw the inner color
tft.fillRoundRect(x + 3, y + height / 5 + 3, width - 6, height * 3 / 5 - 6, 10, TFT_PURPLE);

if (iconPath != "") {
    // Carica e disegna l'icona
    File iconFile = SPIFFS.open(iconPath, FILE_READ);
    if (iconFile) {
        // Inserisci il codice per caricare e disegnare l'immagine BMP
        // tft.drawBitmap(x + width / 4, y + height / 4, iconPath, width / 2, height / 2, TFT_WHITE);
        iconFile.close();
    }
} else {
    // Draw the label text in white
    tft.setTextColor(TFT_WHITE, TFT_PURPLE);
    tft.setTextDatum(MC_DATUM);
    tft.drawString(label, x + width / 2, y + height / 2 - 4); // Slight adjustment for vertical alignment
    tft.setTextDatum(TL_DATUM);
}

}

void drawPage2() {
int buttonWidth = (SCREEN_WIDTH / 3) - 4; // Slightly reduced width for space between buttons
int buttonHeight = (SCREEN_HEIGHT / 2) - 4; // Slightly reduced height for space between buttons

String buttonLabels[] = {"In-game", "Offline", "ABS", "Pit", "Streaming", "TC"}; // Replace with desired labels
uint16_t buttonColors[6]; // Array to store button colors

// Load the button colors from saved data
for (int i = 0; i < numButtons; i++) {
    buttonColors[i] = TFT_WHITE; // Default color
    // Leggi i colori dei pulsanti salvati
    // buttonColors[i] = ...
}

for (int i = 0; i < numButtons; i++) {
    int x = (i % 3) * (buttonWidth + 4) + 2; // Add space between buttons
    int y = (i / 3) * (buttonHeight + 4) + 2; // Add space between buttons
    drawColoredButton(x, y, buttonWidth, buttonHeight, buttonLabels[i], buttonColors[i], "/icon" + String(i + 1) + ".bmp");
}

}
3. Gestione dei file SPIFFS per le immagini
Aggiungi il codice per gestire SPIFFS, leggere e scrivere i file di configurazione:

cpp
Copia codice
bool loadConfig() {
File configFile = SPIFFS.open("/config.json", "r");
if (!configFile) {
Serial.println("Failed to open config file");
return false;
}

size_t size = configFile.size();
std::unique_ptr<char[]> buf(new char[size]);
configFile.readBytes(buf.get(), size);

DynamicJsonDocument doc(1024);
auto error = deserializeJson(doc, buf.get());
if (error) {
    Serial.println("Failed to parse config file");
    return false;
}

numButtons = doc["numButtons"];
for (int i = 0; i < numButtons; i++) {
    buttonColors[i] = strtol(doc["color" + String(i + 1)].as<String>().c_str(), NULL, 16);
}

return true;

}

bool saveConfig() {
DynamicJsonDocument doc(1024);
doc["numButtons"] = numButtons;
for (int i = 0; i < numButtons; i++) {
doc["color" + String(i + 1)] = String(buttonColors[i], HEX);
}

File configFile = SPIFFS.open("/config.json", "w");
if (!configFile) {
    Serial.println("Failed to open config file for writing");
    return false;
}

serializeJson(doc, configFile);
return true;

}
Con queste modifiche, l'ESP32 sarà in grado di fornire una pagina web per la personalizzazione dei pulsanti, gestire il caricamento delle immagini e disegnare i pulsanti personalizzati sul display. Puoi estendere ulteriormente questa configurazione per includere ulteriori opzioni di personalizzazione secondo le tue esigenze.

come si integra nel codice originale ? potemmo crare un secondo file dove confiura la pagina web? mi aggiorni i codici completi?
ChatGPT
Certo! Per integrare questa funzionalità nel tuo codice originale, possiamo creare un secondo file per la configurazione della pagina web e la gestione del server. Qui di seguito trovi il codice suddiviso in due file:

File principale (SHCustomProtocol.h)
File di configurazione web (WebConfig.h)

  1. File principale: SHCustomProtocol.h
    cpp
    Copia codice
    #ifndef SHCUSTOMPROTOCOL_H
    #define SHCUSTOMPROTOCOL_H
    #define LGFX_USE_V1
    #include <LovyanGFX.hpp>
    // selezione la configurazione nella cartella lgfx_user
    // #include <LGFX_AUTODETECT.hpp> // Preparare la classe "LGFX"
    // #include <lgfx_user/LGFX_ESP8266_sample.hpp>
    #include <lgfx_user/LGFX_ESP32_esp32-2432s028.hpp>
    // dashboard + free deck grafica
    #include <Arduino.h>
    #include
    #include <BleGamepad.h> // libreria bluetooth

#include "WebConfig.h" // Include the web configuration file

static LGFX tft;

static const int SCREEN_WIDTH = 320;
static const int SCREEN_HEIGHT = 240;
static const int X_CENTER = SCREEN_WIDTH / 2;
static const int Y_CENTER = SCREEN_HEIGHT / 2;
static const int ROWS = 5;
static const int COLS = 5;
static const int CELL_WIDTH = SCREEN_WIDTH / COLS;
static const int HALF_CELL_WIDTH = CELL_WIDTH / 2;
static const int CELL_HEIGHT = SCREEN_HEIGHT / ROWS;
static const int HALF_CELL_HEIGHT = CELL_HEIGHT / 2;
static const int COL[] = {0, CELL_WIDTH, CELL_WIDTH * 2, CELL_WIDTH * 3, CELL_WIDTH * 4, CELL_WIDTH * 6, CELL_WIDTH * 7};
static const int ROW[] = {0, CELL_HEIGHT, CELL_HEIGHT * 2, CELL_HEIGHT * 3, CELL_HEIGHT * 4, CELL_HEIGHT * 6, CELL_HEIGHT * 7};

std::map<String, String> prevData;
std::map<String, int32_t> prevColor;

// configuro ble per gamepad

BleGamepad bleGamepad("ESP32 Touch Gamepad", "YourCompany", 100);
BleGamepadConfiguration bleGamepadConfig;

int currentPage = 1; // Variabile per tenere traccia della pagina corrente
bool forceUpdate = false; // Variabile per forzare l'aggiornamento delle celle
bool page2Drawn = false; // Variabile per tracciare se la pagina 2 è stata disegnata

int numButtons = 6; // Variabile per il numero di pulsanti
uint16_t buttonColors[6] = {TFT_RED, TFT_GREEN, TFT_BLUE, TFT_YELLOW, TFT_ORANGE, TFT_WHITE}; // Array per i colori dei pulsanti

class SHCustomProtocol
{
private:
// Global variables
int rpmPercent = 50;
int prev_rpmPercent = 50;
int rpmRedLineSetting = 90;
String gear = "N";
String prev_gear;
String speed = "0";
String currentLapTime = "00:00.00";
String lastLapTime = "00:00.00";
String bestLapTime = "00.00.00";
String sessionBestLiveDeltaSeconds = "0.000";
String sessionBestLiveDeltaProgressSeconds = "0.00";
String tyrePressureFrontLeft = "00.0";
String tyrePressureFrontRight = "00.0";
String tyrePressureRearLeft = "00.0";
String tyrePressureRearRight = "00.0";
String tcLevel = "0";
String tcActive = "0";
String absLevel = "0";
String absActive = "0";
String isTCCutNull = "True";
String tcTcCut = "0 0";
String brakeBias = "0";
String brake = "0";
String lapInvalidated = "False";
uint16_t touchX, touchY; // definisce i due interi per gestione touchscreen

public:
/*
CUSTOM PROTOCOL CLASS
SEE https://github.com/zegreatclan/SimHub/wiki/Custom-Arduino-hardware-support

GENERAL RULES :
    - ALWAYS BACKUP THIS FILE, reinstalling/updating SimHub would overwrite it with the default version.
    - Read data AS FAST AS POSSIBLE in the read function
    - NEVER block the arduino (using delay for instance)
    - Make sure the data read in "read()" function READS ALL THE DATA from the serial port matching the custom protocol definition
    - Idle function is called hundreds of times per second, never use it for slow code, arduino performances would fall
    - If you use library suspending interrupts make sure to use it only in the "read" function when ALL data has been read from the serial port.
        It is the only interrupt safe place

COMMON FUNCTIONS :
    - FlowSerialReadStringUntil('\n')
        Read the incoming data up to the end (\n) won't be included
    - FlowSerialReadStringUntil(';')
        Read the incoming data up to the separator (;) separator won't be included
    - FlowSerialDebugPrintLn(string)
        Send a debug message to simhub which will display in the log panel and log file (only use it when debugging, it would slow down arduino in run conditions)

*/

// Called when starting the arduino (setup method in main sketch)
void setup()
{
//    Serial.begin(115200);   //x debug
//    Serial.println("Test seriale avviato!"); //x debug
    
    tft.init();
    tft.setRotation(1);
    tft.fillScreen(TFT_BLACK);

    bleGamepadConfig.setAutoReport(true);  // in false non invia i comandi a windows
    bleGamepadConfig.setAxesMax(32760);
    bleGamepadConfig.setIncludeSlider1(false);
    bleGamepadConfig.setIncludeXAxis(false);
    bleGamepadConfig.setIncludeYAxis(false);
    bleGamepadConfig.setIncludeZAxis(false);
    bleGamepadConfig.setIncludeRxAxis(false);
    bleGamepadConfig.setIncludeRyAxis(false);
    bleGamepadConfig.setIncludeRzAxis(false);
    bleGamepadConfig.setButtonCount(numButtons);  // Variabile per il numero di pulsanti
    bleGamepad.begin(&bleGamepadConfig);
    
    // Carica la configurazione
    loadConfig();
    
    // Avvia il server web
    startWebConfig();
}

// Called when new data is coming from computer
void read()
{
    String full = "";

    speed = FlowSerialReadStringUntil(';').toInt();
    gear = FlowSerialReadStringUntil(';');
    rpmPercent = FlowSerialReadStringUntil(';').toInt();
    rpmRedLineSetting = FlowSerialReadStringUntil(';').toInt();
    currentLapTime = FlowSerialReadStringUntil(';');
    lastLapTime = FlowSerialReadStringUntil(';');
    bestLapTime = FlowSerialReadStringUntil(';');
    sessionBestLiveDeltaSeconds = FlowSerialReadStringUntil(';');
    sessionBestLiveDeltaProgressSeconds = FlowSerialReadStringUntil(';');
    tyrePressureFrontLeft  = FlowSerialReadStringUntil(';');
    tyrePressureFrontRight  = FlowSerialReadStringUntil(';');
    tyrePressureRearLeft  = FlowSerialReadStringUntil(';');
    tyrePressureRearRight  = FlowSerialReadStringUntil(';');
    tcLevel  = FlowSerialReadStringUntil(';');
    tcActive  = FlowSerialReadStringUntil(';');
    absLevel  = FlowSerialReadStringUntil(';');
    absActive  = FlowSerialReadStringUntil(';');
    isTCCutNull  = FlowSerialReadStringUntil(';');
    tcTcCut  = FlowSerialReadStringUntil(';');
    brakeBias  = FlowSerialReadStringUntil(';');
    brake  = FlowSerialReadStringUntil(';');
    lapInvalidated  = FlowSerialReadStringUntil(';');

    const String rest = FlowSerialReadStringUntil('\n');
}

// Called once per arduino loop, timing can't be predicted,
// but it's called between each command sent to the arduino
void loop()
{
    readTouch(); // Leggi il touchscreen dello schermo

    static int lastPage = currentPage;
    if (currentPage != lastPage) {
        tft.fillScreen(TFT_BLACK);
        forceUpdate = true;
        lastPage = currentPage;
        if (currentPage == 2) {
            page2Drawn = false; // Reset the state when switching to page 2
        }
    }
    
    if (currentPage == 1) {
        drawPage1(forceUpdate);
    } else {
        if (!page2Drawn) {
            drawPage2();
            page2Drawn = true; // Mark page 2 as drawn
        }
    }

    forceUpdate = false;

    if (tft.getTouch(&touchX, &touchY)) {
        if (touchX < 50 && touchY < 50) {
            // Cambia pagina quando tocchi nell'angolo in alto a sinistra
            currentPage = (currentPage == 1) ? 2 : 1;
        }
        if (currentPage == 2) {
            drawPage2(); // Redraw page 2 on touch
        }
    }

    // Gestisce le richieste del server
    handleClient();
}

void idle() {}

void drawPage1(bool forceUpdate = false) {
    drawRpmMeter(0, 0, SCREEN_WIDTH, HALF_CELL_HEIGHT);
    drawGear(COL[2], COL[1]);
            
    // First+Second Column (Lap times)
    drawCell(COL[0], ROW[1], bestLapTime, "bestLapTime", "Best Lap", "left", TFT_WHITE, 4, forceUpdate);
    drawCell(COL[0], ROW[2], lastLapTime, "lastLapTime", "Last Lap", "left", TFT_WHITE, 4, forceUpdate);
    drawCell(COL[0], ROW[3], currentLapTime, "currenLapTime", "Current Lap", "left", lapInvalidated == "True" ? TFT_RED : TFT_WHITE, 4, forceUpdate);

    // Third Column (speed)
    drawCell(COL[2], ROW[3], speed, "speed", "Speed", "center", TFT_WHITE, 4, forceUpdate);

    // Fourth+Fifth Column (delta)
    drawCell(SCREEN_WIDTH, ROW[1], sessionBestLiveDeltaSeconds, "sessionBestLiveDeltaSeconds", "Delta", "right", sessionBestLiveDeltaSeconds.indexOf('-') >= 0 ? TFT_GREEN : TFT_RED, 4, forceUpdate);
    drawCell(SCREEN_WIDTH, ROW[2], sessionBestLiveDeltaProgressSeconds, "sessionBestLiveDeltaProgressSeconds", "Delta P", "right", sessionBestLiveDeltaProgressSeconds.indexOf('-') >= 0 ? TFT_GREEN : TFT_RED, 4, forceUpdate);

    // (TC, ABS, BB)
    if (isTCCutNull == "False")
        drawCell(COL[0], ROW[4], tcTcCut, "tcTcCut", "TC TC2", "center", TFT_YELLOW, 4, forceUpdate);
    else
        drawCell(COL[0], ROW[4], tcLevel, "tcLevel", "TC", "center", TFT_YELLOW, 4, forceUpdate);
    drawCell(COL[1], ROW[4], absLevel, "absLevel", "ABS", "center", TFT_BLUE, 4, forceUpdate);
    drawCell(COL[2], ROW[4], brakeBias, "brakeBias", "BB", "center", TFT_MAGENTA, 4, forceUpdate);

    // (tyre pressure)
    drawCell(COL[3], ROW[3], tyrePressureFrontLeft, "tyrePressureFrontLeft", "FL", "center", TFT_CYAN, 4, forceUpdate);
    drawCell(COL[4], ROW[3], tyrePressureFrontRight, "tyrePressureFrontRight", "FR", "center", TFT_CYAN, 4, forceUpdate);
    drawCell(COL[3], ROW[4], tyrePressureRearLeft, "tyrePressureRearLeft", "RL", "center", TFT_CYAN, 4, forceUpdate);
    drawCell(COL[4], ROW[4], tyrePressureRearRight, "tyrePressureRearRight", "RR", "center", TFT_CYAN, 4, forceUpdate);
}

void drawColoredButton(int x, int y, int width, int height, String label, uint16_t color, String iconPath = "") {
    // Draw the dark background for the button
    tft.fillRoundRect(x, y, width, height, 10, TFT_BLACK);

    // Draw the top and bottom color edges
    tft.fillRoundRect(x + 3, y + 14, width - 6, height / 5, 10, color); // Top edge
    tft.fillRoundRect(x + 3, y + height - height / 5 - 14, width - 6, height / 5, 10, color); // Bottom edge

    // Draw the inner color
    tft.fillRoundRect(x + 3, y + height / 5 + 3, width - 6, height * 3 / 5 - 6, 10, TFT_PURPLE);

    if (iconPath != "") {
        // Carica e disegna l'icona
        File iconFile = SPIFFS.open(iconPath, FILE_READ);
        if (iconFile) {
            // Inserisci il codice per caricare e disegnare l'immagine BMP
            // tft.drawBitmap(x + width / 4, y + height / 4, iconPath, width / 2, height / 2, TFT_WHITE);
            iconFile.close();
        }
    } else {
        // Draw the label text in white
        tft.setTextColor(TFT_WHITE, TFT_PURPLE);
        tft.setTextDatum(MC_DATUM);
        tft.drawString(label, x + width / 2, y + height / 2 - 4); // Slight adjustment for vertical alignment
        tft.setTextDatum(TL_DATUM);
    }
}

void drawPage2() // pagina pulsanti
{
    int buttonWidth = (SCREEN_WIDTH / 3) - 4; // Riduciamo leggermente la larghezza per spazio tra i pulsanti
    int buttonHeight = (SCREEN_HEIGHT / 2) - 4; // Riduciamo leggermente l'altezza per spazio tra i pulsanti
    String buttonLabels[] = {"In-game", "Offline", "ABS", "Pit", "Streaming", "TC"}; // Sostituisci con le etichette desiderate

    for (int i = 0; i < numButtons; i++) {
        int x = (i % 3) * (buttonWidth + 4) + 2; // Aggiungiamo spazio tra i pulsanti
        int y = (i / 3) * (buttonHeight + 4) + 2; // Aggiungiamo spazio tra i pulsanti
        drawColoredButton(x, y, buttonWidth, buttonHeight, buttonLabels[i], buttonColors[i], "/icon" + String(i + 1) + ".bmp");
    }  
}

void drawGear(int32_t x, int32_t y)
{
    // draw gear only when it changes
    if (gear != prev_gear)
    {
        // tft.loadFont("Formula1_Regular_web_072pt7b", SPIFFS);
        tft.setTextColor(TFT_YELLOW, TFT_BLACK);
        tft.setTextSize(8);
        tft.setTextDatum(MC_DATUM);
        tft.setCursor(x + 12, y + HALF_CELL_HEIGHT);
        tft.print(gear);
        tft.setTextSize(1);
        tft.setTextDatum(TL_DATUM);

        prev_gear = gear;
    }
}

boolean isDrawGearRpmRedRec()
{
    if (rpmPercent >= rpmRedLineSetting)
    {
        return true;
    }
    return false;
}

void drawRpmMeter(int32_t x, int32_t y, int width, int height)
{
    int meterWidth = (width * rpmPercent) / 100;

    int yPlusOne = y + 1;
    int innerWidth = width - meterWidth - 1;
    int innerHeight = height - 4;

    if (prev_rpmPercent > rpmPercent)
    {
        tft.fillRect(meterWidth, yPlusOne, innerWidth, innerHeight, TFT_BLACK); // clear the part after the current rect width
    }

    if (rpmPercent >= rpmRedLineSetting)
    {
        tft.fillRect(x, yPlusOne, meterWidth - 2, innerHeight, TFT_RED);
    }
    else if (rpmPercent >= rpmRedLineSetting - 5)
    {
        tft.fillRect(x, yPlusOne, meterWidth - 2, innerHeight, TFT_ORANGE);
    }
    else
    {
        tft.fillRect(x, yPlusOne, meterWidth - 2, innerHeight, TFT_GREEN);
    }

    // draw the frame only if it ont there
    if (prev_rpmPercent == 50) tft.drawRect(x, y, width, height-2, TFT_WHITE);
    
    prev_rpmPercent = rpmPercent;
}

void drawCell(int32_t x, int32_t y, String data, String id, String name = "Data", String align = "center", int32_t color = TFT_WHITE, int fontSize = 4, bool forceUpdate = false)
{
    const static int titleHeight = 19;
    const static int hPadding = 5;
    const static int vPadding = 1;

    tft.setTextColor(color, TFT_BLACK);

    bool dataChanged =  (prevData[id] != data) || forceUpdate;
    bool colorChanged =  (prevColor[id] != color) || forceUpdate;

    if (dataChanged) {

        if (align == "left")
        {
            
            if (colorChanged) tft.drawRoundRect(x, y, CELL_WIDTH * 2 - 1, CELL_HEIGHT - 2, 5, color);        // Rectangle
            if (colorChanged) tft.drawString(name, x + hPadding, y + vPadding, 2);                        // Title
            tft.drawString(data, x + hPadding, y + titleHeight, fontSize); // Data
        }
        else if (align == "right")
        {
            if (colorChanged) tft.drawRoundRect(x - (CELL_WIDTH * 2), y, CELL_WIDTH * 2 - 1, CELL_HEIGHT - 2, 5, color); // Rectangle
            if (colorChanged) tft.drawRightString(name, x - hPadding, y + vPadding, 2);                        // Title
            tft.drawRightString(data, x - hPadding, y + titleHeight, fontSize);      // Data
        }
        else // "center"
        {
            if (colorChanged) tft.drawRoundRect(x, y, CELL_WIDTH - 2, CELL_HEIGHT - 2, 5, color);     // Rectangle
            if (colorChanged) tft.drawCentreString(name, x + HALF_CELL_WIDTH, y + vPadding, 2);             // Title
            tft.drawCentreString(data, x + HALF_CELL_WIDTH, y + titleHeight, fontSize); // Data
        }

        // Clean the previous speed if it was wider
        if (prevData[id].length() > data.length())
        {
            tft.setTextColor(TFT_BLACK, TFT_BLACK);
            if (align == "left")
            {
                tft.drawString(prevData[id], x + hPadding, y + titleHeight, fontSize);
            }
            else if (align == "right")
            {
                tft.drawRightString(prevData[id], x - hPadding, y + titleHeight, fontSize);
            }
            else
            {
                tft.drawCentreString(prevData[id], x + HALF_CELL_WIDTH, y + titleHeight, fontSize);
            }
        }

        prevData[id] = data;
        prevColor[id] = color;
    }

}

void readTouch()
{
    if (currentPage == 2) {
        if (tft.getTouch(&touchX, &touchY)) {
            int buttonIndex = getTouchButtonIndex(touchX, touchY);

            if (buttonIndex != -1) {
                // Effetto di pressione
                int x = (buttonIndex % 3) * (SCREEN_WIDTH / 3);
                int y = (buttonIndex / 3) * (SCREEN_HEIGHT / 2);
                drawColoredButton(x, y, SCREEN_WIDTH / 3, SCREEN_HEIGHT / 2, "Pressed", TFT_DARKGREY);

                bleGamepad.press(buttonIndex + 1);
                delay(100); // debounce delay
                bleGamepad.release(buttonIndex + 1);

                // Ripristina il pulsante dopo la pressione
                String buttonLabels[] = {"In-game", "Offline", "ABS", "Pit", "Streaming", "TC"};
                drawColoredButton(x, y, SCREEN_WIDTH / 3, SCREEN_HEIGHT / 2, buttonLabels[buttonIndex], buttonColors[buttonIndex], "/icon" + String(buttonIndex + 1) + ".bmp");
            }
        }
    }
}

int getTouchButtonIndex(int x, int y)
{
    int buttonWidth = SCREEN_WIDTH / 3;
    int buttonHeight = SCREEN_HEIGHT / 2;
    int col = x / buttonWidth;
    int row = y / buttonHeight;
    int index = col + row * 3;
    return (index < numButtons) ? index : -1; // Restituisce l'indice del pulsante (0 a numButtons-1)
}

};

#endif
2. File di configurazione web: WebConfig.h
cpp
Copia codice
#ifndef WEBCONFIG_H
#define WEBCONFIG_H

#include <WiFi.h>
#include <WebServer.h>
#include <FS.h>
#include <SPIFFS.h>
#include <ArduinoJson.h>

const char* ssid = "your_SSID";
const char* password = "your_PASSWORD";

WebServer server(80);

String htmlPage = R"rawliteral(

<title>ESP32 Button Configuration</title>

Configure Buttons

Number of Buttons:

Button Colors

Button 1:
Button 2:
Button 3:
Button 4:
Button 5:
Button 6:

Button Icons

Button 1:
Button 2:
Button 3:
Button 4:
Button 5:
Button 6:
)rawliteral";

// Funzione per gestire la richiesta della pagina principale
void handleRoot() {
server.send(200, "text/html", htmlPage);
}

// Funzione per salvare la configurazione
void handleSave() {
if (server.hasArg("numButtons")) {
numButtons = server.arg("numButtons").toInt();
// Salva il numero di pulsanti in un file o in una variabile
}
for (int i = 1; i <= 6; i++) {
if (server.hasArg("color" + String(i))) {
String color = server.arg("color" + String(i));
// Convert the color from HEX to 16-bit integer
buttonColors[i-1] = strtol(color.substring(1).c_str(), NULL, 16);
}
if (server.hasArg("icon" + String(i))) {
HTTPUpload& upload = server.upload();
if (upload.status == UPLOAD_FILE_START) {
// Salva l'icona del pulsante in SPIFFS
File file = SPIFFS.open("/icon" + String(i) + ".bmp", FILE_WRITE);
if (file) {
file.write(upload.buf, upload.currentSize);
file.close();
}
}
}
}
server.send(200, "text/html", "

Configuration Saved!

Go Back");

// Salva la configurazione
saveConfig();
}

// Funzione per caricare la configurazione
bool loadConfig() {
File configFile = SPIFFS.open("/config.json", "r");
if (!configFile) {
Serial.println("Failed to open config file");
return false;
}

size_t size = configFile.size();
std::unique_ptr<char[]> buf(new char[size]);
configFile.readBytes(buf.get(), size);

DynamicJsonDocument doc(1024);
auto error = deserializeJson(doc, buf.get());
if (error) {
Serial.println("Failed to parse config file");
return false;
}

numButtons = doc["numButtons"];
for (int i = 0; i < numButtons; i++) {
buttonColors[i] = strtol(doc["color" + String(i + 1)].as().c_str(), NULL, 16);
}

return true;
}

// Funzione per salvare la configurazione
bool saveConfig() {
DynamicJsonDocument doc(1024);
doc["numButtons"] = numButtons;
for (int i = 0; i < numButtons; i++) {
doc["color" + String(i + 1)] = String(buttonColors[i], HEX);
}

File configFile = SPIFFS.open("/config.json", "w");
if (!configFile) {
Serial.println("Failed to open config file for writing");
return false;
}

serializeJson(doc, configFile);
return true;
}

void startWebConfig() {
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.println("Connecting to WiFi...");
}
Serial.println("Connected to WiFi");

if (!SPIFFS.begin(true)) {
Serial.println("An error has occurred while mounting SPIFFS");
return;
}

server.on("/", handleRoot);
server.on("/save", HTTP_POST, handleSave);
server.begin();
}

void handleClient() {
server.handleClient();
}

#endif
Spiegazione
File SHCustomProtocol.h: Questo è il tuo file principale che contiene la logica del programma. Qui abbiamo incluso il file WebConfig.h per gestire la configurazione web. Nella funzione setup del tuo programma, avviamo il server web e carichiamo la configurazione dei pulsanti. La funzione loop chiama handleClient per gestire le richieste del server.

File WebConfig.h: Questo file contiene tutte le funzionalità per la configurazione web. Abbiamo definito la pagina

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.