Pico Unicorn Pack not working in Micropython

Hi,
I recently bought 3 Picos with Unicorn Packs to go with them - because sparkly lights is what I think my kids need to get them to do some Python.

I cannot get them to work.
I’ve successfully flashed the Picos with your latest MicroPython firmware (currently 0.0.5 alpha)
following pimoroni-pico/setting-up-micropython.md at main · pimoroni/pimoroni-pico · GitHub.

Using Thonny I can connect to them and run scripts or use the REPL command prompt.
The Picos are alive and responding.

BUT:

  1. There are no examples of use of the Unicorn pack in your micropython example folder (pimoroni-pico/micropython/examples at main · pimoroni/pimoroni-pico · GitHub)
  2. The following code works:
    import picounicorn as uni
    w = uni.get_width()
    h = uni.get_height()
    print(‘Unicorn size’,w,‘by’,h)
    displaying 'Unicorn size 16 x 7
  3. The following doesn’t:
    print('uni.is_pressed(uni.BUTTON_A)') # Prints True whether button is pressed or not.
    uni.clear() # Causes some LEDs to briefly flash.
    uni.clear() # Second call causes all LEDs except first four columns to light up cyan - then freezes the REPL.

Does the Unicorn Pack actually work under Micropython yet?

Documentation and examples for the Pico stuff seem to be in draft/not done yet unfortunately.

Have you updated to the latest v0.0.5 Alpha firmware? I think there was a note of some sort of bug in the older versions of the Uniforn firmware, that might help with the odd LED issues.

Ok, some progress at last.
Not very exciting, but this at least does work:
import picounicorn as uni

uni.init() # This must be called EXACTLY ONCE

w = uni.get_width()
h = uni.get_height()
print('Unicorn size',w,'by',h)

def fill_rect(minx, miny, maxx, maxy, r, g, b):
    for x in range(minx, maxx):
        for y in range(miny, maxy):
            uni.set_pixel(x,y, r, g, b)

while True:
    for r in range(16):
        for g in range(16):
            for b in range(16):
                fill_rect(0,0, w,h, r*16,g*16,b*16)

Do not use uni.clear() - it hangs.
is_pressed(…) works once init() called.
Calling init() multiple times tends to cause a crash - this is a problem when rerunning a script during dev.

Anyway, signs of life at last!

Yes, I’m using the 0.0.5 release.

Spent the evening fiddling with this - successfully got a Conway’s Life implementation running!
If anyone wants to try it, here you go. Happy to donate this to the examples.

import picounicorn as uni
import random
import time

uni.init()

w = uni.get_width()
h = uni.get_height()

class Cells:
    def __init__(self):
        self.cells = [[0]*h for i in range(w)]

    def clear_all(self):
        for x in range(w):
            for y in range(h):
                self.cells[x][y] = 0

    def set_random_cells_to_value(self, prob, value):
        for x in range(w):
            for y in range(h):
                if random.random() < prob:
                    self.cells[x][y] = value

    def is_alive(self,x,y):
        x = x % w
        y = y % h
        return self.cells[x][y] == 255

    def get_num_live_neighbours(self, x, y):
        num = 0
        num += (1 if self.is_alive(x-1,y-1) else 0)
        num += (1 if self.is_alive(x  ,y-1) else 0)
        num += (1 if self.is_alive(x+1,y-1) else 0)
        num += (1 if self.is_alive(x-1,y) else 0)
        num += (1 if self.is_alive(x+1,y) else 0)
        num += (1 if self.is_alive(x-1,y+1) else 0)
        num += (1 if self.is_alive(x  ,y+1) else 0)
        num += (1 if self.is_alive(x+1,y+1) else 0)
        return num
        
    def iterate_from(self, fromCells):
        for x in range(w):
            for y in range(h):
                num_live_nbrs = fromCells.get_num_live_neighbours(x,y)
                is_alive = fromCells.is_alive(x,y)
                if is_alive and (num_live_nbrs < 2 or num_live_nbrs > 3):
                    self.cells[x][y] = 0 # Died
                elif not is_alive and num_live_nbrs == 3:
                    self.cells[x][y] = 255 # Born
                else:
                    self.cells[x][y] = fromCells.cells[x][y] # Unchanged state

