mercoledì 30 dicembre 2015

Controllare Lonhand USR-WP1 con Python






Controllare Lonhand USR-WP1 con Python


L' IoT, Internet of Things si sta concretizzando ogni giorno di più. Presto sarà normale che qualsiasi elettrodomestico sia in grado, tramite Internet, di essere raggiungibile dal suo proprietario che potrà avere informazioni sul suo stato, avviarlo/spegnerlo e così via. Nel frattempo tramite piccoli apparecchi come questo USR-WP1 è possibile comandare da remoto l'accensione e lo spegnimento di altri apparecchi che attualmente l'IoT nemmeno sanno cosa sia. 

Vuoi mettere avviare le luci dell'albero di Natale da cellulare stando seduto comodamente sul divano, oppure mentre stai rincasando la sera? :-) O la lavatrice mentre sei al lavoro? O accendere e spegnere la lampada del soggiorno per dare l'impressione che ci sia qualcuno in casa? 

L'USR-WP1 è una scatolina che va collegata direttamente alla presa a muro e che a sua volta dispone di una presa a cui collegare l'apparecchio da comandare. Regge carichi fino a 2200W (sarà vero?), va collegata alla propria rete casalinga via WiFi ed è relativamente facile da configurare. Dispone di un pulsante frontale che consente l'accensione/spegnimento di ciò che vi si collega, ma la sua funzione primaria è naturalmente quella di consentire accensione e spegnimento da remoto tramite apposita app scaricabile su smartphone o via linguaggio di programmazione. Non costa moltissimo, con un po' di fortuna si trova l'offerta giusta sulla E-Baia tra i 18 ed i 30 euro.





Il manuale fornito è in sola lingua inglese; l'unica difficoltà se non si ha molta confidenza con la lingua potrà essere il momento di prima configurazione: per farla occorre scaricare l'app sul proprio smartphone dal sito del produttore (per i cellulari Android occorre disabilitare le impostazioni di sicurezza che impediscono l'installazione da fonti diverse dal Play Store) e collegare l'USR-WP1 alla rete elettrica. Appena acceso l'apparecchio si avvierà di default in modalità wifi AP creando una propria rete che non necessita di password di connessione, il cui nome sarà (doh!...) USR-WP1. Occorre quindi procedere con lo smartphone alla scansione delle reti disponibili, individuare la rete USR-WP1 e collegarvisi. Una volta connessi basterà avviare l'app che permetterà di portare a termine la configurazione dell'USR-WP1 (essenziale sarà indicargli la password della rete wifi casalinga per consentire all'apparecchio di connettervisi). 
Al termine l'apparecchiatura si riavvierà, smetterà di fungere da AP e si collegherà automaticamente alla rete wifi di casa, pronta a ricevere dallo smartphone i comandi di accensione/spegnimento inviati dall'app.
E' possibile inoltre rendere accessibile l'apparecchio da Internet tramite cloud (altrimenti sarà raggiungibile solo dall'interno della propria rete casalinga). 
Può essere inoltre programmato come timer: dispone infatti di una memoria interna presso la quale salvare data, ora e durata di attivazione per diversi eventi ON/OFF, compresi cicli quotidiani e settimanali.

Certo che quest'ultima funzione sarebbe molto più flessibile se fosse possibile gestire accensione e spegnimento grazie ad un pc che invii i comandi alla bisogna, magari un minipc tipo Raspberry Pi che non consuma nulla, costa pochissimo e si incarica di gestire i cicli ON/OFF a seconda degli utilizzi più disparati: timer per le luci natalizie? Irrigazione del giardino? Luce per il portico?
Il minipc potrebbe rilevare da sensori esterni lo stato dell'umidità del prato per decidere quando attivare la pompa di irrigazione, oppure la luminosità ambientale prima di accendere la lampada in salotto. 
Oppure ancora si potrebbe programmare come timer molto più efficiente per gestire le luci di Natale (la notte di Natale le lasciamo accese tutta la notte...), o come già accennato creare un "effetto presenza" accendendo e spegnendo a ritmi opportuni la lampada del salotto dalle ore xx:xx del venerdì alle ore xx:xx del lunedì. Oppure millemila altre idee!... Basterà inserire gli eventi e gli orari di attivazione nel (mini)pc ad esempio in CRON, così all'ora opportuna esso invierà via rete i comandi alla USR-WP1.

Ma come far "parlare" tra loro il minipc e USR-WP1?  Non è difficile:



Python, USR-WP1 e GPIO

Premessa: poiché a me "basta che funzioni", l'eleganza nelle soluzioni la lascio volentieri a chi ne abbisogna.  Non per una questione di sciatteria ma per il fatto che spesso l'eleganza richiede tempo...
...Che non ho. :-)


