Passaggio all'ora legale 29 marzo 2020 02:0003:00 sposta avanti l'orologio di 1 ora (si dorme 1 ora in meno)
Tempo fa, parlando di sicurezza sui luoghi di lavoro, mi sono reso conto che, in alcuni casi, la registrazione degli ingressi/uscite avviene su sistemi elettronici. Sistemi che non sempre risultano accessibili in caso di mancanza di corrente, rendendo impossibile quantificare quante persone sono all'interno della struttura.

Ho voluto provare a realizzare una possibile soluzione IoT realizzando un contatore degli accessi, controllato tramite WI-FI, che mantenesse l'ultimo valore anche in mancanza di corrente.
Una possibile soluzione era usare un display elettronico con una batteria tampone, oppure ... seguire un approccio diverso, come quello che segue:
realizzazione finalerealizzazione finale
Per realizzare la visualizzazione del conteggio ho utilizzato un visualizzatore "meccanico", usando un coperchio del barattolo della nutella e 2 servo del tipo usati nel modellismo:
coperchio tagliato in 2 unito al servocoperchio tagliato in 2 unito al servo
assemblati tramite l'utilizzo di legno, per il supporto, e di scotch per unire le varie parti:
supporto servosupporto servo
Per il pilotaggio tramite WI-FI ho utilizzato un chip ESP 8266.

I materiali necessari sono:
  • Nr. 1 coperchio della Nutella
  • Nr. 1 Esp 8266
  • Nr. 1 Basetta di prototipazione e alimentazione per ESP8266
  • Nr. 1 Alimentatore da 6 a 24 V cc da collegare alla basetta
  • serie di cavi di collegamento
  • 2 serie di numeri stampati su carta da 10 x 2 cm
  • Legno
  • Viti
  • Seghetto per legno, cacciavite e forbice oltre a una scatola di cartone
materiali necessarimateriali necessari
Nella mia realizzazione ho usato un solo coperchio della nutella tagliato in 2 parti. Questa soluzione non si è rivela ideale in quanto le due parti rsultano poco rigide e tendono a deformarsi. Forse la soluzione migliore è utilizzare 2 coperchi interi incollati al supporto dei servo.
Questo è il risultato finale del "display meccanico":
e qui si vede l'interno durante il funzionamento:

Il dispay può essere controllato tramite browser inserendo una url tipo: http://<ip esp8266>/set?counter=<valore del conteggio> ad esempio: http://192.168.1.50/set?counter=43

Il software per controllarlo è il seguente:
// Contatore mreccanico
// visualizza un numero da 0 a 99
// by Sgart.it
// http://www.sgart.it
// Board: 'Generic ESP8266 Module'
// Flash Mode: 'DIO'
// Flash Frequency '40MHz'
// CPU Frequency '80 MHz'
// Flash Size: '1M (512KSPIFFS)'
// Debug Port: 'Disabled'
// Debug Level: 'None'
// Reset Method: 'nodemcu'
// Upload Speed: '115200'
// Port: COM3

#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <Servo.h>
#include <EEPROM.h>

//---------------------------------------------------
Servo servoUnita;
Servo servoDecine;

const int PIN_UNITA = 5; // D1 - GPIO5
const int PIN_DECINE = 4; // D2 - GPIO4

// posizione del servo delle unità e delle decine
// valori da tarare dopo la realizzazione
//                         {000, 001, 002, 003, 004, 005, 006, 007, 008, 009
const int ANGLE_UNITA[] =  { 31,  44,  59,  74,  90, 105, 120, 135, 148, 163};
const int ANGLE_DECINE[] = {155, 142, 127, 112,  98,  82,  67,  53,  39,  27};
const int OFFSET_UNITA = 0;
const int OFFSET_DECINE = 0;

//---------------------------------------------------
// valori da impostare in base al rete locale / router
const char WiFiSSID[] = "ssid-xxx";
const char WiFiPSK[] = "password";

// IP statico
IPAddress ip(192, 168, 1, 50);
IPAddress gateway(192,168,1,254);
IPAddress subnet(255,255,255,0);

//---------------------------------------------------
ESP8266WebServer server(80);  //listen to port 80

//---------------------------------------------------
// EEPROM
int addressCounter = 0;

//---------------------------------------------------
// INIZIO
void setup() {
  // serial
  Serial.begin(115200);
  delay(10);
  // WIFI
  Serial.println("- Sgart.it -----------------------------");
  Serial.println("Attempting to connect to: " + String(WiFiSSID) + " ...");
  WiFi.begin(WiFiSSID, WiFiPSK);
  while ( WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    delay(500);
  }
  // SET IP
  Serial.print("\nSet IP ...");
  WiFi.config(ip, gateway, subnet);
    Serial.print(WiFi.localIP());
  // WEB SERVER
  Serial.print("\nWebServer starting ...");
  server.on("/set", handleSet);
  server.on("/reset", handleReset);
  server.on("/set-d", handleSetD);
  server.on("/set-u", handleSetU); 
  server.begin();
  Serial.print(" started");
  // EEPROM - per salvare il conteggio mi serve solo 1 byte
  EEPROM.begin(1);
  // leggo il valore precedentemente memorizzato
  int counter = EEPROM.read(addressCounter);
  if(counter > 99) {
    counter = 0;
  }
  // SET Servo
  servoUnita.attach(PIN_UNITA);
  servoDecine.attach(PIN_DECINE);
  setCounter(counter);
  Serial.println("\n------------------------------");
}

