Using the package¶
For people consuming ENTS data or working with a node. If you are changing the package itself, see Developing the package.
Install¶
pip install ents
Python 3.11 or newer.
Getting data out of DirtViz¶
BackendClient wraps the DirtViz API. Data is keyed by cell, and a cell is
looked up by name or id.
from datetime import datetime, timedelta
from ents.dirtviz import BackendClient
client = BackendClient()
cell = client.cell_from_name("Field Cell 3")
end = datetime.now()
start = end - timedelta(days=7)
power = client.power_data(cell, start, end)
teros = client.teros_data(cell, start, end)
print(power.head())
Every method returns a pandas DataFrame with a timestamp column.
cells() lists everything available, and cell_from_id() is the counterpart to
cell_from_name().
Two ingest paths¶
A node’s data lands in one of two places depending on which fPort its firmware uploads on, and they are served by different endpoints:
fPort |
Stored in |
Read with |
|---|---|---|
1 |
dedicated power / teros tables |
|
2 |
generic sensor table |
|
Current firmware uses fPort 2. Older nodes are still on fPort 1, so a query
across the whole fleet has to try both. A node that returns nothing from
power_data() is not necessarily dead; it may simply be on the other path.
sensor_data() is named by the SensorType enum name plus the human readable
measurement name from SENSOR_DATA, not by a short code:
client.sensor_data(cell, "POWER_VOLTAGE", "Voltage", start, end)
("power", "v") returns nothing at all, which is the trap.
Warning
The endpoint returns an identical empty response for a sensor that does not exist and one that simply has no data in the window. A typo is indistinguishable from a dead node.
GET /cell/<id>/sensors lists what a cell actually has registered, and is the
only reliable way to find the right names:
$ curl https://dirtviz.jlab.ucsc.edu/api/cell/1483/sensors
[{"name": "POWER_VOLTAGE", "measurement": "Voltage", "unit": "mV"}, ...]
Resampling¶
The API aggregates into hourly buckets by default. That matters whenever you care about when readings happened rather than their values: against hourly buckets every node appears to report once an hour no matter what it really does, and anything shorter than a multi-hour gap is invisible. Raw intervals on the current deployment range from 14 seconds to 5 minutes.
sensor_data() takes a resample argument for this. Pass "none" for raw
measurements.
Plotting¶
from ents.dirtviz import plot_data
Takes the DataFrames above and renders them with matplotlib.
Encoding and decoding measurements¶
from ents.proto import decode_measurement
meas = decode_measurement(payload_bytes)
For the newer repeated-measurement format used on fPort 2:
from ents.proto.sensor import parse_sensor_measurement
meas = parse_sensor_measurement(payload_bytes)
for m in meas["measurements"]:
print(m["type"], m["name"], m["unit"])
parse_sensor_measurement() is also what the DirtViz backend calls on every
uplink, so a SensorType it cannot resolve rejects the whole batch. See
Developing the package before adding one.
Configuring a node¶
ents --help
The CLI covers encoding test payloads, calibration against a reference instrument, and writing a node’s user configuration over serial.
Simulating a node¶
Useful when you want data flowing without hardware:
from ents.simulator import NodeSimulator
tools/http_decoder.py in the repository runs a small webserver that decodes
uploads, which pairs well with the simulator.