# Meshtastic TIG Stack

- URL: https://www.technolitero.com/blog/meshtastic-tig-stack
- Markdown: https://www.technolitero.com/raw/meshtastic-tig-stack
- Date: 2026-01-01
- Author: Buddy
- Topic: Meshtastic
- Tags: meshtastic, grafana, mqtt
- Source: https://github.com/Technolitero/Meshtastic

Grafana does not speak MQTT well, and MQTT is a terrible long-term database. Telegraf plus InfluxDB is how Meshtastic JSON becomes a map and a message log.

Meshtastic can be used as a simple mesh between devices, but it also can be used for tracking devices through a Grafana dashboard. Grafana doesn’t work with MQTT very well. MQTT is not designed for long-term storage. It is great for small messages on defined topics. Visualizing and keeping those messages over time is the TIG stack: Telegraf, InfluxDB, and Grafana.

I am not going into detail on Docker or MQTT. Follow this to get a basic stack on Ubuntu. I keep containers under `/containers/tigstack`.

```yaml filename="docker-compose.yaml"
version: "3.8"
services:
  influxdb:
    image: influxdb:latest
    container_name: influxdb
    ports:
      - "8086:8086"
    volumes:
      - influxdb_data:/var/lib/influxdb2
    environment:
      - DOCKER_INFLUXDB_INIT_MODE=setup
      - DOCKER_INFLUXDB_INIT_USERNAME=admin
      - DOCKER_INFLUXDB_INIT_PASSWORD=SecureLogonPassword
      - DOCKER_INFLUXDB_INIT_ORG=YourOrganization
      - DOCKER_INFLUXDB_INIT_BUCKET=meshtastic_data
      - DOCKER_INFLUXDB_INIT_ADMIN_TOKEN=SecureInfluxPassword

  telegraf:
    image: telegraf:latest
    container_name: telegraf
    depends_on:
      - influxdb
    volumes:
      - ./:/etc/telegraf:ro
    environment:
      - MQTT_BROKER_URL=tcp://10.10.10.10:1883
      - MQTT_USER=meshtastic
      - MQTT_PASS=SecureMQTTPassword
      - INFLUX_TOKEN=InfluxDBAdminToken

  grafana:
    image: grafana/grafana:latest
    container_name: grafana
    ports:
      - "3000:3000"
    depends_on:
      - influxdb
    volumes:
      - grafana_data:/var/lib/grafana

volumes:
  influxdb_data:
  grafana_data:
```

Create `telegraf.conf` next to the compose file. Topics must be JSON. NRF nodes need the [decode bridge](https://www.technolitero.com/blog/meshtastic-nrf-mqtt-decoding) first.

```ini filename="telegraf.conf"
[agent]
  interval = "10s"

outputs.influxdb_v2
  urls = ["http://influxdb:8086"]
  token = "${INFLUX_TOKEN}"
  organization = "Organization"
  bucket = "meshtastic_data"

inputs.mqtt_consumer
  servers = ["${MQTT_BROKER_URL}"]
  topics = [
    "msh/Meshtastic/2/json/Main/!9e75dbcc",
    "msh/Meshtastic/2/json/Private/!9e75dbcc"
  ]
  username = "${MQTT_USER}"
  password = "${MQTT_PASS}"
  client_id = "telegraf_meshtastic"
  data_format = "json"
  json_string_fields = ["payload_text"]
```

Bring it up with `docker compose up -d`. Log into Influx at `:8086`, generate an all-access API token, then in Grafana (`:3000`, admin/admin the first time) add an InfluxDB data source using Flux, the compose org, bucket, and that token.

## Message table

This Flux query pulls messages, maps node IDs to names, and keeps channel plus text.

```ini filename="messages.flux"
import "regexp"

from(bucket: "meshtastic_data")
  |> range(start: v.timeRangeStart, stop: v.timeRangeStop)
  |> filter(fn: (r) => r._measurement == "mqtt_consumer")
  |> filter(fn: (r) => r._field == "payload_text" or r._field == "from" or r._field == "payload_from")
  |> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value")
  |> map(fn: (r) => ({
      r with
      node_id: if exists r.from then string(v: r.from)
               else if exists r.payload_from then string(v: r.payload_from)
               else "unknown"
    }))
  |> map(fn: (r) => ({
      r with
      node_name: if r.node_id == "2658588132" then "Base"
                 else if r.node_id == "1685377223" then "Node1"
                 else r.node_id
    }))
  |> filter(fn: (r) => exists r.payload_text and r.payload_text != "")
  |> keep(columns: ["_time", "node_name", "payload_text"])
  |> rename(columns: {payload_text: "message", node_name: "node_id"})
```

Last-known GPS for 30 days is the same pattern: pivot lat/lon, map names, `top(n: 1)` per node, divide `latitude_i` by 10,000,000. A second query without `top` gives the track over the dashboard time range.

ESP32 nodes can publish JSON and encrypted MQTT. NRF nodes do not have Wi-Fi and do not speak JSON. In my setup an ESP32 client uplinks over Wi-Fi into Home Assistant’s MQTT broker, and NRF SenseCaps stay on the mesh as trackers.

> **GitHub:** [Technolitero/Meshtastic](https://github.com/Technolitero/Meshtastic)
