=> 9d032a29d26229748d616774ddf8050bc37e72c2
[1mdiff --git a/.gitignore b/.gitignore[m [1mnew file mode 100644[m [1mindex 0000000..084ac1e[m [1m--- /dev/null[m [1m+++ b/.gitignore[m [36m@@ -0,0 +1,2 @@[m [32m+[m[32m.vscode/[m [32m+[m[32m*.swp[m [1mdiff --git a/LICENSE b/LICENSE[m [1mnew file mode 100644[m [1mindex 0000000..c939536[m [1m--- /dev/null[m [1m+++ b/LICENSE[m [36m@@ -0,0 +1,21 @@[m [32m+[m[32mMIT License[m [32m+[m [32m+[m[32mCopyright (c) 2023 Mike Cifelli[m [32m+[m [32m+[m[32mPermission is hereby granted, free of charge, to any person obtaining a copy[m [32m+[m[32mof this software and associated documentation files (the "Software"), to deal[m [32m+[m[32min the Software without restriction, including without limitation the rights[m [32m+[m[32mto use, copy, modify, merge, publish, distribute, sublicense, and/or sell[m [32m+[m[32mcopies of the Software, and to permit persons to whom the Software is[m [32m+[m[32mfurnished to do so, subject to the following conditions:[m [32m+[m [32m+[m[32mThe above copyright notice and this permission notice shall be included in all[m [32m+[m[32mcopies or substantial portions of the Software.[m [32m+[m [32m+[m[32mTHE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR[m [32m+[m[32mIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,[m [32m+[m[32mFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE[m [32m+[m[32mAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER[m [32m+[m[32mLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,[m [32m+[m[32mOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE[m [32m+[m[32mSOFTWARE.[m [1mdiff --git a/README.md b/README.md[m [1mnew file mode 100644[m [1mindex 0000000..68be770[m [1m--- /dev/null[m [1m+++ b/README.md[m [36m@@ -0,0 +1,3 @@[m [32m+[m[32m# aquifer[m [32m+[m [32m+[m[32mA Raspberry Pi Pico W project for marshalling a car into a garage.[m [1mdiff --git a/install-on-device-fs b/install-on-device-fs[m [1mnew file mode 100755[m [1mindex 0000000..387f748[m [1m--- /dev/null[m [1m+++ b/install-on-device-fs[m [36m@@ -0,0 +1,54 @@[m [32m+[m[32m#!/usr/bin/env bash[m [32m+[m [32m+[m[32mDEVICES=$(mpremote connect list | grep MicroPython | cut -d " " -f 1)[m [32m+[m [32m+[m[32mif [ -z $DEVICES ] ; then[m [32m+[m[32m echo "No MicroPython devices found in FS mode"[m [32m+[m[32m exit 1[m [32m+[m[32mfi[m [32m+[m [32m+[m[32mDEVICE=${DEVICES[0]}[m [32m+[m [32m+[m[32mecho "Copying firmware files to ${DEVICE}"[m [32m+[m [32m+[m[32mfunction create_directory {[m [32m+[m[32m echo -n "> creating directory $1"[m [32m+[m [32m+[m[32m RESULT=$(mpremote connect ${DEVICE} mkdir $1)[m [32m+[m[32m ERROR=$?[m [32m+[m [32m+[m [32m+[m[32m if [ $ERROR -eq 0 ] ; then[m [32m+[m[32m echo " .. done!"[m [32m+[m[32m else[m [32m+[m[32m if [[ "$RESULT" == *"EEXIST"* ]] ; then[m [32m+[m[32m echo " .. already exists, skipping."[m [32m+[m[32m else[m [32m+[m[32m echo " .. failed!"[m [32m+[m[32m echo "! it looks like this device is already in use - is Thonny running?"[m [32m+[m[32m exit 1[m [32m+[m[32m fi[m [32m+[m[32m fi[m [32m+[m[32m}[m [32m+[m [32m+[m[32mfunction copy {[m [32m+[m[32m for file in $1[m [32m+[m[32m do[m [32m+[m[32m echo -n "> copying file $file"[m [32m+[m[32m mpremote connect ${DEVICE} cp $file $2 > /dev/null[m [32m+[m[32m if [ $? -eq 0 ] ; then[m [32m+[m[32m echo " .. done!"[m [32m+[m[32m else[m [32m+[m[32m echo " .. failed!"[m [32m+[m[32m fi[m [32m+[m[32m done[m [32m+[m[32m}[m [32m+[m [32m+[m [32m+[m[32mcreate_directory net[m [32m+[m[32mcreate_directory sensors[m [32m+[m[32mcreate_directory www[m [32m+[m [32m+[m[32mcopy "main.py" :[m [32m+[m[32mcopy "net/*.py" :net/[m [32m+[m[32mcopy "www/*" :www/[m [1mdiff --git a/main.py b/main.py[m [1mnew file mode 100644[m [1mindex 0000000..f5811e5[m [1m--- /dev/null[m [1m+++ b/main.py[m [36m@@ -0,0 +1,139 @@[m [32m+[m[32mimport time[m [32m+[m [32m+[m[32mfrom machine import Pin[m [32m+[m[32mfrom machine import ADC[m [32m+[m[32mfrom net import logging[m [32m+[m[32mfrom net import ntp[m [32m+[m[32mfrom net import Server[m [32m+[m[32mfrom net import templates[m [32m+[m[32mfrom net import util[m [32m+[m[32mfrom net.config import config[m [32m+[m [32m+[m [32m+[m[32mclass Marshaller(Server):[m [32m+[m [32m+[m[32m def __init__(self):[m [32m+[m[32m self.switch = Pin(8, Pin.IN, Pin.PULL_UP)[m [32m+[m[32m self.red_led = Pin(11, Pin.OUT)[m [32m+[m[32m self.blue_led = Pin(13, Pin.OUT)[m [32m+[m[32m self.green_led = Pin(20, Pin.OUT)[m [32m+[m[32m self.yellow_led = Pin(18, Pin.OUT)[m [32m+[m[32m self.v = Pin(27, Pin.OUT)[m [32m+[m[32m self.a = ADC(2)[m [32m+[m[32m self.last_value = -1[m [32m+[m [32m+[m[32m self.v.on()[m [32m+[m [32m+[m[32m super().__init__()[m [32m+[m [32m+[m[32m self.last_http_activation_ticks = time.ticks_ms()[m [32m+[m[32m self.http_activation_interval_in_seconds = 2 * 60[m [32m+[m[32m self.is_http_activation = False[m [32m+[m[32m self.ntp_interval_in_seconds = 3 * 60 * 60[m [32m+[m[32m self.ntp_ticks = time.ticks_ms()[m [32m+[m[32m self.distance_interval_in_milliseconds = 50[m [32m+[m[32m self.distance_ticks = time.ticks_ms()[m [32m+[m[32m self.isWaterPresent = False[m [32m+[m [32m+[m[32m ntp.sync()[m [32m+[m [32m+[m[32m def work(self):[m [32m+[m[32m ticks = time.ticks_ms()[m [32m+[m [32m+[m[32m if self.is_scanning():[m [32m+[m[32m self.v.on()[m [32m+[m[32m if util.millisecondsElapsed(ticks, self.distance_ticks) > self.distance_interval_in_milliseconds:[m [32m+[m[32m self.distance_ticks = ticks[m [32m+[m[32m self.show_color()[m [32m+[m [32m+[m[32m if self.is_http_activation and util.secondsElapsed(ticks, self.last_http_activation_ticks) > self.http_activation_interval_in_seconds:[m [32m+[m[32m self.is_http_activation = False[m [32m+[m[32m else:[m [32m+[m[32m self.v.off()[m [32m+[m[32m self.leds_off()[m [32m+[m[32m super().work()[m [32m+[m [32m+[m[32m if util.secondsElapsed(ticks, self.ntp_ticks) > self.ntp_interval_in_seconds:[m [32m+[m[32m self.ntp_ticks = ticks[m [32m+[m[32m ntp.sync()[m [32m+[m [32m+[m[32m def is_scanning(self):[m [32m+[m[32m return self.switch.value() == 0 or self.is_http_activation[m [32m+[m [32m+[m[32m def get_buffered_distance_in_inches(self):[m [32m+[m[32m distance = self.get_distance_in_inches()[m [32m+[m [32m+[m[32m if abs(distance - self.last_value) > 0.25:[m [32m+[m[32m self.last_value = distance[m [32m+[m[32m return distance[m [32m+[m [32m+[m[32m return self.last_value[m [32m+[m [32m+[m[32m # TODO - don't convert distances to inches, convert thresholds in the opposite direction[m [32m+[m[32m def get_distance_in_inches(self):[m [32m+[m[32m return self.a.read_u16() / 65535 * 1024 * 5 * 0.03937008[m [32m+[m [32m+[m[32m def handlePath(self, path):[m [32m+[m[32m if (path == 'on'):[m [32m+[m[32m return self.http_activation()[m [32m+[m [32m+[m[32m return self.getPathData(path)[m [32m+[m [32m+[m[32m def getPathData(self, path):[m [32m+[m[32m if path.endswith('.txt') or path.endswith('.ico'):[m [32m+[m[32m with open(f'www/{path}', 'rb') as f:[m [32m+[m[32m return f.read()[m [32m+[m [32m+[m[32m return templates.render([m [32m+[m[32m f'www/{path or self.default_path}',[m [32m+[m[32m hostname=config['hostname'],[m [32m+[m[32m datetime=util.datetime(),[m [32m+[m[32m is_active=self.is_scanning()[m[41m [m [32m+[m[32m )[m [32m+[m [32m+[m[32m def http_activation(self):[m [32m+[m[32m self.is_http_activation = True[m [32m+[m[32m self.last_http_activation_ticks = time.ticks_ms()[m [32m+[m [32m+[m[32m # todo - no content status[m [32m+[m[32m return ""[m [32m+[m [32m+[m[32m def show_color(self):[m [32m+[m[32m distance_in_inches = self.get_buffered_distance_in_inches()[m [32m+[m [32m+[m[32m if distance_in_inches < 15:[m [32m+[m[32m self.leds_off()[m [32m+[m[32m self.red_led.on()[m [32m+[m[32m elif distance_in_inches < 20:[m [32m+[m[32m self.leds_off()[m [32m+[m[32m self.green_led.on()[m [32m+[m[32m self.yellow_led.on()[m [32m+[m[32m elif distance_in_inches < 25:[m [32m+[m[32m self.leds_off()[m [32m+[m[32m self.green_led.on()[m [32m+[m[32m elif distance_in_inches < 40:[m [32m+[m[32m self.leds_off()[m [32m+[m[32m self.yellow_led.on()[m [32m+[m[32m else:[m [32m+[m[32m self.leds_off()[m [32m+[m[32m self.blue_led.on()[m [32m+[m [32m+[m[32m def leds_off(self):[m [32m+[m[32m self.red_led.off()[m [32m+[m[32m self.blue_led.off()[m [32m+[m[32m self.green_led.off()[m [32m+[m[32m self.yellow_led.off()[m [32m+[m [32m+[m[32m def cleanup(self):[m [32m+[m[32m self.v.off()[m [32m+[m[32m self.leds_off()[m [32m+[m[32m super().cleanup()[m [32m+[m [32m+[m [32m+[m[32mdef main():[m [32m+[m[32m logging.log_file = 'www/log.txt'[m [32m+[m[32m Marshaller().run()[m [32m+[m [32m+[m [32m+[m[32mif __name__ == '__main__':[m [32m+[m[32m main()[m [1mdiff --git a/net/__init__.py b/net/__init__.py[m [1mnew file mode 100644[m [1mindex 0000000..b7f2cf5[m [1m--- /dev/null[m [1m+++ b/net/__init__.py[m [36m@@ -0,0 +1 @@[m [32m+[m[32mfrom .server import Server[m [1mdiff --git a/net/config.sample.py b/net/config.sample.py[m [1mnew file mode 100644[m [1mindex 0000000..e3a09e2[m [1m--- /dev/null[m [1m+++ b/net/config.sample.py[m [36m@@ -0,0 +1,8 @@[m [32m+[m[32mconfig = {[m [32m+[m[32m 'hostname': '',[m [32m+[m[32m}[m [32m+[m [32m+[m[32msecrets = {[m [32m+[m[32m 'ssid': 'ssid',[m [32m+[m[32m 'password': 'password'[m [32m+[m[32m}[m [1mdiff --git a/net/http.py b/net/http.py[m [1mnew file mode 100644[m [1mindex 0000000..15edf03[m [1m--- /dev/null[m [1m+++ b/net/http.py[m [36m@@ -0,0 +1,6 @@[m [32m+[m[32mokResponse = 'HTTP/1.1 200 OK\r\ncontent-type: text/html\r\n\r\n'.encode('ascii')[m [32m+[m[32mokTextResponse = 'HTTP/1.1 200 OK\r\ncontent-type: text/plain\r\n\r\n'.encode('ascii')[m [32m+[m[32mokJsonResponse = 'HTTP/1.1 200 OK\r\ncontent-type: application/json\r\n\r\n'.encode('ascii')[m [32m+[m[32mokIconResponse = 'HTTP/1.1 200 OK\r\ncontent-type: image/x-icon\r\n\r\n'.encode('ascii')[m [32m+[m[32mnotFoundResponse = 'HTTP/1.1 404 Not Found\r\n\r\n'.encode('ascii')[m [32m+[m[32mserverErrorResponse = 'HTTP/1.1 500 Internal Server Error\r\n\r\n'.encode('ascii')[m [1mdiff --git a/net/logging.py b/net/logging.py[m [1mnew file mode 100644[m [1mindex 0000000..675683d[m [1m--- /dev/null[m [1m+++ b/net/logging.py[m [36m@@ -0,0 +1,127 @@[m [32m+[m[32mimport machine[m [32m+[m[32mimport os[m [32m+[m[32mimport gc[m [32m+[m [32m+[m[32mfrom . import util[m [32m+[m [32m+[m[32mlog_file = 'log.txt'[m [32m+[m [32m+[m[32mLOG_INFO = 0b00001[m [32m+[m[32mLOG_WARNING = 0b00010[m [32m+[m[32mLOG_ERROR = 0b00100[m [32m+[m[32mLOG_DEBUG = 0b01000[m [32m+[m[32mLOG_EXCEPTION = 0b10000[m [32m+[m[32mLOG_ALL = LOG_INFO | LOG_WARNING | LOG_ERROR | LOG_DEBUG | LOG_EXCEPTION[m [32m+[m [32m+[m[32m_logging_types = LOG_ALL[m [32m+[m [32m+[m[32m# the log file will be truncated if it exceeds _log_truncate_at bytes in[m [32m+[m[32m# size. the defaults values are designed to limit the log to at most[m [32m+[m[32m# three blocks on the Pico[m [32m+[m[32m_log_truncate_at = 11 * 1024[m [32m+[m[32m_log_truncate_to = 8 * 1024[m [32m+[m [32m+[m [32m+[m[32mdef file_size(file):[m [32m+[m[32m try:[m [32m+[m[32m return os.stat(file)[6][m [32m+[m[32m except OSError:[m [32m+[m[32m return None[m [32m+[m [32m+[m [32m+[m[32mdef set_truncate_thresholds(truncate_at, truncate_to):[m [32m+[m[32m global _log_truncate_at[m [32m+[m[32m global _log_truncate_to[m [32m+[m[32m _log_truncate_at = truncate_at[m [32m+[m[32m _log_truncate_to = truncate_to[m [32m+[m [32m+[m [32m+[m[32mdef enable_logging_types(types):[m [32m+[m[32m global _logging_types[m [32m+[m[32m _logging_types = _logging_types | types[m [32m+[m [32m+[m [32m+[m[32mdef disable_logging_types(types):[m [32m+[m[32m global _logging_types[m [32m+[m[32m _logging_types = _logging_types & ~types[m [32m+[m [32m+[m [32m+[m[32m# truncates the log file down to a target size while maintaining[m [32m+[m[32m# clean line breaks[m [32m+[m[32mdef truncate(file, target_size):[m [32m+[m[32m # get the current size of the log file[m [32m+[m[32m size = file_size(file)[m [32m+[m [32m+[m[32m # calculate how many bytes we're aiming to discard[m [32m+[m[32m discard = size - target_size[m [32m+[m[32m if discard <= 0:[m [32m+[m[32m return[m [32m+[m [32m+[m[32m with open(file, 'rb') as infile:[m [32m+[m[32m with open(file + '.tmp', 'wb') as outfile:[m [32m+[m[32m # skip a bunch of the input file until we've discarded[m [32m+[m[32m # at least enough[m [32m+[m[32m while discard > 0:[m [32m+[m[32m chunk = infile.read(1024)[m [32m+[m[32m discard -= len(chunk)[m [32m+[m [32m+[m[32m # try to find a line break nearby to split first chunk on[m [32m+[m[32m break_position = max([m [32m+[m[32m chunk.find(b'\n', -discard), # search forward[m [32m+[m[32m chunk.rfind(b'\n', 0, -discard) # search backwards[m [32m+[m[32m )[m [32m+[m[32m if break_position != -1: # if we found a line break..[m [32m+[m[32m outfile.write(chunk[break_position + 1:])[m [32m+[m [32m+[m[32m # now copy the rest of the file[m [32m+[m[32m while True:[m [32m+[m[32m chunk = infile.read(1024)[m [32m+[m[32m if not chunk:[m [32m+[m[32m break[m [32m+[m[32m outfile.write(chunk)[m [32m+[m [32m+[m[32m # delete the old file and replace with the new[m [32m+[m[32m os.remove(file)[m [32m+[m[32m os.rename(file + '.tmp', file)[m [32m+[m [32m+[m [32m+[m[32mdef log(level, text):[m [32m+[m[32m log_entry = '{0} [{1:8} /{2:>4}kB] {3}'.format([m [32m+[m[32m util.datetime(),[m [32m+[m[32m level,[m [32m+[m[32m round(gc.mem_free() / 1024),[m [32m+[m[32m text[m [32m+[m[32m )[m [32m+[m [32m+[m[32m print(log_entry)[m [32m+[m [32m+[m[32m with open(log_file, 'a') as logfile:[m [32m+[m[32m logfile.write(log_entry + '\n')[m [32m+[m [32m+[m[32m if _log_truncate_at and file_size(log_file) > _log_truncate_at:[m [32m+[m[32m truncate(log_file, _log_truncate_to)[m [32m+[m [32m+[m [32m+[m[32mdef info(*items):[m [32m+[m[32m if _logging_types & LOG_INFO:[m [32m+[m[32m log('info', ' '.join(map(str, items)))[m [32m+[m [32m+[m [32m+[m[32mdef warn(*items):[m [32m+[m[32m if _logging_types & LOG_WARNING:[m [32m+[m[32m log('warning', ' '.join(map(str, items)))[m [32m+[m [32m+[m [32m+[m[32mdef error(*items):[m [32m+[m[32m if _logging_types & LOG_ERROR:[m [32m+[m[32m log('error', ' '.join(map(str, items)))[m [32m+[m [32m+[m [32m+[m[32mdef debug(*items):[m [32m+[m[32m if _logging_types & LOG_DEBUG:[m [32m+[m[32m log('debug', ' '.join(map(str, items)))[m [32m+[m [32m+[m [32m+[m[32mdef exception(*items):[m [32m+[m[32m if _logging_types & LOG_EXCEPTION:[m [32m+[m[32m log('exception', ' '.join(map(str, items)))[m [1mdiff --git a/net/ntp.py b/net/ntp.py[m [1mnew file mode 100644[m [1mindex 0000000..c154309[m [1m--- /dev/null[m [1m+++ b/net/ntp.py[m [36m@@ -0,0 +1,41 @@[m [32m+[m[32mimport machine[m [32m+[m[32mimport struct[m [32m+[m[32mimport time[m [32m+[m[32mimport usocket[m [32m+[m [32m+[m[32mfrom . import logging[m [32m+[m[32mfrom . import util[m [32m+[m [32m+[m [32m+[m[32mdef sync():[m [32m+[m[32m if fetch(timeout=3):[m [32m+[m[32m logging.info(f'time updated to {util.datetime()}')[m [32m+[m[32m else:[m [32m+[m[32m logging.error(f'failed to update time')[m [32m+[m [32m+[m [32m+[m[32mdef fetch(synch_with_rtc=True, timeout=10):[m [32m+[m[32m ntp_host = 'time.cifelli.xyz'[m [32m+[m [32m+[m[32m timestamp = None[m [32m+[m[32m try:[m [32m+[m[32m query = bytearray(48)[m [32m+[m[32m query[0] = 0x1b[m [32m+[m[32m address = usocket.getaddrinfo(ntp_host, 123)[0][-1][m [32m+[m[32m socket = usocket.socket(usocket.AF_INET, usocket.SOCK_DGRAM)[m [32m+[m[32m socket.settimeout(timeout)[m [32m+[m[32m socket.sendto(query, address)[m [32m+[m[32m data = socket.recv(48)[m [32m+[m[32m socket.close()[m [32m+[m[32m local_epoch = 2208988800[m [32m+[m[32m timestamp = struct.unpack("!I", data[40:44])[0] - local_epoch[m [32m+[m[32m timestamp = time.gmtime(timestamp)[m [32m+[m[32m except Exception as e:[m [32m+[m[32m return None[m [32m+[m [32m+[m[32m if synch_with_rtc:[m [32m+[m[32m machine.RTC().datetime(([m [32m+[m[32m timestamp[0], timestamp[1], timestamp[2], timestamp[6],[m [32m+[m[32m timestamp[3], timestamp[4], timestamp[5], 0))[m [32m+[m [32m+[m[32m return timestamp[m [1mdiff --git a/net/server.py b/net/server.py[m [1mnew file mode 100644[m [1mindex 0000000..acdca36[m [1m--- /dev/null[m [1m+++ b/net/server.py[m [36m@@ -0,0 +1,99 @@[m [32m+[m[32mimport io[m [32m+[m[32mimport select[m [32m+[m[32mimport socket[m [32m+[m[32mimport sys[m [32m+[m [32m+[m[32mfrom . import logging[m [32m+[m[32mfrom . import wifi[m [32m+[m[32mfrom . import http[m [32m+[m [32m+[m [32m+[m[32mclass Server:[m [32m+[m [32m+[m[32m def __init__(self):[m [32m+[m[32m self.wlan = wifi.connect()[m [32m+[m[32m self.socket = socket.socket()[m [32m+[m[32m self.poller = select.poll()[m [32m+[m[32m self.default_path = 'index.html'[m [32m+[m [32m+[m[32m def cleanup(self):[m [32m+[m[32m self.socket.close()[m [32m+[m[32m self.wlan.disconnect()[m [32m+[m [32m+[m[32m def run(self):[m [32m+[m[32m try:[m [32m+[m[32m addr = self.listen()[m [32m+[m[32m self.poller.register(self.socket, select.POLLIN)[m [32m+[m [32m+[m[32m logging.info(f'listening on {addr}')[m [32m+[m [32m+[m[32m while True:[m [32m+[m[32m try:[m [32m+[m[32m self.serve()[m [32m+[m[32m self.work()[m [32m+[m[32m except Exception as e:[m [32m+[m[32m self.logException(e)[m [32m+[m[32m finally:[m [32m+[m[32m self.cleanup()[m [32m+[m [32m+[m[32m def logException(self, e):[m [32m+[m[32m buf = io.StringIO()[m [32m+[m[32m sys.print_exception(e, buf)[m [32m+[m[32m logging.debug(f'exception:', buf.getvalue())[m [32m+[m [32m+[m[32m def listen(self):[m [32m+[m[32m addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1][m [32m+[m[32m self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)[m [32m+[m[32m self.socket.bind(addr)[m [32m+[m[32m self.socket.listen(1)[m [32m+[m [32m+[m[32m return addr[m [32m+[m [32m+[m[32m def serve(self):[m [32m+[m[32m evts = self.poller.poll(500)[m [32m+[m [32m+[m[32m for sock, _evt in evts:[m [32m+[m[32m try:[m [32m+[m[32m conn, addr = sock.accept()[m [32m+[m[32m logging.info(f'client connected from {addr}')[m [32m+[m[32m request = conn.recv(1024).decode('utf-8').strip()[m [32m+[m [32m+[m[32m self.handleRequest(conn, request)[m [32m+[m[32m except:[m [32m+[m[32m conn.write(http.serverErrorResponse)[m [32m+[m[32m raise[m [32m+[m[32m finally:[m [32m+[m[32m conn.close()[m [32m+[m [32m+[m[32m def handleRequest(self, conn, request):[m [32m+[m[32m [method, path, _protocol] = request.partition('\n')[0].split()[m [32m+[m [32m+[m[32m logging.info(f'{method} {path}')[m [32m+[m [32m+[m[32m try:[m [32m+[m[32m if method == 'GET':[m [32m+[m[32m response = self.handlePath(path.strip('/'))[m [32m+[m [32m+[m[32m conn.write(self.getPathContentType(path))[m [32m+[m[32m conn.write(response)[m [32m+[m[32m else:[m [32m+[m[32m conn.write(http.notFoundResponse)[m [32m+[m[32m except OSError:[m [32m+[m[32m conn.write(http.notFoundResponse)[m [32m+[m [32m+[m[32m def getPathContentType(self, path):[m [32m+[m[32m if path.endswith('.txt'):[m [32m+[m[32m return http.okTextResponse[m [32m+[m[32m elif path.endswith('.json'):[m [32m+[m[32m return http.okJsonResponse[m [32m+[m[32m elif path.endswith('.ico'):[m [32m+[m[32m return http.okIconResponse[m [32m+[m [32m+[m[32m return http.okResponse[m [32m+[m [32m+[m[32m def handlePath(self, _path):[m [32m+[m[32m return ''[m [32m+[m [32m+[m[32m def work(self):[m [32m+[m[32m if not self.wlan.isconnected():[m [32m+[m[32m self.wlan = wifi.connect()[m [1mdiff --git a/net/templates.py b/net/templates.py[m [1mnew file mode 100644[m [1mindex 0000000..f2be3d5[m [1m--- /dev/null[m [1m+++ b/net/templates.py[m [36m@@ -0,0 +1,41 @@[m [32m+[m[32mdef render(template, **kwargs):[m [32m+[m[32m [startString, endString] = ['{{', '}}'][m [32m+[m[32m [startLength, endLength] = [len(startString), len(endString)][m [32m+[m [32m+[m[32m with open(template) as f:[m [32m+[m[32m data = f.read()[m [32m+[m[32m tokenCaret = 0[m [32m+[m[32m result = ''[m [32m+[m[32m isRendering = True[m [32m+[m [32m+[m[32m while isRendering:[m [32m+[m[32m start = data.find(startString, tokenCaret)[m [32m+[m[32m end = data.find(endString, start)[m [32m+[m [32m+[m[32m isRendering = start != -1 and end != -1[m [32m+[m [32m+[m[32m if isRendering:[m [32m+[m[32m token = data[start + startLength:end].strip()[m [32m+[m [32m+[m[32m result = ([m [32m+[m[32m result +[m [32m+[m[32m data[tokenCaret:start] +[m [32m+[m[32m replaceToken(token, kwargs)[m [32m+[m[32m )[m [32m+[m [32m+[m[32m tokenCaret = end + endLength[m [32m+[m[32m else:[m [32m+[m[32m result = result + data[tokenCaret:][m [32m+[m [32m+[m[32m return result[m [32m+[m [32m+[m [32m+[m[32mdef replaceToken(token, values):[m [32m+[m[32m result = str(values[token]) if token in values else ''[m [32m+[m[32m result = result.replace('&', '&')[m [32m+[m[32m result = result.replace('"', '"')[m [32m+[m[32m result = result.replace("'", ''')[m [32m+[m[32m result = result.replace('>', '>')[m [32m+[m[32m result = result.replace('<', '<')[m [32m+[m [32m+[m[32m return result[m [1mdiff --git a/net/util.py b/net/util.py[m [1mnew file mode 100644[m [1mindex 0000000..4170548[m [1m--- /dev/null[m [1m+++ b/net/util.py[m [36m@@ -0,0 +1,21 @@[m [32m+[m[32mimport machine[m [32m+[m[32mimport time[m [32m+[m [32m+[m [32m+[m[32mdef datetime():[m [32m+[m[32m dt = machine.RTC().datetime()[m [32m+[m [32m+[m[32m return '{0:04d}-{1:02d}-{2:02d} {4:02d}:{5:02d}:{6:02d} UTC'.format(*dt)[m [32m+[m [32m+[m [32m+[m[32mdef datetimeISO8601():[m [32m+[m[32m dt = machine.RTC().datetime()[m [32m+[m [32m+[m[32m return '{0:04d}-{1:02d}-{2:02d}T{4:02d}:{5:02d}:{6:02d}Z'.format(*dt)[m [32m+[m [32m+[m [32m+[m[32mdef secondsElapsed(ticks1, ticks2):[m [32m+[m[32m return time.ticks_diff(ticks1, ticks2) / 1000[m [32m+[m [32m+[m[32mdef millisecondsElapsed(ticks1, ticks2):[m [32m+[m[32m return time.ticks_diff(ticks1, ticks2)[m [1mdiff --git a/net/wifi.py b/net/wifi.py[m [1mnew file mode 100644[m [1mindex 0000000..3eccfca[m [1m--- /dev/null[m [1m+++ b/net/wifi.py[m [36m@@ -0,0 +1,49 @@[m [32m+[m[32mimport network[m [32m+[m[32mimport rp2[m [32m+[m[32mimport time[m [32m+[m [32m+[m[32mfrom . import logging[m [32m+[m[32mfrom .config import secrets[m [32m+[m [32m+[m [32m+[m[32mclass WifiConnectionError(RuntimeError):[m [32m+[m[32m pass[m [32m+[m [32m+[m [32m+[m[32mdef connect():[m [32m+[m[32m while True:[m [32m+[m[32m try:[m [32m+[m[32m return connectToWifi()[m [32m+[m[32m except WifiConnectionError as e:[m [32m+[m[32m logging.error(e.value)[m [32m+[m[32m time.sleep(180)[m [32m+[m [32m+[m [32m+[m[32mdef connectToWifi():[m [32m+[m[32m rp2.country('US')[m [32m+[m [32m+[m[32m wlan = network.WLAN(network.STA_IF)[m [32m+[m[32m wlan.active(True)[m [32m+[m[32m wlan.config(pm=0xa11140)[m [32m+[m[32m wlan.connect(secrets['ssid'], secrets['password'])[m [32m+[m [32m+[m[32m wait_for_connection(wlan)[m [32m+[m [32m+[m[32m logging.info('connected')[m [32m+[m[32m logging.info(f'ip = {wlan.ifconfig()[0]}')[m [32m+[m [32m+[m[32m return wlan[m [32m+[m [32m+[m [32m+[m[32mdef wait_for_connection(wlan):[m [32m+[m[32m maxWait = 10[m [32m+[m [32m+[m[32m while maxWait > 0:[m [32m+[m[32m if wlan.status() < 0 or wlan.status() >= 3:[m [32m+[m[32m break[m [32m+[m[32m maxWait -= 1[m [32m+[m[32m logging.info('waiting for connection...')[m [32m+[m[32m time.sleep(1)[m [32m+[m [32m+[m[32m if wlan.status() != 3:[m [32m+[m[32m raise WifiConnectionError('network connection failed')[m [1mdiff --git a/www/favicon.ico b/www/favicon.ico[m [1mnew file mode 100644[m [1mindex 0000000..1d4aa63[m Binary files /dev/null and b/www/favicon.ico differ [1mdiff --git a/www/index.html b/www/index.html[m [1mnew file mode 100644[m [1mindex 0000000..8171ee1[m [1m--- /dev/null[m [1m+++ b/www/index.html[m [36m@@ -0,0 +1,22 @@[m [32m+[m[32m[m [32m+[m [32m+[m[32m[m [32m+[m[32m{{hostname}} [m [32m+[m[32m [m [32m+[m[32m[m [32m+[m [32m+[m[32m[m [32m+[m[32m{{hostname}}
[m [32m+[m[32m{{datetime}}
[m [32m+[m[32mActive: {{is_active}}
[m [32m+[m[32m[m [32m+[m [32m+[m[32m[m
text/gemini; charset=utf-8
This content has been proxied by September (3851b).