Twitterboom

From Frack - Hackerspace Friesland
Jump to navigationJump to search
Project: Twitterboom
Twitterboom 05.jpg
Status voltooid
Betrokkenen
Gebruiker JTAG.jpg JTAG
Gebruiker JTAG.jpgDanny Bokma (JTAG) Rol: niet-deelnemer Deskundig met: AVR, CNC, CNC-Frezen, ENC28J60, Elektronica, Solderen, Stappenmotor Werkt aan: Geen projecten :(
,
Gebruiker Lijnenspel.jpg Lijnenspel
Gebruiker Lijnenspel.jpgJelle (Lijnenspel) Rol: niet-deelnemer Deskundig met: Arduino, Programmeren, Sammy Werkt aan: Geen projecten :(
,
Gebruiker Elmer.jpg Elmer
Gebruiker Elmer.jpgElmer de Looff (Elmer) Rol: niet-deelnemer Deskundig met: Arduino, ENC28J60, Elektronica, GnuCash, HTML, LPD8806, Linux, MediaWiki, Mercurial, Netwerken, OpenVPN, Programmeren, Python, SSH, Software, Solderen Beginnend met: Dm-crypt, Javascript Werkt aan: Geen projecten :(
,
Afbeelding Anoniem.png Riddergraniet
Riddergraniet Rol: niet-deelnemer Werkt aan: Geen projecten :(
,
Gebruiker CodeHunger.jpg CodeHunger
Gebruiker CodeHunger.jpgCodeHunger (CodeHunger) Rol: niet-deelnemer Deskundig met: Arduino, HTML, Javascript, Mustache, Programmeren, Python, Sammy Werkt aan: Geen projecten :(
,
Afbeelding Anoniem.png Failbaitr
Failbaitr Rol: deelnemer Deskundig met: Arduino, CNC, CNC-Frezen, Canvas, Digitale Fabricatie, ENC28J60, Glasvezel lassen, HTML, Hydroponics, Javascript, LPD8806, Linux, Mercurial, Programmeren, Python, Robotica, Software, Stappenmotor Beginnend met: Elektronica Werkt aan: Geen projecten :(
Kennisgebied(en) Python, AVR, HTML, CSS
, Javascript
Afgeleide projecten Ledwall, Lightbox
ProjectoverzichtProject toevoegen

Een interactieve, verlichte en Twittergestuurde kerstboom die eind 2011 te zien was in de Bibliotheek Leeuwarden. Hieruit voortgekomen is een deel van de verlichting in de space, bestuurd door Lightbox.

Uiteindelijk bestaat het project uit een groot aantal aspecten. In de basis kunnen gebruikers van twitter een kerstwens met een kleur de kleur van de kerstboom in de bibliotheek Leeuwarden beïnvloeden. Een ieder die twittert met de juiste hashtag krijg een onderdeel van de kerstboom toegewezen. Aanvullingen zijn het doorgeven van een video beeld naar de website http://www.twitterboom.nl en het weergeven van de tweets op de website + de lichtkrant achter de boom.

Totstandkoming

Tijdens de brainstorm sessie na de deelnemersvergadering(29-11-'11) kwamen we met het idee om op korte termijn een interactieve kerstboom te realiseren. De boom dient als promotie actie om Frack meer bekendheid te geven en komt in de bieb van Leeuwarden te staan. Omdat de bibliotheek en Frack het zelfde doel hebben (delen van kennis) is deze samenwerking erg zinvol.

Leden

Hieronder een lijstje van de mensen die aan het project hebben meegewerkt en waar ze verantwoordelijk voor zijn gewest

  • JTAG: het idee! de RGBController met software en leverancier IP-cam + ledbalk
  • Lijnenspel: Website en database + woensdagochtendinstallatie @ Bibliotheek Leeuwarden
  • RidderGraniet: Twitter Search module
  • Elmer: Aansturing van RGBController; Updaten van ledbalk in de bieb, tweets en camerashot naar website versturen
  • Codehunger: Javascript voor website
  • Failbaitr: Hosting, javascript and website python rewrite naar high-performance oplossing

Afbeeldingen


Componenten van de Twitterboom

De Twitterboom bestaat uit de volgende componenten:

  • RGB Led-controller: Een USB/serieel aangestuurde microcontroller die direct de ledstrips in de kerstboom aanstuurt.
  • Python Lightbox: Een Python module om kleurovergangen op de RGB controller te realiseren, en bij te houden welke strip het laatst gebruikt is
  • Twitter Search: Een tweede python module om tweets met de verschillende kerstboom hashtags bij Twitter op te halen.
  • Sony IP-camera om recente plaatjes van de boom op het internet te kunnen plaatsen
  • Amplus lichtkrant om tweets naar #kerstboomlwd in de bibliotheek te tonen.
  • Een Python programma, Mastermind, om de bovengenoemde items aan te sturen en te koppelen, en om tweets en camerabeelden naar de webserver te sturen
  • Een website gebaseerd op uWeb en Apache om dit alles online en zichtbaar te maken.

RGBController

Schematisch overzicht van de RGBController
Bovenkant PCB
Onderkant PCB
Gerealiseerde print in behuizing. De opening voor de uitgaande kabels is hier nog niet gemaakt (maar zou linksonder komen)

De RGBControllerbox is in staat 5 unieke RGB ledstrips aan te sturen. Elke uitgang aansturing bestaat uit 3 kanalen (rood, groen en blauw). De aansturing van het kastje gaat met een USB kabel, na het aansluiten verschijnt een virtuele COM-poort op de computer. Voor de energie van de ledstrips en de elektronica is het aansluiten van een voeding noodzakelijk. Intern word de aangeboden spanning lineair naar beneden gebracht met een 78L05. De spanning voor de ledstrips word rechtstreek vanuit de aangeboden voeding gehaald. De ledstrip heeft een continue spanning en een geschakelde massa (ofwel open drain 1.5A max - CommonAnode is wenselijk). In de kerstboom passen we 12.5 meter ledstrip van Velleman toe van het type CHLS24RGB.

Interne opbouw

Een klein aantal chips zorgt intern voor het verwerken van de data en zet de data om naar 16 PWM kanalen. Elke ledstrip heeft 3 kanalen nodig voor respectievelijk rood, groen en blauw. Het maximaal aanstuurbare aantal ledstrips word dus beperkt door het aantal uitgangen en komt neer op 5 strips (16 / 3 = 5, rest 1). Het hart van de RGBcontroller word gevormd door de Atmega168 van Atmel, deze vertaalt de seriële informatie van de FT232RL naar I2C voor de PCA9685.

Datasheets gebruikte chips:

Schematisch:

Protocol

De communicatie over de seriele poort gaat met:

  • 57600 baud, 8N1 voor de ingebouwde bootloader (STK500 zelfde als Arduino, reset d.m.v. puls op DTR)
  • 57600 baud, 8N1 voor het communiceren met de controller

Heartbeat

Om de communicatie in stand te houden is het nodig elke 2 seconden een zogenaamde heartbeat te sturen.:

H\r\n

Een heartbeat mag niet tijdens een ander commando verstuurd worden.

Aansturing individuele uitgang

De aansturing van een individuele uitgang wordt in de RGBController op deze wijze verwerkt:

sscanf(ReceiveBuffer,"$%3d,%3d,%3d,%3d",&module,&red,&green,&blue);

Het te sturen commando is dan een string als deze:

${uitgang},{rood},{groen},{blauw}\r\n

Dit commando bestaat uit de volgende onderdelen:

  • $ "Een enkele uitgang aansturen";
  • {uitgang} getal dat de uitgang selecteert [0-4];
  • {rood} kleurwaarden voor het rood-kanaal [0-255];
  • {groen} kleurwaarden voor het groen-kanaal [0-255];
  • {blauw} kleurwaarden voor het blauw-kanaal [0-255];
  • \r\n De carriage-return & line-feed die het commado afsluit.

N.B. de kleurwaarden hebben geen voorloopnullen nodig.

Voorbeeldcommando om de tweede uitgang donkergeel te kleuren:

$1,128,128,0\r\n

Aansturing gecombineerde uitgangen

Het is mogelijk alle uitgangen in een commando dezelfde kleur te geven. Dit is efficienter dan per-uitgang aansturing wanneer alle uitgangen dezelfde kleur moeten krijgen. Na het verwerken van het commando nemen alle uitgangen deze RGB waarde aan. Aan de microcontroller kant wordt de volgende leesactie gedaan met scanf:

sscanf(ReceiveBuffer,"#%3d,%3d,%3d",&red,&green,&blue);

Het te sturen commando is dan een string als deze:

#{rood},{groen},{blauw}\r\n

Dit commando bestaat uit de volgende onderdelen:

  • # "De gegeven kleur geldt voor alle uitgangen";
  • {rood} kleurwaarden voor het rood-kanaal [0-255];
  • {groen} kleurwaarden voor het groen-kanaal [0-255];
  • {blauw} kleurwaarden voor het blauw-kanaal [0-255];
  • \r\n De carriage-return & line-feed die het commado afsluit.

Voorbeeldcommando om alle uitgangen lila te kleuren:

#127,0,255\r\n

Antwoord op commando's

Na elk verzonden commando (m.u.v. de Heartbeat) antwoord de box als volgt:

R\r\n

Men dient een tweede kleurcommando pas te sturen wanneer dit antwoord correct ontvangen is.


Broncode

De broncode voor de RGBController is te downloaden van JTAG's dropbox.

Lightbox

Twitter Searcher

De Twitter Searcher is zelfgeschreven Python klasse die als thread twee queues van informatie voorziet. De eerste ie een queue waarin de kleurwijzigingen voor de boom (en bijbehorende tweets op de website) uit gelezen worden. De tweede queue wordt gebruikt om op de lichtkrant de laatste tweets te laten zien.

Om ervoor te zorgen dat we geen dubbele tweets verwerken, slaan we het laatste tweet ID op in een simpel tekstbestand. Wanneer het programma wordt herstart worden alleen nieuwe zoekresultaten verwerkt. Wanneer er geen ID bekend is (het bestand is leeg of afwezig) wordt aangenomen dat alle zoekresultaten verwerkt dienen te worden.

Kleur queue

Het proces om de kleur queue te vullen ziet er als volgt uit:

  • De publieke Twitter JSON search API wordt afgezocht naar tweets met een van de twitterboom hashtags (#kerstboom, #kerstboom_lwd en #kerstboomlwd);
  • In die tweets wordt naar een kleur gezocht op de volgende wijzen:
    • Door de woorden in de tweet te vergelijken met de voorgedefinieerde lijst van kleuren. Als een of meer van deze voorkomen wordt er een gekozen als kleur;
    • Alternatief wordt de HTML-kleurcode gebruikt (in de vorm #1470cf);
    • Als laatste wordt gezocht of een van de kleurwoorden voorkomt in de tekst (groene boom levert de kleur groen op).
  • De tweet en gevonden kleurwaarde worden toegevoegd aan de queue

Lichtkrant queue

Alle tweets voor de hashtags #kerstboom_lwd en #kerstboomlwd (met of zonder kleur) worden toevoegd aan een tweede queue voor het tonen op de lichtkrant in de bibliotheek.

Gotchas

  • De Twitter search engine vindt het niet leuk als je geen unieke User Agent meestuurt met je request voor tweets. Standaard stuurt de urllib "Python-urllib/VVV" (waarbij VVV het versienummer is) en veel services weigeren dit soort non-human requests. Twitter weigert je niet, maar zet wel lagere limiet op het aantal requests dat je kan doen dan normaal het geval zou zijn. De oplossing is om een subclass te maken van het URLopener object en daarin de version property naar iets unieks veranderen. Zie details hier.


Mastermind

Het hoofdprogramma dat in de bibliotheek alle onderdelen bestuurt betaat uit een aantal verschillende subcomponenten, de meeste uitgevoerd in een losse thread.

Tweet zoeker (Thread 1)

De eerder beschreven tweet zoeker zet de verschillende tweets in twee queues. Deze thread wordt gestart vanuit het hoofdprogramma, en de twee queues worden hier ook aangemaakt een doorgegeven. Elk van de volgende twee threads

Updaten kerstboomkleur (Thread 2)

Deze thread wordt gestart met de kleur-queue en een lightbox object.

Waneer er een nieuwe tweet binnenkomt wordt de volgende ledstrip geselecteerd uit de lightbox en wordt opdracht gegeven om de ledstrip langzaam naar de gekozen kleur te laten verschuiven. Tegelijketijd wordt er een POST naar de website gedaan waarin de tweet informatie, kleur en ledstripnummer worden meegegeven. Dit laatste zodat de tweet op de juiste plaats in de boom 'gehangen' kan worden.

Wanneer de kleur te donker is (de sterkste kleurwaarde is minder van 50 (van de max 255), wordt deze kleur niet in de boom geupdate. Dit om het geheel kleurig te houden. Nadat er een tweet met kleur verwerkt is, wordt er 15 seconden gewacht, zodat tweets te allen tijde een goede tijd op de website te zien zijn.

Updaten van de lichtkrant (Thread 3)

Deze thread wordt gestart met de lichtkrant-queue en een seriele verbinding met de Amplus lichtkrant.

Op de lichtkrant worden drie tweets ingeprogrammeerd, die een voor een voorbij scrollen, waarna de cyclus herhaalt. Wanneer er nieuwe tweets binnenkomen wordt de oudste tweet op de lichtkrant overschreven. Om te garanderen dat elke tweet tenminste een paar keer te zien is, wordt na het updaten van de lichtkrant 1 minuut gewacht. Dit betekent dat elke tweet ten minste drie minuten wordt getoond.

Update camerabeeld (Thread 4)

Eens per 3 seconden wordt het huidige camerabeeld van de IP-cam gedownload. Dit wordt vervolgens met behulp van imagemagick 90 graden gedraaid en naar de webserver geupload. Het draaien van het beeld is nodig omdat de camera een kwartslag gedraaid is, dit omdat de kerstboom hoger is dan breed :-)


Media

Schematisch overzicht van de verschillende componenten

Door derden

Door Frack

Publicatie alle code

Omdat de code tussen alle systemen niet onbeveiligd is / er bij publicatie van de code verschillende kwetsbare punten duidelijk zou kunnen worden, kiezen we er voor de internet gekoppelde code pas te publiceren wanneer ze niet meer in gebruik is.

In hindsight

Traffic and stats: During the few days that twitterboom.nl has been live, it has attracked many visitors, from all over the netherlands, but also from beyond our countries borders.

At the peak of interest, on Wednesday the 21st of December at around 13:00 local time, traffic to twitterboom.nl and frack.nl peaked at about 45mbit outbound traffic and nearly 2mbit of inbound http requests. As our server was being hammered by unoptimized MediaWiki requests to frack.nl's wiki mixed in with the twitterboom the load of the machine shot trough the roof until we enabled memcached, and tweaked some apache2 settings.

How to handle such traffic in the future

A beter sollution would have been to use a webserver that's light on ram usage, and can thus be allowed to handle more incoming concurent connections than a standard apache2 does in a given amount of ram.

Spoonfeeding is also an issue, if there's a 1000 concurrent connections to your webserver, some (and in this case, A lot because of the nature of the application) clients are going to either dialup or mobile users, with a slow or laggy connection. As your webserver instance is requiered to wait for these connections to have received all of their requested content (such are the rules of tcp) the big chunk of ram its using cannot be used by any other client. thus limiting the total amount of users that can enjoy the already hammered site. By using a proxy like squid in reverse mode, we would be able to keep all of those slow loading clients on a super tiny ram footprint and have the apache instanced finish their work much quicker since squid would be able to receive the content from apache at lightspeed.

In a normal situation, handling a news post would require the webserver to send out the same message to every user that requests it, this would enable the mysql, and disk caches to be hot, and thus serve exclusively from ram. It would also mean, that per user who has the content we only have a small percentage of people continueing to load pages from the server, and if they do so, its probably going to be somewhat limited by their reading spead.

In this case, the avarage user read the page, (and thus received updated camera stills every x seconds) but also waited to see the effect of tweet to our tree, thus racking up the amount of requests resulting in a continuous stream of well over 800 concurrent connection from the moment the tweakers.net article went live to around 8 hours later. To make things worse, the wiki hosted on the same machine was starving the cpu's from cycles.

A better situation would have been to move mostly static files to disk instead of regenerate them for every client, and possibly host them on a separate server which runs a webserver like ngnix of lighttpd without any cgi modules enabled.

Running the files on a noatime mounted filesystem would have helped a bit also, by not requiring the kernel to update the 'last access time' for the files on every read we would have saved some disk io, but not so much as it did in years gone by when im uessing the kernel handled this stuff less shiny as it does today.

It would have been a godsend to have some way of telling clients with a constantly looping javascript file to refresh their javascript and or html, and to increase the time between each request. Google mail does this very nicely, if the gmail connection is lost their script will keep calling out their server, but it will increase the time it waits before trying again after every failure. It also has a feature in which the server can tell the client to reload the whole interface if its running an outdated version. In effect, we had not way of telling those 1000 users that we were not going to be able to serve them any files at their original request speed anymore. The javascript did however have some checking code that made sure a 404, or invalid json or image woudn't trip the client, and thus we also had no way of crashing the older javascripts.

What we did to make things better

  • During the onslaught, we tweaked Apache to drop connections after a shorter amount of waiting time, for slow users like mobile and dialup users, this will have resulted in timeout errors, but so be it. For every slow user we could server a couple of fast users that were already lined up.
  • We disabled mod_python autoreloading.
  • We enabled memcached for the wiki, with a minimal amount of ram to keep just enough wiki information in there ready to be served (since 95% of the visitors would be looking at 1 page only), while not using up memory that Apache might have a good use for.
  • We used thread persistent storage in mod_python to keep the json and camera files in ram.
  • After a while we noticed things got better, but not good enough, we rewrote the scripts to push the camera image to the static folder so to avoid the base64_decode process for every user and speed things up by using the static handler.
  • We wrote a small cronjob to regenerate the json file every minute, while this meant we were not going to display some tweets, it did mean we could also push that file out as a static file, enabling linux to warm up its file caches and thus remove some IOwait.
  • And we tweaked the clientside javascript to correlate the json and camera reloading events to the updates that our server would be doing to those files.
  • We tweaked the Apache serverside logging to disable logs for the json, camera, favicon and other images.

Statistieken

  • Er zijn 589 tweets verwerkt, gestuurd vanaf 316 verschillende Twitter accounts.
  • In totaal zijn er 63 verschillende kleuren genoemd, de meest populaire:
    1. 101x #ff0000
    2. 91x #7fff00
    3. 74x #0000ff
    4. 53x #ffa500
    5. 39x #ffff00
    6. 39x #ff00ff
    7. 36x #ffffff
    8. 26x #ff1493
    9. 20x #ee82ee
    10. 13x #ffd700
    11. 11x #000000