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 SENSORbinary request/response protocol (CayenneLPP-encoded, permission-gated): a client asks a node variantfor transmitsits periodictelemetry environmental readings overand the meshnode usingreplies. Readings are not broadcast on a lightweightschedule binaryas packetfree-floating format.packets, and the room server does not log structured sensor lines. This page describes howthe sensorreal datacapture flowspath throughusing the network,meshcore_py Python library, then how to capturewrite itthe atdecoded a room server, and how to build a Python integration that writes sensor recordsvalues 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.

SENSORHow VariantSensor NodeNodes BehaviourBehave

A MeshCore node flashedrunning standard firmware built with thesensor SENSORsupport firmware variant enters a repeating sleep/wake cycle. On each wake event itenabled reads allits attached I2C sensors (BME680,for SHTC3,example a BME680 or otherSHTC3) supportedand devices),exposes constructsthe readings as telemetry. Rather than waking on a compacttimer 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 payload, and transmits it as a standard MeshCore DATA packet addressed to the mesh broadcast ID. The node then returns to deep sleep until the next scheduled interval.

Sensor packets propagate through repeater nodes identically to chat messages, using the same store-and-forward and duplicate-suppression logic. Any node that receives the packet can decode it; a room server node provides a convenient aggregation point because it maintains persistent uptime and logs allthe traffic.responses.

Sensor Data Flow Through the Mesh

  1. SENSORA sensor node wakes, reads BME680:its BME680 (illustrative values: T=21.8°C, H=62.3%, P=1011.4 hPa, IAQ=42
Node constructs a sensor DATA packet with a standard MeshCore header (sender ID, hop count, timestamp)42) and a typed sensor payload. Packet is relayed hop-by-hop through repeater nodes toward the room server. Room server receives and logs the packet. Its web interface displaysholds the latest reading peras telemetry. 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). The request and the response are routed hop-by-hop over the mesh as addressed traffic between the collector and the sensor node. AThe backgroundnode Pythonreplies listenerwith ona CayenneLPP-encoded telemetry response (channel + LPP data type + value per metric), which the sameclient machinedecodes. readsThe room server output andcollector writes the decoded values to a time-series store.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.

ParsingCapturing SensorTelemetry Packetswith from Room Server Logsmeshcore_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 logs incoming packets to stdoutscrape in acapture structuredhappens linethrough format.the Aprotocol, sensornot packetby lineparsing looksconsole like:

2024-04-30T14:22:07Z SENSOR 0xDEADBEEF seq=1042 T=21.8 H=62.3 P=1011.4 IAQ=42 batt=3821

A simple Python parser to extract these fields:text.

import re,asyncio
subprocess,from datetimemeshcore SENSOR_REimport MeshCore, EventType

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

    SENSOR# (0x[0-9A-Fa-f]+)Subscribe seq=\d+to "telemetry r"T=([\d.]+)responses H=([\d.]+)as P=([\d.]+)they IAQ=([\d.]+) batt=(\d+)"
)arrive
    def parse_sensor_line(line)on_telemetry(event):
        m# 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 = SENSOR_RE.search(line)await ifmc.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 m:as returna Noneregex ts,match node_id,over temp,a hum,text pres,log iaq,line. battMap =those m.groups()decoded returnfields {to "time":your ts,own "node_id":record node_id,dict "temperature":before float(temp)writing to a store. (On a node, the device-side CLI exposes only sensor list, "humidity":sensor float(hum)get <key>, "pressure":and float(pres),sensor "iaq":set float(iaq),<key> "battery_mv":<value> int(batt), }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" {int(rec['time_epoch'])}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.