From 7778903b91e09c91f2079d6cae30d2f583e11911 Mon Sep 17 00:00:00 2001 From: Ethan Li Date: Wed, 7 May 2025 23:27:38 -0700 Subject: [PATCH 01/17] Unify hardware controller variants into one entrypoint script --- device-backend/control/main.py | 37 +++++++++++ device-backend/control/poetry.lock | 65 ++++++++++++++++++- device-backend/control/pyproject.toml | 1 + ...backend.controller-planktoscopehat.service | 18 ----- ...ope-org.device-backend.controller.service} | 4 +- .../node-red-dashboard/adafruithat/flows.json | 4 +- .../planktoscopehat/flows.json | 4 +- software/node-red-dashboard/settings.js | 1 + 8 files changed, 109 insertions(+), 25 deletions(-) create mode 100644 device-backend/control/main.py delete mode 100644 software/distro/setup/planktoscope-app-env/python-hardware-controller/etc/systemd/system/planktoscope-org.device-backend.controller-planktoscopehat.service rename software/distro/setup/planktoscope-app-env/python-hardware-controller/etc/systemd/system/{planktoscope-org.device-backend.controller-adafruithat.service => planktoscope-org.device-backend.controller.service} (75%) diff --git a/device-backend/control/main.py b/device-backend/control/main.py new file mode 100644 index 000000000..dc55db35c --- /dev/null +++ b/device-backend/control/main.py @@ -0,0 +1,37 @@ +import typing + +import loguru +import yaml + +# TODO: instead check `~/PlanktoScope/config.json`'s `acq_instrument` field? +INSTALLER_CONFIG_FILE = "/usr/share/planktoscope/installer-config.yml" + + +def determine_variant(config_file: str): + with open(config_file, "r") as file: + variant: typing.Optional[str] = None + try: + config = yaml.safe_load(file) + variant = config.hardware + return variant + except yaml.YAMLError: + loguru.logger.exception( + f"Couldn't parse {INSTALLER_CONFIG_FILE} as YAML file" + ) + + +def main(): + loguru.logger.info("Determining configured hardware variant...") + variant = determine_variant(INSTALLER_CONFIG_FILE) + loguru.logger.info(f"Hardware variant: {variant}") + # Note: once the `main.py` files are rewritten to have a main() function, we can import the + # appropriate module and invoke its main function. For now, we have to do an `exec`: + with open(f"{variant}/main.py") as script: + exec(script.read()) + + +if __name__ == "__main__": + try: + main() + except Exception: + loguru.logger.exception("Unhandled exception") diff --git a/device-backend/control/poetry.lock b/device-backend/control/poetry.lock index 712bc3c1a..143e3d27c 100644 --- a/device-backend/control/poetry.lock +++ b/device-backend/control/poetry.lock @@ -1212,6 +1212,69 @@ files = [ {file = "pyusb-1.2.1.tar.gz", hash = "sha256:a4cc7404a203144754164b8b40994e2849fde1cfff06b08492f12fff9d9de7b9"}, ] +[[package]] +name = "pyyaml" +version = "6.0.2" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, + {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, + {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, + {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, + {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, + {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, + {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, + {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, + {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, + {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, + {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, + {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, + {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, + {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, + {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, + {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, + {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, +] + [[package]] name = "radon" version = "5.1.0" @@ -1662,4 +1725,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.1" python-versions = ">=3.9.2" -content-hash = "af71a8926af5aeb4674154f5f5313854af50e44c2a6c7f3d4573fa156ff30511" +content-hash = "3d4a5233b38bd749b77fdb7c5d1e81026e21a1bc1ce2ec0412ab9fe832ad2124" diff --git a/device-backend/control/pyproject.toml b/device-backend/control/pyproject.toml index 0fe1bad56..b5aaf7685 100644 --- a/device-backend/control/pyproject.toml +++ b/device-backend/control/pyproject.toml @@ -36,6 +36,7 @@ python = ">=3.9.2" paho-mqtt = "^1.6.1" loguru = "^0.5.3" readerwriterlock = "^1.0.9" +pyyaml = "^6.0.2" [tool.poetry.group.hw.dependencies] rpi-gpio = "^0.7.0" diff --git a/software/distro/setup/planktoscope-app-env/python-hardware-controller/etc/systemd/system/planktoscope-org.device-backend.controller-planktoscopehat.service b/software/distro/setup/planktoscope-app-env/python-hardware-controller/etc/systemd/system/planktoscope-org.device-backend.controller-planktoscopehat.service deleted file mode 100644 index d271e77b4..000000000 --- a/software/distro/setup/planktoscope-app-env/python-hardware-controller/etc/systemd/system/planktoscope-org.device-backend.controller-planktoscopehat.service +++ /dev/null @@ -1,18 +0,0 @@ -[Unit] -Description=Run the PlanktoScope hardware controller for the PlanktoScope HAT -# Wait for Forklift to bring up mosquitto, in case the controller needs to start after mosquitto -Wants=forklift-apply.service -After=forklift-apply.service -# Start after Node-RED so that Node-RED can catch all MQTT messages emitted at startup -After=nodered.service - -[Service] -Type=simple -Environment=HOME=/home/pi -WorkingDirectory=/home/pi/PlanktoScope/device-backend/control -ExecStart=/home/pi/.local/bin/poetry run python -u planktoscopehat/main.py -User=pi -Group=pi - -[Install] -WantedBy=multi-user.target diff --git a/software/distro/setup/planktoscope-app-env/python-hardware-controller/etc/systemd/system/planktoscope-org.device-backend.controller-adafruithat.service b/software/distro/setup/planktoscope-app-env/python-hardware-controller/etc/systemd/system/planktoscope-org.device-backend.controller.service similarity index 75% rename from software/distro/setup/planktoscope-app-env/python-hardware-controller/etc/systemd/system/planktoscope-org.device-backend.controller-adafruithat.service rename to software/distro/setup/planktoscope-app-env/python-hardware-controller/etc/systemd/system/planktoscope-org.device-backend.controller.service index ce32e4a51..d49cc1d88 100644 --- a/software/distro/setup/planktoscope-app-env/python-hardware-controller/etc/systemd/system/planktoscope-org.device-backend.controller-adafruithat.service +++ b/software/distro/setup/planktoscope-app-env/python-hardware-controller/etc/systemd/system/planktoscope-org.device-backend.controller.service @@ -1,5 +1,5 @@ [Unit] -Description=Run the PlanktoScope hardware controller for the Adafruit HAT +Description=Run the PlanktoScope hardware controller # Wait for Forklift to bring up mosquitto, in case the controller needs to start after mosquitto Wants=forklift-apply.service After=forklift-apply.service @@ -10,7 +10,7 @@ After=nodered.service Type=simple Environment=HOME=/home/pi WorkingDirectory=/home/pi/PlanktoScope/device-backend/control -ExecStart=/home/pi/.local/bin/poetry run python -u adafruithat/main.py +ExecStart=/home/pi/.local/bin/poetry run python -u main.py User=pi Group=pi diff --git a/software/node-red-dashboard/adafruithat/flows.json b/software/node-red-dashboard/adafruithat/flows.json index 96d774c43..8e03caceb 100644 --- a/software/node-red-dashboard/adafruithat/flows.json +++ b/software/node-red-dashboard/adafruithat/flows.json @@ -7118,7 +7118,7 @@ "id": "bd5cceef.b17ad", "type": "exec", "z": "9daf9e2b.019fc", - "command": "sudo systemctl restart planktoscope-org.device-backend.controller-adafruithat.service", + "command": "sudo systemctl restart planktoscope-org.device-backend.controller.service", "addpay": false, "append": "", "useSpawn": "false", @@ -9096,4 +9096,4 @@ ] ] } -] \ No newline at end of file +] diff --git a/software/node-red-dashboard/planktoscopehat/flows.json b/software/node-red-dashboard/planktoscopehat/flows.json index 56f7062c3..6a91df283 100644 --- a/software/node-red-dashboard/planktoscopehat/flows.json +++ b/software/node-red-dashboard/planktoscopehat/flows.json @@ -7298,7 +7298,7 @@ "id": "c54d948575dbcff8", "type": "exec", "z": "9daf9e2b.019fc", - "command": "sudo systemctl restart planktoscope-org.device-backend.controller-planktoscopehat.service", + "command": "sudo systemctl restart planktoscope-org.device-backend.controller.service", "addpay": false, "append": "", "useSpawn": "false", @@ -8960,4 +8960,4 @@ "y": 120, "wires": [] } -] \ No newline at end of file +] diff --git a/software/node-red-dashboard/settings.js b/software/node-red-dashboard/settings.js index f613732a8..7b00a485c 100644 --- a/software/node-red-dashboard/settings.js +++ b/software/node-red-dashboard/settings.js @@ -5,6 +5,7 @@ const fs = require("fs"); const path = require("path"); const doc = yaml.load( + // TODO: instead check `~/PlanktoScope/config.json`? fs.readFileSync("/usr/share/planktoscope/installer-config.yml", "utf8"), ); const userDir = path.join(__dirname, doc.hardware); From 5f6e31d91c7b8c8f7ea8db3d2e47beaba83d10f0 Mon Sep 17 00:00:00 2001 From: Ethan Li Date: Wed, 7 May 2025 23:55:27 -0700 Subject: [PATCH 02/17] Update hardware controller install script --- .../python-hardware-controller/install.sh | 23 ++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/software/distro/setup/planktoscope-app-env/python-hardware-controller/install.sh b/software/distro/setup/planktoscope-app-env/python-hardware-controller/install.sh index db199f9d2..fadc56baf 100755 --- a/software/distro/setup/planktoscope-app-env/python-hardware-controller/install.sh +++ b/software/distro/setup/planktoscope-app-env/python-hardware-controller/install.sh @@ -11,14 +11,14 @@ hardware_type="$1" # should be either adafruithat, planktoscopehat, or fairscope default_config="$hardware_type-latest" case "$hardware_type" in "fairscope-latest") - hardware_type="planktoscopehat" - default_config="fairscope-latest" - ;; + hardware_type="planktoscopehat" + default_config="fairscope-latest" + ;; esac ## Install basic tooling sudo -E apt-get install -y -o Dpkg::Progress-Fancy=0 \ - git python3-pip python3-venv pipx + git python3-pip python3-venv pipx # Suppress keyring dialogs when setting up the PlanktoScope distro on a graphical desktop # (see https://github.com/pypa/pip/issues/7883) @@ -38,19 +38,16 @@ PATH="$PATH:/home/pi/.local/bin" # Raspberry Pi OS has been updated. In which case this can be removed. echo "If the next command fails, see comment in install.sh" sudo -E apt-get install -y -o Dpkg::Progress-Fancy=0 --only-upgrade \ - python3-libcamera=0.5.0+rpt20250429-1 python3-av=12.3.0-2+rpt1 + python3-libcamera=0.5.0+rpt20250429-1 python3-av=12.3.0-2+rpt1 sudo -E apt-get install -y --no-install-recommends -o Dpkg::Progress-Fancy=0 \ - i2c-tools libopenjp2-7 python3-picamera2 + i2c-tools libopenjp2-7 python3-picamera2 poetry --directory "$HOME/PlanktoScope/device-backend/control" install \ - --no-root --compile -file="/etc/systemd/system/planktoscope-org.device-backend.controller-adafruithat.service" -sudo cp "$config_files_root$file" "$file" -# or for the PlanktoScope HAT -file="/etc/systemd/system/planktoscope-org.device-backend.controller-planktoscopehat.service" + --no-root --compile +file="/etc/systemd/system/planktoscope-org.device-backend.controller.service" sudo cp "$config_files_root$file" "$file" +sudo systemctl enable "planktoscope-org.device-backend.controller.service" # Select the enabled hardware controller mkdir -p "$HOME/PlanktoScope" -sudo systemctl enable "planktoscope-org.device-backend.controller-$hardware_type.service" cp "$HOME/PlanktoScope/device-backend/default-configs/$default_config.hardware.json" \ - "$HOME/PlanktoScope/hardware.json" + "$HOME/PlanktoScope/hardware.json" From 07ec0301fd585caa6dff660d9217bf8cbeb4d9bf Mon Sep 17 00:00:00 2001 From: Ethan Li Date: Thu, 8 May 2025 01:09:32 -0700 Subject: [PATCH 03/17] Fix syntax error --- device-backend/control/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/device-backend/control/main.py b/device-backend/control/main.py index dc55db35c..47d7e0569 100644 --- a/device-backend/control/main.py +++ b/device-backend/control/main.py @@ -12,7 +12,7 @@ def determine_variant(config_file: str): variant: typing.Optional[str] = None try: config = yaml.safe_load(file) - variant = config.hardware + variant = config["hardware"] return variant except yaml.YAMLError: loguru.logger.exception( From 15b7baf3393b56a087b8b7a474ea9d9e7fd8244b Mon Sep 17 00:00:00 2001 From: Ethan Li Date: Thu, 8 May 2025 01:54:29 -0700 Subject: [PATCH 04/17] Try to fix module import issues --- device-backend/control/adafruithat/__init__.py | 0 device-backend/control/adafruithat/main.py | 18 ++++++------------ .../adafruithat/planktoscope/camera/mqtt.py | 4 ++-- .../planktoscope/camera/test_preview.py | 2 +- .../adafruithat/planktoscope/display.py | 9 +++------ .../adafruithat/planktoscope/imager/mqtt.py | 6 +++--- .../adafruithat/planktoscope/stepper.py | 18 +++++++++--------- .../control/planktoscopehat/__init__.py | 0 device-backend/control/planktoscopehat/main.py | 18 ++++++------------ .../planktoscope/camera/mqtt.py | 2 +- .../planktoscope/camera/test_preview.py | 2 +- .../planktoscopehat/planktoscope/focus.py | 2 +- .../planktoscope/imager/mqtt.py | 4 ++-- .../planktoscopehat/planktoscope/module.py | 6 +++--- .../planktoscopehat/planktoscope/pump.py | 2 +- 15 files changed, 39 insertions(+), 54 deletions(-) create mode 100644 device-backend/control/adafruithat/__init__.py create mode 100644 device-backend/control/planktoscopehat/__init__.py diff --git a/device-backend/control/adafruithat/__init__.py b/device-backend/control/adafruithat/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/device-backend/control/adafruithat/main.py b/device-backend/control/adafruithat/main.py index f72c8e799..4953eef11 100644 --- a/device-backend/control/adafruithat/main.py +++ b/device-backend/control/adafruithat/main.py @@ -23,12 +23,8 @@ from loguru import logger # for logging with multiprocessing -import planktoscope.mqtt -import planktoscope.stepper -import planktoscope.light # Fan HAT LEDs -import planktoscope.identity -import planktoscope.display # Fan HAT OLED screen -from planktoscope.imager import mqtt as imager +from adafruithat.planktoscope import stepper, light, identity, display +from adafruithat.planktoscope.imager import mqtt as imager # enqueue=True is necessary so we can log across modules # rotation happens everyday at 01:00 if not restarted @@ -97,9 +93,7 @@ def handler_stop_signals(signum, _): # create the path! os.makedirs(img_path) - logger.info( - f"This PlanktoScope's machine name is {planktoscope.identity.load_machine_name()}" - ) + logger.info(f"This PlanktoScope's machine name is {identity.load_machine_name()}") # Prepare the event for a graceful shutdown shutdown_event = multiprocessing.Event() @@ -107,7 +101,7 @@ def handler_stop_signals(signum, _): # Starts the stepper process for actuators logger.info("Starting the stepper control process (step 2/4)") - stepper_thread = planktoscope.stepper.StepperProcess(shutdown_event) + stepper_thread = stepper.StepperProcess(shutdown_event) stepper_thread.start() # Starts the imager control process @@ -121,10 +115,10 @@ def handler_stop_signals(signum, _): imager_thread.start() logger.info("Starting the display module (step 4/4)") - display = planktoscope.display.Display() + display = display.Display() logger.success("Looks like the controller is set up and running, have fun!") - planktoscope.light.ready() + light.ready() while run: # TODO look into ways of restarting the dead threads diff --git a/device-backend/control/adafruithat/planktoscope/camera/mqtt.py b/device-backend/control/adafruithat/planktoscope/camera/mqtt.py index 2a00b4bc4..ee2df81cd 100644 --- a/device-backend/control/adafruithat/planktoscope/camera/mqtt.py +++ b/device-backend/control/adafruithat/planktoscope/camera/mqtt.py @@ -8,8 +8,8 @@ import loguru -from planktoscope import mqtt as messaging -from planktoscope.camera import hardware, mjpeg +from .. import mqtt as messaging +from . import hardware, mjpeg loguru.logger.info("planktoscope.camera is loaded") diff --git a/device-backend/control/adafruithat/planktoscope/camera/test_preview.py b/device-backend/control/adafruithat/planktoscope/camera/test_preview.py index 62e2c9886..dcf450dcc 100644 --- a/device-backend/control/adafruithat/planktoscope/camera/test_preview.py +++ b/device-backend/control/adafruithat/planktoscope/camera/test_preview.py @@ -8,7 +8,7 @@ import picamera2 # type: ignore from picamera2 import encoders, outputs -from planktoscope.camera import hardware, mjpeg +from . import hardware, mjpeg def main() -> None: diff --git a/device-backend/control/adafruithat/planktoscope/display.py b/device-backend/control/adafruithat/planktoscope/display.py index 2e002d315..49e3d46ef 100644 --- a/device-backend/control/adafruithat/planktoscope/display.py +++ b/device-backend/control/adafruithat/planktoscope/display.py @@ -18,18 +18,15 @@ # Logger library compatible with multiprocessing from loguru import logger -import datetime -import os - import Adafruit_SSD1306 import PIL.Image import PIL.ImageDraw import PIL.ImageFont -logger.info("planktoscope.display is loading") +from . import identity -import planktoscope.identity +logger.info("planktoscope.display is loading") class Display(object): @@ -54,7 +51,7 @@ def __init__(self): def display_machine_name(self): if self.display_available: self.__clear() - machineName = planktoscope.identity.load_machine_name() + machineName = identity.load_machine_name() self.display_text(machineName.replace(" ", "\n")) def display_text(self, message): diff --git a/device-backend/control/adafruithat/planktoscope/imager/mqtt.py b/device-backend/control/adafruithat/planktoscope/imager/mqtt.py index c7516d2ae..35b91b03a 100644 --- a/device-backend/control/adafruithat/planktoscope/imager/mqtt.py +++ b/device-backend/control/adafruithat/planktoscope/imager/mqtt.py @@ -10,9 +10,9 @@ import loguru -from planktoscope import identity, integrity, mqtt -from planktoscope.camera import mqtt as camera -from planktoscope.imager import stopflow +from .. import identity, integrity, mqtt +from ..camera import mqtt as camera +from . import stopflow loguru.logger.info("planktoscope.imager is loaded") diff --git a/device-backend/control/adafruithat/planktoscope/stepper.py b/device-backend/control/adafruithat/planktoscope/stepper.py index d21b1ea54..cb88e2f2c 100644 --- a/device-backend/control/adafruithat/planktoscope/stepper.py +++ b/device-backend/control/adafruithat/planktoscope/stepper.py @@ -21,11 +21,11 @@ import time import json import os -import planktoscope.mqtt -import planktoscope.light import multiprocessing import RPi.GPIO +from . import mqtt, light + # Logger library compatible with multiprocessing from loguru import logger @@ -203,11 +203,11 @@ def __message_pump(self, last_message): "status/pump", '{"status":"Interrupted"}' ) - planktoscope.light.ready() + light.ready() elif last_message["action"] == "move": logger.debug("We have received a move pump command") - planktoscope.light.pumping() + light.pumping() if ( "direction" not in last_message @@ -256,11 +256,11 @@ def __message_focus(self, last_message): "status/focus", '{"status":"Interrupted"}' ) - planktoscope.light.ready() + light.ready() elif last_message["action"] == "move": logger.debug("We have received a move focus command") - planktoscope.light.focusing() + light.focusing() if "direction" not in last_message or "distance" not in last_message: logger.error( @@ -422,7 +422,7 @@ def run(self): # it doesn't see changes and calls made by self.actuator_client because this one # only exist in the master process # see https://stackoverflow.com/questions/17172878/using-pythons-multiprocessing-process-class - self.actuator_client = planktoscope.mqtt.MQTT_Client( + self.actuator_client = mqtt.MQTT_Client( topic="actuator/#", name="actuator_client" ) # Publish the status "Ready" to via MQTT to Node-RED @@ -437,14 +437,14 @@ def run(self): self.treat_command() if self.pump_stepper.move(): delay = 0.0001 - planktoscope.light.ready() + light.ready() self.actuator_client.client.publish( "status/pump", '{"status":"Done"}', ) if self.focus_stepper.move(): delay = 0.0001 - planktoscope.light.ready() + light.ready() self.actuator_client.client.publish( "status/focus", '{"status":"Done"}', diff --git a/device-backend/control/planktoscopehat/__init__.py b/device-backend/control/planktoscopehat/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/device-backend/control/planktoscopehat/main.py b/device-backend/control/planktoscopehat/main.py index a65f7b83c..40f69fcea 100644 --- a/device-backend/control/planktoscopehat/main.py +++ b/device-backend/control/planktoscopehat/main.py @@ -6,12 +6,8 @@ from loguru import logger -import planktoscope.mqtt -import planktoscope.pump -import planktoscope.focus -import planktoscope.light -import planktoscope.identity -from planktoscope.imager import mqtt as imager +from planktoscopehat.planktoscope import pump, focus, light, identity +from planktoscopehat.planktoscope.imager import mqtt as imager # enqueue=True is necessary so we can log across modules # rotation happens everyday at 01:00 if not restarted @@ -80,9 +76,7 @@ def handler_stop_signals(signum, frame): # create the path! os.makedirs(img_path) - logger.info( - f"This PlanktoScope's machine name is {planktoscope.identity.load_machine_name()}" - ) + logger.info(f"This PlanktoScope's machine name is {identity.load_machine_name()}") # Prepare the event for a graceful shutdown shutdown_event = multiprocessing.Event() @@ -90,12 +84,12 @@ def handler_stop_signals(signum, frame): # Starts the pump process logger.info("Starting the pump control process (step 2/5)") - pump_thread = planktoscope.pump.PumpProcess(shutdown_event) + pump_thread = pump.PumpProcess(shutdown_event) pump_thread.start() # Starts the focus process logger.info("Starting the focus control process (step 3/5)") - focus_thread = planktoscope.focus.FocusProcess(shutdown_event) + focus_thread = focus.FocusProcess(shutdown_event) focus_thread.start() # TODO try to isolate the imager thread (or another thread) @@ -112,7 +106,7 @@ def handler_stop_signals(signum, frame): # Starts the light process logger.info("Starting the light control process (step 5/5)") try: - light_thread = planktoscope.light.LightProcess(shutdown_event) + light_thread = light.LightProcess(shutdown_event) except Exception: logger.error("The light control process could not be started") light_thread = None diff --git a/device-backend/control/planktoscopehat/planktoscope/camera/mqtt.py b/device-backend/control/planktoscopehat/planktoscope/camera/mqtt.py index 2a00b4bc4..24cfe99b0 100644 --- a/device-backend/control/planktoscopehat/planktoscope/camera/mqtt.py +++ b/device-backend/control/planktoscopehat/planktoscope/camera/mqtt.py @@ -9,7 +9,7 @@ import loguru from planktoscope import mqtt as messaging -from planktoscope.camera import hardware, mjpeg +from . import hardware, mjpeg loguru.logger.info("planktoscope.camera is loaded") diff --git a/device-backend/control/planktoscopehat/planktoscope/camera/test_preview.py b/device-backend/control/planktoscopehat/planktoscope/camera/test_preview.py index 62e2c9886..dcf450dcc 100644 --- a/device-backend/control/planktoscopehat/planktoscope/camera/test_preview.py +++ b/device-backend/control/planktoscopehat/planktoscope/camera/test_preview.py @@ -8,7 +8,7 @@ import picamera2 # type: ignore from picamera2 import encoders, outputs -from planktoscope.camera import hardware, mjpeg +from . import hardware, mjpeg def main() -> None: diff --git a/device-backend/control/planktoscopehat/planktoscope/focus.py b/device-backend/control/planktoscopehat/planktoscope/focus.py index 13f3319d9..ba5384605 100644 --- a/device-backend/control/planktoscopehat/planktoscope/focus.py +++ b/device-backend/control/planktoscopehat/planktoscope/focus.py @@ -5,7 +5,7 @@ from loguru import logger -from planktoscope.stepper import stepper +from .stepper import stepper from . import mqtt diff --git a/device-backend/control/planktoscopehat/planktoscope/imager/mqtt.py b/device-backend/control/planktoscopehat/planktoscope/imager/mqtt.py index c7516d2ae..52030ff06 100644 --- a/device-backend/control/planktoscopehat/planktoscope/imager/mqtt.py +++ b/device-backend/control/planktoscopehat/planktoscope/imager/mqtt.py @@ -11,8 +11,8 @@ import loguru from planktoscope import identity, integrity, mqtt -from planktoscope.camera import mqtt as camera -from planktoscope.imager import stopflow +from ..camera import mqtt as camera +from . import stopflow loguru.logger.info("planktoscope.imager is loaded") diff --git a/device-backend/control/planktoscopehat/planktoscope/module.py b/device-backend/control/planktoscopehat/planktoscope/module.py index 8cf3bbf5d..93a09d0ca 100644 --- a/device-backend/control/planktoscopehat/planktoscope/module.py +++ b/device-backend/control/planktoscopehat/planktoscope/module.py @@ -10,7 +10,7 @@ import multiprocessing # Basic planktoscope communication libraries -import planktoscope.mqtt +from . import mqtt logger.info("planktoscope.module is loaded") @@ -69,7 +69,7 @@ def run(self): ) # MQTT Service connection - self.module_client = planktoscope.mqtt.MQTT_Client( + self.module_client = mqtt.MQTT_Client( topic="module/#", name="module_client" ) @@ -93,4 +93,4 @@ def run(self): # This is called if this script is launched directly if __name__ == "__main__": # This should be tests of your module - pass \ No newline at end of file + pass diff --git a/device-backend/control/planktoscopehat/planktoscope/pump.py b/device-backend/control/planktoscopehat/planktoscope/pump.py index 1e4b666f6..a820441c1 100644 --- a/device-backend/control/planktoscopehat/planktoscope/pump.py +++ b/device-backend/control/planktoscopehat/planktoscope/pump.py @@ -5,7 +5,7 @@ from loguru import logger -from planktoscope.stepper import stepper +from .stepper import stepper from . import mqtt From cec68978d63cf6672eb6b3bdc862c8d0f347b581 Mon Sep 17 00:00:00 2001 From: Ethan Li Date: Thu, 8 May 2025 01:59:26 -0700 Subject: [PATCH 05/17] Run the autoformatter --- device-backend/control/main.py | 4 +--- .../control/planktoscopehat/planktoscope/camera/mqtt.py | 1 + device-backend/control/planktoscopehat/planktoscope/focus.py | 3 +-- device-backend/control/planktoscopehat/planktoscope/pump.py | 3 +-- 4 files changed, 4 insertions(+), 7 deletions(-) diff --git a/device-backend/control/main.py b/device-backend/control/main.py index 47d7e0569..631fd2542 100644 --- a/device-backend/control/main.py +++ b/device-backend/control/main.py @@ -15,9 +15,7 @@ def determine_variant(config_file: str): variant = config["hardware"] return variant except yaml.YAMLError: - loguru.logger.exception( - f"Couldn't parse {INSTALLER_CONFIG_FILE} as YAML file" - ) + loguru.logger.exception(f"Couldn't parse {INSTALLER_CONFIG_FILE} as YAML file") def main(): diff --git a/device-backend/control/planktoscopehat/planktoscope/camera/mqtt.py b/device-backend/control/planktoscopehat/planktoscope/camera/mqtt.py index 24cfe99b0..e9e4ba7e7 100644 --- a/device-backend/control/planktoscopehat/planktoscope/camera/mqtt.py +++ b/device-backend/control/planktoscopehat/planktoscope/camera/mqtt.py @@ -9,6 +9,7 @@ import loguru from planktoscope import mqtt as messaging + from . import hardware, mjpeg loguru.logger.info("planktoscope.camera is loaded") diff --git a/device-backend/control/planktoscopehat/planktoscope/focus.py b/device-backend/control/planktoscopehat/planktoscope/focus.py index ba5384605..52d65c6d1 100644 --- a/device-backend/control/planktoscopehat/planktoscope/focus.py +++ b/device-backend/control/planktoscopehat/planktoscope/focus.py @@ -5,9 +5,8 @@ from loguru import logger -from .stepper import stepper - from . import mqtt +from .stepper import stepper logger.info("planktoscope.focus is loaded") diff --git a/device-backend/control/planktoscopehat/planktoscope/pump.py b/device-backend/control/planktoscopehat/planktoscope/pump.py index a820441c1..42afcc9e4 100644 --- a/device-backend/control/planktoscopehat/planktoscope/pump.py +++ b/device-backend/control/planktoscopehat/planktoscope/pump.py @@ -5,9 +5,8 @@ from loguru import logger -from .stepper import stepper - from . import mqtt +from .stepper import stepper logger.info("planktoscope.pump is loaded") From 265c42fe84f0bf594a42a04fa672dadafb52e099 Mon Sep 17 00:00:00 2001 From: Ethan Li Date: Thu, 8 May 2025 02:04:14 -0700 Subject: [PATCH 06/17] Fix incorrect import, run the autoformatter again --- .../control/planktoscopehat/planktoscope/camera/mqtt.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/device-backend/control/planktoscopehat/planktoscope/camera/mqtt.py b/device-backend/control/planktoscopehat/planktoscope/camera/mqtt.py index e9e4ba7e7..3357826f2 100644 --- a/device-backend/control/planktoscopehat/planktoscope/camera/mqtt.py +++ b/device-backend/control/planktoscopehat/planktoscope/camera/mqtt.py @@ -8,8 +8,7 @@ import loguru -from planktoscope import mqtt as messaging - +from .. import mqtt as messaging from . import hardware, mjpeg loguru.logger.info("planktoscope.camera is loaded") @@ -163,7 +162,9 @@ def _receive_message(self, message: dict[str, typing.Any]) -> typing.Optional[st settings = message["payload"]["settings"] try: converted_settings = _convert_settings( - settings, self._camera.settings.white_balance_gains, self._camera.sensor_name + settings, + self._camera.settings.white_balance_gains, + self._camera.sensor_name, ) _validate_settings(converted_settings) except (TypeError, ValueError) as e: From b6c0c49f15715464e3a3544eadd7abcc191077e0 Mon Sep 17 00:00:00 2001 From: Ethan Li Date: Thu, 8 May 2025 08:53:26 -0700 Subject: [PATCH 07/17] Make `fairscope-latest` image work --- device-backend/control/main.py | 8 +++++++- .../control/planktoscopehat/planktoscope/imager/mqtt.py | 2 +- software/node-red-dashboard/settings.js | 6 +++++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/device-backend/control/main.py b/device-backend/control/main.py index 631fd2542..d415078e6 100644 --- a/device-backend/control/main.py +++ b/device-backend/control/main.py @@ -6,6 +6,8 @@ # TODO: instead check `~/PlanktoScope/config.json`'s `acq_instrument` field? INSTALLER_CONFIG_FILE = "/usr/share/planktoscope/installer-config.yml" +# FIXME: move loguru configuration to here instead + def determine_variant(config_file: str): with open(config_file, "r") as file: @@ -13,9 +15,13 @@ def determine_variant(config_file: str): try: config = yaml.safe_load(file) variant = config["hardware"] + if variant == "fairscope-latest": + variant = "planktoscopehat" return variant except yaml.YAMLError: - loguru.logger.exception(f"Couldn't parse {INSTALLER_CONFIG_FILE} as YAML file") + loguru.logger.exception( + f"Couldn't parse {INSTALLER_CONFIG_FILE} as YAML file" + ) def main(): diff --git a/device-backend/control/planktoscopehat/planktoscope/imager/mqtt.py b/device-backend/control/planktoscopehat/planktoscope/imager/mqtt.py index 52030ff06..35b91b03a 100644 --- a/device-backend/control/planktoscopehat/planktoscope/imager/mqtt.py +++ b/device-backend/control/planktoscopehat/planktoscope/imager/mqtt.py @@ -10,7 +10,7 @@ import loguru -from planktoscope import identity, integrity, mqtt +from .. import identity, integrity, mqtt from ..camera import mqtt as camera from . import stopflow diff --git a/software/node-red-dashboard/settings.js b/software/node-red-dashboard/settings.js index 7b00a485c..a2146bd38 100644 --- a/software/node-red-dashboard/settings.js +++ b/software/node-red-dashboard/settings.js @@ -8,7 +8,11 @@ const doc = yaml.load( // TODO: instead check `~/PlanktoScope/config.json`? fs.readFileSync("/usr/share/planktoscope/installer-config.yml", "utf8"), ); -const userDir = path.join(__dirname, doc.hardware); +let variant = doc.hardware; +if (variant === "fairscope-latest") { + variant = "planktoscopehat"; +} +const userDir = path.join(__dirname, variant); module.exports = { /******************************************************************************* From 889679ca30f886f6dc0452fe3bc36912dcf12520 Mon Sep 17 00:00:00 2001 From: Ethan Li Date: Thu, 8 May 2025 09:41:36 -0700 Subject: [PATCH 08/17] Remove `segmenter-only` support; use `planktoscopehat` variant as fallback --- device-backend/control/main.py | 51 ++-- software/distro/setup/ci-record-version.sh | 287 +++++++++--------- software/distro/setup/cleanup.sh | 40 +-- .../node-red-frontend/install.sh | 32 +- .../setup/planktoscope-app-env/setup.sh | 51 ++-- software/distro/setup/setup.sh | 38 +-- software/node-red-dashboard/settings.js | 39 ++- 7 files changed, 281 insertions(+), 257 deletions(-) diff --git a/device-backend/control/main.py b/device-backend/control/main.py index d415078e6..829ca8930 100644 --- a/device-backend/control/main.py +++ b/device-backend/control/main.py @@ -3,30 +3,47 @@ import loguru import yaml -# TODO: instead check `~/PlanktoScope/config.json`'s `acq_instrument` field? -INSTALLER_CONFIG_FILE = "/usr/share/planktoscope/installer-config.yml" - # FIXME: move loguru configuration to here instead -def determine_variant(config_file: str): - with open(config_file, "r") as file: - variant: typing.Optional[str] = None - try: - config = yaml.safe_load(file) - variant = config["hardware"] - if variant == "fairscope-latest": - variant = "planktoscopehat" - return variant - except yaml.YAMLError: - loguru.logger.exception( - f"Couldn't parse {INSTALLER_CONFIG_FILE} as YAML file" - ) +def load_variant_setting(config_file: str): + config = {} + try: + with open(config_file, "r") as file: + try: + config = yaml.safe_load(file) + except Exception: + loguru.logger.exception(f"Couldn't parse {config_file} as YAML file") + return None + except Exception: + loguru.logger.exception(f"Couldn't open {config_file}") + return None + + variant: typing.Optional[str] = None + try: + variant = config["hardware"] + except Exception: + loguru.logger.error(f"{config_file} lacks a 'hardware' field") + return None + + if variant == "fairscope-latest": + return "planktoscopehat" + + return variant + + +# TODO: instead check `~/PlanktoScope/config.json`'s `acq_instrument` field? +INSTALLER_CONFIG_FILE = "/usr/share/planktoscope/installer-config.yml" def main(): loguru.logger.info("Determining configured hardware variant...") - variant = determine_variant(INSTALLER_CONFIG_FILE) + variant = load_variant_setting(INSTALLER_CONFIG_FILE) + if variant is None: + variant = "planktoscopehat" + loguru.logger.warning( + f"Couldn't load hardware variant setting, defaulting to {variant}" + ) loguru.logger.info(f"Hardware variant: {variant}") # Note: once the `main.py` files are rewritten to have a main() function, we can import the # appropriate module and invoke its main function. For now, we have to do an `exec`: diff --git a/software/distro/setup/ci-record-version.sh b/software/distro/setup/ci-record-version.sh index fdf41dc2d..1630434ba 100755 --- a/software/distro/setup/ci-record-version.sh +++ b/software/distro/setup/ci-record-version.sh @@ -5,7 +5,7 @@ # REPO (the repo used for setup, e.g. github.com/PlanktoScope/PlanktoScope) # VERSION_QUERY (the version query, e.g. a commit hash) # QUERY_TYPE (eitiher branch, tag, or hash) -# HARDWARE (either none, segmenter-only, adafruithat, or planktoscopehat) +# HARDWARE (either none, adafruithat, planktoscopehat, or fairscope-latest) # VERSION_QUERY_DIR (the filesystem path of the git repo used for getting version info) # Utilities for user interaction @@ -24,160 +24,154 @@ MAGENTA="$(tput setaf 5 2>/dev/null || printf '')" NO_COLOR="$(tput sgr0 2>/dev/null || printf '')" info() { - printf '%s\n' "${BOLD}${GREY}>${NO_COLOR} $*" + printf '%s\n' "${BOLD}${GREY}>${NO_COLOR} $*" } warn() { - printf '%s\n' "${YELLOW}! $*${NO_COLOR}" + printf '%s\n' "${YELLOW}! $*${NO_COLOR}" } error() { - printf '%s\n' "${RED}x $*${NO_COLOR}" >&2 + printf '%s\n' "${RED}x $*${NO_COLOR}" >&2 } completed() { - printf '%s\n' "${GREEN}✓${NO_COLOR} $*" + printf '%s\n' "${GREEN}✓${NO_COLOR} $*" } - - # Utilities for interacting with Git repositories resolve_commit() { - mirror_dir="$1" - query_type="$2" - tag_prefix="$3" - version_query="$4" - if [ "${query_type}" = "tag" ]; then - version_query="${tag_prefix}${version_query}" - fi - cmd="git -C ${mirror_dir} rev-list -n 1 ${version_query}" - $cmd && return 0 || rc=$? - - error "Command failed (exit code $rc): ${BLUE}${cmd}${NO_COLOR}" - printf "\n" >&2 - return $rc + mirror_dir="$1" + query_type="$2" + tag_prefix="$3" + version_query="$4" + if [ "$query_type" = "tag" ]; then + version_query="${tag_prefix}${version_query}" + fi + cmd="git -C ${mirror_dir} rev-list -n 1 ${version_query}" + "$cmd" && return 0 || rc=$? + + error "Command failed (exit code $rc): ${BLUE}${cmd}${NO_COLOR}" + printf "\n" >&2 + return "$rc" } resolve_tag() { - mirror_dir="$1" - tag_prefix="$2" - commit_hash="$3" - git -C "${mirror_dir}" describe --tags --exact-match --match "${TAG_PREFIX}*" "${commit_hash}" \ - 2> /dev/null || printf "" + mirror_dir="$1" + tag_prefix="$2" + commit_hash="$3" + git -C "$mirror_dir" describe --tags --exact-match --match "${TAG_PREFIX}*" "$commit_hash" \ + 2>/dev/null || printf "" } resolve_pseudoversion() { - mirror_dir="$1" - tag_prefix="$2" - commit_hash="$3" - git -C "${mirror_dir}" describe --tags --match "${TAG_PREFIX}*" --abbrev=7 "${commit_hash}" \ - 2> /dev/null || printf "" + mirror_dir="$1" + tag_prefix="$2" + commit_hash="$3" + git -C "$mirror_dir" describe --tags --match "${TAG_PREFIX}*" --abbrev=7 "$commit_hash" \ + 2>/dev/null || printf "" } - - # Main function with_empty_placeholder() { - if [ -z "$1" ]; then - printf "(none)" - else - printf "%s" "$1" - fi + if [ "$1" = "" ]; then + printf "(none)" + else + printf "%s" "$1" + fi } # All code with side-effects is wrapped in a main function # called at the bottom of the file, so that a truncated partial # download doesn't cause execution of half a script. main() { - # Print configuration information - - pretty_repo=$(printf "%s" "${REPO}" | sed "s~^.*://~~") - - printf " %s\n" "${UNDERLINE}Configuration${NO_COLOR}" - info "${BOLD}Repo${NO_COLOR}: ${GREEN}${pretty_repo}${NO_COLOR}" - info "${BOLD}Version query${NO_COLOR}: ${GREEN}${VERSION_QUERY}${NO_COLOR}" - info "${BOLD}Query type${NO_COLOR}: ${GREEN}${QUERY_TYPE}${NO_COLOR}" - info "${BOLD}Hardware${NO_COLOR}: ${GREEN}${HARDWARE}${NO_COLOR}" - if [ -n "${VERBOSE-}" ]; then - VERBOSE=v - info "${BOLD}Tag prefix${NO_COLOR}: $(with_empty_placeholder "${TAG_PREFIX}")" - info "${BOLD}Entrypoint${NO_COLOR}: $(with_empty_placeholder "${SETUP_ENTRYPOINT}")" - info "${BOLD}Verbose${NO_COLOR}: yes" - else - VERBOSE= - fi - printf '\n' - - # Resolve versioning information - - normalized_repo="${REPO}" - if printf "%s" "${REPO}" | grep -q '@'; then - # Repo was specified with SCP-like syntax for SSH protocol - : - elif printf "%s" "${REPO}" | grep -q '://'; then - # Repo was specified with a protocol identifier - : - else - normalized_repo="https://${REPO}" - fi - - commit_hash="$(resolve_commit "${VERSION_QUERY_DIR}" "${QUERY_TYPE}" "${TAG_PREFIX}" "${VERSION_QUERY}")" - short_commit_hash="$(printf "%s" "${commit_hash}" | cut -c 1-7)" - tag="$(resolve_tag "${VERSION_QUERY_DIR}" "${TAG_PREFIX}" "${commit_hash}")" - version_string="$(resolve_pseudoversion "${VERSION_QUERY_DIR}" "${TAG_PREFIX}" "${commit_hash}" \ - | sed "s~^${TAG_PREFIX}~~")" - if [ -n "${VERBOSE-}" ]; then - printf "\n" - fi - - printf " %s\n" "${UNDERLINE}Versioning${NO_COLOR}" - info "${BOLD}Git Commit${NO_COLOR}: ${short_commit_hash}" - info "${BOLD}Git Tag${NO_COLOR}: $(with_empty_placeholder "${tag}")" - info "${BOLD}Version${NO_COLOR}: ${version_string}" - printf '\n' - - # Record versioning information - - # Note: "${HOME}/.local/etc/pkscope-distro" is deprecated - for versioning_dir in "${HOME}/.local/etc/pkscope-distro" "/usr/share/planktoscope"; do - info "Recording versioning information to ${versioning_dir}..." - if [ -d "${versioning_dir}" ]; then - warn "The ${versioning_dir} directory already exists, so it will be erased." - if ! rm -rf "${versioning_dir}" 2>/dev/null; then - sudo rm -rf "${versioning_dir}" - fi - fi - if ! mkdir -p "${versioning_dir}" 2>/dev/null; then - sudo mkdir -p "${versioning_dir}" - fi - - installer_file_header="# This file was auto-generated!" - installer_config_file="${versioning_dir}/installer-config.yml" - printf "%s\n" "${installer_file_header}" | sudo tee "${installer_config_file}" > /dev/null - printf "%s: \"%s\"\n" \ - "repo" "${pretty_repo}" \ - "version-query" "${VERSION_QUERY}" \ - "query-type" "${QUERY_TYPE}" \ - "hardware" "${HARDWARE}" \ - "tag-prefix" "${TAG_PREFIX}" \ - "setup-entrypoint" "${SETUP_ENTRYPOINT}" \ - | sudo tee --append "${installer_config_file}" > /dev/null - - installer_versioning_file="${versioning_dir}/installer-versioning.yml" - printf "%s\n" "${installer_file_header}" | sudo tee "${installer_versioning_file}" > /dev/null - printf "%s: \"%s\"\n" \ - "repo" "${pretty_repo}" \ - "commit" "${commit_hash}" \ - "tag" "${tag}" \ - "version" "${version_string}" \ - | sudo tee --append "${installer_versioning_file}" > /dev/null - done + # Print configuration information + + pretty_repo=$(printf "%s" "$REPO" | sed "s~^.*://~~") + + printf " %s\n" "${UNDERLINE}Configuration${NO_COLOR}" + info "${BOLD}Repo${NO_COLOR}: ${GREEN}${pretty_repo}${NO_COLOR}" + info "${BOLD}Version query${NO_COLOR}: ${GREEN}${VERSION_QUERY}${NO_COLOR}" + info "${BOLD}Query type${NO_COLOR}: ${GREEN}${QUERY_TYPE}${NO_COLOR}" + info "${BOLD}Hardware${NO_COLOR}: ${GREEN}${HARDWARE}${NO_COLOR}" + if [ "${VERBOSE-}" != "" ]; then + VERBOSE=v + info "${BOLD}Tag prefix${NO_COLOR}: $(with_empty_placeholder "$TAG_PREFIX")" + info "${BOLD}Entrypoint${NO_COLOR}: $(with_empty_placeholder "$SETUP_ENTRYPOINT")" + info "${BOLD}Verbose${NO_COLOR}: yes" + else + VERBOSE= + fi + printf '\n' + + # Resolve versioning information + + normalized_repo="$REPO" + if printf "%s" "$REPO" | grep -q '@'; then + # Repo was specified with SCP-like syntax for SSH protocol + : + elif printf "%s" "$REPO" | grep -q '://'; then + # Repo was specified with a protocol identifier + : + else + normalized_repo="https://${REPO}" + fi + + commit_hash="$(resolve_commit "$VERSION_QUERY_DIR" "$QUERY_TYPE" "$TAG_PREFIX" "$VERSION_QUERY")" + short_commit_hash="$(printf "%s" "$commit_hash" | cut -c 1-7)" + tag="$(resolve_tag "$VERSION_QUERY_DIR" "$TAG_PREFIX" "$commit_hash")" + version_string="$(resolve_pseudoversion "$VERSION_QUERY_DIR" "$TAG_PREFIX" "$commit_hash" | + sed "s~^${TAG_PREFIX}~~")" + if [ "${VERBOSE-}" != "" ]; then + printf "\n" + fi + + printf " %s\n" "${UNDERLINE}Versioning${NO_COLOR}" + info "${BOLD}Git Commit${NO_COLOR}: ${short_commit_hash}" + info "${BOLD}Git Tag${NO_COLOR}: $(with_empty_placeholder "$tag")" + info "${BOLD}Version${NO_COLOR}: ${version_string}" + printf '\n' + + # Record versioning information + + # Note: "${HOME}/.local/etc/pkscope-distro" is deprecated + for versioning_dir in "${HOME}/.local/etc/pkscope-distro" "/usr/share/planktoscope"; do + info "Recording versioning information to ${versioning_dir}..." + if [ -d "$versioning_dir" ]; then + warn "The ${versioning_dir} directory already exists, so it will be erased." + if ! rm -rf "$versioning_dir" 2>/dev/null; then + sudo rm -rf "$versioning_dir" + fi + fi + if ! mkdir -p "$versioning_dir" 2>/dev/null; then + sudo mkdir -p "$versioning_dir" + fi + + installer_file_header="# This file was auto-generated!" + installer_config_file="${versioning_dir}/installer-config.yml" + printf "%s\n" "$installer_file_header" | sudo tee "$installer_config_file" >/dev/null + printf "%s: \"%s\"\n" \ + "repo" "$pretty_repo" \ + "version-query" "$VERSION_QUERY" \ + "query-type" "$QUERY_TYPE" \ + "hardware" "$HARDWARE" \ + "tag-prefix" "$TAG_PREFIX" \ + "setup-entrypoint" "$SETUP_ENTRYPOINT" | + sudo tee --append "$installer_config_file" >/dev/null + + installer_versioning_file="${versioning_dir}/installer-versioning.yml" + printf "%s\n" "$installer_file_header" | sudo tee "$installer_versioning_file" >/dev/null + printf "%s: \"%s\"\n" \ + "repo" "$pretty_repo" \ + "commit" "$commit_hash" \ + "tag" "$tag" \ + "version" "$version_string" | + sudo tee --append "$installer_versioning_file" >/dev/null + done } - - # Imperative section REPO="$1" @@ -187,45 +181,44 @@ HARDWARE="$4" VERSION_QUERY_DIR="$5" # Set default values for the command-line arguments -if [ -z "${REPO-}" ]; then - exit 1 +if [ "${REPO-}" = "" ]; then + exit 1 fi DEFAULT_VERSION_QUERY="software/stable" -if [ -z "${VERSION_QUERY-}" ]; then - error "VERSION_QUERY environment variable was not set!" - exit 1 +if [ "${VERSION_QUERY-}" = "" ]; then + error "VERSION_QUERY environment variable was not set!" + exit 1 fi DEFAULT_QUERY_TYPE="branch" -if [ -z "${QUERY_TYPE-}" ]; then - error "QUERY_TYPE environment variable was not set!" - exit 1 +if [ "${QUERY_TYPE-}" = "" ]; then + error "QUERY_TYPE environment variable was not set!" + exit 1 fi DEFAULT_HARDWARE="planktoscopehat" -if [ -z "${HARDWARE-}" ]; then - error "HARDWARE environment variable was not set!" - exit 1 +if [ "${HARDWARE-}" = "" ]; then + error "HARDWARE environment variable was not set!" + exit 1 fi DEFAULT_TAG_PREFIX="software/" -if [ -z "${TAG_PREFIX-}" ]; then - TAG_PREFIX="${DEFAULT_TAG_PREFIX}" +if [ "${TAG_PREFIX-}" = "" ]; then + TAG_PREFIX="$DEFAULT_TAG_PREFIX" fi DEFAULT_SETUP_ENTRYPOINT="software/distro/setup/setup.sh" -if [ -z "${SETUP_ENTRYPOINT-}" ]; then - SETUP_ENTRYPOINT="${DEFAULT_SETUP_ENTRYPOINT}" +if [ "${SETUP_ENTRYPOINT-}" = "" ]; then + SETUP_ENTRYPOINT="$DEFAULT_SETUP_ENTRYPOINT" fi -if [ -z "${VERBOSE-}" ]; then - VERBOSE="" +if [ "${VERBOSE-}" = "" ]; then + VERBOSE="" fi # Ensure a valid query type -case "${QUERY_TYPE}" in - branch | tag | hash) - ;; - *) - error "Unknown query type: ${QUERY_TYPE}" - usage - exit 1 - ;; +case "$QUERY_TYPE" in +branch | tag | hash) ;; +*) + error "Unknown query type: ${QUERY_TYPE}" + usage + exit 1 + ;; esac main diff --git a/software/distro/setup/cleanup.sh b/software/distro/setup/cleanup.sh index ef3494a83..60e7b7995 100755 --- a/software/distro/setup/cleanup.sh +++ b/software/distro/setup/cleanup.sh @@ -3,11 +3,11 @@ # Determine the base path for sub-scripts -setup_scripts_root=$(dirname $(realpath $BASH_SOURCE)) +setup_scripts_root=$(dirname "$(realpath "$BASH_SOURCE")") # Get command-line args -hardware_type="$1" # should be either none, adafruithat, planktoscopehat, or segmenter-only +hardware_type="$1" # should be either none, adafruithat, planktoscopehat, or fairscope-latest # Set up pretty error printing @@ -21,36 +21,36 @@ error_fmt="\e[${bold};${red_fg}m" reset_fmt='\e[0m' function report_starting { - echo - echo -e "${script_fmt}Starting: ${1}...${reset_fmt}" + echo + echo -e "${script_fmt}Starting: ${1}...${reset_fmt}" } function report_finished { - echo - echo -e "${script_fmt}Finished: ${1}!${reset_fmt}" + echo + echo -e "${script_fmt}Finished: ${1}!${reset_fmt}" } function panic { - echo -e "${error_fmt}Error: couldn't ${1}${reset_fmt}" - exit 1 + echo -e "${error_fmt}Error: couldn't ${1}${reset_fmt}" + exit 1 } # Run sub-scripts -if [ $hardware_type = "none" ]; then - echo "Warning: skipping PlanktoScope-specific setup because hardware type was specified as: $hardware_type" +if [ "$hardware_type" = "none" ]; then + echo "Warning: skipping PlanktoScope-specific setup because hardware type was specified as: $hardware_type" else - description="remove unnecessary artifacts from the PlanktoScope application environment" - report_starting "$description" - if $setup_scripts_root/planktoscope-app-env/cleanup.sh ; then - report_finished "$description" - else - panic "$description" - fi + description="remove unnecessary artifacts from the PlanktoScope application environment" + report_starting "$description" + if "$setup_scripts_root/planktoscope-app-env/cleanup.sh"; then + report_finished "$description" + else + panic "$description" + fi fi description="remove unnecessary artifacts from the base operating system" report_starting "$description" -if $setup_scripts_root/base-os/cleanup.sh ; then - report_finished "$description" +if "$setup_scripts_root/base-os/cleanup.sh"; then + report_finished "$description" else - panic "$description" + panic "$description" fi diff --git a/software/distro/setup/planktoscope-app-env/node-red-frontend/install.sh b/software/distro/setup/planktoscope-app-env/node-red-frontend/install.sh index 82571facd..7e7f051e7 100755 --- a/software/distro/setup/planktoscope-app-env/node-red-frontend/install.sh +++ b/software/distro/setup/planktoscope-app-env/node-red-frontend/install.sh @@ -5,20 +5,12 @@ config_files_root=$(dirname "$(realpath "$BASH_SOURCE")") # Get command-line args -hardware_type="$1" # should be either adafruithat, planktoscopehat, fairscope-latest, or segmenter-only +hardware_type="$1" # should be either adafruithat, planktoscopehat, or fairscope-latest default_config="$hardware_type-latest" -case "$hardware_type" in -"fairscope-latest") - hardware_type="planktoscopehat" - default_config="fairscope-latest" - ;; -"segmenter-only") - # FIXME: instead set up the segmenter-only version of the Node-RED dashboard! - echo "Warning: setting up adafruithat version of Node-RED dashboard for hardware type: $hardware_type" - hardware_type=adafruithat - default_config="adafruithat-latest" - ;; -esac +if [ "$hardware_type" = "fairscope-latest" ]; then + hardware_type="planktoscopehat" + default_config="fairscope-latest" +fi # Install dependencies # smbus is needed by some python3 nodes in the Node-RED dashboard for the Adafruit HAT. @@ -27,22 +19,22 @@ esac # FIXME: get rid of the Node-RED nodes depending on smbus! That functionality should be moved into # the Python backend. if ! sudo apt-get install -y python3-smbus2; then - sudo apt-get install -y python3-pip - pip3 install smbus2==0.4.3 + sudo apt-get install -y python3-pip + pip3 install smbus2==0.4.3 fi # Install Node-RED # TODO: run Node-RED in a Docker container instead curl -sL https://raw.githubusercontent.com/node-red/linux-installers/master/deb/update-nodejs-and-nodered | - bash -s - --confirm-install --confirm-pi --no-init + bash -s - --confirm-install --confirm-pi --no-init cp "$HOME/PlanktoScope/software/node-red-dashboard/default-configs/$default_config.config.json" \ - "$HOME"/PlanktoScope/config.json + "$HOME"/PlanktoScope/config.json # Configure node-red -npm --prefix "$HOME"/PlanktoScope/software/node-red-dashboard install +npm --prefix "$HOME/PlanktoScope/software/node-red-dashboard" install sudo mkdir -p /etc/systemd/system/nodered.service.d -sudo cp $config_files_root/30-override.conf /etc/systemd/system/nodered.service.d/30-override.conf +sudo cp "$config_files_root/30-override.conf" /etc/systemd/system/nodered.service.d/30-override.conf # Install dependencies to make them available to Node-RED -npm --prefix "$HOME"/PlanktoScope/software/node-red-dashboard/$hardware_type install +npm --prefix "$HOME/PlanktoScope/software/node-red-dashboard/$hardware_type" install diff --git a/software/distro/setup/planktoscope-app-env/setup.sh b/software/distro/setup/planktoscope-app-env/setup.sh index 5b0b748ae..5b182a720 100755 --- a/software/distro/setup/planktoscope-app-env/setup.sh +++ b/software/distro/setup/planktoscope-app-env/setup.sh @@ -11,7 +11,7 @@ build_scripts_root=$(dirname "$(realpath "$BASH_SOURCE")") # Get command-line args -hardware_type="$1" # should be either adafruithat, planktoscopehat, fairscope-latest, or segmenter-only +hardware_type="$1" # should be either adafruithat, planktoscopehat, fairscope-latest # Set up pretty error printing @@ -24,15 +24,15 @@ error_fmt="\e[${bold};${red_fg}m" reset_fmt='\e[0m' function report_starting { - echo - echo -e "${subscript_fmt}Starting: ${1}...${reset_fmt}" + echo + echo -e "${subscript_fmt}Starting: ${1}...${reset_fmt}" } function report_finished { - echo -e "${subscript_fmt}Finished: ${1}!${reset_fmt}" + echo -e "${subscript_fmt}Finished: ${1}!${reset_fmt}" } function panic { - echo -e "${error_fmt}Error: couldn't ${1}${reset_fmt}" - exit 1 + echo -e "${error_fmt}Error: couldn't ${1}${reset_fmt}" + exit 1 } # Run sub-scripts @@ -40,54 +40,49 @@ function panic { description="set up /home/pi/PlanktoScope" report_starting "$description" if "$build_scripts_root/PlanktoScope/install.sh"; then - report_finished "$description" + report_finished "$description" else - panic "$description" + panic "$description" fi description="set up Node-RED frontend" report_starting "$description" if "$build_scripts_root/node-red-frontend/install.sh" "$hardware_type"; then - report_finished "$description" + report_finished "$description" else - panic "$description" -fi - -if [ "$hardware_type" = "segmenter-only" ]; then - echo "Warning: skipping PlanktoScope hardware-specific setup because hardware type was specified as: $hardware_type" - exit 0 + panic "$description" fi description="set up Python hardware controller" report_starting "$description" if "$build_scripts_root/python-hardware-controller/install.sh" "$hardware_type"; then - report_finished "$description" + report_finished "$description" else - panic "$description" + panic "$description" fi if [ "$hardware_type" = "adafruithat" ]; then - description="set up GPS and clock driver" - report_starting "$description" - if "$build_scripts_root/gps/install.sh"; then - report_finished "$description" - else - panic "$description" - fi + description="set up GPS and clock driver" + report_starting "$description" + if "$build_scripts_root/gps/install.sh"; then + report_finished "$description" + else + panic "$description" + fi fi description="enable CPU overclocking" report_starting "$description" if "$build_scripts_root/overclocking/config.sh"; then - report_finished "$description" + report_finished "$description" else - panic "$description" + panic "$description" fi description="update and configure bootloader" report_starting "$description" if "$build_scripts_root/bootloader/install.sh"; then - report_finished "$description" + report_finished "$description" else - panic "$description" + panic "$description" fi diff --git a/software/distro/setup/setup.sh b/software/distro/setup/setup.sh index c4ba79d97..eab146c2a 100755 --- a/software/distro/setup/setup.sh +++ b/software/distro/setup/setup.sh @@ -11,7 +11,7 @@ setup_scripts_root=$(dirname "$(realpath "$BASH_SOURCE")") # Get command-line args -hardware_type="$1" # should be either none, adafruithat, planktoscopehat, fairscope-latest, or segmenter-only +hardware_type="$1" # should be either none, adafruithat, planktoscopehat, or fairscope-latest # Set up pretty error printing @@ -25,16 +25,16 @@ error_fmt="\e[${bold};${red_fg}m" reset_fmt='\e[0m' function report_starting { - echo - echo -e "${script_fmt}Starting: ${1}...${reset_fmt}" + echo + echo -e "${script_fmt}Starting: ${1}...${reset_fmt}" } function report_finished { - echo - echo -e "${script_fmt}Finished: ${1}!${reset_fmt}" + echo + echo -e "${script_fmt}Finished: ${1}!${reset_fmt}" } function panic { - echo -e "${error_fmt}Error: couldn't ${1}${reset_fmt}" - exit 1 + echo -e "${error_fmt}Error: couldn't ${1}${reset_fmt}" + exit 1 } # Run sub-scripts @@ -44,23 +44,23 @@ echo -e "${script_fmt}Setting up full operating system...${reset_fmt}" description="set up base operating system" report_starting "$description" if "$setup_scripts_root"/base-os/setup.sh; then - report_finished "$description" - source "$setup_scripts_root"/base-os/export-env.sh + report_finished "$description" + source "$setup_scripts_root"/base-os/export-env.sh else - panic "$description" + panic "$description" fi if [ "$hardware_type" = "none" ]; then - echo "Warning: skipping PlanktoScope-specific setup because hardware type was specified as: $hardware_type" + echo "Warning: skipping PlanktoScope-specific setup because hardware type was specified as: $hardware_type" else - description="set up PlanktoScope application environment" - report_starting "$description" - if "$setup_scripts_root"/planktoscope-app-env/setup.sh "$hardware_type"; then - report_finished "$description" - source "$setup_scripts_root"/planktoscope-app-env/export-env.sh - else - panic "$description" - fi + description="set up PlanktoScope application environment" + report_starting "$description" + if "$setup_scripts_root"/planktoscope-app-env/setup.sh "$hardware_type"; then + report_finished "$description" + source "$setup_scripts_root"/planktoscope-app-env/export-env.sh + else + panic "$description" + fi fi "$setup_scripts_root"/cleanup.sh "$hardware_type" diff --git a/software/node-red-dashboard/settings.js b/software/node-red-dashboard/settings.js index a2146bd38..995cc279b 100644 --- a/software/node-red-dashboard/settings.js +++ b/software/node-red-dashboard/settings.js @@ -4,14 +4,41 @@ const yaml = require("js-yaml"); const fs = require("fs"); const path = require("path"); -const doc = yaml.load( - // TODO: instead check `~/PlanktoScope/config.json`? - fs.readFileSync("/usr/share/planktoscope/installer-config.yml", "utf8"), -); -let variant = doc.hardware; -if (variant === "fairscope-latest") { +function load_variant_setting(config_file) { + let doc = {}; + try { + doc = yaml.load( + // TODO: instead check `~/PlanktoScope/config.json`? + fs.readFileSync(installer_config_file, "utf8"), + ); + } catch (e) { + console.warn(`Couldn't open & parse ${config_file} as YAML file`); + return undefined; + } + + let variant = doc.hardware; + if (variant === undefined) { + console.warn(`${config_file} lacks a 'hardware' field`); + return undefined; + } + + if (variant === "fairscope-latest") { + variant = "planktoscopehat"; + } + + return variant; +} + +const installer_config_file = "/usr/share/planktoscope/installer-config.yml"; +console.log("Determining configured hardware variant..."); +let variant = load_variant_setting(installer_config_file); +if (variant === undefined) { variant = "planktoscopehat"; + console.warn( + `Couldn't load hardware variant setting, defaulting to ${variant}`, + ); } +console.log(`Hardware variant: ${variant}`); const userDir = path.join(__dirname, variant); module.exports = { From a6c39bef2d0dad5409de2c121ab84aff8ba04069 Mon Sep 17 00:00:00 2001 From: Ethan Li Date: Thu, 8 May 2025 10:24:44 -0700 Subject: [PATCH 09/17] Try to fix regression caused by my text editor's shell script autoformatter --- software/distro/setup/ci-record-version.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/software/distro/setup/ci-record-version.sh b/software/distro/setup/ci-record-version.sh index 1630434ba..6e71f7523 100755 --- a/software/distro/setup/ci-record-version.sh +++ b/software/distro/setup/ci-record-version.sh @@ -49,8 +49,7 @@ resolve_commit() { if [ "$query_type" = "tag" ]; then version_query="${tag_prefix}${version_query}" fi - cmd="git -C ${mirror_dir} rev-list -n 1 ${version_query}" - "$cmd" && return 0 || rc=$? + git -C ${mirror_dir} rev-list -n 1 ${version_query} && return 0 || rc=$? error "Command failed (exit code $rc): ${BLUE}${cmd}${NO_COLOR}" printf "\n" >&2 From 481bdf46182da43297081d170b3b46726c30b963 Mon Sep 17 00:00:00 2001 From: Ethan Li Date: Thu, 8 May 2025 11:57:06 -0700 Subject: [PATCH 10/17] Select variant based on `config.json` instead of `installer-config.yml` --- device-backend/control/main.py | 51 +++++++++------ device-backend/control/poetry.lock | 65 +------------------ device-backend/control/pyproject.toml | 1 - .../node-red-frontend/install.sh | 3 +- software/node-red-dashboard/package-lock.json | 23 +------ software/node-red-dashboard/package.json | 5 +- software/node-red-dashboard/settings.js | 49 ++++++++------ 7 files changed, 64 insertions(+), 133 deletions(-) diff --git a/device-backend/control/main.py b/device-backend/control/main.py index 829ca8930..2ef5aaabf 100644 --- a/device-backend/control/main.py +++ b/device-backend/control/main.py @@ -1,53 +1,62 @@ -import typing +import json +from os import path import loguru -import yaml # FIXME: move loguru configuration to here instead -def load_variant_setting(config_file: str): +HARDWARE_VARIANTS = { + "PlanktoScope v2.1": "adafruithat", + "PlanktoScope v2.3": "planktoscopehat", + "PlanktoScope v2.5": "planktoscopehat", + "PlanktoScope v2.6": "planktoscopehat", + "PlanktoScope v3.0": "planktoscopehat", + # Note: null is the default version value for planktoscopehat-latest.config.json; see + # https://github.com/PlanktoScope/PlanktoScope/pull/432 for details. + None: "planktoscopehat", +} + + +def load_variant_setting(config_path: str): config = {} try: - with open(config_file, "r") as file: + with open(config_path, "r") as file: try: - config = yaml.safe_load(file) + config = json.load(file) except Exception: - loguru.logger.exception(f"Couldn't parse {config_file} as YAML file") + loguru.logger.exception(f"Couldn't parse {config_path} as JSON file") return None except Exception: - loguru.logger.exception(f"Couldn't open {config_file}") + loguru.logger.exception(f"Couldn't open {config_path}") return None - variant: typing.Optional[str] = None - try: - variant = config["hardware"] - except Exception: - loguru.logger.error(f"{config_file} lacks a 'hardware' field") + if "acq_instrument" not in config: + loguru.logger.error(f"{config_path} lacks a 'acq_instrument' field") return None - if variant == "fairscope-latest": - return "planktoscopehat" - - return variant + return HARDWARE_VARIANTS.get(config["acq_instrument"], None) -# TODO: instead check `~/PlanktoScope/config.json`'s `acq_instrument` field? -INSTALLER_CONFIG_FILE = "/usr/share/planktoscope/installer-config.yml" +CONFIG_PATH = path.join( + path.dirname(path.dirname(path.dirname(__file__))), + "config.json", +) def main(): loguru.logger.info("Determining configured hardware variant...") - variant = load_variant_setting(INSTALLER_CONFIG_FILE) + variant = load_variant_setting(CONFIG_PATH) if variant is None: variant = "planktoscopehat" loguru.logger.warning( - f"Couldn't load hardware variant setting, defaulting to {variant}" + f"Couldn't load hardware variant setting from config, defaulting to {variant}" ) loguru.logger.info(f"Hardware variant: {variant}") # Note: once the `main.py` files are rewritten to have a main() function, we can import the # appropriate module and invoke its main function. For now, we have to do an `exec`: - with open(f"{variant}/main.py") as script: + script_path = path.join(path.dirname(__file__), variant, "main.py") + with open(script_path) as script: exec(script.read()) diff --git a/device-backend/control/poetry.lock b/device-backend/control/poetry.lock index 143e3d27c..712bc3c1a 100644 --- a/device-backend/control/poetry.lock +++ b/device-backend/control/poetry.lock @@ -1212,69 +1212,6 @@ files = [ {file = "pyusb-1.2.1.tar.gz", hash = "sha256:a4cc7404a203144754164b8b40994e2849fde1cfff06b08492f12fff9d9de7b9"}, ] -[[package]] -name = "pyyaml" -version = "6.0.2" -description = "YAML parser and emitter for Python" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, - {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, - {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, - {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, - {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, - {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, - {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, - {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, - {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, - {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, - {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, - {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, - {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, - {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, - {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, - {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, - {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, - {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, - {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, - {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, - {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, - {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, - {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, - {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, - {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, - {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, - {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, - {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, - {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, - {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, - {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, - {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, - {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, - {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, - {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, -] - [[package]] name = "radon" version = "5.1.0" @@ -1725,4 +1662,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.1" python-versions = ">=3.9.2" -content-hash = "3d4a5233b38bd749b77fdb7c5d1e81026e21a1bc1ce2ec0412ab9fe832ad2124" +content-hash = "af71a8926af5aeb4674154f5f5313854af50e44c2a6c7f3d4573fa156ff30511" diff --git a/device-backend/control/pyproject.toml b/device-backend/control/pyproject.toml index b5aaf7685..0fe1bad56 100644 --- a/device-backend/control/pyproject.toml +++ b/device-backend/control/pyproject.toml @@ -36,7 +36,6 @@ python = ">=3.9.2" paho-mqtt = "^1.6.1" loguru = "^0.5.3" readerwriterlock = "^1.0.9" -pyyaml = "^6.0.2" [tool.poetry.group.hw.dependencies] rpi-gpio = "^0.7.0" diff --git a/software/distro/setup/planktoscope-app-env/node-red-frontend/install.sh b/software/distro/setup/planktoscope-app-env/node-red-frontend/install.sh index 7e7f051e7..5f27d0248 100755 --- a/software/distro/setup/planktoscope-app-env/node-red-frontend/install.sh +++ b/software/distro/setup/planktoscope-app-env/node-red-frontend/install.sh @@ -37,4 +37,5 @@ sudo mkdir -p /etc/systemd/system/nodered.service.d sudo cp "$config_files_root/30-override.conf" /etc/systemd/system/nodered.service.d/30-override.conf # Install dependencies to make them available to Node-RED -npm --prefix "$HOME/PlanktoScope/software/node-red-dashboard/$hardware_type" install +npm --prefix "$HOME/PlanktoScope/software/node-red-dashboard/adafruithat" install +npm --prefix "$HOME/PlanktoScope/software/node-red-dashboard/planktoscopehat" install diff --git a/software/node-red-dashboard/package-lock.json b/software/node-red-dashboard/package-lock.json index e33bc6c7a..52fad34d5 100644 --- a/software/node-red-dashboard/package-lock.json +++ b/software/node-red-dashboard/package-lock.json @@ -5,28 +5,7 @@ "requires": true, "packages": { "": { - "name": "node-red-dashboard", - "dependencies": { - "js-yaml": "^4.1.0" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "license": "Python-2.0" - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } + "name": "node-red-dashboard" } } } diff --git a/software/node-red-dashboard/package.json b/software/node-red-dashboard/package.json index eb37479cb..47f2da729 100644 --- a/software/node-red-dashboard/package.json +++ b/software/node-red-dashboard/package.json @@ -1,7 +1,4 @@ { "name": "node-red-dashboard", - "private": true, - "dependencies": { - "js-yaml": "^4.1.0" - } + "private": true } diff --git a/software/node-red-dashboard/settings.js b/software/node-red-dashboard/settings.js index 995cc279b..7497be5f3 100644 --- a/software/node-red-dashboard/settings.js +++ b/software/node-red-dashboard/settings.js @@ -1,41 +1,50 @@ // https://nodered.org/docs/user-guide/runtime/configuration -const yaml = require("js-yaml"); const fs = require("fs"); const path = require("path"); -function load_variant_setting(config_file) { - let doc = {}; +const hardware_variants = { + "PlanktoScope v2.1": "adafruithat", + "PlanktoScope v2.3": "planktoscopehat", + "PlanktoScope v2.5": "planktoscopehat", + "PlanktoScope v2.6": "planktoscopehat", + "PlanktoScope v3.0": "planktoscopehat", + // Note: null is the default version value for planktoscopehat-latest.config.json; see + // https://github.com/PlanktoScope/PlanktoScope/pull/432 for details. + null: "planktoscopehat", +}; +function load_variant_setting(config_path) { + let config = {}; try { - doc = yaml.load( - // TODO: instead check `~/PlanktoScope/config.json`? - fs.readFileSync(installer_config_file, "utf8"), - ); + const file = fs.readFileSync(config_path, "utf8"); + try { + config = JSON.parse(file); + } catch (e) { + console.error(`Couldn't parse ${config_path} as JSON file`); + return undefined; + } } catch (e) { - console.warn(`Couldn't open & parse ${config_file} as YAML file`); + console.error(`Couldn't open ${config_path}`); return undefined; } - let variant = doc.hardware; - if (variant === undefined) { - console.warn(`${config_file} lacks a 'hardware' field`); + if (config.acq_instrument === undefined) { + console.error(`${config_path} lacks a 'acq_instrument' field`); return undefined; } - - if (variant === "fairscope-latest") { - variant = "planktoscopehat"; - } - - return variant; + return hardware_variants[config.acq_instrument]; } -const installer_config_file = "/usr/share/planktoscope/installer-config.yml"; +const config_path = path.join( + path.dirname(path.dirname(__dirname)), + "config.json", +); console.log("Determining configured hardware variant..."); -let variant = load_variant_setting(installer_config_file); +let variant = load_variant_setting(config_path); if (variant === undefined) { variant = "planktoscopehat"; console.warn( - `Couldn't load hardware variant setting, defaulting to ${variant}`, + `Couldn't load hardware variant setting from config, defaulting to ${variant}`, ); } console.log(`Hardware variant: ${variant}`); From f1ffd7ee25c7f2b9c5a94eee48c98c4114c53124 Mon Sep 17 00:00:00 2001 From: Ethan Li Date: Mon, 12 May 2025 11:55:00 -0700 Subject: [PATCH 11/17] Simplify hardware variant selection code --- device-backend/control/main.py | 18 +++++------------- software/node-red-dashboard/settings.js | 18 +++++++----------- 2 files changed, 12 insertions(+), 24 deletions(-) diff --git a/device-backend/control/main.py b/device-backend/control/main.py index 2ef5aaabf..39aeae826 100644 --- a/device-backend/control/main.py +++ b/device-backend/control/main.py @@ -6,18 +6,6 @@ # FIXME: move loguru configuration to here instead -HARDWARE_VARIANTS = { - "PlanktoScope v2.1": "adafruithat", - "PlanktoScope v2.3": "planktoscopehat", - "PlanktoScope v2.5": "planktoscopehat", - "PlanktoScope v2.6": "planktoscopehat", - "PlanktoScope v3.0": "planktoscopehat", - # Note: null is the default version value for planktoscopehat-latest.config.json; see - # https://github.com/PlanktoScope/PlanktoScope/pull/432 for details. - None: "planktoscopehat", -} - - def load_variant_setting(config_path: str): config = {} try: @@ -35,7 +23,11 @@ def load_variant_setting(config_path: str): loguru.logger.error(f"{config_path} lacks a 'acq_instrument' field") return None - return HARDWARE_VARIANTS.get(config["acq_instrument"], None) + # This is a special case for legacy hardware; new hardware designs should all be part of the + # planktoscopehat codebase: + if config["acq_instrument"] == "PlanktoScope v2.1": + return "adafruithat" + return "planktoscopehat" CONFIG_PATH = path.join( diff --git a/software/node-red-dashboard/settings.js b/software/node-red-dashboard/settings.js index 7497be5f3..67fe52f2f 100644 --- a/software/node-red-dashboard/settings.js +++ b/software/node-red-dashboard/settings.js @@ -3,16 +3,6 @@ const fs = require("fs"); const path = require("path"); -const hardware_variants = { - "PlanktoScope v2.1": "adafruithat", - "PlanktoScope v2.3": "planktoscopehat", - "PlanktoScope v2.5": "planktoscopehat", - "PlanktoScope v2.6": "planktoscopehat", - "PlanktoScope v3.0": "planktoscopehat", - // Note: null is the default version value for planktoscopehat-latest.config.json; see - // https://github.com/PlanktoScope/PlanktoScope/pull/432 for details. - null: "planktoscopehat", -}; function load_variant_setting(config_path) { let config = {}; try { @@ -32,7 +22,13 @@ function load_variant_setting(config_path) { console.error(`${config_path} lacks a 'acq_instrument' field`); return undefined; } - return hardware_variants[config.acq_instrument]; + + // This is a special case for legacy hardware; new hardware designs should all be part of the + // planktoscopehat codebase: + if (config.acq_instrument === "PlanktoScope v2.1") { + return "adafruithat"; + } + return "planktoscopehat"; } const config_path = path.join( From e27123245c8cdb8c26b12e230b9d6527113d339d Mon Sep 17 00:00:00 2001 From: Ethan Li Date: Mon, 12 May 2025 12:05:20 -0700 Subject: [PATCH 12/17] Undo autoformatting changes, fix incorrect indentation scheme --- .../planktoscope/camera/mqtt.py | 4 +- software/distro/setup/ci-record-version.sh | 286 +++++++++--------- software/distro/setup/cleanup.sh | 40 +-- .../node-red-frontend/install.sh | 16 +- .../python-hardware-controller/install.sh | 16 +- .../setup/planktoscope-app-env/setup.sh | 44 +-- software/distro/setup/setup.sh | 36 +-- 7 files changed, 224 insertions(+), 218 deletions(-) diff --git a/device-backend/control/planktoscopehat/planktoscope/camera/mqtt.py b/device-backend/control/planktoscopehat/planktoscope/camera/mqtt.py index 3357826f2..ee2df81cd 100644 --- a/device-backend/control/planktoscopehat/planktoscope/camera/mqtt.py +++ b/device-backend/control/planktoscopehat/planktoscope/camera/mqtt.py @@ -162,9 +162,7 @@ def _receive_message(self, message: dict[str, typing.Any]) -> typing.Optional[st settings = message["payload"]["settings"] try: converted_settings = _convert_settings( - settings, - self._camera.settings.white_balance_gains, - self._camera.sensor_name, + settings, self._camera.settings.white_balance_gains, self._camera.sensor_name ) _validate_settings(converted_settings) except (TypeError, ValueError) as e: diff --git a/software/distro/setup/ci-record-version.sh b/software/distro/setup/ci-record-version.sh index 6e71f7523..8dda3dbd0 100755 --- a/software/distro/setup/ci-record-version.sh +++ b/software/distro/setup/ci-record-version.sh @@ -5,7 +5,7 @@ # REPO (the repo used for setup, e.g. github.com/PlanktoScope/PlanktoScope) # VERSION_QUERY (the version query, e.g. a commit hash) # QUERY_TYPE (eitiher branch, tag, or hash) -# HARDWARE (either none, adafruithat, planktoscopehat, or fairscope-latest) +# HARDWARE (either none, adafruithat, or planktoscopehat) # VERSION_QUERY_DIR (the filesystem path of the git repo used for getting version info) # Utilities for user interaction @@ -24,153 +24,160 @@ MAGENTA="$(tput setaf 5 2>/dev/null || printf '')" NO_COLOR="$(tput sgr0 2>/dev/null || printf '')" info() { - printf '%s\n' "${BOLD}${GREY}>${NO_COLOR} $*" + printf '%s\n' "${BOLD}${GREY}>${NO_COLOR} $*" } warn() { - printf '%s\n' "${YELLOW}! $*${NO_COLOR}" + printf '%s\n' "${YELLOW}! $*${NO_COLOR}" } error() { - printf '%s\n' "${RED}x $*${NO_COLOR}" >&2 + printf '%s\n' "${RED}x $*${NO_COLOR}" >&2 } completed() { - printf '%s\n' "${GREEN}✓${NO_COLOR} $*" + printf '%s\n' "${GREEN}✓${NO_COLOR} $*" } + + # Utilities for interacting with Git repositories resolve_commit() { - mirror_dir="$1" - query_type="$2" - tag_prefix="$3" - version_query="$4" - if [ "$query_type" = "tag" ]; then - version_query="${tag_prefix}${version_query}" - fi - git -C ${mirror_dir} rev-list -n 1 ${version_query} && return 0 || rc=$? - - error "Command failed (exit code $rc): ${BLUE}${cmd}${NO_COLOR}" - printf "\n" >&2 - return "$rc" + mirror_dir="$1" + query_type="$2" + tag_prefix="$3" + version_query="$4" + if [ "${query_type}" = "tag" ]; then + version_query="${tag_prefix}${version_query}" + fi + cmd="git -C ${mirror_dir} rev-list -n 1 ${version_query}" + $cmd && return 0 || rc=$? + + error "Command failed (exit code $rc): ${BLUE}${cmd}${NO_COLOR}" + printf "\n" >&2 + return $rc } resolve_tag() { - mirror_dir="$1" - tag_prefix="$2" - commit_hash="$3" - git -C "$mirror_dir" describe --tags --exact-match --match "${TAG_PREFIX}*" "$commit_hash" \ - 2>/dev/null || printf "" + mirror_dir="$1" + tag_prefix="$2" + commit_hash="$3" + git -C "${mirror_dir}" describe --tags --exact-match --match "${TAG_PREFIX}*" "${commit_hash}" \ + 2> /dev/null || printf "" } resolve_pseudoversion() { - mirror_dir="$1" - tag_prefix="$2" - commit_hash="$3" - git -C "$mirror_dir" describe --tags --match "${TAG_PREFIX}*" --abbrev=7 "$commit_hash" \ - 2>/dev/null || printf "" + mirror_dir="$1" + tag_prefix="$2" + commit_hash="$3" + git -C "${mirror_dir}" describe --tags --match "${TAG_PREFIX}*" --abbrev=7 "${commit_hash}" \ + 2> /dev/null || printf "" } + + # Main function with_empty_placeholder() { - if [ "$1" = "" ]; then - printf "(none)" - else - printf "%s" "$1" - fi + if [ -z "$1" ]; then + printf "(none)" + else + printf "%s" "$1" + fi } # All code with side-effects is wrapped in a main function # called at the bottom of the file, so that a truncated partial # download doesn't cause execution of half a script. main() { - # Print configuration information - - pretty_repo=$(printf "%s" "$REPO" | sed "s~^.*://~~") - - printf " %s\n" "${UNDERLINE}Configuration${NO_COLOR}" - info "${BOLD}Repo${NO_COLOR}: ${GREEN}${pretty_repo}${NO_COLOR}" - info "${BOLD}Version query${NO_COLOR}: ${GREEN}${VERSION_QUERY}${NO_COLOR}" - info "${BOLD}Query type${NO_COLOR}: ${GREEN}${QUERY_TYPE}${NO_COLOR}" - info "${BOLD}Hardware${NO_COLOR}: ${GREEN}${HARDWARE}${NO_COLOR}" - if [ "${VERBOSE-}" != "" ]; then - VERBOSE=v - info "${BOLD}Tag prefix${NO_COLOR}: $(with_empty_placeholder "$TAG_PREFIX")" - info "${BOLD}Entrypoint${NO_COLOR}: $(with_empty_placeholder "$SETUP_ENTRYPOINT")" - info "${BOLD}Verbose${NO_COLOR}: yes" - else - VERBOSE= - fi - printf '\n' - - # Resolve versioning information - - normalized_repo="$REPO" - if printf "%s" "$REPO" | grep -q '@'; then - # Repo was specified with SCP-like syntax for SSH protocol - : - elif printf "%s" "$REPO" | grep -q '://'; then - # Repo was specified with a protocol identifier - : - else - normalized_repo="https://${REPO}" - fi - - commit_hash="$(resolve_commit "$VERSION_QUERY_DIR" "$QUERY_TYPE" "$TAG_PREFIX" "$VERSION_QUERY")" - short_commit_hash="$(printf "%s" "$commit_hash" | cut -c 1-7)" - tag="$(resolve_tag "$VERSION_QUERY_DIR" "$TAG_PREFIX" "$commit_hash")" - version_string="$(resolve_pseudoversion "$VERSION_QUERY_DIR" "$TAG_PREFIX" "$commit_hash" | - sed "s~^${TAG_PREFIX}~~")" - if [ "${VERBOSE-}" != "" ]; then - printf "\n" - fi - - printf " %s\n" "${UNDERLINE}Versioning${NO_COLOR}" - info "${BOLD}Git Commit${NO_COLOR}: ${short_commit_hash}" - info "${BOLD}Git Tag${NO_COLOR}: $(with_empty_placeholder "$tag")" - info "${BOLD}Version${NO_COLOR}: ${version_string}" - printf '\n' - - # Record versioning information - - # Note: "${HOME}/.local/etc/pkscope-distro" is deprecated - for versioning_dir in "${HOME}/.local/etc/pkscope-distro" "/usr/share/planktoscope"; do - info "Recording versioning information to ${versioning_dir}..." - if [ -d "$versioning_dir" ]; then - warn "The ${versioning_dir} directory already exists, so it will be erased." - if ! rm -rf "$versioning_dir" 2>/dev/null; then - sudo rm -rf "$versioning_dir" - fi - fi - if ! mkdir -p "$versioning_dir" 2>/dev/null; then - sudo mkdir -p "$versioning_dir" - fi - - installer_file_header="# This file was auto-generated!" - installer_config_file="${versioning_dir}/installer-config.yml" - printf "%s\n" "$installer_file_header" | sudo tee "$installer_config_file" >/dev/null - printf "%s: \"%s\"\n" \ - "repo" "$pretty_repo" \ - "version-query" "$VERSION_QUERY" \ - "query-type" "$QUERY_TYPE" \ - "hardware" "$HARDWARE" \ - "tag-prefix" "$TAG_PREFIX" \ - "setup-entrypoint" "$SETUP_ENTRYPOINT" | - sudo tee --append "$installer_config_file" >/dev/null - - installer_versioning_file="${versioning_dir}/installer-versioning.yml" - printf "%s\n" "$installer_file_header" | sudo tee "$installer_versioning_file" >/dev/null - printf "%s: \"%s\"\n" \ - "repo" "$pretty_repo" \ - "commit" "$commit_hash" \ - "tag" "$tag" \ - "version" "$version_string" | - sudo tee --append "$installer_versioning_file" >/dev/null - done + # Print configuration information + + pretty_repo=$(printf "%s" "${REPO}" | sed "s~^.*://~~") + + printf " %s\n" "${UNDERLINE}Configuration${NO_COLOR}" + info "${BOLD}Repo${NO_COLOR}: ${GREEN}${pretty_repo}${NO_COLOR}" + info "${BOLD}Version query${NO_COLOR}: ${GREEN}${VERSION_QUERY}${NO_COLOR}" + info "${BOLD}Query type${NO_COLOR}: ${GREEN}${QUERY_TYPE}${NO_COLOR}" + info "${BOLD}Hardware${NO_COLOR}: ${GREEN}${HARDWARE}${NO_COLOR}" + if [ -n "${VERBOSE-}" ]; then + VERBOSE=v + info "${BOLD}Tag prefix${NO_COLOR}: $(with_empty_placeholder "${TAG_PREFIX}")" + info "${BOLD}Entrypoint${NO_COLOR}: $(with_empty_placeholder "${SETUP_ENTRYPOINT}")" + info "${BOLD}Verbose${NO_COLOR}: yes" + else + VERBOSE= + fi + printf '\n' + + # Resolve versioning information + + normalized_repo="${REPO}" + if printf "%s" "${REPO}" | grep -q '@'; then + # Repo was specified with SCP-like syntax for SSH protocol + : + elif printf "%s" "${REPO}" | grep -q '://'; then + # Repo was specified with a protocol identifier + : + else + normalized_repo="https://${REPO}" + fi + + commit_hash="$(resolve_commit "${VERSION_QUERY_DIR}" "${QUERY_TYPE}" "${TAG_PREFIX}" "${VERSION_QUERY}")" + short_commit_hash="$(printf "%s" "${commit_hash}" | cut -c 1-7)" + tag="$(resolve_tag "${VERSION_QUERY_DIR}" "${TAG_PREFIX}" "${commit_hash}")" + version_string="$(resolve_pseudoversion "${VERSION_QUERY_DIR}" "${TAG_PREFIX}" "${commit_hash}" \ + | sed "s~^${TAG_PREFIX}~~")" + if [ -n "${VERBOSE-}" ]; then + printf "\n" + fi + + printf " %s\n" "${UNDERLINE}Versioning${NO_COLOR}" + info "${BOLD}Git Commit${NO_COLOR}: ${short_commit_hash}" + info "${BOLD}Git Tag${NO_COLOR}: $(with_empty_placeholder "${tag}")" + info "${BOLD}Version${NO_COLOR}: ${version_string}" + printf '\n' + + # Record versioning information + + # Note: "${HOME}/.local/etc/pkscope-distro" is deprecated + for versioning_dir in "${HOME}/.local/etc/pkscope-distro" "/usr/share/planktoscope"; do + info "Recording versioning information to ${versioning_dir}..." + if [ -d "${versioning_dir}" ]; then + warn "The ${versioning_dir} directory already exists, so it will be erased." + if ! rm -rf "${versioning_dir}" 2>/dev/null; then + sudo rm -rf "${versioning_dir}" + fi + fi + if ! mkdir -p "${versioning_dir}" 2>/dev/null; then + sudo mkdir -p "${versioning_dir}" + fi + + installer_file_header="# This file was auto-generated!" + installer_config_file="${versioning_dir}/installer-config.yml" + printf "%s\n" "${installer_file_header}" | sudo tee "${installer_config_file}" > /dev/null + printf "%s: \"%s\"\n" \ + "repo" "${pretty_repo}" \ + "version-query" "${VERSION_QUERY}" \ + "query-type" "${QUERY_TYPE}" \ + "hardware" "${HARDWARE}" \ + "tag-prefix" "${TAG_PREFIX}" \ + "setup-entrypoint" "${SETUP_ENTRYPOINT}" \ + | sudo tee --append "${installer_config_file}" > /dev/null + + installer_versioning_file="${versioning_dir}/installer-versioning.yml" + printf "%s\n" "${installer_file_header}" | sudo tee "${installer_versioning_file}" > /dev/null + printf "%s: \"%s\"\n" \ + "repo" "${pretty_repo}" \ + "commit" "${commit_hash}" \ + "tag" "${tag}" \ + "version" "${version_string}" \ + | sudo tee --append "${installer_versioning_file}" > /dev/null + done } + + # Imperative section REPO="$1" @@ -180,44 +187,45 @@ HARDWARE="$4" VERSION_QUERY_DIR="$5" # Set default values for the command-line arguments -if [ "${REPO-}" = "" ]; then - exit 1 +if [ -z "${REPO-}" ]; then + exit 1 fi DEFAULT_VERSION_QUERY="software/stable" -if [ "${VERSION_QUERY-}" = "" ]; then - error "VERSION_QUERY environment variable was not set!" - exit 1 +if [ -z "${VERSION_QUERY-}" ]; then + error "VERSION_QUERY environment variable was not set!" + exit 1 fi DEFAULT_QUERY_TYPE="branch" -if [ "${QUERY_TYPE-}" = "" ]; then - error "QUERY_TYPE environment variable was not set!" - exit 1 +if [ -z "${QUERY_TYPE-}" ]; then + error "QUERY_TYPE environment variable was not set!" + exit 1 fi DEFAULT_HARDWARE="planktoscopehat" -if [ "${HARDWARE-}" = "" ]; then - error "HARDWARE environment variable was not set!" - exit 1 +if [ -z "${HARDWARE-}" ]; then + error "HARDWARE environment variable was not set!" + exit 1 fi DEFAULT_TAG_PREFIX="software/" -if [ "${TAG_PREFIX-}" = "" ]; then - TAG_PREFIX="$DEFAULT_TAG_PREFIX" +if [ -z "${TAG_PREFIX-}" ]; then + TAG_PREFIX="${DEFAULT_TAG_PREFIX}" fi DEFAULT_SETUP_ENTRYPOINT="software/distro/setup/setup.sh" -if [ "${SETUP_ENTRYPOINT-}" = "" ]; then - SETUP_ENTRYPOINT="$DEFAULT_SETUP_ENTRYPOINT" +if [ -z "${SETUP_ENTRYPOINT-}" ]; then + SETUP_ENTRYPOINT="${DEFAULT_SETUP_ENTRYPOINT}" fi -if [ "${VERBOSE-}" = "" ]; then - VERBOSE="" +if [ -z "${VERBOSE-}" ]; then + VERBOSE="" fi # Ensure a valid query type -case "$QUERY_TYPE" in -branch | tag | hash) ;; -*) - error "Unknown query type: ${QUERY_TYPE}" - usage - exit 1 - ;; +case "${QUERY_TYPE}" in + branch | tag | hash) + ;; + *) + error "Unknown query type: ${QUERY_TYPE}" + usage + exit 1 + ;; esac main diff --git a/software/distro/setup/cleanup.sh b/software/distro/setup/cleanup.sh index 60e7b7995..7d44389f9 100755 --- a/software/distro/setup/cleanup.sh +++ b/software/distro/setup/cleanup.sh @@ -3,11 +3,11 @@ # Determine the base path for sub-scripts -setup_scripts_root=$(dirname "$(realpath "$BASH_SOURCE")") +setup_scripts_root=$(dirname $(realpath $BASH_SOURCE)) # Get command-line args -hardware_type="$1" # should be either none, adafruithat, planktoscopehat, or fairscope-latest +hardware_type="$1" # should be either none, adafruithat, or planktoscopehat # Set up pretty error printing @@ -21,36 +21,36 @@ error_fmt="\e[${bold};${red_fg}m" reset_fmt='\e[0m' function report_starting { - echo - echo -e "${script_fmt}Starting: ${1}...${reset_fmt}" + echo + echo -e "${script_fmt}Starting: ${1}...${reset_fmt}" } function report_finished { - echo - echo -e "${script_fmt}Finished: ${1}!${reset_fmt}" + echo + echo -e "${script_fmt}Finished: ${1}!${reset_fmt}" } function panic { - echo -e "${error_fmt}Error: couldn't ${1}${reset_fmt}" - exit 1 + echo -e "${error_fmt}Error: couldn't ${1}${reset_fmt}" + exit 1 } # Run sub-scripts -if [ "$hardware_type" = "none" ]; then - echo "Warning: skipping PlanktoScope-specific setup because hardware type was specified as: $hardware_type" +if [ $hardware_type = "none" ]; then + echo "Warning: skipping PlanktoScope-specific setup because hardware type was specified as: $hardware_type" else - description="remove unnecessary artifacts from the PlanktoScope application environment" - report_starting "$description" - if "$setup_scripts_root/planktoscope-app-env/cleanup.sh"; then - report_finished "$description" - else - panic "$description" - fi + description="remove unnecessary artifacts from the PlanktoScope application environment" + report_starting "$description" + if $setup_scripts_root/planktoscope-app-env/cleanup.sh ; then + report_finished "$description" + else + panic "$description" + fi fi description="remove unnecessary artifacts from the base operating system" report_starting "$description" -if "$setup_scripts_root/base-os/cleanup.sh"; then - report_finished "$description" +if $setup_scripts_root/base-os/cleanup.sh ; then + report_finished "$description" else - panic "$description" + panic "$description" fi diff --git a/software/distro/setup/planktoscope-app-env/node-red-frontend/install.sh b/software/distro/setup/planktoscope-app-env/node-red-frontend/install.sh index 5f27d0248..f92dcc112 100755 --- a/software/distro/setup/planktoscope-app-env/node-red-frontend/install.sh +++ b/software/distro/setup/planktoscope-app-env/node-red-frontend/install.sh @@ -8,8 +8,8 @@ config_files_root=$(dirname "$(realpath "$BASH_SOURCE")") hardware_type="$1" # should be either adafruithat, planktoscopehat, or fairscope-latest default_config="$hardware_type-latest" if [ "$hardware_type" = "fairscope-latest" ]; then - hardware_type="planktoscopehat" - default_config="fairscope-latest" + hardware_type="planktoscopehat" + default_config="fairscope-latest" fi # Install dependencies @@ -19,22 +19,22 @@ fi # FIXME: get rid of the Node-RED nodes depending on smbus! That functionality should be moved into # the Python backend. if ! sudo apt-get install -y python3-smbus2; then - sudo apt-get install -y python3-pip - pip3 install smbus2==0.4.3 + sudo apt-get install -y python3-pip + pip3 install smbus2==0.4.3 fi # Install Node-RED # TODO: run Node-RED in a Docker container instead curl -sL https://raw.githubusercontent.com/node-red/linux-installers/master/deb/update-nodejs-and-nodered | - bash -s - --confirm-install --confirm-pi --no-init + bash -s - --confirm-install --confirm-pi --no-init cp "$HOME/PlanktoScope/software/node-red-dashboard/default-configs/$default_config.config.json" \ - "$HOME"/PlanktoScope/config.json + "$HOME"/PlanktoScope/config.json # Configure node-red -npm --prefix "$HOME/PlanktoScope/software/node-red-dashboard" install +npm --prefix "$HOME"/PlanktoScope/software/node-red-dashboard install sudo mkdir -p /etc/systemd/system/nodered.service.d -sudo cp "$config_files_root/30-override.conf" /etc/systemd/system/nodered.service.d/30-override.conf +sudo cp $config_files_root/30-override.conf /etc/systemd/system/nodered.service.d/30-override.conf # Install dependencies to make them available to Node-RED npm --prefix "$HOME/PlanktoScope/software/node-red-dashboard/adafruithat" install diff --git a/software/distro/setup/planktoscope-app-env/python-hardware-controller/install.sh b/software/distro/setup/planktoscope-app-env/python-hardware-controller/install.sh index fadc56baf..356b3f5ce 100755 --- a/software/distro/setup/planktoscope-app-env/python-hardware-controller/install.sh +++ b/software/distro/setup/planktoscope-app-env/python-hardware-controller/install.sh @@ -11,14 +11,14 @@ hardware_type="$1" # should be either adafruithat, planktoscopehat, or fairscope default_config="$hardware_type-latest" case "$hardware_type" in "fairscope-latest") - hardware_type="planktoscopehat" - default_config="fairscope-latest" - ;; + hardware_type="planktoscopehat" + default_config="fairscope-latest" + ;; esac ## Install basic tooling sudo -E apt-get install -y -o Dpkg::Progress-Fancy=0 \ - git python3-pip python3-venv pipx + git python3-pip python3-venv pipx # Suppress keyring dialogs when setting up the PlanktoScope distro on a graphical desktop # (see https://github.com/pypa/pip/issues/7883) @@ -38,11 +38,11 @@ PATH="$PATH:/home/pi/.local/bin" # Raspberry Pi OS has been updated. In which case this can be removed. echo "If the next command fails, see comment in install.sh" sudo -E apt-get install -y -o Dpkg::Progress-Fancy=0 --only-upgrade \ - python3-libcamera=0.5.0+rpt20250429-1 python3-av=12.3.0-2+rpt1 + python3-libcamera=0.5.0+rpt20250429-1 python3-av=12.3.0-2+rpt1 sudo -E apt-get install -y --no-install-recommends -o Dpkg::Progress-Fancy=0 \ - i2c-tools libopenjp2-7 python3-picamera2 + i2c-tools libopenjp2-7 python3-picamera2 poetry --directory "$HOME/PlanktoScope/device-backend/control" install \ - --no-root --compile + --no-root --compile file="/etc/systemd/system/planktoscope-org.device-backend.controller.service" sudo cp "$config_files_root$file" "$file" sudo systemctl enable "planktoscope-org.device-backend.controller.service" @@ -50,4 +50,4 @@ sudo systemctl enable "planktoscope-org.device-backend.controller.service" # Select the enabled hardware controller mkdir -p "$HOME/PlanktoScope" cp "$HOME/PlanktoScope/device-backend/default-configs/$default_config.hardware.json" \ - "$HOME/PlanktoScope/hardware.json" + "$HOME/PlanktoScope/hardware.json" diff --git a/software/distro/setup/planktoscope-app-env/setup.sh b/software/distro/setup/planktoscope-app-env/setup.sh index 5b182a720..c70f927bb 100755 --- a/software/distro/setup/planktoscope-app-env/setup.sh +++ b/software/distro/setup/planktoscope-app-env/setup.sh @@ -24,15 +24,15 @@ error_fmt="\e[${bold};${red_fg}m" reset_fmt='\e[0m' function report_starting { - echo - echo -e "${subscript_fmt}Starting: ${1}...${reset_fmt}" + echo + echo -e "${subscript_fmt}Starting: ${1}...${reset_fmt}" } function report_finished { - echo -e "${subscript_fmt}Finished: ${1}!${reset_fmt}" + echo -e "${subscript_fmt}Finished: ${1}!${reset_fmt}" } function panic { - echo -e "${error_fmt}Error: couldn't ${1}${reset_fmt}" - exit 1 + echo -e "${error_fmt}Error: couldn't ${1}${reset_fmt}" + exit 1 } # Run sub-scripts @@ -40,49 +40,49 @@ function panic { description="set up /home/pi/PlanktoScope" report_starting "$description" if "$build_scripts_root/PlanktoScope/install.sh"; then - report_finished "$description" + report_finished "$description" else - panic "$description" + panic "$description" fi description="set up Node-RED frontend" report_starting "$description" if "$build_scripts_root/node-red-frontend/install.sh" "$hardware_type"; then - report_finished "$description" + report_finished "$description" else - panic "$description" + panic "$description" fi description="set up Python hardware controller" report_starting "$description" if "$build_scripts_root/python-hardware-controller/install.sh" "$hardware_type"; then - report_finished "$description" + report_finished "$description" else - panic "$description" + panic "$description" fi if [ "$hardware_type" = "adafruithat" ]; then - description="set up GPS and clock driver" - report_starting "$description" - if "$build_scripts_root/gps/install.sh"; then - report_finished "$description" - else - panic "$description" - fi + description="set up GPS and clock driver" + report_starting "$description" + if "$build_scripts_root/gps/install.sh"; then + report_finished "$description" + else + panic "$description" + fi fi description="enable CPU overclocking" report_starting "$description" if "$build_scripts_root/overclocking/config.sh"; then - report_finished "$description" + report_finished "$description" else - panic "$description" + panic "$description" fi description="update and configure bootloader" report_starting "$description" if "$build_scripts_root/bootloader/install.sh"; then - report_finished "$description" + report_finished "$description" else - panic "$description" + panic "$description" fi diff --git a/software/distro/setup/setup.sh b/software/distro/setup/setup.sh index eab146c2a..7303c4931 100755 --- a/software/distro/setup/setup.sh +++ b/software/distro/setup/setup.sh @@ -25,16 +25,16 @@ error_fmt="\e[${bold};${red_fg}m" reset_fmt='\e[0m' function report_starting { - echo - echo -e "${script_fmt}Starting: ${1}...${reset_fmt}" + echo + echo -e "${script_fmt}Starting: ${1}...${reset_fmt}" } function report_finished { - echo - echo -e "${script_fmt}Finished: ${1}!${reset_fmt}" + echo + echo -e "${script_fmt}Finished: ${1}!${reset_fmt}" } function panic { - echo -e "${error_fmt}Error: couldn't ${1}${reset_fmt}" - exit 1 + echo -e "${error_fmt}Error: couldn't ${1}${reset_fmt}" + exit 1 } # Run sub-scripts @@ -44,23 +44,23 @@ echo -e "${script_fmt}Setting up full operating system...${reset_fmt}" description="set up base operating system" report_starting "$description" if "$setup_scripts_root"/base-os/setup.sh; then - report_finished "$description" - source "$setup_scripts_root"/base-os/export-env.sh + report_finished "$description" + source "$setup_scripts_root"/base-os/export-env.sh else - panic "$description" + panic "$description" fi if [ "$hardware_type" = "none" ]; then - echo "Warning: skipping PlanktoScope-specific setup because hardware type was specified as: $hardware_type" + echo "Warning: skipping PlanktoScope-specific setup because hardware type was specified as: $hardware_type" else - description="set up PlanktoScope application environment" - report_starting "$description" - if "$setup_scripts_root"/planktoscope-app-env/setup.sh "$hardware_type"; then - report_finished "$description" - source "$setup_scripts_root"/planktoscope-app-env/export-env.sh - else - panic "$description" - fi + description="set up PlanktoScope application environment" + report_starting "$description" + if "$setup_scripts_root"/planktoscope-app-env/setup.sh "$hardware_type"; then + report_finished "$description" + source "$setup_scripts_root"/planktoscope-app-env/export-env.sh + else + panic "$description" + fi fi "$setup_scripts_root"/cleanup.sh "$hardware_type" From f42cb26e6163e73f6439126eb49c5b69d6f5a1b1 Mon Sep 17 00:00:00 2001 From: Sonny Piers Date: Tue, 20 May 2025 16:21:47 +0000 Subject: [PATCH 13/17] remove useless package.json --- software/node-red-dashboard/package-lock.json | 11 ----------- software/node-red-dashboard/package.json | 4 ---- 2 files changed, 15 deletions(-) delete mode 100644 software/node-red-dashboard/package-lock.json delete mode 100644 software/node-red-dashboard/package.json diff --git a/software/node-red-dashboard/package-lock.json b/software/node-red-dashboard/package-lock.json deleted file mode 100644 index 52fad34d5..000000000 --- a/software/node-red-dashboard/package-lock.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "node-red-dashboard", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "node-red-dashboard" - } - } -} diff --git a/software/node-red-dashboard/package.json b/software/node-red-dashboard/package.json deleted file mode 100644 index 47f2da729..000000000 --- a/software/node-red-dashboard/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "node-red-dashboard", - "private": true -} From d3f6900e812f8f46469542106cfc1e64c61ddcd7 Mon Sep 17 00:00:00 2001 From: Sonny Piers Date: Tue, 20 May 2025 16:47:05 +0000 Subject: [PATCH 14/17] Use absolute paths for well known files --- device-backend/control/main.py | 17 +++++------------ .../node-red-frontend/install.sh | 2 +- software/node-red-dashboard/settings.js | 14 ++++++-------- 3 files changed, 12 insertions(+), 21 deletions(-) diff --git a/device-backend/control/main.py b/device-backend/control/main.py index 39aeae826..c7e3ff935 100644 --- a/device-backend/control/main.py +++ b/device-backend/control/main.py @@ -5,8 +5,10 @@ # FIXME: move loguru configuration to here instead - -def load_variant_setting(config_path: str): +# This is a special case for legacy hardware; new hardware designs should all be part of the +# planktoscopehat codebase: +CONFIG_PATH = "/home/pi/PlanktoScope/config.json" +def load_variant_setting(config_path: str = CONFIG_PATH): config = {} try: with open(config_path, "r") as file: @@ -23,22 +25,13 @@ def load_variant_setting(config_path: str): loguru.logger.error(f"{config_path} lacks a 'acq_instrument' field") return None - # This is a special case for legacy hardware; new hardware designs should all be part of the - # planktoscopehat codebase: if config["acq_instrument"] == "PlanktoScope v2.1": return "adafruithat" return "planktoscopehat" - -CONFIG_PATH = path.join( - path.dirname(path.dirname(path.dirname(__file__))), - "config.json", -) - - def main(): loguru.logger.info("Determining configured hardware variant...") - variant = load_variant_setting(CONFIG_PATH) + variant = load_variant_setting() if variant is None: variant = "planktoscopehat" loguru.logger.warning( diff --git a/software/distro/setup/planktoscope-app-env/node-red-frontend/install.sh b/software/distro/setup/planktoscope-app-env/node-red-frontend/install.sh index 0d7360c1c..2004d7b91 100755 --- a/software/distro/setup/planktoscope-app-env/node-red-frontend/install.sh +++ b/software/distro/setup/planktoscope-app-env/node-red-frontend/install.sh @@ -43,4 +43,4 @@ cp "$HOME/PlanktoScope/software/node-red-dashboard/default-configs/$default_conf # Configure node-red npm --prefix "$HOME/PlanktoScope/software/node-red-dashboard/adafruithat" install -npm --prefix "$HOME/PlanktoScope/software/node-red-dashboard/planktoscopehat" install \ No newline at end of file +npm --prefix "$HOME/PlanktoScope/software/node-red-dashboard/planktoscopehat" install diff --git a/software/node-red-dashboard/settings.js b/software/node-red-dashboard/settings.js index 67fe52f2f..250a2dc98 100644 --- a/software/node-red-dashboard/settings.js +++ b/software/node-red-dashboard/settings.js @@ -3,7 +3,10 @@ const fs = require("fs"); const path = require("path"); -function load_variant_setting(config_path) { +// This is a special case for legacy hardware; new hardware designs should all be part of the +// planktoscopehat codebase: +const CONFIG_PATH = "/home/pi/PlanktoScope/config.json"; +function load_variant_setting(config_path = CONFIG_PATH) { let config = {}; try { const file = fs.readFileSync(config_path, "utf8"); @@ -23,20 +26,15 @@ function load_variant_setting(config_path) { return undefined; } - // This is a special case for legacy hardware; new hardware designs should all be part of the - // planktoscopehat codebase: if (config.acq_instrument === "PlanktoScope v2.1") { return "adafruithat"; } return "planktoscopehat"; } -const config_path = path.join( - path.dirname(path.dirname(__dirname)), - "config.json", -); + console.log("Determining configured hardware variant..."); -let variant = load_variant_setting(config_path); +let variant = load_variant_setting(); if (variant === undefined) { variant = "planktoscopehat"; console.warn( From f668d952daec3bc568de6c76b86e455ce9e3629a Mon Sep 17 00:00:00 2001 From: Sonny Piers Date: Tue, 20 May 2025 16:59:31 +0000 Subject: [PATCH 15/17] Use platform main() --- device-backend/control/adafruithat/main.py | 2 +- device-backend/control/main.py | 13 ++++++++++--- device-backend/control/planktoscopehat/main.py | 2 +- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/device-backend/control/adafruithat/main.py b/device-backend/control/adafruithat/main.py index 4953eef11..7d41683cd 100644 --- a/device-backend/control/adafruithat/main.py +++ b/device-backend/control/adafruithat/main.py @@ -63,7 +63,7 @@ def handler_stop_signals(signum, _): run = False -if __name__ == "__main__": +def main(): logger.info("Welcome!") logger.info( "Initialising signals handling and sanitizing the directories (step 1/4)" diff --git a/device-backend/control/main.py b/device-backend/control/main.py index c7e3ff935..6f97307c8 100644 --- a/device-backend/control/main.py +++ b/device-backend/control/main.py @@ -40,9 +40,16 @@ def main(): loguru.logger.info(f"Hardware variant: {variant}") # Note: once the `main.py` files are rewritten to have a main() function, we can import the # appropriate module and invoke its main function. For now, we have to do an `exec`: - script_path = path.join(path.dirname(__file__), variant, "main.py") - with open(script_path) as script: - exec(script.read()) + # script_path = path.join(path.dirname(__file__), variant, "main.py") + # with open(script_path) as script: + # exec(script.read()) + + if variant == "adafruithat": + from adafruithat import main as platform + else: + from planktoscopehat import main as platform + + platform.main() if __name__ == "__main__": diff --git a/device-backend/control/planktoscopehat/main.py b/device-backend/control/planktoscopehat/main.py index f1d6537ee..abb1f19b7 100644 --- a/device-backend/control/planktoscopehat/main.py +++ b/device-backend/control/planktoscopehat/main.py @@ -47,7 +47,7 @@ def handler_stop_signals(signum, frame): run = False -if __name__ == "__main__": +def main(): logger.info("Welcome!") logger.info( "Initialising configuration, signals handling and sanitizing the directories (step 1/5)" From d26bee18eac8f547e2b198b3447ef9a95eea16b1 Mon Sep 17 00:00:00 2001 From: Sonny Piers Date: Tue, 20 May 2025 17:03:21 +0000 Subject: [PATCH 16/17] move loguru config to main main --- device-backend/control/adafruithat/main.py | 25 -------------- device-backend/control/main.py | 34 +++++++++++++++---- .../control/planktoscopehat/main.py | 25 -------------- 3 files changed, 27 insertions(+), 57 deletions(-) diff --git a/device-backend/control/adafruithat/main.py b/device-backend/control/adafruithat/main.py index 7d41683cd..695d61a9b 100644 --- a/device-backend/control/adafruithat/main.py +++ b/device-backend/control/adafruithat/main.py @@ -26,31 +26,6 @@ from adafruithat.planktoscope import stepper, light, identity, display from adafruithat.planktoscope.imager import mqtt as imager -# enqueue=True is necessary so we can log across modules -# rotation happens everyday at 01:00 if not restarted -logs_path = "/home/pi/device-backend-logs/control" -if not os.path.exists(logs_path): - os.makedirs(logs_path) -logger.add( - # sys.stdout, - "/home/pi/device-backend-logs/control/{time}.log", - rotation="5 MB", - retention="1 week", - compression=".tar.gz", - enqueue=True, - level="DEBUG", -) - -# The available level for the logger are as follows: -# Level name Severity Logger method -# TRACE 5 logger.trace() -# DEBUG 10 logger.debug() -# INFO 20 logger.info() -# SUCCESS 25 logger.success() -# WARNING 30 logger.warning() -# ERROR 40 logger.error() -# CRITICAL 50 logger.critical() - logger.info("Starting the PlanktoScope hardware controller!") run = True # global variable to enable clean shutdown from stop signals diff --git a/device-backend/control/main.py b/device-backend/control/main.py index 6f97307c8..471e77117 100644 --- a/device-backend/control/main.py +++ b/device-backend/control/main.py @@ -3,11 +3,36 @@ import loguru -# FIXME: move loguru configuration to here instead +# enqueue=True is necessary so we can log across modules +# rotation happens everyday at 01:00 if not restarted +logs_path = "/home/pi/device-backend-logs/control" +if not os.path.exists(logs_path): + os.makedirs(logs_path) +logger.add( + # sys.stdout, + "/home/pi/device-backend-logs/control/{time}.log", + rotation="5 MB", + retention="1 week", + compression=".tar.gz", + enqueue=True, + level="DEBUG", +) + +# The available level for the logger are as follows: +# Level name Severity Logger method +# TRACE 5 logger.trace() +# DEBUG 10 logger.debug() +# INFO 20 logger.info() +# SUCCESS 25 logger.success() +# WARNING 30 logger.warning() +# ERROR 40 logger.error() +# CRITICAL 50 logger.critical() # This is a special case for legacy hardware; new hardware designs should all be part of the # planktoscopehat codebase: CONFIG_PATH = "/home/pi/PlanktoScope/config.json" + + def load_variant_setting(config_path: str = CONFIG_PATH): config = {} try: @@ -38,14 +63,9 @@ def main(): f"Couldn't load hardware variant setting from config, defaulting to {variant}" ) loguru.logger.info(f"Hardware variant: {variant}") - # Note: once the `main.py` files are rewritten to have a main() function, we can import the - # appropriate module and invoke its main function. For now, we have to do an `exec`: - # script_path = path.join(path.dirname(__file__), variant, "main.py") - # with open(script_path) as script: - # exec(script.read()) if variant == "adafruithat": - from adafruithat import main as platform + from adafruithat import main as platform else: from planktoscopehat import main as platform diff --git a/device-backend/control/planktoscopehat/main.py b/device-backend/control/planktoscopehat/main.py index abb1f19b7..d7555873c 100644 --- a/device-backend/control/planktoscopehat/main.py +++ b/device-backend/control/planktoscopehat/main.py @@ -10,31 +10,6 @@ from planktoscopehat.planktoscope import pump, focus, light, identity from planktoscopehat.planktoscope.imager import mqtt as imager -# enqueue=True is necessary so we can log across modules -# rotation happens everyday at 01:00 if not restarted -logs_path = "/home/pi/device-backend-logs/control" -if not os.path.exists(logs_path): - os.makedirs(logs_path) -logger.add( - # sys.stdout, - "/home/pi/device-backend-logs/control/{time}.log", - rotation="5 MB", - retention="1 week", - compression=".tar.gz", - enqueue=True, - level="DEBUG", -) - -# The available level for the logger are as follows: -# Level name Severity Logger method -# TRACE 5 logger.trace() -# DEBUG 10 logger.debug() -# INFO 20 logger.info() -# SUCCESS 25 logger.success() -# WARNING 30 logger.warning() -# ERROR 40 logger.error() -# CRITICAL 50 logger.critical() - logger.info("Starting the PlanktoScope python script!") run = True # global variable to enable clean shutdown from stop signals From 1d3e5b50acd25e39d489b176a9ff697fb3e7468c Mon Sep 17 00:00:00 2001 From: Sonny Piers Date: Tue, 20 May 2025 17:13:01 +0000 Subject: [PATCH 17/17] f --- device-backend/control/main.py | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/device-backend/control/main.py b/device-backend/control/main.py index 471e77117..d354ea8e8 100644 --- a/device-backend/control/main.py +++ b/device-backend/control/main.py @@ -1,13 +1,13 @@ import json -from os import path +from os import makedirs, path -import loguru +from loguru import logger # enqueue=True is necessary so we can log across modules # rotation happens everyday at 01:00 if not restarted logs_path = "/home/pi/device-backend-logs/control" -if not os.path.exists(logs_path): - os.makedirs(logs_path) +if not path.exists(logs_path): + makedirs(logs_path) logger.add( # sys.stdout, "/home/pi/device-backend-logs/control/{time}.log", @@ -40,34 +40,35 @@ def load_variant_setting(config_path: str = CONFIG_PATH): try: config = json.load(file) except Exception: - loguru.logger.exception(f"Couldn't parse {config_path} as JSON file") + logger.exception(f"Couldn't parse {config_path} as JSON file") return None except Exception: - loguru.logger.exception(f"Couldn't open {config_path}") + logger.exception(f"Couldn't open {config_path}") return None if "acq_instrument" not in config: - loguru.logger.error(f"{config_path} lacks a 'acq_instrument' field") + logger.error(f"{config_path} lacks a 'acq_instrument' field") return None if config["acq_instrument"] == "PlanktoScope v2.1": return "adafruithat" return "planktoscopehat" + def main(): - loguru.logger.info("Determining configured hardware variant...") + logger.info("Determining configured hardware variant...") variant = load_variant_setting() if variant is None: variant = "planktoscopehat" - loguru.logger.warning( + logger.warning( f"Couldn't load hardware variant setting from config, defaulting to {variant}" ) - loguru.logger.info(f"Hardware variant: {variant}") + logger.info(f"Hardware variant: {variant}") if variant == "adafruithat": - from adafruithat import main as platform + from adafruithat import main as platform # noqa: IMR200 else: - from planktoscopehat import main as platform + from planktoscopehat import main as platform # noqa: IMR200 platform.main() @@ -76,4 +77,4 @@ def main(): try: main() except Exception: - loguru.logger.exception("Unhandled exception") + logger.exception("Unhandled exception")