Dotstar LEDs with Raspberry Pi - the Python bit
In this post, I showed how I made my Raspberry Pi jukebox. Here's how it is controlled.
At it's core, it's just a python script which waits for interrupts from the Pi's GPIO pins. That's the easy bit (although it would be a lot easier if there wasn't two different ways to number GPIO pins - I mean really...)
I do know that just making a bunch of functions isn't particularly pythonic and I should probably do something with classes and objects and stuff, but I had a really immovable deadline on this project so I just stuck with what I knew (which mostly comes from php, years ago).
Adafruit provide a library to let you access the Dotstar leds, but it's pretty basic. That's cool, it was fun learning how to figure stuff out. I ended up using a couple of modules to generate colour gradients and to shift between various colourspaces.
A nifty thing happened when I wanted to flash random, but bright colours as part of rave mode. Taking random RGB values was getting very pastel colours most of the time, which wasn't quite what I was looking for. A friend on Facebook suggested using HSV, so I could set saturation/value to a high value (70-100%) and then take a random hue. Worked wonderfully, but I couldn't ever get a decent video of it in action to show the difference.
I used threading (for the first time in python, yay!) to run a background thread that polls Kodi to see if it's playing, because Kodi can be controlled by means other than the buttons, so we need to know if it's playing to decide whether to put the lights on or not.
Some leds are "fixed" - they're the ones over the switches - and will remain a single colour while the rest of the strip phases colours. That lets you know which switch is on.
Ravemode is still my favourite bit. That was good fun to come up with.
Code is long so it's after the jump. I know it could be tidier, and a bit smarter, but I was on a deadline. I shall return to fiddle with it later on. There's more I'd like this box to do, but for now it just needs to play music.
At it's core, it's just a python script which waits for interrupts from the Pi's GPIO pins. That's the easy bit (although it would be a lot easier if there wasn't two different ways to number GPIO pins - I mean really...)
I do know that just making a bunch of functions isn't particularly pythonic and I should probably do something with classes and objects and stuff, but I had a really immovable deadline on this project so I just stuck with what I knew (which mostly comes from php, years ago).
Adafruit provide a library to let you access the Dotstar leds, but it's pretty basic. That's cool, it was fun learning how to figure stuff out. I ended up using a couple of modules to generate colour gradients and to shift between various colourspaces.
A nifty thing happened when I wanted to flash random, but bright colours as part of rave mode. Taking random RGB values was getting very pastel colours most of the time, which wasn't quite what I was looking for. A friend on Facebook suggested using HSV, so I could set saturation/value to a high value (70-100%) and then take a random hue. Worked wonderfully, but I couldn't ever get a decent video of it in action to show the difference.
I used threading (for the first time in python, yay!) to run a background thread that polls Kodi to see if it's playing, because Kodi can be controlled by means other than the buttons, so we need to know if it's playing to decide whether to put the lights on or not.
Some leds are "fixed" - they're the ones over the switches - and will remain a single colour while the rest of the strip phases colours. That lets you know which switch is on.
Ravemode is still my favourite bit. That was good fun to come up with.
Code is long so it's after the jump. I know it could be tidier, and a bit smarter, but I was on a deadline. I shall return to fiddle with it later on. There's more I'd like this box to do, but for now it just needs to play music.
#!/usr/bin/env python import requests import json import urllib import os from threading import Thread import time from random import randint,choice,uniform import RPi.GPIO as GPIO import time from dotstar import Adafruit_DotStar from colour import Color from subprocess import call from lxml import etree as ET ############################################################# #### assorted global variables and general purpose shiz # button states - pin-no:0/1 for up/down bState = { 19:0,16:0,26:0,20:0,21:0 } # this is checked in main loop to determine wtf is going on # this appears to be being deprecated in favour of kodistate which is updated by polling kodi # only ravemode uses thingHappening any more thingHappening = "nothing" # let's get that light party started numpixels = 30 # fixed leds set to their number if so. set to > numpixels if not switchstates = [99,99,99,99] switchcolours = {0:0xFFFF00,1:0xFFFF00,2:0xFFFF00,3:0xFFFF00} # fade to black def turnOff(intv=0.04): c1 = "#" + hex(strip.getPixelColor(0)).ljust(8,"0").replace("0x","") phase(c1,"#000000",intv) ############################################################# #### RAVEMODE #### # length of lit group in colourWave grouplen = 10 def hsv_to_rgb(h, s, v): if s == 0.0: v*=255; return [v, v, v] i = int(h*6.) # XXX assume int() truncates! f = (h*6.)-i; p,q,t = int(255*(v*(1.-s))), int(255*(v*(1.-s*f))), int(255*(v*(1.-s*(1.-f)))); v*=255; i%=6 if i == 0: return [v, t, p] if i == 1: return [q, v, p] if i == 2: return [p, v, t] if i == 3: return [p, q, v] if i == 4: return [t, p, v] if i == 5: return [v, p, q] def randColour(): # weighted HSV h = uniform(0,100)/100 s = uniform(70,100)/100 v = uniform(70,100)/100 rb = hsv_to_rgb(h,s,v) r = int(round(rb[0])) g = int(round(rb[1])) b = int(round(rb[2])) col = (r,g,b) return col # control fuctions for sideFlash() feature def setLeft(c): for n in range(22,30): strip.setPixelColor(n,c[0],c[1],c[2]) strip.show() def setRight(c): for n in range(0,7): strip.setPixelColor(n,c[0],c[1],c[2]) strip.show() def setFront(c): for n in range(8,21): strip.setPixelColor(n,c[0],c[1],c[2]) strip.show() def sideFlash(): cl = randColour() cr = randColour() cf = randColour() off = (0,0,0) for a in range(0,80): r = randint(0,6) if r == 0: setLeft(cl) time.sleep(round(uniform(0,10)/100,2)) if r == 1: setRight(cr) time.sleep(round(uniform(0,10)/100,2)) if r == 2: setFront(cf) time.sleep(round(uniform(0,10)/100,2)) if r == 3: setLeft(off) time.sleep(round(uniform(0,10)/100,2)) if r == 4: setRight(off) time.sleep(round(uniform(0,10)/100,2)) if r == 5: setFront(off) time.sleep(round(uniform(0,10)/100,2)) setLeft(off) setRight(off) setFront(off) # it's like a fabulous Knight Rider def colourWave(): for r in range(randint(1,6)): head = 0 tail = 0 - grouplen for n in range(numpixels + grouplen): c = randColour() strip.setPixelColor(n,c[0],c[1],c[2]) strip.show() time.sleep(0.005) strip.setPixelColor(tail,0) time.sleep(0.005) head += 1 tail += 1 head = numpixels tail = numpixels + grouplen for n in range(numpixels + grouplen + 2): c = randColour() strip.setPixelColor(head,c[0],c[1],c[2]) strip.show() time.sleep(0.005) strip.setPixelColor(tail,0) time.sleep(0.005) head -= 1 tail -= 1 def flashStrip(): global bigrainbow for r in range(randint(5,16)): rcol = randColour() for n in range(numpixels): strip.setPixelColor(n,rcol[0],rcol[1],rcol[2]) strip.show() time.sleep(0.09) for n in range(numpixels): strip.setPixelColor(n,0) strip.show() time.sleep(0.03) time.sleep(0.05) def raveMode(duration): global thingHappening # max out teh bright strip.setBrightness(255) ts = time.time() + duration while time.time() < ts: r = randint(0,2) if r == 0: flashStrip() if r == 1: colourWave() if r == 2: sideFlash() turnOff() thingHappening = "nothing" ############################################################# #### phase strip #### def phase(c1,c2,interval,px=None): c1 = Color(c1) c2 = Color(c2) grad = list(c1.range_to(c2,128)) for c in grad: rbg = c.rgb red = int(round(255 * rbg[0])) blue = int(round(255 * rbg[1])) green = int(round(255 * rbg[2])) if px != None: strip.setPixelColor(px,red,blue,green) else: for n in range(numpixels): if n in switchstates: for i,v in enumerate(switchstates): if v == n: # TODO fix phase-in for fixed pixels #phasePixel(n,switchcolours[i],0.2) strip.setPixelColor(n,switchcolours[i]) else: strip.setPixelColor(n,red,blue,green) strip.show() time.sleep(interval) def phaseStrip(): strip.setBrightness(40) # TODO check if any px are lit, if so start from their colour, fade to red # otherwise start from black # phaseStrip("#000000","#FF0000",0.002) # R to the G to the B fade on. phase("#FF0000","#00FF00",0.02) phase("#00FF00","#0000FF",0.02) phase("#0000FF","#FF0000",0.02) time.sleep(0.0001) def phasePixel(px,c2,intv,c1=None): if c1 == None: c1 = Color("#" + hex(strip.getPixelColor(px)).ljust(8,"0").replace("0x","")) #else: phase(c1,c2,intv,px) ############################################################# #### holding pattern #### def holdingPattern(): rainbow = [0x0079E5,0x0050E5,0x0027E5,0x0200E5,0x2B00E5,0x5500E6,0x7E00E6,0xA800E6,0xD200E6,0xE600D1,0xE700A8,0xE7007E,0xE70054,0xE7002B,0xE70001,0xE82800,0xE85200,0xE87C00,0xE8A600,0xE8D000,0xD7E900,0xADE900,0x83E900,0x59E900,0x2EE900,0x04EA00,0x00EA25,0x00EA50,0x00EA7A,0x00EBA5] brightness = 1 for n in range(numpixels): strip.setPixelColor(n,rainbow[n]) strip.setBrightness(int(round(brightness))) brightness += 1 strip.show() time.sleep(0.1) for n in range(numpixels): strip.setPixelColor(n,0) strip.setBrightness(int(round(brightness))) brightness -= 1 strip.show() time.sleep(0.1) ############################################################# #### Kodi/playlist bizniss #### # playing list, tracks which switches are on playing = [] # special case tracking for hyperplaylist hyperplay = False def doKodiThings(button): # add to playing list playing.append(button) # stop anything currently playing if kodistate == "playing": kodiAllStop() # do it do it do it kodiPlay() def doResetThings(button): global hyperplaying # what is playing now? nowplaying = playing[-1] # remove turned off switch from playing list playing.remove(button) # stahp, if the button turned off is the one playing if button == nowplaying or hyperplaying == True: kodiAllStop() # if some switches are still on, and we haven't turned off a non-playing switch, # and hyperplay is active, play the most recently turned on switch/hyperplaylist if len(playing) > 0: if nowplaying != playing[-1] or hyperplaying == True: kodiPlay() else: hyperplaying = False def kodiAllStop(): # this stop command works, it shouldn't stopcmd = "/usr/bin/kodi-send --host=localhost --port=9777 --action=\"Playmedia(Stop)\"" call(stopcmd, shell=True) clrcmd = "/usr/bin/kodi-send --host=localhost --port=9777 --action=\"Playlist.Clear\"" call(clrcmd, shell=True) def kodiPlay(): # plays the last (most recent) item on the playing list # name playlists Magicplay_$name # if more than one switch on, make unified playlist if len(playing) > 1: hyperPlaylist(playing) plc = "magic" else: # just one switch on, so play that plc = str(playing[-1]) playcmd = "/usr/bin/kodi-send --host=localhost --port=9777 --action=\"Playmedia(special://profile/playlists/music/Magicplay_" + plc + ".xsp)\"" call(playcmd, shell=True) def pinToName(pin): if pin == 19: return 'farleft' if pin == 26: return 'left' if pin == 16: return 'right' if pin == 20: return 'farright' def hyperPlaylist(playlists): global hyperplaying rootpath = "/home/kodi/.kodi/userdata/playlists/music/Magicplay_" savepath = rootpath + "magic.xsp" smp = ET.Element('smartplaylist') smp.set('type','songs') nm = ET.SubElement(smp, 'name') nm.text = "Magicplay_Magic" mt = ET.SubElement(smp, 'match') mt.text = "any" for f in playlists: rl = ET.SubElement(smp, 'rule') rl.set('field', 'playlist') rl.set('operator', 'is') rl.text = "Magicplay_" + f oot = ET.tostring(smp, pretty_print=True, standalone=True, xml_declaration=True, encoding='UTF-8') with open(savepath, "w") as of: of.write(oot) hyperplaying = True ############################################################# #### KODI MONITOR #### kodistate = "not" def kodiMonitor(): global kodistate headers = {'content-type': 'application/json'} xbmc_host = 'localhost' xbmc_port = 8080 xbmc_json_rpc_url = "http://" + xbmc_host + ":" + str(xbmc_port) + "/jsonrpc" payload = {"jsonrpc": "2.0", "method": "Player.GetActivePlayers", "id": 1} url_param = urllib.urlencode({'request': json.dumps(payload)}) while True: response = requests.get(xbmc_json_rpc_url + '?' + url_param, headers=headers) data = json.loads(response.text) if data['result']: # kodi is playing kodistate = "playing" else: kodistate = "not" time.sleep(1) ############################################################# #### GPIO SHIT #### def buttonHandler(pin): # rising/falling state seems to have some lag, add a delay time.sleep(0.5) # if pin falling, switch is off if GPIO.input(pin) == 1: if bState[pin] == 1: bState[pin] = 0 switchOff(pin) # pin rising, switch is on else: if bState[pin] == 0: bState[pin] = 1 switchOn(pin) def switchOn(pin): global thingHappening global switchstates global prevThing # update switch status # this allows an led to stay lit during phasing to indicate a switch is on. if pin == 19: # farleft switchstates[0] = 10 if pin == 26: # left switchstates[1] = 13 if pin == 16: # right switchstates[2] = 17 if pin == 20: # farright switchstates[3] = 20 # update thingHappening, only applies to ravemode, other updates come via kodiMonitor() if pin == 21: thingHappening = "ravemode" else: # playlist shit doKodiThings(pinToName(pin)) def switchOff(pin): global switchstates # update switch status if pin == 19: # farleft switchstates[0] = 99 if pin == 26: # left switchstates[1] = 99 if pin == 16: # right switchstates[2] = 99 if pin == 20: # farright switchstates[3] = 99 # now, is audio playing? If so thingHappening should be 'playing', otherwise: if pin != 21: doResetThings(pinToName(pin)) def main(): # consider working in GPIO.BOARD because BCM pinouts don't make any fucking sense GPIO.setmode(GPIO.BCM) # bridge to pin 6 (or gnd) to drop voltage GPIO.setup(19, GPIO.IN, pull_up_down=GPIO.PUD_UP) # pin 35 GPIO.setup(16, GPIO.IN, pull_up_down=GPIO.PUD_UP) # pin 36 GPIO.setup(26, GPIO.IN, pull_up_down=GPIO.PUD_UP) # pin 37 GPIO.setup(20, GPIO.IN, pull_up_down=GPIO.PUD_UP) # pin 38 GPIO.setup(21, GPIO.IN, pull_up_down=GPIO.PUD_UP) # pin 40 # pinouts here, we're in BCM mode # http://www.raspberrypi-spy.co.uk/wp-content/uploads/2012/09/Raspberry-Pi-GPIO-Layout-Revision-1.png # Button callbacks GPIO.add_event_detect(19, GPIO.BOTH, callback=buttonHandler, bouncetime=500) # button 0 GPIO.add_event_detect(16, GPIO.BOTH, callback=buttonHandler, bouncetime=500) # button 1 GPIO.add_event_detect(26, GPIO.BOTH, callback=buttonHandler, bouncetime=500) # button 2 GPIO.add_event_detect(20, GPIO.BOTH, callback=buttonHandler, bouncetime=500) # button 3 GPIO.add_event_detect(21, GPIO.BOTH, callback=buttonHandler, bouncetime=500) # button 4 # main loop while True: try: time.sleep(0.2) if kodistate == "not": # check if strip is on. If so, phase off. c1 = "#" + hex(strip.getPixelColor(1)).ljust(8,"0").replace("0x","") if c1 != "#000000": turnOff() time.sleep(5) # occasional knight ride colourwave rather than constant phasing if randint(1,10000) > 9998: holdingPattern() if kodistate == "playing": phaseStrip() if thingHappening == "ravemode": raveMode(15) except: turnOff() break GPIO.cleanup() ############################################################# #### Ready set go go go #### if __name__=="__main__": strip = Adafruit_DotStar(numpixels) strip.begin() kmon = Thread(target=kodiMonitor) kmon.daemon = True kmon.start() main()
Hi, I saw your post on reddit and it inspired me to try to do something similar to your project. I think it would be cool to try to sync up the lights in an LED strip with the music that is being played. But I'm looking through the Dotsar LED docs and examples, and I don't see anything that says I can set the brightness of individual pixels. Did you come across anything like this? I'm essentially trying to assign a pixel to a frequency and increase/decrease the brightness of that pixel in sync with the magnitude of the frequency.
ReplyDeleteThanks, and again, love your project!
Matt M.
Hey, so how'd it go? I'm interested in making one too. Perhaps with a clock, and alarm functionality. I've never done anything with Raspberry Pi or Python so far though...
DeleteIt went pretty well! I ended up rewriting this to use MPD instead of Kodi, but apart from that, yeah. Good.
DeleteThere are some pictures here: http://imgur.com/a/BUCaF
Ah! Nevermind--just hit me that I can just use RGB values then multiply them by the proportion that I want the brightness to reflect-- (255,0,0 is the same as 100,0,0 just different brightness).
ReplyDeleteAnyway, still love your idea, thanks for helping me jog my memory! Ha.
Nice solution! As far as I know you can only set brightness on the strip (via strip.setbrightness()), not per pixel, but your way works. I might update my fadeToBlack() function to work that way..
Deletebtw, reading the source for Adafruit_Dotstar module is a good idea, it's well commented and there's a few bits in there which aren't mentioned anywhere else.
Hope your project works out!