OpenADR VTN/VEN deployment + Grafana dashboard update
This commit is contained in:
58
scripts/fm_fix_and_schedule.sh
Normal file
58
scripts/fm_fix_and_schedule.sh
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Cariflex - Correction des assets et lancement du scheduling
|
||||||
|
|
||||||
|
echo "=== Correction des flex_context des assets ==="
|
||||||
|
|
||||||
|
# Clear all flex_context for battery and EV assets
|
||||||
|
docker exec flexmeasures-db psql -U flexmeasures -d flexmeasures -c "
|
||||||
|
UPDATE generic_asset SET flex_context = '{}' WHERE id BETWEEN 51 AND 80;
|
||||||
|
UPDATE generic_asset SET flex_model = '{}' WHERE id BETWEEN 51 AND 80;
|
||||||
|
SELECT id, name, flex_context, flex_model FROM generic_asset WHERE id = 51;
|
||||||
|
" 2>&1
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Lancement du scheduling Batteries ==="
|
||||||
|
for sensor_id in $(seq 51 60); do
|
||||||
|
echo " Scheduling sensor $sensor_id..."
|
||||||
|
docker exec flexmeasures-server bash -c "
|
||||||
|
cd /app && .venv/bin/flexmeasures add schedule \
|
||||||
|
--sensor $sensor_id \
|
||||||
|
--start \$(date -u +'%Y-%m-%dT%H:%M:%S+00:00') \
|
||||||
|
--duration PT24H \
|
||||||
|
--resolution PT15M \
|
||||||
|
--soc-at-start 0.5 \
|
||||||
|
--flex-model '{\"soc-min\": \"0.1 MWh\", \"soc-max\": \"1 MWh\", \"power-capacity\": \"0.05 MW\"}' 2>&1
|
||||||
|
" 2>&1 | grep -E "Successfully|SAVED|Error" | head -3
|
||||||
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Lancement du forecasting PV ==="
|
||||||
|
for sensor_id in $(seq 41 45); do
|
||||||
|
echo " Forecasting sensor $sensor_id..."
|
||||||
|
docker exec flexmeasures-server bash -c "
|
||||||
|
cd /app && .venv/bin/flexmeasures add forecasts \
|
||||||
|
--sensor $sensor_id \
|
||||||
|
--to-date \$(date -u -d '+24 hours' +'%Y-%m-%dT%H:%M:%S+00:00') 2>&1
|
||||||
|
" 2>&1 | grep -E "Successfully|SAVED" | head -1
|
||||||
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Vérification des données ==="
|
||||||
|
docker exec flexmeasures-db psql -U flexmeasures -d flexmeasures -c "
|
||||||
|
SELECT
|
||||||
|
'forecasts' as type,
|
||||||
|
COUNT(*) as count
|
||||||
|
FROM timed_belief
|
||||||
|
WHERE source_id IN (SELECT id FROM data_source WHERE name LIKE '%forecast%')
|
||||||
|
AND event_start > NOW() - INTERVAL '1 hour'
|
||||||
|
UNION ALL
|
||||||
|
SELECT
|
||||||
|
'schedules' as type,
|
||||||
|
COUNT(*) as count
|
||||||
|
FROM timed_belief
|
||||||
|
WHERE source_id IN (SELECT id FROM data_source WHERE name LIKE '%schedule%')
|
||||||
|
AND event_start > NOW() - INTERVAL '1 hour';
|
||||||
|
" 2>&1
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Terminé ==="
|
||||||
85
scripts/fm_forecast_schedule.py
Normal file
85
scripts/fm_forecast_schedule.py
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Cariflex - Déclenche le forecasting et scheduling FlexMeasures."""
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
import json
|
||||||
|
from datetime import datetime, timezone, timedelta
|
||||||
|
|
||||||
|
FM_HOST = "https://cariflex.digitribe.fr"
|
||||||
|
|
||||||
|
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)} 2>&1"]
|
||||||
|
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
|
||||||
|
return result.stdout.strip(), result.stderr.strip(), result.returncode
|
||||||
|
|
||||||
|
# ========================================
|
||||||
|
# 1. Forecasting - Prévisions PV
|
||||||
|
# ========================================
|
||||||
|
print("=== Forecasting PV ===")
|
||||||
|
for sensor_id in range(41, 51):
|
||||||
|
stdout, stderr, rc = run_fm_cli([
|
||||||
|
"add", "forecasts",
|
||||||
|
"--sensor", str(sensor_id),
|
||||||
|
"--to-date", (datetime.now(timezone.utc) + timedelta(hours=24)).strftime("%Y-%m-%dT%H:%M:%S+00:00"),
|
||||||
|
])
|
||||||
|
if "Successfully" in stdout or "SAVED" in stdout:
|
||||||
|
print(f" Sensor {sensor_id}: OK")
|
||||||
|
else:
|
||||||
|
print(f" Sensor {sensor_id}: {stdout[:100]}")
|
||||||
|
|
||||||
|
# ========================================
|
||||||
|
# 2. Forecasting - Prévisions Charge VE
|
||||||
|
# ========================================
|
||||||
|
print("\n=== Forecasting Charge VE ===")
|
||||||
|
for sensor_id in range(61, 71):
|
||||||
|
stdout, stderr, rc = run_fm_cli([
|
||||||
|
"add", "forecasts",
|
||||||
|
"--sensor", str(sensor_id),
|
||||||
|
"--to-date", (datetime.now(timezone.utc) + timedelta(hours=24)).strftime("%Y-%m-%dT%H:%M:%S+00:00"),
|
||||||
|
])
|
||||||
|
if "Successfully" in stdout or "SAVED" in stdout:
|
||||||
|
print(f" Sensor {sensor_id}: OK")
|
||||||
|
else:
|
||||||
|
print(f" Sensor {sensor_id}: {stdout[:100]}")
|
||||||
|
|
||||||
|
# ========================================
|
||||||
|
# 3. Scheduling - Plans de charge Batteries
|
||||||
|
# ========================================
|
||||||
|
print("\n=== Scheduling Batteries ===")
|
||||||
|
for sensor_id in range(51, 61):
|
||||||
|
stdout, stderr, rc = run_fm_cli([
|
||||||
|
"add", "schedule",
|
||||||
|
"--sensor", str(sensor_id),
|
||||||
|
"--start", datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S+00:00"),
|
||||||
|
"--duration", "PT24H",
|
||||||
|
"--resolution", "PT15M",
|
||||||
|
"--soc-at-start", "0.5",
|
||||||
|
"--flex-model", '{"soc-min": "10 kWh", "soc-max": "100 kWh", "power-capacity": "50 kW"}',
|
||||||
|
])
|
||||||
|
if "Successfully" in stdout or "SAVED" in stdout:
|
||||||
|
print(f" Sensor {sensor_id}: OK")
|
||||||
|
else:
|
||||||
|
print(f" Sensor {sensor_id}: {stdout[:100]}")
|
||||||
|
|
||||||
|
# ========================================
|
||||||
|
# 4. Scheduling - Plans de charge V2G
|
||||||
|
# ========================================
|
||||||
|
print("\n=== Scheduling V2G ===")
|
||||||
|
for sensor_id in range(71, 81):
|
||||||
|
stdout, stderr, rc = run_fm_cli([
|
||||||
|
"add", "schedule",
|
||||||
|
"--sensor", str(sensor_id),
|
||||||
|
"--start", datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S+00:00"),
|
||||||
|
"--duration", "PT24H",
|
||||||
|
"--resolution", "PT15M",
|
||||||
|
"--soc-at-start", "0.5",
|
||||||
|
"--flex-model", '{"soc-min": "15 kWh", "soc-max": "75 kWh", "power-capacity": "11 kW"}',
|
||||||
|
])
|
||||||
|
if "Successfully" in stdout or "SAVED" in stdout:
|
||||||
|
print(f" Sensor {sensor_id}: OK")
|
||||||
|
else:
|
||||||
|
print(f" Sensor {sensor_id}: {stdout[:100]}")
|
||||||
|
|
||||||
|
print("\n=== Terminé ===")
|
||||||
106
scripts/openadr_ven.py
Normal file
106
scripts/openadr_ven.py
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Cariflex - OpenADR VEN (Virtual End Node)
|
||||||
|
Reçoit les signaux DSR du VTN et les transmet à FlexMeasures.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
from datetime import datetime, timezone, timedelta
|
||||||
|
from openleadr import OpenADRClient, enable_default_logging
|
||||||
|
import requests
|
||||||
|
import re
|
||||||
|
|
||||||
|
enable_default_logging()
|
||||||
|
logger = logging.getLogger("openleadr")
|
||||||
|
|
||||||
|
FM_HOST = "https://cariflex.digitribe.fr"
|
||||||
|
FM_EMAIL = "admin@digitribe.fr"
|
||||||
|
FM_PASSWORD = "Digitribe972"
|
||||||
|
VEN_ID = "Cariflex-VEN"
|
||||||
|
VTN_URL = "http://localhost:8081"
|
||||||
|
|
||||||
|
# Sensor IDs in FM
|
||||||
|
SENSOR_CONSUMPTION_PRICE = 84
|
||||||
|
SENSOR_LOAD_CONTROL = 86
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
if match:
|
||||||
|
csrf = match.group(1)
|
||||||
|
r = session.post(f"{FM_HOST}/login", data={
|
||||||
|
"email": FM_EMAIL, "password": FM_PASSWORD,
|
||||||
|
"csrf_token": csrf, "remember": "y"
|
||||||
|
}, allow_redirects=True)
|
||||||
|
if "dashboard" in r.url:
|
||||||
|
return session
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def post_sensor_data(session, sensor_id, value, unit, start, duration="PT1H"):
|
||||||
|
r = session.post(
|
||||||
|
f"{FM_HOST}/api/v3_0/sensors/{sensor_id}/data",
|
||||||
|
json={"values": [value], "start": start, "duration": duration, "unit": unit},
|
||||||
|
timeout=30
|
||||||
|
)
|
||||||
|
return r.status_code in [200, 201, 202]
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
client = OpenADRClient(
|
||||||
|
ven_id=VEN_ID,
|
||||||
|
ven_name=VEN_ID,
|
||||||
|
vtn_url=VTN_URL,
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info(f"Starting Cariflex VEN: {VEN_ID}")
|
||||||
|
logger.info(f"Connecting to VTN: {VTN_URL}")
|
||||||
|
|
||||||
|
# Login to FM
|
||||||
|
session = fm_login()
|
||||||
|
if session:
|
||||||
|
logger.info("Logged in to FlexMeasures")
|
||||||
|
else:
|
||||||
|
logger.error("Failed to login to FlexMeasures")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Add event handler
|
||||||
|
@client.on_event
|
||||||
|
async def handle_event(event):
|
||||||
|
logger.info(f"Received event: {event}")
|
||||||
|
|
||||||
|
# Extract signal data
|
||||||
|
for signal in event.get("event_signals", []):
|
||||||
|
signal_name = signal.get("signal_name")
|
||||||
|
|
||||||
|
for interval in signal.get("intervals", []):
|
||||||
|
payload = interval.get("signal_payload", 0)
|
||||||
|
dtstart = interval.get("dtstart", datetime.now(timezone.utc))
|
||||||
|
|
||||||
|
if signal_name == "ENERGY_PRICE":
|
||||||
|
price = round(float(payload), 2)
|
||||||
|
success = post_sensor_data(
|
||||||
|
session, SENSOR_CONSUMPTION_PRICE, price, "EUR/MWh",
|
||||||
|
dtstart.isoformat() if isinstance(dtstart, datetime) else str(dtstart)
|
||||||
|
)
|
||||||
|
logger.info(f"Price {price} EUR/MWh: {success}")
|
||||||
|
|
||||||
|
elif signal_name == "LOAD_CONTROL":
|
||||||
|
success = post_sensor_data(
|
||||||
|
session, SENSOR_LOAD_CONTROL, round(float(payload), 2), "%",
|
||||||
|
dtstart.isoformat() if isinstance(dtstart, datetime) else str(dtstart)
|
||||||
|
)
|
||||||
|
logger.info(f"Load control {payload}: {success}")
|
||||||
|
|
||||||
|
return "optIn"
|
||||||
|
|
||||||
|
# Run the client
|
||||||
|
await client.run()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(main())
|
||||||
91
scripts/openadr_vtn.py
Normal file
91
scripts/openadr_vtn.py
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Cariflex - OpenADR VTN (Virtual Top Node) Simulé
|
||||||
|
Envoie des signaux DSR (prix, charge/décharge) au VEN Cariflex.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import random
|
||||||
|
from datetime import datetime, timezone, timedelta
|
||||||
|
from openleadr import OpenADRServer, enable_default_logging
|
||||||
|
from openleadr.objects import Interval
|
||||||
|
import logging
|
||||||
|
|
||||||
|
enable_default_logging()
|
||||||
|
logger = logging.getLogger("openleadr")
|
||||||
|
|
||||||
|
VTN_ID = "Cariflex-VTN"
|
||||||
|
VEN_ID = "Cariflex-VEN"
|
||||||
|
PORT = 8082 # Use 8082 to avoid conflicts
|
||||||
|
|
||||||
|
|
||||||
|
async def ven_lookup(ven_id, *args, **kwargs):
|
||||||
|
"""Look up a VEN by ID - required for auto-registration."""
|
||||||
|
logger.info(f"VEN lookup: {ven_id}")
|
||||||
|
if ven_id == VEN_ID:
|
||||||
|
return {"ven_name": VEN_ID}
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
server = OpenADRServer(
|
||||||
|
vtn_id=VTN_ID,
|
||||||
|
http_port=PORT,
|
||||||
|
http_host="0.0.0.0",
|
||||||
|
ven_lookup=ven_lookup, # This enables auto-registration
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info(f"Starting Cariflex VTN: {VTN_ID} on port {PORT}")
|
||||||
|
|
||||||
|
# Create price event
|
||||||
|
now = datetime.now(timezone.utc)
|
||||||
|
price_intervals = []
|
||||||
|
for h in range(24):
|
||||||
|
dt = now + timedelta(hours=h)
|
||||||
|
hour_of_day = dt.hour
|
||||||
|
if 6 <= hour_of_day <= 22:
|
||||||
|
price = round(random.uniform(80, 150), 2)
|
||||||
|
else:
|
||||||
|
price = round(random.uniform(40, 80), 2)
|
||||||
|
price_intervals.append(
|
||||||
|
Interval(dtstart=dt, duration=timedelta(hours=1), signal_payload=price)
|
||||||
|
)
|
||||||
|
|
||||||
|
server.add_event(
|
||||||
|
ven_id=VEN_ID,
|
||||||
|
signal_name="ENERGY_PRICE",
|
||||||
|
signal_type="price",
|
||||||
|
intervals=price_intervals,
|
||||||
|
event_id=f"price-{now.strftime('%Y%m%d%H%M%S')}",
|
||||||
|
targets=[{"ven_id": VEN_ID}],
|
||||||
|
market_context="https://cariflex.digitribe.fr/price",
|
||||||
|
)
|
||||||
|
logger.info(f"Created price event with {len(price_intervals)} intervals")
|
||||||
|
|
||||||
|
# Create load control event
|
||||||
|
load_intervals = []
|
||||||
|
for h in range(4):
|
||||||
|
dt = now + timedelta(hours=h)
|
||||||
|
payload = random.choice([0.0, 0.0, 0.0, 0.5, 1.0])
|
||||||
|
load_intervals.append(
|
||||||
|
Interval(dtstart=dt, duration=timedelta(hours=1), signal_payload=payload)
|
||||||
|
)
|
||||||
|
|
||||||
|
server.add_event(
|
||||||
|
ven_id=VEN_ID,
|
||||||
|
signal_name="LOAD_CONTROL",
|
||||||
|
signal_type="x-loadControlCapacity",
|
||||||
|
intervals=load_intervals,
|
||||||
|
event_id=f"load-{now.strftime('%Y%m%d%H%M%S')}",
|
||||||
|
targets=[{"ven_id": VEN_ID}],
|
||||||
|
market_context="https://cariflex.digitribe.fr/load",
|
||||||
|
)
|
||||||
|
logger.info(f"Created load control event with {len(load_intervals)} intervals")
|
||||||
|
logger.info("Created 2 DSR events, starting server...")
|
||||||
|
|
||||||
|
# Start server (blocks forever)
|
||||||
|
await server.run()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(main())
|
||||||
170
scripts/openleadr_ven.py
Normal file
170
scripts/openleadr_ven.py
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Cariflex - OpenLEADR VEN (Virtual End Node)
|
||||||
|
Reçoit les signaux DSR du TSO/DSO et les transmet à FlexMeasures.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
from datetime import datetime, timezone, timedelta
|
||||||
|
from openleadr import OpenADRClient, enable_default_logging
|
||||||
|
import requests
|
||||||
|
import re
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
FM_HOST = "https://cariflex.digitribe.fr"
|
||||||
|
FM_EMAIL = "admin@digitribe.fr"
|
||||||
|
FM_PASSWORD = "Digitribe972"
|
||||||
|
VEN_NAME = "Cariflex-VEN"
|
||||||
|
VTN_URL = "http://localhost:8080" # URL du VTN OpenADR (à configurer)
|
||||||
|
|
||||||
|
# Sensors IDs dans FM
|
||||||
|
SENSOR_CONSUMPTION_PRICE = 84
|
||||||
|
SENSOR_PRODUCTION_PRICE = 85
|
||||||
|
SENSOR_PV_FORECAST = 41 # PV_01
|
||||||
|
SENSOR_BAT_SOC = 51 # Bat_01
|
||||||
|
|
||||||
|
# Setup logging
|
||||||
|
enable_default_logging()
|
||||||
|
logger = logging.getLogger("openleadr")
|
||||||
|
|
||||||
|
class CariflexVEN:
|
||||||
|
"""OpenLEADR VEN pour Cariflex EMS."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.client = OpenADRClient(
|
||||||
|
vad_name=VEN_NAME,
|
||||||
|
vtn_url=VTN_URL,
|
||||||
|
)
|
||||||
|
self.fm_session = None
|
||||||
|
|
||||||
|
def fm_login(self):
|
||||||
|
"""Login to FlexMeasures."""
|
||||||
|
self.fm_session = requests.Session()
|
||||||
|
self.fm_session.verify = False
|
||||||
|
r = self.fm_session.get(f"{FM_HOST}/login")
|
||||||
|
match = re.search(r'<input[^>]*csrf_token[^>]*value="([^"]+)"', r.text)
|
||||||
|
if match:
|
||||||
|
csrf = match.group(1)
|
||||||
|
r = self.fm_session.post(f"{FM_HOST}/login", data={
|
||||||
|
"email": FM_EMAIL, "password": FM_PASSWORD,
|
||||||
|
"csrf_token": csrf, "remember": "y"
|
||||||
|
}, allow_redirects=True)
|
||||||
|
return "dashboard" in r.url
|
||||||
|
return False
|
||||||
|
|
||||||
|
def post_sensor_data(self, sensor_id, values, unit, start, duration="PT1H"):
|
||||||
|
"""Post sensor data to FlexMeasures."""
|
||||||
|
if not self.fm_session:
|
||||||
|
if not self.fm_login():
|
||||||
|
return False
|
||||||
|
|
||||||
|
r = self.fm_session.post(
|
||||||
|
f"{FM_HOST}/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]
|
||||||
|
|
||||||
|
async def handle_dsr_event(self, event):
|
||||||
|
"""
|
||||||
|
Handle DSR event from TSO/DSO.
|
||||||
|
|
||||||
|
Event format:
|
||||||
|
{
|
||||||
|
"event_id": "...",
|
||||||
|
"modification_number": 0,
|
||||||
|
"event_status": "active",
|
||||||
|
"created_date_time": "...",
|
||||||
|
"event_descriptor": {...},
|
||||||
|
"active_period": {...},
|
||||||
|
"event_signals": [
|
||||||
|
{
|
||||||
|
"signal_name": "LOAD_CONTROL",
|
||||||
|
"signal_type": "X_LOAD_CONTROL",
|
||||||
|
"intervals": [
|
||||||
|
{"duration": "PT1H", "signal_payload": 0.5}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
logger.info(f"Received DSR event: {event.get('event_id')}")
|
||||||
|
|
||||||
|
for signal in event.get("event_signals", []):
|
||||||
|
signal_name = signal.get("signal_name")
|
||||||
|
signal_type = signal.get("signal_type")
|
||||||
|
|
||||||
|
for interval in signal.get("intervals", []):
|
||||||
|
payload = interval.get("signal_payload")
|
||||||
|
duration = interval.get("duration", "PT1H")
|
||||||
|
|
||||||
|
# Convert payload to price signal
|
||||||
|
# payload: 0.0 = low price, 1.0 = high price
|
||||||
|
if signal_name in ["LOAD_CONTROL", "PRICE"]:
|
||||||
|
# Calculate price based on payload
|
||||||
|
base_price = 100 # EUR/MWh
|
||||||
|
price = base_price * (0.5 + payload) # 50-150 EUR/MWh
|
||||||
|
|
||||||
|
# Post to FM
|
||||||
|
now = datetime.now(timezone.utc)
|
||||||
|
success = self.post_sensor_data(
|
||||||
|
SENSOR_CONSUMPTION_PRICE,
|
||||||
|
[round(price, 2)],
|
||||||
|
"EUR/MWh",
|
||||||
|
now.isoformat(),
|
||||||
|
duration
|
||||||
|
)
|
||||||
|
logger.info(f"Posted price {price} EUR/MWh: {success}")
|
||||||
|
|
||||||
|
elif signal_name == "RENEWABLE_GENERATION":
|
||||||
|
# Adjust PV forecast based on DSR signal
|
||||||
|
# payload: 0.0 = no PV, 1.0 = max PV
|
||||||
|
pv_factor = payload
|
||||||
|
# TODO: Adjust PV forecast
|
||||||
|
logger.info(f"PV factor: {pv_factor}")
|
||||||
|
|
||||||
|
return "OK"
|
||||||
|
|
||||||
|
async def handle_s2_message(self, message):
|
||||||
|
"""
|
||||||
|
Handle S2 message from TSO/DSO.
|
||||||
|
|
||||||
|
S2 messages are used for real-time control of flexible resources.
|
||||||
|
"""
|
||||||
|
logger.info(f"Received S2 message: {message.get('message_type')}")
|
||||||
|
|
||||||
|
msg_type = message.get("message_type")
|
||||||
|
|
||||||
|
if msg_type == "FRBC.ActuatorStatus":
|
||||||
|
# Update actuator status in FM
|
||||||
|
pass
|
||||||
|
elif msg_type == "FRBC.Instruction":
|
||||||
|
# Execute instruction in FM
|
||||||
|
pass
|
||||||
|
elif msg_type == "FRBC.SystemDescription":
|
||||||
|
# Update system description in FM
|
||||||
|
pass
|
||||||
|
|
||||||
|
return "OK"
|
||||||
|
|
||||||
|
async def run(self):
|
||||||
|
"""Run the OpenLEADR VEN."""
|
||||||
|
logger.info(f"Starting Cariflex VEN: {VEN_NAME}")
|
||||||
|
|
||||||
|
# Register event handler
|
||||||
|
self.client.add_handler("on_event", self.handle_dsr_event)
|
||||||
|
|
||||||
|
# Login to FM
|
||||||
|
if self.fm_login():
|
||||||
|
logger.info("Logged in to FlexMeasures")
|
||||||
|
else:
|
||||||
|
logger.error("Failed to login to FlexMeasures")
|
||||||
|
|
||||||
|
# Start the VEN
|
||||||
|
await self.client.run()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
ven = CariflexVEN()
|
||||||
|
asyncio.run(ven.run())
|
||||||
Reference in New Issue
Block a user