diff --git a/docker-compose.yml b/docker-compose.yml
index 4451efd..4544c92 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,15 +1,22 @@
version: '3.8'
+# ============================================================
+# Smart Parking v2.0 — Docker Compose
+# Services : MariaDB + App Node.js + Mosquitto MQTT
+# ============================================================
+
services:
+
+ # ── Base de données MariaDB ────────────────────────────────
db:
image: mariadb:10.11
container_name: smartparking-db
restart: always
environment:
- MARIADB_ROOT_PASSWORD: rootpassword # À changer
+ MARIADB_ROOT_PASSWORD: rootpassword # ⚠️ À changer en production
MARIADB_DATABASE: smartparking
MARIADB_USER: smartparking_user
- MARIADB_PASSWORD: smartparking_pass # À changer
+ MARIADB_PASSWORD: smartparking_pass # ⚠️ À changer en production
volumes:
- db_data:/var/lib/mysql
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
@@ -20,6 +27,25 @@ services:
timeout: 10s
retries: 5
+ # ── Broker MQTT Mosquitto ──────────────────────────────────
+ # Si vous avez déjà Mosquitto installé directement sur le Pi
+ # (pas dans Docker), commentez ce bloc et mettez
+ # MQTT_HOST=localhost dans la section "app" ci-dessous.
+ mqtt:
+ image: eclipse-mosquitto:2
+ container_name: smartparking-mqtt
+ restart: always
+ ports:
+ - "1883:1883" # Port MQTT (Arduino se connecte ici)
+ - "9001:9001" # Port WebSocket (optionnel)
+ volumes:
+ - ./mosquitto/config/mosquitto.conf:/mosquitto/config/mosquitto.conf
+ - mosquitto_data:/mosquitto/data
+ - mosquitto_log:/mosquitto/log
+ networks:
+ - smartparking-network
+
+ # ── Application Node.js ────────────────────────────────────
app:
build: .
container_name: smartparking-app
@@ -29,19 +55,31 @@ services:
depends_on:
db:
condition: service_healthy
+ mqtt:
+ condition: service_started
environment:
- DB_HOST: db
- DB_PORT: 3306
- DB_USER: smartparking_user
+ # Base de données
+ DB_HOST: db
+ DB_PORT: 3306
+ DB_USER: smartparking_user
DB_PASSWORD: smartparking_pass
- DB_NAME: smartparking
- JWT_SECRET: ${JWT_SECRET:-une_chaine_tres_longue_et_secrete}
+ DB_NAME: smartparking
+ # JWT
+ JWT_SECRET: ${JWT_SECRET:-une_chaine_tres_longue_et_secrete_changez_moi}
+ # MQTT — utiliser "mqtt" si Mosquitto est dans Docker
+ # utiliser "localhost" si Mosquitto est installé directement sur le Pi
+ MQTT_HOST: mqtt
+ MQTT_PORT: 1883
+ # Environnement
NODE_ENV: production
+ PORT: 3000
networks:
- smartparking-network
volumes:
db_data:
+ mosquitto_data:
+ mosquitto_log:
networks:
smartparking-network:
\ No newline at end of file
diff --git a/js/map.js b/js/map.js
index 1b71d88..0319154 100644
--- a/js/map.js
+++ b/js/map.js
@@ -1,120 +1,163 @@
/**
* ============================================
* MAP.JS - Carte des places de parking
- * Smart Parking - BTS CIEL IR
+ * Smart Parking v2.0
+ * MODIFIÉ : polling API toutes les 3s pour recevoir
+ * les mises à jour en temps réel depuis Arduino
* ============================================
*/
-// Configuration
const MAP_CONFIG = {
- totalSpots: 10, // Nombre total de places
- updateInterval: 5000 // Intervalle de mise à jour
+ totalSpots: 10,
+ updateInterval: 3000 // Refresh depuis l'API toutes les 3 secondes
};
-// État des places
let spotsState = {
- spots: [],
+ spots: [],
selectedSpot: null
};
-// Types de places
-const SPOT_STATUS = {
- FREE: 'free',
- OCCUPIED: 'occupied',
- RESERVED: 'reserved'
-};
+const SPOT_STATUS = { FREE: 'free', OCCUPIED: 'occupied', RESERVED: 'reserved' };
+
+// ============================================
+// INITIALISATION
+// ============================================
-// Initialisation
document.addEventListener('DOMContentLoaded', () => {
console.log('🗺️ Initialisation de la carte...');
initParkingMap();
});
-/**
- * Initialise la carte du parking
- */
-function initParkingMap() {
- // Charger les places depuis le stockage local ou créer les places par défaut
- loadSpots();
-
- // Rendre la carte
+async function initParkingMap() {
+ // Essayer d'abord de charger depuis l'API (données MariaDB + Arduino)
+ const loaded = await loadSpotsFromAPI();
+
+ // Si pas d'API disponible, utiliser le localStorage (mode offline)
+ if (!loaded) {
+ loadSpotsFromStorage();
+ }
+
renderMap();
-
- // Mettre à jour les statistiques
updateStats();
-
- // Mettre à jour le formulaire de réservation
updateReservationForm();
-
- // Démarrer la simulation (si pas admin)
- if (!isAdmin()) {
- startSimulation();
+
+ // ⭐ Polling toutes les 3 secondes pour recevoir les updates Arduino
+ startAPIPolling();
+}
+
+// ============================================
+// CHARGEMENT DES PLACES
+// ============================================
+
+/**
+ * Charge les places depuis l'API (MariaDB)
+ * Retourne true si succès, false si hors-ligne
+ */
+async function loadSpotsFromAPI() {
+ try {
+ const token = localStorage.getItem('smart_parking_token');
+ if (!token) return false;
+
+ const response = await fetch('/api/spots', {
+ headers: { 'Authorization': 'Bearer ' + token }
+ });
+
+ if (!response.ok) return false;
+
+ const data = await response.json();
+ if (!data.success || !data.data.length) return false;
+
+ // Convertir le format API → format interne
+ spotsState.spots = data.data.map(s => ({
+ id: s.id,
+ number: s.number,
+ status: s.status,
+ lastUpdate: s.last_update,
+ sensorId: s.sensor_id
+ }));
+
+ // Synchroniser avec localStorage pour le mode offline
+ saveSpots();
+ return true;
+
+ } catch (_err) {
+ // Serveur non joignable → mode offline
+ return false;
}
}
/**
- * Charge les places
+ * Charge les places depuis le localStorage (mode offline)
*/
-function loadSpots() {
+function loadSpotsFromStorage() {
const stored = localStorage.getItem('smart_parking_spots');
-
if (stored) {
spotsState.spots = JSON.parse(stored);
} else {
- // Créer les places par défaut
createDefaultSpots();
}
}
-/**
- * Crée les places par défaut
- */
function createDefaultSpots() {
spotsState.spots = [];
-
for (let i = 1; i <= MAP_CONFIG.totalSpots; i++) {
- // Distribution: 60% libre, 25% occupé, 15% réservé
- const rand = Math.random();
- let status = SPOT_STATUS.FREE;
-
- if (rand > 0.85) {
- status = SPOT_STATUS.RESERVED;
- } else if (rand > 0.60) {
- status = SPOT_STATUS.OCCUPIED;
- }
-
spotsState.spots.push({
- id: i,
- number: i,
- status: status,
+ id: i,
+ number: i,
+ status: SPOT_STATUS.FREE,
lastUpdate: new Date().toISOString(),
- sensorId: `SENSOR_${String(i).padStart(3, '0')}`
+ sensorId: `SENSOR_${String(i).padStart(3, '0')}`
});
}
-
saveSpots();
}
-/**
- * Sauvegarde les places
- */
function saveSpots() {
localStorage.setItem('smart_parking_spots', JSON.stringify(spotsState.spots));
}
+// ============================================
+// ⭐ POLLING TEMPS RÉEL (mises à jour Arduino)
+// ============================================
+
/**
- * Rend la carte
+ * Interroge l'API toutes les 3 secondes.
+ * Si l'Arduino a changé l'état d'une place via MQTT,
+ * la carte se met à jour automatiquement.
*/
+function startAPIPolling() {
+ setInterval(async () => {
+ const loaded = await loadSpotsFromAPI();
+ if (loaded) {
+ renderMap();
+ updateStats();
+ updateReservationForm();
+ // Rafraîchir les détails si une place est sélectionnée
+ if (spotsState.selectedSpot) {
+ const updated = spotsState.spots.find(s => s.id === spotsState.selectedSpot.id);
+ if (updated) {
+ spotsState.selectedSpot = updated;
+ showSpotDetails(updated);
+ }
+ }
+ }
+ }, MAP_CONFIG.updateInterval);
+}
+
+// ============================================
+// RENDU DE LA CARTE
+// ============================================
+
function renderMap() {
const mapContainer = document.getElementById('parkingMap');
if (!mapContainer) return;
-
+
mapContainer.innerHTML = spotsState.spots.map(spot => `
-
${spot.number}
${getStatusIcon(spot.status)}
@@ -122,27 +165,21 @@ function renderMap() {
`).join('');
}
-/**
- * Gère le clic sur une place
- */
function handleSpotClick(spotId) {
const spot = spotsState.spots.find(s => s.id === spotId);
if (!spot) return;
-
spotsState.selectedSpot = spot;
showSpotDetails(spot);
}
-/**
- * Affiche les détails d'une place
- */
function showSpotDetails(spot) {
const container = document.getElementById('spotDetails');
if (!container) return;
-
- const isReserved = spot.status === SPOT_STATUS.RESERVED;
- const reservation = isReserved ? findReservationForSpot(spot.id) : null;
-
+
+ const reservation = spot.status === SPOT_STATUS.RESERVED
+ ? findReservationForSpot(spot.id)
+ : null;
+
container.innerHTML = `
@@ -157,7 +194,7 @@ function showSpotDetails(spot) {
Capteur
- ${spot.sensorId}
+ ${spot.sensorId || 'N/A'}
Dernière mise à jour
@@ -174,7 +211,8 @@ function showSpotDetails(spot) {
` : ''}
${spot.status === SPOT_STATUS.FREE ? `
-