132 lines
4.4 KiB
Python
132 lines
4.4 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Cariflex - Automatic Scheduling Service
|
|
Creates battery/EV schedules based on OpenADR price signals and flexibility events.
|
|
"""
|
|
|
|
import json, logging, os, sys
|
|
from datetime import datetime, timezone, timedelta
|
|
|
|
# Add FM path
|
|
sys.path.insert(0, '/app')
|
|
|
|
logging.basicConfig(level=logging.INFO)
|
|
logger = logging.getLogger("cariflex-scheduling")
|
|
|
|
# FM imports
|
|
from flexmeasures.data.models.planning.storage import StorageScheduler
|
|
from flexmeasures.data.services.scheduling import make_schedule
|
|
from flexmeasures.data.models.generic_asset import GenericAsset
|
|
from flexmeasures.data.models.sensor import Sensor
|
|
from flexmeasures.data import db
|
|
|
|
# Scheduling config
|
|
SCHEDULING_CONFIG = {
|
|
"battery_sensors": list(range(51, 61)), # Bat_01 to Bat_10
|
|
"ev_sensors": list(range(61, 71)), # EV_01 to EV_10
|
|
"price_sensor": 84, # consumption_price (OpenADR)
|
|
"load_control_sensor": 86, # load_control_signal (OpenADR)
|
|
"demand_response_sensor": 87, # demand_response_signal (OpenADR)
|
|
"planning_horizon_hours": 24,
|
|
"resolution_minutes": 15,
|
|
}
|
|
|
|
|
|
def get_sensor_data(sensor_id, start, end):
|
|
"""Get sensor data from FM database."""
|
|
sensor = db.session.query(Sensor).filter(Sensor.id == sensor_id).first()
|
|
if not sensor:
|
|
return None
|
|
|
|
beliefs = db.session.query(
|
|
TimedBelief.event_start, TimedBelief.event_value
|
|
).filter(
|
|
TimedBelief.sensor_id == sensor_id,
|
|
TimedBelief.event_start >= start,
|
|
TimedBelief.event_start <= end
|
|
).order_by(TimedBelief.event_start).all()
|
|
|
|
return beliefs
|
|
|
|
|
|
def create_battery_schedule(sensor_id, start_time, duration_hours=24):
|
|
"""Create a schedule for a battery based on price signals."""
|
|
from flexmeasures.data.models.sensor import Sensor
|
|
from flexmeasures.data.models.generic_asset import GenericAsset
|
|
|
|
sensor = db.session.query(Sensor).filter(Sensor.id == sensor_id).first()
|
|
if not sensor:
|
|
logger.warning(f"Sensor {sensor_id} not found")
|
|
return None
|
|
|
|
asset = db.session.query(GenericAsset).filter(GenericAsset.id == sensor.generic_asset_id).first()
|
|
if not asset:
|
|
logger.warning(f"Asset for sensor {sensor_id} not found")
|
|
return None
|
|
|
|
logger.info(f"Creating schedule for {asset.name} (sensor {sensor_id})")
|
|
|
|
# Get price data
|
|
end_time = start_time + timedelta(hours=duration_hours)
|
|
price_sensor = db.session.query(Sensor).filter(Sensor.id == SCHEDULING_CONFIG["price_sensor"]).first()
|
|
|
|
if not price_sensor:
|
|
logger.warning("Price sensor not found")
|
|
return None
|
|
|
|
# Create schedule using FM's built-in scheduler
|
|
try:
|
|
schedule = make_schedule(
|
|
sensor=sensor,
|
|
start=start_time,
|
|
end=end_time,
|
|
resolution=timedelta(minutes=SCHEDULING_CONFIG["resolution_minutes"]),
|
|
flex_model={
|
|
"soc-min": "0.1 MWh",
|
|
"soc-max": "1 MWh",
|
|
"power-capacity": "0.05 MW",
|
|
"charging-efficiency": "0.95",
|
|
"discharging-efficiency": "0.95",
|
|
}
|
|
)
|
|
logger.info(f"Schedule created for {asset.name}: {len(schedule) if schedule else 0} points")
|
|
return schedule
|
|
except Exception as e:
|
|
logger.error(f"Scheduling error for {asset.name}: {e}")
|
|
return None
|
|
|
|
|
|
def run_scheduling():
|
|
"""Run scheduling for all batteries and EVs."""
|
|
logger.info("Starting automatic scheduling...")
|
|
|
|
now = datetime.now(timezone.utc)
|
|
start_time = now.replace(minute=0, second=0, microsecond=0)
|
|
|
|
# Schedule batteries
|
|
for sensor_id in SCHEDULING_CONFIG["battery_sensors"]:
|
|
try:
|
|
create_battery_schedule(sensor_id, start_time)
|
|
except Exception as e:
|
|
logger.error(f"Error scheduling battery {sensor_id}: {e}")
|
|
|
|
# Schedule EVs
|
|
for sensor_id in SCHEDULING_CONFIG["ev_sensors"]:
|
|
try:
|
|
create_battery_schedule(sensor_id, start_time)
|
|
except Exception as e:
|
|
logger.error(f"Error scheduling EV {sensor_id}: {e}")
|
|
|
|
logger.info("Scheduling complete")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
# Run scheduling every 15 minutes
|
|
import time
|
|
while True:
|
|
try:
|
|
run_scheduling()
|
|
except Exception as e:
|
|
logger.error(f"Scheduling error: {e}")
|
|
time.sleep(900) # 15 minutes
|