diff --git a/Dockerfile b/Dockerfile index 36c47cb5..629de9c8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ FROM python:3.12-slim 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/ EXPOSE 8081 # Healthcheck endpoint (simple HTTP server) diff --git a/grafana_smart-city-overview.json b/grafana_smart-city-overview.json new file mode 100644 index 00000000..04c2eee1 --- /dev/null +++ b/grafana_smart-city-overview.json @@ -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}} \ No newline at end of file diff --git a/populate_influx.py b/populate_influx.py new file mode 100644 index 00000000..45801b8a --- /dev/null +++ b/populate_influx.py @@ -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()