Fonometro

Il Fonometro nasce per dare la possibilità al relatore di una conferenza di capire se in fondo alla sala le persone sentono bene la sua voce o meno. Il dispositivo percepisce l'intensità sonora e accende delle luci in base al valore in decibel rilevato.

Open source e documentazione

Questo progetto è open source, chiunque può scaricare i file necessari, ricreare il progetto e contribuire al suo miglioramento. Non ci sono restrizioni di licenza d'uso, ma si invita a citare che è stato realizzato dagli studenti ASIRID.

Tutto il materiale necessario si trova su Gitlab.

circuiteria_scatola_1 circuiteria_scatola_2 circuiteria_scatola_3

Come funziona?

Il dispositivo è costituito da tre luci di colore diverse, che servono ad indicare la percezione uditiva: il rosso indica un’intensità bassa, il giallo un’intensità intermedia e il verde indica un’intensità ottimale. Il microcontrollore, una volta avviato, si connette al wifi e carica una pagina web dalla quale sarà possibile andare a modificare alcuni parametri di funzionamento. Di default il fonometro avrà dei valori di soglia prestabiliti, tuttavia è importante ricordare che tali valori di soglia varieranno da stanza in stanza e da relatore a relatore. Pertanto è possibile andare ad effettuare una calibrazione manuale tramite pulsanti e schermo direttamente sul dispositivo oppure tramite la pagina web. Il fonometro ha tre pulsanti, uno verde di MENU, tramite cui è possibile scorrere tra le varie schermate del menu; e due pulsanti gialli che corrispondono a “più” e “meno”.

Il menu ha 6 schermate: la prima visualizza il valore in decibel che il microfono sta rilevando in tempo reale*, la seconda permette di visualizzare e/o modificare la soglia bassa (ovvero la soglia tra il rosso e il giallo), la terza permette di visualizzare e/o modificare la soglia alta (ovvero la soglia tra giallo e verde), la quarta permette di visualizzare e/o modificare il valore di luminosità dei LED, la quinta visualizza l’IP Address della pagina web di configurazione, la sesta permette di SALVARE la configurazione modificata direttamente dal dispositivo nelle schermate precedenti.

Requisiti Hardware

Topologia:

circuito

Requisiti Software

Per lo sviluppo di questo progetto è stato utilizzato Arduino IDE, in particolare una versione legacy (1.8.X). Questa versione è fondamentale per il caricamento della cartella data all'interno della memoria del microcontrollore.

Librerie utilizzate

Installare le seguenti librerie manualmente seguendo i link forniti di seguito oppure dall’IDE di Arduino tramite il Library Manager. È importante che le librerie siano installate nelle versioni indicate:

N.B.: Per la libreria ElegantOTA è stata utilizzata la modalità asincrona (Async). Per utilizzarla bisogna abiltarla attraverso i seguenti passaggi:

  1. Vai nella directory in cui sono memorizzate le librerie Arduino

  2. Apri la directory ElegantOTA e successivamente src

  3. Individua la costante ELEGANTOTA_USE_ASYNC_WEBSERVER nel file ElegantOTA.h e impostala ad 1:

    #define ELEGANTOTA_USE_ASYNC_WEBSERVER 1

Board utilizzata

Utilizzare la seguente board con la versione specificata; in caso contrario, potrebbero verificarsi problemi di incompatibilità tra le librerie installate e la board.

Strumenti Arduino IDE

Seleziona le seguenti opzioni dal menù a tendina:

  • Scheda: vai su ESP32 Arduino e seleziona ESP32 Dev Module
  • Partition Scheme: seleziona Default 4MB with spiffs (1.2MB APP/1.5MB APP SPIFFS)

Installazione del Tool per Caricare i File all'interno dell'ESP32

  1. Vai alla pagina delle release e clicca sul file ESP32FS-1.0.zip per scaricarlo.
  2. Trova il percorso della cartella degli sketch dalle impostazioni dell'IDE Arduino.
  3. Vai nella cartella degli sketch e crea una cartella tools.
  4. Decomprimi il file zip scaricato. Aprila la cartella decompressa e copia la cartella ESP32FS nella cartella tools creata nel passaggio precedente. la struttura delle cartelle dovrebbe essere simile a questa: <Sketchbook-location>/tools/ESP32FS/tool/esp32fs.jar
  5. Riavvia Arduino IDE.

