kopia lustrzana https://github.com/peterhinch/micropython-samples
Add picoweb installation guide.
rodzic
8953cbf519
commit
b0bd198c70
|
@ -0,0 +1,94 @@
|
||||||
|
# Running Picoweb on hardware devices
|
||||||
|
|
||||||
|
This has regularly caused dificulty on the forum.
|
||||||
|
|
||||||
|
The target hardware is assumed to be running official MicroPython firmware.
|
||||||
|
|
||||||
|
This repo aims to clarify the installation process. Paul Sokolovsky's Picoweb
|
||||||
|
code is unchanged except for the name of the logging library. The demos are
|
||||||
|
trivially changed to use IP '0.0.0.0' and port 80.
|
||||||
|
|
||||||
|
To install on a hardware platform such as ESP32 or Pyboard D it is necessary to
|
||||||
|
copy this directory and its contents (including subdirectories) to the target.
|
||||||
|
If using `rshell` on an ESP32 change to this directory, at the `rshell` prompt
|
||||||
|
issue
|
||||||
|
|
||||||
|
```
|
||||||
|
/my/tree/PicoWeb> rsync . /pyboard
|
||||||
|
```
|
||||||
|
This may take some time.
|
||||||
|
|
||||||
|
At the REPL connect to the network and determine your IP address
|
||||||
|
```
|
||||||
|
>>> import network
|
||||||
|
>>> w = network.WLAN()
|
||||||
|
>>> w.ifconfig()
|
||||||
|
```
|
||||||
|
|
||||||
|
issue
|
||||||
|
```
|
||||||
|
>>> from picoweb import example_webapp
|
||||||
|
```
|
||||||
|
|
||||||
|
or
|
||||||
|
```
|
||||||
|
>>> from picoweb import example_webapp2
|
||||||
|
```
|
||||||
|
|
||||||
|
Then point your browser at the IP address determined above.
|
||||||
|
|
||||||
|
Note that some platforms will have `uasyncio` installed as frozen bytecode: in
|
||||||
|
such cases there is no need to copy the `uasyncio` subdirectory (if you do, it
|
||||||
|
will be ignored).
|
||||||
|
|
||||||
|
# ESP8266
|
||||||
|
|
||||||
|
RAM limitations require the use of frozen bytecode, and getting the examples
|
||||||
|
running is a little more involved. Create a directory on your PC and copy the
|
||||||
|
contents of this directory to it. Then add the files `inisetup.py`, `_boot.py`
|
||||||
|
and `flashbdev.py` which may be found in the MicroPython source tree under
|
||||||
|
`ports/esp8266/modules`. You may also want to add a custom connect module to
|
||||||
|
simplify connection to your WiFi. Then build the firmware. The script I used
|
||||||
|
was
|
||||||
|
```bash
|
||||||
|
#! /bin/bash
|
||||||
|
|
||||||
|
# Test picoweb on ESP8266
|
||||||
|
|
||||||
|
DIRECTORY='/home/adminpete/temp/picoweb'
|
||||||
|
|
||||||
|
cd /mnt/qnap2/data/Projects/MicroPython/micropython/ports/esp8266
|
||||||
|
|
||||||
|
make clean
|
||||||
|
esptool.py --port /dev/ttyUSB0 erase_flash
|
||||||
|
|
||||||
|
if make -j 8 FROZEN_MPY_DIR=$DIRECTORY
|
||||||
|
then
|
||||||
|
sleep 1
|
||||||
|
esptool.py --port /dev/ttyUSB0 --baud 115200 write_flash --flash_size=detect -fm dio 0 build/firmware-combined.bin
|
||||||
|
sleep 4
|
||||||
|
rshell -p /dev/ttyUSB0 --buffer-size=30 --editor nano
|
||||||
|
else
|
||||||
|
echo Build failure
|
||||||
|
fi
|
||||||
|
```
|
||||||
|
For the demos you will need to make the `example_webapp.py` source file and
|
||||||
|
`squares.tpl` accessible in the filesystem. The following `rshell` commands,
|
||||||
|
executed from this directory or the one created above, will make these
|
||||||
|
available.
|
||||||
|
```
|
||||||
|
path/to/repo> mkdir /pyboard/picoweb
|
||||||
|
path/to/repo> mkdir /pyboard/picoweb/templates
|
||||||
|
path/to/repo> cp picoweb/example_webapp.py /pyboard/picoweb/
|
||||||
|
path/to/repo> cp picoweb/templates/squares.tpl /pyboard/picoweb/templates/
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
# Documentation and further examples
|
||||||
|
|
||||||
|
See [the PicoWeb docs](https://github.com/pfalcon/picoweb)
|
||||||
|
|
||||||
|
Note that to run under official MicroPython, references to `ulogging` in these
|
||||||
|
demos must be changed to `logging`. You may also want to change IP and port as
|
||||||
|
above.
|
||||||
|
|
|
@ -0,0 +1,94 @@
|
||||||
|
import sys
|
||||||
|
|
||||||
|
CRITICAL = 50
|
||||||
|
ERROR = 40
|
||||||
|
WARNING = 30
|
||||||
|
INFO = 20
|
||||||
|
DEBUG = 10
|
||||||
|
NOTSET = 0
|
||||||
|
|
||||||
|
_level_dict = {
|
||||||
|
CRITICAL: "CRIT",
|
||||||
|
ERROR: "ERROR",
|
||||||
|
WARNING: "WARN",
|
||||||
|
INFO: "INFO",
|
||||||
|
DEBUG: "DEBUG",
|
||||||
|
}
|
||||||
|
|
||||||
|
_stream = sys.stderr
|
||||||
|
|
||||||
|
class Logger:
|
||||||
|
|
||||||
|
level = NOTSET
|
||||||
|
|
||||||
|
def __init__(self, name):
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
def _level_str(self, level):
|
||||||
|
l = _level_dict.get(level)
|
||||||
|
if l is not None:
|
||||||
|
return l
|
||||||
|
return "LVL%s" % level
|
||||||
|
|
||||||
|
def setLevel(self, level):
|
||||||
|
self.level = level
|
||||||
|
|
||||||
|
def isEnabledFor(self, level):
|
||||||
|
return level >= (self.level or _level)
|
||||||
|
|
||||||
|
def log(self, level, msg, *args):
|
||||||
|
if level >= (self.level or _level):
|
||||||
|
_stream.write("%s:%s:" % (self._level_str(level), self.name))
|
||||||
|
if not args:
|
||||||
|
print(msg, file=_stream)
|
||||||
|
else:
|
||||||
|
print(msg % args, file=_stream)
|
||||||
|
|
||||||
|
def debug(self, msg, *args):
|
||||||
|
self.log(DEBUG, msg, *args)
|
||||||
|
|
||||||
|
def info(self, msg, *args):
|
||||||
|
self.log(INFO, msg, *args)
|
||||||
|
|
||||||
|
def warning(self, msg, *args):
|
||||||
|
self.log(WARNING, msg, *args)
|
||||||
|
|
||||||
|
def error(self, msg, *args):
|
||||||
|
self.log(ERROR, msg, *args)
|
||||||
|
|
||||||
|
def critical(self, msg, *args):
|
||||||
|
self.log(CRITICAL, msg, *args)
|
||||||
|
|
||||||
|
def exc(self, e, msg, *args):
|
||||||
|
self.log(ERROR, msg, *args)
|
||||||
|
sys.print_exception(e, _stream)
|
||||||
|
|
||||||
|
def exception(self, msg, *args):
|
||||||
|
self.exc(sys.exc_info()[1], msg, *args)
|
||||||
|
|
||||||
|
|
||||||
|
_level = INFO
|
||||||
|
_loggers = {}
|
||||||
|
|
||||||
|
def getLogger(name):
|
||||||
|
if name in _loggers:
|
||||||
|
return _loggers[name]
|
||||||
|
l = Logger(name)
|
||||||
|
_loggers[name] = l
|
||||||
|
return l
|
||||||
|
|
||||||
|
def info(msg, *args):
|
||||||
|
getLogger(None).info(msg, *args)
|
||||||
|
|
||||||
|
def debug(msg, *args):
|
||||||
|
getLogger(None).debug(msg, *args)
|
||||||
|
|
||||||
|
def basicConfig(level=INFO, filename=None, stream=None, format=None):
|
||||||
|
global _level, _stream
|
||||||
|
_level = level
|
||||||
|
if stream:
|
||||||
|
_stream = stream
|
||||||
|
if filename is not None:
|
||||||
|
print("logging.basicConfig: filename arg is not supported")
|
||||||
|
if format is not None:
|
||||||
|
print("logging.basicConfig: format arg is not supported")
|
|
@ -0,0 +1,300 @@
|
||||||
|
# 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
|
||||||
|
import pkg_resources
|
||||||
|
|
||||||
|
from .utils import parse_qs
|
||||||
|
|
||||||
|
|
||||||
|
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, serve_static=True):
|
||||||
|
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
|
||||||
|
if serve_static:
|
||||||
|
self.url_map.append((re.compile("^/(static/.+)"), self.handle_static))
|
||||||
|
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):
|
||||||
|
if self.debug > 1:
|
||||||
|
micropython.mem_info()
|
||||||
|
|
||||||
|
close = True
|
||||||
|
req = None
|
||||||
|
try:
|
||||||
|
request_line = yield from reader.readline()
|
||||||
|
if request_line == b"":
|
||||||
|
if self.debug >= 0:
|
||||||
|
self.log.error("%s: EOF on request start" % reader)
|
||||||
|
yield from writer.aclose()
|
||||||
|
return
|
||||||
|
req = HTTPRequest()
|
||||||
|
# TODO: bytes vs str
|
||||||
|
request_line = request_line.decode()
|
||||||
|
method, path, proto = request_line.split()
|
||||||
|
if self.debug >= 0:
|
||||||
|
self.log.info('%.3f %s %s "%s %s"' % (utime.time(), req, writer, method, path))
|
||||||
|
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:
|
||||||
|
if self.debug >= 0:
|
||||||
|
self.log.exc(e, "%.3f %s %s %r" % (utime.time(), req, writer, e))
|
||||||
|
|
||||||
|
if close is not False:
|
||||||
|
yield from writer.aclose()
|
||||||
|
if __debug__ and self.debug > 1:
|
||||||
|
self.log.debug("%.3f %s Finished processing request", utime.time(), req)
|
||||||
|
|
||||||
|
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 sendfile(self, writer, fname, content_type=None, headers=None):
|
||||||
|
if not content_type:
|
||||||
|
content_type = get_mime_type(fname)
|
||||||
|
try:
|
||||||
|
with pkg_resources.resource_stream(self.pkg, fname) as f:
|
||||||
|
yield from start_response(writer, content_type, "200", headers)
|
||||||
|
yield from sendstream(writer, f)
|
||||||
|
except OSError as e:
|
||||||
|
if e.args[0] == uerrno.ENOENT:
|
||||||
|
yield from http_error(writer, "404")
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
|
def handle_static(self, req, resp):
|
||||||
|
path = req.url_match.group(1)
|
||||||
|
print(path)
|
||||||
|
if ".." in path:
|
||||||
|
yield from http_error(resp, "403")
|
||||||
|
return
|
||||||
|
yield from self.sendfile(resp, path)
|
||||||
|
|
||||||
|
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="127.0.0.1", port=8081, debug=False, lazy_init=False, log=None):
|
||||||
|
if log is None and debug >= 0:
|
||||||
|
import logging
|
||||||
|
log = logging.getLogger("picoweb")
|
||||||
|
if debug > 0:
|
||||||
|
log.setLevel(logging.DEBUG)
|
||||||
|
self.log = log
|
||||||
|
gc.collect()
|
||||||
|
self.debug = int(debug)
|
||||||
|
self.init()
|
||||||
|
if not lazy_init:
|
||||||
|
for app in self.mounts:
|
||||||
|
app.init()
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
if debug > 0:
|
||||||
|
print("* Running on http://%s:%s/" % (host, port))
|
||||||
|
loop.create_task(asyncio.start_server(self._handle, host, port))
|
||||||
|
loop.run_forever()
|
||||||
|
loop.close()
|
|
@ -0,0 +1,55 @@
|
||||||
|
#
|
||||||
|
# This is a picoweb example showing a centralized web page route
|
||||||
|
# specification (classical Django style).
|
||||||
|
#
|
||||||
|
import ure as re
|
||||||
|
import picoweb
|
||||||
|
|
||||||
|
|
||||||
|
def index(req, resp):
|
||||||
|
# You can construct an HTTP response completely yourself, having
|
||||||
|
# a full control of headers sent...
|
||||||
|
yield from resp.awrite("HTTP/1.0 200 OK\r\n")
|
||||||
|
yield from resp.awrite("Content-Type: text/html\r\n")
|
||||||
|
yield from resp.awrite("\r\n")
|
||||||
|
yield from resp.awrite("I can show you a table of <a href='squares'>squares</a>.<br/>")
|
||||||
|
yield from resp.awrite("Or my <a href='file'>source</a>.<br/>")
|
||||||
|
yield from resp.awrite("Or enter /iam/Mickey Mouse after the URL for regexp match.")
|
||||||
|
|
||||||
|
|
||||||
|
def squares(req, resp):
|
||||||
|
# Or can use a convenience function start_response() (see its source for
|
||||||
|
# extra params it takes).
|
||||||
|
yield from picoweb.start_response(resp)
|
||||||
|
yield from app.render_template(resp, "squares.tpl", (req,))
|
||||||
|
|
||||||
|
|
||||||
|
def hello(req, resp):
|
||||||
|
yield from picoweb.start_response(resp)
|
||||||
|
# Here's how you extract matched groups from a regex URI match
|
||||||
|
yield from resp.awrite("Hello " + req.url_match.group(1))
|
||||||
|
|
||||||
|
|
||||||
|
ROUTES = [
|
||||||
|
# You can specify exact URI string matches...
|
||||||
|
("/", index),
|
||||||
|
("/squares", squares),
|
||||||
|
("/file", lambda req, resp: (yield from app.sendfile(resp, "example_webapp.py"))),
|
||||||
|
# ... or match using a regex, the match result available as req.url_match
|
||||||
|
# for match group extraction in your view.
|
||||||
|
(re.compile("^/iam/(.+)"), hello),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
import logging
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
#logging.basicConfig(level=logging.DEBUG)
|
||||||
|
|
||||||
|
app = picoweb.WebApp(__name__, ROUTES)
|
||||||
|
# debug values:
|
||||||
|
# -1 disable all logging
|
||||||
|
# 0 (False) normal logging: requests and errors
|
||||||
|
# 1 (True) debug logging
|
||||||
|
# 2 extra debug logging
|
||||||
|
app.run(debug=1, host='0.0.0.0', port=80)
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
#
|
||||||
|
# This is a picoweb example showing a web page route
|
||||||
|
# specification using view decorators (Flask style).
|
||||||
|
#
|
||||||
|
import picoweb
|
||||||
|
|
||||||
|
|
||||||
|
app = picoweb.WebApp(__name__)
|
||||||
|
|
||||||
|
@app.route("/")
|
||||||
|
def index(req, resp):
|
||||||
|
yield from picoweb.start_response(resp)
|
||||||
|
yield from resp.awrite("I can show you a table of <a href='squares'>squares</a>.")
|
||||||
|
|
||||||
|
@app.route("/squares")
|
||||||
|
def squares(req, resp):
|
||||||
|
yield from picoweb.start_response(resp)
|
||||||
|
yield from app.render_template(resp, "squares.tpl", (req,))
|
||||||
|
|
||||||
|
|
||||||
|
import logging
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
|
app.run(debug=True, host='0.0.0.0', port=80)
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
{% args req %}
|
||||||
|
<html>
|
||||||
|
Request path: '{{req.path}}'<br>
|
||||||
|
<table border="1">
|
||||||
|
{% for i in range(5) %}
|
||||||
|
<tr><td> {{i}} </td><td> {{"%2d" % i ** 2}} </td></tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
</html>
|
|
@ -0,0 +1,28 @@
|
||||||
|
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
|
||||||
|
|
||||||
|
#print(parse_qs("foo"))
|
||||||
|
#print(parse_qs("fo%41o+bar=+++1"))
|
||||||
|
#print(parse_qs("foo=1&foo=2"))
|
|
@ -0,0 +1,27 @@
|
||||||
|
import uio
|
||||||
|
|
||||||
|
c = {}
|
||||||
|
|
||||||
|
def resource_stream(package, resource):
|
||||||
|
if package not in c:
|
||||||
|
try:
|
||||||
|
if package:
|
||||||
|
p = __import__(package + ".R", None, None, True)
|
||||||
|
else:
|
||||||
|
p = __import__("R")
|
||||||
|
c[package] = p.R
|
||||||
|
except ImportError:
|
||||||
|
if package:
|
||||||
|
p = __import__(package)
|
||||||
|
d = p.__path__
|
||||||
|
else:
|
||||||
|
d = "."
|
||||||
|
# if d[0] != "/":
|
||||||
|
# import uos
|
||||||
|
# d = uos.getcwd() + "/" + d
|
||||||
|
c[package] = d + "/"
|
||||||
|
|
||||||
|
p = c[package]
|
||||||
|
if isinstance(p, dict):
|
||||||
|
return uio.BytesIO(p[resource])
|
||||||
|
return open(p + resource, "rb")
|
|
@ -0,0 +1,258 @@
|
||||||
|
import uerrno
|
||||||
|
import uselect as select
|
||||||
|
import usocket as _socket
|
||||||
|
from uasyncio.core import *
|
||||||
|
|
||||||
|
|
||||||
|
DEBUG = 0
|
||||||
|
log = None
|
||||||
|
|
||||||
|
def set_debug(val):
|
||||||
|
global DEBUG, log
|
||||||
|
DEBUG = val
|
||||||
|
if val:
|
||||||
|
import logging
|
||||||
|
log = logging.getLogger("uasyncio")
|
||||||
|
|
||||||
|
|
||||||
|
class PollEventLoop(EventLoop):
|
||||||
|
|
||||||
|
def __init__(self, runq_len=16, waitq_len=16):
|
||||||
|
EventLoop.__init__(self, runq_len, waitq_len)
|
||||||
|
self.poller = select.poll()
|
||||||
|
self.objmap = {}
|
||||||
|
|
||||||
|
def add_reader(self, sock, cb, *args):
|
||||||
|
if DEBUG and __debug__:
|
||||||
|
log.debug("add_reader%s", (sock, cb, args))
|
||||||
|
if args:
|
||||||
|
self.poller.register(sock, select.POLLIN)
|
||||||
|
self.objmap[id(sock)] = (cb, args)
|
||||||
|
else:
|
||||||
|
self.poller.register(sock, select.POLLIN)
|
||||||
|
self.objmap[id(sock)] = cb
|
||||||
|
|
||||||
|
def remove_reader(self, sock):
|
||||||
|
if DEBUG and __debug__:
|
||||||
|
log.debug("remove_reader(%s)", sock)
|
||||||
|
self.poller.unregister(sock)
|
||||||
|
del self.objmap[id(sock)]
|
||||||
|
|
||||||
|
def add_writer(self, sock, cb, *args):
|
||||||
|
if DEBUG and __debug__:
|
||||||
|
log.debug("add_writer%s", (sock, cb, args))
|
||||||
|
if args:
|
||||||
|
self.poller.register(sock, select.POLLOUT)
|
||||||
|
self.objmap[id(sock)] = (cb, args)
|
||||||
|
else:
|
||||||
|
self.poller.register(sock, select.POLLOUT)
|
||||||
|
self.objmap[id(sock)] = cb
|
||||||
|
|
||||||
|
def remove_writer(self, sock):
|
||||||
|
if DEBUG and __debug__:
|
||||||
|
log.debug("remove_writer(%s)", sock)
|
||||||
|
try:
|
||||||
|
self.poller.unregister(sock)
|
||||||
|
self.objmap.pop(id(sock), None)
|
||||||
|
except OSError as e:
|
||||||
|
# StreamWriter.awrite() first tries to write to a socket,
|
||||||
|
# and if that succeeds, yield IOWrite may never be called
|
||||||
|
# for that socket, and it will never be added to poller. So,
|
||||||
|
# ignore such error.
|
||||||
|
if e.args[0] != uerrno.ENOENT:
|
||||||
|
raise
|
||||||
|
|
||||||
|
def wait(self, delay):
|
||||||
|
if DEBUG and __debug__:
|
||||||
|
log.debug("poll.wait(%d)", delay)
|
||||||
|
# We need one-shot behavior (second arg of 1 to .poll())
|
||||||
|
res = self.poller.ipoll(delay, 1)
|
||||||
|
#log.debug("poll result: %s", res)
|
||||||
|
# Remove "if res" workaround after
|
||||||
|
# https://github.com/micropython/micropython/issues/2716 fixed.
|
||||||
|
if res:
|
||||||
|
for sock, ev in res:
|
||||||
|
cb = self.objmap[id(sock)]
|
||||||
|
if ev & (select.POLLHUP | select.POLLERR):
|
||||||
|
# These events are returned even if not requested, and
|
||||||
|
# are sticky, i.e. will be returned again and again.
|
||||||
|
# If the caller doesn't do proper error handling and
|
||||||
|
# unregister this sock, we'll busy-loop on it, so we
|
||||||
|
# as well can unregister it now "just in case".
|
||||||
|
self.remove_reader(sock)
|
||||||
|
if DEBUG and __debug__:
|
||||||
|
log.debug("Calling IO callback: %r", cb)
|
||||||
|
if isinstance(cb, tuple):
|
||||||
|
cb[0](*cb[1])
|
||||||
|
else:
|
||||||
|
cb.pend_throw(None)
|
||||||
|
self.call_soon(cb)
|
||||||
|
|
||||||
|
|
||||||
|
class StreamReader:
|
||||||
|
|
||||||
|
def __init__(self, polls, ios=None):
|
||||||
|
if ios is None:
|
||||||
|
ios = polls
|
||||||
|
self.polls = polls
|
||||||
|
self.ios = ios
|
||||||
|
|
||||||
|
def read(self, n=-1):
|
||||||
|
while True:
|
||||||
|
yield IORead(self.polls)
|
||||||
|
res = self.ios.read(n)
|
||||||
|
if res is not None:
|
||||||
|
break
|
||||||
|
# This should not happen for real sockets, but can easily
|
||||||
|
# happen for stream wrappers (ssl, websockets, etc.)
|
||||||
|
#log.warn("Empty read")
|
||||||
|
if not res:
|
||||||
|
yield IOReadDone(self.polls)
|
||||||
|
return res
|
||||||
|
|
||||||
|
def readexactly(self, n):
|
||||||
|
buf = b""
|
||||||
|
while n:
|
||||||
|
yield IORead(self.polls)
|
||||||
|
res = self.ios.read(n)
|
||||||
|
assert res is not None
|
||||||
|
if not res:
|
||||||
|
yield IOReadDone(self.polls)
|
||||||
|
break
|
||||||
|
buf += res
|
||||||
|
n -= len(res)
|
||||||
|
return buf
|
||||||
|
|
||||||
|
def readline(self):
|
||||||
|
if DEBUG and __debug__:
|
||||||
|
log.debug("StreamReader.readline()")
|
||||||
|
buf = b""
|
||||||
|
while True:
|
||||||
|
yield IORead(self.polls)
|
||||||
|
res = self.ios.readline()
|
||||||
|
assert res is not None
|
||||||
|
if not res:
|
||||||
|
yield IOReadDone(self.polls)
|
||||||
|
break
|
||||||
|
buf += res
|
||||||
|
if buf[-1] == 0x0a:
|
||||||
|
break
|
||||||
|
if DEBUG and __debug__:
|
||||||
|
log.debug("StreamReader.readline(): %s", buf)
|
||||||
|
return buf
|
||||||
|
|
||||||
|
def aclose(self):
|
||||||
|
yield IOReadDone(self.polls)
|
||||||
|
self.ios.close()
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<StreamReader %r %r>" % (self.polls, self.ios)
|
||||||
|
|
||||||
|
|
||||||
|
class StreamWriter:
|
||||||
|
|
||||||
|
def __init__(self, s, extra):
|
||||||
|
self.s = s
|
||||||
|
self.extra = extra
|
||||||
|
|
||||||
|
def awrite(self, buf, off=0, sz=-1):
|
||||||
|
# This method is called awrite (async write) to not proliferate
|
||||||
|
# incompatibility with original asyncio. Unlike original asyncio
|
||||||
|
# whose .write() method is both not a coroutine and guaranteed
|
||||||
|
# to return immediately (which means it has to buffer all the
|
||||||
|
# data), this method is a coroutine.
|
||||||
|
if sz == -1:
|
||||||
|
sz = len(buf) - off
|
||||||
|
if DEBUG and __debug__:
|
||||||
|
log.debug("StreamWriter.awrite(): spooling %d bytes", sz)
|
||||||
|
while True:
|
||||||
|
res = self.s.write(buf, off, sz)
|
||||||
|
# If we spooled everything, return immediately
|
||||||
|
if res == sz:
|
||||||
|
if DEBUG and __debug__:
|
||||||
|
log.debug("StreamWriter.awrite(): completed spooling %d bytes", res)
|
||||||
|
return
|
||||||
|
if res is None:
|
||||||
|
res = 0
|
||||||
|
if DEBUG and __debug__:
|
||||||
|
log.debug("StreamWriter.awrite(): spooled partial %d bytes", res)
|
||||||
|
assert res < sz
|
||||||
|
off += res
|
||||||
|
sz -= res
|
||||||
|
yield IOWrite(self.s)
|
||||||
|
#assert s2.fileno() == self.s.fileno()
|
||||||
|
if DEBUG and __debug__:
|
||||||
|
log.debug("StreamWriter.awrite(): can write more")
|
||||||
|
|
||||||
|
# Write piecewise content from iterable (usually, a generator)
|
||||||
|
def awriteiter(self, iterable):
|
||||||
|
for buf in iterable:
|
||||||
|
yield from self.awrite(buf)
|
||||||
|
|
||||||
|
def aclose(self):
|
||||||
|
yield IOWriteDone(self.s)
|
||||||
|
self.s.close()
|
||||||
|
|
||||||
|
def get_extra_info(self, name, default=None):
|
||||||
|
return self.extra.get(name, default)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<StreamWriter %r>" % self.s
|
||||||
|
|
||||||
|
|
||||||
|
def open_connection(host, port, ssl=False):
|
||||||
|
if DEBUG and __debug__:
|
||||||
|
log.debug("open_connection(%s, %s)", host, port)
|
||||||
|
ai = _socket.getaddrinfo(host, port, 0, _socket.SOCK_STREAM)
|
||||||
|
ai = ai[0]
|
||||||
|
s = _socket.socket(ai[0], ai[1], ai[2])
|
||||||
|
s.setblocking(False)
|
||||||
|
try:
|
||||||
|
s.connect(ai[-1])
|
||||||
|
except OSError as e:
|
||||||
|
if e.args[0] != uerrno.EINPROGRESS:
|
||||||
|
raise
|
||||||
|
if DEBUG and __debug__:
|
||||||
|
log.debug("open_connection: After connect")
|
||||||
|
yield IOWrite(s)
|
||||||
|
# if __debug__:
|
||||||
|
# assert s2.fileno() == s.fileno()
|
||||||
|
if DEBUG and __debug__:
|
||||||
|
log.debug("open_connection: After iowait: %s", s)
|
||||||
|
if ssl:
|
||||||
|
print("Warning: uasyncio SSL support is alpha")
|
||||||
|
import ussl
|
||||||
|
s.setblocking(True)
|
||||||
|
s2 = ussl.wrap_socket(s)
|
||||||
|
s.setblocking(False)
|
||||||
|
return StreamReader(s, s2), StreamWriter(s2, {})
|
||||||
|
return StreamReader(s), StreamWriter(s, {})
|
||||||
|
|
||||||
|
|
||||||
|
def start_server(client_coro, host, port, backlog=10):
|
||||||
|
if DEBUG and __debug__:
|
||||||
|
log.debug("start_server(%s, %s)", host, port)
|
||||||
|
ai = _socket.getaddrinfo(host, port, 0, _socket.SOCK_STREAM)
|
||||||
|
ai = ai[0]
|
||||||
|
s = _socket.socket(ai[0], ai[1], ai[2])
|
||||||
|
s.setblocking(False)
|
||||||
|
|
||||||
|
s.setsockopt(_socket.SOL_SOCKET, _socket.SO_REUSEADDR, 1)
|
||||||
|
s.bind(ai[-1])
|
||||||
|
s.listen(backlog)
|
||||||
|
while True:
|
||||||
|
if DEBUG and __debug__:
|
||||||
|
log.debug("start_server: Before accept")
|
||||||
|
yield IORead(s)
|
||||||
|
if DEBUG and __debug__:
|
||||||
|
log.debug("start_server: After iowait")
|
||||||
|
s2, client_addr = s.accept()
|
||||||
|
s2.setblocking(False)
|
||||||
|
if DEBUG and __debug__:
|
||||||
|
log.debug("start_server: After accept: %s", s2)
|
||||||
|
extra = {"peername": client_addr}
|
||||||
|
yield client_coro(StreamReader(s2), StreamWriter(s2, extra))
|
||||||
|
|
||||||
|
|
||||||
|
import uasyncio.core
|
||||||
|
uasyncio.core._event_loop_class = PollEventLoop
|
|
@ -0,0 +1,315 @@
|
||||||
|
import utime as time
|
||||||
|
import utimeq
|
||||||
|
import ucollections
|
||||||
|
|
||||||
|
|
||||||
|
type_gen = type((lambda: (yield))())
|
||||||
|
|
||||||
|
DEBUG = 0
|
||||||
|
log = None
|
||||||
|
|
||||||
|
def set_debug(val):
|
||||||
|
global DEBUG, log
|
||||||
|
DEBUG = val
|
||||||
|
if val:
|
||||||
|
import logging
|
||||||
|
log = logging.getLogger("uasyncio.core")
|
||||||
|
|
||||||
|
|
||||||
|
class CancelledError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class TimeoutError(CancelledError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class EventLoop:
|
||||||
|
|
||||||
|
def __init__(self, runq_len=16, waitq_len=16):
|
||||||
|
self.runq = ucollections.deque((), runq_len, True)
|
||||||
|
self.waitq = utimeq.utimeq(waitq_len)
|
||||||
|
# Current task being run. Task is a top-level coroutine scheduled
|
||||||
|
# in the event loop (sub-coroutines executed transparently by
|
||||||
|
# yield from/await, event loop "doesn't see" them).
|
||||||
|
self.cur_task = None
|
||||||
|
|
||||||
|
def time(self):
|
||||||
|
return time.ticks_ms()
|
||||||
|
|
||||||
|
def create_task(self, coro):
|
||||||
|
# CPython 3.4.2
|
||||||
|
self.call_later_ms(0, coro)
|
||||||
|
# CPython asyncio incompatibility: we don't return Task object
|
||||||
|
|
||||||
|
def call_soon(self, callback, *args):
|
||||||
|
if __debug__ and DEBUG:
|
||||||
|
log.debug("Scheduling in runq: %s", (callback, args))
|
||||||
|
self.runq.append(callback)
|
||||||
|
if not isinstance(callback, type_gen):
|
||||||
|
self.runq.append(args)
|
||||||
|
|
||||||
|
def call_later(self, delay, callback, *args):
|
||||||
|
self.call_at_(time.ticks_add(self.time(), int(delay * 1000)), callback, args)
|
||||||
|
|
||||||
|
def call_later_ms(self, delay, callback, *args):
|
||||||
|
if not delay:
|
||||||
|
return self.call_soon(callback, *args)
|
||||||
|
self.call_at_(time.ticks_add(self.time(), delay), callback, args)
|
||||||
|
|
||||||
|
def call_at_(self, time, callback, args=()):
|
||||||
|
if __debug__ and DEBUG:
|
||||||
|
log.debug("Scheduling in waitq: %s", (time, callback, args))
|
||||||
|
self.waitq.push(time, callback, args)
|
||||||
|
|
||||||
|
def wait(self, delay):
|
||||||
|
# Default wait implementation, to be overriden in subclasses
|
||||||
|
# with IO scheduling
|
||||||
|
if __debug__ and DEBUG:
|
||||||
|
log.debug("Sleeping for: %s", delay)
|
||||||
|
time.sleep_ms(delay)
|
||||||
|
|
||||||
|
def run_forever(self):
|
||||||
|
cur_task = [0, 0, 0]
|
||||||
|
while True:
|
||||||
|
# Expire entries in waitq and move them to runq
|
||||||
|
tnow = self.time()
|
||||||
|
while self.waitq:
|
||||||
|
t = self.waitq.peektime()
|
||||||
|
delay = time.ticks_diff(t, tnow)
|
||||||
|
if delay > 0:
|
||||||
|
break
|
||||||
|
self.waitq.pop(cur_task)
|
||||||
|
if __debug__ and DEBUG:
|
||||||
|
log.debug("Moving from waitq to runq: %s", cur_task[1])
|
||||||
|
self.call_soon(cur_task[1], *cur_task[2])
|
||||||
|
|
||||||
|
# Process runq
|
||||||
|
l = len(self.runq)
|
||||||
|
if __debug__ and DEBUG:
|
||||||
|
log.debug("Entries in runq: %d", l)
|
||||||
|
while l:
|
||||||
|
cb = self.runq.popleft()
|
||||||
|
l -= 1
|
||||||
|
args = ()
|
||||||
|
if not isinstance(cb, type_gen):
|
||||||
|
args = self.runq.popleft()
|
||||||
|
l -= 1
|
||||||
|
if __debug__ and DEBUG:
|
||||||
|
log.info("Next callback to run: %s", (cb, args))
|
||||||
|
cb(*args)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if __debug__ and DEBUG:
|
||||||
|
log.info("Next coroutine to run: %s", (cb, args))
|
||||||
|
self.cur_task = cb
|
||||||
|
delay = 0
|
||||||
|
try:
|
||||||
|
if args is ():
|
||||||
|
ret = next(cb)
|
||||||
|
else:
|
||||||
|
ret = cb.send(*args)
|
||||||
|
if __debug__ and DEBUG:
|
||||||
|
log.info("Coroutine %s yield result: %s", cb, ret)
|
||||||
|
if isinstance(ret, SysCall1):
|
||||||
|
arg = ret.arg
|
||||||
|
if isinstance(ret, SleepMs):
|
||||||
|
delay = arg
|
||||||
|
elif isinstance(ret, IORead):
|
||||||
|
cb.pend_throw(False)
|
||||||
|
self.add_reader(arg, cb)
|
||||||
|
continue
|
||||||
|
elif isinstance(ret, IOWrite):
|
||||||
|
cb.pend_throw(False)
|
||||||
|
self.add_writer(arg, cb)
|
||||||
|
continue
|
||||||
|
elif isinstance(ret, IOReadDone):
|
||||||
|
self.remove_reader(arg)
|
||||||
|
elif isinstance(ret, IOWriteDone):
|
||||||
|
self.remove_writer(arg)
|
||||||
|
elif isinstance(ret, StopLoop):
|
||||||
|
return arg
|
||||||
|
else:
|
||||||
|
assert False, "Unknown syscall yielded: %r (of type %r)" % (ret, type(ret))
|
||||||
|
elif isinstance(ret, type_gen):
|
||||||
|
self.call_soon(ret)
|
||||||
|
elif isinstance(ret, int):
|
||||||
|
# Delay
|
||||||
|
delay = ret
|
||||||
|
elif ret is None:
|
||||||
|
# Just reschedule
|
||||||
|
pass
|
||||||
|
elif ret is False:
|
||||||
|
# Don't reschedule
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
assert False, "Unsupported coroutine yield value: %r (of type %r)" % (ret, type(ret))
|
||||||
|
except StopIteration as e:
|
||||||
|
if __debug__ and DEBUG:
|
||||||
|
log.debug("Coroutine finished: %s", cb)
|
||||||
|
continue
|
||||||
|
except CancelledError as e:
|
||||||
|
if __debug__ and DEBUG:
|
||||||
|
log.debug("Coroutine cancelled: %s", cb)
|
||||||
|
continue
|
||||||
|
# Currently all syscalls don't return anything, so we don't
|
||||||
|
# need to feed anything to the next invocation of coroutine.
|
||||||
|
# If that changes, need to pass that value below.
|
||||||
|
if delay:
|
||||||
|
self.call_later_ms(delay, cb)
|
||||||
|
else:
|
||||||
|
self.call_soon(cb)
|
||||||
|
|
||||||
|
# Wait until next waitq task or I/O availability
|
||||||
|
delay = 0
|
||||||
|
if not self.runq:
|
||||||
|
delay = -1
|
||||||
|
if self.waitq:
|
||||||
|
tnow = self.time()
|
||||||
|
t = self.waitq.peektime()
|
||||||
|
delay = time.ticks_diff(t, tnow)
|
||||||
|
if delay < 0:
|
||||||
|
delay = 0
|
||||||
|
self.wait(delay)
|
||||||
|
|
||||||
|
def run_until_complete(self, coro):
|
||||||
|
def _run_and_stop():
|
||||||
|
yield from coro
|
||||||
|
yield StopLoop(0)
|
||||||
|
self.call_soon(_run_and_stop())
|
||||||
|
self.run_forever()
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
self.call_soon((lambda: (yield StopLoop(0)))())
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class SysCall:
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
self.args = args
|
||||||
|
|
||||||
|
def handle(self):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
# Optimized syscall with 1 arg
|
||||||
|
class SysCall1(SysCall):
|
||||||
|
|
||||||
|
def __init__(self, arg):
|
||||||
|
self.arg = arg
|
||||||
|
|
||||||
|
class StopLoop(SysCall1):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class IORead(SysCall1):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class IOWrite(SysCall1):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class IOReadDone(SysCall1):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class IOWriteDone(SysCall1):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
_event_loop = None
|
||||||
|
_event_loop_class = EventLoop
|
||||||
|
def get_event_loop(runq_len=16, waitq_len=16):
|
||||||
|
global _event_loop
|
||||||
|
if _event_loop is None:
|
||||||
|
_event_loop = _event_loop_class(runq_len, waitq_len)
|
||||||
|
return _event_loop
|
||||||
|
|
||||||
|
def sleep(secs):
|
||||||
|
yield int(secs * 1000)
|
||||||
|
|
||||||
|
# Implementation of sleep_ms awaitable with zero heap memory usage
|
||||||
|
class SleepMs(SysCall1):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.v = None
|
||||||
|
self.arg = None
|
||||||
|
|
||||||
|
def __call__(self, arg):
|
||||||
|
self.v = arg
|
||||||
|
#print("__call__")
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
#print("__iter__")
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __next__(self):
|
||||||
|
if self.v is not None:
|
||||||
|
#print("__next__ syscall enter")
|
||||||
|
self.arg = self.v
|
||||||
|
self.v = None
|
||||||
|
return self
|
||||||
|
#print("__next__ syscall exit")
|
||||||
|
_stop_iter.__traceback__ = None
|
||||||
|
raise _stop_iter
|
||||||
|
|
||||||
|
_stop_iter = StopIteration()
|
||||||
|
sleep_ms = SleepMs()
|
||||||
|
|
||||||
|
|
||||||
|
def cancel(coro):
|
||||||
|
prev = coro.pend_throw(CancelledError())
|
||||||
|
if prev is False:
|
||||||
|
_event_loop.call_soon(coro)
|
||||||
|
|
||||||
|
|
||||||
|
class TimeoutObj:
|
||||||
|
def __init__(self, coro):
|
||||||
|
self.coro = coro
|
||||||
|
|
||||||
|
|
||||||
|
def wait_for_ms(coro, timeout):
|
||||||
|
|
||||||
|
def waiter(coro, timeout_obj):
|
||||||
|
res = yield from coro
|
||||||
|
if __debug__ and DEBUG:
|
||||||
|
log.debug("waiter: cancelling %s", timeout_obj)
|
||||||
|
timeout_obj.coro = None
|
||||||
|
return res
|
||||||
|
|
||||||
|
def timeout_func(timeout_obj):
|
||||||
|
if timeout_obj.coro:
|
||||||
|
if __debug__ and DEBUG:
|
||||||
|
log.debug("timeout_func: cancelling %s", timeout_obj.coro)
|
||||||
|
prev = timeout_obj.coro.pend_throw(TimeoutError())
|
||||||
|
#print("prev pend", prev)
|
||||||
|
if prev is False:
|
||||||
|
_event_loop.call_soon(timeout_obj.coro)
|
||||||
|
|
||||||
|
timeout_obj = TimeoutObj(_event_loop.cur_task)
|
||||||
|
_event_loop.call_later_ms(timeout, timeout_func, timeout_obj)
|
||||||
|
return (yield from waiter(coro, timeout_obj))
|
||||||
|
|
||||||
|
|
||||||
|
def wait_for(coro, timeout):
|
||||||
|
return wait_for_ms(coro, int(timeout * 1000))
|
||||||
|
|
||||||
|
|
||||||
|
def coroutine(f):
|
||||||
|
return f
|
||||||
|
|
||||||
|
#
|
||||||
|
# The functions below are deprecated in uasyncio, and provided only
|
||||||
|
# for compatibility with CPython asyncio
|
||||||
|
#
|
||||||
|
|
||||||
|
def ensure_future(coro, loop=_event_loop):
|
||||||
|
_event_loop.call_soon(coro)
|
||||||
|
# CPython asyncio incompatibility: we don't return Task object
|
||||||
|
return coro
|
||||||
|
|
||||||
|
|
||||||
|
# CPython asyncio incompatibility: Task is a function, not a class (for efficiency)
|
||||||
|
def Task(coro, loop=_event_loop):
|
||||||
|
# Same as async()
|
||||||
|
_event_loop.call_soon(coro)
|
|
@ -0,0 +1,14 @@
|
||||||
|
class Loader:
|
||||||
|
|
||||||
|
def __init__(self, pkg, dir):
|
||||||
|
if dir == ".":
|
||||||
|
dir = ""
|
||||||
|
else:
|
||||||
|
dir = dir.replace("/", ".") + "."
|
||||||
|
if pkg and pkg != "__main__":
|
||||||
|
dir = pkg + "." + dir
|
||||||
|
self.p = dir
|
||||||
|
|
||||||
|
def load(self, name):
|
||||||
|
name = name.replace(".", "_")
|
||||||
|
return __import__(self.p + name, None, None, (name,)).render
|
|
@ -0,0 +1,190 @@
|
||||||
|
# os module is loaded on demand
|
||||||
|
#import os
|
||||||
|
|
||||||
|
from . import compiled
|
||||||
|
|
||||||
|
|
||||||
|
class Compiler:
|
||||||
|
|
||||||
|
START_CHAR = "{"
|
||||||
|
STMNT = "%"
|
||||||
|
STMNT_END = "%}"
|
||||||
|
EXPR = "{"
|
||||||
|
EXPR_END = "}}"
|
||||||
|
|
||||||
|
def __init__(self, file_in, file_out, indent=0, seq=0, loader=None):
|
||||||
|
self.file_in = file_in
|
||||||
|
self.file_out = file_out
|
||||||
|
self.loader = loader
|
||||||
|
self.seq = seq
|
||||||
|
self._indent = indent
|
||||||
|
self.stack = []
|
||||||
|
self.in_literal = False
|
||||||
|
self.flushed_header = False
|
||||||
|
self.args = "*a, **d"
|
||||||
|
|
||||||
|
def indent(self, adjust=0):
|
||||||
|
if not self.flushed_header:
|
||||||
|
self.flushed_header = True
|
||||||
|
self.indent()
|
||||||
|
self.file_out.write("def render%s(%s):\n" % (str(self.seq) if self.seq else "", self.args))
|
||||||
|
self.stack.append("def")
|
||||||
|
self.file_out.write(" " * (len(self.stack) + self._indent + adjust))
|
||||||
|
|
||||||
|
def literal(self, s):
|
||||||
|
if not s:
|
||||||
|
return
|
||||||
|
if not self.in_literal:
|
||||||
|
self.indent()
|
||||||
|
self.file_out.write('yield """')
|
||||||
|
self.in_literal = True
|
||||||
|
self.file_out.write(s.replace('"', '\\"'))
|
||||||
|
|
||||||
|
def close_literal(self):
|
||||||
|
if self.in_literal:
|
||||||
|
self.file_out.write('"""\n')
|
||||||
|
self.in_literal = False
|
||||||
|
|
||||||
|
def render_expr(self, e):
|
||||||
|
self.indent()
|
||||||
|
self.file_out.write('yield str(' + e + ')\n')
|
||||||
|
|
||||||
|
def parse_statement(self, stmt):
|
||||||
|
tokens = stmt.split(None, 1)
|
||||||
|
if tokens[0] == "args":
|
||||||
|
if len(tokens) > 1:
|
||||||
|
self.args = tokens[1]
|
||||||
|
else:
|
||||||
|
self.args = ""
|
||||||
|
elif tokens[0] == "set":
|
||||||
|
self.indent()
|
||||||
|
self.file_out.write(stmt[3:].strip() + "\n")
|
||||||
|
elif tokens[0] == "include":
|
||||||
|
if not self.flushed_header:
|
||||||
|
# If there was no other output, we still need a header now
|
||||||
|
self.indent()
|
||||||
|
tokens = tokens[1].split(None, 1)
|
||||||
|
args = ""
|
||||||
|
if len(tokens) > 1:
|
||||||
|
args = tokens[1]
|
||||||
|
if tokens[0][0] == "{":
|
||||||
|
self.indent()
|
||||||
|
# "1" as fromlist param is uPy hack
|
||||||
|
self.file_out.write('_ = __import__(%s.replace(".", "_"), None, None, 1)\n' % tokens[0][2:-2])
|
||||||
|
self.indent()
|
||||||
|
self.file_out.write("yield from _.render(%s)\n" % args)
|
||||||
|
return
|
||||||
|
|
||||||
|
with self.loader.input_open(tokens[0][1:-1]) as inc:
|
||||||
|
self.seq += 1
|
||||||
|
c = Compiler(inc, self.file_out, len(self.stack) + self._indent, self.seq)
|
||||||
|
inc_id = self.seq
|
||||||
|
self.seq = c.compile()
|
||||||
|
self.indent()
|
||||||
|
self.file_out.write("yield from render%d(%s)\n" % (inc_id, args))
|
||||||
|
elif len(tokens) > 1:
|
||||||
|
if tokens[0] == "elif":
|
||||||
|
assert self.stack[-1] == "if"
|
||||||
|
self.indent(-1)
|
||||||
|
self.file_out.write(stmt + ":\n")
|
||||||
|
else:
|
||||||
|
self.indent()
|
||||||
|
self.file_out.write(stmt + ":\n")
|
||||||
|
self.stack.append(tokens[0])
|
||||||
|
else:
|
||||||
|
if stmt.startswith("end"):
|
||||||
|
assert self.stack[-1] == stmt[3:]
|
||||||
|
self.stack.pop(-1)
|
||||||
|
elif stmt == "else":
|
||||||
|
assert self.stack[-1] == "if"
|
||||||
|
self.indent(-1)
|
||||||
|
self.file_out.write("else:\n")
|
||||||
|
else:
|
||||||
|
assert False
|
||||||
|
|
||||||
|
def parse_line(self, l):
|
||||||
|
while l:
|
||||||
|
start = l.find(self.START_CHAR)
|
||||||
|
if start == -1:
|
||||||
|
self.literal(l)
|
||||||
|
return
|
||||||
|
self.literal(l[:start])
|
||||||
|
self.close_literal()
|
||||||
|
sel = l[start + 1]
|
||||||
|
#print("*%s=%s=" % (sel, EXPR))
|
||||||
|
if sel == self.STMNT:
|
||||||
|
end = l.find(self.STMNT_END)
|
||||||
|
assert end > 0
|
||||||
|
stmt = l[start + len(self.START_CHAR + self.STMNT):end].strip()
|
||||||
|
self.parse_statement(stmt)
|
||||||
|
end += len(self.STMNT_END)
|
||||||
|
l = l[end:]
|
||||||
|
if not self.in_literal and l == "\n":
|
||||||
|
break
|
||||||
|
elif sel == self.EXPR:
|
||||||
|
# print("EXPR")
|
||||||
|
end = l.find(self.EXPR_END)
|
||||||
|
assert end > 0
|
||||||
|
expr = l[start + len(self.START_CHAR + self.EXPR):end].strip()
|
||||||
|
self.render_expr(expr)
|
||||||
|
end += len(self.EXPR_END)
|
||||||
|
l = l[end:]
|
||||||
|
else:
|
||||||
|
self.literal(l[start])
|
||||||
|
l = l[start + 1:]
|
||||||
|
|
||||||
|
def header(self):
|
||||||
|
self.file_out.write("# Autogenerated file\n")
|
||||||
|
|
||||||
|
def compile(self):
|
||||||
|
self.header()
|
||||||
|
for l in self.file_in:
|
||||||
|
self.parse_line(l)
|
||||||
|
self.close_literal()
|
||||||
|
return self.seq
|
||||||
|
|
||||||
|
|
||||||
|
class Loader(compiled.Loader):
|
||||||
|
|
||||||
|
def __init__(self, pkg, dir):
|
||||||
|
super().__init__(pkg, dir)
|
||||||
|
self.dir = dir
|
||||||
|
if pkg == "__main__":
|
||||||
|
# if pkg isn't really a package, don't bother to use it
|
||||||
|
# it means we're running from "filesystem directory", not
|
||||||
|
# from a package.
|
||||||
|
pkg = None
|
||||||
|
|
||||||
|
self.pkg_path = ""
|
||||||
|
if pkg:
|
||||||
|
p = __import__(pkg)
|
||||||
|
if isinstance(p.__path__, str):
|
||||||
|
# uPy
|
||||||
|
self.pkg_path = p.__path__
|
||||||
|
else:
|
||||||
|
# CPy
|
||||||
|
self.pkg_path = p.__path__[0]
|
||||||
|
self.pkg_path += "/"
|
||||||
|
|
||||||
|
def input_open(self, template):
|
||||||
|
path = self.pkg_path + self.dir + "/" + template
|
||||||
|
return open(path)
|
||||||
|
|
||||||
|
def compiled_path(self, template):
|
||||||
|
return self.dir + "/" + template.replace(".", "_") + ".py"
|
||||||
|
|
||||||
|
def load(self, name):
|
||||||
|
try:
|
||||||
|
return super().load(name)
|
||||||
|
except (OSError, ImportError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
compiled_path = self.pkg_path + self.compiled_path(name)
|
||||||
|
|
||||||
|
f_in = self.input_open(name)
|
||||||
|
f_out = open(compiled_path, "w")
|
||||||
|
c = Compiler(f_in, f_out, loader=self)
|
||||||
|
c.compile()
|
||||||
|
f_in.close()
|
||||||
|
f_out.close()
|
||||||
|
return super().load(name)
|
|
@ -13,6 +13,11 @@ utilities for users of official MicroPython firmware to simplify installation.
|
||||||
Scripts for building MicroPython for various target hardware types and for
|
Scripts for building MicroPython for various target hardware types and for
|
||||||
updating your local source. See [docs](./fastbuild/README.md)
|
updating your local source. See [docs](./fastbuild/README.md)
|
||||||
|
|
||||||
|
# PicoWeb
|
||||||
|
|
||||||
|
[Easy installation](./PICOWEB.md) guide. Simplify installing this on
|
||||||
|
MicroPython hardware platforms under official MicroPython firmware.
|
||||||
|
|
||||||
# SSD1306
|
# SSD1306
|
||||||
|
|
||||||
A means of rendering multiple larger fonts to the SSD1306 OLED display. The
|
A means of rendering multiple larger fonts to the SSD1306 OLED display. The
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
MY_ID = '2\n'
|
MY_ID = '2\n'
|
||||||
#_SERVER = '192.168.0.35' # Laptop
|
#_SERVER = '192.168.0.35' # Laptop
|
||||||
SERVER = '192.168.0.33' # Pi
|
SERVER = '192.168.0.10' # Pi
|
||||||
PORT = 8123
|
PORT = 8123
|
||||||
|
|
Ładowanie…
Reference in New Issue