Il modulo Sonoff della Itead è un relè WiFi dal costo contenuto, può essere collegato direttamente a 220V permettendo di alimentare sia il modulo che il carico pilotato dal relè. Nelle ultime versioni del software la Itead ha introdotto la modalità DIY che ci permette di gestire i dispositivi della famiglia Sonoff senza transitare dal cloud eWeLink comandandoli in locale con connessione WiFi utilizzando le REST API.
Per attivare la modalità DIY occorre aprire il dispositivo ed inserire il ponticello nei due pin esposti.

Configurazione dispositivo
Ora occorre attivare sullo smartphone la modalità hotspot (Trasformando il telefono in un router WiFi) creando una rete WiFi con SSID sonoffDiy e con password 20170618sn (Attenzione alle lettere maiuscole e minuscole). Alla accensione il dispositivo si connetterà immediatamente alla rete WiFi creata, a conferma dell'avvenuta connessione il led blu lampeggerà 2 volte consecutive continuamente.
Ora utilizzando un PC Windows scaricare ed installare il Tool 01DIY85(v3.3.0).exe (In Internet troverete svariati link da dove scaricarlo). Installato il programma si dovrà connettere il PC alla stessa rete WiFi dell'hotspot creato con il cellulare.
Eseguendo il tool ci si troverà una schermata come quella riportata in basso, da dove è possibile configurare il dispositivo. Nella prima casella bordata in rosso compare l'identificativo del dispositivo, è importante annotarsi questo numero perchè servirà per l'accesso al dispositivo.
Selezionando il dispositivo ed agendo sul tasto Change SSID Password è possibile configurare la rete WiFi a cui connetterlo. Indicando i parametri della propria rete WiFi il dispositivo si sconnetterà dall'hotspot e si connetterà alla rete definita. Ora sarà possibile riconnettere alla stessa rete anche il PC e se tutto è corretto nel programma comparirà nuovamente il dispositivo e sarà possibile comandarne il relè e configurarlo.
Purtroppo il programma non ci dà alcuna informazione sull'indirizzo IP assegnato al dispositivo, per individuarlo ho eseguito una scansione della rete.

