- flexmeasures-entsoe: ENTSO-E data plugin - flexmeasures-weather: Weather data plugin - USEF Flex Trading Protocol PDF (2.4MB) - Cariflex simulator (publishes to Redis) - Dashboard Grafana updated with correct InfluxDB queries - All tools extracted in /tools/
138 lines
5.0 KiB
Python
138 lines
5.0 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Cariflex Simulator - Publishes simulated EV charging data to Redis.
|
|
Simulates 40 assets: 10 PV, 10 Battery, 10 EV Charger, 10 EV V2G
|
|
"""
|
|
import redis
|
|
import json
|
|
import time
|
|
import random
|
|
import math
|
|
from datetime import datetime, timezone
|
|
|
|
# Redis connection
|
|
r = redis.Redis(host='flexmeasures-redis', port=6379, db=0, decode_responses=True)
|
|
|
|
# Asset configurations
|
|
ASSETS = {
|
|
# PV panels (production)
|
|
"pv_{:02d}": {"type": "pv", "unit": "kW", "min": 0, "max": 5, "base": 2.5},
|
|
# Batteries (storage)
|
|
"bat_{:02d}": {"type": "battery", "unit": "kWh", "min": 10, "max": 100, "base": 50},
|
|
# EV Chargers (consumption)
|
|
"chg_{:02d}": {"type": "ev_charger", "unit": "kW", "min": 0, "max": 22, "base": 11},
|
|
# EVs (V2G - bidirectional)
|
|
"ev_{:02d}": {"type": "ev_v2g", "unit": "kW", "min": -11, "max": 11, "base": 0},
|
|
}
|
|
|
|
def generate_value(asset_config, hour):
|
|
"""Generate a realistic value based on asset type and time of day."""
|
|
cfg = asset_config
|
|
base = cfg["base"]
|
|
|
|
if cfg["type"] == "pv":
|
|
# Solar production: peaks at noon
|
|
solar_factor = max(0, math.sin((hour - 6) * math.pi / 12)) if 6 <= hour <= 18 else 0
|
|
noise = random.gauss(0, 0.5)
|
|
value = base * solar_factor * 2 + noise
|
|
elif cfg["type"] == "battery":
|
|
# SOC: slowly varies throughout the day
|
|
variation = 20 * math.sin(hour * math.pi / 12)
|
|
noise = random.gauss(0, 3)
|
|
value = base + variation + noise
|
|
elif cfg["type"] == "ev_charger":
|
|
# Charging: more active during day and evening
|
|
if 8 <= hour <= 22:
|
|
factor = random.uniform(0.3, 1.0)
|
|
else:
|
|
factor = random.uniform(0, 0.2)
|
|
noise = random.gauss(0, 1)
|
|
value = cfg["max"] * factor + noise
|
|
elif cfg["type"] == "ev_v2g":
|
|
# V2G: charges at night, discharges during peak
|
|
if 0 <= hour <= 6:
|
|
factor = random.uniform(0.3, 0.8) # charging
|
|
elif 17 <= hour <= 21:
|
|
factor = random.uniform(-0.6, -0.2) # discharging
|
|
else:
|
|
factor = random.uniform(-0.1, 0.1)
|
|
noise = random.gauss(0, 0.5)
|
|
value = cfg["max"] * factor + noise
|
|
else:
|
|
value = base + random.gauss(0, 1)
|
|
|
|
return round(max(cfg["min"], min(cfg["max"], value)), 2)
|
|
|
|
def main():
|
|
print("🚗 Cariflex Simulator - Publishing to Redis")
|
|
print(f" Assets: 40 (10 PV, 10 Bat, 10 Chg, 10 EV)")
|
|
print(f" Redis: flexmeasures-redis:6379/0")
|
|
print()
|
|
|
|
# Test Redis connection
|
|
try:
|
|
r.ping()
|
|
print("✅ Redis connected")
|
|
except redis.ConnectionError:
|
|
print("❌ Redis connection failed")
|
|
return
|
|
|
|
# Publish loop
|
|
iteration = 0
|
|
while True:
|
|
now = datetime.now(timezone.utc)
|
|
hour = now.hour
|
|
timestamp = now.isoformat()
|
|
|
|
# Publish each asset's data
|
|
for template, cfg in ASSETS.items():
|
|
for i in range(1, 11):
|
|
asset_id = template.format(i)
|
|
value = generate_value(cfg, hour)
|
|
|
|
# Create data packet
|
|
data = {
|
|
"asset_id": asset_id,
|
|
"type": cfg["type"],
|
|
"value": value,
|
|
"unit": cfg["unit"],
|
|
"timestamp": timestamp,
|
|
"iteration": iteration
|
|
}
|
|
|
|
# Publish to Redis (list per asset)
|
|
key = f"cariflex:asset:{asset_id}"
|
|
r.lpush(key, json.dumps(data))
|
|
r.ltrim(key, 0, 99) # Keep last 100 values
|
|
r.expire(key, 3600) # 1h TTL
|
|
|
|
# Also publish to a pub/sub channel
|
|
r.publish("cariflex:data", json.dumps(data))
|
|
|
|
# Publish aggregate data
|
|
aggregate = {
|
|
"timestamp": timestamp,
|
|
"total_pv_kw": sum(generate_value({"type": "pv", "base": 2.5, "min": 0, "max": 5}, hour) for _ in range(10)),
|
|
"total_battery_soc": sum(generate_value({"type": "battery", "base": 50, "min": 10, "max": 100}, hour) for _ in range(10)) / 10,
|
|
"total_charger_kw": sum(generate_value({"type": "ev_charger", "base": 11, "min": 0, "max": 22}, hour) for _ in range(10)),
|
|
"total_ev_v2g_kw": sum(generate_value({"type": "ev_v2g", "base": 0, "min": -11, "max": 11}, hour) for _ in range(10)),
|
|
"flexibility_available_kw": 0 # Will be calculated
|
|
}
|
|
aggregate["flexibility_available_kw"] = round(
|
|
abs(aggregate["total_ev_v2g_kw"]) +
|
|
abs(aggregate["total_charger_kw"] * 0.3) + # 30% of charger can be modulated
|
|
abs(aggregate["total_battery_soc"] * 0.5), # 50% of battery capacity
|
|
2
|
|
)
|
|
|
|
r.set("cariflex:aggregate", json.dumps(aggregate), ex=300)
|
|
|
|
iteration += 1
|
|
if iteration % 10 == 0:
|
|
print(f" 📊 Iteration {iteration}: published {40} assets to Redis")
|
|
|
|
time.sleep(10) # Publish every 10 seconds
|
|
|
|
if __name__ == "__main__":
|
|
main()
|