Gør det selv-IOT: Sådan bygger du en dims der kan tænde/slukke for andre dimser over internettet – Del 4/4: Softwaren og test af det endelige ’produkt’

VIGTIGT: LÆS DETTE, før du beslutter dig for at bygge noget i denne retning. Denne enhed forbindes direkte til 230V, hvilket er livsfarligt at rode med! Jeg joker ikke – Det gør i bedste fald p*sseondt at få stød, og i værste fald DØR du af det. Du kan starte en brand og det kan man også DØ af! Du bør ikke rode med netspænding uden et minimum af viden&erfaring.

I de forløbne tre klummer har vi specificeret hvad vi vil have at enheden skal kunne gøre. Vi har foretaget nogle valg, og vi har forberedt vores raspberry pi til at virke i den kontekst som vores produkt skal. Senest har vi loddet noget hardware sammen. Vigtigst af alt: Jeg har overlevet. Ingen stød og software-frustrationerne har ikke været af en så alvorlig karakter at det har kunnet få mig til at hoppe ud af vinduet. Success!

Denne fjerde og sidste del af serien vil handle om selve den software vi skal skrive for at implementere vores API og for at kunne manipulere med GPIO pindene.

Jeg afslutter artiklen med en lille demo.

Først skal vi lige have afklaret cliff-hangeren fra sidste del, og se det færdige produkt. Her er dimsen i al sin gloværdighed:

dimsen

Ikke det mest sexede design, men som ingeniør er jeg fokuseret på funktion frem for design, og det væsentligste funktionelle krav, at minimere risikoen for at jeg dør af elektrisk stød, er opfyldt.


Men hvad er dog det for to underlige huller i frontpladen? Det er et klassisk eksempel på konsekvensen af at man ikke tænker sig om. Jeg skulle montere Raspberry pi’en i kassen og sad og prøvede alle mulige kombinationer. Det var på den forkerte side af kl. 23.00 en hverdagsaften, og jeg havde sat mig et mål om at kassen skulle være færdigmonteret før jeg gik i seng. Jeg fandt den optimale placering, tænkte ’Yes!’, greb boremaskinen og borede to huller til skruerne. Så vendte jeg boksen om… og sprang direkte til sidste del af punkt 5 i hardware opskriften fra sidste afsnit:

 

Nå heldigvis er skaden ikke så stor, da jeg alligevel har tænkt mig at sætte en lille rekvalme på fronten af boksen som kommer til at dække over det.

final_product

Apple, go home!

Nu skal der kodes! Først laver jeg et lille script som skal styre status-LED’en. Som I måske husker så fandt jeg en RGB LED i skuffen, og kan dermed lave masser af farver. Det giver mulighed for at signalere en masse forskellige ting (system ok, men wifi ikke forbundet, forkert wpa key, intern temperatur for høj, etc.), men jeg vælger at være lidt primitiv og blot implementere rød (= fejl/ikke forbundet til internettet) og grøn (=hvad tror du selv?). Mere fancy funktionalitet må skydes til version2.

