# MeshCore Sensor Data Integration

## MeshCore Sensor Data Integration

MeshCore supports environmental sensors, but the capability is more limited than a polished, deploy-and-forget telemetry system. Sensor support is a **compile-time feature** (the firmware's environment-sensor manager, enabled by build flags and example builds such as `simple_sensor` / `SensorMesh`) on standard MeshCore firmware roles (Companion, Repeater, Room Server) — there is no separate "SENSOR" firmware variant or role. Telemetry is delivered through MeshCore's **binary request/response protocol** (CayenneLPP-encoded, permission-gated): a client asks a node for its telemetry and the node replies. Readings are **not** broadcast on a schedule as free-floating packets, and the room server does **not** log structured sensor lines. This page describes the real capture path using the `meshcore_py` Python library, then how to write the decoded values to InfluxDB or CSV for analysis.

Note: parts of MeshCore's sensor/telemetry support are evolving and build-dependent. Confirm a sensor-capable build and the telemetry permission settings for your node before relying on the workflow below. If your firmware build does not include sensor support, telemetry capture is **not currently available** on that node.

### How Sensor Nodes Behave

A MeshCore node running standard firmware *built with sensor support enabled* reads its attached I2C sensors (for example a BME680 or SHTC3) and exposes the readings as **telemetry**. Rather than waking on a timer and broadcasting a reading to everyone, the node serves telemetry on request: a connected or in-range client issues a telemetry request and the node returns a CayenneLPP-encoded response. Access is permission-gated, so only authorised clients receive the data.

Because telemetry is request/response traffic, it is **addressed** rather than broadcast. It travels to and from the node over learned routes (falling back to flooding only when a path is not yet known), routed like other directed MeshCore messages — not as unsolicited broadcasts that "any node can decode." To collect readings continuously, you run an always-on client (a host PC or single-board computer connected to a MeshCore node over USB serial, TCP, or BLE) that polls each sensor node and logs the responses.

### Sensor Data Flow Through the Mesh

1. A sensor node reads its BME680 (illustrative values: T=21.8°C, H=62.3%, P=1011.4 hPa, IAQ=42) and holds the latest reading as telemetry.
2. A collector client sends a telemetry **request** to the node (in `meshcore_py`, via `commands.req_telemetry()` for a remote contact, or `commands.get_self_telemetry()` for the locally attached node).
3. The request and the response are routed hop-by-hop over the mesh as addressed traffic between the collector and the sensor node.
4. The node replies with a **CayenneLPP-encoded telemetry response** (channel + LPP data type + value per metric), which the client decodes.
5. The collector writes the decoded values to a time-series store (InfluxDB) or CSV.

Aggregation and visualisation are done by these external tools (for example a custom Python collector, or community projects such as MeshMonitor) — the stock room server is a store-and-forward BBS, not a per-node sensor dashboard.

### Capturing Telemetry with meshcore\_py

The supported way to capture MeshCore telemetry programmatically is the `meshcore_py` library, running on a host connected to a MeshCore node over serial, TCP, or BLE. You connect, request telemetry (or subscribe to telemetry responses), and decode the CayenneLPP payload. There is no structured `SENSOR ...` stdout log on the room server to scrape — capture happens through the protocol, not by parsing console text.

```
import asyncio
from meshcore import MeshCore, EventType

# Connect to a locally attached MeshCore node (also: create_tcp, create_ble)
async def main():
    mc = await MeshCore.create_serial("/dev/ttyUSB0")

    # Subscribe to telemetry responses as they arrive
    def on_telemetry(event):
        # event.payload contains the decoded CayenneLPP telemetry
        # (channel + LPP type + value per metric)
        print("telemetry:", event.payload)

    mc.subscribe(EventType.TELEMETRY_RESPONSE, on_telemetry)

    # Read the locally attached node's own sensors
    await mc.commands.get_self_telemetry()

    # Or request telemetry from a remote contact by key/name
    contacts = await mc.commands.get_contacts()
    # await mc.commands.req_telemetry(some_contact)

    await asyncio.sleep(60)

asyncio.run(main())
```

The decoded telemetry arrives as structured CayenneLPP fields (temperature, humidity, pressure, battery voltage, etc.), keyed by LPP channel and data type — not as a regex match over a text log line. Map those decoded fields to your own record dict before writing to a store. (On a node, the device-side CLI exposes only `sensor list`, `sensor get <key>`, and `sensor set <key> <value>` — a generic key/value store, not a live `sensor read` command.)

### Writing to InfluxDB

Once you have a record dict of decoded telemetry values, write it to InfluxDB using the line protocol. Compute an epoch-nanosecond timestamp from the time the reading was captured (or omit the explicit timestamp and let InfluxDB assign the write time):

```
import time
from influxdb_client import InfluxDBClient, WriteOptions

INFLUX_URL = "http://localhost:8086"
INFLUX_TOKEN = "your-token-here"
INFLUX_ORG = "meshamerica"
INFLUX_BUCKET = "mesh_sensors"

client = InfluxDBClient(url=INFLUX_URL, token=INFLUX_TOKEN, org=INFLUX_ORG)
write_api = client.write_api(write_options=WriteOptions(batch_size=1))

def write_record(rec):
    # rec holds decoded telemetry values from meshcore_py
    # InfluxDB line protocol; timestamp in epoch nanoseconds
    ts_ns = int(time.time() * 1e9)
    point = (
        "environment"
        f",node_id={rec['node_id']}"
        f" temperature={rec['temperature']},"
        f"humidity={rec['humidity']},"
        f"pressure={rec['pressure']},"
        f"iaq={rec['iaq']},"
        f"battery_mv={rec['battery_mv']}"
        f" {ts_ns}"
    )
    write_api.write(bucket=INFLUX_BUCKET, record=point)
```

Follow the [InfluxDB line protocol](https://docs.influxdata.com/influxdb/v2/reference/syntax/line-protocol/) rules for escaping tag and field values, and make sure the values feeding this function come from a real telemetry source (the `meshcore_py` path above).

### Writing to CSV for Data Science Workflows

For ad-hoc analysis with pandas or R, a CSV sink is often more convenient than a full time-series database:

```
import csv, pathlib

CSV_PATH = pathlib.Path("/var/log/mesh_sensors.csv")

def append_csv(rec):
    write_header = not CSV_PATH.exists()
    with CSV_PATH.open("a", newline="") as f:
        w = csv.DictWriter(f, fieldnames=rec.keys())
        if write_header:
            w.writeheader()
        w.writerow(rec)
```

Load into pandas for analysis: `df = pd.read_csv("/var/log/mesh_sensors.csv",parse_dates=["time"])`. Standard pandas operations (resampling, rolling averages, correlation with external weather data) apply directly.

### Integration with Python Data Science Tools

- **pandas** - Resample to hourly/daily averages, detect anomalies, compute sensor drift over time.
- **matplotlib / seaborn** - Plot temperature and humidity heatmaps across a sensor grid.
- **scikit-learn** - Train a simple regression to predict indoor temperature from outdoor readings. Useful for HVAC optimisation use cases.
- **Jupyter Notebook** - Combine data ingestion, analysis, and visualisation in a reproducible, shareable format ideal for community reporting.