sâmbătă, 20 februarie 2016

Triplu stecher controlat prin internet (Partea I)


Pentru inceput...

     In articolul trecut am prezentat o modalitate simpla de a controla 4 leduri cu ajutorul unei interfete web, folosind numai html si css. 
In cele urmeaza voi ridica un pic stacheta si voi adauga proiectului cateva elemente grafice, precum si posibilitatea de a trimite clientului, in  functie de calitatea  acestuia (admin sau guest),  pagini html diferite. 

     Proiectul propus va controla 3 prize alimentate la 220V. Fiecare din cele trei prize va primi un nume si nu va putea fi controlata decat de clientul "logat" ca  administrator al sistemului. Oaspetii vor avea dreaptul de a vizualiza  starea sistemului, fara insa de a putea interactiona in vreun fel cu acesta.

     Asadar sa incepem... Ce a iesit se poate vedea in filmuletul de mai jos:



Elemente grafice...

     Am gasit ca unic mod de a trimite clientului, in interiorul paginii html fisiere cu imagini, stocarea acestora pe un server extern, asa ca am inregistrat un domeniu (gratuit) pe "www.hostinger.ro". Dupa inregistrare am beneficiat de spatiu de stocare dar... si de un server html complet. Ideea mea a fost ca toate resursele:
  • cod JavaScript;
  • stiluri de formatare CSS;
  • imagini (.png, .jpg)
sa fie accesate dintr-un mediu extern. Acest fapt aduce cu sine nenumarate avantaje din care voi enumera doar cateva:
  • NodeMcu trimite numai cod html, din acest motiv viteza de transfer a datelor catre client este mult mai mare;
  • in cazul in care codul JavaScript necesita ajustari, acestea pot fi facute rapid nefiind necesara reincarcrea intregii pagini in memoria controller-ului (timp foarte mare de incarcare);
  • la fel in cazul stilurilor de formatare;
  • se pot trimite imagini clientului.
  • ORICINE POATE FOLOSI RESURSELE AFLATE PE SERVERUL WEB.
Functionare...

     In NodeMcu sunt incarcate trei pagini - fiecare cu fiserul JavaScript aferent (incarcat pe serverul extern) :
  • index.html - pagina de start;
  • guest.html - pagina ce va fi afisata doar "oaspetilor";
  • admin.thml - pagina ce va fi afisata administratorului;
Index.html:




     Dupa ce a fost introdusa in browser adresa IP a controller-ului va fi afisata pagina de mai sus. Daca utilizatorul cunoaste codul de acces (stocat in memoria NodeMcu) se poate "loga" ca admin. In continuare vor fi afisate campurile prin care se cer numele aparatelor conectate la prize. Apsand butonul "Submit"...

Admin.html:


     Browser-ul va afisa pagina de mai sus. Dupa cum se observa fiecare priza a capatat o reprezentare grafica (nu am talente artistice... am facut si eu cum m-am priceput). In cazul de fata nefiind cunoscuta starea sistemului poza care descrie starea sistemului este reprezentata cu un semn de intrebare. Apasand butonul "GET STATUS" va fi afista pagina urmatoare:


     Asadar, in cazul de fata toate prizele sunt oprite. In caseta text "LOG CONSOLE"  sunt afisate datele sistemului: comenzile date, numele dispozitivelor conectate etc. Daca dorim sa comandam o priza nu trebuie decat sa face un simplu click pe switch-ul aferent acesteia:


     Se observa ca imaginea care descrie starea prizei se schimba, iar consola inregistreaza activitatea.

   Sa vedem acum ce se intampla daca un oaspete doreste sa utilizeze sistemul. 


Guest.html:




Apasand butonul "Login as guest" va fi afisata o pagina identica cu cea a admin-ului. Dupa apasarea butonului "GET STATUS":


     Daca "oaspetele" doreste sa porneasca/opreasca una din cele 3 prize, consola va afisa urmatorul mesaj:




Programarea...

      NodeMcu

     Codul scris pentru NodeMcu este prezentat in caseta de mai jos. Fata de programul din articolul anterior au aparut cateva modificari, insa consider ca nu este nevoie sa fie explicate in detaliu. Schita, si fisierele html  pot fi descarcate de aici.

Dupa descarcare fisierul .zip va fi dezarhivat  in folderul "Sketchbook". Pentru a incarca fisierele html in memoria controller-ului se va utiliza metoda descrisa in articolul precedent.

Codul JavaScript, si imaginile sunt preluate automat de pe domeniul inregistrat de catre mine.


/////////////////////////////////////////////////////////////////////////////
//LOCAL NETWORK  SERVER WITH NODEMCU                                      //
//PETRESCU CRISTIAN                                                      //
//////////////////////////////////////////////////////////////////////////
//file system reference:
//http://arduino.esp8266.com/versions/1.6.5-1160-gef26c5f/doc/reference.html#file-system-object-spiffs
//html and java sript reference:
//http://www.w3schools.com/
//check my blog :www.hobbyelectro.blogspot.ro

#include "FS.h"
#include <ESP8266WiFi.h>
int socketState[3] = {0, 0, 0};
int sk[4] = {D0, D1, D2};
String buff;
String dev = "SK1/SK2/SK3/z";
String pwd="test1234";// the password for logging as administrator
String stateOn = "on/";
String stateOff = "off/";
const char* ssid = "********";//your ssid
const char* password = "*******";//your wifi network password
WiFiServer server(80);


////////////////////////////////////////////////////////////////////////////
//this function builds the string which will be sent to the client       //
//it will transform to a string the array which holds the states of     //
//the plugs.                                                           //
////////////////////////////////////////////////////////////////////////

String responseBuilder() {
  String buff = "";
  for (int i = 0; i < 3; i++) {
    buff.concat(String(socketState[i]));//add state of led to the response string
    buff.concat("/");
  }
  return buff;
}


////////////////////////////////////////////////////////////////////////////
//send the html page to client                                           //
//////////////////////////////////////////////////////////////////////////
void printPage(WiFiClient k, String page) {
  File f = SPIFFS.open(page, "r");//open html page for reading
  if (!f) {
    Serial.println("file open failed");// check if file exists
    return;
  }
  buff = f.readString();// read all file at once
  f.close();// close the file
  // send data to browser in 2 packets of 2KB each
  k.print(buff.substring(0, 2000));//send first 2000chars to browser
  k.print(buff.substring(2000));//send second 2000chars to browser
  //k.print(buff.substring(4000));// send the rest of file to browser
}
////////////////////////////////////////////////////////////////////////////
//check if client wants to read the states of sk or want to change them //
//////////////////////////////////////////////////////////////////////////
void checkRequest(WiFiClient k, String request) {// change the states of the plugs
  //check if request it's a command
  if (request.indexOf("data") != -1) {
    request.remove(0, request.lastIndexOf('/') + 1);
    //Serial.println(request);
    int i = request.toInt();
    if (socketState[i] == 0) {
      socketState[i] = 1;
      digitalWrite(sk[i], socketState[i]);
      //Serial.println(String(responseBuilder()+String(i)+"/on/z"));
      k.println(String(responseBuilder() + String(i) + "/on/z")); // send response to client
      return;
    }
    if (socketState[i] == 1) {
      socketState[i] = 0;
      digitalWrite(sk[i], socketState[i]);
      //Serial.println(String(responseBuilder()+String(i)+"/off/z"));
      k.println(String(responseBuilder() + String(i) + "/off/z")); // send response to client
      return;
    }
  }

  if (request.indexOf("status") != -1) {//check if client wants to read the state of sk
    //Serial.println(String(responseBuilder()+dev));
    k.println(String(responseBuilder() + dev)); // send response to client
    return;
  }
}


void response(WiFiClient k) {
  k.println("HTTP/1.1 200 OK");
  k.println("Content-Type: text/html");
  k.println(""); //  do not forget this one
  String request = k.readStringUntil('H');//read request
  //Serial.println(request);
  //check if request

  if(request.indexOf("guest") != -1){// check if the clinets wants to log in as guest
    printPage(k,"/guest.html");
    return;
    }


  if(request.indexOf("pwd")!=-1){// check if client wants to log in as admin
    request.remove(0, 5);
    int j=request.indexOf("/");
    //Serial.println(request);
    //Serial.println(request.length());
    String str=request.substring(0,j);
    //Serial.println(str);
    if(str==pwd){//check if password sent by client matches the passwor defined
      Serial.println(pwd);
      k.println("ok");
      }
    return;
    }

  if(request.indexOf("ok")!=-1){//the client is logged as admin
    request.remove(0, 5);
    //Serial.println(request);
    dev=request.substring(0,request.lastIndexOf("/"));// read the names of the devices
    //Serial.println(dev);
    if(dev.indexOf("%20")!=-1){//repalce "%20" with space
        dev.replace("%20"," ");
      }
    //Serial.println(dev);
    printPage(k,"/admin.html");// send the admin.html page file
    return;
    }
  if(request.indexOf("status")!=-1||request.indexOf("data")!=-1){//check if client wants to read or change states
    request.remove(0, 5);
    checkRequest(k, request);
    return;
    }
    printPage(k,"/index.html");// send the index.html file
}

void setup() {
  SPIFFS.begin();//start spiffs (spi file system)
  Serial.begin(115200);// start serial session
  Serial.println();
  int i;
  // set led pins as output
  for (i = 0; i < 3; i++) {
    pinMode(sk[i], OUTPUT);
    digitalWrite(sk[i], 0);
  }
  Serial.println();
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);
  // Connect to WiFi network
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected");

  // Start the server
  server.begin();
  Serial.println("Server started");

  // Print the IP address
  Serial.print("Use this URL to connect: ");
  Serial.print("http://");
  Serial.print(WiFi.localIP());
  Serial.println("/");
}

