Grafana dashboard flexibilite/agregation

This commit is contained in:
Eric F
2026-06-09 18:59:37 -04:00
parent 1ab3a4a643
commit d1b57769c0
7 changed files with 986 additions and 0 deletions

View File

@@ -0,0 +1,78 @@
#!/usr/bin/env python3
"""Cariflex - Crée les sensors météo et prix DSO pour la simulation."""
import json
import requests
import re
import warnings
warnings.filterwarnings("ignore")
from datetime import datetime, timezone, timedelta
FM_HOST = "https://cariflex.digitribe.fr"
CREDS_FILE = "/tmp/fm_creds.json"
with open(CREDS_FILE) as f:
creds = json.load(f)
session = requests.Session()
session.verify = False
# Login
r = session.get(f"{FM_HOST}/login")
match = re.search(r'<input[^>]*csrf_token[^>]*value="([^"]+)"', r.text)
csrf = match.group(1)
r = session.post(f"{FM_HOST}/login", data={
"email": creds["email"], "password": creds["password"],
"csrf_token": csrf, "remember": "y"
}, allow_redirects=True)
print(f"Login: {'OK' if 'dashboard' in r.url else 'FAILED'}")
# Step 1: Check existing asset types
r = session.get(f"{FM_HOST}/api/v3_0/generic_asset_types")
print(f"Asset types: HTTP {r.status_code}")
types = {}
if r.status_code == 200:
for t in r.json():
types[t["name"]] = t["id"]
print(f" {t['id']}: {t['name']}")
# Step 2: Check existing assets
r = session.get(f"{FM_HOST}/api/v3_0/assets")
print(f"\nAssets: HTTP {r.status_code}")
assets = {}
if r.status_code == 200:
for a in r.json()[:10]:
assets[a["name"]] = a["id"]
print(f" {a['id']}: {a['name']} ({a.get('generic_asset_type', {}).get('name', '?')})")
# Step 3: Create assets for weather and pricing
# Use existing asset types or create generic ones
print("\n=== Création des assets ===")
new_assets = [
{"name": "Météo Martinique", "type_id": 1, "lat": 14.6091, "lon": -61.2155},
{"name": "Marché Énergie", "type_id": 1, "lat": 14.6091, "lon": -61.2155},
]
for asset in new_assets:
r = session.post(f"{FM_HOST}/api/v3_0/assets", json={
"name": asset["name"],
"generic_asset_type_id": asset["type_id"],
"latitude": asset["lat"],
"longitude": asset["lon"],
})
print(f" Asset '{asset['name']}': HTTP {r.status_code}")
if r.status_code == 201:
aid = r.json().get("id")
print(f" ID: {aid}")
assets[asset["name"]] = aid
else:
try:
err = r.json()
print(f" Error: {err.get('message', r.text[:200])}")
except:
print(f" Error: {r.text[:200]}")
print(f"\n=== Assets existants ===")
for name, aid in assets.items():
print(f" {aid}: {name}")

View File

