Developing the package

For changing the ents package itself. If you only want to use it, see Using the package.

Setup

From the repository root:

cd python
python -m venv .venv
source .venv/bin/activate      # Windows: .venv\Scripts\activate
pip install -e .[dev]

Editable install, so changes take effect without reinstalling.

Running things locally

All three scripts honour CI=true, which makes them report problems instead of fixing them, exactly as CI runs them.

./test.sh                  # python -m unittest
./lint.sh                  # ruff check --fix   (CI=true: check only)
./format.sh                # ruff format        (CI=true: --check only)

A single test file, while iterating:

python -m unittest tests.test_sensor -v

Important

Match CI’s ruff version. pip install -e .[dev] is unpinned, so a newer ruff can report findings CI does not, or miss ones it does. If ./lint.sh passes locally and CI disagrees, check the version in the CI log first.

Building these docs

cd python/docs
./build.sh            # ./build.sh --open to open it afterwards

Output in docs/build/html/index.html. Requires the docs extra:

pip install -e .[docs]

Pages are Markdown via MyST. The API reference is generated from docstrings, so document the code, not the reference page.

Docstrings are Google style, which is what sphinx.ext.napoleon is configured for:

def example(cell, start):
    """One line summary.

    Longer explanation if it earns its place.

    Args:
        cell: What it is.
        start: What it is.

    Returns:
        What comes back.

    Raises:
        ValueError: When.
    """

Adding a SensorType

Adding a value to the SensorType enum takes three steps, and skipping the third breaks data collection in a way nothing warns you about.

  1. Add it to proto/sensor.proto.

  2. Regenerate the bindings:

    cd proto
    make            # or: make c / make python
    

    This writes both the C sources under embedded/libents/ and sensor_pb2.py here. Commit the generated files; they are checked in.

  3. Add an entry to SENSOR_DATA in src/ents/proto/sensor.py.

Step 3 is not optional. get_sensor_data() looks the type up with a bare dict access, and DirtViz calls parse_sensor_measurement() from this package on every uplink. A missing entry raises KeyError, which the backend turns into an HTTP 400 for the entire batch — discarding every real measurement that happened to be uploaded alongside it, on every upload, with nothing on the node to indicate anything is wrong.

Nothing currently fails the test suite when an entry is missing, so this is worth checking by hand until something does.

The two names matter downstream: DirtViz files a measurement under the enum name (POWER_VOLTAGE) with the name field as its measurement (Voltage), and that pair is what sensor_data() queries by.

Regenerating protobuf without a system protoc

The proto/Makefile expects protoc and nanopb_generator on PATH. If you only need the python side:

pip install grpcio-tools
python -m grpc_tools.protoc -I proto --python_out=proto/build proto/sensor.proto
cp proto/build/sensor_pb2.py python/src/ents/proto/

Check the generated header comment says a protobuf version compatible with the pin in pyproject.toml; generated code newer than the runtime fails at import.

Releasing

pyproject.toml holds the version. Bumping it matters beyond PyPI: DirtViz imports parse_sensor_measurement from this package, so a decoding change only reaches production once a release ships and the backend picks it up.