Add data flow diagram (Mermaid MD, HTML, PDF) for Smart City Digital Twin

This commit is contained in:
Eric FELIXINE
2026-05-04 17:43:08 -04:00
parent df725eadbc
commit fb5b98043c
3 changed files with 655 additions and 0 deletions

464
data-flow-diagram.html Normal file
View File

@@ -0,0 +1,464 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Smart City Digital Twin - Flux de Données</title>
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'JetBrains Mono', monospace;
background: #020617;
min-height: 100vh;
padding: 2rem;
color: white;
}
.container {
max-width: 1400px;
margin: 0 auto;
}
.header {
margin-bottom: 2rem;
}
.header-row {
display: flex;
align-items: center;
gap: 1rem;
margin-bottom: 0.5rem;
}
.pulse-dot {
width: 12px;
height: 12px;
background: #22d3ee;
border-radius: 50%;
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
h1 {
font-size: 1.5rem;
font-weight: 700;
letter-spacing: -0.025em;
}
.subtitle {
color: #94a3b8;
font-size: 0.875rem;
margin-left: 1.75rem;
}
.diagram-container {
background: rgba(15, 23, 42, 0.5);
border-radius: 1rem;
border: 1px solid #1e293b;
padding: 1.5rem;
overflow-x: auto;
}
svg {
width: 100%;
min-width: 1200px;
display: block;
}
.cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 1rem;
margin-top: 2rem;
}
.card {
background: rgba(15, 23, 42, 0.5);
border-radius: 0.75rem;
border: 1px solid #1e293b;
padding: 1.25rem;
}
.card-header {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.75rem;
}
.card-dot {
width: 8px;
height: 8px;
border-radius: 50%;
}
.card-dot.cyan { background: #22d3ee; }
.card-dot.emerald { background: #34d399; }
.card-dot.violet { background: #a78bfa; }
.card-dot.amber { background: #fbbf24; }
.card-dot.rose { background: #fb7185; }
.card-dot.orange { background: #fb923c; }
.card h3 {
font-size: 0.875rem;
font-weight: 600;
}
.card ul {
list-style: none;
color: #94a3b8;
font-size: 0.75rem;
}
.card li {
margin-bottom: 0.375rem;
}
.footer {
text-align: center;
margin-top: 1.5rem;
color: #475569;
font-size: 0.75rem;
}
</style>
</head>
<body>
<div class="container">
<!-- Header -->
<div class="header">
<div class="header-row">
<div class="pulse-dot"></div>
<h1>Smart City Digital Twin — Flux de Données</h1>
</div>
<p class="subtitle">Martinique • Simulator → Brokers → Context Brokers → Visualization</p>
</div>
<!-- Main Diagram -->
<div class="diagram-container">
<svg viewBox="0 0 1300 750">
<!-- Definitions -->
<defs>
<marker id="arrowhead" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#64748b" />
</marker>
<marker id="arrowhead-cyan" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#22d3ee" />
</marker>
<marker id="arrowhead-emerald" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#34d399" />
</marker>
<marker id="arrowhead-orange" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#fb923c" />
</marker>
<pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse">
<path d="M 40 0 L 0 0 0 40" fill="none" stroke="#1e293b" stroke-width="0.5"/>
</pattern>
</defs>
<!-- Background Grid -->
<rect width="100%" height="100%" fill="url(#grid)" />
<!-- ===== LAYER 1: Data Sources ===== -->
<text x="30" y="30" fill="#94a3b8" font-size="12" font-weight="600">📡 COUCHE 1 : SOURCES DE DONNÉES</text>
<!-- Smart City Simulator -->
<rect x="30" y="50" width="180" height="70" rx="6" fill="#0f172a"/>
<rect x="30" y="50" width="180" height="70" rx="6" fill="rgba(251, 146, 60, 0.3)" stroke="#fb923c" stroke-width="1.5"/>
<text x="120" y="80" fill="white" font-size="12" font-weight="600" text-anchor="middle">Smart City Simulator</text>
<text x="120" y="96" fill="#94a3b8" font-size="9" text-anchor="middle">Python • 10 capteurs</text>
<text x="120" y="112" fill="#fb923c" font-size="8" text-anchor="middle">MQTT + REST API</text>
<!-- ===== LAYER 2: MQTT BROKERS ===== -->
<text x="30" y="170" fill="#94a3b8" font-size="12" font-weight="600">📡 COUCHE 2 : MQTT BROKERS</text>
<!-- EMQX -->
<rect x="30" y="190" width="140" height="60" rx="6" fill="#0f172a"/>
<rect x="30" y="190" width="140" height="60" rx="6" fill="rgba(34, 211, 238, 0.3)" stroke="#22d3ee" stroke-width="1.5"/>
<text x="100" y="215" fill="white" font-size="11" font-weight="600" text-anchor="middle">EMQX</text>
<text x="100" y="231" fill="#94a3b8" font-size="9" text-anchor="middle">Port 11883 (MQTT)</text>
<!-- Mosquitto -->
<rect x="190" y="190" width="140" height="60" rx="6" fill="#0f172a"/>
<rect x="190" y="190" width="140" height="60" rx="6" fill="rgba(34, 211, 238, 0.3)" stroke="#22d3ee" stroke-width="1.5"/>
<text x="260" y="215" fill="white" font-size="11" font-weight="600" text-anchor="middle">Mosquitto</text>
<text x="260" y="231" fill="#94a3b8" font-size="9" text-anchor="middle">Port 1883 (MQTT)</text>
<!-- BunkerM -->
<rect x="350" y="190" width="140" height="60" rx="6" fill="#0f172a"/>
<rect x="350" y="190" width="140" height="60" rx="6" fill="rgba(34, 211, 238, 0.3)" stroke="#22d3ee" stroke-width="1.5"/>
<text x="420" y="215" fill="white" font-size="11" font-weight="600" text-anchor="middle">BunkerM</text>
<text x="420" y="231" fill="#94a3b8" font-size="9" text-anchor="middle">Port 1900 (MQTTS)</text>
<!-- ===== LAYER 3: CONTEXT BROKERS ===== -->
<text x="30" y="290" fill="#94a3b8" font-size="12" font-weight="600">🔄 COUCHE 3 : CONTEXT BROKERS (NGSI-LD)</text>
<!-- Orion-LD -->
<rect x="30" y="310" width="160" height="80" rx="6" fill="#0f172a"/>
<rect x="30" y="310" width="160" height="80" rx="6" fill="rgba(52, 211, 153, 0.3)" stroke="#34d399" stroke-width="1.5"/>
<text x="110" y="335" fill="white" font-size="11" font-weight="600" text-anchor="middle">Orion-LD</text>
<text x="110" y="351" fill="#94a3b8" font-size="9" text-anchor="middle">NGSI-LD</text>
<text x="110" y="367" fill="#94a3b8" font-size="8" text-anchor="middle">Port 1026</text>
<text x="110" y="383" fill="#34d399" font-size="8" text-anchor="middle">Entities: Traffic, Air</text>
<!-- Stellio -->
<rect x="210" y="310" width="160" height="80" rx="6" fill="#0f172a"/>
<rect x="210" y="310" width="160" height="80" rx="6" fill="rgba(52, 211, 153, 0.3)" stroke="#34d399" stroke-width="1.5"/>
<text x="290" y="335" fill="white" font-size="11" font-weight="600" text-anchor="middle">Stellio</text>
<text x="290" y="351" fill="#94a3b8" font-size="9" text-anchor="middle">NGSI-LD</text>
<text x="290" y="367" fill="#94a3b8" font-size="8" text-anchor="middle">Port 8080</text>
<text x="290" y="383" fill="#34d399" font-size="8" text-anchor="middle">14 payloads entities</text>
<!-- FROST-Server -->
<rect x="390" y="310" width="160" height="80" rx="6" fill="#0f172a"/>
<rect x="390" y="310" width="160" height="80" rx="6" fill="rgba(52, 211, 153, 0.3)" stroke="#34d399" stroke-width="1.5"/>
<text x="470" y="335" fill="white" font-size="11" font-weight="600" text-anchor="middle">FROST-Server</text>
<text x="470" y="351" fill="#94a3b8" font-size="9" text-anchor="middle">SensorThings API</text>
<text x="470" y="367" fill="#94a3b8" font-size="8" text-anchor="middle">21k+ observations</text>
<text x="470" y="383" fill="#34d399" font-size="8" text-anchor="middle">PostgreSQL+Timescale</text>
<!-- ===== LAYER 4: IOT PLATFORM ===== -->
<text x="30" y="430" fill="#94a3b8" font-size="12" font-weight="600">🏠 COUCHE 4 : PLATEFORME IOT (OpenRemote)</text>
<!-- OpenRemote Manager -->
<rect x="30" y="450" width="200" height="90" rx="6" fill="#0f172a"/>
<rect x="30" y="450" width="200" height="90" rx="6" fill="rgba(167, 139, 250, 0.3)" stroke="#a78bfa" stroke-width="1.5"/>
<text x="130" y="478" fill="white" font-size="12" font-weight="600" text-anchor="middle">OpenRemote Manager</text>
<text x="130" y="494" fill="#94a3b8" font-size="9" text-anchor="middle">Realm: Smart City</text>
<text x="130" y="510" fill="#94a3b8" font-size="8" text-anchor="middle">33 assets IoT</text>
<text x="130" y="526" fill="#a78bfa" font-size="8" text-anchor="middle">Port 8080 + Keycloak</text>
<!-- Keycloak -->
<rect x="250" y="465" width="120" height="60" rx="6" fill="#0f172a"/>
<rect x="250" y="465" width="120" height="60" rx="6" fill="rgba(251, 113, 133, 0.3)" stroke="#fb7185" stroke-width="1.5"/>
<text x="310" y="490" fill="white" font-size="10" font-weight="600" text-anchor="middle">Keycloak</text>
<text x="310" y="506" fill="#94a3b8" font-size="8" text-anchor="middle">Auth OpenID</text>
<!-- ===== LAYER 5: STORAGE & METRICS ===== -->
<text x="30" y="570" fill="#94a3b8" font-size="12" font-weight="600">💾 COUCHE 5 : STOCKAGE & MÉTRIQUES</text>
<!-- InfluxDB -->
<rect x="30" y="590" width="140" height="60" rx="6" fill="#0f172a"/>
<rect x="30" y="590" width="140" height="60" rx="6" fill="rgba(120, 53, 15, 0.3)" stroke="#fbbf24" stroke-width="1.5"/>
<text x="100" y="615" fill="white" font-size="11" font-weight="600" text-anchor="middle">InfluxDB</text>
<text x="100" y="631" fill="#94a3b8" font-size="9" text-anchor="middle">IoT Data Bucket</text>
<!-- Prometheus -->
<rect x="190" y="590" width="140" height="60" rx="6" fill="#0f172a"/>
<rect x="190" y="590" width="140" height="60" rx="6" fill="rgba(120, 53, 15, 0.3)" stroke="#fbbf24" stroke-width="1.5"/>
<text x="260" y="615" fill="white" font-size="11" font-weight="600" text-anchor="middle">Prometheus</text>
<text x="260" y="631" fill="#94a3b8" font-size="9" text-anchor="middle">Metrics + Alerting</text>
<!-- GeoServer -->
<rect x="350" y="590" width="140" height="60" rx="6" fill="#0f172a"/>
<rect x="350" y="590" width="140" height="60" rx="6" fill="rgba(120, 53, 15, 0.3)" stroke="#fbbf24" stroke-width="1.5"/>
<text x="420" y="615" fill="white" font-size="11" font-weight="600" text-anchor="middle">GeoServer</text>
<text x="420" y="631" fill="#94a3b8" font-size="9" text-anchor="middle">WMS/WFS + PostGIS</text>
<!-- ===== LAYER 6: VISUALIZATION ===== -->
<text x="650" y="170" fill="#94a3b8" font-size="12" font-weight="600">📊 COUCHE 6 : VISUALISATION & ANALYSE</text>
<!-- Grafana -->
<rect x="650" y="190" width="160" height="80" rx="6" fill="#0f172a"/>
<rect x="650" y="190" width="160" height="80" rx="6" fill="rgba(8, 51, 68, 0.4)" stroke="#22d3ee" stroke-width="1.5"/>
<text x="730" y="218" fill="white" font-size="12" font-weight="600" text-anchor="middle">Grafana</text>
<text x="730" y="234" fill="#94a3b8" font-size="9" text-anchor="middle">Dashboards</text>
<text x="730" y="250" fill="#94a3b8" font-size="8" text-anchor="middle">Datasources:</text>
<text x="730" y="266" fill="#22d3ee" font-size="8" text-anchor="middle">InfluxDB, FROST, Orion</text>
<!-- MapStore -->
<rect x="830" y="190" width="160" height="80" rx="6" fill="#0f172a"/>
<rect x="830" y="190" width="160" height="80" rx="6" fill="rgba(8, 51, 68, 0.4)" stroke="#22d3ee" stroke-width="1.5"/>
<text x="910" y="218" fill="white" font-size="12" font-weight="600" text-anchor="middle">MapStore</text>
<text x="910" y="234" fill="#94a3b8" font-size="9" text-anchor="middle">Cartographie</text>
<text x="910" y="250" fill="#94a3b8" font-size="8" text-anchor="middle">Sources:</text>
<text x="910" y="266" fill="#22d3ee" font-size="8" text-anchor="middle">GeoServer WMS</text>
<!-- OpenRemote UI -->
<rect x="650" y="310" width="160" height="80" rx="6" fill="#0f172a"/>
<rect x="650" y="310" width="160" height="80" rx="6" fill="rgba(8, 51, 68, 0.4)" stroke="#22d3ee" stroke-width="1.5"/>
<text x="730" y="338" fill="white" font-size="12" font-weight="600" text-anchor="middle">OpenRemote UI</text>
<text x="730" y="354" fill="#94a3b8" font-size="9" text-anchor="middle">Manager Interface</text>
<text x="730" y="370" fill="#22d3ee" font-size="8" text-anchor="middle">Realm: Smart City</text>
<!-- ===== ARROWS: Data Flows ===== -->
<!-- Simulator → MQTT Brokers -->
<line x1="120" y1="120" x2="100" y2="188" stroke="#fb923c" stroke-width="2" marker-end="url(#arrowhead-orange)"/>
<text x="95" y="150" fill="#fb923c" font-size="8">MQTT</text>
<line x1="120" y1="120" x2="260" y2="188" stroke="#fb923c" stroke-width="2" marker-end="url(#arrowhead-orange)"/>
<line x1="120" y1="120" x2="420" y2="188" stroke="#fb923c" stroke-width="2" marker-end="url(#arrowhead-orange)"/>
<!-- MQTT Brokers → Context Brokers -->
<line x1="100" y1="252" x2="110" y2="308" stroke="#22d3ee" stroke-width="2" marker-end="url(#arrowhead-cyan)"/>
<text x="85" y="280" fill="#22d3ee" font-size="8">NGSI-LD</text>
<line x1="260" y1="252" x2="290" y2="308" stroke="#22d3ee" stroke-width="2" marker-end="url(#arrowhead-cyan)"/>
<line x1="420" y1="252" x2="470" y2="308" stroke="#22d3ee" stroke-width="2" marker-end="url(#arrowhead-cyan)"/>
<!-- Simulator → OpenRemote (REST) -->
<line x1="180" y1="85" x2="130" y2="448" stroke="#a78bfa" stroke-width="2" stroke-dasharray="5,5" marker-end="url(#arrowhead-emerald)"/>
<text x="160" y="250" fill="#a78bfa" font-size="8">REST API</text>
<!-- Simulator → InfluxDB -->
<line x1="180" y1="100" x2="100" y2="588" stroke="#fbbf24" stroke-width="2" marker-end="url(#arrowhead)"/>
<text x="145" y="340" fill="#fbbf24" font-size="8">HTTP</text>
<!-- Context Brokers → Visualization -->
<line x1="110" y1="392" x2="730" y2="188" stroke="#34d399" stroke-width="2" marker-end="url(#arrowhead-emerald)"/>
<text x="350" y="280" fill="#34d399" font-size="8">Query</text>
<line x1="290" y1="392" x2="730" y2="188" stroke="#34d399" stroke-width="2" marker-end="url(#arrowhead-emerald)"/>
<line x1="470" y1="392" x2="730" y2="188" stroke="#34d399" stroke-width="2" marker-end="url(#arrowhead-emerald)"/>
<!-- GeoServer → MapStore -->
<line x1="420" y1="652" x2="910" y2="268" stroke="#fbbf24" stroke-width="2" marker-end="url(#arrowhead)"/>
<text x="600" y="440" fill="#fbbf24" font-size="8">WMS/WFS</text>
<!-- Context Brokers → GeoServer (PostGIS) -->
<path d="M 110 392 Q 110 500 420 590" fill="none" stroke="#34d399" stroke-width="1.5" stroke-dasharray="3,3"/>
<text x="200" y="480" fill="#34d399" font-size="7">DB Sync</text>
<!-- OpenRemote → Grafana -->
<line x1="130" y1="542" x2="730" y2="268" stroke="#a78bfa" stroke-width="2" marker-end="url(#arrowhead-emerald)"/>
<text x="350" y="380" fill="#a78bfa" font-size="8">API Query</text>
<!-- All → Prometheus (Metrics) -->
<line x1="320" y1="500" x2="260" y2="588" stroke="#fbbf24" stroke-width="1.5" stroke-dasharray="4,4"/>
<text x="260" y="540" fill="#fbbf24" font-size="7">Metrics</text>
<!-- ===== LEGEND ===== -->
<text x="1050" y="30" fill="white" font-size="11" font-weight="600">Légende</text>
<rect x="1050" y="42" width="16" height="12" rx="2" fill="rgba(251, 146, 60, 0.3)" stroke="#fb923c" stroke-width="1"/>
<text x="1072" y="52" fill="#94a3b8" font-size="9">Source de données</text>
<rect x="1050" y="60" width="16" height="12" rx="2" fill="rgba(34, 211, 238, 0.3)" stroke="#22d3ee" stroke-width="1"/>
<text x="1072" y="70" fill="#94a3b8" font-size="9">MQTT Broker</text>
<rect x="1050" y="78" width="16" height="12" rx="2" fill="rgba(52, 211, 153, 0.3)" stroke="#34d399" stroke-width="1"/>
<text x="1072" y="88" fill="#94a3b8" font-size="9">Context Broker</text>
<rect x="1050" y="96" width="16" height="12" rx="2" fill="rgba(167, 139, 250, 0.3)" stroke="#a78bfa" stroke-width="1"/>
<text x="1072" y="106" fill="#94a3b8" font-size="9">IoT Platform</text>
<rect x="1050" y="114" width="16" height="12" rx="2" fill="rgba(120, 53, 15, 0.3)" stroke="#fbbf24" stroke-width="1"/>
<text x="1072" y="124" fill="#94a3b8" font-size="9">Stockage / Métriques</text>
<rect x="1050" y="132" width="16" height="12" rx="2" fill="rgba(8, 51, 68, 0.4)" stroke="#22d3ee" stroke-width="1"/>
<text x="1072" y="142" fill="#94a3b8" font-size="9">Visualisation</text>
<line x1="1050" y1="156" x2="1066" y2="156" stroke="#34d399" stroke-width="2" marker-end="url(#arrowhead-emerald)"/>
<text x="1072" y="159" fill="#94a3b8" font-size="9">Flux de données</text>
<line x1="1050" y1="174" x2="1066" y2="174" stroke="#fb923c" stroke-width="2" marker-end="url(#arrowhead-orange)"/>
<text x="1072" y="177" fill="#94a3b8" font-size="9">MQTT</text>
<line x1="1050" y1="190" x2="1066" y2="190" stroke="#a78bfa" stroke-width="2" stroke-dasharray="5,5"/>
<text x="1072" y="193" fill="#94a3b8" font-size="9">REST API</text>
</svg>
</div>
<!-- Info Cards -->
<div class="cards">
<div class="card">
<div class="card-header">
<div class="card-dot orange"></div>
<h3>Sources & Simulator</h3>
</div>
<ul>
<li>• Smart City Simulator (Python)</li>
<li>• 10 capteurs : Traffic, Air, Parking, Noise, Weather, Light</li>
<li>• Intervalle : 10 secondes</li>
<li>• Protocoles : MQTT + REST API</li>
</ul>
</div>
<div class="card">
<div class="card-header">
<div class="card-dot cyan"></div>
<h3>MQTT Brokers</h3>
</div>
<ul>
<li>• EMQX : Port 11883 (public)</li>
<li>• Mosquitto : Port 1883 (Traefik)</li>
<li>• BunkerM : Port 1900 (TLS)</li>
<li>• OpenRemote : Port 1883 (interne)</li>
</ul>
</div>
<div class="card">
<div class="card-header">
<div class="card-dot emerald"></div>
<h3>Context Brokers (NGSI-LD)</h3>
</div>
<ul>
<li>• Orion-LD : 10 entités NGSI-LD</li>
<li>• Stellio : 14 payloads entités</li>
<li>• FROST-Server : 21k+ observations</li>
<li>• Smart Data Models utilisés</li>
</ul>
</div>
<div class="card">
<div class="card-header">
<div class="card-dot violet"></div>
<h3>OpenRemote Platform</h3>
</div>
<ul>
<li>• Realm : Smart City Martinique</li>
<li>• 33 assets IoT configurés</li>
<li>• Keycloak pour l'authentification</li>
<li>• REST API pour les capteurs</li>
</ul>
</div>
<div class="card">
<div class="card-header">
<div class="card-dot amber"></div>
<h3>Stockage & Métriques</h3>
</div>
<ul>
<li>• InfluxDB : Bucket iot_data</li>
<li>• Prometheus : Metrics brokers</li>
<li>• GeoServer : PostGIS + WMS</li>
<li>• PostgreSQL : OpenRemote + FROST</li>
</ul>
</div>
<div class="card">
<div class="card-header">
<div class="card-dot cyan"></div>
<h3>Visualisation & Analyse</h3>
</div>
<ul>
<li>• Grafana : Dashboards (port 3001)</li>
<li>• MapStore : Cartographie WMS</li>
<li>• OpenRemote UI : Manager Smart City</li>
<li>• Datasources : InfluxDB, FROST, Orion</li>
</ul>
</div>
</div>
<!-- Footer -->
<p class="footer">
Smart City Digital Twin Martinique • Stack complet déployé sur digitribe.fr •
Dernière mise à jour : 04 Mai 2026
</p>
</div>
</body>
</html>