@@ -0,0 +1,92 @@
#!/usr/bin/env python3
"""Cariflex - Crée les assets et sensors pour la simulation météo et prix DSO."""
import json
import requests
import re
import warnings
warnings.filterwarnings("ignore")
from datetime import datetime, timezone, timedelta
FM_HOST = "https://cariflex.digitribe.fr"
CREDS_FILE = "/tmp/fm_creds.json"
with open(CREDS_FILE) as f:
creds = json.load(f)
session = requests.Session()
session.verify = False
# Login
r = session.get(f"{FM_HOST}/login")
match = re.search(r'<input[^>]*csrf_token[^>]*value="([^"]+)"', r.text)
csrf = match.group(1)
r = session.post(f"{FM_HOST}/login", data={
"email": creds["email"], "password": creds["password"],
"csrf_token": csrf, "remember": "y"
}, allow_redirects=True)
print(f"Login: {'OK' if 'dashboard' in r.url else 'FAILED'}")
# Get auth token for API
token = r.cookies.get("session", "")
headers = {"Authorization": f"Bearer {token}"} if token else {}
def fm_post(path, data=None, json_data=None):
url = f"{FM_HOST}/api/v3_0{path}"
r = session.post(url, data=data, json=json_data)
return r
def fm_get(path):
url = f"{FM_HOST}/api/v3_0{path}"
r = session.get(url)
return r
# ========================================
# 1. Créer les assets météo
# ========================================
print("\n=== Création des assets météo ===")
meteo_assets = [
{
"name": "Météo Martinique",
"asset_type_name": "Weather Station",
"latitude": 14.6091,
"longitude": -61.2155,
"sensors": [
{"name": "irradiance", "unit": "W/m²"},
{"name": "temperature", "unit": "°C"},
{"name": "wind_speed", "unit": "m/s"},
{"name": "humidity", "unit": "%"},
]
},
{
"name": "Prix DSO",
"asset_type_name": "Market",
"latitude": 14.6091,
"longitude": -61.2155,
"sensors": [
{"name": "consumption_price", "unit": "EUR/MWh"},
{"name": "production_price", "unit": "EUR/MWh"},
]
}
]
for asset in meteo_assets:
r = fm_post("/assets", json_data={
"name": asset["name"],
"latitude": asset["latitude"],
"longitude": asset["longitude"],
})
print(f" Asset '{asset['name']}': HTTP {r.status_code}")
if r.status_code in [200, 201]:
try:
resp = r.json()
asset_id = resp.get("id")
print(f" ID: {asset_id}")
except:
print(f" Response: {r.text[:200]}")
else:
print(f" Error: {r.text[:200]}")
print("\n=== Simulation setup complete ===")

132
scripts/inject_final.py Normal file
View File

@@ -0,0 +1,132 @@
#!/usr/bin/env python3
"""Cariflex - Injecte les données météo réelles et prix DSO dans FM via le CLI."""
import subprocess
import json
import random
import math
from datetime import datetime, timezone, timedelta
import pytz
import requests
MARTINIQUE_TZ = pytz.timezone("America/Martinique")
# Sensor IDs (from previous creation)
SENSORS = {
"irradiance": 81,
"temperature": 82,
"wind_speed": 83,
"consumption_price": 84,
"production_price": 85,
}
def run_fm_cli(args):
"""Run FM CLI inside the container."""
cmd = ["docker", "exec", "flexmeasures-server", "bash", "-c",
f"cd /app && .venv/bin/flexmeasures {' '.join(args)}"]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
return result.stdout.strip(), result.stderr.strip(), result.returncode
def post_sensor_data(sensor_id, values, unit, start, duration="PT1H"):
"""Post sensor data via FM API."""
import re
# Login first
session = requests.Session()
session.verify = False
r = session.get("https://cariflex.digitribe.fr/login")
match = re.search(r'<input[^>]*csrf_token[^>]*value="([^"]+)"', r.text)
if not match:
return False
csrf = match.group(1)
r = session.post("https://cariflex.digitribe.fr/login", data={
"email": "admin@digitribe.fr", "password": "Digitribe972",
"csrf_token": csrf, "remember": "y"
}, allow_redirects=True)
if "dashboard" not in r.url:
return False
# Post data - use proper ISO format with timezone
r = session.post(
f"https://cariflex.digitribe.fr/api/v3_0/sensors/{sensor_id}/data",
json={
"values": values,
"start": start,
"duration": duration,
"unit": unit
},
timeout=30
)
return r.status_code in [200, 201, 202]
# ========================================
# 1. Données météo réelles via API Open-Meteo
# ========================================
print("=== Récupération des données météo ===")
LAT, LON = 14.6091, -61.2155
today = datetime.now(MARTINIQUE_TZ).strftime("%Y-%m-%d")
resp = requests.get(
f"https://api.open-meteo.com/v1/forecast?"
f"latitude={LAT}&longitude={LON}"
f"&hourly=shortwave_radiation,temperature_2m,wind_speed_10m"
f"&timezone=America/Martinique"
f"&start_date={today}&end_date={today}",
timeout=15
)
if resp.status_code == 200:
wdata = resp.json()
times = wdata["hourly"]["time"]
for sensor_name, (key, unit) in {
"irradiance": ("shortwave_radiation", "W/m²"),
"temperature": ("temperature_2m", "°C"),
"wind_speed": ("wind_speed_10m", "m/s")
}.items():
sensor_id = SENSORS[sensor_name]
values = wdata["hourly"][key]
posted = 0
for i, t in enumerate(times):
# Parse datetime and format properly
dt = datetime.fromisoformat(t)
dt_utc = dt.astimezone(timezone.utc)
start_str = dt_utc.strftime("%Y-%m-%dT%H:%M:%S+00:00")
val = round(max(0, values[i]), 1)
if post_sensor_data(sensor_id, [val], unit, start_str):
posted += 1
print(f" '{sensor_name}': {posted}/{len(times)} posted")
else:
print(f" Erreur API: {resp.status_code}")
# ========================================
# 2. Prix DSO simulés (24h)
# ========================================
print("\n=== Injection des prix DSO ===")
now_utc = datetime.now(timezone.utc)
for sensor_name, sensor_id in [("consumption_price", SENSORS["consumption_price"]),
("production_price", SENSORS["production_price"])]:
posted = 0
for h in range(24):
dt = now_utc - timedelta(hours=23-h)
hour_of_day = dt.hour
if 6 <= hour_of_day <= 22:
price = round(random.uniform(80, 150), 2) if "consumption" in sensor_name else round(random.uniform(60, 120), 2)
else:
price = round(random.uniform(40, 80), 2) if "consumption" in sensor_name else round(random.uniform(30, 60), 2)
start_str = dt.strftime("%Y-%m-%dT%H:%M:%S+00:00")
if post_sensor_data(sensor_id, [price], "EUR/MWh", start_str):
posted += 1
print(f" '{sensor_name}': {posted}/24 posted")
print("\n=== Terminé ===")

