You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

245 lines
5.9 KiB

# Copyright 2020 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import array
import random
import time
import asynced
import iptime
from neopixel_write import neopixel_write
import board
import machine
import neopixel
RANGES = {
'dude': 0,
'ok': 5,
'ten': 8,
'seven': 11,
'twelve': 16,
'even': 22,
'eleven': 22,
'eight': 28,
'four': 33,
'five': 37,
'two': 41,
'three': 44,
'six': 49,
'one': 52,
'past': 55,
'run': 60,
'nine': 62,
'to': 66,
'TEN': 69,
'HALF': 73,
'TWENTY': 77,
'TWENTYFIVE': 77,
'FIVE': 83,
'QUARTER': 90,
'A': 98,
'AM': 106,
'PM': 108,
'is': 102,
'it': 99,
'its': 99,
}
CIEL8 = (0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 5, 5, 6, 6, 7, 8, 8, 9,
10, 11, 11, 12, 13, 14, 15, 16, 17, 18, 19, 21, 22, 23, 25, 26, 28,
30, 31, 33, 35, 37, 39, 40, 42, 44, 46, 49, 51, 53, 55, 57, 60, 63,
66, 68, 71, 74, 77, 80, 83, 86, 90, 93, 96, 100, 103, 107, 111, 115,
118, 122, 127, 131, 136, 140, 144, 149, 153, 158, 163, 168, 173, 179,
184, 189, 195, 200, 206, 212, 218, 224, 230, 236, 242, 249, 255)
MINUTES = ('oclock', 'FIVE past', 'TEN past', 'QUARTER past', 'TWENTY past',
'TWENTYFIVE past', 'HALF past', 'TWENTYFIVE to', 'TWENTY to',
'QUARTER to', 'TEN to', 'FIVE to')
HOURS = ('twelve', 'one', 'two', 'three', 'four', 'five', 'six', 'seven',
'eight', 'nine', 'ten', 'eleven')
AMPM = ('AM', 'PM')
ITS = ('its', 'it is', 'its dude', 'it is dude', 'its ok', 'it is ok')
COLOUR = (200, 255, 200)
def split(secs: int) -> tuple:
secs = int(secs)
mins = (secs // 60) % 60
minute = mins // 5
hour = (secs // 3600) % 24
if mins >= 35:
hour += 1
ampm = (hour // 12) % len(AMPM)
hour %= len(HOURS)
return MINUTES[minute], HOURS[hour], AMPM[ampm]
def to_rgb(i, colour) -> tuple:
return ((i * colour[0]) >> 8, (i * colour[1]) >> 8, (i * colour[2]) >> 8)
class Frame:
Max = 255
Zeros = tuple([0] * (11 * 10))
def __init__(self, n: int = 11 * 10, pixels: list = None):
if pixels is None:
pixels = list(self.Zeros)
self.pixels = pixels
def set(self, idx: int) -> 'Frame':
self.pixels[idx] = self.Max
return self
def __add__(self, other):
return Frame(pixels=[
min(self.Max, x + y) for x, y in zip(self.pixels, other.pixels)
])
def muldiv(self, num: int, den: int):
return Frame(pixels=[x * num // den for x in self.pixels])
@micropython.native
def merge(f1: Frame, f2: Frame, num1: int, num2: int, den: int) -> Frame:
m = Frame.Max
p1 = f1.pixels
p2 = f2.pixels
o = array.array('H', Frame.Zeros)
for i in range(len(p1)):
k = (p1[i] * num1 + p2[i] * num2) // den
if k >= m:
k = m
o[i] = k
return Frame(pixels=o)
def render(words: list) -> Frame:
f = Frame()
for word in words:
for w in word.split():
idx = RANGES.get(w, None)
if idx is not None:
for i in range(len(w)):
f.set(idx + i)
else:
print('warning: %s is missing' % w)
return f
def prefix() -> tuple:
return (ITS[random.randint(0, len(ITS) - 1)], )
def show(f: Frame, n):
p = f.pixels
buf = bytearray(len(p) * 3)
r, g, b = COLOUR
i = 0
for c in p:
buf[i + 1] = (r * c) >> 8
buf[i + 0] = (g * c) >> 8
buf[i + 2] = (b * c) >> 8
i += 3
neopixel_write(n.pin, buf)
def scan() -> generator:
f = Frame()
while True:
for i in range(len(f.pixels)):
yield f.set(i)
f = f.muldiv(80, 100)
def intensity(num: int, den: int) -> int:
idx = num * len(CIEL8) // den
if idx >= len(CIEL8):
return 255
return CIEL8[idx]
def brightness(secs):
hour = int(secs) // 60 // 60
if hour >= 7 and hour < 22:
return 255
return 1200
def run() -> generator:
words = None
fade = 2000
secs = iptime.day_sec()
# Flash until time is synced.
sc = scan()
for s, meta in secs:
if s is not None and meta is not None:
break
yield next(sc)
src = Frame()
while True:
yield None
s, m2 = next(secs)
nxt = split(s)
if nxt == words:
yield from asynced.delay(2)
continue
dest = render(prefix() + nxt)
for at in asynced.wait(fade):
yield merge(src, dest, intensity(fade - at, fade),
intensity(at, fade), brightness(s))
words = nxt
src = dest
def blink() -> generator:
led = machine.Pin(16, machine.Pin.OUT)
while True:
led.off()
yield from asynced.delay(0.03)
led.on()
yield from asynced.delay(2.9)
def bench() -> generator:
yield None
i = 30
while True:
start = time.monotonic()
yield from range(i)
elapsed = time.monotonic() - start
print('%d in %.3f %.3f/1 %.1f FPS' %
(i, elapsed, elapsed / i, i / elapsed))
def main():
num = 11 * 10
n = neopixel.NeoPixel(board.GPIO2, num, brightness=1, auto_write=False)
routines = (run(), blink(), asynced.fps(30), bench())
while True:
for r in routines:
f = next(r)
if f is None:
continue
elif isinstance(f, Frame):
show(f, n)