Skip to main content

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 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.