97
scripts/inject_weather.py Normal file
View File

@@ -0,0 +1,97 @@
#!/usr/bin/env python3
"""Cariflex - Corrige l'injection des données météo."""
import json
import requests
import re
import warnings
import random
from datetime import datetime, timezone, timedelta
import pytz
warnings.filterwarnings("ignore")
FM_HOST = "https://cariflex.digitribe.fr"
CREDS_FILE = "/tmp/fm_creds.json"
MARTINIQUE_TZ = pytz.timezone("America/Martinique")
with open(CREDS_FILE) as f:
creds = json.load(f)
session = requests.Session()
session.verify = False
# Login
r = session.get(f"{FM_HOST}/login")
match = re.search(r'<input[^>]*csrf_token[^>]*value="([^"]+)"', r.text)
csrf = match.group(1)
r = session.post(f"{FM_HOST}/login", data={
"email": creds["email"], "password": creds["password"],
"csrf_token": csrf, "remember": "y"
}, allow_redirects=True)
print(f"Login: {'OK' if 'dashboard' in r.url else 'FAILED'}")
# Sensor IDs (from previous creation)
sensors = {
"irradiance": 81,
"temperature": 82,
"wind_speed": 83,
"consumption_price": 84,
"production_price": 85,
}
# Inject weather data
print("\n=== Données météo réelles ===")
LAT, LON = 14.6091, -61.2155
today = datetime.now(MARTINIQUE_TZ).strftime("%Y-%m-%d")
resp = requests.get(
f"https://api.open-meteo.com/v1/forecast?"
f"latitude={LAT}&longitude={LON}"
f"&hourly=shortwave_radiation,temperature_2m,wind_speed_10m"
f"&timezone=America/Martinique"
f"&start_date={today}&end_date={today}",
timeout=15
)
if resp.status_code == 200:
wdata = resp.json()
times = wdata["hourly"]["time"]
for sensor_name, (key, unit) in {
"irradiance": ("shortwave_radiation", "W/m²"),
"temperature": ("temperature_2m", "°C"),
"wind_speed": ("wind_speed_10m", "m/s")
}.items():
sensor_id = sensors[sensor_name]
values = wdata["hourly"][key]
posted = 0
for i, t in enumerate(times):
dt = datetime.fromisoformat(t)
val = round(max(0, values[i]), 1)
try:
r = session.post(
f"{FM_HOST}/api/v3_0/sensors/{sensor_id}/data",
json={
"values": [val],
"start": dt.isoformat(),
"duration": "PT1H",
"unit": unit
},
timeout=30
)
if r.status_code in [200, 201, 202]:
posted += 1
else:
if posted == 0:
print(f" Error response: {r.text[:100]}")
except Exception as e:
print(f" Exception: {e}")
print(f" '{sensor_name}': {posted}/{len(times)} posted")
else:
print(f" API error: {resp.status_code}")
print("\n=== Terminé ===")

