Detected wireless networks:
") - yield from resp.awrite("") - yield from resp.awrite("") - yield from resp.awrite("") - -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 to abort main.py execution and 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 dst_offset(month, day, dow): - if month < 3 or month > 10: - return 0 - if month > 3 and month < 10: - return 1 - previous_sunday = day - dow - if month == 3: - return int(previous_sunday >= 25) - return int(previous_sunday < 25) - - -def dump_time(year, month, day, hour, minute, second, dow): - offset = dst_offset(month, day, dow) - if DEBUG: - print("Time is %02d:%02d:%02d, dst offset %d" % (hour, minute, second, offset)) - hour = (hour + TIMEZONE + offset) % 24 - 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 - - -# Boot up test sequence -for j in range(0, 10): - for i in range(0, 6): - bitbang_digit(j) - latch.on() - latch.off() - sleep_ms(500) - - -while True: - if countdown <= 0: - try: - ticks_then, time_then = time.ticks_ms(), ntptime.time() - except OSError: - sleep_ms(500) - print("Resync failed") - continue - else: - countdown = RESYNC - print("Resync done") - else: - year, month, day, hour, minute, second, dow, _ = time.localtime(time_then + (time.ticks_ms() - ticks_then) // 1000) - sleep_ms(500-(time.ticks_ms() - ticks_then) % 1000) - blink = True - dump_time(year, month, day, hour, minute, second, dow) - latch.on() - latch.off() - countdown -= 1 - - year, month, day, hour, minute, second, dow, _ = time.localtime(time_then + (time.ticks_ms() - ticks_then) // 1000) - sleep_ms(1001-(time.ticks_ms() - ticks_then) % 1000) - blink = False - dump_time(year, month, day, hour, minute, second, dow) - latch.on() - latch.off() - -main() diff --git a/firmware/picoweb.py b/firmware/picoweb.py deleted file mode 100644 index 76d1908..0000000 --- a/firmware/picoweb.py +++ /dev/null @@ -1,279 +0,0 @@ -# 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() - diff --git a/firmware/timezone.py b/firmware/timezone.py deleted file mode 100644 index d3a4c66..0000000 --- a/firmware/timezone.py +++ /dev/null @@ -1,75 +0,0 @@ -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") -)