Ciò che mi aspettavo (...speravo...) di trovare come modo di comunicare in rete con questa macchinetta era qualcosa di simile alle pagine di configurazione della grande maggioranza dei routers wifi che abbiamo in casa: connettersi in HTTP all'IP del router ed iniziare una sequenza di URL opportunamente formate, tipo:
http://192.168.1.1/admin?user=utente&password=ciao&command=accendi   
Questo tipo di utilizzo delle funzioni di un apparecchio è molto comune, ad esempio, per configurare o riprodurre il video proveniente da una webcam IP. Abbiamo un esempio qui.  

Invece no, per far parlare questa scatolina con la nostra Raspberry Pi occorre leggersi il manuale di programmazione dei suoi GPIO (ingressi/uscite) scaricabile nella zona Download della pagina del produttore. Il link al file viene oltretutto segnalato come malevolo dal browser, ma (tralasciando le disquisizioni sulla sicurezza di questo genere di file) si tratta pur sempre soltanto di un file PDF... Forse ad essere malevola è stata la concorrenza che ha segnalato come malevolo il link. Mah?

Il manuale GPIO è in (quasi) inglese, diciamo Cino-Inglese, ed è molto tecnico e te lo risparmio se ciò che intendi fare è solo accendere o spegnere la presa da remoto con Python. Per accedere ad altre funzionalità però è indispensabile... Quindi se proprio devi farti del male vai e scaricalo. :-) 

Si tratta di inviare codici esadecimali alla USR-WP1 per comunicarle le nostre intenzioni, a me per ora interessa solo dirle accenditi o spegniti
Esiste naturalmente un ordine particolare da utilizzare per i codici da inviare, ma se ci sono arrivato io significa che si può fare.

Dai che si va. Come già detto ho utilizzato una Raspberry Pi, esattamente il modello B+ che è più che sufficiente per questo genere di utilizzo, ma andranno sicuramente bene anche modelli inferiori o superiori. In sostanza va bene qualsiasi tipo di computer purché vi installiamo Python.

Invieremo i comandi direttamente alla porta TCP 8899 del USR-WP1, lo faremo utilizzando la libreria Python socket. I comandi sono formati ciascuno da una stringa di bytes in esadecimale, ai quali la USR-WP1 risponderà in modo appropriato (o non risponderà affatto se sono calcolati male...).  Risponderà sia attuando quanto richiesto, sia tramite un messaggio di risposta sulla stessa porta TCP, che potremo utilizzare per determinare da remoto se il comando è stato eseguito con successo o no. 

NOTA: Si presuppone che la USR-WP1 sia stata configurata per l'accesso alla rete wireless di casa, sia accesa da almeno 6 secondi ed abbia acquisito un indirizzo IP interno (della stessa classe fornita, di solito, dal DHCP del router di casa). E' possibile rinvenire l'IP assegnato alla USR-WP1 dall'app di configurazione della USR-WP1 stessa.

NOTA 2: come già detto i comandi dovranno essere trasmessi in esadecimale (HEX), non come stringa altrimenti non verranno recepiti. Per la trasformazione ho utilizzato
binascii.a2b_hex(stringa).
(vedi esempio a fine articolo).



La comunicazione si svolge come segue:

- Apertura del socket di comunicazione TCP, porta 8899 verso l'IP della USR-WP1;
- Invio della password in esadecimale, seguita dai bytes 0D 0A;
- Invio del/dei comando/i desiderato/i (accendi o spegni nel nostro caso);
- Chiusura del socket TCP.



Comando di accensione:

La stringa di bytes da inviare (dopo la password) è la seguente:  55 AA 00 03 00 02 01 06
La USR-WP1 risponderà AA 55 00 04 00 82 01 01 88, e contemporaneamente si sentirà il rassicurante "click" del relé dell'apparecchio, da noi così attivato. Il LED frontale blu diverrà inoltre di colore violetto, segnalando anch'esso l'avvenuta accensione.