View File

@@ -0,0 +1,165 @@
#!/usr/bin/env python3
"""Cariflex - Injecte les données météo réelles et prix DSO simulés."""
import json
import requests
import re
import warnings
import random
from datetime import datetime, timezone, timedelta
import pytz
warnings.filterwarnings("ignore")
FM_HOST = "https://cariflex.digitribe.fr"
CREDS_FILE = "/tmp/fm_creds.json"
MARTINIQUE_TZ = pytz.timezone("America/Martinique")
with open(CREDS_FILE) as f:
creds = json.load(f)
session = requests.Session()
session.verify = False
# Login
r = session.get(f"{FM_HOST}/login")
match = re.search(r'<input[^>]*csrf_token[^>]*value="([^"]+)"', r.text)
csrf = match.group(1)
r = session.post(f"{FM_HOST}/login", data={
"email": creds["email"], "password": creds["password"],
"csrf_token": csrf, "remember": "y"
}, allow_redirects=True)
print(f"Login: {'OK' if 'dashboard' in r.url else 'FAILED'}")
# Get existing sensors
r = session.get(f"{FM_HOST}/api/v3_0/sensors")
sensors = {}
if r.status_code == 200:
for s in r.json():
sensors[s["name"]] = s["id"]
print(f" Sensor: {s['id']} - {s['name']} (asset {s.get('generic_asset_id', '?')})")
# ========================================
# 1. Injecter les prévisions météo réelles
# ========================================
print("\n=== Injection des données météo ===")
LAT, LON = 14.6091, -61.2155
now_local = datetime.now(MARTINIQUE_TZ)
today = now_local.strftime("%Y-%m-%d")
url = (
f"https://api.open-meteo.com/v1/forecast?"
f"latitude={LAT}&longitude={LON}"
f"&hourly=shortwave_radiation,temperature_2m,wind_speed_10m"
f"&timezone=America/Martinique"
f"&start_date={today}&end_date={today}"
)
try:
resp = requests.get(url, timeout=15)
if resp.status_code == 200:
wdata = resp.json()
hourly = wdata["hourly"]
times = hourly["time"]
irradiance = hourly["shortwave_radiation"]
temperature = hourly["temperature_2m"]
wind_speed = hourly["wind_speed_10m"]
print(f" Données météo récupérées: {len(times)} heures")
# Map sensor names to data
sensor_data_map = {
"irradiance": (irradiance, "W/m²"),
"temperature": (temperature, "°C"),
"wind_speed": (wind_speed, "m/s"),
}
for sensor_name, (data, unit) in sensor_data_map.items():
if sensor_name not in sensors:
print(f" Sensor '{sensor_name}' non trouvé, ignoré")
continue
sensor_id = sensors[sensor_name]
posted = 0
for i, t in enumerate(times):
dt = datetime.fromisoformat(t)
value = max(0, data[i]) if data[i] is not None else 0
r = session.post(
f"{FM_HOST}/api/v3_0/sensors/{sensor_id}/data",
json={
"values": [round(value, 1)],
"start": dt.isoformat(),
"duration": "PT1H",
"unit": unit
},
timeout=30
)
if r.status_code in [200, 201, 202]:
posted += 1
print(f" Sensor '{sensor_name}' (ID {sensor_id}): {posted}/{len(times)} values posted")
else:
print(f" Erreur API météo: HTTP {resp.status_code}")
except Exception as e:
print(f" Erreur récupération météo: {e}")
# ========================================
# 2. Injecter les prix DSO simulés
# ========================================
print("\n=== Injection des prix DSO ===")
if "consumption_price" in sensors and "production_price" in sensors:
consumption_price_id = sensors["consumption_price"]
production_price_id = sensors["production_price"]
now_utc = datetime.now(timezone.utc)
posted = 0
for hour in range(24):
dt = now_utc - timedelta(hours=23-hour)
hour_of_day = dt.hour
# Price pattern: peak during day, lower at night
if 6 <= hour_of_day <= 22:
consumption_price = round(random.uniform(80, 150), 2)
production_price = round(random.uniform(60, 120), 2)
else:
consumption_price = round(random.uniform(40, 80), 2)
production_price = round(random.uniform(30, 60), 2)
# Post consumption price
r = session.post(
f"{FM_HOST}/api/v3_0/sensors/{consumption_price_id}/data",
json={
"values": [consumption_price],
"start": dt.isoformat(),
"duration": "PT1H",
"unit": "EUR/MWh"
},
timeout=30
)
if r.status_code in [200, 201, 202]:
posted += 1
# Post production price
r = session.post(
f"{FM_HOST}/api/v3_0/sensors/{production_price_id}/data",
json={
"values": [production_price],
"start": dt.isoformat(),
"duration": "PT1H",
"unit": "EUR/MWh"
},
timeout=30
)
if r.status_code in [200, 201, 202]:
posted += 1
print(f" Prix DSO injectés: {posted} values")
else:
print(" Sensors de prix non trouvés")
print("\n=== Injection terminée ===")

