Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
@@ -1 +1,13 @@
.venv
.git
.github
.venv
**/.venv
**/__pycache__
**/.pytest_cache
.devcontainer/data
site
paper
PCBs
Enclosure_Design
Website_Development
archived_files
28 changes: 25 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
name: ci
name: ci
on:
push:
branches:
- master
- main
pull_request:
branches:
- master
- main
permissions:
contents: write

Expand All @@ -29,16 +33,34 @@ jobs:
# The documentation plugin stack is not yet compatible with Python 3.14.
python-version: "3.13"

- run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV
- run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV
- uses: actions/cache@v5
with:
key: mkdocs-material-${{ env.cache_id }}
path: .cache
path: .cache
restore-keys: |
mkdocs-material-

- name: "Install dependencies"
run: uv sync --dev --python 3.13

- name: "Test collector polling and packaging"
run: |
cd collector
uv sync --no-dev --python 3.13
uv run python -m unittest discover -s tests
cd ..
uv run --python 3.13 scripts/package_pico.py --config collector/src/config.example.json

- name: "Test storage authentication"
run: |
cd storage
uv sync --no-dev --python 3.13
PYTHONPATH=src uv run python -m unittest discover -s tests

- name: "Build documentation"
run: uv run mkdocs build

- name: "Deploy Docs"
if: github.event_name == 'push'
run: uv run mkdocs gh-deploy --force
37 changes: 37 additions & 0 deletions .github/workflows/pico-hil.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.

name: Pico W hardware-in-the-loop

on:
workflow_dispatch:

permissions:
contents: read

concurrency:
group: pico-w-hil
cancel-in-progress: false

jobs:
pico:
runs-on: [self-hosted, micropython, pico-w]
timeout-minutes: 10
steps:
- uses: actions/checkout@v6
- uses: actions/setup-python@v6
with:
python-version: "3.13"
- name: Install board tooling
run: python -m pip install "mpremote==1.26.1"
- name: Create hardware configuration
env:
PICO_CONFIG_JSON: ${{ secrets.PICO_CONFIG_JSON }}
run: python -c "import os, pathlib; pathlib.Path('pico-config.json').write_text(os.environ['PICO_CONFIG_JSON'])"
- name: Package and flash
run: ./scripts/flash_pico.sh pico-config.json "${PICO_DEVICE:-auto}"
- name: Run one-cycle hardware smoke test
run: mpremote connect "${PICO_DEVICE:-auto}" run scripts/pico_hil_test.py | tee pico-hil.log
- name: Verify result
run: grep -q HIL_OK pico-hil.log
35 changes: 35 additions & 0 deletions .github/workflows/pico-package.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.

name: Pico W package

on:
workflow_dispatch:
push:
branches: [main]
paths:
- "collector/**"
- "common/**"
- "scripts/package_pico.py"
- ".github/workflows/pico-package.yml"

permissions:
contents: read

jobs:
package:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-python@v6
with:
python-version: "3.13"
- name: Build Pico package with example configuration
run: python scripts/package_pico.py --config collector/src/config.example.json
- name: Upload Pico package
uses: actions/upload-artifact@v7
with:
name: openCollector-pico
path: dist/pico.zip
if-no-files-found: error
27 changes: 27 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.

FROM ghcr.io/astral-sh/uv:0.8.22 AS uv

FROM python:3.13-slim AS builder
COPY --from=uv /uv /usr/local/bin/uv
WORKDIR /app
COPY common ./common
COPY storage/pyproject.toml storage/uv.lock ./storage/
WORKDIR /app/storage
RUN uv sync --frozen --no-dev --no-install-project

FROM python:3.13-slim AS runtime
ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
PATH=/app/storage/.venv/bin:$PATH
RUN useradd --create-home --uid 10001 opencollector
WORKDIR /app
COPY --from=builder /app/storage/.venv ./storage/.venv
COPY common ./common
COPY storage/src ./storage/src
WORKDIR /app/storage/src
USER opencollector
EXPOSE 8000
CMD ["litestar", "run", "--app", "app:app", "--host", "0.0.0.0", "--port", "8000"]
15 changes: 8 additions & 7 deletions collector/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
[project]
name = "collector"
version = "0.1.0"
description = "The component of openCollector which interfaces with sensors and uploads data to the database."
dependencies = ["common"]
requires-python = ">= 3.12"

[tool.uv]
dev-dependencies = [
description = "The component of openCollector which interfaces with sensors and uploads data to the database."
license = "MPL-2.0"
dependencies = ["common", "requests>=2.32.0"]
requires-python = ">= 3.12"

[dependency-groups]
dev = [
"micropython-rp2-pico-stubs>=1.20.0.post5",
"typing>=3.10.0.0",
]

[tool.uv.sources]
common = { path = "../common" }
common = { path = "../common" }
2 changes: 1 addition & 1 deletion collector/src/config/datastructures.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from typing import Any

from common import SensorCodeEnum

from .utils import get_attribute


Expand Down Expand Up @@ -106,7 +107,6 @@ def __init__(
config: The sensor config object, ie. a
dictionary contained within the "sensors" array in the config file.
index: The index of the sensor config within the "sensors" array.
allowed_gpio: The allowed GPIO pins as configured in the device config.
"""
self.sensor_id = get_attribute(config, "sensor_id", f"sensor[{index}]")
self.sensor_code = SensorCodeEnum[
Expand Down
18 changes: 9 additions & 9 deletions collector/src/config/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,32 +45,32 @@ def initialize_config(self):
"""
# Load config into a dictionary.
with open("config.json") as f:
config: dict[str, Any] = json.load(f)
raw_config: dict[str, Any] = json.load(f)

# Initialize all configs

# Device config
if "device" not in config:
if "device" not in raw_config:
raise ValueError("Config missing device config.")
device_config = DeviceConfig(config["device"], self.micropython)
device_config = DeviceConfig(raw_config["device"], self.micropython)

# Upload config
if "upload" not in config:
if "upload" not in raw_config:
raise ValueError("Config missing upload config.")
upload_config = UploadConfig(config["upload"])
upload_config = UploadConfig(raw_config["upload"])

# Sensor configs
if "sensors" not in config:
if "sensors" not in raw_config:
raise ValueError("Config missing sensors config.")

sensors: list[SensorConfig] = []
for config, index in config["sensors"]:
sensor = SensorConfig(config, index)
for index, sensor_config in enumerate(raw_config["sensors"]):
sensor = SensorConfig(sensor_config, index)
sensors.append(sensor)

# Collector config
collector_config = CollectorConfig(
config, device_config, upload_config, sensors
raw_config, device_config, upload_config, sensors
)

self._config = collector_config
Expand Down
2 changes: 1 addition & 1 deletion collector/src/config/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def get_attribute(
The config attribute, or None if it does not exist and is not required.
"""
try:
value = config["attribute"]
value = config[attribute]
except KeyError:
if required:
raise ValueError(
Expand Down
2 changes: 1 addition & 1 deletion collector/src/datastructures.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from common import SensorCodeEnum

type SensorData = dict[str, float | int]
SensorData = dict[str, float | int]
"""Alias for data returned by a sensor. The keys must be values of record_id in the sensor metadata and the values are numerical."""


Expand Down
Loading