Min magiska lilla röda [Raspberry pi+16 bit LCD=användbar maskin]

Trädvy Permalänk
Medlem
Plats
Göteborg
Registrerad
Aug 2010

Min magiska lilla röda [Raspberry pi+16 bit LCD=användbar maskin]

### Varning: Just nu finns det inga bilder, så det är mest en wall of text. Har försökt att sära lite på det med code- och spoiler-taggar, men förstår att det just nu är lite svårläst. Blir det lättare eller jobbigare att läsa med spoiler-taggarna? Ville ändå lägga upp det eftersom jag inte hittade någon spara-funktion och jag får lite eld i baken att fortsätta fylla på och inte glömma bort det. ###

Backstory:
För ett tag sedan skaffade jag mig en Raspberry pi. Eftersom jag aldrig sysslat med Linux förut sysselsatte jag mig i ett par veckor med att lära mig hur man navigerar och lekar med Raspberry pi, linux och python. Jag skrev små script som hämtade ip-adresser, kollade av resultat och andra automatiska saker. Jag köpte en kameramodul och gjorde en timelapse-film. Sen hittade jag en liten LCD i ett paket som skulle passa en RPi och klickade hem en, men när jag fick hem den såg jag till min förvåning (varför jag blev förvånad vet jag inte) att man behövde LÖDA IHOP sk*ten. Jag har aldrig lött något i hela mitt liv, hade ingen utrustning och blev lite trött på'et. Det som brukar hända hände och min lilla elektroniska pryttel hamnde i skymundan medan jag sysslade med andra saker. Tills en dag...

TL;DR: Jag köpte en RPi+LCD, kunde inte löda så jag sket i det.

Projektet:
Jag vill väldigt gärna pyssla med att koda för en LCD-skärm för RPi:n, men innan jag kan göra det så behöver jag sätta ihop den. På grund av detta har jag låtit den ligga ett tag, men i förra veckan när jag hittade en kodsnutt för att hämta senaste BitCoin-priset så kände jag igen att det vore roligt om man kunde ha någonting att visa det på.

Eftersom skärmen har knappar också ser min idé ut såhär:
Fixa ihop en liten låda med en RPi och en 16 bit LCD-skärm som kan visa information på ett enkelt sätt. Lådan skall placeras vid min ytterdörr så att man snabbt kan kolla av tex när nästa buss går från närmaste hållplats innan man sticker.

Att göra:
Löda ihop LCD-skärmen och sätta den på min Raspberry pi.
Bygga en låda som kan hålla RPi:n och skärmen (just nu verkar det bli Lego till att börja med).
Skriva kod så att lådan kan visa olika typer av information beroende på vilka knappar man trycker. Just nu har jag dessa funktioner i åtanke:

  • Nästa västtrafik-tur från närmaste busshållplats

  • Vädret i närheten

  • BitCoin-pris just nu

  • Eftersom jag även sitter på en kamera-modul så kanske jag kopplar in den så man kan ta en bild med ett enkelt knapptryck.

Har ni några andra idéer på vad man skulle kunna lägga in så hit me!

Material:
1 x Raspberry Pi Model B
1 x Adafruit Blue&White 16x2 LCD+Keypad Kit for Raspberry Pi
1 x lödstation
Lödtenn
∞ x legobitar
Strömsladd & ethernet till RPi:n
Instruktioner för LCD:n

(Tyvärr finns det inga bilder på den tidiga delen av projektet, eftersom jag kom på det här med byggloggen lite sent.)

Lödande:

Från början så lånade jag en gammal lödkolv av en kollega. Han tipsade mig om att det vore en bra idé om jag rengjorde spetsen innan jag började löda, men såklart lyssnade jag inte på honom. Detta resulterade i att mitt första löd-försök gick käpprätt åt skogen. Enligt instruktionerna i kittet så skulle man börja med att löda fast en resistor, men min lånade kolv blev inte tillräckligt varm. Detta gjorde att den lila plattan som man skall värma upp samtidigt som lödtennet inte blev varm och det blev mest en massa tennklumpar lite varstans. Efter mycket slit och svärande lyckades jag löda fast en av... benen(?) på resistorn, men det andra visades sig vara smått omöjligt. Till slut gick benet också av innan det var fastlött, vilket ju inte gjorde min frustration mindre.