View File

@@ -0,0 +1,299 @@
#!/usr/bin/env python3
"""Cariflex - Script optimal de simulation avec page web intégrée."""
import json
import requests
import re
import warnings
import random
import math
from datetime import datetime, timezone, timedelta
from flask import Flask, render_template_string, request, jsonify
import pytz
warnings.filterwarnings("ignore")
FM_HOST = "https://cariflex.digitribe.fr"
CREDS_FILE = "/tmp/fm_creds.json"
MARTINIQUE_TZ = pytz.timezone("America/Martinique")
# ========================================
# Web App
# ========================================
app = Flask(__name__)
DASHBOARD_HTML = """
<!DOCTYPE html>
<html>
<head>
<title>Cariflex EMS - Simulation</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
body { background: #1a1a2e; color: #eee; }
.card { background: #16213e; border: 1px solid #0f3460; }
.card-header { background: #0f3460; }
.stat-value { font-size: 2rem; font-weight: bold; color: #e94560; }
.stat-label { font-size: 0.8rem; color: #888; }
.refresh-btn { background: #e94560; border: none; }
.refresh-btn:hover { background: #c73e54; }
#log { background: #0d1117; color: #0f0; font-family: monospace; max-height: 300px; overflow-y: auto; padding: 10px; font-size: 0.8rem; }
</style>
</head>
<body class="p-4">
<div class="container-fluid">
<h1 class="mb-4">🌴 Cariflex EMS - Simulation Temps Réel</h1>
<div class="row mb-4">
<div class="col-md-3">
<div class="card text-center p-3">
<div class="stat-value" id="pv_power">--</div>
<div class="stat-label">Production PV (kW)</div>
</div>
</div>
<div class="col-md-3">
<div class="card text-center p-3">
<div class="stat-value" id="bat_soc">--</div>
<div class="stat-label">SOC Batteries (kWh)</div>
</div>
</div>
<div class="col-md-3">
<div class="card text-center p-3">
<div class="stat-value" id="ev_load">--</div>
<div class="stat-label">Charge VE (kW)</div>
</div>
</div>
<div class="col-md-3">
<div class="card text-center p-3">
<div class="stat-value" id="price">--</div>
<div class="stat-label">Prix DSO (EUR/MWh)</div>
</div>
</div>
</div>
<div class="row mb-4">
<div class="col-md-3">
<div class="card text-center p-3">
<div class="stat-value" id="irradiance">--</div>
<div class="stat-label">Irradiance (W/m²)</div>
</div>
</div>
<div class="col-md-3">
<div class="card text-center p-3">
<div class="stat-value" id="temperature">--</div>
<div class="stat-label">Température (°C)</div>
</div>
</div>
<div class="col-md-3">
<div class="card text-center p-3">
<div class="stat-value" id="flexibility">--</div>
<div class="stat-label">Flexibilité (kW)</div>
</div>
</div>
<div class="col-md-3">
<div class="card text-center p-3">
<div class="stat-value" id="balance">--</div>
<div class="stat-label">Balance Réseau (kW)</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-8">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<span>📊 Données en temps réel</span>
<button class="btn btn-sm refresh-btn" onclick="refreshData()">🔄 Rafraîchir</button>
</div>
<div class="card-body">
<div id="log">Chargement...</div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-header">⚙️ Paramètres</div>
<div class="card-body">
<form id="paramsForm">
<div class="mb-3">
<label class="form-label">Intervalle simulation (sec)</label>
<input type="number" class="form-control" id="interval" value="30" min="5" max="300">
</div>
<div class="mb-3">
<label class="form-label">Multiplicateur PV</label>
<input type="range" class="form-range" id="pv_mult" min="0.5" max="2" step="0.1" value="1">
</div>
<div class="mb-3">
<label class="form-label">Multiplicateur Charge VE</label>
<input type="range" class="form-range" id="ev_mult" min="0.5" max="2" step="0.1" value="1">
</div>
<div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" id="use_real_weather" checked>
<label class="form-check-label">Données météo réelles</label>
</div>
<button type="submit" class="btn btn-primary w-100">Appliquer</button>
</form>
</div>
</div>
</div>
</div>
</div>
<script>
function refreshData() {
fetch('/api/data')
.then(r => r.json())
.then(data => {
document.getElementById('pv_power').textContent = data.pv_power?.toFixed(1) || '--';
document.getElementById('bat_soc').textContent = data.bat_soc?.toFixed(1) || '--';
document.getElementById('ev_load').textContent = data.ev_load?.toFixed(1) || '--';
document.getElementById('price').textContent = data.price?.toFixed(2) || '--';
document.getElementById('irradiance').textContent = data.irradiance?.toFixed(0) || '--';
document.getElementById('temperature').textContent = data.temperature?.toFixed(1) || '--';
document.getElementById('flexibility').textContent = data.flexibility?.toFixed(1) || '--';
document.getElementById('balance').textContent = data.balance?.toFixed(1) || '--';
document.getElementById('log').innerHTML = data.log || 'Pas de données';
});
}
document.getElementById('paramsForm').onsubmit = function(e) {
e.preventDefault();
const params = {
interval: document.getElementById('interval').value,
pv_mult: document.getElementById('pv_mult').value,
ev_mult: document.getElementById('ev_mult').value,
use_real_weather: document.getElementById('use_real_weather').checked
};
fetch('/api/params', {method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(params)})
.then(r => r.json())
.then(data => { alert('Paramètres appliqués'); refreshData(); });
};
setInterval(refreshData, 5000);
refreshData();
</script>
</body>
</html>
"""
# Global state
sim_state = {
"interval": 30,
"pv_mult": 1.0,
"ev_mult": 1.0,
"use_real_weather": True,
"running": False,
"log": [],
"last_data": {}
}
def fm_login():
session = requests.Session()
session.verify = False
r = session.get(f"{FM_HOST}/login")
match = re.search(r'<input[^>]*csrf_token[^>]*value="([^"]+)"', r.text)
csrf = match.group(1)
r = session.post(f"{FM_HOST}/login", data={
"email": "admin@digitribe.fr", "password": "Digitribe972",
"csrf_token": csrf, "remember": "y"
}, allow_redirects=True)
return session if "dashboard" in r.url else None
def get_fm_data(session):
"""Get latest data from FM sensors."""
data = {}
# Get sensor data via API
sensors = {
"pv": (41, 50), # PV sensors
"bat": (51, 60), # Battery sensors
"ev": (61, 70), # EV charger sensors
"v2g": (71, 80), # V2G sensors
"irradiance": 81,
"temperature": 82,
"consumption_price": 84,
}
for name, sensor_range in sensors.items():
if isinstance(sensor_range, tuple):
# Multiple sensors - get aggregate
total = 0
count = 0
for sid in range(sensor_range[0], sensor_range[1] + 1):
r = session.get(f"{FM_HOST}/api/v3_0/sensors/{sid}/data?limit=1")
if r.status_code == 200:
try:
vals = r.json()
if vals and len(vals) > 0:
total += vals[-1].get("event_value", 0)
count += 1
except:
pass
data[name] = total if count > 0 else 0
else:
# Single sensor
r = session.get(f"{FM_HOST}/api/v3_0/sensors/{sensor_range}/data?limit=1")
if r.status_code == 200:
try:
vals = r.json()
if vals and len(vals) > 0:
data[name] = vals[-1].get("event_value", 0)
except:
pass
return data
@app.route("/")
def index():
return render_template_string(DASHBOARD_HTML)
@app.route("/api/data")
def api_data():
session = fm_login()
if not session:
return jsonify({"error": "Auth failed"})
data = get_fm_data(session)
# Calculate derived values
pv_power = data.get("pv", 0)
bat_soc = data.get("bat", 0)
ev_load = data.get("ev", 0)
v2g_power = data.get("v2g", 0)
price = data.get("consumption_price", 0)
irradiance = data.get("irradiance", 0)
temperature = data.get("temperature", 0)
# Flexibility = total battery + V2G capacity
flexibility = bat_soc + v2g_power
# Balance = Production - Consumption
balance = pv_power - ev_load
result = {
"pv_power": pv_power,
"bat_soc": bat_soc,
"ev_load": ev_load,
"price": price,
"irradiance": irradiance,
"temperature": temperature,
"flexibility": flexibility,
"balance": balance,
"log": "<br>".join(sim_state["log"][-20:]) if sim_state["log"] else "En attente de données..."
}
sim_state["last_data"] = result
return jsonify(result)
@app.route("/api/params", methods=["POST"])
def api_params():
params = request.json
sim_state["interval"] = int(params.get("interval", 30))
sim_state["pv_mult"] = float(params.get("pv_mult", 1.0))
sim_state["ev_mult"] = float(params.get("ev_mult", 1.0))
sim_state["use_real_weather"] = bool(params.get("use_real_weather", True))
return jsonify({"status": "ok"})
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5001, debug=False)