Scriptet skriver jeg i python (Den komplette kildekode finder du her: http://www.nørdoteket.dk/index.php/2016/02/06/gds-iot-kildekode-til/)

Virkemåden er, at jeg bestemmer netværkets default gateway, som er routerens IP adresse. I et uendeligt loop pinger jeg den IP adresse, og hvis der er svar sætter jeg LED til grøn, og hvis der ikke er svar sætter jeg LED til rød.

Koden er ganske simpel og python er dejlig nemt at bruge. I python bruger jeg RPi.GPIO biblioteket til at kontrollere GPIO pins. LED’en er forbundet til GPIO#9, 10 og 11 (Grøn, Blå, Rød). GPIO skal initialiseres som enten input eller output pins:

GPIO.setmode(GPIO.BCM) # Use Broadcom pin numbers
GPIO.setup(9, GPIO.OUT) # Green LED, set as output

Og man tænder for en pin ved at sætte den til HIGH:

GPIO.output(9, GPIO.HIGH) # LED on
GPIO.output(9, GPIO.LOW) # LED off

Det primære loop, hvor jeg pinger ser således ud:

while True: # infinite loop
    response = os.system("ping -c 1 " + hostname) # send one ICMP packet to host
    if response == 0: # Reply
        # Network is reachable, LED=Green
        GPIO.output(11, GPIO.LOW)
        GPIO.output(9, GPIO.HIGH)
    else: # No reply
        # Network is NOT reachable, LED=Red
        GPIO.output(11, GPIO.HIGH)
        GPIO.output(9, GPIO.LOW)

Dette program skal køre automatisk ved boot, og derfor skal det tilføjes rc.local:

rpi_admin@skarpline-iot:~ $ sudo nano /etc/rc.local

Tilføj ” /home/rpi_admin/status.py > /dev/null” til filen (husk at tilpasse home dir navn til din bruger, eller hvor du nu måtte lægge status.py filen), så det endelige resultat ser således ud:

#!/bin/sh -e
#
# rc.local
#
# This script is executed at the end of each multiuser runlevel.
# Make sure that the script will "exit 0" on success or any other
# value on error.
#
# In order to enable or disable this script just change the execution
# bits.
#
# By default this script does nothing.

/home/rpi_admin/status.py > /dev/null

# Print the IP address
_IP=$(hostname -I) || true
if [ "$_IP" ]; then
  printf "My IP address is %s\n" "$_IP"
fi

exit 0
 

Med denne metode vil lampen lyse grønt så længe routeren svarer, selv hvis der ikke er forbindelse til internettet. Hvis man udskifter default gateway med noget ude på internettet, f.eks. Google’s DNS server 8.8.8.8, er testen mere retvisende. Det undlader jeg personligt at gøre, da det ikke er god stil at lave noget der kontinuerligt står og generer trafik mod andre menneskers servere.

Nu til selve koden der implementerer API’et. Den komplette kildekode finder du her: http://www.nørdoteket.dk/index.php/2016/02/06/gds-iot-kildekode/.

Det foregår som en webservice, og derfor er det første vi skal gøre, at validere brugeren. Det gøres med HTTP Digest (RFC-2617), og derfor starter vi med at afvise alle requests til vores server, der ikke har authentication information med i headeren. Vi afviser med en ”http 401 / unauthorized”-header, og tilføjer en unik værdi, kaldet et ”nonce” (Løst oversat: en engangs-term).

digest_process

Kigger vi i TCP pakkerne (med wireshark – hvis du ikke har prøvet det endnu, så er det på høje tide. Fantastisk og helt uundværligt værktøj!) ser det således ud:

http_digest_mangler_i_header

Rød tekst er request fra klient. Blå tekst er svaret fra GDS-IOT dimsen.
Og i koden ser det således ud:

if (empty($_SERVER['PHP_AUTH_DIGEST'])) {
  header('HTTP/1.1 401 Unauthorized');
    header('WWW-Authenticate: Digest realm="'.$realm.
     '",qop="auth",nonce="'.uniqid().'",opaque="'.md5($realm).'"');
 
  die('UNAUTHORIZED');
  }

$_SERVER er php’s reserverede variabel der indeholder al den information som er med i det request der er modtaget (http://php.net/manual/en/reserved.variables.server.php).

Nonce genereres med php’s uniqid( ) funktion, der genererer en unik streng, baseret på tidspunktet for requestet (hvis man sender det samme hver gang er man sårbar for playback-angreb).

Hvis vi nu sørger for at sende en request header med korrekt authorization information, kan vi se at vi får det ønskede svar (og en http 200 / ok-header).

http_digest_med_i_header

Der er en del information i det svar, som vi lige skal gennemgå. Først skal vi sikre, at klienten har sendt alle de nødvendige data. Det gøres ved at parse PHP_AUTH_DIGEST variablen.

// analyze the PHP_AUTH_DIGEST variable
if (!($data = http_digest_parse($_SERVER['PHP_AUTH_DIGEST'])) ||
    !isset($users[$data['username']]))
      die('WRONG_CREDENTIALS');

Dernæst beregnes det response som vi forventer at få, hvis ellers afsenderen har brugt korrekt brugernavn og password. Det foregår i HTTP Digest ved at tage et MD5 hash af informationerne (som beskrevet i RFC-2617, §3.2.2).

// generate the valid response
$A1 = md5($data['username'] . ':' . $realm . ':' . $users[$data['username']]);
$A2 = md5($_SERVER['REQUEST_METHOD'].':'.$data['uri']);
$valid_response = md5($A1.':'.$data['nonce'].':'.$data['nc'].':'.$data['cnonce'].':'.$data['qop'].':'.$A2);

Og sluttelig tjekker vi om det modtagne response svarer det det vi selv har beregnet.

 
if ($data['response'] != $valid_response)
  die('WRONG_CREDENTIALS');
 


Nu er brugeren valideret, og vi skal se hvad der bliver bedt om i requestet. Når man skal levere information i et ”http/get” gøres det i URL’en med ”parameter=værdi” par. I PHP læses parametrene med $_GET[’parameter’]:

 $action = $_GET['action']; $port = $_GET['device']; 

Device skal mappes til den korrekte GPIO port, og vi skal sikre, at der ikke spørges efter et ugyldigt device. Det gøres med en switch statement:

switch ($port)
{
case 1:
    $portreal="27"; // Device#1 maps to GPIO#27
    break;
case 2:
    $portreal="17"; // Device#2 maps to GPIO#17
    break;
default:
    die('INVALID_DEVICE');
    break;
}

Endelig, efter så megen skriveri, lodderi og koderi, er vi nået til kernen i det hele: Bliver vi bedt om at tænde eller slukke for et strømudtag. Det tjekker vi også med en switch statement:

switch($action)
{
case ’ON’: // Vi vil tænde
  ..tænd for device
case ’OFF’: // Vi vil slukke
  ..sluk for device
case ’STATUS’:
  ..returner om der er tændt eller slukket
}

Selve interaktionen med GPIO pins foretager vi med WiringPi som blev installeret i del2 (link til del2). WiringPi er et lib til shell kommandoer, hvilket vil side, at vi fra PHP skal lave et systemkald. Det kan gøres med shell_exec kommandoen (hvor $portreal= 17 eller 27):

    $gpio_on = shell_exec("/usr/local/bin/gpio -g write " . $portreal . " 1"); // Turn on the desired GPIO
    $gpio_stat = shell_exec("/usr/local/bin/gpio -g read " . $portreal); // Read status of port


Hermed er projektet nået til sin ende. Egentlig imponerende hvor meget jeg kan skrive om så lidt, og stadig have fornemmelsen af kun lige have ridset overfladen!

Men vi skal da lige have demonstreret dimsen – ellers tror I bare ikke på at den virker. Det er lidt svært at gøre på skrift, så jeg har bikset en lille video sammen til jer.

Med reference til det første afsnit (link til del 1) så er det elektriske apparat som skal testes selvfølgelig den obligatoriske kaffemaskine. Problemet er blot, at min kaffemaskine er en fancy sag, som kræver tryk på en knap for at brygge en kop kaffe.

Hvordan klarer jeg nu det problem? Et helt nyt projekt begynder at forme sig i mit hovede: Et legotårn, en servo, en afhugget finger. Argh, stop! Jeg bliver aldrig færdig med at skrive, og Computerworld løber tør for anslag, så nu holder jeg mund.

Sig goddag til Steens kælderlab, se en demo af GDS-IOT dimsen og ikke mindst, se hvordan jeg løste problemet med at trykke på kaffemaskinens knap her.

Håber I har nydt at læse serien. Jeg har lavet og laver mange af denne slags småprojekter, men det er første gang jeg har prøvet at beskrive det i detaljer på denne måde. Er der interesse for lignende beskrivelser i fremtiden, så skriv det gerne i kommentarfeltet nedenunder.

Al kildekode finder I som sagt her: http://www.nørdoteket.dk/index.php/2016/02/06/gds-iot-kildekode/ og den står til fri afbenyttelse i henhold til revision 42 af Beer-ware licensen (en af mange geniale ting fra phk’s hånd).

Hvis jeg havde mere tid
Hvis nu man ville lege videre med udgangspunkt i denne dims:
Hardware:
– Flere udtag
o Det kommer an på dit behov. Det er særdeles nemt at udvide med flere, det koster blot en stikkontakt og et relæ samt driver kredsløb per stk.
– Måling af effektforbrug per udtag
o Det kunne være sjovt at lege med. Kræver en strømsensor per udtag. Output fra strømsensorer er typisk analoge, så en ADC (analog til digital konverter) skal bruges per strømudtag. Eller man kunne overveje at skifte Raspberry Pi ud med en anden platform der allerede har ADC kanaler.
– Mulighed for at lysdæmpe en lampe tilsluttet et udtag
o Måske ikke så relevant ved en strømskinne?
– Skrumpning af design, så det kan skohornes ind i en strømskinne.
– Læg print ud og lav det hele fra bunden, bedre, billigere og mindre.
– Andre forslag?
Software:
– Skriv en app til kontrol. Det er jo så moderne.
– Timerfunktion
o Udvid API, så man kan sige ”tænd device X, og sluk det automatisk om Y minutter”
– ’Feriemode’
o Lav funktion som tænder og slukker for de forskellige udtag på tilfældige tidspunkter
– WPS, så det er nemt at installere
– UPnP, så det er nemt at installere
– Andre forslag?

8 meninger om “Gør det selv-IOT: Sådan bygger du en dims der kan tænde/slukke for andre dimser over internettet – Del 4/4: Softwaren og test af det endelige ’produkt’”

  1. Hej,
    Så din udmærkede artikel om gør det selv-IOT.
    Jeg ved ikke om det er det rigtige sted, men nu prøver jeg alligevel.
    Nu til udfordringen:

    Jeg kunne godt tænke mig (mest for sjov), at kunne måle detonationshastigheden i sprængstof samt nøjagtigheden i detonatorer. Stopurs metoden duer ikke rigtigt da hastigheden er op til 8.500 m/sek.
    Man kan købe et kommercielt system der kan gøre det, men det koster …..
    Så så jeg at man kan få/lave en masse forskellige sensorer til RPi, men spørgsmålet er om den kan tælle hurtigt nok når der skal måles over en afstand på ca. 20 – 50 cm?
    Start/stop signaler (tror jeg) man kan få via 2 x fiberkabler med transceivere (GBIG’er).
    Altså, – man tager en dynamitstang på ca. 25 cm i længden og placere de 2 fiberkabler i hver ende inde i sprængstoffet.
    Når detonationsbølgen passere det første kabel, sendes et lyssignal igennem fiberen og tælleren gå i gang. Når bølgen 25 cm længere fremme når det andet fiberkabel sker det sammen og tælleren slukker.
    Når man kender den nøjagtige afstand imellem de 2 målepunkter, skulle det være en smal sag at regne hastigheden ud.
    Fiberkablerne kan bruges mange gange. Hvis de er tilstrækkeligt lange kan man bare klippe et meters penge af efter hver test.
    Det er ikke noget du skal bruge en masse tid på, men jeg kunne godt tænke mig at høre din umiddelbare mening.
    Lyder det helt åndsvagt eller mener du det kunne lade sig gøre?

    På forhånd tak.
    Mvh
    Mogens Jørgensen

    1. Hej Mogens.

      Først vil jeg lige sige tak for et superfedt spørgsmål! Den applikation havde jeg ikke tænkt på før 🙂

      Der er to dele i dit spørgsmål som jeg tager separat: Sensor og raspberry pi. Din ide med fiberkabler forstår jeg som at signalet gennem fiberen er “lyset fra selve eksplosionen” ? Det er jeg ikke sikker på er særlig pålideligt – men jeg indrømmer at jeg ikke ved specielt meget om hvordan lys vil koble til fiber i en eksplosion 🙂 Jeg tænker på at selv hvis et lysglimt kobler til fiberen så vil intensitet og flanker være uforudsigelige, og det vil have stor betydning for timingen i triggeren.

      Mit forslag ville være at lave to loops som eksplosionen så skal bryde, så tælleren istedet trigges af, at et kendt signal forsvinder. Det tror jeg vil være mere forudsigeligt og dermed pålideligt. Det behøver ikke være fiber – det kan lige så godt være almindelig ledning (hvor signalet stadig propagerer med ca. 2/3 af lysets hastighed). Så du trækker to ledningspar fra tælleren til dynamitstangen og de skal selvfølgelig være samme længde og kabeltype. Der er nogle overvejelser om indgangskapacitans etc. som lige skal tænkes ordentligt igennem for at sikre at timingen bliver så nøjagtig som mulig.

      Kan man bruge en Raspberry pi til dette? For at kunne besvare dette skal vi lige regne lidt på hvad kravene er til tidsopløsningen.

      Hastigheden, V, kan udtrykkes som afstanden, D, divideret med tiden, T: V=D/T.
      Så hvis vi har en tidsopløsning på et mikrosekund:

      T=29us, D=0,25m => V=8621 m/s
      T=30us, D=0,25m => V=8333 m/s

      Altså med en opløsning på et mikrosekund får vi en hastighedsopløsning på ca. 300 m/s. Det lyder ikke godt nok. Vi skal altså kunne tælle med højere hastighed end med en MHz.

      Hvis vi nu prøver med en tidsopløsning på et nanosekund:
      T=29,000us, D=0,25m => V=8620,69 m/s
      T=29,001us, D=0,25m => V=8620,39 m/s

      Og dermed giver en tidsopløsning på et nanosekund en hastighedsopløsning på 0,3 m/s – hvilket også er forventeligt, da der er en faktor 1000 mellem us og ns..

      Det er nok bedre end der er behov for, så vores tidsopløsning skal altså være et sted i midten. Hvis vi nu vælger 100 ns, svarende til en tæller med 10 MHz så får vi en hastighedsopløsning på 30 m/s.

      Kan man det med en Raspberry Pi? Det bliver svært. For det første kan man som udgangspunkt kun få mikrosekundopløsning med clock_gettime() funktionen i Raspberry Pi (som iøvrigt kan tage op til et mikrosekund at eksekvere!). Det kan vi komme omkring ved direkte at tilgå cyclecounteren i CPU’en, hvor vi får tæt på nanosekundopløsning (700 MHz ~ 1,5ns). Problemet er at en Raspberry pi IKKE kører et realtime operativsystem, og dermed har vi ikke garanti for eksekveringstiden for f.eks. interrupthåndteringen som skal starte/stoppe timeren. Hvad hjælper det at din timer har en opløsning på 1,5 ns, hvis det kan tage alt imellem 50 og 1500 ns for at timeren trigges af interruptet?

      Det er muligt at det kan gøres hvis man er kodeguru, men jeg tvivler på at det vil blive pålideligt.

      Jeg vil foreslå at man i stedet skriver det som et embedded system uden operativsystem på en dedikeret platform. F.eks. en 8-bit PIC som med 40MHz clock giver en tidsopløsning på 25 ns. Her har du direkte kontrol over hvad der eksekveres, og du har mulighed for timingsforudsigelighed helt ned på instruktionsniveau. Og så er den endda billigere end Raspberry PI’en 🙂

      Så mit svar: Ja man kan godt lave en “eksplosionshastighedsmåler”, men ikke baseret på en Raspberry Pi.

      Det lyder oven i købet som en sjov opgave, og måske endda en klumme værd? Jeg har travlt de næste 2-3 måneder, men hvis det har interesse og kan vente lidt så sig til og så kan vi se om vi kan finde ud af et eller andet sammen. Mit modkrav: Jeg vil se og filme demo’en – og jeg vil have lov til at trykke på knappen! 😉

      1. Det er ikke muligt at måle med 100 ns opløsning via Raspberry Pi eller nogen anden normal embedded CPU ej heller via fiber interface. Du er nødt til at bygge en HW tæller med en clock på f.eks 50MHz eller mere og så lave synkrone gate funktioner som starter/nulstiller og stopper tæller. Derefter kan en vilkårlig CPU aflæse tælleren og omregne.
        vh
        Ingeniør Peter

  2. Hej Steen,

    Ja, – jeg have det godt nok på fornemmelsen at PI’en er for “langsom”.
    Grunden til at jeg nævnte fiberen er, at den metode bruges på det kommercielle system jeg har set.
    Jeg er ret sikker på at når detonationsfronten passere fiberen, sendes der rigtigt meget lys igennem, men om det kan bruges ved jeg ikke. Det afhænger vel også af tranceiveren.
    Kan et (kobber) kabel-loop kan løse opgaven er det helt fint og sikkert billigere.
    Som jeg skrev indledningsvis er det mest for sjov. Jeg har dog haft opgaver hvor det var et vigtigt parameter at konstruere et meget nøjagtigt tændsystem. Producenternes angivelser af detonationshastigheder er mildt sagt ikke eksakt, men afhænger af mange ydre omstændigheder.
    En anden ting er nøjagtigheden i detonatores indbyrdes forsinkelsestrin (typisk 17 ms – 25 ms – xx ms). Det ville være en god ting at kunne checke det, da det langt fra altid passer med opgivelserne.

    Er det noget du på et tidspunkt kunne tænke dig at være med til, kunne det være rigtigt spændende.
    Det gør ikke så meget om det bliver om 2-3 måneder eller et halvt år, eller? Det er på ingen måde noget der haster.
    Jeg har masser af eksplosiver og mulighed for at lave alle de test der skal til.
    Kan det resultere i et system jeg kan bruge, må du have resten inkl. alt hvad du måtte have lyst til at skrive og fortælle og vi kan lave alt den foto og video dokumentation vi mener er relevant.
    Materialeomkostningerne skal jeg nok afholde.
    Og ja, – selvfølgelig må du “trykke på knappen” alt det du har lyst til.

    Du kan også kontakte mig på tlf. 6597 2954 / 2835 5893 eller på mj@explosive.dk

    Mvh
    Mogens

  3. Hej Steen

    Vil bare lige gøre opmærksom på, at link til kildekode ikke virker.

    Mvh. Joe

  4. Hej Steen

    Jeg har fundet denne “Sonoff” basis – der enkeltvis kan slukke og tænde for 230V apparater via App der forbinder til Google Home eller Apple Home Kit. (det er vist nok hvad de lover)
    Har du hørt, skrevet eller testet det brand?

    1. Hej Mika,
      Jeg har hørt om dem men har desværre ikke haft mulighed for at teste dem, så jeg kan ikke udtale mig om kvaliteten eller sikkerheden i produktet.

Skriv et svar

Din e-mailadresse vil ikke blive publiceret.

This site uses Akismet to reduce spam. Learn how your comment data is processed.