Efter lite googlade kom jag fram till att det var kolven som var för kall och letade istället upp en lödstation på Kjell för 124 spänn. Nu har jag ju lite dålig koll på vad de kostar i vanliga fall, men det kändes som ett klipp så jag slog till. Jag köpte även "krokodilarmar" då jag tänkte att det kan ju vara bra att kunna spänna fast och vrida lätt på det man löder. De som jag köpte var aaaningen små och inte helt stabila, men vad kan man förvänta sig för 69 spänn?

Med min nya lödstation och en ledig söndag satte jag ut för att löda skiten ur LCD:n och herregud vilken skillnad det var med en varm lödkolv. "Bara" att värma upp ringen runt hålet och sedan lödtenn så flöt det på plats precis som det skulle. Det är ju säkert inte så snyggt som det kunde ha varit och vissa var riktigt jävliga, men jag är ändå riktigt nöjd med resultatet.

Lite nervös var jag när det var dags att löda fast knapparna eftersom det i instruktionerna stod "Solder each leg, taking care not to accidentally 'short' two button legs together.". Det gick bra ändå, men med tanke på mina bristande kunskaper kunde det nog ha tagit slut redan där. Speciellt som jag i sann devil may care-anda inte har någon tennsug eller liknande för att rätta till misstag.

Det tog någon timme och sedan var det klart. Eftersom jag kom på att jag skulle göra den här loggen lite sent så har jag inga bilder från det egentligen lödandet. I vilket fall blev det inga större missöden och jag kunde ge mig på koden direkt.

Dold text

Kodande:

### Om det är någon som tänkt använda sig av det här inlägget för att försöka sig på det här själv, tänk på att indentering är väldigt viktigt i Python, så se så att det ser likadant ut efter att du kopierat. ###

Jag använder mig av en RPi som sedan tidigare är installerad och kan nås via SSH. För att göra RPi:n klar för att användas tillsammans med LCD:n behövde man dock uppdatera och installera lite paket.

/etc/modules (sudo nano /etc/modules) and add: i2c-bcm2708 i2c-dev

Reboot

sudo apt-get install python-smbus sudo apt-get install i2c-tools

Sedan kör man kommandot nedan för att kolla att LCD:n hittas på rätt plats (0x20).

sudo i2cdetect -y 0 (if you are using a version 1 Raspberry Pi) sudo i2cdetect -y 1 (if you are using a version 2 Raspberry Pi)

För att ladda ner exempel-pythodkod för att styra skärmen gör man följande.

sudo apt-get install git git clone https://github.com/adafruit/Adafruit-Raspberry-Pi-Python-Code... cd Adafruit-Raspberry-Pi-Python-Code cd Adafruit_CharLCDPlate sudo apt-get install python-dev sudo apt-get install python-rpi.gpio

Och så kommandot nedanför för att köra igång ett test:

sudo python Adafruit_CharLCDPlate.py

(Allt detta enligt instruktionerna som finns länkade under material.)

Det testet gör är att köra upp lite text på skärmen och sedan händer det lite saker om man trycker på knapparna. Till att börja med såg jag ingen text alls, men efter lite skruvande på kontrast-skruven så löste det sig.

Efter det började jag bena ut hur exempel-filen fungerade.

Initiera skärmen och meddelande:

Efter att ha initierat skärmen:

from time import sleep from Adafruit_CharLCDPlate import Adafruit_CharLCDPlate # Initialize the LCD plate. Should auto-detect correct I2C bus. If not, # pass '0' for early 256 MB Model B boards or '1' for all later versions lcd = Adafruit_CharLCDPlate()

Var det inte svårare än såhär att slänga upp ett meddelande på skärmen:

lcd.message("Hej")

Däremot märkte jag att man manuellt behövde rensa skärmen innan man lade upp ett nytt meddelande, detta genom:

lcd.clear()

Dold text

Knapparna:

Eftersom min LCD var blue/white så struntade jag i att kolla något på backlight-grejerna och gick direkt på knapparna. I exemplet hade de löst det genom att köra den här koden:

while True: for b in btn: if lcd.buttonPressed(b[0]): if b is not prev: lcd.clear() lcd.message(b[1]) lcd.backlight(b[2]) prev = b break

Men det kändes lite krångligt att börja så, varpå jag benade ut varje knapp istället. Tanken är att den här skall skrivas om för att fungera mer elegant senare:

while True: try: if lcd.buttonPressed(lcd.RIGHT): lcd.clear() lcd.message('right') if lcd.buttonPressed(lcd.LEFT): lcd.clear() lcd.message('left') if lcd.buttonPressed(lcd.UP): lcd.clear() lcd.message('left') if lcd.buttonPressed(lcd.DOWN): lcd.clear() lcd.message('left') if lcd.buttonPressed(lcd.SELECT): lcd.clear() lcd.message('select') lcd.clear() #stänger av backlight lcd.backlight(lcd.OFF) #avslutar while och stänger python-scriptet break except KeyboardInterrupt: self.stopMenu(lcd)

I det här skedet så ändras texten på skärmen varje gång man trycker på en knapp. Trycker man på select (knappen längst till höger) så stänger skärmer ner sig och python-scriptet stoppas.

Dold text
Dold text

(Jag har löst det med den första lite mer eleganta koden och även lagt in tre funktioner, en för Västtrafik, en för BitCoins och en för vädret, men just nu hinner jag inte skriva mer. Ska försöka lägga in lite bilder i morgon när min kamera är laddad igen och jag har lite ljus.)

Mb: MSI Z77A-G43 ATX | Ram: Crucial Ballistix Sport 8GB 1600Mhz | CPU: Intel Core i5 3450 3,1Ghz (Ivy Bridge) | Chassi: Cooler Master Elite 430 Svart | Psu: Fractal Design Integra 500W | Grafikkort: PowerColor Radeon HD7970 | Hdd: 1TB Seagate Barracuda | SSD: Samsung EVO Basic 256gb | Monitor: HP Compaq LA2306x 23'' & (random) Acer 19''
Diverse projekt

Trädvy Permalänk
Medlem
Plats
Kalmar
Registrerad
Feb 2010

När det gäller lödkolv så är det absolut bästa enligt mig en gas-driven kolv, jag köpte en Dremel kolv på jula för 400-500 kronor, och jag lär aldrig mera behöva en annan kolv i mitt liv! Visst, den kostar lite mer, men den blir riktigt varm riktigt snabbt och är sladd-lös vilket ger en hel del när det kommer till detalj-lödning.

Detta är bara ett tips till dig om det är så att du blir intresserad utav att köpa något bättre i framtiden.

Ska bli kul att följa ditt bygge, kör på!

[Swec Marknad - Referenser]
"I think it's hilarious, Geforce Experience launched Steam, which then launched Uplay, which then launched FC3... It's like Rube Goldberg works by ubisoft." -Whippy

Trädvy Permalänk
Medlem
Plats
Göteborg
Registrerad
Aug 2010
Skrivet av ToxicTeaBag:

När det gäller lödkolv så är det absolut bästa enligt mig en gas-driven kolv, jag köpte en Dremel kolv på jula för 400-500 kronor, och jag lär aldrig mera behöva en annan kolv i mitt liv! Visst, den kostar lite mer, men den blir riktigt varm riktigt snabbt och är sladd-lös vilket ger en hel del när det kommer till detalj-lödning.

Detta är bara ett tips till dig om det är så att du blir intresserad utav att köpa något bättre i framtiden.

Ska bli kul att följa ditt bygge, kör på!

Kul att du följer!

Tack så mycket för tipset! Jag är lite snål, men är helt med på att om jag skall fortsätta med det här i framtiden så är det (som med så mycket annat) bäst att lägga lite mer pengar för att få kvalité. Kommer väl använda den här tills den dör och så får jag ta tag i det då.

Mb: MSI Z77A-G43 ATX | Ram: Crucial Ballistix Sport 8GB 1600Mhz | CPU: Intel Core i5 3450 3,1Ghz (Ivy Bridge) | Chassi: Cooler Master Elite 430 Svart | Psu: Fractal Design Integra 500W | Grafikkort: PowerColor Radeon HD7970 | Hdd: 1TB Seagate Barracuda | SSD: Samsung EVO Basic 256gb | Monitor: HP Compaq LA2306x 23'' & (random) Acer 19''
Diverse projekt

Trädvy Permalänk
Medlem
Plats
Göteborg
Registrerad
Aug 2010

