diff --git a/.gitignore b/.gitignore index 3b25a6c..6f1357d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ build *.uf2 mpy-cross* +adafruit-circuitpython-bundle* diff --git a/build.py b/build.py old mode 100644 new mode 100755 index 9c76418..45facd0 --- a/build.py +++ b/build.py @@ -1,16 +1,17 @@ +#!/usr/bin/env python3 """ Builder script to compile .py files to .mpy bytecode using mpy-cross """ import glob -from subprocess import PIPE, Popen +from errno import ENOTDIR from os import listdir, makedirs from os.path import join, splitext -from shutil import copytree, copy, rmtree -from errno import ENOTDIR +from shutil import copy, copytree, rmtree +from subprocess import PIPE, Popen -SRC = 'src' -DST = 'build' +SRC = "src" +DST = "build" def recursive_copy(src: str, dst: str): @@ -58,20 +59,21 @@ def main(): # Remove the build directory if it exists, then create it again rmtree(DST, ignore_errors=True) makedirs(DST, exist_ok=True) + makedirs(join(DST, "lib"), exist_ok=True) -# Find the path of the mpy-cross binary + # Find the path of the mpy-cross binary mpy_cross_bin = join(".", glob.glob("mpy-cross.static*")[0]) -# Process each entry in the source directory + # Process each entry in the source directory for entry in listdir(SRC): src_path = join(SRC, entry) # If the entry is a Python source file that needs to be compiled if name := to_compile(entry): - # Compile the file using mpy-cross with Popen( - [mpy_cross_bin, "-o", join(DST, f"{name}.mpy"), src_path], - stdout=PIPE, + [mpy_cross_bin, "-o", + join(DST, "lib", f"{name}.mpy"), src_path], + stdout=PIPE, ) as process: process.communicate() else: diff --git a/src/api.py b/src/api.py index 315e327..8a2807e 100644 --- a/src/api.py +++ b/src/api.py @@ -1,9 +1,10 @@ """ Handler code to interact with the backend for each incoming web request """ -import json import os +from adafruit_httpserver import JSONResponse, Request + import logs from ducky import run_script, run_script_file @@ -16,25 +17,24 @@ def create(path, contents=b""): file.write(contents) -def handle(body, response): +def handle(request: Request): """ Handle all the API requests from the web interface like create, load, store, delete and run. """ + body = request.json() action = body["action"] if action == "list": - response.send(json.dumps(os.listdir("payloads"))) - return + return JSONResponse(request, os.listdir("payloads")) if action == "logs": - response.send(logs.consume()) - return + return JSONResponse(request, logs.consume()) filename = body.get("filename") path = f"payloads/{filename}" if action == "load": with open(path) as file: - response.send(json.dumps({"contents": file.read()})) + return JSONResponse(request, {"contents": file.read()}) elif action == "store": create(path, body["contents"].encode()) elif action == "delete": @@ -46,3 +46,4 @@ def handle(body, response): run_script_file(path) elif contents := body["contents"]: run_script(contents) + return JSONResponse(request, {}) diff --git a/src/code.py b/src/code.py index e2222f6..b7598c0 100644 --- a/src/code.py +++ b/src/code.py @@ -3,17 +3,12 @@ """ import asyncio -import json import os import microcontroller import socketpool import wifi -from adafruit_httpserver.methods import HTTPMethod -from adafruit_httpserver.mime_type import MIMEType -from adafruit_httpserver.request import HTTPRequest -from adafruit_httpserver.response import HTTPResponse -from adafruit_httpserver.server import HTTPServer +from adafruit_httpserver import POST, FileResponse, Request, Server from api import handle @@ -28,27 +23,23 @@ async def main(): """ wifi.radio.start_ap(ssid=os.getenv("SSID"), password=os.getenv("PASSWORD")) pool = socketpool.SocketPool(wifi.radio) - server = HTTPServer(pool) + server = Server(pool) @server.route("/") - def base(request: HTTPRequest): - with HTTPResponse(request, content_type=MIMEType.TYPE_HTML) as response: - response.send_file("static/index.html") + def base(request: Request): + return FileResponse(request, "index.html", root_path="/static") @server.route("/main.css") - def css(request: HTTPRequest): - with HTTPResponse(request, content_type=MIMEType.TYPE_CSS) as response: - response.send_file("static/main.css") + def css(request: Request): + return FileResponse(request, "main.css", root_path="/static") @server.route("/script.js") - def javascript(request: HTTPRequest): - with HTTPResponse(request, content_type=MIMEType.TYPE_JS) as response: - response.send_file("static/script.js") - - @server.route("/api", HTTPMethod.POST) - def api(request: HTTPRequest): - with HTTPResponse(request, content_type=MIMEType.TYPE_JSON) as response: - handle(json.loads(request.body), response) + def javascript(request: Request): + return FileResponse(request, "script.js", root_path="/static") + + @server.route("/api", POST) + def api(request: Request): + return handle(request) server.serve_forever(str(wifi.radio.ipv4_address_ap)) diff --git a/src/ducky.py b/src/ducky.py index b61249b..9babdac 100644 --- a/src/ducky.py +++ b/src/ducky.py @@ -54,15 +54,14 @@ def press_keys(line: str): Really useful for keyboard shortcuts like Meta+R. """ # loop on each key filtering empty values - for key in filter(None, line.split(" ")): - key = key.upper() + for key in filter(None, line.upper().split(" ")): if command_keycode := Keycode.__dict__.get(key): # If this is a valid key, send its keycode kbd.press(command_keycode) continue # If it's not a known key name, log it for diagnosis warn(f"unknown key: <{key}>") - kbd.release_all() + kbd.release_all() def repeat(contents: str, times: int): @@ -98,7 +97,7 @@ def run_script(contents): elif path := after("IMPORT"): run_script_file(path) elif millis := after("DEFAULT_DELAY", "DEFAULTDELAY"): - default_delay = int(millis) * 10 + default_delay = int(millis) elif after("LED") is not None: LED.value ^= True elif string := after("STRING"): diff --git a/src/logs.py b/src/logs.py index 08a0425..9f3b2d7 100644 --- a/src/logs.py +++ b/src/logs.py @@ -3,8 +3,6 @@ for the bottom pane of the Web UI. """ -import json - logs = [] @@ -13,7 +11,7 @@ def consume() -> str: Convert all the log entries from the module's global mutable list to json return them, clearing the list after the dump. """ - dump = json.dumps(logs) + dump = logs.copy() logs.clear() return dump