Files
Parking/server/server.js

200 lines
7.3 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* ============================================
* SERVER.JS - Serveur principal Smart Parking
* VERSION 2.0 - MQTT Arduino + Expiration réservations
* ============================================
*/
const express = require('express');
const cors = require('cors');
const bodyParser = require('body-parser');
const path = require('path');
const mqtt = require('mqtt');
require('dotenv').config();
const db = require('./db/database');
const apiRoutes = require('./routes/api');
const PORT = process.env.PORT || 3000;
const app = express();
app.use(cors());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.static(path.join(__dirname, '..')));
app.use('/api', apiRoutes);
app.get('/', (_req, res) => res.sendFile(path.join(__dirname, '..', 'index.html')));
app.get('/dashboard', (_req, res) => res.sendFile(path.join(__dirname, '..', 'pages', 'dashboard.html')));
// ============================================
// CONNEXION MQTT (Mosquitto sur le Raspberry Pi)
// ============================================
/**
* Topics attendus depuis l'Arduino :
*
* smartparking/sensor/1 → "1" = voiture détectée (occupée)
* smartparking/sensor/1 → "0" = place libre
*
* L'Arduino publie sur ce topic via le shield Ethernet/WiFi.
* Le numéro à la fin correspond au numéro de place (1 à N).
*
* Pour tester sans Arduino (ligne de commande sur le Pi) :
* mosquitto_pub -h localhost -t "smartparking/sensor/1" -m "1"
* mosquitto_pub -h localhost -t "smartparking/sensor/1" -m "0"
*/
const MQTT_HOST = process.env.MQTT_HOST || 'localhost';
const MQTT_PORT = process.env.MQTT_PORT || 1883;
const MQTT_TOPIC = 'smartparking/sensor/#'; // # = wildcard, reçoit tous les capteurs
let mqttClient = null;
function connectMQTT() {
const brokerUrl = `mqtt://${MQTT_HOST}:${MQTT_PORT}`;
console.log(`🔌 Connexion au broker MQTT : ${brokerUrl}`);
mqttClient = mqtt.connect(brokerUrl, {
clientId: 'smartparking-server-' + Math.random().toString(16).slice(3),
keepalive: 60,
reconnectPeriod: 5000, // Reconnexion automatique toutes les 5s si coupure
connectTimeout: 10000
});
mqttClient.on('connect', () => {
console.log('✅ MQTT connecté au broker Mosquitto');
mqttClient.subscribe(MQTT_TOPIC, (err) => {
if (err) {
console.error('❌ Erreur abonnement MQTT :', err.message);
} else {
console.log(`📡 Abonné au topic : ${MQTT_TOPIC}`);
}
});
});
// Message reçu depuis un capteur Arduino
mqttClient.on('message', async (topic, messageBuffer) => {
const message = messageBuffer.toString().trim();
const topicParts = topic.split('/'); // ['smartparking', 'sensor', '1']
const spotNumber = parseInt(topicParts[2]); // numéro de place
if (isNaN(spotNumber)) {
console.warn(`⚠️ Topic MQTT invalide : ${topic}`);
return;
}
console.log(`📩 MQTT reçu → topic: ${topic} | valeur: ${message}`);
// "1" = voiture présente → occupée | "0" = libre
const newStatus = message === '1' ? 'occupied' : 'free';
try {
// Récupérer la place par son numéro
const spots = await db.getAllSpots();
const spot = spots.find(s => s.number === spotNumber);
if (!spot) {
console.warn(`⚠️ Place numéro ${spotNumber} introuvable en base`);
return;
}
// Ne pas écraser une place RÉSERVÉE avec un simple signal capteur
// (la réservation prime sur le capteur physique)
if (spot.status === 'reserved' && newStatus === 'occupied') {
console.log(` Place ${spotNumber} déjà réservée — capteur ignoré`);
await db.recordMqttEvent(topic, message);
return;
}
// Mettre à jour en base
await db.updateSpotStatus(spot.id, newStatus);
await db.recordMqttEvent(topic, message);
console.log(`✅ Place ${spotNumber} mise à jour → ${newStatus}`);
} catch (err) {
console.error('❌ Erreur traitement message MQTT :', err.message);
}
});
mqttClient.on('error', (err) => {
console.error('❌ Erreur MQTT :', err.message);
});
mqttClient.on('reconnect', () => {
console.log('🔄 Reconnexion MQTT en cours...');
});
mqttClient.on('offline', () => {
console.warn('⚠️ Client MQTT hors-ligne');
});
}
// ============================================
// DÉMARRAGE DU SERVEUR
// ============================================
async function startServer() {
try {
// 1. Initialiser la base de données
await db.initDatabase();
// 2. Démarrer le serveur HTTP
app.listen(PORT, () => {
console.log(`
╔══════════════════════════════════════════════════════╗
║ 🅿️ SMART PARKING SERVER v2.0 - PRÊT ║
╠══════════════════════════════════════════════════════╣
║ 🌐 Port HTTP : ${PORT}
║ 🗄️ Base : MariaDB (${process.env.DB_HOST || 'localhost'})
║ 📡 MQTT : ${MQTT_HOST}:${MQTT_PORT}
║ 🔐 JWT sécurisé
╚══════════════════════════════════════════════════════╝
`);
});
// 3. Connecter le client MQTT (broker Mosquitto)
connectMQTT();
// 4. Enregistrer les statistiques toutes les 5 minutes
setInterval(async () => {
try {
const spots = await db.getAllSpots();
const total = spots.length;
const free = spots.filter(s => s.status === 'free').length;
const occupied = spots.filter(s => s.status === 'occupied').length;
const reserved = spots.filter(s => s.status === 'reserved').length;
await db.recordStats(total, free, occupied, reserved);
} catch (err) {
console.error('❌ Erreur stats :', err.message);
}
}, 5 * 60 * 1000);
// 5. ⭐ EXPIRATION AUTOMATIQUE DES RÉSERVATIONS toutes les minutes
// Libère les places dont l'heure de fin est dépassée
setInterval(async () => {
try {
const count = await db.expireReservations();
if (count > 0) {
console.log(`${count} réservation(s) expirée(s) — places libérées`);
}
} catch (err) {
console.error('❌ Erreur expiration réservations :', err.message);
}
}, 60 * 1000); // toutes les 60 secondes
} catch (err) {
console.error('❌ Erreur au démarrage :', err);
process.exit(1);
}
}
// Arrêt propre
process.on('SIGINT', async () => {
console.log('\n🛑 Arrêt du serveur...');
if (mqttClient) mqttClient.end();
await db.closeDatabase();
process.exit(0);
});
startServer();