Automazione, parte prima: principi e codice sorgente.

Questo primo articolo è dedicato a rivisitare il grande tema dell’automazione secondo le nuove possibilità tecnologiche. arduino

L’ impianto di nuova generazione deve avere un dispositivo centrale che sia sostanzialmente della categoria server.

Quindi un sistema operativo server, come Ubuntu Server, una collezione di servizi che permetta il controllo, monitoraggio, l’archiviazione di dati e log, modalità di accesso diverse web, socket, app, applicazioni native, console, ssh, …

Il dispositivo centrale deve permettere  la coesistenza di applicazioni di autori diversi, realizzate con strumenti diversi, C, Qt, QML, Java, Python, PHP

L’architettura deve essere aperta per permettere l’integrazione di parti diverse in un contesto di rispetto.

Applicazioni a codice aperto devono comunque coesistere con aplicazioni commerciali. In sostanza si adotta il modello community affiancato al commerciale.

La nuova visione concepisce un impianto:

  • con molte piccole schede dotate di  processore, che interagiscono tra loro
  • con accessi di debug e controllo da qualunque nodo ed a caldo, hot plug  USB
  • il costo delle schede con processore deve essere basso
  • centralizzato in modo robusto
  • il server centrale contenga un database di tabelle con le informazioni attuali e storiche, ma anche l’integrale descrizione del sistema, tale da permettere, per esempio, la generazione in automatico delle sorgenti da compilare di ogni scheda appartenente all’impianto
  • un programma permetta, attraverso una semplice GUI, di gestire queste sorgenti anche da personale router non informatico
  • il database centrale contenga le informazioni per la generazione automatica delle GUI da utilizzare nelle visualizzazioni dei dispositivi di controllo, in sostanza App per tavolette di nuova generazione
  • la comunicazione tra i nodi deve essere la più robusta, diffusa e collaudata possibile, quindi USB, ethernet, seriale, I2C, SPI, …
  • e molto altro …

Arduino Micro Without Headers     Image  Grove - Base Shield V1.3

 

 

Linux, Qt, QML, Arduino, Raspberry PI e le nuove schede ARM sono elementi fondamentali per costruire i sistemi.

I campi di applicazione sono moltissimi.

La prima applicazione è la gestione del CED, in modo da poter:

  • misurare la forza elettromotrice presente e, soprattutto, eventuali interruzioni di fornitura
  • gli UPS, preferibilmente misurando le tensioni in uscita con tempi stimati di durata inseriti manualmente
  • le temperature nei locali, ma anche in prossimità di apparecchiature considerate critiche
  • l’accensione di ventole o condizionatori, nel caso di eventi particolari come interruzione o carenze di impianti centrali, infatti  talvolta un comune ventilatore acceso in caso di mancata refrigerazione, evita surrisccaldamenti di computer con ruoli estremamente delicati
  • lo spegnimeto e la riaccensione da remoto di dispositivi quali router, che in caso di blocco possano impedire, per esmpio, l’accesso dall’esterno
  • l’illuminazione, sia per correggere dimenticanze sia per accenderla in caso di utilizzo per esempio di dispositivi di ripresa video
  • l’integrazione con Nagios
  • molto altro …

Una seconda è nel campo dell’illuminotecnica, che permetta di sfruttare i nuovissimi prodotti per l’illuminazione.

Una terza è l’integrazione di un sistema di controllo presenza ed accessi, anche nell’accezione più ampia di controllo ambiente domestico o perfino dell’automobile. Quindi sarà possibile collocare nella propria dimora un microserver che progressivamente assumerà funzioni diverse, dalla rilevazione di temperatura ed avvio elettrici, alla gestione di punti PIR con l’invio di avvisi ricevibili da smartphone. Oppure lasciare nel camper un dispositivo RPI connesso in 3G ed in poll con una scheda micro che invii avvisi in caso di attivazioni elettriche o riscontri positivi da PIR.

