Fixed clock drift and added setup wizard
This commit is contained in:
parent
aaaa3417bb
commit
341326af20
17
boot.py
17
boot.py
@ -1,17 +0,0 @@
|
||||
|
||||
# Disable AP
|
||||
import network
|
||||
ap_if = network.WLAN(network.AP_IF)
|
||||
ap_if.active(False)
|
||||
print("Access point disabled")
|
||||
|
||||
# Connect to wireless network as client
|
||||
sta_if = network.WLAN(network.STA_IF)
|
||||
sta_if.active(True)
|
||||
sta_if.connect("Robootikaklubi", "u4HNj3sgYK")
|
||||
while not sta_if.isconnected():
|
||||
pass
|
||||
|
||||
# Clean up
|
||||
import gc
|
||||
gc.collect()
|
162
firmware/main.py
Normal file
162
firmware/main.py
Normal file
@ -0,0 +1,162 @@
|
||||
import gc
|
||||
import network
|
||||
import picoweb
|
||||
import json
|
||||
from time import sleep_ms
|
||||
from timezone import TIMEZONES
|
||||
|
||||
app = picoweb.WebApp(__name__)
|
||||
ap_if = network.WLAN(network.AP_IF)
|
||||
sta_if = network.WLAN(network.STA_IF)
|
||||
nets = sta_if.scan()
|
||||
sta_if.active(True)
|
||||
config = dict()
|
||||
try:
|
||||
with open("config.json") as fh:
|
||||
config = json.loads(fh.read())
|
||||
sta_if.connect(config.get("ssid"), config.get("password"))
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
print("Scanning for wireless networks...")
|
||||
|
||||
@app.route("/connect")
|
||||
def index(req, resp):
|
||||
if req.method == "POST":
|
||||
yield from req.read_form_data()
|
||||
else:
|
||||
req.parse_qs()
|
||||
yield from picoweb.start_response(resp)
|
||||
with open("config.json", "w") as fh:
|
||||
fh.write(json.dumps(req.form))
|
||||
yield from resp.awrite("Setting saved please power cycle device")
|
||||
|
||||
|
||||
@app.route("/")
|
||||
def index(req, resp):
|
||||
print("Serving index")
|
||||
yield from picoweb.start_response(resp)
|
||||
yield from resp.awrite("<html>")
|
||||
yield from resp.awrite("<head>")
|
||||
yield from resp.awrite("<meta name='viewport' content='width=device-width, initial-scale=1'>")
|
||||
yield from resp.awrite("</head>")
|
||||
yield from resp.awrite("<body>")
|
||||
yield from resp.awrite("<h>Welcome to NixiESP12 configuration wizard</h>")
|
||||
yield from resp.awrite("<p>Detected wireless networks:</p>")
|
||||
yield from resp.awrite("<form action='/connect' method='post'>")
|
||||
yield from resp.awrite("<p>Select wireless network:</p>")
|
||||
yield from resp.awrite("<select name='ssid'>")
|
||||
for ssid, _, _, snr, crypto, _ in nets:
|
||||
ssid = ssid.decode("utf-8")
|
||||
yield from resp.awrite("<option>" + ssid + "</option>")
|
||||
yield from resp.awrite("</select>")
|
||||
yield from resp.awrite("<p>Wireless password is applicable:</p>")
|
||||
yield from resp.awrite("<input type='password' name='password'/>")
|
||||
yield from resp.awrite("<p>Timezone:</p>")
|
||||
yield from resp.awrite("<select name='timezone'>")
|
||||
for index, (dst, offset, title) in enumerate(TIMEZONES):
|
||||
yield from resp.awrite("<option value='%d'>%s</option>" % (index, title))
|
||||
yield from resp.awrite("</select>")
|
||||
yield from resp.awrite("<p>NTP resynchronization interval:</p>")
|
||||
yield from resp.awrite("<select name='interval'>")
|
||||
for j in range(0, 73):
|
||||
yield from resp.awrite("<option value='%d'>%d hours</option>" % (j*3600, j))
|
||||
yield from resp.awrite("</select>")
|
||||
yield from resp.awrite("<p> </p>")
|
||||
yield from resp.awrite("<input type='submit'/>")
|
||||
yield from resp.awrite("</form>")
|
||||
yield from resp.awrite("</body>")
|
||||
yield from resp.awrite("<html>")
|
||||
|
||||
timed_out = True
|
||||
if config:
|
||||
print("Connecting to", config.get("ssid"))
|
||||
for j in range(0,30):
|
||||
if sta_if.isconnected():
|
||||
ap_if.active(False)
|
||||
timed_out = False
|
||||
break
|
||||
sleep_ms(200)
|
||||
|
||||
if timed_out:
|
||||
ap_if.active(True)
|
||||
print("Starting setup wizard on", ap_if.ifconfig())
|
||||
app.run()
|
||||
|
||||
|
||||
TIMEZONE = TIMEZONES[int(config.get("timezone", 30))][1]
|
||||
print("Using timezone", TIMEZONES[int(config.get("timezone", 30))])
|
||||
RESYNC = int(config.get("interval")) # Resync once in 8 hours
|
||||
print("NTP resynchronization interval", RESYNC, "seconds")
|
||||
DEBUG = False
|
||||
|
||||
print("Press Ctrl-C now abort main.py execution and to retain keyboard input")
|
||||
sleep_ms(2000)
|
||||
|
||||
import time
|
||||
import ntptime
|
||||
from machine import Pin, Timer
|
||||
|
||||
# Note that keyboard input is lost beyond this point!
|
||||
clock = Pin(3, mode=Pin.OUT)
|
||||
latch = Pin(0, mode=Pin.OUT)
|
||||
data = Pin(2, mode=Pin.OUT)
|
||||
blink = False
|
||||
lookup = 11, 9, 12, 8, 0, 4, 1, 3, 2, 10
|
||||
countdown = 0
|
||||
|
||||
def bitbang_bit(value):
|
||||
if value & 1:
|
||||
data.on()
|
||||
else:
|
||||
data.off()
|
||||
clock.on()
|
||||
clock.off()
|
||||
|
||||
def bitbang_digit(digit):
|
||||
bitbang_bit(blink)
|
||||
for i in range(0,4):
|
||||
bitbang_bit(lookup[digit] << i >> 3)
|
||||
bitbang_bit(blink)
|
||||
bitbang_bit(blink)
|
||||
bitbang_bit(blink)
|
||||
|
||||
def dump_time(hour, minute, second):
|
||||
if DEBUG:
|
||||
print("Time is %02d:%02d:%02d" % (hour, minute, second))
|
||||
bitbang_digit(hour // 10)
|
||||
bitbang_digit(hour % 10)
|
||||
bitbang_digit(minute // 10)
|
||||
bitbang_digit(minute % 10)
|
||||
bitbang_digit(second // 10)
|
||||
bitbang_digit(second % 10)
|
||||
|
||||
# RTC accuracy is still garbage, time.ticks_ms() which is bound to CPU ticks seems to be more accurate
|
||||
# https://forum.micropython.org/viewtopic.php?t=3251#p19092
|
||||
|
||||
while True:
|
||||
if countdown <= 0:
|
||||
try:
|
||||
ticks_then, time_then = time.ticks_ms(), ntptime.time()
|
||||
except OSError:
|
||||
print("Resync failed")
|
||||
else:
|
||||
countdown = RESYNC
|
||||
print("Resync done")
|
||||
else:
|
||||
year, month, day, hour, minute, second, _, _ = time.localtime(time_then + (time.ticks_ms() - ticks_then) // 1000)
|
||||
sleep_ms(500-(time.ticks_ms() - ticks_then) % 1000)
|
||||
blink = True
|
||||
dump_time((hour + TIMEZONE) % 24, minute, second)
|
||||
latch.on()
|
||||
latch.off()
|
||||
countdown -= 1
|
||||
|
||||
year, month, day, hour, minute, second, _, _ = time.localtime(time_then + (time.ticks_ms() - ticks_then) // 1000)
|
||||
sleep_ms(1001-(time.ticks_ms() - ticks_then) % 1000)
|
||||
blink = False
|
||||
dump_time((hour + TIMEZONE) % 24, minute, second)
|
||||
latch.on()
|
||||
latch.off()
|
||||
|
||||
main()
|
279
firmware/picoweb.py
Normal file
279
firmware/picoweb.py
Normal file
@ -0,0 +1,279 @@
|
||||
# Picoweb web pico-framework for MicroPython
|
||||
# Copyright (c) 2014-2018 Paul Sokolovsky
|
||||
# SPDX-License-Identifier: MIT
|
||||
import sys
|
||||
import gc
|
||||
import micropython
|
||||
import utime
|
||||
import uio
|
||||
import ure as re
|
||||
import uerrno
|
||||
import uasyncio as asyncio
|
||||
|
||||
def unquote_plus(s):
|
||||
# TODO: optimize
|
||||
s = s.replace("+", " ")
|
||||
arr = s.split("%")
|
||||
arr2 = [chr(int(x[:2], 16)) + x[2:] for x in arr[1:]]
|
||||
return arr[0] + "".join(arr2)
|
||||
|
||||
def parse_qs(s):
|
||||
res = {}
|
||||
if s:
|
||||
pairs = s.split("&")
|
||||
for p in pairs:
|
||||
vals = [unquote_plus(x) for x in p.split("=", 1)]
|
||||
if len(vals) == 1:
|
||||
vals.append(True)
|
||||
old = res.get(vals[0])
|
||||
if old is not None:
|
||||
if not isinstance(old, list):
|
||||
old = [old]
|
||||
res[vals[0]] = old
|
||||
old.append(vals[1])
|
||||
else:
|
||||
res[vals[0]] = vals[1]
|
||||
return res
|
||||
|
||||
def get_mime_type(fname):
|
||||
# Provide minimal detection of important file
|
||||
# types to keep browsers happy
|
||||
if fname.endswith(".html"):
|
||||
return "text/html"
|
||||
if fname.endswith(".css"):
|
||||
return "text/css"
|
||||
if fname.endswith(".png") or fname.endswith(".jpg"):
|
||||
return "image"
|
||||
return "text/plain"
|
||||
|
||||
def sendstream(writer, f):
|
||||
buf = bytearray(64)
|
||||
while True:
|
||||
l = f.readinto(buf)
|
||||
if not l:
|
||||
break
|
||||
yield from writer.awrite(buf, 0, l)
|
||||
|
||||
|
||||
def jsonify(writer, dict):
|
||||
import ujson
|
||||
yield from start_response(writer, "application/json")
|
||||
yield from writer.awrite(ujson.dumps(dict))
|
||||
|
||||
def start_response(writer, content_type="text/html", status="200", headers=None):
|
||||
yield from writer.awrite("HTTP/1.0 %s NA\r\n" % status)
|
||||
yield from writer.awrite("Content-Type: ")
|
||||
yield from writer.awrite(content_type)
|
||||
if not headers:
|
||||
yield from writer.awrite("\r\n\r\n")
|
||||
return
|
||||
yield from writer.awrite("\r\n")
|
||||
if isinstance(headers, bytes) or isinstance(headers, str):
|
||||
yield from writer.awrite(headers)
|
||||
else:
|
||||
for k, v in headers.items():
|
||||
yield from writer.awrite(k)
|
||||
yield from writer.awrite(": ")
|
||||
yield from writer.awrite(v)
|
||||
yield from writer.awrite("\r\n")
|
||||
yield from writer.awrite("\r\n")
|
||||
|
||||
def http_error(writer, status):
|
||||
yield from start_response(writer, status=status)
|
||||
yield from writer.awrite(status)
|
||||
|
||||
|
||||
class HTTPRequest:
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def read_form_data(self):
|
||||
size = int(self.headers[b"Content-Length"])
|
||||
data = yield from self.reader.read(size)
|
||||
form = parse_qs(data.decode())
|
||||
self.form = form
|
||||
|
||||
def parse_qs(self):
|
||||
form = parse_qs(self.qs)
|
||||
self.form = form
|
||||
|
||||
|
||||
class WebApp:
|
||||
|
||||
def __init__(self, pkg, routes=None):
|
||||
if routes:
|
||||
self.url_map = routes
|
||||
else:
|
||||
self.url_map = []
|
||||
if pkg and pkg != "__main__":
|
||||
self.pkg = pkg.split(".", 1)[0]
|
||||
else:
|
||||
self.pkg = None
|
||||
self.mounts = []
|
||||
self.inited = False
|
||||
# Instantiated lazily
|
||||
self.template_loader = None
|
||||
self.headers_mode = "parse"
|
||||
|
||||
def parse_headers(self, reader):
|
||||
headers = {}
|
||||
while True:
|
||||
l = yield from reader.readline()
|
||||
if l == b"\r\n":
|
||||
break
|
||||
k, v = l.split(b":", 1)
|
||||
headers[k] = v.strip()
|
||||
return headers
|
||||
|
||||
def _handle(self, reader, writer):
|
||||
close = True
|
||||
try:
|
||||
request_line = yield from reader.readline()
|
||||
if request_line == b"":
|
||||
yield from writer.aclose()
|
||||
return
|
||||
req = HTTPRequest()
|
||||
# TODO: bytes vs str
|
||||
request_line = request_line.decode()
|
||||
method, path, proto = request_line.split()
|
||||
path = path.split("?", 1)
|
||||
qs = ""
|
||||
if len(path) > 1:
|
||||
qs = path[1]
|
||||
path = path[0]
|
||||
|
||||
#print("================")
|
||||
#print(req, writer)
|
||||
#print(req, (method, path, qs, proto), req.headers)
|
||||
|
||||
# Find which mounted subapp (if any) should handle this request
|
||||
app = self
|
||||
while True:
|
||||
found = False
|
||||
for subapp in app.mounts:
|
||||
root = subapp.url
|
||||
#print(path, "vs", root)
|
||||
if path[:len(root)] == root:
|
||||
app = subapp
|
||||
found = True
|
||||
path = path[len(root):]
|
||||
if not path.startswith("/"):
|
||||
path = "/" + path
|
||||
break
|
||||
if not found:
|
||||
break
|
||||
|
||||
# We initialize apps on demand, when they really get requests
|
||||
if not app.inited:
|
||||
app.init()
|
||||
|
||||
# Find handler to serve this request in app's url_map
|
||||
found = False
|
||||
for e in app.url_map:
|
||||
pattern = e[0]
|
||||
handler = e[1]
|
||||
extra = {}
|
||||
if len(e) > 2:
|
||||
extra = e[2]
|
||||
|
||||
if path == pattern:
|
||||
found = True
|
||||
break
|
||||
elif not isinstance(pattern, str):
|
||||
# Anything which is non-string assumed to be a ducktype
|
||||
# pattern matcher, whose .match() method is called. (Note:
|
||||
# Django uses .search() instead, but .match() is more
|
||||
# efficient and we're not exactly compatible with Django
|
||||
# URL matching anyway.)
|
||||
m = pattern.match(path)
|
||||
if m:
|
||||
req.url_match = m
|
||||
found = True
|
||||
break
|
||||
|
||||
if not found:
|
||||
headers_mode = "skip"
|
||||
else:
|
||||
headers_mode = extra.get("headers", self.headers_mode)
|
||||
|
||||
if headers_mode == "skip":
|
||||
while True:
|
||||
l = yield from reader.readline()
|
||||
if l == b"\r\n":
|
||||
break
|
||||
elif headers_mode == "parse":
|
||||
req.headers = yield from self.parse_headers(reader)
|
||||
else:
|
||||
assert headers_mode == "leave"
|
||||
|
||||
if found:
|
||||
req.method = method
|
||||
req.path = path
|
||||
req.qs = qs
|
||||
req.reader = reader
|
||||
close = yield from handler(req, writer)
|
||||
else:
|
||||
yield from start_response(writer, status="404")
|
||||
yield from writer.awrite("404\r\n")
|
||||
#print(req, "After response write")
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
if close is not False:
|
||||
yield from writer.aclose()
|
||||
|
||||
def mount(self, url, app):
|
||||
"Mount a sub-app at the url of current app."
|
||||
# Inspired by Bottle. It might seem that dispatching to
|
||||
# subapps would rather be handled by normal routes, but
|
||||
# arguably, that's less efficient. Taking into account
|
||||
# that paradigmatically there's difference between handing
|
||||
# an action and delegating responisibilities to another
|
||||
# app, Bottle's way was followed.
|
||||
app.url = url
|
||||
self.mounts.append(app)
|
||||
|
||||
def route(self, url, **kwargs):
|
||||
def _route(f):
|
||||
self.url_map.append((url, f, kwargs))
|
||||
return f
|
||||
return _route
|
||||
|
||||
def add_url_rule(self, url, func, **kwargs):
|
||||
# Note: this method skips Flask's "endpoint" argument,
|
||||
# because it's alleged bloat.
|
||||
self.url_map.append((url, func, kwargs))
|
||||
|
||||
def _load_template(self, tmpl_name):
|
||||
if self.template_loader is None:
|
||||
import utemplate.source
|
||||
self.template_loader = utemplate.source.Loader(self.pkg, "templates")
|
||||
return self.template_loader.load(tmpl_name)
|
||||
|
||||
def render_template(self, writer, tmpl_name, args=()):
|
||||
tmpl = self._load_template(tmpl_name)
|
||||
for s in tmpl(*args):
|
||||
yield from writer.awrite(s)
|
||||
|
||||
def render_str(self, tmpl_name, args=()):
|
||||
#TODO: bloat
|
||||
tmpl = self._load_template(tmpl_name)
|
||||
return ''.join(tmpl(*args))
|
||||
|
||||
def init(self):
|
||||
"""Initialize a web application. This is for overriding by subclasses.
|
||||
This is good place to connect to/initialize a database, for example."""
|
||||
self.inited = True
|
||||
|
||||
def run(self, host="0.0.0.0", port=80, lazy_init=False):
|
||||
gc.collect()
|
||||
self.init()
|
||||
if not lazy_init:
|
||||
for app in self.mounts:
|
||||
app.init()
|
||||
loop = asyncio.get_event_loop()
|
||||
loop.create_task(asyncio.start_server(self._handle, host, port))
|
||||
loop.run_forever()
|
||||
loop.close()
|
||||
|
75
firmware/timezone.py
Normal file
75
firmware/timezone.py
Normal file
@ -0,0 +1,75 @@
|
||||
TIMEZONES = (
|
||||
(0, -12, "(GMT-12:00) International Date Line West"),
|
||||
(0, -11, "(GMT-11:00) Midway Island, Samoa"),
|
||||
(0, -10, "(GMT-10:00) Hawaii"),
|
||||
(1, -9, "(GMT-09:00) Alaska"),
|
||||
(1, -8, "(GMT-08:00) Pacific Time (US & Canada)"),
|
||||
(1, -8, "(GMT-08:00) Tijuana, Baja California"),
|
||||
(0, -7, "(GMT-07:00) Arizona"),
|
||||
(1, -7, "(GMT-07:00) Chihuahua, La Paz, Mazatlan"),
|
||||
(1, -7, "(GMT-07:00) Mountain Time (US & Canada)"),
|
||||
(0, -6, "(GMT-06:00) Central America"),
|
||||
(1, -6, "(GMT-06:00) Central Time (US & Canada)"),
|
||||
(1, -6, "(GMT-06:00) Guadalajara, Mexico City, Monterrey"),
|
||||
(0, -6, "(GMT-06:00) Saskatchewan"),
|
||||
(0, -5, "(GMT-05:00) Bogota, Lima, Quito, Rio Branco"),
|
||||
(1, -5, "(GMT-05:00) Eastern Time (US & Canada)"),
|
||||
(1, -5, "(GMT-05:00) Indiana (East)"),
|
||||
(1, -4, "(GMT-04:00) Atlantic Time (Canada)"),
|
||||
(0, -4, "(GMT-04:00) Caracas, La Paz"),
|
||||
(0, -4, "(GMT-04:00) Manaus"),
|
||||
(1, -4, "(GMT-04:00) Santiago"),
|
||||
(1, -3, "(GMT-03:00) Brasilia"),
|
||||
(0, -3, "(GMT-03:00) Buenos Aires, Georgetown"),
|
||||
(1, -3, "(GMT-03:00) Greenland"),
|
||||
(1, -3, "(GMT-03:00) Montevideo"),
|
||||
(1, -2, "(GMT-02:00) Mid-Atlantic"),
|
||||
(0, -1, "(GMT-01:00) Cape Verde Is."),
|
||||
(1, -1, "(GMT-01:00) Azores"),
|
||||
(0, 0, "(GMT+00:00) Casablanca, Monrovia, Reykjavik"),
|
||||
(1, 0, "(GMT+00:00) Greenwich Mean Time : Dublin, Edinburgh, Lisbon, London"),
|
||||
(1, 1, "(GMT+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna"),
|
||||
(1, 1, "(GMT+01:00) Belgrade, Bratislava, Budapest, Ljubljana, Prague"),
|
||||
(1, 1, "(GMT+01:00) Brussels, Copenhagen, Madrid, Paris"),
|
||||
(1, 1, "(GMT+01:00) Sarajevo, Skopje, Warsaw, Zagreb"),
|
||||
(1, 1, "(GMT+01:00) West Central Africa"),
|
||||
(1, 2, "(GMT+02:00) Amman"),
|
||||
(1, 2, "(GMT+02:00) Athens, Bucharest, Istanbul"),
|
||||
(1, 2, "(GMT+02:00) Beirut"),
|
||||
(1, 2, "(GMT+02:00) Cairo"),
|
||||
(0, 2, "(GMT+02:00) Harare, Pretoria"),
|
||||
(1, 2, "(GMT+02:00) Helsinki, Kyiv, Riga, Sofia, Tallinn, Vilnius"),
|
||||
(1, 2, "(GMT+02:00) Jerusalem"),
|
||||
(1, 2, "(GMT+02:00) Minsk"),
|
||||
(1, 2, "(GMT+02:00) Windhoek"),
|
||||
(0, 3, "(GMT+03:00) Kuwait, Riyadh, Baghdad"),
|
||||
(1, 3, "(GMT+03:00) Moscow, St. Petersburg, Volgograd"),
|
||||
(0, 3, "(GMT+03:00) Nairobi"),
|
||||
(0, 3, "(GMT+03:00) Tbilisi"),
|
||||
(0, 4, "(GMT+04:00) Abu Dhabi, Muscat"),
|
||||
(1, 4, "(GMT+04:00) Baku"),
|
||||
(1, 4, "(GMT+04:00) Yerevan"),
|
||||
(1, 5, "(GMT+05:00) Yekaterinburg"),
|
||||
(0, 5, "(GMT+05:00) Islamabad, Karachi, Tashkent"),
|
||||
(1, 6, "(GMT+06:00) Almaty, Novosibirsk"),
|
||||
(0, 6, "(GMT+06:00) Astana, Dhaka"),
|
||||
(0, 7, "(GMT+07:00) Bangkok, Hanoi, Jakarta"),
|
||||
(1, 7, "(GMT+07:00) Krasnoyarsk"),
|
||||
(0, 8, "(GMT+08:00) Beijing, Chongqing, Hong Kong, Urumqi"),
|
||||
(0, 8, "(GMT+08:00) Kuala Lumpur, Singapore"),
|
||||
(0, 8, "(GMT+08:00) Irkutsk, Ulaan Bataar"),
|
||||
(0, 8, "(GMT+08:00) Perth"),
|
||||
(0, 8, "(GMT+08:00) Taipei"),
|
||||
(0, 9, "(GMT+09:00) Osaka, Sapporo, Tokyo"),
|
||||
(0, 9, "(GMT+09:00) Seoul"),
|
||||
(1, 9, "(GMT+09:00) Yakutsk"),
|
||||
(0, 10, "(GMT+10:00) Brisbane"),
|
||||
(1, 10, "(GMT+10:00) Canberra, Melbourne, Sydney"),
|
||||
(1, 10, "(GMT+10:00) Hobart"),
|
||||
(0, 10, "(GMT+10:00) Guam, Port Moresby"),
|
||||
(1, 10, "(GMT+10:00) Vladivostok"),
|
||||
(1, 11, "(GMT+11:00) Magadan, Solomon Is., New Caledonia"),
|
||||
(1, 12, "(GMT+12:00) Auckland, Wellington"),
|
||||
(0, 12, "(GMT+12:00) Fiji, Kamchatka, Marshall Is."),
|
||||
(0, 13, "(GMT+13:00) Nuku'alofa")
|
||||
)
|
72
main.py
72
main.py
@ -1,72 +0,0 @@
|
||||
|
||||
import ntptime
|
||||
import esp
|
||||
from time import sleep, localtime
|
||||
from machine import Pin, Timer
|
||||
|
||||
TIMEZONE = 3
|
||||
RESYNC = 7200 # Resync once in two hours
|
||||
|
||||
print("Press Ctrl-C now abort main.py execution and to retain keyboard input")
|
||||
sleep(1)
|
||||
|
||||
# Configure powersave
|
||||
esp.sleep_type(esp.SLEEP_LIGHT)
|
||||
|
||||
# Note that keyboard input is lost beyond this point!
|
||||
clock = Pin(3, mode=Pin.OUT)
|
||||
latch = Pin(0, mode=Pin.OUT)
|
||||
data = Pin(2, mode=Pin.OUT)
|
||||
blink = 0
|
||||
lookup = 11, 9, 12, 8, 0, 4, 1, 3, 2, 10
|
||||
countdown = 0
|
||||
|
||||
def bitbang_bit(value):
|
||||
if value & 1:
|
||||
data.on()
|
||||
else:
|
||||
data.off()
|
||||
clock.on()
|
||||
clock.off()
|
||||
|
||||
def bitbang_digit(digit):
|
||||
bitbang_bit(blink)
|
||||
for i in range(0,4):
|
||||
bitbang_bit(lookup[digit] << i >> 3)
|
||||
bitbang_bit(blink)
|
||||
bitbang_bit(blink)
|
||||
bitbang_bit(blink)
|
||||
|
||||
timer = Timer(-1)
|
||||
|
||||
def schedule(delay=0):
|
||||
global countdown
|
||||
if countdown <= 0:
|
||||
ntptime.settime()
|
||||
countdown = RESYNC
|
||||
print("Resync")
|
||||
countdown -= 1
|
||||
if delay:
|
||||
timer.init(period=1000, mode=Timer.ONE_SHOT, callback=dump)
|
||||
else:
|
||||
dump()
|
||||
|
||||
def dump_time(hour, minute, second):
|
||||
print("Time is %02d:%02d:%02d" % (hour, minute, second))
|
||||
bitbang_digit(hour // 10)
|
||||
bitbang_digit(hour % 10)
|
||||
bitbang_digit(minute // 10)
|
||||
bitbang_digit(minute % 10)
|
||||
bitbang_digit(second // 10)
|
||||
bitbang_digit(second % 10)
|
||||
|
||||
def dump(t=None):
|
||||
global blink
|
||||
year, month, day, hour, minute, second, _, millis = localtime()
|
||||
dump_time((hour + TIMEZONE) % 24, minute, second)
|
||||
latch.on()
|
||||
latch.off()
|
||||
blink = 1-blink
|
||||
schedule(1000-millis)
|
||||
|
||||
schedule()
|
Loading…
Reference in New Issue
Block a user