Skip to content

Instantly share code, notes, and snippets.

@dglaude
Last active January 23, 2023 16:55
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dglaude/a18be5eccbf8332c1ef0aa8e77a6e5a1 to your computer and use it in GitHub Desktop.
Save dglaude/a18be5eccbf8332c1ef0aa8e77a6e5a1 to your computer and use it in GitHub Desktop.
Blinkt! API for microPython (ESP8266)
f=open("random.py","w")
f.write('''import os
def getrandbits(k):
numbytes = (k + 7) // 8
x = int.from_bytes(os.urandom(numbytes), 'big')
return x >> (numbytes * 8 - k)
def bit_length(n):
res = n
count = 1
while res>1:
res = res>>1
count = count+1
return count
def randbelow(n):
k = bit_length(n)
r = getrandbits(k)
while r >= n:
r = getrandbits(k)
return r
def randrange(start, stop=None, step=1):
istart = int(start)
if istart != start:
raise ValueError("non-integer arg 1 for randrange()")
if stop is None:
if istart > 0:
return randbelow(istart)
raise ValueError("empty range for randrange()")
# stop argument supplied.
istop = int(stop)
if istop != stop:
raise ValueError("non-integer stop for randrange()")
width = istop - istart
if step == 1 and width > 0:
return istart + randbelow(width)
if step == 1:
raise ValueError("empty range for randrange() (%d,%d, %d)" % (istart, istop, width))
# Non-unit step argument supplied.
istep = int(step)
if istep != step:
raise ValueError("non-integer step for randrange()")
if istep > 0:
n = (width + istep - 1) // istep
elif istep < 0:
n = (width + istep + 1) // istep
else:
raise ValueError("zero step for randrange()")
if n <= 0:
raise ValueError("empty range for randrange()")
return istart + istep*randbelow(n)
def randint(a, b):
return randrange(a, b+1)
def sample(population, k):
n = len(population)
if not 0 <= k <= n:
raise ValueError("Sample larger than population")
result = [None] * k
selected = set()
selected_add = selected.add
for i in range(k):
j = randbelow(n)
while j in selected:
j = randbelow(n)
selected_add(j)
result[i] = population[j]
return result
''')
f.close()

microPython already has build in support for APA102: http://docs.micropython.org/en/latest/esp8266/esp8266/quickref.html#apa102-driver So it is easy to use the Blinkt! with an ESP8266 running microPython firmware.

You can find Blink! on Pimoroni Web Shop: https://shop.pimoroni.com/products/blinkt

From an hardware point of view the Blinkt! require:

Unfortunately, on my ESP8266 I don't have 5V (when USB is not connected), but only regulated 3V. But by chance, the APA102 is OK with that, maybe it is less bright, but good enough for me.

AFAIR, I choosed 13 and 14 as GPIO on the ESP8266. I guess 13 to be connected to 23 and 14 to 24.

To make it even more easy, and be able to reuse Blinkt! example, I created a think layer that provide the same API as the Blinkt! library.

