Here's some Mote Examples

Hi guys,

I’ve written a few examples for the new Mote board.

My Examples (Dropbox share)

Mainly pretty lights stuff but a few examples of useful stuff like a CPU monitor. These have been adapted from either Blinkt examples or Adafruit’s Dotstar examples.

Let me know what you think?

Tom.

Looks great! Feel free to submit to repo!

Thanks for posting your code! It was great to see an image start to light up a strip, since I just started messing with my mote. I managed to get the same chunk of image to light up all 4, but haven’t yet got the 4 to continue distributing the rest of the image.p

I’m trying to convert the Adafruit PI-Painter program to work with mote. I made that entire project with Dotstar strips, which are just too fragile for the project I’m doing. Does anyone have any advice on changing the Adafruit code to use mote instead of a dotstar strip? I’m relatively a newbie when it comes to python (other than a few Adafruit projects) and have been struggling for hours trying make it work (in a multitude of ways!)

I like the button activation and the USB delivery of images that are in the Adafruit code, which is why I think I need to slot in way for it to find more.

-Lori

Lori,

I’m also a newbie to Python. Maybe if you share your code with me I can have a go at making it work?

I have forked the mote git and done a pull request but I have been contacted by the guys at Pimoroni to make a few changes. I haven’t got round to doing them yet but I plan to!

Cheers,

Bucky.

I’ll upload what I have so far for code as soon as I get to my studio today. (New studio has TERRIBLE WIFI, so I’m at home to use the internet).

I’ve been able to make the DotStarPiPainter code run on the PiZero ( with Mote hub and Mote LEDS hooked up to it with the buttons for the PiPainter also hooked up to the PiZero to the specified pins). I also put in some of the Mote code placed within the PiPainter code and got it to the point where it doesn’t crash. It will send an error if I have the Mote unplugged, which I think is a good sign.

What I can’t figure out is where/how to put in the location of the Mote within PiPainter code. I think this has to take place where there is the Adafruit_DotStar, but guess there’s more to it than just that swap:

strip     = Adafruit_DotStar(numpixels, datapin, clockpin)`

If I change the Adafruit_DotStar to Mote() and put anything within the (), then it crashes. It will run with just Mote(), but the LEDs still do not light up. I can get the code for the USB to run, but no lights.

I was able to get an image showing on all 4 strips of the Mote LEDs with some alterations to your code. When I run a photographic image, the #16 LEDs on each strip don’t light up. #16s do light up when I have a color gradient .jpg vs. a photographic .jpg, which makes no sense to me.

My goal is to be able to have the automation of the USB stick removal, auto processing of images, and the buttons to fire the LEDs for the device that I want to build (DotStarPiPainter code), but I want to use the Mote hardware, which is much sturdier than the 1m DotStars. I’m making a wearable, so the Mote strips are the perfect size and solution with the cables between them and the hub.

I’ll update my exact code as soon as I can today and maybe the internet will come to the rescue! I can also put this out as it’s own topic, if that’s better (moderator?) for getting others to respond.

The new Mote Phat is also intriguing for my project, but I won’t be able to get my hands on one of those for a few weeks. I took a look at that code and it is completely different, possibly easier? I don’t know! :)

OK, this took quite awhile, as I was traveling and just got back to the project! If anyone has a suggestion for making this work with Mote, I’d be 1000% appreciative (and maybe will mail you some art)!

> #!/usr/bin/python

> # --------------------------------------------------------------------------
> # DotStar Light Painter for Raspberry Pi.
> #
> # Hardware requirements:
> # - Raspberry Pi computer (any model)
> # - DotStar LED strip (any length, but 144 pixel/m is ideal):
> #   www.adafruit.com/products/2242
> # - Five momentary pushbuttons for controls, such as:
> #   www.adafruit.com/products/1010
> # - One 74AHCT125 logic level shifter IC:
> #   www.adafruit.com/products/1787
> # - High-current, high-capacity USB battery bank such as:
> #   www.adafruit.com/products/1566
> # - Perma-Proto HAT for Raspberry Pi:
> #   www.adafruit.com/products/2310
> # - Various bits and bobs to integrate the above parts.  Wire, Perma-Proto
> #   PCB, 3D-printed enclosure, etc.  Your approach may vary...improvise!
> #
> # Software requirements:
> # - Raspbian (2015-05-05 "Wheezy" version recommended; can work with Jessie
> #   or other versions, but Wheezy's a bit smaller and boots to the command
> #   line by default).
> # - Adafruit DotStar library for Raspberry Pi:
> #   github.com/adafruit/Adafruit_DotStar_Pi
> # - usbmount:
> #   sudo apt-get install usbmount
> #   See file "99_lightpaint_mount" for add'l info.
> #
> # Written by Phil Burgess / Paint Your Dragon for Adafruit Industries.
> #
> # Adafruit invests time and resources providing this open source code,
> # please support Adafruit and open-source hardware by purchasing products
> # from Adafruit!
> # --------------------------------------------------------------------------

> import os
> import select
> import signal
> import time
> import RPi.GPIO as GPIO
> #import atexit
> from dotstar import Adafruit_DotStar
> from evdev import InputDevice, ecodes
> from lightpaint import LightPaint
> from PIL import Image
> from mote import Mote

> mote = Mote()

> mote.configure_channel(1, 17, False)
> mote.configure_channel(2, 33, False)
> mote.configure_channel(3, 49, False)
> mote.configure_channel(4, 65, False)


> # CONFIGURABLE STUFF -------------------------------------------------------

> num_leds   = 144    # Length of LED strip, in pixels
> pin_go     = 22     # GPIO pin numbers (Broadcom numbering) for 'go' button,
> pin_next   = 17     # previous image, next image and speed +/-.
> pin_prev   =  4
> pin_faster = 23
> pin_slower = 24
> order      = 'brg'  # 'brg' for current DotStars, 'gbr' for pre-2015 strips
> vflip      = 'true' # 'true' if strip input at bottom, else 'false'
> strip      = Mote()

> # DotStar strip data & clock MUST connect to hardware SPI pins
> # (GPIO 10 & 11).  12000000 (12 MHz) is the SPI clock rate; this is the
> # fastest I could reliably operate a 288-pixel strip without glitching.
> # You can try faster, or may need to set it lower, no telling.
> # If using older (pre-2015) DotStar strips, declare "order='gbr'" above
> # for correct color order.

> strip = Adafruit_DotStar(num_leds, 12000000, order=order)



> path      = '/media/usb'         # USB stick mount point
> mousefile = '/dev/input/mouse0'  # Mouse device (as positional encoder)
> eventfile = '/dev/input/event0'  # Mouse events accumulate here
> dev       = None                 # None unless mouse is detected

> gamma          = (2.8, 2.8, 2.8) # Gamma correction curves for R,G,B
> color_balance  = (128, 255, 180) # Max brightness for R,G,B (white balance)
> power_settings = (1450, 1550)    # Battery avg and peak current

> # INITIALIZATION -----------------------------------------------------------

> # Set control pins to inputs and enable pull-up resistors.
> # Buttons should connect between these pins and ground.
> GPIO.setmode(GPIO.BCM)
> GPIO.setup(pin_go    , GPIO.IN, pull_up_down=GPIO.PUD_UP)
> GPIO.setup(pin_prev  , GPIO.IN, pull_up_down=GPIO.PUD_UP)
> GPIO.setup(pin_next  , GPIO.IN, pull_up_down=GPIO.PUD_UP)
> GPIO.setup(pin_slower, GPIO.IN, pull_up_down=GPIO.PUD_UP)
> GPIO.setup(pin_faster, GPIO.IN, pull_up_down=GPIO.PUD_UP)

> mote.configure_channel(1, 15, False)
> mote.configure_channel(2, 31, False)
> mote.configure_channel(3, 47, False)
> mote.configure_channel(4, 63, False)

> #strip.begin() # Initialize SPI pins for output

> ledBuf     =  strip.getPixels() # Pointer to 'raw' LED strip data
> clearBuf   = bytearray([0xFF, 0, 0, 0] * num_leds)
> imgNum     = 0    # Index of currently-active image
> duration   = 2.0  # Image paint time, in seconds
> filename   = None # List of image files (nothing loaded yet)
> lightpaint = None # LightPaint object for currently-active image (none yet)

> # If a mouse is plugged in, set up epoll for sensing position
> #if os.path.exists(mousefile):
> #	dev = InputDevice(eventfile)
> #	# Register mouse descriptor with epoll
> #	epoll = select.epoll()
> #	epoll.register(dev.fileno(), select.EPOLLIN)
> #	print 'Using mouse for positional input'


> # FUNCTIONS ----------------------------------------------------------------

> # Signal handler when SIGUSR1 is received (USB flash drive mounted,
> # triggered by usbmount and 99_lightpaint_mount script).
> def sigusr1_handler(signum, frame):
> 	scandir()

> # Ditto for SIGUSR2 (USB drive removed -- clears image file list)
> def sigusr2_handler(signum, frame):
> 	global filename
> 	filename = None
> 	imgNum   = 0
> 	# Current LightPaint object is left resident

> # Scan root folder of USB drive for viable image files.
> def scandir():
> 	global imgNum, lightpaint, filename
> 	files     = os.listdir(path)
> 	num_files = len(files) # Total # of files, whether images or not
> 	filename  = []         # Filename list of valid images
> 	imgNum    = 0
> 	if num_files == 0: return
> 	for i, f in enumerate(files):
> 		lower =  i      * num_leds / num_files
> 		upper = (i + 1) * num_leds / num_files
> 		for n in range(lower, upper):
> 			strip.setPixelColor(n, 0x010100) # Yellow
> 		strip.show()
> 		if f[0] == '.': continue
> 		try:    Image.open(os.path.join(path, f))
> 		except: continue   # Is directory or non-image file; skip
> 		filename.append(f) # Valid image, add to list
> 		time.sleep(0.05)   # Tiny pause so progress bar is visible
> 	strip.clear()
> 	strip.show()
> 	if len(filename) > 0:                  # Found some image files?
> 		filename.sort()                # Sort list alphabetically
> 		lightpaint = loadImage(imgNum) # Load first image

> # Load image, do some conversion and processing as needed before painting.
> def loadImage(index):
> 	num_images = len(filename)
> 	lower      =  index      * num_leds / num_images
> 	upper      = (index + 1) * num_leds / num_images
> 	for n in range(lower, upper):
> 		strip.setPixelColor(n, 0x010000) # Red = loading
> 	strip.show()
> 	print "Loading '" + filename[index] + "'..."
> 	startTime = time.time()
> 	# Load image, convert to RGB if needed
> 	img = Image.open(os.path.join(path, filename[index])).convert("RGB")
> 	print "\t%dx%d pixels" % img.size

> 	# If necessary, image is vertically scaled to match LED strip.
> 	# Width is NOT resized, this is on purpose.  Pixels need not be
> 	# square!  This makes for higher-resolution painting on the X axis.
> 	if img.size[1] != num_leds:
> 		print "\tResizing...",
> 		img = img.resize((img.size[0], num_leds), Image.BICUBIC)
> 		print "now %dx%d pixels" % img.size

> 	# Convert raw RGB pixel data to a string buffer.
> 	# The C module can easily work with this format.
> 	pixels = img.tostring()
> 	print "\t%f seconds" % (time.time() - startTime)

> 	# Do external C processing on image; this provides 16-bit gamma
> 	# correction, diffusion dithering and brightness adjustment to
> 	# match power source capabilities.
> 	for n in range(lower, upper):
> 		strip.setPixelColor(n, 0x010100) # Yellow
> 	mote.show()
> 	print "Processing..."
> 	startTime  = time.time()
> 	# Pixel buffer, image size, gamma, color balance and power settings
> 	# are REQUIRED arguments.  One or two additional arguments may
> 	# optionally be specified:  "order='gbr'" changes the DotStar LED
> 	# color component order to be compatible with older strips (same
> 	# setting needs to be present in the Adafruit_DotStar declaration
> 	# near the top of this code).  "vflip='true'" indicates that the
> 	# input end of the strip is at the bottom, rather than top (I
> 	# prefer having the Pi at the bottom as it provides some weight).
> 	# Returns a LightPaint object which is used later for dithering
> 	# and display.
> 	lightpaint = LightPaint(pixels, img.size, gamma, color_balance,
> 	  power_settings, order=order, vflip=vflip)
> 	print "\t%f seconds" % (time.time() - startTime)

> 	# Success!
> 	for n in range(lower, upper):
> 		strip.setPixelColor(n, 0x000100) # Green
> 	strip.show()
> 	time.sleep(0.25) # Tiny delay so green 'ready' is visible
> 	print "Ready!"

> 	strip.clear()
> 	strip.show()
> 	return lightpaint

> def btn():
> 	if not GPIO.input(pin_go):     return 1
> 	if not GPIO.input(pin_faster): return 2
> 	if not GPIO.input(pin_slower): return 3
> 	if not GPIO.input(pin_next):   return 4
> 	if not GPIO.input(pin_prev):   return 5
> 	return 0

> # MAIN LOOP ----------------------------------------------------------------

> # Init some stuff for speed selection...
> max_time    = 10.0
> min_time    =  0.1
> time_range  = (max_time - min_time)
> speed_pixel = int(num_leds * (duration - min_time) / time_range)
> duration    = min_time + time_range * speed_pixel / (num_leds - 1)
> prev_btn    = 0
> rep_time    = 0.2

> scandir() # USB drive might already be inserted
> signal.signal(signal.SIGUSR1, sigusr1_handler) # USB mount signal
> signal.signal(signal.SIGUSR2, sigusr2_handler) # USB unmount signal

> try:
> 	while True:
> 		b = btn()
> 		if b == 1 and lightpaint != None:
> 			# Paint!

> 			if dev is None: # Time-based

> 				startTime = time.time()
> 				while True:
> 					t1        = time.time()
> 					elapsed   = t1 - startTime
> 					if elapsed > duration: break
> 					# dither() function is passed a
> 					# destination buffer and a float
> 					# from 0.0 to 1.0 indicating which
> 					# column of the source image to
> 					# render.  Interpolation happens.
> 					lightpaint.dither(ledBuf,
> 					  elapsed / duration)
> 					strip.show(ledBuf)

> 			else: # Encoder-based

> 				mousepos = 0
> 				scale    = 0.01 / (speed_pixel + 1)
> 				while True:
> 					input = epoll.poll(-1) # Non-blocking
> 					for i in input: # For each pending...
> 						try:
> 							for event in dev.read():
> 								if(event.type == ecodes.EV_REL and
> 								   event.code == ecodes.REL_X):
> 									mousepos += event.value
> 						except:
> 							# If this occurs, usually power settings
> 							# are too high for battery source.
> 							# Voltage sags, Pi loses track of USB device.
> 							print 'LOST MOUSE CONNECTION'
> 							continue

> 					pos = abs(mousepos) * scale
> 					if pos > 1.0: break
> 					lightpaint.dither(ledBuf, pos)
> 					strip.show(ledBuf)

> 			if btn() != pin_go: # Button released?
> 				strip.show(clearBuf)

> 		elif b == 2:
> 			# Decrease paint duration
> 			if speed_pixel > 0:
> 				speed_pixel -= 1
> 				duration = (min_time + time_range *
> 				  speed_pixel / (num_leds - 1))
> 			strip.setPixelColor(speed_pixel, 0x000080)
> 			strip.show()
> 			startTime = time.time()
> 			while (btn() == 2 and ((time.time() - startTime) <
> 			  rep_time)): continue
> 			strip.clear()
> 			strip.show()
> 		elif b == 3:
> 			# Increase paint duration (up to 10 sec maximum)
> 			if speed_pixel < num_leds - 1:
> 				speed_pixel += 1
> 				duration = (min_time + time_range *
> 				  speed_pixel / (num_leds - 1))
> 			strip.setPixelColor(speed_pixel, 0x000080)
> 			strip.show()
> 			startTime = time.time()
> 			while (btn() == 3 and ((time.time() - startTime) <
> 			  rep_time)): continue
> 			strip.clear()
> 			strip.show()
> 		elif b == 4 and filename != None:
> 			# Next image (if USB drive present)
> 			imgNum += 1
> 			if imgNum >= len(filename): imgNum = 0
> 			lightpaint = loadImage(imgNum)
> 			while btn() == 4: continue
> 		elif b == 5 and filename != None:
> 			# Previous image (if USB drive present)
> 			imgNum -= 1
> 			if imgNum < 0: imgNum = len(filename) - 1
> 			lightpaint = loadImage(imgNum)
> 			while btn() == 5: continue
> 		if b > 0 and b == prev_btn:
> 			# If button held, accelerate speed selection
> 			rep_time *= 0.92
> 			if rep_time < 0.01: rep_time = 0.01
> 		else:
> 			rep_time = 0.2
> 		prev_btn = b

> except KeyboardInterrupt:
> 	print "Cleaning up"
> 	GPIO.cleanup()
> 	strip.clear()
> 	strip.show()
> 	print "Done!"

I now have Mote running with Hyperion and Kodi.
This means that there are ambient lights behind the TV screen that reflect what is happening on the screen.
There are plenty of videos showing Hyperion and Kodi together so I have not made another one.
With only 64 LEDs (4 Mote sticks) it is best suited to a small (say 32-inch) screen but it is usable on larger ones.

I found some code use Pimoroni Unicorn HAT as a Art-Net & OPC/FadeCandy network device … so I adapted that to drive the Pimoroni Mote.
It works (although there are some unhandled issues largely due to networking not being 100% reliable).
I plan to put it on GitHub after it has survived some soak tests.

1 Like

This is lovely and great work as many went for the mote with that in mind but were too lazy or not skilled enough to do it.

I hope that in addition to sharing your code, you can also share some trick to update an LibreELEC/OpenELEC/OSMC/XBian distribution and add your code to that, as this is the most frequent usecase for Kodi users.

If I understand your code and feel able to modify it (and licence permit it), then I have some plan:

  • Do it with the edge LED from a unicorn HAT, pHAT or HD HAT.
  • Do a fake display (TV on / presence simulator) with all the LEDs from a unicorn HAT, pHAT, unicorn HD HAT.

Regards

If there is a volunteer with Mote (4 sticks) and OSMC then let me know and I can give you some draft instructions to see if it works for you.

Note - I just tried it on a large TV (with Kodi / Hyperion / Light server all on the same OSMC RPi) and it is not great because 64 LEDs on 4 short sticks means that you cannot get coverage from corner to corner on the TV so you end up with lights shining to represent a colour that might be 6 inches away. It is not a disaster … but not ideal.
I went for no lights along the bottom - i.e. 16 going up the left side, 32 along the top and then 16 down the right side.

1 Like

The code is here for now


(note - it is in a branch called “mote” rather than “master” because I still want to fiddle around with it to make it a bit more general purpose and then propose it back to the original author of the Unicorn version.