Comando di spegnimento:

Questa è la stringa di bytes da utilizzare: 55 AA 00 03 00 01 01 05
La risposta sarà  AA 55 00 04 00 81 01 00 86, il relé farà un altro "click" ed il LED tornerà blu.



Prima di passare al listato del programmino di test in Python ecco un cenno sul significato/ordine dei bytes, che può essere utile se si vuole approfondire la questione e sperimentare nuovi comandi, rinvenibili nel succitato manuale GPIO scaricabile dal sito del produttore. 

Come già detto per prima cosa occorre stabilire la connessione socket ed inviare la password. Se si è lasciata invariata quella originale essa sarà "admin", in minuscolo (e ovviamente senza virgolette). Dovremo trasformarla però da ASCII in esadecimale, quindi admin diverrà:

61 64 6D 69 6E

Ma non basta: perchè la USR-WP1 riconosca che le stiamo mandando la password di accesso dovremo aggiungere in coda alla stessa i bytes 0D 0A. La password completa che invieremo sarà quindi:

61 64 6D 69 6E 0D 0A

Ovviamente se è stata cambiata la password in fase di configurazione si dovrà trasformare la nuova password in HEX e poi aggiungere alla fine i fatidici 0D 0A. La lunghezza massima della password (testuale) è di 12 caratteri (esclusi quindi i due bytes 0D 0A).

Se si invierà una password sbagliata la USR-WP1 non eseguirà alcun comando e risponderà laconicamente NO
Se invece la password è corretta risponderà OK e sarà pronta ad eseguire il comando successivo, o meglio il comando vero e proprio. 
Per capire come si compone il comando di accensione lo suddividerò in colori per permettere di distinguere le varie parti di cui è formato:


55 AA 00 03 00 02 01 06


Il comando vero e proprio sono i due bytes  02  01  , il resto è solo "contorno". Tuttavia è necessario anche il "contorno" perchè il comando venga interpretato ed accettato! Vediamo un po':

La parte 55 AA  è comune e contraddistingue un comando, a differenza -ad esempio- della password che, come visto, va inviata così com'è (con l'aggiunta finale di 0D 0A).

00 03  rappresenta la lunghezza in bytes del comando (vero e proprio) che segue, compreso il byte 00 di cui parleremo tra un attimo. 

Seguono quindi i 3 bytes:  00 02 01 . Questa lunghezza può variare a seconda del comando, ad esempio nel caso di un comando di rinomina del device, che conterrà anche il nuovo nome assegnato: il byte di lunghezza (e quello di checksum) andranno ricalcolati anche in funzione della lunghezza del nuovo nome. 

00 è fisso, riservato e non utilizzabile. Concorre però, come appena visto, a formare la lunghezza in bytes del comando.

Ecco che ci imbattiamo nel comando vero e proprio: ( 02 01 ) .
La prima parte rappresenta il comando, la seconda il parametro; in questo caso stiamo dando il comando di accensione ( 02 ) e come parametro il numero di canale I/O da accendere (  01 ).

Infine 06  è il controllo di parità o checksum, e rappresenta la somma (in HEX) delle parti:

 00 03 00 02 01  
(0x00 + 0x03 + 0x00 + 0x02 + 0x01 = 0x06)


Appena ricevuto il messaggio corretto (password e comando) la USR-WP1 attiverà il relé e ci risponderà con 

 AA 55 00 04 00 82 01 01 88

Notiamo subito che AA e 55 sono invertiti rispetto al comando da noi inviato (55 AA), questo contrassegna il messaggio come risposta della USR-WP1. Anche il resto della risposta (ed i conseguenti checksum-parità) sono diversi e costituiscono nel nostro caso la conferma dell'avvenuta accensione (82 01 01), dove 82 01 è la risposta di conferma al comando di accensione, e l'ultimo 01 il "canale" (o porta) che è stato acceso (questo apparecchio dispone di un canale soltanto).


Una volta inviato il comando e ricevuta la conferma è infine necessario chiudere la comunicazione (il socket) per svincolare la USR-WP1 dal nostro controllo.

In caso necessitassimo di inviare un nuovo comando sarà necessario ripetere tutto dall'inizio: 
apertura socket, invio password, invio comando, chiusura socket.