To help run more example "out of the box", I added some more helper library for missing function in microPython.

  1. I put all my library (those that you "import") in the lib folder. That folder does not exist by default, but is in the default search path. So this is the right place to put such file.

  2. I only do microPython from the REPL, so to create file I use a trick with f.write(''' This require to us the paste without indentation and no to do too big files. Feel free to install any other way.

  3. There are other issue that make using Blinkt! code example difficult:

3.1) There is not colorsys library build in, so I made a mini version with what I need.

3.2) You can not really execute a python script, you can only import the file, once. So it is best to create a main function and call that one when you want.

3.3) There is no decent random library and a lot of example use integer random number. I provided a mini implementation of the function needed.

3.4) The time resolution is to the second only, so no decimal value. It is best to use time.ticks_ms() if sub second precision is required.

Except for point 3.4 that require some deep change in the code, most example might work "out of the box". You will find my version of the rainbow.py example that work. Make sure you put this file at the root of the "filesystem".

If you want to auto-start some example when you power the ESP8266, you can use "main.py" file for that:

import my_rainbow my_rainbow.main()

You can find other example using Blinkt on the github of the Blinkt! library: https://github.com/pimoroni/blinkt/tree/master/examples

Here will come a tentative to see what example are likely or unlikely to work on ESP8266

  • 1d_tetris.py This fail, but I don't know why. I don't see why it would not work.
  • binary_clock.py There is no RTC on my ESP8266 and localtime() does not produce the same tuple as in Python3.
  • binary_clock_meld.py See above
  • cheerlights.py Making this one work should be a goal as typically IoT, but module requests is missing, there is a some copy floating, that lack TSL support. Would need to try harder.
  • cpu_load.py Not workinkg or likely useless on ESP8266
  • cpu_temp.py Not working or likely useless on ESP8266
  • gradient_graph.py WORKING by (1) removing "set_clear_on_exit()" that is not implemented (2) using time.ticks_ms()/500 rather than time.time() * 2
  • graph.py WORKING by (1) removing "set_clear_on_exit()" that is not implemented (2) using time.ticks_ms() / 1000 rather than time.time()
  • larson.py WORKING by changing time: start_time = time.ticks_ms() and delta = (time.ticks_diff(start_time,time.ticks_ms())) / 1000 * 16 ... after some time I get the following error: "IndexError: list index out of range". Making the array bigger by one might help, for unknown reason
  • larson_hue.py NOT TESTED
  • mem_load.py Not working or likely useless on ESP8266
  • morse_code.py WORKING as is without modification
  • mqtt.py FAIL because paho.mqtt.client is not available
  • pulse.py FAIL because numpy is not available
  • rainbow.py WORKING with provided version
  • random_blink.py FAIL because at random.sample(range(8), random.randint(1, 5)) because random.sample is not implemented
  • random_blink_colours.py WORKING as is without modification (but fake set_clear_on_exit)
  • resistor_clock.py There is no RTC on my ESP8266 and localtime() does not produce the same tuple as in Python3.
  • rgb.py FAIL we can not execute python script in microPython, so passing parameter fail
  • solid_colours.py WORKING as is without modification (but fake set_clear_on_exit)
  • twitter_monitor.py Library tweepy likely missing and/or too big and/or relying on missing library
import os
os.mkdir("lib")
os.chdir("lib")
f=open("colorsys.py","w")
f.write('''
def hsv_to_rgb(h, s, v):
if s == 0.0:
return v, v, v
i = int(h*6.0)
f = (h*6.0) - i
p = v*(1.0 - s)
q = v*(1.0 - s*f)
t = v*(1.0 - s*(1.0-f))
i = 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
''')
f.close()
f=open("blinkt.py","w")
f.write('''from apa102 import APA102
from machine import Pin
DAT = 13
CLK = 14
NUM_PIXELS = 8
BRIGHTNESS = 7
clock = Pin(CLK, Pin.OUT)
data = Pin(DAT, Pin.OUT)
pixels = APA102(clock, data, NUM_PIXELS)
def set_brightness(brightness):
for x in range(NUM_PIXELS):
pixels[x] = (pixels[x][0], pixels[x][1], pixels[x][2], int(31.0 * brightness) & 0b11111)
set_brightness(BRIGHTNESS)
def clear():
for x in range(NUM_PIXELS):
pixels[x] = (0, 0, 0, pixels[x][3])
def show():
pixels.write()
def set_all(r, g, b, brightness=None):
for x in range(NUM_PIXELS):
set_pixel(x, r, g, b, brightness)
def set_pixel(x, r, g, b, brightness=None):
if brightness is None:
brightness = pixels[x][3]
else:
brightness = int(31.0 * brightness) & 0b11111
pixels[x] = [int(r) & 0xff, int(g) & 0xff, int(b) & 0xff, brightness]
def set_clear_on_exit(value=True):
pass
''')
f.close()
f=open("random.py","w")
f.write('''
import os
def getrandbits(k):
numbytes = (k + 7) // 8
x = int.from_bytes(os.urandom(numbytes), 'big')
return x >> (numbytes * 8 - k)
def bit_length(n):
res = n
count = 1
while res>1:
res = res>>1
count = count+1
return count
def randbelow(n):
k = bit_length(n)
r = getrandbits(k)
while r >= n:
r = getrandbits(k)
return r
def randrange(start, stop=None, step=1):
istart = int(start)
if istart != start:
raise ValueError("non-integer arg 1 for randrange()")
if stop is None:
if istart > 0:
return randbelow(istart)
raise ValueError("empty range for randrange()")
# stop argument supplied.
istop = int(stop)
if istop != stop:
raise ValueError("non-integer stop for randrange()")
width = istop - istart
if step == 1 and width > 0:
return istart + randbelow(width)
if step == 1:
raise ValueError("empty range for randrange() (%d,%d, %d)" % (istart, istop, width))
# Non-unit step argument supplied.
istep = int(step)
if istep != step:
raise ValueError("non-integer step for randrange()")
if istep > 0:
n = (width + istep - 1) // istep
elif istep < 0:
n = (width + istep + 1) // istep
else:
raise ValueError("zero step for randrange()")
if n <= 0:
raise ValueError("empty range for randrange()")
return istart + istep*randbelow(n)
def randint(a, b):
return randrange(a, b+1)
''')
f.close()
f=open("my_gradient.py","w")
f.write('''
import colorsys
import math
import time
from blinkt import set_brightness, set_pixel, show
hue_range = 120
hue_start = 0
max_brightness = 0.2
def show_graph(v, r, g, b):
v *= 8
for x in range(8):
hue = ((hue_start + ((x/8.0) * hue_range)) % 360) / 360.0
r, g, b = [int(c * 255) for c in colorsys.hsv_to_rgb(hue,1.0,1.0)]
if v < 0:
brightness = 0
else:
brightness = min(v,1.0) * max_brightness
set_pixel(x, r, g, b, brightness)
v -= 1
show()
set_brightness(0.1)
try:
while True:
t = time.ticks_ms() / 500
v = (math.sin(t) + 1) / 2 # Get a value between 0 and 1
show_graph(v,255,0,255)
time.sleep(0.01)
except KeyboardInterrupt:
pass
''')
f.close()
f=open("graph.py","w")
f.write('''import math
import time
from blinkt import set_brightness, set_pixel, show
def show_graph(v, r, g, b):
v *= 8
for x in range(8):
if v < 0:
r, g, b = 0, 0, 0
else:
r, g, b = [int(min(v,1.0) * c) for c in [r,g,b]]
set_pixel(x, r, g, b)
v -= 1
show()
set_brightness(0.1)
try:
while True:
t = time.ticks_ms() / 1000
v = (math.sin(t) + 1) / 2 # Get a value between 0 and 1
show_graph(v,255,0,255)
time.sleep(0.01)
except KeyboardInterrupt:
pass
''')
f.close()
f=open("larson.py","w")
f.write('''import math
import time
from blinkt import set_pixel, show, set_brightness
reds = [0, 0, 0, 0, 0, 16, 64, 255, 64, 16, 0, 0, 0, 0, 0]
start_time = time.ticks_ms()
while True:
delta = (time.ticks_diff(start_time,time.ticks_ms())) / 1000 * 16
offset = int(abs((delta % 16) - 8))
for i in range(8):
set_pixel(i , reds[offset + i], 0, 0)
show()
time.sleep(0.1)
''')
f.close()
f=open("my_rainbow.py","w")
f.write('''
import time
from blinkt import set_brightness, set_pixel, show
import colorsys
def main():
spacing = 360.0 / 16.0
hue = 0
set_brightness(0.1)
while True:
hue = int(time.ticks_ms() / 10) % 360
for x in range(8):
offset = x * spacing
h = ((hue + offset) % 360) / 360.0
r, g, b = [int(c*255) for c in colorsys.hsv_to_rgb(h, 1.0, 1.0)]
set_pixel(x,r,g,b)
show()
time.sleep_ms(1)
''')
f.close()
f=open("solid_colours.py","w")
f.write('''import time
from blinkt import set_clear_on_exit, set_all, show
set_clear_on_exit()
step = 0
while True:
if step == 0:
set_all(128,0,0)
if step == 1:
set_all(0,128,0)
if step == 2:
set_all(0,0,128)
step+=1
step%=3
show()
time.sleep(0.5)
''')
f.close()
f=open("morse.py","w")
f.write('''import time
from blinkt import set_clear_on_exit, set_pixel, show
set_clear_on_exit()
def show_all(state):
for i in range(8):
val = state * 255
set_pixel(i, val, val, val)
show()
def dot():
show_all(1)
time.sleep(0.05)
show_all(0)
time.sleep(0.2)
def dash():
show_all(1)
time.sleep(0.2)
show_all(0)
time.sleep(0.2)
def space():
time.sleep(0.2)
#0 is a space, 1 is a dot and 2 is a dash
morse = '211101101211022101020120210212000'
while True:
for m in morse:
if m == '0':
space()
elif m == '1':
dot()
elif m == '2':
dash()
''')
f.close()
@dglaude
Copy link
Author

dglaude commented Oct 8, 2016

For Blinkt! usage, the new version of random.py is not required.
That new version only add an implementation of sample that I need for another project.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment