Add provisioning file.

Close #5
This commit is contained in:
Gabriel Augendre 2023-03-13 21:59:56 +01:00
parent 884a9937bd
commit ef110abebd
4 changed files with 137 additions and 43 deletions

View file

@ -21,10 +21,8 @@ This will install dependencies required by PyCharm to run its MicroPython tools.
invoke --list invoke --list
# Start by getting your board id # Start by getting your board id
inv list inv list
# Then wipe the board # Then provision the board
inv wipe <board_id> inv provision-all
# Then run the initial setup
inv initial-setup <board_id>
# After that, just update the code when changes are made locally # After that, just update the code when changes are made locally
inv update-code <board_id> inv update-code <board_id>
``` ```

36
provisioning.yaml Normal file
View file

@ -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

View file

@ -4,3 +4,4 @@ black
pre-commit pre-commit
requests requests
Pillow Pillow
pyyaml

137
tasks.py
View file

@ -1,6 +1,9 @@
import ast
import subprocess
import time import time
from pathlib import Path from pathlib import Path
import yaml
from PIL import Image from PIL import Image
from invoke import task, Context from invoke import task, Context
@ -13,41 +16,62 @@ MICROPYTHON_DEPENDENCIES = [
] ]
@task @task(name="list")
def wipe(c: Context, board_id: str): def list_boards(c: Context) -> 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 list(c: Context):
"""List connected boards with mpremote.""" """List connected boards with mpremote."""
c.run("mpremote devs", pty=True, echo=True) c.run("mpremote devs", pty=True, echo=True)
@task @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 requests
import sys import sys
sys.path.insert(0, str(SRC_DIR)) 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} headers = {"Authorization": "Bearer " + HA_ACCESS_TOKEN}
res = requests.get(url, headers=headers) res = requests.get(url, headers=headers)
data = res.json() data = res.json()
@ -71,23 +95,28 @@ def download_image(c: Context):
image.save(image_path) image.save(image_path)
@task(pre=[download_image]) @task
def initial_setup(c: Context, board_id: str): def wipe(c: Context, board_id: str) -> None:
"""Install dependencies and copy project files to the board.""" """Wipe the board with mpremote."""
wipe(c, board_id) c.run(
with c.cd(SRC_DIR): f'mpremote connect id:{board_id} exec --no-follow "'
if MICROPYTHON_DEPENDENCIES: "import os, machine, rp2;"
deps = " ".join(MICROPYTHON_DEPENDENCIES) "os.umount('/');"
c.run( "bdev = rp2.Flash();"
f"mpremote connect id:{board_id} " f"mip install {deps}", "os.VfsLfs2.mkfs(bdev, progsize=256);"
pty=True, "vfs = os.VfsLfs2(bdev, progsize=256);"
echo=True, "os.mount(vfs, '/');"
) 'machine.reset()"',
update_code(c, board_id) pty=True,
echo=True,
)
print("Board wiped, waiting for it to reboot...")
time.sleep(3)
print("Done!")
@task @task
def update_code(c: Context, board_id: str): def update_code(c: Context, board_id: str) -> None:
"""Update code on the board.""" """Update code on the board."""
with c.cd(SRC_DIR): with c.cd(SRC_DIR):
c.run("find . -name '.DS_Store' -delete", pty=True, echo=True) 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, pty=True,
echo=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