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