Heltec WiFi LoRa 32 met OLED en SX1276 aan LoRaWAN via The Things Network

Ik kocht vorig jaar via AliExpress een tweetal low-cost Heltec ESP32 modules met ingebouwde SX1276 LoRa radio en een OLED display. Het doel was om hiermee een mobiele CO2 detector te maken, maar ik kwam er niet aan toe om ervaring met de modules op te doen. Gisteravond had ik die gelegenheid wel en kwam tot de conclusie dat het nog niet zo rechtlijnig aansluiten was.

De Heltec WiFi LoRa 32 is een ESP32 development module met Bluetooth, WiFi, LoRa en USB connectiviteit. Voor Bluetooth en WiFi zijn antennes ingebouwd, voor LoRa is een MHF (U.FL, I-PEX) socket aanwezig. Je kunt de Heltec modules via AliExpress verkrijgen; let erop dat je voor de in Nederland ondersteunde 868MHz versie gaat. Het huidige versienummer is V2, ik maak nog gebruik van V1. Op sommige plaatsen in de software moet hiervoor iets worden aangepast.

De module heeft een 0,96″ blauw-zwart OLED display ingebouwd met een resolutie van 128 x 64 pixels. Hiernaast kan er een LiPo accu worden aangesloten en ook in ingebouwde acculader is voorzien, waarbij de accu dan via USB wordt opgeladen. De Heltec module is voorzien van een Arduino bootloader en wordt via de Arduino omgeving geprogrammeerd, hoewel voor de ESP32 microcontroller ook andere alternatieven voorhanden zijn.

De Heltec WiFi LoRa 32 modules zijn op dit moment de goedkoopste manier om een LoRaWAN oplossing te maken: voor een kleine 15 euro heb je een volledig werkende oplossing om bijvoorbeeld een sensor met The Things Network (KPN LoRa zal even goed werken) te verbinden.

Voorbereiden van The Things Network

Om gebruik te maken van The Things Network is een account nodig. Maak deze aan als je dat nog niet eerder had gedaan. In je account kun je naar de Console, waar je kunt kiezen tussen Applications en Gateways (als je zoals ik een eigen gateway tot je beschikking hebt).

Ga naar Applications en maak indien nodig een nieuwe Application aan. Je kunt meerdere Devices, zoals de Heltec module, binnen een Application gebruiken, maar als je een specifieke toepassing hebt is het handig hiervoor een aparte Application aan te maken. Heel veel informatie is hier niet voor nodig: een technische naam (kleine letters, geen spaties) en een omschrijving is voldoende. Klik dan op Add application. De technische naam moet uniek zijn binnen The Things Network. Omdat je niet kunt inzien welke namen al in gebruik zijn, zul je wat moeten experimenteren. The Things Network genereert nu een Application EUI.

Na het toevoegen van je Application kom je in het overzichtsscherm terecht, waar je een Device aan je Application kunt toevoegen. Ook zie je hier het door The Things Network gegenereerde Application EUI, een reeks van acht hexadecimale getallen, in het voorbeeld 70 B3 D5 7E D0 02 0E BB.

Gebruik Register device om de Heltec WiFi LoRa 32 module aan The Things Network toe te voegen. Ieder device moet worden voorzien van een Device ID, een identificerende technische naam. Later kan hier ook een ‘human friendly’ Description aan worden toegevoegd. Klik in het Register device scherm op het icoon gelijk onder Device EUI. De tekst in het vak wordt dan ’this field will be generated’. Device EUI, App Key en App EUI worden later gebruikt in de software van de Heltec WiFi LoRa 32 module. Klik voor nu op Register.

Nadat het Device is toegevoegd kunnen enkele eigenschappen worden aangepast in het Settings scherm. Geef hier een begrijpbare naam aan als Description. Laat Activation Method op OTAA (Over The Air Activation) staan. We gaan straks naar dit scherm terug om de Device EUI, Application EUI en App Key over te nemen in de software van de Heltec module.

Voorbereiden van de Arduino omgeving

Begin met het aansluiten van de LoRa antenne op de module; het inschakelen van de module zonder, of met een verkeerde antenne kan schadelijk zijn voor de LoRa radio. De voor LoRa gebruikte frequentie in Nederland is 868 MHz.

Download de codebibliotheek als .ZIP bestand van Heltec en voeg dit .ZIP bestand toe aan de bibliotheken in de Arduino ontwikkelomgeving met Schets > Bibliotheek gebruiken > Voeg .ZIP bibliotheek toe…

Er moeten 3 zaken ingesteld worden, voor we met de software aan de slag kunnen:

  • Om gebruik te kunnen maken van de Heltec codebibliotheek, is per module een licentie van Heltec nodig. V2 modules kunnen de code elektronisch vinden, voor V1 modules moet je een email naar Heltec sturen. In beide gevallen moet eerst het Chip ID met een Arduino sketch worden opgevraagd
  • In het bestand Commissioning.h moeten de Device EUI, Application EUI en App Key worden opgenomen (voor iedere volgende module moet dit bestand opnieuw worden aangepast)
  • In het bestand LoRaMac-definitions.h moet de juiste frequentieband worden ingesteld

Kies in de Arduino omgeving de juiste poort en het juiste bord (Heltec WiFi LoRa 32). Laad de volgende sketch om de Chip ID op te vragen:

uint64_t chipid;

void setup() {
  Serial.begin(115200);
}

void loop() {
  chipid = ESP.getEfuseMac(); // 6 bytes
  Serial.printf("ESP32 Chip ID = %04X", (uint16_t) (chipid>>32));
  Serial.printf("%08X\n", (uint32_t) chipid);
  delay(3000);
}

In de Seriële monitor van de Arduino omgeving wordt iedere drie seconden het Chip ID van de module getoond, in mijn geval:

ESP32 Chip ID = A01B56A4AE30

Ga in het geval van een V2 module (op de printplaat staat duidelijk ‘V2’) naar de Heltec website, waar het licentienummer voor dit Chip ID wordt getoond. In het geval van een V1 module dien je een email aan Heltec te sturen. Ik had binnen een half uurtje een reply email met de volgende tekst:

Zoek het bestand Commissioning.h. Dit staat waarschijnlijk in Arduino\libraries\ESP32_LoRaWAN-master\src. Wijzig de volgende definities in dit bestand:

#define LORAWAN_DEVICE_EUI
#define LORAWAN_APPLICATION_EUI
#define LORAWAN_APPLICATION_KEY

Geef hier de waarden op uit het Settings scherm van het Device van The Things Network:

Bewaar dit bestand en open het bestand LoRaMac-definitions.h dat in dezelfde map staat als Commissioning.h. Wijzig de regel met #define USE_BAND_470 in #define USE_BAND_868. Bewaar het bestand.

Verbinding maken met The Things Network

Laad de voorbeeldsketch Bestand > Voorbeelden > ESP32_LoRaWAN > OTAA. Wijzig in de sketch de regel met LICENSE[4] naar het licentienummer dat van Heltec, voor deze module, is verkregen:

Compileer de sketch en stuur de gecompileerde code naar de Heltec WiFi LoRa 32 module. In de Seriële monitor van de Arduino omgeving is te zien dat de module verbinding probeert te maken. LoRaWAN vindt plaats op diverse frequenties in de 868 MHz band, met Tx Freq sordt aangegeven, welke frequentie is gebruikt. Met Received Signal Strength Indication (RSSI) wordt het ontvangen vermogen aangegeven in dBm, een negatief getal. Hoe dichter bij 0, hoe beter het signaal. De ervaring leert dat het minimum RSSI op -120 dBm zit (een heel zwak signaal), en dat -30 een sterk signaal aangeeft. De -45 dBm in het voorbeeld is niet zo’n heel sterk signaal en voor een gateway op enkele meters afstand voor verbetering vatbaar.

In de terminal van The Things Network is na een paar seconden te zien dat de module verbinding heeft gemaakt en data heeft verstuurd (‘Frames up’) en data heeft ontvangen (‘Frames down’):

Eigen data versturen en ontvangen

De voorbeeldsketch van Heltec verstuurt data, maar zonder dat daar iets voor is ingeregeld. Om eigen data te versturen dienen drie variabelen in de sketch te worden aangepast:

  • AppPort: default 2 voor demonstraties, ieder ander getal is prima
  • AppData: array, maximaal 16 bytes
  • AppDataSize: aantal bytes eigen data

Met de aanpassing hierboven wordt de eigen data (de ‘payload’) de drie getallen 1, 2 en 3. Die zie je dan ook op The Things Network binnenkomen:

Conclusie

De Heltec WiFi LoRa 32 ESP32 development kit werkt eenvoudig samen met The Things Network, als je de juiste stappen in de goede volgorde uitvoert. De module vormt een goedkope en flexibele oplossing om sensoren met LoRaWAN aan het internet te koppelen. Om de typische LoRa-kilometers-afstanden te bereiken is het zaaks om een goede antenne (en kabel) te gebruiken. De signaalsterkte RSSI zal anders maar matig zijn.

De DJI Spark drone crash en hierop volgende succesvolle reparatie/restauratie

Afgelopen week maakte ik filmopnames van een groep trainees en probeerde ook nog wat opnames van het gebouw te maken waarin het traineeship werd gegeven. Dat werd mijn drone, een DJI Spark, fataal: door een vooralsnog niet duidelijke oorzaak probeerde de drone het gebouw in te vliegen en crashte van 65 meter hoogte op de straatstenen.

De crash

Beelden van de dronecamera ter hoogte van de 18e verdieping
Toevallig beeld van de crashende drone

Voorafgaande aan de crash was de drone op de gewenste hoogte van ongeveer 65 meter (18e verdieping) op een meter of vier afstand van het gebouw. De bedoeling was om van de voorzijde van dat gebouw naar de zijkant ervan te vliegen en daarbij de camera op de ramen gericht te houden. Aan alle kanten rondom het gebouw was voldoende ruimte en onder het vluchtpad van de drone liep een brede stoep. Geen gevaar voor wandelaars of passerende auto’s. Er liep wel iemand te filmen op een meter of twintig afstand hetgeen ook de ‘live stuiterfoto’ hierboven opleverde.