A proposito dello "svincolare" mi è successo in almeno un'occasione che l'apparecchio non obbedisse ai comandi dati in Python mentre tenevo aperta l'app sullo smartphone, alla quale invece obbediva docilmente. Forse l'app si comporta in modo esclusivo e/o non chiude il suo socket mentre è in esecuzione. Tenerlo presente in caso di insuccessi...

I comandi che si possono utilizzare sono diversi, se si vuole approfondire la questione non resta che "divertirsi" a tradurli dal manuale Cino-Inglese. 

Questa stringa ad esempio alterna lo stato di accensione/spegnimento:

55 AA 00 03 00 06 00 09
 

Questa invece controlla lo stato attuale del device, acceso o spento:

55 AA 00 03 00 0A 00 0D


La risposta sarà la seguente:

Device acceso: AA 55 00 03 00 8A 01 8E

Device spento: AA 55 00 03 00 8A 00 8D

La differenza sta nel byte evidenziato in verde, oltre naturalmente al relativo checksum.


Di seguito infine un programma di esempio contenente le stringhe citate in questo articolo. Solo la stringa di accensione è attiva, le altre sono #commentate. Per utilizzarlo ricordarsi di cambiare (eventualmente) la password che è settata ad admin e naturalmente l'IP che dovrà essere quello della vostra USR-WP1.


Al prossimo pasticcio!

Nik






#!/usr/bin/env python
# -*- coding: UTF-8 -*-

import socket
import binascii


TCP_IP = '192.168.0.43' #<--- Cambiami/change me
TCP_PORT = 8899
BUFFER_SIZE = 2048


TestoHex = ("61 64 6D 69 6E 0D 0A")  #<--- PASSWORD (admin)
msgtmp = TestoHex.replace(" ", "")


MESSAGE = binascii.a2b_hex(msgtmp) 

# Rimuoviamo gli spazi e trasformiamo i caratteri in HEX
# Remove spaces and set the string to Hex

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((TCP_IP, TCP_PORT))
s.send(MESSAGE)
data = s.recv(BUFFER_SIZE)

print"\n*** Invio PASSWORD ***" # Send password
print "Dati ricevuti: ", data   # Received data
print "Dati ricevuti (in HEX): ", binascii.b2a_hex(data)


TestoHex = ("55 AA 00 03 00 02 01 06")    #<-- Comando ON

#TestoHex = ("55 AA 00 03 00 01 01 05")   #<-- Comando OFF 
#TestoHex = ("55 AA 00 03 00 06 00 09")   #<-- Alterna stato
#TestoHex = ("55 AA 00 03 00 0A 00 0D")   #<-- Controlla stato
 
 
msgtmp = TestoHex.replace(" ", "")
MESSAGE = binascii.a2b_hex(msgtmp)

s.send(MESSAGE)
data = s.recv(BUFFER_SIZE)

print "\n*** Invio COMANDO ***" # Send command
print "Dati ricevuti: ", data   # Received data
print "Dati ricevuti (in HEX): ", binascii.b2a_hex(data)

s.close











Chi (non) sono



Ciao!

Non sono un blogger.

Non basta saper scrivere un blog per essere un blogger, così come non basta saper pasticciare con un linguaggio di programmazione per essere un programmatore. Infatti non sono nemmeno un programmatore... Però mi diverto un sacco a pasticciare con elettronica, programmazione e solo il Grande Giove sa cosa altro. 
Avevo bisogno di uno spazio dove scrivere i miei appunti quando pasticcio con i miei progetti e mi è venuto in mente che magari i miei pasticci potrebbero essere utili a qualcuno, così lo spazio ora è pubblico.

Tante sono le cose per me speciali, cose che magari per i Veri Blogger ed i Veri Programmatori sono sciocchezze... Io non ho la pretesa di scoprire nulla, né di farlo nel migliore dei modi. 
Questo è solo il mio modo di prendere appunti durante i miei progetti (ho la memoria corta) per ricordarli in seguito. Sarò felice se dai miei appunti potrai ricavare qualche idea buona per te, e ancora più felice se vorrai condividere i tuoi risultati (che saranno sicuramente migliori dei miei).

Segui perciò le mie pagine con cautela, non sarà colpa mia se incendi la cuccia del gatto o se ti si frigge la batteria del cellulare: se a me è andata bene quando ho seguito un progetto potrebbe essere stata solo questione di fortuna...  ;-)

Buona lettura!

Nik