In questa fase si presuppone dimestichezza con la IDE Arduino. Il riferimento sia la IDE versione 1.0.5 disponbile in repository di Ubuntu 13.10. Consideriamo la gestione di Arduino da Linux per la stabilità e la precisione difficilmente raggiungibili da altri ambienti. Quindi, con i privilegi di root, eseguire synaptic e scegliere Arduino, infine confermare l’installazione. Ricordiamo di eseguire la IDE con i privilegi amministrativi per poter gestire la porta USB con perfetto controllo. In una fase prossima allestiremo una situazione nella quale all’utente non siano richieste le competenze relative all’uso dell’IDE in questione. Collegare la scheda compatibile Arduino che si intende utilizzare, avviare arduino con i privilegi amministrativi, selezionare il tipo di scheda adottato e la porta, caricare un semplice esempio, compilare e caricare il compilato nella scheda. Si è operativi se non sono emersi errori.

Riprendiamo il nostro sistema che immaginiamo costituito da molti nodi, ognuno dei quali sia dotato preferibilmente di un suo autonomo LCD, nella versione più economica a due righe di caratteri. In questo modo si facilità la comprenzione dello stato del nodo. Miglioremo la proposta adottando un display con fondo RGB, da poter utilizzare un cambio colori per avvisi immediati: verde funzionamento normale, giallo avvisi, rosso stato di errore.

Arduino Leonardo

L’interazione con l’impianto è attraverso comandi, che devono essere  semplici, naturali, per facilitare l’utilizzo ed il debug.

Quindi stringhe termiante con il tradizionale a capo ( \n  NewLine ), per esempio

set port=1 value=0\n

oppure
read port=3\n .

La risposta è una stringa coerente.

Così possiamo immaginare di poter connettere il proprio notebook alla porta USB di un qualunque nodo dell’impianto, ed interagire a mano in modo semplice.

E’ possibile usare il terminale della IDE di sviluppo Arduino. Per sfruttare la massima velocità della seriale  il terminale va configurato con A capo (NL) e 115200 baud.

La denominazione ijk rimane valida e costituisce l’indirizzo di ogni dispositivo. In seguito verrà introdotto il parametro device=ij per indirizzare i comandi stessi. Si apre la q routeruestione di sulla scrittura dell’indirizzo, che in questa prima fase proponiamo i.j.k ovvero
1.0.0 è la scheda o device o nodo con i=1,
3.0.2 è il sensore o attuatore k=2 del nodo i=3
2.1.0 è la scheda j=1 del nodo i=2
2.1.7 è il suo settimo sensore o attuatore di
indice k=7 del nodo o device 2.1

Il microserver, radice dell’albero o grafo, ha il compito fondamentale di prelevare i dati letti riportandoli in tabelle, leggere i comandi da eseguire da tabelle ed inviarli ai destinatari. Inoltre ha un altro compito fondamentale di router per comandi od eventi tra linee diverse, ovvero i diversi.

I dispositivi al livello i sono:

1. microcontroller connessi mediante seriale-USB

2. microcontroller con porta ethernet

Quindi due dispositivi  i’ ed i” possono comunicare tra loro mediante il microserver.

I dispositivi di secondo livello, ovvero ij,  sono connessi al loro padre di livello i, mediante:

1. porta seriale RS232 o TTL 5V o TTL 3.3 V; in merito ricordiamo che libreria SoftwareSerial permette di gestire dal dispositivo  molti dispositivi seriali, dedicando due pin per ogni dispositivo controllato mediante seriale; il protocollo è del tipo Master/Slave, dove la porta principale connessa alla USB è in modalità Slave, le altre seriali dei device i sono in modalità Master, mentre i dispositivi ij, di livello j, sono in modalità Slave; svilupperemo concreatamente questo aspetto

2. mediante porta seriale RS485, quindi una linea di molti dispositivi

3. mediante I2C, che quindi può prevedere molti dispositivi

Il primo passo da fare è pensare alla situazione più semplice, ovvero un unico microcontroller con la porta seriale-USB, con la finalità di relizzare la situazione di collegamento e scollegamento a caldo per debug o controllo.

Ricordiamo che il reset legato al DTR deve essere opportunamente gestito, per esempio mediante taglio del ponticello ed adozione del reset manuale per il bootloader. Nella situazione che stiamo realizzando, l’opzione di reset automatico conviene che rimanga attiva, infatti il poll costante del microserver con la scheda via USB, ha così un meccanismo di recupero in caso di problemi di comunicazione. E’ ovvio che, in questo caso, il collegamento USB si intende fisso. Il cambio del device tty, inevitabile con la sconnessione e successiva connessione del canale USB, verrà gestita mediante attenta interazione con udev.

E’ importante per creare un contesto di laboratorio utilizzare degli strumenti per creare prototipi molto rapidamente, per esempio adottando lo standard Arduino TinkerKit o Grove.

TinkerKit Mega Sensor Shield V.2Grove - Base Shield V1.2

Lo shield Grove 1.2 ha problemi con alcune schede Arduino, mentre con Leonardo (con header) oppure  Olimexino 328 è perfetto, bisogna selezionare 5V e non 3.3V di funzionamento.

OLIMEXINO-328 - Open Source Hardware Board

Quindi passiamo a presentare la prima versione del codice da compilare e caricare nella scheda utilizzata.

 

La finalità è di realizzare un nodo che se connesso al terminale mediante USB-serial risponda ai primi ed importanti comandi oppuri permetta il debug.

La scheda tipo Arduino, appena avviata, per ora, attende un comando, link, come conferma connessione. Questa versione rimarrà tale nel caso di scehda con USB- reset attivo. Affronteremo successivamente le questioni correlate. La risposta è:

Linked with <link> mc_ver.: SC.0.1.01\n

 

Prevediamo quindi il comando ovvio help\n .

 

Ed i seguenti comandi di ovvio utilizzo:

    mc_version|ver|version

   mc_getvaried|getvaried

   mc_geall|getall

   mc_getad|getad

   mc_setport|setport out<n>=0|1

 

Utilizziamo

setport out3=1\n

invece del più elegante

set port=3 value=1\n

per poter realizzare assegnazioni multiple, tipo

setport out1=0 out3=0 out4=1\n

Inoltre introduciamo il concetto di variazione delle rilevazioni, ovvero, ai fini di allegerire i dati comunicati, inviamo solo quelli che sono variati.

Il microserver ha la funzione di servizio in pool che invia comandi e gestisce i dati ad essi legati.

Quindi presentiamo la prima versione del programma per Arduino che realizza quanto detto.

 

La prima parte dichiara le prime variabili utilizzate:

const char* FIRMWARE_DESCRIPTION = “SC.0.1.012”;
const int CYCLE_DELAY     = 20;        // in millisecondi
char   incomingChar              = ‘\0’;
String incomingString          = “”;
String command                      = “”;
String parameters                  = “”;
String parameter                    = “”;
String tempString                   = “”;
String tempString1                = “”;
String tempString2                = “”;
int    port                                        = 0;
String portvalue                       = “”;
String portname                      = “”;
int    pos                                         = 0;
int    pos1                                      = 0;
int    pos2                                      = 0;
int    pos3                                      = 0;
int    tempInt                              = 0;
char   tempChar                      = ‘\0’;
int    inPortNum                       = 3;  
int    inPortMap[7];     
int    valueIn[7];       
int    valueInPrev[7];
int    valueInVaried[7];
int    outPortNum                    = 3; 
int    outPortMap[7];
int    valueOut[7];
int    ADPortNum                     = 4;
int    ADPortMap[5];
int    ADPortValue[5];
int    ADPortPrevValue[5];
int    ADPortVariedValue[5];

Questa prima versione prevede l’assegnazione a mano dei pin digitali in uscita e di quelli in entrata: outPortNum ed inPortNum. I valori ora assegnati, ovvero 3, sono arbitrari e per cominciare a creare un sistema che funzioni.

La matrice inPortMap è utilizzata per la corrispondeza tra porte utilizzate nei comandi del sistema e porte fisicamente utilizzate.

Quindi presentiamo la prima versione di setup()

