WIP: Dockerfile update + Grafana dashboard JSON + InfluxDB population script
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
FROM python:3.12-slim
|
FROM python:3.12-slim
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
RUN pip install --no-cache-dir paho-mqtt requests
|
RUN pip install --no-cache-dir paho-mqtt requests influxdb-client
|
||||||
COPY simulator.py /app/
|
COPY simulator.py /app/
|
||||||
EXPOSE 8081
|
EXPOSE 8081
|
||||||
# Healthcheck endpoint (simple HTTP server)
|
# Healthcheck endpoint (simple HTTP server)
|
||||||
|
|||||||
1
grafana_smart-city-overview.json
Normal file
1
grafana_smart-city-overview.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"meta":{"type":"db","canSave":true,"canEdit":true,"canAdmin":true,"canStar":true,"canDelete":true,"slug":"smart-city-overview","url":"/d/e09bfbc3-6e4b-4134-963c-c98f9ffbb0f8/smart-city-overview","expires":"0001-01-01T00:00:00Z","created":"2026-05-04T15:26:52Z","updated":"2026-05-04T15:26:52Z","updatedBy":"admin","createdBy":"admin","version":1,"hasAcl":false,"isFolder":false,"folderId":0,"folderUid":"","folderTitle":"General","folderUrl":"","provisioned":false,"provisionedExternalId":"","annotationsPermissions":{"dashboard":{"canAdd":true,"canEdit":true,"canDelete":true},"organization":{"canAdd":true,"canEdit":true,"canDelete":true}}},"dashboard":{"id":4,"panels":[{"fieldConfig":{"defaults":{"color":{"mode":"thresholds"},"thresholds":{"mode":"absolute","steps":[{"color":"red","value":null},{"color":"yellow","value":10},{"color":"green","value":15}]},"unit":"short"}},"gridPos":{"h":4,"w":4,"x":0,"y":0},"id":1,"targets":[{"query":"from(bucket: \"iot_data\") |\u003e range(start: -1h) |\u003e filter(fn: (r) =\u003e r[\"_measurement\"] == \"sensors\") |\u003e distinct(column: \"device\") |\u003e count()"}],"title":"Active Sensors","type":"stat"},{"fieldConfig":{"defaults":{"color":{"mode":"thresholds"},"unit":"short"}},"gridPos":{"h":4,"w":4,"x":4,"y":0},"id":2,"targets":[{"query":"from(bucket: \"iot_data\") |\u003e range(start: -1h) |\u003e filter(fn: (r) =\u003e r[\"_measurement\"] == \"traffic\") |\u003e mean(column: \"vehicle_count\")"}],"title":"Average Traffic","type":"stat"},{"fieldConfig":{"defaults":{"color":{"mode":"thresholds"},"max":5,"min":1,"thresholds":{"steps":[{"color":"green","value":null},{"color":"yellow","value":2},{"color":"orange","value":3},{"color":"red","value":4}]},"unit":"short"}},"gridPos":{"h":4,"w":4,"x":8,"y":0},"id":3,"targets":[{"query":"from(bucket: \"iot_data\") |\u003e range(start: -1h) |\u003e filter(fn: (r) =\u003e r[\"_measurement\"] == \"airquality\") |\u003e mean(column: \"air_quality_index\")"}],"title":"Air Quality Index","type":"gauge"},{"fieldConfig":{"defaults":{"color":{"mode":"thresholds"},"unit":"short"}},"gridPos":{"h":4,"w":4,"x":12,"y":0},"id":4,"targets":[{"query":"from(bucket: \"iot_data\") |\u003e range(start: -1h) |\u003e filter(fn: (r) =\u003e r[\"_measurement\"] == \"parking\") |\u003e mean(column: \"available_spots\")"}],"title":"Parking Availability","type":"gauge"},{"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"unit":"short"}},"gridPos":{"h":6,"w":12,"x":0,"y":4},"id":5,"targets":[{"query":"from(bucket: \"iot_data\") |\u003e range(start: -24h) |\u003e filter(fn: (r) =\u003e r[\"_measurement\"] == \"traffic\") |\u003e aggregateWindow(every: 5m, fn: mean)"}],"title":"Traffic Over Time","type":"timeseries"},{"gridPos":{"h":6,"w":12,"x":12,"y":4},"id":6,"options":{"layers":[{"config":{"location":{"mode":"coords"},"value":"pm25_ugm3"},"type":"markers"}],"view":{"lat":46.1667,"lon":-1.15,"zoom":12}},"targets":[{"query":"from(bucket: \"iot_data\") |\u003e range(start: -1h) |\u003e filter(fn: (r) =\u003e r[\"_measurement\"] == \"airquality\")"}],"title":"Air Quality Map","type":"geomap"},{"gridPos":{"h":3,"w":24,"x":0,"y":10},"id":7,"options":{"content":"## Current Weather\nTemperature: 18°C | Humidity: 65% | Wind: 15 km/h | UV Index: 3","mode":"markdown"},"title":"Weather Conditions","type":"text"}],"tags":["smart-city","digital-twin","overview"],"timezone":"browser","title":"Smart City Overview","uid":"e09bfbc3-6e4b-4134-963c-c98f9ffbb0f8","version":1}}
|
||||||
76
populate_influx.py
Normal file
76
populate_influx.py
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Populate InfluxDB with Smart City sensor data for Martinique."""
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
import random
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
|
try:
|
||||||
|
import influxdb_client
|
||||||
|
from influxdb_client.client.write_api import SYNCHRONOUS
|
||||||
|
except ImportError:
|
||||||
|
print("❌ influxdb-client not installed. Run: pip install influxdb-client")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
# Config
|
||||||
|
INFLUX_URL = os.environ.get("INFLUX_URL", "http://localhost:8086")
|
||||||
|
INFLUX_ORG = os.environ.get("INFLUX_ORG", "digitribe")
|
||||||
|
INFLUX_BUCKET = os.environ.get("INFLUX_BUCKET", "iot_data")
|
||||||
|
INFLUX_TOKEN = os.environ.get("INFLUX_TOKEN",
|
||||||
|
"yA8zFZYsPOLDdDxlviIfHw_5gELH8k439TANamk2JiJIyAbhyNCHDzUeYJkjL-hAy99fs_96j_59WprZy98A==")
|
||||||
|
|
||||||
|
# Martinique coordinates (Fort-de-France area)
|
||||||
|
BASE_LAT = 14.6091
|
||||||
|
BASE_LON = -61.2155
|
||||||
|
|
||||||
|
# Sensor types and their fields
|
||||||
|
SENSOR_TYPES = {
|
||||||
|
"traffic": {
|
||||||
|
"fields": {"vehicle_count": (10, 150), "average_speed_kmh": (10, 80), "congestion_level": (0, 5)},
|
||||||
|
"locations": ["Carrefour Central", "Avenue des Caraïbes", "Boulevard Pasteur", "Rue des Flamboyants", "Place de la République"]
|
||||||
|
},
|
||||||
|
"airquality": {
|
||||||
|
"fields": {"pm25_ugm3": (5, 80), "pm10_ugm3": (10, 150), "no2_ugm3": (5, 60), "o3_ugm3": (20, 120)},
|
||||||
|
"locations": ["Quartier Bonde", "Port de Fort-de-France", "Château Denis", "Lamentin Aéroport"]
|
||||||
|
},
|
||||||
|
"parking": {
|
||||||
|
"fields": {"total_spots": (50, 500), "available_spots": (0, 500), "occupancy_percent": (0, 100)},
|
||||||
|
"locations": ["Parking Rivière-Salée", "Parking Cluny", "Parking Médoc", "Parking Grand-Camp"]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def main():
|
||||||
|
print("📈 Connecting to InfluxDB...")
|
||||||
|
client = influxdb_client.InfluxDBClient(url=INFLUX_URL, token=INFLUX_TOKEN, org=INFLUX_ORG)
|
||||||
|
write_api = client.write_api(write_options=SYNCHRONOUS)
|
||||||
|
|
||||||
|
points = []
|
||||||
|
now = datetime.now(timezone.utc)
|
||||||
|
|
||||||
|
print("📊 Generating sensor data for Martinique...")
|
||||||
|
|
||||||
|
for stype, config in SENSOR_TYPES.items():
|
||||||
|
for i, location in enumerate(config["locations"]):
|
||||||
|
sid = f"{stype}_{i:03d}"
|
||||||
|
lat = BASE_LAT + random.uniform(-0.05, 0.05)
|
||||||
|
lon = BASE_LON + random.uniform(-0.05, 0.05)
|
||||||
|
|
||||||
|
for field, (lo, hi) in config["fields"].items():
|
||||||
|
value = round(random.uniform(lo, hi), 1)
|
||||||
|
p = influxdb_client.Point(stype)\
|
||||||
|
.tag("sensor_id", sid)\
|
||||||
|
.tag("location", location)\
|
||||||
|
.field(field, float(value))\
|
||||||
|
.field("lat", lat)\
|
||||||
|
.field("lon", lon)\
|
||||||
|
.time(now)
|
||||||
|
points.append(p)
|
||||||
|
|
||||||
|
print(f"📤 Writing {len(points)} points to bucket '{INFLUX_BUCKET}'...")
|
||||||
|
write_api.write(bucket=INFLUX_BUCKET, record=points)
|
||||||
|
print(f"✅ Done! {len(points)} points written.")
|
||||||
|
|
||||||
|
client.close()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user