//---------------------------------------------------
void loop() {
  server.handleClient();
}

//---------------------------------------------------
void handleSet() {
  boolean save = false;
  
  if (server.hasArg("counter") == false) {
    // se non ho il paramentro esco
    server.send(404, "text/plain", "Count not received");
    return;
  }
  if (server.hasArg("save") == true) {
     save = true;
  }
  
  String counter = server.arg("counter");
  int l = counter.length();
  if (l == 0) {
    // parametro non valorizzato
    server.send(404, "text/plain", "Count empty");
    return;
  }
  if (l > 2) {
    // conteggio fuori range
    server.send(404, "text/plain", "Count too big");
    return;
  }
  int unita = -1;
  int decine = 0;
  if (l == 2) {
    // decine + unita
    unita = charToInt(counter[1]);
    decine = charToInt(counter[0]);
  } else {
    unita = charToInt(counter[0]);
  }
  boolean result = false;
  int counterInt = decine * 10 + unita;
  if (unita != -1 && decine != -1) {
    result = setCounter(counterInt);
  }
  if (result == false) {
    // caratteri non validi
    server.send(404, "text/plain", "Invalid number");
    return;
  }

  String message = counter + "\n";

  if(save == true){
    // salvo il valore attuale sulla EEPROM
    EEPROM.write(addressCounter, counterInt);
    EEPROM.commit();
    Serial.println("Counter saved");
  }
  server.send(200, "text/plain", message);
  Serial.println(message);
}

void handleReset() {
  setCounter(0);
  server.send(200, "text/plain", "0\n");
}

boolean setCounter(int value) {
  Serial.print("\nsetCounter: "); Serial.print(value);

  if (value < 0 || value > 99) {
    return false;
  }

  int iDecine = value / 10;
  int iUnita = value - (iDecine * 10);

  Serial.print("\nIndex: "); Serial.print(iDecine); Serial.print(" "); Serial.print(iUnita);


  int vUnita = OFFSET_UNITA + ANGLE_UNITA[iUnita];
  int vDecine = OFFSET_DECINE + ANGLE_DECINE[iDecine];

  Serial.print("\nAngle: "); Serial.print(vDecine); Serial.print(" "); Serial.print(vUnita);

  servoUnita.write(vUnita);
  servoDecine.write(vDecine);

  Serial.println("\nsetCounter-ok");
  return true;
}

int charToInt(char c) {
  switch (c) {
    case '0': return 0;
    case '1': return 1;
    case '2': return 2;
    case '3': return 3;
    case '4': return 4;
    case '5': return 5;
    case '6': return 6;
    case '7': return 7;
    case '8': return 8;
    case '9': return 9;
  }
  return -1;
}

//solo per test
void handleSetU() {
  if (server.hasArg("angle") == false) {
    // se non ho il paramentro esco
    server.send(404, "text/plain", "angle not received");
    return;
  }
  String value = server.arg("angle");
  int v = value.toInt();
  servoUnita.write(v);

  Serial.println(v);
  server.send(200, "text/plain", value);
}

void handleSetD() {
  if (server.hasArg("angle") == false) {
    // se non ho il paramentro esco
    server.send(404, "text/plain", "angle not received");
    return;
  }
  String value = server.arg("angle");
  int v = value.toInt();
  servoDecine.write(v);

  Serial.println(v);
  server.send(200, "text/plain", value);
}
Vanno valorizzate le variabili WiFiSSID e WiFiPSK con i valori della tua rete. Poi va settato l'ip statico compatibile con il tuo indirizzamento (vedi 192.168.1.50), netmask e gateway.

Come detto prima, il conteggio può essere impostato tramite una url tipo http://192.168.1.50/set?counter=43. A questa url può essere aggiunto un parametro save che permette di salvare il conteggio nella memoria EEPROM del ESP 8266. In questo modo il valore viene mantenuto e ripristinato, anche dopo un riavvio. Ad esempio per salvare il valore 76 la url sarà: http://192.168.1.50/set?counter=76&save=
non serve passare un valore al parametro save
Il salataggio e successiva lettura del valore dalla EEPROM avviene tramite la libreria EEPROM.h.

Per gestire le chiamate tramite uso la libreria ESP8266WebServer.h che permette di associare, ad ogni url, una specifica funzione. Per far questo si usa il metodo:
server.on("/miaurl", miafunzione);

Una volta assemblato sarà necessario tarare i valori delle posizioni dei numeri agendo sui valori contenuti negli array ANGLE_UNITA[] e ANGLE_DECINE[].
Per ricavare i valori corretti bisognerà procedere per tentativi usando queste due url:
  • http://<indirizzo ip>/set-u?angle=<gradi> per settare le unità
  • http://<indirizzo ip>/set-d?angle=<gradi> per settare le decine
ad esempio la url http://192.168.1.50/set-u?angle=116 fa ruotare il servo delle unità ad un angolo di 116 gradi.