• RSNL Clubkampioenschappen 2024

    Na meer dan veertien jaar heb ik eindelijk weer eens een wedstrijd gereden. Het was afwachten wat de overhand zou hebben; de verbetering van de techniek of de veroudering van het gestel 😜. Het bleek het laatste te zijn, want de PRs van toen zijn nu ver buiten bereik gebleken. Ik moest me tevredenstellen met een 01:03.220 op de 500 meter en 02:09.900 op de 1000 meter. Ter vergelijking, dit waren de PRs die ik bij Isis heb gereden:

    Afstand Datum PR
    300 m 2009-01-14 (Eindhoven) 00:37.920
    500 m 2009-02-18 (Eindhoven) 00:57.720
    1000 m 2009-02-18 (Eindhoven) 01:58.710
    1500 m 2009-02-19 (Eindhoven) 02:57.660
    3000 m 2008-03-11 (Thialf) 06:16.750

    Noud aan de start bij de NRW Pokal
    2023-12-16: Noud aan de start bij de NRW Pokal

    Inmiddels heb ik wel opvolging gekregen, want Noud rijdt sinds dit jaar ook fanatiek op de schaats en heeft al een aantal wedstrijden verreden. Met de sprongen die hij dit jaar heeft gemaakt duurt het niet lang voordat hij mijn tijden verpulvert. Ik kijk er nu al naar uit, hij heeft er in ieder geval heel veel plezier in; op naar de laatste wedstrijd van het seizoen!

    Datum Wedstrijd 100 meter 300 meter 500 meter 1000 meter
    2023-12-09 Förderkreispokal   01:06.870 01:46.330  
    2023-12-16 NRW pokal (2) 00:22.370   01:44.070  
    2024-01-07 Super Sprint 00:20.470 00:57.090    
    2024-02-17 RSNL Clubkampioenschappen     01:31.660 02:56.190
    2024-02-24 NRW pokal (3) 00:18.140 00:51.760    

    Update 25 februari: Tijden NRW pokal (3) toegevoegd.

    Lees verder
  • Hoe werkt een mechanisch horloge?

    In navolging van mijn vorige post, waarin ik zei dat we onze rol van ontdekkingsreizigers op het wereldwijde web weer moesten herpakken, vond ik een prachtig voorbeeld van zo’n website die er gewoon is om te zijn. De site van Bartosz Ciechanowski bevat niet heel veel artikelen, maar het zijn stuk voor stuk juweeltjes. Met werkende 3D modellen legt hij in jouw eigen webbrowser uiteenlopende principes stap voor stap uit.

    Een kleine greep uit het aanbod: Een gedetailleerde omschrijving van de verschillende bewegingen in een mechanisch horloge, van de grote veer tot aan de datumaanwijzer, de kroon en het automatisch opwindmechanisme. Een uiteenzetting van de principes achter het Global Positioning System (GPS), waaruit ook meteen af te leiden is dat zowat elke televisieserie die GPS-signalen beweert te hacken volledige onzin is. Een hogerdimensionale reis in de wereld van de tesseract die je doet duizelen. Een technische uitleg hoe een digitale computer toch met floating point getallen kan rekenen.

    Elke pagina is weer een ijzersterk didactisch verhaal gecombineerd met een vernuftig stukje techniek.

    Lees verder
  • Waar zijn alle websites heen?

    Ik zag laatst een artikel op Slashdot langskomen, waar de schrijver zich afvroeg waar alle websites gebleven waren.

    Het verhaal begint in 2009, toen het internet nog een digitale speeltuin was vol verrassingen. Facebook en Instagram waren als de coole kids op het schoolplein die de laatste roddels verspreidden, en surfen op het web betekende het intikken van webadressen met de hoop op een avontuurlijke ontdekkingsreis bij elke klik.

    Fast forward naar 2024 en het internet lijkt een totale make-over te hebben ondergaan. De “Voor Jou Pagina” levert nu op maat gemaakte content van je favoriete “creators”, speciaal afgestemd op verschillende platforms. De ooit gevarieerde wereld van websites lijkt echter te zijn verdwenen, alles is in hetzelfde format gedwongen, en dat roept vragen op over de huidige toestand van het web en waarom we er helemaal niet zo blij meer mee zijn.

    De echte verandering zit hem echter in onszelf. De vreugde van ontdekking en “curatie”, ooit essentieel voor de internetervaring, lijkt te zijn uitbesteed aan bedrijfsalgoritmen. We scrollen nu eindeloos passief door content, vertrouwend op algoritmes voor onze digitale avonturen. Algoritmes die echter hele andere belangen dienen…

    CoderDojo: bouw je eigen website

    De oplossing? Laten we teruggaan naar onze rol als ontdekkingsreizigers! Iedereen kan een curator worden door open webportals te creëren. Gebruik platforms zoals Linktree.com om je favoriete blogposts, artiesten of coole websites te delen. Heb je meer te melden, open je eigen website op WordPress.com en link ernaar vanuit je socials in plaats van jouw ideeën op te sluiten in de platforms van anderen. Laten we de interconnectiviteit nieuw leven inblazen en het web weer leuk maken om te ontdekken, zonder dat je een computernerd hoeft te zijn.

    En als je nog een stapje verder wilt gaan hoeft het helemaal niet ingewikkeld te zijn. Het boek hiernaast heb ik bij ons in de bibliotheek gevonden voor Jasper die op school als verrijkingsproject “Een eigen website” heeft gekozen. Bouw je eigen website, stap voor stap, van de grond op. Je eigen virtuele volkstuintje op het internet.

    Lees verder
  • Arduino ✕ PlatformIO

    De electronica en code voor de Rheinturm zijn al een hele tijd af, maar ik moet nog steeds een “behuizing” voor het geheel bedenken. Ik twijfel nog tussen een eenvoudige houten plaat of een uitgesneden vorm, maar een 3D geprint model zou ook heel tof zijn. Blijkbaar zijn 3D-printers tegenwoordig ook niet meer super duur als je van prutsen tinkeren houdt… Anyway, dat is weer een heel andere hobby!

    Voorlopig ligt de boel dus veilig in een doos opgeborgen te wachten op de afwerking. Tot die tijd wil ik de code wel een beetje netjes achterlaten, zodat het later makkelijk weer op te pakken is. Daarbij heb ik al gemerkt dat de Arduino IDE niet het beste stuk software is om dat mee te doen. Arduino-Rheinturm gebruikt een aantal software-bibliotheken die ingeladen moeten worden en de vraag is of die over twee of drie jaar nog werken in de Arduino IDE.

    Daarnaast is het prototype wat ik nu heb gebouwd weliswaar gebaseerd op een Arduino UNO, maar heb ik tegen de tijd dat ik de definitieve versie maak, misschien een ander type controller waar de Arduino IDE helemaal niet mee samenwerkt. Wat ik nodig heb, is een manier om te zeggen welke bibliotheken ik nodig heb en hoe de broncode moet worden gebouwd voor de controller die ik op dat moment heb. Als je ooit een Java-project hebt gemaakt, zul je denken aan Maven of Gradle. Voor Ruby heb je Gemspecs en Bundler, voor Python is er setup.py (of tegenwoordig project.toml) en pip en in de JavaScript-wereld gebruik je package.json en npm.

    PlatformIO

    Het van oorsprong Oekraïense project PlatformIO is zo’n tool voor embedded software development. Een enkele platformio.ini in je project en de sources op een aangewezen plek (Convention over Configuration), meer heb je niet nodig om snel weer op weg te zijn als je een tijd weg bent geweest van je project.

    [env:uno]
    platform = atmelavr
    board = uno
    framework = arduino
    lib_deps =
        bxparks/AceTime @ ^2.0.1
        adafruit/Adafruit NeoPixel @ ^1.11.0
        northernwidget/DS3231 @ ^1.1.2
        rlogiacco/CircularBuffer @ ^1.3.3
    

    Doordat spullen op voor PlatformIO bekende plaatsen staan, kan die ook de benodigde projectbestanden genereren om het te openen in je IDE, zonder dat die bestanden in Git hoeven te worden gezet. Vandaag de dag werk ik met JetBrains CLion, maar misschien is dat morgen Visual Studio Code. Die snappen elkaars projectstructuur niet, dus dan is het fijn dat je niet helemaal opnieuw hoeft te beginnen met de inrichting, maar gewoon platformio -c clion init of platformio -c vscode init kan doen en alles staat weer recht.

    Daar houdt het echter niet op, want als ik een andere microcontroller gebruik, dan kan ik eenvoudig een nieuwe minimale configuratie aanmaken en ben ik ook weer op weg. Met de juiste hardware zou het zelfs mogelijk moeten zijn om unit testen te schrijven die de code kan verifiëren.

    CLion

    De overstap van Arduino IDE via PlatformIO naar CLion was wel fundamenteel, want plotseling had ik een hele krachtige omgeving tot mijn beschikking waarmee het programmeren in C++ daadwerkelijk ondersteund wordt. Het nodigde uit tot opschonen en opsplitsen van de code, waardoor ik nu een heel nette structuur heb die ik over een jaar of twee ook nog begrijp. En ja, dan kan je de code ook maar beter publiek maken, want misschien heeft iemand er ooit nog iets aan.

    Lees verder
  • VR panorama browser

    Zoals ik afgelopen week al schreef, wilde ik de panorama-viewer verder uitbreiden om meer foto’s toe te voegen. Interacties in de derde dimensie werken echter nét anders dan op het platte scherm. Daarnaast zijn allerlei conventies nog helemaal niet uitgewerkt, dus iedereen doet maar wat en dat is heel verfrissend.

    In de 20e eeuw werden de nieuwe grafische interfaces aangeleerd door het gebruik van bedieningselementen die waren ontworpen naar het voorbeeld van een fysieke tegenhanger, een skeuomorfisme zoals dat heet. Mappen, schuifknoppen en zandlopers; allemaal elementen uit de echte wereld om ons te laten begrijpen hoe de interface werkt in de platte digitale wereld.


    Meta Quest: Zwevende 2D windows met een 3D sausje

    Digitale 3D werelden kunnen werken op een heel andere manier, maar de interface-ontwerpers maken het zich er makkelijk vanaf en plakken gewoon een 2D interface in de lucht, want die kennen we al. Ze willen het ons nu nog niet te lastig maken, maar de échte revolutie moet nog komen, denk ik.

    Gemiste kans
    Apple visionOS: Nog niet veel beter

    Simpel

    Hoe dan ook, ik wilde iets simpels maken om mee te beginnen. Met A-Frame doe je dat met Custom Elements in HTML. Hieronder zie je het specifieke deel om de scene te definiëren.

    <a-scene>
        <a-assets>
            <img id="image-0"   src="image-0.jpg">
            <img id="city"      src="https://cdn.aframe.io/a-painter/images/sky.jpg">
            <img id="floor"     src="https://cdn.aframe.io/a-painter/images/floor.jpg">
        </a-assets>
        <a-sky src="#city"></a-sky>
        <a-curvedimage id="panorama"    src="#image-0"
                       position="0 2 0" height="2.5" radius="2"
                       theta-start="60" theta-length="240"></a-curvedimage>
        <a-cylinder id="ground" src="#floor" radius="32" height="0.1"></a-cylinder>
    </a-scene>
    

    Eerst definieer je een aantal assets, in dit geval afbeeldingen, waar je vanaf andere plekken naar kan verwijzen. Om te voorkomen dat je in het luchtledige lijkt te zweven is het verstandig een a-sky toe te voegen met een 360º afbeelding. Daarnaast heb ik een a-cylinder toegevoegd die als een soort vloer dient.

    De a-curvedimage bevat het echte panorama-plaatje en is eigenlijk een boogsegment van de wand van een cilinder. Het is een beetje zoeken naar de juiste waardes, maar de meeste van mijn panorama’s zijn ongeveer 240º. Als het niet helemaal klopt, is dat nog niet zo’n ramp, want dat zie je amper, behalve als je met veel rechte lijnen werkt en het allemaal precies zou moet passen.

    Nu voeg ik nog twee a-image toe onder de panorama om zometeen vooruit en achteruit te kunnen en voila, de scène is zo’n beetje klaar:

    Overzicht van de scene
    Overzicht van de scene

    Met A-Frame hoef je je om de rendering verder geen zorgen meer te maken. Je headset schakelt om naar VR-modus en je wordt omringd door de scène die je hebt gebouwd. De interactie daarentegen zul je wel zelf moeten opbouwen, want anders dan op het platte web kan je hier niet gewoon ergens een <a href=...> wegzetten om een link te maken.

    Interactie

    In 2D interfaces zijn we gewend dat we één aanwijzer hebben die we eventueel met meerdere apparaten kunnen bedienen ( muis, touchpad, tekenpen, …). In de 3D wereld zijn al die apparaten opeens onafhankelijke bedieningen geworden, vergelijkbaar met hoe een smartphone elke vinger apart detecteert en “multitouch gestures” herkent. A-Frame ondersteunt hoofd- en controller-tracking, dus je kijkrichting en wat je met je controllers doet kan als invoer dienen. Omdat je hoofd (waarschijnlijk) geen knoppen heeft, werkt een interactie daarmee anders dan met een hand-controller.

    Een controller declareer je door een a-entity te maken met een aantal attributen, bijvoorbeeld zo:

    <a-entity oculus-touch-controls="hand: right" 
              laser-controls 
              b-button-listener></a-entity>
    

    Met oculus-touch-controls="hand: right" zeg je dat je de Meta Quest controller voor de rechter hand wil configureren. De laser-controls zorgt ervoor dat de controller een laserstraal afschiet in de wijsrichting, en dat een aantal events worden afgevuurd als die laserstraal een object raakt, waarover later meer.

    Uit de VR-modus gaan

    Ten slotte koppel ik met b-button-listener mijn eigen code aan de controller hardware:

    AFRAME.registerComponent('b-button-listener', {
        init: function () {
            this.el.addEventListener('bbuttondown', function (e) {
                const sceneEl = document.querySelector('a-scene');
                if (sceneEl.is('vr-mode')) {
                    sceneEl.exitVR();
                }
            });
        }
    });
    

    Dit zorgt ervoor dat als je op de B knop op de controller drukt wanneer je in VR-mode bent, dat je uit VR-mode gaat.

    Een foto vooruit of achteruit gaan

    De twee kleine fotootjes onderaan op het screenshot had ik nog niet aan de scène toegevoegd, dus dat doen we nog even:

    <a-image id="prev-image"        src="#image-2"
             position="-1 0.5 -1.5" rotation="-45 30 0"
             width="1"              height="0.33"
             cursor-listener="direction: prev"></a-image>
    <a-image id="next-image"        src="#image-1"
             position="1 0.5 -1.5"  rotation="-45 -30 0"
             width="1"              height="0.33"
             cursor-listener="direction: next"></a-image>
    

    Hier zie je de cursor-listener component. Deze code is straks verantwoordelijk voor het naar het click signaal luisteren dat de laser-controls component produceert wanneer je op een van de a-images “klikt”. Je ziet dat je extra informatie kan meegeven, net als bij de oculus-touch-controls, om de component te configureren.

    AFRAME.registerComponent('cursor-listener', {
        schema: {
            direction: {type: 'string', default: 'next'}
        },
    
        init: function () {
            this.el.addEventListener('click', (e) => {
                switch (this.data.direction) {
                    case 'prev':
                        nextImage(-1);
                        break;
                    default:
                        nextImage(1);
                        break;
                }
            });
        }
    });
    

    In de code hierboven wordt aan this.el, dat is de a-image waar het op staat, een event listener toegevoegd voor het click signaal. Afhankelijk van de waarde van direction zal hij nu de volgende of de vorige afbeelding inladen.

    De logica voor het wisselen van de afbeelding is niet A-Frame of WebXR specifiek en kan je vinden in de source van de panorama pagina (net als de rest van deze blog trouwens).

    En dat is het! Het resultaat is een wereld waarin je kan schakelen tussen zes panorama’s met behulp van je controllers! Voor degenen zonder headset, een filmpje:

    Lees verder
  • Aan de slag in Virtual Reality met WebXR

    We hebben onszelf laatst een VR-headset cadeau gedaan. Om Beat Saber te spelen natuurlijk, maar ook om lekker mee te knutselen. Er zijn native ontwikkelomgevingen voor zo’n beetje elke headset die op de markt te krijgen is, maar er is ook een W3C specificatie voor VR- en AR-applicaties, gezamenlijk vaak XR genoemd. Die specificatie heet WebXR en breidt het 2D web uit met de derde dimensie.

    Nu is de W3C specificatie vrij low-level dus is het lastig daar een toepassing mee te maken, maar er zijn al frameworks ontwikkeld die het veel makkelijker maken. Een van die frameworks is A-Frame en daarmee heb ik al een simpele viewer geknutseld voor de panoramafoto’s die ik in de loop van de tijd heb gemaakt.

    Met een desktop browser kan je die pagina gewoon openen en in XR-emulatie bekijken, met een headset krijg je een “immersieve ervaring” zoals dat zo mooi heet.

    Kijk maar eens naar het panorama van de Pont du Gard in VR wat ik afgelopen vakantie heb gemaakt.

    Pont du Gard

    Ik wil de viewer nog verder uitbreiden zodat het een echte applicatie is waarin je meerdere panorama’s kan bekijken, want ze komen in VR echt veel beter tot hun recht dan op een plat beeldscherm.

    Lees verder
  • Djöflaterta

    Lang geleden leefde ik een paar maanden in IJsland, waar ik kennismaakte met de perfecte IJslandse variant van de Devil’s Pie: Djöflaterta. Nu was ik niet helemaal kapot van het recept wat ik destijds had en ben ik op zoek gegaan naar een nieuwe samenstelling.

    Ik heb denk ik de perfecte receptuur nu gevonden: Djöflaterta sem bráðnar í munninum oftewel “Duivelse taart die smelt in je mond”.

    Voor het beslag

    Voeg de ingrediënten een voor een toe en blijf mixen op hoge snelheid.

    150 gr margarine
    1 1/2 cup suiker (355 ml)
    3 eieren
    2 cups bloem (473 ml)
    1 tl bakpoeder
    1/2 tl zout
    1 tl baking soda
    2 tl vanilledruppels
    1 cup melk (237 ml)
    2 el cacao

    Meng alles door elkaar en doe in 2 springvormen en bak 20-30 min op 180°. Houd het in de gaten, zodat het bovenop niet te donker wordt.

    Met een enkele springvorm lukt het ook, dan duurt het ongeveer 40-50 minuten en moet je het uiteindelijk horizontaal snijden. Dit recept gebruikt zowel bakpoeder als baking soda; het deeg is dan ook heel erg “fluffy” als het is gebakken.

    Voor de crème

    50 gr boter
    300 gr poedersuiker
    1 ei
    1 tl vanilledruppels
    2-6 el melk
    2 el cacao
    of 2 eieren en sla de melk over

    Laat de cake volledig afkoelen voordat je de creme erop smeert, want anders loopt het er zo weer uit.

    Voor de ganache

    Hiervoor heb ik een ander “recept” gevonden wat goed werkt. Gebruik pure chocolade en slagroom in 1:1 verhouding. Smelt de chocolade au bain-marie en laat het een beetje afkoelen. Breng de slagroom aan de kook en voeg het bij de chocolade. Goed roeren en laten afkoelen tot het een stevige pasta is, dat kan prima in de koelkast. Wel regelmatig doorroeren zodat je niet een grote dikke klont krijgt.

    Nu kan je de pasta eenvoudigweg eroverheen smeren en ziet het er zalig uit, en zo smaakt het ook…

    Djöflaterta

    Lees verder
  • Stabiel de tijd bijhouden tussen herstarten

    Met baby steps, zo komen we er wel. Eerder had ik het erover dat de Arduino geen eigen RTC (Real Time Clock) heeft en daardoor ten eerste een vrij grote afwijking van enkele seconden per dag heeft en ten tweede de tijd-instelling steeds kwijtraakt bij herstarten.

    Er zijn twee goed ondersteunde RTCs voor Arduino verkrijgbaar; de DS1307 en de DS3231. Qua prijs ontlopen ze elkaar niet veel, maar qua precisie is de DS3231 absoluut superieur met een afwijking van maar ±2 minuten per jaar. Aansluiten op de Arduino, het wordt langzaam een beetje voorspelbaar, is weer een fluitje van een cent. VCC en GND naar de plus en min, SDA direct op analoge poort A4 en SCL direct naar analoge poort A5.


    DS3231 met batterijhouder en pins

    Vervolgens kan je met de DS3231 library de boel besturen:

    #include <DS3231.h>
    #include <AceTime.h>
    
    BasicZoneProcessor zoneProcessor;
    TimeZone tz = TimeZone::forZoneInfo(&zonedb::kZoneEurope_Amsterdam, &zoneProcessor);
    
    void setup() {
        // Initialiseer de verbinding
        Wire.begin();
        Serial.begin(9600);
    }
    
    void loop() {
        // Haal de huidige tijd op uit de RTC
        DateTime rtcNow = RTClib::now();
        ZonedDateTime now = ZonedDateTime::forUnixSeconds64(rtcNow.unixtime(), tz);
        now.printTo(Serial);
        delay(100);
    }
    

    De tijd instellen

    De kans dat de RTC de juiste tijd geeft als je hem voor het eerst gebruikt is op zich vrij klein, dus moet je hem eerst instellen. Via de seriële poort van de Arduino kunnen we de module instellen:

    void loop() {
        // Als er iets op de seriële poort klaar staat...
        if (Serial.available()) {
            // ... dan lees het als een integer
            long readLong = Serial.parseInt();
            // ... en lees de rest van de buffer leeg, zoals een <enter> of spaties
            while (Serial.available()) {
                Serial.read();
            }
            // stel de RTC in op de integer die van de seriële poort is ingelezen
            rtc.setEpoch(readLong);
        }
        ZonedDateTime now = ZonedDateTime::forUnixSeconds64(RTClib::now().unixtime(), tz);
        now.printTo(Serial);
        delay(100);
    }
    

    Vervolgens kunnen we de tijd instellen door de Unix epoch timestamp over de seriële poort naar de Arduino te sturen. De huidige Unix epoch krijg je met het commando date +%s. Je kan de uitvoer daarvan in de Arduino IDE naar de seriële poort sturen (Tools | Serial monitor), maar het kan ook vanaf de command line met het cu commando. Eerst moet je weten welk device je seriële poort heeft; in de Arduino IDE zie je dat onderin het scherm als je Arduino verbonden is. Bij mij is dat /dev/cu.usbmodem101, vervolgens start je cu met het commando:

    $ sudo cu -l /dev/cu.usbmodem101
    

    Nu zie je alles wat over de seriële poort binnenkomt, dus in dit geval elke 100 milliseconden de huidige tijd. Het cu commando kan echter ook berichten terugsturen (zie man cu), en heeft zelfs nog functionaliteit om de invoer uit een ander programma te halen door middel van ~$<command>. We krijgen de huidige epoch met date +%s (zie ook man date).

    Als je door de stroom aan timestamps heen het commando <enter>~$date +%s invoert, zul je zien dat de timestamps opeens de huidige tijd weergeven. Aangezien je dit maar af en toe hoeft te doen zou je kunnen overwegen dit in een apart programma te doen, want dat scheelt weer in het geheugengebruik. Je sluit cu netjes af met het commando <enter>~..

    Andere tijdsignaal-bronnen

    Een RTC is zeker niet de enige bron van tijdsignalen. Als je een WiFi module toevoegt aan de Arduino, kan de tijd opgehaald worden vanaf een NTP-server. Met een GPS-ontvanger kan je de tijd ophalen van satellieten. Een nog andere optie is om een tijdsignaal van de DCF77-zender in Mainflingen bij Frankfurt op te vangen. Ook de originele klok in de Rheinturm doet dit!

    Ik wil daar in de toekomst nog wel eens naar kijken, maar op dit moment is de klok volledig zelfstandig operationeel! De volgende stap is eerst iets compleet anders: de lichtstrip moet worden ingebouwd in een meer permanente opstelling.

    Lees verder
  • Vrolijke steentjes uit Tsjechië gevonden, en dan?

    Met Pasen was ik samen met Jasper op vader-zoon weekend naar Parijs, waarover later misschien nog wat meer. Op onze zoektocht naar de Vrijheidsbeelden van Parijs kwamen we in de Jardin du Luxembourg een exemplaar tegen. Er staan twee bordjes bij die wat achtergrondinformatie over dit beeld geven en vóór een van die bordjes vonden we een beschilderde kiezelsteen.

    Keihard surprise-ei

    Nu kom je die overal wel tegen, elk dorp en elke stad heeft wel een stel creatievelingen die het leuk vindt om een glimlach op het gezicht van een ander te toveren. Rond het begin van de coronapandemie zag je ze overal opduiken, “Happy Stones” werd het genoemd in goed Nederlands, in Engelstalige landen heette het “Kindness Rocks” (mét woordspeling!). Vaak gingen ze anoniem de wereld in, maker en vinder bleven onbekend. Soms stond er nog een hashtag op die je naar een Instagram- of Facebook-pagina stuurt, maar het was allemaal kleinschalig en individueel. Ook wij hebben nog eens wat geknutseld voor op school, met eigen hashtag en Insta-account. Het grootste initiatief lijkt Happy Stones Limburg te zijn, maar dat heeft ook maar een paar duizend deelnemers.

    Deze steen was anders; deze had de Tsjechische vlag 🇨🇿 en het Facebook-logo erop staan, een nummer van vijf cijfers (67555) en iets wat op een datum (01/23 - januari 2023?) lijkt. Iemand in Tsjechië heeft deze steen bovendien gesigneerd met LH. Ik besluit uit te gaan zoeken hoe ze Happy Stones in Tsjechië doen.

    Veselé Kameny

    De vertaling van Happy Stones volgens Google Translate is Veselé Kameny. Op Facebook is dat een nogal lege pagina met 30 volgers en vrijwel geen leven. De zoekfunctie geeft me wel veel resultaten uit een andere groep genaamd Kamínky. Google Translate vertaalt dit ook met stenen, maar Tsjechisch is familie van het Pools, waarvan ik weet dat de uitgang ‘-nki’ en ‘-nka’ verkleinwoorden maken: Steentjes dus. De groep heeft bijna 400.000 leden, ik denk dat ik hier wel goed zit. Happy Stones is huge in Tsjechië!

    Een klein nadeel, want net als dat de kleine Nederlandse groepen in het Nederlands zijn, is deze groep volledig in het Tsjechisch. Uit de uitleg maak ik op dat het nummer de postcode van deze kunstenaar is. De postcode 675 55 is Hrotovice of Slavětice, zo’n 50 kilometer ten westen van Brno. Ik besluit een post in de groep te zetten en nadat deze is goedgekeurd krijgt die al vrij snel een reactie met daarin een naam die voldoet aan de initialen LH die inderdaad in Slavětice woont. De posts van het profiel in de groep onthullen vele honderden stenen die ze al de wijde wereld in heeft gestuurd!

    Alomtegenwoordig Facebook…

    Er zijn heel wat redenen om niet op Facebook te zitten, maar het enorme bereik van het netwerk zorgt wel voor een laagdrempelige manier om dit soort toevallige ontmoetingen mogelijk te maken. De kamínky hadden nooit zo populair geworden als men ze stuk voor stuk als Geocaching Travel Bugs hadden geregistreerd, omdat het bereik van Geocaching nou eenmaal beperkt is. In een boek kan je nog een uitleg meegeven hoe BookCrossing werkt, maar ook de ruimte op een kiezel is beperkt. Het Facebook-logo is herkenbaar en met minimale context en uitleg kan iedereen meedoen.

    Lees verder
  • Automatisch dimmen als het donker wordt

    Het project ligt even een beetje stil, maar ik heb in de tussentijd wel een kleine uitbreiding gemaakt zodat je ‘s nachts niet wordt verblind door een zee aan licht, maar dat je tegelijk overdag de klok ook nog kan aflezen.

    Met een fotodiode meet ik het omgevingslicht en dankzij de in de Arduino ingebouwde analoog-digitaalomzetter is het weer een poepsimpel circuitje geworden:

    Schakeling voor lichtsensor

    De analoge poort A0 lees je eenvoudig uit met de functie analogRead(A0). De waarde uit de analoge poort wordt omgezet naar een 10-bit integer, dus tussen 0 en 1024. Omdat setBrightness een waarde tussen 0 en 255 wil, delen we die door 4 en om te voorkomen dat het licht helemaal uitgaat in het donker zetten we er een minimum op.

    void updateBrightness()
    {
      int brightnessReading = analogRead(A0) / 4;
      pixels.setBrightness(max(brightnessReading, 1));
    }
    

    Dit kan echter — zoals bij veel regelsystemen — leiden tot hysterese, waarbij het licht feller gaat branden omdat het licht wegvalt, maar doordat het licht feller gaat branden het weer lichter wordt en de helderheid bij de volgende keer dat de sensor wordt uitgelezen weer naar beneden wordt bijgesteld, waarna het hele circus opnieuw begint. Dit kunnen we voorkomen door de helderheid afhankelijk te maken van een langere periode.

    Hieronder slaan we de ingelezen waarde tijdelijk op in een circulaire buffer van lengte 10, om vervolgens het gemiddelde aan setBrightness te voeren. Het voordeel in het gebruik van zo’n type buffer is dat je waardes erin kan blijven duwen (push()) en als hij vol is valt de oudste waarde eruit. Omdat ik een lopend gemiddelde wil bepalen, haal ik die oudste waarde er zelf uit (shift()) en trek die van het gemiddelde af.

    #include <CircularBuffer.h>
    
    CircularBuffer<int, 10> brightnessReadings;
    int brightnessAverage = 0;
    
    void updateBrightness()
    {
      int brightnessReading = analogRead(A0) / 4;
      brightnessAverage += brightnessReading;
    
      if (brightnessReadings.isFull()) {
        brightnessAverage -= brightnessReadings.shift();
      }
    
      brightnessReadings.push(brightnessReading);
    
      pixels.setBrightness(max(brightnessAverage / brightnessReadings.size(), 1));
    }
    

    De waarde die uiteindelijk naar setBrightness gaat is het gemiddelde van de 10 laatste waarden. Een plotse verandering in helderheid wordt daardoor uitgesmeerd over 100 milliseconden (de loop() functie heeft een delay(10) die ervoor zorgt dat die aan het eind van elk rondje 10 milliseconden wacht) en hysterese wordt voorkomen.

    Lees verder
  • De tijd weergeven op 60 LEDs

    In de vorige post zagen we de hardwarekant van mijn model van de Lichtzeitpegel. In deze post gaan we de softwarekant verkennen, om er vervolgens achter te komen dat we nog lang niet klaar zijn met de hardware…

    NeoPixel

    Vorige keer hadden we de strandtest al gedaan om te bevestigen dat de schakeling werkt. Nu gaan we een simpel looplicht maken om de NeoPixel library te leren kennen.

    Onderstaande code is alles om mee te beginnen:

    #include <Adafruit_NeoPixel.h>
    
    #define LED_PIN    6            // Which pin on the Arduino is connected to the strip?
    #define LED_COUNT  60           // How many LEDs are on the strip?
    #define HUE_STEP   65536 / 90   // Full rainbow cycle in 90 frames
    
    // Declare the strip object:
    Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);
    int hue = 0;
    
    void setup() {
      strip.begin();                // Initialize library
      strip.show();                 // Initialize LEDs
      strip.setBrightness(50);      // Set default brightness at about 1/5
    }
    
    void loop() {
      for (int i = 0; i < LED_COUNT; i++) {
        strip.clear();              // Clear LED color in controller RAM
        for (int j = 0; j <= 9; j++) {
          strip.setPixelColor(
            (i - j + LED_COUNT) % LED_COUNT, // Makes for a fluid run-off at the end
            strip.ColorHSV(hue, 255, 255 - j * (255 / 9))
          );
        }
        strip.show();               // Update LEDs according to controller RAM
        delay(10);
        hue += HUE_STEP;
      }
    }
    

    En dat levert dan iets dergelijks op als dit:


    Regenboog looplicht

    Logische klok

    LED index – 0-based Kleur Omschrijving
    56 – 59 Geel Geen functie
    54 – 55 Wit Tien uren
    53 Geel Scheidingslamp
    44 – 52 Wit Uren
    43 Geel Scheidingslamp
    42 Rood Luchtbaken
    37 – 41 Wit Tien minuten
    36 Geel Scheidingslamp
    27 – 35 Wit Minuten
    26 Rood Luchtbaken
    25 Geel Scheidingslamp
    20 – 24 Wit Tien seconden
    19 Geel Scheidingslamp
    10 – 18 Wit Seconden
    0 – 9 Geel Geen functie

    Werkende Lichtzeitpegel van 19:59:55 tot 20:00:02

    Er zijn online niet heel veel details te vinden van de werking van de klok. Op de Duitstalige Wikipedia-pagina van de Lichtzeitpegel staat wel een tabel met de verdeling van de lampen. Het origineel heeft er 62, maar omdat ik maar 60 LEDs in de strip heb, heb ik de eerste en laatste lamp zonder functie laten vervallen. Ik hoop dat iemand me kan vergeven.

    De gele lampen zijn niet geheel zonder functie. Aan het eind van elke minuut gaan alle scheidingslampen een seconde lang aan. Aan het eind van elk uur gaan alle scheidingslampen bovendien een hele minuut lang aan. Het effect bij de volle minuut is ook te zien in een video op YouTube. De precieze werking van het hele uur is daar echter niet zichtbaar. Ik gok dat ik daarvoor echt zelf naar Düsseldorf zal moeten.

    Code

    Opvallend genoeg is het algoritme eenvoudiger te lezen dan de tijd aflezen van een klok op een zendmast.

    void updateTime(const ZonedDateTime& dt)
    {
      for (int i = 0; i < NUMPIXELS; i++) theLEDs[i] = 0; // Reset
      for (int i = 0; i < dt.second() % 10; i++) theLEDs[10 + i] = 1; // Single seconds
      for (int i = 0; i < dt.second() / 10; i++) theLEDs[20 + i] = 1; // Ten seconds
      for (int i = 0; i < dt.minute() % 10; i++) theLEDs[27 + i] = 1; // Single minutes
      for (int i = 0; i < dt.minute() / 10; i++) theLEDs[37 + i] = 1; // Ten minutes
      for (int i = 0; i < dt.hour() % 10; i++) theLEDs[44 + i] = 1; // Single hours
      for (int i = 0; i < dt.hour() / 10; i++) theLEDs[54 + i] = 1; // Ten hours
      /* Whole Minute and Whole Hour effect */
      if (dt.second() == 59 || dt.minute() == 59) {
        for (int i = 0; i < 10; i++) theLEDs[i] = 2;
        theLEDs[19] = 2;
        theLEDs[25] = 2;
        theLEDs[36] = 2;
        theLEDs[43] = 2;
        theLEDs[53] = 2;
        for (int i = 56; i < 60; i++) theLEDs[i] = 2;
      }
    }
    

    Nu de tijd getoond kan worden op de strip, hebben we natuurlijk de tijd zelf nog nodig.

    Een Arduino komt niet met een eigen klok en een losse RTC (Real Time Clock) module heb ik (nog) niet. Voorlopig zet ik de tijd handmatig tijdens compilatie en kan de tijd bijgesteld worden via de seriële interface. En dat moet regelmatig gebeuren, want de millis()functie die Arduino biedt om het aantal milliseconden sinds het starten te bepalen is niet erg precies. Na een nacht heb je al een afwijking van zo’n 7 seconden te pakken.

    Daarnaast is onmogelijk om de helderheid van de LEDs te bepalen voor zowel een zonnige dag als een donkere nacht. Er moet dus een dimmer in komen die afhangt van het omgevingslicht.

    Twee onderwerpen voor een volgende post; wordt vervolgd!

    Lees verder
  • Lichtzeitpegel in je woonkamer

    Naast programmeren was ik altijd al geïnteresseerd in de hardware-kant, wat zelfs in een vlaag van verstandsverbijstering leidde tot een studiejaar elektrotechniek. Wat me altijd een beetje er vanaf hield om er meer mee te doen was de enorme verscheidenheid aan componenten en het grillige gedrag van analoge schakelingen wat ik niet altijd goed begreep. Er was eigenlijk gewoon teveel wat ik niet wist om goed te beginnen.

    Sinds een paar maanden rommel ik soms in de avonduren wat met de Arduino Starter Kit die ik een tijd geleden heb aangeschaft. Eigenlijk zijn hiermee beide problemen opgelost, want ik hoefde nu niet zelf te bepalen welke componenten ik nodig zou gaan hebben en de voorbeelden zijn van een niveau dat analoog gedoe geen roet in het eten gooit. Hierdoor begon ik beter te begrijpen wat de verschillende componenten doen; in ieder geval beter dan tijdens het college Netwerken.

    Rheinturm in Düsseldorf

    Van het een kwam het ander en zo kwam ik uiteindelijk terecht op een ander verloren project uit de jaren 90; het nabouwen van de decimale klok in de Rheinturm in Düsseldorf. Dit vereist wellicht wat meer uitleg, want de Lichtzeitpegel zoals de installatie heet is nét iets minder bekend dan pakweg de verlichting van de Eiffeltoren of de melodie van Big Ben.

    Langs de toren zijn 62 lampen aangebracht die van boven naar onder de decimalen van een 24-uurs klok voorstellen. De eerste twee lampen geven de tientallen uren aan, de volgende tien lampen de enkele uren, daaronder zes lampen die tientallen minuten aangeven en tien lampen de enkele minuten. Ten slotte zijn er nog zes lampen voor de tientallen seconden en tien lampen voor de enkele seconden.

    De klok hiernaast geeft 16:56:39 aan, omdat van boven naar onder 1, 6, 5, 6, 3 en 9 lampen branden. Er zijn nog wat extra’s, zoals scheidingslampen tussen de groepen die op elk volle uur een minuut lang branden, maar dit zijn de pure regels voor de klok.

    Het Elektor project

    De toren met klok staat al sinds 1981, maar eind jaren 90 werd in het blad Elektor 05-1998 een elektrisch schakelschema gepubliceerd waarmee de klok na te bouwen was. Omdat het een erg populair artikel was, werd het in Elektor 01-2000 nog eens dunnetjes overgedaan.

    In onderstaande video laat Roger Leifert zijn versie zien die nog steeds voldoende aandacht krijgt op tentoonstellingen. Vanaf 6:20 zie je de ingewikkelde schakeling en lijmblob die alle leds bij elkaar houdt.

    … en toen kwam de WS2812B

    De WS28xx serie RGB LED modules veroorzaakten een revolutie in aanstuurbare verlichting. Voeding en aansturing werd gescheiden, waardoor je schakeling een stuk eenvoudiger kan worden. Daarnaast biedt de WS28xx ook een uitgangssignaal, wat alle inkomende berichten doorstuurt minus het bericht wat voor de huidige LED is bedoeld! Dat betekent dat je er ketens van kan bouwen die je met een enkele data-lijn kan aansturen!

    Schakelschema van drie WS2812B in serie
    Drie maal WS2812B in serie

    Je kan zelf de losse WS2812Bs aan elkaar koppelen, maar er zijn ook kant-en-klare LED-strips die zelf op lengte te maken zijn. Ze zijn er met 30, 60 en zelfs 144 LEDs per meter. Ze zijn er trouwens ook in de vorm van ringen, arrays (meerdere strips naast elkaar), ze zijn waterdicht en stofdicht verkrijgbaar. Voor mijn toepassing is 60 / meter optimaal; het model van de Lichtzeitpegel wordt dan ongeveer een meter hoog.


    Belachelijk simpele schakeling

    Het resultaat is in de basis een belachelijk simpele schakeling met alleen twee draadjes voor de power en een weerstandje tussen de I/O poort van de Arduino en de data-lijn van de WS2812B strip. De Arduino in combinatie met de NeoPixel library doet de rest. In de Arduino IDE kan je voorbeeld-code voor de NeoPixel library inladen. Deze laat onder andere onderstaande bewegende regenboog zien. Mooi! De schakeling werkt!


    NeoPixel ‘strandtest’ demonstratie

    De enige uitdaging die er nu nog overblijft, is de code voor de klok schrijven. Dát is voor een volgende post!

    Lees verder