###UPDATE###
Jag har fortfarande inte några bra bilder, men har kommit lite längre i mitt kodande. Vi tar en lite förklarande text först så postar jag den fungerande koden i slutet för er som vill nörda ner er.

För er som inte har läst första inlägget helt (jag förstår er, det var skitlångt) så är det en Python-script som körs. Det bygger på en while True-loop som reagerar när man trycker på en av knapparna nedanför skärmen. När scriptet körs så startar den på ett av alternativen i en meny; tex [1]Nasta tur. Om an klickar åt vänster eller höger så går man +1 eller -1 i menyn, just nu till antingen [2]BitCoin eller [3]Vader. När man kommer till ett alternativ man vill titta på så klickar man neråt för att ta fram information. När man gör det så körs den funktionen som är kopplad till menyvalet. För "Nästa tur" är detta att den hämtar vilken buss som är nästa som går från närmaste hållplats.

Härifrån kan man antingen trycka vänster eller höger för att gå upp till toppmenyn och vidare till nästa menyval (man behöver alltså inte gå upp hela vägen för att byta) eller klicka sig vidare neråt. Just nu är det bara just buss meny-valet som har fler än en "nivå" och när man klickar sig ner till #2 kommer de bussarna som går efter fram.

Om man inte gör något på (mellan 1-15 sekunder)+15 sekunder (beroende på när i en cykel man klickat på en knapp) så stänger skärmen av sig och man behöver klicka på Select-knappen (den längst till höger) för att sätta på den. Detta är dels för att spara den lilla ström som skärmen drar, men mest för att hålla ner värmen och för att ljuset är lite störigt att ha på hela tiden.

Här har ni koden, med några förhoppningsvis förklarande kommentarer. Disclosure: det här är ju fortfarande work-in-progress, så ta de lugnt med kritiken på koden. Sysslar inte med Python i vanliga fall så tar gärna emot förslag på hur jag kan göra den bättre och även vad jag skulle kunna lägga till för funktioner i "menyn".

Om du har frågor på koden är det bara att fråga. JAG vet ju precis hur jag tänker, men det är ju inte säkert att det framgår av koden.