Als piloot kon ik de hele situatie overzien, hoewel de drone zelf met de zon in de rug maar net zichtbaar was. Het vliegen aan de voorkant ging prima, de rotatie op de hoek ook. Ik kreeg een melding dat de maximale vlieghoogte was bereikt en heb dat in het menu veranderd. Toen ik met de drone de hoek om ging, ging het mis: de drone vloog steeds dichter naar het gebouw en stuiterde tegen het glas, verloor een propeller (deze viel het eerst op straat en de drone viel hulpeloos naar beneden.

Na de crash hielpen omstanders de losse onderdelen te verzamelen. De drone zag er toen uit als op onderstaande foto: twee gebroken rotorarmen en een volledig total loss batterij. Ik heb de drone eerst maar eens in de koffer opgeborgen en later de schade beoordeeld.

De gimbal is zo op het oog nog intact (zit aan de andere kant van de gebroken rotorarmen) en de behuizing lijkt onbeschadigd. Ook de motoren draaien nog en de elektronica lijkt het nog te doen. Alle losse onderdelen waren verzameld, inclusief de dopjes en de veertjes. Alleen het micro-SD kaartje met de film die op het moment van de crash werd gemaakt ontbrak en zal waarschijnlijk nog op de plek van de crash liggen.

Gebroken armen, defecte batterij en mogelijk interne schade

Na de crash heb ik de vluchtanalyse van de DJI Go 4 app geladen en het gelinkte filmpje gemaakt om aan de hand hiervan de omstandigheden en de oorzaak van de crash te analyseren. Dat is nog niet zo eenvoudig, het lijkt zo op het eerste gezicht een navigatiefout, maar het is nog onduidelijk of het te voorkomen was. Ook is het onduidelijk of er problemen waren met de GPS ontvangst.

Waarschijnlijk vervang en/of restaureer ik de drone binnenkort, maar het is wel belangrijk om niet weer dezelfde problemen te krijgen. Tips en adviezen? Laat het me weten!

De restauratie

Het lijkt te doen om een ernstig beschadigde DJI Spark te herstellen, door het frame te vervangen door een nieuw exemplaar. Dat heeft natuurlijk alleen zin als de andere onderdelen (interne vluchtregelaar, gimbal en camera, motoren) nog bruikbaar zijn. Zo’n frame is na te bestellen en kost een kleine 20 euro. Het frame is dan al voorzien van nieuwe bedrading voor de motoren.

Dank zij YouTube heeft iedereen de beschikking over de opgenomen ervaringen van anderen. Adamant IT, ‘a small computer repair shop in Shaftesbury, England’ repareerde al eens een DJI Spark met soortgelijke schade als de mijne en dankzij de zorgvuldig gedocumenteerde demontage-montagevideo had ik de Spark in geen half uur in onderdelen op de werkbank liggen.

Zo op het oog leken alleen het frame, de accu en de propellers gesneuveld. Ik ben daarom maar eens begonnen met het bestellen van een nieuw frame.

Bestelpagina op AliExpress

Het nieuwe frame bevat de draden naar de motoren en enkele subassemblages. Ik heb het demonteren alle onderdelen op kleine papiertjes met een schets van de subassemblage geplakt, zodat er maar een klein risico was dat ik het geheel niet weer precies in elkaar zou krijgen. De kleine connectoren die met iets van siliconenkit waren gefixeerd waren lastig los te maken; met een pincet verwijderen van de kit bleek een goede aanpak.

Het nieuwe frame (links) klaar voor de subassemblages uit het oude frame (rechts)

Voor het demonteren en monteren heb ik het volgende gereedschap gebruikt:

  • Kruiskop schroevendraaiers
  • Torx schroevendraaiers
  • Pincet
  • Desoldeerbout
  • Soldeerbout
  • Derde handje voor het solderen van de motoren en snelheidsregelaars
  • Papier, plakband, potlood, zipzakjes en watervaste stift om onderdelen van subassemblages te documenteren, vast te plakken en in zakjes in volgorde te bewaren tot de assemblage
Kleine connectors met veel aansluitingen worden verzegeld met wat schilderskit

Na de montage met een nieuwe accu de drone aan DJI’s Assistant software gehangen. Ik kreeg een foutmelding dat een motor defect was. Mij leek dat sterk, die motoren zien eruit alsof ze wel tegen een stootje kunnen dus ik heb naast twee nieuwe motoren ook twee snelheidsregelaars besteld. Uiteindelijk bleek dat er inderdaad twee snelheidsregelaars defect waren en geen motor. Na het vervangen van beide snelheidsregelaars en het bijwerken van de software daarvan leek alles weer te werken. Ik heb de drone voorzien van een nieuwe, groene afdekplaat: DJI Spark Groen, klaar voor een proefvlucht.

De proefnemingen

De gerestaureerde DJI Spark, nu met groen deksel, hoewel de oorspronkelijke witte niet beschadigd was

De proefvlucht op lage hoogte op een afgelegen veldje leek in eerste instantie heel succesvol, maar leverde na een paar minuten een problematische situatie op: de drone werd onbestuurbaar. Ik kon de drone nog net landen, voor hij op de vlucht was geslagen. De onbestuurbaarheid leek nog het meest op het wegdrijven in de lucht: de drone reageerde accuraat op de afstandsbediening, maar deed geen enkele moeite om stil in de lucht te blijven hangen als er geen signalen van de afstandsbediening binnenkwamen. Analyse van de vluchtgegevens leverde het volgende beeld:

Vluchtgegevens van de eerste testvlucht met de gerestaureerde DJI Spark

In de documentatie van de DJI Spark staat vermeld, dat bij voortdurende kompasfouten de GPS module wordt uitgeschakeld. Persoonlijk zie ik daar de logica niet van, maar het verklaart het feit dat de drone ervandoor wilde gaan: zonder kompas en GPS is een drone aan de elementen overgeleverd1 en het waaide stevig op de testdag.

Een test van het kompas ging eenvoudig via de DJI GO 4 app, waar je de metingen van het kompas op het scherm getoond ziet. Viel gelijk op dat er alleen toevalsgetallen getoond worden: 34, 256, 2, 312, 2, 128, .. Ook het calibreren van het kompas ging steevast met een foutmelding gepaard.

Het was even zoeken waar de compas in de DJI Spark zit: bij de GPS module, op de hoofdprint, op de IMU module of op de 3D Vision module. Ik heb na wat Duck-Duck-Go-en op de 3D Vision module gegokt. De 3D Vision module zit voorin de drone, met hierop een camera, een afstandsmeter en (hopelijk) het elektronisch kompas. Ik heb er een gebruikte vervanger uit China voor laten komen.

Vision Sensor Module op de bestelpagina van AliExpress

Het vervangen van de 3D Vision module ging eenvoudig: een handjevol kruiskopschroeven losdraaien, een connectortje lospeuteren en overzetten op de nieuwe printplaat en de boel kon weer terug. Gelijk in de DJI GO 4 applicatie de metingen van het kompas bekeken en die waren heel anders dan de oude: stabiele waarden die afhankelijk van de draaiing van de Spark tussen de 0 en 1000 zaten. Ook de kompascalibratie ging nu foutloos.

Ik heb alle connectors voorzien van wat schilderskit (tandenstokertjehoeveelheid). Er wachten nog een paar testen maar ik durf te stellen dat de kans groot is dat de drone weer zo goed als nieuw is.

Bill of Materials:

  • Frame, inclusief bedrading € 17,37
  • 2 snelheidsregelaars 2 x € 10,48
  • 3D Vision module € 15,49
  • Nieuwe groene afdekplaat (niet nodig, wel mooi) € 9,31

Totale reparatiekosten: € 63,13

  1. Technisch gezien zou de accelerometer of de naar-beneden-gerichte camera driften moeten kunnen voorkomen. In de praktijk is hier door DJI niet voor gekozen. ↩︎

Hoe groot is mijn kofferbak?

Als je op zoek bent naar een nieuwe auto en nu rijdt in een Volvo V70 dan is er een gerede kans dat je naar een Tesla Model 3 aan het kijken bent. En je dan achter je oren krabbelt hoe je ooit al die hockeytassen / boodschappen / vakantiespullen in de ‘3’ krijgt die je normaal in het vrachtruim van je V70 stouwt.

De eerste elektrische auto in de prijsklasse van een V70 met een ruime kofferbak moet ik nog zien, maar hoe groot een kofferbak dan precies moet zijn is nog wat mistig. De V70 is op dit gebied goed bedeeld, maar over het algemeen is die kofferbak niet veelgebruikt. Pas als je op vakantie gaat wil je de ruimte: de achterbanken kunnen dan meestal niet plat (want kinderen) en je moet het dan doen met de kofferbak zelf. Als je boodschappen doet kun je over het algemeen wel een achterbank platleggen als je extra ruimte nodig hebt. Iets aan vergelijkingsmateriaal is daarom belangrijk.

Elektrische auto’s hebben vaak een extra bagageruimte in de motorkap; ruimte die ontstaat door het ontbreken van een grote benzinemotor. Dat geldt niet voor alle EA’s overigens: een Hyundai Kona Electric bijvoorbeeld heeft op de plaats van de bezinemotor een warmtewisselaar voor de verwarming en koeling. De meeste moderne auto’s hebben hiernaast geen reservewiel meer mee. Dat heeft verschillende redenen, maar bottom line is dat die ruimte soms beschikbaar komt als bagageruimte. Hybrides (auto’s met zowel een benzinemotor als een elektromotor) boeten vaak in op bagageruimte en zijn sowieso een uitstervend ras; in de tabel hieronder kom je ze daarom niet tegen.

4 passagiers1 passagier
Volvo V70575 liter1600 liter
Tesla Model 3463 liter + 95 liter
(+85 voorzijde)
895 liter + 95 liter
(+85 voorzijde)
Hyundai Kona Electric332 liter1114 liter
Citroën Berlingo624 liter2800 liter
Citroën C5 Tourer505 liter1462 liter
Mercedes A (1e gen)390 liter1740 liter

De vraag, of de kofferbak van de Tesla Model 3 groot genoeg is voor dagelijks gebruik, wordt vaker gesteld:

De precieze afmetingen van de bagageruimte zijn lastig te geven, omdat de vorm van de kofferbak wat taps toeloopt en een draagbalk halverwege heeft. Maar laten we ruime maten nemen:

  • Kofferbak afmetingen ca. 95 cm breed, 107 cm diep (lang), 46 cm hoog
  • Kofferbak met banken plat ca. 95 cm breed, minimaal 170 cm diep (lang), 46 hoog. De feitelijk te gebruiken ruimte is een stuk groter, er zijn veel inhammen en de ruimte is aanzienlijk hoger in de cabine
  • Kofferbak verborgen ruimte ca. 69 cm breed, 46 cm diep, 31 cm hoog

Hoe veel bagage neem je mee? Hieronder een tabel met situaties (werk in uitvoering):

SituatieAantal passagiersLiters bagagePast in de Model 3
Normaal woon-werk verkeer010Ja
Boodschappen doen1150Ja
Hockey wedstrijd3 (geen keeper)240Ja
Hockey wedstrijd3 (met keeper)330Ja (?)
Weekendje weg met familie3400Ja

Kletsende afscheidskaart met Speaker pHAT en Raspberry Pi

Een collega nam deze week afscheid van het team en na enig brainstormen kwamen we tot de conclusie dat het afscheidskado iets met ‘een kaart met onze namen’, ‘gadget’, ‘irritant’ en ‘iets met lopen’ moest zijn. Genoeg aanknopingspunten om een A4-kaart met ingebouwde ghettoblaster en “goedbedoelde adviezen van de oud-collega’s” te knutselen.

Collega Klaas nam het kaart-deel voor z’n rekening, iedereen kwam met een opname van wat goedbedoelde adviezen en ik heb het technische deel opgepakt: een Raspberry Pi Zero en een Pimoroni Speaker pHAT.

Pimoroni Speaker pHAT op Raspberry Pi Zero

Van de audioboodschappen die de collega’s hadden aangeleverd heb ik een stuk of 50 verschillende .wav bestanden gemaakt en die met scp naar de Raspberry Pi geupload. Het resultaat was een directory met allemaal verschillende .wav bestanden.

De Speaker pHAT komt met een handige one-line installer van Pimoroni, die ervoor zorgt dat de benodigde software wordt geïnstalleerd en dat de SPI-aansturing, waarmee de Speaker pHAT werkt, wordt ingesteld:

curl -sS https://get.pimoroni.com/speakerphat | bash

De aangemaakte Pimoroni directory kun je gelijk weggooien: het enige bruikbare is een grappig audiobestand test.wav. Een .wav afspelen gaat met het geïnstalleerde programma aplay. Om een willekeurige .wav uit een directory af te spelen combineer ik de linux commando’s ls en shuf:

aplay -N "$(ls /home/pi/wavs/*.wav | shuf -n1)"

Ik laat het commando iedere minuut uitvoeren door een regel in de crontab.

De Speaker pHAT met een opstartmuziekje en een eerste audioboodschap

De Speaker pHAT maakt een wat ielig geluid. Zodra je het geheel in foamboard monteert echter, verandert dat als bij klokslag: het geluid wordt voller en luider. Achteraf gezien had ik het kleine luidsprekertje niet alleen vast moeten schroeven, maar ook met iets van kit moeten vastlijmen; dan was het geluid nog indrukwekkender geweest.

De kaart zelf bestaat uit een zwart stuk foamboard op A4-formaat. Deze komen per stuk of per 10 van de betere schilderbenodigdhedenzaak. De witte runner en het groen-blauwe logo werden door Klaas ge-airbrusht, waarvoor hij blij was over meerdere stukken foamboard te kunnen beschikken, want ook bij hem lag de lat hoog. Met een klein beetje voorontwerpen konden we de runner en de plek van de Speaker pHAT goed op elkaar afstemmen, zodat deze er naadloos inpaste en met wat hot glue gefixeerd kon worden. Klaas had ook een handige uitklap-standaard gemaakt, zodat de kaart keurig onder een hoek kon staan.

Pimoroni Speaker pHAT en Raspberry Pi Zero in zwart A4 foamboard met airbrush wit en groen/blauw. Achterzijde heeft USB powerbank en standaard. Namen van de teamleden zouden later met een goudkleurige stift individueel worden toegevoegd

Het resultaat was fenomenaal, hoewel we de reactie van de ontvanger in onze gedachten wellicht wat hadden overschat: de goedbedoelde audioboodschappen zaten al veel sneller dan gedacht in de allergie-hoek van de nieuwe kamergenoten van onze afscheid-nemende collega. Maar oh, wat hebben we er zelf van genoten 🙂

Behuizingen lasersnijden zoals voor een Raspberry Pi cluster

Als je zoals ik van het ‘doe het zelf’ type bent, dan heb je al lang je oog eens laten vallen op een 3D printer om behuizingen voor elektronische apparaten mee te maken. In de praktijk vind ik 3D geprinte behuizingen te lang duren om te maken en niet mooi genoeg om direct te gebruiken. Hiernaast is het beschikbare materiaal net niet handig genoeg voor behuizingen: transparante vensters zijn lastig te maken en stickers plakken net niet goed genoeg op het niet-niet-naadloos-gladde materiaal van de 3D geprinte behuizing.

De oplossing is lasersnijden: je neemt het (plaat) materiaal naar keuze en snijd de vorm die je nodig hebt uit. Desnoods gebruik je verschillende materialen. Om door hout en kunststof te kunnen lasersnijden heb je een CO2-laser nodig van tenminste 40 watt, de bebehorende koelingsinstallatie en, niet te vergeten, een goedwerkende afzuiging. Dat willen we niet thuis hebben.

Gelukkig zijn er voldoende mogelijkheden om materialen te laten lasersnijden. Mijn favoriet is op dit moment Snijlab, dat een brede keus aan materialen heeft en levert binnen 5 werkdagen.

Acrylbehuizing, door Snijlab geproduceerd

Het proces van laten laseren is eigenlijk vrij eenvoudig:

  • Maak een snijplan in een tekenpakket zoals InkScape of Adobe Illustrator, gebruik de kleuren blauw voor snijden en rood voor inbranden
  • Log in op de opdrachtenpagina en kies het materiaal waar het ontwerp uit moet worden gesneden
  • Upload de tekening in PDF formaat
  • Kies de levertijd en betaal met iDeal of PayPal
  • Neem na 5 werkdagen het pakket aan van PostNL
Ontwerp van een behuizing. De gebruikte kleuren zijn bepalend voor de verwerkingingen snijden, inbranden of negeren

Er zijn verschillende manieren om een doos te ontwerpen. Ik begin met een basisdoos waarvan de panelen al zijn voorzien van het mechanisme om de doos later samen te voegen. Voor hout kies je al snel voor lijmen en scharnieren en dan zijn lijmbare nokken handig. Acryl klik je het liefst demonteerbaar inelkaar en dan is een vergrendelsysteem met elastische clips praktisch.

Op Climbers.net vind je een basisdoos met instelbare afmetingen die voorzien zijn van elastische clips. De clips brengen wel met zich mee dat de minimum afmetingen van de doos niet kleiner kunnen zijn dan 45 x 75 x 75 mm.

Snel-leveren-want-haast prototype van een LoRaWAN/The Things Network CO2 meter

In januari 2017 viel ik in verschillende discussies over de voorzieningen die de dan nog in conceptstadium verkerende ‘5G’ standaard zou gaan bevatten en manieren om deze voor een groot publiek beschikbaar te stellen. De gesprekken gingen ondermeer over of ‘lora’ als oplossing voor internet of things apparaten zou gaan uitmaken van de 5G-specificatie, of dat ‘narrowband’ meer op dat pad zou liggen. Een voorbeeld van een mogelijke toepassing van de technieken zou bij de beeldvorming en de discussies kunnen helpen en de opdracht voor een ‘prototype van een met 5G-internet gekoppelde leefkwaliteitmeter voor klaslokalen in de regio’ was geboren. Met als deadline ‘op korte termijn’.

De schakeling

Het idee van dit prototype is het meten van de CO2-gehalte in klaslokalen, en het centraal registreren van de gemeten waarden. Het tonen van de gemeten waarde in de klas is leerzaam voor de aanwezigen, en het feit of er al dan niet mensen in de ruimte zijn ook. In totaal twee sensoren: CO2 en beweging met een passief-infrarood-detector (PIR-detector). Beide sensoren hebben hun eigen ingebouwde elektronica, maar de CO2-sensor levert een CO2-waarde via een seriele poort en de PIR-detector levert slechts een digitaal ‘er was beweging’ signaal, dat na verloop van tijd weer terugvalt naar ‘er is (al een tijdje) geen beweging meer’.

KPN LoRa en The Things Network bevonden zich in 2017 beide nog in het beginstadium van wat tegenwoordig betrouwbare en eenvoudig toegankelijke netwerken zijn en de voorzieningen op via LoRa verbinding te maken met ‘een backend’ waren op één hand te tellen. Ik had al redelijk snel succes (hoewel met de nodige handbewegingen en een forse dosis mazzel) met een Microchip RN2483, een degelijke, door de LoRa Alliance gecertificeerde manier om verbinding te maken met een LoRaWAN gateway van KPN of The Things Network. De RN2483 is een SoC die bestaat uit een Semtech SX1276 LoRa zendontvanger en een Microchip PIC18LF46K22 microcontroller. Deze laatste heeft mogelijkheden genoeg om zelfstandig de sensoren in te lezen, een display aan te sturen én de metingen via de SX1276 te versturen, maar om de certificering in stand te houden heeft Microchip ervoor gekozen het lastig te maken om de PIC18LF46K22 te herprogrammeren of aan te vullen met extra programmatuur. De RN2483 daarom wordt als een soort internetmodem gebruikt en via twee draden bestuurd; de credentials worden aan de serverkant opgeslagen en aan de hand van het apparaatnummer gecontroleerd.

Proefopstelling met een Arduino Leonardo en RN2483, onderling verbonden via een serieel protocol over twee dragen (geel en grijs/wit)

Na de initiële verbinding van de RN2483 met het LoRa netwerk en het vaststellen van de credentials (hier verder niet besproken) is het maken van een vervolgverbinding eenvoudig:

1
2
3
4
5
6
7
8
9
10
11
sendCommand("sys reset");
sendCommand("mac join otaa");
if (getResponse() == "ok") {
  if (getResponse() == "accepted") {
    // There. A valid LoRa connection
  } else {
    // Denied. Either no free channels or something else
  }
} else {
  // Not a wanted response. Something with the hardware
  // We might want to throw a panic here
Uitbreiding van de schakeling met CO2-sensor, PIR-detector en 6-cijferig led display. Je mag iets vinden van de provisorische manier van verbinden

Het inlezen van de status van de PIR-detector was eenvoudig: de PIR-detector verbindt met één draad aan de Arduino en in de code volstaat het om met enig regelmaat het signaalniveau te controleren van de aan de PIR-detector aangesloten pin. De gevoeligheid en de tijdsduur van het digitale signaal dat beweging aangeeft kan middels instelpotentiometers op de PIR-detector worden ingesteld:

1
2
3
4
5
6
7
#define pirPin 13
pinMode(pirPin, INPUT);
 
// Repeat this as often as needed
if (digitalRead(pirPin)) {
  // Something moved allright
}

De CO2-sensor was lastiger, omdat deze zelf een flinke hoeveelheid slimmigheid herbergt en pas met enig aandringen afstand wil doen van de gemeten waarden. De stappen zijn:

  • open de seriële verbinding met de sensor
  • geef het commando om een meetwaarde op te leveren
  • lees de 9 bytes van het antwoord
  • bepaal de checksum van het gegeven antwoord en
  • vergelijk dit met de eveneens ontvangen checksum
  • bereken de ppm aan de hand van twee bytes

De checksum wijkt eigenlijk alleen af wanneer de CO2-sensor net wordt ingeschakeld: de eerste minuten leveren geen betrouwbare meetwaarden. Voor proefopstellingen waar dit als bekend wordt verondersteld werkt onderstaande code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <SoftwareSerial.h>
#define SensorRX 10
#define SensorTX 11
SoftwareSerial co2sensor (SensorRX, SensorTX);
byte cmd[9] = {
  0xFF,0x01,0x86,0x00,0x00,0x00,0x00,0x00,0x79}; 
byte response[9]; 
co2sensor.begin(9600);
co2sensor.write(cmd, 9);
co2sensor.readBytes(response, 9);
 
// Calculate checksum and compare with response[8] here
// If all checks out, response is an actual ppm
ppm = (256 * response[2]) + response[3];
co2sensor.end();

Als laatste het display, dat ook met een seriële verbinding werkt en waar reguliere printopdrachten heengestuurd kunnen worden. Om de stroomopname van het apparaat wat in de hand te houden (de stroomvoorziening geschiedde middels een USB powerbank) kan het display een lege tekst tonen, waardoor de stroomopname effectief naar (bijna) 0 schiet. De code voor het aansturen van het display is in elk geval aardig eenvoudig:

1
2
3
4
5
6
7
8
9
10
11
12
13
#define displayRX 5
#define displayTX 6
#define displayID 255
String message = "3.1415";
SoftwareSerial leddisplay (displayRX, displayTX);
leddisplay.begin(9600);
while (message.length() < 6) {
  message = " " + message;
}
leddisplay.write(displayID);
leddisplay.write('b');
leddisplay.println(message);
leddisplay.end();

De behuizing

Voor de behuizing had ik in elk geval als eis dat ik ‘m met de mij beschikbare middelen kon produceren én dat het er op geen enkele manier als een ‘elektronicaprojectje’ uit zou zien. Dat betekende geen standaard projectdoos, maar een maatwerk kastje. Dat had van foamboard gekund (handig snijden met lineaal en scherp mes), maar leek me iets te fragiel voor een prototype dat wat verkennende gesprekken over het doel en nut van LoRa zou moeten doormaken. Vandaar hout: bijna even gemakkelijk te bewerken maar net even een graadje degelijker. Ik had net een mooie nauwkeurige tafelcirkelzaag aangeschaft en dit kastje was een leuke vingeroefening.

Behuizing uit multiplex, geschilderd en strakgemaakt

Toen het kastje klaar was (enkele keren in de grondverf gezet en strak geschuurd, geplamuurd en nog meer geschuurd) heb ik de maten zorgvuldig opgenomen en deze in Adobe Illustrator overgenomen. Het logo van ‘5Groningen’ zou prominent op het prototype gaan prijken, zodat het een leuk bespreekobject zou gaan worden in het lab. De copyshop in de wijk heeft een fantastische service en na een uurtje kon ik het snijwerk van de zelfklevende folie gaan uitvoeren. Best eng, want dan moet het allemaal precies passen en mooi strak zitten. Maar, ging heel aardig.

Beplakken van de behuizing met zelfklevende vinyl folie met opdruk

De software en het prototype

Het leuke van prototypes vind ik het moment vlak voordat je ze oplevert aan de klant: je mag er dan zelf als eerste mee spelen je acceptatietesten op los laten. Leuk, om een nieuw gadget te hebben waarvan je precies weet wat het doet, hoe en waarom het dat doet en wat er wel en niet mee kan. En dan kastje dicht (de fragiele verbindingen met wat tape, klei en verpakkingsmateriaal fixeren) en alles stevig verpakken en verschepen. Klus geklaard, op naar het maken van een dashboard in The Things Network om de meetwaarden te verzamelen.

Testen en demonstratiegereed maken van het prototype, met de volledige schakeling buiten de behuizing

Ik heb de software van het prototype hieronder opgenomen om een indruk op te kunnen doen van de extra controles, timings en teksten:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
// 5Groningen Leefkwaliteit sensor
// All rights reserved
// Written by Rudi Niemeijer - TGRP - rn@tgrp.nl
// Version 1.0 - 23-01-2017 - Creation
//
// This is the firmware for the Leefkwaliteit Sensor, a prototype for a future 5G application. This
// particular device has consists of the following hardware:
// Arduino Leonardo
// PIR-sensor                               .
// SC6Dlite serial 6-digital led display    .
// MH-Z14A CO2 sensor                       .
// RN2483 LoRaWAN module                    .
// 5V USB Power Bank
// All is mounted in a dedicated box with 5Groningen artwork on top.
//
// The working is as follows:
// The Leonardo will take a CO2 measurement from the MH-Z14A. The measured value is added to the running average
// and the value is divided by two. The resuling value is sent via LoRaWAN to the backend.
// The Leonardo will count 30 seconds down. 
 
#include <SoftwareSerial.h>
 
#define SensorRX 10           // Yellow
#define SensorTX 11           // White
#define LoraRX     8          // Yellow
#define LoraTX     9          // White
#define displayRX      5
#define displayTX      6
#define defaultWakeTime 30
#define pirPin 13
#define displayID 255
#define warmUpSeconds 0       // CO2 sensor seconds before first measurement can be taken, should be 180
#define sendEvery 60
 
bool motionDetected = false;        // Once motion is detected, wakeTime is set to defaultWakeTime
bool motionLastTimeChecked = false; // Was there motion last time we checked?
int co2AveragePpm = 0;              // Keep track of average running CO2 ppm
bool lastCO2measurementOK = false;  // Last CO2 measurement was OK
int wakeTime = 0;                   // Seconds before display is switched off
bool displayIsOn = false;           // Status of the display: on or off 
bool lifeDot = false;               // Dot led 6
byte crlf[2] = {0x0D,0x0A};         // Used to terminate RN2486 commands
bool networkJoined = false;         // Keep track if we're connected
bool recentSendSuccess = false;           // Some indication for succesful send data
int cycles = 0;
bool bootState = true;              // If we're booting, sensor is not trustable
long startMillis = millis();        // Milliseconds since program start
bool demoMode = false;              // Demo mode is entered when invalid CO2 reading
 
// Define the serial ports
SoftwareSerial co2sensor (SensorRX, SensorTX);
SoftwareSerial lora (LoraRX, LoraTX);
SoftwareSerial leddisplay (displayRX, displayTX);
 
int runningSeconds() {
  return (millis() - startMillis) / 1000;
}
 
// Assume the lora to be open
void sendCommand(String cmd) {
  delay(1000);                  // This delay seems to be pretty important
  Serial.println("> " + cmd);
  lora.print(cmd);
  lora.write(crlf, 2);
  delay(1000);
}
 
// Assume the lora port to be open
String getResponse() {
  String response = "";
  while (lora.available()) {
    response = response + char(lora.read());
  } 
  response = response + "";
  return response;
}
 
// Initialise the RN2486 LoRa device
bool initLora() {
  String r;
  lora.begin(57600);
 
  delay(1000);
 
  sendCommand("sys reset");
  delay(1000);
  Serial.println(getResponse());
 
  sendCommand("mac join otaa");
 
  r = getResponse();
  r.trim();
  Serial.println(r);
  if (r == "ok") {
    Serial.println("(wait for next part)");
 
    while (!lora.available()) {
    }
    r = getResponse();
    Serial.println(r);
    r.trim();
    if (r == "accepted") {
      // There. A valid LoRa connection
      Serial.println("Network joined");
      networkJoined = true;
    } else {
      // Denied. Either no free channels or something else
      Serial.println("Denied. That's a bummer. No free channel or budget used up. Retry later");
      networkJoined = false;
    }
  } else {
    // Not a wanted response. Something with the hardware
    // We might want to throw a panic here
    Serial.println("Connecting with The Things Network didn't work out. No idea why");
    networkJoined = false;
  }  
  if (networkJoined) {
    return true;
  } else {
    return false;
  }
  lora.end();
}
 
void sendMeasurement(int payload) {
  String n = "";
  byte b1, b2;
  if (payload < 256) {
    n = String(payload, HEX);
  } else {
    b1 = payload / 256;
    b2 = payload - (b1 * 256);
    n = String(b1, HEX) + String(b2, HEX);
  }
  transmitData(n);
}
 
int transmitData(String payload) {
  String r;
  recentSendSuccess = false;
  if (networkJoined) {
    lora.begin(57600);
 
    sendCommand("mac tx cnf 1 " + payload);
    r = getResponse();
    r.trim();
    Serial.println(r);
    if (r == "ok") {
      while (!lora.available()) {
      }
      r = getResponse();
      r.trim();
      Serial.println(r);
      if (r != "mac_tx_ok") {
        // We got data back! Not sure what to do with it though.
        recentSendSuccess = true;
      } else {
        // No data back, just a succesful send
        recentSendSuccess = true;
        // mac_tx_ok
      }
    } else {
      recentSendSuccess = false;
      // Invalid parameters
    }
    lora.end();
  }
}
 
// display a message
// 
void displayMessage(String message) {
  leddisplay.begin(9600);
 
  if (bootState) {
    if (runningSeconds() > 30) {
      bootState = false;
    } else {
      message = "boot " + String(3 - (runningSeconds() / 10));
    }
  }
 
  // display the message here
  while (message.length() < 6) {
    message = " " + message;
  }
  leddisplay.write(displayID);
  leddisplay.write('b');
  if (wakeTime > 0) {
    leddisplay.println(message);
  } else {
    leddisplay.println("");
  }
  leddisplay.end();
}
 
void checkMotionPresent() {
  int pinHigh;
  motionLastTimeChecked = digitalRead(pirPin);
  if (motionLastTimeChecked) {
    motionDetected = true;
    if (motionDetected) {
      wakeTime = defaultWakeTime;  // Reset wakeTime counter to defaultWakeTime seconds
    }
  } else {
    if (wakeTime == 0) {
      motionDetected = false;
    }
  }
  Serial.println("Motion checked " + String(motionLastTimeChecked) + " timeout " + String(wakeTime));
}
 
void dotStatus() {
  byte dots = 0;
 
  if (motionDetected) {
    bitSet(dots, 0);
    Serial.println("Status: Motion with delay");
  }
 
  if (motionLastTimeChecked) {
    bitSet(dots, 1);
    Serial.println("Status: Actual motion");
  }
 
  if (networkJoined) {
    bitSet(dots, 2);
    Serial.println("Status: LoRa");
  }
 
  if (recentSendSuccess) {
    bitSet(dots, 3);
    Serial.println("Status: Send success");
  }
 
  if (lastCO2measurementOK) {
    bitSet(dots, 4);
    Serial.println("Status: CO2");
  }
 
  if (demoMode) {
    dots = 0;
    bitSet(dots, 5);
  }
 
  leddisplay.begin(9600);
  leddisplay.write(displayID);
  leddisplay.write('c');
  leddisplay.write(dots);
  leddisplay.end();
}
 
int takeCO2Reading() {
  byte cmd[9] = {
    0xFF,0x01,0x86,0x00,0x00,0x00,0x00,0x00,0x79}; 
  byte response[9]; 
  byte calculatedChecksum, givenChecksum;
  int ppm;
 
  co2sensor.begin(9600);
 
  // while(co2sensor.available()) {
  //   co2sensor.read();
  // }
 
  co2sensor.write(cmd, 9);
  delay(100);
  co2sensor.readBytes(response, 9);
 
  // Check checksum and set value below to true
  //    0 - Starting byte   0xFF
  //    1 - Command         0x86
  //    2 - MSB measurement
  //    3 - LSB measurement
  // 4..7 - not used
  //    8 - Checksum
 
  givenChecksum = (byte) response[8];  
  calculatedChecksum = 0;
  for (int i = 1; i<= 7; i++) {
    calculatedChecksum = calculatedChecksum + response[i];  // calculatedChecksum will overflow
  }
  calculatedChecksum = (0xFF - calculatedChecksum) + 1;
  Serial.println("Given checksum: " + String(givenChecksum, DEC) + ", calculated: " + String(calculatedChecksum, DEC));
  if (calculatedChecksum == givenChecksum) {
    lastCO2measurementOK = true;
    demoMode = false;
    byte responseHigh = response[2];
    byte responseLow = response[3];
    ppm = (256 * responseHigh) + responseLow;
  } else {
    lastCO2measurementOK = false;
    byte responseHigh = response[2];
    byte responseLow = response[3];
    demoMode = true;
    ppm = 400 + random(50);
  }
 
  co2sensor.end();
  return ppm;
}
 
void setup() {
  pinMode(pirPin, INPUT);
  wakeTime = defaultWakeTime;
 
  Serial.begin(57600);
  displayMessage("");
  co2AveragePpm = takeCO2Reading();
  initLora();
}
 
// Main loop that is run every second
// Contains all logic, measurements and display calls
void loop() {
  cycles += 1;
 
  delay(400);                // Delay 400 milliseconds in order to have this loop run every second
 
  // Count down, if no motion the display is switched off
  checkMotionPresent();
  if (wakeTime > 0) {
     wakeTime = wakeTime - 1;
  }
 
  // Measure CO2 value and display
  co2AveragePpm = (4 * co2AveragePpm + takeCO2Reading()) / 5; // Dampen new value a bit
  Serial.println("Current average ppm "+String(co2AveragePpm));
  displayMessage(String(co2AveragePpm)); 
 
  delay(1500);
 
  // Send CO2 value through LoRa, but not every loop
  if (cycles > sendEvery) {
    sendMeasurement(co2AveragePpm);
    cycles = 0;
  }
  if (!networkJoined) {
    initLora();
  }
  dotStatus();
}

De klant was blij verrast, getuige de Twitter-reactie:

Arduino communicatie met een RN2483 LoRaWAN module aan The Things Network

Ik heb vandaag geprobeerd een RN2483 LoRaWAN module met een Arduino aan de praat te krijgen. Het parsen van de communicatie van en naar de mdule ging niet gemakkelijk: de RN2483 module heeft soms een precieze timing, variaties in de antwoorden en de TheThingsNetwork.h bibliotheek is op de meeste combinaties van Arduino’s en uitbreidingen niet te gebruiken (Arduino Diecimila bijvoorbeeld, of Arduino Leonardo met Xbee Shield). En als je moeite hebt om het werkend te krijgen dan heb je met TheThingsNetwork.h als abstractielaag vervolgens ook geen idee wat er verkeerd gaat. Met een terminal daarentegen is het vrij gemakkelijk om een RN2486 met The Things Network aan de gang te krijgen:

> sys reset
RN2486...
> mac join otaa
ok
accepted
> mac tx cnf 1 1234
ok
mac_tx_ok

Bovenstaande instructies is genoeg om data te versturen. Ik heb vandaag daarom een paar uur besteed om de abstractielaag weg te poetsen en de module direct via de seriële Arduino poort aan te sturen.

// Routine that uses the SoftwareSerial library to communicatie
// with an RN2483 LoRaWAN module
// Apart from SoftwareSerial there is no abstraction layer, thus
// all steps and code are visible.
// Note: although this code does work, it is not complete. Take this
// code as an example how to communicate with the RN2483 module

// SoftwareSerial can hook up to any two digital pins. More than
// one serial port can exist, but only one can be open at any time
#include <SoftwareSerial.h>

// Connect the RN2486 module to the following pins
#define LoraRX 8 // Yellow
#define LoraTX 9 // White

// Some variables to set right
byte crlf[2] = {0x0D,0x0A};
String r;
SoftwareSerial lora (LoraRX, LoraTX);

// This is the main send routine
void sendCommand(String cmd) {
  lora.print(cmd);
  lora.write(crlf, 2);
}

// This is the main receive routine
String getResponse() {
  r = "";
  while (!lora.available()) { // Linger here while no response
  } // Might be better to create a timeout

  // There is data to get, get it while it lasts
  while (lora.available()) {
    r = r + char(lora.read());
  } 
  r = r + "";
  r.trim();
  return r;
}

void setup() {
  lora.begin(57600);  // RN2486 default baud rate
  delay(100);  // Short delay to make sure the comms are up

  // Reset the module 
  sendCommand("sys reset");  // Reset the module
  r = getResponse();  // We expect a long string here
  // Containing module version information

  // Connect with the network 
  sendCommand("mac join otaa");  // Join OTAA
  r = getResponse();
  if (r == "ok") {  // That is the response we would like to have
    r = getResponse();  // There is going to be a follow-up
    if (r == "accepted") {
      // We're joined with the network
    } else {
      // 'denied'. No free channel, or max usage exceeded
      // Safe to try again later
    }
  } else { // not ok
    // Something went wrong, might be hardware
    // This should be thrown higher up
  }
  lora.end();
}

void transmitData(int payload) {
  lora.begin(57600);
  delay(100); // Short delay to make sure the comms are up

  // Send the payload confirmed, group 1
  sendCommand("mac tx cnf 1 " + String(payload));
  r = getResponse();
  if (r == "ok") {
    r = getResponse();
    if (r != "mac_tx_ok") {
      // We got data back! Parse the values from r
    } else {
      // 'mac_tx_ok'. No data back, just a succesful send
  } else {
    // 'invalid params'. Invalid parameters, programming error
  }
  lora.end();
}
void loop() {
  // This would be the place to send data, if the
  // connection with The Things Network succeeded

  // transmitData(measureSomeSensor());
}

LoRaWAN gateway construeren en verbinden met The Things Network

The Things Network timmert hard aan de weg om wereldwijde LoRaWAN dekking te realiseren. Ze zijn hiervoor volledig afhankelijk van avonturiers en bedrijven die LoRaWAN gateways doneren. Dat hoeven er nog niet eens zo veel te zijn ook: met een stuk of tien gateways heb je Groningen op de kaart.

Onze waddeneilanden hebben nog weinig tot geen LoRaWAN dekking: op drie geplande (maar nog niet aanwezige) gateways op Terschelling na is het on-ontgonnen gebied. Met één zorgvuldig geplaatste gateway heb je op een eiland op de meeste plaatsen wel bereik, dus dat leek ons een mooi doel: Texel op de TTN-kaart zetten. Stap 1: een LoRaWAN gateway construeren. Stap 2: Texel verbinden met The Things Network. Stap 3: Verschillende eiland-toepassingen realiseren.

De TTN-wiki heeft een goed bouwverhaal over het maken van een gateway. Het verhaal eindigt echter wat abrupt na de bouw en installatie van de software: hoe je de gateway aan de praat krijgt blijft onbesproken. Mooie gelegenheid om het verhaal aan te vullen en van wat ervaringen te voorzien.

Kaart van verschillende LoRaWAN gateways in de buurt van de stad Groningen

Bouw

  • Sourcen van onderdelen: een IMST ic880A-concentrator board, een Raspberry Pi B+, een wifi-dongle, een antenne en verbindingskabeltje (‘SMA pigtail’), een waterdichte doos en wat verbindingsdraadjes
  • Bodemplaat voor in de waterdichte doos passend gemaakt en voorzien van gaten voor de ic880A en de Raspberry Pi
  • Printplaten, antenne en bodemplaat monteren
  • Bedrading leggen
  • Alle aansluiting controleren (en nog een keer controleren)
  • Raspberry Pi verbinden met een USB-voeding. Ook de ic880A wordt hiermee van stroom voorzien. Beide bordjes hebben verschillende leds die nu gaan branden
  • Raspberry Pi voorzien van de meest recente software (apt-get install en apt-get upgrade)
  • Raspberry Pi voorzien van de ic880A software met git clone -b spi https://github.com/ttn-zh/ic880a-gateway.git ~/ic880a-gateway
  • De software installeren met het install.sh shellscript dat in de /home/pi/ic880a-gateway directory is neergezet (sudo ./install.sh)
  • Het installatiescript toont nu het MAC adres van het ic880A concentrator board. Noteer dit, deze moet later in de console van TTN worden ingevoerd
  • Antwoord n op de vraag ‘Do you want to use remote settings file?’ en accepteer de default waardes voor ‘Host name’ en ‘Descriptive name’. Vul een bestaand email adres in bij ‘Contact email’. Zoek de coordinaten van de locatie waar de gateway wordt geplaatst op en vul deze bij ‘Latitude’ en ‘Longitude’ in. Geef de antennehoogte aan bij ‘Altitude’. De installatie wordt nu verder automatisch uitgevoerd
Prototype met concentrator en Raspberry Pi 3B

Inregelen en configureren

  • Tijdens de installatie van de software is er in de home directory een directory aangemaakt die /home/pi/ic880-gateway heet. Hiernaast is er een directory /opt/packet-forwarder aangemaakt. Beide softwaremodules hebben hun functie: de gateway regelt het verkeer aan de LoRa (radio) kant, de packet-forwarder zorgt voor het ontvangen en versturen van internetverkeer aan de backhaul (wifi) kant
  • Bij de installatie is de EUI van de gateway getoond. Deze moet aan de TTN kant bij het aanmaken van een nieuwe gateway worden ingevoerd

Testen

  • Een werkende gateway meldt zich binnen enkele seconden met zijn EUI op de staging pagina van The Things Network. Staat de gateway hier niet? Dat verbindt hij niet met TTN
  • Een werkende gateway waarvan de EUI bekend is op TTN wordt op de gateway pagina getoond en de doorvoer wordt gemonitord

Mogelijk problemen en oplossingen hiervoor

  • De gateway wordt bij het booten van de Raspberry Pi opgestart met /lib/systemd/system/ttn-gateway.service. Als alternatief is er een opstartscript genaamd /opt/ttn-gateway/bin/start.sh. Raspbian lijkt systemd niet geheel te ondersteunen. Neem dan start.sh op in /etc/rc.local
  • Voor alle vormen van foutzoeken is het handmatig opstarten van de gateway met start.sh verruit het handigst, omdat deze alle verbindingsinformatie over de terminal laat lopen

Nieuwe, compactere behuizing

Omdat het oog ook wat wil, de concentrator met alle spannende leds en andere onderdelen precies op de kop gemonteerd was, een Raspberry Pi Zero W ook prima zou werken en de behuizing van de gateway wel heel groot uitviel, heb ik eens geprobeerd hoe klein de behuizing zou kunnen zijn als ik de concentrator wat hoger zou monteren zodat de aansluitingen aan de onderzijde voldoende ruimte hebben en dan aan de bovenzijde de leds zichtbaar zouden zijn. Een waterdichte montagedoos is precies groot genoeg voor de concentrator en laat dan net voldoende ruimte voor een Raspeberry Pi Zero W, die ik dan wat lager monteer zodat de verbindingen hier aan de bovenkant kunnen zitten. 

Montagebehuizing met concentrator en Raspberry Pi Zero W

De montagegaten zaten niet op een handige plaats en om te voorkomen dat ik de behuizing van het waterdichte zou afhelpen heb ik een kleine plaat acrylglas op maat gezaagd om aan de binnenzijde op de aanwezige montagenokken te kunnen monteren, en in die plaat de benodigde gaten gemaakt om de concentrator met langere nylon schroeven en de Raspberry Pi Zero W met korte afstandsbussen vast te kunnen zetten.

Eindresultaat: LoRaWAN Gateway in waterdichte behuizing met mobiele antenne en zichtbare statusleds

Uitgekristalliseerde, praktische gereedschapskist voor thuis of in het vakantiehuis

Voor een ieder die een gereedschapskist wil samenstellen heb ik een lijst van gereedschappen die niet mogen ontbreken voor kleine, ongeplande klussen in en rond het huis. Vorig jaar heb ik al eens een poging gedaan de inhoud van een goede gereedschapskist te beschrijven, maar nu is de lijst getest en akkoord bevonden. Ik heb een basis set gereedschap in een kist gedaan en voor iedere nieuwe klus gekeken wat er nog aan gereedschap ontbrak en zo nodig aangevuld. Resultaat: een praktische set van onontbeerlijke hulpjes zonder al te veel onnodige poespas.

IMG_1047

Meer waddeneilanden dan je wist tussen Nederland en Denemarken!

Wij komen met regelmaat op Texel, het eerste en grootste van de Nederlandse waddeneilanden. Onze kinderen kennen het ‘TVTAS’ ezelsbruggetje. Maar wist je dat die sinds een tijdje niet meer klopt, dat het eigenlijk ‘NTVTAS’ zou moeten zijn? En welke eilanden komen er na Schiermonnikoog? En hoe heten die zandplaten die tussen de eilanden verstopt liggen? Vandaag maar eens een onderzoekje gedaan. Er zijn meer dan 50 waddeneilanden waar meer dan 80 duizend mensen wonen!

Noorzee- en waddeneilanden

“Waddeneilanden,” zegt Wikipedia, “liggen in de Noordzee, ten noorden van Nederland en Duitsland en ten westen van Denemarken. Tussen de eilanden en het vasteland ligt de Waddenzee. Het grootste eiland is het Nederlandse Texel, gevolgd door het Deense Rømø en het Duitse Sylt. Sylt heeft met ongeveer 21.000 inwoners de grootste bevolking van alle eilanden.” Huh? Maar de Waddenzee lag toch zo’n beetje tussen Den Helder en Termunterzijl? Nee dus: “de Waddenzee (Fries: Waadsee, Duits: Wattenmeer, Deens: Vadehavet) is de binnenzee tussen de Waddeneilanden en de Noordzee aan de ene kant, en aan de andere kant het vasteland van Nederland, Duitsland en Denemarken.”

Er wordt onderscheid gemaakt tussen eilanden en zandplaten. Eilanden staan (bij gemiddeld hoog water) voor tenminste 1,6 km2 boven water. Wikipedia: “Als de platen droogliggen kan er zich zand afzetten, waardoor ze steeds hoger worden. Sommige platen kunnen zo groeien dat ze alleen nog bij springvloed onder komen te staan of bij extreem hoogwater. Zo kunnen ze uitgroeien tot kleine eilanden.”

Door de wind en de zee is er een hoop beweging in de Waddenzee. Zandplaten groeien, verkleinen en verplaatsen, eilanden ontstaan en verdwijnen. Het meest recente nieuwe eiland is het Duitse Kachelotplate, dat in 2003 ontstond. Gaswinning lijkt daar overigens geen rol bij te spelen.

In de tabel hieronder heb ik alle eilanden en zandplaten in de Waddenzee opgenomen. Veel eilanden zijn gewoon wat je zou verwachten: een stuk land in zee, huizen erop, strand en toegankelijk per veerboot. Maar er zijn er, die met een dijk toegankelijk zijn. Of waar héél weinig mensen wonen. Of slechts ééntje.

Nr Naam Type Land Oppervlakte Inwoners
1 Noorderhaaks Eiland Nederland 4 km² 0
2 Texel Eiland Nederland 161 km² 13614
3 Vlieland Eiland Nederland 36 km² 1072
4 Richel Zandplaat Nederland < 1 km² 0
5 Griend Zandplaat Nederland < 1 km² 0
6 Terschelling Eiland Nederland 86 km² 4832
7 Ameland Eiland Nederland  59 km²  3617
8 Rif Zandplaat Nederland < 1 km² 0
9 Engelsmanplaat Zandplaat Nederland  < 1 km² 0
10 Schiermonnikoog Eiland Nederland  44 km² 914
11 Simonszand Zandplaat Nederland  < 1 km² 0
12 Rottumerplaat Eiland Nederland 8 km²  0
13 Rottumeroog Eiland Nederland  3 km² 0
14 Borkum Eiland Duitsland 31 km²  5225
15 Lütje Hörn Zandplaat Duitsland  < 1 km²  0
16 Kachelotplate Eiland (2003) Duitsland  6 km²  0
17 Memmert Eiland Duitsland  5 km²  0
18 Juist Eiland Duitsland  16 km²  1.589
19 Norderney Eiland Duitsland 26 km² 5.875
20 Baltrum Eiland Duitsland  7 km²  617
21 Langeoog Eiland Duitsland  20 km²  1.757
22 Spiekeroog Eiland Duitsland 18 km² 773
23 Wangerooge Eiland Duitsland 5 km²  1.055
24 Minsener-Oldoog Eiland Duitsland 4 km² 0
25 Mellum Eiland Duitsland 8 km²  0
26 Langlutjen I & II Eilanden Duitsland 3 km²  0
27 Neuwerk Eiland Duitsland 3 km²  33
28 Scharhörn Zandplaat Duitsland < 1 km²  0
29 Nigehörn Zandplaat Duitsland < 1 km²  0
30 Trischen Eiland Duitsland 3 km²  1
31 Blauort Zandplaat Duitsland < 1 km²  0
32 Pellworm Eiland Duitsland 37 km²  1158
33 Nordstrand Schiereiland Duitsland 40 km²  2218
34 de Halligen (10) Eilandengroep Duitsland 23 km²  256
35 Amrum Eiland Duitsland 20 km²  2300
36 Föhr Eiland Duitsland 82 km²  8600
37 Sylt Schiereiland Duitsland 99 km²  21000
38 Rømø Schiereiland Denemarken 130 km²  1000
39 Mandø Schiereiland Denemarken 8 km²  30
40 Fanø Eiland Denemarken 56 km²  3207
41 Langli Zandplaat Denemarken < 1 km²  0