Compare commits
3 commits
ffead18f6e
...
d537de05fc
Author | SHA1 | Date | |
---|---|---|---|
d537de05fc | |||
a8c6c04e11 | |||
e7d92b5013 |
6 changed files with 121 additions and 53 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1 +1,2 @@
|
||||||
*.stamp
|
*.stamp
|
||||||
|
secrets.py
|
||||||
|
|
|
@ -13,21 +13,23 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
import compat
|
||||||
|
|
||||||
|
|
||||||
def delay(secs: float) -> generator:
|
def delay(secs: float) -> generator:
|
||||||
end = time.monotonic() + secs
|
end = compat.monotonic() + secs
|
||||||
while time.monotonic() < end:
|
while compat.monotonic() < end:
|
||||||
yield None
|
yield None
|
||||||
|
|
||||||
|
|
||||||
def fps(fps: float) -> generator:
|
def fps(fps: float) -> generator:
|
||||||
yield None
|
yield None
|
||||||
dt = 1 / fps
|
dt = 1 / fps
|
||||||
until = time.monotonic() + dt
|
until = compat.monotonic() + dt
|
||||||
while True:
|
while True:
|
||||||
remain = until - time.monotonic()
|
remain = until - compat.monotonic()
|
||||||
if remain > 0:
|
if remain > 0:
|
||||||
time.sleep(remain)
|
compat.sleep(remain)
|
||||||
until += dt
|
until += dt
|
||||||
if remain < -dt:
|
if remain < -dt:
|
||||||
# Catch up a bit
|
# Catch up a bit
|
||||||
|
@ -36,9 +38,9 @@ def fps(fps: float) -> generator:
|
||||||
|
|
||||||
|
|
||||||
def wait(stop: int) -> generator:
|
def wait(stop: int) -> generator:
|
||||||
start = time.monotonic()
|
start = compat.monotonic()
|
||||||
while True:
|
while True:
|
||||||
elapsed = int((time.monotonic() - start) * 1000)
|
elapsed = int((compat.monotonic() - start) * 1000)
|
||||||
elapsed = min(stop, elapsed)
|
elapsed = min(stop, elapsed)
|
||||||
yield elapsed
|
yield elapsed
|
||||||
if elapsed >= stop:
|
if elapsed >= stop:
|
||||||
|
|
27
src/compat.py
Normal file
27
src/compat.py
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
# 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 random
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
|
def monotonic():
|
||||||
|
return time.ticks_ms() * 1e-3
|
||||||
|
|
||||||
|
|
||||||
|
def sleep(secs):
|
||||||
|
time.sleep_ms(int(secs * 1e3))
|
||||||
|
|
||||||
|
|
||||||
|
def randint(start, limit):
|
||||||
|
return start + random.getrandbits(30) % (limit - start + 1)
|
|
@ -12,29 +12,27 @@
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
import random
|
import random
|
||||||
import urllib.urequest
|
import time
|
||||||
|
|
||||||
|
import urequests
|
||||||
|
|
||||||
import asynced
|
import asynced
|
||||||
import ujson
|
import compat
|
||||||
import time
|
|
||||||
|
|
||||||
|
|
||||||
def _fetch():
|
def _fetch():
|
||||||
try:
|
try:
|
||||||
resp = urllib.urequest.urlopen('https://juju.nz/api/iptime/now')
|
return urequests.get("http://worldtimeapi.org/api/ip").json()
|
||||||
t = ujson.load(resp)
|
|
||||||
resp.close()
|
|
||||||
return t
|
|
||||||
except OSError as ex:
|
except OSError as ex:
|
||||||
print(ex)
|
print("OSError", ex)
|
||||||
return None
|
return None
|
||||||
except ValueError as ex:
|
except ValueError as ex:
|
||||||
print(ex)
|
print("ValueError", ex)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _jitter(mid: int) -> int:
|
def _jitter(mid: int) -> int:
|
||||||
return random.randint(mid, mid * 120 // 100)
|
return compat.randint(mid, mid * 120 // 100)
|
||||||
|
|
||||||
|
|
||||||
def _sync() -> generator:
|
def _sync() -> generator:
|
||||||
|
@ -45,7 +43,7 @@ def _sync() -> generator:
|
||||||
if got is not None:
|
if got is not None:
|
||||||
yield got
|
yield got
|
||||||
break
|
break
|
||||||
yield from asynced.delay(_jitter(15))
|
yield from asynced.delay(_jitter(5))
|
||||||
|
|
||||||
# Poll slowly until the connection drops.
|
# Poll slowly until the connection drops.
|
||||||
while True:
|
while True:
|
||||||
|
@ -56,6 +54,16 @@ def _sync() -> generator:
|
||||||
yield got
|
yield got
|
||||||
|
|
||||||
|
|
||||||
|
def _get_day_sec(resp):
|
||||||
|
parts = resp.get("datetime", "").split("T")
|
||||||
|
if len(parts) != 2:
|
||||||
|
return None
|
||||||
|
hms = parts[1].split("+")[0].split(":")
|
||||||
|
if len(hms) != 3:
|
||||||
|
return None
|
||||||
|
return float(hms[0]) * 3600 + float(hms[1]) * 60 + float(hms[2])
|
||||||
|
|
||||||
|
|
||||||
def day_sec() -> generator:
|
def day_sec() -> generator:
|
||||||
s = _sync()
|
s = _sync()
|
||||||
# Spin until the first result comes in.
|
# Spin until the first result comes in.
|
||||||
|
@ -63,21 +71,21 @@ def day_sec() -> generator:
|
||||||
if got is None:
|
if got is None:
|
||||||
yield None, None
|
yield None, None
|
||||||
continue
|
continue
|
||||||
local = time.monotonic()
|
local = compat.monotonic()
|
||||||
base = got.get('day_sec', None)
|
base = _get_day_sec(got)
|
||||||
if base is not None:
|
if base is not None:
|
||||||
break
|
break
|
||||||
good = got
|
good = got
|
||||||
|
|
||||||
for got in s:
|
for got in s:
|
||||||
now = base + time.monotonic() - local
|
now = base + compat.monotonic() - local
|
||||||
yield now % (60 * 60 * 24), good
|
yield now % (60 * 60 * 24), good
|
||||||
|
|
||||||
if got is not None:
|
if got is not None:
|
||||||
# Update the baseline.
|
# Update the baseline.
|
||||||
b2 = got.get('day_sec', None)
|
b2 = _get_day_sec(got)
|
||||||
if b2 is not None:
|
if b2 is not None:
|
||||||
local = time.monotonic()
|
local = compat.monotonic()
|
||||||
base = b2
|
base = b2
|
||||||
good = got
|
good = got
|
||||||
|
|
||||||
|
@ -85,8 +93,8 @@ def day_sec() -> generator:
|
||||||
def test():
|
def test():
|
||||||
for secs, meta in day_sec():
|
for secs, meta in day_sec():
|
||||||
print(secs)
|
print(secs)
|
||||||
time.sleep(0.3)
|
compat.sleep(0.3)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
test()
|
test()
|
||||||
|
|
14
src/main.py
14
src/main.py
|
@ -13,18 +13,24 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
import time
|
import time
|
||||||
|
|
||||||
import microcontroller
|
import machine
|
||||||
|
import network
|
||||||
|
|
||||||
|
import compat
|
||||||
import wordclock
|
import wordclock
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
time.sleep(1)
|
compat.sleep(1)
|
||||||
wordclock.main()
|
wordclock.main()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
try:
|
try:
|
||||||
main()
|
main()
|
||||||
|
except Exception as ex:
|
||||||
|
print(ex)
|
||||||
finally:
|
finally:
|
||||||
microcontroller.reset()
|
print("Resetting")
|
||||||
|
compat.sleep(10)
|
||||||
|
machine.reset()
|
||||||
|
|
|
@ -13,16 +13,18 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
import array
|
import array
|
||||||
import random
|
import random
|
||||||
|
import secrets
|
||||||
import time
|
import time
|
||||||
|
|
||||||
import asynced
|
|
||||||
import iptime
|
|
||||||
|
|
||||||
from neopixel_write import neopixel_write
|
|
||||||
import board
|
|
||||||
import machine
|
import machine
|
||||||
import neopixel
|
import neopixel
|
||||||
|
import network
|
||||||
|
|
||||||
|
import asynced
|
||||||
|
import compat
|
||||||
|
import iptime
|
||||||
|
|
||||||
|
# fmt: off
|
||||||
RANGES = {
|
RANGES = {
|
||||||
'dude': 0,
|
'dude': 0,
|
||||||
'ok': 5,
|
'ok': 5,
|
||||||
|
@ -75,6 +77,7 @@ AMPM = ('AM', 'PM')
|
||||||
ITS = ('its', 'it is', 'its dude', 'it is dude', 'its ok', 'it is ok')
|
ITS = ('its', 'it is', 'its dude', 'it is dude', 'its ok', 'it is ok')
|
||||||
|
|
||||||
COLOUR = (200, 255, 200)
|
COLOUR = (200, 255, 200)
|
||||||
|
# fmt: on
|
||||||
|
|
||||||
|
|
||||||
def split(secs: int) -> tuple:
|
def split(secs: int) -> tuple:
|
||||||
|
@ -102,14 +105,14 @@ class Frame:
|
||||||
pixels = list(self.Zeros)
|
pixels = list(self.Zeros)
|
||||||
self.pixels = pixels
|
self.pixels = pixels
|
||||||
|
|
||||||
def set(self, idx: int) -> 'Frame':
|
def set(self, idx: int) -> "Frame":
|
||||||
self.pixels[idx] = self.Max
|
self.pixels[idx] = self.Max
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def __add__(self, other):
|
def __add__(self, other):
|
||||||
return Frame(pixels=[
|
return Frame(
|
||||||
min(self.Max, x + y) for x, y in zip(self.pixels, other.pixels)
|
pixels=[min(self.Max, x + y) for x, y in zip(self.pixels, other.pixels)]
|
||||||
])
|
)
|
||||||
|
|
||||||
def muldiv(self, num: int, den: int):
|
def muldiv(self, num: int, den: int):
|
||||||
return Frame(pixels=[x * num // den for x in self.pixels])
|
return Frame(pixels=[x * num // den for x in self.pixels])
|
||||||
|
@ -120,7 +123,7 @@ def merge(f1: Frame, f2: Frame, num1: int, num2: int, den: int) -> Frame:
|
||||||
m = Frame.Max
|
m = Frame.Max
|
||||||
p1 = f1.pixels
|
p1 = f1.pixels
|
||||||
p2 = f2.pixels
|
p2 = f2.pixels
|
||||||
o = array.array('H', Frame.Zeros)
|
o = array.array("H", Frame.Zeros)
|
||||||
for i in range(len(p1)):
|
for i in range(len(p1)):
|
||||||
k = (p1[i] * num1 + p2[i] * num2) // den
|
k = (p1[i] * num1 + p2[i] * num2) // den
|
||||||
if k >= m:
|
if k >= m:
|
||||||
|
@ -138,12 +141,12 @@ def render(words: list) -> Frame:
|
||||||
for i in range(len(w)):
|
for i in range(len(w)):
|
||||||
f.set(idx + i)
|
f.set(idx + i)
|
||||||
else:
|
else:
|
||||||
print('warning: %s is missing' % w)
|
print("warning: %s is missing" % w)
|
||||||
return f
|
return f
|
||||||
|
|
||||||
|
|
||||||
def prefix() -> tuple:
|
def prefix() -> tuple:
|
||||||
return (ITS[random.randint(0, len(ITS) - 1)], )
|
return (ITS[compat.randint(0, len(ITS) - 1)],)
|
||||||
|
|
||||||
|
|
||||||
def show(f: Frame, n):
|
def show(f: Frame, n):
|
||||||
|
@ -153,11 +156,11 @@ def show(f: Frame, n):
|
||||||
i = 0
|
i = 0
|
||||||
|
|
||||||
for c in p:
|
for c in p:
|
||||||
buf[i + 1] = (r * c) >> 8
|
n.buf[i + 1] = (r * c) >> 8
|
||||||
buf[i + 0] = (g * c) >> 8
|
n.buf[i + 0] = (g * c) >> 8
|
||||||
buf[i + 2] = (b * c) >> 8
|
n.buf[i + 2] = (b * c) >> 8
|
||||||
i += 3
|
i += 3
|
||||||
neopixel_write(n.pin, buf)
|
n.write()
|
||||||
|
|
||||||
|
|
||||||
def scan() -> generator:
|
def scan() -> generator:
|
||||||
|
@ -205,8 +208,13 @@ def run() -> generator:
|
||||||
continue
|
continue
|
||||||
dest = render(prefix() + nxt)
|
dest = render(prefix() + nxt)
|
||||||
for at in asynced.wait(fade):
|
for at in asynced.wait(fade):
|
||||||
yield merge(src, dest, intensity(fade - at, fade),
|
yield merge(
|
||||||
intensity(at, fade), brightness(s))
|
src,
|
||||||
|
dest,
|
||||||
|
intensity(fade - at, fade),
|
||||||
|
intensity(at, fade),
|
||||||
|
brightness(s),
|
||||||
|
)
|
||||||
words = nxt
|
words = nxt
|
||||||
src = dest
|
src = dest
|
||||||
|
|
||||||
|
@ -224,17 +232,33 @@ def bench() -> generator:
|
||||||
yield None
|
yield None
|
||||||
i = 30
|
i = 30
|
||||||
while True:
|
while True:
|
||||||
start = time.monotonic()
|
start = compat.monotonic()
|
||||||
yield from range(i)
|
yield from range(i)
|
||||||
elapsed = time.monotonic() - start
|
elapsed = compat.monotonic() - start
|
||||||
print('%d in %.3f %.3f/1 %.1f FPS' %
|
if elapsed > 0:
|
||||||
(i, elapsed, elapsed / i, i / elapsed))
|
print("%d in %.3f %.3f/1 %.1f FPS" % (i, elapsed, elapsed / i, i / elapsed))
|
||||||
|
|
||||||
|
|
||||||
|
def connect(wlan) -> generator:
|
||||||
|
yield None
|
||||||
|
|
||||||
|
while True:
|
||||||
|
if not wlan.isconnected():
|
||||||
|
wlan.active(True)
|
||||||
|
wlan.connect(secrets.WLAN_ESSID, secrets.WLAN_PASSWORD)
|
||||||
|
else:
|
||||||
|
# Override the DNS server as MicroPython and dnsmasq seem
|
||||||
|
# to be incompatible.
|
||||||
|
cfg = list(wlan.ifconfig())
|
||||||
|
wlan.ifconfig(cfg[:3] + ["8.8.8.8"])
|
||||||
|
yield from asynced.delay(10)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
num = 11 * 10
|
num = 11 * 10
|
||||||
n = neopixel.NeoPixel(board.GPIO2, num, brightness=1, auto_write=False)
|
n = neopixel.NeoPixel(machine.Pin(2), num)
|
||||||
routines = (run(), blink(), asynced.fps(30), bench())
|
wlan = network.WLAN(network.STA_IF)
|
||||||
|
routines = (connect(wlan), run(), blink(), asynced.fps(30), bench())
|
||||||
while True:
|
while True:
|
||||||
for r in routines:
|
for r in routines:
|
||||||
f = next(r)
|
f = next(r)
|
||||||
|
|
Loading…
Reference in a new issue