void setup() {
   Serial.begin(115200);
  for (int i = 0; i <= outPortNum – 1; i++) {
    pinMode(11 + i, OUTPUT);
    digitalWrite(11 + i, LOW);
    outPortMap[i] = 11 + i;
  }
for (int i = 0; i <= inPortNum – 1; i++) {
    pinMode(8 + i, INPUT);
    inPortMap[i]= 8 + i;
    valueIn[i] = digitalRead(8 + i);
    valueInPrev[i] = valueIn[i];
    valueInVaried[i]     = 1;
  }
 for (int i = 0; i <= ADPortNum – 1; i++) {
    ADPortMap[i]       = i;
    ADPortPrevValue[i] = analogRead(ADPortMap[i]);
    ADPortValue[i]     = ADPortPrevValue[i];
    ADPortVariedValue[i]   = 1;
  }
  establishContact();   
}

Segue il canonico loop()

void loop() {
  aggiornavalueIn(); 
  aggiornavalueAD();
  if (Serial.available() > 0) {
     incomingChar    = Serial.read();
    if (incomingChar == ‘\n’) {
          execCommand();
         incomingString = “”;
    }
    else {
       incomingString += incomingChar; 
      if (incomingString.length() > 254) {
        Serial.print(“Error: “);
        Serial.println(incomingString);
        incomingString = “”;
      }
    }
  }
}

 

Quindi la collezione di funzioni utilizzate:

void establishContact() {
  while (Serial.available() <= 0) {
    delay(100);
  }
  incomingChar = ‘\0’;
  while ( (Serial.available() > 0 )  &&  (incomingChar != ‘\n’) )  {
    incomingChar = Serial.read();
    if ((incomingChar != ‘\n’) && (incomingChar != ‘\r’)) {
      incomingString += incomingChar;
    }
  }
  Serial.print(“Linked with <“);
  Serial.print(incomingString);
  Serial.print(“> mc_ver.: “);
  Serial.println(FIRMWARE_DESCRIPTION);
  incomingString = “”;
  delay(50);
}

void execCommand() {
  incomingString.trim();
  pos = incomingString.indexOf(” “);  // parte da zero
  command = incomingString;
  if (pos == -1) {
    command = incomingString;
    if ((command == “mc_ver”) || (command == “ver”) || (command == “version”)) {
      Serial.print (“0  mc_ver.:  “);
      Serial.println(FIRMWARE_DESCRIPTION);
    }
    else if ((command == “mc_getvaried”) || (command == “getvaried”)) {
      SerialPrintInVariedValue ();
    }
    else if ((command == “mc_getall”) || (command == “getall”)) {
      SerialPrintInAllValue ();
    }
    else if ((command == “mc_getad”) || (command == “getad”)) {
      SerialPrintADAllValue ();
    }
    else if ((command == “mc_help”) || (command == “help”)) {
      PrintHelp();
    }
    else {
      Serial.println(“-1 Error: command unknown.”);
    }

  }
  else {
    command     = incomingString.substring(0, pos);
    command.trim();
    parameters  = incomingString.substring(pos);
    parameters.trim();

    if ((command == “mc_setport”) || (command == “setport”)) {
      setPort();
    }
    else {
      Serial.println(“-1 Error: command unknown.”);
    }
  }
};

// *******************************
void PrintHelp() {
  Serial.println(“0 mc_help:”);
  Serial.println(“——————————————————–“);
  Serial.println(“”);
  Serial.println(“mc_ver|ver|version                 print version”);
  Serial.println(“”);
  Serial.println(“mc_getvaried|getvaried             get port value varied”);
  Serial.println(” “);
  Serial.println(“mc_getall|getall                   get all port value”);
  Serial.println(” “);
  Serial.println(“mc_getad|getad                     get all port AD in value”);
  Serial.println(” “);
  Serial.println(“mc_setport|setport  out<n>=0|1     set port n to value 0|1”);
  Serial.println(“”);
  Serial.println(“”);
  Serial.println(“————————————————“);
}

