IoT project for monitoring a kennel. The frontend displays environmental KPIs based on temperature, atmospheric pressure, and sound level, plus a map of where each probe (sonde) is reporting from.
There are two goals behind the readings this project collects:
- Animal welfare. Understand how the dogs' barking relates to temperature — does noise go up when it gets hotter, or colder? Each probe reports temperature, atmospheric pressure, and sound level (as a proxy for barking) from its spot in the kennel, so staff can spot the correlation, catch heat- or cold-related distress early, and act on it (ventilation, shade, or a human round) instead of finding out after the fact.
- Legal noise compliance. Check whether the noise reaching the kennel's neighbors stays within the legally allowed limits, so the same sound level data doubles as evidence that the kennel is operating within regulation, not just a welfare signal.
This is also why the dashboard includes a temperature/noise correlation chart, and why the map's noise legend is framed around dog stress levels (calm → distress) rather than generic noise thresholds.
T-IOT-902-STG_7/
Front/ # Vue, Vuetify, and Vite frontend
backend/ # Flask API with WebSocket support
IoT/ # Sensor and device code
docker-compose.yml # Docker orchestration for the projectThe dashboard is available at:
/dashboardIt fetches readings from GET /api/readings and stays live via the WebSocket (see below), so new
measurements show up without reloading the page. It includes:
- temperature, pressure, and sound level KPIs
- a sonde filter — view a single probe, or average all of them together
- time-based curves
- correlation matrix between sensors
- temperature vs. noise analysis
- operational indicators for the kennel
The map is available at:
/mapIt plots the latest known position of each probe (from the gps field) on an OpenStreetMap layer,
color-coded by noise level, and updates live over the same WebSocket as the dashboard.
Go to the frontend folder:
cd FrontInstall dependencies:
pnpm installStart the development server:
pnpm run devOpen:
http://localhost:5173/
http://localhost:5173/dashboardMake sure your .env file is present at the project root, then from the project root:
docker-compose up -d --buildThis starts the frontend, backend API, and InfluxDB together.
| Service | URL |
|---|---|
| Frontend | http://localhost:8080 |
| Dashboard | http://localhost:8080/dashboard |
| Map | http://localhost:8080/map |
| API | http://localhost:3001 |
| InfluxDB UI | http://localhost:8086 |
To start only specific services:
docker-compose up -d influxdb # InfluxDB only
docker-compose up -d influxdb api # InfluxDB + API (no frontend)Stop all containers:
docker-compose downNote (macOS): if using Colima, run
colima startbefore the commands above.
To validate the frontend:
cd Front
pnpm run buildThe backend is a Flask API with WebSocket support (via Flask-SocketIO). It receives sensor data, writes it to InfluxDB, and broadcasts it in real time to connected clients.
Create a .env file at the project root before running anything:
API_PORT=3001
SECRET_KEY=your-secret-key
INFLUXDB_INIT_PASSWORD=your-influx-password
INFLUXDB_INIT_ADMIN_TOKEN=your-influx-token
# Only needed when running the API outside Docker (defaults to localhost)
# INFLUXDB_URL=http://localhost:8086When running with Docker Compose, INFLUXDB_URL is injected automatically and points to the influxdb service.
cd backend
pip install -r requirements.txt
python app.pyThe API is available at http://localhost:3001 (or the port set in .env). InfluxDB must be running separately — start it with:
docker-compose up -d influxdbHealth check. Returns 200 if the API is up.
{ "message": "API OK" }Receives a sensor reading, writes it to InfluxDB, and broadcasts it to all connected WebSocket clients.
Request body:
{
"id": "sonde-1",
"temperature": 26.4,
"pressure": 1013.0,
"sound": 71.0,
"gps": "48.8566,2.3522"
}id, temperature, pressure, and sound are required — returns 400 if any are missing or the
body is invalid. gps (as a "lat,lng" string) is used by the map view but isn't validated, so a
missing gps currently causes a 500 instead of a 400 rather than being rejected cleanly.
Note: send
temperature,pressure, andsoundas floats (e.g.71.0, not71). InfluxDB fixes a field's type from its first write, and these fields were first written as floats — an integer value for one of them will fail with a500due to a type conflict.
Success response 201:
{ "message": "Donnée reçue et traitée" }Error responses:
| Status | Reason |
|---|---|
400 |
Missing or invalid JSON body |
500 |
Failed to write to InfluxDB |
Returns sensor readings stored in InfluxDB, optionally filtered by time range and/or probe.
Query parameters (optional):
| Parameter | Format | Description |
|---|---|---|
from |
RFC3339 (2026-06-18T00:00:00Z) |
Start of the time range |
to |
RFC3339 (2026-06-18T23:59:59Z) |
End of the time range |
id |
string | Only return readings from this probe (sonde) |
If from/to are omitted, from defaults to the Unix epoch and to defaults to now. If id is
omitted, readings from every probe are returned.
Example:
GET /api/readings?from=2026-06-18T00:00:00Z&to=2026-06-18T23:59:59Z&id=sonde-1Success response 200:
[
{
"time": "2026-06-18T14:00:00Z",
"id": "sonde-1",
"temperature": 26.4,
"pressure": 1013.0,
"sound": 71.0,
"gps": "48.8566,2.3522"
}
]Error responses:
| Status | Reason |
|---|---|
500 |
Failed to read from InfluxDB |
The API uses Socket.IO. Connect to the same host and port as the REST API.
JavaScript example:
import { io } from 'socket.io-client'
const socket = io('http://localhost:3001')
socket.on('connect', () => console.log('Connected'))
socket.on('disconnect', () => console.log('Disconnected'))
// Receive real-time sensor data pushed by the server
socket.on('sensor_data', (data) => {
console.log(data)
// { id: 'sonde-1', temperature: 26.4, pressure: 1013.0, sound: 71.0, gps: '48.8566,2.3522' }
})The dashboard and the map both open this connection on mount and use it to update their view as
soon as new data comes in, without polling GET /api/readings again.
Events:
| Direction | Event | Payload | Description |
|---|---|---|---|
| Client → Server | sensor_update |
{ id, temperature, pressure, sound, gps } |
Send a sensor reading directly via socket |
| Server → Client | sensor_data |
{ id, temperature, pressure, sound, gps } |
Broadcast emitted on every POST /api/sensor |
Only
POST /api/sensorresults in asensor_databroadcast to all connected clients, and is the only path that persists the data to InfluxDB. Thesensor_updatesocket event currently just echoes the payload back to whoever sent it (emitwithoutbroadcast=True) — it doesn't reach other clients and doesn't get written to the database.
Docker Compose orchestrates the frontend, backend API, and InfluxDB together.