void loop() {
  // Check if a client has connected
  WiFiClient client = server.available();
  if (!client) {
    return;
  }
  //analyze data received from client
  response(client);
  client.flush();
  delay(1);
  //Serial.println("Client disonnected");
  //Serial.println("");
}


In articolul urmator voi incerca sa realizez si partea practica.

Sa auzim de bine !`


11 comentarii:

  1. chiar cautam asa ceva. am bagat blogul tau la fav. pt ca am vazut ca mai ai si chestii interesante. sa auzim de bine.

    RăspundețiȘtergere
  2. am vrut sa scriu, "mai ai si alte chestii interesante"

    RăspundețiȘtergere
  3. Salut, foarte frumos prezentat, la aceste prize s-ar putea pune si o temporizare care sa poata fi setata din pagina, adica dupa pornire sa stea timpul setat din pagina si la final sa opreasca priza automat???

    RăspundețiȘtergere
  4. Da se poate.M-am gandit si eu la asta. Probabil intr-un articol viitor voi incerca sa fac modificarea. Ideea este ca ar trebui utilizat un RTC (real time clock) pentru siguranta.

    RăspundețiȘtergere
  5. Da se poate.M-am gandit si eu la asta. Probabil intr-un articol viitor voi incerca sa fac modificarea. Ideea este ca ar trebui utilizat un RTC (real time clock) pentru siguranta.

    RăspundețiȘtergere
  6. ar fi ok daca ai putea face ca si paginile html sa fie pe server. o idee la ce ma gandeam eu, nodemcu sa trimita datele la serverla un anumit interval, serverul le salveaza, si cand accesezi pagina se stie deja cum sunt prizele. cand modifici prin AJAX sau javascript sa se trimita la nodemcu sa faca modificarile. la final in nodemcu nu mai ai pagini html si chestii care sa il ingreuneze el va trimite starea prizelor si cand ii se transmite va modifica starea lor

    RăspundețiȘtergere
  7. Pai in cazul de fata NodeMcu este serverul. Ceea ce sugerezi tu este o comunicatie intre 2 servere: NodeMcu pe de o parte si serverul html pe de alta.
    Probabil s ar putea face dar nu fara un program server side impreuna cu Json.
    Momentan sunt la inceput. Dar ideea ta merita luata in calcul.

    RăspundețiȘtergere
  8. Acest comentariu a fost eliminat de autor.

    RăspundețiȘtergere
  9. cu arduino le am cat de cat dar pe partea de retea si eu sunt la inceput, o sa mai studiez si eu cand am timp liber. toate cele bune

    RăspundețiȘtergere
  10. In primul rand as vrea sa te felicit pentru ceea ce faci si sa-ti multumesc ca ai facut posibil ca aceste informatii sa ajunga si la noi.

    Din dorinta de a mai descoperi lucruri noi, mi-am propus sa fac un sistem de automatizare care sa fie capabil controleze diverse device-uri de prin casa si sa verifice valorile unor sensori. (facand un pic de research am descoperit si acest valoros blog)
    Ca sa fie extensibil si un pic mai safe (sa poti pune un client de vpn in cazul in care vrei sa accessezi din extern, mi-a venit ideea sa folosesc un Raspberry pi ca si HUB care sa comunice cu mai multe device-uri nodemcu.
    RaspberryPi- actioneaza ca un server (pe el am o baza de date si un server apache +php)
    Momentan prin intermediul unui simplu script php citesc valorile sensorului de temperatura nodemcu si le stochez in baza de date.
    Urmatorul pas este sa fac si o simpla interfata.
    Desi parea simplu, m-am blocat la urmatoare chestie, comunicarea intre servere :)
    Practic as vrea ca la apasarea unui buton din pagina web (de pe server-ul raspberrypi, sa trimit un request catre serverul nodemcu).

    Din cate am vazut exista acest ajax, care face posibila comunicarea asyncron (cam ce ai folosit tu intr-un articol anterior (Server senzori cu NodeMcu) . Insa din pacate nu prea sunt prea am reusit sa inteleg daca e suficient sa adaug crossOrigin: true ca sa functioneze si comunicarea cu alt server.

    O alta varianta ar mai fi sa comunic prin intermediul unui script php. html trimite request catre phop script, si aici sunt 2 variante:
    - ori modific statusul in baza de date si nodemcu citeste statusul prin intermediul unui alt script php ( in cazul asta nodemcu este doar un client care trimite request-uri si citeste raspunsul)
    -ori folosesc o fopen(urlnodmcu,r) ca sa trimit request la nodemcu (insa aici nodemcu e si server)

    Orice sugestie e bine venita :)

    Numai bine,
    Adrian

    RăspundețiȘtergere