// *********************************************
void SerialPrintInVariedValue () {
  static int toggle = 0;
  if (toggle)
    toggle = 0;
  else
    toggle = 1;
  for (int i = 0; i <= inPortNum – 1; i++) {
    if ( valueInVaried[i] ) {
      Serial.print(“in”);
      Serial.print(i + 1);
      Serial.print(“=”);
      Serial.print(valueIn[i]);
      Serial.print(“;”);
      valueInVaried[i] = 0;
    };
 }; 
  Serial.println(“”);

}

void SerialPrintInAllValue () {
  for (int i = 0; i <= inPortNum – 1; i++) {
    Serial.print(“in”);
    Serial.print(i + 1);
    Serial.print(“=”);
    Serial.print(valueIn[i]);
    Serial.print(“;”);
  };
   
  Serial.println(“”);
}

void SerialPrintADAllValue () {
 
  for (int i = 0; i <= ADPortNum – 1; i++) {
    Serial.print(“ad”);
    Serial.print(i + 1);
    Serial.print(“=”);
    Serial.print(ADPortValue[i]);
    Serial.print(“;”);  
  };
 
  Serial.println(“”);
}

void SerialPrintADVariedValue () {
  static int toggle = 0;

  for (int i = 0; i <= ADPortNum – 1; i++) {
    if ( ADPortVariedValue[i] ) {
      Serial.print(“ad”);
      Serial.print(i + 1);
      Serial.print(“=”);
      Serial.print(ADPortValue[i]);
      Serial.print(“;”);
    }
  };
 
  Serial.println(“”);
}

// **********************************************
void   aggiornavalueIn() {

  for (int i = 0; i <= inPortNum -1; i++) {
    valueInPrev[i] = valueIn[i];
    valueIn[i] = digitalRead(inPortMap[i]);
    if (valueIn[i] != valueInPrev[i]) {
      valueInVaried[i] = 1;
    };
  }    
};
// **********************************************
void   aggiornavalueAD() {

  for (int i = 0; i <= ADPortNum – 1; i++) {

    ADPortValue[i]         = analogRead(ADPortMap[i]);
 
    if (ADPortPrevValue[i] != ADPortValue[i]) {
      ADPortPrevValue[i]     = ADPortValue[i];
      ADPortVariedValue[i]   = 1;
    }  
  }
    
};