Per verificare se il plugin è stato installato correttamente, apri Arduino IDE. Seleziona la tua scheda ESP32, vai su Strumenti e verifica la presenza dell'opzione "ESP32 Data Upload".

Caricare la cartella data utilizzando il Tool installato

All'interno della cartella del progetto, troverai una cartella denominata data. Questa cartella contiene i file necessari per il funzionamento del server web, che è ospitato direttamente sul microcontrollore ESP32.

Il contenuto della cartella data viene caricato nel file system del microcontrollore (SPIFFS), permettendo al microcontrollore di gestire autonomamente i file del server web, come pagine HTML, fogli di stile CSS e file JavaScript.

  1. Apri Arduino IDE e seleziona la scheda ESP32 Dev Module.
  2. Dal menù superiore, vai su Strumenti e seleziona ESP32 Data Upload. Questa opzione è disponibile solo se il tool è stato installato correttamente.
  3. Arduino IDE inizierà il processo di caricamento dei file presenti nella cartella data all'interno del file system del microcontrollore.

Il file Fonometro.ino

Il codice come già riportato sopra utilizza diverse librerie per gestire la funzionalità del dispositivo:

  • Wire.h e U8g2lib.h: Per la gestione dell'I2C e del display OLED.
  • ESPAsyncWebServer.h, AsyncTCP.h e ESPAsync_WiFiManager.h: Per la gestione del server web e della connessione WiFi.
  • ElegantOTA.h: Per eseguire aggiornamenti OTA (Over-The-Air).
  • SPIFFS.h e ArduinoJson.h: Per la gestione del filesystem e delle configurazioni in formato JSON.

Inizializzazione del sistema

Nel setup, vengono configurati:

  • La seriale per il debug.
  • La connessione WiFi tramite un manager interattivo.
  • Il filesystem SPIFFS per leggere e salvare configurazioni.
  • I pin dei LED e dei pulsanti.
  • Il display OLED per la visualizzazione dei dati.

Gestione del menù

La funzione loop gestisce un menu interattivo sul display OLED:

  • Schermata 0: Mostra il valore attuale dei decibel.
  • Schermata 1: Permette di modificare la soglia bassa.
  • Schermata 2: Permette di modificare la soglia alta.
  • Schermata 3: Regola la luminosità del display.
  • Schermata 4: Mostra l'indirizzo IP del dispositivo.
  • Schermata 5: Consente di salvare la configurazione.

Gestione dei LED

I LED indicano i livelli di decibel rispetto alle soglie impostate:

  • Rosso: Livello sotto la soglia bassa.
  • Giallo: Livello tra la soglia bassa e quella alta.
  • Verde: Livello sopra la soglia alta.

I valori di luminosità vengono mappati con la funzione map() per gestire la PWM.

Configurazione e Salvataggio

Le configurazioni vengono salvate e caricate da un file JSON su SPIFFS:

  • La funzione saveConfiguration() salva le impostazioni.
  • La funzione loadConfiguration() le legge all'avvio del sistema.
#include <Wire.h>
#include <U8g2lib.h>

#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <ESPAsync_WiFiManager.h>
#include <ElegantOTA.h>

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

#define FORMAT_SPIFFS_IF_FAILED true

#define PIN_LED_GREEN 14
#define PIN_LED_RED 33
#define PIN_LED_YELLOW 32
#define PIN_BTN_PLUS 15
#define PIN_BTN_MINUS 13
#define PIN_BTN_ENTER 25

#define I2C_SDA 17
#define I2C_SCL 16

#define DBM_ADDR 0x48
#define DBM_REG_DECIBEL 0x0A
#define DBM_REG_VERSION 0x00

#define HTTP_PORT 80

#define MAX_CHARATERS 30

const char* config_filename = "/config.json";

String user;
const char* username = "admin";
String pass;
const char* password = "admin";

const char* hostName = "FONOMETRO";

int num_schermate = 5;
int menu_btn = 0;
uint8_t db = 0;
uint8_t th_val_low = 60;
uint8_t th_val_high = 65;

int8_t brightness = 20;

char buffer[45];

U8G2_SSD1306_128X64_NONAME_F_HW_I2C display(U8G2_R0, /* reset=*/U8X8_PIN_NONE);

TwoWire dbmeter = TwoWire(1);

AsyncWebServer server(HTTP_PORT);
AsyncDNSServer dns;