def ExportToLeds(cells):
    for x in range(w):
        for y in range(h):
            value = cells[x][y]
            uni.set_pixel_value(x,y,value)


cellsA = Cells()
cellsB = Cells()
start = True

while True:
    if start:
        cellsA.clear_all()
        cellsA.set_random_cells_to_value(0.2, 255)
        start = False
    ExportToLeds(cellsA.cells)
    time.sleep_ms(200)
    cellsB.iterate_from(cellsA)
    (cellsA, cellsB) = (cellsB, cellsA)
    start = uni.is_pressed(uni.BUTTON_A)

It is a shame about the low level of documentation at the moment. We just have to post our findings when we work something out.

Thank you for this code. I picked up a great deal from reading it.

I bought an Explorer and it was very easy to port it to the Explorer screen with 48 x 48 cells displayed.

I’ve put the Explorer version below to help those users. I hope this is OK with you.

It will be very easy to port to Display as well.

# Conway's Game of Life
# Converted by Tony Goodhew from Steve Baine's post for Pico Unicorn Pack
# https://forums.pimoroni.com/t/pico-unicorn-pack-not-working-in-micropython/15997/4
# 13 Feb 2021 Tested with Pimoroni UF2 Version 0.0.7
import picoexplorer as display
import time, random
width = display.get_width()
height = display.get_height()
display_buffer = bytearray(width * height * 2)
display.init(display_buffer)

w = 48 # Horizontal cell
h = 48 # Vertical cells
class Cells:
    def __init__(self):
        self.cells = [[0]*h for i in range(w)]

    def clear_all(self):
        for x in range(w):
            for y in range(h):
                self.cells[x][y] = 0

    def set_random_cells_to_value(self, prob, value):
        for x in range(w):
            for y in range(h):
                if random.random() < prob:
                    self.cells[x][y] = value

    def is_alive(self,x,y):
        x = x % w
        y = y % h
        return self.cells[x][y] == 255

    def get_num_live_neighbours(self, x, y):
        num = 0
        num += (1 if self.is_alive(x-1,y-1) else 0)
        num += (1 if self.is_alive(x  ,y-1) else 0)
        num += (1 if self.is_alive(x+1,y-1) else 0)
        num += (1 if self.is_alive(x-1,y) else 0)
        num += (1 if self.is_alive(x+1,y) else 0)
        num += (1 if self.is_alive(x-1,y+1) else 0)
        num += (1 if self.is_alive(x  ,y+1) else 0)
        num += (1 if self.is_alive(x+1,y+1) else 0)
        return num
        
    def iterate_from(self, fromCells):
        for x in range(w):
            for y in range(h):
                num_live_nbrs = fromCells.get_num_live_neighbours(x,y)
                is_alive = fromCells.is_alive(x,y)
                if is_alive and (num_live_nbrs < 2 or num_live_nbrs > 3):
                    self.cells[x][y] = 0 # Died
                elif not is_alive and num_live_nbrs == 3:
                    self.cells[x][y] = 255 # Born
                else:
                    self.cells[x][y] = fromCells.cells[x][y] # Unchanged state

def ExportToScreen(cells):
    for x in range(w):
        for y in range(h):
            value = cells[x][y] 
            display.set_pen(value,value,0)
            xx = x * 5
            yy = y * 5
            for xr in range(5):
                for yr in range(5):
                    display.pixel(xx+xr,yy+yr)
    display.update()
    
def blk():
    display.set_pen(0,0,0)
    display.clear()
    display.update()
    
def title(msg,r,g,b):
    blk()
    display.set_pen(r,g,b)
    display.text(msg, 20, 70, 200, 4)
    display.update()
    time.sleep(2)
   
title("Conway's game of life",0,0,255)
display.set_pen(120,120,120)
display.text(" Hold button Y to halt", 5, 190, 230, 2)
display.text("  Button A to re-seed", 5, 210, 230, 2)
display.update()
time.sleep(3)
cellsA = Cells()
cellsB = Cells()
start = True
display.set_pen(0,0,0)
display.update()
running = True
while running:
    if start:
        cellsA.clear_all()
        cellsA.set_random_cells_to_value(0.2, 255)
        blk()
        display.set_pen(200,200,200)
        display.text("New Colony", 40, 120, 230, 3)
        time.sleep(0.5)
        display.update()
        start = False
    ExportToScreen(cellsA.cells)

    cellsB.iterate_from(cellsA)
    (cellsA, cellsB) = (cellsB, cellsA)
    start = display.is_pressed(display.BUTTON_A)
    if display.is_pressed(3): # Y button is pressed ?
        running = False