#!/usr/bin/python from time import sleep from Adafruit_CharLCDPlate import Adafruit_CharLCDPlate import urllib2, json, sys, unicodedata, feedparser from bs4 import BeautifulSoup from threading import Timer # Initialize the LCD plate. Should auto-detect correct I2C bus. If not, # pass '0' for early 256 MB Model B boards or '1' for all later versions lcd = Adafruit_CharLCDPlate() lcd.clear() global optionSelected global optionList global menuLevel global backlightStatus global action backlightStatus = 1 action = 1 def updateBus(direction): global first global second global third global fourth global menuLevel lcd.clear() if (direction == 'down'): if (menuLevel == -1): lcd.message('Laddar ...') #kolla om det finns nagon nasta tur if ((BeautifulSoup(urllib2.urlopen('http://wap.vasttrafik.se/QueryForm.aspx?hpl=Utlandagatan+(G%C...').read()).find('table', {'class': 'tableMenuCell'})) == None): lcd.clear() lcd.message('Inga avgangar') else: soup = BeautifulSoup(urllib2.urlopen('http://wap.vasttrafik.se/QueryForm.aspx?hpl=Utlandagatan+(G%C...').read()) rows = soup.find('table', {'class': 'tableMenuCell'}).find_all('tr') first_dest = None second_dest = None first = None second = None third = None fourth = None tds_array = [] for row in rows: tds = row.find_all('td') for td in tds: tds_array.append(td.text) first_dest = tds_array[1] if (len(first_dest) > 10): first_dest = first_dest[0:10] first = (tds_array[0]+'v'+first_dest+' '+tds_array[2]) #om det finns en andra avgang at det hallet if (tds_array[4] != None): third = (tds_array[0]+'^'+first_dest+' '+tds_array[4]) #om det finns en andra tur if (len(tds_array) > 6): second_dest = tds_array[7] if (len(second_dest) > 10): second_dest = second_dest[0:10] second = (tds_array[6]+'v'+second_dest+' '+tds_array[8]) #om det finns en andra avgang at det hallet if (tds_array[10] != None): fourth = (tds_array[6]+'^'+second_dest+' '+tds_array[10]) first = unicodedata.normalize('NFKD', first).encode('ascii', 'ignore') second = unicodedata.normalize('NFKD', second).encode('ascii', 'ignore') third = unicodedata.normalize('NFKD', third).encode('ascii', 'ignore') fourth = unicodedata.normalize('NFKD', fourth).encode('ascii', 'ignore') lcd.clear() lcd.message(first) if (len(tds_array) > 6): lcd.message('\n') lcd.message(second) if (menuLevel == -2): lcd.message(third) if (second != None): lcd.message('\n') lcd.message(fourth) if (direction == 'up'): if (menuLevel == -1): lcd.message(first) if (second != None): lcd.message('\n') lcd.message(second) if (menuLevel == -2): lcd.message(third) if (second != None): lcd.message('\n') lcd.message(fourth) def bitcoin(direction): lcd.clear() lcd.message('Buy: \nSell: ') buy = float(json.loads(urllib2.urlopen('https://mtgox.com/api/1/BTCUSD/ticker').read())['return']['buy']['value']) buy = str(buy) sell = float(json.loads(urllib2.urlopen('https://mtgox.com/api/1/BTCUSD/ticker').read())['return']['sell']['value']) sell = str(sell) lcd.clear() lcd.message('Buy: $'+buy+'\nSell: $'+sell) def updateWeather(direction): feed = feedparser.parse("http://open.live.bbc.co.uk/weather/feeds/en/2711537/observati...") weather = feed.entries[0]['title'] #print feed.entries[0]['summary'] weather = weather.split("-")[1] weather = unicodedata.normalize('NFKD', weather).encode('ascii', 'ignore') weather_one = weather.split(",")[0] weather_two = weather.split(",")[1] lcd.clear() lcd.message(weather_one[12:]) if (weather_two[0:1] == " "): lcd.message("\n"+weather_two[1:]) else: lcd.message("\n"+weather_two) #kolla om nagon tryckt pa en knapp pa 30 sekunder, annars pausa skarmen def checkAction(): global action global backlightStatus if (action == 1): action = 0 t = Timer(15.0, checkAction) t.start() else: backlightStatus = 0 lcd.backlight(lcd.OFF) optionList = (("Nasta tur" , updateBus),("Bitcoin-pris", bitcoin),("Vader", updateWeather)) optionMax = len(optionList)-1 optionSelected = 1 #Borjar pa 1 eftersom vi kor ett vanstertryck som forst, da borjar den pa forsta funktionen i listan ovanfor def upBtn(): global menuLevel menuLevel+=1 up = 'up' optionList[optionSelected][1](up) def downBtn(): global menuLevel menuLevel-=1 down = 'down' optionList[optionSelected][1](down) def selectBtn(): global backlightStatus if (backlightStatus == 0): backlightStatus = 1 lcd.backlight(lcd.ON) t = Timer(15.0, checkAction) t.start() def leftBtn(): global optionSelected global menuLevel menuLevel=0; if (optionSelected > 0): optionSelected-=1 else: optionSelected=optionMax lcd.clear() lcd.message("["+str(optionSelected+1)+"] "+optionList[optionSelected][0]) #visar vilket menyval man ar pa def rightBtn(): global optionSelected global menuLevel menuLevel=0; if (optionSelected < optionMax): optionSelected+=1 else: optionSelected=0 lcd.clear() lcd.message("["+str(optionSelected+1)+"] "+optionList[optionSelected][0]) #visar vilket menyval man ar pa btn = ((lcd.LEFT , leftBtn), (lcd.UP , upBtn), (lcd.DOWN , downBtn), (lcd.RIGHT , rightBtn), (lcd.SELECT, selectBtn)) t = Timer(15.0, checkAction) t.start() #starta action for att kolla om det hander nagot varje 30 s leftBtn() #starta koden med forsta i menyn while True: try: for b in btn: if lcd.buttonPressed(b[0]): action = 1 #print b[1] #Printar vilken funktion som anvands b[1]() sleep(0.5) except KeyboardInterrupt: lcd.clear() lcd.message('break') lcd.clear() lcd.backlight(lcd.OFF) sys.exit("Stopped")

Dold text