void setup() {
  Serial.begin(115200);
  Serial.println("Starting...");

  Wire.setClock(10000);
  Wire.begin();
  display.begin();

  display.setFont(u8g2_font_helvR14_tf);
  display.drawStr(0, 15, "Fonometro");
  display.sendBuffer();

  ESPAsync_WiFiManager wifiManager(&server, &dns, hostName);

  if (!wifiManager.autoConnect("ESP32-Config-AP")) {
    Serial.println("Failed to connect to WiFi, restarting...");
    delay(3000);
    ESP.restart();
  }

  Serial.print("Connected successfully to network ");
  Serial.println(WiFi.SSID());

  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());

  if (!SPIFFS.begin(FORMAT_SPIFFS_IF_FAILED)) {
    Serial.println("\nSPIFFS mount failed");
    return;
  }

  Serial.println("\nSPIFFS mounted successfully");

  if (!SPIFFS.exists(config_filename)) writeDefaultConfig(config_filename);
  loadConfiguration();

  ElegantOTA.begin(&server);

  init_server();
  Serial.println(F("\nHTTP server started"));

  pinMode(PIN_BTN_PLUS, INPUT_PULLUP);
  pinMode(PIN_BTN_MINUS, INPUT_PULLUP);
  pinMode(PIN_BTN_ENTER, INPUT_PULLUP);

  pinMode(PIN_LED_GREEN, OUTPUT);
  pinMode(PIN_LED_YELLOW, OUTPUT);
  pinMode(PIN_LED_RED, OUTPUT);

  digitalWrite(PIN_LED_RED, 1);
  digitalWrite(PIN_LED_YELLOW, 1);
  digitalWrite(PIN_LED_GREEN, 1);

  delay(1000);

  digitalWrite(PIN_LED_RED, 0);
  digitalWrite(PIN_LED_YELLOW, 0);
  digitalWrite(PIN_LED_GREEN, 0);

  delay(3000);

  dbmeter.begin (I2C_SDA, I2C_SCL, 10000);
}

void loop() {

  if (WiFi.status() != WL_CONNECTED) ESP.restart();

  ElegantOTA.loop();

  display.clearBuffer();

  db = dbmeter_readreg(&dbmeter, DBM_REG_DECIBEL);

  if (digitalRead(PIN_BTN_ENTER) == 0) {
    menu_btn++;
    if (menu_btn > num_schermate) menu_btn = 0;
  }

  if (menu_btn == 0) {
    display.drawStr(0, 15, "Value:");
    snprintf(buffer, sizeof(buffer), "%d dB", db);
    display.drawStr(0, 45, buffer);
    display.sendBuffer();
    delay(100);
  } else if (menu_btn == 1) {
    display.drawStr(0, 15, "Low_th_val:");
    snprintf(buffer, sizeof(buffer), "%d dB", th_val_low);
    display.drawStr(0, 45, buffer);
    display.sendBuffer();
    delay(100);

    if (digitalRead(PIN_BTN_PLUS) == 0) th_val_low++;
    else if (digitalRead(PIN_BTN_MINUS) == 0) th_val_low--;

  } else if (menu_btn == 2) {
    display.drawStr(0, 15, "High_th_val:");
    snprintf(buffer, sizeof(buffer), "%d dB", th_val_high);
    display.drawStr(0, 45, buffer);
    display.sendBuffer();
    delay(100);

    if (digitalRead(PIN_BTN_PLUS) == 0) th_val_high++;
    else if (digitalRead(PIN_BTN_MINUS) == 0) th_val_high--;

  } else if (menu_btn == 3) {
    display.drawStr(0, 15, "Brightness:");
    snprintf(buffer, sizeof(buffer), "%d %", brightness);
    display.drawStr(0, 45, buffer);
    display.sendBuffer();
    delay(100);

    if (digitalRead(PIN_BTN_PLUS) == 0) {
      brightness++;
      if (brightness > 100) brightness = 100;
    } else if (digitalRead(PIN_BTN_MINUS) == 0) {
      brightness--;
      if (brightness < 1) brightness = 1;
    }
  } else if (menu_btn == 4) {
    display.drawStr(0, 15, "IP Address: ");
    snprintf(buffer, sizeof(buffer), "%s", WiFi.localIP().toString().c_str());
    display.drawStr(0, 45, buffer);
    display.sendBuffer();
    delay(100);
  } else if (menu_btn == 5) {
    display.drawStr(0, 15, "Do you want to save to memory?");
    display.drawStr(0, 45, "Press ENTER to confirm");
    display.sendBuffer();
    delay(100);
    if (digitalRead(PIN_BTN_ENTER) == 0) {
      saveConfiguration();
      printConfigFile(config_filename);
      delay(1000);
      menu_btn = 0;
    }

  }

  uint8_t pwm_value = map(brightness, 0, 100, 0, 255);

  if (db <= th_val_low) {
    analogWrite(PIN_LED_RED, pwm_value);
    analogWrite(PIN_LED_YELLOW, 0);
    analogWrite(PIN_LED_GREEN, 0);
  } else if (db <= th_val_high) {
    analogWrite(PIN_LED_RED, 0);
    analogWrite(PIN_LED_YELLOW, pwm_value);
    analogWrite(PIN_LED_GREEN, 0);
  } else {
    analogWrite(PIN_LED_RED, 0);
    analogWrite(PIN_LED_YELLOW, 0);
    analogWrite(PIN_LED_GREEN, pwm_value);
  }

}

Il file dbmeter.ino

  • Avvia una trasmissione verso il dispositivo all'indirizzo I2C DBM_ADDR.
  • Scrive l'indirizzo del registro (regaddr) per specificare quale valore leggere.
  • Termina la trasmissione.
  • Richiede 1 byte di dati dal dispositivo.
  • Attende 10 ms con delay(10) per garantire il completamento della comunicazione.
  • Legge e restituisce il valore del registro.
uint8_t dbmeter_readreg (TwoWire *dev, uint8_t regaddr)
{
  dev->beginTransmission (DBM_ADDR);
  dev->write (regaddr);
  dev->endTransmission();
  dev->requestFrom (DBM_ADDR, 1);
  delay (10);
  return dev->read();
}

Il file lib.ino

Descrizione delle funzioni

  • loadConfiguration()
    Legge il file di configurazione dalla memoria SPIFFS. Se il file è presente e valido, deserializza i dati JSON nelle variabili globali e stampa la configurazione sul monitor seriale.

  • writeDefaultConfig()
    Scrive una configurazione predefinita nella memoria SPIFFS. Sovrascrive eventuali configurazioni esistenti.

  • FS_readConfig()
    Legge il contenuto del file di configurazione dalla memoria SPIFFS come stringa e lo restituisce. Restituisce una stringa vuota in caso di errore.

  • printConfigFile()
    Stampa il contenuto del file di configurazione sul monitor seriale. Segnala un errore se il file non è leggibile.

  • saveConfiguration()
    Serializza i valori delle variabili globali in un file JSON e li salva nella memoria SPIFFS. Sovrascrive la configurazione esistente e segnala eventuali errori.

void loadConfiguration() {
  File file = SPIFFS.open(config_filename, "r");
  if (!file) {
    Serial.print(F("Error opening file: unable to open "));
    Serial.println(config_filename);
    return;
  }

  StaticJsonDocument<2048> doc;
  DeserializationError error = deserializeJson(doc, file);
  if (error) {
    Serial.println(F("Error reading file: "));
    Serial.println(error.c_str());
  }
  file.close();

  JsonObject root = doc.as<JsonObject>();
  user = root["Username"].as<String>();
  pass = root["Password"].as<String>();
  username = user.c_str();
  password = pass.c_str();

  th_val_low = root["LowThreshold"].as<int>();
  th_val_high = root["HighThreshold"].as<int>();
  brightness = root["Brightness"].as<int>();

  Serial.print(F("\nUsername: "));
  Serial.println(username);
  Serial.print(F("Password: "));
  Serial.println(password);
  Serial.print(F("Low Threshold: "));
  Serial.println(th_val_low);
  Serial.print(F("High Threshold: "));
  Serial.println(th_val_high);
  Serial.print(F("Brightness: "));
  Serial.println(brightness);

  Serial.println(F("\nConfiguration loaded!"));
}

void writeDefaultConfig(const char *filename) {
  Serial.println(F("Loading default configuration..."));

  File file = SPIFFS.open(filename, "w");

  if (!file) {
    Serial.println(F("Error opening file"));
    return;
  }

  String defaultConfig = "{"
                         "\"Username\": \"admin\","
                         "\"Password\": \"admin\","
                         "\"LowThreshold\": 60,"
                         "\"HighThreshold\": 65,"
                         "\"Brightness\": 20"
                         "}";

  file.print(defaultConfig);
  file.close();

  Serial.println(F("Loaded default configuration!"));
}

String FS_readConfig() {
  File file = SPIFFS.open(config_filename, "r");
  if (!file) {
    Serial.println(F("Error reading file"));
    return "";
  }
  String fileContent = file.readString();
  file.close();
  return fileContent;
}

void printConfigFile(const char *filename) {
  File file = SPIFFS.open(filename, "r");
  if (!file) {
    Serial.println(F("Error reading file"));
    return;
  }

  while (file.available()) {
    Serial.print((char)file.read());
  }
  Serial.println();
  file.close();
}

void saveConfiguration() {
  StaticJsonDocument<2048> doc;
  JsonObject root = doc.to<JsonObject>();

  root["Username"] = user;
  root["Password"] = pass;
  root["LowThreshold"] = th_val_low;
  root["HighThreshold"] = th_val_high;
  root["Brightness"] = brightness;

  File file = SPIFFS.open(config_filename, "w");
  if (!file) {
    Serial.println(F("Error opening file"));
    return;
  }

  serializeJson(doc, file);
  file.close();

  Serial.println(F("Configuration saved!"));
}

Il file webserver.ino

Descrizione delle funzioni

  • / (GET)
    Gestisce la pagina principale, autenticando l'utente con username e password. Carica e invia il file index.html dalla memoria SPIFFS con una cache di 1 ora.

  • /style.css (GET)
    Serve il file CSS per la pagina, caricato dalla memoria SPIFFS e con cache di 1 ora.

  • /script.js (GET)
    Serve il file JavaScript per la pagina, caricato dalla memoria SPIFFS e con cache di 1 ora.

  • /upload (POST)
    Riceve dati JSON dal client, deserializza i valori (soglie di luminosità e luminosità) e li salva in variabili globali.

  • /saveToMemory (POST)
    Salva una nuova configurazione JSON nella memoria SPIFFS. Riavvia il dispositivo per applicare le modifiche se il salvataggio è avvenuto con successo.

  • /getStartupConfig (GET)
    Restituisce il file di configurazione salvato in memoria SPIFFS in formato JSON. Segnala un errore se il file non è leggibile.

  • /getRunningConfig (GET)
    Restituisce la configurazione attualmente in uso (variabili globali) in formato JSON.

  • /restart (GET)
    Ricarica la pagina principale (index.html) e riavvia il dispositivo.

  • server.begin()
    Avvia il server web per gestire tutte le richieste.

void init_server() {

  server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) {
    if (!request->authenticate(username, password))
      return request->requestAuthentication();
    AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/index.html", "text/html");
    response->addHeader("Cache-Control", "max-age=3600");
    request->send(response);
  });

  server.on("/style.css", HTTP_GET, [](AsyncWebServerRequest * request) {
    AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/style.css", "text/css");
    response->addHeader("Cache-Control", "max-age=3600");
    request->send(response);
  });

  server.on("/script.js", HTTP_GET, [](AsyncWebServerRequest * request) {
    AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/script.js", "application/javascript");
    response->addHeader("Cache-Control", "max-age=3600");
    request->send(response);
  });

  server.on("/upload", HTTP_POST, [](AsyncWebServerRequest * request) {
  }, NULL, [](AsyncWebServerRequest * request, uint8_t *data, size_t len, size_t index, size_t total) {
    String jsondata;
    for (size_t i = 0; i < len; i++) {
      jsondata += (char)data[i];
    }

    DynamicJsonDocument doc(2048);
    deserializeJson(doc, jsondata);
    th_val_low = doc["LowThreshold"];
    th_val_high = doc["HighThreshold"];
    brightness = doc["Brightness"];

    Serial.println("\nSaving data into variables");
    Serial.print(F("Low Threshold: "));
    Serial.println(th_val_low);
    Serial.print(F("High Threshold: "));
    Serial.println(th_val_high);
    Serial.print(F("Brightness: "));
    Serial.println(brightness);

    request->send(200);
  });

  server.on("/saveToMemory", HTTP_POST, [](AsyncWebServerRequest * request) {
  }, NULL, [](AsyncWebServerRequest * request, uint8_t *data, size_t len, size_t index, size_t total) {
    String new_config;
    for (size_t i = 0; i < len; i++) {
      new_config += (char)data[i];
    }

    Serial.println(F("\nWriting new configuration..."));
    Serial.println(new_config);

    if (SPIFFS.begin()) {
      File f = SPIFFS.open(config_filename, "w"); // Assicurati che il nome del file sia corretto
      if (f) {
        f.print(new_config);
        f.close();
        request->send(200, "text/plain", "Configuration loaded successfully");
        delay(1000);
        ESP.restart();  // da vedere se riavviare
      } else {
        request->send(500, "text/plain", "Error during attempt to save configuration");
      }
    }
  });

  server.on("/getStartupConfig", HTTP_GET, [](AsyncWebServerRequest * request) {
    String configData = FS_readConfig();
    if (configData.length() > 0) {
      request->send(200, "application/json", configData);
    } else {
      request->send(500, "text/plain", "Error during attempt to read the configuration");
    }
  });

  server.on("/getRunningConfig", HTTP_GET, [](AsyncWebServerRequest * request) {
    StaticJsonDocument<2048> doc;

    JsonObject object = doc.to<JsonObject>();
    object["Username"] = username;
    object["Password"] = password;
    object["LowThreshold"] = th_val_low;
    object["HighThreshold"] = th_val_high;
    object["Brightness"] = brightness;

    String response;
    serializeJson(object, response);

    request->send(200, "application/json", response);
  });

  server.on("/restart", HTTP_GET, [](AsyncWebServerRequest * request) {
    AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/index.html", "text/html");
    request->send(response);
    delay(1000);
    ESP.restart();
  });

  server.begin();

}