Invio comandi REST
A questo punto è possibile testare il funzionamento inviando direttamente i comandi REST dal browser Chrome, per fare questo occorre installare l'App Advanced REST client che permette di gestire l'invio di comandi HTTP in modalità POST. La descrizione dei comandi REST è disponibile in file pdf dal repository del fornitore. Per inviare i comandi occorre indicare l'ID dispositivo così come visualizzata nel DIY Tool.
Tutti comandi e le risposte sono in formato JSON, non stò ad elencare tutti i possibili comandi, riporto solo i comandi per attivare/disattivare il relè. Inviando in POST all'indirizzo http://xxx.xxx.xxx.xxx:8081/zeroconf/switch la stringa {"deviceid": "10***4**d4", "data":{"switch": "on"}} attiveremo il relè, inviando la stringa {"deviceid": "10***4**d4", "data":{"switch": "off"}} disattiveremo il relè.
Esempi
ST_SonOffDIY: Viene gestito un dispositivo SonOff utilizzando le API REST, è possibile da debug agire sulle variabili indicate per comandare il relè sul dispositivo. Ogni 10 secondi il programma invia il comando REST di lettura stato ed acquisisce lo stato del relè.
SonOffByFB: Come nel programma precedente viene gestito un dispositivo SonOff utilizzando le API REST, a gestione è realizzata tramite un blocco funzione (Di cui è fornito il programma sorgente). Il FB è realizzato in modo da permetterne la gestione in cascata per poter gestire più dispositivi SonOff.
PROGRAM ST_SonOffDIY VAR i : UDINT; (* Auxiliary variable *) SpyOn : BOOL; (* Attiva spionaggio *) RCommand : BOOL; (* Relay command *) RStatus : BOOL; (* Relay status *) DOk : BOOL; (* Device Ok *) Sequence : UDINT; (* Sequence letta dal dispositivo *) ErrorNr : UDINT; (* Error number *) RCPulse : BOOL; (* Relay command pulse *) DError : UDINT; (* Device error *) Errors : UDINT; (* Service errors *) PSent : UDINT; (* Packets sent *) HTTPRq : HTTPClient_v1; (* HTTP client *) JDecode : JSONDecode_v2; (* JSON decode *) CaseNr : USINT; (* Case gestione *) Result : STRING[ 256 ]; (* Result string *) Request : STRING[ 64 ]; (* Request string *) TimeBf : UDINT; (* Time buffer (uS) *) Ptr : ARRAY[0..2] OF @BYTE; (* Auxiliary pointer *) RValue : STRING[ 8 ]; (* Relay value da JSON *) JData : STRING[ 256 ]; (* JSON data received *) DAddress : @STRING; (* Device address *) DeviceID : @STRING; (* Device ID *) END_VAR // ***************************************************************************** // PROGRAM "ST_SonOffDIY" // ***************************************************************************** // Viene gestito un dispositivo SonOff in modalità DIY. // // Comandi gestibili da debug. // SpyOn: BOOL, Attiva spionaggio // RCommand: BOOL, Relay command // // Stati gestibili da debug. // DOk: BOOL, Device Ok // RStatus: BOOL, Relay status // DError: UDINT, Device error // PSent: UDINT, Packets sent // Errors: UDINT, Service errors // ----------------------------------------------------------------------------- // ------------------------------------------------------------------------- // INIZIALIZZAZIONI // ------------------------------------------------------------------------- // Eseguo inizializzazione. IF (SysFirstLoop) THEN // Parametrizzo FB HTTPClient. HTTPRq.SpyOn:=SpyOn; //Activate the spy HTTPRq.Method:=1; //Request method, POST HTTPRq.HostAddress:=ADR('192.168.1.38'); //Indirizzo dispositivo HTTPRq.HostName:=HTTPRq.HostAddress; //Hostname HTTPRq.HostPort:=8081; //Server port HTTPRq.Request:=ADR(Request); //Request string HTTPRq.DBSize:=256; //JData buffer size HTTPRq.Timeout:=2000; //Timeout time (mS) // Definizione parametri. DeviceID:=ADR('1000a456d4'); //Device ID END_IF; // ------------------------------------------------------------------------- // ESEGUO GESTIONE ERRORE // ------------------------------------------------------------------------- // Gestione errore di esecuzione. IF (ErrorNr <> 0) THEN IF (SpyOn) THEN IF NOT(SysSpyData(0, 0, 0, 0)) THEN RETURN; END_IF; i:=SysVarsnprintf(ADR(Result), SIZEOF(Result), 'Error:%08d', UDINT_TYPE, ADR(ErrorNr)); i:=SysLWVarsnprintf(ADR(Result), SIZEOF(Result), ', On Case:%d', USINT_TYPE, ADR(CaseNr)); i:=SysSpyData(SPY_ASCII, 16#40000000, ADR('Er'), ADR(Result)); END_IF; // Incremento counter errori. Errors:=Errors+1; //Service errors ErrorNr:=0; //Error number DOk:=FALSE; //Device Ok CaseNr:=0; //Case gestione RETURN; END_IF; // ------------------------------------------------------------------------- // ESEGUO GESTIONE HTTP CLIENT // ------------------------------------------------------------------------- // Eseguo gestione HTTP client e controllo se errore. HTTPRq(); //HTTP client IF (HTTPRq.Fault) THEN HTTPRq.Enable:=FALSE; ErrorNr:=200; RETURN; END_IF; // Acquisizione dati ricevuti, li trasferisco in stringa. IF ((HTTPRq.HPSelector) AND (HTTPRq.DBChars <> 0)) THEN IF ((Sysstrlen(ADR(Result))+HTTPRq.DBChars) < SIZEOF(Result)) THEN i:=Sysmemmove(ADR(Result)+Sysstrlen(ADR(Result)), HTTPRq.DBAddress, HTTPRq.DBChars); END_IF; END_IF; // ------------------------------------------------------------------------- // GESTIONE SEQUENZE // ------------------------------------------------------------------------- // Case gestione sequenze programma. CASE (CaseNr) OF // --------------------------------------------------------------------- // INIT GESTIONE // --------------------------------------------------------------------- // Controllo se variazione comando relè. 0: HTTPRq.Enable:=FALSE; //HTTP get page enable IF (RCommand <> RCPulse) THEN RCPulse:=RCommand; //Relay command pulse PSent:=PSent+1; //Packets sent CaseNr:=10; //Case gestione RETURN; END_IF; // Controllo timeout controllo informazioni. IF ((SysGetSysTime(TRUE)-TimeBf) > 10000000) THEN PSent:=PSent+1; //Packets sent CaseNr:=20; //Case gestione RETURN; END_IF; // --------------------------------------------------------------------- // INVIO COMANDO RELE' // --------------------------------------------------------------------- // Richiesta informazioni. // http://xxx.xxx.xxx.xxx:8081/zeroconf/switch // POST: {"deviceid": "nnnnnnnn", "JData":{"switch": "on"}} 10: i:=Sysmemset(ADR(Request), 0, SIZEOF(Request)); //Empty request i:=Sysmemset(ADR(Result), 0, SIZEOF(Result)); //Empty result IF NOT(RCommand) THEN RValue:='off'; ELSE RValue:='on'; END_IF; i:=SysVarsnprintf(ADR(Request), SIZEOF(Request), '{"deviceid": "%s",', STRING_TYPE, DeviceID); i:=SysLWVarsnprintf(ADR(Request), SIZEOF(Request), '"JData": {"switch": "%s"}}', STRING_TYPE, ADR(RValue)); HTTPRq.Page:=ADR('zeroconf/switch'); //Web page HTTPRq.Enable:=TRUE; //HTTP get page enable CaseNr:=CaseNr+1; //Case gestione // --------------------------------------------------------------------- // Se fine esecuzione controllo stato ritornato. Il numero di sequenza // si incrementa ad ogni invio di comando. // {"seq": 13, "error": 0} 11: IF NOT(HTTPRq.Done) THEN RETURN; END_IF; HTTPRq.Enable:=FALSE; //HTTP get page enable // Controllo se ricevuto risposta da dispositivo. // Decodifico valori da stringa JSON ricevuta. IF NOT(HTTPRq.PLoad) THEN ErrorNr:=1000; RETURN; END_IF; JDecode(Object:=ADR(Result), Name:=ADR('seq'), VType:=UDINT_TYPE, VAddress:=ADR(Sequence), Count:=1); IF (JDecode.ECode <> 0) THEN ErrorNr:=1001; RETURN; END_IF; JDecode(Object:=ADR(Result), Name:=ADR('error'), VType:=UDINT_TYPE, VAddress:=ADR(DError), Count:=1); IF (JDecode.ECode <> 0) THEN ErrorNr:=1002; RETURN; END_IF; CaseNr:=20; //Case gestione (Eseguo lettura informazioni) // --------------------------------------------------------------------- // RICHIESTA INFORMAZIONI // --------------------------------------------------------------------- // Richiesta informazioni. // http://xxx.xxx.xxx.xxx:8081/zeroconf/info // POST: {"deviceid": "nnnnnnnn","JData": {}} 20: i:=Sysmemset(ADR(Request), 0, SIZEOF(Request)); //Empty request i:=Sysmemset(ADR(Result), 0, SIZEOF(Result)); //Empty result i:=SysVarsnprintf(ADR(Request), SIZEOF(Request), '{"deviceid": "%s","JData": {}}', STRING_TYPE, DeviceID); HTTPRq.Page:=ADR('zeroconf/info'); //Web page HTTPRq.Enable:=TRUE; //HTTP get page enable CaseNr:=CaseNr+1; //Case gestione // --------------------------------------------------------------------- // Se fine esecuzione controllo stato ritornato. // {"seq":2,"error":0,"JData":"{\"switch\":\"off\",\"startup\":\"off\",\"pulse\":\"off\",\"pulseWidth\":500,\"ssid\":\"Elsist WiFi\",\"otaUnlock\":false}"} 21: IF NOT(HTTPRq.Done) THEN RETURN; END_IF; HTTPRq.Enable:=FALSE; //HTTP get page enable TimeBf:=SysGetSysTime(TRUE); //Time buffer (uS) // Controllo se ricevuto risposta da dispositivo. // Decodifico valori da stringa JSON ricevuta. IF NOT(HTTPRq.PLoad) THEN ErrorNr:=2000; RETURN; END_IF; JDecode(Object:=ADR(Result), Name:=ADR('seq'), VType:=UDINT_TYPE, VAddress:=ADR(Sequence), Count:=1); IF (JDecode.ECode <> 0) THEN ErrorNr:=2001; RETURN; END_IF; JDecode(Object:=ADR(Result), Name:=ADR('error'), VType:=UDINT_TYPE, VAddress:=ADR(DError), Count:=1); IF (JDecode.ECode <> 0) THEN ErrorNr:=2002; RETURN; END_IF; JDecode(Object:=ADR(Result), Name:=ADR('JData'), VType:=STRING_TYPE, VAddress:=ADR(JData), Count:=1, Size:=SIZEOF(JData)); IF (JDecode.ECode <> 0) THEN ErrorNr:=2003; RETURN; END_IF; // Il valore di "JData" è un oggetto JSON ritornato come stringa. // Al simbolo " è anteposto "\" ne eseguo eliminazione. Ptr[0]:=ADR(JData)+Sysstrlen(ADR(JData)); //Auxiliary pointer FOR Ptr[1]:=ADR(JData) TO Ptr[0] DO Ptr[2]:=Ptr[1]+1; //Auxiliary pointer IF ((@Ptr[1] = 16#5C) AND (@Ptr[2] = 16#22)) THEN i:=Sysmemmove(Ptr[1], Ptr[2], Ptr[0]-Ptr[2]); END_IF; IF (@Ptr[1] = 16#7D) THEN @Ptr[2]:=0; END_IF; END_FOR; // Acquisizione stato relè. JDecode(Object:=ADR(JData), Name:=ADR('switch'), VType:=STRING_TYPE, VAddress:=ADR(RValue), Count:=1, Size:=SIZEOF(RValue)); IF (JDecode.ECode <> 0) THEN ErrorNr:=2004; RETURN; END_IF; RStatus:=TO_BOOL(RValue = 'on'); // Se acquisito valore inviato da dispositivo setto Ok. DOk:=TRUE; //Device Ok CaseNr:=0; //Case gestione END_CASE; // [End of file]