// **********************************************
void   setPort() {   

  // esempio:    setPort out1=1 out2=0 out5=1
  //          che permette agisce su pià porte con un solo comando

  do  { // esiste una prima coppia di parentesi tonde

    // elabora parameters

    // parte funzionante e legge parametri comandi tipo        out3=1
    pos          = parameters.indexOf(“=”);
    portname     = parameters.substring(0, pos);  // leggiamo la prima stringa a sinistra del primo uguale
    portname.trim();
    parameters   = parameters.substring(pos);
    parameters.trim();                            // togliamo tutto fino al primo uguale, questo compreso
    pos          = parameters.indexOf(” “);       // supponiamo che i parametri siano suddivisi da almeno uno spazio
    portvalue    = parameters.substring(1, pos);
    portvalue.trim();
    parameters   = parameters.substring(pos);
    parameters.trim();                            // togliamo tutto fino al primo uguale, questo compreso
    pos          = parameters.indexOf(“=”);

    /*

     // sintassi nuova            port=3 value=1
     //      migliore sintassi ed in liena con qt_micro, ma agisce su una sola porta per comando
     
     portname=””;
     portvalue=””,
     
     parameters.trim();
     pos          = parameters.indexOf(” “);
     tempString1   = parameters.substring(0, pos);  
     tempString1.trim();
     tempString2  = parameters.substring(pos,parameters.length());
     tempString2.trim();
     
     
     if (tempString1.indexOf(“port”)>0){
     portname=tempString1.substring(pos,tempString1.length());
     }else{
     if (tempString2.indexOf(“port”)>0){
     portname=tempString2.substring(pos,tempString2.length());
     }else{
     // Serial.println(“-1 Error mc_setport: syntax not valid.”);
     }
     }    
     portname.trim();
     
     if (tempString1.indexOf(“value”)>0){
     portvalue=tempString1.substring(pos,tempString1.length());
     }else{
     if (tempString2.indexOf(“value”)>0){
     portvalue=tempString2.substring(pos,tempString2.length());
     }else{
     // Serial.println(“-1 Error mc_setport: syntax not valid.”);
     }
     }
     portvalue.trim();
     
     */

    /*    
     Serial.println(“Debug: “);
     Serial.println(pos);
     
     Serial.println(portname);
     Serial.println(portvalue);
     
     Serial.println(portname.length());
     Serial.println(portname.substring(0,3));
     
     Serial.println(“===================”);
     */

    if (  ((portname.length() == 4 ) && (portname.substring(0, 3) == “out”)) && ((portvalue == “0”) || (portvalue == “1”) )) {           // tipo in1

      //tempInt  = portname.charAt(3);

      //tempInt  = tempInt – 48;  // ottenendo n di outn, ma gli indici inziano da 0, quindi:
      //tempInt  = tempInt – 48 -1;

      //  combinando le precedenti

      tempInt  = portname.charAt(3) – 49;

      // ovvero

      port = outPortMap[portname.charAt(3) – 49];

      /*
      Serial.print(“Debug – pin: “);
       Serial.println(port);
       Serial.println(“===================”);
       */

      if ((tempInt >= 0) && (tempInt <= outPortNum)) {

        if (portvalue == “0”) {
          /*
          Serial.print(“Debug: “);
           Serial.println(port);
           Serial.println(portvalue);
           Serial.println(“===================”);
           */
          digitalWrite(port, LOW);
          Serial.println(“0  Success mc_setport”);
        }  
        else {
          /*
          Serial.print(“Debug: “);
           Serial.println(port);
           Serial.println(portvalue);
           Serial.println(“===================”);
           */
          digitalWrite(port, HIGH);
          Serial.println(“0  Success mc_setport”);
        }
      }
      else {          //   Errore:
        Serial.println(“-1 Error mc_setport: port not valid.”);
      }
      ;  // if
    }
    else { //   Errore:
      Serial.println(“-1 Error mc_setport: syntax not valid.”);
    }; // if ; // if

  }
  while (pos > -1);

}  // setPort
void   getPort() {   // getPort   p
  pos = parameters.indexOf(” “);
  while ( pos > 0 ) {
    parameter   = parameters.substring(0, pos);
    parameter.trim();
    parameters  = parameters.substring(pos);
    parameters.trim();
    if (parameter.length() == 1 ) {
      parameter.setCharAt(1, tempChar);
      tempChar = tempChar – 48;
      if ((tempChar >= 0) && (tempChar <= 7)) {
        port     = tempChar + 22 ;
      }
    }
    else {
      //   Error:
    };
  };
}

 

A questo punto, connessa la propria scheda compatibile Arduino, avviato arduino con i privilegi di root, selezionata dal menu della IDE scheda e porta, copiato ed incollato il codice, controllato il proprio ino che non contenga errori da copia incolla o comunque banali, compilati e caricato nella scheda, avviato il terminale configurato con A capo (NL) e 115200 baud, si esegua il primo comando:

link
ricevendola risposta che la scheda è connessa, quindi si invii

help

oppure altri comandi come suggeriti dal help.

Si connetta uno shield Grove e, per esempio, l’attuatore relè ed il pulsante, quindi si inviino i comandi per accendere e spegenre il relè e per leggere lo stato del pulsante.

In caso di difficoltà si consulti il sito ufficiale Arduino e i suoi ottimi forum.

Nella situazione attuale stiamo gestendo a mano la scheda, il microserver provvederà alla gestione automatica con riferimento a tabelle di comandi e di  stati. La scheda è semplicemente il tramile verso altre schede, attuatori e sensori. La maggior parte dei processi viene demandata al microserver per allegerire il più possibile le schede, che sono dotate di CPU piccole.

Concludiamo lasciando un account di contatto per i progetti trattati:

automazione@ubuntusistemi.it

 Ringraziamo per ogni forma di contributo e proposta.