blk()
print("Finished")

Video added here:
Conway’s Game of Life - Demonstration With Code - Instructables

For those of you with a Pico Display here is a working version - tested with Version 0.0.7
Thank you Steve.

# Conway's Game of Life
# Converted by Tony Goodhew from Steve Baine's post for Pico Unicorn Pack
# https://forums.pimoroni.com/t/pico-unicorn-pack-not-working-in-micropython/15997/4
# 14 Feb 2021 Tested with 0.0.7
import picodisplay as display
import time, random
width = display.get_width()
height = display.get_height()
display_buffer = bytearray(width * height * 2)
display.init(display_buffer)
# Set the backlight to 50%
display.set_backlight(0.5)

w = 60 # Horizontal cell
h = 33 # Vertical cells
class Cells:
    def __init__(self):
        self.cells = [[0]*h for i in range(w)]

    def clear_all(self):
        for x in range(w):
            for y in range(h):
                self.cells[x][y] = 0

    def set_random_cells_to_value(self, prob, value):
        for x in range(w):
            for y in range(h):
                if random.random() < prob:
                    self.cells[x][y] = value

    def is_alive(self,x,y):
        x = x % w
        y = y % h
        return self.cells[x][y] == 255

    def get_num_live_neighbours(self, x, y):
        num = 0
        num += (1 if self.is_alive(x-1,y-1) else 0)
        num += (1 if self.is_alive(x  ,y-1) else 0)
        num += (1 if self.is_alive(x+1,y-1) else 0)
        num += (1 if self.is_alive(x-1,y) else 0)
        num += (1 if self.is_alive(x+1,y) else 0)
        num += (1 if self.is_alive(x-1,y+1) else 0)
        num += (1 if self.is_alive(x  ,y+1) else 0)
        num += (1 if self.is_alive(x+1,y+1) else 0)
        return num
        
    def iterate_from(self, fromCells):
        for x in range(w):
            for y in range(h):
                num_live_nbrs = fromCells.get_num_live_neighbours(x,y)
                is_alive = fromCells.is_alive(x,y)
                if is_alive and (num_live_nbrs < 2 or num_live_nbrs > 3):
                    self.cells[x][y] = 0 # Died
                elif not is_alive and num_live_nbrs == 3:
                    self.cells[x][y] = 255 # Born
                else:
                    self.cells[x][y] = fromCells.cells[x][y] # Unchanged state

def ExportToScreen(cells):
    for x in range(w):
        for y in range(h):
            value = cells[x][y] 
            display.set_pen(value,value,0)
            xx = x * 4
            yy = y * 4
            for xr in range(4):
                for yr in range(4):
                    display.pixel(xx+xr,yy+yr +1)
    display.update()
    
def blk():
    display.set_pen(0,0,0)
    display.clear()
    display.update()
    
def title(msg,r,g,b):
    blk()
    display.set_pen(r,g,b)
    display.text(msg, 5, 5, 200, 4)
    display.update()
    time.sleep(2)
   
title("Conway's game of life",0,0,255)
display.set_pen(120,120,120)
display.text(" Hold button Y to halt", 5, 100, 230, 2)
display.text("  Button A to re-seed", 5, 120, 230, 2)
display.update()
time.sleep(3)
cellsA = Cells()
cellsB = Cells()
start = True
display.set_pen(0,0,0)
display.update()
running = True
while running:
    if start:
        cellsA.clear_all()
        cellsA.set_random_cells_to_value(0.2, 255)
        blk()
        display.set_pen(200,200,200)
        display.text("New Colony", 40, 60, 230, 3)
        time.sleep(0.5)
        display.update()
        start = False
    ExportToScreen(cellsA.cells)

    cellsB.iterate_from(cellsA)
    (cellsA, cellsB) = (cellsB, cellsA)
    start = display.is_pressed(display.BUTTON_A)
    if display.is_pressed(3): # Y button is pressed ?
        running = False
blk()
display.set_pen(200,0,0)
display.text("All Done!", 55, 40, 200, 3)
display.update()
time.sleep(2)
blk()