From ef110abebdcfdb6e2f47caeaa85e51eeb895b38d Mon Sep 17 00:00:00 2001 From: Gabriel Augendre Date: Mon, 13 Mar 2023 21:59:56 +0100 Subject: [PATCH] Add provisioning file. Close #5 --- README.md | 6 +- provisioning.yaml | 36 ++++++++++++ requirements.txt | 1 + tasks.py | 137 +++++++++++++++++++++++++++++++++------------- 4 files changed, 137 insertions(+), 43 deletions(-) create mode 100644 provisioning.yaml diff --git a/README.md b/README.md index 71a7e10..0bb521a 100644 --- a/README.md +++ b/README.md @@ -21,10 +21,8 @@ This will install dependencies required by PyCharm to run its MicroPython tools. invoke --list # Start by getting your board id inv list -# Then wipe the board -inv wipe -# Then run the initial setup -inv initial-setup +# Then provision the board +inv provision-all # After that, just update the code when changes are made locally inv update-code ``` diff --git a/provisioning.yaml b/provisioning.yaml new file mode 100644 index 0000000..a4e187a --- /dev/null +++ b/provisioning.yaml @@ -0,0 +1,36 @@ +e6614864d336b436: + HA_PLANT_ID: plant.peperomia_obtusifolia + HA_PLANT_MOISTURE_SENSOR: sensor.peperomia_obtusifolia_soil_moisture + HA_PLANT_TEMPERATURE_SENSOR: sensor.peperomia_obtusifolia_temperature + HA_PLANT_CONDUCTIVITY_SENSOR: sensor.peperomia_obtusifolia_conductivity + HA_PLANT_ILLUMINANCE_SENSOR: sensor.peperomia_obtusifolia_illuminance +e6614864d35f9934: + HA_PLANT_ID: plant.hedera_helix + HA_PLANT_MOISTURE_SENSOR: sensor.hedera_helix_soil_moisture + HA_PLANT_TEMPERATURE_SENSOR: sensor.hedera_helix_temperature + HA_PLANT_CONDUCTIVITY_SENSOR: sensor.hedera_helix_conductivity + HA_PLANT_ILLUMINANCE_SENSOR: sensor.hedera_helix_illuminance +e6614864d3417f36: + HA_PLANT_ID: plant.calathea_makoyana + HA_PLANT_MOISTURE_SENSOR: sensor.calathea_makoyana_soil_moisture + HA_PLANT_TEMPERATURE_SENSOR: sensor.calathea_makoyana_temperature + HA_PLANT_CONDUCTIVITY_SENSOR: sensor.calathea_makoyana_conductivity + HA_PLANT_ILLUMINANCE_SENSOR: sensor.calathea_makoyana_illuminance +ficus: + HA_PLANT_ID: plant.ficus + HA_PLANT_MOISTURE_SENSOR: sensor.ficus_soil_moisture + HA_PLANT_TEMPERATURE_SENSOR: sensor.ficus_temperature + HA_PLANT_CONDUCTIVITY_SENSOR: sensor.ficus_conductivity + HA_PLANT_ILLUMINANCE_SENSOR: sensor.ficus_illuminance +aloe: + HA_PLANT_ID: plant.aloe_vera + HA_PLANT_MOISTURE_SENSOR: sensor.aloe_vera_soil_moisture + HA_PLANT_TEMPERATURE_SENSOR: sensor.aloe_vera_temperature + HA_PLANT_CONDUCTIVITY_SENSOR: sensor.aloe_vera_conductivity + HA_PLANT_ILLUMINANCE_SENSOR: sensor.aloe_vera_illuminance +toupic: + HA_PLANT_ID: plant.senecio_himalaya + HA_PLANT_MOISTURE_SENSOR: sensor.senecio_himalaya_soil_moisture + HA_PLANT_TEMPERATURE_SENSOR: sensor.senecio_himalaya_temperature + HA_PLANT_CONDUCTIVITY_SENSOR: sensor.senecio_himalaya_conductivity + HA_PLANT_ILLUMINANCE_SENSOR: sensor.senecio_himalaya_illuminance diff --git a/requirements.txt b/requirements.txt index a9807b1..55c4abe 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,3 +4,4 @@ black pre-commit requests Pillow +pyyaml diff --git a/tasks.py b/tasks.py index 09a778c..fc52b5c 100644 --- a/tasks.py +++ b/tasks.py @@ -1,6 +1,9 @@ +import ast +import subprocess import time from pathlib import Path +import yaml from PIL import Image from invoke import task, Context @@ -13,41 +16,62 @@ MICROPYTHON_DEPENDENCIES = [ ] -@task -def wipe(c: Context, board_id: str): - """Wipe the board with mpremote.""" - c.run( - f'mpremote connect id:{board_id} exec --no-follow "' - "import os, machine, rp2;" - "os.umount('/');" - "bdev = rp2.Flash();" - "os.VfsLfs2.mkfs(bdev, progsize=256);" - "vfs = os.VfsLfs2(bdev, progsize=256);" - "os.mount(vfs, '/');" - 'machine.reset()"', - pty=True, - echo=True, - ) - print("Board wiped, waiting for it to reboot...") - time.sleep(3) - print("Done!") - - -@task -def list(c: Context): +@task(name="list") +def list_boards(c: Context) -> None: """List connected boards with mpremote.""" c.run("mpremote devs", pty=True, echo=True) @task -def download_image(c: Context): +def provision_all(c: Context) -> None: + """Provision all connected boards sequentially.""" + # Here's an example output of `mpremote devs`: + # /dev/cu.Bluetooth-Incoming-Port None 0000:0000 None None + # /dev/cu.usbmodem101 e6614864d35f9934 2e8a:0005 MicroPython Board in FS mode + # /dev/cu.usbmodem112201 e6614864d3417f36 2e8a:0005 MicroPython Board in FS mode + + output = subprocess.run(["mpremote", "devs"], stdout=subprocess.PIPE).stdout.decode( + "utf-8" + ) + lines = output.splitlines() + ids = [] + for line in lines: + if "Bluetooth" not in line: + ids.append(line.split()[1]) + + for board_id in ids: + provision(c, board_id) + + +@task +def provision(c: Context, board_id: str) -> None: + """Install dependencies and copy project files to the board.""" + prepare(board_id) + download_image(c, board_id) + wipe(c, board_id) + with c.cd(SRC_DIR): + if MICROPYTHON_DEPENDENCIES: + deps = " ".join(MICROPYTHON_DEPENDENCIES) + c.run( + f"mpremote connect id:{board_id} " f"mip install {deps}", + pty=True, + echo=True, + ) + update_code(c, board_id) + + +@task +def download_image(c: Context, board_id: str) -> None: + """Download and prepare the proper plant picture for the board.""" import requests import sys sys.path.insert(0, str(SRC_DIR)) - from secrets import HA_ACCESS_TOKEN, HA_BASE_URL, HA_PLANT_ID + from secrets import HA_ACCESS_TOKEN, HA_BASE_URL - url = HA_BASE_URL + "/states/" + HA_PLANT_ID + provisioning = get_provisioning(board_id) + + url = HA_BASE_URL + "/states/" + provisioning["HA_PLANT_ID"] headers = {"Authorization": "Bearer " + HA_ACCESS_TOKEN} res = requests.get(url, headers=headers) data = res.json() @@ -71,23 +95,28 @@ def download_image(c: Context): image.save(image_path) -@task(pre=[download_image]) -def initial_setup(c: Context, board_id: str): - """Install dependencies and copy project files to the board.""" - wipe(c, board_id) - with c.cd(SRC_DIR): - if MICROPYTHON_DEPENDENCIES: - deps = " ".join(MICROPYTHON_DEPENDENCIES) - c.run( - f"mpremote connect id:{board_id} " f"mip install {deps}", - pty=True, - echo=True, - ) - update_code(c, board_id) +@task +def wipe(c: Context, board_id: str) -> None: + """Wipe the board with mpremote.""" + c.run( + f'mpremote connect id:{board_id} exec --no-follow "' + "import os, machine, rp2;" + "os.umount('/');" + "bdev = rp2.Flash();" + "os.VfsLfs2.mkfs(bdev, progsize=256);" + "vfs = os.VfsLfs2(bdev, progsize=256);" + "os.mount(vfs, '/');" + 'machine.reset()"', + pty=True, + echo=True, + ) + print("Board wiped, waiting for it to reboot...") + time.sleep(3) + print("Done!") @task -def update_code(c: Context, board_id: str): +def update_code(c: Context, board_id: str) -> None: """Update code on the board.""" with c.cd(SRC_DIR): c.run("find . -name '.DS_Store' -delete", pty=True, echo=True) @@ -96,3 +125,33 @@ def update_code(c: Context, board_id: str): pty=True, echo=True, ) + + +def prepare(board_id: str) -> None: + """Update secrets.py with the correct values for the board.""" + provisioning = get_provisioning(board_id) + + with (SRC_DIR / "secrets.py").open() as f: + secrets = f.read() + + secrets = ast.parse(secrets) + for node in secrets.body: + if isinstance(node, ast.Assign): + for target in node.targets: + var_name = target.id + if var_name in provisioning: + node.value = ast.Constant(provisioning[var_name]) + + with (SRC_DIR / "secrets.py").open("w") as f: + f.write(ast.unparse(secrets)) + + +def get_provisioning(board_id: str) -> dict[str, str]: + # load provisioning.yaml + with (BASE_DIR / "provisioning.yaml").open() as f: + provisioning = yaml.safe_load(f) + provisioning = provisioning.get(board_id) + if not provisioning: + msg = "Couldn't find board %s in provisioning.yaml" % board_id + raise ValueError(msg) + return provisioning