123
scripts/simulation_setup.py Normal file
View File

@@ -0,0 +1,123 @@
#!/usr/bin/env python3
"""Cariflex - Crée les sensors et injecte données météo/prix."""
import json
import requests
import re
import warnings
import random
from datetime import datetime, timezone, timedelta
import pytz
warnings.filterwarnings("ignore")
FM_HOST = "https://cariflex.digitribe.fr"
CREDS_FILE = "/tmp/fm_creds.json"
MARTINIQUE_TZ = pytz.timezone("America/Martinique")
with open(CREDS_FILE) as f:
creds = json.load(f)
session = requests.Session()
session.verify = False
# Login
r = session.get(f"{FM_HOST}/login")
match = re.search(r'<input[^>]*csrf_token[^>]*value="([^"]+)"', r.text)
csrf = match.group(1)
r = session.post(f"{FM_HOST}/login", data={
"email": creds["email"], "password": creds["password"],
"csrf_token": csrf, "remember": "y"
}, allow_redirects=True)
print(f"Login: {'OK' if 'dashboard' in r.url else 'FAILED'}")
RESOLUTION = "PT1H"
print("\n=== Création des sensors ===")
sensors_to_create = [
{"name": "irradiance", "unit": "W/m²", "generic_asset_id": 81, "event_resolution": RESOLUTION},
{"name": "temperature", "unit": "°C", "generic_asset_id": 81, "event_resolution": RESOLUTION},
{"name": "wind_speed", "unit": "m/s", "generic_asset_id": 81, "event_resolution": RESOLUTION},
{"name": "consumption_price", "unit": "EUR/MWh", "generic_asset_id": 82, "event_resolution": RESOLUTION},
{"name": "production_price", "unit": "EUR/MWh", "generic_asset_id": 82, "event_resolution": RESOLUTION},
]
created_sensors = {}
for s in sensors_to_create:
r = session.post(f"{FM_HOST}/api/v3_0/sensors", json=s)
if r.status_code == 201:
sid = r.json().get("id")
created_sensors[s["name"]] = sid
print(f" ✓ Sensor '{s['name']}' (ID {sid})")
if not created_sensors:
# Find existing sensors
r = session.get(f"{FM_HOST}/api/v3_0/sensors")
for s in r.json():
if s["name"] in ["irradiance", "temperature", "wind_speed", "consumption_price", "production_price"]:
created_sensors[s["name"]] = s["id"]
print(f" Found sensor '{s['name']}' (ID {s['id']})")
print(f"\nSensors: {created_sensors}")
# Inject weather data from Open-Meteo
print("\n=== Données météo réelles ===")
LAT, LON = 14.6091, -61.2155
today = datetime.now(MARTINIQUE_TZ).strftime("%Y-%m-%d")
try:
resp = requests.get(
f"https://api.open-meteo.com/v1/forecast?"
f"latitude={LAT}&longitude={LON}"
f"&hourly=shortwave_radiation,temperature_2m,wind_speed_10m"
f"&timezone=America/Martinique"
f"&start_date={today}&end_date={today}",
timeout=15
)
if resp.status_code == 200:
wdata = resp.json()
times = wdata["hourly"]["time"]
for sensor_name, (key, unit) in {
"irradiance": ("shortwave_radiation", "W/m²"),
"temperature": ("temperature_2m", "°C"),
"wind_speed": ("wind_speed_10m", "m/s")
}.items():
if sensor_name not in created_sensors:
continue
sensor_id = created_sensors[sensor_name]
values = wdata["hourly"][key]
posted = 0
for i, t in enumerate(times):
dt = datetime.fromisoformat(t)
r = session.post(f"{FM_HOST}/api/v3_0/sensors/{sensor_id}/data",
json={"values": [round(max(0, values[i]), 1)], "start": dt.isoformat(), "duration": RESOLUTION, "unit": unit},
timeout=30)
if r.status_code in [200, 201, 202]:
posted += 1
print(f" '{sensor_name}': {posted}/{len(times)} posted")
else:
print(f" API error: {resp.status_code}")
except Exception as e:
print(f" Error: {e}")
# Inject DSO prices (24h)
print("\n=== Prix DSO ===")
if "consumption_price" in created_sensors and "production_price" in created_sensors:
c_id = created_sensors["consumption_price"]
p_id = created_sensors["production_price"]
now_utc = datetime.now(timezone.utc)
posted = 0
for h in range(24):
dt = now_utc - timedelta(hours=23-h)
hd = dt.hour
cp = round(random.uniform(80, 150) if 6 <= hd <= 22 else random.uniform(40, 80), 2)
pp = round(random.uniform(60, 120) if 6 <= hd <= 22 else random.uniform(30, 60), 2)
for sid, price in [(c_id, cp), (p_id, pp)]:
r = session.post(f"{FM_HOST}/api/v3_0/sensors/{sid}/data",
json={"values": [price], "start": dt.isoformat(), "duration": RESOLUTION, "unit": "EUR/MWh"}, timeout=30)
if r.status_code in [200, 201, 202]:
posted += 1
print(f" Prix injectés: {posted}")
print("\n=== Terminé ===")