Mb: MSI Z77A-G43 ATX | Ram: Crucial Ballistix Sport 8GB 1600Mhz | CPU: Intel Core i5 3450 3,1Ghz (Ivy Bridge) | Chassi: Cooler Master Elite 430 Svart | Psu: Fractal Design Integra 500W | Grafikkort: PowerColor Radeon HD7970 | Hdd: 1TB Seagate Barracuda | SSD: Samsung EVO Basic 256gb | Monitor: HP Compaq LA2306x 23'' & (random) Acer 19''
Diverse projekt

Trädvy Permalänk
Medlem
Plats
Flyinge
Registrerad
Mar 2012

Låter som en riktigt ball idé det här! Vad är det för skärm du använder dig utav?

Fractal Design Arc | Intel Core i5 3570k @ 3.4 Ghz | Corsair 8 GB 2 x 4GB LP | Gigabyte Geforce GTX 670 2GB OC | be quiet! Pure Power 630w 80+ Bronze Modular | MSI Z77A-GD55 ATX |Samsung Basic SSD 830 128 GB | W7HP

Trädvy Permalänk
Medlem
Plats
Göteborg
Registrerad
Aug 2010
Skrivet av Rasmuus:

Låter som en riktigt ball idé det här! Vad är det för skärm du använder dig utav?

Kul att du tycker det!

Jag använder mig av en Arduino 16x2-bit LCD skärm i blått/vitt som jag klickat hem härifrån: https://www.m.nu/adafruit-bluewhite-16x2-lcdkeypad-kit-for-ra...

Man får in 16 tecken på varje rad så det är inte mycket att lira på, men det räcker om man bara ska visa kort information.

Mb: MSI Z77A-G43 ATX | Ram: Crucial Ballistix Sport 8GB 1600Mhz | CPU: Intel Core i5 3450 3,1Ghz (Ivy Bridge) | Chassi: Cooler Master Elite 430 Svart | Psu: Fractal Design Integra 500W | Grafikkort: PowerColor Radeon HD7970 | Hdd: 1TB Seagate Barracuda | SSD: Samsung EVO Basic 256gb | Monitor: HP Compaq LA2306x 23'' & (random) Acer 19''
Diverse projekt

Trädvy Permalänk
Medlem
Plats
Skåne
Registrerad
Aug 2007
Skrivet av ToxicTeaBag:

När det gäller lödkolv så är det absolut bästa enligt mig en gas-driven kolv, jag köpte en Dremel kolv på jula för 400-500 kronor, och jag lär aldrig mera behöva en annan kolv i mitt liv! Visst, den kostar lite mer, men den blir riktigt varm riktigt snabbt och är sladd-lös vilket ger en hel del när det kommer till detalj-lödning.

Detta är bara ett tips till dig om det är så att du blir intresserad utav att köpa något bättre i framtiden.

Ska bli kul att följa ditt bygge, kör på!

En riktig lödstation är ju det bästa, då man kan välja temp och det går inte på en gas-driven kolv. Men jag undrar: vad är en " Dremel kolv"

Pc 1--> [AsRock DualSata2][AMD4600+X2][7800GT] [Intel SSD X-25 80GB G2][1TB F3][750GB IDE][4GB XMSCorsiar]Pc 2--> [Asus Crosshair] [AMD 4800+X2] [2st 8800GT i SLI] [RAID 0 2x250GB] [6GB XMSCorsair] [Corsair HX750]Pc 3-->[NAS DNS-323 3TB][Skärmar=24",24",24" Eyefinity]Pc 4--> AsRock P67 Extreme 4,i7 2600K @ 4.0 GHz,830 256GB,16GB 1600MHz,HD 6950 2GBFoto [Nikon D3100][70-300/35 1,8/18-55

Trädvy Permalänk
Medlem
Plats
Kalmar
Registrerad
Feb 2010
Skrivet av skog1s:

En riktig lödstation är ju det bästa, då man kan välja temp och det går inte på en gas-driven kolv. Men jag undrar: vad är en " Dremel kolv"

Ja, det där med att kunna sätta exakt temperatur är något jag saknar så det kan jag hålla med om att en bra lödstation har som fördel, jag har dock testat att arbeta med både trådad lödstation med temperatur kontroll och med gaskolv och jag föredrar det sistnämnda endast på grund utav mobiliteten.

Dremel Kolven i fråga: Clickety click

[Swec Marknad - Referenser]
"I think it's hilarious, Geforce Experience launched Steam, which then launched Uplay, which then launched FC3... It's like Rube Goldberg works by ubisoft." -Whippy