Il file index.html

  • Sezioni principali:

    • Intestazione
      Contiene il titolo della pagina ("Fonometro") e un link al foglio di stile esterno style.css.
    • Container
      Un contenitore principale con elementi:
      • Campi di configurazione
        • Low Threshold, High Threshold, e Brightness (input numerici, inizialmente disabilitati).
        • Username e Password (input testuali per credenziali).
        • Una checkbox (edit-switch) per abilitare la modifica dei campi disabilitati.
      • Pulsanti di controllo
        • Reset: Ripristina i valori predefiniti.
        • Save: Salva i dati configurati (variabili globali).
        • Save to memory: Salva i dati nella memoria del dispositivo.
        • Restart: Riavvia il dispositivo.
  • Script esterno
    Il file script.js gestisce le funzionalità interattive della pagina.

<!DOCTYPE html>
<html lang="it">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" href="style.css" />
    <title>Fonometro</title>
  </head>

  <body>
    <div class="container">
      <h1 style="text-align: center;">Device Configuration</h1>

      <div class="field-group">
        <label for="low-threshold">Low Threshold:</label>
        <input type="number" id="low-threshold" disabled />
      </div>

      <div class="field-group">
        <label for="high-threshold">High Threshold:</label>
        <input type="number" id="high-threshold" disabled />
      </div>

      <div class="field-group">
        <label for="brightness">Brightness:</label>
        <input type="number" id="brightness" disabled />
      </div>

      <div class="field-group">
        <label for="username">Username:</label>
        <input type="text" id="username" />
      </div>

      <div class="field-group">
        <label for="password">Password:</label>
        <input type="password" id="password" />
      </div>

      <div class="field-group">
        <label for="edit-switch">
          <input type="checkbox" id="edit-switch" />
          Enables changes to the fields
        </label>
      </div>

      <div class="buttons">
        <button class="reset" id="reset">Reset</button>
        <button class="save" id="save">Save</button>
        <button class="save-memory" id="save-memory">Salva to memory</button>
        <button class="restart" id="restart">Restart</button>
      </div>
    </div>

    <script src="script.js"></script>
  </body>
</html>

Il file script.js

  • Variabile globale
    intervalId: Usata per gestire l'intervallo di aggiornamento della configurazione corrente.

  • Funzioni principali:

    • toggleFields()
      Abilita o disabilita i campi di input in base allo stato della checkbox edit-switch. Interrompe o avvia il polling della configurazione corrente ogni 5 secondi.

    • resetFields()
      Reimposta i valori di tutti i campi di input a vuoti.

    • saveFields()
      Invia i valori dei campi LowThreshold, HighThreshold e Brightness al server tramite una richiesta POST all'endpoint /upload. Mostra un messaggio di successo o errore.

    • saveToMemory()
      Salva i valori di tutti i campi di input (incluse credenziali) nella memoria del dispositivo tramite una richiesta POST all'endpoint /saveToMemory.

    • restart()
      Chiede conferma per riavviare il dispositivo. Invia una richiesta all'endpoint /restart e aggiorna la pagina dopo 10 secondi se il riavvio è stato avviato con successo.

    • getRunningConfig()
      Recupera la configurazione corrente dal server tramite una richiesta GET all'endpoint /getRunningConfig e aggiorna i campi di input con i valori ricevuti.

  • Event Listeners:

    • Cambiamento dello stato della checkbox edit-switch: chiama toggleFields().
    • Click sui pulsanti:
      • Reset: chiama resetFields().
      • Save: chiama saveFields().
      • Save to memory: chiama saveToMemory().
      • Restart: chiama restart().
  • Polling della configurazione corrente
    setInterval() richiama getRunningConfig() ogni 5 secondi per mantenere aggiornati i valori dei campi.

