diff --git a/.gitignore b/.gitignore index 723ef36..e6b3fb6 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ -.idea \ No newline at end of file +__pycache__/ +*.pyc +.DS_Store +.idea diff --git a/assets/images/01_docker_push.png b/assets/images/01_docker_push.png new file mode 100644 index 0000000..5bfdd2d Binary files /dev/null and b/assets/images/01_docker_push.png differ diff --git a/assets/images/02_mosquitto_running.png b/assets/images/02_mosquitto_running.png new file mode 100644 index 0000000..370ec93 Binary files /dev/null and b/assets/images/02_mosquitto_running.png differ diff --git a/assets/images/03_grafana_dashboard.png b/assets/images/03_grafana_dashboard.png new file mode 100644 index 0000000..3a5bb74 Binary files /dev/null and b/assets/images/03_grafana_dashboard.png differ diff --git a/assets/images/04_grafana_stats.png b/assets/images/04_grafana_stats.png new file mode 100644 index 0000000..6f0f9f6 Binary files /dev/null and b/assets/images/04_grafana_stats.png differ diff --git a/assets/images/05_mqtt_stream.png b/assets/images/05_mqtt_stream.png new file mode 100644 index 0000000..8be770e Binary files /dev/null and b/assets/images/05_mqtt_stream.png differ diff --git a/assets/images/06_influxdb_data.png b/assets/images/06_influxdb_data.png new file mode 100644 index 0000000..e4e0d9f Binary files /dev/null and b/assets/images/06_influxdb_data.png differ diff --git a/report.md b/report.md new file mode 100644 index 0000000..60340e5 --- /dev/null +++ b/report.md @@ -0,0 +1,257 @@ +# Лабораторная работа 2 - Docker + +## Введение + +Задача: развернуть систему сбора данных с датчиков через MQTT-брокер с визуализацией в Grafana. Все сервисы запускаются в Docker-контейнерах на трёх виртуальных машинах из лабораторной 1. + +Среда выполнения: Mac M1, Docker Desktop. Три "машины" эмулируются через отдельные docker-compose файлы, объединённые общей сетью `lab2_net`. + +--- + +## Архитектура системы + +``` +Linux A (client) Linux B (gateway) Linux C (server) +6 sensor containers --> mosquitto:1883 --> telegraf --> influxdb:8086 +igml1/sensor-sim --> grafana:3000 +``` + +| Машина | Сервисы | Порты | +|--------|---------|-------| +| Linux A | 6 sensor-sim контейнеров | - | +| Linux B | eclipse-mosquitto | 1883 | +| Linux C | influxdb:1.8, telegraf, grafana | 8086, 3000 | + +--- + +## Linux A - Симуляторы датчиков + +### Класс Sensor и наследники + +Файл `vms/client/simulator/sensor.py`. + +Базовый класс `Sensor` - абстрактный, определяет метод `read()` и логику формулы генерации значения. Формула выбирается через ENV-переменную `FORMULA_TYPE`: + +- `random` - случайный шум в диапазоне амплитуды +- `sin` - синусоидальный сигнал: `sin(step * 11 / 100) * amplitude` (11 - день рождения) +- `linear` - пилообразный сигнал с периодом 11 + +Четыре типа датчиков: + +| Класс | Тип | Базовое значение | Амплитуда | +|-------|-----|-----------------|-----------| +| TemperatureSensor | temperature | 25.0 °C | 5.0 | +| PressureSensor | pressure | 1.013 бар | 0.05 | +| CurrentSensor | current | 2.5 А | 1.1 (= 11/10) | +| HumiditySensor | humidity | 60.0 % | 11.0 | + +Амплитуды датчиков тока и влажности используют число 11 (день рождения 11.11) как операнд формулы. + +### Конфигурация через переменные среды + +| Переменная | Назначение | По умолчанию | +|-----------|-----------|--------------| +| `SENSOR_TYPE` | Тип датчика (temperature/pressure/current/humidity) | temperature | +| `SENSOR_NAME` | Имя датчика | sensor1 | +| `MQTT_BROKER` | Хост брокера | mosquitto | +| `MQTT_PORT` | Порт брокера | 1883 | +| `INTERVAL` | Интервал публикации, сек | 5 | +| `FORMULA_TYPE` | Тип формулы (random/sin/linear) | random | +| `TOPIC_FORMAT` | Формат топика (value/json) | value | + +### Топики MQTT + +При `TOPIC_FORMAT=value` (по умолчанию): +- топик: `sensors//` +- payload: `22.4` (plain float) + +При `TOPIC_FORMAT=json`: +- топик: `sensors/` +- payload: `{"name": "sensor1", "value": 22.4}` + +### Шесть контейнеров + +| Контейнер | Тип | Имя | Интервал | Формула | +|-----------|-----|-----|----------|---------| +| sensor_temp_1 | temperature | temp_room | 3 сек | sin | +| sensor_temp_2 | temperature | temp_outdoor | 5 сек | random | +| sensor_pressure_1 | pressure | pressure_main | 4 сек | linear | +| sensor_pressure_2 | pressure | pressure_backup | 7 сек | random | +| sensor_current_1 | current | current_line1 | 2 сек | sin | +| sensor_humidity_1 | humidity | humidity_hall | 6 сек | linear | + +### Docker Hub + +Образ опубликован в публичный репозиторий: `igml1/sensor-sim:latest` + +```bash +docker build -t igml1/sensor-sim:latest ./vms/client/simulator +docker push igml1/sensor-sim:latest +``` + +![Скриншот 1: docker push igml1/sensor-sim](assets/images/01_docker_push.png) + +--- + +## Linux B - Mosquitto брокер + +### Конфигурация (`vms/gateway/mosquitto/mosquitto.conf`) + +``` +listener 1883 +allow_anonymous true +persistence true +persistence_location /mosquitto/data/ +log_dest stdout +``` + +Брокер принимает анонимные подключения, слушает порт 1883, сохраняет данные между перезапусками. + +### Запуск + +Через docker-compose (`vms/gateway/docker-compose.yml`): + +```bash +docker compose -f vms/gateway/docker-compose.yml up -d +``` + +Или через скрипт (`vms/gateway/mosquitto/start.sh`): + +```bash +docker run \ + -v "$(pwd)/mosquitto:/mosquitto/config" \ + -p 1883:1883 \ + --name mosquitto \ + --network lab2_net \ + --rm \ + eclipse-mosquitto +``` + +![Скриншот 2: mosquitto_sub - брокер принимает сообщения от датчиков](assets/images/02_mosquitto_running.png) + +--- + +## Linux C - InfluxDB + Telegraf + Grafana + +### InfluxDB 1.8 + +База данных `sensors`, пользователь `telegraf`/`telegraf`. Инициализируется через переменные среды при первом запуске: + +```yaml +environment: + - INFLUXDB_DB=sensors + - INFLUXDB_USER=telegraf + - INFLUXDB_USER_PASSWORD=telegraf + - INFLUXDB_ADMIN_USER=admin + - INFLUXDB_ADMIN_PASSWORD=admin +``` + +### Telegraf (`vms/server/telegraf/telegraf.conf`) + +Подписка на все топики брокера, запись в InfluxDB. Ссылки на сервисы - по alias контейнеров, не по IP: + +```toml +[[inputs.mqtt_consumer]] + servers = ["tcp://mosquitto:1883"] + topics = ["sensors/#"] + data_format = "value" + data_type = "float" + name_override = "sensor_data" + +[[outputs.influxdb]] + urls = ["http://influxdb:8086"] + database = "sensors" + username = "telegraf" + password = "telegraf" + skip_database_creation = true +``` + +Каждое сообщение сохраняется как measurement `sensor_data` с тегом `topic` = полный путь топика. + +### Grafana + +Порт 3000, логин `admin`/`admin`. + +Datasource и dashboard подгружаются автоматически через provisioning (`vms/server/grafana/provisioning/`). + +Dashboard содержит: +- Time series со всеми 6 датчиками на одном графике (GROUP BY topic) +- 6 stat-панелей с текущим значением каждого датчика +- Общую stat-панель со средним значением по всем датчикам + +![Скриншот 3: Grafana web-интерфейс доступен на localhost:3000](assets/images/03_grafana_dashboard.png) + +![Скриншот 4: Sensor Dashboard - time series всех датчиков, текущие значения и средние](assets/images/04_grafana_stats.png) + +### Volumes + +Данные InfluxDB и конфигурация Grafana сохраняются в именованных томах - при пересоздании контейнеров данные и дашборды не теряются: + +```yaml +volumes: + influx_data:/var/lib/influxdb + grafana_data:/var/lib/grafana +``` + +--- + +## Проверка работы + +### Мониторинг потока данных + +```bash +# Смотреть сообщения в брокере +docker exec mosquitto mosquitto_sub -t "sensors/#" -v + +# Последние значения в InfluxDB +docker exec influxdb influx -database sensors -execute "SELECT last(value) FROM sensor_data GROUP BY topic" +``` + +![Скриншот 5: mosquitto_sub - поток сообщений от датчиков](assets/images/05_mqtt_stream.png) + +![Скриншот 6: InfluxDB - данные в базе](assets/images/06_influxdb_data.png) + +--- + +## Инструкция по запуску + +```bash +# 1. Создать сеть +docker network create lab2_net + +# 2. Запустить брокер (Linux B) +docker compose -f vms/gateway/docker-compose.yml up -d + +# 3. Запустить сервер (Linux C) +docker compose -f vms/server/docker-compose.yml up -d + +# 4. Запустить датчики (Linux A) - образ скачается с Docker Hub +docker compose -f vms/client/docker-compose.yml up -d + +# 5. Открыть Grafana: http://localhost:3000 (admin/admin) +``` + +--- + +## Структура репозитория + +``` +lab2/ + assets/images/ - скриншоты для отчёта + vms/ + client/ + simulator/ - код симулятора (sensor.py, main.py, Dockerfile) + docker-compose.yml - 6 контейнеров датчиков + gateway/ + mosquitto/ + mosquitto.conf - конфиг брокера + start.sh - скрипт запуска без compose + docker-compose.yml + server/ + telegraf/ + telegraf.conf - подписка на MQTT, запись в InfluxDB + grafana/ + provisioning/ - автоматическая загрузка datasource и dashboard + docker-compose.yml - influxdb + telegraf + grafana + report.md +``` diff --git a/vms/client/docker-compose.yml b/vms/client/docker-compose.yml new file mode 100644 index 0000000..cc4b716 --- /dev/null +++ b/vms/client/docker-compose.yml @@ -0,0 +1,82 @@ +services: + sensor_temp_1: + image: igml1/sensor-sim:latest + container_name: sensor_temp_1 + environment: + - SENSOR_TYPE=temperature + - SENSOR_NAME=temp_room + - MQTT_BROKER=mosquitto + - INTERVAL=3 + - FORMULA_TYPE=sin + networks: + - lab2_net + restart: always + + sensor_temp_2: + image: igml1/sensor-sim:latest + container_name: sensor_temp_2 + environment: + - SENSOR_TYPE=temperature + - SENSOR_NAME=temp_outdoor + - MQTT_BROKER=mosquitto + - INTERVAL=5 + - FORMULA_TYPE=random + networks: + - lab2_net + restart: always + + sensor_pressure_1: + image: igml1/sensor-sim:latest + container_name: sensor_pressure_1 + environment: + - SENSOR_TYPE=pressure + - SENSOR_NAME=pressure_main + - MQTT_BROKER=mosquitto + - INTERVAL=4 + - FORMULA_TYPE=linear + networks: + - lab2_net + restart: always + + sensor_pressure_2: + image: igml1/sensor-sim:latest + container_name: sensor_pressure_2 + environment: + - SENSOR_TYPE=pressure + - SENSOR_NAME=pressure_backup + - MQTT_BROKER=mosquitto + - INTERVAL=7 + - FORMULA_TYPE=random + networks: + - lab2_net + restart: always + + sensor_current_1: + image: igml1/sensor-sim:latest + container_name: sensor_current_1 + environment: + - SENSOR_TYPE=current + - SENSOR_NAME=current_line1 + - MQTT_BROKER=mosquitto + - INTERVAL=2 + - FORMULA_TYPE=sin + networks: + - lab2_net + restart: always + + sensor_humidity_1: + image: igml1/sensor-sim:latest + container_name: sensor_humidity_1 + environment: + - SENSOR_TYPE=humidity + - SENSOR_NAME=humidity_hall + - MQTT_BROKER=mosquitto + - INTERVAL=6 + - FORMULA_TYPE=linear + networks: + - lab2_net + restart: always + +networks: + lab2_net: + external: true diff --git a/vms/client/simulator/Dockerfile b/vms/client/simulator/Dockerfile index eb66b3a..11396f1 100644 --- a/vms/client/simulator/Dockerfile +++ b/vms/client/simulator/Dockerfile @@ -1,6 +1,6 @@ -FROM python:alpine3.19 +FROM python:3.11-alpine WORKDIR /app COPY requirements.txt . RUN pip install -r requirements.txt -COPY . . +COPY sensor.py main.py ./ CMD ["python", "main.py"] diff --git a/vms/client/simulator/main.py b/vms/client/simulator/main.py index 4d17730..65f7c05 100644 --- a/vms/client/simulator/main.py +++ b/vms/client/simulator/main.py @@ -1,27 +1,48 @@ -import paho.mqtt.client as paho -from os import environ +import os import time +import json +import paho.mqtt.client as mqtt +from sensor import SENSOR_TYPES -from entity.sensor import * +BROKER_HOST = os.environ.get("MQTT_BROKER", "mosquitto") +BROKER_PORT = int(os.environ.get("MQTT_PORT", "1883")) +SENSOR_TYPE = os.environ.get("SENSOR_TYPE", "temperature") +SENSOR_NAME = os.environ.get("SENSOR_NAME", "sensor1") +INTERVAL = float(os.environ.get("INTERVAL", "5")) +FORMULA_TYPE = os.environ.get("FORMULA_TYPE", "random") +TOPIC_FORMAT = os.environ.get("TOPIC_FORMAT", "value") -broker = "localhost" if "SIM_HOST" not in environ.keys() else environ["SIM_HOST"] -port = 1883 if "SIM_PORT" not in environ.keys() else int(environ["SIM_PORT"]) -name = "sensor" if "SIM_NAME" not in environ.keys() else environ["SIM_NAME"] -period = 1 if "SIM_PERIOD" not in environ.keys() else int(environ["SIM_PERIOD"]) -type_sim = "temperature" if "SIM_TYPE" not in environ.keys() else environ["SIM_TYPE"] -sensors = {"temperature": Temperature, "pressure": Pressure, "current": Current} +sensor_class = SENSOR_TYPES.get(SENSOR_TYPE, SENSOR_TYPES["temperature"]) +sensor = sensor_class(name=SENSOR_NAME, formula=FORMULA_TYPE) +client = mqtt.Client() -def on_publish(client, userdata, result): # create function for callback - print(f"data published {userdata}") - pass +def on_connect(c, userdata, flags, rc): + print(f"Connected to {BROKER_HOST}:{BROKER_PORT} (rc={rc})") +client.on_connect = on_connect -sensor = sensors[type_sim](name=name) -client1 = paho.Client(sensor.name) # create client object -client1.on_publish = on_publish # assign function to callback -client1.connect(broker, port) # establish connection while True: - sensor.generate_new_value() - ret = client1.publish("sensors/" + sensor.type + "/" + sensor.name, sensor.get_data()) # publish - time.sleep(period) + try: + client.connect(BROKER_HOST, BROKER_PORT, 60) + break + except Exception as e: + print(f"Broker not ready: {e}, retry in 3s...") + time.sleep(3) + +client.loop_start() + +topic_base = f"sensors/{SENSOR_TYPE}" + +while True: + value = sensor.read() + if TOPIC_FORMAT == "json": + topic = topic_base + payload = json.dumps({"name": SENSOR_NAME, "value": value}) + else: + topic = f"{topic_base}/{SENSOR_NAME}" + payload = str(value) + + client.publish(topic, payload) + print(f"[{SENSOR_NAME}] {topic} -> {payload}") + time.sleep(INTERVAL) diff --git a/vms/client/simulator/requirements.txt b/vms/client/simulator/requirements.txt index 13e9d8e..4d9eb06 100644 --- a/vms/client/simulator/requirements.txt +++ b/vms/client/simulator/requirements.txt @@ -1 +1,2 @@ -paho_mqtt==1.6.1 +paho-mqtt==1.6.1 + diff --git a/vms/client/simulator/sensor.py b/vms/client/simulator/sensor.py new file mode 100644 index 0000000..62b1c39 --- /dev/null +++ b/vms/client/simulator/sensor.py @@ -0,0 +1,66 @@ +import math +import random +from abc import ABC, abstractmethod + +BIRTH_DAY = 11 # 11.11 - день и месяц рождения + + +class Sensor(ABC): + def __init__(self, name: str, formula: str = "random"): + self.name = name + self.formula = formula + self._step = 0 + + @abstractmethod + def _base(self) -> float: + pass + + @abstractmethod + def _amplitude(self) -> float: + pass + + def read(self) -> float: + self._step += 1 + base = self._base() + amp = self._amplitude() + + if self.formula == "sin": + noise = math.sin(self._step * BIRTH_DAY / 100.0) * amp + elif self.formula == "linear": + noise = ((self._step % BIRTH_DAY) / BIRTH_DAY * 2 - 1) * amp + else: + noise = random.uniform(-amp, amp) + + return round(base + noise, 2) + + +class TemperatureSensor(Sensor): + """Датчик температуры, диапазон 18-32 C""" + def _base(self): return 25.0 + def _amplitude(self): return 5.0 + + +class PressureSensor(Sensor): + """Датчик давления, диапазон 0.9-1.1 бар""" + def _base(self): return 1.013 + def _amplitude(self): return 0.05 + + +class CurrentSensor(Sensor): + """Датчик тока, диапазон 0.5-4.5 А""" + def _base(self): return 2.5 + def _amplitude(self): return 1.1 # 11/10 - используем день рождения + + +class HumiditySensor(Sensor): + """Датчик влажности, диапазон 40-80%""" + def _base(self): return 60.0 + def _amplitude(self): return 11.0 # amplitude = BIRTH_DAY + + +SENSOR_TYPES = { + "temperature": TemperatureSensor, + "pressure": PressureSensor, + "current": CurrentSensor, + "humidity": HumiditySensor, +} diff --git a/vms/gateway/docker-compose.yml b/vms/gateway/docker-compose.yml new file mode 100644 index 0000000..ed4e209 --- /dev/null +++ b/vms/gateway/docker-compose.yml @@ -0,0 +1,17 @@ +services: + mosquitto: + image: eclipse-mosquitto + container_name: mosquitto + ports: + - "1883:1883" + volumes: + - ./mosquitto/mosquitto.conf:/mosquitto/config/mosquitto.conf + networks: + lab2_net: + aliases: + - mosquitto + restart: always + +networks: + lab2_net: + external: true diff --git a/vms/gateway/mosquitto/mosquitto.conf b/vms/gateway/mosquitto/mosquitto.conf index c8348ac..710c582 100644 --- a/vms/gateway/mosquitto/mosquitto.conf +++ b/vms/gateway/mosquitto/mosquitto.conf @@ -1,2 +1,5 @@ listener 1883 allow_anonymous true +persistence true +persistence_location /mosquitto/data/ +log_dest stdout diff --git a/vms/gateway/mosquitto/start.sh b/vms/gateway/mosquitto/start.sh new file mode 100644 index 0000000..bbcd483 --- /dev/null +++ b/vms/gateway/mosquitto/start.sh @@ -0,0 +1,8 @@ +#!/bin/sh +docker run \ + -v "$(pwd)/mosquitto:/mosquitto/config" \ + -p 1883:1883 \ + --name mosquitto \ + --network lab2_net \ + --rm \ + eclipse-mosquitto diff --git a/vms/server/docker-compose.yml b/vms/server/docker-compose.yml new file mode 100644 index 0000000..2a1eb6b --- /dev/null +++ b/vms/server/docker-compose.yml @@ -0,0 +1,52 @@ +services: + influxdb: + image: influxdb:1.8 + container_name: influxdb + environment: + - INFLUXDB_DB=sensors + - INFLUXDB_USER=telegraf + - INFLUXDB_USER_PASSWORD=telegraf + - INFLUXDB_ADMIN_USER=admin + - INFLUXDB_ADMIN_PASSWORD=admin + volumes: + - influx_data:/var/lib/influxdb + networks: + lab2_net: + aliases: + - influxdb + restart: always + + telegraf: + image: telegraf + container_name: telegraf + volumes: + - ./telegraf/telegraf.conf:/etc/telegraf/telegraf.conf:ro + networks: + - lab2_net + depends_on: + - influxdb + restart: always + + grafana: + image: grafana/grafana + container_name: grafana + ports: + - "3000:3000" + environment: + - GF_SECURITY_ADMIN_PASSWORD=admin + volumes: + - ./grafana/provisioning:/etc/grafana/provisioning + - grafana_data:/var/lib/grafana + networks: + - lab2_net + depends_on: + - influxdb + restart: always + +volumes: + influx_data: + grafana_data: + +networks: + lab2_net: + external: true diff --git a/vms/server/grafana/provisioning/dashboards/default.yaml b/vms/server/grafana/provisioning/dashboards/default.yaml new file mode 100644 index 0000000..da1363b --- /dev/null +++ b/vms/server/grafana/provisioning/dashboards/default.yaml @@ -0,0 +1,11 @@ +apiVersion: 1 + +providers: + - name: sensors + orgId: 1 + type: file + disableDeletion: false + editable: true + allowUiUpdates: true + options: + path: /etc/grafana/provisioning/dashboards diff --git a/vms/server/grafana/provisioning/dashboards/sensors.json b/vms/server/grafana/provisioning/dashboards/sensors.json new file mode 100644 index 0000000..fa6afe9 --- /dev/null +++ b/vms/server/grafana/provisioning/dashboards/sensors.json @@ -0,0 +1,130 @@ +{ + "title": "Sensor Dashboard", + "uid": "sensors-main", + "schemaVersion": 38, + "version": 1, + "refresh": "5s", + "time": { "from": "now-5m", "to": "now" }, + "panels": [ + { + "id": 1, + "title": "All Sensors - Time Series", + "type": "timeseries", + "gridPos": { "h": 8, "w": 24, "x": 0, "y": 0 }, + "datasource": { "uid": "influxdb-sensors" }, + "targets": [ + { + "refId": "A", + "rawQuery": true, + "query": "SELECT \"value\" FROM \"sensor_data\" WHERE $timeFilter GROUP BY \"topic\"", + "alias": "$tag_topic" + } + ] + }, + { + "id": 2, + "title": "temp_room (Temperature)", + "type": "stat", + "gridPos": { "h": 4, "w": 4, "x": 0, "y": 8 }, + "datasource": { "uid": "influxdb-sensors" }, + "targets": [ + { + "refId": "A", + "rawQuery": true, + "query": "SELECT last(\"value\") FROM \"sensor_data\" WHERE \"topic\" = 'sensors/temperature/temp_room' AND $timeFilter" + } + ], + "options": { "reduceOptions": { "calcs": ["lastNotNull"] } } + }, + { + "id": 3, + "title": "temp_outdoor (Temperature)", + "type": "stat", + "gridPos": { "h": 4, "w": 4, "x": 4, "y": 8 }, + "datasource": { "uid": "influxdb-sensors" }, + "targets": [ + { + "refId": "A", + "rawQuery": true, + "query": "SELECT last(\"value\") FROM \"sensor_data\" WHERE \"topic\" = 'sensors/temperature/temp_outdoor' AND $timeFilter" + } + ], + "options": { "reduceOptions": { "calcs": ["lastNotNull"] } } + }, + { + "id": 4, + "title": "pressure_main (Pressure)", + "type": "stat", + "gridPos": { "h": 4, "w": 4, "x": 8, "y": 8 }, + "datasource": { "uid": "influxdb-sensors" }, + "targets": [ + { + "refId": "A", + "rawQuery": true, + "query": "SELECT last(\"value\") FROM \"sensor_data\" WHERE \"topic\" = 'sensors/pressure/pressure_main' AND $timeFilter" + } + ], + "options": { "reduceOptions": { "calcs": ["lastNotNull"] } } + }, + { + "id": 5, + "title": "pressure_backup (Pressure)", + "type": "stat", + "gridPos": { "h": 4, "w": 4, "x": 12, "y": 8 }, + "datasource": { "uid": "influxdb-sensors" }, + "targets": [ + { + "refId": "A", + "rawQuery": true, + "query": "SELECT last(\"value\") FROM \"sensor_data\" WHERE \"topic\" = 'sensors/pressure/pressure_backup' AND $timeFilter" + } + ], + "options": { "reduceOptions": { "calcs": ["lastNotNull"] } } + }, + { + "id": 6, + "title": "current_line1 (Current)", + "type": "stat", + "gridPos": { "h": 4, "w": 4, "x": 16, "y": 8 }, + "datasource": { "uid": "influxdb-sensors" }, + "targets": [ + { + "refId": "A", + "rawQuery": true, + "query": "SELECT last(\"value\") FROM \"sensor_data\" WHERE \"topic\" = 'sensors/current/current_line1' AND $timeFilter" + } + ], + "options": { "reduceOptions": { "calcs": ["lastNotNull"] } } + }, + { + "id": 7, + "title": "humidity_hall (Humidity)", + "type": "stat", + "gridPos": { "h": 4, "w": 4, "x": 20, "y": 8 }, + "datasource": { "uid": "influxdb-sensors" }, + "targets": [ + { + "refId": "A", + "rawQuery": true, + "query": "SELECT last(\"value\") FROM \"sensor_data\" WHERE \"topic\" = 'sensors/humidity/humidity_hall' AND $timeFilter" + } + ], + "options": { "reduceOptions": { "calcs": ["lastNotNull"] } } + }, + { + "id": 8, + "title": "Average across all sensors", + "type": "stat", + "gridPos": { "h": 4, "w": 24, "x": 0, "y": 12 }, + "datasource": { "uid": "influxdb-sensors" }, + "targets": [ + { + "refId": "A", + "rawQuery": true, + "query": "SELECT mean(\"value\") FROM \"sensor_data\" WHERE $timeFilter GROUP BY \"topic\"" + } + ], + "options": { "reduceOptions": { "calcs": ["mean"] } } + } + ] +} diff --git a/vms/server/grafana/provisioning/datasources/influxdb.yaml b/vms/server/grafana/provisioning/datasources/influxdb.yaml new file mode 100644 index 0000000..48437f3 --- /dev/null +++ b/vms/server/grafana/provisioning/datasources/influxdb.yaml @@ -0,0 +1,15 @@ +apiVersion: 1 + +datasources: + - name: InfluxDB + uid: influxdb-sensors + type: influxdb + access: proxy + url: http://influxdb:8086 + database: sensors + user: telegraf + secureJsonData: + password: telegraf + jsonData: + httpMode: GET + isDefault: true diff --git a/vms/server/telegraf/telegraf.conf b/vms/server/telegraf/telegraf.conf new file mode 100644 index 0000000..73b6664 --- /dev/null +++ b/vms/server/telegraf/telegraf.conf @@ -0,0 +1,17 @@ +[agent] + interval = "5s" + flush_interval = "10s" + +[[inputs.mqtt_consumer]] + servers = ["tcp://mosquitto:1883"] + topics = ["sensors/#"] + data_format = "value" + data_type = "float" + name_override = "sensor_data" + +[[outputs.influxdb]] + urls = ["http://influxdb:8086"] + database = "sensors" + username = "telegraf" + password = "telegraf" + skip_database_creation = true