let intervalId;

function toggleFields() {
  const isEditable = document.getElementById("edit-switch").checked;
  document.getElementById("low-threshold").disabled = !isEditable;
  document.getElementById("high-threshold").disabled = !isEditable;
  document.getElementById("brightness").disabled = !isEditable;

  document
    .getElementById("low-threshold")
    .classList.toggle("disabled", !isEditable);
  document
    .getElementById("high-threshold")
    .classList.toggle("disabled", !isEditable);
  document
    .getElementById("brightness")
    .classList.toggle("disabled", !isEditable);

  if (isEditable) {
    clearInterval(intervalId);
  } else {
    intervalId = setInterval(getRunningConfig, 5000);
  }
}

function resetFields() {
  document.getElementById("low-threshold").value = "";
  document.getElementById("high-threshold").value = "";
  document.getElementById("brightness").value = "";
  document.getElementById("username").value = "";
  document.getElementById("password").value = "";
}

function saveFields() {
  const lowThreshold = document.getElementById("low-threshold").value;
  const highThreshold = document.getElementById("high-threshold").value;
  const brightness = document.getElementById("brightness").value;

  if (!lowThreshold || !highThreshold || !brightness) {
    alert("Please fill in all the required fields");
    return;
  }

  fetch("/upload", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      LowThreshold: lowThreshold,
      HighThreshold: highThreshold,
      Brightness: brightness,
    }),
  })
    .then((response) => {
      if (response.ok) {
        alert("Configuration saved successfully");
      } else {
        throw new Error("Error during attempt to save configuration");
      }
    })
    .catch((error) => {
      alert(error.message);
    });
}

function saveToMemory() {
  fetch("/saveToMemory", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      LowThreshold: document.getElementById("low-threshold").value,
      HighThreshold: document.getElementById("high-threshold").value,
      Brightness: document.getElementById("brightness").value,
      Username: document.getElementById("username").value,
      Password: document.getElementById("password").value,
    }),
  })
    .then((response) => {
      if (response.ok) {
        alert("Configuration saved to memory successfully");
      } else {
        throw new Error("Error during attempt to save configuration to memory");
      }
    })
    .catch((error) => {
      alert(error.message);
    });
}

function restart() {
  if (confirm("Are you sure you want to restart the device?")) {
    fetch("/restart")
      .then((response) => {
        if (response.ok) {
          alert("Device restart initiated successfully");
          setTimeout(() => {
            window.location.reload(true);
          }, 10000);
        } else {
          throw new Error("Error during attempt to restart device");
        }
      })
      .catch((error) => {
        alert(error.message);
      });
  }
}

function getRunningConfig() {
  fetch("/getRunningConfig")
    .then((response) => response.json())
    .then((data) => {
      document.getElementById("low-threshold").value = data.LowThreshold;
      document.getElementById("high-threshold").value = data.HighThreshold;
      document.getElementById("brightness").value = data.Brightness;
      document.getElementById("username").value = data.Username;
      document.getElementById("password").value = data.Password;
    })
    .catch(() => {
      alert("Error during attempt to get running configuration");
    });
}

document.getElementById("edit-switch").addEventListener("change", toggleFields);
document.getElementById("reset").addEventListener("click", resetFields);
document.getElementById("save").addEventListener("click", saveFields);
document.getElementById("save-memory").addEventListener("click", saveToMemory);
document.getElementById("restart").addEventListener("click", restart);

intervalId = setInterval(getRunningConfig, 5000);
getRunningConfig();

Stato attuale del progetto

Il progetto è stato terminato nel Dicembre 2024.

Autori

Giovanni Pompigna, Francesco Sparascio, Luca Pezzi