diff --git a/.flake8 b/.flake8
index 05ec680..c7e5c32 100755
--- a/.flake8
+++ b/.flake8
@@ -3,5 +3,5 @@ exclude = .git,__pycache__,documentation
max-line-length = 160
max-complexity = 13
select = B,C,E,F,W,B9
-ignore = E501, W503, E203
+ignore = E501, W503, E203, E231
diff --git a/.gitignore b/.gitignore
index 2819977..ffd2eea 100644
--- a/.gitignore
+++ b/.gitignore
@@ -176,3 +176,10 @@ cython_debug/
.vscode
*.pickle
flexmeasures.log
+*.png
+
+#MacOS
+.DS_Store
+flexmeasures_s2/profile_steering/examples/example_schedule_itho_flask.py
+flexmeasures_s2/profile_steering/examples/run_itho_with_debug.sh
+flexmeasures_s2/profile_steering/examples/instructions/
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..13566b8
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 0000000..a55e7a1
--- /dev/null
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/flexmeasures-s2.iml b/.idea/flexmeasures-s2.iml
new file mode 100644
index 0000000..e8fd289
--- /dev/null
+++ b/.idea/flexmeasures-s2.iml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 0000000..105ce2d
--- /dev/null
+++ b/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..f8f2fd7
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..b88603f
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index 1f9652d..d13909c 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,14 @@
using the FLEXMEASURES_PLUGINS setting (a list).
Alternatively, if you installed this plugin as a package (e.g. via `python setup.py install`, `pip install -e` or `pip install flexmeasures_s2` should this project be on Pypi), then "flexmeasures_s2" suffices.
-2.
+2. Config settings (during development)
+
+In your `flexmeasures.cfg`, you can set the following config settings for this plugin:
+
+- `FLEXMEASURES_S2_TARGET_MODE`: "energy" or "costs"
+- `FLEXMEASURES_S2_PRICE_SENSOR`: sensor ID of the price sensor
+
+To do: move these settings to the db asset (preferred scheduler and flex-context).
## Development
@@ -29,3 +36,7 @@ or:
Try it:
pre-commit run --all-files --show-diff-on-failure
+
+For profiling, use:
+
+ pyinstrument -o profiling_results.html test_frbc_device.py
diff --git a/flexmeasures_s2/api/somedata.py b/flexmeasures_s2/api/somedata.py
index 17608d9..80a88e4 100755
--- a/flexmeasures_s2/api/somedata.py
+++ b/flexmeasures_s2/api/somedata.py
@@ -1,5 +1,5 @@
-from flask_security import auth_token_required
from flask_json import as_json
+from flask_security import auth_token_required
from .. import flexmeasures_s2_api_bp
diff --git a/flexmeasures_s2/api/tests/test_api.py b/flexmeasures_s2/api/tests/test_api.py
index d2048ad..3f4eb6f 100644
--- a/flexmeasures_s2/api/tests/test_api.py
+++ b/flexmeasures_s2/api/tests/test_api.py
@@ -1,16 +1,52 @@
from flask import url_for
+from flask_security import decorators as fs_decorators
+from rq.job import Job
+from flexmeasures.api.tests.test_auth_token import patched_check_token
+from flexmeasures.api.tests.utils import UserContext
+from flexmeasures.data.services.scheduling import (
+ handle_scheduling_exception,
+ get_data_source_for_job,
+)
+from flexmeasures.data.tests.utils import work_on_rq
-def test_get_somedata_needs_authtoken(client):
- response = client.get(
- url_for("flexmeasures-s2 API.somedata"),
- headers={"content-type": "application/json"},
- follow_redirects=True,
- )
- assert response.status_code == 401 # HTTP error code 401 Unauthorized.
- assert "application/json" in response.content_type
- assert "not be properly authenticated" in response.json["message"]
+def test_s2_frbc_api(monkeypatch, app, setup_frbc_asset):
+ sensor = setup_frbc_asset.sensors[0]
-# TODO: The somedata endpoint requires authentication to be testes successfully.
-# We'll need to add a user in conftest, which also requires us to add a db to testing
+ with UserContext("test_admin_user@seita.nl") as admin:
+ auth_token = admin.get_auth_token()
+
+ monkeypatch.setattr(fs_decorators, "_check_token", patched_check_token)
+ with app.test_client() as client:
+ trigger_schedule_response = client.post(
+ url_for("SensorAPI:trigger_schedule", id=sensor.id),
+ json={
+ "flex-context": {
+ "target-profile": {}, # add target profile
+ }
+ },
+ headers={"Authorization": auth_token},
+ )
+ print("Server responded with:\n%s" % trigger_schedule_response.json)
+ assert trigger_schedule_response.status_code == 200
+ job_id = trigger_schedule_response.json["schedule"]
+
+ # Now that our scheduling job was accepted, we process the scheduling queue
+ work_on_rq(app.queues["scheduling"], exc_handler=handle_scheduling_exception)
+ job = Job.fetch(job_id, connection=app.queues["scheduling"].connection).is_finished
+ assert job.is_finished is True
+
+ # First, make sure the expected scheduler data source is now there
+ job.refresh() # catch meta info that was added on this very instance
+ scheduler_source = get_data_source_for_job(job)
+ assert scheduler_source.model == "S2Scheduler"
+
+ # try to retrieve the schedule through the /sensors//schedules/ [GET] api endpoint
+ # todo: to be discussed: the response from the S2Scheduler might get a different format than the FM default
+ # get_schedule_response = client.get(
+ # url_for("SensorAPI:get_schedule", id=sensor.id, uuid=job_id),
+ # query_string={"duration": "PT48H"},
+ # )
+ # print("Server responded with:\n%s" % get_schedule_response.json)
+ # assert get_schedule_response.status_code == 200
diff --git a/flexmeasures_s2/conftest.py b/flexmeasures_s2/conftest.py
index 186ee1a..a97ad46 100644
--- a/flexmeasures_s2/conftest.py
+++ b/flexmeasures_s2/conftest.py
@@ -1,12 +1,18 @@
import pytest
+from flask_sqlalchemy import SQLAlchemy
+
+from flexmeasures import Asset, AssetType, Sensor, Account
from flexmeasures.app import create as create_flexmeasures_app
+from flexmeasures.auth.policy import ADMIN_ROLE
from flexmeasures.conftest import ( # noqa F401
db,
fresh_db,
-) # Use these fixtures to rely on the FlexMeasures database.
+) # Use these fixtures to rely on the FlexMeasures database. There might be others in flexmeasures/conftest you want to also re-use
+from flexmeasures.data.services.users import create_user
-# There might be others in flexmeasures/conftest you want to also re-use
+from flexmeasures_s2 import S2_SCHEDULER_SPECS
+from flexmeasures_s2.models.const import FRBC_TYPE
@pytest.fixture(scope="session")
@@ -25,3 +31,43 @@ def app():
ctx.pop()
print("DONE WITH APP FIXTURE")
+
+
+@pytest.fixture(scope="module")
+def setup_admin(db: SQLAlchemy): # noqa: F811
+ account = Account(name="Some FlexMeasures host")
+ db.session.add(account)
+ create_user(
+ username="Test Admin User",
+ email="test_admin_user@seita.nl",
+ account_name=account.name,
+ password="testtest",
+ user_roles=dict(name=ADMIN_ROLE, description="A user who can do everything."),
+ )
+ yield account
+
+
+@pytest.fixture(scope="module")
+def setup_frbc_asset(db: SQLAlchemy, setup_admin): # noqa: F811
+ asset_type = AssetType(name=FRBC_TYPE)
+ asset = Asset(
+ name="Test FRBC asset",
+ generic_asset_type=asset_type,
+ owner=setup_admin,
+ )
+ asset.attributes = {
+ "custom-scheduler": S2_SCHEDULER_SPECS,
+ "flex-model": {
+ "S2-FRBC-device-state": None, # ?todo: add serialized state
+ },
+ }
+ db.session.add(asset)
+ sensor = Sensor(
+ name="power",
+ unit="kW",
+ event_resolution="PT5M",
+ generic_asset=asset,
+ )
+ db.session.add(sensor)
+ db.session.flush() # assign (asset and sensor) IDs
+ yield asset
diff --git a/flexmeasures_s2/models/__init__.py b/flexmeasures_s2/models/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/flexmeasures_s2/models/const.py b/flexmeasures_s2/models/const.py
new file mode 100644
index 0000000..6ff6a35
--- /dev/null
+++ b/flexmeasures_s2/models/const.py
@@ -0,0 +1,2 @@
+NAMESPACE = "fm-s2"
+FRBC_TYPE = f"{NAMESPACE}.FRBC"
diff --git a/flexmeasures_s2/profile_steering/__init__.py b/flexmeasures_s2/profile_steering/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/flexmeasures_s2/profile_steering/cluster_plan.py b/flexmeasures_s2/profile_steering/cluster_plan.py
new file mode 100644
index 0000000..d031f89
--- /dev/null
+++ b/flexmeasures_s2/profile_steering/cluster_plan.py
@@ -0,0 +1,679 @@
+from datetime import datetime
+from typing import List, Dict, Any, Optional
+import uuid
+
+# Common data types
+from flexmeasures_s2.profile_steering.common.joule_profile import JouleProfile
+
+# Import from common data structures to avoid circular imports
+from flexmeasures_s2.profile_steering.common_data_structures import (
+ ClusterState,
+)
+from flexmeasures_s2.profile_steering.common.device_plan import DevicePlan
+
+# Import cluster target
+from flexmeasures_s2.profile_steering.cluster_target import ClusterTarget
+
+
+class ClusterPlanData:
+ """Class representing planning data for a cluster."""
+
+ class CpData:
+ """Class representing data for a congestion point."""
+
+ def __init__(
+ self,
+ cp_id: str,
+ cp_plan: List[float],
+ der_plans: Dict[str, List[float]],
+ cp_consumption: List[float],
+ cp_production: List[float],
+ cp_consumption_max: float,
+ cp_production_max: float,
+ ):
+ self._cp_id = cp_id
+ self._cp_plan = cp_plan
+ self._der_plans = der_plans
+ self._cp_consumption = cp_consumption
+ self._cp_production = cp_production
+ self._cp_consumption_max = cp_consumption_max
+ self._cp_production_max = cp_production_max
+
+ def get_cp_id(self) -> str:
+ return self._cp_id
+
+ def get_cp_plan(self) -> List[float]:
+ return self._cp_plan
+
+ def get_der_plans(self) -> Dict[str, List[float]]:
+ return self._der_plans
+
+ def get_cp_consumption(self) -> List[float]:
+ return self._cp_consumption
+
+ def get_cp_production(self) -> List[float]:
+ return self._cp_production
+
+ def get_cp_consumption_max(self) -> float:
+ return self._cp_consumption_max
+
+ def get_cp_production_max(self) -> float:
+ return self._cp_production_max
+
+ def add_der_plan(
+ self, der_name: str, value: JouleProfile
+ ) -> "ClusterPlanData.CpData":
+ """Add a device energy resource plan to this congestion point.
+
+ Args:
+ der_name: The name of the device energy resource
+ value: The profile of the device energy resource
+
+ Returns:
+ A new CpData instance with the added device energy resource plan
+ """
+ der_plan = to_float_array(value)
+ new_cp_plan = [0.0] * len(self._cp_plan)
+ cp_consumption = [0.0] * len(self._cp_plan)
+ cp_production = [0.0] * len(self._cp_plan)
+ consumption_max = self._cp_consumption_max
+ production_max = self._cp_production_max
+
+ for i in range(len(self._cp_plan)):
+ new_cp_plan[i] = der_plan[i] + self._cp_plan[i]
+ cp_consumption[i] = self._cp_consumption[i]
+ cp_production[i] = self._cp_production[i]
+
+ if der_plan[i] >= 0:
+ cp_consumption[i] += der_plan[i]
+ consumption_max = max(cp_consumption[i], consumption_max)
+ else:
+ cp_production[i] += der_plan[i]
+ production_max = min(cp_production[i], production_max)
+
+ new_der_plans = self._der_plans.copy()
+ new_der_plans[der_name] = der_plan
+
+ return ClusterPlanData.CpData(
+ self._cp_id,
+ new_cp_plan,
+ new_der_plans,
+ cp_consumption,
+ cp_production,
+ consumption_max,
+ production_max,
+ )
+
+ @classmethod
+ def empty(cls, cp_id: str, profile_metadata: Any) -> "ClusterPlanData.CpData":
+ """Create an empty CpData instance.
+
+ Args:
+ cp_id: The congestion point ID
+ profile_metadata: Metadata about the profile
+
+ Returns:
+ An empty CpData instance
+ """
+ timesteps = profile_metadata.nr_of_timesteps
+ return cls(
+ cp_id,
+ [0.0] * timesteps,
+ {},
+ [0.0] * timesteps,
+ [0.0] * timesteps,
+ 0.0,
+ 0.0,
+ )
+
+ def __init__(
+ self,
+ device_plans: Optional[List[DevicePlan]] = None,
+ profile_metadata: Any = None,
+ _id: Optional[str] = None,
+ reason: Optional[str] = None,
+ target: Any = None,
+ active_target: Any = None,
+ current_plan: Optional[List[float]] = None,
+ start: Optional[int] = None,
+ step: Optional[int] = None,
+ global_deviation_score: float = 1.0,
+ constraint_violation_score: float = 1.0,
+ cp_datas: Optional[Dict[str, CpData]] = None,
+ ):
+ self._device_plans = device_plans or []
+ self._profile_metadata = profile_metadata
+ self._id = _id
+ self._reason = reason
+ self._target = target
+ self._active_target = active_target
+ self._current_plan = current_plan
+ self._start = start
+ self._step = step
+ self._global_deviation_score = global_deviation_score
+ self._constraint_violation_score = constraint_violation_score
+ self._cp_datas = cp_datas or {}
+
+ def get_device_plans(self) -> List[DevicePlan]:
+ return self._device_plans
+
+ @property
+ def metadata(self):
+ return self._profile_metadata
+
+ def get_id(self) -> str:
+ return self._id or ""
+
+ def get_reason(self) -> str:
+ return self._reason or ""
+
+ def get_target(self) -> Any:
+ return self._target
+
+ def get_active_target(self) -> Any:
+ return self._active_target
+
+ def get_current_plan(self) -> List[float]:
+ return self._current_plan or []
+
+ def get_start(self) -> int:
+ return self._start or 0
+
+ def get_step(self) -> int:
+ return self._step or 0
+
+ def get_global_deviation_score(self) -> float:
+ return self._global_deviation_score
+
+ def get_constraint_violation_score(self) -> float:
+ return self._constraint_violation_score
+
+ def get_cp_datas(self) -> Dict[str, CpData]:
+ return self._cp_datas
+
+ def is_compatible(self, other: Any) -> bool:
+ """Check if this cluster plan data is compatible with another profile.
+
+ Args:
+ other: The other profile to check compatibility with
+
+ Returns:
+ True if compatible, False otherwise
+ """
+ if self._profile_metadata is None:
+ return False
+
+ return self._profile_metadata.is_compatible(other.metadata)
+
+ def subprofile(self, new_start_date: datetime) -> "ClusterPlanData":
+ """Create a subprofile starting at the specified date.
+
+ Args:
+ new_start_date: The new start date for the profile
+
+ Returns:
+ A new ClusterPlanData instance with adjusted start date
+ """
+ # Create a copy of this instance with adjusted device plans
+ new_device_plans = []
+ for device_plan in self._device_plans:
+ new_profile = device_plan.get_profile().subprofile(new_start_date)
+ new_device_plans.append(DevicePlan(device_plan.device_id, new_profile))
+
+ # Create new profile metadata with adjusted start date
+ new_profile_metadata = self._profile_metadata.subprofile(new_start_date)
+
+ return ClusterPlanData(
+ device_plans=new_device_plans,
+ profile_metadata=new_profile_metadata,
+ _id=self._id,
+ reason=self._reason,
+ target=self._target,
+ active_target=self._active_target,
+ current_plan=self._current_plan,
+ start=int(new_start_date.timestamp() * 1000),
+ # Convert to milliseconds
+ step=self._step,
+ global_deviation_score=self._global_deviation_score,
+ constraint_violation_score=self._constraint_violation_score,
+ cp_datas=self._cp_datas,
+ )
+
+ def adjust_nr_of_elements(self, nr_of_elements: int) -> "ClusterPlanData":
+ """Adjust the number of elements in the profile.
+
+ Args:
+ nr_of_elements: The new number of elements
+
+ Returns:
+ A new ClusterPlanData instance with adjusted number of elements
+ """
+ # Create a copy of this instance with adjusted device plans
+ new_device_plans = []
+ for device_plan in self._device_plans:
+ new_profile = device_plan.get_profile().adjust_nr_of_elements(
+ nr_of_elements
+ )
+ new_device_plans.append(DevicePlan(device_plan.device_id, new_profile))
+
+ # Create new profile metadata with adjusted number of elements
+ new_profile_metadata = self._profile_metadata.adjust_nr_of_elements(
+ nr_of_elements
+ )
+
+ return ClusterPlanData(
+ device_plans=new_device_plans,
+ profile_metadata=new_profile_metadata,
+ _id=self._id,
+ reason=self._reason,
+ target=self._target,
+ active_target=self._active_target,
+ current_plan=(
+ self._current_plan[:nr_of_elements] if self._current_plan else None
+ ),
+ start=self._start,
+ step=self._step,
+ global_deviation_score=self._global_deviation_score,
+ constraint_violation_score=self._constraint_violation_score,
+ cp_datas=self._cp_datas,
+ )
+
+ @classmethod
+ def from_cluster_plan(
+ cls,
+ cluster_plan: "ClusterPlan",
+ active_target: Optional[ClusterTarget] = None,
+ active_plan: Optional[JouleProfile] = None,
+ ) -> "ClusterPlanData":
+ """Create a ClusterPlanData instance from a ClusterPlan.
+
+ Args:
+ cluster_plan: The cluster plan to create the data from
+ active_target: The active target for the cluster
+ active_plan: The active plan for the cluster
+
+ Returns:
+ A ClusterPlanData instance
+ """
+ congestion_points = {}
+
+ # Get the device plans from the cluster plan
+ cluster_plan_data = cluster_plan.get_plan_data()
+ if cluster_plan_data is None:
+ return None # type: ignore
+
+ # Process each device plan
+ for device_plan in cluster_plan_data.get_device_plans():
+ cp_id = cluster_plan.get_state().get_congestion_point(
+ device_plan.connection_id
+ )
+ if cp_id is None:
+ # The interface needs all devices to function properly. So for devices without congestion point, set a
+ # dummy congestion point so that those device plans go through.
+ cp_id = "No congestion point"
+
+ if cp_id not in congestion_points:
+ congestion_points[cp_id] = ClusterPlanData.CpData.empty(
+ cp_id, cluster_plan.get_plan_data().metadata
+ )
+
+ congestion_points[cp_id] = congestion_points[cp_id].add_der_plan(
+ device_plan.device_id, device_plan.get_profile()
+ )
+
+ # Create the current plan
+ if active_plan is None:
+ # Extract the plan from the cluster plan's JouleProfile
+ joule_profile = cluster_plan.get_joule_profile()
+ current_plan = [
+ element if element is not None else 0.0
+ for element in joule_profile.elements
+ ]
+ else:
+ # Use the active plan, adjusting it to the profile metadata
+ profile_start = cluster_plan.get_plan_data().metadata.profile_start
+ nr_of_timesteps = cluster_plan.get_plan_data().metadata.nr_of_timesteps
+
+ subprofile = active_plan.subprofile(profile_start)
+ adjusted_profile = subprofile.adjust_nr_of_elements(nr_of_timesteps)
+ current_plan = [
+ element if element is not None else 0.0
+ for element in adjusted_profile.elements
+ ]
+
+ # Get the profile metadata
+ profile_metadata = cluster_plan.get_plan_data().metadata
+
+ # Set up the active target
+ actual_active_target = (
+ active_target if active_target is not None else cluster_plan.get_target()
+ )
+
+ # Calculate timestamps
+ start_time = (
+ profile_metadata.profile_start.timestamp() * 1000
+ ) # Convert to milliseconds
+ timestep_duration = (
+ profile_metadata.timestep_duration.total_seconds() * 1000
+ ) # Convert to milliseconds
+
+ # Get scores, defaulting to 1.0 if they're NaN
+ global_deviation_score = cluster_plan.get_global_deviation_score()
+ global_deviation_score = (
+ 1.0 if global_deviation_score is None else global_deviation_score
+ )
+
+ constraint_violation_score = cluster_plan.get_constraint_violation_score()
+ constraint_violation_score = (
+ 1.0 if constraint_violation_score is None else constraint_violation_score
+ )
+
+ return cls(
+ device_plans=cluster_plan_data.get_device_plans(),
+ profile_metadata=profile_metadata,
+ _id=str(cluster_plan.get_id()),
+ reason=cluster_plan.get_reason(),
+ target=cluster_plan.get_target(),
+ active_target=actual_active_target,
+ current_plan=current_plan, # type: ignore[arg-type]
+ start=int(start_time),
+ step=int(timestep_duration),
+ global_deviation_score=global_deviation_score,
+ constraint_violation_score=constraint_violation_score,
+ cp_datas=congestion_points,
+ )
+
+
+def to_float_array(profile: JouleProfile) -> List[float]:
+ """Convert a JouleProfile to a list of floats.
+
+ Args:
+ profile: The profile to convert
+
+ Returns:
+ A list of floats
+ """
+ result = [0.0] * profile.metadata.nr_of_timesteps
+ for i, element in enumerate(profile.elements):
+ result[i] = 0.0 if element is None else float(element)
+ return result
+
+
+class ClusterPlan:
+ """Class representing a plan for a cluster."""
+
+ def __init__(
+ self,
+ state: ClusterState,
+ target: ClusterTarget,
+ plan_data: ClusterPlanData,
+ reason: str,
+ plan_due_by_date: datetime,
+ parent_plan: Optional["ClusterPlan"] = None,
+ _id: Optional[str] = None,
+ global_deviation_score: Optional[float] = None,
+ constraint_violation_score: Optional[float] = None,
+ activated_at: Optional[datetime] = None,
+ planned_energy: Optional[float] = None,
+ ):
+ self._state = state
+ self._target = target
+ self._plan_data = plan_data
+ self._reason = reason
+ self._plan_due_by_date = plan_due_by_date
+ self._parent_plan = parent_plan
+ self._id = _id or str(uuid.uuid4())
+ self._global_deviation_score = global_deviation_score
+ self._constraint_violation_score = constraint_violation_score
+ self._joule_profile = None # Will be initialized when needed
+ self._activated_at = activated_at
+ self._planned_energy = planned_energy # Lazy evaluation field
+
+ def get_state(self) -> ClusterState:
+ return self._state
+
+ def get_target(self) -> ClusterTarget:
+ return self._target
+
+ def get_plan_data(self) -> ClusterPlanData:
+ return self._plan_data
+
+ def get_reason(self) -> str:
+ return self._reason
+
+ def get_plan_due_by_date(self) -> datetime:
+ return self._plan_due_by_date
+
+ def get_parent_plan(self) -> Optional["ClusterPlan"]:
+ return self._parent_plan
+
+ def get_id(self) -> str:
+ return self._id
+
+ def get_activated_at(self) -> Optional[datetime]:
+ return self._activated_at
+
+ def get_target_energy(self) -> float:
+ """Get the target energy for this plan.
+
+ Returns:
+ The target energy
+ """
+ return self._target.get_target_energy()
+
+ def get_planned_energy(self) -> float:
+ """Get the planned energy for this plan.
+
+ Returns:
+ The planned energy
+ """
+ if self._planned_energy is None:
+ # Calculate the planned energy as the sum of all device plans
+ sum_energy = 0.0
+ for device_plan in self._plan_data.get_device_plans():
+ sum_energy += device_plan.get_profile().get_total_energy
+ self._planned_energy = sum_energy
+
+ return self._planned_energy
+
+ def get_global_deviation_score(self) -> Optional[float]:
+ """Calculate the score for the deviation between target and plan. Lower is better.
+
+ Returns:
+ The global deviation score, or None if not set
+ """
+ if self._global_deviation_score is None:
+ joule_target_segment = (
+ self._target.get_global_target_profile().target_elements_to_joule_profile()
+ )
+ if not joule_target_segment.elements:
+ # No joule target, so that's by default a perfect score!
+ return 0.0
+
+ target = joule_target_segment.elements
+ plan_segment = self.get_joule_profile().subprofile(
+ joule_target_segment.metadata.profile_start
+ )
+ plan = plan_segment.elements
+
+ sum_squared_distance = 0.0
+ for i in range(len(target)):
+ if target[i] is not None:
+ sum_squared_distance += abs(target[i] - plan[i])
+
+ if plan_segment.get_total_energy == 0.0:
+ self._global_deviation_score = 0.0
+ else:
+ self._global_deviation_score = (
+ sum_squared_distance / plan_segment.get_total_energy
+ )
+
+ return self._global_deviation_score
+
+ def get_constraint_violation_score(self) -> Optional[float]:
+ """Calculate the score for violation of constraint targets. Lower is better.
+
+ Returns:
+ The constraint violation score, or None if not set
+ """
+ if self._constraint_violation_score is None:
+ violation_sum = 0.0
+
+ for cp_id, cp_profile in self.get_profile_per_congestion_point().items():
+ plan = cp_profile.elements
+ congestion_point_target = self._target.get_congestion_point_target(
+ cp_id
+ )
+
+ if congestion_point_target is not None:
+ # If there is no target, we don't need to calculate it for the violation sum
+ target_elements = congestion_point_target.elements
+
+ for i in range(len(target_elements)):
+ element = target_elements[i]
+ max_joule = element.max_joule
+ min_joule = element.min_joule
+
+ if max_joule is not None and plan[i] > max_joule:
+ violation_sum += abs(plan[i] - max_joule)
+
+ if min_joule is not None and plan[i] < min_joule:
+ violation_sum += abs(min_joule - plan[i])
+
+ planned_energy = self.get_planned_energy()
+ if planned_energy == 0.0:
+ self._constraint_violation_score = 0.0
+ else:
+ self._constraint_violation_score = violation_sum / planned_energy
+
+ return self._constraint_violation_score
+
+ def has_constraint_violation(self) -> bool:
+ """Check if this plan has any constraint violations.
+
+ Returns:
+ True if there are constraint violations, False otherwise
+ """
+ for cp_id, cp_profile in self.get_profile_per_congestion_point().items():
+ plan = cp_profile.elements
+ congestion_point_target = self._target.get_congestion_point_target(cp_id)
+
+ if congestion_point_target is not None:
+ # If there is no target, we don't need to check for violations
+ target_elements = congestion_point_target.elements
+
+ for i in range(len(target_elements)):
+ element = target_elements[i]
+ max_joule = element.max_joule
+ min_joule = element.min_joule
+ plan_value = plan[i]
+
+ if max_joule is not None and plan_value > max_joule:
+ return True
+
+ if min_joule is not None and plan_value < min_joule:
+ return True
+
+ return False
+
+ def is_compatible(self, other: Any) -> bool:
+ """Check if this cluster plan is compatible with another profile.
+
+ Args:
+ other: The other profile to check compatibility with
+
+ Returns:
+ True if compatible, False otherwise
+ """
+ return self._plan_data.is_compatible(other)
+
+ def subprofile(self, new_start_date: datetime) -> "ClusterPlan":
+ """Create a subprofile starting at the specified date.
+
+ Args:
+ new_start_date: The new start date for the profile
+
+ Returns:
+ A new ClusterPlan instance with adjusted start date
+ """
+ return ClusterPlan(
+ state=self._state,
+ target=self._target.subprofile(new_start_date),
+ plan_data=self._plan_data.subprofile(new_start_date),
+ reason=self._reason,
+ plan_due_by_date=self._plan_due_by_date,
+ parent_plan=None,
+ _id=self._id,
+ activated_at=None,
+ )
+
+ def adjust_nr_of_elements(self, nr_of_elements: int) -> "ClusterPlan":
+ """Adjust the number of elements in the profile.
+
+ Args:
+ nr_of_elements: The new number of elements
+
+ Returns:
+ A new ClusterPlan instance with adjusted number of elements
+ """
+ return ClusterPlan(
+ state=self._state,
+ target=self._target.adjust_nr_of_elements(nr_of_elements),
+ plan_data=self._plan_data.adjust_nr_of_elements(nr_of_elements),
+ reason=self._reason,
+ plan_due_by_date=self._plan_due_by_date,
+ parent_plan=None,
+ _id=self._id,
+ activated_at=None,
+ )
+
+ @property
+ def metadata(self) -> Any:
+ """Get the profile metadata for this plan.
+
+ Returns:
+ The profile metadata
+ """
+ return self._target.metadata
+
+ def get_profile_per_congestion_point(self) -> Dict[str, JouleProfile]:
+ """Get the profile for each congestion point.
+
+ Returns:
+ A dictionary mapping congestion point IDs to profiles
+ """
+ result: Dict[str, JouleProfile] = {}
+
+ for device_plan in self._plan_data.get_device_plans():
+ cp_id = self._state.get_congestion_point(device_plan.connection_id)
+ profile = device_plan.get_profile()
+
+ if cp_id in result:
+ result[cp_id] = result[cp_id].add(profile)
+ elif cp_id is not None:
+ result[cp_id] = profile
+ else:
+ raise ValueError(
+ f"No congestion point found for device plan {device_plan.device_id}"
+ )
+
+ return result
+
+ def get_joule_profile(self) -> JouleProfile:
+ """Get the JouleProfile for this plan.
+
+ Returns:
+ The JouleProfile for this plan
+ """
+
+ # Add all device plans to the profile
+ sum_profile = JouleProfile(
+ profile_start=self.metadata.profile_start,
+ timestep_duration=self.metadata.timestep_duration,
+ profile_length=self.metadata.nr_of_timesteps,
+ value=0,
+ )
+ for device_plan in self._plan_data.get_device_plans():
+ sum_profile = sum_profile.add(device_plan.energy_profile)
+
+ return sum_profile
diff --git a/flexmeasures_s2/profile_steering/cluster_target.py b/flexmeasures_s2/profile_steering/cluster_target.py
new file mode 100644
index 0000000..0db49c3
--- /dev/null
+++ b/flexmeasures_s2/profile_steering/cluster_target.py
@@ -0,0 +1,241 @@
+from datetime import datetime
+from typing import Dict, Optional, Any, List
+import uuid
+
+# Common data types
+from flexmeasures_s2.profile_steering.common.joule_range_profile import (
+ JouleRangeProfile,
+)
+from flexmeasures_s2.profile_steering.common.target_profile import TargetProfile
+
+
+class ClusterTarget:
+ """
+ ClusterTarget instances are used by traders to express how a plan should look like.
+
+ There are two levels at which a target can be set:
+ 1. Global level (i.e., a target for the cluster as a whole)
+ 2. Congestion point level
+
+ The global target is a TargetProfile (i.e., a line) and the congestion point targets
+ are JouleRangeProfile instances, allowing congestion targets to be set with
+ minimum and maximum constraints.
+ """
+
+ def __init__(
+ self,
+ generated_at: datetime,
+ parent_id: Any,
+ generated_by: Any,
+ global_target_profile: TargetProfile,
+ congestion_point_targets: Optional[Dict[str, JouleRangeProfile]] = None,
+ ):
+ """
+ Initialize a ClusterTarget instance.
+
+ Args:
+ generated_at: When the target was generated
+ parent_id: The ID of the parent target
+ generated_by: The entity that generated the target
+ global_target_profile: The global target profile for the cluster
+ congestion_point_targets: Targets for specific congestion points
+ """
+ self._id = str(uuid.uuid4())
+ self._generated_at = generated_at
+ self._parent_id = parent_id
+ self._generated_by = generated_by
+ self._global_target_profile = global_target_profile
+ self._congestion_point_targets = congestion_point_targets or {}
+
+ # Validate
+ if global_target_profile is None:
+ raise ValueError("global_target_profile cannot be None")
+
+ if congestion_point_targets is not None:
+ for cp_id, cp_target in congestion_point_targets.items():
+ if not cp_target.is_compatible(global_target_profile):
+ raise ValueError(
+ f"Congestion point target {cp_id} is not compatible with the global target profile. "
+ f"Expected global metadata: {global_target_profile.metadata}, "
+ f"but congestion profile had {cp_target.metadata}"
+ )
+
+ def get_id(self) -> str:
+ """
+ Get the ID of this target.
+
+ Returns:
+ The target ID
+ """
+ return self._id
+
+ def get_generated_at(self) -> datetime:
+ """
+ Get when this target was generated.
+ Returns:
+ The generation timestamp
+ """
+ return self._generated_at
+
+ def get_parent_id(self) -> Any:
+ """
+ Get the parent ID of this target.
+
+ Returns:
+ The parent ID
+ """
+ return self._parent_id
+
+ def get_generated_by(self) -> Any:
+ """
+ Get the entity that generated this target.
+
+ Returns:
+ The generator entity
+ """
+ return self._generated_by
+
+ def get_global_target_profile(self) -> TargetProfile:
+ """
+ Get the global target profile.
+
+ Returns:
+ The global target profile
+ """
+ return self._global_target_profile
+
+ def get_congestion_point_targets(self) -> Dict[str, JouleRangeProfile]:
+ """
+ Get all congestion point targets.
+
+ Returns:
+ A dictionary mapping congestion point IDs to their targets
+ """
+ return self._congestion_point_targets
+
+ def get_congestion_point_target(
+ self, congestion_point_id: str
+ ) -> Optional[JouleRangeProfile]:
+ """
+ Get the target for a specific congestion point.
+
+ Args:
+ congestion_point_id: The ID of the congestion point
+
+ Returns:
+ The target for the congestion point, or None if not found
+ """
+ return self._congestion_point_targets.get(congestion_point_id)
+
+ @property
+ def metadata(self) -> Any:
+ """
+ Get the profile metadata.
+
+ Returns:
+ The profile metadata
+ """
+ return self._global_target_profile.metadata
+
+ def get_target_energy(self) -> float:
+ """
+ Get the total target energy.
+
+ Returns:
+ The total energy
+ """
+ return self._global_target_profile.get_total_energy()
+
+ def is_compatible(self, other: Any) -> bool:
+ """
+ Check if this target is compatible with another profile.
+
+ Args:
+ other: The other profile to check compatibility with
+
+ Returns:
+ True if compatible, False otherwise
+ """
+ return self._global_target_profile.is_compatible(other)
+
+ def subprofile(self, new_start_date: datetime) -> "ClusterTarget":
+ """
+ Create a subprofile starting at the specified date.
+
+ Args:
+ new_start_date: The new start date for the profile
+
+ Returns:
+ A new ClusterTarget instance with adjusted start date
+ """
+ plans = {}
+ for cp_id, cp_target in self._congestion_point_targets.items():
+ plans[cp_id] = cp_target.subprofile(new_start_date)
+
+ return ClusterTarget(
+ self._generated_at,
+ self._parent_id,
+ self._generated_by,
+ self._global_target_profile.subprofile(new_start_date),
+ plans,
+ )
+
+ def adjust_nr_of_elements(self, nr_of_elements: int) -> "ClusterTarget":
+ """
+ Adjust the number of elements in the profile.
+
+ Args:
+ nr_of_elements: The new number of elements
+
+ Returns:
+ A new ClusterTarget instance with adjusted number of elements
+ """
+ plans = {}
+ for cp_id, cp_target in self._congestion_point_targets.items():
+ plans[cp_id] = cp_target.adjust_nr_of_elements(nr_of_elements)
+
+ return ClusterTarget(
+ self._generated_at,
+ self._parent_id,
+ self._generated_by,
+ self._global_target_profile.adjust_nr_of_elements(nr_of_elements),
+ plans,
+ )
+
+ def set_congestion_point_target(
+ self,
+ congestion_point_id: str,
+ congestion_point_target: JouleRangeProfile,
+ elements: Optional[List[Any]] = None,
+ ):
+ """
+ Set a target for a specific congestion point.
+
+ Args:
+ congestion_point_id: The ID of the congestion point
+ congestion_point_target: The target for the congestion point
+ elements: Optional elements to set in the target profile
+ """
+ if not congestion_point_target.is_compatible(self._global_target_profile):
+ raise ValueError(
+ f"Congestion point target {congestion_point_id} is not compatible with the global target profile"
+ )
+
+ self._congestion_point_targets[congestion_point_id] = congestion_point_target
+
+ if elements is not None:
+ self._congestion_point_targets[congestion_point_id].elements = elements
+
+ def contains_energy_target(self) -> bool:
+ """
+ Check if this cluster target contains any energy targets (JouleElement instances).
+
+ Returns:
+ True if there are any JouleElement instances in the global target profile, False otherwise
+ """
+ from flexmeasures_s2.profile_steering.common.target_profile import TargetProfile
+
+ for element in self._global_target_profile.elements:
+ if isinstance(element, TargetProfile.JouleElement):
+ return True
+ return False
diff --git a/flexmeasures_s2/profile_steering/common/__init__.py b/flexmeasures_s2/profile_steering/common/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/flexmeasures_s2/profile_steering/common/abstract_profile.py b/flexmeasures_s2/profile_steering/common/abstract_profile.py
new file mode 100644
index 0000000..2eb1f28
--- /dev/null
+++ b/flexmeasures_s2/profile_steering/common/abstract_profile.py
@@ -0,0 +1,157 @@
+from abc import ABC, abstractmethod
+from typing import List, TypeVar, Generic, Optional
+from datetime import datetime, timedelta
+from flexmeasures_s2.profile_steering.common.profile_metadata import ProfileMetadata
+
+E = TypeVar("E")
+PT = TypeVar("PT", bound="AbstractProfile")
+
+
+class AbstractProfile(ABC, Generic[E, PT]):
+ def __init__(
+ self,
+ profile_metadata: Optional[ProfileMetadata] = None,
+ elements: Optional[List[E]] = None,
+ profile_start: Optional[datetime] = None,
+ timestep_duration: Optional[timedelta] = None,
+ value: Optional[E] = None,
+ nr_of_elements: Optional[int] = None,
+ ):
+ """
+ Initialize an AbstractProfile with various parameter combinations.
+
+ Args:
+ profile_metadata: ProfileMetadata object containing start time and duration
+ elements: List of profile elements
+ profile_start: Start time of the profile
+ timestep_duration: Duration of each timestep
+ value: Single value to fill the entire profile
+ nr_of_elements: Number of elements when using a single value
+ """
+ # Case 1: Initialize with metadata and elements
+ if profile_metadata is not None and elements is not None:
+ self.metadata = profile_metadata
+ self.elements = elements
+ self.validate(profile_metadata, elements)
+ return
+
+ # Case 2: Initialize with start time, duration and elements
+ if (
+ profile_start is not None
+ and timestep_duration is not None
+ and elements is not None
+ ):
+ self.metadata = ProfileMetadata(
+ profile_start, timestep_duration, len(elements)
+ )
+ self.elements = elements
+ self.validate(self.metadata, elements)
+ return
+
+ # Case 3: Initialize with start time, duration, single value and number of elements
+ if (
+ profile_start is not None
+ and timestep_duration is not None
+ and value is not None
+ and nr_of_elements is not None
+ ):
+ self.elements = [value] * nr_of_elements
+ self.metadata = ProfileMetadata(
+ profile_start, timestep_duration, nr_of_elements
+ )
+ self.validate(self.metadata, self.elements)
+ return
+
+ # Case 4: Empty initialization (for serialization)
+ self.metadata = ProfileMetadata(datetime.now(), timedelta(hours=1), 0)
+ self.elements = []
+
+ @abstractmethod
+ def validate(self, profile_metadata: ProfileMetadata, elements: List[E]):
+ if elements is None:
+ raise ValueError("elements cannot be null")
+ if (
+ 24 * 60 * 60 * 1000
+ ) % profile_metadata.timestep_duration.total_seconds() * 1000 != 0:
+ raise ValueError("A day should be dividable by the timeStepDuration")
+ if (
+ not self.start_of_current_aligned_date(
+ profile_metadata.profile_start,
+ profile_metadata.timestep_duration,
+ )
+ == profile_metadata.profile_start
+ ):
+ raise ValueError(
+ "The startTimeDuration should be aligned with the timeStepDuration"
+ )
+
+ @property
+ def get_elements(self) -> List[E]:
+ return self.elements
+
+ def get_element_end_date_at(self, index: int) -> datetime:
+ return self.metadata.get_profile_start_at_timestep(index + 1)
+
+ def index_at(self, date: datetime) -> int:
+ return self.metadata.get_starting_step_nr(date)
+
+ def element_at(self, date: datetime) -> E:
+ index = self.index_at(date)
+ if index >= 0:
+ return self.elements[index]
+ raise ValueError(f"No element found at date - index {index} is invalid")
+
+ @staticmethod
+ def next_aligned_date(date: datetime, time_step_duration: timedelta) -> datetime:
+ time_step_duration_ms = time_step_duration.total_seconds() * 1000
+ ms_since_last_aligned_date = (date.timestamp() * 1000) % time_step_duration_ms
+
+ if ms_since_last_aligned_date == 0:
+ return date
+ else:
+ return date + timedelta(
+ milliseconds=(time_step_duration_ms - ms_since_last_aligned_date)
+ )
+
+ @staticmethod
+ def start_of_current_aligned_date(
+ date: datetime, time_step_duration: timedelta
+ ) -> datetime:
+ ms_since_last_aligned_date = (
+ (date.timestamp() * 1000) % time_step_duration.total_seconds() * 1000
+ )
+ if ms_since_last_aligned_date == 0:
+ return date
+ else:
+ return datetime.fromtimestamp(
+ date.timestamp() - ms_since_last_aligned_date / 1000
+ )
+
+ @staticmethod
+ def end_of_current_aligned_date(
+ date: datetime, time_step_duration: timedelta
+ ) -> datetime:
+ return (
+ AbstractProfile.start_of_current_aligned_date(date, time_step_duration)
+ + time_step_duration
+ )
+
+ @staticmethod
+ def get_start_of_day(date: datetime) -> datetime:
+ return datetime.combine(date.date(), datetime.min.time())
+
+ @abstractmethod
+ def subprofile(self, new_start_date: datetime) -> PT:
+ pass
+
+ @abstractmethod
+ def adjust_nr_of_elements(self, nr_of_elements: int) -> PT:
+ pass
+
+ @abstractmethod
+ def is_compatible(self, other: PT) -> bool:
+ return self.metadata == other.metadata
+
+ @abstractmethod
+ def default_value(self) -> E:
+ pass
diff --git a/flexmeasures_s2/profile_steering/common/device_plan.py b/flexmeasures_s2/profile_steering/common/device_plan.py
new file mode 100644
index 0000000..6082845
--- /dev/null
+++ b/flexmeasures_s2/profile_steering/common/device_plan.py
@@ -0,0 +1,23 @@
+from typing import Optional, Union
+from flexmeasures_s2.profile_steering.common.pydantic_base import FlexMeasuresBaseModel
+
+from flexmeasures_s2.profile_steering.common.joule_profile import JouleProfile
+from flexmeasures_s2.profile_steering.common.soc_profile import SoCProfile
+from flexmeasures_s2.profile_steering.device_planner.frbc.s2_frbc_instruction_profile import (
+ S2FrbcInstructionProfile,
+)
+from flexmeasures_s2.profile_steering.device_planner.ddbc.s2_ddbc_instruction_profile import (
+ S2DdbcInstructionProfile,
+)
+
+
+class DevicePlan(FlexMeasuresBaseModel):
+
+ device_id: str
+ device_name: str
+ connection_id: str
+ energy_profile: JouleProfile
+ fill_level_profile: Optional[SoCProfile] = None
+ instruction_profile: Optional[
+ Union[S2FrbcInstructionProfile, S2DdbcInstructionProfile]
+ ] = None
diff --git a/flexmeasures_s2/profile_steering/common/joule_profile.py b/flexmeasures_s2/profile_steering/common/joule_profile.py
new file mode 100644
index 0000000..b51d501
--- /dev/null
+++ b/flexmeasures_s2/profile_steering/common/joule_profile.py
@@ -0,0 +1,220 @@
+from datetime import datetime, timedelta
+from typing import List, Optional
+from flexmeasures_s2.profile_steering.common.abstract_profile import AbstractProfile
+from flexmeasures_s2.profile_steering.common.profile_metadata import ProfileMetadata
+
+# Developer Note: Type ignores in this module
+# =============================================
+# JouleProfile uses type ignores in super().__init__() and other calls because it accepts
+# List[Optional[int]] for flexible initialization (allowing None values), while
+# AbstractProfile expects List[int]. This design choice allows for more flexible
+# profile creation where missing values can be specified as None and handled
+# appropriately during processing.
+
+
+class JouleProfile(AbstractProfile[int, "JouleProfile"]):
+ def __init__(
+ self,
+ profile_start: Optional[datetime] = None,
+ timestep_duration: Optional[timedelta] = None,
+ elements: Optional[List[Optional[int]]] = None,
+ metadata: Optional[ProfileMetadata] = None,
+ value: Optional[int] = None,
+ profile_length: Optional[int] = None,
+ other_profile: Optional["JouleProfile"] = None,
+ ):
+ """
+ Initialize a JouleProfile with various parameter combinations.
+
+ Args:
+ profile_start: Start time of the profile
+ timestep_duration: Duration of each timestep
+ elements: List of energy values
+ metadata: ProfileMetadata object containing start time and duration
+ value: Single value to fill the entire profile
+ profile_length: Length of the profile when using a single value
+ other_profile: Another JouleProfile to copy from
+ """
+ # Case 1: Copy from another profile
+ if other_profile is not None:
+ super().__init__(
+ profile_metadata=other_profile.metadata,
+ elements=other_profile.elements,
+ )
+ return
+
+ # Case 2: Initialize from metadata
+ if metadata is not None:
+ if elements is not None:
+ super().__init__(profile_metadata=metadata, elements=elements) # type: ignore[arg-type]
+ elif value is not None:
+ elements = [value] * metadata.nr_of_timesteps
+ super().__init__(profile_metadata=metadata, elements=elements) # type: ignore[arg-type]
+ else:
+ super().__init__(profile_metadata=metadata, elements=[]) # type: ignore[arg-type]
+ return
+
+ # Case 3: Initialize with single value and profile length
+ if value is not None and profile_length is not None:
+ elements = [value] * profile_length
+ super().__init__(
+ profile_start=profile_start,
+ timestep_duration=timestep_duration,
+ elements=elements, # type: ignore[arg-type]
+ )
+ return
+
+ # Case 4: Basic initialization
+ if profile_start is not None and timestep_duration is not None:
+ super().__init__(
+ profile_start=profile_start,
+ timestep_duration=timestep_duration,
+ elements=elements if elements is not None else [], # type: ignore[arg-type]
+ )
+ return
+
+ # Case 5: Empty initialization (for serialization)
+ super().__init__()
+
+ def validate(self, profile_metadata: ProfileMetadata, elements: List[int]):
+ super().validate(profile_metadata, elements)
+ # Add any JouleProfile-specific validation here if needed
+
+ def default_value(self) -> None: # type: ignore
+ return None
+
+ def subprofile(self, new_start_date: datetime) -> "JouleProfile":
+ index = self.index_at(new_start_date)
+ if index < 0:
+ raise ValueError("New start date is outside profile range")
+ new_elements = self.elements[index:]
+ return JouleProfile(
+ new_start_date,
+ self.metadata.timestep_duration,
+ new_elements, # type: ignore[arg-type]
+ )
+
+ def adjust_nr_of_elements(self, nr_of_elements: int) -> "JouleProfile":
+ if nr_of_elements < len(self.elements):
+ new_elements = self.elements[:nr_of_elements]
+ else:
+ new_elements = self.elements + [0] * (nr_of_elements - len(self.elements))
+ return JouleProfile(
+ self.metadata.profile_start,
+ self.metadata.timestep_duration,
+ new_elements, # type: ignore[arg-type]
+ )
+
+ def is_compatible(self, other: AbstractProfile) -> bool:
+ return self.metadata == other.metadata
+
+ def avg_power_at(self, date: datetime) -> Optional[float]:
+ element = self.element_at(date)
+ return element / self.metadata.timestep_duration.total_seconds()
+
+ def add(self, other: "JouleProfile") -> "JouleProfile":
+ if not self.is_compatible(other):
+ raise ValueError("Profiles are not compatible")
+ summed_elements = [0] * len(self.elements)
+ for i in range(len(self.elements)):
+ if self.elements[i] is None or other.elements[i] is None:
+ # mypy complains that the return is None
+ summed_elements[i] = self.default_value() # type: ignore
+ else:
+ summed_elements[i] = self.elements[i] + other.elements[i]
+ return JouleProfile(
+ self.metadata.profile_start,
+ self.metadata.timestep_duration,
+ summed_elements, # type: ignore[arg-type]
+ )
+
+ def subtract(self, other: "JouleProfile") -> "JouleProfile":
+ if not self.is_compatible(other):
+ raise ValueError("Profiles are not compatible")
+ diff_elements = [0] * len(self.elements)
+ for i in range(len(self.elements)):
+ if self.elements[i] is None or other.elements[i] is None:
+ # mypy complains that the return is None
+ diff_elements[i] = self.default_value() # type: ignore
+ else:
+ diff_elements[i] = self.elements[i] - other.elements[i]
+ return JouleProfile(
+ self.metadata.profile_start,
+ self.metadata.timestep_duration,
+ diff_elements, # type: ignore[arg-type]
+ )
+
+ def absolute_values(self) -> "JouleProfile":
+ abs_elements = [abs(e) for e in self.elements]
+ return JouleProfile(
+ self.metadata.profile_start,
+ self.metadata.timestep_duration,
+ abs_elements, # type: ignore[arg-type]
+ )
+
+ def sum_quadratic_distance(self) -> float:
+ return sum(e**2 for e in self.elements if e is not None)
+
+ def is_below_or_equal(self, other: "JouleProfile") -> bool:
+ if not self.is_compatible(other):
+ raise ValueError("Profiles are not compatible")
+ return all(
+ a <= b for a, b in zip(self.elements, other.elements) if b is not None
+ )
+
+ def is_above_or_equal(self, other: "JouleProfile") -> bool:
+ if not self.is_compatible(other):
+ raise ValueError("Profiles are not compatible")
+ return all(
+ a >= b for a, b in zip(self.elements, other.elements) if b is not None
+ )
+
+ def minimum(self, other: "JouleProfile") -> "JouleProfile":
+ if not self.is_compatible(other):
+ raise ValueError("Profiles are not compatible")
+ # skip None values
+ min_elements = [
+ min(a, b)
+ for a, b in zip(self.elements, other.elements)
+ if a is not None and b is not None
+ ]
+ return JouleProfile(
+ self.metadata.profile_start,
+ self.metadata.timestep_duration,
+ min_elements, # type: ignore[arg-type]
+ )
+
+ def maximum(self, other: "JouleProfile") -> "JouleProfile":
+ if not self.is_compatible(other):
+ raise ValueError("Profiles are not compatible")
+ # skip None values
+ max_elements = [
+ max(a, b)
+ for a, b in zip(self.elements, other.elements)
+ if a is not None and b is not None
+ ]
+ return JouleProfile(
+ self.metadata.profile_start,
+ self.metadata.timestep_duration,
+ max_elements, # type: ignore[arg-type]
+ )
+
+ @property
+ def get_total_energy(self) -> int:
+ return sum(self.elements)
+
+ @property
+ def get_total_energy_production(self) -> int:
+ return sum(min(0, e) for e in self.elements)
+
+ @property
+ def get_total_energy_consumption(self) -> int:
+ return sum(max(0, e) for e in self.elements)
+
+ def get_energy_for_timestep(self, index: int) -> Optional[int]:
+ if 0 <= index < len(self.elements):
+ return self.elements[index]
+ return None
+
+ def __str__(self) -> str:
+ return f"JouleProfile(elements={self.elements}, profile_start={self.metadata.profile_start}, timestep_duration={self.metadata.timestep_duration})"
diff --git a/flexmeasures_s2/profile_steering/common/joule_range_profile.py b/flexmeasures_s2/profile_steering/common/joule_range_profile.py
new file mode 100644
index 0000000..fe39323
--- /dev/null
+++ b/flexmeasures_s2/profile_steering/common/joule_range_profile.py
@@ -0,0 +1,259 @@
+from dataclasses import dataclass
+from datetime import datetime, timedelta
+from typing import ClassVar, List, Optional, Union
+
+from flexmeasures_s2.profile_steering.common.abstract_profile import AbstractProfile
+from flexmeasures_s2.profile_steering.common.profile_metadata import ProfileMetadata
+from flexmeasures_s2.profile_steering.common.joule_profile import JouleProfile
+
+
+@dataclass
+class Element:
+ """
+ Represents an element in a JouleRangeProfile with min and max joule values.
+ Both values can be None, representing unbounded values.
+ """
+
+ min_joule: Optional[int] = None
+ max_joule: Optional[int] = None
+
+ # Class constant for NULL element
+ NULL: ClassVar[Optional["Element"]] = None # Will be set after class definition
+
+ def __eq__(self, other):
+ if not isinstance(other, Element):
+ return False
+ return self.min_joule == other.min_joule and self.max_joule == other.max_joule
+
+ def __str__(self):
+ return f"Element(min_joule={self.min_joule}, max_joule={self.max_joule})"
+
+
+# Set the NULL element after class definition
+Element.NULL = Element(None, None)
+
+
+class JouleRangeProfile(AbstractProfile[Element, "JouleRangeProfile"]):
+ """
+ A profile containing elements with minimum and maximum joule values.
+ This is the Python implementation of the Java JouleRangeProfile class.
+ """
+
+ def __init__(
+ self,
+ profile_start: Union[datetime, ProfileMetadata],
+ timestep_duration: Optional[timedelta] = None,
+ elements: Optional[List[Element]] = None,
+ min_value: Optional[int] = None,
+ max_value: Optional[int] = None,
+ nr_of_timesteps: Optional[int] = None,
+ ):
+ """
+ Initialize a JouleRangeProfile with various constructor options.
+
+ Args:
+ profile_start: Either a datetime representing profile start or a ProfileMetadata
+ timestep_duration: Duration of each timestep (if profile_start is datetime)
+ elements: List of Element objects with min/max joule values
+ min_value: Optional default min value for all elements
+ max_value: Optional default max value for all elements
+ nr_of_timesteps: Number of timesteps for the profile
+ """
+ if isinstance(profile_start, ProfileMetadata):
+ metadata = profile_start
+ if nr_of_timesteps is None:
+ nr_of_timesteps = metadata.nr_of_timesteps
+
+ elements = self._create_element_array(nr_of_timesteps, min_value, max_value)
+
+ else:
+ if elements is not None:
+ if timestep_duration is None:
+ raise ValueError(
+ "Timestep duration is required when elements are provided"
+ )
+ metadata = ProfileMetadata(
+ profile_start, timestep_duration, len(elements)
+ )
+ elif nr_of_timesteps is not None:
+ if timestep_duration is None:
+ raise ValueError(
+ "Timestep duration is required when nr_of_timesteps is provided"
+ )
+ metadata = ProfileMetadata(
+ profile_start, timestep_duration, nr_of_timesteps
+ )
+ elements = self._create_element_array(
+ nr_of_timesteps, min_value, max_value
+ )
+ else:
+ if timestep_duration is None:
+ raise ValueError(
+ "Timestep duration is required when no elements or nr_of_timesteps are provided"
+ )
+ metadata = ProfileMetadata(profile_start, timestep_duration, 0)
+ elements = []
+
+ super().__init__(metadata, elements)
+
+ @staticmethod
+ def _create_element_array(
+ nr_of_elements: int, min_value: Optional[int], max_value: Optional[int]
+ ) -> List[Element]:
+ """Create an array of elements with the given min and max values."""
+ return [Element(min_value, max_value) for _ in range(nr_of_elements)]
+
+ def default_value(self) -> Element:
+ return Element(None, None)
+
+ def validate(self, profile_metadata: ProfileMetadata, elements: List[Element]):
+ """Validate the elements and metadata for this profile."""
+ super().validate(profile_metadata, elements)
+ # Add any JouleRangeProfile-specific validation here if needed
+
+ def is_within_range(self, other: JouleProfile) -> bool:
+ """
+ Check if the given JouleProfile is within the range defined by this profile.
+
+ Args:
+ other: JouleProfile to check
+
+ Returns:
+ True if other is within the range, False otherwise
+ """
+ if not self.is_compatible(other):
+ raise ValueError("Profiles are not compatible")
+
+ for i in range(self.metadata.nr_of_timesteps):
+ element = self.elements[i]
+ other_value = other.get_energy_for_timestep(i)
+
+ if (
+ element.min_joule is not None
+ and other_value is not None
+ and other_value < element.min_joule
+ ):
+ return False
+
+ if (
+ element.max_joule is not None
+ and other_value is not None
+ and other_value > element.max_joule
+ ):
+ return False
+
+ return True
+
+ def difference_with_max_value(self, other: JouleProfile) -> JouleProfile:
+ """
+ Calculate the difference between this profile's max values and the other profile.
+
+ Args:
+ other: JouleProfile to compare against
+
+ Returns:
+ JouleProfile containing the differences
+ """
+ if not self.is_compatible(other):
+ raise ValueError("Profiles are not compatible")
+
+ return_values: List[Optional[int]] = []
+
+ for i in range(self.metadata.nr_of_timesteps):
+ other_energy = other.get_energy_for_timestep(i)
+ element_max_joule = self.elements[i].max_joule
+ if element_max_joule is None or other_energy is None:
+ return_values.append(None)
+ else:
+ return_values.append(element_max_joule - other_energy)
+
+ return JouleProfile(metadata=self.metadata, elements=return_values)
+
+ def difference_with_min_value(self, other: JouleProfile) -> JouleProfile:
+ """
+ Calculate the difference between this profile's min values and the other profile.
+
+ Args:
+ other: JouleProfile to compare against
+
+ Returns:
+ JouleProfile containing the differences
+ """
+ if not self.is_compatible(other):
+ raise ValueError("Profiles are not compatible")
+
+ return_values: List[Optional[int]] = []
+
+ for i in range(self.metadata.nr_of_timesteps):
+ element_min_joule = self.elements[i].min_joule
+ other_energy = other.get_energy_for_timestep(i)
+ if element_min_joule is None or other_energy is None:
+ return_values.append(None)
+ else:
+ return_values.append(element_min_joule - other_energy)
+
+ return JouleProfile(metadata=self.metadata, elements=return_values)
+
+ def subprofile(self, new_start_date: datetime) -> "JouleRangeProfile":
+ """
+ Create a subprofile starting from the given date.
+
+ Args:
+ new_start_date: Start date for the new profile
+
+ Returns:
+ New JouleRangeProfile starting from the given date
+ """
+ index = self.index_at(new_start_date)
+ if index < 0:
+ raise ValueError("New start date is outside profile range")
+
+ new_elements = self.elements[index:]
+ return JouleRangeProfile(
+ new_start_date, self.metadata.timestep_duration, new_elements
+ )
+
+ def adjust_nr_of_elements(self, nr_of_elements: int) -> "JouleRangeProfile":
+ """
+ Adjust the number of elements in this profile.
+
+ Args:
+ nr_of_elements: New number of elements
+
+ Returns:
+ New JouleRangeProfile with adjusted number of elements
+ """
+ if nr_of_elements < len(self.elements):
+ new_elements = self.elements[:nr_of_elements]
+ else:
+ # Ignore type error because Element.NULL is not None
+ new_elements = self.elements + [Element.NULL] * ( # type: ignore[list-item]
+ nr_of_elements - len(self.elements)
+ )
+
+ return JouleRangeProfile(
+ self.metadata.profile_start,
+ self.metadata.timestep_duration,
+ new_elements,
+ )
+
+ def is_compatible(self, other: AbstractProfile) -> bool:
+ """
+ Check if this profile is compatible with another profile.
+
+ Args:
+ other: Another profile to check compatibility with
+
+ Returns:
+ True if compatible, False otherwise
+
+ """
+ return self.metadata == other.metadata
+
+ def __str__(self) -> str:
+ """Return a string representation of this profile."""
+ return (
+ f"JouleRangeProfile(elements={self.elements}, "
+ f"profile_start={self.metadata.profile_start}, "
+ f"timestep_duration={self.metadata.timestep_duration})"
+ )
diff --git a/flexmeasures_s2/profile_steering/common/power_range_wrapper.py b/flexmeasures_s2/profile_steering/common/power_range_wrapper.py
new file mode 100644
index 0000000..97b7cfe
--- /dev/null
+++ b/flexmeasures_s2/profile_steering/common/power_range_wrapper.py
@@ -0,0 +1,46 @@
+import math
+from typing import Any
+
+
+class PowerRangeWrapper:
+ def __init__(self, unwrapped_range: Any):
+ self.start_of_range: float = float(unwrapped_range.start_of_range)
+ self.end_of_range: float = float(unwrapped_range.end_of_range)
+ self.commodity_quantity: Any = unwrapped_range.commodity_quantity
+
+ def get_start_of_range(self) -> float:
+ return self.start_of_range
+
+ def get_end_of_range(self) -> float:
+ return self.end_of_range
+
+ def get_commodity_quantity(self) -> Any:
+ return self.commodity_quantity
+
+ def get_power(self, factor: float) -> float:
+ return (
+ (self.end_of_range - self.start_of_range) * factor
+ ) + self.start_of_range
+
+ def __eq__(self, o: object) -> bool:
+ if self is o:
+ return True
+ if not isinstance(o, PowerRangeWrapper):
+ return False
+ that = o
+ return (
+ math.isclose(that.start_of_range, self.start_of_range)
+ and math.isclose(that.end_of_range, self.end_of_range)
+ and self.commodity_quantity == that.commodity_quantity
+ )
+
+ def __hash__(self) -> int:
+ return hash((self.start_of_range, self.end_of_range, self.commodity_quantity))
+
+ def __str__(self) -> str:
+ return (
+ f"PowerRangeWrapper("
+ f"startOfRange={self.start_of_range}, "
+ f"endOfRange={self.end_of_range}, "
+ f"commodityQuantity={self.commodity_quantity})"
+ )
diff --git a/flexmeasures_s2/profile_steering/common/profile_metadata.py b/flexmeasures_s2/profile_steering/common/profile_metadata.py
new file mode 100644
index 0000000..f0e7d54
--- /dev/null
+++ b/flexmeasures_s2/profile_steering/common/profile_metadata.py
@@ -0,0 +1,106 @@
+from datetime import datetime, timedelta
+
+
+class ProfileMetadata:
+ NR_OF_TIMESTEPS_KEY = "nrOfTimesteps"
+ TIMESTEP_DURATION_KEY = "timestepDurationMs"
+ PROFILE_START_KEY = "profileStart"
+
+ def __init__(
+ self,
+ profile_start: datetime,
+ timestep_duration: timedelta,
+ nr_of_timesteps: int,
+ ):
+ self.profile_start = profile_start
+ self.timestep_duration = timestep_duration
+ self.nr_of_timesteps = nr_of_timesteps
+ self.profile_end = profile_start + timestep_duration * nr_of_timesteps
+ self.profile_duration = self.profile_end - self.profile_start
+
+ def get_profile_start_at_timestep(self, i: int) -> datetime:
+ if i >= self.nr_of_timesteps or i < 0:
+ raise ValueError(
+ f"Expected i to be between 0 <= i < {self.nr_of_timesteps} but was {i}"
+ )
+ return self.profile_start + self.timestep_duration * i
+
+ def get_starting_step_nr(self, instant: datetime) -> int:
+ if instant < self.profile_start:
+ return -1
+ elif instant >= self.profile_end:
+ return self.nr_of_timesteps
+ else:
+ duration_between_secs = (instant - self.profile_start).total_seconds()
+ timestep_duration_secs = self.timestep_duration.total_seconds()
+ return int(duration_between_secs // timestep_duration_secs)
+
+ def is_aligned_with(self, other: "ProfileMetadata") -> bool:
+ return (
+ self.timestep_duration == other.timestep_duration
+ and (
+ abs((self.profile_start - other.profile_start).total_seconds())
+ % self.timestep_duration.total_seconds()
+ )
+ == 0
+ )
+
+ def to_dict(self) -> dict:
+ return {
+ self.PROFILE_START_KEY: int(self.profile_start.timestamp() * 1000),
+ self.TIMESTEP_DURATION_KEY: int(
+ self.timestep_duration.total_seconds() * 1000
+ ),
+ self.NR_OF_TIMESTEPS_KEY: self.nr_of_timesteps,
+ }
+
+ @staticmethod
+ def from_dict(data: dict) -> "ProfileMetadata":
+ profile_start = datetime.fromtimestamp(
+ data[ProfileMetadata.PROFILE_START_KEY] / 1000
+ )
+ timestep_duration = timedelta(
+ milliseconds=data[ProfileMetadata.TIMESTEP_DURATION_KEY]
+ )
+ nr_of_timesteps = data[ProfileMetadata.NR_OF_TIMESTEPS_KEY]
+ return ProfileMetadata(profile_start, timestep_duration, nr_of_timesteps)
+
+ def subprofile(self, new_start_date: datetime) -> "ProfileMetadata":
+ if new_start_date < self.profile_start:
+ raise ValueError(
+ "The new start date should be after the current start date"
+ )
+ new_start_date = self.next_aligned_date(new_start_date, self.timestep_duration)
+ skipped_steps = int(
+ (new_start_date - self.profile_start).total_seconds()
+ // self.timestep_duration.total_seconds()
+ )
+ nr_of_elements = max(0, self.nr_of_timesteps - skipped_steps)
+ return ProfileMetadata(new_start_date, self.timestep_duration, nr_of_elements)
+
+ def adjust_nr_of_elements(self, nr_of_elements: int) -> "ProfileMetadata":
+ return ProfileMetadata(
+ self.profile_start, self.timestep_duration, nr_of_elements
+ )
+
+ @staticmethod
+ def next_aligned_date(date: datetime, timestep_duration: timedelta) -> datetime:
+ # Align the date to the next timestep
+ remainder = (
+ date - datetime.min
+ ).total_seconds() % timestep_duration.total_seconds()
+ if remainder == 0:
+ return date
+ return date + timedelta(seconds=(timestep_duration.total_seconds() - remainder))
+
+ def __eq__(self, other: object) -> bool:
+ if not isinstance(other, ProfileMetadata):
+ return False
+ return (
+ self.nr_of_timesteps == other.nr_of_timesteps
+ and self.profile_start == other.profile_start
+ and self.timestep_duration == other.timestep_duration
+ )
+
+ def __hash__(self) -> int:
+ return hash((self.nr_of_timesteps, self.profile_start, self.timestep_duration))
diff --git a/flexmeasures_s2/profile_steering/common/proposal.py b/flexmeasures_s2/profile_steering/common/proposal.py
new file mode 100644
index 0000000..066d20c
--- /dev/null
+++ b/flexmeasures_s2/profile_steering/common/proposal.py
@@ -0,0 +1,130 @@
+from typing import Optional
+from flexmeasures_s2.profile_steering.common.joule_profile import JouleProfile
+from flexmeasures_s2.profile_steering.common.target_profile import TargetProfile
+from flexmeasures_s2.profile_steering.device_planner.device_planner_abstract import (
+ DevicePlanner,
+)
+
+
+class Proposal:
+ def __init__(
+ self,
+ global_diff_target: TargetProfile,
+ diff_to_congestion_max: JouleProfile,
+ diff_to_congestion_min: JouleProfile,
+ proposed_plan: JouleProfile,
+ old_plan: JouleProfile,
+ origin: DevicePlanner,
+ ):
+ self.global_diff_target = global_diff_target
+ self.diff_to_congestion_max = diff_to_congestion_max
+ self.diff_to_congestion_min = diff_to_congestion_min
+ self.proposed_plan = proposed_plan
+ self.old_plan = old_plan
+ self.global_improvement_value: Optional[float] = None
+ self.congestion_improvement_value: Optional[float] = None
+ self.cost_improvement_value: Optional[float] = None
+ self.origin = origin
+
+ def get_global_improvement_value(self) -> float:
+ if self.global_improvement_value is None:
+ # print(f"self.old_plan: {self.old_plan}")
+ # print(f"self.proposed_plan: {self.proposed_plan}")
+ # print the quadratic distance of the global diff target
+ # print(f"self.global_diff_target.sum_quadratic_distance(): {self.global_diff_target.sum_quadratic_distance()}")
+ self.global_improvement_value = (
+ self.global_diff_target.sum_quadratic_distance()
+ - self.global_diff_target.add(self.old_plan)
+ .subtract(self.proposed_plan)
+ .sum_quadratic_distance()
+ )
+ return self.global_improvement_value
+
+ def get_cost_improvement_value(self) -> float:
+ if self.cost_improvement_value is None:
+ self.cost_improvement_value = self.get_cost(
+ self.old_plan, self.global_diff_target
+ ) - self.get_cost(self.proposed_plan, self.global_diff_target)
+ return self.cost_improvement_value
+
+ @staticmethod
+ def get_cost(plan: JouleProfile, target_profile: TargetProfile) -> float:
+ cost = 0.0
+ tariff_count = 0
+ null_count = 0
+ for i in range(target_profile.metadata.nr_of_timesteps):
+ joule_usage = plan.elements[i]
+ target_element = target_profile.elements[i]
+ if isinstance(target_element, TargetProfile.TariffElement):
+ tariff_count += 1
+ if joule_usage is not None:
+ cost += (joule_usage / 3_600_000) * target_element.get_tariff()
+ elif isinstance(target_element, TargetProfile.NullElement):
+ null_count += 1
+ if tariff_count == 0 and null_count > 0:
+ # print(
+ # f" WARNING: get_cost called with {null_count} NullElements, {tariff_count} TariffElements - cost will be 0!"
+ # )
+ pass
+ return cost
+
+ def get_congestion_improvement_value(self) -> float:
+ if self.congestion_improvement_value is None:
+ zero_profile = JouleProfile(
+ self.old_plan.metadata.profile_start,
+ self.old_plan.metadata.timestep_duration,
+ [0] * len(self.old_plan.elements),
+ )
+ exceed_max_target_old = self.diff_to_congestion_max.minimum(
+ zero_profile
+ ).sum_quadratic_distance()
+ # print(f"exceed_max_target_old: {exceed_max_target_old}")
+ # print(f"self.diff_to_congestion_max: {self.diff_to_congestion_max}")
+ exceed_max_target_proposal = (
+ self.diff_to_congestion_max.add(self.old_plan)
+ .subtract(self.proposed_plan)
+ .minimum(zero_profile)
+ .sum_quadratic_distance()
+ )
+ # print(f"exceed_max_target_proposal: {exceed_max_target_proposal}")
+ exceed_min_target_old = self.diff_to_congestion_min.maximum(
+ zero_profile
+ ).sum_quadratic_distance()
+ # print(f"exceed_min_target_old: {exceed_min_target_old}")
+ exceed_min_target_proposal = (
+ self.diff_to_congestion_min.add(self.old_plan)
+ .subtract(self.proposed_plan)
+ .maximum(zero_profile)
+ .sum_quadratic_distance()
+ )
+ # print(f"exceed_min_target_proposal: {exceed_min_target_proposal}")
+ if (
+ exceed_max_target_old == exceed_max_target_proposal
+ and exceed_min_target_old == exceed_min_target_proposal
+ ):
+ self.congestion_improvement_value = 0.0
+ else:
+ self.congestion_improvement_value = (
+ exceed_max_target_old
+ + exceed_min_target_old
+ - exceed_max_target_proposal
+ - exceed_min_target_proposal
+ )
+ # print(f"congestion_improvement_value: {self.congestion_improvement_value}")
+ return self.congestion_improvement_value
+
+ def is_preferred_to(self, other: "Proposal") -> bool:
+ if self.get_congestion_improvement_value() >= 0:
+ if (
+ self.get_global_improvement_value()
+ > other.get_global_improvement_value()
+ ):
+ return True
+ elif (
+ self.get_global_improvement_value()
+ == other.get_global_improvement_value()
+ and self.get_cost_improvement_value()
+ > other.get_cost_improvement_value()
+ ):
+ return True
+ return False
diff --git a/flexmeasures_s2/profile_steering/common/pydantic_base.py b/flexmeasures_s2/profile_steering/common/pydantic_base.py
new file mode 100644
index 0000000..2d083f5
--- /dev/null
+++ b/flexmeasures_s2/profile_steering/common/pydantic_base.py
@@ -0,0 +1,8 @@
+from pydantic import BaseModel
+
+
+class FlexMeasuresBaseModel(BaseModel):
+ """Base model for FlexMeasures S2 with arbitrary types allowed."""
+
+ class Config:
+ arbitrary_types_allowed = True
diff --git a/flexmeasures_s2/profile_steering/common/s2_actuator_configuration.py b/flexmeasures_s2/profile_steering/common/s2_actuator_configuration.py
new file mode 100644
index 0000000..b70b0c9
--- /dev/null
+++ b/flexmeasures_s2/profile_steering/common/s2_actuator_configuration.py
@@ -0,0 +1,20 @@
+class S2ActuatorConfiguration:
+ def __init__(self, operation_mode_id, factor):
+ self.operation_mode_id = operation_mode_id
+ self.factor = factor
+
+ def get_operation_mode_id(self):
+ return self.operation_mode_id
+
+ def get_factor(self):
+ return self.factor
+
+ def to_dict(self):
+ return {"operationModeId": self.operation_mode_id, "factor": self.factor}
+
+ @staticmethod
+ def from_dict(data):
+ return S2ActuatorConfiguration(data["operationModeId"], data["factor"])
+
+ def __str__(self):
+ return f"S2ActuatorConfiguration [operationModeId={self.operation_mode_id}, factor={self.factor}]"
diff --git a/flexmeasures_s2/profile_steering/common/soc_profile.py b/flexmeasures_s2/profile_steering/common/soc_profile.py
new file mode 100644
index 0000000..e4b8fd2
--- /dev/null
+++ b/flexmeasures_s2/profile_steering/common/soc_profile.py
@@ -0,0 +1,50 @@
+from typing import List, Optional
+from flexmeasures_s2.profile_steering.common.abstract_profile import AbstractProfile
+from flexmeasures_s2.profile_steering.common.profile_metadata import ProfileMetadata
+from datetime import datetime
+
+
+class SoCProfile(AbstractProfile[float, "SoCProfile"]):
+ def __init__(
+ self, profile_metadata: ProfileMetadata, elements: Optional[List[float]] = None
+ ):
+ self.profile_metadata = profile_metadata
+ self.timestep_duration = self.profile_metadata.timestep_duration
+ self.profile_start = self.profile_metadata.profile_start
+ self.profile_end = self.profile_metadata.profile_end
+ super().__init__(
+ self.profile_metadata, elements if elements is not None else []
+ )
+
+ # mypy complains that the return is None
+ def default_value(self) -> None: # type: ignore
+ return None
+
+ def __str__(self) -> str:
+ return f"SoCProfile(elements={self.elements}, profile_start={self.profile_start}, timestep_duration={self.timestep_duration})"
+
+ def is_compatible(self, other: AbstractProfile) -> bool:
+ return (
+ self.metadata.timestep_duration == other.metadata.timestep_duration
+ and len(self.elements) == len(other.elements)
+ )
+
+ def validate(self, profile_metadata: ProfileMetadata, elements: List[float]):
+ super().validate(profile_metadata, elements)
+
+ def subprofile(self, new_start_date: datetime) -> "SoCProfile":
+ index = self.index_at(new_start_date)
+ if index < 0:
+ raise ValueError("New start date is outside profile range")
+ new_elements = self.elements[index:]
+ return SoCProfile(self.metadata, new_elements)
+
+ def adjust_nr_of_elements(self, nr_of_elements: int) -> "SoCProfile":
+ if nr_of_elements < len(self.elements):
+ new_elements = self.elements[:nr_of_elements]
+ else:
+ new_elements = self.elements + [0.0] * (nr_of_elements - len(self.elements))
+ return SoCProfile(
+ self.metadata,
+ new_elements,
+ )
diff --git a/flexmeasures_s2/profile_steering/common/target_profile.py b/flexmeasures_s2/profile_steering/common/target_profile.py
new file mode 100644
index 0000000..6b4f6ec
--- /dev/null
+++ b/flexmeasures_s2/profile_steering/common/target_profile.py
@@ -0,0 +1,205 @@
+from typing import List, Union, Optional
+from datetime import datetime, timedelta
+from flexmeasures_s2.profile_steering.common.joule_profile import JouleProfile
+from flexmeasures_s2.profile_steering.common.abstract_profile import AbstractProfile
+from flexmeasures_s2.profile_steering.common.profile_metadata import ProfileMetadata
+
+
+class TargetProfile(
+ AbstractProfile[Union["TargetProfile.Element", None], "TargetProfile"]
+):
+ class Element:
+ pass
+
+ class JouleElement(Element):
+ def __init__(self, joules: int):
+ self.joules = joules
+
+ class TariffElement(Element):
+ def __init__(self, tariff: float):
+ self.tariff = tariff
+
+ def get_tariff(self) -> float:
+ return self.tariff
+
+ class NullElement(Element):
+ pass
+
+ NULL_ELEMENT: "TargetProfile.Element" = NullElement()
+
+ def __init__(
+ self,
+ profile_start: datetime,
+ timestep_duration: timedelta,
+ elements: List["TargetProfile.Element | None"],
+ ):
+ metadata = ProfileMetadata(profile_start, timestep_duration, len(elements))
+ super().__init__(metadata, elements)
+ # if elements is a list of ints, convert it to a list of JouleElement
+ if isinstance(elements, list) and all(isinstance(e, int) for e in elements):
+ # Ignore type error because e is int
+ self.elements = [TargetProfile.JouleElement(e) for e in elements] # type: ignore[arg-type]
+ else:
+ self.elements = elements
+
+ def validate(
+ self,
+ profile_metadata: ProfileMetadata,
+ elements: List["TargetProfile.Element | None"],
+ ):
+ super().validate(profile_metadata, elements)
+ # Add any TargetProfile-specific validation if needed
+
+ def get_energy_for_timestep(self, index: int) -> Optional[int]:
+ if 0 <= index < len(self.elements) and isinstance(
+ self.elements[index], TargetProfile.JouleElement
+ ):
+ return self.elements[index].joules # type: ignore
+ return None
+
+ def default_value(self) -> "TargetProfile.Element":
+ return TargetProfile.NULL_ELEMENT
+
+ def subprofile(self, new_start_date: datetime) -> "TargetProfile":
+ index = self.index_at(new_start_date)
+ if index < 0:
+ raise ValueError("New start date is outside profile range")
+ new_elements = self.elements[index:]
+ return TargetProfile(
+ new_start_date, self.metadata.timestep_duration, new_elements
+ )
+
+ def adjust_nr_of_elements(self, nr_of_elements: int) -> "TargetProfile":
+ if nr_of_elements < len(self.elements):
+ new_elements = self.elements[:nr_of_elements]
+ else:
+ new_elements = self.elements + [self.NULL_ELEMENT] * (
+ nr_of_elements - len(self.elements)
+ )
+ return TargetProfile(
+ self.metadata.profile_start,
+ self.metadata.timestep_duration,
+ new_elements,
+ )
+
+ def is_compatible(self, other: AbstractProfile) -> bool:
+ return (
+ self.metadata.timestep_duration == other.metadata.timestep_duration
+ and len(self.elements) == len(other.elements)
+ )
+
+ def get_total_energy(self) -> int:
+ return sum(
+ e.joules for e in self.elements if isinstance(e, TargetProfile.JouleElement)
+ )
+
+ def target_elements_to_joule_profile(self) -> JouleProfile:
+ joules = [
+ e.joules for e in self.elements if isinstance(e, TargetProfile.JouleElement)
+ ]
+ # Ignore type error because joules is a list of ints
+ return JouleProfile(
+ self.metadata.profile_start,
+ self.metadata.timestep_duration,
+ joules, # type: ignore[arg-type]
+ )
+
+ def nr_of_joule_target_elements(self) -> int:
+ return len(
+ [e for e in self.elements if isinstance(e, TargetProfile.JouleElement)]
+ )
+
+ def subtract(self, other: JouleProfile) -> "TargetProfile":
+ if not self.is_compatible(other):
+ raise ValueError("Profiles are not compatible")
+ diff_elements: List["TargetProfile.Element | None"] = []
+ for i, element in enumerate(self.elements):
+ if isinstance(element, TargetProfile.JouleElement):
+ other_energy = other.get_energy_for_timestep(i)
+ if other_energy is not None:
+ diff_elements.append(
+ TargetProfile.JouleElement(element.joules - other_energy)
+ )
+ else:
+ diff_elements.append(self.NULL_ELEMENT)
+ else:
+ diff_elements.append(self.NULL_ELEMENT)
+ return TargetProfile(
+ self.metadata.profile_start,
+ self.metadata.timestep_duration,
+ diff_elements,
+ )
+
+ def add(self, other: JouleProfile) -> "TargetProfile":
+ if not self.is_compatible(other):
+ raise ValueError("Profiles are not compatible")
+ sum_elements: List["TargetProfile.Element | None"] = []
+ for i, element in enumerate(self.elements):
+ if isinstance(element, TargetProfile.JouleElement):
+ other_energy = other.get_energy_for_timestep(i)
+ if other_energy is not None:
+ sum_elements.append(
+ TargetProfile.JouleElement(element.joules + other_energy)
+ )
+ else:
+ sum_elements.append(self.NULL_ELEMENT)
+ else:
+ sum_elements.append(self.NULL_ELEMENT)
+ return TargetProfile(
+ self.metadata.profile_start,
+ self.metadata.timestep_duration,
+ sum_elements,
+ )
+
+ def sum_quadratic_distance(self) -> float:
+ return sum(
+ e.joules**2
+ for e in self.elements
+ if isinstance(e, TargetProfile.JouleElement)
+ )
+
+ def __str__(self) -> str:
+ return f"TargetProfile(elements={self.elements}, profile_start={self.metadata.profile_start}, timestep_duration={self.metadata.timestep_duration})"
+
+ @staticmethod
+ def null_profile(metadata: ProfileMetadata) -> "TargetProfile":
+ return TargetProfile(
+ metadata.profile_start,
+ metadata.timestep_duration,
+ [TargetProfile.NULL_ELEMENT] * metadata.nr_of_timesteps,
+ )
+
+ @staticmethod
+ def from_joule_profile(joule_profile: JouleProfile) -> "TargetProfile":
+ return TargetProfile(
+ joule_profile.metadata.profile_start,
+ joule_profile.metadata.timestep_duration,
+ [TargetProfile.JouleElement(e) for e in joule_profile.elements],
+ )
+
+ @staticmethod
+ def from_tariff_values(
+ metadata: ProfileMetadata, tariff_values: List[float]
+ ) -> "TargetProfile":
+ """Create a TargetProfile with TariffElement instances from tariff values.
+
+ Args:
+ metadata: ProfileMetadata for the target profile
+ tariff_values: List of tariff values (cost per kWh)
+
+ Returns:
+ TargetProfile with TariffElement instances
+ """
+ if len(tariff_values) != metadata.nr_of_timesteps:
+ raise ValueError(
+ f"Number of tariff values ({len(tariff_values)}) must match nr_of_timesteps ({metadata.nr_of_timesteps})"
+ )
+
+ elements: List[Union["TargetProfile.Element", None]] = [
+ TargetProfile.TariffElement(tariff) for tariff in tariff_values
+ ]
+ return TargetProfile(
+ metadata.profile_start,
+ metadata.timestep_duration,
+ elements,
+ )
diff --git a/flexmeasures_s2/profile_steering/common_data_structures.py b/flexmeasures_s2/profile_steering/common_data_structures.py
new file mode 100644
index 0000000..b6e9a53
--- /dev/null
+++ b/flexmeasures_s2/profile_steering/common_data_structures.py
@@ -0,0 +1,33 @@
+from datetime import datetime
+from typing import List, Dict, Any, Optional
+
+
+class ClusterState:
+ """Class representing the state of a cluster of devices."""
+
+ def __init__(
+ self,
+ timestamp: datetime,
+ device_states: Dict[str, Any] = {},
+ congestion_points_by_connection_id: Dict[str, str] = {},
+ ):
+ self._timestamp = timestamp
+ self._device_states = device_states or {}
+ self._congestion_points_by_connection_id = (
+ congestion_points_by_connection_id or {}
+ )
+
+ def set_device_states(self, device_states: Dict[str, Any]):
+ self._device_states = device_states or {}
+
+ def get_device_states(self) -> Dict[str, Any]:
+ return self._device_states
+
+ def get_congestion_point(self, connection_id: str) -> Optional[str]:
+ return self._congestion_points_by_connection_id.get(connection_id)
+
+ def set_congestion_point(self, connection_id: str, congestion_point_id: str):
+ self._congestion_points_by_connection_id[connection_id] = congestion_point_id
+
+ def get_congestion_points(self) -> List[str]:
+ return list(set(self._congestion_points_by_connection_id.values()))
diff --git a/flexmeasures_s2/profile_steering/congestion_point_planner.py b/flexmeasures_s2/profile_steering/congestion_point_planner.py
new file mode 100644
index 0000000..e64449d
--- /dev/null
+++ b/flexmeasures_s2/profile_steering/congestion_point_planner.py
@@ -0,0 +1,340 @@
+from datetime import datetime
+from typing import Any, Optional, List
+import concurrent.futures
+from flexmeasures_s2.profile_steering.common.joule_profile import JouleProfile
+from flexmeasures_s2.profile_steering.common.joule_range_profile import (
+ JouleRangeProfile,
+)
+from flexmeasures_s2.profile_steering.common.proposal import Proposal
+from flexmeasures_s2.profile_steering.device_planner.device_planner_abstract import (
+ DevicePlanner,
+)
+from flexmeasures_s2.profile_steering.common.target_profile import TargetProfile
+
+
+def _get_device_proposal(
+ device: DevicePlanner,
+ difference_profile: TargetProfile,
+ diff_to_max_value: JouleProfile,
+ diff_to_min_value: JouleProfile,
+ plan_due_by_date: datetime,
+) -> Optional[Proposal]:
+ """Helper function to be called by the process pool executor for pickling."""
+ try:
+ return device.create_improved_planning(
+ difference_profile,
+ diff_to_max_value,
+ diff_to_min_value,
+ plan_due_by_date,
+ )
+ except Exception:
+ # print(f"Error getting proposal from device {device.device_id} in worker: {e}")
+ return None
+
+
+def _get_initial_device_plan(
+ device: DevicePlanner, plan_due_by_date: datetime
+) -> tuple[str, Any]:
+ """Helper function to be called by the process pool executor for initial planning."""
+ try:
+ plan = device.create_initial_planning(plan_due_by_date)
+ return (device.device_id, plan)
+ except Exception as e:
+ print(
+ f"Error getting initial plan from device {device.device_id} in worker: {e}"
+ )
+ print(f"Exception type: {type(e).__name__}")
+ import traceback
+
+ print("Full traceback:")
+ traceback.print_exc()
+ return (device.device_id, None)
+
+
+class CongestionPointPlanner:
+ def __init__(
+ self,
+ congestion_point_id: str,
+ congestion_target: JouleRangeProfile,
+ multithreaded: bool = False,
+ ):
+ """Initialize a congestion point planner.
+
+ Args:
+
+ congestion_point_id: Unique identifier for this congestion point
+ congestion_target: Target profile with range constraints for this congestion point
+ multithreaded: Whether to use multiprocessing for device planning
+ """
+ self.MAX_ITERATIONS = 1000
+ self.congestion_point_id = congestion_point_id
+ self.congestion_target = congestion_target
+ self.profile_metadata = congestion_target.metadata
+ self.multithreaded = multithreaded
+
+ # Create an empty profile (using all zeros)
+ self.empty_profile = JouleProfile(
+ profile_start=self.profile_metadata.profile_start,
+ timestep_duration=self.profile_metadata.timestep_duration,
+ elements=[0] * self.profile_metadata.nr_of_timesteps, # type: ignore[list-item]
+ )
+
+ # List of device controllers that can be used for planning
+ self.devices: List[DevicePlanner] = []
+
+ # Keep track of accepted and latest plans
+ self.accepted_plan = self.empty_profile
+ self.latest_plan = self.empty_profile
+
+ def add_device(self, device):
+ """Add a device controller to this congestion point."""
+ self.devices.append(device)
+
+ @staticmethod
+ def is_storage_available(self) -> bool:
+ """Check if storage is available at this congestion point."""
+ # For now, always assume storage is available
+ return True
+
+ def create_initial_planning(
+ self, plan_due_by_date: datetime
+ ) -> JouleProfile: # noqa: C901
+ """Create an initial plan for this congestion point.
+
+ Args:
+ plan_due_by_date: The date by which the plan must be ready
+
+ Returns:
+ A JouleProfile representing the initial plan
+ """
+ current_planning = self.empty_profile
+
+ # Aggregate initial plans from all devices (parallel or sequential based on multithreaded setting)
+ device_plans: dict[str, Any] = {}
+ if self.multithreaded:
+ # Use multiprocessing for parallel execution
+ with concurrent.futures.ProcessPoolExecutor() as executor:
+ futures = {
+ executor.submit(_get_initial_device_plan, device, plan_due_by_date)
+ for device in self.devices
+ }
+ for future in concurrent.futures.as_completed(futures):
+ device_id, plan = future.result()
+ if plan is not None:
+ device_plans[device_id] = plan
+ else:
+ # Use sequential execution to avoid pickling issues
+ for device in self.devices:
+ device_id, plan = _get_initial_device_plan(device, plan_due_by_date)
+ if plan is not None:
+ device_plans[device_id] = plan
+
+ # Update the state of the original device objects and aggregate energy profiles
+ for device in self.devices:
+ if device.device_id in device_plans:
+ plan = device_plans[device.device_id]
+ device.set_accepted_plan(plan)
+ current_planning = current_planning.add(plan.energy)
+
+ # Check if the current planning is within the congestion target range
+ if self.congestion_target.is_within_range(current_planning):
+ # print(
+ # "Current planning is within the congestion target range. Returning it."
+ # )
+ return current_planning
+
+ # If the current planning is not within the congestion target range, optimize it
+ # print(
+ # "Current planning is not within the congestion target range. Optimizing it."
+ # )
+
+ # print(f"Congestion target: {self.congestion_target}")
+ # This is an old implementation that does not use the root planner
+ # It is kept here for reference
+ i = 0
+ best_proposal = None
+
+ max_priority_class = self.max_priority_class()
+ min_priority_class = self.min_priority_class()
+
+ # Iterate over priority classes
+ for priority_class in range(min_priority_class, max_priority_class + 1):
+ # print(f"Optimizing priority class: {priority_class}")
+ pass
+ while True:
+ best_proposal = None
+ diff_to_max = self.congestion_target.difference_with_max_value(
+ current_planning
+ )
+ diff_to_min = self.congestion_target.difference_with_min_value(
+ current_planning
+ )
+
+ # Try to get improved plans from each device controller
+ for device in self.devices:
+ if device.priority_class <= priority_class:
+ try:
+ proposal = device.create_improved_planning(
+ self.empty_profile, # type: ignore[arg-type]
+ diff_to_max,
+ diff_to_min,
+ plan_due_by_date,
+ )
+ if proposal is None:
+ raise ValueError(
+ f"No proposal found for device {device.device_id}"
+ )
+ # print(
+ # f"congestion point improvement for '{device.device_id}': {proposal.get_congestion_improvement_value()}"
+ # )
+ if (
+ best_proposal is None
+ or proposal.get_congestion_improvement_value()
+ > best_proposal.get_congestion_improvement_value()
+ ):
+ best_proposal = proposal
+ except Exception:
+ # print(
+ # f"Error getting proposal from device {device.device_id}: {e}"
+ # )
+ continue
+
+ if (
+ best_proposal is None
+ or best_proposal.get_congestion_improvement_value() <= 0
+ ):
+ break
+
+ # Update the current planning based on the best proposal
+ current_planning = current_planning.subtract(
+ best_proposal.old_plan
+ ).add(best_proposal.proposed_plan)
+ best_proposal.origin.accept_proposal(best_proposal)
+ i += 1
+
+ # print(
+ # f"Initial planning '{self.congestion_point_id}': best controller '{best_proposal.origin.device_id}' with congestion improvement of {best_proposal.get_congestion_improvement_value()}. Iteration {i}."
+ # )
+
+ if i >= self.MAX_ITERATIONS:
+ break
+
+ return current_planning
+
+ def create_improved_planning(
+ self,
+ difference_profile: TargetProfile,
+ priority_class: int,
+ plan_due_by_date: datetime,
+ ) -> Optional[Proposal]:
+ """Create an improved plan based on the difference profile.
+
+ Args:
+ difference_profile: The difference between target and current planning
+ priority_class: Priority class for this planning iteration
+ plan_due_by_date: The date by which the plan must be ready
+
+ Returns:
+ A Proposal object if an improvement was found, None otherwise
+ """
+ best_proposal = None
+
+ current_planning = self.get_current_planning()
+
+ diff_to_max_value = self.congestion_target.difference_with_max_value(
+ current_planning
+ )
+ diff_to_min_value = self.congestion_target.difference_with_min_value(
+ current_planning
+ )
+ # print(f"diff_to_max_value: {diff_to_max_value}")
+ # print(f"diff_to_min_value: {diff_to_min_value}")
+ # Try to get improved plans from each device controller
+ devices_to_plan = [
+ d for d in self.devices if d.priority_class <= priority_class
+ ]
+
+ proposals = []
+ if devices_to_plan:
+ if self.multithreaded:
+ # Use multiprocessing for parallel execution
+ with concurrent.futures.ProcessPoolExecutor() as executor:
+ futures = {
+ executor.submit(
+ _get_device_proposal,
+ device,
+ difference_profile,
+ diff_to_max_value,
+ diff_to_min_value,
+ plan_due_by_date,
+ )
+ for device in devices_to_plan
+ }
+ for future in concurrent.futures.as_completed(futures):
+ proposal = future.result()
+ if proposal:
+ proposals.append(proposal)
+ else:
+ # Use sequential execution to avoid pickling issues
+ for device in devices_to_plan:
+ proposal = _get_device_proposal(
+ device,
+ difference_profile,
+ diff_to_max_value,
+ diff_to_min_value,
+ plan_due_by_date,
+ )
+ if proposal:
+ proposals.append(proposal)
+
+ for proposal in proposals:
+ if proposal.get_congestion_improvement_value() < 0:
+ # print(
+ # f"{proposal.origin.device_name}, congestion improvement: {proposal.get_congestion_improvement_value()}"
+ # )
+ pass
+
+ if best_proposal is None or proposal.is_preferred_to(best_proposal):
+ best_proposal = proposal
+
+ if best_proposal is None:
+ # print(
+ # f"CP '{self.congestion_point_id}': No proposal available at current priority level ({priority_class})"
+ # )
+ pass
+ else:
+ if best_proposal.get_congestion_improvement_value() == float("-inf"):
+ raise ValueError(
+ "Invalid proposal with negative infinity improvement value"
+ )
+
+ # print(
+ # f"CP '{self.congestion_point_id}': Selected best controller '{best_proposal.origin.device_name}' with improvement of {best_proposal.get_global_improvement_value()}."
+ # )
+
+ return best_proposal
+
+ def get_current_planning(self) -> JouleProfile:
+ """Get the current planning profile."""
+ # Return the latest accepted plan as the current planning
+ current_planning = self.empty_profile
+ for device in self.devices:
+ # Ignore type error because current_profile is a JouleProfile but its considered a callable JouleProfile
+ current_planning = current_planning.add(device.current_profile()) # type: ignore[arg-type]
+ return current_planning
+
+ def add_device_controller(self, device):
+ """Add a device controller to this congestion point."""
+ self.devices.append(device)
+
+ def max_priority_class(self) -> int:
+ """Get the maximum priority class among all devices."""
+ if not self.devices:
+ return 1
+ return max(device.priority_class for device in self.devices)
+
+ def min_priority_class(self) -> int:
+ """Get the minimum priority class among all devices."""
+ if not self.devices:
+ return 1
+ return min(device.priority_class for device in self.devices)
diff --git a/flexmeasures_s2/profile_steering/device_planner/__init__.py b/flexmeasures_s2/profile_steering/device_planner/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/flexmeasures_s2/profile_steering/device_planner/ddbc/__init__.py b/flexmeasures_s2/profile_steering/device_planner/ddbc/__init__.py
new file mode 100644
index 0000000..a214524
--- /dev/null
+++ b/flexmeasures_s2/profile_steering/device_planner/ddbc/__init__.py
@@ -0,0 +1 @@
+# DDBC (Demand-Driven Based Control) device planner package
diff --git a/flexmeasures_s2/profile_steering/device_planner/ddbc/avg_demand_forecast_util.py b/flexmeasures_s2/profile_steering/device_planner/ddbc/avg_demand_forecast_util.py
new file mode 100644
index 0000000..b36b3ef
--- /dev/null
+++ b/flexmeasures_s2/profile_steering/device_planner/ddbc/avg_demand_forecast_util.py
@@ -0,0 +1,127 @@
+from datetime import datetime, timedelta
+from typing import List, Any, Optional
+
+
+class AvgDemandForecastElement:
+ """Element representing average demand forecast for a time period."""
+
+ def __init__(self, start: datetime, end: datetime, avg_demand: float):
+ self.start = start
+ self.end = end
+ self.avg_demand = avg_demand
+
+ def get_start(self) -> datetime:
+ return self.start
+
+ def get_end(self) -> datetime:
+ return self.end
+
+ def get_avg_demand(self) -> float:
+ return self.avg_demand
+
+ def get_duration(self) -> timedelta:
+ return self.end - self.start
+
+ def __str__(self) -> str:
+ return f"AvgDemandForecastElement(avgDemand={self.avg_demand}, start={self.start}, end={self.end})"
+
+
+class AvgDemandForecastProfile:
+ """Profile of average demand forecast elements."""
+
+ def __init__(self, elements: List[AvgDemandForecastElement]):
+ self.elements = elements
+
+ def get_elements(self) -> List[AvgDemandForecastElement]:
+ return self.elements
+
+ def get_start(self) -> Optional[datetime]:
+ if not self.elements:
+ return None
+ return self.elements[0].get_start()
+
+ def get_end(self) -> Optional[datetime]:
+ if not self.elements:
+ return None
+ return self.elements[-1].get_end()
+
+ def sub_profile(self, start: datetime, end: datetime) -> "AvgDemandForecastProfile":
+ """Get a sub-profile between start and end times."""
+ sub_elements = []
+
+ for element in self.elements:
+ element_start = max(element.get_start(), start)
+ element_end = min(element.get_end(), end)
+
+ if element_start < element_end:
+ sub_elements.append(
+ AvgDemandForecastElement(
+ element_start, element_end, element.get_avg_demand()
+ )
+ )
+
+ return AvgDemandForecastProfile(sub_elements)
+
+
+class AvgDemandForecastUtil:
+ """Utility class for converting DDBC average demand forecasts."""
+
+ @staticmethod
+ def from_avg_demand_rate_forecast(demand_forecast: Any) -> AvgDemandForecastProfile:
+ """Convert a DDBC average demand rate forecast to a profile."""
+ elements: List[AvgDemandForecastElement] = []
+
+ start = demand_forecast.start_time
+
+ for element in demand_forecast.elements:
+ if isinstance(element.duration, (int, float)):
+ duration_seconds = element.duration
+ elif hasattr(element.duration, "root"):
+ duration_seconds = element.duration.root
+ else:
+ duration_seconds = int(element.duration)
+ end = start + timedelta(seconds=duration_seconds)
+ elements.append(
+ AvgDemandForecastElement(
+ start, end, float(element.demand_rate_expected)
+ )
+ )
+ start = end + timedelta(milliseconds=1)
+
+ return AvgDemandForecastProfile(elements)
+
+ @staticmethod
+ def get_avg_demand_forecast_for_timestep(
+ avg_demand_forecast: AvgDemandForecastProfile,
+ timestep_start: datetime,
+ timestep_end: datetime,
+ ) -> Optional[float]:
+ """Get weighted average demand forecast for a timestep."""
+ if avg_demand_forecast is None:
+ return None
+
+ timestep_end_adjusted = timestep_end - timedelta(milliseconds=1)
+
+ sub_profile = avg_demand_forecast.sub_profile(
+ timestep_start, timestep_end_adjusted
+ )
+
+ if not sub_profile.get_elements():
+ return None
+
+ demand = 0.0
+ for element in sub_profile.get_elements():
+ duration_ms = element.get_duration().total_seconds() * 1000
+ demand += element.get_avg_demand() * duration_ms
+
+ start = sub_profile.get_start()
+ end = sub_profile.get_end()
+ if start is None or end is None:
+ return None
+
+ total_duration_ms = (end - start).total_seconds() * 1000
+
+ if total_duration_ms == 0:
+ return None
+
+ return demand / total_duration_ms
diff --git a/flexmeasures_s2/profile_steering/device_planner/ddbc/ddbc_operation_mode_wrapper.py b/flexmeasures_s2/profile_steering/device_planner/ddbc/ddbc_operation_mode_wrapper.py
new file mode 100644
index 0000000..3ca24b7
--- /dev/null
+++ b/flexmeasures_s2/profile_steering/device_planner/ddbc/ddbc_operation_mode_wrapper.py
@@ -0,0 +1,148 @@
+from typing import List, Any, Dict, Optional
+from flexmeasures_s2.profile_steering.common.power_range_wrapper import (
+ PowerRangeWrapper,
+)
+from flexmeasures_s2.profile_steering.device_planner.ddbc.number_range_wrapper import (
+ NumberRangeWrapper,
+)
+from s2python.common import CommodityQuantity
+
+
+class DdbcOperationModeWrapper:
+ """Wrapper for DDBC operation mode to provide utility methods."""
+
+ EPSILON = 1e-4
+
+ def __init__(self, ddbc_operation_mode: Any):
+ """Initialize from a DDBC operation mode."""
+ self.id = (
+ ddbc_operation_mode.Id
+ if hasattr(ddbc_operation_mode, "Id")
+ else ddbc_operation_mode.id
+ )
+ self.diagnostic_label = getattr(ddbc_operation_mode, "diagnostic_label", None)
+ self.abnormal_condition_only = getattr(
+ ddbc_operation_mode, "abnormal_condition_only", False
+ )
+
+ self.power_ranges: List[PowerRangeWrapper] = []
+ for unwrapped_power_range in ddbc_operation_mode.power_ranges:
+ self.power_ranges.append(PowerRangeWrapper(unwrapped_power_range))
+
+ if isinstance(ddbc_operation_mode.supply_range, list):
+ self.supply_range = NumberRangeWrapper(ddbc_operation_mode.supply_range[0])
+ else:
+ self.supply_range = NumberRangeWrapper(ddbc_operation_mode.supply_range)
+
+ self.running_costs: Optional[NumberRangeWrapper]
+ if (
+ hasattr(ddbc_operation_mode, "running_costs")
+ and ddbc_operation_mode.running_costs is not None
+ ):
+ self.running_costs = NumberRangeWrapper(ddbc_operation_mode.running_costs)
+ else:
+ self.running_costs = None
+
+ # Determine if this operation mode uses a factor
+ uses_factor = False
+
+ if (
+ abs(
+ self.supply_range.get_start_of_range()
+ - self.supply_range.get_end_of_range()
+ )
+ > self.EPSILON
+ ):
+ uses_factor = True
+
+ for power_range in self.power_ranges:
+ if (
+ abs(power_range.get_start_of_range() - power_range.get_end_of_range())
+ > self.EPSILON
+ ):
+ uses_factor = True
+
+ self.uses_factor = uses_factor
+
+ def get_id(self) -> str:
+ # Handle UUID objects with root attribute
+ if hasattr(self.id, "root"):
+ return str(self.id.root)
+ else:
+ return str(self.id)
+
+ def get_diagnostic_label(self) -> Optional[str]:
+ return self.diagnostic_label
+
+ def get_power_ranges(self) -> List[PowerRangeWrapper]:
+ return self.power_ranges
+
+ def get_supply_range(self) -> NumberRangeWrapper:
+ return self.supply_range
+
+ def get_running_costs(self) -> Optional[NumberRangeWrapper]:
+ return self.running_costs
+
+ def uses_factor_method(self) -> bool:
+ return self.uses_factor
+
+ def get_operation_mode_electrical_power(self, factor: float) -> float:
+ """Calculate electrical power consumption for a given factor."""
+ power_watt = 0.0
+
+ electric_commodities = [
+ CommodityQuantity.ELECTRIC_POWER_L1,
+ CommodityQuantity.ELECTRIC_POWER_L2,
+ CommodityQuantity.ELECTRIC_POWER_L3,
+ CommodityQuantity.ELECTRIC_POWER_3_PHASE_SYMMETRIC,
+ ]
+
+ for power_range in self.get_power_ranges():
+ if power_range.get_commodity_quantity() in electric_commodities:
+ start = power_range.get_start_of_range()
+ end = power_range.get_end_of_range()
+ power_watt += (end - start) * factor + start
+
+ return power_watt
+
+ def get_operation_mode_gas_consumption(self, factor: float) -> float:
+ """Calculate natural gas consumption (liters per second) for a given factor."""
+ liters_gas_per_second = 0.0
+
+ for power_range in self.get_power_ranges():
+ if (
+ power_range.get_commodity_quantity()
+ == CommodityQuantity.NATURAL_GAS_FLOW_RATE
+ ):
+ start = power_range.get_start_of_range()
+ end = power_range.get_end_of_range()
+ liters_gas_per_second += (end - start) * factor + start
+
+ return liters_gas_per_second
+
+ def get_operation_mode_supply_rate(self, factor: float) -> float:
+ """Calculate supply rate for a given factor."""
+ start = self.get_supply_range().get_start_of_range()
+ end = self.get_supply_range().get_end_of_range()
+ return (end - start) * factor + start
+
+ def convert_to_actuator_config(self, factor: float):
+ """Convert to S2DdbcActuatorConfiguration."""
+ from flexmeasures_s2.profile_steering.device_planner.ddbc.s2_ddbc_actuator_configuration import (
+ S2DdbcActuatorConfiguration,
+ )
+
+ power_per_commodity_quantity: Dict[str, float] = {}
+
+ for power_range in self.power_ranges:
+ commodity_quantity_value = power_range.get_commodity_quantity().value
+ power_per_commodity_quantity[
+ commodity_quantity_value
+ ] = power_range.get_power(factor)
+
+ return S2DdbcActuatorConfiguration(
+ operation_mode_id=self.get_id(),
+ factor=factor,
+ supply_rate=self.get_operation_mode_supply_rate(factor),
+ power_per_commodity_quantity=power_per_commodity_quantity,
+ )
diff --git a/flexmeasures_s2/profile_steering/device_planner/ddbc/ddbc_planning_window.py b/flexmeasures_s2/profile_steering/device_planner/ddbc/ddbc_planning_window.py
new file mode 100644
index 0000000..780d569
--- /dev/null
+++ b/flexmeasures_s2/profile_steering/device_planner/ddbc/ddbc_planning_window.py
@@ -0,0 +1,222 @@
+from datetime import datetime
+from typing import List, Optional, Callable, TypeVar, Any, Dict, TYPE_CHECKING, cast
+from flexmeasures_s2.profile_steering.common.profile_metadata import ProfileMetadata
+from flexmeasures_s2.profile_steering.common.joule_profile import JouleProfile
+from flexmeasures_s2.profile_steering.common.target_profile import TargetProfile
+from flexmeasures_s2.profile_steering.device_planner.ddbc.s2_ddbc_device_state import (
+ S2DdbcDeviceState,
+)
+from flexmeasures_s2.profile_steering.device_planner.ddbc.s2_ddbc_device_state_wrapper import (
+ S2DdbcDeviceStateWrapper,
+)
+from flexmeasures_s2.profile_steering.device_planner.ddbc.ddbc_timestep import (
+ DdbcTimestep,
+)
+from flexmeasures_s2.profile_steering.device_planner.ddbc.ddbc_state import DdbcState
+from flexmeasures_s2.profile_steering.device_planner.ddbc.avg_demand_forecast_util import (
+ AvgDemandForecastUtil,
+ AvgDemandForecastProfile,
+)
+from flexmeasures_s2.profile_steering.device_planner.ddbc.s2_ddbc_insights_profile import (
+ S2DdbcInsightsProfile,
+)
+from flexmeasures_s2.profile_steering.device_planner.ddbc.s2_ddbc_plan import S2DdbcPlan
+
+if TYPE_CHECKING:
+ pass
+
+T = TypeVar("T")
+
+
+class DdbcPlanningWindow:
+ """Planning window for DDBC device planning."""
+
+ def __init__(
+ self,
+ device_state: S2DdbcDeviceState,
+ profile_metadata: ProfileMetadata,
+ plan_due_by_date: datetime,
+ ):
+ self.device_state = S2DdbcDeviceStateWrapper(device_state)
+ self.profile_metadata = profile_metadata
+ self.plan_due_by_date = plan_due_by_date
+ self.timestep_duration_seconds = int(
+ profile_metadata.timestep_duration.total_seconds()
+ )
+
+ self.timesteps: List[DdbcTimestep] = []
+ self._generate_timesteps()
+
+ def _generate_timesteps(self):
+ """Generate timesteps for the planning window."""
+ timestep_start = self.profile_metadata.profile_start
+
+ for i in range(self.profile_metadata.nr_of_timesteps):
+ timestep_end = timestep_start + self.profile_metadata.timestep_duration
+
+ if i == 0 and timestep_start < self.plan_due_by_date < timestep_end:
+ timestep_start = self.plan_due_by_date
+
+ current_system_description = self.get_latest_before(
+ timestep_start,
+ self.device_state.get_system_descriptions(),
+ lambda sd: sd.valid_from,
+ )
+
+ current_avg_demand_forecast_obj = self.get_latest_before(
+ timestep_start,
+ self.device_state.get_demand_forecasts(),
+ lambda df: df.start_time,
+ )
+
+ current_demand_profile: Optional[AvgDemandForecastProfile] = None
+ if current_avg_demand_forecast_obj is not None:
+ current_demand_profile = (
+ AvgDemandForecastUtil.from_avg_demand_rate_forecast(
+ current_avg_demand_forecast_obj
+ )
+ )
+
+ avg_demand_rate_forecast = self.get_avg_demand_forecast_for_timestep(
+ current_demand_profile, timestep_start, timestep_end
+ )
+
+ self.timesteps.append(
+ DdbcTimestep(
+ timestep_start,
+ timestep_end,
+ current_system_description,
+ avg_demand_rate_forecast,
+ )
+ )
+
+ timestep_start = timestep_end
+
+ @staticmethod
+ def get_latest_before(
+ before: datetime, select_from: List[T], get_datetime: Callable[[T], datetime]
+ ) -> Optional[T]:
+ """Get the latest item from a list that occurs before a given datetime."""
+ latest_before: Optional[T] = None
+ latest_before_datetime: Optional[datetime] = None
+
+ if select_from is not None:
+ for current in select_from:
+ if current is not None:
+ current_datetime = get_datetime(current)
+
+ if current_datetime <= before:
+ if latest_before is None or (
+ latest_before_datetime is not None
+ and current_datetime > latest_before_datetime
+ ):
+ latest_before = current
+ latest_before_datetime = current_datetime
+
+ return latest_before
+
+ @staticmethod
+ def get_avg_demand_forecast_for_timestep(
+ avg_demand_forecast: Optional[AvgDemandForecastProfile],
+ timestep_start: datetime,
+ timestep_end: datetime,
+ ) -> Optional[float]:
+ """Get the average demand forecast for a timestep."""
+ if avg_demand_forecast is None:
+ return None
+
+ return AvgDemandForecastUtil.get_avg_demand_forecast_for_timestep(
+ avg_demand_forecast, timestep_start, timestep_end
+ )
+
+ def find_best_plan(
+ self,
+ target_profile: TargetProfile,
+ diff_to_min_profile: JouleProfile,
+ diff_to_max_profile: JouleProfile,
+ ) -> "S2DdbcPlan":
+ """Find the best plan for the device."""
+ for i in range(len(self.timesteps)):
+ ts = self.timesteps[i]
+ ts.clear()
+ target_element = target_profile.elements[i]
+ if target_element is not None:
+ ts.set_targets(
+ target_element,
+ diff_to_min_profile.elements[i],
+ diff_to_max_profile.elements[i],
+ )
+
+ first_timestep_index = -1
+ for i in range(len(self.timesteps)):
+ if self.timesteps[i].get_system_description() is not None:
+ first_timestep_index = i
+ break
+
+ if first_timestep_index == -1:
+ first_timestep_index = self.profile_metadata.nr_of_timesteps
+ else:
+ first_timestep = self.timesteps[first_timestep_index]
+
+ state_zero = DdbcState(self.device_state, first_timestep)
+ state_zero.generate_next_timestep_states(first_timestep)
+
+ for i in range(first_timestep_index, len(self.timesteps) - 1):
+ current_timestep = self.timesteps[i]
+ next_timestep = self.timesteps[i + 1]
+
+ best_state = current_timestep.get_best_state()
+ if best_state is not None:
+ best_state.generate_next_timestep_states(next_timestep)
+
+ return self._convert_to_plan(first_timestep_index)
+
+ def _convert_to_plan(self, first_timestep_index_with_state: int) -> "S2DdbcPlan":
+ """Convert the planning window to a plan."""
+ energy: List[int] = []
+ actuators: List[Dict[str, Any]] = []
+ insight_elements: List[Any] = []
+
+ for i in range(self.profile_metadata.nr_of_timesteps):
+ if i >= first_timestep_index_with_state:
+ timestep = self.timesteps[i]
+ state = timestep.get_best_state()
+
+ if state is not None:
+ energy.append(int(state.get_timestep_energy()))
+ actuators.append(state.get_actuator_configurations())
+ insight_elements.append(
+ S2DdbcInsightsProfile.Element(
+ timestep.get_avg_demand_rate_forecast(),
+ state.get_supply_rate(),
+ state.get_actuator_configurations(),
+ )
+ )
+ else:
+ energy.append(0)
+ actuators.append({})
+ insight_elements.append(None)
+ else:
+ energy.append(0)
+ actuators.append({})
+ insight_elements.append(None)
+
+ energy[0] = energy[0] + int(self.device_state.get_energy_in_current_timestep())
+
+ energy_profile = JouleProfile(
+ self.profile_metadata.profile_start,
+ self.profile_metadata.timestep_duration,
+ cast(List[Optional[int]], energy),
+ )
+
+ return S2DdbcPlan(
+ idle=False,
+ energy=energy_profile,
+ operation_mode_id=actuators,
+ s2_ddbc_insights_profile=S2DdbcInsightsProfile(
+ self.profile_metadata, insight_elements
+ ),
+ )
+
+ def get_timestep_duration_seconds(self) -> int:
+ return self.timestep_duration_seconds
diff --git a/flexmeasures_s2/profile_steering/device_planner/ddbc/ddbc_state.py b/flexmeasures_s2/profile_steering/device_planner/ddbc/ddbc_state.py
new file mode 100644
index 0000000..7c6b72c
--- /dev/null
+++ b/flexmeasures_s2/profile_steering/device_planner/ddbc/ddbc_state.py
@@ -0,0 +1,415 @@
+from datetime import datetime
+from typing import Optional, Dict, TYPE_CHECKING
+from flexmeasures_s2.profile_steering.common.target_profile import TargetProfile
+from flexmeasures_s2.profile_steering.device_planner.ddbc.s2_ddbc_actuator_configuration import (
+ S2DdbcActuatorConfiguration,
+)
+
+if TYPE_CHECKING:
+ from flexmeasures_s2.profile_steering.device_planner.ddbc.s2_ddbc_device_state_wrapper import (
+ S2DdbcDeviceStateWrapper,
+ )
+ from flexmeasures_s2.profile_steering.device_planner.ddbc.ddbc_timestep import (
+ DdbcTimestep,
+ )
+
+
+class DdbcState:
+ """Represents a state in the DDBC planning algorithm."""
+
+ CONSTRAINT_EPSILON = 1e-4
+
+ def __init__(
+ self,
+ device_state_wrapper: "S2DdbcDeviceStateWrapper",
+ timestep: "DdbcTimestep",
+ previous_state: Optional["DdbcState"] = None,
+ actuator_configurations: Optional[
+ Dict[str, S2DdbcActuatorConfiguration]
+ ] = None,
+ ):
+ """Initialize state. If previous_state is None, this is state zero."""
+ self.device_state_wrapper = device_state_wrapper
+ self.timestep = timestep
+ self.previous_state = previous_state
+
+ if previous_state is None:
+ self._init_state_zero()
+ else:
+ assert (
+ actuator_configurations is not None
+ ), "actuator_configurations must not be None when previous_state is provided"
+ self._init_from_previous_state(actuator_configurations)
+
+ def _init_state_zero(self):
+ """Initialize as state zero (initial state)."""
+ self.supply_rate = 0.0
+ self.timestep_energy = 0.0
+ self.sum_squared_distance = 0.0
+ self.sum_squared_constraint_violation = 0.0
+ self.sum_energy_cost = 0.0
+ self.sum_natural_gas_cost = 0.0
+ self.sum_squared_energy = 0.0
+
+ self.actuator_configurations: Dict[str, S2DdbcActuatorConfiguration] = {}
+
+ actuator_statuses = self.device_state_wrapper.get_actuator_statuses()
+
+ for actuator in self.timestep.get_system_description().actuators:
+ actuator_id_str = str(actuator.id)
+ actuator_status = actuator_statuses.get(actuator_id_str)
+
+ if actuator_status is None:
+ continue
+
+ # Convert active_operation_mode_id to string to handle UUID objects
+ active_om_id = actuator_status.active_operation_mode_id
+ if hasattr(active_om_id, "root"):
+ active_om_id_str = str(active_om_id.root)
+ else:
+ active_om_id_str = str(active_om_id)
+
+ om = self.device_state_wrapper.get_operation_mode(
+ self.timestep, actuator_id_str, active_om_id_str
+ )
+
+ factor = float(actuator_status.operation_mode_factor)
+ actuator_config = om.convert_to_actuator_config(factor)
+ self.actuator_configurations[actuator_id_str] = actuator_config
+
+ self.timer_elapse_map = (
+ self._get_initial_timer_elapse_map_for_system_description(
+ self.timestep.get_system_description()
+ )
+ )
+ self.gas_price_per_liter = (
+ self.device_state_wrapper.get_gas_price_per_m3() / 1000.0
+ )
+
+ def _init_from_previous_state(
+ self, actuator_configurations: Dict[str, S2DdbcActuatorConfiguration]
+ ):
+ """Initialize from a previous state."""
+ assert self.previous_state is not None, "previous_state must not be None"
+ self.gas_price_per_liter = self.previous_state.gas_price_per_liter
+
+ self.actuator_configurations = self._calculate_missing_factor(
+ actuator_configurations, self.timestep.get_avg_demand_rate_forecast()
+ )
+
+ timestep_energy = 0.0
+ timestep_natural_gas_liters = 0.0
+ timestep_supply_rate = 0.0
+ seconds = self.timestep.get_duration_seconds()
+
+ for actuator_id, actuator_configuration in self.actuator_configurations.items():
+ om = self.device_state_wrapper.get_operation_mode(
+ self.timestep,
+ actuator_id,
+ actuator_configuration.get_operation_mode_id(),
+ )
+ timestep_supply_rate += om.get_operation_mode_supply_rate(
+ actuator_configuration.get_factor()
+ )
+ timestep_energy += (
+ om.get_operation_mode_electrical_power(
+ actuator_configuration.get_factor()
+ )
+ * seconds
+ )
+ timestep_natural_gas_liters += (
+ om.get_operation_mode_gas_consumption(
+ actuator_configuration.get_factor()
+ )
+ * seconds
+ )
+
+ self.timestep_energy = timestep_energy
+ self.supply_rate = timestep_supply_rate
+
+ self._update_timer_elapse_map()
+ self._calculate_scores(timestep_natural_gas_liters)
+
+ self.timestep.add_state(self)
+
+ def _update_timer_elapse_map(self):
+ """Update timers based on transitions."""
+ if (
+ self.previous_state.get_system_description().valid_from
+ == self.get_system_description().valid_from
+ ):
+ self.timer_elapse_map = dict(self.previous_state.timer_elapse_map)
+
+ for actuator_id, actuator_config in self.actuator_configurations.items():
+ previous_operation_mode_id = (
+ self.previous_state.actuator_configurations[
+ actuator_id
+ ].get_operation_mode_id()
+ )
+ new_operation_mode_id = actuator_config.get_operation_mode_id()
+
+ if previous_operation_mode_id != new_operation_mode_id:
+ from flexmeasures_s2.profile_steering.device_planner.ddbc.s2_ddbc_device_state_wrapper import (
+ S2DdbcDeviceStateWrapper,
+ )
+
+ transition = S2DdbcDeviceStateWrapper.get_transition(
+ self.timestep,
+ actuator_id,
+ previous_operation_mode_id,
+ new_operation_mode_id,
+ )
+
+ for timer_id in transition.start_timers:
+ duration = S2DdbcDeviceStateWrapper.get_timer_duration(
+ self.timestep, actuator_id, timer_id
+ )
+ new_finished_at = self.timestep.get_start_date() + duration
+ self.timer_elapse_map[
+ self._timer_key(actuator_id, timer_id)
+ ] = new_finished_at
+ else:
+ self.timer_elapse_map = (
+ self._get_initial_timer_elapse_map_for_system_description(
+ self.get_system_description()
+ )
+ )
+
+ def _calculate_scores(self, timestep_natural_gas_liters: float):
+ """Calculate scoring metrics for this state."""
+ assert self.previous_state is not None, "previous_state must not be None"
+ target = self.timestep.get_target()
+
+ if isinstance(target, TargetProfile.JouleElement):
+ self.sum_squared_distance = (
+ self.previous_state.sum_squared_distance
+ + (target.joules - self.timestep_energy) ** 2
+ )
+ self.sum_energy_cost = self.previous_state.sum_energy_cost
+ elif isinstance(target, TargetProfile.TariffElement):
+ self.sum_squared_distance = self.previous_state.sum_squared_distance
+ self.sum_energy_cost = (
+ self.previous_state.sum_energy_cost
+ + target.tariff * (self.timestep_energy / 3_600_000.0)
+ )
+ else:
+ self.sum_squared_distance = self.previous_state.sum_squared_distance
+ self.sum_energy_cost = self.previous_state.sum_energy_cost
+
+ squared_constraint_violation = (
+ self.previous_state.sum_squared_constraint_violation
+ )
+
+ if self.timestep.get_max_constraint() is not None:
+ if self.timestep_energy > self.timestep.get_max_constraint():
+ squared_constraint_violation += (
+ self.timestep_energy - self.timestep.get_max_constraint()
+ ) ** 2
+
+ if self.timestep.get_min_constraint() is not None:
+ if self.timestep_energy < self.timestep.get_min_constraint():
+ squared_constraint_violation += (
+ self.timestep.get_min_constraint() - self.timestep_energy
+ ) ** 2
+
+ self.sum_natural_gas_cost = self.previous_state.sum_natural_gas_cost + (
+ timestep_natural_gas_liters * self.gas_price_per_liter
+ )
+ self.sum_squared_constraint_violation = squared_constraint_violation
+ self.sum_squared_energy = (
+ self.previous_state.sum_squared_energy + self.timestep_energy**2
+ )
+
+ def _calculate_missing_factor(
+ self,
+ actuator_configurations: Dict[str, S2DdbcActuatorConfiguration],
+ desired_supply_rate: Optional[float],
+ ) -> Dict[str, S2DdbcActuatorConfiguration]:
+ """Calculate the missing factor (if any) and return a new map with the factor filled in."""
+ if desired_supply_rate is None:
+ return actuator_configurations
+
+ missing_factor_actuator_id = None
+
+ for actuator_id, config in actuator_configurations.items():
+ if (
+ config.get_factor() is None
+ and self.device_state_wrapper.operation_mode_uses_factor(
+ self.timestep, actuator_id, config.get_operation_mode_id()
+ )
+ ):
+ missing_factor_actuator_id = actuator_id
+ break
+
+ if missing_factor_actuator_id is None:
+ return actuator_configurations
+
+ supply_rate = 0.0
+ for actuator_id, actuator_configuration in actuator_configurations.items():
+ if actuator_id != missing_factor_actuator_id:
+ om = self.device_state_wrapper.get_operation_mode(
+ self.timestep,
+ actuator_id,
+ actuator_configuration.get_operation_mode_id(),
+ )
+ supply_rate += om.get_operation_mode_supply_rate(
+ actuator_configuration.get_factor()
+ )
+
+ missing_supply_rate = desired_supply_rate - supply_rate
+ om = self.device_state_wrapper.get_operation_mode(
+ self.timestep,
+ missing_factor_actuator_id,
+ actuator_configurations[missing_factor_actuator_id].get_operation_mode_id(),
+ )
+ supply_range = om.get_supply_range()
+ calculated_factor = (
+ missing_supply_rate - supply_range.get_start_of_range()
+ ) / (supply_range.get_end_of_range() - supply_range.get_start_of_range())
+
+ calculated_factor = min(1.0, max(0.0, calculated_factor))
+
+ result = dict(actuator_configurations)
+ result[missing_factor_actuator_id] = om.convert_to_actuator_config(
+ calculated_factor
+ )
+ return result
+
+ @staticmethod
+ def _timer_key(actuator_id: str, timer_id: str) -> str:
+ """Generate a key for timer elapse map."""
+ return f"{actuator_id}-{timer_id}"
+
+ @staticmethod
+ def _get_initial_timer_elapse_map_for_system_description(
+ system_description,
+ ) -> Dict[str, datetime]:
+ """Get initial timer elapse map for a system description."""
+ timer_elapse_map = {}
+
+ for actuator in system_description.actuators:
+ for timer in actuator.timers:
+ timer_elapse_map[
+ DdbcState._timer_key(actuator.id, timer.id)
+ ] = timer.finished_at
+
+ return timer_elapse_map
+
+ @staticmethod
+ def try_create_next_state(
+ previous_state: "DdbcState",
+ target_timestep: "DdbcTimestep",
+ actuator_configs_for_target_timestep: Dict[str, S2DdbcActuatorConfiguration],
+ ) -> bool:
+ """Try to create a next state. Returns True if successful, False if constraints are violated."""
+ from flexmeasures_s2.profile_steering.device_planner.ddbc.s2_ddbc_device_state_wrapper import (
+ S2DdbcDeviceStateWrapper,
+ )
+
+ if (
+ previous_state.get_system_description().valid_from
+ == target_timestep.get_system_description().valid_from
+ ):
+ for actuator_id, config in actuator_configs_for_target_timestep.items():
+ previous_operation_mode_id = previous_state.actuator_configurations[
+ actuator_id
+ ].get_operation_mode_id()
+ new_operation_mode_id = config.get_operation_mode_id()
+
+ if previous_operation_mode_id != new_operation_mode_id:
+ transition = S2DdbcDeviceStateWrapper.get_transition(
+ target_timestep,
+ actuator_id,
+ previous_operation_mode_id,
+ new_operation_mode_id,
+ )
+
+ if transition is None:
+ return False
+
+ for timer_id in transition.blocking_timers:
+ timer_is_finished_at = previous_state.timer_elapse_map.get(
+ DdbcState._timer_key(actuator_id, timer_id)
+ )
+ if (
+ timer_is_finished_at is not None
+ and target_timestep.get_start_date() < timer_is_finished_at
+ ):
+ return False
+
+ DdbcState(
+ previous_state.device_state_wrapper,
+ target_timestep,
+ previous_state,
+ actuator_configs_for_target_timestep,
+ )
+ return True
+
+ def generate_next_timestep_states(self, target_timestep: "DdbcTimestep"):
+ """Generate all possible states for the next timestep."""
+ all_actions = (
+ self.device_state_wrapper.get_all_possible_actuator_configurations(
+ target_timestep
+ )
+ )
+ for action_map in all_actions:
+ self.try_create_next_state(self, target_timestep, action_map)
+
+ def is_preferable_than(self, other: "DdbcState") -> bool:
+ """Determine if this state is better than another state."""
+ if (
+ abs(
+ self.sum_squared_constraint_violation
+ - other.sum_squared_constraint_violation
+ )
+ >= self.CONSTRAINT_EPSILON
+ ):
+ return (
+ self.sum_squared_constraint_violation
+ < other.sum_squared_constraint_violation
+ )
+ else:
+ if self.sum_squared_distance != 0:
+ raise RuntimeError("Expect distance 0 as only tariff elements")
+
+ if (
+ abs(self.sum_squared_distance - other.sum_squared_distance)
+ >= self.CONSTRAINT_EPSILON
+ ):
+ return self.sum_squared_distance < other.sum_squared_distance
+ else:
+ total_cost = self.sum_energy_cost + self.sum_natural_gas_cost
+ other_total_cost = other.sum_energy_cost + other.sum_natural_gas_cost
+
+ if abs(total_cost - other_total_cost) >= self.CONSTRAINT_EPSILON:
+ return total_cost < other_total_cost
+ else:
+ return self.sum_squared_energy > other.sum_squared_energy
+
+ def supply_demand_distance(self) -> float:
+ """Calculate distance between supply and demand."""
+ return abs(self.supply_rate - self.timestep.get_avg_demand_rate_forecast())
+
+ def get_timestep(self) -> "DdbcTimestep":
+ return self.timestep
+
+ def get_system_description(self):
+ return self.timestep.get_system_description()
+
+ def get_device_state_wrapper(self) -> "S2DdbcDeviceStateWrapper":
+ return self.device_state_wrapper
+
+ def get_previous_state(self) -> Optional["DdbcState"]:
+ return self.previous_state
+
+ def get_actuator_configurations(self) -> Dict[str, S2DdbcActuatorConfiguration]:
+ return self.actuator_configurations
+
+ def get_supply_rate(self) -> float:
+ return self.supply_rate
+
+ def get_timestep_energy(self) -> float:
+ return self.timestep_energy
+
+ def get_timer_elapse_map(self) -> Dict[str, datetime]:
+ return self.timer_elapse_map
diff --git a/flexmeasures_s2/profile_steering/device_planner/ddbc/ddbc_timestep.py b/flexmeasures_s2/profile_steering/device_planner/ddbc/ddbc_timestep.py
new file mode 100644
index 0000000..76cb259
--- /dev/null
+++ b/flexmeasures_s2/profile_steering/device_planner/ddbc/ddbc_timestep.py
@@ -0,0 +1,104 @@
+from datetime import datetime, timedelta
+from typing import Optional, Any, List, TYPE_CHECKING
+from flexmeasures_s2.profile_steering.common.target_profile import TargetProfile
+
+if TYPE_CHECKING:
+ from flexmeasures_s2.profile_steering.device_planner.ddbc.ddbc_state import (
+ DdbcState,
+ )
+
+
+class DdbcTimestep:
+ """Represents a single timestep in DDBC planning."""
+
+ DISTANCE_EPSILON = 500.0 # 500 W tolerance (5% of 10kW typical system)
+
+ def __init__(
+ self,
+ start_date: datetime,
+ end_date: datetime,
+ system_description: Any,
+ avg_demand_rate_forecast: Optional[float],
+ ):
+ self.start_date = start_date
+ self.end_date = end_date
+ self.system_description = system_description
+ self.avg_demand_rate_forecast = avg_demand_rate_forecast
+
+ self.target: Optional[TargetProfile.Element] = None
+ self.min_constraint: Optional[int] = None
+ self.max_constraint: Optional[int] = None
+
+ self.best_state: Optional["DdbcState"] = None
+ self.emergency_state: Optional["DdbcState"] = None
+
+ def set_targets(
+ self,
+ target: TargetProfile.Element,
+ min_constraint: Optional[int],
+ max_constraint: Optional[int],
+ ):
+ """Set the target and constraints for this timestep."""
+ self.target = target
+ self.min_constraint = min_constraint
+ self.max_constraint = max_constraint
+
+ def add_state(self, state: "DdbcState"):
+ """Add a state to this timestep and track the best state."""
+ distance = state.supply_demand_distance()
+
+ if distance < self.DISTANCE_EPSILON:
+ if self.best_state is None:
+ self.best_state = state
+ elif state.is_preferable_than(self.best_state):
+ self.best_state = state
+ else:
+ if (
+ self.emergency_state is None
+ or distance < self.emergency_state.supply_demand_distance()
+ ):
+ self.emergency_state = state
+
+ def add_all_states(self, states: List["DdbcState"]):
+ """Add multiple states to this timestep."""
+ for state in states:
+ self.add_state(state)
+
+ def get_best_state(self) -> Optional["DdbcState"]:
+ """Get the best state for this timestep, or emergency state if no best state exists."""
+ if self.best_state is None:
+ return self.emergency_state
+ else:
+ return self.best_state
+
+ def get_start_date(self) -> datetime:
+ return self.start_date
+
+ def get_end_date(self) -> datetime:
+ return self.end_date
+
+ def get_system_description(self) -> Any:
+ return self.system_description
+
+ def get_duration(self) -> timedelta:
+ return self.end_date - self.start_date
+
+ def get_duration_seconds(self) -> int:
+ return int(self.get_duration().total_seconds())
+
+ def get_target(self) -> Optional[TargetProfile.Element]:
+ return self.target
+
+ def get_min_constraint(self) -> Optional[int]:
+ return self.min_constraint
+
+ def get_max_constraint(self) -> Optional[int]:
+ return self.max_constraint
+
+ def get_avg_demand_rate_forecast(self) -> Optional[float]:
+ return self.avg_demand_rate_forecast
+
+ def clear(self):
+ """Clear the states for this timestep."""
+ self.best_state = None
+ self.emergency_state = None
diff --git a/flexmeasures_s2/profile_steering/device_planner/ddbc/number_range_wrapper.py b/flexmeasures_s2/profile_steering/device_planner/ddbc/number_range_wrapper.py
new file mode 100644
index 0000000..7c32c79
--- /dev/null
+++ b/flexmeasures_s2/profile_steering/device_planner/ddbc/number_range_wrapper.py
@@ -0,0 +1,49 @@
+import math
+from typing import Any, Optional
+
+
+class NumberRangeWrapper:
+ """Wrapper for CommonNumberRange to provide utility methods."""
+
+ def __init__(
+ self,
+ unwrapped_range: Any = None,
+ start_of_range: Optional[float] = None,
+ end_of_range: Optional[float] = None,
+ ):
+ """Initialize from either an unwrapped range object or explicit values."""
+ if unwrapped_range is not None:
+ self.start_of_range: float = float(unwrapped_range.start_of_range)
+ self.end_of_range: float = float(unwrapped_range.end_of_range)
+ else:
+ assert (
+ start_of_range is not None and end_of_range is not None
+ ), "start_of_range and end_of_range must be provided if unwrapped_range is None"
+ self.start_of_range = start_of_range
+ self.end_of_range = end_of_range
+
+ def get_start_of_range(self) -> float:
+ return self.start_of_range
+
+ def get_end_of_range(self) -> float:
+ return self.end_of_range
+
+ def __eq__(self, o: object) -> bool:
+ if self is o:
+ return True
+ if not isinstance(o, NumberRangeWrapper):
+ return False
+ that = o
+ return math.isclose(that.start_of_range, self.start_of_range) and math.isclose(
+ that.end_of_range, self.end_of_range
+ )
+
+ def __hash__(self) -> int:
+ return hash((self.start_of_range, self.end_of_range))
+
+ def __str__(self) -> str:
+ return (
+ f"NumberRangeWrapper("
+ f"startOfRange={self.start_of_range}, "
+ f"endOfRange={self.end_of_range})"
+ )
diff --git a/flexmeasures_s2/profile_steering/device_planner/ddbc/s2_ddbc_actuator_configuration.py b/flexmeasures_s2/profile_steering/device_planner/ddbc/s2_ddbc_actuator_configuration.py
new file mode 100644
index 0000000..1c406c4
--- /dev/null
+++ b/flexmeasures_s2/profile_steering/device_planner/ddbc/s2_ddbc_actuator_configuration.py
@@ -0,0 +1,51 @@
+from typing import Dict, Optional
+from flexmeasures_s2.profile_steering.common.s2_actuator_configuration import (
+ S2ActuatorConfiguration,
+)
+
+
+class S2DdbcActuatorConfiguration(S2ActuatorConfiguration):
+ """Configuration for a DDBC actuator including power per commodity and supply rate."""
+
+ def __init__(
+ self,
+ operation_mode_id: str,
+ factor: Optional[float],
+ supply_rate: Optional[float],
+ power_per_commodity_quantity: Dict[str, float],
+ ):
+ super().__init__(operation_mode_id, factor)
+ self.supply_rate = supply_rate
+ self.power_per_commodity_quantity = power_per_commodity_quantity
+
+ def get_supply_rate(self) -> Optional[float]:
+ return self.supply_rate
+
+ def get_power_per_commodity_quantity(self) -> Dict[str, float]:
+ return self.power_per_commodity_quantity
+
+ def to_dict(self) -> Dict:
+ return {
+ "operationModeId": self.operation_mode_id,
+ "factor": self.factor,
+ "supplyRate": self.supply_rate,
+ "powerPerCommodityQuantity": self.power_per_commodity_quantity,
+ }
+
+ @staticmethod
+ def from_dict(data: Dict):
+ return S2DdbcActuatorConfiguration(
+ data["operationModeId"],
+ data.get("factor"),
+ data.get("supplyRate"),
+ data.get("powerPerCommodityQuantity", {}),
+ )
+
+ def __str__(self):
+ return (
+ f"S2DdbcActuatorConfiguration["
+ f"operationModeId={self.operation_mode_id}, "
+ f"factor={self.factor}, "
+ f"supplyRate={self.supply_rate}, "
+ f"powerPerCommodityQuantity={self.power_per_commodity_quantity}]"
+ )
diff --git a/flexmeasures_s2/profile_steering/device_planner/ddbc/s2_ddbc_device_planner.py b/flexmeasures_s2/profile_steering/device_planner/ddbc/s2_ddbc_device_planner.py
new file mode 100644
index 0000000..9120d83
--- /dev/null
+++ b/flexmeasures_s2/profile_steering/device_planner/ddbc/s2_ddbc_device_planner.py
@@ -0,0 +1,230 @@
+from datetime import datetime
+from typing import Optional
+import logging
+
+from flexmeasures_s2.profile_steering.common.joule_profile import JouleProfile
+from flexmeasures_s2.profile_steering.common.proposal import Proposal
+from flexmeasures_s2.profile_steering.common.target_profile import TargetProfile
+from flexmeasures_s2.profile_steering.device_planner.ddbc.s2_ddbc_plan import S2DdbcPlan
+from flexmeasures_s2.profile_steering.device_planner.ddbc.s2_ddbc_instruction_profile import (
+ S2DdbcInstructionProfile,
+)
+from flexmeasures_s2.profile_steering.common.profile_metadata import ProfileMetadata
+from flexmeasures_s2.profile_steering.common.device_plan import DevicePlan
+from flexmeasures_s2.profile_steering.device_planner.ddbc.ddbc_planning_window import (
+ DdbcPlanningWindow,
+)
+from flexmeasures_s2.profile_steering.device_planner.ddbc.s2_ddbc_device_state import (
+ S2DdbcDeviceState,
+)
+from flexmeasures_s2.profile_steering.device_planner.device_planner_abstract import (
+ DevicePlanner,
+)
+
+
+class S2DdbcDevicePlanner(DevicePlanner):
+ """Device planner for Demand-Driven Based Control (DDBC) devices."""
+
+ STRATIFICATION_LAYERS = 30 # Number of stratification layers (matching FRBC)
+
+ def __init__(
+ self,
+ s2_ddbc_state: S2DdbcDeviceState,
+ profile_metadata: ProfileMetadata,
+ plan_due_by_date: datetime,
+ congestion_point_id: str,
+ ):
+ self._congestion_point_id = congestion_point_id
+ self.s2_ddbc_state = s2_ddbc_state
+ self.profile_metadata = profile_metadata
+ self.zero_profile = JouleProfile(
+ profile_metadata.profile_start,
+ profile_metadata.timestep_duration,
+ [0] * profile_metadata.nr_of_timesteps,
+ )
+ self.null_profile = JouleProfile(
+ profile_metadata.profile_start,
+ profile_metadata.timestep_duration,
+ elements=[None] * profile_metadata.nr_of_timesteps,
+ )
+
+ self.state_tree: Optional[DdbcPlanningWindow]
+ if self._is_device_available(self.s2_ddbc_state):
+ self.state_tree = DdbcPlanningWindow(
+ self.s2_ddbc_state,
+ profile_metadata,
+ plan_due_by_date,
+ )
+ else:
+ self.state_tree = None
+
+ self._priority_class = s2_ddbc_state.get_priority_class()
+ self.latest_plan: Optional[S2DdbcPlan] = None
+ self.accepted_plan: Optional[S2DdbcPlan] = None
+
+ def _is_device_available(self, ddbc_state: S2DdbcDeviceState) -> bool:
+ """Check if the device is available for planning."""
+ return ddbc_state.is_device_online()
+
+ @property
+ def priority_class(self) -> int:
+ return self._priority_class
+
+ @property
+ def device_id(self) -> str:
+ return self.s2_ddbc_state.get_device_id()
+
+ @property
+ def connection_id(self) -> str:
+ return self.s2_ddbc_state.get_connection_id()
+
+ @property
+ def congestion_point_id(self) -> str:
+ return self._congestion_point_id
+
+ @property
+ def device_name(self) -> str:
+ return self.s2_ddbc_state.get_device_name()
+
+ def create_improved_planning(
+ self,
+ diff_to_global_target: TargetProfile,
+ diff_to_max: JouleProfile,
+ diff_to_min: JouleProfile,
+ plan_due_by_date: datetime,
+ ) -> Proposal:
+ """Create an improved planning based on the difference to global target."""
+ if self.accepted_plan is None:
+ raise ValueError("No accepted plan found")
+
+ target = diff_to_global_target.add(self.accepted_plan.get_energy())
+ max_profile = diff_to_max.add(self.accepted_plan.get_energy())
+ min_profile = diff_to_min.add(self.accepted_plan.get_energy())
+
+ if (
+ self._is_device_available(self.s2_ddbc_state)
+ and self.state_tree is not None
+ ):
+ self.latest_plan = self.state_tree.find_best_plan(
+ target, min_profile, max_profile
+ )
+ else:
+ self.latest_plan = S2DdbcPlan(
+ idle=True,
+ energy=self.zero_profile,
+ operation_mode_id=None,
+ s2_ddbc_insights_profile=None,
+ )
+
+ if self.latest_plan is None:
+ raise ValueError("No latest plan found")
+
+ proposal = Proposal(
+ global_diff_target=diff_to_global_target,
+ diff_to_congestion_max=diff_to_max,
+ diff_to_congestion_min=diff_to_min,
+ proposed_plan=self.latest_plan.get_energy(),
+ old_plan=self.accepted_plan.get_energy(),
+ origin=self,
+ )
+ return proposal
+
+ def create_initial_planning(self, plan_due_by_date: datetime) -> S2DdbcPlan:
+ """Create an initial planning."""
+ if (
+ self._is_device_available(self.s2_ddbc_state)
+ and self.state_tree is not None
+ ):
+ self.latest_plan = self.state_tree.find_best_plan(
+ TargetProfile.null_profile(self.profile_metadata),
+ self.null_profile,
+ self.null_profile,
+ )
+ else:
+ self.latest_plan = S2DdbcPlan(
+ idle=True,
+ energy=self.zero_profile,
+ operation_mode_id=None,
+ s2_ddbc_insights_profile=None,
+ )
+ self.accepted_plan = self.latest_plan
+ return self.latest_plan
+
+ def accept_proposal(self, accepted_proposal: Proposal) -> None:
+ """Accept a proposal."""
+ if self.latest_plan is None:
+ raise ValueError("No latest plan found")
+ if accepted_proposal.origin != self:
+ raise ValueError(
+ f"Device controller '{self.device_id}' received a proposal that it did not send."
+ )
+ if not accepted_proposal.proposed_plan == self.latest_plan.get_energy():
+ raise ValueError(
+ f"Device controller '{self.device_id}' received a proposal that it did not send."
+ )
+ if accepted_proposal.get_congestion_improvement_value() < 0:
+ raise ValueError(
+ f"Device controller '{self.device_id}' received a proposal with negative improvement"
+ )
+ self.accepted_plan = self.latest_plan
+
+ def get_latest_plan(self) -> Optional[S2DdbcPlan]:
+ """Get the latest plan."""
+ return self.latest_plan
+
+ def set_accepted_plan(self, plan: S2DdbcPlan) -> None:
+ """Set the accepted plan."""
+ if not isinstance(plan, S2DdbcPlan):
+ raise TypeError(f"Expected S2DdbcPlan, but got {type(plan)}")
+ self.accepted_plan = plan
+
+ def current_profile(self) -> JouleProfile:
+ """Get the current profile."""
+ if self.accepted_plan is None:
+ raise ValueError("No accepted plan found")
+ return self.accepted_plan.get_energy()
+
+ def get_device_plan(self) -> Optional[DevicePlan]:
+ """Get the device plan."""
+ if self.accepted_plan is None:
+ return None
+
+ logging.debug(
+ dict(
+ device_id=self.device_id,
+ device_name=self.device_name,
+ connection_id=self.connection_id,
+ energy_profile=self.accepted_plan.get_energy(),
+ instruction_profile=self.convert_plan_to_instructions(
+ self.profile_metadata, self.accepted_plan
+ ),
+ )
+ )
+ return DevicePlan(
+ device_id=self.device_id,
+ device_name=self.device_name,
+ connection_id=self.connection_id,
+ energy_profile=self.accepted_plan.get_energy(),
+ fill_level_profile=None,
+ instruction_profile=self.convert_plan_to_instructions(
+ self.profile_metadata, self.accepted_plan
+ ),
+ insights_profile=self.accepted_plan.get_s2_ddbc_insights_profile(),
+ )
+
+ @staticmethod
+ def convert_plan_to_instructions(
+ profile_metadata: ProfileMetadata, device_plan: S2DdbcPlan
+ ) -> S2DdbcInstructionProfile:
+ """Convert a plan to instruction profile."""
+ elements = []
+ actuator_configurations_per_timestep = device_plan.get_operation_mode_id()
+
+ if actuator_configurations_per_timestep is not None:
+ for actuator_configurations in actuator_configurations_per_timestep:
+ new_element = S2DdbcInstructionProfile.Element(
+ not actuator_configurations, actuator_configurations
+ )
+ elements.append(new_element)
+
+ return S2DdbcInstructionProfile(profile_metadata, elements)
diff --git a/flexmeasures_s2/profile_steering/device_planner/ddbc/s2_ddbc_device_state.py b/flexmeasures_s2/profile_steering/device_planner/ddbc/s2_ddbc_device_state.py
new file mode 100644
index 0000000..ca7d9d8
--- /dev/null
+++ b/flexmeasures_s2/profile_steering/device_planner/ddbc/s2_ddbc_device_state.py
@@ -0,0 +1,76 @@
+from datetime import datetime
+from typing import List, Optional, Any, Dict
+
+
+class S2DdbcDeviceState:
+ """Device state for Demand-Driven Based Control (DDBC)."""
+
+ DEFAULT_GAS_PRICE_PER_M3 = 2.0
+
+ def __init__(
+ self,
+ device_id: str,
+ device_name: str,
+ connection_id: str,
+ priority_class: int,
+ timestamp: datetime,
+ energy_in_current_timestep: float,
+ is_online: bool,
+ power_forecast: Optional[Any],
+ system_descriptions: List[Any],
+ demand_forecasts: List[Any],
+ actuator_statuses: Optional[Dict[str, Any]] = None,
+ gas_price_per_m3: Optional[float] = None,
+ ):
+ self.device_id = device_id
+ self.device_name = device_name
+ self.connection_id = connection_id
+ self.priority_class = priority_class
+ self.timestamp = timestamp
+ self.energy_in_current_timestep = energy_in_current_timestep
+ self.is_online = is_online
+ self.power_forecast = power_forecast
+ self.system_descriptions = system_descriptions
+ self.demand_forecasts = demand_forecasts
+ self.actuator_statuses = actuator_statuses or {}
+ self.gas_price_per_m3 = (
+ gas_price_per_m3
+ if gas_price_per_m3 is not None
+ else self.DEFAULT_GAS_PRICE_PER_M3
+ )
+
+ def get_device_id(self) -> str:
+ return self.device_id
+
+ def get_device_name(self) -> str:
+ return self.device_name
+
+ def get_connection_id(self) -> str:
+ return self.connection_id
+
+ def get_priority_class(self) -> int:
+ return self.priority_class
+
+ def get_timestamp(self) -> datetime:
+ return self.timestamp
+
+ def get_energy_in_current_timestep(self) -> float:
+ return self.energy_in_current_timestep
+
+ def is_device_online(self) -> bool:
+ return self.is_online
+
+ def get_power_forecast(self) -> Optional[Any]:
+ return self.power_forecast
+
+ def get_system_descriptions(self) -> List[Any]:
+ return self.system_descriptions
+
+ def get_demand_forecasts(self) -> List[Any]:
+ return self.demand_forecasts
+
+ def get_gas_price_per_m3(self) -> float:
+ return self.gas_price_per_m3
+
+ def get_actuator_statuses(self) -> Dict[str, Any]:
+ return self.actuator_statuses
diff --git a/flexmeasures_s2/profile_steering/device_planner/ddbc/s2_ddbc_device_state_wrapper.py b/flexmeasures_s2/profile_steering/device_planner/ddbc/s2_ddbc_device_state_wrapper.py
new file mode 100644
index 0000000..72d7506
--- /dev/null
+++ b/flexmeasures_s2/profile_steering/device_planner/ddbc/s2_ddbc_device_state_wrapper.py
@@ -0,0 +1,412 @@
+from datetime import datetime, timedelta
+from typing import List, Dict, Set, Optional, Any, TYPE_CHECKING
+from flexmeasures_s2.profile_steering.device_planner.ddbc.s2_ddbc_device_state import (
+ S2DdbcDeviceState,
+)
+from flexmeasures_s2.profile_steering.device_planner.ddbc.s2_ddbc_actuator_configuration import (
+ S2DdbcActuatorConfiguration,
+)
+from flexmeasures_s2.profile_steering.device_planner.ddbc.ddbc_operation_mode_wrapper import (
+ DdbcOperationModeWrapper,
+)
+
+if TYPE_CHECKING:
+ from flexmeasures_s2.profile_steering.device_planner.ddbc.ddbc_timestep import (
+ DdbcTimestep,
+ )
+
+
+class S2DdbcDeviceStateWrapper:
+ """
+ Wrapper for S2DdbcDeviceState that adds utility functions and caching.
+ """
+
+ EPSILON = 1e-4
+ STRATIFICATION_LAYERS = 30 # Number of stratification layers (matching FRBC)
+
+ def __init__(self, device_state: S2DdbcDeviceState):
+ self.device_state = device_state
+
+ self.actuator_operation_mode_map_per_timestep: Dict[
+ datetime, Dict[str, List[str]]
+ ] = {}
+ self.all_actions: Dict[
+ datetime, List[Dict[str, S2DdbcActuatorConfiguration]]
+ ] = {}
+ self.operation_mode_uses_factor_map: Dict[str, bool] = {}
+ self.operation_modes: Dict[str, DdbcOperationModeWrapper] = {}
+
+ def is_online(self) -> bool:
+ return self.device_state.is_device_online()
+
+ def get_power_forecast(self) -> Any:
+ return self.device_state.get_power_forecast()
+
+ def get_system_descriptions(self) -> List[Any]:
+ return self.device_state.get_system_descriptions()
+
+ def get_demand_forecasts(self) -> List[Any]:
+ return self.device_state.get_demand_forecasts()
+
+ def get_energy_in_current_timestep(self) -> float:
+ return self.device_state.get_energy_in_current_timestep()
+
+ def get_gas_price_per_m3(self) -> float:
+ return self.device_state.get_gas_price_per_m3()
+
+ def get_actuator_statuses(self):
+ return self.device_state.get_actuator_statuses()
+
+ def get_actuators(self, target_timestep: "DdbcTimestep") -> Set[str]:
+ """Get all actuator IDs for a timestep."""
+ actuator_operation_mode_map = self.actuator_operation_mode_map_per_timestep.get(
+ target_timestep.get_start_date()
+ )
+ if actuator_operation_mode_map is None:
+ actuator_operation_mode_map = self._create_actuator_operation_mode_map(
+ target_timestep
+ )
+ return set(actuator_operation_mode_map.keys())
+
+ def get_normal_operation_modes_for_actuator(
+ self, target_timestep: "DdbcTimestep", actuator_id: str
+ ) -> List[str]:
+ """Get all normal operation mode IDs for an actuator."""
+ actuator_operation_mode_map = self.actuator_operation_mode_map_per_timestep.get(
+ target_timestep.get_start_date()
+ )
+ if actuator_operation_mode_map is None:
+ actuator_operation_mode_map = self._create_actuator_operation_mode_map(
+ target_timestep
+ )
+ return actuator_operation_mode_map[actuator_id]
+
+ def _create_actuator_operation_mode_map(
+ self, target_timestep: "DdbcTimestep"
+ ) -> Dict[str, List[str]]:
+ """Create a map from actuator ID to list of normal operation mode IDs."""
+ actuator_operation_mode_map: Dict[str, List[str]] = {}
+
+ for actuator in target_timestep.get_system_description().actuators:
+ # Convert both actuator ID and operation mode IDs to strings
+ operation_mode_ids = [
+ str(om.id)
+ for om in actuator.operation_modes
+ if not om.abnormal_condition_only
+ ]
+ actuator_operation_mode_map[str(actuator.id)] = operation_mode_ids
+
+ self.actuator_operation_mode_map_per_timestep[
+ target_timestep.get_start_date()
+ ] = actuator_operation_mode_map
+ return actuator_operation_mode_map
+
+ def get_operation_mode(
+ self, target_timestep: "DdbcTimestep", actuator_id: str, operation_mode_id: str
+ ) -> DdbcOperationModeWrapper:
+ """Get an operation mode wrapper."""
+
+ # Handle UUID objects with root attribute - extract the actual UUID value
+ if hasattr(operation_mode_id, "root"):
+ # Extract the root attribute and convert to string
+ operation_mode_id = str(operation_mode_id.root)
+ elif not isinstance(operation_mode_id, str):
+ operation_mode_id = str(operation_mode_id)
+ # If it's already a string, use it as is
+
+ om_key = f"{actuator_id}-{operation_mode_id}"
+
+ if om_key in self.operation_modes:
+ return self.operation_modes[om_key]
+
+ found_actuator_description = None
+ system_actuators = target_timestep.get_system_description().actuators
+
+ for actuator_description in system_actuators:
+ actuator_desc_id_str = str(actuator_description.id)
+ if (
+ actuator_desc_id_str == actuator_id
+ or actuator_description.id == actuator_id
+ ):
+ found_actuator_description = actuator_description
+ break
+
+ if found_actuator_description is None:
+ raise ValueError(f"Actuator {actuator_id} not found")
+
+ found_om_ids = []
+ for idx, operation_mode in enumerate(
+ found_actuator_description.operation_modes
+ ):
+ # Try to get the operation mode ID from multiple sources
+ operation_mode_id_obj = None
+ if hasattr(operation_mode, "Id"):
+ operation_mode_id_obj = operation_mode.Id
+ elif hasattr(operation_mode, "id"):
+ operation_mode_id_obj = operation_mode.id
+ else:
+ operation_mode_id_obj = None
+
+ # Convert to string for comparison
+ if operation_mode_id_obj is not None and hasattr(
+ operation_mode_id_obj, "root"
+ ):
+ operation_mode_id_str = str(operation_mode_id_obj.root)
+ elif operation_mode_id_obj is not None and hasattr(
+ operation_mode_id_obj, "hex"
+ ):
+ operation_mode_id_str = str(operation_mode_id_obj)
+ else:
+ operation_mode_id_str = str(operation_mode_id_obj)
+
+ found_om_ids.append(operation_mode_id_str)
+
+ if operation_mode_id_str == operation_mode_id:
+ found_operation_mode = DdbcOperationModeWrapper(operation_mode)
+ self.operation_modes[om_key] = found_operation_mode
+ return found_operation_mode
+
+ raise ValueError(
+ f"Operation mode '{operation_mode_id}' (type: {type(operation_mode_id)}) not found for actuator {actuator_id}. "
+ f"Available operation modes: {found_om_ids}. "
+ f"Comparison details: searching='{operation_mode_id}' vs found={found_om_ids}"
+ )
+
+ def operation_mode_uses_factor(
+ self, target_timestep: "DdbcTimestep", actuator_id: str, operation_mode_id: str
+ ) -> bool:
+ """Check if an operation mode uses a factor."""
+ # Handle UUID objects with root attribute
+ if hasattr(operation_mode_id, "root"):
+ operation_mode_id = str(operation_mode_id.root)
+ else:
+ operation_mode_id = str(operation_mode_id)
+
+ key = f"{actuator_id}-{operation_mode_id}"
+ result_from_map = self.operation_mode_uses_factor_map.get(key)
+
+ if result_from_map is None:
+ result = self.get_operation_mode(
+ target_timestep, actuator_id, operation_mode_id
+ ).uses_factor_method()
+ self.operation_mode_uses_factor_map[key] = result
+ return result
+ else:
+ return result_from_map
+
+ def get_all_possible_actuator_configurations(
+ self, target_timestep: "DdbcTimestep"
+ ) -> List[Dict[str, S2DdbcActuatorConfiguration]]:
+ """Get all possible actuator configurations for a timestep."""
+ timestep_date = target_timestep.get_start_date()
+
+ if timestep_date in self.all_actions:
+ return self.all_actions[timestep_date]
+
+ possible_actuator_configs: Dict[str, List[S2DdbcActuatorConfiguration]] = {}
+
+ for actuator_id in self.get_actuators(target_timestep):
+ actuator_list: List[S2DdbcActuatorConfiguration] = []
+
+ for operation_mode_id in self.get_normal_operation_modes_for_actuator(
+ target_timestep, actuator_id
+ ):
+ if self.operation_mode_uses_factor(
+ target_timestep, actuator_id, operation_mode_id
+ ):
+ actuator_list.append(
+ S2DdbcActuatorConfiguration(operation_mode_id, None, None, {})
+ )
+ else:
+ om = self.get_operation_mode(
+ target_timestep, actuator_id, operation_mode_id
+ )
+ actuator_list.append(om.convert_to_actuator_config(0.0))
+
+ possible_actuator_configs[actuator_id] = actuator_list
+
+ keys = list(possible_actuator_configs.keys())
+ operation_mode_configurations: List[Dict[str, S2DdbcActuatorConfiguration]] = []
+
+ combination = [0] * len(keys)
+ operation_mode_configurations.append(
+ self._combination_to_map(combination, keys, possible_actuator_configs)
+ )
+
+ while self._increase(combination, keys, possible_actuator_configs):
+ operation_mode_configurations.append(
+ self._combination_to_map(combination, keys, possible_actuator_configs)
+ )
+
+ finished = False
+ strat_iter_count = 0
+ # Safety limit to prevent infinite loops in stratification
+ MAX_STRATIFICATION_ITERATIONS = 1000
+
+ while not finished and strat_iter_count < MAX_STRATIFICATION_ITERATIONS:
+ found_multiple = False
+ for i in range(len(operation_mode_configurations)):
+ c = operation_mode_configurations[i]
+ if self._contains_multiple_oms_with_factor(c, target_timestep):
+ operation_mode_configurations.pop(i)
+ new_configs = self._apply_stratification_layers(c, target_timestep)
+ operation_mode_configurations.extend(new_configs)
+ found_multiple = True
+ break
+ finished = not found_multiple
+ strat_iter_count += 1
+
+ self.all_actions[timestep_date] = operation_mode_configurations
+ return operation_mode_configurations
+
+ def _apply_stratification_layers(
+ self,
+ configurations: Dict[str, S2DdbcActuatorConfiguration],
+ target_timestep: "DdbcTimestep",
+ ) -> List[Dict[str, S2DdbcActuatorConfiguration]]:
+ """Apply stratification layers to configurations with factors."""
+ actuator_id = None
+ operation_mode_id = None
+ copy = dict(configurations)
+
+ for config_actuator_id, config in list(copy.items()):
+ if self.operation_mode_uses_factor(
+ target_timestep, config_actuator_id, config.get_operation_mode_id()
+ ):
+ actuator_id = config_actuator_id
+ operation_mode_id = config.get_operation_mode_id()
+ del copy[config_actuator_id]
+ break
+
+ if actuator_id is None or operation_mode_id is None:
+ raise ValueError("No actuator with factor found")
+
+ configs: List[Dict[str, S2DdbcActuatorConfiguration]] = []
+ om = self.get_operation_mode(target_timestep, actuator_id, operation_mode_id)
+
+ # If stratification is disabled, just use factor 0.0
+ if self.STRATIFICATION_LAYERS == 0:
+ config_actuator_map = dict(copy)
+ config_actuator_map[actuator_id] = om.convert_to_actuator_config(0.0)
+ configs.append(config_actuator_map)
+ else:
+ for i in range(self.STRATIFICATION_LAYERS + 1):
+ config_actuator_map = dict(copy)
+ factor_for_actuator = i * (1.0 / self.STRATIFICATION_LAYERS)
+ config_actuator_map[actuator_id] = om.convert_to_actuator_config(
+ factor_for_actuator
+ )
+ configs.append(config_actuator_map)
+
+ return configs
+
+ def _contains_multiple_oms_with_factor(
+ self,
+ configurations: Dict[str, S2DdbcActuatorConfiguration],
+ target_timestep: "DdbcTimestep",
+ ) -> bool:
+ """Check if configurations contain multiple operation modes with factors."""
+ found_one = False
+ for actuator_id, config in configurations.items():
+ if self.operation_mode_uses_factor(
+ target_timestep, actuator_id, config.get_operation_mode_id()
+ ):
+ if found_one:
+ return True
+ else:
+ found_one = True
+ return False
+
+ def _combination_to_map(
+ self,
+ cur: List[int],
+ keys: List[str],
+ possible_actuator_configs: Dict[str, List[S2DdbcActuatorConfiguration]],
+ ) -> Dict[str, S2DdbcActuatorConfiguration]:
+ """Convert combination indices to a configuration map."""
+ combination: Dict[str, S2DdbcActuatorConfiguration] = {}
+ for i in range(len(keys)):
+ key = keys[i]
+ combination[key] = possible_actuator_configs[key][cur[i]]
+ return combination
+
+ def _increase(
+ self,
+ cur: List[int],
+ keys: List[str],
+ possible_actuator_configs: Dict[str, List[S2DdbcActuatorConfiguration]],
+ ) -> bool:
+ """Increase combination indices."""
+ cur[0] += 1
+ for i in range(len(keys)):
+ key = keys[i]
+ if cur[i] >= len(possible_actuator_configs[key]):
+ if i + 1 >= len(keys):
+ return False
+ cur[i] = 0
+ cur[i + 1] += 1
+ return True
+
+ @staticmethod
+ def get_transition(
+ target_timestep: "DdbcTimestep",
+ actuator_id: str,
+ from_operation_mode_id: str,
+ to_operation_mode_id: str,
+ ) -> Optional[Any]:
+ """Get a transition between operation modes."""
+ actuator_description = S2DdbcDeviceStateWrapper.get_actuator_description(
+ target_timestep, actuator_id
+ )
+
+ found_transition = None
+ for transition in actuator_description.transitions:
+ if (
+ getattr(transition, "from", None) == from_operation_mode_id
+ and transition.to == to_operation_mode_id
+ ):
+ found_transition = transition
+ break
+
+ return found_transition
+
+ @staticmethod
+ def get_timer_duration_milliseconds(
+ target_timestep: "DdbcTimestep", actuator_id: str, timer_id: str
+ ) -> int:
+ """Get timer duration in milliseconds."""
+ actuator_description = S2DdbcDeviceStateWrapper.get_actuator_description(
+ target_timestep, actuator_id
+ )
+ timer = next(t for t in actuator_description.timers if t.id == timer_id)
+ return timer.duration
+
+ @staticmethod
+ def get_timer_duration(
+ target_timestep: "DdbcTimestep", actuator_id: str, timer_id: str
+ ) -> timedelta:
+ """Get timer duration as timedelta."""
+ return timedelta(
+ milliseconds=S2DdbcDeviceStateWrapper.get_timer_duration_milliseconds(
+ target_timestep, actuator_id, timer_id
+ )
+ )
+
+ @staticmethod
+ def get_actuator_description(
+ target_timestep: "DdbcTimestep", actuator_id: str
+ ) -> Any:
+ """Get actuator description by ID."""
+ found_actuator_description = None
+ for actuator_description in target_timestep.get_system_description().actuators:
+ if (
+ str(actuator_description.id) == actuator_id
+ or actuator_description.id == actuator_id
+ ):
+ found_actuator_description = actuator_description
+ break
+
+ if found_actuator_description is None:
+ raise ValueError(f"Actuator {actuator_id} not found")
+
+ return found_actuator_description
diff --git a/flexmeasures_s2/profile_steering/device_planner/ddbc/s2_ddbc_insights_profile.py b/flexmeasures_s2/profile_steering/device_planner/ddbc/s2_ddbc_insights_profile.py
new file mode 100644
index 0000000..1f40881
--- /dev/null
+++ b/flexmeasures_s2/profile_steering/device_planner/ddbc/s2_ddbc_insights_profile.py
@@ -0,0 +1,40 @@
+from typing import List, Dict, Optional, Any
+from flexmeasures_s2.profile_steering.common.profile_metadata import ProfileMetadata
+
+
+class S2DdbcInsightsProfile:
+ """Insights profile for DDBC device providing additional information about the plan."""
+
+ class Element:
+ """Single element in the insights profile."""
+
+ def __init__(
+ self,
+ demand_rate_forecast: Optional[float],
+ supply_rate: float,
+ actuator_configurations: Dict[str, Any],
+ ):
+ self.demand_rate_forecast = demand_rate_forecast
+ self.supply_rate = supply_rate
+ self.actuator_configurations = actuator_configurations
+
+ def get_demand_rate_forecast(self) -> Optional[float]:
+ return self.demand_rate_forecast
+
+ def get_supply_rate(self) -> float:
+ return self.supply_rate
+
+ def get_actuator_configurations(self) -> Dict[str, Any]:
+ return self.actuator_configurations
+
+ def __init__(
+ self, profile_metadata: ProfileMetadata, elements: List[Optional[Element]]
+ ):
+ self.profile_metadata = profile_metadata
+ self.elements = elements
+
+ def get_profile_metadata(self) -> ProfileMetadata:
+ return self.profile_metadata
+
+ def get_elements(self) -> List[Optional[Element]]:
+ return self.elements
diff --git a/flexmeasures_s2/profile_steering/device_planner/ddbc/s2_ddbc_instruction_profile.py b/flexmeasures_s2/profile_steering/device_planner/ddbc/s2_ddbc_instruction_profile.py
new file mode 100644
index 0000000..d188d27
--- /dev/null
+++ b/flexmeasures_s2/profile_steering/device_planner/ddbc/s2_ddbc_instruction_profile.py
@@ -0,0 +1,29 @@
+from typing import List, Dict, Any
+from flexmeasures_s2.profile_steering.common.profile_metadata import ProfileMetadata
+
+
+class S2DdbcInstructionProfile:
+ """Instruction profile for DDBC device."""
+
+ class Element:
+ """Single instruction element."""
+
+ def __init__(self, is_empty: bool, actuator_configurations: Dict[str, Any]):
+ self.is_empty = is_empty
+ self.actuator_configurations = actuator_configurations
+
+ def is_element_empty(self) -> bool:
+ return self.is_empty
+
+ def get_actuator_configurations(self) -> Dict[str, Any]:
+ return self.actuator_configurations
+
+ def __init__(self, profile_metadata: ProfileMetadata, elements: List[Element]):
+ self.profile_metadata = profile_metadata
+ self.elements = elements
+
+ def get_profile_metadata(self) -> ProfileMetadata:
+ return self.profile_metadata
+
+ def get_elements(self) -> List[Element]:
+ return self.elements
diff --git a/flexmeasures_s2/profile_steering/device_planner/ddbc/s2_ddbc_plan.py b/flexmeasures_s2/profile_steering/device_planner/ddbc/s2_ddbc_plan.py
new file mode 100644
index 0000000..8f270ff
--- /dev/null
+++ b/flexmeasures_s2/profile_steering/device_planner/ddbc/s2_ddbc_plan.py
@@ -0,0 +1,35 @@
+from typing import List, Dict, Optional, Any, TYPE_CHECKING
+from flexmeasures_s2.profile_steering.common.joule_profile import JouleProfile
+
+if TYPE_CHECKING:
+ from flexmeasures_s2.profile_steering.device_planner.ddbc.s2_ddbc_insights_profile import (
+ S2DdbcInsightsProfile,
+ )
+
+
+class S2DdbcPlan:
+ """Plan for a DDBC device."""
+
+ def __init__(
+ self,
+ idle: bool,
+ energy: JouleProfile,
+ operation_mode_id: Optional[List[Dict[str, Any]]],
+ s2_ddbc_insights_profile: Optional["S2DdbcInsightsProfile"],
+ ):
+ self.idle = idle
+ self.energy = energy
+ self.operation_mode_id = operation_mode_id
+ self.s2_ddbc_insights_profile = s2_ddbc_insights_profile
+
+ def is_idle(self) -> bool:
+ return self.idle
+
+ def get_energy(self) -> JouleProfile:
+ return self.energy
+
+ def get_operation_mode_id(self) -> Optional[List[Dict[str, Any]]]:
+ return self.operation_mode_id
+
+ def get_s2_ddbc_insights_profile(self) -> Optional["S2DdbcInsightsProfile"]:
+ return self.s2_ddbc_insights_profile
diff --git a/flexmeasures_s2/profile_steering/device_planner/device_planner_abstract.py b/flexmeasures_s2/profile_steering/device_planner/device_planner_abstract.py
new file mode 100644
index 0000000..f9df657
--- /dev/null
+++ b/flexmeasures_s2/profile_steering/device_planner/device_planner_abstract.py
@@ -0,0 +1,109 @@
+from __future__ import annotations
+
+from abc import ABC, abstractmethod
+from datetime import datetime
+from typing import Any, Optional, TYPE_CHECKING
+
+if TYPE_CHECKING:
+ from flexmeasures_s2.profile_steering.common.proposal import Proposal
+from flexmeasures_s2.profile_steering.common.joule_profile import JouleProfile
+from flexmeasures_s2.profile_steering.common.device_plan import (
+ DevicePlan,
+)
+from flexmeasures_s2.profile_steering.common.target_profile import TargetProfile
+
+
+class DevicePlanner(ABC):
+ """Abstract base class for all device planners."""
+
+ @property
+ @abstractmethod
+ def priority_class(self) -> int:
+ """The priority class of the device."""
+ raise NotImplementedError
+
+ @property
+ @abstractmethod
+ def device_id(self) -> str:
+ """The device ID."""
+ raise NotImplementedError
+
+ @property
+ @abstractmethod
+ def device_name(self) -> str:
+ """The device name."""
+ raise NotImplementedError
+
+ @property
+ @abstractmethod
+ def connection_id(self) -> str:
+ """The connection ID."""
+ raise NotImplementedError
+
+ @property
+ @abstractmethod
+ def congestion_point_id(self) -> str:
+ """The congestion point ID this device belongs to."""
+ raise NotImplementedError
+
+ @abstractmethod
+ def create_improved_planning(
+ self,
+ difference_profile: TargetProfile,
+ diff_to_max_value: JouleProfile,
+ diff_to_min_value: JouleProfile,
+ plan_due_by_date: datetime,
+ ) -> Optional["Proposal"]:
+ """Create an improved planning profile based on the difference profile.
+
+ Args:
+ difference_profile: The difference profile for the cluster
+ diff_to_max_value: The difference to the maximum profile
+ diff_to_min_value: The difference to the minimum profile
+ plan_due_by_date: The date by which the plan must be ready
+
+ Returns:
+ A Proposal object if an improvement was found, None otherwise
+ """
+ pass
+
+ @abstractmethod
+ def create_initial_planning(self, plan_due_by_date: datetime) -> "Any":
+ """Create an initial planning profile.
+
+ Args:
+ plan_due_by_date: The date by which the plan must be ready
+
+ Returns:
+ A plan object for the device.
+ """
+ pass
+
+ @abstractmethod
+ def accept_proposal(self, proposal: "Proposal") -> None:
+ """This method is called when a proposal from this device is accepted by a higher-level controller.
+
+ Args:
+ proposal: The proposal that was accepted
+ """
+ pass
+
+ @abstractmethod
+ def current_profile(self) -> JouleProfile:
+ """Get the current profile of the device."""
+ pass
+
+ @abstractmethod
+ def get_device_plan(self) -> Optional[DevicePlan]:
+ """Get the device plan."""
+ pass
+
+ @abstractmethod
+ def get_latest_plan(self) -> Any:
+ """Get the latest calculated plan, which may not have been accepted yet."""
+ pass
+
+ @abstractmethod
+ def set_accepted_plan(self, plan: Any):
+ """Forcefully set the accepted plan for the device."""
+ pass
diff --git a/flexmeasures_s2/profile_steering/device_planner/frbc/fill_level_target_util.py b/flexmeasures_s2/profile_steering/device_planner/frbc/fill_level_target_util.py
new file mode 100644
index 0000000..1a453d8
--- /dev/null
+++ b/flexmeasures_s2/profile_steering/device_planner/frbc/fill_level_target_util.py
@@ -0,0 +1,69 @@
+from datetime import timedelta, timezone
+
+
+class FillLevelTargetElement:
+ def __init__(self, start, end, lower_limit, upper_limit):
+ self.start = start
+ self.end = end
+ self.lower_limit = lower_limit
+ self.upper_limit = upper_limit
+
+ def split(self, date):
+ if self.date_in_element(date):
+ return [
+ FillLevelTargetElement(
+ self.start, date, self.lower_limit, self.upper_limit
+ ),
+ FillLevelTargetElement(
+ date, self.end, self.lower_limit, self.upper_limit
+ ),
+ ]
+ return []
+
+ def date_in_element(self, date):
+ return self.start <= date <= self.end
+
+
+class FillLevelTargetUtil:
+ @staticmethod
+ def from_fill_level_target_profile(fill_level_target_profile):
+ elements = []
+ start = fill_level_target_profile.start_time.astimezone(timezone.utc)
+ for element in fill_level_target_profile.elements:
+ # Extract duration value - handle both int and Duration object
+ duration = element.duration
+ if isinstance(duration, (int, float)):
+ duration_ms = int(duration)
+ elif hasattr(duration, "root"):
+ duration_ms = int(duration.root)
+ elif hasattr(duration, "__root__"):
+ duration_ms = int(duration.__root__)
+ else:
+ try:
+ duration_ms = int(duration)
+ except (TypeError, ValueError):
+ duration_ms = int(str(duration))
+
+ end = start + timedelta(milliseconds=duration_ms)
+ elements.append(
+ FillLevelTargetElement(
+ start,
+ end,
+ element.fill_level_range.start_of_range,
+ element.fill_level_range.end_of_range,
+ )
+ )
+ start = end + timedelta(milliseconds=1)
+ return elements
+
+ @staticmethod
+ def get_elements_in_range(target_profile, start, end):
+ elements_in_range = []
+ start = start.replace(tzinfo=None)
+ end = end.replace(tzinfo=None)
+ for element in target_profile:
+ element_start = element.start.replace(tzinfo=None)
+ element_end = element.end.replace(tzinfo=None)
+ if element_start <= end and element_end >= start:
+ elements_in_range.append(element)
+ return elements_in_range
diff --git a/flexmeasures_s2/profile_steering/device_planner/frbc/frbc_operation_mode_wrapper.py b/flexmeasures_s2/profile_steering/device_planner/frbc/frbc_operation_mode_wrapper.py
new file mode 100644
index 0000000..ecdcbbc
--- /dev/null
+++ b/flexmeasures_s2/profile_steering/device_planner/frbc/frbc_operation_mode_wrapper.py
@@ -0,0 +1,98 @@
+import numpy as np
+from typing import List, Optional
+
+from flexmeasures_s2.profile_steering.common.power_range_wrapper import (
+ PowerRangeWrapper,
+)
+from flexmeasures_s2.profile_steering.s2_utils.number_range_wrapper import (
+ NumberRangeWrapper,
+)
+from s2python.frbc import FRBCOperationMode, FRBCOperationModeElement
+
+
+class FrbcOperationModeElementWrapper:
+ def __init__(self, element: FRBCOperationModeElement):
+ self.fill_level_range = NumberRangeWrapper(
+ element.fill_level_range.start_of_range,
+ element.fill_level_range.end_of_range,
+ )
+ self.fill_rate = NumberRangeWrapper(
+ element.fill_rate.start_of_range, element.fill_rate.end_of_range
+ )
+ self.power_ranges: List[PowerRangeWrapper] = [
+ PowerRangeWrapper(pr) for pr in element.power_ranges
+ ]
+
+ if element.running_costs is None:
+ self.running_costs: Optional[NumberRangeWrapper] = None
+ else:
+ self.running_costs = NumberRangeWrapper(
+ element.running_costs.start_of_range,
+ element.running_costs.end_of_range,
+ )
+
+ def __eq__(self, o: object) -> bool:
+ if not isinstance(o, FrbcOperationModeElementWrapper):
+ return False
+ return (
+ self.fill_level_range == o.fill_level_range
+ and self.fill_rate == o.fill_rate
+ and self.power_ranges == o.power_ranges
+ and self.running_costs == o.running_costs
+ )
+
+ def __hash__(self) -> int:
+ return hash(
+ (
+ self.fill_level_range,
+ self.fill_rate,
+ tuple(self.power_ranges),
+ self.running_costs,
+ )
+ )
+
+
+class FrbcOperationModeWrapper:
+ def __init__(self, frbc_operation_mode: FRBCOperationMode):
+ self.id = frbc_operation_mode.id
+ self.diagnostic_label = frbc_operation_mode.diagnostic_label
+ self.abnormal_condition_only = frbc_operation_mode.abnormal_condition_only
+ self.elements = [
+ FrbcOperationModeElementWrapper(element)
+ for element in frbc_operation_mode.elements
+ ]
+ self.uses_factor = self.calculate_uses_factor()
+
+ # Pre-process elements into NumPy arrays for vectorization
+ if self.elements:
+ self.fill_level_starts = np.array(
+ [e.fill_level_range.start_of_range for e in self.elements]
+ )
+ self.fill_level_ends = np.array(
+ [e.fill_level_range.end_of_range for e in self.elements]
+ )
+ else:
+ self.fill_level_starts = np.array([])
+ self.fill_level_ends = np.array([])
+
+ def calculate_uses_factor(self) -> bool:
+ from flexmeasures_s2.profile_steering.device_planner.frbc.s2_frbc_device_state_wrapper import (
+ S2FrbcDeviceStateWrapper,
+ )
+
+ for element in self.elements:
+ if (
+ abs(element.fill_rate.start_of_range - element.fill_rate.end_of_range)
+ > S2FrbcDeviceStateWrapper.epsilon
+ ):
+ return True
+ for power_range in element.power_ranges:
+ if (
+ abs(power_range.start_of_range - power_range.end_of_range)
+ > S2FrbcDeviceStateWrapper.epsilon
+ ):
+ return True
+ return False
+
+ def is_uses_factor(self) -> bool:
+ return self.uses_factor
diff --git a/flexmeasures_s2/profile_steering/device_planner/frbc/frbc_state.py b/flexmeasures_s2/profile_steering/device_planner/frbc/frbc_state.py
new file mode 100644
index 0000000..bbe2c4a
--- /dev/null
+++ b/flexmeasures_s2/profile_steering/device_planner/frbc/frbc_state.py
@@ -0,0 +1,494 @@
+from datetime import datetime
+from typing import Dict, Optional, Any, Tuple
+import logging
+
+# from flask import current_app as app
+from s2python.frbc import (
+ FRBCSystemDescription,
+)
+
+from flexmeasures_s2.profile_steering.common.target_profile import TargetProfile
+import flexmeasures_s2.profile_steering.device_planner.frbc.frbc_timestep as frbc_timestep
+import flexmeasures_s2.profile_steering.device_planner.frbc.s2_frbc_device_state_wrapper as s2_frbc_device_state_wrapper
+
+from flexmeasures_s2.profile_steering.device_planner.frbc.s2_frbc_actuator_configuration import (
+ S2ActuatorConfiguration,
+)
+from flexmeasures_s2.profile_steering.device_planner.frbc.s2_frbc_device_state import (
+ S2FrbcDeviceState,
+)
+from flexmeasures_s2.profile_steering.device_planner.frbc.selection_reason_result import (
+ SelectionResult,
+ SelectionReason,
+)
+
+logger = logging.getLogger(__name__)
+
+
+class FrbcState:
+ constraint_epsilon = 1e-4
+ tariff_epsilon = 0.5
+ transition_cache: Dict[Any, Any] = {}
+
+ def __init__( # noqa: C901
+ self,
+ timestep: frbc_timestep.FrbcTimestep,
+ device_state: Optional[S2FrbcDeviceState] = None,
+ previous_state: Optional["FrbcState"] = None,
+ actuator_configurations: Optional[Dict[str, S2ActuatorConfiguration]] = None,
+ initial_fill_level: float = 0.0,
+ ):
+ self.fill_level = initial_fill_level
+ self.timer_elapse_map: Dict[tuple, datetime]
+ self.actuator_configurations: Dict[str, S2ActuatorConfiguration]
+ self.sum_squared_distance: float
+ self.sum_energy_cost: float
+ self.sum_squared_constraint_violation: float
+ self.sum_squared_energy: float
+ if previous_state:
+ if device_state:
+ self.device_state = (
+ s2_frbc_device_state_wrapper.S2FrbcDeviceStateWrapper(device_state)
+ )
+ else:
+ self.device_state = previous_state.device_state
+ self.timestep = timestep
+ self.previous_state = previous_state
+ self.system_description = timestep.system_description
+ self.bucket = 0
+ self.timestep_energy = 0.0
+ seconds = self.timestep.get_duration_seconds()
+ for actuator_id, actuator_configuration in (
+ actuator_configurations or {}
+ ).items():
+ power, fill_rate = self._calculate_and_cache_op_mode_values(
+ previous_state,
+ actuator_id,
+ actuator_configuration,
+ )
+
+ self.timestep_energy += power * seconds
+ self.fill_level += fill_rate * seconds
+
+ leakage_rate = (
+ s2_frbc_device_state_wrapper.S2FrbcDeviceStateWrapper.get_leakage_rate(
+ self.timestep, self.fill_level
+ )
+ )
+ leakage = leakage_rate * seconds
+ self.fill_level -= leakage
+ self.fill_level += self.timestep.forecasted_fill_level_usage
+
+ self.bucket = (
+ s2_frbc_device_state_wrapper.S2FrbcDeviceStateWrapper.calculate_bucket(
+ self.timestep, self.fill_level
+ )
+ )
+ if (
+ previous_state.system_description.valid_from
+ == self.system_description.valid_from
+ ):
+ self.timer_elapse_map = previous_state.timer_elapse_map.copy()
+ for (
+ actuator_id,
+ actuator_configuration,
+ ) in (actuator_configurations or {}).items():
+ previous_operation_mode_id = previous_state.actuator_configurations[
+ actuator_id
+ ].operation_mode_id
+ new_operation_mode_id = actuator_configuration.operation_mode_id
+ if previous_operation_mode_id != new_operation_mode_id:
+ transition = s2_frbc_device_state_wrapper.S2FrbcDeviceStateWrapper.get_transition(
+ self.timestep,
+ actuator_id,
+ previous_operation_mode_id,
+ new_operation_mode_id,
+ )
+ last_timer_id = None
+ new_finished_at: datetime = datetime.min
+ if transition is not None:
+ for timer_id in transition.start_timers:
+ duration = s2_frbc_device_state_wrapper.S2FrbcDeviceStateWrapper.get_timer_duration(
+ self.timestep, actuator_id, str(timer_id)
+ )
+ new_finished_at = self.timestep.start_date + duration
+ last_timer_id = timer_id
+ if last_timer_id is not None:
+ key = FrbcState.timer_key(
+ actuator_id, str(last_timer_id)
+ )
+ self.timer_elapse_map[key] = new_finished_at
+ else:
+ self.timer_elapse_map = (
+ self.get_initial_timer_elapse_map_for_system_description(
+ self.system_description
+ )
+ )
+ # calculate scores
+ target = self.timestep.get_target()
+ if isinstance(target, TargetProfile.JouleElement):
+ self.sum_squared_distance = (
+ previous_state.sum_squared_distance
+ + (target.joules - self.timestep_energy) ** 2
+ )
+ self.sum_energy_cost = previous_state.sum_energy_cost
+ elif isinstance(target, TargetProfile.TariffElement):
+ self.sum_squared_distance = previous_state.sum_squared_distance
+ # Convert Joules to kWh before multiplying by tariff
+ # 1 kWh = 3,600,000 Joules
+ energy_kwh = self.timestep_energy / 3_600_000
+ cost_increment = target.tariff * energy_kwh
+ self.sum_energy_cost = previous_state.sum_energy_cost + cost_increment
+ else:
+ self.sum_squared_distance = previous_state.sum_squared_distance
+ self.sum_energy_cost = previous_state.sum_energy_cost
+
+ squared_constraint_violation = (
+ previous_state.sum_squared_constraint_violation
+ )
+ if self.timestep.max_constraint is not None:
+ if self.timestep_energy > self.timestep.max_constraint:
+ squared_constraint_violation += (
+ self.timestep_energy - self.timestep.max_constraint
+ ) ** 2
+ if self.timestep.min_constraint is not None:
+ if self.timestep_energy < self.timestep.min_constraint:
+ squared_constraint_violation += (
+ self.timestep.min_constraint - self.timestep_energy
+ ) ** 2
+ self.sum_squared_constraint_violation = (
+ previous_state.sum_squared_constraint_violation
+ + squared_constraint_violation
+ )
+ self.sum_squared_energy = (
+ previous_state.sum_squared_energy + self.timestep_energy**2
+ )
+
+ self.selection_reason: Optional[SelectionReason] = None
+ self.actuator_configurations = {
+ **previous_state.actuator_configurations,
+ **(actuator_configurations or {}),
+ }
+
+ self.timestep.add_state(self)
+
+ else:
+ FrbcState.transition_cache.clear()
+ if device_state is not None:
+ self.device_state = (
+ s2_frbc_device_state_wrapper.S2FrbcDeviceStateWrapper(device_state)
+ )
+ else:
+ self.device_state = None # type: ignore[assignment]
+ self.timestep = timestep
+ self.previous_state = None # type: ignore[assignment]
+ self.system_description = timestep.system_description
+ self.fill_level = initial_fill_level
+ self.bucket = 0
+ self.timestep_energy = 0.0
+ self.sum_squared_distance = 0.0
+ self.sum_squared_constraint_violation = 0.0
+ self.sum_energy_cost = 0.0
+ self.sum_squared_energy = 0.0
+ self.selection_reason: Optional[SelectionReason] = None # type: ignore[no-redef]
+ self.actuator_configurations = {}
+ self.timer_elapse_map = (
+ self.get_initial_timer_elapse_map_for_system_description(
+ self.system_description
+ )
+ )
+ for a in self.device_state.actuator_statuses:
+ actuator_status = a
+ actuators = self.system_description.actuators
+ for actuator in actuators:
+ if actuator.id == a.actuator_id:
+ self.actuator_configurations[
+ str(a.actuator_id)
+ ] = S2ActuatorConfiguration(
+ str(actuator_status.active_operation_mode_id),
+ actuator_status.operation_mode_factor,
+ )
+
+ @staticmethod
+ def get_initial_timer_elapse_map_for_system_description(
+ system_description: FRBCSystemDescription,
+ ) -> Dict[tuple, datetime]:
+ timer_elapse_map = {}
+ for actuator in system_description.actuators:
+ for timer in actuator.timers:
+ key = FrbcState.timer_key(str(actuator.id), str(timer.id))
+ timer_elapse_map[key] = datetime.min # arbitrary day in the past
+ return timer_elapse_map
+
+ def calculate_state_values(
+ self,
+ previous_state: "FrbcState",
+ actuator_configurations: Dict[str, S2ActuatorConfiguration],
+ ):
+ self.timestep_energy = 0.0
+ self.fill_level = previous_state.fill_level
+ seconds = self.timestep.get_duration_seconds()
+ for actuator_id, actuator_configuration in actuator_configurations.items():
+ om = self.device_state.get_operation_mode(
+ self.timestep,
+ actuator_id,
+ actuator_configuration.operation_mode_id,
+ )
+ self.timestep_energy += (
+ self.device_state.get_operation_mode_power(
+ om,
+ previous_state.fill_level,
+ actuator_configuration.factor,
+ )
+ * seconds
+ )
+ self.fill_level += (
+ self.device_state.get_operation_mode_fill_rate(
+ om,
+ previous_state.fill_level,
+ actuator_configuration.factor,
+ )
+ * seconds
+ )
+ self.fill_level -= (
+ s2_frbc_device_state_wrapper.S2FrbcDeviceStateWrapper.get_leakage_rate(
+ self.timestep, self.fill_level
+ )
+ * seconds
+ )
+ self.fill_level += self.timestep.forecasted_fill_level_usage
+ self.bucket = (
+ s2_frbc_device_state_wrapper.S2FrbcDeviceStateWrapper.calculate_bucket(
+ self.timestep, self.fill_level
+ )
+ )
+ self.update_timers(previous_state, actuator_configurations)
+ self.calculate_scores(previous_state)
+ self.timestep.add_state(self)
+
+ def update_timers(
+ self,
+ previous_state: "FrbcState",
+ actuator_configurations: Dict[str, S2ActuatorConfiguration],
+ ):
+ if (
+ previous_state.system_description.valid_from
+ == self.system_description.valid_from
+ ):
+ self.timer_elapse_map = previous_state.timer_elapse_map.copy()
+ for actuator_id, actuator_configuration in actuator_configurations.items():
+ previous_operation_mode_id = previous_state.actuator_configurations[
+ actuator_id
+ ].operation_mode_id
+ new_operation_mode_id = actuator_configuration.operation_mode_id
+ if previous_operation_mode_id != new_operation_mode_id:
+ transition = s2_frbc_device_state_wrapper.S2FrbcDeviceStateWrapper.get_transition(
+ self.timestep,
+ actuator_id,
+ previous_operation_mode_id,
+ new_operation_mode_id,
+ )
+ if transition is None:
+ continue
+ for timer_id in transition.start_timers:
+ duration = s2_frbc_device_state_wrapper.S2FrbcDeviceStateWrapper.get_timer_duration(
+ self.timestep, actuator_id, str(timer_id)
+ )
+ new_finished_at = self.timestep.start_date + duration
+ key = FrbcState.timer_key(actuator_id, str(timer_id))
+ self.timer_elapse_map[key] = new_finished_at
+ else:
+ self.timer_elapse_map = (
+ self.get_initial_timer_elapse_map_for_system_description(
+ self.system_description
+ )
+ )
+
+ def calculate_scores(self, previous_state: "FrbcState"):
+ target = self.timestep.get_target()
+ if isinstance(target, TargetProfile.JouleElement):
+ self.sum_squared_distance = (
+ previous_state.sum_squared_distance
+ + (target.joules - self.timestep_energy) ** 2
+ )
+ self.sum_energy_cost = previous_state.sum_energy_cost
+
+ else:
+ self.sum_squared_distance = previous_state.sum_squared_distance
+ self.sum_energy_cost = previous_state.sum_energy_cost
+ squared_constraint_violation = previous_state.sum_squared_constraint_violation
+ if (
+ self.timestep.max_constraint is not None
+ and self.timestep_energy > self.timestep.max_constraint
+ ):
+ squared_constraint_violation += (
+ self.timestep_energy - self.timestep.max_constraint
+ ) ** 2
+ if (
+ self.timestep.min_constraint is not None
+ and self.timestep_energy < self.timestep.min_constraint
+ ):
+ squared_constraint_violation += (
+ self.timestep.min_constraint - self.timestep_energy
+ ) ** 2
+ self.sum_squared_constraint_violation = (
+ previous_state.sum_squared_constraint_violation
+ + squared_constraint_violation
+ )
+ self.sum_squared_energy = (
+ previous_state.sum_squared_energy + self.timestep_energy**2
+ )
+
+ @staticmethod
+ def timer_key(actuator_id: str, timer_id: str) -> tuple:
+ return (actuator_id, timer_id)
+
+ def generate_next_timestep_states(self, target_timestep):
+ all_actions = self.device_state.get_all_possible_actuator_configurations(
+ target_timestep
+ )
+ # todo: try vectorizing this
+ for action in all_actions:
+ self.try_create_next_state(self, target_timestep, action)
+
+ def _calculate_and_cache_op_mode_values(
+ self,
+ previous_state: "FrbcState",
+ actuator_id: str,
+ actuator_configuration: S2ActuatorConfiguration,
+ ) -> Tuple[float, float]:
+ discretized_fill_level = round(previous_state.fill_level, 4)
+ cache_key = (
+ actuator_id,
+ actuator_configuration.operation_mode_id,
+ actuator_configuration.factor,
+ discretized_fill_level,
+ )
+ if cache_key in FrbcState.transition_cache:
+ return FrbcState.transition_cache[cache_key]
+
+ om = self.device_state.get_operation_mode(
+ self.timestep,
+ actuator_id,
+ actuator_configuration.operation_mode_id,
+ )
+ power = self.device_state.get_operation_mode_power(
+ om,
+ previous_state.fill_level,
+ actuator_configuration.factor,
+ )
+ fill_rate = self.device_state.get_operation_mode_fill_rate(
+ om,
+ previous_state.fill_level,
+ actuator_configuration.factor,
+ )
+
+ FrbcState.transition_cache[cache_key] = (power, fill_rate)
+ return power, fill_rate
+
+ @staticmethod
+ def try_create_next_state(
+ previous_state: "FrbcState",
+ target_timestep,
+ actuator_configs_for_target_timestep: Dict[str, S2ActuatorConfiguration],
+ ):
+ if (
+ previous_state.system_description.valid_from
+ == target_timestep.system_description.valid_from
+ ):
+ for (
+ actuator_id,
+ actuator_configuration,
+ ) in actuator_configs_for_target_timestep.items():
+
+ # print all the keys in the previous_state.actuator_configurations
+ # print(previous_state.actuator_configurations.keys())
+ try:
+ previous_operation_mode_id = previous_state.actuator_configurations[
+ actuator_id
+ ].operation_mode_id
+ except KeyError:
+ # app.logger.debug(
+ # f"previous_state.actuator_configurations = {previous_state.actuator_configurations}"
+ # )
+ raise KeyError(
+ f"UUID {actuator_id} not in actuator configurations"
+ ) from None
+
+ new_operation_mode_id = actuator_configuration.operation_mode_id
+ if previous_operation_mode_id != new_operation_mode_id:
+ transition = s2_frbc_device_state_wrapper.S2FrbcDeviceStateWrapper.get_transition(
+ target_timestep,
+ actuator_id,
+ previous_operation_mode_id,
+ new_operation_mode_id,
+ )
+ if transition is None:
+ return False
+ for timer_id in transition.blocking_timers:
+ timer_is_finished_at = previous_state.timer_elapse_map.get(
+ FrbcState.timer_key(actuator_id, str(timer_id))
+ )
+ if (
+ timer_is_finished_at
+ and timer_is_finished_at.year != 1
+ and target_timestep.start_date
+ < timer_is_finished_at.astimezone(
+ target_timestep.start_date.tzinfo
+ )
+ ):
+ return False
+ FrbcState(
+ timestep=target_timestep,
+ previous_state=previous_state,
+ actuator_configurations=actuator_configs_for_target_timestep,
+ initial_fill_level=previous_state.fill_level,
+ )
+ return True
+
+ def is_preferable_than(self, other_state: "FrbcState") -> SelectionResult:
+ other_violation = other_state.sum_squared_constraint_violation
+ other_distance = other_state.sum_squared_distance
+ other_cost = other_state.sum_energy_cost
+ other_energy = other_state.sum_squared_energy
+
+ if (
+ abs(self.sum_squared_constraint_violation - other_violation)
+ >= self.constraint_epsilon
+ ):
+ return SelectionResult(
+ result=self.sum_squared_constraint_violation < other_violation,
+ reason=SelectionReason.CONGESTION_CONSTRAINT,
+ )
+ elif abs(self.sum_squared_distance - other_distance) >= self.constraint_epsilon:
+ return SelectionResult(
+ result=self.sum_squared_distance < other_distance,
+ reason=SelectionReason.ENERGY_TARGET,
+ )
+ elif abs(self.sum_energy_cost - other_cost) >= self.tariff_epsilon:
+ return SelectionResult(
+ result=self.sum_energy_cost < other_cost,
+ reason=SelectionReason.TARIFF_TARGET,
+ )
+ else:
+ return SelectionResult(
+ result=self.sum_squared_energy < other_energy,
+ reason=SelectionReason.MIN_ENERGY,
+ )
+
+ def is_within_fill_level_range(self):
+ fill_level_range = self.system_description.storage.fill_level_range
+ return (
+ self.fill_level >= fill_level_range.start_of_range
+ and self.fill_level <= fill_level_range.end_of_range
+ )
+
+ def get_fill_level_distance(self):
+ fill_level_range = self.system_description.storage.fill_level_range
+ if self.fill_level < fill_level_range.start_of_range:
+ return fill_level_range.start_of_range - self.fill_level
+ else:
+ return self.fill_level - fill_level_range.end_of_range
+
+ def set_selection_reason(self, selection_reason):
+ self.selection_reason = selection_reason
diff --git a/flexmeasures_s2/profile_steering/device_planner/frbc/frbc_timestep.py b/flexmeasures_s2/profile_steering/device_planner/frbc/frbc_timestep.py
new file mode 100644
index 0000000..3950276
--- /dev/null
+++ b/flexmeasures_s2/profile_steering/device_planner/frbc/frbc_timestep.py
@@ -0,0 +1,198 @@
+from datetime import datetime, timedelta
+from typing import List, Optional, Dict, Tuple, TYPE_CHECKING
+import logging
+
+from flexmeasures_s2.profile_steering.common.target_profile import TargetProfile
+from flexmeasures_s2.profile_steering.s2_utils.number_range_wrapper import (
+ NumberRangeWrapper,
+)
+from s2python.frbc import FRBCSystemDescription, FRBCLeakageBehaviour
+
+if TYPE_CHECKING:
+ from flexmeasures_s2.profile_steering.device_planner.frbc.frbc_state import (
+ FrbcState,
+ )
+
+from flexmeasures_s2.profile_steering.device_planner.frbc.s2_frbc_device_state import (
+ S2FrbcDeviceState,
+)
+from enum import Enum
+
+logger = logging.getLogger(__name__)
+
+
+class SelectionReason(Enum):
+ NO_ALTERNATIVE = "NA"
+ EMERGENCY_STATE = "ES"
+ CONGESTION_CONSTRAINT = "CC"
+ ENERGY_TARGET = "ET"
+ TARIFF_TARGET = "TT"
+ MIN_ENERGY = "ME"
+
+ @property
+ def abbr(self) -> str:
+ return self.value
+
+
+class FrbcTimestep:
+ def __init__(
+ self,
+ start_date: datetime,
+ end_date: datetime,
+ system_description: FRBCSystemDescription,
+ leakage_behaviour: FRBCLeakageBehaviour,
+ fill_level_target: Optional[NumberRangeWrapper],
+ forecasted_fill_level_usage: float,
+ computational_parameters: "S2FrbcDeviceState.ComputationalParameters",
+ **kwargs,
+ ):
+ self.system_description = system_description
+ self.computational_parameters = computational_parameters
+ self.leakage_behaviour = leakage_behaviour
+ self.forecasted_fill_level_usage = forecasted_fill_level_usage
+ self.fill_level_target = fill_level_target
+ self.previous_actuator_statuses: Dict[str, str] = kwargs.get(
+ "previous_actuator_statuses", {}
+ )
+ self.timers: Dict[str, timedelta] = kwargs.get("timers", {})
+ self._states_by_bucket: Dict[int, FrbcState] = {}
+ self.start_date = start_date
+ self.end_date = end_date
+ self.best_plan: Optional[FrbcState] = None
+ self.emergency_state: Optional[FrbcState] = None
+ self.target: Optional[TargetProfile.Element] = None
+ self.min_constraint: float = 0.0
+ self.max_constraint: float = 0.0
+
+ # Cache for bucket calculation
+ self._bucket_calc_params: Optional[Tuple[float, float, int]] = None
+
+ def get_bucket_calculation_params(self) -> Tuple[float, float, int]:
+ if self._bucket_calc_params is None:
+ fill_level_range = self.system_description.storage.fill_level_range
+ self._bucket_calc_params = (
+ fill_level_range.start_of_range,
+ fill_level_range.end_of_range,
+ self.get_nr_of_buckets(),
+ )
+ return self._bucket_calc_params
+
+ def get_nr_of_buckets(self) -> int:
+ if self.computational_parameters is None:
+ raise ValueError("Computational parameters are not set")
+ return self.computational_parameters.get_nr_of_buckets()
+
+ def set_targets(
+ self,
+ target: TargetProfile.Element,
+ min_constraint: float,
+ max_constraint: float,
+ ) -> None:
+ self.target = target
+ self.min_constraint = min_constraint
+ self.max_constraint = max_constraint
+
+ def add_state(self, state: Optional["FrbcState"]) -> None:
+ if state is None:
+ return
+
+ if state.is_within_fill_level_range():
+ bucket = state.bucket
+ stored_state = self._states_by_bucket.get(bucket)
+ if stored_state is None:
+ self._states_by_bucket[bucket] = state
+ state.set_selection_reason(SelectionReason.NO_ALTERNATIVE)
+ # logger.debug(
+ # f" New state in bucket {bucket}: fill_level={state.fill_level:.2f}, energy={state.timestep_energy:.0f}"
+ # )
+ else:
+ selection_result = state.is_preferable_than(stored_state)
+ if selection_result.result:
+ # logger.debug(
+ # f" Bucket {bucket}: Replacing state (fill_level={stored_state.fill_level:.2f}) with better state (fill_level={state.fill_level:.2f})"
+ # )
+ # logger.debug(
+ # f" Reason: {selection_result.reason.value if hasattr(selection_result.reason, 'value') else selection_result.reason}"
+ # )
+ self._states_by_bucket[bucket] = state
+ else:
+ # logger.debug(
+ # f" Bucket {bucket}: Keeping existing state (fill_level={stored_state.fill_level:.2f}) over new state (fill_level={state.fill_level:.2f})"
+ # )
+ pass
+ self._states_by_bucket[bucket].set_selection_reason(
+ selection_result.reason
+ )
+ else:
+ # logger.debug(
+ # f" State outside fill level range: fill_level={state.fill_level:.2f} (emergency state)"
+ # )
+ pass
+ if (
+ self.emergency_state is None
+ or state.get_fill_level_distance()
+ < self.emergency_state.get_fill_level_distance()
+ ):
+ self.emergency_state = state
+ state.set_selection_reason(SelectionReason.EMERGENCY_STATE)
+
+ def add_all_states(self, states: List[Optional["FrbcState"]]) -> None:
+ for state in states:
+ self.add_state(state)
+
+ @property
+ def duration(self) -> timedelta:
+ return self.end_date - self.start_date
+
+ def get_duration_seconds(self) -> int:
+ return int(self.duration.total_seconds())
+
+ def get_target(self) -> Optional[TargetProfile.Element]:
+ return self.target
+
+ def get_final_states(self) -> List["FrbcState"]:
+ final_states = list(self._states_by_bucket.values())
+
+ if not final_states and self.emergency_state is not None:
+ return [self.emergency_state]
+ return final_states
+
+ def get_final_states_within_fill_level_target(self) -> List["FrbcState"]:
+ final_states = self.get_final_states()
+ if self.fill_level_target is None:
+ return final_states
+ final_states = [
+ s for s in final_states if self.state_is_within_fill_level_target_range(s)
+ ]
+ if final_states:
+ return final_states
+ best_state = min(
+ self.get_final_states(), key=self.get_fill_level_target_distance
+ )
+ return [best_state]
+
+ def state_is_within_fill_level_target_range(self, state: "FrbcState") -> bool:
+ if self.fill_level_target is None:
+ return True
+ return (
+ self.fill_level_target.start_of_range is None
+ or state.fill_level >= self.fill_level_target.start_of_range
+ ) and (
+ self.fill_level_target.end_of_range is None
+ or state.fill_level <= self.fill_level_target.end_of_range
+ )
+
+ def get_fill_level_target_distance(self, state: "FrbcState") -> float:
+ if self.fill_level_target is None:
+ return 0
+ if (
+ self.fill_level_target.end_of_range is None
+ or state.fill_level < self.fill_level_target.start_of_range
+ ):
+ return self.fill_level_target.start_of_range - state.fill_level
+ else:
+ return state.fill_level - self.fill_level_target.end_of_range
+
+ def clear(self) -> None:
+ self._states_by_bucket.clear()
+ self.emergency_state = None
diff --git a/flexmeasures_s2/profile_steering/device_planner/frbc/operation_mode_profile_tree.py b/flexmeasures_s2/profile_steering/device_planner/frbc/operation_mode_profile_tree.py
new file mode 100644
index 0000000..777aeb3
--- /dev/null
+++ b/flexmeasures_s2/profile_steering/device_planner/frbc/operation_mode_profile_tree.py
@@ -0,0 +1,540 @@
+from datetime import datetime, timedelta
+from typing import List, Optional, Any
+import matplotlib.pyplot as plt
+import matplotlib.dates as mdates
+import logging
+
+from flexmeasures_s2.profile_steering.device_planner.frbc.frbc_timestep import (
+ FrbcTimestep,
+)
+from flexmeasures_s2.profile_steering.device_planner.frbc.s2_frbc_device_state_wrapper import (
+ S2FrbcDeviceStateWrapper,
+)
+
+from flexmeasures_s2.profile_steering.device_planner.frbc.frbc_state import FrbcState
+from flexmeasures_s2.profile_steering.device_planner.frbc.fill_level_target_util import (
+ FillLevelTargetUtil,
+)
+from flexmeasures_s2.profile_steering.device_planner.frbc.usage_forecast_util import (
+ UsageForecastUtil,
+)
+from flexmeasures_s2.profile_steering.s2_utils.number_range_wrapper import (
+ NumberRangeWrapper,
+)
+from flexmeasures_s2.profile_steering.device_planner.frbc.s2_frbc_plan import S2FrbcPlan
+from flexmeasures_s2.profile_steering.common.profile_metadata import ProfileMetadata
+from flexmeasures_s2.profile_steering.common.joule_profile import JouleProfile
+from flexmeasures_s2.profile_steering.common.soc_profile import SoCProfile
+from flexmeasures_s2.profile_steering.device_planner.frbc.s2_frbc_device_state import (
+ S2FrbcDeviceState,
+)
+from flexmeasures_s2.profile_steering.common.target_profile import TargetProfile
+from pint import UnitRegistry
+
+SI = UnitRegistry()
+logger = logging.getLogger(__name__)
+
+
+# TODO: Add S2FrbcInsightsProfile?->Update 08-02-2025: Not needed for now
+
+
+def plot_planning_results(
+ timestep_start_times, energy_elements, fill_level_elements, operation_mode_ids, ids
+):
+ """
+ Plots the energy, fill level, actuator usage, and operation mode ID lists using matplotlib.
+
+ :param timestep_start_times: List of datetime objects representing the start time of each timestep.
+ :param energy_elements: List of energy values.
+ :param fill_level_elements: List of fill level values.
+ :param operation_mode_ids: List of dictionaries with actuator configurations.
+ :param ids: List of tuples (id, name) for debugging.
+ """
+ # Create a figure and a set of subplots
+ fig, axs = plt.subplots(4, 1, figsize=(12, 16), sharex=True)
+
+ # Plot energy elements
+ axs[0].plot(timestep_start_times, energy_elements, label="Energy", color="blue")
+ axs[0].set_ylabel("Energy (Joules)")
+ axs[0].legend(loc="upper right")
+ axs[0].grid(True)
+
+ # Plot fill level elements
+ axs[1].plot(
+ timestep_start_times, fill_level_elements, label="Fill Level", color="green"
+ )
+ axs[1].set_ylabel("Fill Level (%)")
+ axs[1].legend(loc="upper right")
+ axs[1].grid(True)
+
+ # Create a dictionary from the list of tuples for easier lookup
+ id_to_name = {id_val: name for id_val, name in ids}
+
+ # Create a color mapping for names
+ unique_names = set(name for _, name in ids)
+ actuator_colors = {name: f"C{i}" for i, name in enumerate(unique_names)}
+
+ # Plot actuator usage
+ for i, (time, config) in enumerate(zip(timestep_start_times, operation_mode_ids)):
+ for actuator_id, config in config.items():
+ actuator_name = id_to_name.get(str(actuator_id), str(actuator_id))
+ axs[2].scatter(
+ time,
+ actuator_name,
+ color=actuator_colors.get(actuator_name, "black"),
+ label=actuator_name if i == 0 else "",
+ )
+ axs[2].set_ylabel("Actuator Name")
+ handles, labels = axs[2].get_legend_handles_labels()
+ by_label = dict(zip(labels, handles))
+ axs[2].legend(by_label.values(), by_label.keys(), loc="upper right")
+ axs[2].grid(True)
+
+ # Plot operation modes
+ for i, (time, config) in enumerate(zip(timestep_start_times, operation_mode_ids)):
+ for actuator_id, config in config.items():
+ operation_mode_id = getattr(config, "operation_mode_id", None)
+ if operation_mode_id:
+ operation_mode_name = id_to_name.get(
+ str(operation_mode_id), str(operation_mode_id)
+ )
+ axs[3].scatter(
+ time,
+ operation_mode_name,
+ color=actuator_colors.get(operation_mode_name, "black"),
+ label=operation_mode_name if i == 0 else "",
+ )
+ axs[3].set_ylabel("Operation Mode Name")
+ handles, labels = axs[3].get_legend_handles_labels()
+ by_label = dict(zip(labels, handles))
+ axs[3].legend(by_label.values(), by_label.keys(), loc="upper right")
+ axs[3].grid(True)
+
+ # Format the x-axis to show time and set ticks every 30 minutes
+ axs[3].xaxis.set_major_locator(mdates.MinuteLocator(interval=30))
+ axs[3].xaxis.set_major_formatter(mdates.DateFormatter("%H:%M:%S"))
+ fig.autofmt_xdate()
+
+ # Adjust layout
+ plt.tight_layout()
+ plt.show()
+
+
+class OperationModeProfileTree:
+ def __init__(
+ self,
+ device_state: S2FrbcDeviceState,
+ profile_metadata: ProfileMetadata,
+ plan_due_by_date: datetime,
+ ):
+ self.device_state = S2FrbcDeviceStateWrapper(device_state)
+ self.profile_metadata = profile_metadata
+ self.plan_due_by_date = plan_due_by_date
+ self.timestep_duration_seconds = int(
+ profile_metadata.timestep_duration.total_seconds()
+ )
+ self.timesteps: List[FrbcTimestep] = []
+ self.generate_timesteps()
+
+ def generate_timesteps(self) -> None:
+ # logger.info("=" * 80)
+ # logger.info("GENERATING TIMESTEPS")
+ # logger.info("=" * 80)
+ # logger.info(f"Profile start: {self.profile_metadata.profile_start}")
+ # logger.info(f"Number of timesteps: {self.profile_metadata.nr_of_timesteps}")
+ # logger.info(f"Timestep duration: {self.profile_metadata.timestep_duration}")
+ # logger.info(f"Plan due by date: {self.plan_due_by_date}")
+
+ time_step_start = self.profile_metadata.profile_start
+ for i in range(self.profile_metadata.nr_of_timesteps):
+ time_step_end = time_step_start + self.profile_metadata.timestep_duration
+
+ if i == 0:
+ time_step_start = self.plan_due_by_date
+ # logger.debug(
+ # f"Timestep {i}: Adjusted start to plan_due_by_date: {time_step_start}"
+ # )
+
+ system_default_timezone = time_step_start.astimezone().tzinfo
+ time_step_start_dt = time_step_start.astimezone(system_default_timezone)
+ current_system_description = self.get_latest_before(
+ time_step_start_dt,
+ self.device_state.get_system_descriptions(),
+ lambda sd: sd.valid_from,
+ )
+ current_leakage_behaviour = self.get_latest_before(
+ time_step_start_dt,
+ self.device_state.get_leakage_behaviours(),
+ lambda lb: lb.valid_from,
+ )
+ current_fill_level = self.get_latest_before(
+ time_step_start_dt,
+ self.device_state.fill_level_target_profiles,
+ lambda fl: fl.start_time,
+ )
+ current_fill_level_target = None
+ if current_fill_level:
+ current_fill_level_target = (
+ FillLevelTargetUtil.from_fill_level_target_profile(
+ current_fill_level
+ )
+ )
+ fill_level_target = self.get_fill_level_target_for_timestep(
+ current_fill_level_target, time_step_start, time_step_end
+ )
+ current_usage_forecast = self.get_latest_before(
+ time_step_start_dt,
+ self.device_state.usage_forecasts,
+ lambda uf: uf.start_time,
+ )
+ current_usage_forecast_profile = None
+ if current_usage_forecast:
+ current_usage_forecast_profile = (
+ UsageForecastUtil.from_storage_usage_profile(current_usage_forecast)
+ )
+
+ usage_forecast = self.get_usage_forecast_for_timestep(
+ current_usage_forecast_profile, time_step_start, time_step_end
+ )
+
+ # Log timestep details every 10 timesteps and first/last
+ if i % 10 == 0 or i == 0 or i == self.profile_metadata.nr_of_timesteps - 1:
+ # logger.info(
+ # f"Timestep {i}: {time_step_start.strftime('%H:%M:%S')} -> {time_step_end.strftime('%H:%M:%S')}"
+ # )
+ # if fill_level_target:
+ # logger.info(
+ # f" Fill level target: [{fill_level_target.start_of_range}, {fill_level_target.end_of_range}]"
+ # )
+ # else:
+ # logger.info(" Fill level target: None")
+ # logger.info(f" Usage forecast: {usage_forecast}")
+ # if current_leakage_behaviour:
+ # logger.info(" Leakage behaviour: present")
+ # else:
+ # logger.info(" Leakage behaviour: None")
+ pass
+
+ self.timesteps.append(
+ FrbcTimestep(
+ time_step_start.astimezone(system_default_timezone),
+ time_step_end.astimezone(system_default_timezone),
+ current_system_description,
+ current_leakage_behaviour,
+ fill_level_target,
+ usage_forecast,
+ self.device_state.get_computational_parameters(),
+ )
+ )
+ time_step_start = time_step_end
+
+ # logger.info(f"Generated {len(self.timesteps)} timesteps")
+ # logger.info("=" * 80)
+
+ @staticmethod
+ def get_latest_before(before, select_from, get_date_time):
+ latest_before = None
+ latest_before_date_time = None
+ if select_from:
+ for current in select_from:
+ if current:
+ current_date_time = get_date_time(current).replace(tzinfo=None)
+ before = before.replace(tzinfo=None)
+ if current_date_time <= before and (
+ latest_before is None
+ or current_date_time > latest_before_date_time
+ ):
+ latest_before = current
+ latest_before_date_time = current_date_time
+ return latest_before
+
+ @staticmethod
+ def get_fill_level_target_for_timestep(
+ fill_level_target_profile: Optional[Any],
+ time_step_start: datetime,
+ time_step_end: datetime,
+ ) -> Optional[NumberRangeWrapper]:
+ if not fill_level_target_profile:
+ return None
+ time_step_end -= timedelta(milliseconds=1)
+ lower, upper = None, None
+ for e in FillLevelTargetUtil.get_elements_in_range(
+ fill_level_target_profile, time_step_start, time_step_end
+ ):
+ if e.lower_limit is not None and (lower is None or e.lower_limit > lower):
+ lower = e.lower_limit
+ if e.upper_limit is not None and (upper is None or e.upper_limit < upper):
+ upper = e.upper_limit
+ if lower is None and upper is None:
+ return None
+ return NumberRangeWrapper(lower, upper)
+
+ @staticmethod
+ def get_usage_forecast_for_timestep(
+ usage_forecast: Optional[Any],
+ time_step_start: datetime,
+ time_step_end: datetime,
+ ) -> float:
+ if not usage_forecast:
+ return 0
+ time_step_end -= timedelta(milliseconds=1)
+ usage = 0
+ usage = UsageForecastUtil.sub_profile(
+ usage_forecast=usage_forecast,
+ time_step_start=time_step_start,
+ time_step_end=time_step_end,
+ )
+
+ return usage
+
+ def find_best_plan(
+ self,
+ target_profile: TargetProfile,
+ diff_to_min_profile: Any,
+ diff_to_max_profile: Any,
+ ids: Optional[dict] = None,
+ ) -> S2FrbcPlan:
+ # logger.info("=" * 80)
+ # logger.info("FINDING BEST PLAN")
+ # logger.info("=" * 80)
+
+ # Set targets for each timestep
+ for i, ts in enumerate(self.timesteps):
+ ts.clear()
+ target_element = target_profile.elements[i]
+ if target_element is not None:
+ ts.set_targets(
+ target_element,
+ diff_to_min_profile.elements[i],
+ diff_to_max_profile.elements[i],
+ )
+ if i % 10 == 0 or i == 0 or i == len(self.timesteps) - 1:
+ # logger.debug(
+ # f"Timestep {i} target: {target_element}, diff_min: {diff_to_min_profile.elements[i]}, diff_max: {diff_to_max_profile.elements[i]}"
+ # )
+ pass
+
+ first_timestep_index = next(
+ (i for i, ts in enumerate(self.timesteps) if ts.system_description),
+ -1,
+ )
+ # logger.info(f"First timestep with system description: {first_timestep_index}")
+
+ first_timestep = self.timesteps[first_timestep_index]
+ last_timestep = self.timesteps[-1]
+
+ initial_fill_level = (
+ self.device_state.device_state.storage_status.present_fill_level
+ )
+ # logger.info(f"Initial fill level: {initial_fill_level}")
+
+ state_zero = FrbcState(
+ device_state=self.device_state.device_state,
+ timestep=first_timestep,
+ initial_fill_level=initial_fill_level,
+ )
+
+ # logger.info(f"Generating states for initial timestep {first_timestep_index}...")
+ state_zero.generate_next_timestep_states(first_timestep)
+ initial_states = first_timestep.get_final_states_within_fill_level_target()
+ # logger.info(f"Generated {len(initial_states)} initial states")
+
+ # Sample some initial states to log
+ if initial_states:
+ for idx, state in enumerate(initial_states[:3]): # Log first 3 states
+ # logger.debug(
+ # f" Initial state {idx}: fill_level={state.fill_level:.2f}, energy={state.timestep_energy}, cost={getattr(state, 'cost', 'N/A')}"
+ # )
+ pass
+
+ # Generate states for each timestep
+ for i in range(first_timestep_index, len(self.timesteps) - 1):
+ current_timestep = self.timesteps[i]
+ next_timestep = self.timesteps[i + 1]
+ final_states = current_timestep.get_final_states_within_fill_level_target()
+
+ # Log progress every 10 timesteps
+ if i % 10 == 0 or i == first_timestep_index:
+ # logger.info(
+ # f"Timestep {i}: Processing {len(final_states)} states -> generating states for timestep {i+1}"
+ # )
+ # if final_states:
+ # # Log sample states
+ # for idx, state in enumerate(final_states[:2]): # Log first 2 states
+ # logger.debug(
+ # f" State {idx}: fill_level={state.fill_level:.2f}, energy={state.timestep_energy}, bucket={getattr(state, 'bucket_index', 'N/A')}"
+ # )
+ pass
+
+ for frbc_state in final_states:
+ frbc_state.generate_next_timestep_states(next_timestep)
+
+ # Find best end state
+ final_states = last_timestep.get_final_states_within_fill_level_target()
+ # logger.info(f"Final timestep has {len(final_states)} states")
+
+ if final_states:
+ for idx, state in enumerate(final_states[:5]): # Log first 5 final states
+ # cost = getattr(state, "sum_energy_cost", "N/A")
+ # constraint_violation = getattr(
+ # state, "sum_squared_constraint_violation", "N/A"
+ # )
+ # logger.info(
+ # f" Final state {idx}: fill_level={state.fill_level:.2f}, energy={state.timestep_energy:.0f}, cost={cost}, constraint_violation={constraint_violation}"
+ # )
+ pass
+
+ end_state = self.find_best_end_state(final_states)
+ # logger.info(
+ # f"Best end state selected: fill_level={end_state.fill_level:.2f}, energy={end_state.timestep_energy:.0f}"
+ # )
+ # logger.info(
+ # f" Selection reason: {getattr(end_state, 'selection_reason', 'N/A')}"
+ # )
+ # logger.info(
+ # f" Best state cost: {getattr(end_state, 'sum_energy_cost', 'N/A')}"
+ # )
+ # logger.info(
+ # f" Best state constraint_violation: {getattr(end_state, 'sum_squared_constraint_violation', 'N/A')}"
+ # )
+
+ plan = self.convert_to_plan(first_timestep_index, end_state)
+
+ # logger.info("=" * 80)
+ # logger.info("PLAN GENERATION COMPLETE")
+ # logger.info("=" * 80)
+
+ return plan
+
+ @staticmethod
+ def find_best_end_state(states: List[FrbcState]) -> FrbcState:
+ # logger.info(f"Comparing {len(states)} end states to find best...")
+ best_state = states[0]
+ comparisons = 0
+
+ # best_cost = getattr(best_state, "sum_energy_cost", 0)
+ # best_violation = getattr(best_state, "sum_squared_constraint_violation", 0)
+ # logger.info(
+ # f" Initial best state: fill_level={best_state.fill_level:.2f}, energy={best_state.timestep_energy:.0f}, cost={best_cost:.4f}, violation={best_violation:.4f}"
+ # )
+
+ for idx, state in enumerate(states[1:], 1):
+ # state_cost = getattr(state, "sum_energy_cost", 0)
+ # state_violation = getattr(state, "sum_squared_constraint_violation", 0)
+ comparison_result = state.is_preferable_than(best_state)
+
+ if idx <= 5: # Log first 5 comparisons
+ # logger.info(
+ # f" Comparing state {idx}: fill_level={state.fill_level:.2f}, energy={state.timestep_energy:.0f}, cost={state_cost:.4f}, violation={state_violation:.4f}"
+ # )
+ # logger.info(
+ # f" Comparison result: {comparison_result.result}, reason: {comparison_result.reason if hasattr(comparison_result, 'reason') else 'N/A'}"
+ # )
+ # logger.info(
+ # f" Cost difference: {abs(state_cost - best_cost):.4f}, tariff_epsilon: {FrbcState.tariff_epsilon}"
+ # )
+ pass
+
+ if comparison_result.result:
+ # logger.info(
+ # f" State {idx} is BETTER: fill_level={state.fill_level:.2f} vs {best_state.fill_level:.2f}"
+ # )
+ # logger.info(
+ # f" Reason: {comparison_result.reason if hasattr(comparison_result, 'reason') else 'N/A'}"
+ # )
+ best_state = state
+ # best_cost = state_cost
+ # best_violation = state_violation
+ comparisons += 1
+
+ # logger.info(f"Best state found after {comparisons} preference changes")
+ # logger.info(
+ # f" Final best: fill_level={best_state.fill_level:.2f}, energy={best_state.timestep_energy:.0f}, cost={best_cost:.4f}"
+ # )
+ return best_state
+
+ def convert_to_plan(
+ self, first_timestep_index_with_state: int, end_state: FrbcState
+ ) -> S2FrbcPlan:
+ # logger.info("Converting end state to plan...")
+ # logger.info(f" First timestep index: {first_timestep_index_with_state}")
+
+ energy: List[int] = [0] * self.profile_metadata.nr_of_timesteps
+ fill_level: List[float] = [0.0] * self.profile_metadata.nr_of_timesteps
+ actuators: List[dict] = [{}] * self.profile_metadata.nr_of_timesteps
+ insight_elements = [None] * self.profile_metadata.nr_of_timesteps
+ state_selection_reasons: List[str] = [
+ ""
+ ] * self.profile_metadata.nr_of_timesteps
+
+ state = end_state
+ states_traced = 0
+
+ # Trace back through states
+ for i in range(self.profile_metadata.nr_of_timesteps - 1, -1, -1):
+ if i >= first_timestep_index_with_state:
+ energy[i] = int(state.timestep_energy)
+ fill_level[i] = state.fill_level
+ actuators[i] = state.actuator_configurations
+ state_selection_reasons[i] = state.selection_reason # type: ignore
+
+ # Log every 10 states
+ if i % 10 == 0 or i == first_timestep_index_with_state:
+ # logger.debug(
+ # f" Timestep {i}: energy={energy[i]}, fill_level={fill_level[i]:.2f}, actuator_configs={len(actuators[i])} actuator(s)"
+ # )
+ # if actuators[i]:
+ # for actuator_id, config in actuators[i].items():
+ # mode_id = getattr(config, "operation_mode_id", "N/A")
+ # logger.debug(
+ # f" Actuator {str(actuator_id)[:8]}...: mode {str(mode_id)[:8]}..."
+ # )
+ pass
+
+ state = state.previous_state
+ states_traced += 1
+ else:
+ energy[i] = 0
+ fill_level[i] = 0.0
+ actuators[i] = {}
+ insight_elements[i] = None
+
+ # logger.info(f"Traced back through {states_traced} states")
+
+ # Add current energy
+ current_energy = self.device_state.get_energy_in_current_timestep().value
+ # logger.info(f"Adding current timestep energy: {current_energy}")
+ energy[0] += current_energy
+
+ # Calculate plan statistics
+ # total_energy = sum(energy)
+ # avg_fill_level = sum(fill_level) / len(fill_level) if fill_level else 0
+ # mode_changes = 0
+ # prev_mode = None
+ # for actuator_config in actuators:
+ # for actuator_id, config in actuator_config.items():
+ # mode_id = getattr(config, "operation_mode_id", None)
+ # if mode_id and mode_id != prev_mode:
+ # mode_changes += 1
+ # prev_mode = mode_id
+
+ # logger.info("Plan statistics:")
+ # logger.info(
+ # f" Total energy: {total_energy} J ({total_energy/3600000:.2f} kWh)"
+ # )
+ # logger.info(f" Average fill level: {avg_fill_level:.2f}")
+ # logger.info(f" Operation mode changes: {mode_changes}")
+
+ return S2FrbcPlan(
+ False,
+ JouleProfile(
+ profile_start=self.profile_metadata.profile_start,
+ timestep_duration=self.profile_metadata.timestep_duration,
+ elements=energy, # type: ignore[arg-type]
+ ),
+ SoCProfile(
+ self.profile_metadata,
+ fill_level,
+ ),
+ actuators,
+ )
diff --git a/flexmeasures_s2/profile_steering/device_planner/frbc/s2_frbc_actuator_configuration.py b/flexmeasures_s2/profile_steering/device_planner/frbc/s2_frbc_actuator_configuration.py
new file mode 100644
index 0000000..3a10f36
--- /dev/null
+++ b/flexmeasures_s2/profile_steering/device_planner/frbc/s2_frbc_actuator_configuration.py
@@ -0,0 +1,19 @@
+from typing import Dict, Any
+
+# TODO: Should this just be an actuator description
+
+
+class S2ActuatorConfiguration:
+ def __init__(self, operation_mode_id: str, factor):
+ self.operation_mode_id = operation_mode_id
+ self.factor = factor
+
+ def to_dict(self):
+ return {"operationModeId": self.operation_mode_id, "factor": self.factor}
+
+ @staticmethod
+ def from_dict(data: Dict[str, Any]) -> "S2ActuatorConfiguration":
+ return S2ActuatorConfiguration(str(data["operationModeId"]), data["factor"])
+
+ def __str__(self):
+ return f"S2ActuatorConfiguration [operationModeId={self.operation_mode_id}, factor={self.factor}]"
diff --git a/flexmeasures_s2/profile_steering/device_planner/frbc/s2_frbc_device_planner.py b/flexmeasures_s2/profile_steering/device_planner/frbc/s2_frbc_device_planner.py
new file mode 100644
index 0000000..4315ab0
--- /dev/null
+++ b/flexmeasures_s2/profile_steering/device_planner/frbc/s2_frbc_device_planner.py
@@ -0,0 +1,342 @@
+from datetime import datetime
+from typing import Optional
+
+# import logging
+
+from flexmeasures_s2.profile_steering.common.joule_profile import JouleProfile
+from flexmeasures_s2.profile_steering.common.proposal import Proposal
+from flexmeasures_s2.profile_steering.common.target_profile import TargetProfile
+from flexmeasures_s2.profile_steering.device_planner.frbc.s2_frbc_plan import S2FrbcPlan
+from flexmeasures_s2.profile_steering.device_planner.frbc.s2_frbc_instruction_profile import (
+ S2FrbcInstructionProfile,
+)
+from flexmeasures_s2.profile_steering.common.profile_metadata import ProfileMetadata
+from flexmeasures_s2.profile_steering.common.device_plan import (
+ DevicePlan,
+)
+from flexmeasures_s2.profile_steering.device_planner.frbc.operation_mode_profile_tree import (
+ OperationModeProfileTree,
+)
+from flexmeasures_s2.profile_steering.device_planner.frbc.s2_frbc_device_state import (
+ S2FrbcDeviceState,
+)
+from flexmeasures_s2.profile_steering.device_planner.device_planner_abstract import (
+ DevicePlanner,
+)
+from s2python.frbc import FRBCInstruction
+
+
+class S2FrbcDevicePlanner(DevicePlanner):
+ def __init__(
+ self,
+ s2_frbc_state: S2FrbcDeviceState,
+ profile_metadata: ProfileMetadata,
+ plan_due_by_date: datetime,
+ congestion_point_id: str,
+ ):
+ self._congestion_point_id = congestion_point_id
+ self.s2_frbc_state = s2_frbc_state
+ self.profile_metadata = profile_metadata
+ self.zero_profile = JouleProfile(
+ profile_metadata.profile_start,
+ profile_metadata.timestep_duration,
+ [0] * profile_metadata.nr_of_timesteps,
+ )
+ self.null_profile = JouleProfile(
+ profile_metadata.profile_start,
+ profile_metadata.timestep_duration,
+ elements=[None] * profile_metadata.nr_of_timesteps,
+ )
+ if self.is_storage_available(self.s2_frbc_state):
+ self.state_tree = OperationModeProfileTree(
+ self.s2_frbc_state,
+ profile_metadata,
+ plan_due_by_date,
+ )
+ self._priority_class = 1
+ self.latest_plan: Optional[S2FrbcPlan] = None
+ self.accepted_plan: Optional[S2FrbcPlan] = None
+
+ def is_storage_available(self, storage_state: S2FrbcDeviceState) -> bool:
+ latest_before_first_ptu = OperationModeProfileTree.get_latest_before(
+ self.profile_metadata.profile_start.replace(tzinfo=None),
+ storage_state.get_system_descriptions(),
+ lambda sd: sd.valid_from.replace(tzinfo=None),
+ )
+ if not storage_state.get_system_descriptions():
+ return False
+ if latest_before_first_ptu is None:
+ active_and_upcoming_system_descriptions_has_active_storage = any(
+ # TODO: ask if TypeError: can't compare offset-naive and offset-aware datetimes could be solved differently
+ self.profile_metadata.profile_end.replace(tzinfo=None)
+ >= sd.valid_from.replace(tzinfo=None)
+ >= self.profile_metadata.profile_start.replace(tzinfo=None)
+ for sd in storage_state.get_system_descriptions()
+ )
+ else:
+ active_and_upcoming_system_descriptions_has_active_storage = any(
+ self.profile_metadata.profile_end.replace(tzinfo=None)
+ >= sd.valid_from.replace(tzinfo=None)
+ >= latest_before_first_ptu.valid_from.replace(tzinfo=None)
+ for sd in storage_state.get_system_descriptions()
+ )
+ return (
+ storage_state.is_online
+ and active_and_upcoming_system_descriptions_has_active_storage
+ )
+
+ @property
+ def priority_class(self) -> int:
+ return self.s2_frbc_state.priority_class
+
+ @property
+ def device_id(self) -> str:
+ return self.s2_frbc_state.device_id
+
+ @property
+ def connection_id(self) -> str:
+ return self.s2_frbc_state.connection_id
+
+ @property
+ def congestion_point_id(self) -> str:
+ return self._congestion_point_id
+
+ @property
+ def device_name(self) -> str:
+ return self.s2_frbc_state.device_name
+
+ def create_improved_planning(
+ self,
+ diff_to_global_target: TargetProfile,
+ diff_to_max: JouleProfile,
+ diff_to_min: JouleProfile,
+ plan_due_by_date: datetime,
+ ) -> Proposal:
+ if self.accepted_plan is None:
+ raise ValueError("No accepted plan found")
+
+ # print(f"\n{'='*80}")
+ # print(f"DEVICE PLANNER: create_improved_planning for {self.device_name}")
+ # print(
+ # f" Accepted plan energy: {sum(self.accepted_plan.energy.elements) if self.accepted_plan.energy else 0} J"
+ # )
+ # print(
+ # f" diff_to_global_target has Joule elements: {diff_to_global_target.nr_of_joule_target_elements()}"
+ # )
+ # print(
+ # f" diff_to_global_target nr_of_timesteps: {diff_to_global_target.metadata.nr_of_timesteps}"
+ # )
+
+ # Count tariff elements
+ # tariff_count = sum(
+ # 1
+ # for e in diff_to_global_target.elements
+ # if isinstance(e, TargetProfile.TariffElement)
+ # )
+ # null_count = sum(
+ # 1
+ # for e in diff_to_global_target.elements
+ # if isinstance(e, TargetProfile.NullElement)
+ # )
+ # print(
+ # f" Target elements: {tariff_count} TariffElement, {null_count} NullElement, {len(diff_to_global_target.elements)} total"
+ # )
+
+ # Show sample tariff values
+ # if tariff_count > 0:
+ # sample_tariffs = [
+ # e.get_tariff()
+ # for e in diff_to_global_target.elements[:10]
+ # if isinstance(e, TargetProfile.TariffElement)
+ # ]
+ # print(f" Sample tariff values (first 10): {sample_tariffs}")
+ pass
+
+ # TODO: check if diff_to_global_target has tarrif elements
+ # TODO: check for NULL elements in target
+ if diff_to_global_target.nr_of_joule_target_elements() != 0:
+ target = diff_to_global_target.add(self.accepted_plan.energy)
+ else:
+ target = diff_to_global_target
+ # print(
+ # "diff_to_global_target has no Joule elements, using the target directly"
+ # )
+ pass
+
+ # print(" Final target for find_best_plan:")
+ # tariff_count_final = sum(
+ # 1 for e in target.elements if isinstance(e, TargetProfile.TariffElement)
+ # )
+ # null_count_final = sum(
+ # 1 for e in target.elements if isinstance(e, TargetProfile.NullElement)
+ # )
+ # print(f" {tariff_count_final} TariffElement, {null_count_final} NullElement")
+ # print(f"{'='*80}\n")
+
+ max_profile = diff_to_max.add(self.accepted_plan.energy)
+ min_profile = diff_to_min.add(self.accepted_plan.energy)
+
+ if self.is_storage_available(self.s2_frbc_state):
+ self.latest_plan = self.state_tree.find_best_plan(
+ target, min_profile, max_profile
+ )
+
+ # Log the new plan
+ # new_energy = (
+ # sum(self.latest_plan.energy.elements)
+ # if self.latest_plan and self.latest_plan.energy
+ # else 0
+ # )
+ # print(f" New plan energy: {new_energy} J")
+ # print(
+ # f" Energy difference: {new_energy - sum(self.accepted_plan.energy.elements)} J"
+ # )
+ else:
+ self.latest_plan = S2FrbcPlan(
+ idle=True,
+ energy=self.zero_profile,
+ fill_level=None,
+ operation_mode_id=[],
+ )
+ if self.latest_plan is None:
+ raise ValueError("No latest plan found")
+ proposal = Proposal(
+ global_diff_target=diff_to_global_target,
+ diff_to_congestion_max=diff_to_max,
+ diff_to_congestion_min=diff_to_min,
+ proposed_plan=self.latest_plan.energy,
+ old_plan=self.accepted_plan.energy,
+ origin=self,
+ )
+ return proposal
+
+ def create_initial_planning(
+ self, plan_due_by_date: datetime, ids: Optional[dict] = None
+ ) -> S2FrbcPlan:
+ if self.is_storage_available(self.s2_frbc_state):
+ if ids is None:
+ self.latest_plan = self.state_tree.find_best_plan(
+ TargetProfile.null_profile(self.profile_metadata),
+ self.null_profile,
+ self.null_profile,
+ )
+ else:
+ self.latest_plan = self.state_tree.find_best_plan(
+ TargetProfile.null_profile(self.profile_metadata),
+ self.null_profile,
+ self.null_profile,
+ ids,
+ )
+ else:
+ self.latest_plan = S2FrbcPlan(
+ idle=True,
+ energy=self.zero_profile,
+ fill_level=None,
+ operation_mode_id=[],
+ )
+ self.accepted_plan = self.latest_plan
+ return self.latest_plan
+
+ def accept_proposal(self, accepted_proposal: Proposal) -> None:
+ if self.latest_plan is None:
+ raise ValueError("No latest plan found")
+ if accepted_proposal.origin != self:
+ raise ValueError(
+ f"Storage controller '{self.device_id}' received a proposal that he did not send."
+ )
+ if not accepted_proposal.proposed_plan == self.latest_plan.energy:
+ raise ValueError(
+ f"Storage controller '{self.device_id}' received a proposal that he did not send."
+ )
+ if accepted_proposal.get_congestion_improvement_value() < 0:
+ raise ValueError(
+ f"Storage controller '{self.device_id}' received a proposal with negative improvement"
+ )
+ self.accepted_plan = self.latest_plan
+
+ def get_latest_plan(self) -> Optional[S2FrbcPlan]:
+ return self.latest_plan
+
+ def set_accepted_plan(self, plan: S2FrbcPlan) -> None:
+ if not isinstance(plan, S2FrbcPlan):
+ raise TypeError(f"Expected S2FrbcPlan, but got {type(plan)}")
+ self.accepted_plan = plan
+
+ def current_profile(self) -> JouleProfile:
+ if self.accepted_plan is None:
+ raise ValueError("No accepted plan found")
+ return self.accepted_plan.energy
+
+ def get_device_plan(self) -> Optional[DevicePlan]:
+ if self.accepted_plan is None:
+ return None
+ # logging.debug(
+ # dict(
+ # device_id=self.device_id,
+ # device_name=self.device_name,
+ # connection_id=self.connection_id,
+ # energy_profile=self.accepted_plan.energy,
+ # fill_level_profile=self.accepted_plan.fill_level,
+ # instruction_profile=self.convert_plan_to_instructions(
+ # self.profile_metadata, self.accepted_plan
+ # ),
+ # )
+ # )
+ return DevicePlan(
+ device_id=self.device_id,
+ device_name=self.device_name,
+ connection_id=self.connection_id,
+ energy_profile=self.accepted_plan.energy,
+ fill_level_profile=self.accepted_plan.fill_level,
+ instruction_profile=self.convert_plan_to_instructions(
+ self.profile_metadata, self.accepted_plan
+ ),
+ )
+
+ @staticmethod
+ def convert_plan_to_instructions(
+ profile_metadata: ProfileMetadata, device_plan: S2FrbcPlan
+ ) -> S2FrbcInstructionProfile:
+ import uuid
+ from datetime import timedelta
+
+ elements = []
+ actuator_configurations_per_timestep = device_plan.get_operation_mode_id()
+
+ if actuator_configurations_per_timestep is not None:
+ for timestep_index, actuator_configurations_dict in enumerate(
+ actuator_configurations_per_timestep
+ ):
+ # Calculate execution time for this timestep
+ execution_time = profile_metadata.profile_start + timedelta(
+ seconds=timestep_index
+ * profile_metadata.timestep_duration.total_seconds()
+ )
+ # Ensure timezone-aware datetime
+ if execution_time.tzinfo is None:
+ from datetime import timezone
+
+ execution_time = execution_time.replace(tzinfo=timezone.utc)
+
+ # Create instructions for each actuator at this timestep
+ for (
+ actuator_id,
+ actuator_config,
+ ) in actuator_configurations_dict.items():
+ if actuator_config is not None:
+ instruction = FRBCInstruction(
+ message_id=str(uuid.uuid4()),
+ id=str(uuid.uuid4()),
+ actuator_id=str(actuator_id),
+ operation_mode=str(actuator_config.operation_mode_id),
+ operation_mode_factor=float(actuator_config.factor),
+ execution_time=execution_time,
+ abnormal_condition=False,
+ )
+ elements.append(instruction)
+
+ return S2FrbcInstructionProfile(
+ profile_start=profile_metadata.profile_start,
+ timestep_duration=profile_metadata.timestep_duration,
+ elements=elements,
+ )
diff --git a/flexmeasures_s2/profile_steering/device_planner/frbc/s2_frbc_device_state.py b/flexmeasures_s2/profile_steering/device_planner/frbc/s2_frbc_device_state.py
new file mode 100644
index 0000000..8a27c99
--- /dev/null
+++ b/flexmeasures_s2/profile_steering/device_planner/frbc/s2_frbc_device_state.py
@@ -0,0 +1,76 @@
+from datetime import datetime
+from typing import List, Optional
+from s2python.common import PowerForecast
+from s2python.frbc import (
+ FRBCSystemDescription,
+ FRBCLeakageBehaviour,
+ FRBCUsageForecast,
+ FRBCFillLevelTargetProfile,
+)
+from s2python.frbc.frbc_actuator_status import FRBCActuatorStatus
+from s2python.generated.gen_s2 import FRBCStorageStatus, PowerValue
+
+
+class S2FrbcDeviceState:
+ class ComputationalParameters:
+ def __init__(self, nr_of_buckets: int, stratification_layers: int):
+ self.nr_of_buckets = nr_of_buckets
+ self.stratification_layers = stratification_layers
+
+ def get_nr_of_buckets(self) -> int:
+ return self.nr_of_buckets
+
+ def get_stratification_layers(self) -> int:
+ return self.stratification_layers
+
+ def __init__(
+ self,
+ device_id: str,
+ device_name: str,
+ connection_id: str,
+ priority_class: int,
+ timestamp: datetime,
+ energy_in_current_timestep: PowerValue,
+ is_online: bool,
+ power_forecast: Optional[PowerForecast],
+ system_descriptions: List[FRBCSystemDescription],
+ leakage_behaviours: List[FRBCLeakageBehaviour],
+ usage_forecasts: List[FRBCUsageForecast],
+ fill_level_target_profiles: List[FRBCFillLevelTargetProfile],
+ computational_parameters: ComputationalParameters,
+ storage_status: FRBCStorageStatus = None,
+ actuator_statuses: Optional[List[FRBCActuatorStatus]] = None,
+ ):
+ self.device_id = device_id
+ self.device_name = device_name
+ self.connection_id = connection_id
+ self.priority_class = priority_class
+ self.timestamp = timestamp
+ self.energy_in_current_timestep = energy_in_current_timestep
+ self.is_online = is_online
+ self.power_forecast = power_forecast
+ self.system_descriptions = system_descriptions
+ self.leakage_behaviours = leakage_behaviours
+ self.usage_forecasts = usage_forecasts
+ self.fill_level_target_profiles = fill_level_target_profiles
+ self.computational_parameters = computational_parameters
+ self.storage_status = storage_status
+ self.actuator_statuses = actuator_statuses
+
+ def get_system_descriptions(self) -> List[FRBCSystemDescription]:
+ return self.system_descriptions
+
+ def get_leakage_behaviours(self) -> List[FRBCLeakageBehaviour]:
+ return self.leakage_behaviours
+
+ def get_device_name(self) -> str:
+ return self.device_name
+
+ def get_computational_parameters(self) -> ComputationalParameters:
+ return self.computational_parameters
+
+ def get_power_forecast(self) -> Optional[PowerForecast]:
+ return self.power_forecast
+
+ def get_energy_in_current_timestep(self) -> PowerValue:
+ return self.energy_in_current_timestep
diff --git a/flexmeasures_s2/profile_steering/device_planner/frbc/s2_frbc_device_state_wrapper.py b/flexmeasures_s2/profile_steering/device_planner/frbc/s2_frbc_device_state_wrapper.py
new file mode 100644
index 0000000..a4ec7de
--- /dev/null
+++ b/flexmeasures_s2/profile_steering/device_planner/frbc/s2_frbc_device_state_wrapper.py
@@ -0,0 +1,340 @@
+from datetime import datetime, timedelta
+from typing import Dict, List, Optional, Any, TYPE_CHECKING
+from functools import lru_cache
+import numpy as np
+
+from flexmeasures_s2.profile_steering.device_planner.frbc.s2_frbc_actuator_configuration import (
+ S2ActuatorConfiguration,
+)
+from s2python.common import CommodityQuantity
+from flexmeasures_s2.profile_steering.device_planner.frbc.frbc_operation_mode_wrapper import (
+ FrbcOperationModeWrapper,
+)
+
+if TYPE_CHECKING:
+ from flexmeasures_s2.profile_steering.device_planner.frbc.frbc_timestep import (
+ FrbcTimestep,
+ )
+
+from s2python.common.transition import Transition
+from s2python.frbc import (
+ FRBCLeakageBehaviour,
+ FRBCLeakageBehaviourElement,
+ FRBCActuatorDescription,
+ FRBCActuatorStatus,
+ FRBCStorageStatus,
+)
+from flexmeasures_s2.profile_steering.device_planner.frbc.s2_frbc_device_state import (
+ S2FrbcDeviceState,
+)
+
+
+class S2FrbcDeviceStateWrapper:
+ epsilon = 1e-4
+
+ def __init__(self, device_state: S2FrbcDeviceState):
+ self.device_state: S2FrbcDeviceState = device_state
+ computational_params = self.device_state.get_computational_parameters()
+ self.nr_of_buckets: int = computational_params.get_nr_of_buckets()
+ self.nr_of_stratification_layers: int = (
+ computational_params.get_stratification_layers()
+ )
+ self.actuator_operation_mode_map_per_timestep: Dict[
+ datetime, Dict[str, List[str]]
+ ] = {}
+ self.all_actions: Dict[datetime, List[Dict[str, S2ActuatorConfiguration]]] = {}
+ self.operation_mode_uses_factor_map: Dict[str, bool] = {}
+ self.operation_modes: Dict[str, FrbcOperationModeWrapper] = {}
+
+ @property
+ def is_online(self) -> bool:
+ return self.device_state.is_online
+
+ def get_power_forecast(self) -> Any:
+ return self.device_state.get_power_forecast()
+
+ def get_system_descriptions(self) -> Any:
+ return self.device_state.get_system_descriptions()
+
+ def get_leakage_behaviours(self) -> Any:
+ return self.device_state.get_leakage_behaviours()
+
+ @property
+ def usage_forecasts(self) -> Any:
+ return self.device_state.usage_forecasts
+
+ @property
+ def fill_level_target_profiles(self) -> Any:
+ return self.device_state.fill_level_target_profiles
+
+ def get_computational_parameters(self) -> Any:
+ return self.device_state.get_computational_parameters()
+
+ @lru_cache(maxsize=None)
+ def get_actuators(self, target_timestep: "FrbcTimestep") -> List[str]:
+ return list(self.create_actuator_operation_mode_map(target_timestep).keys())
+
+ @lru_cache(maxsize=None)
+ def get_normal_operation_modes_for_actuator(
+ self, target_timestep: "FrbcTimestep", actuator_id: str
+ ) -> List[str]:
+ actuator_operation_mode_map = self.create_actuator_operation_mode_map(
+ target_timestep
+ )
+ return actuator_operation_mode_map.get(actuator_id, [])
+
+ @lru_cache(maxsize=None)
+ def create_actuator_operation_mode_map(
+ self, target_timestep: "FrbcTimestep"
+ ) -> Dict[str, List[str]]:
+ actuator_operation_mode_map = {}
+ for a in target_timestep.system_description.actuators:
+ actuator_operation_mode_map[str(a.id)] = [
+ str(om.id) for om in a.operation_modes if not om.abnormal_condition_only
+ ]
+ return actuator_operation_mode_map
+
+ @lru_cache(maxsize=None)
+ def get_operation_mode(
+ self, target_timestep, actuator_id: str, operation_mode_id: str
+ ):
+ om_key = f"{actuator_id}-{operation_mode_id}"
+ if om_key in self.operation_modes:
+ return self.operation_modes[om_key]
+
+ actuator_description = self.get_actuator_description(
+ target_timestep, actuator_id
+ )
+ if actuator_description:
+ for operation_mode in actuator_description.operation_modes:
+ if str(operation_mode.id) == operation_mode_id:
+ found_operation_mode = FrbcOperationModeWrapper(operation_mode)
+ self.operation_modes[om_key] = found_operation_mode
+ return found_operation_mode
+ return None
+
+ @lru_cache(maxsize=None)
+ def operation_mode_uses_factor(
+ self,
+ target_timestep: "FrbcTimestep",
+ actuator_id: str,
+ operation_mode_id: str,
+ ) -> bool:
+ return self.get_operation_mode(
+ target_timestep, actuator_id, operation_mode_id
+ ).is_uses_factor()
+
+ @lru_cache(maxsize=None)
+ def get_all_possible_actuator_configurations(
+ self, target_timestep: "FrbcTimestep"
+ ) -> List[Dict[Any, S2ActuatorConfiguration]]:
+ possible_actuator_configs = {}
+ for actuator_id in self.get_actuators(target_timestep):
+ actuator_list = []
+ for op_mode_id in self.get_normal_operation_modes_for_actuator(
+ target_timestep, actuator_id
+ ):
+ if self.operation_mode_uses_factor(
+ target_timestep, actuator_id, op_mode_id
+ ):
+ factors = np.linspace(
+ 0.0, 1.0, self.nr_of_stratification_layers + 1
+ )
+ actuator_list.extend(
+ [
+ S2ActuatorConfiguration(op_mode_id, factor)
+ for factor in factors
+ ]
+ )
+ else:
+ actuator_list.append(S2ActuatorConfiguration(op_mode_id, 0.0))
+ possible_actuator_configs[actuator_id] = actuator_list
+
+ keys = list(possible_actuator_configs.keys())
+ import itertools
+
+ combinations = itertools.product(
+ *(possible_actuator_configs[key] for key in keys)
+ )
+
+ return [dict(zip(keys, combo)) for combo in combinations]
+
+ @staticmethod
+ @lru_cache(maxsize=None)
+ def get_transition(
+ target_timestep,
+ actuator_id: str,
+ from_operation_mode_id: str,
+ to_operation_mode_id: str,
+ ) -> Optional[Transition]:
+ actuator_description = S2FrbcDeviceStateWrapper.get_actuator_description(
+ target_timestep, actuator_id
+ )
+ if actuator_description is None:
+ return None
+
+ for transition in actuator_description.transitions:
+ if (
+ str(transition.from_) == from_operation_mode_id
+ and str(transition.to) == to_operation_mode_id
+ ):
+ return transition
+ return None
+
+ def get_operation_mode_power(
+ self, om_wrapper: FrbcOperationModeWrapper, fill_level: float, factor: float
+ ) -> float:
+ element = self.find_operation_mode_element(om_wrapper, fill_level)
+ power_watt = 0
+ for power_range in element.power_ranges:
+ if power_range.commodity_quantity in [
+ CommodityQuantity.ELECTRIC_POWER_L1,
+ CommodityQuantity.ELECTRIC_POWER_L2,
+ CommodityQuantity.ELECTRIC_POWER_L3,
+ CommodityQuantity.ELECTRIC_POWER_3_PHASE_SYMMETRIC,
+ ]:
+ start = power_range.start_of_range
+ end = power_range.end_of_range
+ power_watt += (end - start) * factor + start
+ return power_watt
+
+ @staticmethod
+ def find_operation_mode_element(
+ om_wrapper: FrbcOperationModeWrapper, fill_level: float
+ ):
+ starts = om_wrapper.fill_level_starts
+ ends = om_wrapper.fill_level_ends
+
+ # Assumes that starts is sorted, which is a reasonable assumption for S2 data.
+ # Find the index of the interval start that is just less than or equal to fill_level.
+ idx = np.searchsorted(starts, fill_level, side="right") - 1
+
+ # Check if fill_level is within the found range.
+ if idx >= 0 and fill_level <= ends[idx]:
+ return om_wrapper.elements[idx]
+
+ # Handle edge cases and gaps, consistent with the original logic.
+ return (
+ om_wrapper.elements[0]
+ if fill_level < starts[0]
+ else om_wrapper.elements[-1]
+ )
+
+ def get_operation_mode_fill_rate(
+ self, om_wrapper: FrbcOperationModeWrapper, fill_level: float, factor: float
+ ) -> float:
+ element = self.find_operation_mode_element(om_wrapper, fill_level)
+ fill_rate = element.fill_rate
+ start = fill_rate.end_of_range
+ end = fill_rate.start_of_range
+ return (start - end) * factor + end
+
+ @staticmethod
+ def get_leakage_rate(target_timestep: "FrbcTimestep", fill_level: float) -> float:
+ if target_timestep.leakage_behaviour is None:
+ return 0.0
+
+ element = S2FrbcDeviceStateWrapper.find_leakage_element(
+ target_timestep.leakage_behaviour, fill_level
+ )
+ return element.leakage_rate if element else 0.0
+
+ @staticmethod
+ def find_leakage_element(
+ leakage_behaviour: FRBCLeakageBehaviour, fill_level: float
+ ) -> Optional[FRBCLeakageBehaviourElement]:
+ if not leakage_behaviour.elements:
+ return None
+
+ # Calculate starts and ends arrays directly to avoid monkey-patching issues
+ starts = np.array(
+ [e.fill_level_range.start_of_range for e in leakage_behaviour.elements]
+ )
+ ends = np.array(
+ [e.fill_level_range.end_of_range for e in leakage_behaviour.elements]
+ )
+
+ # Use binary search for efficient lookup
+ idx = np.searchsorted(starts, fill_level, side="right") - 1
+
+ if idx >= 0 and fill_level <= ends[idx]:
+ return leakage_behaviour.elements[idx]
+
+ return (
+ leakage_behaviour.elements[0]
+ if fill_level < starts[0]
+ else leakage_behaviour.elements[-1]
+ )
+
+ @staticmethod
+ def calculate_bucket(target_timestep: "FrbcTimestep", fill_level: float) -> int:
+ (
+ fill_level_lower_limit,
+ fill_level_upper_limit,
+ nr_of_buckets,
+ ) = target_timestep.get_bucket_calculation_params()
+
+ if fill_level_upper_limit == fill_level_lower_limit:
+ return 0
+
+ bucket = int(
+ (fill_level - fill_level_lower_limit)
+ / (fill_level_upper_limit - fill_level_lower_limit)
+ * nr_of_buckets
+ )
+ return min(max(bucket, 0), nr_of_buckets - 1)
+
+ @staticmethod
+ @lru_cache(maxsize=None)
+ def get_timer_duration(
+ target_timestep: "FrbcTimestep", actuator_id: str, timer_id: str
+ ) -> timedelta:
+ return timedelta(
+ milliseconds=S2FrbcDeviceStateWrapper.get_timer_duration_milliseconds(
+ target_timestep, actuator_id, timer_id
+ )
+ )
+
+ @staticmethod
+ @lru_cache(maxsize=None)
+ def get_timer_duration_milliseconds(
+ target_timestep: "FrbcTimestep", actuator_id: str, timer_id: str
+ ) -> int:
+ actuator_description = S2FrbcDeviceStateWrapper.get_actuator_description(
+ target_timestep, actuator_id
+ )
+ if actuator_description is None:
+ raise ValueError(
+ f"Actuator description not found for actuator {actuator_id}"
+ )
+
+ timer = next(
+ (t for t in actuator_description.timers if str(t.id) == timer_id), None
+ )
+ return timer.duration.root if timer else 0
+
+ @staticmethod
+ @lru_cache(maxsize=None)
+ def get_actuator_description(
+ target_timestep: "FrbcTimestep", actuator_id: str
+ ) -> Optional[FRBCActuatorDescription]:
+ return next(
+ (
+ ad
+ for ad in target_timestep.system_description.actuators
+ if str(ad.id) == actuator_id
+ ),
+ None,
+ )
+
+ def get_energy_in_current_timestep(self) -> CommodityQuantity:
+ return self.device_state.get_energy_in_current_timestep()
+
+ @property
+ def actuator_statuses(self) -> List[FRBCActuatorStatus]:
+ return self.device_state.actuator_statuses or []
+
+ @property
+ def storage_status(self) -> List[FRBCStorageStatus]:
+ # The return type is correct but mypy does not understand the structure of s2python correctly
+ return self.device_state.storage_status # type: ignore[return-value]
diff --git a/flexmeasures_s2/profile_steering/device_planner/frbc/s2_frbc_instruction_profile.py b/flexmeasures_s2/profile_steering/device_planner/frbc/s2_frbc_instruction_profile.py
new file mode 100644
index 0000000..c3fe6d8
--- /dev/null
+++ b/flexmeasures_s2/profile_steering/device_planner/frbc/s2_frbc_instruction_profile.py
@@ -0,0 +1,20 @@
+from typing import List
+from datetime import datetime, timedelta
+from s2python.frbc import FRBCInstruction
+
+
+class S2FrbcInstructionProfile:
+ # This class represents a profile generated by the Reflex as a list of
+ # S2 FRBCInstruction s
+ def __init__(
+ self,
+ profile_start: datetime,
+ timestep_duration: timedelta,
+ elements: List[FRBCInstruction],
+ ):
+ self.profile_start = profile_start
+ self.timestep_duration = timestep_duration
+ self.elements = elements
+
+ def __str__(self) -> str:
+ return f"S2FrbcInstructionProfile(elements={self.elements})"
diff --git a/flexmeasures_s2/profile_steering/device_planner/frbc/s2_frbc_plan.py b/flexmeasures_s2/profile_steering/device_planner/frbc/s2_frbc_plan.py
new file mode 100644
index 0000000..0e147ba
--- /dev/null
+++ b/flexmeasures_s2/profile_steering/device_planner/frbc/s2_frbc_plan.py
@@ -0,0 +1,28 @@
+from typing import List, Dict
+from flexmeasures_s2.profile_steering.device_planner.frbc.s2_frbc_actuator_configuration import (
+ S2ActuatorConfiguration,
+)
+from flexmeasures_s2.profile_steering.common.joule_profile import JouleProfile
+
+
+class S2FrbcPlan:
+ def __init__(
+ self,
+ idle: bool,
+ energy: JouleProfile,
+ fill_level,
+ operation_mode_id: List[Dict[str, S2ActuatorConfiguration]],
+ ):
+ self.idle = idle
+ self.energy = energy
+ self.fill_level = fill_level
+ self.operation_mode_id = operation_mode_id
+
+ def is_idle(self) -> bool:
+ return self.idle
+
+ def get_energy(self):
+ return self.energy
+
+ def get_operation_mode_id(self) -> List[Dict[str, S2ActuatorConfiguration]]:
+ return self.operation_mode_id
diff --git a/flexmeasures_s2/profile_steering/device_planner/frbc/selection_reason_result.py b/flexmeasures_s2/profile_steering/device_planner/frbc/selection_reason_result.py
new file mode 100644
index 0000000..757b859
--- /dev/null
+++ b/flexmeasures_s2/profile_steering/device_planner/frbc/selection_reason_result.py
@@ -0,0 +1,20 @@
+from enum import Enum
+
+
+class SelectionReason(Enum):
+ CONGESTION_CONSTRAINT = "C"
+ ENERGY_TARGET = "E"
+ TARIFF_TARGET = "T"
+ MIN_ENERGY = "M"
+ NO_ALTERNATIVE = "_"
+ EMERGENCY_STATE = "!"
+
+
+class SelectionResult:
+ def __init__(
+ self,
+ result: bool,
+ reason: SelectionReason,
+ ):
+ self.result = result
+ self.reason = reason
diff --git a/flexmeasures_s2/profile_steering/device_planner/frbc/usage_forecast_util.py b/flexmeasures_s2/profile_steering/device_planner/frbc/usage_forecast_util.py
new file mode 100644
index 0000000..9f9cbd6
--- /dev/null
+++ b/flexmeasures_s2/profile_steering/device_planner/frbc/usage_forecast_util.py
@@ -0,0 +1,116 @@
+# TODO: Is this the same as FRBCUsageForecast?
+
+
+from datetime import timedelta, timezone
+from typing import List
+
+
+class UsageForecastElement:
+ def __init__(self, start, end, usage_rate):
+ self.start = start.replace(tzinfo=None)
+ self.end = end.replace(tzinfo=None)
+ self.usage_rate = usage_rate
+
+ def get_usage(self):
+ seconds = (
+ self.end - self.start
+ ).total_seconds() + 0.001 # one milisecond added for the offeset from total_Seconds()
+ return seconds * self.usage_rate
+
+ def split(self, date):
+ if self.date_in_element(date):
+ return [
+ UsageForecastElement(self.start, date, self.usage_rate),
+ UsageForecastElement(date, self.end, self.usage_rate),
+ ]
+ # If the date is not within the range, return the element itself twice to avoid index errors
+ return [self, self]
+
+ def date_in_element(self, date):
+ # Ensure both self.start and self.end are offset-naive or offset-aware
+ if self.start.tzinfo is None and self.end.tzinfo is None:
+ # Make date offset-naive
+ date = date.replace(tzinfo=None)
+ elif self.start.tzinfo is not None and self.end.tzinfo is not None:
+ # Make date offset-aware with the same timezone as self.start
+ date = date.astimezone(self.start.tzinfo)
+ else:
+ # If there's a mismatch, raise an error or handle it appropriately
+ raise ValueError("Mismatch between offset-naive and offset-aware datetimes")
+
+ return self.start <= date <= self.end
+
+
+class UsageForecastUtil:
+ @staticmethod
+ def from_storage_usage_profile(usage_forecast):
+ elements = []
+ start = usage_forecast.start_time
+ start = start.astimezone(timezone.utc)
+ for element in usage_forecast.elements:
+ end = start + timedelta(seconds=element.duration.root)
+ elements.append(
+ UsageForecastElement(start, end, element.usage_rate_expected)
+ )
+ start = end + timedelta(milliseconds=1)
+ return elements
+
+ @staticmethod
+ def sub_profile(
+ usage_forecast: List[UsageForecastElement], time_step_start, time_step_end
+ ):
+ if usage_forecast is None:
+ return 0
+
+ usage = 0
+ time_step_start = time_step_start.replace(tzinfo=None)
+ time_step_end = time_step_end.replace(tzinfo=None)
+
+ for element in usage_forecast:
+ element_start = element.start.replace(tzinfo=None)
+ element_end = element.end.replace(tzinfo=None)
+
+ if element_start < time_step_start < element_end < time_step_end:
+ # case 1: ....s.....e
+ # case 1: |-------|..
+ split = element.split(time_step_start)
+ usage += split[1].get_usage()
+ elif element_start > time_step_start and element_end < time_step_end:
+ # case 2: s...........e
+ # case 2: ..|-------|..
+ usage += element.get_usage()
+ elif time_step_start < element_start < time_step_end < element_end:
+ # case 3: s.....e....
+ # case 3: ..|-------|
+ split = element.split(time_step_end)
+ usage += split[0].get_usage()
+ elif element_start < time_step_start and element_end > time_step_end:
+ # case 4: ....s...e....
+ # case 4: |-----------|
+ split1 = element.split(time_step_start)
+ split2 = split1[1].split(time_step_end)
+ usage += split2[0].get_usage()
+ elif element_start == time_step_start and element_end == time_step_end:
+ # case 5: s...........e
+ # case 5: |-----------|
+ usage += element.get_usage()
+ elif element_start == time_step_start and element_end > time_step_end:
+ # case 6: s.....e......
+ # case 6: |-----------|
+ split = element.split(time_step_end)
+ usage += split[0].get_usage()
+ elif element_start == time_step_start and element_end < time_step_end:
+ # case 7: s...............e
+ # case 7: |-----------|....
+ usage += element.get_usage()
+ elif element_start < time_step_start and element_end == time_step_end:
+ # case 8: ......s.....e
+ # case 8: |-----------|
+ split = element.split(time_step_start)
+ usage += split[1].get_usage()
+ elif element_start > time_step_start and element_end == time_step_end:
+ # case 9: s...........e
+ # case 9: ...|--------|
+ usage += element.get_usage()
+
+ return usage
diff --git a/flexmeasures_s2/profile_steering/device_planner/nocontrol/__init__.py b/flexmeasures_s2/profile_steering/device_planner/nocontrol/__init__.py
new file mode 100644
index 0000000..42bd487
--- /dev/null
+++ b/flexmeasures_s2/profile_steering/device_planner/nocontrol/__init__.py
@@ -0,0 +1,19 @@
+from flexmeasures_s2.profile_steering.device_planner.nocontrol.s2_nocontrol_device_state import (
+ S2NoControlDeviceState,
+)
+from flexmeasures_s2.profile_steering.device_planner.nocontrol.s2_nocontrol_device_planner import (
+ S2NoControlDevicePlanner,
+)
+from flexmeasures_s2.profile_steering.device_planner.nocontrol.s2_nocontrol_plan import (
+ S2NoControlPlan,
+)
+from flexmeasures_s2.profile_steering.device_planner.nocontrol.proposal_without_improvement import (
+ ProposalWithoutImprovement,
+)
+
+__all__ = [
+ "S2NoControlDeviceState",
+ "S2NoControlDevicePlanner",
+ "S2NoControlPlan",
+ "ProposalWithoutImprovement",
+]
diff --git a/flexmeasures_s2/profile_steering/device_planner/nocontrol/conversion_utils.py b/flexmeasures_s2/profile_steering/device_planner/nocontrol/conversion_utils.py
new file mode 100644
index 0000000..7da1c11
--- /dev/null
+++ b/flexmeasures_s2/profile_steering/device_planner/nocontrol/conversion_utils.py
@@ -0,0 +1,115 @@
+from typing import Optional
+from s2python.common import PowerForecast, CommodityQuantity
+from flexmeasures_s2.profile_steering.common.joule_profile import JouleProfile
+from flexmeasures_s2.profile_steering.common.profile_metadata import ProfileMetadata
+
+
+def convert_power_forecast_to_joule_profile(
+ power_forecast: Optional[PowerForecast], profile_metadata: ProfileMetadata
+) -> JouleProfile:
+ """
+ Convert a PowerForecast to a JouleProfile.
+ This is a Python port of the Java ConversionTools.convertPowerForecast method.
+
+ Args:
+ power_forecast: The power forecast to convert (can be None)
+ profile_metadata: The metadata for the output profile
+
+ Returns:
+ A JouleProfile with energy values aligned to the profile metadata
+ """
+ if power_forecast is None:
+ return JouleProfile(
+ metadata=profile_metadata,
+ value=0,
+ )
+
+ forecast_start = power_forecast.start_time
+ profile_start = profile_metadata.profile_start
+
+ if forecast_start > profile_start:
+ current_forecast_element_index = 0
+ time_diff = forecast_start - profile_start
+ current_profile_step_index = int(
+ time_diff.total_seconds()
+ / profile_metadata.timestep_duration.total_seconds()
+ )
+ else:
+ current_profile_step_index = 0
+ time_diff = profile_start - forecast_start
+ seconds_into_forecast = time_diff.total_seconds()
+ current_forecast_element_index = 0
+ accumulated_seconds = 0
+
+ for i, element in enumerate(power_forecast.elements):
+ # element.duration is a Duration object with .root in milliseconds
+ # We need to convert to seconds
+ element_duration_seconds = element.duration.root / 1000.0
+ if accumulated_seconds + element_duration_seconds > seconds_into_forecast:
+ current_forecast_element_index = i
+ break
+ accumulated_seconds += element_duration_seconds
+ else:
+ current_forecast_element_index = len(power_forecast.elements)
+
+ joule_profile_elements = [0] * profile_metadata.nr_of_timesteps
+ forecast_elements = power_forecast.elements
+
+ if (
+ 0 <= current_profile_step_index < profile_metadata.nr_of_timesteps
+ and 0 <= current_forecast_element_index < len(forecast_elements)
+ ):
+ profile_timestep_duration_seconds = (
+ profile_metadata.timestep_duration.total_seconds()
+ )
+ while (
+ current_profile_step_index < profile_metadata.nr_of_timesteps
+ and current_forecast_element_index < len(forecast_elements)
+ ):
+ forecast_element = forecast_elements[current_forecast_element_index]
+ power_watts = get_expected_electrical_power(forecast_element)
+ # forecast_element.duration is a Duration object with .root in milliseconds
+ # We need to convert to seconds
+ forecast_element_duration_seconds = forecast_element.duration.root / 1000.0
+ energy_joules = int(power_watts * forecast_element_duration_seconds)
+ number_of_profile_steps = int(
+ forecast_element_duration_seconds / profile_timestep_duration_seconds
+ )
+
+ for _ in range(number_of_profile_steps):
+ if current_profile_step_index >= profile_metadata.nr_of_timesteps:
+ break
+ joule_profile_elements[current_profile_step_index] = int(
+ energy_joules / number_of_profile_steps
+ )
+ current_profile_step_index += 1
+
+ current_forecast_element_index += 1
+
+ return JouleProfile(
+ metadata=profile_metadata,
+ elements=joule_profile_elements, # type: ignore[arg-type]
+ )
+
+
+def get_expected_electrical_power(forecast_element) -> float:
+ """
+ Get the expected electrical power from a forecast element.
+ Sums up all electrical power values from different phases.
+
+ Args:
+ forecast_element: The forecast element containing power values
+
+ Returns:
+ The total expected electrical power in watts
+ """
+ expected_power = 0.0
+ for power_value in forecast_element.power_values:
+ if power_value.commodity_quantity in [
+ CommodityQuantity.ELECTRIC_POWER_L1,
+ CommodityQuantity.ELECTRIC_POWER_L2,
+ CommodityQuantity.ELECTRIC_POWER_L3,
+ CommodityQuantity.ELECTRIC_POWER_3_PHASE_SYMMETRIC,
+ ]:
+ expected_power += power_value.value_expected
+ return expected_power
diff --git a/flexmeasures_s2/profile_steering/device_planner/nocontrol/proposal_without_improvement.py b/flexmeasures_s2/profile_steering/device_planner/nocontrol/proposal_without_improvement.py
new file mode 100644
index 0000000..ace296c
--- /dev/null
+++ b/flexmeasures_s2/profile_steering/device_planner/nocontrol/proposal_without_improvement.py
@@ -0,0 +1,36 @@
+from flexmeasures_s2.profile_steering.common.proposal import Proposal
+from flexmeasures_s2.profile_steering.common.joule_profile import JouleProfile
+from flexmeasures_s2.profile_steering.device_planner.device_planner_abstract import (
+ DevicePlanner,
+)
+
+
+class ProposalWithoutImprovement(Proposal):
+ """
+ A special proposal for devices that cannot be improved.
+ This is used for nocontrol devices that have fixed power forecasts.
+ """
+
+ def __init__(self, plan: JouleProfile, origin: DevicePlanner):
+ # Call parent constructor with None for diff profiles since
+ # nocontrol devices don't respond to target profiles
+ super().__init__(
+ global_diff_target=None, # type: ignore[arg-type]
+ diff_to_congestion_max=None, # type: ignore[arg-type]
+ diff_to_congestion_min=None, # type: ignore[arg-type]
+ proposed_plan=plan,
+ old_plan=plan,
+ origin=origin,
+ )
+
+ def get_global_improvement_value(self) -> float:
+ """No improvement possible for nocontrol devices."""
+ return 0.0
+
+ def get_congestion_improvement_value(self) -> float:
+ """No improvement possible for nocontrol devices."""
+ return 0.0
+
+ def get_cost_improvement_value(self) -> float:
+ """No improvement possible for nocontrol devices."""
+ return 0.0
diff --git a/flexmeasures_s2/profile_steering/device_planner/nocontrol/s2_nocontrol_device_planner.py b/flexmeasures_s2/profile_steering/device_planner/nocontrol/s2_nocontrol_device_planner.py
new file mode 100644
index 0000000..3dfee0f
--- /dev/null
+++ b/flexmeasures_s2/profile_steering/device_planner/nocontrol/s2_nocontrol_device_planner.py
@@ -0,0 +1,172 @@
+from datetime import datetime
+from flexmeasures_s2.profile_steering.device_planner.device_planner_abstract import (
+ DevicePlanner,
+)
+from flexmeasures_s2.profile_steering.common.joule_profile import JouleProfile
+from flexmeasures_s2.profile_steering.common.profile_metadata import ProfileMetadata
+from flexmeasures_s2.profile_steering.common.target_profile import TargetProfile
+from flexmeasures_s2.profile_steering.common.device_plan import DevicePlan
+from flexmeasures_s2.profile_steering.device_planner.nocontrol.s2_nocontrol_device_state import (
+ S2NoControlDeviceState,
+)
+from flexmeasures_s2.profile_steering.device_planner.nocontrol.proposal_without_improvement import (
+ ProposalWithoutImprovement,
+)
+from flexmeasures_s2.profile_steering.device_planner.nocontrol.conversion_utils import (
+ convert_power_forecast_to_joule_profile,
+)
+from flexmeasures_s2.profile_steering.device_planner.nocontrol.s2_nocontrol_plan import (
+ S2NoControlPlan,
+)
+
+
+class S2NoControlDevicePlanner(DevicePlanner):
+ """
+ Device planner for devices with no control capabilities.
+ These devices have a fixed power forecast and cannot be optimized.
+ This is a Python port of the Java S2NoControlDevicePlanner class.
+ """
+
+ def __init__(
+ self,
+ device_state: S2NoControlDeviceState,
+ profile_metadata: ProfileMetadata,
+ congestion_point_id: str,
+ ):
+ self.device_state = device_state
+ self.profile_metadata = profile_metadata
+ self._congestion_point_id = congestion_point_id
+
+ if device_state.get_power_forecast() is None:
+ self.profile = JouleProfile(metadata=profile_metadata, value=0)
+ else:
+ self.profile = convert_power_forecast_to_joule_profile(
+ device_state.get_power_forecast(), profile_metadata
+ )
+
+ @property
+ def device_id(self) -> str:
+ return self.device_state.device_id
+
+ @property
+ def device_name(self) -> str:
+ return self.device_state.device_name
+
+ @property
+ def connection_id(self) -> str:
+ return self.device_state.connection_id
+
+ @property
+ def congestion_point_id(self) -> str:
+ return self._congestion_point_id
+
+ @property
+ def priority_class(self) -> int:
+ return self.device_state.priority_class
+
+ def create_initial_planning(self, plan_due_by_date: datetime) -> S2NoControlPlan:
+ """
+ Create initial planning for a nocontrol device.
+ Since the device cannot be controlled, this just returns the fixed profile.
+
+ Args:
+ plan_due_by_date: The date by which the plan must be ready (not used)
+
+ Returns:
+ A plan with the fixed power forecast profile
+ """
+ return S2NoControlPlan(self.profile)
+
+ def create_improved_planning(
+ self,
+ difference_profile: TargetProfile,
+ diff_to_max_value: JouleProfile,
+ diff_to_min_value: JouleProfile,
+ plan_due_by_date: datetime,
+ ) -> ProposalWithoutImprovement:
+ """
+ Attempt to create an improved planning for a nocontrol device.
+ Since the device cannot be controlled, this returns a proposal with no improvement.
+
+ Args:
+ difference_profile: The difference profile for the cluster (not used)
+ diff_to_max_value: The difference to the maximum profile (not used)
+ diff_to_min_value: The difference to the minimum profile (not used)
+ plan_due_by_date: The date by which the plan must be ready (not used)
+
+ Returns:
+ A ProposalWithoutImprovement indicating no changes can be made
+ """
+ return ProposalWithoutImprovement(self.profile, self)
+
+ def accept_proposal(self, proposal: ProposalWithoutImprovement) -> None: # type: ignore[override]
+ """
+ Accept a proposal for this device.
+ Validates that the proposal is from this planner and hasn't changed.
+
+ Args:
+ proposal: The proposal to accept
+
+ Raises:
+ ValueError: If the proposal is not valid
+ """
+ if proposal.origin != self:
+ raise ValueError(
+ f"Planner for '{self.device_id}' received a proposal that it did not send."
+ )
+
+ if proposal.proposed_plan != self.profile or proposal.old_plan != self.profile:
+ raise ValueError(
+ f"Planner for '{self.device_id}' received a proposal that it did not send."
+ )
+
+ def current_profile(self) -> JouleProfile:
+ """
+ Get the current profile of the device.
+
+ Returns:
+ The fixed power forecast profile
+ """
+ return self.profile
+
+ def get_device_plan(self) -> DevicePlan:
+ """
+ Get the device plan for this nocontrol device.
+
+ Returns:
+ A DevicePlan with the fixed energy profile
+ """
+ return DevicePlan(
+ device_id=self.device_id,
+ device_name=self.device_name,
+ connection_id=self.connection_id,
+ energy_profile=self.profile,
+ fill_level_profile=None,
+ instruction_profile=None,
+ )
+
+ def get_latest_plan(self) -> S2NoControlPlan:
+ """
+ Get the latest calculated plan.
+ For nocontrol devices, this is always the fixed profile.
+
+ Returns:
+ A plan with the fixed power forecast profile
+ """
+ return S2NoControlPlan(self.profile)
+
+ def set_accepted_plan(self, plan: S2NoControlPlan) -> None:
+ """
+ Forcefully set the accepted plan for the device.
+ For nocontrol devices, this validates but doesn't change the profile.
+
+ Args:
+ plan: The plan to set
+
+ Raises:
+ ValueError: If the plan doesn't match the fixed profile
+ """
+ if not isinstance(plan, S2NoControlPlan) or plan.energy != self.profile:
+ raise ValueError(
+ f"Cannot set a different plan for nocontrol device '{self.device_id}'"
+ )
diff --git a/flexmeasures_s2/profile_steering/device_planner/nocontrol/s2_nocontrol_device_state.py b/flexmeasures_s2/profile_steering/device_planner/nocontrol/s2_nocontrol_device_state.py
new file mode 100644
index 0000000..42f9d68
--- /dev/null
+++ b/flexmeasures_s2/profile_steering/device_planner/nocontrol/s2_nocontrol_device_state.py
@@ -0,0 +1,33 @@
+from datetime import datetime
+from typing import Optional
+from s2python.common import PowerForecast, PowerValue
+
+
+class S2NoControlDeviceState:
+ """
+ Device state for devices with no control capabilities.
+ These devices only provide a power forecast and cannot be controlled.
+ """
+
+ def __init__(
+ self,
+ device_id: str,
+ device_name: str,
+ connection_id: str,
+ priority_class: int,
+ timestamp: datetime,
+ energy_in_current_timestep: PowerValue,
+ is_online: bool,
+ power_forecast: Optional[PowerForecast],
+ ):
+ self.device_id = device_id
+ self.device_name = device_name
+ self.connection_id = connection_id
+ self.priority_class = priority_class
+ self.timestamp = timestamp
+ self.energy_in_current_timestep = energy_in_current_timestep
+ self.is_online = is_online
+ self.power_forecast = power_forecast
+
+ def get_power_forecast(self) -> Optional[PowerForecast]:
+ return self.power_forecast
diff --git a/flexmeasures_s2/profile_steering/device_planner/nocontrol/s2_nocontrol_plan.py b/flexmeasures_s2/profile_steering/device_planner/nocontrol/s2_nocontrol_plan.py
new file mode 100644
index 0000000..66366c2
--- /dev/null
+++ b/flexmeasures_s2/profile_steering/device_planner/nocontrol/s2_nocontrol_plan.py
@@ -0,0 +1,14 @@
+from flexmeasures_s2.profile_steering.common.joule_profile import JouleProfile
+
+
+class S2NoControlPlan:
+ """
+ Simple plan wrapper for nocontrol devices.
+ Since these devices cannot be controlled, the plan only contains an energy profile.
+ """
+
+ def __init__(self, energy: JouleProfile):
+ self.energy = energy
+
+ def get_energy(self) -> JouleProfile:
+ return self.energy
diff --git a/flexmeasures_s2/profile_steering/examples/example_schedule_ddbc.py b/flexmeasures_s2/profile_steering/examples/example_schedule_ddbc.py
new file mode 100644
index 0000000..2636694
--- /dev/null
+++ b/flexmeasures_s2/profile_steering/examples/example_schedule_ddbc.py
@@ -0,0 +1,560 @@
+"""
+Example script demonstrating DDBC (Demand-Driven Based Control) planning using Flask scheduler.
+
+This script tests the S2FlaskScheduler with DDBC devices, following the pattern from
+example_schedule_itho_flask.py and using test data from S2DdbcTests.java.
+
+The test creates a hybrid heating system with:
+- A gas boiler actuator (natural gas commodity)
+- A heat pump actuator (electricity commodity)
+- Average demand rate forecasts
+- Tariff-based targets to optimize cost
+
+To run:
+ python flexmeasures_s2/profile_steering/examples/example_schedule_ddbc.py
+"""
+
+from datetime import datetime, timedelta, timezone
+from typing import Optional
+import logging
+import time
+import pandas as pd
+import os
+import uuid
+from decimal import Decimal
+
+# Import Flask app creation
+from flexmeasures.app import create as create_flexmeasures_app
+
+from flexmeasures_s2.scheduler.scheduler_flask import S2FlaskScheduler
+from flexmeasures_s2.profile_steering.device_planner.ddbc.s2_ddbc_device_state import (
+ S2DdbcDeviceState,
+)
+from s2python.ddbc import (
+ DDBCSystemDescription,
+ DDBCAverageDemandRateForecast,
+ DDBCAverageDemandRateForecastElement,
+ DDBCActuatorDescription,
+ DDBCActuatorStatus,
+ DDBCOperationMode,
+)
+from s2python.common import NumberRange, PowerRange, CommodityQuantity, Commodity
+import matplotlib.pyplot as plt
+
+# Configuration parameters
+PLANNING_WINDOW = pd.Timedelta("PT24H")
+PLANNING_RESOLUTION = pd.Timedelta("PT1H")
+# S = 30 # Number of stratification layers (matching FRBC) [NOT CURRENTLY USED]
+
+T = PLANNING_WINDOW // PLANNING_RESOLUTION # number of time steps
+TIMESTEP_DURATION = PLANNING_RESOLUTION / pd.Timedelta("PT1S")
+
+
+class MockDDBCDeviceData:
+ """Mock DDBCDeviceData class to mimic the structure from WebSocket integration."""
+
+ def __init__(self):
+ self.system_description = None
+ self.demand_forecasts = []
+ self.actuator_statuses = {}
+ self.resource_id = None
+
+
+def create_hybrid_heating_device_state(
+ device_id: str,
+ start_time: datetime,
+ profile_length: int = T,
+) -> S2DdbcDeviceState:
+ """
+ Create a DDBC device state for a hybrid heating system.
+
+ Based on S2DdbcTests.java testByPlotting() method.
+ System has:
+ - Gas boiler: 0-20kW (using natural gas)
+ - Heat pump: 0-2kW (using electricity)
+ - Both provide 0-10kW heating supply rate
+ - Average demand rate forecast of 10kW
+
+ Args:
+ device_id: Unique device identifier
+ start_time: Start time for planning
+ profile_length: Number of timesteps
+
+ Returns:
+ S2DdbcDeviceState configured for hybrid heating
+ """
+ # Create UUIDs for actuators and operation modes
+ # Use strings - s2python validates and expects string UUIDs
+ gas_id = str(uuid.uuid4())
+ gas_work_mode_id = str(uuid.uuid4())
+ hp_id = str(uuid.uuid4())
+ hp_work_mode_id = str(uuid.uuid4())
+
+ # Gas boiler actuator
+ # 1 m3 gas ~= 10 kWh energy, so 20kW boiler uses ~2000/3600 liters/second
+ gas_actuator = DDBCActuatorDescription(
+ id=gas_id,
+ diagnostic_label="gas",
+ supported_commodites=[Commodity.GAS],
+ operation_modes=[
+ DDBCOperationMode(
+ Id=gas_work_mode_id,
+ id=gas_work_mode_id,
+ diagnostic_label="gas_on",
+ power_ranges=[
+ PowerRange(
+ start_of_range=Decimal("0"),
+ end_of_range=Decimal(str(2000 / 3600)), # liters/second
+ commodity_quantity=CommodityQuantity.NATURAL_GAS_FLOW_RATE,
+ )
+ ],
+ supply_range=[
+ NumberRange(
+ start_of_range=Decimal("0"),
+ end_of_range=Decimal("10000"), # 10kW in Watts
+ )
+ ],
+ abnormal_condition_only=False,
+ )
+ ],
+ transitions=[],
+ timers=[],
+ )
+
+ # Heat pump actuator
+ hp_actuator = DDBCActuatorDescription(
+ id=hp_id,
+ diagnostic_label="hp",
+ supported_commodites=[Commodity.ELECTRICITY],
+ operation_modes=[
+ DDBCOperationMode(
+ Id=hp_work_mode_id,
+ id=hp_work_mode_id,
+ diagnostic_label="hp_on",
+ power_ranges=[
+ PowerRange(
+ start_of_range=Decimal("0"),
+ end_of_range=Decimal("2000"), # 2kW in Watts
+ commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1,
+ )
+ ],
+ supply_range=[
+ NumberRange(
+ start_of_range=Decimal("0"),
+ end_of_range=Decimal("10000"), # 10kW in Watts
+ )
+ ],
+ abnormal_condition_only=False,
+ )
+ ],
+ transitions=[],
+ timers=[],
+ )
+
+ # Actuator statuses - both starting in their "on" mode at factor 1.0
+ # Dictionary keys must be strings to match device state wrapper expectations
+ actuator_statuses = {
+ gas_id: DDBCActuatorStatus(
+ message_id=str(uuid.uuid4()),
+ actuator_id=gas_id,
+ active_operation_mode_id=gas_work_mode_id,
+ operation_mode_factor=Decimal("1.0"),
+ ),
+ hp_id: DDBCActuatorStatus(
+ message_id=str(uuid.uuid4()),
+ actuator_id=hp_id,
+ active_operation_mode_id=hp_work_mode_id,
+ operation_mode_factor=Decimal("1.0"),
+ ),
+ }
+
+ # System description
+ system_description = DDBCSystemDescription(
+ message_id=str(uuid.uuid4()),
+ valid_from=start_time,
+ actuators=[gas_actuator, hp_actuator],
+ present_demand_rate=NumberRange(
+ start_of_range=Decimal("10000"),
+ end_of_range=Decimal("10000"),
+ ),
+ provides_average_demand_rate_forecast=True,
+ )
+
+ # Average demand rate forecast - constant 10kW demand
+ demand_rate_elements = []
+ for _ in range(profile_length):
+ demand_rate_elements.append(
+ DDBCAverageDemandRateForecastElement(
+ duration=int(TIMESTEP_DURATION),
+ demand_rate_expected=Decimal("10000"), # 10kW constant demand
+ )
+ )
+
+ demand_rate_forecast = DDBCAverageDemandRateForecast(
+ message_id=str(uuid.uuid4()),
+ start_time=start_time,
+ elements=demand_rate_elements,
+ )
+
+ # Create device state
+ device_state = S2DdbcDeviceState(
+ device_id=device_id,
+ device_name=f"DDBC Hybrid Heating {device_id}",
+ connection_id=f"{device_id}_connection",
+ priority_class=0,
+ timestamp=start_time,
+ energy_in_current_timestep=0.0,
+ is_online=True,
+ power_forecast=None,
+ system_descriptions=[system_description],
+ demand_forecasts=[demand_rate_forecast],
+ actuator_statuses=actuator_statuses,
+ gas_price_per_m3=2.0, # €2 per m3 of gas
+ )
+
+ return device_state
+
+
+def create_ddbc_device_data_from_device_state(
+ device_state: S2DdbcDeviceState, resource_id: str
+) -> MockDDBCDeviceData:
+ """Convert S2DdbcDeviceState to DDBCDeviceData structure for scheduler."""
+ ddbc_data = MockDDBCDeviceData()
+ ddbc_data.resource_id = resource_id
+
+ if device_state.system_descriptions:
+ ddbc_data.system_description = device_state.system_descriptions[0]
+
+ ddbc_data.demand_forecasts = device_state.demand_forecasts
+
+ if hasattr(device_state, "actuator_statuses"):
+ # Convert dict to dict (already in correct format)
+ ddbc_data.actuator_statuses = device_state.actuator_statuses
+
+ return ddbc_data
+
+
+def get_cost_target_profile_elements(num_elements: int) -> list:
+ """
+ Create cost target profile with time-of-use pricing.
+
+ Simulates variable electricity pricing to test cost optimization:
+ - Night hours (00:00-06:00): 0.10 EUR/kWh (cheap - favor heat pump)
+ - Morning (06:00-09:00): 0.15 EUR/kWh
+ - Peak hours (09:00-17:00): 0.30 EUR/kWh (expensive - favor gas boiler)
+ - Evening (17:00-21:00): 0.25 EUR/kWh
+ - Late evening (21:00-24:00): 0.15 EUR/kWh
+ """
+ cost_elements = []
+
+ for timestep in range(num_elements):
+ # Calculate hour of day based on timestep
+ hour = (timestep * int(PLANNING_RESOLUTION.total_seconds() / 60)) // 60
+
+ if 0 <= hour < 6:
+ tariff = 0.10 # Cheap night rate
+ elif 6 <= hour < 9:
+ tariff = 0.15
+ elif 9 <= hour < 17:
+ tariff = 0.30 # Expensive peak rate
+ elif 17 <= hour < 21:
+ tariff = 0.25
+ else:
+ tariff = 0.15
+
+ cost_elements.append(tariff)
+
+ return cost_elements
+
+
+def test_ddbc_with_flask_scheduler(
+ profile_length: int = T,
+ use_tariff_target: bool = True,
+ suffix="_ddbc_flask",
+):
+ """
+ Test S2FlaskScheduler with DDBC hybrid heating device.
+
+ Args:
+ profile_length: Number of timesteps to plan
+ use_tariff_target: If True, use tariff-based targets; else use joule targets
+ suffix: Suffix for output files
+ """
+ print("=" * 80)
+ print("Test: DDBC Hybrid Heating with S2FlaskScheduler")
+ print(f" Profile length: {profile_length} timesteps")
+ print(f" Target type: {'Tariff' if use_tariff_target else 'Joule'}")
+ print("=" * 80)
+
+ # Create Flask app for scheduler context
+ app = create_flexmeasures_app(env="development")
+
+ with app.app_context():
+ # Configure app settings for scheduler
+ app.config.setdefault(
+ "FLEXMEASURES_S2_TARGET_MODE", "costs" if use_tariff_target else "energy"
+ )
+ app.config.setdefault("FLEXMEASURES_S2_PRICE_SENSOR", 2)
+
+ # Start time aligned to the planning resolution
+ resolution_minutes = PLANNING_RESOLUTION // pd.Timedelta(minutes=1)
+ now = datetime.now(timezone.utc)
+ minutes = (now.minute // resolution_minutes) * resolution_minutes
+ start_time = now.replace(minute=minutes, second=0, microsecond=0)
+ print(f"Planning start time: {start_time}")
+
+ # Create device state
+ device_id = f"ddbc_hybrid_heating_{suffix}"
+ device_state = create_hybrid_heating_device_state(
+ device_id,
+ start_time,
+ profile_length,
+ )
+
+ # Convert device state to DDBCDeviceData format
+ ddbc_device_data = create_ddbc_device_data_from_device_state(
+ device_state, device_id
+ )
+
+ # Create S2FlaskScheduler instance
+ scheduler = S2FlaskScheduler.__new__(S2FlaskScheduler)
+
+ # Set basic time parameters
+ scheduler.sensor = None
+ scheduler.asset = None
+ scheduler.start = start_time
+ scheduler.end = start_time + timedelta(
+ seconds=TIMESTEP_DURATION * profile_length
+ )
+ scheduler.resolution = PLANNING_RESOLUTION
+ scheduler.belief_time = start_time
+ scheduler.round_to_decimals = 6
+ scheduler.flex_model = {}
+ scheduler.flex_context = {}
+ scheduler.fallback_scheduler_class = None
+
+ # Initialize scheduler attributes
+ scheduler.planning_service = None
+ scheduler.config_deserialized = False
+ scheduler.ddbc_device_data = ddbc_device_data # Note: using ddbc_device_data
+
+ # Set data source if available
+ try:
+ from flexmeasures.data.services.users import get_or_create_source
+ from flexmeasures import User
+
+ user = User.query.first()
+ if user:
+ data_source = get_or_create_source(user)
+ scheduler.data_source = data_source
+ except Exception as e:
+ app.logger.debug(f"Could not set data source: {e}")
+ scheduler.data_source = None
+
+ print(
+ f"Scheduler window: {scheduler.start.strftime('%Y-%m-%d %H:%M:%S')} → "
+ f"{scheduler.end.strftime('%Y-%m-%d %H:%M:%S')}"
+ )
+ print("Generating plan using S2FlaskScheduler...")
+
+ # Generate plan using scheduler
+ start_planning_time = time.time()
+
+ # Note: The scheduler's compute() method will call PlanningServiceImpl
+ # which should handle DDBC devices if properly integrated
+ schedule_results = scheduler.compute()
+
+ end_planning_time = time.time()
+ execution_time = end_planning_time - start_planning_time
+
+ print(f"Plan generated in {execution_time:.2f} seconds")
+
+ # Extract results
+ energy_data = [
+ result
+ for result in schedule_results
+ if isinstance(result, dict) and "device" in result and "data" in result
+ ]
+
+ print(f"Generated energy data for {len(energy_data)} device(s)")
+
+ # Process energy profile
+ total_energy_kwh = 0.0
+ if energy_data:
+ energy_series = energy_data[0]["data"]
+ # Convert Joules to kWh - trim to profile_length
+ energy_values_joules = [
+ energy if pd.notna(energy) else 0
+ for energy in energy_series.values[:profile_length]
+ ]
+ total_energy_joules = sum(energy_values_joules)
+ total_energy_kwh = total_energy_joules / 3_600_000
+ print(f"Total energy consumption: {total_energy_kwh:.2f} kWh")
+
+ # Convert to Watts for plotting
+ power_profile_watts = [
+ energy / TIMESTEP_DURATION if energy > 0 else 0
+ for energy in energy_values_joules
+ ]
+ else:
+ power_profile_watts = [0] * profile_length
+ print("Warning: No energy data generated")
+
+ # Create cost profile if using tariff targets
+ cost_elements = None
+ cost_target_elements = None
+ if use_tariff_target:
+ cost_target_elements = get_cost_target_profile_elements(profile_length)
+ if energy_data:
+ # Calculate actual costs
+ cost_elements = []
+ for energy_joules, tariff in zip(
+ energy_values_joules, cost_target_elements
+ ):
+ kwh = energy_joules / 3_600_000
+ cost = kwh * tariff
+ cost_elements.append(cost)
+ total_cost = sum(cost_elements)
+ print(f"Total cost: ${total_cost:.4f}")
+
+ # Plot results
+ plot_ddbc_results(
+ timestep_duration=timedelta(seconds=TIMESTEP_DURATION),
+ nr_of_timesteps=profile_length,
+ energy_profile=power_profile_watts,
+ cost_elements=cost_elements,
+ cost_target_elements=cost_target_elements,
+ start_time=start_time,
+ suffix=suffix,
+ )
+
+ print("DDBC Flask scheduler test completed!")
+
+ return {
+ "energy_profile": power_profile_watts,
+ "total_energy_kwh": total_energy_kwh if energy_data else 0,
+ "total_cost": sum(cost_elements) if cost_elements else 0,
+ }
+
+
+def plot_ddbc_results(
+ timestep_duration: timedelta,
+ nr_of_timesteps: int,
+ energy_profile: list,
+ cost_elements: Optional[list] = None,
+ cost_target_elements: Optional[list] = None,
+ start_time: Optional[datetime] = None,
+ suffix: str = "",
+):
+ """Plot DDBC planning results."""
+ # Create output directory
+ os.makedirs("plots", exist_ok=True)
+
+ if cost_elements is not None or cost_target_elements is not None:
+ # Create dual-axis plot for energy and cost
+ fig, ax1 = plt.subplots(1, 1, figsize=(14, 8))
+
+ timesteps = list(range(nr_of_timesteps))
+
+ # Energy plot on left y-axis
+ ax1.plot(
+ timesteps,
+ energy_profile,
+ label="Energy (Watts)",
+ color="green",
+ linewidth=2,
+ marker="o",
+ )
+ ax1.set_ylabel("Power (Watts)", color="green")
+ ax1.tick_params(axis="y", labelcolor="green")
+ ax1.set_xlabel("Timestep")
+ ax1.grid(True, alpha=0.3)
+
+ # Cost plot on right y-axis
+ ax2 = ax1.twinx()
+ if cost_target_elements is not None:
+ ax2.plot(
+ timesteps,
+ cost_target_elements,
+ label="Tariff (EUR/kWh)",
+ color="orange",
+ linestyle="dashed",
+ linewidth=2,
+ marker="s",
+ )
+ if cost_elements is not None:
+ ax2.plot(
+ timesteps,
+ cost_elements,
+ label="Cost (EUR)",
+ color="blue",
+ linewidth=2,
+ marker="^",
+ )
+ ax2.set_ylabel("Cost / Tariff", color="blue")
+ ax2.tick_params(axis="y", labelcolor="blue")
+
+ # Combined legend
+ lines1, labels1 = ax1.get_legend_handles_labels()
+ lines2, labels2 = ax2.get_legend_handles_labels()
+ ax1.legend(lines1 + lines2, labels1 + labels2, loc="upper left")
+
+ ax1.set_title("DDBC Hybrid Heating - Energy and Cost Planning")
+ else:
+ # Simple energy plot
+ fig, ax1 = plt.subplots(1, 1, figsize=(12, 6))
+
+ timesteps = list(range(nr_of_timesteps))
+ ax1.plot(
+ timesteps,
+ energy_profile,
+ label="Energy (Watts)",
+ color="green",
+ linewidth=2,
+ marker="o",
+ )
+ ax1.set_xlabel("Timestep")
+ ax1.set_ylabel("Power (Watts)")
+ ax1.set_title("DDBC Hybrid Heating - Energy Profile")
+ ax1.legend()
+ ax1.grid(True)
+
+ plt.tight_layout()
+ plot_filename = f"plots/ddbc_planning_results{suffix}.png"
+ plt.savefig(plot_filename)
+ print(f"Plot saved to {plot_filename}")
+ plt.close()
+
+
+if __name__ == "__main__":
+ # Configure logging
+ logging.basicConfig(
+ level=logging.INFO,
+ format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
+ datefmt="%H:%M:%S",
+ )
+
+ print("=" * 80)
+ print("DDBC Hybrid Heating Planning with S2FlaskScheduler")
+ print("=" * 80)
+ print()
+
+ try:
+ # Test with tariff-based optimization
+ results = test_ddbc_with_flask_scheduler(
+ profile_length=T,
+ use_tariff_target=True,
+ suffix="_tariff",
+ )
+
+ print("\n" + "=" * 80)
+ print("Test Summary:")
+ print(f" Total energy: {results['total_energy_kwh']:.2f} kWh")
+ print(f" Total cost: ${results['total_cost']:.4f}")
+ print("=" * 80)
+ except Exception as e:
+ print(f"\n❌ Test failed with error: {e}")
+ import traceback
+
+ traceback.print_exc()
+ raise
diff --git a/flexmeasures_s2/profile_steering/examples/example_schedule_ddbc_simple.py b/flexmeasures_s2/profile_steering/examples/example_schedule_ddbc_simple.py
new file mode 100644
index 0000000..94d7243
--- /dev/null
+++ b/flexmeasures_s2/profile_steering/examples/example_schedule_ddbc_simple.py
@@ -0,0 +1,237 @@
+"""
+Simplified DDBC test that uses the simplified device state.
+This test demonstrates DDBC device integration without complex S2 objects.
+"""
+
+from flexmeasures_s2.profile_steering.common.target_profile import TargetProfile
+from datetime import datetime, timedelta, timezone
+
+# import time
+import pandas as pd
+import os
+from s2python.common import PowerValue, CommodityQuantity
+from flexmeasures_s2.profile_steering.device_planner.ddbc.s2_ddbc_device_state import (
+ S2DdbcDeviceState,
+)
+from flexmeasures_s2.profile_steering.common.profile_metadata import ProfileMetadata
+
+from flexmeasures_s2.scheduler.schedulers import (
+ PlanningServiceImpl,
+ PlanningServiceConfig,
+ ClusterState,
+ ClusterTarget,
+)
+import matplotlib.dates as mdates
+import matplotlib.pyplot as plt
+
+D = 2 # Number of DDBC devices
+PLANNING_WINDOW = pd.Timedelta("PT2H") # Shorter window for testing
+PLANNING_RESOLUTION = pd.Timedelta("PT5M")
+
+T = PLANNING_WINDOW // PLANNING_RESOLUTION
+TIMESTEP_DURATION = PLANNING_RESOLUTION / pd.Timedelta("PT1S")
+
+
+def create_ddbc_device_state_simple(
+ device_id: str, start_time: datetime
+) -> S2DdbcDeviceState:
+ """
+ Create a simplified DDBC device state for testing.
+ Uses empty lists for system descriptions and demand forecasts.
+ """
+ device_state = S2DdbcDeviceState(
+ device_id=device_id,
+ device_name=device_id,
+ connection_id=device_id + "_connection",
+ priority_class=1,
+ timestamp=start_time,
+ energy_in_current_timestep=PowerValue(
+ value=0, commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1
+ ),
+ is_online=True,
+ power_forecast=None, # DDBC devices don't need power forecast
+ system_descriptions=[], # Simplified: empty list
+ demand_forecasts=[], # Simplified: empty list
+ gas_price_per_m3=2.0, # €2 per m3 of gas
+ )
+ return device_state
+
+
+def plot_planning_results(
+ timestep_duration,
+ nr_of_timesteps,
+ predicted_energy_elements,
+ target_energy_elements,
+):
+ """
+ Plots the energy comparison for DDBC devices.
+ """
+ timestep_start_times = [
+ datetime(1970, 1, 1, tzinfo=timezone.utc)
+ + timedelta(seconds=i * timestep_duration.total_seconds())
+ for i in range(nr_of_timesteps)
+ ]
+
+ fig, ax1 = plt.subplots(1, 1, figsize=(12, 8))
+
+ ax1.plot(
+ timestep_start_times,
+ predicted_energy_elements,
+ label="Predicted Energy (DDBC Devices)",
+ color="blue",
+ )
+ ax1.plot(
+ timestep_start_times,
+ target_energy_elements,
+ label="Target Energy",
+ color="red",
+ linestyle="dotted",
+ linewidth=2,
+ )
+
+ ax1.set_ylabel("Energy (Joules)")
+ ax1.set_title("Predicted vs Target Energy - DDBC Devices (Simplified)")
+ ax1.legend(loc="best")
+ ax1.grid(True)
+
+ ax1.xaxis.set_major_locator(mdates.MinuteLocator(interval=15))
+ ax1.xaxis.set_major_formatter(mdates.DateFormatter("%H:%M"))
+ fig.autofmt_xdate()
+
+ plt.tight_layout()
+ os.makedirs("plots", exist_ok=True)
+ plt.savefig(f"plots/ddbc_simple_plot_D={D}_T={T}.png")
+ # print(f"Plot saved to plots/ddbc_simple_plot_D={D}_T={T}.png")
+
+
+def get_target_profile_elements(number_of_elements: int):
+ """Create target profile elements for testing."""
+ target_elements = []
+ # Simple varying target for testing
+ for i in range(number_of_elements):
+ if i < number_of_elements // 4:
+ target_elements.append(5000000) # 5 MJ
+ elif i < number_of_elements // 2:
+ target_elements.append(3000000) # 3 MJ
+ elif i < 3 * number_of_elements // 4:
+ target_elements.append(2000000) # 2 MJ
+ else:
+ target_elements.append(4000000) # 4 MJ
+ return target_elements
+
+
+def test_planning_service_impl_with_ddbc_devices_simple():
+ """Test the PlanningServiceImpl with simplified DDBC devices."""
+ # print("Testing PlanningServiceImpl with simplified DDBC devices")
+ # print(f"Number of devices: {D}")
+ # print(f"Number of timesteps: {T}")
+
+ target_metadata = ProfileMetadata(
+ profile_start=datetime(1970, 1, 1, tzinfo=timezone.utc),
+ timestep_duration=timedelta(seconds=TIMESTEP_DURATION),
+ nr_of_timesteps=T,
+ )
+ plan_due_by_date = target_metadata.profile_start + timedelta(seconds=10)
+ target_profile_elements = get_target_profile_elements(T)
+
+ global_target_profile = TargetProfile(
+ profile_start=target_metadata.profile_start,
+ timestep_duration=target_metadata.timestep_duration,
+ elements=target_profile_elements,
+ )
+
+ device_states = [
+ create_ddbc_device_state_simple(
+ f"ddbc_device_{i + 1}",
+ target_metadata.profile_start,
+ )
+ for i in range(D)
+ ]
+
+ device_states_dict = {
+ device_state.device_id: device_state for device_state in device_states
+ }
+
+ congestion_points_by_connection_id = {
+ device_state.connection_id: "" for device_state in device_states
+ }
+
+ cluster_state = ClusterState(
+ datetime.now(), device_states_dict, congestion_points_by_connection_id
+ )
+
+ congestion_point_targets = {}
+ cluster_target = ClusterTarget(
+ datetime.now(),
+ None,
+ None,
+ global_target_profile=global_target_profile,
+ congestion_point_targets=congestion_point_targets,
+ )
+
+ config = PlanningServiceConfig(
+ energy_improvement_criterion=10.0,
+ cost_improvement_criterion=1.0,
+ congestion_retry_iterations=10,
+ multithreaded=False,
+ )
+
+ # print("Generating plan!")
+
+ service = PlanningServiceImpl(config)
+
+ # start_time = time.time()
+ cluster_plan = service.plan(
+ state=cluster_state,
+ target=cluster_target,
+ planning_window=TIMESTEP_DURATION * T,
+ reason="Testing DDBC planning (simplified)",
+ plan_due_by_date=plan_due_by_date,
+ optimize_for_target=True,
+ max_priority_class=1,
+ )
+ # end_time = time.time()
+ # execution_time = end_time - start_time
+
+ # print(f"Plan generated in {execution_time:.2f} seconds")
+
+ assert cluster_plan is not None
+ # print("Got cluster plan")
+
+ if cluster_plan is None:
+ # print("Cluster plan is None")
+ return
+
+ device_plans = cluster_plan.get_plan_data().get_device_plans()
+ energy_profile = cluster_plan.get_joule_profile()
+
+ plot_planning_results(
+ timestep_duration=timedelta(seconds=TIMESTEP_DURATION),
+ nr_of_timesteps=T,
+ predicted_energy_elements=energy_profile.elements,
+ target_energy_elements=target_profile_elements,
+ )
+
+ device_plans = [plan for plan in device_plans if plan is not None]
+
+ assert len(device_plans) > 0
+ # print(f"Got {len(device_plans)} device plans")
+
+ for device_plan in device_plans:
+ if device_plan:
+ # print(f"Device {device_plan.device_id}:")
+ # total_energy = sum(
+ # e for e in device_plan.energy_profile.elements if e is not None
+ # )
+ # print(f" Total energy: {total_energy} Joules (zero for simplified test)")
+ # print(f" Instruction profile: {device_plan.instruction_profile}")
+ pass
+
+ assert len(energy_profile.elements) == target_metadata.nr_of_timesteps
+ # print("Test completed successfully!")
+ # print("\nNote: This is a simplified test. Full DDBC planning with state trees")
+ # print(" and optimization would be implemented in ddbc_planning_window.py")
+
+
+if __name__ == "__main__":
+ test_planning_service_impl_with_ddbc_devices_simple()
diff --git a/flexmeasures_s2/profile_steering/examples/example_schedule_frbc.py b/flexmeasures_s2/profile_steering/examples/example_schedule_frbc.py
new file mode 100644
index 0000000..43c5529
--- /dev/null
+++ b/flexmeasures_s2/profile_steering/examples/example_schedule_frbc.py
@@ -0,0 +1,1027 @@
+from flexmeasures_s2.profile_steering.common.target_profile import TargetProfile
+from flexmeasures_s2.profile_steering.common.joule_profile import JouleProfile
+from datetime import datetime, timedelta, timezone
+import uuid
+
+# import logging
+# import time
+import pandas as pd
+import os
+from s2python.frbc.frbc_actuator_description import FRBCActuatorDescription
+from s2python.frbc.frbc_fill_level_target_profile_element import (
+ FRBCFillLevelTargetProfileElement,
+)
+from s2python.frbc.frbc_leakage_behaviour_element import FRBCLeakageBehaviourElement
+from s2python.frbc.frbc_operation_mode import FRBCOperationMode
+from s2python.frbc.frbc_usage_forecast_element import FRBCUsageForecastElement
+from flexmeasures_s2.profile_steering.device_planner.frbc.s2_frbc_device_state import (
+ S2FrbcDeviceState,
+)
+from flexmeasures_s2.profile_steering.common.profile_metadata import ProfileMetadata
+from s2python.frbc import FRBCSystemDescription
+from s2python.frbc import FRBCUsageForecast
+from s2python.frbc import FRBCFillLevelTargetProfile
+from s2python.frbc import FRBCLeakageBehaviour
+from s2python.frbc import FRBCOperationModeElement
+from s2python.frbc import FRBCActuatorStatus, FRBCStorageStatus, FRBCStorageDescription
+from s2python.common import PowerRange, Duration
+from s2python.common import NumberRange
+from s2python.common import CommodityQuantity
+from s2python.common import Transition
+from s2python.common import Timer
+from s2python.common import PowerValue
+from s2python.common import Commodity
+
+from flexmeasures_s2.scheduler.schedulers import (
+ PlanningServiceImpl,
+ PlanningServiceConfig,
+ ClusterState,
+ ClusterTarget,
+)
+import matplotlib.dates as mdates
+import matplotlib.pyplot as plt
+import json
+
+# Global variables to store IDs for debugging
+# make a list of tuples with the ids and the names
+ids = []
+
+# -> todo: plot of run time vs D, vs B, vs S and vs T
+D = 1 # number of devices -> todo: multiprocessing on create_improved_planning
+B = 200 # number of buckets -> todo: vectorize computation of next state from current state
+S = 30 # number of stratification layers
+PLANNING_WINDOW = pd.Timedelta("PT24H")
+PLANNING_RESOLUTION = pd.Timedelta("PT5M")
+
+T = PLANNING_WINDOW // PLANNING_RESOLUTION # number of time steps
+TIMESTEP_DURATION = PLANNING_RESOLUTION / pd.Timedelta(
+ "PT1S"
+) # duration of a time step in seconds
+
+
+def create_ev_device_state(
+ device_id: str,
+ omloop_starts_at: datetime,
+ cet: timezone,
+ charge_power_soc_percentage_per_second_night: float,
+ charging_power_kw_night: float,
+ charge_power_soc_percentage_per_second_day: float,
+ charging_power_kw_day: float,
+ soc_percentage_before_charging1: float,
+ final_fill_level_target1: float,
+ recharge_duration1: timedelta,
+ start_of_recharge1: datetime,
+ drive_duration1: timedelta,
+ start_of_drive1: datetime,
+ drive_consume_soc_per_second1: float,
+ soc_percentage_before_driving1: float,
+ soc_percentage_before_charging2: float,
+ final_fill_level_target2: float,
+ recharge_duration2: timedelta,
+ start_of_recharge2: datetime,
+) -> S2FrbcDeviceState:
+ # Create system description
+ (
+ recharge_system_description1,
+ charge_actuator_status1,
+ storage_status1,
+ ) = create_recharge_system_description(
+ start_of_recharge=start_of_recharge1,
+ charge_power_soc_percentage_per_second=charge_power_soc_percentage_per_second_night,
+ charging_power_kw=charging_power_kw_night,
+ soc_percentage_before_charging=soc_percentage_before_charging1,
+ )
+
+ # Create leakage behaviour
+ recharge_leakage1 = create_recharge_leakage_behaviour(start_of_recharge1)
+
+ # Create usage forecast
+ recharge_usage_forecast1 = create_recharge_usage_forecast(
+ start_of_recharge1, recharge_duration1
+ )
+
+ # Create fill level target profile
+ recharge_fill_level_target1 = create_recharge_fill_level_target_profile(
+ start_of_recharge1,
+ recharge_duration1,
+ final_fill_level_target1,
+ soc_percentage_before_charging1,
+ )
+
+ (
+ drive_system_description1,
+ off_actuator_status1,
+ storage_status1,
+ ) = create_driving_system_description(
+ start_of_drive1, soc_percentage_before_driving1
+ )
+ drive_usage_forecast1 = create_driving_usage_forecast(
+ start_of_drive1, drive_duration1, drive_consume_soc_per_second1
+ )
+
+ (
+ recharge_system_description2,
+ charge_actuator_status2,
+ storage_status2,
+ ) = create_recharge_system_description(
+ start_of_recharge2,
+ charge_power_soc_percentage_per_second_day,
+ charging_power_kw_day,
+ soc_percentage_before_charging2,
+ )
+ recharge_leakage2 = create_recharge_leakage_behaviour(start_of_recharge2)
+ recharge_usage_forecast2 = create_recharge_usage_forecast(
+ start_of_recharge2, recharge_duration2
+ )
+ recharge_fill_level_target2 = create_recharge_fill_level_target_profile(
+ start_of_recharge2,
+ recharge_duration2,
+ final_fill_level_target2,
+ soc_percentage_before_charging2,
+ )
+
+ # Create done state
+ done_start = start_of_recharge2 + recharge_duration2
+ done_duration = timedelta(hours=4, minutes=10) # 4 hours & 10 minutes
+ done_leakage = create_recharge_leakage_behaviour(done_start)
+ (
+ done_system_description,
+ done_actuator_status,
+ done_storage_status,
+ ) = create_driving_system_description(done_start, final_fill_level_target2)
+ done_usage_forecast = create_recharge_usage_forecast(done_start, done_duration)
+
+ device_state = S2FrbcDeviceState(
+ device_id=device_id,
+ device_name=device_id,
+ connection_id=device_id + "_cid1",
+ priority_class=1,
+ timestamp=omloop_starts_at,
+ energy_in_current_timestep=PowerValue(
+ value=0, commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1
+ ),
+ is_online=True,
+ power_forecast=None,
+ system_descriptions=[
+ recharge_system_description1,
+ drive_system_description1,
+ recharge_system_description2,
+ done_system_description,
+ ],
+ leakage_behaviours=[recharge_leakage1, recharge_leakage2, done_leakage],
+ usage_forecasts=[
+ recharge_usage_forecast1,
+ drive_usage_forecast1,
+ recharge_usage_forecast2,
+ done_usage_forecast,
+ ],
+ fill_level_target_profiles=[
+ recharge_fill_level_target1,
+ recharge_fill_level_target2,
+ ],
+ computational_parameters=S2FrbcDeviceState.ComputationalParameters(B, S),
+ actuator_statuses=[
+ off_actuator_status1,
+ charge_actuator_status1,
+ charge_actuator_status2,
+ done_actuator_status,
+ ],
+ storage_status=storage_status2, # Use first storage status (matches first system description)
+ )
+ return device_state
+
+
+def create_recharge_system_description(
+ start_of_recharge,
+ charge_power_soc_percentage_per_second,
+ charging_power_kw,
+ soc_percentage_before_charging,
+) -> FRBCSystemDescription:
+ # Create and return a mock system description for recharging
+ on_operation_element = FRBCOperationModeElement(
+ fill_level_range=NumberRange(start_of_range=0, end_of_range=100),
+ fill_rate=NumberRange(
+ start_of_range=0,
+ end_of_range=charge_power_soc_percentage_per_second,
+ ),
+ power_ranges=[
+ PowerRange(
+ start_of_range=charging_power_kw * 0,
+ end_of_range=charging_power_kw * 1000,
+ commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1,
+ )
+ ],
+ )
+ id_on_operation_mode = str(uuid.uuid4())
+ # logging.debug(f"id_on_operation_mode: {id_on_operation_mode}")
+ ids.append((id_on_operation_mode, "charge_on_operation_mode"))
+ on_operation_mode = FRBCOperationMode(
+ id=id_on_operation_mode,
+ diagnostic_label="charge.on",
+ elements=[on_operation_element],
+ abnormal_condition_only=False,
+ )
+ off_operation_element = FRBCOperationModeElement(
+ fill_level_range=NumberRange(start_of_range=0, end_of_range=100),
+ fill_rate=NumberRange(start_of_range=0, end_of_range=0),
+ power_ranges=[
+ PowerRange(
+ start_of_range=0,
+ end_of_range=0,
+ commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1,
+ )
+ ],
+ )
+ id_off_operation_mode = str(uuid.uuid4())
+ # logging.debug(f"id_off_operation_mode: {id_off_operation_mode}")
+ ids.append((id_off_operation_mode, "charge_off_operation_mode"))
+ off_operation_mode = FRBCOperationMode(
+ id=id_off_operation_mode,
+ diagnostic_label="charge.off",
+ elements=[off_operation_element],
+ abnormal_condition_only=False,
+ )
+
+ id_on_to_off_timer = str(uuid.uuid4())
+ # logging.debug(f"id_on_to_off_timer: {id_on_to_off_timer}")
+ ids.append((id_on_to_off_timer, "charge_on_to_off_timer"))
+ on_to_off_timer = Timer(
+ id=id_on_to_off_timer,
+ diagnostic_label="charge_on.to.off.timer",
+ duration=Duration(root=30),
+ )
+ id_off_to_on_timer = str(uuid.uuid4())
+ # logging.debug(f"id_off_to_on_timer: {id_off_to_on_timer}")
+ ids.append((id_off_to_on_timer, "charge_off_to_on_timer"))
+ off_to_on_timer = Timer(
+ id=id_off_to_on_timer,
+ diagnostic_label="charge_off.to.on.timer",
+ duration=Duration(root=30),
+ )
+ transition_id_from_on_to_off = str(uuid.uuid4())
+ # logging.debug(f"transition_id_from_on_to_off: {transition_id_from_on_to_off}")
+ ids.append(
+ (transition_id_from_on_to_off, "transition_from_charge_on_to_charge_off")
+ )
+ transition_from_on_to_off = Transition(
+ id=transition_id_from_on_to_off,
+ **{"from": id_on_operation_mode},
+ to=id_off_operation_mode,
+ start_timers=[id_off_to_on_timer],
+ blocking_timers=[id_on_to_off_timer],
+ transition_duration=None,
+ abnormal_condition_only=False,
+ )
+ transition_id_from_off_to_on = str(uuid.uuid4())
+ # logging.debug(f"transition_id_from_off_to_on: {transition_id_from_off_to_on}")
+ ids.append(
+ (transition_id_from_off_to_on, "transition_from_charge_off_to_charge_on")
+ )
+ transition_from_off_to_on = Transition(
+ id=transition_id_from_off_to_on, # type: ignore[arg-type]
+ **{"from": id_off_operation_mode}, # type: ignore[arg-type]
+ to=id_on_operation_mode, # type: ignore[arg-type]
+ start_timers=[id_on_to_off_timer], # type: ignore[arg-type]
+ blocking_timers=[id_off_to_on_timer], # type: ignore[arg-type]
+ transition_duration=None,
+ abnormal_condition_only=False,
+ )
+ charge_actuator_id = str(uuid.uuid4())
+ # logging.debug(f"charge_actuator_id: {charge_actuator_id}")
+ ids.append((charge_actuator_id, "charge_actuator"))
+ charge_actuator_status = FRBCActuatorStatus(
+ message_id=charge_actuator_id, # type: ignore[arg-type]
+ actuator_id=charge_actuator_id, # type: ignore[arg-type]
+ active_operation_mode_id=id_on_operation_mode, # type: ignore[arg-type]
+ operation_mode_factor=0,
+ )
+
+ charge_actuator_description = FRBCActuatorDescription(
+ id=charge_actuator_id, # type: ignore[arg-type]
+ diagnostic_label="charge.actuator",
+ operation_modes=[on_operation_mode, off_operation_mode],
+ transitions=[transition_from_on_to_off, transition_from_off_to_on],
+ timers=[on_to_off_timer, off_to_on_timer],
+ supported_commodities=[Commodity.ELECTRICITY],
+ )
+ storage_status_id = str(uuid.uuid4())
+ # logging.debug(f"storage_status_id: {storage_status_id}")
+ ids.append((storage_status_id, "storage_status"))
+ storage_status = FRBCStorageStatus(
+ message_id=storage_status_id, # type: ignore[arg-type]
+ present_fill_level=soc_percentage_before_charging,
+ )
+
+ frbc_storage_description = FRBCStorageDescription(
+ diagnostic_label="battery",
+ fill_level_label="SoC %",
+ provides_leakage_behaviour=False,
+ provides_fill_level_target_profile=True,
+ provides_usage_forecast=False,
+ fill_level_range=NumberRange(start_of_range=0, end_of_range=100),
+ )
+ system_description_id = str(uuid.uuid4())
+ # logging.debug(f"system_description_id: {system_description_id}")
+ ids.append((system_description_id, "system_description"))
+ frbc_system_description = FRBCSystemDescription(
+ message_id=system_description_id, # type: ignore[arg-type]
+ valid_from=start_of_recharge,
+ actuators=[charge_actuator_description],
+ storage=frbc_storage_description,
+ )
+
+ return frbc_system_description, charge_actuator_status, storage_status
+
+
+def create_recharge_leakage_behaviour(start_of_recharge):
+ leakage_id = str(uuid.uuid4())
+ # logging.debug(f"leakage_id: {leakage_id}")
+ ids.append((leakage_id, "leakage"))
+ return FRBCLeakageBehaviour(
+ message_id=leakage_id,
+ valid_from=start_of_recharge,
+ elements=[
+ FRBCLeakageBehaviourElement(
+ fill_level_range=NumberRange(start_of_range=0, end_of_range=100),
+ leakage_rate=0,
+ )
+ ],
+ )
+
+
+def create_recharge_usage_forecast(start_of_recharge, recharge_duration):
+ no_usage = FRBCUsageForecastElement(
+ duration=int(recharge_duration.total_seconds()), usage_rate_expected=0
+ )
+ usage_id = str(uuid.uuid4())
+ # logging.debug(f"usage_id: {usage_id}")
+ ids.append((usage_id, "usage"))
+ return FRBCUsageForecast(
+ message_id=usage_id, start_time=start_of_recharge, elements=[no_usage]
+ )
+
+
+def create_recharge_fill_level_target_profile(
+ start_of_recharge,
+ recharge_duration,
+ final_fill_level_target,
+ soc_percentage_before_charging,
+):
+ during_charge = FRBCFillLevelTargetProfileElement(
+ duration=max(recharge_duration.total_seconds() - 10, 0),
+ fill_level_range=NumberRange(
+ start_of_range=soc_percentage_before_charging, end_of_range=100
+ ),
+ )
+ end_of_charge = FRBCFillLevelTargetProfileElement(
+ duration=min(recharge_duration.total_seconds(), 10),
+ fill_level_range=NumberRange(
+ start_of_range=final_fill_level_target, end_of_range=100
+ ),
+ )
+ fill_level_id = str(uuid.uuid4())
+ # logging.debug(f"fill_level_id: {fill_level_id}")
+ ids.append((fill_level_id, "fill_level"))
+ return FRBCFillLevelTargetProfile(
+ message_id=fill_level_id,
+ start_time=start_of_recharge,
+ elements=[during_charge, end_of_charge],
+ )
+
+
+def create_driving_system_description(start_of_drive, soc_percentage_before_driving):
+ global off_actuator_id, id_off_operation_mode
+ off_operation_element = FRBCOperationModeElement(
+ fill_level_range=NumberRange(start_of_range=0, end_of_range=100),
+ fill_rate=NumberRange(start_of_range=0, end_of_range=0),
+ power_ranges=[
+ PowerRange(
+ start_of_range=0,
+ end_of_range=0,
+ commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1,
+ )
+ ],
+ )
+ id_off_operation_mode = str(uuid.uuid4())
+ # logging.debug(f"operation_mode_driving: {id_off_operation_mode}")
+ ids.append((id_off_operation_mode, "operation_mode_driving"))
+ off_operation_mode = FRBCOperationMode(
+ id=id_off_operation_mode,
+ diagnostic_label="off",
+ elements=[off_operation_element],
+ abnormal_condition_only=False,
+ )
+ off_actuator_id = str(uuid.uuid4())
+ # logging.debug(f"actuator_driving: {off_actuator_id}")
+ ids.append((off_actuator_id, "actuator_driving"))
+ off_actuator_status = FRBCActuatorStatus(
+ message_id=str(uuid.uuid4()),
+ actuator_id=off_actuator_id,
+ active_operation_mode_id=id_off_operation_mode,
+ operation_mode_factor=0,
+ )
+ off_actuator = FRBCActuatorDescription(
+ id=off_actuator_id,
+ diagnostic_label="off",
+ operation_modes=[off_operation_mode],
+ transitions=[],
+ timers=[],
+ supported_commodities=[Commodity.ELECTRICITY],
+ )
+ storage_status = FRBCStorageStatus(
+ message_id=str(uuid.uuid4()), present_fill_level=soc_percentage_before_driving
+ )
+ storage_description = FRBCStorageDescription(
+ diagnostic_label="battery",
+ fill_level_label="SoC %",
+ provides_leakage_behaviour=False,
+ provides_fill_level_target_profile=True,
+ provides_usage_forecast=False,
+ fill_level_range=NumberRange(start_of_range=0, end_of_range=100),
+ )
+ return (
+ FRBCSystemDescription(
+ message_id=str(uuid.uuid4()),
+ valid_from=start_of_drive,
+ actuators=[off_actuator],
+ storage=storage_description,
+ ),
+ off_actuator_status,
+ storage_status,
+ )
+
+
+def create_driving_usage_forecast(
+ start_of_driving, next_drive_duration, soc_usage_per_second
+):
+ no_usage = FRBCUsageForecastElement(
+ duration=int(next_drive_duration.total_seconds()),
+ usage_rate_expected=(-1 * soc_usage_per_second),
+ )
+ return FRBCUsageForecast(
+ message_id=str(uuid.uuid4()), start_time=start_of_driving, elements=[no_usage]
+ )
+
+
+def plot_planning_results(
+ timestep_duration,
+ nr_of_timesteps,
+ predicted_energy_elements,
+ target_energy_elements,
+ cost_elements=None,
+ target_cost_elements=None,
+):
+ """
+ Plots the energy, fill level, actuator usage, and operation mode ID lists using matplotlib.
+
+ :param timestep_duration: Duration of each timestep.
+ :param nr_of_timesteps: Number of timesteps.
+ :param predicted_energy_elements: List of predicted energy values.
+ :param target_energy_elements: List of target energy values.
+ :param cost_elements: Optional list of cost values.
+ :param target_cost_elements: Optional list of target cost values (tariffs).
+ """
+ # Generate timestep_start_times
+ timestep_start_times = [
+ datetime(1970, 1, 1, tzinfo=timezone.utc)
+ + timedelta(seconds=i * timestep_duration.total_seconds())
+ for i in range(nr_of_timesteps)
+ ]
+
+ if cost_elements is not None or target_cost_elements is not None:
+ # Create a figure with dual y-axes for energy and cost
+ fig, ax1 = plt.subplots(1, 1, figsize=(14, 8))
+
+ # Energy plot on left y-axis
+ ax1.plot(
+ timestep_start_times,
+ predicted_energy_elements,
+ label="Predicted Energy",
+ color="green",
+ linewidth=2,
+ )
+ ax1.plot(
+ timestep_start_times,
+ target_energy_elements,
+ label="Target Energy",
+ color="red",
+ linestyle="dotted",
+ linewidth=2,
+ )
+ ax1.set_ylabel("Energy (Joules)", color="green")
+ ax1.tick_params(axis="y", labelcolor="green")
+ ax1.grid(True, alpha=0.3)
+
+ # Cost plot on right y-axis
+ ax2 = ax1.twinx()
+ if cost_elements is not None:
+ ax2.plot(
+ timestep_start_times,
+ cost_elements,
+ label="Predicted Cost",
+ color="blue",
+ linewidth=2,
+ )
+ if target_cost_elements is not None:
+ ax2.plot(
+ timestep_start_times,
+ target_cost_elements,
+ label="Target Cost (Tariffs)",
+ color="orange",
+ linestyle="dashed",
+ linewidth=2,
+ )
+ ax2.set_ylabel("Cost ($/kWh)", color="blue")
+ ax2.tick_params(axis="y", labelcolor="blue")
+
+ # Combined legend
+ lines1, labels1 = ax1.get_legend_handles_labels()
+ lines2, labels2 = ax2.get_legend_handles_labels()
+ ax1.legend(lines1 + lines2, labels1 + labels2, loc="upper left")
+
+ ax1.set_title("Energy and Cost Planning Results")
+ else:
+ # Create a figure with a single subplot for energy only
+ fig, ax1 = plt.subplots(1, 1, figsize=(12, 8))
+
+ # Plot both lines on the same subplot
+ ax1.plot(
+ timestep_start_times,
+ predicted_energy_elements,
+ label="Predicted Energy",
+ color="green",
+ )
+ ax1.plot(
+ timestep_start_times,
+ target_energy_elements,
+ label="Target Energy",
+ color="red",
+ linestyle="dotted",
+ linewidth=2,
+ )
+
+ # Set labels and grid
+ ax1.set_ylabel("Energy (Joules)")
+ ax1.set_title("Predicted vs Target Energy")
+ ax1.legend(loc="best")
+ ax1.grid(True)
+
+ # Format the x-axis to show time and set ticks every 30 minutes
+ ax1.xaxis.set_major_locator(mdates.MinuteLocator(interval=30))
+ ax1.xaxis.set_major_formatter(mdates.DateFormatter("%H:%M:%S"))
+ fig.autofmt_xdate()
+
+ # Adjust layout
+ plt.tight_layout()
+ # Save it in a directory called plots
+ os.makedirs("plots", exist_ok=True)
+ suffix = (
+ "_cost" if cost_elements is not None or target_cost_elements is not None else ""
+ )
+ plt.savefig(f"plots/my plot - D = {D} - B = {B} - S = {S} - T = {T}{suffix}")
+
+
+def create_device_state(
+ device_id: str, omloop_starts_at: datetime, cet: timezone
+) -> S2FrbcDeviceState:
+ # Device charging parameters
+ charge_power_soc_percentage_per_second_night = 0.01099537114
+ charging_power_kw_night = 57
+ charge_power_soc_percentage_per_second_day = 0.01099537114
+ charging_power_kw_day = 57
+
+ # First recharge period
+ start_of_recharge1 = omloop_starts_at.astimezone(cet)
+ recharge_duration1 = timedelta(hours=7, minutes=15)
+ soc_percentage_before_charging1 = 0
+ final_fill_level_target1 = 100
+
+ # Driving period
+ start_of_drive1 = start_of_recharge1 + recharge_duration1
+ drive_duration1 = timedelta(hours=4, minutes=35)
+ drive_consume_soc_per_second1 = 0.00375927565821256038647342995169
+ soc_percentage_before_driving1 = 100
+
+ # Second recharge period
+ start_of_recharge2 = start_of_drive1 + drive_duration1
+ recharge_duration2 = timedelta(hours=7)
+ soc_percentage_before_charging2 = 37.0
+ final_fill_level_target2 = 94.4825134
+
+ # Create the device state
+ device_state = create_ev_device_state(
+ device_id,
+ omloop_starts_at,
+ cet,
+ charge_power_soc_percentage_per_second_night,
+ charging_power_kw_night,
+ charge_power_soc_percentage_per_second_day,
+ charging_power_kw_day,
+ soc_percentage_before_charging1,
+ final_fill_level_target1,
+ recharge_duration1,
+ start_of_recharge1,
+ drive_duration1,
+ start_of_drive1,
+ drive_consume_soc_per_second1,
+ soc_percentage_before_driving1,
+ soc_percentage_before_charging2,
+ final_fill_level_target2,
+ recharge_duration2,
+ start_of_recharge2,
+ )
+ return device_state
+
+
+def get_target_profile_elements(number_of_elements: int):
+ """Create target profile elements with the same pattern as the Java code."""
+ target_elements = []
+ # First 38 elements of 0
+ target_elements.extend([0] * 38)
+ # Next 62 elements of 8400000
+ target_elements.extend([8400000] * 62)
+ # Next 55 elements of 0
+ target_elements.extend([0] * 45)
+ # Next 18 elements of 8400000
+ target_elements.extend([8400000] * 28)
+ # Last 115 elements of 176000000
+ target_elements.extend([176000000] * 115)
+ return target_elements[:number_of_elements]
+
+
+def get_cost_target_profile_elements(number_of_elements: int):
+ """Create cost target profile elements with dynamic pricing pattern."""
+ cost_elements = []
+ # Simulate time-of-use pricing for a 24-hour period (288 = 5-minute intervals)
+ intervals_per_hour = number_of_elements // 24
+
+ for hour in range(24):
+ if 0 <= hour < 6: # Night hours (00:00-06:00) - lowest cost
+ tariff = 0.10 # 10 cents per kWh
+ elif 6 <= hour < 9: # Morning ramp-up (06:00-09:00)
+ tariff = 0.15 # 15 cents per kWh
+ elif 9 <= hour < 17: # Peak hours (09:00-17:00) - highest cost
+ tariff = 0.30 # 30 cents per kWh
+ elif 17 <= hour < 21: # Evening peak (17:00-21:00)
+ tariff = 0.25 # 25 cents per kWh
+ else: # Late evening (21:00-24:00)
+ tariff = 0.15 # 15 cents per kWh
+
+ # Add the tariff for all intervals in this hour
+ cost_elements.extend([tariff] * intervals_per_hour)
+
+ return cost_elements[:number_of_elements]
+
+
+def calculate_cost_from_energy_and_tariffs(energy_elements, tariff_elements):
+ """Calculate cost from energy usage and tariff rates."""
+ cost_elements = []
+ for energy, tariff in zip(energy_elements, tariff_elements):
+ if energy is not None and tariff is not None:
+ # Convert Joules to kWh and multiply by tariff
+ kwh = energy / 3_600_000 # 1 kWh = 3,600,000 Joules
+ cost = kwh * tariff
+ cost_elements.append(cost)
+ else:
+ cost_elements.append(0.0)
+ return cost_elements
+
+
+def test_planning_service_impl_with_ev_device():
+ """Test the PlanningServiceImpl with an EV device."""
+ # print("Test the PlanningServiceImpl with an EV device.")
+ # Create 10 device states
+ numberOfDevices = D
+
+ # Create profile metadata and target profile
+ target_metadata = ProfileMetadata(
+ profile_start=datetime(1970, 1, 1, tzinfo=timezone.utc),
+ timestep_duration=timedelta(seconds=TIMESTEP_DURATION),
+ nr_of_timesteps=T,
+ )
+ plan_due_by_date = target_metadata.profile_start + timedelta(seconds=10)
+ target_profile_elements = get_target_profile_elements(T)
+
+ # Create a target profile
+ global_target_profile = TargetProfile(
+ profile_start=target_metadata.profile_start,
+ timestep_duration=target_metadata.timestep_duration,
+ elements=target_profile_elements,
+ )
+
+ device_states = [
+ create_device_state(
+ f"battery{i + 1}",
+ datetime.fromtimestamp(3600),
+ timezone(timedelta(hours=1)),
+ )
+ for i in range(numberOfDevices)
+ ]
+
+ # Create Dictionary of device states
+ device_states_dict = {
+ device_state.device_id: device_state for device_state in device_states
+ }
+
+ # Create congestion points mapping - follow pattern from example_schedule_itho.py
+ # Note: Uses device_id as key, not connection_id (system will fallback to DEFAULT_CONGESTION_POINT if not found)
+ congestion_points_by_connection_id = {
+ device_id: "" for device_id in device_states_dict.keys()
+ }
+ # Create a cluster state using the list of device states
+ start_time = target_metadata.profile_start
+ cluster_state = ClusterState(
+ start_time, device_states_dict, congestion_points_by_connection_id
+ )
+ # Create an empty map for congestion point targets
+ congestion_point_targets = {}
+ # Create a cluster target
+ cluster_target = ClusterTarget(
+ start_time,
+ None,
+ None,
+ global_target_profile=global_target_profile,
+ congestion_point_targets=congestion_point_targets,
+ )
+
+ config = PlanningServiceConfig(
+ energy_improvement_criterion=10.0,
+ cost_improvement_criterion=1.0,
+ congestion_retry_iterations=10,
+ multithreaded=False,
+ )
+
+ # print("Generating plan!")
+
+ # Create planning service implementation
+ service = PlanningServiceImpl(config)
+
+ # Set due by date for planning
+ plan_due_by_date = target_metadata.profile_start + timedelta(seconds=10)
+ # Act - Generate a plan
+ # start_time = time.time()
+ cluster_plan = service.plan(
+ state=cluster_state,
+ target=cluster_target,
+ planning_window=TIMESTEP_DURATION * T, # Full planning window in seconds
+ reason="Testing EV planning",
+ plan_due_by_date=plan_due_by_date,
+ optimize_for_target=True,
+ max_priority_class=1,
+ )
+ # end_time = time.time()
+ # execution_time = end_time - start_time
+
+ # Log information
+ # print(f"Plan generated in {execution_time: .2f} seconds")
+
+ # Assert
+ assert cluster_plan is not None
+ # print("Got cluster plan")
+ if cluster_plan is None:
+ # print("Cluster plan is None")
+ return
+ # Get the plan for our device
+ device_plans = cluster_plan.get_plan_data().get_device_plans()
+
+ # Manually compute energy profile, filtering out None device plans
+ # This is necessary because get_joule_profile() doesn't handle None device plans
+ energy_profile = JouleProfile(
+ profile_start=cluster_plan.metadata.profile_start,
+ timestep_duration=cluster_plan.metadata.timestep_duration,
+ profile_length=cluster_plan.metadata.nr_of_timesteps,
+ value=0,
+ )
+ for device_plan in device_plans:
+ if device_plan is not None and device_plan.energy_profile is not None:
+ energy_profile = energy_profile.add(device_plan.energy_profile)
+
+ # Save the energy profile to a file,
+ # with open(f"energy_profile-D={D}_B={B}_S={S}_T={T}.json", "w") as f:
+ # json.dump(energy_profile.elements, f)
+
+ # Plot the planning results
+ plot_planning_results(
+ timestep_duration=timedelta(seconds=TIMESTEP_DURATION),
+ nr_of_timesteps=T,
+ predicted_energy_elements=energy_profile.elements,
+ target_energy_elements=target_profile_elements,
+ )
+
+ if D == 3 or D == 10 or D == 5:
+ expected_file = f"energy_profile-D={D}_B={B}_S={S}_T={T}.json"
+ if os.path.exists(expected_file):
+ with open(expected_file, "r") as f:
+ assert energy_profile.elements == json.load(f)
+ print("Energy profile matches expected values")
+ else:
+ print(f"Skipping validation: {expected_file} does not exist")
+ # Get only the non-None plans
+ device_plans_filtered = [plan for plan in device_plans if plan is not None]
+
+ # Debug: Print information about device plans
+ if len(device_plans_filtered) == 0:
+ print(
+ f"Warning: No device plans were generated. Total device plans: {len(device_plans)}, None plans: {sum(1 for p in device_plans if p is None)}"
+ )
+ # If no plans were generated, we still have an energy profile (all zeros), so continue
+ else:
+ print(
+ f"Successfully generated {len(device_plans_filtered)} device plan(s) out of {len(device_plans)} total"
+ )
+
+ # Note: We don't assert here because the energy profile computation above handles None plans gracefully
+ # The planning might fail for some devices, but we can still proceed with the energy profile
+ # print("Got device plan")
+
+ # Print instructions and analyze operation mode changes
+ all_instructions = []
+ for device_plan in device_plans_filtered:
+ if device_plan and device_plan.instruction_profile:
+ instructions = device_plan.instruction_profile.elements
+ all_instructions.extend(instructions)
+ # print(
+ # f"Device {device_plan.device_id} has {len(instructions)} instructions"
+ # )
+
+ # print(f"Total number of instructions: {len(all_instructions)}")
+
+ # Find instructions with different operation modes than previous instruction
+ mode_changes = []
+ previous_mode = None
+
+ for i, instruction in enumerate(all_instructions):
+ if hasattr(instruction, "operation_mode"):
+ current_mode = instruction.operation_mode
+ if previous_mode is not None and current_mode != previous_mode:
+ mode_changes.append(
+ {
+ "index": i,
+ "instruction": instruction,
+ "previous_mode": previous_mode,
+ "current_mode": current_mode,
+ "execution_time": getattr(instruction, "execution_time", "N/A"),
+ "operation_mode_factor": getattr(
+ instruction, "operation_mode_factor", "N/A"
+ ),
+ }
+ )
+ previous_mode = current_mode
+
+ print(f"Found {len(mode_changes)} operation mode changes:")
+ # for change in mode_changes:
+ # print(
+ # f" Instruction {change['index']}: {change['previous_mode']} -> {change['current_mode']}"
+ # )
+ # print(f" Execution time: {change['execution_time']}")
+ # print(f" Operation mode factor: {change['operation_mode_factor']}")
+ # print(f" Instruction ID: {getattr(change['instruction'], 'id', 'N/A')}")
+ # print()
+
+ # Basic assertion - the energy profile should have the expected number of elements
+ assert len(energy_profile.elements) == target_metadata.nr_of_timesteps
+ # pass
+
+
+def test_planning_service_impl_with_cost_target():
+ """Test the PlanningServiceImpl with cost targeting."""
+ # print("Test the PlanningServiceImpl with cost targeting.")
+
+ # Create profile metadata
+ target_metadata = ProfileMetadata(
+ profile_start=datetime(1970, 1, 1, tzinfo=timezone.utc),
+ timestep_duration=timedelta(seconds=TIMESTEP_DURATION),
+ nr_of_timesteps=T,
+ )
+ plan_due_by_date = target_metadata.profile_start + timedelta(seconds=10)
+
+ # Create cost target profile instead of energy target
+ cost_target_elements = get_cost_target_profile_elements(T)
+
+ # Create a cost-based target profile
+ cost_target_profile = TargetProfile.from_tariff_values(
+ target_metadata, cost_target_elements
+ )
+
+ device_states = [
+ create_device_state(
+ f"battery{i + 1}",
+ datetime.fromtimestamp(3600),
+ timezone(timedelta(hours=1)),
+ )
+ for i in range(D)
+ ]
+
+ # Create Dictionary of device states
+ device_states_dict = {
+ device_state.device_id: device_state for device_state in device_states
+ }
+
+ # Create congestion points mapping - follow pattern from example_schedule_itho.py
+ # Note: Uses device_id as key, not connection_id (system will fallback to DEFAULT_CONGESTION_POINT if not found)
+ congestion_points_by_connection_id = {
+ device_id: "" for device_id in device_states_dict.keys()
+ }
+
+ # Create a cluster state using the list of device states
+ start_time = target_metadata.profile_start
+ cluster_state = ClusterState(
+ start_time, device_states_dict, congestion_points_by_connection_id
+ )
+
+ # Create a cluster target with cost target
+ cluster_target = ClusterTarget(
+ start_time,
+ None,
+ None,
+ global_target_profile=cost_target_profile,
+ congestion_point_targets={},
+ )
+
+ config = PlanningServiceConfig(
+ energy_improvement_criterion=10.0,
+ cost_improvement_criterion=1.0,
+ congestion_retry_iterations=10,
+ multithreaded=False,
+ )
+
+ # print("Generating cost-optimized plan!")
+
+ # Create planning service implementation
+ service = PlanningServiceImpl(config)
+
+ # Act - Generate a plan
+ # start_time = time.time()
+ cluster_plan = service.plan(
+ state=cluster_state,
+ target=cluster_target,
+ planning_window=TIMESTEP_DURATION * T, # Full planning window in seconds
+ reason="Testing Cost planning",
+ plan_due_by_date=plan_due_by_date,
+ optimize_for_target=True,
+ max_priority_class=1,
+ )
+ # end_time = time.time()
+ # execution_time = end_time - start_time
+
+ # Log information
+ # print(f"Cost-optimized plan generated in {execution_time: .2f} seconds")
+
+ # Assert
+ assert cluster_plan is not None
+ # print("Got cost-optimized cluster plan")
+
+ if cluster_plan is None:
+ # print("Cluster plan is None")
+ return
+
+ # Get the plan for our device
+ device_plans = cluster_plan.get_plan_data().get_device_plans()
+
+ # Manually compute energy profile, filtering out None device plans
+ # This is necessary because get_joule_profile() doesn't handle None device plans
+ energy_profile = JouleProfile(
+ profile_start=cluster_plan.metadata.profile_start,
+ timestep_duration=cluster_plan.metadata.timestep_duration,
+ profile_length=cluster_plan.metadata.nr_of_timesteps,
+ value=0,
+ )
+ for device_plan in device_plans:
+ if device_plan is not None and device_plan.energy_profile is not None:
+ energy_profile = energy_profile.add(device_plan.energy_profile)
+
+ # Calculate the actual costs from the energy profile and tariffs
+ cost_elements = calculate_cost_from_energy_and_tariffs(
+ energy_profile.elements, cost_target_elements
+ )
+
+ # print(
+ # f"Total energy consumption: {sum(energy_profile.elements) / 3_600_000:.2f} kWh" #
+ # )
+ # print(f"Total cost: ${sum(cost_elements):.2f}")
+
+ # Plot the cost-optimized planning results with dual axes
+ plot_planning_results(
+ timestep_duration=timedelta(seconds=TIMESTEP_DURATION),
+ nr_of_timesteps=T,
+ predicted_energy_elements=energy_profile.elements,
+ target_energy_elements=[0] * T, # use the energy target just for plotting
+ cost_elements=cost_elements,
+ target_cost_elements=cost_target_elements,
+ )
+
+ # Basic assertion - the energy profile should have the expected number of elements
+ assert len(energy_profile.elements) == target_metadata.nr_of_timesteps
+
+ # print("Cost targeting test completed successfully!")
+
+
+# Main function
+if __name__ == "__main__":
+ # Test energy targeting (original functionality)
+ test_planning_service_impl_with_ev_device()
+
+ # print("\n" + "=" * 60 + "\n")
+
+ # Test cost targeting (new functionality)
+ test_planning_service_impl_with_cost_target()
diff --git a/flexmeasures_s2/profile_steering/examples/example_schedule_itho.py b/flexmeasures_s2/profile_steering/examples/example_schedule_itho.py
new file mode 100644
index 0000000..d32c980
--- /dev/null
+++ b/flexmeasures_s2/profile_steering/examples/example_schedule_itho.py
@@ -0,0 +1,1910 @@
+from flexmeasures_s2.profile_steering.common.target_profile import TargetProfile
+from datetime import datetime, timedelta, timezone
+import uuid
+import logging
+import time
+import pandas as pd
+import os
+from s2python.frbc.frbc_actuator_description import FRBCActuatorDescription
+from s2python.frbc.frbc_fill_level_target_profile_element import (
+ FRBCFillLevelTargetProfileElement,
+)
+from s2python.frbc.frbc_leakage_behaviour_element import FRBCLeakageBehaviourElement
+from s2python.frbc.frbc_operation_mode import FRBCOperationMode
+from s2python.frbc.frbc_usage_forecast_element import FRBCUsageForecastElement
+from flexmeasures_s2.profile_steering.device_planner.frbc.s2_frbc_device_state import (
+ S2FrbcDeviceState,
+)
+from flexmeasures_s2.profile_steering.common.profile_metadata import ProfileMetadata
+from s2python.frbc import FRBCSystemDescription
+from s2python.frbc import FRBCUsageForecast
+from s2python.frbc import FRBCFillLevelTargetProfile
+from s2python.frbc import FRBCLeakageBehaviour
+from s2python.frbc import FRBCOperationModeElement
+from s2python.frbc import FRBCActuatorStatus, FRBCStorageStatus, FRBCStorageDescription
+from s2python.common import PowerRange, Duration
+from s2python.common import NumberRange
+from s2python.common import CommodityQuantity
+from s2python.common import Transition
+from s2python.common import Timer
+from s2python.common import PowerValue
+from s2python.common import Commodity
+
+from flexmeasures_s2.scheduler.schedulers import (
+ PlanningServiceImpl,
+ PlanningServiceConfig,
+ ClusterState,
+ ClusterTarget,
+)
+import matplotlib.dates as mdates
+import matplotlib.pyplot as plt
+
+# Configuration parameters
+D = 1 # number of devices
+B = 200 # number of buckets
+S = 30 # number of stratification layers
+PLANNING_WINDOW = pd.Timedelta("PT24H")
+PLANNING_RESOLUTION = pd.Timedelta("PT5M")
+
+T = PLANNING_WINDOW // PLANNING_RESOLUTION
+TIMESTEP_DURATION = PLANNING_RESOLUTION / pd.Timedelta("PT1S")
+
+
+def adjust_fill_level_target_profile_for_current_time(
+ fill_level_target_profile: FRBCFillLevelTargetProfile,
+ current_time: datetime,
+) -> FRBCFillLevelTargetProfile:
+ """
+ Adjust fill level target profile when current time is ahead of profile start_time.
+
+ Removes elements that have already passed and adjusts the start_time to current_time.
+ This keeps fill_level_ranges at fixed positions in time regardless of when scheduling happens.
+
+ Args:
+ fill_level_target_profile: The original fill level target profile
+ current_time: The current scheduling time (when planning actually happens)
+
+ Returns:
+ Adjusted fill level target profile with start_time set to current_time
+ """
+ if fill_level_target_profile is None:
+ return None
+
+ profile_start = fill_level_target_profile.start_time
+ if isinstance(profile_start, datetime):
+ profile_start = (
+ profile_start.replace(tzinfo=timezone.utc)
+ if profile_start.tzinfo is None
+ else profile_start
+ )
+ else:
+ profile_start = profile_start.astimezone(timezone.utc)
+
+ current_time = (
+ current_time.replace(tzinfo=timezone.utc)
+ if current_time.tzinfo is None
+ else current_time
+ )
+
+ # If current time is not ahead of profile start, just update start_time
+ if current_time <= profile_start:
+ return fill_level_target_profile.model_copy(update={"start_time": current_time})
+
+ # Calculate elapsed time since profile start
+ elapsed_time_ms = int((current_time - profile_start).total_seconds() * 1000)
+
+ # Helper function to extract duration in milliseconds
+ def get_duration_ms(elem):
+ duration = elem.duration
+ if isinstance(duration, (int, float)):
+ return int(duration)
+ elif hasattr(duration, "root"):
+ return int(duration.root)
+ else:
+ return int(duration)
+
+ # Find which elements have passed and which we're currently in
+ remaining_elements = []
+ cumulative_duration_ms = 0
+
+ for element in fill_level_target_profile.elements:
+ element_duration_ms = get_duration_ms(element)
+ element_end_ms = cumulative_duration_ms + element_duration_ms
+
+ if element_end_ms <= elapsed_time_ms:
+ # This element has completely passed, skip it
+ cumulative_duration_ms = element_end_ms
+ continue
+ elif cumulative_duration_ms < elapsed_time_ms:
+ # We're in the middle of this element, truncate it
+ remaining_duration_ms = element_end_ms - elapsed_time_ms
+ if remaining_duration_ms > 0:
+ remaining_elements.append(
+ FRBCFillLevelTargetProfileElement(
+ duration=Duration(root=remaining_duration_ms),
+ fill_level_range=element.fill_level_range,
+ )
+ )
+ cumulative_duration_ms = element_end_ms
+ else:
+ # This element is in the future, keep it as-is
+ remaining_elements.append(element)
+ cumulative_duration_ms = element_end_ms
+
+ # Create adjusted profile with current_time as start_time
+ adjusted_profile = FRBCFillLevelTargetProfile(
+ message_id=str(uuid.uuid4()),
+ start_time=current_time,
+ elements=remaining_elements,
+ )
+
+ return adjusted_profile
+
+
+def create_itho_device_state(
+ device_id: str,
+ start_time: datetime,
+ planning_window_duration: timedelta | None = None,
+ include_fill_level_target: bool = True,
+ include_usage_forecast: bool = True,
+) -> S2FrbcDeviceState:
+ """
+ Create device state from FRBC messages extracted from itho.yaml
+ """
+
+ # FRBC.SystemDescription from line 2 of itho.yaml
+ # DHW_OFF operation mode
+ dhw_off_mode_id = "959eb2c3-3d92-4ca1-8005-1d9caab972a5"
+ dhw_off_element = FRBCOperationModeElement(
+ fill_level_range=NumberRange(start_of_range=0.0, end_of_range=100.0),
+ fill_rate=NumberRange(start_of_range=0.0, end_of_range=0.0),
+ power_ranges=[
+ PowerRange(
+ start_of_range=0.0,
+ end_of_range=0.0,
+ commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1,
+ )
+ ],
+ running_costs=NumberRange(start_of_range=0.0, end_of_range=0.0),
+ )
+ dhw_off_mode = FRBCOperationMode(
+ id=dhw_off_mode_id,
+ diagnostic_label="DHW_OFF",
+ elements=[dhw_off_element],
+ abnormal_condition_only=False,
+ )
+
+ # DHW_ON operation mode
+ dhw_on_mode_id = "31314092-4456-43f8-89dd-0421194bc821"
+ dhw_on_element = FRBCOperationModeElement(
+ fill_level_range=NumberRange(start_of_range=10.0, end_of_range=61.5),
+ fill_rate=NumberRange(
+ start_of_range=0.002031807896078361, end_of_range=0.002031807896078361
+ ),
+ power_ranges=[
+ PowerRange(
+ start_of_range=1100.0,
+ end_of_range=1100.0,
+ commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1,
+ )
+ ],
+ )
+ dhw_on_mode = FRBCOperationMode(
+ id=dhw_on_mode_id,
+ diagnostic_label="DHW_ON",
+ elements=[dhw_on_element],
+ abnormal_condition_only=False,
+ )
+
+ # Timers
+ min_on_timer_id = "702cd17f-ac40-429e-93d4-ce69c8eb8040"
+ min_on_timer = Timer(
+ id=min_on_timer_id,
+ diagnostic_label="DHW Minimum On Time",
+ duration=Duration(root=1800000), # 30 minutes in milliseconds
+ )
+
+ min_off_timer_id = "b27ef8c9-c440-44cb-8deb-f7f85f491a92"
+ min_off_timer = Timer(
+ id=min_off_timer_id,
+ diagnostic_label="DHW Minimum Off Time",
+ duration=Duration(root=600000), # 10 minutes in milliseconds
+ )
+
+ # Transitions
+ off_to_on_transition_id = "4359a2e3-8782-4192-9775-2a29b828c45e"
+ off_to_on_transition = Transition(
+ id=off_to_on_transition_id,
+ **{"from": dhw_off_mode_id},
+ to=dhw_on_mode_id,
+ start_timers=[min_on_timer_id],
+ blocking_timers=[min_off_timer_id],
+ transition_duration=300000, # 5 minutes in milliseconds
+ abnormal_condition_only=False,
+ )
+
+ on_to_off_transition_id = "8d3bdeae-5052-4579-a62b-a36b427df63a"
+ on_to_off_transition = Transition(
+ id=on_to_off_transition_id,
+ **{"from": dhw_on_mode_id},
+ to=dhw_off_mode_id,
+ start_timers=[min_off_timer_id],
+ blocking_timers=[min_on_timer_id],
+ transition_duration=180000, # 3 minutes in milliseconds
+ abnormal_condition_only=False,
+ )
+
+ # Actuator
+ actuator_id = "189f6a58-e78a-4c3f-bcda-6cd151342285"
+ actuator_description = FRBCActuatorDescription(
+ id=actuator_id,
+ diagnostic_label="Heat Pump (DHW)",
+ supported_commodities=[Commodity.ELECTRICITY],
+ operation_modes=[dhw_off_mode, dhw_on_mode],
+ transitions=[off_to_on_transition, on_to_off_transition],
+ timers=[min_on_timer, min_off_timer],
+ )
+
+ # Storage description
+ storage_description = FRBCStorageDescription(
+ diagnostic_label="DHW Buffer",
+ fill_level_label="Temperature in degrees Celcius",
+ provides_leakage_behaviour=True,
+ provides_fill_level_target_profile=True,
+ provides_usage_forecast=True,
+ fill_level_range=NumberRange(start_of_range=10.0, end_of_range=61.5),
+ )
+
+ # System description
+ system_description = FRBCSystemDescription(
+ message_id=str(uuid.uuid4()),
+ valid_from=start_time,
+ actuators=[actuator_description],
+ storage=storage_description,
+ )
+
+ # FRBC.ActuatorStatus from line 123 (present_fill_level=16.182)
+ actuator_status = FRBCActuatorStatus(
+ message_id=str(uuid.uuid4()),
+ actuator_id=actuator_id,
+ active_operation_mode_id=dhw_off_mode_id,
+ operation_mode_factor=0.0,
+ )
+
+ # FRBC.StorageStatus from line 94
+ present_fill_level = 16.182 # Current fill level from device
+ storage_status = FRBCStorageStatus(
+ message_id=str(uuid.uuid4()),
+ present_fill_level=present_fill_level,
+ )
+
+ # FRBC.LeakageBehaviour from line 71
+ leakage_behaviour = FRBCLeakageBehaviour(
+ message_id=str(uuid.uuid4()),
+ valid_from=start_time,
+ elements=[
+ FRBCLeakageBehaviourElement(
+ fill_level_range=NumberRange(start_of_range=10.0, end_of_range=61.5),
+ leakage_rate=0.00004464134898917508,
+ )
+ ],
+ )
+
+ # Calculate total planning window in milliseconds
+ if planning_window_duration is None:
+ planning_window_duration = PLANNING_WINDOW
+
+ # Create fill level target profile (only if requested)
+ fill_level_target_profile = None
+ if include_fill_level_target:
+ # FRBC.FillLevelTargetProfile - create with fixed reference time (start of day)
+ # This keeps fill_level_ranges at fixed positions in time regardless of when scheduling happens
+ # The profile will be adjusted for the actual scheduling time later
+ fixed_reference_time = start_time.replace(
+ hour=0, minute=0, second=0, microsecond=0
+ )
+
+ # Get heating parameters from SystemDescription
+ # From DHW_ON operation mode: fill_rate = 0.002031807896078361 per second
+ # This means: 0.002031807896078361 °C per second when heating at full power (1100W)
+ fill_rate_per_second = 0.002031807896078361
+ timestep_duration_seconds = TIMESTEP_DURATION
+ fill_level_min = (
+ 10.0 # From fill level target profile in itho.yaml (maintenance periods)
+ )
+ fill_level_max = (
+ 55.0 # From fill level target profile in itho.yaml (maintenance periods)
+ )
+ peak_target_min = 51.5 # Peak period minimum target
+ peak_target_max = 55.0 # Peak period maximum target
+
+ # Calculate how long it takes to heat from current level to peak target
+ # Time needed = (target_temp - current_temp) / fill_rate
+ temp_rise_needed = peak_target_min - present_fill_level
+ heating_time_seconds = temp_rise_needed / fill_rate_per_second
+ heating_time_timesteps = (
+ int(heating_time_seconds / timestep_duration_seconds) + 1
+ ) # Add 1 for safety margin
+
+ print("=" * 80)
+ print("FILL LEVEL TARGET CONFIGURATION")
+ print("=" * 80)
+ print(f"Current fill level:{present_fill_level}°C")
+ print(f"Peak target: [{peak_target_min}, {peak_target_max}]°C")
+ print(f"Fill rate: {fill_rate_per_second} °C/second (when heating at 1100W)")
+ print(f"Temperature rise needed: {temp_rise_needed:.2f}°C")
+ print(
+ f"Heating time required: {heating_time_seconds / 3600:.2f} hours ({heating_time_timesteps} timesteps)"
+ )
+ print("")
+ print("STRATEGY:")
+ print(" - Peak periods require 51.5-55.0°C (constraint: fill_level >= 51.5°C)")
+ print(" - Optimizer sees constraint violation at peak times if not heated")
+ print(" - Optimizer MUST heat to 51.5°C before/during peak periods")
+ print(" - Optimizer will minimize cost by heating during CHEAP hours")
+ print("=" * 80)
+
+ # Create fill level target profile elements
+ # Use the EXACT pattern from itho.yaml line 116
+ # Pattern: 10 elements with alternating maintenance and peak periods
+ # Maintenance: [10.0, 55.0]°C, Peak: [51.5, 55.0]°C
+
+ elements = []
+
+ # Original pattern from itho.yaml (durations in milliseconds):
+ # Element 1: 23400000 ms (6.5 hours) - maintenance [10.0, 55.0]
+ # Element 2: 5400000 ms (1.5 hours) - peak [51.5, 55.0]
+ # Element 3: 32400000 ms (9 hours) - maintenance [10.0, 55.0]
+ # Element 4: 10800000 ms (3 hours) - peak [51.5, 55.0]
+ # Element 5: 14400000 ms (4 hours) - maintenance [10.0, 55.0]
+ # Element 6: 23400000 ms (6.5 hours) - maintenance [10.0, 55.0]
+ # Element 7: 5400000 ms (1.5 hours) - peak [51.5, 55.0]
+ # Element 8: 32400000 ms (9 hours) - maintenance [10.0, 55.0]
+ # Element 9: 10800000 ms (3 hours) - peak [51.5, 55.0]
+ # Element 10: 14400000 ms (4 hours) - maintenance [10.0, 55.0]
+
+ # Total duration:
+ # Define the pattern exactly as in itho.yaml
+ fill_level_target_elements = [
+ (23400000, fill_level_min, fill_level_max), # Maintenance
+ (5400000, peak_target_min, peak_target_max), # Peak
+ (32400000, fill_level_min, fill_level_max), # Maintenance
+ (10800000, peak_target_min, peak_target_max), # Peak
+ (14400000, fill_level_min, fill_level_max), # Maintenance
+ (23400000, fill_level_min, fill_level_max), # Maintenance
+ (5400000, peak_target_min, peak_target_max), # Peak
+ (32400000, fill_level_min, fill_level_max), # Maintenance
+ (10800000, peak_target_min, peak_target_max), # Peak
+ (14400000, fill_level_min, fill_level_max), # Maintenance
+ ]
+
+ for duration_ms, range_min, range_max in fill_level_target_elements:
+ elements.append(
+ FRBCFillLevelTargetProfileElement(
+ duration=Duration(root=duration_ms),
+ fill_level_range=NumberRange(
+ start_of_range=range_min, end_of_range=range_max
+ ),
+ )
+ )
+
+ # Verify total duration matches planning window
+ # Extract duration value (Duration objects store milliseconds in .root)
+ def get_duration_ms(elem):
+ duration = elem.duration
+ if isinstance(duration, (int, float)):
+ return int(duration) # Assume milliseconds if numeric
+ elif hasattr(duration, "root"):
+ return int(duration.root) # Duration.root is in milliseconds
+ else:
+ return int(duration)
+
+ total_duration_ms = sum(get_duration_ms(elem) for elem in elements)
+ total_duration_seconds = total_duration_ms / 1000.0
+ print(
+ f"\nTarget profile duration: {total_duration_seconds / 3600:.2f} hours ({total_duration_ms / 1000:.0f} seconds)"
+ )
+ print(
+ f"Planning window duration: {planning_window_duration.total_seconds() / 3600:.2f} hours ({planning_window_duration.total_seconds():.0f} seconds)"
+ )
+ print(f"Number of elements: {len(elements)}")
+
+ # Create profile with fixed reference time (start of day)
+ fill_level_target_profile = FRBCFillLevelTargetProfile(
+ message_id=str(uuid.uuid4()),
+ start_time=fixed_reference_time,
+ elements=elements,
+ )
+
+ # Adjust profile for actual scheduling time
+ # This removes elements that have already passed and sets start_time to current scheduling time
+ fill_level_target_profile = adjust_fill_level_target_profile_for_current_time(
+ fill_level_target_profile,
+ start_time,
+ )
+
+ if fill_level_target_profile:
+ print("\nAdjusted fill level target profile:")
+ print(f" Original reference time: {fixed_reference_time}")
+ print(f" Adjusted start_time: {fill_level_target_profile.start_time}")
+ print(f" Remaining elements: {len(fill_level_target_profile.elements)}")
+ else:
+ print(
+ "\nFill level target profile: NOT CREATED (include_fill_level_target=False)"
+ )
+
+ # Create simulated usage forecast (only if requested)
+ usage_forecast = None
+ if include_usage_forecast:
+ # Usage represents hot water being drawn, which reduces fill level (temperature)
+ # Positive usage_rate_expected means fill level decreases per second
+ # Match the pattern from fill level target profile (10 elements with same durations)
+ # Higher usage during peak periods to force heating
+
+ usage_elements = []
+
+ # Usage rates (fill level decrease per second)
+ # Higher usage during peak periods to force heating
+ base_usage_rate = 0.00001 # Low base usage (0.00001 °C/second = 0.036 °C/hour)
+ peak_usage_rate = (
+ 0.0001 # High usage during peaks (0.0001 °C/second = 0.36 °C/hour)
+ )
+
+ # Match the fill level target profile pattern exactly
+ # Use same durations and pattern: maintenance periods have low usage, peak periods have high usage
+ usage_forecast_elements = [
+ (23400000, base_usage_rate), # Maintenance period 1
+ (5400000, peak_usage_rate), # Peak period 1
+ (32400000, base_usage_rate), # Maintenance period 2
+ (10800000, peak_usage_rate), # Peak period 2
+ (14400000, base_usage_rate), # Maintenance period 3
+ (23400000, base_usage_rate), # Maintenance period 4
+ (5400000, peak_usage_rate), # Peak period 3
+ (32400000, base_usage_rate), # Maintenance period 5
+ (10800000, peak_usage_rate), # Peak period 4
+ (14400000, base_usage_rate), # Maintenance period 6
+ ]
+
+ for duration_ms, usage_rate in usage_forecast_elements:
+ usage_elements.append(
+ FRBCUsageForecastElement(
+ duration=duration_ms,
+ usage_rate_expected=usage_rate,
+ )
+ )
+
+ # Verify total duration (extract duration value, handling both int/float and Duration objects)
+ def get_duration_ms_usage(elem):
+ duration = elem.duration
+ if isinstance(duration, (int, float)):
+ return int(duration) # Assume milliseconds if numeric
+ elif hasattr(duration, "root"):
+ return int(duration.root) # Duration.root is in milliseconds
+ else:
+ return int(duration)
+
+ total_usage_duration_ms = sum(
+ get_duration_ms_usage(elem) for elem in usage_elements
+ )
+ total_usage_duration_seconds = total_usage_duration_ms / 1000.0
+
+ print("\nUsage forecast created:")
+ print(
+ f" Total duration: {total_usage_duration_seconds / 3600:.2f} hours ({total_usage_duration_ms / 1000:.0f} seconds)"
+ )
+ print(f" Number of elements: {len(usage_elements)}")
+ print(
+ f" Base usage rate: {base_usage_rate} °C/second ({base_usage_rate * 3600:.4f} °C/hour)"
+ )
+ print(
+ f" Peak usage rate: {peak_usage_rate} °C/second ({peak_usage_rate * 3600:.4f} °C/hour)"
+ )
+ print(" Usage during peaks will reduce fill level, forcing optimizer to heat")
+
+ usage_forecast = FRBCUsageForecast(
+ message_id=str(uuid.uuid4()),
+ start_time=start_time,
+ elements=usage_elements,
+ )
+ else:
+ print("\nUsage forecast: NOT CREATED (include_usage_forecast=False)")
+
+ # Create device state with optional fill level target and usage forecast
+ device_state = S2FrbcDeviceState(
+ device_id=device_id,
+ device_name="ITHO DHW Heat Pump",
+ connection_id=device_id + "_connection",
+ priority_class=1,
+ timestamp=start_time,
+ energy_in_current_timestep=PowerValue(
+ value=0, commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1
+ ),
+ is_online=True,
+ power_forecast=None,
+ system_descriptions=[system_description],
+ leakage_behaviours=[leakage_behaviour],
+ usage_forecasts=[usage_forecast]
+ if (include_usage_forecast and usage_forecast)
+ else [],
+ fill_level_target_profiles=(
+ [fill_level_target_profile]
+ if (include_fill_level_target and fill_level_target_profile)
+ else []
+ ),
+ computational_parameters=S2FrbcDeviceState.ComputationalParameters(B, S),
+ actuator_statuses=[actuator_status],
+ storage_status=storage_status,
+ )
+
+ return device_state
+
+
+def convert_fill_level_target_to_timeseries(
+ fill_level_target_profile,
+ start_time,
+ timestep_duration,
+ nr_of_timesteps,
+):
+ """
+ Convert FRBCFillLevelTargetProfile to time series arrays for plotting.
+
+ Returns:
+ tuple: (target_min_values, target_max_values) as lists
+ """
+ target_min = []
+ target_max = []
+
+ if fill_level_target_profile is None or not fill_level_target_profile.elements:
+ return [None] * nr_of_timesteps, [None] * nr_of_timesteps
+
+ current_timestep = 0
+ timestep_seconds = timestep_duration.total_seconds()
+
+ for element in fill_level_target_profile.elements:
+ # Duration is in milliseconds - extract the actual value
+ duration_ms = (
+ element.duration
+ if isinstance(element.duration, (int, float))
+ else getattr(element.duration, "root", element.duration)
+ )
+ duration_seconds = duration_ms / 1000
+ num_timesteps = int(duration_seconds / timestep_seconds)
+
+ # Get the fill level range for this element
+ min_level = element.fill_level_range.start_of_range
+ max_level = element.fill_level_range.end_of_range
+
+ # Add values for all timesteps in this element's duration
+ for _ in range(min(num_timesteps, nr_of_timesteps - current_timestep)):
+ target_min.append(min_level)
+ target_max.append(max_level)
+ current_timestep += 1
+
+ if current_timestep >= nr_of_timesteps:
+ break
+
+ # Fill remaining timesteps if needed
+ while len(target_min) < nr_of_timesteps:
+ target_min.append(target_min[-1] if target_min else None)
+ target_max.append(target_max[-1] if target_max else None)
+
+ return target_min[:nr_of_timesteps], target_max[:nr_of_timesteps]
+
+
+def plot_planning_results(
+ timestep_duration,
+ nr_of_timesteps,
+ predicted_energy_elements,
+ fill_level_profile=None,
+ fill_level_target_min=None,
+ fill_level_target_max=None,
+ start_time=None,
+ suffix="",
+ tariff_elements=None,
+):
+ """
+ Plots the energy and optionally fill level profiles using matplotlib.
+
+ Args:
+ suffix: Optional suffix for the filename (e.g., "_cost" or "_energy")
+ tariff_elements: Optional list of tariff values to display (for cost optimization)
+ """
+ # Generate timestep_start_times
+ if start_time is None:
+ start_time = datetime(1970, 1, 1, tzinfo=timezone.utc)
+
+ timestep_start_times = [
+ start_time + timedelta(seconds=i * timestep_duration.total_seconds())
+ for i in range(nr_of_timesteps)
+ ]
+
+ if fill_level_profile is not None:
+ # Create a figure with dual y-axes
+ fig, ax1 = plt.subplots(1, 1, figsize=(14, 8))
+
+ # If tariffs are provided, add them as background shading
+ if tariff_elements is not None:
+ # Normalize tariffs to 0-1 range for color intensity
+ min_tariff = min(tariff_elements)
+ max_tariff = max(tariff_elements)
+ tariff_range = max_tariff - min_tariff
+
+ # Color background based on tariff (red = expensive, green = cheap)
+ for i in range(len(timestep_start_times) - 1):
+ if tariff_range > 0:
+ intensity = (tariff_elements[i] - min_tariff) / tariff_range
+ else:
+ intensity = 0
+ # Red for high prices, yellow for medium, green for low
+ color = (intensity, 1 - intensity * 0.5, 0) # RGB
+ ax1.axvspan(
+ timestep_start_times[i],
+ timestep_start_times[i + 1],
+ alpha=0.15,
+ color=color,
+ )
+
+ # Add a text annotation explaining the background colors
+ ax1.text(
+ 0.02,
+ 0.98,
+ f"Background: Red=High price (${max_tariff:.2f}/kWh), Green=Low price (${min_tariff:.2f}/kWh)",
+ transform=ax1.transAxes,
+ verticalalignment="top",
+ bbox=dict(boxstyle="round", facecolor="white", alpha=0.8),
+ fontsize=9,
+ )
+
+ # Energy plot on left y-axis
+ ax1.plot(
+ timestep_start_times,
+ predicted_energy_elements,
+ label="Power Consumption (W)",
+ color="darkblue",
+ linewidth=2.5,
+ zorder=10,
+ )
+ ax1.set_ylabel("Power (W)", color="darkblue")
+ ax1.tick_params(axis="y", labelcolor="darkblue")
+ ax1.grid(True, alpha=0.3, zorder=0)
+
+ # Fill level plot on right y-axis
+ ax2 = ax1.twinx()
+ ax2.plot(
+ timestep_start_times,
+ fill_level_profile,
+ label="Actual Temperature (°C)",
+ color="red",
+ linewidth=2,
+ )
+
+ # Plot fill level targets if provided
+ if fill_level_target_min is not None and fill_level_target_max is not None:
+ # Filter out None values and convert to numeric arrays
+ target_min_numeric = []
+ target_max_numeric = []
+ times_numeric = []
+
+ for i, (t, min_val, max_val) in enumerate(
+ zip(timestep_start_times, fill_level_target_min, fill_level_target_max)
+ ):
+ if min_val is not None and max_val is not None:
+ try:
+ min_float = float(min_val)
+ max_float = float(max_val)
+ target_min_numeric.append(min_float)
+ target_max_numeric.append(max_float)
+ times_numeric.append(t)
+ except (ValueError, TypeError):
+ continue
+
+ if target_min_numeric and target_max_numeric:
+ ax2.fill_between(
+ times_numeric,
+ target_min_numeric,
+ target_max_numeric,
+ alpha=0.2,
+ color="blue",
+ label="Target Range (°C)",
+ )
+ ax2.plot(
+ times_numeric,
+ target_min_numeric,
+ color="blue",
+ linestyle="--",
+ linewidth=1,
+ alpha=0.5,
+ )
+ ax2.plot(
+ times_numeric,
+ target_max_numeric,
+ color="blue",
+ linestyle="--",
+ linewidth=1,
+ alpha=0.5,
+ )
+
+ ax2.set_ylabel("Temperature (°C)", color="red")
+ ax2.tick_params(axis="y", labelcolor="red")
+
+ # Combined legend
+ lines1, labels1 = ax1.get_legend_handles_labels()
+ lines2, labels2 = ax2.get_legend_handles_labels()
+ ax1.legend(lines1 + lines2, labels1 + labels2, loc="upper left")
+
+ ax1.set_title("ITHO DHW Heat Pump Schedule")
+ else:
+ # Create a figure with a single subplot for energy only
+ fig, ax1 = plt.subplots(1, 1, figsize=(14, 8))
+
+ ax1.plot(
+ timestep_start_times,
+ predicted_energy_elements,
+ label="Power Consumption (W)",
+ color="green",
+ linewidth=2,
+ )
+ ax1.set_ylabel("Power (W)")
+ ax1.set_title("ITHO DHW Heat Pump Schedule - Power Consumption")
+ ax1.legend(loc="best")
+ ax1.grid(True)
+
+ # Format the x-axis to show time with hourly ticks
+ ax1.xaxis.set_major_locator(mdates.HourLocator(interval=1))
+ ax1.xaxis.set_major_formatter(mdates.DateFormatter("%H:%M"))
+ fig.autofmt_xdate()
+
+ # Adjust layout
+ plt.tight_layout()
+
+ # Save plot
+ os.makedirs("plots", exist_ok=True)
+ filename = f"plots/itho_schedule{suffix}_D={D}_B={B}_S={S}_T={T}.png"
+ plt.savefig(filename)
+ # print(f"Plot saved to {filename}")
+
+ plt.show()
+
+
+def get_energy_target_profile_elements(number_of_elements: int, start_time: datetime):
+ """
+ Create energy target profile elements based on time of day.
+ Encourage heating during off-peak hours, discourage during peak hours.
+ """
+ target_elements = []
+
+ for i in range(number_of_elements):
+ current_time = start_time + timedelta(seconds=i * TIMESTEP_DURATION)
+ hour = current_time.hour
+
+ # Energy targets in Joules per timestep
+ # Heat pump consumes 1100W, so per 5-minute timestep: 1100W * 300s = 330,000 J
+ if 0 <= hour < 6: # Night hours (00:00-06:00) - encourage heating
+ target_elements.append(330000) # Full power
+ elif 6 <= hour < 9: # Morning (06:00-09:00) - moderate
+ target_elements.append(165000) # Half power
+ elif 9 <= hour < 17: # Peak hours (09:00-17:00) - discourage
+ target_elements.append(0) # Minimize
+ elif 17 <= hour < 21: # Evening (17:00-21:00) - moderate
+ target_elements.append(165000) # Half power
+ else: # Late evening (21:00-24:00) - encourage
+ target_elements.append(330000) # Full power
+
+ return target_elements
+
+
+def get_cost_target_profile_elements(number_of_elements: int):
+ """
+ Create cost target profile elements with dynamic pricing pattern.
+ Similar to the EV example.
+ """
+ cost_elements = []
+ intervals_per_hour = number_of_elements // 24
+
+ for hour in range(24):
+ if 0 <= hour < 6: # Night hours (00:00-06:00) - lowest cost
+ tariff = 0.10 # 10 cents per kWh
+ elif 6 <= hour < 9: # Morning ramp-up (06:00-09:00)
+ tariff = 0.15 # 15 cents per kWh
+ elif 9 <= hour < 17: # Peak hours (09:00-17:00) - highest cost
+ tariff = 0.30 # 30 cents per kWh
+ elif 17 <= hour < 21: # Evening peak (17:00-21:00)
+ tariff = 0.25 # 25 cents per kWh
+ else: # Late evening (21:00-24:00)
+ tariff = 0.15 # 15 cents per kWh
+
+ # Add the tariff for all intervals in this hour
+ cost_elements.extend([tariff] * intervals_per_hour)
+
+ return cost_elements[:number_of_elements]
+
+
+def calculate_cost_from_energy_and_tariffs(energy_elements, tariff_elements):
+ """Calculate cost from energy usage and tariff rates."""
+ cost_elements = []
+ for energy, tariff in zip(energy_elements, tariff_elements):
+ if energy is not None and tariff is not None:
+ # Convert Joules to kWh and multiply by tariff
+ kwh = energy / 3_600_000 # 1 kWh = 3,600,000 Joules
+ cost = kwh * tariff
+ cost_elements.append(cost)
+ else:
+ cost_elements.append(0.0)
+ return cost_elements
+
+
+def save_instructions_to_file(
+ device_plans_list, filename_suffix, start_time, device_state=None
+):
+ """
+ Save instruction profiles to a text file.
+
+ Args:
+ device_plans_list: List of device plans containing instruction profiles
+ filename_suffix: Suffix for the output filename
+ start_time: Start time of the planning window
+ device_state: Device state containing operation mode mappings (optional)
+ """
+ if not device_plans_list:
+ # print(f"No device plans to save for {filename_suffix}")
+ return
+
+ # Create operation mode ID to name mapping
+ operation_mode_names = {}
+ if device_state and device_state.system_descriptions:
+ for system_desc in device_state.system_descriptions:
+ if hasattr(system_desc, "actuators"):
+ for actuator in system_desc.actuators:
+ if hasattr(actuator, "operation_modes"):
+ for mode in actuator.operation_modes:
+ operation_mode_names[mode.id] = mode.diagnostic_label
+
+ os.makedirs("instructions", exist_ok=True)
+ filename = (
+ f"instructions/itho_instructions{filename_suffix}_D={D}_B={B}_S={S}_T={T}.txt"
+ )
+
+ with open(filename, "w") as f:
+ f.write("=" * 80 + "\n")
+ f.write(
+ f"ITHO DHW Heat Pump Instructions{filename_suffix.replace('_', ' ').title()}\n"
+ )
+ f.write("=" * 80 + "\n")
+ f.write(f"Planning start time: {start_time}\n")
+ f.write(f"Planning window: {PLANNING_WINDOW}\n")
+ f.write(f"Resolution: {PLANNING_RESOLUTION}\n")
+ f.write(f"Total timesteps: {T}\n")
+ f.write("=" * 80 + "\n\n")
+
+ for device_plan in device_plans_list:
+ if device_plan and device_plan.instruction_profile:
+ f.write(f"Device: {device_plan.device_id}\n")
+ f.write(
+ f"Number of instructions: {len(device_plan.instruction_profile.elements)}\n"
+ )
+ f.write("-" * 80 + "\n\n")
+
+ instructions = device_plan.instruction_profile.elements
+
+ # Group consecutive instructions with same operation mode
+ if instructions:
+ current_mode = None
+ current_mode_start = None
+ current_mode_start_idx = 0
+ mode_count = 0
+
+ f.write("Instruction Timeline:\n")
+ f.write("-" * 80 + "\n")
+
+ for i, instruction in enumerate(instructions):
+ if hasattr(instruction, "operation_mode"):
+ if instruction.operation_mode != current_mode:
+ # Print previous mode summary if exists
+ if current_mode is not None:
+ duration = i - current_mode_start_idx
+ duration_minutes = duration * TIMESTEP_DURATION / 60
+ mode_name = operation_mode_names.get(
+ current_mode, current_mode
+ )
+ f.write(f" Mode: {mode_name}\n")
+ f.write(
+ f" Start: {current_mode_start} (timestep {current_mode_start_idx})\n"
+ )
+ f.write(
+ f" Duration: {duration} timesteps ({duration_minutes:.1f} minutes)\n"
+ )
+ f.write(
+ f" Operation mode factor: {getattr(instruction, 'operation_mode_factor', 'N/A')}\n"
+ )
+ f.write("-" * 40 + "\n")
+ mode_count += 1
+
+ # Start new mode
+ current_mode = instruction.operation_mode
+ current_mode_start = getattr(
+ instruction, "execution_time", f"Timestep {i}"
+ )
+ current_mode_start_idx = i
+
+ # Print final mode
+ if current_mode is not None:
+ duration = len(instructions) - current_mode_start_idx
+ duration_minutes = duration * TIMESTEP_DURATION / 60
+ mode_name = operation_mode_names.get(current_mode, current_mode)
+ f.write(f" Mode: {mode_name}\n")
+ f.write(
+ f" Start: {current_mode_start} (timestep {current_mode_start_idx})\n"
+ )
+ f.write(
+ f" Duration: {duration} timesteps ({duration_minutes:.1f} minutes)\n"
+ )
+ f.write("-" * 40 + "\n")
+ mode_count += 1
+
+ f.write(f"\nTotal operation mode changes: {mode_count - 1}\n")
+ f.write("=" * 80 + "\n\n")
+
+ # Detailed instruction list
+ f.write("Detailed Instruction List:\n")
+ f.write("=" * 80 + "\n")
+ for i, instruction in enumerate(instructions):
+ f.write(f"\nInstruction #{i + 1}:\n")
+ f.write(f" Timestep: {i}\n")
+ f.write(
+ f" Time: {start_time + timedelta(seconds=i * TIMESTEP_DURATION)}\n"
+ )
+
+ if hasattr(instruction, "id"):
+ f.write(f" ID: {instruction.id}\n")
+ if hasattr(instruction, "actuator_id"):
+ f.write(f" Actuator ID: {instruction.actuator_id}\n")
+ if hasattr(instruction, "operation_mode"):
+ mode_name = operation_mode_names.get(
+ instruction.operation_mode, instruction.operation_mode
+ )
+ f.write(f" Operation Mode: {mode_name}\n")
+ if operation_mode_names.get(instruction.operation_mode):
+ f.write(
+ f" Operation Mode ID: {instruction.operation_mode}\n"
+ )
+ if hasattr(instruction, "operation_mode_factor"):
+ f.write(
+ f" Operation Mode Factor: {instruction.operation_mode_factor}\n"
+ )
+ if hasattr(instruction, "execution_time"):
+ f.write(f" Execution Time: {instruction.execution_time}\n")
+
+ f.write("-" * 40 + "\n")
+
+ # print(f"Instructions saved to: {filename}")
+
+
+def test_itho_planning_with_energy_target():
+ """Test the PlanningServiceImpl with ITHO DHW device using energy targets."""
+ # print("=" * 80)
+ # print("Test 1: ITHO DHW Heat Pump with Energy Target Profile")
+ # print("=" * 80)
+
+ # Start time is 15 minutes from now, aligned to 5-minute intervals
+ now = datetime.now(timezone.utc)
+ # Round to nearest 5-minute interval
+ minutes = (now.minute // 5) * 5
+ start_time = now.replace(minute=minutes, second=0, microsecond=0) + timedelta(
+ minutes=15
+ )
+ # print(f"Planning start time: {start_time}")
+
+ # Create profile metadata
+ target_metadata = ProfileMetadata(
+ profile_start=start_time,
+ timestep_duration=timedelta(seconds=TIMESTEP_DURATION),
+ nr_of_timesteps=T,
+ )
+
+ # Create device state
+ device_id = "itho_dhw_device_001"
+ device_state = create_itho_device_state(device_id, start_time)
+
+ # Create Dictionary of device states
+ device_states_dict = {device_id: device_state}
+
+ # Create congestion points mapping
+ congestion_points_by_connection_id = {device_id: ""}
+
+ # Create a cluster state
+ cluster_state = ClusterState(
+ start_time, device_states_dict, congestion_points_by_connection_id
+ )
+
+ # Create energy target profile based on time of day
+ target_profile_elements = get_energy_target_profile_elements(T, start_time)
+ global_target_profile = TargetProfile(
+ profile_start=target_metadata.profile_start,
+ timestep_duration=target_metadata.timestep_duration,
+ elements=target_profile_elements,
+ )
+
+ # Create a cluster target
+ cluster_target = ClusterTarget(
+ start_time,
+ None,
+ None,
+ global_target_profile=global_target_profile,
+ congestion_point_targets={},
+ )
+
+ # Configuration similar to example_schedule_frbc.py
+ config = PlanningServiceConfig(
+ energy_improvement_criterion=10.0,
+ cost_improvement_criterion=1.0,
+ congestion_retry_iterations=10,
+ multithreaded=False,
+ )
+
+ # print("Generating plan...")
+
+ # Create planning service implementation
+ service = PlanningServiceImpl(config)
+
+ # Generate a plan
+ plan_due_by_date = start_time + timedelta(seconds=10)
+ start_planning_time = time.time()
+
+ cluster_plan = service.plan(
+ state=cluster_state,
+ target=cluster_target,
+ planning_window=TIMESTEP_DURATION * T,
+ reason="ITHO DHW Heat Pump scheduling",
+ plan_due_by_date=plan_due_by_date,
+ optimize_for_target=True,
+ max_priority_class=1,
+ )
+
+ end_planning_time = time.time()
+ execution_time = end_planning_time - start_planning_time
+
+ print(f"Plan generated in {execution_time:.2f} seconds")
+
+ # Assert and process results
+ assert cluster_plan is not None, "Cluster plan is None"
+ print("Successfully generated cluster plan")
+
+ # Get the plan data
+ device_plans = cluster_plan.get_plan_data().get_device_plans()
+ energy_profile = cluster_plan.get_joule_profile()
+
+ # Convert Joules to Watts (W = J / timestep_in_seconds)
+ power_profile_watts = [
+ energy / TIMESTEP_DURATION if energy is not None else 0
+ for energy in energy_profile.elements
+ ]
+
+ # Get fill level profile if available
+ fill_level_profile = None
+ device_plans_list = [plan for plan in device_plans if plan is not None]
+
+ if device_plans_list:
+ device_plan = device_plans_list[0]
+ if (
+ hasattr(device_plan, "fill_level_profile")
+ and device_plan.fill_level_profile
+ ):
+ fill_level_profile = device_plan.fill_level_profile.elements
+ print(
+ f"Fill level profile available with {len(fill_level_profile)} elements"
+ )
+
+ if device_plan.instruction_profile:
+ instructions = device_plan.instruction_profile.elements
+ print(f"Device has {len(instructions)} instructions")
+
+ # Analyze operation mode changes
+ mode_changes = 0
+ previous_mode = None
+ for instruction in instructions:
+ if hasattr(instruction, "operation_mode"):
+ if previous_mode and instruction.operation_mode != previous_mode:
+ mode_changes += 1
+ previous_mode = instruction.operation_mode
+ # print(f"Number of operation mode changes: {mode_changes}")
+
+ # Calculate total energy consumption
+ total_energy_kwh = sum(energy_profile.elements) / 3_600_000
+ print(f"Total energy consumption: {total_energy_kwh:.2f} kWh")
+
+ # Extract fill level targets from device state
+ fill_level_target_min = None
+ fill_level_target_max = None
+ if device_state.fill_level_target_profiles:
+ target_profile = device_state.fill_level_target_profiles[0]
+ (
+ fill_level_target_min,
+ fill_level_target_max,
+ ) = convert_fill_level_target_to_timeseries(
+ target_profile,
+ start_time,
+ timedelta(seconds=TIMESTEP_DURATION),
+ T,
+ )
+ # print("Fill level targets extracted from device state")
+
+ # Save instructions to file
+ save_instructions_to_file(device_plans_list, "_energy", start_time, device_state)
+
+ # Plot the results
+ plot_planning_results(
+ timestep_duration=timedelta(seconds=TIMESTEP_DURATION),
+ nr_of_timesteps=T,
+ predicted_energy_elements=power_profile_watts,
+ fill_level_profile=fill_level_profile,
+ fill_level_target_min=fill_level_target_min,
+ fill_level_target_max=fill_level_target_max,
+ start_time=start_time,
+ suffix="_energy",
+ )
+
+ # print("ITHO energy target test completed successfully!")
+
+ return cluster_plan
+
+
+def test_itho_planning_with_constant_cost():
+ """Test the PlanningServiceImpl with ITHO DHW device using constant cost."""
+ # print("\n" + "=" * 80)
+ # print("Test 2: ITHO DHW Heat Pump with Constant Cost (1 EUR/kWh)")
+ # print("=" * 80)
+
+ # Start time is 15 minutes from now, aligned to 5-minute intervals
+ now = datetime.now(timezone.utc)
+ # Round to nearest 5-minute interval
+ minutes = (now.minute // 5) * 5
+ start_time = now.replace(minute=minutes, second=0, microsecond=0) + timedelta(
+ minutes=15
+ )
+ # print(f"Planning start time: {start_time}")
+
+ # Create profile metadata
+ target_metadata = ProfileMetadata(
+ profile_start=start_time,
+ timestep_duration=timedelta(seconds=TIMESTEP_DURATION),
+ nr_of_timesteps=T,
+ )
+
+ # Create device state
+ device_id = "itho_dhw_device_002"
+ device_state = create_itho_device_state(device_id, start_time)
+
+ # Create Dictionary of device states
+ device_states_dict = {device_id: device_state}
+
+ # Create congestion points mapping
+ congestion_points_by_connection_id = {device_id: ""}
+
+ # Create a cluster state
+ cluster_state = ClusterState(
+ start_time, device_states_dict, congestion_points_by_connection_id
+ )
+
+ # Create constant cost target profile (1 EUR/kWh for all timesteps)
+ constant_cost_elements = [1.0] * T
+
+ # Create a cost-based target profile
+ cost_target_profile = TargetProfile.from_tariff_values(
+ target_metadata, constant_cost_elements
+ )
+
+ # Create a cluster target with cost target
+ cluster_target = ClusterTarget(
+ start_time,
+ None,
+ None,
+ global_target_profile=cost_target_profile,
+ congestion_point_targets={},
+ )
+
+ # Configuration similar to example_schedule_frbc.py
+ config = PlanningServiceConfig(
+ energy_improvement_criterion=10.0,
+ cost_improvement_criterion=1.0,
+ congestion_retry_iterations=10,
+ multithreaded=False,
+ )
+
+ # print("Generating constant-cost plan...")
+
+ # Create planning service implementation
+ service = PlanningServiceImpl(config)
+
+ # Generate a plan
+ plan_due_by_date = start_time + timedelta(seconds=10)
+ # start_planning_time = time.time()
+
+ cluster_plan = service.plan(
+ state=cluster_state,
+ target=cluster_target,
+ planning_window=TIMESTEP_DURATION * T,
+ reason="ITHO DHW Heat Pump constant cost optimization",
+ plan_due_by_date=plan_due_by_date,
+ optimize_for_target=True,
+ max_priority_class=1,
+ )
+
+ # end_planning_time = time.time()
+ # execution_time = end_planning_time - start_planning_time
+
+ # print(f"Constant-cost plan generated in {execution_time:.2f} seconds")
+
+ # Assert and process results
+ assert cluster_plan is not None, "Cluster plan is None"
+ # print("Successfully generated constant-cost cluster plan")
+
+ # Get the plan data
+ device_plans = cluster_plan.get_plan_data().get_device_plans()
+ energy_profile = cluster_plan.get_joule_profile()
+
+ # Convert Joules to Watts (W = J / timestep_in_seconds)
+ power_profile_watts = [
+ energy / TIMESTEP_DURATION if energy is not None else 0
+ for energy in energy_profile.elements
+ ]
+
+ # Get fill level profile if available
+ fill_level_profile = None
+ device_plans_list = [plan for plan in device_plans if plan is not None]
+
+ if device_plans_list:
+ device_plan = device_plans_list[0]
+ if (
+ hasattr(device_plan, "fill_level_profile")
+ and device_plan.fill_level_profile
+ ):
+ fill_level_profile = device_plan.fill_level_profile.elements
+ print(
+ f"Fill level profile available with {len(fill_level_profile)} elements"
+ )
+
+ if device_plan.instruction_profile:
+ instructions = device_plan.instruction_profile.elements
+ # print(f"Device has {len(instructions)} instructions")
+
+ # Analyze operation mode changes
+ mode_changes = 0
+ previous_mode = None
+ for instruction in instructions:
+ if hasattr(instruction, "operation_mode"):
+ if previous_mode and instruction.operation_mode != previous_mode:
+ mode_changes += 1
+ previous_mode = instruction.operation_mode
+ # print(f"Number of operation mode changes: {mode_changes}")
+
+ # Calculate total energy consumption
+ # total_energy_kwh = sum(energy_profile.elements) / 3_600_000
+ # print(f"Total energy consumption: {total_energy_kwh:.2f} kWh")
+
+ # Calculate the actual costs from the energy profile and tariffs
+ # cost_elements = calculate_cost_from_energy_and_tariffs(
+ # energy_profile.elements, constant_cost_elements
+ # )
+ # total_cost = sum(cost_elements)
+ # print(f"Total cost: €{total_cost:.2f}")
+
+ # Extract fill level targets from device state
+ fill_level_target_min = None
+ fill_level_target_max = None
+ if device_state.fill_level_target_profiles:
+ target_profile = device_state.fill_level_target_profiles[0]
+ (
+ fill_level_target_min,
+ fill_level_target_max,
+ ) = convert_fill_level_target_to_timeseries(
+ target_profile,
+ start_time,
+ timedelta(seconds=TIMESTEP_DURATION),
+ T,
+ )
+ # print("Fill level targets extracted from device state")
+
+ # Save instructions to file
+ save_instructions_to_file(
+ device_plans_list, "_constant_cost", start_time, device_state
+ )
+
+ # Plot the results with constant tariff information
+ plot_planning_results(
+ timestep_duration=timedelta(seconds=TIMESTEP_DURATION),
+ nr_of_timesteps=T,
+ predicted_energy_elements=power_profile_watts,
+ fill_level_profile=fill_level_profile,
+ fill_level_target_min=fill_level_target_min,
+ fill_level_target_max=fill_level_target_max,
+ start_time=start_time,
+ suffix="_constant_cost",
+ tariff_elements=constant_cost_elements,
+ )
+
+ # print("ITHO constant cost test completed successfully!")
+
+ return cluster_plan
+
+
+def test_itho_planning_with_cost_target():
+ """Test the PlanningServiceImpl with ITHO DHW device using variable cost targets."""
+ # print("\n" + "=" * 80)
+ # print("Test 3: ITHO DHW Heat Pump with Variable Cost Target Profile")
+ # print("=" * 80)
+
+ # Start time is 15 minutes from now, aligned to 5-minute intervals
+ now = datetime.now(timezone.utc)
+ # Round to nearest 5-minute interval
+ minutes = (now.minute // 5) * 5
+ start_time = now.replace(minute=minutes, second=0, microsecond=0) + timedelta(
+ minutes=15
+ )
+ # print(f"Planning start time: {start_time}")
+
+ # Create profile metadata
+ target_metadata = ProfileMetadata(
+ profile_start=start_time,
+ timestep_duration=timedelta(seconds=TIMESTEP_DURATION),
+ nr_of_timesteps=T,
+ )
+
+ # Create device state
+ device_id = "itho_dhw_device_003"
+ device_state = create_itho_device_state(device_id, start_time)
+
+ # Create Dictionary of device states
+ device_states_dict = {device_id: device_state}
+
+ # Create congestion points mapping
+ congestion_points_by_connection_id = {device_id: ""}
+
+ # Create a cluster state
+ cluster_state = ClusterState(
+ start_time, device_states_dict, congestion_points_by_connection_id
+ )
+
+ # Create variable cost target profile
+ cost_target_elements = get_cost_target_profile_elements(T)
+
+ # Create a cost-based target profile
+ cost_target_profile = TargetProfile.from_tariff_values(
+ target_metadata, cost_target_elements
+ )
+
+ # Create a cluster target with cost target
+ cluster_target = ClusterTarget(
+ start_time,
+ None,
+ None,
+ global_target_profile=cost_target_profile,
+ congestion_point_targets={},
+ )
+
+ # Configuration similar to example_schedule_frbc.py
+ config = PlanningServiceConfig(
+ energy_improvement_criterion=10.0,
+ cost_improvement_criterion=1.0,
+ congestion_retry_iterations=10,
+ multithreaded=False,
+ )
+
+ # print("Generating cost-optimized plan...")
+
+ # Create planning service implementation
+ service = PlanningServiceImpl(config)
+
+ # Generate a plan
+ plan_due_by_date = start_time + timedelta(seconds=10)
+ start_planning_time = time.time()
+
+ cluster_plan = service.plan(
+ state=cluster_state,
+ target=cluster_target,
+ planning_window=TIMESTEP_DURATION * T,
+ reason="ITHO DHW Heat Pump cost optimization",
+ plan_due_by_date=plan_due_by_date,
+ optimize_for_target=True,
+ max_priority_class=1,
+ )
+
+ end_planning_time = time.time()
+ execution_time = end_planning_time - start_planning_time
+
+ print(f"Cost-optimized plan generated in {execution_time:.2f} seconds")
+
+ # Assert and process results
+ assert cluster_plan is not None, "Cluster plan is None"
+ print("Successfully generated cost-optimized cluster plan")
+
+ # Get the plan data
+ device_plans = cluster_plan.get_plan_data().get_device_plans()
+ energy_profile = cluster_plan.get_joule_profile()
+
+ # Convert Joules to Watts (W = J / timestep_in_seconds)
+ power_profile_watts = [
+ energy / TIMESTEP_DURATION if energy is not None else 0
+ for energy in energy_profile.elements
+ ]
+
+ # Get fill level profile if available
+ fill_level_profile = None
+ device_plans_list = [plan for plan in device_plans if plan is not None]
+
+ if device_plans_list:
+ device_plan = device_plans_list[0]
+ if (
+ hasattr(device_plan, "fill_level_profile")
+ and device_plan.fill_level_profile
+ ):
+ fill_level_profile = device_plan.fill_level_profile.elements
+ print(
+ f"Fill level profile available with {len(fill_level_profile)} elements"
+ )
+
+ if device_plan.instruction_profile:
+ instructions = device_plan.instruction_profile.elements
+ # print(f"Device has {len(instructions)} instructions")
+
+ # Analyze operation mode changes
+ mode_changes = 0
+ previous_mode = None
+ for instruction in instructions:
+ if hasattr(instruction, "operation_mode"):
+ if previous_mode and instruction.operation_mode != previous_mode:
+ mode_changes += 1
+ previous_mode = instruction.operation_mode
+ # print(f"Number of operation mode changes: {mode_changes}")
+
+ # Calculate total energy consumption
+ total_energy_kwh = sum(energy_profile.elements) / 3_600_000
+ print(f"Total energy consumption: {total_energy_kwh:.2f} kWh")
+
+ # Calculate the actual costs from the energy profile and tariffs
+ cost_elements = calculate_cost_from_energy_and_tariffs(
+ energy_profile.elements, cost_target_elements
+ )
+ total_cost = sum(cost_elements)
+ print(f"Total cost: ${total_cost:.2f}")
+
+ # Extract fill level targets from device state
+ fill_level_target_min = None
+ fill_level_target_max = None
+ if device_state.fill_level_target_profiles:
+ target_profile = device_state.fill_level_target_profiles[0]
+ (
+ fill_level_target_min,
+ fill_level_target_max,
+ ) = convert_fill_level_target_to_timeseries(
+ target_profile,
+ start_time,
+ timedelta(seconds=TIMESTEP_DURATION),
+ T,
+ )
+ # print("Fill level targets extracted from device state")
+
+ # Save instructions to file
+ save_instructions_to_file(device_plans_list, "_cost", start_time, device_state)
+
+ # Plot the results with tariff information
+ plot_planning_results(
+ timestep_duration=timedelta(seconds=TIMESTEP_DURATION),
+ nr_of_timesteps=T,
+ predicted_energy_elements=power_profile_watts,
+ fill_level_profile=fill_level_profile,
+ fill_level_target_min=fill_level_target_min,
+ fill_level_target_max=fill_level_target_max,
+ start_time=start_time,
+ suffix="_cost",
+ tariff_elements=cost_target_elements,
+ )
+
+ # print("ITHO cost target test completed successfully!")
+
+ return cluster_plan
+
+
+def test_itho_planning_with_usage_forecast_only():
+ """Test the PlanningServiceImpl with ITHO DHW device using ONLY usage forecast (no fill level target)."""
+ # print("\n" + "=" * 80)
+ # print(
+ # "Test 4: ITHO DHW Heat Pump with Usage Forecast ONLY (no FillLevelTargetProfile)"
+ # )
+ # print("=" * 80)
+
+ # Start time is 15 minutes from now, aligned to 5-minute intervals
+ now = datetime.now(timezone.utc)
+ # Round to nearest 5-minute interval
+ minutes = (now.minute // 5) * 5
+ start_time = now.replace(minute=minutes, second=0, microsecond=0) + timedelta(
+ minutes=15
+ )
+ # print(f"Planning start time: {start_time}")
+
+ # Create profile metadata
+ target_metadata = ProfileMetadata(
+ profile_start=start_time,
+ timestep_duration=timedelta(seconds=TIMESTEP_DURATION),
+ nr_of_timesteps=T,
+ )
+
+ # Create device state WITH usage forecast but WITHOUT fill level target
+ device_id = "itho_dhw_device_004"
+ device_state = create_itho_device_state(
+ device_id,
+ start_time,
+ include_fill_level_target=False, # NO fill level target
+ include_usage_forecast=True, # YES usage forecast
+ )
+
+ # Create Dictionary of device states
+ device_states_dict = {device_id: device_state}
+
+ # Create congestion points mapping
+ congestion_points_by_connection_id = {device_id: ""}
+
+ # Create a cluster state
+ cluster_state = ClusterState(
+ start_time, device_states_dict, congestion_points_by_connection_id
+ )
+
+ # Create variable cost target profile
+ cost_target_elements = get_cost_target_profile_elements(T)
+
+ # Create a cost-based target profile
+ cost_target_profile = TargetProfile.from_tariff_values(
+ target_metadata, cost_target_elements
+ )
+
+ # Create a cluster target with cost target
+ cluster_target = ClusterTarget(
+ start_time,
+ None,
+ None,
+ global_target_profile=cost_target_profile,
+ congestion_point_targets={},
+ )
+
+ # Configuration similar to example_schedule_frbc.py
+ config = PlanningServiceConfig(
+ energy_improvement_criterion=10.0,
+ cost_improvement_criterion=1.0,
+ congestion_retry_iterations=10,
+ multithreaded=False,
+ )
+
+ # print("Generating plan with usage forecast only (no fill level target)...")
+
+ # Create planning service implementation
+ service = PlanningServiceImpl(config)
+
+ # Generate a plan
+ plan_due_by_date = start_time + timedelta(seconds=10)
+ # start_planning_time = time.time()
+
+ cluster_plan = service.plan(
+ state=cluster_state,
+ target=cluster_target,
+ planning_window=TIMESTEP_DURATION * T,
+ reason="ITHO DHW Heat Pump with usage forecast only",
+ plan_due_by_date=plan_due_by_date,
+ optimize_for_target=True,
+ max_priority_class=1,
+ )
+
+ # end_planning_time = time.time()
+ # execution_time = end_planning_time - start_planning_time
+
+ # print(f"Plan generated in {execution_time:.2f} seconds")
+
+ # Assert and process results
+ assert cluster_plan is not None, "Cluster plan is None"
+ # print("Successfully generated cluster plan with usage forecast only")
+
+ # Get the plan data
+ device_plans = cluster_plan.get_plan_data().get_device_plans()
+ energy_profile = cluster_plan.get_joule_profile()
+
+ # Convert Joules to Watts (W = J / timestep_in_seconds)
+ power_profile_watts = [
+ energy / TIMESTEP_DURATION if energy is not None else 0
+ for energy in energy_profile.elements
+ ]
+
+ # Get fill level profile if available
+ fill_level_profile = None
+ device_plans_list = [plan for plan in device_plans if plan is not None]
+
+ if device_plans_list:
+ device_plan = device_plans_list[0]
+ if (
+ hasattr(device_plan, "fill_level_profile")
+ and device_plan.fill_level_profile
+ ):
+ fill_level_profile = device_plan.fill_level_profile.elements
+ print(
+ f"Fill level profile available with {len(fill_level_profile)} elements"
+ )
+
+ if device_plan.instruction_profile:
+ instructions = device_plan.instruction_profile.elements
+ # print(f"Device has {len(instructions)} instructions")
+
+ # Analyze operation mode changes
+ mode_changes = 0
+ previous_mode = None
+ for instruction in instructions:
+ if hasattr(instruction, "operation_mode"):
+ if previous_mode and instruction.operation_mode != previous_mode:
+ mode_changes += 1
+ previous_mode = instruction.operation_mode
+ # print(f"Number of operation mode changes: {mode_changes}")
+
+ # Calculate total energy consumption
+ # total_energy_kwh = sum(energy_profile.elements) / 3_600_000
+ # print(f"Total energy consumption: {total_energy_kwh:.2f} kWh")
+
+ # Calculate the actual costs from the energy profile and tariffs
+ # cost_elements = calculate_cost_from_energy_and_tariffs(
+ # energy_profile.elements, cost_target_elements
+ # )
+ # total_cost = sum(cost_elements)
+ # print(f"Total cost: ${total_cost:.2f}")
+
+ # No fill level targets to extract (they were not included)
+ fill_level_target_min = None
+ fill_level_target_max = None
+ # print("No fill level targets (test uses usage forecast only)")
+
+ # Save instructions to file
+ save_instructions_to_file(
+ device_plans_list, "_usage_only", start_time, device_state
+ )
+
+ # Plot the results with tariff information
+ plot_planning_results(
+ timestep_duration=timedelta(seconds=TIMESTEP_DURATION),
+ nr_of_timesteps=T,
+ predicted_energy_elements=power_profile_watts,
+ fill_level_profile=fill_level_profile,
+ fill_level_target_min=fill_level_target_min,
+ fill_level_target_max=fill_level_target_max,
+ start_time=start_time,
+ suffix="_usage_only",
+ tariff_elements=cost_target_elements,
+ )
+
+ # print("ITHO usage forecast only test completed successfully!")
+
+ return cluster_plan
+
+
+def test_itho_planning_with_fill_level_target_only():
+ """Test the PlanningServiceImpl with ITHO DHW device using ONLY fill level target (no usage forecast)."""
+ # print("\n" + "=" * 80)
+ # print(
+ # "Test 5: ITHO DHW Heat Pump with FillLevelTargetProfile ONLY (no UsageForecast)"
+ # )
+ # print("=" * 80)
+
+ # Start time is 15 minutes from now, aligned to 5-minute intervals
+ now = datetime.now(timezone.utc)
+ # Round to nearest 5-minute interval
+ minutes = (now.minute // 5) * 5
+ start_time = now.replace(minute=minutes, second=0, microsecond=0) + timedelta(
+ minutes=15
+ )
+ # print(f"Planning start time: {start_time}")
+
+ # Create profile metadata
+ target_metadata = ProfileMetadata(
+ profile_start=start_time,
+ timestep_duration=timedelta(seconds=TIMESTEP_DURATION),
+ nr_of_timesteps=T,
+ )
+
+ # Create device state WITH fill level target but WITHOUT usage forecast
+ device_id = "itho_dhw_device_005"
+ device_state = create_itho_device_state(
+ device_id,
+ start_time,
+ include_fill_level_target=True, # YES fill level target
+ include_usage_forecast=False, # NO usage forecast
+ )
+
+ # Create Dictionary of device states
+ device_states_dict = {device_id: device_state}
+
+ # Create congestion points mapping
+ congestion_points_by_connection_id = {device_id: ""}
+
+ # Create a cluster state
+ cluster_state = ClusterState(
+ start_time, device_states_dict, congestion_points_by_connection_id
+ )
+
+ # Create variable cost target profile
+ cost_target_elements = get_cost_target_profile_elements(T)
+
+ # Create a cost-based target profile
+ cost_target_profile = TargetProfile.from_tariff_values(
+ target_metadata, cost_target_elements
+ )
+
+ # Create a cluster target with cost target
+ cluster_target = ClusterTarget(
+ start_time,
+ None,
+ None,
+ global_target_profile=cost_target_profile,
+ congestion_point_targets={},
+ )
+
+ # Configuration similar to example_schedule_frbc.py
+ config = PlanningServiceConfig(
+ energy_improvement_criterion=10.0,
+ cost_improvement_criterion=1.0,
+ congestion_retry_iterations=10,
+ multithreaded=False,
+ )
+
+ # print("Generating plan with fill level target only (no usage forecast)...")
+
+ # Create planning service implementation
+ service = PlanningServiceImpl(config)
+
+ # Generate a plan
+ plan_due_by_date = start_time + timedelta(seconds=10)
+ # start_planning_time = time.time()
+
+ cluster_plan = service.plan(
+ state=cluster_state,
+ target=cluster_target,
+ planning_window=TIMESTEP_DURATION * T,
+ reason="ITHO DHW Heat Pump with fill level target only",
+ plan_due_by_date=plan_due_by_date,
+ optimize_for_target=True,
+ max_priority_class=1,
+ )
+
+ # end_planning_time = time.time()
+ # execution_time = end_planning_time - start_planning_time
+
+ # print(f"Plan generated in {execution_time:.2f} seconds")
+
+ # Assert and process results
+ assert cluster_plan is not None, "Cluster plan is None"
+ # print("Successfully generated cluster plan with fill level target only")
+
+ # Get the plan data
+ device_plans = cluster_plan.get_plan_data().get_device_plans()
+ energy_profile = cluster_plan.get_joule_profile()
+
+ # Convert Joules to Watts (W = J / timestep_in_seconds)
+ power_profile_watts = [
+ energy / TIMESTEP_DURATION if energy is not None else 0
+ for energy in energy_profile.elements
+ ]
+
+ # Get fill level profile if available
+ fill_level_profile = None
+ device_plans_list = [plan for plan in device_plans if plan is not None]
+
+ if device_plans_list:
+ device_plan = device_plans_list[0]
+ if (
+ hasattr(device_plan, "fill_level_profile")
+ and device_plan.fill_level_profile
+ ):
+ fill_level_profile = device_plan.fill_level_profile.elements
+ print(
+ f"Fill level profile available with {len(fill_level_profile)} elements"
+ )
+
+ if device_plan.instruction_profile:
+ instructions = device_plan.instruction_profile.elements
+ # print(f"Device has {len(instructions)} instructions")
+
+ # Analyze operation mode changes
+ mode_changes = 0
+ previous_mode = None
+ for instruction in instructions:
+ if hasattr(instruction, "operation_mode"):
+ if previous_mode and instruction.operation_mode != previous_mode:
+ mode_changes += 1
+ previous_mode = instruction.operation_mode
+ # print(f"Number of operation mode changes: {mode_changes}")
+
+ # Calculate total energy consumption
+ total_energy_kwh = sum(energy_profile.elements) / 3_600_000
+ print(f"Total energy consumption: {total_energy_kwh:.2f} kWh")
+
+ # Calculate the actual costs from the energy profile and tariffs
+ cost_elements = calculate_cost_from_energy_and_tariffs(
+ energy_profile.elements, cost_target_elements
+ )
+ total_cost = sum(cost_elements)
+ print(f"Total cost: ${total_cost:.2f}")
+
+ # Extract fill level targets from device state
+ fill_level_target_min = None
+ fill_level_target_max = None
+ if device_state.fill_level_target_profiles:
+ target_profile = device_state.fill_level_target_profiles[0]
+ (
+ fill_level_target_min,
+ fill_level_target_max,
+ ) = convert_fill_level_target_to_timeseries(
+ target_profile,
+ start_time,
+ timedelta(seconds=TIMESTEP_DURATION),
+ T,
+ )
+ # print("Fill level targets extracted from device state")
+ else:
+ print("No fill level targets found in device state")
+
+ # Save instructions to file
+ save_instructions_to_file(
+ device_plans_list, "_filltarget_only", start_time, device_state
+ )
+
+ # Plot the results with tariff information
+ plot_planning_results(
+ timestep_duration=timedelta(seconds=TIMESTEP_DURATION),
+ nr_of_timesteps=T,
+ predicted_energy_elements=power_profile_watts,
+ fill_level_profile=fill_level_profile,
+ fill_level_target_min=fill_level_target_min,
+ fill_level_target_max=fill_level_target_max,
+ start_time=start_time,
+ suffix="_filltarget_only",
+ tariff_elements=cost_target_elements,
+ )
+
+ # print("ITHO fill level target only test completed successfully!")
+
+ return cluster_plan
+
+
+if __name__ == "__main__":
+ # Configure logging to show DEBUG messages
+ logging.basicConfig(
+ level=logging.INFO,
+ format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
+ datefmt="%H:%M:%S",
+ )
+
+ # Optionally, set specific loggers to different levels
+ # logging.getLogger('matplotlib').setLevel(logging.WARNING)
+
+ # print("=" * 80)
+ # print("ITHO DHW Heat Pump Planning Examples with DEBUG Logging")
+ # print("=" * 80)
+ # print()
+
+ # Test 1: Energy targeting
+ # test_itho_planning_with_energy_target()
+
+ # print("\n" + "=" * 80 + "\n")
+
+ # Test 2: Constant cost (1 EUR/kWh)
+ # test_itho_planning_with_constant_cost()
+
+ # print("\n" + "=" * 80 + "\n")
+
+ # Test 3: Variable cost targeting (with both fill level target and usage forecast)
+ # test_itho_planning_with_cost_target()
+
+ # print("\n" + "=" * 80 + "\n")
+
+ # Test 4: Usage forecast ONLY (no fill level target)
+ # test_itho_planning_with_usage_forecast_only()
+
+ # print("\n" + "=" * 80 + "\n")
+
+ # Test 5: Fill level target ONLY (no usage forecast)
+ test_itho_planning_with_fill_level_target_only()
+
+ # Default: run Test 3 (variable cost with both profiles)
+ # test_itho_planning_with_cost_target()
diff --git a/flexmeasures_s2/profile_steering/examples/example_schedule_nocontrol.py b/flexmeasures_s2/profile_steering/examples/example_schedule_nocontrol.py
new file mode 100644
index 0000000..c1f5f8c
--- /dev/null
+++ b/flexmeasures_s2/profile_steering/examples/example_schedule_nocontrol.py
@@ -0,0 +1,284 @@
+from flexmeasures_s2.profile_steering.common.target_profile import TargetProfile
+from datetime import datetime, timedelta, timezone
+
+# import time
+import pandas as pd
+import os
+import uuid
+from s2python.common import PowerForecast, PowerValue, CommodityQuantity
+from s2python.common import PowerForecastElement, PowerForecastValue
+from flexmeasures_s2.profile_steering.device_planner.nocontrol.s2_nocontrol_device_state import (
+ S2NoControlDeviceState,
+)
+from flexmeasures_s2.profile_steering.common.profile_metadata import ProfileMetadata
+
+from flexmeasures_s2.scheduler.schedulers import (
+ PlanningServiceImpl,
+ PlanningServiceConfig,
+ ClusterState,
+ ClusterTarget,
+)
+import matplotlib.dates as mdates
+import matplotlib.pyplot as plt
+
+D = 3
+PLANNING_WINDOW = pd.Timedelta("PT24H")
+PLANNING_RESOLUTION = pd.Timedelta("PT5M")
+
+T = PLANNING_WINDOW // PLANNING_RESOLUTION
+TIMESTEP_DURATION = PLANNING_RESOLUTION / pd.Timedelta("PT1S")
+
+
+def create_simple_power_forecast(
+ start_time: datetime, nr_of_timesteps: int, timestep_duration: float
+) -> PowerForecast:
+ """
+ Create a simple power forecast with varying consumption.
+ This simulates a device with fixed consumption pattern (e.g., a fridge).
+
+ Args:
+ start_time: When the forecast starts
+ nr_of_timesteps: Number of timesteps
+ timestep_duration: Duration of each timestep in seconds
+
+ Returns:
+ A PowerForecast with a realistic consumption pattern
+ """
+ elements = []
+
+ for i in range(nr_of_timesteps):
+ if i % 12 == 0:
+ power_watts = 150.0
+ elif i % 12 < 3:
+ power_watts = 150.0
+ else:
+ power_watts = 0.0
+
+ power_value = PowerForecastValue(
+ value_expected=power_watts,
+ commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1,
+ )
+
+ element = PowerForecastElement(
+ duration=int(timestep_duration), power_values=[power_value]
+ )
+ elements.append(element)
+
+ return PowerForecast(
+ message_id=str(uuid.uuid4()),
+ start_time=start_time,
+ elements=elements,
+ )
+
+
+def create_nocontrol_device_state(
+ device_id: str, start_time: datetime, nr_of_timesteps: int, timestep_duration: float
+) -> S2NoControlDeviceState:
+ """
+ Create a nocontrol device state (e.g., for a non-controllable load like a fridge).
+
+ Args:
+ device_id: ID of the device
+ start_time: When the planning starts
+ nr_of_timesteps: Number of timesteps
+ timestep_duration: Duration of each timestep in seconds
+
+ Returns:
+ An S2NoControlDeviceState with a power forecast
+ """
+ power_forecast = create_simple_power_forecast(
+ start_time, nr_of_timesteps, timestep_duration
+ )
+
+ device_state = S2NoControlDeviceState(
+ device_id=device_id,
+ device_name=device_id,
+ connection_id=device_id + "_connection",
+ priority_class=1,
+ timestamp=start_time,
+ energy_in_current_timestep=PowerValue(
+ value=0, commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1
+ ),
+ is_online=True,
+ power_forecast=power_forecast,
+ )
+ return device_state
+
+
+def plot_planning_results(
+ timestep_duration,
+ nr_of_timesteps,
+ predicted_energy_elements,
+ target_energy_elements,
+):
+ """
+ Plots the energy comparison.
+
+ :param timestep_duration: Duration of each timestep.
+ :param nr_of_timesteps: Number of timesteps.
+ :param predicted_energy_elements: List of predicted energy values.
+ :param target_energy_elements: List of target energy values.
+ """
+ timestep_start_times = [
+ datetime(1970, 1, 1, tzinfo=timezone.utc)
+ + timedelta(seconds=i * timestep_duration.total_seconds())
+ for i in range(nr_of_timesteps)
+ ]
+
+ fig, ax1 = plt.subplots(1, 1, figsize=(12, 8))
+
+ ax1.plot(
+ timestep_start_times,
+ predicted_energy_elements,
+ label="Predicted Energy (NoControl Devices)",
+ color="green",
+ )
+ ax1.plot(
+ timestep_start_times,
+ target_energy_elements,
+ label="Target Energy",
+ color="red",
+ linestyle="dotted",
+ linewidth=2,
+ )
+
+ ax1.set_ylabel("Energy (Joules)")
+ ax1.set_title("Predicted vs Target Energy - NoControl Devices")
+ ax1.legend(loc="best")
+ ax1.grid(True)
+
+ ax1.xaxis.set_major_locator(mdates.MinuteLocator(interval=30))
+ ax1.xaxis.set_major_formatter(mdates.DateFormatter("%H:%M:%S"))
+ fig.autofmt_xdate()
+
+ plt.tight_layout()
+ os.makedirs("plots", exist_ok=True)
+ plt.savefig(f"plots/nocontrol_plot_D={D}_T={T}.png")
+ # print(f"Plot saved to plots/nocontrol_plot_D={D}_T={T}.png")
+
+
+def get_target_profile_elements(number_of_elements: int):
+ """Create target profile elements."""
+ target_elements = []
+ target_elements.extend([0] * 38)
+ target_elements.extend([8400000] * 62)
+ target_elements.extend([0] * 45)
+ target_elements.extend([8400000] * 28)
+ target_elements.extend([176000000] * 115)
+ return target_elements[:number_of_elements]
+
+
+def test_planning_service_impl_with_nocontrol_devices():
+ """Test the PlanningServiceImpl with nocontrol devices."""
+ # print("Testing PlanningServiceImpl with nocontrol devices")
+ # print(f"Number of devices: {D}")
+ # print(f"Number of timesteps: {T}")
+
+ target_metadata = ProfileMetadata(
+ profile_start=datetime(1970, 1, 1, tzinfo=timezone.utc),
+ timestep_duration=timedelta(seconds=TIMESTEP_DURATION),
+ nr_of_timesteps=T,
+ )
+ plan_due_by_date = target_metadata.profile_start + timedelta(seconds=10)
+ target_profile_elements = get_target_profile_elements(T)
+
+ global_target_profile = TargetProfile(
+ profile_start=target_metadata.profile_start,
+ timestep_duration=target_metadata.timestep_duration,
+ elements=target_profile_elements,
+ )
+
+ device_states = [
+ create_nocontrol_device_state(
+ f"nocontrol_device_{i + 1}",
+ target_metadata.profile_start,
+ T,
+ TIMESTEP_DURATION,
+ )
+ for i in range(D)
+ ]
+
+ device_states_dict = {
+ device_state.device_id: device_state for device_state in device_states
+ }
+
+ congestion_points_by_connection_id = {
+ device_state.connection_id: "" for device_state in device_states
+ }
+
+ cluster_state = ClusterState(
+ datetime.now(), device_states_dict, congestion_points_by_connection_id
+ )
+
+ congestion_point_targets = {}
+ cluster_target = ClusterTarget(
+ datetime.now(),
+ None,
+ None,
+ global_target_profile=global_target_profile,
+ congestion_point_targets=congestion_point_targets,
+ )
+
+ config = PlanningServiceConfig(
+ energy_improvement_criterion=10.0,
+ cost_improvement_criterion=1.0,
+ congestion_retry_iterations=10,
+ multithreaded=False,
+ )
+
+ # print("Generating plan!")
+
+ service = PlanningServiceImpl(config)
+
+ # start_time = time.time()
+ cluster_plan = service.plan(
+ state=cluster_state,
+ target=cluster_target,
+ planning_window=TIMESTEP_DURATION * T,
+ reason="Testing NoControl planning",
+ plan_due_by_date=plan_due_by_date,
+ optimize_for_target=True,
+ max_priority_class=1,
+ )
+ # end_time = time.time()
+ # execution_time = end_time - start_time
+
+ # print(f"Plan generated in {execution_time:.2f} seconds")
+
+ assert cluster_plan is not None
+ # print("Got cluster plan")
+
+ if cluster_plan is None:
+ # print("Cluster plan is None")
+ return
+
+ device_plans = cluster_plan.get_plan_data().get_device_plans()
+ energy_profile = cluster_plan.get_joule_profile()
+
+ plot_planning_results(
+ timestep_duration=timedelta(seconds=TIMESTEP_DURATION),
+ nr_of_timesteps=T,
+ predicted_energy_elements=energy_profile.elements,
+ target_energy_elements=target_profile_elements,
+ )
+
+ device_plans = [plan for plan in device_plans if plan is not None]
+
+ assert len(device_plans) > 0
+ # print(f"Got {len(device_plans)} device plans")
+
+ for device_plan in device_plans:
+ if device_plan:
+ # print(f"Device {device_plan.device_id}:")
+ # print(
+ # f" Total energy: {sum(e for e in device_plan.energy_profile.elements if e is not None)} Joules"
+ # )
+ # print(f" Instruction profile: {device_plan.instruction_profile}")
+ pass
+
+ assert len(energy_profile.elements) == target_metadata.nr_of_timesteps
+ # print("Test completed successfully!")
+
+
+if __name__ == "__main__":
+ test_planning_service_impl_with_nocontrol_devices()
diff --git a/flexmeasures_s2/profile_steering/examples/instructions/itho_instructions_constant_cost_D=1_B=200_S=30_T=288.txt b/flexmeasures_s2/profile_steering/examples/instructions/itho_instructions_constant_cost_D=1_B=200_S=30_T=288.txt
new file mode 100644
index 0000000..dc24305
--- /dev/null
+++ b/flexmeasures_s2/profile_steering/examples/instructions/itho_instructions_constant_cost_D=1_B=200_S=30_T=288.txt
@@ -0,0 +1,3208 @@
+================================================================================
+ITHO DHW Heat Pump Instructions Constant Cost
+================================================================================
+Planning start time: 2025-11-03 10:30:00+00:00
+Planning window: 1 days 00:00:00
+Resolution: 0 days 00:05:00
+Total timesteps: 288
+================================================================================
+
+Device: itho_dhw_device_002
+Number of instructions: 288
+--------------------------------------------------------------------------------
+
+Instruction Timeline:
+--------------------------------------------------------------------------------
+ Mode: DHW_ON
+ Start: 2025-11-03 10:30:00+00:00 (timestep 0)
+ Duration: 17 timesteps (85.0 minutes)
+ Operation mode factor: 0.0
+----------------------------------------
+ Mode: DHW_OFF
+ Start: 2025-11-03 11:55:00+00:00 (timestep 17)
+ Duration: 8 timesteps (40.0 minutes)
+ Operation mode factor: 0.0
+----------------------------------------
+ Mode: DHW_ON
+ Start: 2025-11-03 12:35:00+00:00 (timestep 25)
+ Duration: 6 timesteps (30.0 minutes)
+ Operation mode factor: 0.0
+----------------------------------------
+ Mode: DHW_OFF
+ Start: 2025-11-03 13:05:00+00:00 (timestep 31)
+ Duration: 257 timesteps (1285.0 minutes)
+----------------------------------------
+
+Total operation mode changes: 3
+================================================================================
+
+Detailed Instruction List:
+================================================================================
+
+Instruction #1:
+ Timestep: 0
+ Time: 2025-11-03 10:30:00+00:00
+ ID: 83c7f07d-5297-45e6-ac4f-89ef120dc6f0
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 10:30:00+00:00
+----------------------------------------
+
+Instruction #2:
+ Timestep: 1
+ Time: 2025-11-03 10:35:00+00:00
+ ID: 7bf4e491-4b12-4832-9ca1-f7d891eb1740
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 10:35:00+00:00
+----------------------------------------
+
+Instruction #3:
+ Timestep: 2
+ Time: 2025-11-03 10:40:00+00:00
+ ID: b14bd3e5-3b10-45e2-a82c-b116d1e8e3fd
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 10:40:00+00:00
+----------------------------------------
+
+Instruction #4:
+ Timestep: 3
+ Time: 2025-11-03 10:45:00+00:00
+ ID: 3af14ed7-54c7-4c44-9f7d-97d48890f7b7
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 10:45:00+00:00
+----------------------------------------
+
+Instruction #5:
+ Timestep: 4
+ Time: 2025-11-03 10:50:00+00:00
+ ID: f3eb8014-5053-4e4f-826f-ddd8fa10f84d
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 10:50:00+00:00
+----------------------------------------
+
+Instruction #6:
+ Timestep: 5
+ Time: 2025-11-03 10:55:00+00:00
+ ID: 1722d79e-5182-4313-8127-090dc2f70dcb
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 10:55:00+00:00
+----------------------------------------
+
+Instruction #7:
+ Timestep: 6
+ Time: 2025-11-03 11:00:00+00:00
+ ID: 6cbc4461-c952-4a46-9a97-13c3eaa7ea34
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 11:00:00+00:00
+----------------------------------------
+
+Instruction #8:
+ Timestep: 7
+ Time: 2025-11-03 11:05:00+00:00
+ ID: 137dbfb8-c8cc-492c-ab90-697eebc4a38d
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 11:05:00+00:00
+----------------------------------------
+
+Instruction #9:
+ Timestep: 8
+ Time: 2025-11-03 11:10:00+00:00
+ ID: 37231f02-26be-41b0-82ec-34f598c99a5b
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 11:10:00+00:00
+----------------------------------------
+
+Instruction #10:
+ Timestep: 9
+ Time: 2025-11-03 11:15:00+00:00
+ ID: 74f23dc3-a418-435d-94fc-3a7f3201f43f
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 11:15:00+00:00
+----------------------------------------
+
+Instruction #11:
+ Timestep: 10
+ Time: 2025-11-03 11:20:00+00:00
+ ID: bdae9b40-e19c-459c-8082-e125267772a8
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 11:20:00+00:00
+----------------------------------------
+
+Instruction #12:
+ Timestep: 11
+ Time: 2025-11-03 11:25:00+00:00
+ ID: 864f7e87-98cf-4010-a75c-aef9a20f7cab
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 11:25:00+00:00
+----------------------------------------
+
+Instruction #13:
+ Timestep: 12
+ Time: 2025-11-03 11:30:00+00:00
+ ID: a1381b07-3039-43a2-bc2f-83e5d31b8b06
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 11:30:00+00:00
+----------------------------------------
+
+Instruction #14:
+ Timestep: 13
+ Time: 2025-11-03 11:35:00+00:00
+ ID: 87ed4010-8bbe-4bfa-81fc-e65e06a4f0ab
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 11:35:00+00:00
+----------------------------------------
+
+Instruction #15:
+ Timestep: 14
+ Time: 2025-11-03 11:40:00+00:00
+ ID: d369e94e-9b3a-4039-bc6c-9d52c31eb16b
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 11:40:00+00:00
+----------------------------------------
+
+Instruction #16:
+ Timestep: 15
+ Time: 2025-11-03 11:45:00+00:00
+ ID: 4fd54730-4822-461b-a497-ad38c3dcac11
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 11:45:00+00:00
+----------------------------------------
+
+Instruction #17:
+ Timestep: 16
+ Time: 2025-11-03 11:50:00+00:00
+ ID: cbbbaeb3-8263-44ea-aea9-b2087c59ac30
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 11:50:00+00:00
+----------------------------------------
+
+Instruction #18:
+ Timestep: 17
+ Time: 2025-11-03 11:55:00+00:00
+ ID: f15d55f8-6ab6-48d4-b47b-c33e5d608a7a
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 11:55:00+00:00
+----------------------------------------
+
+Instruction #19:
+ Timestep: 18
+ Time: 2025-11-03 12:00:00+00:00
+ ID: 7af9d1f5-69cf-471a-82e5-ac154b55d917
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 12:00:00+00:00
+----------------------------------------
+
+Instruction #20:
+ Timestep: 19
+ Time: 2025-11-03 12:05:00+00:00
+ ID: 3a346742-be03-4b61-8574-ab8f27f611ef
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 12:05:00+00:00
+----------------------------------------
+
+Instruction #21:
+ Timestep: 20
+ Time: 2025-11-03 12:10:00+00:00
+ ID: c8f5a56f-311c-487a-b999-9da111e0c397
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 12:10:00+00:00
+----------------------------------------
+
+Instruction #22:
+ Timestep: 21
+ Time: 2025-11-03 12:15:00+00:00
+ ID: a85db965-9c0a-4ecf-9213-5e3961d536f6
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 12:15:00+00:00
+----------------------------------------
+
+Instruction #23:
+ Timestep: 22
+ Time: 2025-11-03 12:20:00+00:00
+ ID: f2de2882-53f3-4e60-9e23-4d3808dc70b8
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 12:20:00+00:00
+----------------------------------------
+
+Instruction #24:
+ Timestep: 23
+ Time: 2025-11-03 12:25:00+00:00
+ ID: 34cc7850-c214-4fa3-92db-79cb6ce38e5e
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 12:25:00+00:00
+----------------------------------------
+
+Instruction #25:
+ Timestep: 24
+ Time: 2025-11-03 12:30:00+00:00
+ ID: 7cfd98d6-853f-4791-ae13-cbb850392345
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 12:30:00+00:00
+----------------------------------------
+
+Instruction #26:
+ Timestep: 25
+ Time: 2025-11-03 12:35:00+00:00
+ ID: fcee59fb-a64a-415e-b6a0-1585d00cdcfc
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 12:35:00+00:00
+----------------------------------------
+
+Instruction #27:
+ Timestep: 26
+ Time: 2025-11-03 12:40:00+00:00
+ ID: f5e2d55a-cad8-4f95-8b32-9606086dbfb4
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 12:40:00+00:00
+----------------------------------------
+
+Instruction #28:
+ Timestep: 27
+ Time: 2025-11-03 12:45:00+00:00
+ ID: 1332ec39-5191-44d8-a035-89a89bc98516
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 12:45:00+00:00
+----------------------------------------
+
+Instruction #29:
+ Timestep: 28
+ Time: 2025-11-03 12:50:00+00:00
+ ID: 74d40833-83b1-4feb-9562-7f46d6ee1854
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 12:50:00+00:00
+----------------------------------------
+
+Instruction #30:
+ Timestep: 29
+ Time: 2025-11-03 12:55:00+00:00
+ ID: fd7e611a-d136-455b-8621-4c63e5334d17
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 12:55:00+00:00
+----------------------------------------
+
+Instruction #31:
+ Timestep: 30
+ Time: 2025-11-03 13:00:00+00:00
+ ID: d7f67bc0-c776-450a-a65b-d642cd1ab4ec
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 13:00:00+00:00
+----------------------------------------
+
+Instruction #32:
+ Timestep: 31
+ Time: 2025-11-03 13:05:00+00:00
+ ID: eae05c52-4cbe-4d2f-a79a-6885cd311440
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 13:05:00+00:00
+----------------------------------------
+
+Instruction #33:
+ Timestep: 32
+ Time: 2025-11-03 13:10:00+00:00
+ ID: 442e99d1-f90c-4113-9332-fd9b2c0b2cbd
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 13:10:00+00:00
+----------------------------------------
+
+Instruction #34:
+ Timestep: 33
+ Time: 2025-11-03 13:15:00+00:00
+ ID: 0822174c-329d-427a-b412-dc56084f316a
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 13:15:00+00:00
+----------------------------------------
+
+Instruction #35:
+ Timestep: 34
+ Time: 2025-11-03 13:20:00+00:00
+ ID: 8f30079a-9d23-4ebe-93fa-dec91e10ef92
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 13:20:00+00:00
+----------------------------------------
+
+Instruction #36:
+ Timestep: 35
+ Time: 2025-11-03 13:25:00+00:00
+ ID: 1e7d53ff-88a1-4a23-9d30-1baa60daab35
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 13:25:00+00:00
+----------------------------------------
+
+Instruction #37:
+ Timestep: 36
+ Time: 2025-11-03 13:30:00+00:00
+ ID: b8b2f438-9b2f-4fd4-b92e-00f246d8cfc6
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 13:30:00+00:00
+----------------------------------------
+
+Instruction #38:
+ Timestep: 37
+ Time: 2025-11-03 13:35:00+00:00
+ ID: a95e5d7b-7b6b-49ca-950e-9b2778e2cf15
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 13:35:00+00:00
+----------------------------------------
+
+Instruction #39:
+ Timestep: 38
+ Time: 2025-11-03 13:40:00+00:00
+ ID: 11f6b222-e637-4b23-a713-9f967ddcb340
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 13:40:00+00:00
+----------------------------------------
+
+Instruction #40:
+ Timestep: 39
+ Time: 2025-11-03 13:45:00+00:00
+ ID: 4b8c562e-83cd-42b3-9207-d50dc0f0b1d5
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 13:45:00+00:00
+----------------------------------------
+
+Instruction #41:
+ Timestep: 40
+ Time: 2025-11-03 13:50:00+00:00
+ ID: 970a0edc-ca34-4249-bdca-85c652dd44bd
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 13:50:00+00:00
+----------------------------------------
+
+Instruction #42:
+ Timestep: 41
+ Time: 2025-11-03 13:55:00+00:00
+ ID: 9a87dced-fd9b-42d3-84ad-0f35549ae2de
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 13:55:00+00:00
+----------------------------------------
+
+Instruction #43:
+ Timestep: 42
+ Time: 2025-11-03 14:00:00+00:00
+ ID: a17b1089-3400-475f-aef3-b34b8dbc0c3d
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 14:00:00+00:00
+----------------------------------------
+
+Instruction #44:
+ Timestep: 43
+ Time: 2025-11-03 14:05:00+00:00
+ ID: d07fff16-5255-4ae1-afe2-790898ed484e
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 14:05:00+00:00
+----------------------------------------
+
+Instruction #45:
+ Timestep: 44
+ Time: 2025-11-03 14:10:00+00:00
+ ID: 4dbb7124-6e64-469c-9675-7881af252e2e
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 14:10:00+00:00
+----------------------------------------
+
+Instruction #46:
+ Timestep: 45
+ Time: 2025-11-03 14:15:00+00:00
+ ID: a983c6d7-fd56-43bc-958d-dd24594d18ec
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 14:15:00+00:00
+----------------------------------------
+
+Instruction #47:
+ Timestep: 46
+ Time: 2025-11-03 14:20:00+00:00
+ ID: 474ce289-8d05-495c-baeb-02dafc665fe5
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 14:20:00+00:00
+----------------------------------------
+
+Instruction #48:
+ Timestep: 47
+ Time: 2025-11-03 14:25:00+00:00
+ ID: cdf451da-e33d-4096-8775-143ce9f2d094
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 14:25:00+00:00
+----------------------------------------
+
+Instruction #49:
+ Timestep: 48
+ Time: 2025-11-03 14:30:00+00:00
+ ID: e5c04030-10bf-45dc-a0c3-4432e85838a5
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 14:30:00+00:00
+----------------------------------------
+
+Instruction #50:
+ Timestep: 49
+ Time: 2025-11-03 14:35:00+00:00
+ ID: 60b65d4f-c258-4551-b76a-fc6958c3fd95
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 14:35:00+00:00
+----------------------------------------
+
+Instruction #51:
+ Timestep: 50
+ Time: 2025-11-03 14:40:00+00:00
+ ID: 4e3a04a0-4970-4a8a-9797-2495bfd6a2be
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 14:40:00+00:00
+----------------------------------------
+
+Instruction #52:
+ Timestep: 51
+ Time: 2025-11-03 14:45:00+00:00
+ ID: 7fc23408-b358-435d-b9f8-699a0cc35753
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 14:45:00+00:00
+----------------------------------------
+
+Instruction #53:
+ Timestep: 52
+ Time: 2025-11-03 14:50:00+00:00
+ ID: 3a16c689-4737-464d-9a00-18d8eb52164a
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 14:50:00+00:00
+----------------------------------------
+
+Instruction #54:
+ Timestep: 53
+ Time: 2025-11-03 14:55:00+00:00
+ ID: ddadc349-8180-473f-b96a-49a2a7541588
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 14:55:00+00:00
+----------------------------------------
+
+Instruction #55:
+ Timestep: 54
+ Time: 2025-11-03 15:00:00+00:00
+ ID: e813ab6d-f0e1-4c1b-90fe-006857acd3bb
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 15:00:00+00:00
+----------------------------------------
+
+Instruction #56:
+ Timestep: 55
+ Time: 2025-11-03 15:05:00+00:00
+ ID: add4e16b-869a-40e1-ad6b-4ba21a683753
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 15:05:00+00:00
+----------------------------------------
+
+Instruction #57:
+ Timestep: 56
+ Time: 2025-11-03 15:10:00+00:00
+ ID: a1fc6722-afed-4894-9dda-15f45a724a16
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 15:10:00+00:00
+----------------------------------------
+
+Instruction #58:
+ Timestep: 57
+ Time: 2025-11-03 15:15:00+00:00
+ ID: 6ce9dba6-e9f6-4b02-b6e7-81f8261ad751
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 15:15:00+00:00
+----------------------------------------
+
+Instruction #59:
+ Timestep: 58
+ Time: 2025-11-03 15:20:00+00:00
+ ID: c8c68d5c-4699-4573-99c9-4a45f3af424d
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 15:20:00+00:00
+----------------------------------------
+
+Instruction #60:
+ Timestep: 59
+ Time: 2025-11-03 15:25:00+00:00
+ ID: ab1e2a90-c26b-4fd6-bc66-d50ca954a24c
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 15:25:00+00:00
+----------------------------------------
+
+Instruction #61:
+ Timestep: 60
+ Time: 2025-11-03 15:30:00+00:00
+ ID: 0c53fc4c-449e-4262-bd79-82bb5c1c030c
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 15:30:00+00:00
+----------------------------------------
+
+Instruction #62:
+ Timestep: 61
+ Time: 2025-11-03 15:35:00+00:00
+ ID: abe1f899-f6e7-4f15-88ad-65bd79cca7a3
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 15:35:00+00:00
+----------------------------------------
+
+Instruction #63:
+ Timestep: 62
+ Time: 2025-11-03 15:40:00+00:00
+ ID: bd166c11-0fa6-4927-b40c-f6931d03761f
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 15:40:00+00:00
+----------------------------------------
+
+Instruction #64:
+ Timestep: 63
+ Time: 2025-11-03 15:45:00+00:00
+ ID: a0a885b6-e8fa-4880-b289-e13ac8d4889c
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 15:45:00+00:00
+----------------------------------------
+
+Instruction #65:
+ Timestep: 64
+ Time: 2025-11-03 15:50:00+00:00
+ ID: ebf4f2b6-6373-4e43-ae12-c9f27aca4249
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 15:50:00+00:00
+----------------------------------------
+
+Instruction #66:
+ Timestep: 65
+ Time: 2025-11-03 15:55:00+00:00
+ ID: 2169d220-1202-4401-b39e-405b2514ad48
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 15:55:00+00:00
+----------------------------------------
+
+Instruction #67:
+ Timestep: 66
+ Time: 2025-11-03 16:00:00+00:00
+ ID: 7f6ec097-050a-4214-9b8d-a91aabbc8ba7
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 16:00:00+00:00
+----------------------------------------
+
+Instruction #68:
+ Timestep: 67
+ Time: 2025-11-03 16:05:00+00:00
+ ID: e3b54f38-34b7-479a-a74b-8ac79c1a5479
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 16:05:00+00:00
+----------------------------------------
+
+Instruction #69:
+ Timestep: 68
+ Time: 2025-11-03 16:10:00+00:00
+ ID: 9b4dcb89-5549-4770-889e-fe6c06f659d9
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 16:10:00+00:00
+----------------------------------------
+
+Instruction #70:
+ Timestep: 69
+ Time: 2025-11-03 16:15:00+00:00
+ ID: e07ceae7-d7d8-4bec-b04a-2f9ac879eace
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 16:15:00+00:00
+----------------------------------------
+
+Instruction #71:
+ Timestep: 70
+ Time: 2025-11-03 16:20:00+00:00
+ ID: 540477f4-bcd1-4810-baa3-7129af7873a0
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 16:20:00+00:00
+----------------------------------------
+
+Instruction #72:
+ Timestep: 71
+ Time: 2025-11-03 16:25:00+00:00
+ ID: 189cab4d-66dc-4c3e-a966-b2981d524df6
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 16:25:00+00:00
+----------------------------------------
+
+Instruction #73:
+ Timestep: 72
+ Time: 2025-11-03 16:30:00+00:00
+ ID: 386b81fb-7158-496c-b4c0-e47f2658eb61
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 16:30:00+00:00
+----------------------------------------
+
+Instruction #74:
+ Timestep: 73
+ Time: 2025-11-03 16:35:00+00:00
+ ID: 613fa089-9130-433e-9ccc-d3d7f6de36b4
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 16:35:00+00:00
+----------------------------------------
+
+Instruction #75:
+ Timestep: 74
+ Time: 2025-11-03 16:40:00+00:00
+ ID: 26ef0fbf-6115-4988-9905-7e1fefbc12f0
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 16:40:00+00:00
+----------------------------------------
+
+Instruction #76:
+ Timestep: 75
+ Time: 2025-11-03 16:45:00+00:00
+ ID: e337fcb0-8396-4591-a78a-e6241bafeb94
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 16:45:00+00:00
+----------------------------------------
+
+Instruction #77:
+ Timestep: 76
+ Time: 2025-11-03 16:50:00+00:00
+ ID: 45813c8c-8f5a-4392-8cb0-f81c1f3021ef
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 16:50:00+00:00
+----------------------------------------
+
+Instruction #78:
+ Timestep: 77
+ Time: 2025-11-03 16:55:00+00:00
+ ID: 4feb4a15-1bab-4733-bfb9-00283bc82f05
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 16:55:00+00:00
+----------------------------------------
+
+Instruction #79:
+ Timestep: 78
+ Time: 2025-11-03 17:00:00+00:00
+ ID: 9dffc33e-3657-44db-a207-6206de2f2bd2
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 17:00:00+00:00
+----------------------------------------
+
+Instruction #80:
+ Timestep: 79
+ Time: 2025-11-03 17:05:00+00:00
+ ID: e7ecfe61-2db3-43b5-8129-232d17fa3db6
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 17:05:00+00:00
+----------------------------------------
+
+Instruction #81:
+ Timestep: 80
+ Time: 2025-11-03 17:10:00+00:00
+ ID: fd4798db-d529-4807-b91b-b8661d3d19d8
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 17:10:00+00:00
+----------------------------------------
+
+Instruction #82:
+ Timestep: 81
+ Time: 2025-11-03 17:15:00+00:00
+ ID: d8736cfe-ac5f-4fa2-a9fc-1b3b1afbcb20
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 17:15:00+00:00
+----------------------------------------
+
+Instruction #83:
+ Timestep: 82
+ Time: 2025-11-03 17:20:00+00:00
+ ID: 9950db1b-6606-48e9-a50f-bcd53e44aae7
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 17:20:00+00:00
+----------------------------------------
+
+Instruction #84:
+ Timestep: 83
+ Time: 2025-11-03 17:25:00+00:00
+ ID: 6e89d142-9bad-4757-bb19-a0de195a7613
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 17:25:00+00:00
+----------------------------------------
+
+Instruction #85:
+ Timestep: 84
+ Time: 2025-11-03 17:30:00+00:00
+ ID: cd88ea58-f189-46ba-98f6-1a39e1cbb857
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 17:30:00+00:00
+----------------------------------------
+
+Instruction #86:
+ Timestep: 85
+ Time: 2025-11-03 17:35:00+00:00
+ ID: 7391d156-4de9-417f-904d-30cdb549a6e7
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 17:35:00+00:00
+----------------------------------------
+
+Instruction #87:
+ Timestep: 86
+ Time: 2025-11-03 17:40:00+00:00
+ ID: c7e1ce27-63d3-4c4d-b2fb-a56a904c1ca2
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 17:40:00+00:00
+----------------------------------------
+
+Instruction #88:
+ Timestep: 87
+ Time: 2025-11-03 17:45:00+00:00
+ ID: 317c2336-c10a-48dc-8a37-af91641cf73a
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 17:45:00+00:00
+----------------------------------------
+
+Instruction #89:
+ Timestep: 88
+ Time: 2025-11-03 17:50:00+00:00
+ ID: 8d521331-c83c-444d-af40-16d0266d13e3
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 17:50:00+00:00
+----------------------------------------
+
+Instruction #90:
+ Timestep: 89
+ Time: 2025-11-03 17:55:00+00:00
+ ID: 5a798515-c141-4275-88e2-90bc05f78628
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 17:55:00+00:00
+----------------------------------------
+
+Instruction #91:
+ Timestep: 90
+ Time: 2025-11-03 18:00:00+00:00
+ ID: b2050394-22d9-442e-bbe6-e804d7922fe1
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 18:00:00+00:00
+----------------------------------------
+
+Instruction #92:
+ Timestep: 91
+ Time: 2025-11-03 18:05:00+00:00
+ ID: 7aaa12e9-b4c2-4f5e-a7b6-926dd960d343
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 18:05:00+00:00
+----------------------------------------
+
+Instruction #93:
+ Timestep: 92
+ Time: 2025-11-03 18:10:00+00:00
+ ID: 525a69e6-d3bd-465a-8431-0f733e7f6358
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 18:10:00+00:00
+----------------------------------------
+
+Instruction #94:
+ Timestep: 93
+ Time: 2025-11-03 18:15:00+00:00
+ ID: 9f54ef57-26dc-4cce-9255-7e9264cf7ab0
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 18:15:00+00:00
+----------------------------------------
+
+Instruction #95:
+ Timestep: 94
+ Time: 2025-11-03 18:20:00+00:00
+ ID: 5bc2a76b-145c-4ce7-bf0e-e87cfdfddf9d
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 18:20:00+00:00
+----------------------------------------
+
+Instruction #96:
+ Timestep: 95
+ Time: 2025-11-03 18:25:00+00:00
+ ID: d1089c9d-f1bd-489d-a2c0-87ea26b9c718
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 18:25:00+00:00
+----------------------------------------
+
+Instruction #97:
+ Timestep: 96
+ Time: 2025-11-03 18:30:00+00:00
+ ID: c6a82451-7f0b-49dd-8437-d0f2dc998ba1
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 18:30:00+00:00
+----------------------------------------
+
+Instruction #98:
+ Timestep: 97
+ Time: 2025-11-03 18:35:00+00:00
+ ID: 5cc09405-1c93-4d9e-a3f3-7491ae737907
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 18:35:00+00:00
+----------------------------------------
+
+Instruction #99:
+ Timestep: 98
+ Time: 2025-11-03 18:40:00+00:00
+ ID: bf1e77c6-e766-4db0-bd92-fab00a9d2446
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 18:40:00+00:00
+----------------------------------------
+
+Instruction #100:
+ Timestep: 99
+ Time: 2025-11-03 18:45:00+00:00
+ ID: 212f5bd4-d9eb-4753-b6ca-544942ea7d15
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 18:45:00+00:00
+----------------------------------------
+
+Instruction #101:
+ Timestep: 100
+ Time: 2025-11-03 18:50:00+00:00
+ ID: a0259ce6-3721-4c18-89a6-4a56e2111a09
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 18:50:00+00:00
+----------------------------------------
+
+Instruction #102:
+ Timestep: 101
+ Time: 2025-11-03 18:55:00+00:00
+ ID: 9e7bcacb-3cae-4939-86fa-34ff677d2acc
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 18:55:00+00:00
+----------------------------------------
+
+Instruction #103:
+ Timestep: 102
+ Time: 2025-11-03 19:00:00+00:00
+ ID: bc561828-249a-4b8d-8a90-e6ecfcb37f15
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 19:00:00+00:00
+----------------------------------------
+
+Instruction #104:
+ Timestep: 103
+ Time: 2025-11-03 19:05:00+00:00
+ ID: 353b4419-9a94-404e-854e-87fecfc36792
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 19:05:00+00:00
+----------------------------------------
+
+Instruction #105:
+ Timestep: 104
+ Time: 2025-11-03 19:10:00+00:00
+ ID: 5a22cd01-d115-48d6-b04a-11925c86efd0
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 19:10:00+00:00
+----------------------------------------
+
+Instruction #106:
+ Timestep: 105
+ Time: 2025-11-03 19:15:00+00:00
+ ID: 3eaed500-60ac-476a-bc52-71ba1e8373e5
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 19:15:00+00:00
+----------------------------------------
+
+Instruction #107:
+ Timestep: 106
+ Time: 2025-11-03 19:20:00+00:00
+ ID: 30ad03f0-d9cb-46bc-be03-f9f8b7283fe0
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 19:20:00+00:00
+----------------------------------------
+
+Instruction #108:
+ Timestep: 107
+ Time: 2025-11-03 19:25:00+00:00
+ ID: a35bd162-8519-4272-9f33-9969c81434c4
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 19:25:00+00:00
+----------------------------------------
+
+Instruction #109:
+ Timestep: 108
+ Time: 2025-11-03 19:30:00+00:00
+ ID: e4488d8c-5fc4-409e-918a-e482aac9da55
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 19:30:00+00:00
+----------------------------------------
+
+Instruction #110:
+ Timestep: 109
+ Time: 2025-11-03 19:35:00+00:00
+ ID: 576d658d-7312-46d5-bdc0-98cf2f639d63
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 19:35:00+00:00
+----------------------------------------
+
+Instruction #111:
+ Timestep: 110
+ Time: 2025-11-03 19:40:00+00:00
+ ID: 790f4410-6360-4abd-b42c-918f985d11ae
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 19:40:00+00:00
+----------------------------------------
+
+Instruction #112:
+ Timestep: 111
+ Time: 2025-11-03 19:45:00+00:00
+ ID: b6cecd12-30c1-43f0-81ad-2a6d57b63d1c
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 19:45:00+00:00
+----------------------------------------
+
+Instruction #113:
+ Timestep: 112
+ Time: 2025-11-03 19:50:00+00:00
+ ID: 97da666e-1696-4e1c-855a-a36ec69bc2e9
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 19:50:00+00:00
+----------------------------------------
+
+Instruction #114:
+ Timestep: 113
+ Time: 2025-11-03 19:55:00+00:00
+ ID: fc49dfd8-59a0-4444-8620-c0ab93a60d5c
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 19:55:00+00:00
+----------------------------------------
+
+Instruction #115:
+ Timestep: 114
+ Time: 2025-11-03 20:00:00+00:00
+ ID: 8b744c8c-2d94-4c8e-ae26-aba4bbeb71e3
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 20:00:00+00:00
+----------------------------------------
+
+Instruction #116:
+ Timestep: 115
+ Time: 2025-11-03 20:05:00+00:00
+ ID: cc208e13-d557-451a-a926-1bd73456b693
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 20:05:00+00:00
+----------------------------------------
+
+Instruction #117:
+ Timestep: 116
+ Time: 2025-11-03 20:10:00+00:00
+ ID: f46633b2-9f8d-43a5-85a1-f5e09f39a924
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 20:10:00+00:00
+----------------------------------------
+
+Instruction #118:
+ Timestep: 117
+ Time: 2025-11-03 20:15:00+00:00
+ ID: 4d6abbd0-205f-4427-bf69-42942752575c
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 20:15:00+00:00
+----------------------------------------
+
+Instruction #119:
+ Timestep: 118
+ Time: 2025-11-03 20:20:00+00:00
+ ID: 842f6ed7-3bc3-46ed-afcc-9b6d7e6d0236
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 20:20:00+00:00
+----------------------------------------
+
+Instruction #120:
+ Timestep: 119
+ Time: 2025-11-03 20:25:00+00:00
+ ID: e6ad36a7-292a-4a5a-adda-45ca072e7996
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 20:25:00+00:00
+----------------------------------------
+
+Instruction #121:
+ Timestep: 120
+ Time: 2025-11-03 20:30:00+00:00
+ ID: cbe9e800-862d-432e-8ed7-f275426b3233
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 20:30:00+00:00
+----------------------------------------
+
+Instruction #122:
+ Timestep: 121
+ Time: 2025-11-03 20:35:00+00:00
+ ID: 96aab91d-2300-4ad3-b1ea-e7c312015ca7
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 20:35:00+00:00
+----------------------------------------
+
+Instruction #123:
+ Timestep: 122
+ Time: 2025-11-03 20:40:00+00:00
+ ID: c95bb76f-c2ca-4f70-88b8-a29170f7fbb6
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 20:40:00+00:00
+----------------------------------------
+
+Instruction #124:
+ Timestep: 123
+ Time: 2025-11-03 20:45:00+00:00
+ ID: 47a66504-c08e-4d2b-9a43-8f5eceeea4bc
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 20:45:00+00:00
+----------------------------------------
+
+Instruction #125:
+ Timestep: 124
+ Time: 2025-11-03 20:50:00+00:00
+ ID: cccbf585-47e3-4559-9e96-bf192eb1479f
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 20:50:00+00:00
+----------------------------------------
+
+Instruction #126:
+ Timestep: 125
+ Time: 2025-11-03 20:55:00+00:00
+ ID: 4b15c41a-088c-440a-bdf6-d088e0f0eb54
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 20:55:00+00:00
+----------------------------------------
+
+Instruction #127:
+ Timestep: 126
+ Time: 2025-11-03 21:00:00+00:00
+ ID: 95c148cf-ab46-4589-a196-60f64f54c0b1
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 21:00:00+00:00
+----------------------------------------
+
+Instruction #128:
+ Timestep: 127
+ Time: 2025-11-03 21:05:00+00:00
+ ID: c32f5101-0e46-4e3b-bb0d-818d07ce6e1b
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 21:05:00+00:00
+----------------------------------------
+
+Instruction #129:
+ Timestep: 128
+ Time: 2025-11-03 21:10:00+00:00
+ ID: ea2c3149-5a60-4a30-b07a-161f0aceaf45
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 21:10:00+00:00
+----------------------------------------
+
+Instruction #130:
+ Timestep: 129
+ Time: 2025-11-03 21:15:00+00:00
+ ID: 95785c76-c8fe-4a57-bb1d-7427a02a056f
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 21:15:00+00:00
+----------------------------------------
+
+Instruction #131:
+ Timestep: 130
+ Time: 2025-11-03 21:20:00+00:00
+ ID: 43157851-da51-4c01-9e85-61223aae14c1
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 21:20:00+00:00
+----------------------------------------
+
+Instruction #132:
+ Timestep: 131
+ Time: 2025-11-03 21:25:00+00:00
+ ID: 137a7ac5-e9c8-4e33-a761-2a3a82804348
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 21:25:00+00:00
+----------------------------------------
+
+Instruction #133:
+ Timestep: 132
+ Time: 2025-11-03 21:30:00+00:00
+ ID: 59b9a783-4a16-4324-b077-bf9a5cecb5a1
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 21:30:00+00:00
+----------------------------------------
+
+Instruction #134:
+ Timestep: 133
+ Time: 2025-11-03 21:35:00+00:00
+ ID: 25aed623-4e39-4118-b7a4-751a60966992
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 21:35:00+00:00
+----------------------------------------
+
+Instruction #135:
+ Timestep: 134
+ Time: 2025-11-03 21:40:00+00:00
+ ID: e00adfe8-52d5-48ce-9a4f-f8f944f4d322
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 21:40:00+00:00
+----------------------------------------
+
+Instruction #136:
+ Timestep: 135
+ Time: 2025-11-03 21:45:00+00:00
+ ID: e1e030f2-7734-4aa8-b4af-135926048031
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 21:45:00+00:00
+----------------------------------------
+
+Instruction #137:
+ Timestep: 136
+ Time: 2025-11-03 21:50:00+00:00
+ ID: a7fd38f3-1c44-47ab-adba-a2b2cee2e9b9
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 21:50:00+00:00
+----------------------------------------
+
+Instruction #138:
+ Timestep: 137
+ Time: 2025-11-03 21:55:00+00:00
+ ID: 1cd985a3-3621-409e-8134-8c5ea5204920
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 21:55:00+00:00
+----------------------------------------
+
+Instruction #139:
+ Timestep: 138
+ Time: 2025-11-03 22:00:00+00:00
+ ID: e2f86b59-953f-4cde-98c8-913ef963d654
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 22:00:00+00:00
+----------------------------------------
+
+Instruction #140:
+ Timestep: 139
+ Time: 2025-11-03 22:05:00+00:00
+ ID: f4cd9864-3b6a-4380-95db-a7f90162a117
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 22:05:00+00:00
+----------------------------------------
+
+Instruction #141:
+ Timestep: 140
+ Time: 2025-11-03 22:10:00+00:00
+ ID: c0ab79ff-7420-4244-879d-d3167bf9b093
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 22:10:00+00:00
+----------------------------------------
+
+Instruction #142:
+ Timestep: 141
+ Time: 2025-11-03 22:15:00+00:00
+ ID: cd22ea01-4d31-4f22-b451-01afc28305cc
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 22:15:00+00:00
+----------------------------------------
+
+Instruction #143:
+ Timestep: 142
+ Time: 2025-11-03 22:20:00+00:00
+ ID: 15374a18-11df-484a-b048-09bf148ebe68
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 22:20:00+00:00
+----------------------------------------
+
+Instruction #144:
+ Timestep: 143
+ Time: 2025-11-03 22:25:00+00:00
+ ID: 1bb9f63f-0612-48cc-9a52-b82911a2caa5
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 22:25:00+00:00
+----------------------------------------
+
+Instruction #145:
+ Timestep: 144
+ Time: 2025-11-03 22:30:00+00:00
+ ID: e43b4c2b-70f6-45e5-8c7e-8ceb92a4d980
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 22:30:00+00:00
+----------------------------------------
+
+Instruction #146:
+ Timestep: 145
+ Time: 2025-11-03 22:35:00+00:00
+ ID: 208fc523-3792-4ef3-88b5-8f8d4fdc84b2
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 22:35:00+00:00
+----------------------------------------
+
+Instruction #147:
+ Timestep: 146
+ Time: 2025-11-03 22:40:00+00:00
+ ID: f4cc05e7-00ee-436c-aa3f-026f7de08c5e
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 22:40:00+00:00
+----------------------------------------
+
+Instruction #148:
+ Timestep: 147
+ Time: 2025-11-03 22:45:00+00:00
+ ID: ec341e07-190c-4bf8-9025-0d452e0199f5
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 22:45:00+00:00
+----------------------------------------
+
+Instruction #149:
+ Timestep: 148
+ Time: 2025-11-03 22:50:00+00:00
+ ID: 3a43c9f7-17f3-4eeb-adbe-5b0baf314e22
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 22:50:00+00:00
+----------------------------------------
+
+Instruction #150:
+ Timestep: 149
+ Time: 2025-11-03 22:55:00+00:00
+ ID: d92bcbd6-c5df-42c6-82c3-5177ae94c09f
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 22:55:00+00:00
+----------------------------------------
+
+Instruction #151:
+ Timestep: 150
+ Time: 2025-11-03 23:00:00+00:00
+ ID: 377af6f9-8e71-4bb6-8a87-9d227210eef2
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 23:00:00+00:00
+----------------------------------------
+
+Instruction #152:
+ Timestep: 151
+ Time: 2025-11-03 23:05:00+00:00
+ ID: 39d9ba66-2333-442a-bc24-1a06c409333c
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 23:05:00+00:00
+----------------------------------------
+
+Instruction #153:
+ Timestep: 152
+ Time: 2025-11-03 23:10:00+00:00
+ ID: cb265400-5a21-4a3c-8d1e-e4eab195e50f
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 23:10:00+00:00
+----------------------------------------
+
+Instruction #154:
+ Timestep: 153
+ Time: 2025-11-03 23:15:00+00:00
+ ID: 250b1a4b-800f-46f5-850c-24f8667ad447
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 23:15:00+00:00
+----------------------------------------
+
+Instruction #155:
+ Timestep: 154
+ Time: 2025-11-03 23:20:00+00:00
+ ID: 2f7f98e2-e8cc-43a4-b54b-c256ed1d42b7
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 23:20:00+00:00
+----------------------------------------
+
+Instruction #156:
+ Timestep: 155
+ Time: 2025-11-03 23:25:00+00:00
+ ID: 564f4a10-a8ae-4f24-985c-326abae623f8
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 23:25:00+00:00
+----------------------------------------
+
+Instruction #157:
+ Timestep: 156
+ Time: 2025-11-03 23:30:00+00:00
+ ID: 9705fa5a-f987-42a2-b77b-97aa84610e87
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 23:30:00+00:00
+----------------------------------------
+
+Instruction #158:
+ Timestep: 157
+ Time: 2025-11-03 23:35:00+00:00
+ ID: 6aa496b5-8583-4e71-b7a2-2732de17de6a
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 23:35:00+00:00
+----------------------------------------
+
+Instruction #159:
+ Timestep: 158
+ Time: 2025-11-03 23:40:00+00:00
+ ID: 8d0d2440-c6bc-4fdf-82ff-97e7186d9387
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 23:40:00+00:00
+----------------------------------------
+
+Instruction #160:
+ Timestep: 159
+ Time: 2025-11-03 23:45:00+00:00
+ ID: 1683cbe6-babb-4a24-9b3e-67c96960295d
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 23:45:00+00:00
+----------------------------------------
+
+Instruction #161:
+ Timestep: 160
+ Time: 2025-11-03 23:50:00+00:00
+ ID: 79a330f9-ca78-4770-b966-55b74c498763
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 23:50:00+00:00
+----------------------------------------
+
+Instruction #162:
+ Timestep: 161
+ Time: 2025-11-03 23:55:00+00:00
+ ID: a3ad8ea0-e244-4343-a01d-3b9ffd9b8e32
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 23:55:00+00:00
+----------------------------------------
+
+Instruction #163:
+ Timestep: 162
+ Time: 2025-11-04 00:00:00+00:00
+ ID: c3cf4d5d-cd89-4346-afb5-a5f8586c9a54
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 00:00:00+00:00
+----------------------------------------
+
+Instruction #164:
+ Timestep: 163
+ Time: 2025-11-04 00:05:00+00:00
+ ID: 53cdf3f7-3ea4-4865-92a7-cb8f601ab308
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 00:05:00+00:00
+----------------------------------------
+
+Instruction #165:
+ Timestep: 164
+ Time: 2025-11-04 00:10:00+00:00
+ ID: c6177f25-23f5-4ae7-b5b8-bb3079984a54
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 00:10:00+00:00
+----------------------------------------
+
+Instruction #166:
+ Timestep: 165
+ Time: 2025-11-04 00:15:00+00:00
+ ID: fd60dd3d-fc63-41b4-a63f-2d20f4268716
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 00:15:00+00:00
+----------------------------------------
+
+Instruction #167:
+ Timestep: 166
+ Time: 2025-11-04 00:20:00+00:00
+ ID: d5fcc44d-07e8-4b70-9603-8965ea111a9c
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 00:20:00+00:00
+----------------------------------------
+
+Instruction #168:
+ Timestep: 167
+ Time: 2025-11-04 00:25:00+00:00
+ ID: d87b3b05-1b78-40ee-9f75-1f3e26a76817
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 00:25:00+00:00
+----------------------------------------
+
+Instruction #169:
+ Timestep: 168
+ Time: 2025-11-04 00:30:00+00:00
+ ID: c51d9d83-d8de-4b4c-8801-391dd4003d22
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 00:30:00+00:00
+----------------------------------------
+
+Instruction #170:
+ Timestep: 169
+ Time: 2025-11-04 00:35:00+00:00
+ ID: 2b7a41e5-ca34-4af7-afd9-315292c5094d
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 00:35:00+00:00
+----------------------------------------
+
+Instruction #171:
+ Timestep: 170
+ Time: 2025-11-04 00:40:00+00:00
+ ID: dedf9772-3570-468f-ae29-2fdcb8f35c37
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 00:40:00+00:00
+----------------------------------------
+
+Instruction #172:
+ Timestep: 171
+ Time: 2025-11-04 00:45:00+00:00
+ ID: 0a06a09a-19d5-445a-8e9f-36c620478891
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 00:45:00+00:00
+----------------------------------------
+
+Instruction #173:
+ Timestep: 172
+ Time: 2025-11-04 00:50:00+00:00
+ ID: d1915a49-0332-4650-9fbd-e22051882cad
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 00:50:00+00:00
+----------------------------------------
+
+Instruction #174:
+ Timestep: 173
+ Time: 2025-11-04 00:55:00+00:00
+ ID: 6261425e-597d-417e-97f7-66517ea23ae9
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 00:55:00+00:00
+----------------------------------------
+
+Instruction #175:
+ Timestep: 174
+ Time: 2025-11-04 01:00:00+00:00
+ ID: 44fc8270-1654-4d9f-863c-cb4a10a59a75
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 01:00:00+00:00
+----------------------------------------
+
+Instruction #176:
+ Timestep: 175
+ Time: 2025-11-04 01:05:00+00:00
+ ID: d9184538-df54-4157-bbf3-5e97adb3d2cf
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 01:05:00+00:00
+----------------------------------------
+
+Instruction #177:
+ Timestep: 176
+ Time: 2025-11-04 01:10:00+00:00
+ ID: e793e154-1103-4f92-a5e6-688ce22ab5fe
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 01:10:00+00:00
+----------------------------------------
+
+Instruction #178:
+ Timestep: 177
+ Time: 2025-11-04 01:15:00+00:00
+ ID: bb728ace-6194-4399-b4d7-8f1cbec7b2be
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 01:15:00+00:00
+----------------------------------------
+
+Instruction #179:
+ Timestep: 178
+ Time: 2025-11-04 01:20:00+00:00
+ ID: 6fc04066-cccf-488b-bdae-e38f39d13a70
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 01:20:00+00:00
+----------------------------------------
+
+Instruction #180:
+ Timestep: 179
+ Time: 2025-11-04 01:25:00+00:00
+ ID: 456af611-d69e-4af5-a125-e839ec203274
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 01:25:00+00:00
+----------------------------------------
+
+Instruction #181:
+ Timestep: 180
+ Time: 2025-11-04 01:30:00+00:00
+ ID: 142beb9f-0a19-472a-aad9-02e39c656bba
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 01:30:00+00:00
+----------------------------------------
+
+Instruction #182:
+ Timestep: 181
+ Time: 2025-11-04 01:35:00+00:00
+ ID: 61411de8-084d-4e8d-9f4e-1f8c49623d2d
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 01:35:00+00:00
+----------------------------------------
+
+Instruction #183:
+ Timestep: 182
+ Time: 2025-11-04 01:40:00+00:00
+ ID: 4c7ff65f-6d5d-4d8e-95dd-e18696d142a1
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 01:40:00+00:00
+----------------------------------------
+
+Instruction #184:
+ Timestep: 183
+ Time: 2025-11-04 01:45:00+00:00
+ ID: 91d7d4dd-1e04-4e27-b0a3-4c9f130d4c35
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 01:45:00+00:00
+----------------------------------------
+
+Instruction #185:
+ Timestep: 184
+ Time: 2025-11-04 01:50:00+00:00
+ ID: 3968c6bf-d5e9-47a8-9d8c-2a618942c8e5
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 01:50:00+00:00
+----------------------------------------
+
+Instruction #186:
+ Timestep: 185
+ Time: 2025-11-04 01:55:00+00:00
+ ID: c34b837c-7bd4-4859-a7c1-083952a36b1e
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 01:55:00+00:00
+----------------------------------------
+
+Instruction #187:
+ Timestep: 186
+ Time: 2025-11-04 02:00:00+00:00
+ ID: f779a9a8-45ae-4e04-baa9-51d3a385995a
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 02:00:00+00:00
+----------------------------------------
+
+Instruction #188:
+ Timestep: 187
+ Time: 2025-11-04 02:05:00+00:00
+ ID: d0297b34-047a-47f6-8382-339e717e94b2
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 02:05:00+00:00
+----------------------------------------
+
+Instruction #189:
+ Timestep: 188
+ Time: 2025-11-04 02:10:00+00:00
+ ID: d55f7750-6113-44ec-9a0f-08bad7e0510b
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 02:10:00+00:00
+----------------------------------------
+
+Instruction #190:
+ Timestep: 189
+ Time: 2025-11-04 02:15:00+00:00
+ ID: d32425cc-2319-41e1-9996-1e7af9e100b0
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 02:15:00+00:00
+----------------------------------------
+
+Instruction #191:
+ Timestep: 190
+ Time: 2025-11-04 02:20:00+00:00
+ ID: f6f8e538-123b-4e05-a68c-5e8bf605205a
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 02:20:00+00:00
+----------------------------------------
+
+Instruction #192:
+ Timestep: 191
+ Time: 2025-11-04 02:25:00+00:00
+ ID: 6d44f4e2-b471-4920-805d-d964b804137a
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 02:25:00+00:00
+----------------------------------------
+
+Instruction #193:
+ Timestep: 192
+ Time: 2025-11-04 02:30:00+00:00
+ ID: 7e5fb3e9-e9cf-464e-b3af-951228a55234
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 02:30:00+00:00
+----------------------------------------
+
+Instruction #194:
+ Timestep: 193
+ Time: 2025-11-04 02:35:00+00:00
+ ID: f720f57f-b2bb-49c0-920a-4d60247c2598
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 02:35:00+00:00
+----------------------------------------
+
+Instruction #195:
+ Timestep: 194
+ Time: 2025-11-04 02:40:00+00:00
+ ID: 769410ff-e1f7-4336-8c27-50d5deb036f7
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 02:40:00+00:00
+----------------------------------------
+
+Instruction #196:
+ Timestep: 195
+ Time: 2025-11-04 02:45:00+00:00
+ ID: 68e5462e-49fe-415a-a56b-5511519d4922
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 02:45:00+00:00
+----------------------------------------
+
+Instruction #197:
+ Timestep: 196
+ Time: 2025-11-04 02:50:00+00:00
+ ID: a17d0e70-52a7-4a1a-96e1-de40d4882028
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 02:50:00+00:00
+----------------------------------------
+
+Instruction #198:
+ Timestep: 197
+ Time: 2025-11-04 02:55:00+00:00
+ ID: 1af448fa-36da-4986-b0a9-8bcbca2072c0
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 02:55:00+00:00
+----------------------------------------
+
+Instruction #199:
+ Timestep: 198
+ Time: 2025-11-04 03:00:00+00:00
+ ID: 6ea92b62-4031-4ede-9659-ece46ce1b773
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 03:00:00+00:00
+----------------------------------------
+
+Instruction #200:
+ Timestep: 199
+ Time: 2025-11-04 03:05:00+00:00
+ ID: ca5fded9-5781-46c9-884f-c9638e4a1b74
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 03:05:00+00:00
+----------------------------------------
+
+Instruction #201:
+ Timestep: 200
+ Time: 2025-11-04 03:10:00+00:00
+ ID: fd32c708-8a57-46db-8fc8-53197be2d66f
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 03:10:00+00:00
+----------------------------------------
+
+Instruction #202:
+ Timestep: 201
+ Time: 2025-11-04 03:15:00+00:00
+ ID: 31f5d943-4128-40dc-87ea-3df1aba84b02
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 03:15:00+00:00
+----------------------------------------
+
+Instruction #203:
+ Timestep: 202
+ Time: 2025-11-04 03:20:00+00:00
+ ID: d2c4f0e4-2941-425a-a2aa-2b28c49ae013
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 03:20:00+00:00
+----------------------------------------
+
+Instruction #204:
+ Timestep: 203
+ Time: 2025-11-04 03:25:00+00:00
+ ID: 0a23e8cc-1ffb-4fc8-a79c-d68689ccebba
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 03:25:00+00:00
+----------------------------------------
+
+Instruction #205:
+ Timestep: 204
+ Time: 2025-11-04 03:30:00+00:00
+ ID: 5e260393-7568-494a-8d88-156abcf7441a
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 03:30:00+00:00
+----------------------------------------
+
+Instruction #206:
+ Timestep: 205
+ Time: 2025-11-04 03:35:00+00:00
+ ID: ed6616d8-8692-48bc-9fcc-55fab9821642
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 03:35:00+00:00
+----------------------------------------
+
+Instruction #207:
+ Timestep: 206
+ Time: 2025-11-04 03:40:00+00:00
+ ID: 0208578e-75b4-4b53-8406-a7da717c62f0
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 03:40:00+00:00
+----------------------------------------
+
+Instruction #208:
+ Timestep: 207
+ Time: 2025-11-04 03:45:00+00:00
+ ID: 09d68eb0-3a5a-4df0-863b-a7ae6bc19cc7
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 03:45:00+00:00
+----------------------------------------
+
+Instruction #209:
+ Timestep: 208
+ Time: 2025-11-04 03:50:00+00:00
+ ID: 3897f5a8-01e1-4c39-828e-6e82c0df8124
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 03:50:00+00:00
+----------------------------------------
+
+Instruction #210:
+ Timestep: 209
+ Time: 2025-11-04 03:55:00+00:00
+ ID: 842609b3-29de-4553-82a3-170eb563b736
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 03:55:00+00:00
+----------------------------------------
+
+Instruction #211:
+ Timestep: 210
+ Time: 2025-11-04 04:00:00+00:00
+ ID: 83150631-b0c4-4b86-9b6f-60d1307600fa
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 04:00:00+00:00
+----------------------------------------
+
+Instruction #212:
+ Timestep: 211
+ Time: 2025-11-04 04:05:00+00:00
+ ID: 3fb5bcb1-7f76-414e-99d4-1e5a98a9c30c
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 04:05:00+00:00
+----------------------------------------
+
+Instruction #213:
+ Timestep: 212
+ Time: 2025-11-04 04:10:00+00:00
+ ID: cc8aee00-c992-4e36-9477-02e3c6b77055
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 04:10:00+00:00
+----------------------------------------
+
+Instruction #214:
+ Timestep: 213
+ Time: 2025-11-04 04:15:00+00:00
+ ID: beda227b-207a-4e24-95b3-968d68c5ad51
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 04:15:00+00:00
+----------------------------------------
+
+Instruction #215:
+ Timestep: 214
+ Time: 2025-11-04 04:20:00+00:00
+ ID: 3f830f7b-e187-4481-af3d-4f64d026a0c9
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 04:20:00+00:00
+----------------------------------------
+
+Instruction #216:
+ Timestep: 215
+ Time: 2025-11-04 04:25:00+00:00
+ ID: 46df39e1-df12-437c-9fe5-546f3b638622
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 04:25:00+00:00
+----------------------------------------
+
+Instruction #217:
+ Timestep: 216
+ Time: 2025-11-04 04:30:00+00:00
+ ID: 6e803b87-b7ca-4bc1-b81c-860d545a0585
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 04:30:00+00:00
+----------------------------------------
+
+Instruction #218:
+ Timestep: 217
+ Time: 2025-11-04 04:35:00+00:00
+ ID: 442cfef0-e871-4946-8ad8-9866a15f12a5
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 04:35:00+00:00
+----------------------------------------
+
+Instruction #219:
+ Timestep: 218
+ Time: 2025-11-04 04:40:00+00:00
+ ID: 4f409b27-0636-4644-939a-6bd1b9be433c
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 04:40:00+00:00
+----------------------------------------
+
+Instruction #220:
+ Timestep: 219
+ Time: 2025-11-04 04:45:00+00:00
+ ID: 5ec7a5f4-46e8-405b-abd8-b37fb90fa48a
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 04:45:00+00:00
+----------------------------------------
+
+Instruction #221:
+ Timestep: 220
+ Time: 2025-11-04 04:50:00+00:00
+ ID: 0a06ddf9-9c02-4756-9d60-63ddd41a7301
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 04:50:00+00:00
+----------------------------------------
+
+Instruction #222:
+ Timestep: 221
+ Time: 2025-11-04 04:55:00+00:00
+ ID: e2cdb543-020c-428b-9919-0f976108f012
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 04:55:00+00:00
+----------------------------------------
+
+Instruction #223:
+ Timestep: 222
+ Time: 2025-11-04 05:00:00+00:00
+ ID: a824603b-a5ed-486a-8ca5-671b97bb0f63
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 05:00:00+00:00
+----------------------------------------
+
+Instruction #224:
+ Timestep: 223
+ Time: 2025-11-04 05:05:00+00:00
+ ID: ee57d585-0c8d-459a-b613-15914720e51b
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 05:05:00+00:00
+----------------------------------------
+
+Instruction #225:
+ Timestep: 224
+ Time: 2025-11-04 05:10:00+00:00
+ ID: 80654488-5783-467e-8e8a-da34e5707ff9
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 05:10:00+00:00
+----------------------------------------
+
+Instruction #226:
+ Timestep: 225
+ Time: 2025-11-04 05:15:00+00:00
+ ID: b955e2f3-fc0d-4195-b634-b95b9ea595dc
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 05:15:00+00:00
+----------------------------------------
+
+Instruction #227:
+ Timestep: 226
+ Time: 2025-11-04 05:20:00+00:00
+ ID: 965c1ed1-bea0-42e9-96c1-2dc7baf0ec81
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 05:20:00+00:00
+----------------------------------------
+
+Instruction #228:
+ Timestep: 227
+ Time: 2025-11-04 05:25:00+00:00
+ ID: 5e97c253-0144-44f4-a773-45fa90bb9771
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 05:25:00+00:00
+----------------------------------------
+
+Instruction #229:
+ Timestep: 228
+ Time: 2025-11-04 05:30:00+00:00
+ ID: 4949890c-5404-473c-a081-f3de4462dcb1
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 05:30:00+00:00
+----------------------------------------
+
+Instruction #230:
+ Timestep: 229
+ Time: 2025-11-04 05:35:00+00:00
+ ID: 0ee3579c-eb33-48ef-a4cd-565f6510cc8e
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 05:35:00+00:00
+----------------------------------------
+
+Instruction #231:
+ Timestep: 230
+ Time: 2025-11-04 05:40:00+00:00
+ ID: 3e6bfb86-b5dc-49e6-8605-14fc994266fd
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 05:40:00+00:00
+----------------------------------------
+
+Instruction #232:
+ Timestep: 231
+ Time: 2025-11-04 05:45:00+00:00
+ ID: 2e858eb3-c863-4a7a-b785-657436d62c85
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 05:45:00+00:00
+----------------------------------------
+
+Instruction #233:
+ Timestep: 232
+ Time: 2025-11-04 05:50:00+00:00
+ ID: f1069a63-5a96-4bd7-83ed-ed2a01197cf3
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 05:50:00+00:00
+----------------------------------------
+
+Instruction #234:
+ Timestep: 233
+ Time: 2025-11-04 05:55:00+00:00
+ ID: b6845a7a-d99c-4008-a2ea-d9999cfc01d8
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 05:55:00+00:00
+----------------------------------------
+
+Instruction #235:
+ Timestep: 234
+ Time: 2025-11-04 06:00:00+00:00
+ ID: af94fb5d-0be3-4c38-b18c-97f04e1449ac
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 06:00:00+00:00
+----------------------------------------
+
+Instruction #236:
+ Timestep: 235
+ Time: 2025-11-04 06:05:00+00:00
+ ID: a5cd576e-e63b-45fa-8724-d266b3a6498a
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 06:05:00+00:00
+----------------------------------------
+
+Instruction #237:
+ Timestep: 236
+ Time: 2025-11-04 06:10:00+00:00
+ ID: cb3d6545-3028-49d8-a8ef-536075fc883f
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 06:10:00+00:00
+----------------------------------------
+
+Instruction #238:
+ Timestep: 237
+ Time: 2025-11-04 06:15:00+00:00
+ ID: 3f5ea738-9c2b-46ae-bf17-50e015549192
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 06:15:00+00:00
+----------------------------------------
+
+Instruction #239:
+ Timestep: 238
+ Time: 2025-11-04 06:20:00+00:00
+ ID: 422e9c32-64bc-4b8e-9bfb-0f033ee0b57e
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 06:20:00+00:00
+----------------------------------------
+
+Instruction #240:
+ Timestep: 239
+ Time: 2025-11-04 06:25:00+00:00
+ ID: 9b3135af-ae18-4339-befe-51b17fb29756
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 06:25:00+00:00
+----------------------------------------
+
+Instruction #241:
+ Timestep: 240
+ Time: 2025-11-04 06:30:00+00:00
+ ID: 8d45702c-f005-4c57-aecc-2872b306c68e
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 06:30:00+00:00
+----------------------------------------
+
+Instruction #242:
+ Timestep: 241
+ Time: 2025-11-04 06:35:00+00:00
+ ID: dd180ef8-93aa-4375-b8e1-809acf5a6294
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 06:35:00+00:00
+----------------------------------------
+
+Instruction #243:
+ Timestep: 242
+ Time: 2025-11-04 06:40:00+00:00
+ ID: c9248f10-f702-411e-ac85-608b854c446a
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 06:40:00+00:00
+----------------------------------------
+
+Instruction #244:
+ Timestep: 243
+ Time: 2025-11-04 06:45:00+00:00
+ ID: a12f0774-10c8-4e59-ba23-a95511f1b137
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 06:45:00+00:00
+----------------------------------------
+
+Instruction #245:
+ Timestep: 244
+ Time: 2025-11-04 06:50:00+00:00
+ ID: d3209d1e-f10e-4698-b44c-cd83af0d8cc7
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 06:50:00+00:00
+----------------------------------------
+
+Instruction #246:
+ Timestep: 245
+ Time: 2025-11-04 06:55:00+00:00
+ ID: c4cda9ee-2721-402a-95aa-1b804fbe17ff
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 06:55:00+00:00
+----------------------------------------
+
+Instruction #247:
+ Timestep: 246
+ Time: 2025-11-04 07:00:00+00:00
+ ID: f0351c3b-52ab-427e-a1b6-2464d9592b9f
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 07:00:00+00:00
+----------------------------------------
+
+Instruction #248:
+ Timestep: 247
+ Time: 2025-11-04 07:05:00+00:00
+ ID: 150fa754-dda4-4e0f-be5f-6707225a26cb
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 07:05:00+00:00
+----------------------------------------
+
+Instruction #249:
+ Timestep: 248
+ Time: 2025-11-04 07:10:00+00:00
+ ID: b338b427-c48d-40c8-9b2c-201999d0f72f
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 07:10:00+00:00
+----------------------------------------
+
+Instruction #250:
+ Timestep: 249
+ Time: 2025-11-04 07:15:00+00:00
+ ID: 17b8f14c-de9b-471c-a365-b90f2625963c
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 07:15:00+00:00
+----------------------------------------
+
+Instruction #251:
+ Timestep: 250
+ Time: 2025-11-04 07:20:00+00:00
+ ID: a177f48e-2181-4e08-8b7e-3a08c3007a84
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 07:20:00+00:00
+----------------------------------------
+
+Instruction #252:
+ Timestep: 251
+ Time: 2025-11-04 07:25:00+00:00
+ ID: 52b2f9c8-4d34-4dfd-8f04-ffb4e3d3bbac
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 07:25:00+00:00
+----------------------------------------
+
+Instruction #253:
+ Timestep: 252
+ Time: 2025-11-04 07:30:00+00:00
+ ID: 5a5c9cd2-76f1-4fde-84f2-fb43f9891948
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 07:30:00+00:00
+----------------------------------------
+
+Instruction #254:
+ Timestep: 253
+ Time: 2025-11-04 07:35:00+00:00
+ ID: 1e7a0332-36c1-48f0-813f-0b33ada638c9
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 07:35:00+00:00
+----------------------------------------
+
+Instruction #255:
+ Timestep: 254
+ Time: 2025-11-04 07:40:00+00:00
+ ID: b4ed6c87-9747-4545-81d1-5185654187c9
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 07:40:00+00:00
+----------------------------------------
+
+Instruction #256:
+ Timestep: 255
+ Time: 2025-11-04 07:45:00+00:00
+ ID: e7f1b1b8-ba74-4c6b-8dc8-4eb451f66a87
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 07:45:00+00:00
+----------------------------------------
+
+Instruction #257:
+ Timestep: 256
+ Time: 2025-11-04 07:50:00+00:00
+ ID: e898e67b-fab7-4992-a571-b1feeeddcbb6
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 07:50:00+00:00
+----------------------------------------
+
+Instruction #258:
+ Timestep: 257
+ Time: 2025-11-04 07:55:00+00:00
+ ID: ab770a9d-6ce4-41ea-9a9d-d3f5f98e1453
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 07:55:00+00:00
+----------------------------------------
+
+Instruction #259:
+ Timestep: 258
+ Time: 2025-11-04 08:00:00+00:00
+ ID: 851feb94-37e6-4df9-88a3-d14331dc8165
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 08:00:00+00:00
+----------------------------------------
+
+Instruction #260:
+ Timestep: 259
+ Time: 2025-11-04 08:05:00+00:00
+ ID: 3faeb273-8559-44b3-8be5-35a6defbc028
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 08:05:00+00:00
+----------------------------------------
+
+Instruction #261:
+ Timestep: 260
+ Time: 2025-11-04 08:10:00+00:00
+ ID: 52acfa9e-33f5-4770-a2e4-4dbd05f87a02
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 08:10:00+00:00
+----------------------------------------
+
+Instruction #262:
+ Timestep: 261
+ Time: 2025-11-04 08:15:00+00:00
+ ID: 681112f5-a7d4-4be7-a8aa-37d62fb6fe13
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 08:15:00+00:00
+----------------------------------------
+
+Instruction #263:
+ Timestep: 262
+ Time: 2025-11-04 08:20:00+00:00
+ ID: 0a5b1201-b92c-4fa3-b4e9-d3e35db1f9d3
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 08:20:00+00:00
+----------------------------------------
+
+Instruction #264:
+ Timestep: 263
+ Time: 2025-11-04 08:25:00+00:00
+ ID: af7c8343-5c11-40ec-b594-2ee9450aeea7
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 08:25:00+00:00
+----------------------------------------
+
+Instruction #265:
+ Timestep: 264
+ Time: 2025-11-04 08:30:00+00:00
+ ID: 7b18d5e5-463a-488a-9b13-1a1c5c3a2ad7
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 08:30:00+00:00
+----------------------------------------
+
+Instruction #266:
+ Timestep: 265
+ Time: 2025-11-04 08:35:00+00:00
+ ID: 3b49bea5-3734-4cb4-902a-627d372690c6
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 08:35:00+00:00
+----------------------------------------
+
+Instruction #267:
+ Timestep: 266
+ Time: 2025-11-04 08:40:00+00:00
+ ID: 48c60b29-fa94-4368-a2d0-8a5e2ff99e0c
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 08:40:00+00:00
+----------------------------------------
+
+Instruction #268:
+ Timestep: 267
+ Time: 2025-11-04 08:45:00+00:00
+ ID: 13528d8d-1ca8-43c5-8751-38603a768f7e
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 08:45:00+00:00
+----------------------------------------
+
+Instruction #269:
+ Timestep: 268
+ Time: 2025-11-04 08:50:00+00:00
+ ID: d17f3930-e3c8-45a3-85ac-44fa38157d92
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 08:50:00+00:00
+----------------------------------------
+
+Instruction #270:
+ Timestep: 269
+ Time: 2025-11-04 08:55:00+00:00
+ ID: 0ed320fb-039e-429d-ba6b-e11c341af7cb
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 08:55:00+00:00
+----------------------------------------
+
+Instruction #271:
+ Timestep: 270
+ Time: 2025-11-04 09:00:00+00:00
+ ID: 4f77207d-0bed-493c-b404-d46b89105c04
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 09:00:00+00:00
+----------------------------------------
+
+Instruction #272:
+ Timestep: 271
+ Time: 2025-11-04 09:05:00+00:00
+ ID: e0abf01e-ade7-421f-bed6-2058996e217e
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 09:05:00+00:00
+----------------------------------------
+
+Instruction #273:
+ Timestep: 272
+ Time: 2025-11-04 09:10:00+00:00
+ ID: 190a56bb-0957-4e32-ab88-2866db996661
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 09:10:00+00:00
+----------------------------------------
+
+Instruction #274:
+ Timestep: 273
+ Time: 2025-11-04 09:15:00+00:00
+ ID: 5246135a-9984-4ac1-aa8e-0fd2000bc609
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 09:15:00+00:00
+----------------------------------------
+
+Instruction #275:
+ Timestep: 274
+ Time: 2025-11-04 09:20:00+00:00
+ ID: 7c028112-2f51-4727-9087-8980a0562237
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 09:20:00+00:00
+----------------------------------------
+
+Instruction #276:
+ Timestep: 275
+ Time: 2025-11-04 09:25:00+00:00
+ ID: 7c0d1427-c4df-4a58-9ab2-3c0f107edaa2
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 09:25:00+00:00
+----------------------------------------
+
+Instruction #277:
+ Timestep: 276
+ Time: 2025-11-04 09:30:00+00:00
+ ID: b220990e-2179-405a-830d-d37848d019ab
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 09:30:00+00:00
+----------------------------------------
+
+Instruction #278:
+ Timestep: 277
+ Time: 2025-11-04 09:35:00+00:00
+ ID: 9ac60ea5-d406-4749-9103-fbb60275649a
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 09:35:00+00:00
+----------------------------------------
+
+Instruction #279:
+ Timestep: 278
+ Time: 2025-11-04 09:40:00+00:00
+ ID: c851842d-bc83-491c-a21e-bb0bd12d3d25
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 09:40:00+00:00
+----------------------------------------
+
+Instruction #280:
+ Timestep: 279
+ Time: 2025-11-04 09:45:00+00:00
+ ID: 5e64be1e-050d-48ed-bf3b-ce8ad07462a2
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 09:45:00+00:00
+----------------------------------------
+
+Instruction #281:
+ Timestep: 280
+ Time: 2025-11-04 09:50:00+00:00
+ ID: 2d3a12b7-bc43-4889-831f-c32b7b63fe5d
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 09:50:00+00:00
+----------------------------------------
+
+Instruction #282:
+ Timestep: 281
+ Time: 2025-11-04 09:55:00+00:00
+ ID: 4988dced-b972-4fa2-85f6-55912b60e30e
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 09:55:00+00:00
+----------------------------------------
+
+Instruction #283:
+ Timestep: 282
+ Time: 2025-11-04 10:00:00+00:00
+ ID: 59f3fd55-d896-4278-aa98-1ed5a15a0775
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 10:00:00+00:00
+----------------------------------------
+
+Instruction #284:
+ Timestep: 283
+ Time: 2025-11-04 10:05:00+00:00
+ ID: 3af01b7f-e1c4-434e-9099-ebbc384c32fd
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 10:05:00+00:00
+----------------------------------------
+
+Instruction #285:
+ Timestep: 284
+ Time: 2025-11-04 10:10:00+00:00
+ ID: bc3d9487-2df9-4046-925e-ece009f3d438
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 10:10:00+00:00
+----------------------------------------
+
+Instruction #286:
+ Timestep: 285
+ Time: 2025-11-04 10:15:00+00:00
+ ID: 34de4a2a-18ba-4fc1-b587-10e769dfd96a
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 10:15:00+00:00
+----------------------------------------
+
+Instruction #287:
+ Timestep: 286
+ Time: 2025-11-04 10:20:00+00:00
+ ID: 6b816706-c164-4255-9431-421326c351a9
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 10:20:00+00:00
+----------------------------------------
+
+Instruction #288:
+ Timestep: 287
+ Time: 2025-11-04 10:25:00+00:00
+ ID: 9c876bc7-075d-408f-9de9-135ba63cb640
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 10:25:00+00:00
+----------------------------------------
diff --git a/flexmeasures_s2/profile_steering/examples/instructions/itho_instructions_cost_D=1_B=200_S=30_T=288.txt b/flexmeasures_s2/profile_steering/examples/instructions/itho_instructions_cost_D=1_B=200_S=30_T=288.txt
new file mode 100644
index 0000000..ec30e29
--- /dev/null
+++ b/flexmeasures_s2/profile_steering/examples/instructions/itho_instructions_cost_D=1_B=200_S=30_T=288.txt
@@ -0,0 +1,3218 @@
+================================================================================
+ITHO DHW Heat Pump Instructions Cost
+================================================================================
+Planning start time: 2025-11-05 14:10:00+00:00
+Planning window: 1 days 00:00:00
+Resolution: 0 days 00:05:00
+Total timesteps: 288
+================================================================================
+
+Device: itho_dhw_device_003
+Number of instructions: 288
+--------------------------------------------------------------------------------
+
+Instruction Timeline:
+--------------------------------------------------------------------------------
+ Mode: DHW_ON
+ Start: 2025-11-05 14:10:00+00:00 (timestep 0)
+ Duration: 7 timesteps (35.0 minutes)
+ Operation mode factor: 0.0
+----------------------------------------
+ Mode: DHW_OFF
+ Start: 2025-11-05 14:45:00+00:00 (timestep 7)
+ Duration: 19 timesteps (95.0 minutes)
+ Operation mode factor: 0.0
+----------------------------------------
+ Mode: DHW_ON
+ Start: 2025-11-05 16:20:00+00:00 (timestep 26)
+ Duration: 53 timesteps (265.0 minutes)
+ Operation mode factor: 0.0
+----------------------------------------
+ Mode: DHW_OFF
+ Start: 2025-11-05 20:45:00+00:00 (timestep 79)
+ Duration: 124 timesteps (620.0 minutes)
+ Operation mode factor: 0.0
+----------------------------------------
+ Mode: DHW_ON
+ Start: 2025-11-06 07:05:00+00:00 (timestep 203)
+ Duration: 6 timesteps (30.0 minutes)
+ Operation mode factor: 0.0
+----------------------------------------
+ Mode: DHW_OFF
+ Start: 2025-11-06 07:35:00+00:00 (timestep 209)
+ Duration: 79 timesteps (395.0 minutes)
+----------------------------------------
+
+Total operation mode changes: 5
+================================================================================
+
+Detailed Instruction List:
+================================================================================
+
+Instruction #1:
+ Timestep: 0
+ Time: 2025-11-05 14:10:00+00:00
+ ID: a607df3d-4615-45e7-8e8d-fd32762b910a
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 14:10:00+00:00
+----------------------------------------
+
+Instruction #2:
+ Timestep: 1
+ Time: 2025-11-05 14:15:00+00:00
+ ID: 8d7a0849-ad6c-4446-962c-991612c1e243
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 14:15:00+00:00
+----------------------------------------
+
+Instruction #3:
+ Timestep: 2
+ Time: 2025-11-05 14:20:00+00:00
+ ID: 1a74f25c-dac4-41b0-b8ab-0fb51ae8cd95
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 14:20:00+00:00
+----------------------------------------
+
+Instruction #4:
+ Timestep: 3
+ Time: 2025-11-05 14:25:00+00:00
+ ID: d66956c2-e904-4e69-9bfc-175a96926377
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 14:25:00+00:00
+----------------------------------------
+
+Instruction #5:
+ Timestep: 4
+ Time: 2025-11-05 14:30:00+00:00
+ ID: 87a39cec-19ed-4c70-93b0-6d6d2bfe842d
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 14:30:00+00:00
+----------------------------------------
+
+Instruction #6:
+ Timestep: 5
+ Time: 2025-11-05 14:35:00+00:00
+ ID: f05b6540-7c25-4c8c-b2c9-b93cfbfc8299
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 14:35:00+00:00
+----------------------------------------
+
+Instruction #7:
+ Timestep: 6
+ Time: 2025-11-05 14:40:00+00:00
+ ID: 1aeabc8f-a212-43b4-86e0-beb6f4c04a2f
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 14:40:00+00:00
+----------------------------------------
+
+Instruction #8:
+ Timestep: 7
+ Time: 2025-11-05 14:45:00+00:00
+ ID: ffb3d047-d2b7-44e0-ac0c-b84750230f84
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 14:45:00+00:00
+----------------------------------------
+
+Instruction #9:
+ Timestep: 8
+ Time: 2025-11-05 14:50:00+00:00
+ ID: aabb95e6-3f9c-4f59-a628-a955e8210d2b
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 14:50:00+00:00
+----------------------------------------
+
+Instruction #10:
+ Timestep: 9
+ Time: 2025-11-05 14:55:00+00:00
+ ID: a2f01523-8833-41db-98c0-f7905439481f
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 14:55:00+00:00
+----------------------------------------
+
+Instruction #11:
+ Timestep: 10
+ Time: 2025-11-05 15:00:00+00:00
+ ID: a4b0d48f-0645-44d1-8d2e-8878223de140
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 15:00:00+00:00
+----------------------------------------
+
+Instruction #12:
+ Timestep: 11
+ Time: 2025-11-05 15:05:00+00:00
+ ID: a114f19f-9393-4ea1-a401-041dae2c0aad
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 15:05:00+00:00
+----------------------------------------
+
+Instruction #13:
+ Timestep: 12
+ Time: 2025-11-05 15:10:00+00:00
+ ID: 68f6fa97-e658-4f0d-a2c0-6198d971d319
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 15:10:00+00:00
+----------------------------------------
+
+Instruction #14:
+ Timestep: 13
+ Time: 2025-11-05 15:15:00+00:00
+ ID: f9cab0d2-d477-4457-a950-da1f5c254019
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 15:15:00+00:00
+----------------------------------------
+
+Instruction #15:
+ Timestep: 14
+ Time: 2025-11-05 15:20:00+00:00
+ ID: d85fb2e6-dc4c-4e95-a66c-8f9d743432a7
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 15:20:00+00:00
+----------------------------------------
+
+Instruction #16:
+ Timestep: 15
+ Time: 2025-11-05 15:25:00+00:00
+ ID: 93c3d453-3880-4a55-8d74-726800f42a29
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 15:25:00+00:00
+----------------------------------------
+
+Instruction #17:
+ Timestep: 16
+ Time: 2025-11-05 15:30:00+00:00
+ ID: 98fd64df-5d60-4102-b0d6-efa54e9bba00
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 15:30:00+00:00
+----------------------------------------
+
+Instruction #18:
+ Timestep: 17
+ Time: 2025-11-05 15:35:00+00:00
+ ID: e1cfb574-5a8a-422c-b4bf-6da482e6746c
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 15:35:00+00:00
+----------------------------------------
+
+Instruction #19:
+ Timestep: 18
+ Time: 2025-11-05 15:40:00+00:00
+ ID: bd35c9ae-2532-4489-9407-18f9a965db92
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 15:40:00+00:00
+----------------------------------------
+
+Instruction #20:
+ Timestep: 19
+ Time: 2025-11-05 15:45:00+00:00
+ ID: 1413cfb4-8273-493b-82ec-de7f780d555d
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 15:45:00+00:00
+----------------------------------------
+
+Instruction #21:
+ Timestep: 20
+ Time: 2025-11-05 15:50:00+00:00
+ ID: c50639a8-4c74-445b-a024-acce1f58a77d
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 15:50:00+00:00
+----------------------------------------
+
+Instruction #22:
+ Timestep: 21
+ Time: 2025-11-05 15:55:00+00:00
+ ID: b774d7fe-3287-4f0b-b561-a6b4228c84f5
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 15:55:00+00:00
+----------------------------------------
+
+Instruction #23:
+ Timestep: 22
+ Time: 2025-11-05 16:00:00+00:00
+ ID: 04e8c6dc-010c-475e-9a33-fd0b5caa01fc
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 16:00:00+00:00
+----------------------------------------
+
+Instruction #24:
+ Timestep: 23
+ Time: 2025-11-05 16:05:00+00:00
+ ID: e3a46199-901e-4a71-b156-d86b45744a8e
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 16:05:00+00:00
+----------------------------------------
+
+Instruction #25:
+ Timestep: 24
+ Time: 2025-11-05 16:10:00+00:00
+ ID: 77baa0ec-667f-49a2-a420-cb97163bf5a9
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 16:10:00+00:00
+----------------------------------------
+
+Instruction #26:
+ Timestep: 25
+ Time: 2025-11-05 16:15:00+00:00
+ ID: 31e9dfe5-5c4a-4865-9d79-f89ea6fe1df4
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 16:15:00+00:00
+----------------------------------------
+
+Instruction #27:
+ Timestep: 26
+ Time: 2025-11-05 16:20:00+00:00
+ ID: 29b12ab2-5929-4508-84de-5f5f45dc4caa
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 16:20:00+00:00
+----------------------------------------
+
+Instruction #28:
+ Timestep: 27
+ Time: 2025-11-05 16:25:00+00:00
+ ID: 55e6d25c-78f6-41b0-8f8b-045ba2da76d1
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 16:25:00+00:00
+----------------------------------------
+
+Instruction #29:
+ Timestep: 28
+ Time: 2025-11-05 16:30:00+00:00
+ ID: 7c2ccebb-b399-4c75-ab2b-93a8a1c8ef7e
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 16:30:00+00:00
+----------------------------------------
+
+Instruction #30:
+ Timestep: 29
+ Time: 2025-11-05 16:35:00+00:00
+ ID: 42546ee8-7230-49bb-b0b1-88f6dfa3a66f
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 16:35:00+00:00
+----------------------------------------
+
+Instruction #31:
+ Timestep: 30
+ Time: 2025-11-05 16:40:00+00:00
+ ID: ceb16ed4-9d74-4aa6-92a1-f909190ce40c
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 16:40:00+00:00
+----------------------------------------
+
+Instruction #32:
+ Timestep: 31
+ Time: 2025-11-05 16:45:00+00:00
+ ID: c459bcbb-4484-4cad-b1a8-7c3afdb294c9
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 16:45:00+00:00
+----------------------------------------
+
+Instruction #33:
+ Timestep: 32
+ Time: 2025-11-05 16:50:00+00:00
+ ID: 98e8dda3-8752-4261-a704-f324c0b02b09
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 16:50:00+00:00
+----------------------------------------
+
+Instruction #34:
+ Timestep: 33
+ Time: 2025-11-05 16:55:00+00:00
+ ID: 9826e3c7-da68-4b01-991b-3281dfae9fec
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 16:55:00+00:00
+----------------------------------------
+
+Instruction #35:
+ Timestep: 34
+ Time: 2025-11-05 17:00:00+00:00
+ ID: 44100e91-704a-490f-86da-a670f317528c
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 17:00:00+00:00
+----------------------------------------
+
+Instruction #36:
+ Timestep: 35
+ Time: 2025-11-05 17:05:00+00:00
+ ID: 103d8a13-f64b-4304-9faf-f72b77482b66
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 17:05:00+00:00
+----------------------------------------
+
+Instruction #37:
+ Timestep: 36
+ Time: 2025-11-05 17:10:00+00:00
+ ID: ee27e3de-5781-4517-9e94-2d2c700fb033
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 17:10:00+00:00
+----------------------------------------
+
+Instruction #38:
+ Timestep: 37
+ Time: 2025-11-05 17:15:00+00:00
+ ID: 6fc365ba-e1fb-4671-bc1a-18f9a9edf469
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 17:15:00+00:00
+----------------------------------------
+
+Instruction #39:
+ Timestep: 38
+ Time: 2025-11-05 17:20:00+00:00
+ ID: de2e293c-eb79-43de-bec2-46e25efd06ae
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 17:20:00+00:00
+----------------------------------------
+
+Instruction #40:
+ Timestep: 39
+ Time: 2025-11-05 17:25:00+00:00
+ ID: 6fab1d8b-07b1-4cc5-8741-f55dfe923cd8
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 17:25:00+00:00
+----------------------------------------
+
+Instruction #41:
+ Timestep: 40
+ Time: 2025-11-05 17:30:00+00:00
+ ID: efc50d22-16a8-4d1d-95e7-47eff66a0794
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 17:30:00+00:00
+----------------------------------------
+
+Instruction #42:
+ Timestep: 41
+ Time: 2025-11-05 17:35:00+00:00
+ ID: 328750e3-6a3c-4fc9-b8eb-f47b82bdb849
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 17:35:00+00:00
+----------------------------------------
+
+Instruction #43:
+ Timestep: 42
+ Time: 2025-11-05 17:40:00+00:00
+ ID: 848342f2-7742-43cc-ad1f-5d11ce7f9fb3
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 17:40:00+00:00
+----------------------------------------
+
+Instruction #44:
+ Timestep: 43
+ Time: 2025-11-05 17:45:00+00:00
+ ID: 85d7e3ef-00c8-4a7e-96ec-ddfa7fe4d03b
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 17:45:00+00:00
+----------------------------------------
+
+Instruction #45:
+ Timestep: 44
+ Time: 2025-11-05 17:50:00+00:00
+ ID: d2b9ed5b-98e9-48a9-8cc7-d7a6ac95200a
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 17:50:00+00:00
+----------------------------------------
+
+Instruction #46:
+ Timestep: 45
+ Time: 2025-11-05 17:55:00+00:00
+ ID: 427613c8-d9d1-4355-85c0-e28eca4f356a
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 17:55:00+00:00
+----------------------------------------
+
+Instruction #47:
+ Timestep: 46
+ Time: 2025-11-05 18:00:00+00:00
+ ID: 821468c6-5583-44ea-8dd9-8c7c86240918
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 18:00:00+00:00
+----------------------------------------
+
+Instruction #48:
+ Timestep: 47
+ Time: 2025-11-05 18:05:00+00:00
+ ID: c79a1cf7-ddd1-441d-b3af-93f92cd61b02
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 18:05:00+00:00
+----------------------------------------
+
+Instruction #49:
+ Timestep: 48
+ Time: 2025-11-05 18:10:00+00:00
+ ID: abb26b86-7bf1-4c69-a553-dbf7455eed6c
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 18:10:00+00:00
+----------------------------------------
+
+Instruction #50:
+ Timestep: 49
+ Time: 2025-11-05 18:15:00+00:00
+ ID: bbb9b27b-bc08-4ad1-be95-96fd80878c85
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 18:15:00+00:00
+----------------------------------------
+
+Instruction #51:
+ Timestep: 50
+ Time: 2025-11-05 18:20:00+00:00
+ ID: 3905d6f9-11f4-42fd-ab7c-d080a248f586
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 18:20:00+00:00
+----------------------------------------
+
+Instruction #52:
+ Timestep: 51
+ Time: 2025-11-05 18:25:00+00:00
+ ID: a5f6e140-8210-46f0-855b-abf4efbd0dd9
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 18:25:00+00:00
+----------------------------------------
+
+Instruction #53:
+ Timestep: 52
+ Time: 2025-11-05 18:30:00+00:00
+ ID: 3bfeddc3-dbe6-4b5b-8953-8f9d10799834
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 18:30:00+00:00
+----------------------------------------
+
+Instruction #54:
+ Timestep: 53
+ Time: 2025-11-05 18:35:00+00:00
+ ID: 4112b2e0-56f2-432d-9f83-eea36da54984
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 18:35:00+00:00
+----------------------------------------
+
+Instruction #55:
+ Timestep: 54
+ Time: 2025-11-05 18:40:00+00:00
+ ID: d0588239-a3f7-48b8-83c8-86206a3753e6
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 18:40:00+00:00
+----------------------------------------
+
+Instruction #56:
+ Timestep: 55
+ Time: 2025-11-05 18:45:00+00:00
+ ID: 99cf5e5a-8a2a-4b67-b9bd-338763441770
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 18:45:00+00:00
+----------------------------------------
+
+Instruction #57:
+ Timestep: 56
+ Time: 2025-11-05 18:50:00+00:00
+ ID: 7f1b451d-f747-4694-992c-e078808b6aa6
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 18:50:00+00:00
+----------------------------------------
+
+Instruction #58:
+ Timestep: 57
+ Time: 2025-11-05 18:55:00+00:00
+ ID: dd1852e7-77e9-4aa0-b31c-1f53d885c8e9
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 18:55:00+00:00
+----------------------------------------
+
+Instruction #59:
+ Timestep: 58
+ Time: 2025-11-05 19:00:00+00:00
+ ID: 5530162a-0f46-4a2e-a6c5-d8bb0d823345
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 19:00:00+00:00
+----------------------------------------
+
+Instruction #60:
+ Timestep: 59
+ Time: 2025-11-05 19:05:00+00:00
+ ID: 1ca5a31e-1999-48a3-a278-d5723b408524
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 19:05:00+00:00
+----------------------------------------
+
+Instruction #61:
+ Timestep: 60
+ Time: 2025-11-05 19:10:00+00:00
+ ID: 32fa55ec-e355-418c-967a-7a591f09739c
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 19:10:00+00:00
+----------------------------------------
+
+Instruction #62:
+ Timestep: 61
+ Time: 2025-11-05 19:15:00+00:00
+ ID: d9b2aa76-d3b5-4e75-a637-0b260adf830c
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 19:15:00+00:00
+----------------------------------------
+
+Instruction #63:
+ Timestep: 62
+ Time: 2025-11-05 19:20:00+00:00
+ ID: 1d97a5c4-a9e0-45af-a9d6-4e4cf26c8761
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 19:20:00+00:00
+----------------------------------------
+
+Instruction #64:
+ Timestep: 63
+ Time: 2025-11-05 19:25:00+00:00
+ ID: 3829d685-6937-49e3-b4a6-ad07d3e746b8
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 19:25:00+00:00
+----------------------------------------
+
+Instruction #65:
+ Timestep: 64
+ Time: 2025-11-05 19:30:00+00:00
+ ID: d72cc742-9c90-4396-86fd-38ccd8c415ca
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 19:30:00+00:00
+----------------------------------------
+
+Instruction #66:
+ Timestep: 65
+ Time: 2025-11-05 19:35:00+00:00
+ ID: 069e1529-4ed2-4b0d-b387-eaabe3b4780e
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 19:35:00+00:00
+----------------------------------------
+
+Instruction #67:
+ Timestep: 66
+ Time: 2025-11-05 19:40:00+00:00
+ ID: 895dbab8-03a4-40b3-b025-bb7de65acff5
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 19:40:00+00:00
+----------------------------------------
+
+Instruction #68:
+ Timestep: 67
+ Time: 2025-11-05 19:45:00+00:00
+ ID: 542af801-45ca-45c9-9251-e17c5af1ec72
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 19:45:00+00:00
+----------------------------------------
+
+Instruction #69:
+ Timestep: 68
+ Time: 2025-11-05 19:50:00+00:00
+ ID: a1848f10-077d-45d8-939d-89eaadb6806e
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 19:50:00+00:00
+----------------------------------------
+
+Instruction #70:
+ Timestep: 69
+ Time: 2025-11-05 19:55:00+00:00
+ ID: 4d3ead0b-2a8f-4828-81d8-0cfb2dbd3975
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 19:55:00+00:00
+----------------------------------------
+
+Instruction #71:
+ Timestep: 70
+ Time: 2025-11-05 20:00:00+00:00
+ ID: adad14b2-e3d1-44b2-8f8b-0050819e6234
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 20:00:00+00:00
+----------------------------------------
+
+Instruction #72:
+ Timestep: 71
+ Time: 2025-11-05 20:05:00+00:00
+ ID: 19e61a9d-043d-4199-87b3-c5d914eb5f47
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 20:05:00+00:00
+----------------------------------------
+
+Instruction #73:
+ Timestep: 72
+ Time: 2025-11-05 20:10:00+00:00
+ ID: e6021c41-4103-426a-8765-721389c36454
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 20:10:00+00:00
+----------------------------------------
+
+Instruction #74:
+ Timestep: 73
+ Time: 2025-11-05 20:15:00+00:00
+ ID: 2f6d3baf-7096-4f1e-84ff-4c49058121f4
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 20:15:00+00:00
+----------------------------------------
+
+Instruction #75:
+ Timestep: 74
+ Time: 2025-11-05 20:20:00+00:00
+ ID: 20ca2a82-c841-4405-bafd-bf001fd8a526
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 20:20:00+00:00
+----------------------------------------
+
+Instruction #76:
+ Timestep: 75
+ Time: 2025-11-05 20:25:00+00:00
+ ID: 0e181ea6-6ef3-4876-8702-f1624d0a50dc
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 20:25:00+00:00
+----------------------------------------
+
+Instruction #77:
+ Timestep: 76
+ Time: 2025-11-05 20:30:00+00:00
+ ID: 3e805c7f-7dfe-4f4e-b31c-2b7c96693681
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 20:30:00+00:00
+----------------------------------------
+
+Instruction #78:
+ Timestep: 77
+ Time: 2025-11-05 20:35:00+00:00
+ ID: 2d1711f5-58c3-425d-abd0-ed8bb8e4f309
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 20:35:00+00:00
+----------------------------------------
+
+Instruction #79:
+ Timestep: 78
+ Time: 2025-11-05 20:40:00+00:00
+ ID: 656988d8-7d13-4919-9782-b77939ffcaca
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 20:40:00+00:00
+----------------------------------------
+
+Instruction #80:
+ Timestep: 79
+ Time: 2025-11-05 20:45:00+00:00
+ ID: e586713e-9992-4d94-a28b-06177dcc0da1
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 20:45:00+00:00
+----------------------------------------
+
+Instruction #81:
+ Timestep: 80
+ Time: 2025-11-05 20:50:00+00:00
+ ID: dda8203c-caad-49da-bafc-558c16825a1d
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 20:50:00+00:00
+----------------------------------------
+
+Instruction #82:
+ Timestep: 81
+ Time: 2025-11-05 20:55:00+00:00
+ ID: 7339772a-f20f-4ff1-8397-3ca260a2c152
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 20:55:00+00:00
+----------------------------------------
+
+Instruction #83:
+ Timestep: 82
+ Time: 2025-11-05 21:00:00+00:00
+ ID: 98e5fe7d-07f7-40d3-bae6-c993a9eaaa0b
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 21:00:00+00:00
+----------------------------------------
+
+Instruction #84:
+ Timestep: 83
+ Time: 2025-11-05 21:05:00+00:00
+ ID: f516bdc8-77b6-4732-9b63-fb6533c471e9
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 21:05:00+00:00
+----------------------------------------
+
+Instruction #85:
+ Timestep: 84
+ Time: 2025-11-05 21:10:00+00:00
+ ID: 0404a191-f425-4977-87f3-210f74d2a49a
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 21:10:00+00:00
+----------------------------------------
+
+Instruction #86:
+ Timestep: 85
+ Time: 2025-11-05 21:15:00+00:00
+ ID: d6c0ec61-7ee5-4382-a700-1f033365bb83
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 21:15:00+00:00
+----------------------------------------
+
+Instruction #87:
+ Timestep: 86
+ Time: 2025-11-05 21:20:00+00:00
+ ID: cbb03cc2-1d4f-4e35-a385-3501d8f2aa26
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 21:20:00+00:00
+----------------------------------------
+
+Instruction #88:
+ Timestep: 87
+ Time: 2025-11-05 21:25:00+00:00
+ ID: 10b0ecb2-715b-476e-85e5-395731d782f4
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 21:25:00+00:00
+----------------------------------------
+
+Instruction #89:
+ Timestep: 88
+ Time: 2025-11-05 21:30:00+00:00
+ ID: 48c73627-8ddf-44ac-9e04-1536a94d6923
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 21:30:00+00:00
+----------------------------------------
+
+Instruction #90:
+ Timestep: 89
+ Time: 2025-11-05 21:35:00+00:00
+ ID: c23e2d55-ff8c-4257-89b3-a52f83f049b3
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 21:35:00+00:00
+----------------------------------------
+
+Instruction #91:
+ Timestep: 90
+ Time: 2025-11-05 21:40:00+00:00
+ ID: 92179365-fc76-44b1-b02c-4903552930c0
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 21:40:00+00:00
+----------------------------------------
+
+Instruction #92:
+ Timestep: 91
+ Time: 2025-11-05 21:45:00+00:00
+ ID: fda08542-2b94-4b14-9b7a-8818d7d8742d
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 21:45:00+00:00
+----------------------------------------
+
+Instruction #93:
+ Timestep: 92
+ Time: 2025-11-05 21:50:00+00:00
+ ID: c25151aa-20bb-42c8-81ef-fdd90bcc76d6
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 21:50:00+00:00
+----------------------------------------
+
+Instruction #94:
+ Timestep: 93
+ Time: 2025-11-05 21:55:00+00:00
+ ID: 6302cc46-6e5d-4b9d-96b5-ec83bd4ba19e
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 21:55:00+00:00
+----------------------------------------
+
+Instruction #95:
+ Timestep: 94
+ Time: 2025-11-05 22:00:00+00:00
+ ID: 812b8c68-35f6-4b10-91da-84d60863b015
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 22:00:00+00:00
+----------------------------------------
+
+Instruction #96:
+ Timestep: 95
+ Time: 2025-11-05 22:05:00+00:00
+ ID: fa8f903c-ae41-4b84-bae4-89d5312968c7
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 22:05:00+00:00
+----------------------------------------
+
+Instruction #97:
+ Timestep: 96
+ Time: 2025-11-05 22:10:00+00:00
+ ID: 52da8785-a1ed-4d5f-bf65-a2ec5a3101ec
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 22:10:00+00:00
+----------------------------------------
+
+Instruction #98:
+ Timestep: 97
+ Time: 2025-11-05 22:15:00+00:00
+ ID: 116c6cc1-c5fc-4481-b3bb-f6abfc2d549f
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 22:15:00+00:00
+----------------------------------------
+
+Instruction #99:
+ Timestep: 98
+ Time: 2025-11-05 22:20:00+00:00
+ ID: 6c78a1b8-94dc-4391-ada3-5f54c11c39d5
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 22:20:00+00:00
+----------------------------------------
+
+Instruction #100:
+ Timestep: 99
+ Time: 2025-11-05 22:25:00+00:00
+ ID: 5ffba73d-299a-4a9e-bd8c-cc6fe816cbe1
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 22:25:00+00:00
+----------------------------------------
+
+Instruction #101:
+ Timestep: 100
+ Time: 2025-11-05 22:30:00+00:00
+ ID: 545f7466-5dae-4d75-a694-057bae68edf6
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 22:30:00+00:00
+----------------------------------------
+
+Instruction #102:
+ Timestep: 101
+ Time: 2025-11-05 22:35:00+00:00
+ ID: bea09ea9-ad25-430b-a978-94a85c49a2ed
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 22:35:00+00:00
+----------------------------------------
+
+Instruction #103:
+ Timestep: 102
+ Time: 2025-11-05 22:40:00+00:00
+ ID: 97fafde2-7237-4398-921a-faebb10d443b
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 22:40:00+00:00
+----------------------------------------
+
+Instruction #104:
+ Timestep: 103
+ Time: 2025-11-05 22:45:00+00:00
+ ID: e1458564-27e5-4184-b9b7-fa61da8446a0
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 22:45:00+00:00
+----------------------------------------
+
+Instruction #105:
+ Timestep: 104
+ Time: 2025-11-05 22:50:00+00:00
+ ID: 9f802d9e-a74b-4b8d-80ce-e991c8b788f8
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 22:50:00+00:00
+----------------------------------------
+
+Instruction #106:
+ Timestep: 105
+ Time: 2025-11-05 22:55:00+00:00
+ ID: a3f86417-33ae-4e6f-bcab-a7563d755632
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 22:55:00+00:00
+----------------------------------------
+
+Instruction #107:
+ Timestep: 106
+ Time: 2025-11-05 23:00:00+00:00
+ ID: 40f35609-75a0-407f-ac08-cd8e28f95c67
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 23:00:00+00:00
+----------------------------------------
+
+Instruction #108:
+ Timestep: 107
+ Time: 2025-11-05 23:05:00+00:00
+ ID: 029862fa-4b41-47df-9008-149ffcb640c1
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 23:05:00+00:00
+----------------------------------------
+
+Instruction #109:
+ Timestep: 108
+ Time: 2025-11-05 23:10:00+00:00
+ ID: 4875bff6-ea9d-47a3-a0ee-51ec0d282060
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 23:10:00+00:00
+----------------------------------------
+
+Instruction #110:
+ Timestep: 109
+ Time: 2025-11-05 23:15:00+00:00
+ ID: bdcd95f1-4cb0-404d-aa97-9871ab571095
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 23:15:00+00:00
+----------------------------------------
+
+Instruction #111:
+ Timestep: 110
+ Time: 2025-11-05 23:20:00+00:00
+ ID: 26474c0c-7a0d-48e9-8d93-458281a59bc8
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 23:20:00+00:00
+----------------------------------------
+
+Instruction #112:
+ Timestep: 111
+ Time: 2025-11-05 23:25:00+00:00
+ ID: aa63520d-2310-4008-914a-a78e41b84b34
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 23:25:00+00:00
+----------------------------------------
+
+Instruction #113:
+ Timestep: 112
+ Time: 2025-11-05 23:30:00+00:00
+ ID: 1e73c7ca-352d-4e4b-9616-d3672c8d7458
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 23:30:00+00:00
+----------------------------------------
+
+Instruction #114:
+ Timestep: 113
+ Time: 2025-11-05 23:35:00+00:00
+ ID: 61336d94-29ec-4cc9-807e-60f81e127af0
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 23:35:00+00:00
+----------------------------------------
+
+Instruction #115:
+ Timestep: 114
+ Time: 2025-11-05 23:40:00+00:00
+ ID: d2163c19-b873-463d-8967-7b4782e3d69d
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 23:40:00+00:00
+----------------------------------------
+
+Instruction #116:
+ Timestep: 115
+ Time: 2025-11-05 23:45:00+00:00
+ ID: 60c4558a-7d7a-46c2-93c6-2cb4b7ed92fc
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 23:45:00+00:00
+----------------------------------------
+
+Instruction #117:
+ Timestep: 116
+ Time: 2025-11-05 23:50:00+00:00
+ ID: e56926b3-b030-4a5a-8c5a-d9803fa7dbf4
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 23:50:00+00:00
+----------------------------------------
+
+Instruction #118:
+ Timestep: 117
+ Time: 2025-11-05 23:55:00+00:00
+ ID: 0ff229c9-e308-42b5-a0ee-2ad0710f03c6
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-05 23:55:00+00:00
+----------------------------------------
+
+Instruction #119:
+ Timestep: 118
+ Time: 2025-11-06 00:00:00+00:00
+ ID: 3d50027c-0020-47f3-a94e-3c1672c8d5bd
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 00:00:00+00:00
+----------------------------------------
+
+Instruction #120:
+ Timestep: 119
+ Time: 2025-11-06 00:05:00+00:00
+ ID: da8eef38-2dba-48c8-8982-c44cc4823d5f
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 00:05:00+00:00
+----------------------------------------
+
+Instruction #121:
+ Timestep: 120
+ Time: 2025-11-06 00:10:00+00:00
+ ID: 6e3c77ab-6745-46a5-83f7-74e676ffa5b0
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 00:10:00+00:00
+----------------------------------------
+
+Instruction #122:
+ Timestep: 121
+ Time: 2025-11-06 00:15:00+00:00
+ ID: 498ba0a9-43f5-47fc-96b4-6e0e92b49846
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 00:15:00+00:00
+----------------------------------------
+
+Instruction #123:
+ Timestep: 122
+ Time: 2025-11-06 00:20:00+00:00
+ ID: e6feea40-81b8-4aba-aa9a-43f36ac8cab1
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 00:20:00+00:00
+----------------------------------------
+
+Instruction #124:
+ Timestep: 123
+ Time: 2025-11-06 00:25:00+00:00
+ ID: 7aed39a9-fda9-4214-b7d3-d38843c33389
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 00:25:00+00:00
+----------------------------------------
+
+Instruction #125:
+ Timestep: 124
+ Time: 2025-11-06 00:30:00+00:00
+ ID: 7e426f16-8f44-469a-90f8-96da1cbd68e2
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 00:30:00+00:00
+----------------------------------------
+
+Instruction #126:
+ Timestep: 125
+ Time: 2025-11-06 00:35:00+00:00
+ ID: eaf5897b-75ae-404e-af7d-79802e817de2
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 00:35:00+00:00
+----------------------------------------
+
+Instruction #127:
+ Timestep: 126
+ Time: 2025-11-06 00:40:00+00:00
+ ID: b3ee2c82-4adb-4ea5-9338-9c5c4bf7bc40
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 00:40:00+00:00
+----------------------------------------
+
+Instruction #128:
+ Timestep: 127
+ Time: 2025-11-06 00:45:00+00:00
+ ID: eb88cee9-ff83-4339-aa4f-9b76bcb5c6af
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 00:45:00+00:00
+----------------------------------------
+
+Instruction #129:
+ Timestep: 128
+ Time: 2025-11-06 00:50:00+00:00
+ ID: 9333baf2-2ff2-4853-aed3-1dd07baf1281
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 00:50:00+00:00
+----------------------------------------
+
+Instruction #130:
+ Timestep: 129
+ Time: 2025-11-06 00:55:00+00:00
+ ID: cc48ea9c-2b4f-46e9-8365-2aae75deacaf
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 00:55:00+00:00
+----------------------------------------
+
+Instruction #131:
+ Timestep: 130
+ Time: 2025-11-06 01:00:00+00:00
+ ID: 69229eaf-f42e-4b3f-b2d6-0126cbf7fcc5
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 01:00:00+00:00
+----------------------------------------
+
+Instruction #132:
+ Timestep: 131
+ Time: 2025-11-06 01:05:00+00:00
+ ID: 73938aac-f889-4f8f-8188-597bde222bc7
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 01:05:00+00:00
+----------------------------------------
+
+Instruction #133:
+ Timestep: 132
+ Time: 2025-11-06 01:10:00+00:00
+ ID: daa6590c-079f-4b3e-92e6-1282a2481043
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 01:10:00+00:00
+----------------------------------------
+
+Instruction #134:
+ Timestep: 133
+ Time: 2025-11-06 01:15:00+00:00
+ ID: 157ba8bb-85fd-42bb-b71a-57941953847e
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 01:15:00+00:00
+----------------------------------------
+
+Instruction #135:
+ Timestep: 134
+ Time: 2025-11-06 01:20:00+00:00
+ ID: 4a53341a-59f8-485a-920e-15203a38ce0e
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 01:20:00+00:00
+----------------------------------------
+
+Instruction #136:
+ Timestep: 135
+ Time: 2025-11-06 01:25:00+00:00
+ ID: f7fbd8ff-4a1c-430c-bf56-347c1ee83150
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 01:25:00+00:00
+----------------------------------------
+
+Instruction #137:
+ Timestep: 136
+ Time: 2025-11-06 01:30:00+00:00
+ ID: 4cf7c322-899d-4432-a55c-99e0244e5dac
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 01:30:00+00:00
+----------------------------------------
+
+Instruction #138:
+ Timestep: 137
+ Time: 2025-11-06 01:35:00+00:00
+ ID: 5c6b12d8-36fb-432a-b5a9-95ef1b9b3bc2
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 01:35:00+00:00
+----------------------------------------
+
+Instruction #139:
+ Timestep: 138
+ Time: 2025-11-06 01:40:00+00:00
+ ID: 11c691cd-58a0-420e-8d65-e811629085a0
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 01:40:00+00:00
+----------------------------------------
+
+Instruction #140:
+ Timestep: 139
+ Time: 2025-11-06 01:45:00+00:00
+ ID: 9d43b5b9-e303-48ea-a56c-1cb090ade2d5
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 01:45:00+00:00
+----------------------------------------
+
+Instruction #141:
+ Timestep: 140
+ Time: 2025-11-06 01:50:00+00:00
+ ID: 64d5f716-145b-4efd-a6bb-7d607b94af8e
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 01:50:00+00:00
+----------------------------------------
+
+Instruction #142:
+ Timestep: 141
+ Time: 2025-11-06 01:55:00+00:00
+ ID: c66e358a-efdb-486a-a800-ffd8bf7eca81
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 01:55:00+00:00
+----------------------------------------
+
+Instruction #143:
+ Timestep: 142
+ Time: 2025-11-06 02:00:00+00:00
+ ID: 8a6bedad-74c7-461b-a865-b47e3081f47f
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 02:00:00+00:00
+----------------------------------------
+
+Instruction #144:
+ Timestep: 143
+ Time: 2025-11-06 02:05:00+00:00
+ ID: 52192718-7125-4531-82a0-58c4101f8372
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 02:05:00+00:00
+----------------------------------------
+
+Instruction #145:
+ Timestep: 144
+ Time: 2025-11-06 02:10:00+00:00
+ ID: 76cccdf4-81f8-4ed4-b29d-51d844357724
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 02:10:00+00:00
+----------------------------------------
+
+Instruction #146:
+ Timestep: 145
+ Time: 2025-11-06 02:15:00+00:00
+ ID: 68781f1f-73ae-4a8e-8a2f-d07ce7972187
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 02:15:00+00:00
+----------------------------------------
+
+Instruction #147:
+ Timestep: 146
+ Time: 2025-11-06 02:20:00+00:00
+ ID: 324c6036-69de-4e32-9b44-35f214251b32
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 02:20:00+00:00
+----------------------------------------
+
+Instruction #148:
+ Timestep: 147
+ Time: 2025-11-06 02:25:00+00:00
+ ID: 564dbf95-7d57-4674-959b-6df6c98bd6d2
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 02:25:00+00:00
+----------------------------------------
+
+Instruction #149:
+ Timestep: 148
+ Time: 2025-11-06 02:30:00+00:00
+ ID: 3c87fe4c-4810-4659-adb0-e436e72f98a0
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 02:30:00+00:00
+----------------------------------------
+
+Instruction #150:
+ Timestep: 149
+ Time: 2025-11-06 02:35:00+00:00
+ ID: d7505a1a-60d3-450a-b2a3-be598ce3ddbc
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 02:35:00+00:00
+----------------------------------------
+
+Instruction #151:
+ Timestep: 150
+ Time: 2025-11-06 02:40:00+00:00
+ ID: ecc38d26-5904-41bf-bd73-53547d66d84a
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 02:40:00+00:00
+----------------------------------------
+
+Instruction #152:
+ Timestep: 151
+ Time: 2025-11-06 02:45:00+00:00
+ ID: 99f1cb77-2654-4f5d-aa51-16ef38a8f893
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 02:45:00+00:00
+----------------------------------------
+
+Instruction #153:
+ Timestep: 152
+ Time: 2025-11-06 02:50:00+00:00
+ ID: 1c6e279f-d6ca-4a29-9483-bbf9e346ae4f
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 02:50:00+00:00
+----------------------------------------
+
+Instruction #154:
+ Timestep: 153
+ Time: 2025-11-06 02:55:00+00:00
+ ID: 01265509-b920-4a4a-b047-a1208e968535
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 02:55:00+00:00
+----------------------------------------
+
+Instruction #155:
+ Timestep: 154
+ Time: 2025-11-06 03:00:00+00:00
+ ID: eb8d9bc8-b3ec-48c6-a69d-d7a2288b978f
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 03:00:00+00:00
+----------------------------------------
+
+Instruction #156:
+ Timestep: 155
+ Time: 2025-11-06 03:05:00+00:00
+ ID: 4aa8c290-77bf-4540-b462-ee59e69bf0ed
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 03:05:00+00:00
+----------------------------------------
+
+Instruction #157:
+ Timestep: 156
+ Time: 2025-11-06 03:10:00+00:00
+ ID: 4c4bf144-ca55-48ef-b719-03f7d04e94a9
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 03:10:00+00:00
+----------------------------------------
+
+Instruction #158:
+ Timestep: 157
+ Time: 2025-11-06 03:15:00+00:00
+ ID: 1ecca388-821c-4a70-af43-d3b3ed546ad7
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 03:15:00+00:00
+----------------------------------------
+
+Instruction #159:
+ Timestep: 158
+ Time: 2025-11-06 03:20:00+00:00
+ ID: 51230534-a7ed-4fae-b294-4a45aece98aa
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 03:20:00+00:00
+----------------------------------------
+
+Instruction #160:
+ Timestep: 159
+ Time: 2025-11-06 03:25:00+00:00
+ ID: f1c82e0f-fcd1-4446-8e7c-8f6464f2d2f5
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 03:25:00+00:00
+----------------------------------------
+
+Instruction #161:
+ Timestep: 160
+ Time: 2025-11-06 03:30:00+00:00
+ ID: 1fa099c9-0124-4109-9e36-7321d7eda90c
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 03:30:00+00:00
+----------------------------------------
+
+Instruction #162:
+ Timestep: 161
+ Time: 2025-11-06 03:35:00+00:00
+ ID: 5e9f46ed-cdfa-4ef1-afa4-fe147bb7bb37
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 03:35:00+00:00
+----------------------------------------
+
+Instruction #163:
+ Timestep: 162
+ Time: 2025-11-06 03:40:00+00:00
+ ID: 867348d0-e0b8-4d12-8c24-4d4447b308c1
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 03:40:00+00:00
+----------------------------------------
+
+Instruction #164:
+ Timestep: 163
+ Time: 2025-11-06 03:45:00+00:00
+ ID: c8e9e326-ad5b-4238-867a-ef6011a881be
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 03:45:00+00:00
+----------------------------------------
+
+Instruction #165:
+ Timestep: 164
+ Time: 2025-11-06 03:50:00+00:00
+ ID: 0b6eb68d-1335-4e49-827f-d65f977e44f1
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 03:50:00+00:00
+----------------------------------------
+
+Instruction #166:
+ Timestep: 165
+ Time: 2025-11-06 03:55:00+00:00
+ ID: fc602029-b588-4f6b-a566-581f5d7850e2
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 03:55:00+00:00
+----------------------------------------
+
+Instruction #167:
+ Timestep: 166
+ Time: 2025-11-06 04:00:00+00:00
+ ID: 5348a8bd-4dd2-4d9f-8b27-575ad014b32e
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 04:00:00+00:00
+----------------------------------------
+
+Instruction #168:
+ Timestep: 167
+ Time: 2025-11-06 04:05:00+00:00
+ ID: 257a3cc5-ffd9-4138-bf13-4870ea134c75
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 04:05:00+00:00
+----------------------------------------
+
+Instruction #169:
+ Timestep: 168
+ Time: 2025-11-06 04:10:00+00:00
+ ID: 8418ecec-46f9-49ab-98b1-91570ba32ebf
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 04:10:00+00:00
+----------------------------------------
+
+Instruction #170:
+ Timestep: 169
+ Time: 2025-11-06 04:15:00+00:00
+ ID: 5c1a055f-1767-455b-bbf0-88ebfb2dc659
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 04:15:00+00:00
+----------------------------------------
+
+Instruction #171:
+ Timestep: 170
+ Time: 2025-11-06 04:20:00+00:00
+ ID: 7d9af51f-37b6-449c-8700-8516710604b9
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 04:20:00+00:00
+----------------------------------------
+
+Instruction #172:
+ Timestep: 171
+ Time: 2025-11-06 04:25:00+00:00
+ ID: c580bd49-3414-46b0-97bc-a5e3811a45af
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 04:25:00+00:00
+----------------------------------------
+
+Instruction #173:
+ Timestep: 172
+ Time: 2025-11-06 04:30:00+00:00
+ ID: 5d6c99aa-c324-45fc-9dab-fafd50194509
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 04:30:00+00:00
+----------------------------------------
+
+Instruction #174:
+ Timestep: 173
+ Time: 2025-11-06 04:35:00+00:00
+ ID: 369d8428-4c4b-4709-a89e-bf6e7161877c
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 04:35:00+00:00
+----------------------------------------
+
+Instruction #175:
+ Timestep: 174
+ Time: 2025-11-06 04:40:00+00:00
+ ID: dbac193a-6653-4996-bb2e-89acd5aee35a
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 04:40:00+00:00
+----------------------------------------
+
+Instruction #176:
+ Timestep: 175
+ Time: 2025-11-06 04:45:00+00:00
+ ID: f677e734-c2b1-47f9-afb5-11c116897b7f
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 04:45:00+00:00
+----------------------------------------
+
+Instruction #177:
+ Timestep: 176
+ Time: 2025-11-06 04:50:00+00:00
+ ID: c0239d1f-6520-486f-9928-82bcc6034b42
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 04:50:00+00:00
+----------------------------------------
+
+Instruction #178:
+ Timestep: 177
+ Time: 2025-11-06 04:55:00+00:00
+ ID: 07b9b89f-d30a-48f2-813e-f0eb5dd2a99b
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 04:55:00+00:00
+----------------------------------------
+
+Instruction #179:
+ Timestep: 178
+ Time: 2025-11-06 05:00:00+00:00
+ ID: 16a0e7de-62df-473e-818e-71552ab206bd
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 05:00:00+00:00
+----------------------------------------
+
+Instruction #180:
+ Timestep: 179
+ Time: 2025-11-06 05:05:00+00:00
+ ID: d531138a-7a2c-4807-8504-dd07a9705a72
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 05:05:00+00:00
+----------------------------------------
+
+Instruction #181:
+ Timestep: 180
+ Time: 2025-11-06 05:10:00+00:00
+ ID: ac0ca978-23dc-4f70-b3d4-9a503c94166a
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 05:10:00+00:00
+----------------------------------------
+
+Instruction #182:
+ Timestep: 181
+ Time: 2025-11-06 05:15:00+00:00
+ ID: f6bbb161-98ab-4717-83ad-d8546d245ba4
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 05:15:00+00:00
+----------------------------------------
+
+Instruction #183:
+ Timestep: 182
+ Time: 2025-11-06 05:20:00+00:00
+ ID: a48373b7-168b-413b-82f8-5d94c765d10f
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 05:20:00+00:00
+----------------------------------------
+
+Instruction #184:
+ Timestep: 183
+ Time: 2025-11-06 05:25:00+00:00
+ ID: 617de272-3555-4534-b3c6-acef2c47bb26
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 05:25:00+00:00
+----------------------------------------
+
+Instruction #185:
+ Timestep: 184
+ Time: 2025-11-06 05:30:00+00:00
+ ID: fb5f57a2-00ea-4ffb-8028-f0f4bd5b2aee
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 05:30:00+00:00
+----------------------------------------
+
+Instruction #186:
+ Timestep: 185
+ Time: 2025-11-06 05:35:00+00:00
+ ID: df12081f-e373-44e1-a172-55d4e136b2e7
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 05:35:00+00:00
+----------------------------------------
+
+Instruction #187:
+ Timestep: 186
+ Time: 2025-11-06 05:40:00+00:00
+ ID: 1f652783-fa41-4b42-965f-2674d8eef106
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 05:40:00+00:00
+----------------------------------------
+
+Instruction #188:
+ Timestep: 187
+ Time: 2025-11-06 05:45:00+00:00
+ ID: ab7391df-3d94-44d1-b518-14048a742351
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 05:45:00+00:00
+----------------------------------------
+
+Instruction #189:
+ Timestep: 188
+ Time: 2025-11-06 05:50:00+00:00
+ ID: c9bacd21-278d-45dc-ac54-78a462333f80
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 05:50:00+00:00
+----------------------------------------
+
+Instruction #190:
+ Timestep: 189
+ Time: 2025-11-06 05:55:00+00:00
+ ID: 76ad44e5-2686-4778-b615-712d2fb29670
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 05:55:00+00:00
+----------------------------------------
+
+Instruction #191:
+ Timestep: 190
+ Time: 2025-11-06 06:00:00+00:00
+ ID: 91a05fd2-37cd-4f2d-81c8-65e04e2943a1
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 06:00:00+00:00
+----------------------------------------
+
+Instruction #192:
+ Timestep: 191
+ Time: 2025-11-06 06:05:00+00:00
+ ID: ccfae7c9-e73b-49cd-addf-e18e53293036
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 06:05:00+00:00
+----------------------------------------
+
+Instruction #193:
+ Timestep: 192
+ Time: 2025-11-06 06:10:00+00:00
+ ID: 7b57d126-70ac-4088-bc18-3ff32dc9e5ac
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 06:10:00+00:00
+----------------------------------------
+
+Instruction #194:
+ Timestep: 193
+ Time: 2025-11-06 06:15:00+00:00
+ ID: 16cbf763-18cf-402a-a844-628e14b027ca
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 06:15:00+00:00
+----------------------------------------
+
+Instruction #195:
+ Timestep: 194
+ Time: 2025-11-06 06:20:00+00:00
+ ID: 33f8fe77-202e-49cd-b0d9-bf05f4e5a93d
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 06:20:00+00:00
+----------------------------------------
+
+Instruction #196:
+ Timestep: 195
+ Time: 2025-11-06 06:25:00+00:00
+ ID: bdfb055e-6a1e-45f1-8a0a-c1c41fc8d8c8
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 06:25:00+00:00
+----------------------------------------
+
+Instruction #197:
+ Timestep: 196
+ Time: 2025-11-06 06:30:00+00:00
+ ID: 36308266-a5e4-4fa4-8e1d-af917f8296c8
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 06:30:00+00:00
+----------------------------------------
+
+Instruction #198:
+ Timestep: 197
+ Time: 2025-11-06 06:35:00+00:00
+ ID: d94f7c4c-572e-4183-8329-a1a638ba3854
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 06:35:00+00:00
+----------------------------------------
+
+Instruction #199:
+ Timestep: 198
+ Time: 2025-11-06 06:40:00+00:00
+ ID: 1378be99-5233-48fc-ba95-b90aa03db619
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 06:40:00+00:00
+----------------------------------------
+
+Instruction #200:
+ Timestep: 199
+ Time: 2025-11-06 06:45:00+00:00
+ ID: 8a5bf51c-450e-4c49-ade3-421443f8f865
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 06:45:00+00:00
+----------------------------------------
+
+Instruction #201:
+ Timestep: 200
+ Time: 2025-11-06 06:50:00+00:00
+ ID: b9d3665d-593f-4da9-be5e-c1ee86cf9713
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 06:50:00+00:00
+----------------------------------------
+
+Instruction #202:
+ Timestep: 201
+ Time: 2025-11-06 06:55:00+00:00
+ ID: 7d2e0d74-70fc-4bf1-8a69-84c68eb41809
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 06:55:00+00:00
+----------------------------------------
+
+Instruction #203:
+ Timestep: 202
+ Time: 2025-11-06 07:00:00+00:00
+ ID: 984bf498-c8e8-4631-a743-a22dc9679616
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 07:00:00+00:00
+----------------------------------------
+
+Instruction #204:
+ Timestep: 203
+ Time: 2025-11-06 07:05:00+00:00
+ ID: 9095b838-1bf5-43b2-9ce1-7365142e9935
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 07:05:00+00:00
+----------------------------------------
+
+Instruction #205:
+ Timestep: 204
+ Time: 2025-11-06 07:10:00+00:00
+ ID: 39357caf-91cb-471c-b776-c80a44e47a89
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 07:10:00+00:00
+----------------------------------------
+
+Instruction #206:
+ Timestep: 205
+ Time: 2025-11-06 07:15:00+00:00
+ ID: c6deecbd-ee86-484b-afe9-8a38a6e72c10
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 07:15:00+00:00
+----------------------------------------
+
+Instruction #207:
+ Timestep: 206
+ Time: 2025-11-06 07:20:00+00:00
+ ID: 58c8bfa8-cb4c-40dc-88fb-9dfaec98f7eb
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 07:20:00+00:00
+----------------------------------------
+
+Instruction #208:
+ Timestep: 207
+ Time: 2025-11-06 07:25:00+00:00
+ ID: e0d10bf7-7035-4abe-a729-ac4dd38ad031
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 07:25:00+00:00
+----------------------------------------
+
+Instruction #209:
+ Timestep: 208
+ Time: 2025-11-06 07:30:00+00:00
+ ID: 3818fbf4-ba0b-4927-a07d-89a62eac2fbb
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 07:30:00+00:00
+----------------------------------------
+
+Instruction #210:
+ Timestep: 209
+ Time: 2025-11-06 07:35:00+00:00
+ ID: 649f00fa-8583-4631-be3d-2316780c9311
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 07:35:00+00:00
+----------------------------------------
+
+Instruction #211:
+ Timestep: 210
+ Time: 2025-11-06 07:40:00+00:00
+ ID: 52b3a9e2-c96a-4e01-8030-dea7cbca29bd
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 07:40:00+00:00
+----------------------------------------
+
+Instruction #212:
+ Timestep: 211
+ Time: 2025-11-06 07:45:00+00:00
+ ID: c0849362-503e-4b01-9ad8-a02c9f336599
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 07:45:00+00:00
+----------------------------------------
+
+Instruction #213:
+ Timestep: 212
+ Time: 2025-11-06 07:50:00+00:00
+ ID: b38fca6b-7460-46a7-b8f8-f9f241c9bafd
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 07:50:00+00:00
+----------------------------------------
+
+Instruction #214:
+ Timestep: 213
+ Time: 2025-11-06 07:55:00+00:00
+ ID: e3afcc3d-5e02-4a0d-8158-fb77c0ca509f
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 07:55:00+00:00
+----------------------------------------
+
+Instruction #215:
+ Timestep: 214
+ Time: 2025-11-06 08:00:00+00:00
+ ID: bfea6500-1470-41ef-9044-5e41a762b7fb
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 08:00:00+00:00
+----------------------------------------
+
+Instruction #216:
+ Timestep: 215
+ Time: 2025-11-06 08:05:00+00:00
+ ID: 05d7cccb-d4a6-4113-bd71-fb7943a467b7
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 08:05:00+00:00
+----------------------------------------
+
+Instruction #217:
+ Timestep: 216
+ Time: 2025-11-06 08:10:00+00:00
+ ID: d9bd37c8-94e5-46da-97a5-f521e0801aae
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 08:10:00+00:00
+----------------------------------------
+
+Instruction #218:
+ Timestep: 217
+ Time: 2025-11-06 08:15:00+00:00
+ ID: a29900fd-740b-46bb-87a7-8ae1b05e5057
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 08:15:00+00:00
+----------------------------------------
+
+Instruction #219:
+ Timestep: 218
+ Time: 2025-11-06 08:20:00+00:00
+ ID: 9341fa89-4798-490c-be37-31846f9e149b
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 08:20:00+00:00
+----------------------------------------
+
+Instruction #220:
+ Timestep: 219
+ Time: 2025-11-06 08:25:00+00:00
+ ID: 40ad3695-2aad-4947-8369-eab0c933f1b5
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 08:25:00+00:00
+----------------------------------------
+
+Instruction #221:
+ Timestep: 220
+ Time: 2025-11-06 08:30:00+00:00
+ ID: dd57595d-d860-44ef-adc6-b10a0aff7fe6
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 08:30:00+00:00
+----------------------------------------
+
+Instruction #222:
+ Timestep: 221
+ Time: 2025-11-06 08:35:00+00:00
+ ID: 7dee7c2f-6fc0-4098-b24b-cf6367b2be53
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 08:35:00+00:00
+----------------------------------------
+
+Instruction #223:
+ Timestep: 222
+ Time: 2025-11-06 08:40:00+00:00
+ ID: d0db5447-7585-43da-9a7a-b6bcc6307960
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 08:40:00+00:00
+----------------------------------------
+
+Instruction #224:
+ Timestep: 223
+ Time: 2025-11-06 08:45:00+00:00
+ ID: 5c832413-0dfa-4ef9-934b-eea951b7fe93
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 08:45:00+00:00
+----------------------------------------
+
+Instruction #225:
+ Timestep: 224
+ Time: 2025-11-06 08:50:00+00:00
+ ID: 3a4cdf54-5584-47b3-b2af-4179167a5596
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 08:50:00+00:00
+----------------------------------------
+
+Instruction #226:
+ Timestep: 225
+ Time: 2025-11-06 08:55:00+00:00
+ ID: 67c8f614-c930-4c31-a59e-fc488a0afb93
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 08:55:00+00:00
+----------------------------------------
+
+Instruction #227:
+ Timestep: 226
+ Time: 2025-11-06 09:00:00+00:00
+ ID: 5004bad1-8a64-49a2-8b8e-51a1199fcb82
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 09:00:00+00:00
+----------------------------------------
+
+Instruction #228:
+ Timestep: 227
+ Time: 2025-11-06 09:05:00+00:00
+ ID: bf584a0d-6d86-43b3-9302-50ab5f558d57
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 09:05:00+00:00
+----------------------------------------
+
+Instruction #229:
+ Timestep: 228
+ Time: 2025-11-06 09:10:00+00:00
+ ID: 943a3e62-e1e5-4a06-9b3c-da7b533fc7b2
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 09:10:00+00:00
+----------------------------------------
+
+Instruction #230:
+ Timestep: 229
+ Time: 2025-11-06 09:15:00+00:00
+ ID: 1a8b6ed6-bb39-4e3c-9266-b645a3b608ba
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 09:15:00+00:00
+----------------------------------------
+
+Instruction #231:
+ Timestep: 230
+ Time: 2025-11-06 09:20:00+00:00
+ ID: 1a6256da-3344-434c-bdb8-2364f0c4bfd1
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 09:20:00+00:00
+----------------------------------------
+
+Instruction #232:
+ Timestep: 231
+ Time: 2025-11-06 09:25:00+00:00
+ ID: 75ca5c13-4f8f-41b7-b0d1-671ab2c14045
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 09:25:00+00:00
+----------------------------------------
+
+Instruction #233:
+ Timestep: 232
+ Time: 2025-11-06 09:30:00+00:00
+ ID: 7bd3f610-7aeb-4a81-b1d3-098f68cc94e3
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 09:30:00+00:00
+----------------------------------------
+
+Instruction #234:
+ Timestep: 233
+ Time: 2025-11-06 09:35:00+00:00
+ ID: 3886258a-3e9c-405f-98ca-b464faf02482
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 09:35:00+00:00
+----------------------------------------
+
+Instruction #235:
+ Timestep: 234
+ Time: 2025-11-06 09:40:00+00:00
+ ID: fb48ab2e-748d-4a03-acf5-b045845a0b93
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 09:40:00+00:00
+----------------------------------------
+
+Instruction #236:
+ Timestep: 235
+ Time: 2025-11-06 09:45:00+00:00
+ ID: 61b9a444-2c4c-4422-99b6-843768eb4673
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 09:45:00+00:00
+----------------------------------------
+
+Instruction #237:
+ Timestep: 236
+ Time: 2025-11-06 09:50:00+00:00
+ ID: fffc1575-9c67-4e7b-810a-15d0b0239b63
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 09:50:00+00:00
+----------------------------------------
+
+Instruction #238:
+ Timestep: 237
+ Time: 2025-11-06 09:55:00+00:00
+ ID: 71ef5036-08de-4283-80d8-446a2770f82b
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 09:55:00+00:00
+----------------------------------------
+
+Instruction #239:
+ Timestep: 238
+ Time: 2025-11-06 10:00:00+00:00
+ ID: c8ff4eca-7337-49fe-bbdf-2ff36cce9021
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 10:00:00+00:00
+----------------------------------------
+
+Instruction #240:
+ Timestep: 239
+ Time: 2025-11-06 10:05:00+00:00
+ ID: 6537b669-7c97-43a2-b721-fb5d35c6928c
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 10:05:00+00:00
+----------------------------------------
+
+Instruction #241:
+ Timestep: 240
+ Time: 2025-11-06 10:10:00+00:00
+ ID: ce4fafa5-ecdb-4d9a-b790-c9374635b62e
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 10:10:00+00:00
+----------------------------------------
+
+Instruction #242:
+ Timestep: 241
+ Time: 2025-11-06 10:15:00+00:00
+ ID: 5517ce79-66de-4dc8-a99a-a29ddd0951e9
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 10:15:00+00:00
+----------------------------------------
+
+Instruction #243:
+ Timestep: 242
+ Time: 2025-11-06 10:20:00+00:00
+ ID: adc1e21e-d73d-480a-a0cc-2b63ba467c4e
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 10:20:00+00:00
+----------------------------------------
+
+Instruction #244:
+ Timestep: 243
+ Time: 2025-11-06 10:25:00+00:00
+ ID: 2076d7a3-5a00-4fbc-909d-eabba7335530
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 10:25:00+00:00
+----------------------------------------
+
+Instruction #245:
+ Timestep: 244
+ Time: 2025-11-06 10:30:00+00:00
+ ID: a6417141-3e92-46c6-8aa3-fc3d7f83c1a1
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 10:30:00+00:00
+----------------------------------------
+
+Instruction #246:
+ Timestep: 245
+ Time: 2025-11-06 10:35:00+00:00
+ ID: d23b12f2-9600-40d6-ba24-698dfe0c4507
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 10:35:00+00:00
+----------------------------------------
+
+Instruction #247:
+ Timestep: 246
+ Time: 2025-11-06 10:40:00+00:00
+ ID: 8787d207-75a3-4808-a5c5-1ab6f64156ef
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 10:40:00+00:00
+----------------------------------------
+
+Instruction #248:
+ Timestep: 247
+ Time: 2025-11-06 10:45:00+00:00
+ ID: ff0d7a4d-b44a-44d7-b826-87d10b6ee8f5
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 10:45:00+00:00
+----------------------------------------
+
+Instruction #249:
+ Timestep: 248
+ Time: 2025-11-06 10:50:00+00:00
+ ID: 723e1ab3-72b1-45d2-a209-7dc58b3957b3
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 10:50:00+00:00
+----------------------------------------
+
+Instruction #250:
+ Timestep: 249
+ Time: 2025-11-06 10:55:00+00:00
+ ID: 67d70eb2-e77b-40ce-9136-975545373dae
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 10:55:00+00:00
+----------------------------------------
+
+Instruction #251:
+ Timestep: 250
+ Time: 2025-11-06 11:00:00+00:00
+ ID: 0a67383a-9d7f-4007-869f-3128873dc484
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 11:00:00+00:00
+----------------------------------------
+
+Instruction #252:
+ Timestep: 251
+ Time: 2025-11-06 11:05:00+00:00
+ ID: 9a3beea4-43a2-4d6d-ac97-d8c42cac73d1
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 11:05:00+00:00
+----------------------------------------
+
+Instruction #253:
+ Timestep: 252
+ Time: 2025-11-06 11:10:00+00:00
+ ID: 40859fc5-07e9-4ac3-80f0-513f35bc1b8d
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 11:10:00+00:00
+----------------------------------------
+
+Instruction #254:
+ Timestep: 253
+ Time: 2025-11-06 11:15:00+00:00
+ ID: f31a5ba0-8601-443c-8abe-17b113d055bc
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 11:15:00+00:00
+----------------------------------------
+
+Instruction #255:
+ Timestep: 254
+ Time: 2025-11-06 11:20:00+00:00
+ ID: f479043d-1a0b-4769-9c67-8a585e4445d6
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 11:20:00+00:00
+----------------------------------------
+
+Instruction #256:
+ Timestep: 255
+ Time: 2025-11-06 11:25:00+00:00
+ ID: 96d996df-fb76-4e78-a211-6871a7520530
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 11:25:00+00:00
+----------------------------------------
+
+Instruction #257:
+ Timestep: 256
+ Time: 2025-11-06 11:30:00+00:00
+ ID: 71c5cf45-5b9c-4d27-aaf0-914b3e4c66cd
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 11:30:00+00:00
+----------------------------------------
+
+Instruction #258:
+ Timestep: 257
+ Time: 2025-11-06 11:35:00+00:00
+ ID: e36688b2-94f3-41f0-a213-199dfb0d3014
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 11:35:00+00:00
+----------------------------------------
+
+Instruction #259:
+ Timestep: 258
+ Time: 2025-11-06 11:40:00+00:00
+ ID: 8bfab8d2-32d6-4724-be46-57e7cef0ddb6
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 11:40:00+00:00
+----------------------------------------
+
+Instruction #260:
+ Timestep: 259
+ Time: 2025-11-06 11:45:00+00:00
+ ID: 88de47e9-aa5c-4d2f-af95-8b266bd7578c
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 11:45:00+00:00
+----------------------------------------
+
+Instruction #261:
+ Timestep: 260
+ Time: 2025-11-06 11:50:00+00:00
+ ID: fba6b767-8f80-4f14-ba52-9e64affa96b7
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 11:50:00+00:00
+----------------------------------------
+
+Instruction #262:
+ Timestep: 261
+ Time: 2025-11-06 11:55:00+00:00
+ ID: 5eac828e-9d59-410f-bc42-cf7601fffb67
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 11:55:00+00:00
+----------------------------------------
+
+Instruction #263:
+ Timestep: 262
+ Time: 2025-11-06 12:00:00+00:00
+ ID: 87270fb2-7a59-4089-bc03-6f3f7e649d15
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 12:00:00+00:00
+----------------------------------------
+
+Instruction #264:
+ Timestep: 263
+ Time: 2025-11-06 12:05:00+00:00
+ ID: ed3f690a-39ce-4d8b-a203-14a242c0d54d
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 12:05:00+00:00
+----------------------------------------
+
+Instruction #265:
+ Timestep: 264
+ Time: 2025-11-06 12:10:00+00:00
+ ID: 775b15cf-e945-4c3d-9664-75cedc515825
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 12:10:00+00:00
+----------------------------------------
+
+Instruction #266:
+ Timestep: 265
+ Time: 2025-11-06 12:15:00+00:00
+ ID: 1ba0db26-b5ac-4233-9892-0a183c07ed60
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 12:15:00+00:00
+----------------------------------------
+
+Instruction #267:
+ Timestep: 266
+ Time: 2025-11-06 12:20:00+00:00
+ ID: 5150a558-1d0b-4eec-80e6-b01d9c96c59f
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 12:20:00+00:00
+----------------------------------------
+
+Instruction #268:
+ Timestep: 267
+ Time: 2025-11-06 12:25:00+00:00
+ ID: 00487922-a65f-4c67-9657-a69459d19eb7
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 12:25:00+00:00
+----------------------------------------
+
+Instruction #269:
+ Timestep: 268
+ Time: 2025-11-06 12:30:00+00:00
+ ID: 53b11f96-f09d-4304-903d-67de39e6d260
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 12:30:00+00:00
+----------------------------------------
+
+Instruction #270:
+ Timestep: 269
+ Time: 2025-11-06 12:35:00+00:00
+ ID: a9f5dcc8-1c19-46bf-8a84-9b050e07b560
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 12:35:00+00:00
+----------------------------------------
+
+Instruction #271:
+ Timestep: 270
+ Time: 2025-11-06 12:40:00+00:00
+ ID: 7c7cc8d4-7534-423f-b06f-9e4cd7d78fdb
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 12:40:00+00:00
+----------------------------------------
+
+Instruction #272:
+ Timestep: 271
+ Time: 2025-11-06 12:45:00+00:00
+ ID: 580b8807-2df6-4ebd-9335-f84b61c0f1da
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 12:45:00+00:00
+----------------------------------------
+
+Instruction #273:
+ Timestep: 272
+ Time: 2025-11-06 12:50:00+00:00
+ ID: b54b3759-d6a6-43cc-942a-e44a277d74f4
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 12:50:00+00:00
+----------------------------------------
+
+Instruction #274:
+ Timestep: 273
+ Time: 2025-11-06 12:55:00+00:00
+ ID: d9150ee8-9d33-47ab-9a6d-42066263c1da
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 12:55:00+00:00
+----------------------------------------
+
+Instruction #275:
+ Timestep: 274
+ Time: 2025-11-06 13:00:00+00:00
+ ID: 9266f5e2-537d-4f55-9eca-109e4067b6e7
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 13:00:00+00:00
+----------------------------------------
+
+Instruction #276:
+ Timestep: 275
+ Time: 2025-11-06 13:05:00+00:00
+ ID: ad93e9b7-f793-4207-8633-8ddd1225d61b
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 13:05:00+00:00
+----------------------------------------
+
+Instruction #277:
+ Timestep: 276
+ Time: 2025-11-06 13:10:00+00:00
+ ID: c10943db-17d6-4f77-b6fe-a072f7098554
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 13:10:00+00:00
+----------------------------------------
+
+Instruction #278:
+ Timestep: 277
+ Time: 2025-11-06 13:15:00+00:00
+ ID: 2b45be86-a0d4-4a8a-ab8f-848c304d220b
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 13:15:00+00:00
+----------------------------------------
+
+Instruction #279:
+ Timestep: 278
+ Time: 2025-11-06 13:20:00+00:00
+ ID: ca4bd560-3319-4c84-b411-ee0137634b69
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 13:20:00+00:00
+----------------------------------------
+
+Instruction #280:
+ Timestep: 279
+ Time: 2025-11-06 13:25:00+00:00
+ ID: e5d0e58e-30ba-4e71-b969-10d0e9ed84f5
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 13:25:00+00:00
+----------------------------------------
+
+Instruction #281:
+ Timestep: 280
+ Time: 2025-11-06 13:30:00+00:00
+ ID: 83e18688-a642-4d8f-8914-7f1cb2212092
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 13:30:00+00:00
+----------------------------------------
+
+Instruction #282:
+ Timestep: 281
+ Time: 2025-11-06 13:35:00+00:00
+ ID: 36b577a5-0971-4dc2-a3be-e364b305ae86
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 13:35:00+00:00
+----------------------------------------
+
+Instruction #283:
+ Timestep: 282
+ Time: 2025-11-06 13:40:00+00:00
+ ID: a8bdf3b8-0ba8-450f-ac9d-54c7c0bfa2ae
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 13:40:00+00:00
+----------------------------------------
+
+Instruction #284:
+ Timestep: 283
+ Time: 2025-11-06 13:45:00+00:00
+ ID: 2cdcdf7f-4428-45ef-b021-f336768d6a76
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 13:45:00+00:00
+----------------------------------------
+
+Instruction #285:
+ Timestep: 284
+ Time: 2025-11-06 13:50:00+00:00
+ ID: 86f52287-9782-4e2d-a86e-a4a8b501c844
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 13:50:00+00:00
+----------------------------------------
+
+Instruction #286:
+ Timestep: 285
+ Time: 2025-11-06 13:55:00+00:00
+ ID: a725b018-5dce-4fdd-a476-53f583a5f0db
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 13:55:00+00:00
+----------------------------------------
+
+Instruction #287:
+ Timestep: 286
+ Time: 2025-11-06 14:00:00+00:00
+ ID: af427759-40ae-4e32-9acc-caead28d50fc
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 14:00:00+00:00
+----------------------------------------
+
+Instruction #288:
+ Timestep: 287
+ Time: 2025-11-06 14:05:00+00:00
+ ID: 0f5d6517-b80f-4616-89f1-4b54086c1dab
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-06 14:05:00+00:00
+----------------------------------------
diff --git a/flexmeasures_s2/profile_steering/examples/instructions/itho_instructions_energy_D=1_B=200_S=30_T=288.txt b/flexmeasures_s2/profile_steering/examples/instructions/itho_instructions_energy_D=1_B=200_S=30_T=288.txt
new file mode 100644
index 0000000..2043c5b
--- /dev/null
+++ b/flexmeasures_s2/profile_steering/examples/instructions/itho_instructions_energy_D=1_B=200_S=30_T=288.txt
@@ -0,0 +1,3218 @@
+================================================================================
+ITHO DHW Heat Pump Instructions Energy
+================================================================================
+Planning start time: 2025-11-03 10:30:00+00:00
+Planning window: 1 days 00:00:00
+Resolution: 0 days 00:05:00
+Total timesteps: 288
+================================================================================
+
+Device: itho_dhw_device_001
+Number of instructions: 288
+--------------------------------------------------------------------------------
+
+Instruction Timeline:
+--------------------------------------------------------------------------------
+ Mode: DHW_ON
+ Start: 2025-11-03 10:30:00+00:00 (timestep 0)
+ Duration: 17 timesteps (85.0 minutes)
+ Operation mode factor: 0.0
+----------------------------------------
+ Mode: DHW_OFF
+ Start: 2025-11-03 11:55:00+00:00 (timestep 17)
+ Duration: 8 timesteps (40.0 minutes)
+ Operation mode factor: 0.0
+----------------------------------------
+ Mode: DHW_ON
+ Start: 2025-11-03 12:35:00+00:00 (timestep 25)
+ Duration: 6 timesteps (30.0 minutes)
+ Operation mode factor: 0.0
+----------------------------------------
+ Mode: DHW_OFF
+ Start: 2025-11-03 13:05:00+00:00 (timestep 31)
+ Duration: 131 timesteps (655.0 minutes)
+ Operation mode factor: 0.0
+----------------------------------------
+ Mode: DHW_ON
+ Start: 2025-11-04 00:00:00+00:00 (timestep 162)
+ Duration: 72 timesteps (360.0 minutes)
+ Operation mode factor: 0.0
+----------------------------------------
+ Mode: DHW_OFF
+ Start: 2025-11-04 06:00:00+00:00 (timestep 234)
+ Duration: 54 timesteps (270.0 minutes)
+----------------------------------------
+
+Total operation mode changes: 5
+================================================================================
+
+Detailed Instruction List:
+================================================================================
+
+Instruction #1:
+ Timestep: 0
+ Time: 2025-11-03 10:30:00+00:00
+ ID: 7400f900-b57a-4b2e-9b65-317490543af5
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 10:30:00+00:00
+----------------------------------------
+
+Instruction #2:
+ Timestep: 1
+ Time: 2025-11-03 10:35:00+00:00
+ ID: 952cb0f9-ceb8-4cd1-bfdd-227c889eb324
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 10:35:00+00:00
+----------------------------------------
+
+Instruction #3:
+ Timestep: 2
+ Time: 2025-11-03 10:40:00+00:00
+ ID: e4e8d856-006e-4687-aca0-c1322950ced9
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 10:40:00+00:00
+----------------------------------------
+
+Instruction #4:
+ Timestep: 3
+ Time: 2025-11-03 10:45:00+00:00
+ ID: e2d19ae2-6029-41fd-8f6d-b43b41dd334d
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 10:45:00+00:00
+----------------------------------------
+
+Instruction #5:
+ Timestep: 4
+ Time: 2025-11-03 10:50:00+00:00
+ ID: 6b6a92b8-3b84-43a2-8cd7-8f0d06323244
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 10:50:00+00:00
+----------------------------------------
+
+Instruction #6:
+ Timestep: 5
+ Time: 2025-11-03 10:55:00+00:00
+ ID: 7258f35d-8185-4551-b140-cef4a665b8ed
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 10:55:00+00:00
+----------------------------------------
+
+Instruction #7:
+ Timestep: 6
+ Time: 2025-11-03 11:00:00+00:00
+ ID: fa8ff845-3c14-4b1f-a327-c56c15660801
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 11:00:00+00:00
+----------------------------------------
+
+Instruction #8:
+ Timestep: 7
+ Time: 2025-11-03 11:05:00+00:00
+ ID: a14edb23-72c4-436a-bfc2-1e9f47b01fab
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 11:05:00+00:00
+----------------------------------------
+
+Instruction #9:
+ Timestep: 8
+ Time: 2025-11-03 11:10:00+00:00
+ ID: 16d68d00-1d0a-47a4-9c3f-b218b1d1d35a
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 11:10:00+00:00
+----------------------------------------
+
+Instruction #10:
+ Timestep: 9
+ Time: 2025-11-03 11:15:00+00:00
+ ID: fe0f6882-9ea0-4b84-9e65-af34bd9336ef
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 11:15:00+00:00
+----------------------------------------
+
+Instruction #11:
+ Timestep: 10
+ Time: 2025-11-03 11:20:00+00:00
+ ID: 2e7ece7c-41e3-4940-8946-ae80566aff0a
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 11:20:00+00:00
+----------------------------------------
+
+Instruction #12:
+ Timestep: 11
+ Time: 2025-11-03 11:25:00+00:00
+ ID: 8ea5bb8b-e826-4376-b227-358230ed3a08
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 11:25:00+00:00
+----------------------------------------
+
+Instruction #13:
+ Timestep: 12
+ Time: 2025-11-03 11:30:00+00:00
+ ID: ededad99-009f-4a76-ae93-9e2d1c7d2bd7
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 11:30:00+00:00
+----------------------------------------
+
+Instruction #14:
+ Timestep: 13
+ Time: 2025-11-03 11:35:00+00:00
+ ID: c8afa93f-6083-4076-91d0-0c40f57eb5d5
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 11:35:00+00:00
+----------------------------------------
+
+Instruction #15:
+ Timestep: 14
+ Time: 2025-11-03 11:40:00+00:00
+ ID: 1cc470ad-5ae4-47a5-bcf3-8228b3cbd656
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 11:40:00+00:00
+----------------------------------------
+
+Instruction #16:
+ Timestep: 15
+ Time: 2025-11-03 11:45:00+00:00
+ ID: b0afd846-3afc-41e3-80b1-7fd469c6e10d
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 11:45:00+00:00
+----------------------------------------
+
+Instruction #17:
+ Timestep: 16
+ Time: 2025-11-03 11:50:00+00:00
+ ID: df035f0c-d462-4ced-ab12-4d56f46c454e
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 11:50:00+00:00
+----------------------------------------
+
+Instruction #18:
+ Timestep: 17
+ Time: 2025-11-03 11:55:00+00:00
+ ID: 243c7c3e-0897-4183-99e8-f3fc3889f51c
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 11:55:00+00:00
+----------------------------------------
+
+Instruction #19:
+ Timestep: 18
+ Time: 2025-11-03 12:00:00+00:00
+ ID: 81e14785-c224-46da-b33f-7eb6b83c24e8
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 12:00:00+00:00
+----------------------------------------
+
+Instruction #20:
+ Timestep: 19
+ Time: 2025-11-03 12:05:00+00:00
+ ID: c1e45c82-8663-4d96-845a-db966663f0c5
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 12:05:00+00:00
+----------------------------------------
+
+Instruction #21:
+ Timestep: 20
+ Time: 2025-11-03 12:10:00+00:00
+ ID: 1c2d6ccc-b26c-4d7d-a31f-42ad88a829c3
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 12:10:00+00:00
+----------------------------------------
+
+Instruction #22:
+ Timestep: 21
+ Time: 2025-11-03 12:15:00+00:00
+ ID: 3b378056-13e6-4ab2-93c0-fdb9b322fd5b
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 12:15:00+00:00
+----------------------------------------
+
+Instruction #23:
+ Timestep: 22
+ Time: 2025-11-03 12:20:00+00:00
+ ID: b67b4bb2-5e0a-4801-9a83-87a4fe171deb
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 12:20:00+00:00
+----------------------------------------
+
+Instruction #24:
+ Timestep: 23
+ Time: 2025-11-03 12:25:00+00:00
+ ID: 321cc200-59bf-488a-82ff-b4e3d73ee375
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 12:25:00+00:00
+----------------------------------------
+
+Instruction #25:
+ Timestep: 24
+ Time: 2025-11-03 12:30:00+00:00
+ ID: b77b1d38-b116-4810-aad8-da1e8d61cd89
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 12:30:00+00:00
+----------------------------------------
+
+Instruction #26:
+ Timestep: 25
+ Time: 2025-11-03 12:35:00+00:00
+ ID: 4ee267d9-65ee-4197-bc3f-8378e3110f60
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 12:35:00+00:00
+----------------------------------------
+
+Instruction #27:
+ Timestep: 26
+ Time: 2025-11-03 12:40:00+00:00
+ ID: 72e3a38f-5c15-491e-b7fa-972b15652a65
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 12:40:00+00:00
+----------------------------------------
+
+Instruction #28:
+ Timestep: 27
+ Time: 2025-11-03 12:45:00+00:00
+ ID: 68da5b2e-ef90-49b0-9193-f1377da3ca3f
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 12:45:00+00:00
+----------------------------------------
+
+Instruction #29:
+ Timestep: 28
+ Time: 2025-11-03 12:50:00+00:00
+ ID: 5cc1953d-be4a-4492-a003-c47ecbf92df6
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 12:50:00+00:00
+----------------------------------------
+
+Instruction #30:
+ Timestep: 29
+ Time: 2025-11-03 12:55:00+00:00
+ ID: 79265bac-e7d9-4407-964a-3f62d36615d3
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 12:55:00+00:00
+----------------------------------------
+
+Instruction #31:
+ Timestep: 30
+ Time: 2025-11-03 13:00:00+00:00
+ ID: a65510a4-7ce6-480a-b55a-66c93d36b075
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 13:00:00+00:00
+----------------------------------------
+
+Instruction #32:
+ Timestep: 31
+ Time: 2025-11-03 13:05:00+00:00
+ ID: 6b3e62b2-18df-4c4e-8b51-eade9e67e796
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 13:05:00+00:00
+----------------------------------------
+
+Instruction #33:
+ Timestep: 32
+ Time: 2025-11-03 13:10:00+00:00
+ ID: 3684e158-ee43-4390-b3d3-213f81625eca
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 13:10:00+00:00
+----------------------------------------
+
+Instruction #34:
+ Timestep: 33
+ Time: 2025-11-03 13:15:00+00:00
+ ID: 82f975bf-60e1-4cde-817c-4eae4fc54cd3
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 13:15:00+00:00
+----------------------------------------
+
+Instruction #35:
+ Timestep: 34
+ Time: 2025-11-03 13:20:00+00:00
+ ID: 535e3392-8ffb-4951-852e-6fd936b3c0fe
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 13:20:00+00:00
+----------------------------------------
+
+Instruction #36:
+ Timestep: 35
+ Time: 2025-11-03 13:25:00+00:00
+ ID: 304cd113-59b9-488f-9c8b-ed95481a1415
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 13:25:00+00:00
+----------------------------------------
+
+Instruction #37:
+ Timestep: 36
+ Time: 2025-11-03 13:30:00+00:00
+ ID: b2796a84-55fa-47ab-82ab-fcde395abb01
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 13:30:00+00:00
+----------------------------------------
+
+Instruction #38:
+ Timestep: 37
+ Time: 2025-11-03 13:35:00+00:00
+ ID: cf6ea147-47dc-40c4-b87d-c41c3ac46dae
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 13:35:00+00:00
+----------------------------------------
+
+Instruction #39:
+ Timestep: 38
+ Time: 2025-11-03 13:40:00+00:00
+ ID: 839999f7-ee86-49f1-802f-ff09ff1559ee
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 13:40:00+00:00
+----------------------------------------
+
+Instruction #40:
+ Timestep: 39
+ Time: 2025-11-03 13:45:00+00:00
+ ID: 2d16fe32-f0a3-426f-8484-e5449b05a3f6
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 13:45:00+00:00
+----------------------------------------
+
+Instruction #41:
+ Timestep: 40
+ Time: 2025-11-03 13:50:00+00:00
+ ID: 98385695-1ba6-46d5-9dc5-98181c15a3e3
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 13:50:00+00:00
+----------------------------------------
+
+Instruction #42:
+ Timestep: 41
+ Time: 2025-11-03 13:55:00+00:00
+ ID: dea4710e-854a-4ca8-b7d7-3bd9d643cbce
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 13:55:00+00:00
+----------------------------------------
+
+Instruction #43:
+ Timestep: 42
+ Time: 2025-11-03 14:00:00+00:00
+ ID: 9faf4b3a-3721-46da-98b6-903beb61f2da
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 14:00:00+00:00
+----------------------------------------
+
+Instruction #44:
+ Timestep: 43
+ Time: 2025-11-03 14:05:00+00:00
+ ID: 5da54143-e84f-4b54-ac9b-fb199d6e28fc
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 14:05:00+00:00
+----------------------------------------
+
+Instruction #45:
+ Timestep: 44
+ Time: 2025-11-03 14:10:00+00:00
+ ID: 5034652f-4188-4fae-b296-d470fb4a9d2a
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 14:10:00+00:00
+----------------------------------------
+
+Instruction #46:
+ Timestep: 45
+ Time: 2025-11-03 14:15:00+00:00
+ ID: 779fe68e-af66-4224-883c-d30be4169d72
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 14:15:00+00:00
+----------------------------------------
+
+Instruction #47:
+ Timestep: 46
+ Time: 2025-11-03 14:20:00+00:00
+ ID: a26977e6-c088-406e-b2d2-be7879e854ed
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 14:20:00+00:00
+----------------------------------------
+
+Instruction #48:
+ Timestep: 47
+ Time: 2025-11-03 14:25:00+00:00
+ ID: 56d79c4c-9e79-459b-bd5e-4ca9c577ea9a
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 14:25:00+00:00
+----------------------------------------
+
+Instruction #49:
+ Timestep: 48
+ Time: 2025-11-03 14:30:00+00:00
+ ID: 7c4bf6f5-f4b1-4174-9f74-77373121c5c6
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 14:30:00+00:00
+----------------------------------------
+
+Instruction #50:
+ Timestep: 49
+ Time: 2025-11-03 14:35:00+00:00
+ ID: 681911da-ae32-4318-a16d-2a802946a44a
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 14:35:00+00:00
+----------------------------------------
+
+Instruction #51:
+ Timestep: 50
+ Time: 2025-11-03 14:40:00+00:00
+ ID: f178340a-a540-4b4a-9c4d-7f709f6a837a
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 14:40:00+00:00
+----------------------------------------
+
+Instruction #52:
+ Timestep: 51
+ Time: 2025-11-03 14:45:00+00:00
+ ID: 59664dd5-792c-4876-9262-9dd9f03a1edf
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 14:45:00+00:00
+----------------------------------------
+
+Instruction #53:
+ Timestep: 52
+ Time: 2025-11-03 14:50:00+00:00
+ ID: 42e58335-384a-41de-8fba-995569a7ea0c
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 14:50:00+00:00
+----------------------------------------
+
+Instruction #54:
+ Timestep: 53
+ Time: 2025-11-03 14:55:00+00:00
+ ID: 819c34c4-df42-4888-867a-2c6f76367943
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 14:55:00+00:00
+----------------------------------------
+
+Instruction #55:
+ Timestep: 54
+ Time: 2025-11-03 15:00:00+00:00
+ ID: 629dca3f-d7b3-4024-8aeb-95dec8b3e359
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 15:00:00+00:00
+----------------------------------------
+
+Instruction #56:
+ Timestep: 55
+ Time: 2025-11-03 15:05:00+00:00
+ ID: 87c47d29-3c88-433d-93fb-de16715defc3
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 15:05:00+00:00
+----------------------------------------
+
+Instruction #57:
+ Timestep: 56
+ Time: 2025-11-03 15:10:00+00:00
+ ID: 49826e33-969b-4d86-ba20-848c5e7e486c
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 15:10:00+00:00
+----------------------------------------
+
+Instruction #58:
+ Timestep: 57
+ Time: 2025-11-03 15:15:00+00:00
+ ID: ddff1350-f8ea-4322-89fc-4a06f5dcc5b3
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 15:15:00+00:00
+----------------------------------------
+
+Instruction #59:
+ Timestep: 58
+ Time: 2025-11-03 15:20:00+00:00
+ ID: f9aecb24-08f6-4354-afa5-5955b0cad052
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 15:20:00+00:00
+----------------------------------------
+
+Instruction #60:
+ Timestep: 59
+ Time: 2025-11-03 15:25:00+00:00
+ ID: f198c285-b014-4cc9-a005-2ec0b20f855d
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 15:25:00+00:00
+----------------------------------------
+
+Instruction #61:
+ Timestep: 60
+ Time: 2025-11-03 15:30:00+00:00
+ ID: 06398626-ee5c-43fe-b53d-2b144b58f395
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 15:30:00+00:00
+----------------------------------------
+
+Instruction #62:
+ Timestep: 61
+ Time: 2025-11-03 15:35:00+00:00
+ ID: 920abba6-48f5-42d8-b75f-7f1569ea2424
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 15:35:00+00:00
+----------------------------------------
+
+Instruction #63:
+ Timestep: 62
+ Time: 2025-11-03 15:40:00+00:00
+ ID: 88c2227b-dfc1-411c-b9ea-1e3e72c61471
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 15:40:00+00:00
+----------------------------------------
+
+Instruction #64:
+ Timestep: 63
+ Time: 2025-11-03 15:45:00+00:00
+ ID: 0e83acb9-75ff-4683-9c9e-8bf98dbf9d6e
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 15:45:00+00:00
+----------------------------------------
+
+Instruction #65:
+ Timestep: 64
+ Time: 2025-11-03 15:50:00+00:00
+ ID: 9003d4dc-b9da-4a47-b426-fb58f5f12dc4
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 15:50:00+00:00
+----------------------------------------
+
+Instruction #66:
+ Timestep: 65
+ Time: 2025-11-03 15:55:00+00:00
+ ID: d7ffcb24-73b0-4324-8a64-15104cf19f7a
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 15:55:00+00:00
+----------------------------------------
+
+Instruction #67:
+ Timestep: 66
+ Time: 2025-11-03 16:00:00+00:00
+ ID: dfe73b88-68dd-4614-9971-b41ac055f172
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 16:00:00+00:00
+----------------------------------------
+
+Instruction #68:
+ Timestep: 67
+ Time: 2025-11-03 16:05:00+00:00
+ ID: e170163b-6931-4be7-a176-dcbca70ad1cf
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 16:05:00+00:00
+----------------------------------------
+
+Instruction #69:
+ Timestep: 68
+ Time: 2025-11-03 16:10:00+00:00
+ ID: 074ce0d1-dd61-48ce-9239-a81aaef88000
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 16:10:00+00:00
+----------------------------------------
+
+Instruction #70:
+ Timestep: 69
+ Time: 2025-11-03 16:15:00+00:00
+ ID: fa1834a5-ba7c-4ffa-82fd-d99769d98787
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 16:15:00+00:00
+----------------------------------------
+
+Instruction #71:
+ Timestep: 70
+ Time: 2025-11-03 16:20:00+00:00
+ ID: 5017cc8e-9b53-4b4a-a940-5598f676fc85
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 16:20:00+00:00
+----------------------------------------
+
+Instruction #72:
+ Timestep: 71
+ Time: 2025-11-03 16:25:00+00:00
+ ID: 1f313835-83d0-4729-922a-381f285ab3fa
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 16:25:00+00:00
+----------------------------------------
+
+Instruction #73:
+ Timestep: 72
+ Time: 2025-11-03 16:30:00+00:00
+ ID: 97a0014e-badb-41d3-9f99-84890d578e35
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 16:30:00+00:00
+----------------------------------------
+
+Instruction #74:
+ Timestep: 73
+ Time: 2025-11-03 16:35:00+00:00
+ ID: 98e69c11-b876-4619-b417-ff97bbb11973
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 16:35:00+00:00
+----------------------------------------
+
+Instruction #75:
+ Timestep: 74
+ Time: 2025-11-03 16:40:00+00:00
+ ID: 89647b11-a5f7-4574-b560-a18832d657bd
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 16:40:00+00:00
+----------------------------------------
+
+Instruction #76:
+ Timestep: 75
+ Time: 2025-11-03 16:45:00+00:00
+ ID: c7a6a8e5-abe8-47f1-8283-2e7b50551507
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 16:45:00+00:00
+----------------------------------------
+
+Instruction #77:
+ Timestep: 76
+ Time: 2025-11-03 16:50:00+00:00
+ ID: e079a978-0deb-4d69-879a-af66b086224f
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 16:50:00+00:00
+----------------------------------------
+
+Instruction #78:
+ Timestep: 77
+ Time: 2025-11-03 16:55:00+00:00
+ ID: 8855f96c-39d4-4e12-9435-263e77a7c607
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 16:55:00+00:00
+----------------------------------------
+
+Instruction #79:
+ Timestep: 78
+ Time: 2025-11-03 17:00:00+00:00
+ ID: a5638577-f052-4f75-ae97-dc4216f9a5bb
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 17:00:00+00:00
+----------------------------------------
+
+Instruction #80:
+ Timestep: 79
+ Time: 2025-11-03 17:05:00+00:00
+ ID: 572df723-5956-4585-8ac8-255b8de86b59
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 17:05:00+00:00
+----------------------------------------
+
+Instruction #81:
+ Timestep: 80
+ Time: 2025-11-03 17:10:00+00:00
+ ID: 12c5b6a3-4eb9-4411-8900-bc93171b8198
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 17:10:00+00:00
+----------------------------------------
+
+Instruction #82:
+ Timestep: 81
+ Time: 2025-11-03 17:15:00+00:00
+ ID: d240ac15-40d3-46b3-9f35-cc46e98e0be5
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 17:15:00+00:00
+----------------------------------------
+
+Instruction #83:
+ Timestep: 82
+ Time: 2025-11-03 17:20:00+00:00
+ ID: ecf75dd7-9646-4391-a7f7-375221185b3b
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 17:20:00+00:00
+----------------------------------------
+
+Instruction #84:
+ Timestep: 83
+ Time: 2025-11-03 17:25:00+00:00
+ ID: 52d4551b-721b-4d69-90d2-3b9ce068b6e5
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 17:25:00+00:00
+----------------------------------------
+
+Instruction #85:
+ Timestep: 84
+ Time: 2025-11-03 17:30:00+00:00
+ ID: 72bae77b-a316-43d7-a698-34a1d494faa2
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 17:30:00+00:00
+----------------------------------------
+
+Instruction #86:
+ Timestep: 85
+ Time: 2025-11-03 17:35:00+00:00
+ ID: 2c6c1f60-43d1-4e29-a020-c83dbaf62f89
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 17:35:00+00:00
+----------------------------------------
+
+Instruction #87:
+ Timestep: 86
+ Time: 2025-11-03 17:40:00+00:00
+ ID: e0e8f020-f10b-4d6b-9fd4-151f1ea65002
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 17:40:00+00:00
+----------------------------------------
+
+Instruction #88:
+ Timestep: 87
+ Time: 2025-11-03 17:45:00+00:00
+ ID: 86871f59-a99a-45d0-a026-6439e04562bf
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 17:45:00+00:00
+----------------------------------------
+
+Instruction #89:
+ Timestep: 88
+ Time: 2025-11-03 17:50:00+00:00
+ ID: 33b67b16-1802-48da-aa2e-569231b4d414
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 17:50:00+00:00
+----------------------------------------
+
+Instruction #90:
+ Timestep: 89
+ Time: 2025-11-03 17:55:00+00:00
+ ID: 154c9045-63a0-46a8-8d6c-e12df36dde77
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 17:55:00+00:00
+----------------------------------------
+
+Instruction #91:
+ Timestep: 90
+ Time: 2025-11-03 18:00:00+00:00
+ ID: 5b76b5ea-4b80-4903-9412-b78d2fefef49
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 18:00:00+00:00
+----------------------------------------
+
+Instruction #92:
+ Timestep: 91
+ Time: 2025-11-03 18:05:00+00:00
+ ID: 9faba6b5-7b8f-4354-9e81-eaadd9324ac1
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 18:05:00+00:00
+----------------------------------------
+
+Instruction #93:
+ Timestep: 92
+ Time: 2025-11-03 18:10:00+00:00
+ ID: bd26fea2-045e-41c3-9f82-72e868714fc9
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 18:10:00+00:00
+----------------------------------------
+
+Instruction #94:
+ Timestep: 93
+ Time: 2025-11-03 18:15:00+00:00
+ ID: 7ef2b4d6-7f4c-4c3b-b673-a08d968a1699
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 18:15:00+00:00
+----------------------------------------
+
+Instruction #95:
+ Timestep: 94
+ Time: 2025-11-03 18:20:00+00:00
+ ID: a772bbcb-4389-47d2-b3a0-a1b754706c9a
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 18:20:00+00:00
+----------------------------------------
+
+Instruction #96:
+ Timestep: 95
+ Time: 2025-11-03 18:25:00+00:00
+ ID: f47e70a0-c429-4b57-b310-04d574308aab
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 18:25:00+00:00
+----------------------------------------
+
+Instruction #97:
+ Timestep: 96
+ Time: 2025-11-03 18:30:00+00:00
+ ID: 6eacdedf-d376-4829-9b47-0f637b0d86fe
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 18:30:00+00:00
+----------------------------------------
+
+Instruction #98:
+ Timestep: 97
+ Time: 2025-11-03 18:35:00+00:00
+ ID: 96add9b2-f3e6-4689-9bb7-e72038191c91
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 18:35:00+00:00
+----------------------------------------
+
+Instruction #99:
+ Timestep: 98
+ Time: 2025-11-03 18:40:00+00:00
+ ID: 79a5e4fd-2868-4200-a949-449b08b7f7de
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 18:40:00+00:00
+----------------------------------------
+
+Instruction #100:
+ Timestep: 99
+ Time: 2025-11-03 18:45:00+00:00
+ ID: 01cea868-18eb-4752-8032-6e6c1a1c7405
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 18:45:00+00:00
+----------------------------------------
+
+Instruction #101:
+ Timestep: 100
+ Time: 2025-11-03 18:50:00+00:00
+ ID: c2dbde31-fda9-4a71-ab41-4b5754944932
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 18:50:00+00:00
+----------------------------------------
+
+Instruction #102:
+ Timestep: 101
+ Time: 2025-11-03 18:55:00+00:00
+ ID: c189165c-d37d-4bff-8136-976927bdc1ea
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 18:55:00+00:00
+----------------------------------------
+
+Instruction #103:
+ Timestep: 102
+ Time: 2025-11-03 19:00:00+00:00
+ ID: a9ac2cbe-31a3-4947-ad84-2eba74d991bb
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 19:00:00+00:00
+----------------------------------------
+
+Instruction #104:
+ Timestep: 103
+ Time: 2025-11-03 19:05:00+00:00
+ ID: a39dba22-b038-463a-a6dd-ef53859f7ee3
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 19:05:00+00:00
+----------------------------------------
+
+Instruction #105:
+ Timestep: 104
+ Time: 2025-11-03 19:10:00+00:00
+ ID: 7ec502b4-45a2-4536-8c3d-246d4fc46b75
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 19:10:00+00:00
+----------------------------------------
+
+Instruction #106:
+ Timestep: 105
+ Time: 2025-11-03 19:15:00+00:00
+ ID: 7f7135c8-a13e-4fb2-8f8e-e736a5be08a2
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 19:15:00+00:00
+----------------------------------------
+
+Instruction #107:
+ Timestep: 106
+ Time: 2025-11-03 19:20:00+00:00
+ ID: 27a4d75d-2472-4c2a-b187-119716a8bc83
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 19:20:00+00:00
+----------------------------------------
+
+Instruction #108:
+ Timestep: 107
+ Time: 2025-11-03 19:25:00+00:00
+ ID: f483b2d0-d346-4e7a-9693-97ae6541d04d
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 19:25:00+00:00
+----------------------------------------
+
+Instruction #109:
+ Timestep: 108
+ Time: 2025-11-03 19:30:00+00:00
+ ID: ffb97ad5-ead7-4234-b13e-b314385ff486
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 19:30:00+00:00
+----------------------------------------
+
+Instruction #110:
+ Timestep: 109
+ Time: 2025-11-03 19:35:00+00:00
+ ID: ed8dacf6-471d-43fb-8eae-97f8ee6a95a2
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 19:35:00+00:00
+----------------------------------------
+
+Instruction #111:
+ Timestep: 110
+ Time: 2025-11-03 19:40:00+00:00
+ ID: 8938544f-055b-41cc-ad9b-9d3acf6db0c5
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 19:40:00+00:00
+----------------------------------------
+
+Instruction #112:
+ Timestep: 111
+ Time: 2025-11-03 19:45:00+00:00
+ ID: dd162d17-b751-400e-8de0-23a0f7a6e9cb
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 19:45:00+00:00
+----------------------------------------
+
+Instruction #113:
+ Timestep: 112
+ Time: 2025-11-03 19:50:00+00:00
+ ID: 1430c106-477c-45c9-8594-07e79f3f11b5
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 19:50:00+00:00
+----------------------------------------
+
+Instruction #114:
+ Timestep: 113
+ Time: 2025-11-03 19:55:00+00:00
+ ID: 7234d025-d782-46cb-9d66-3522c630db53
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 19:55:00+00:00
+----------------------------------------
+
+Instruction #115:
+ Timestep: 114
+ Time: 2025-11-03 20:00:00+00:00
+ ID: 1b192a6b-bb22-4d1c-915d-fae4357fa9ad
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 20:00:00+00:00
+----------------------------------------
+
+Instruction #116:
+ Timestep: 115
+ Time: 2025-11-03 20:05:00+00:00
+ ID: aed2c21d-ddc2-45f7-8a27-da0cd246ee72
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 20:05:00+00:00
+----------------------------------------
+
+Instruction #117:
+ Timestep: 116
+ Time: 2025-11-03 20:10:00+00:00
+ ID: df5aad69-bc80-4443-8b3c-83bbb58b057e
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 20:10:00+00:00
+----------------------------------------
+
+Instruction #118:
+ Timestep: 117
+ Time: 2025-11-03 20:15:00+00:00
+ ID: 8975a0ed-d27e-4da0-a9ca-17b97fc1f2f0
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 20:15:00+00:00
+----------------------------------------
+
+Instruction #119:
+ Timestep: 118
+ Time: 2025-11-03 20:20:00+00:00
+ ID: fd2b2611-9645-450c-9e6a-46311829ab18
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 20:20:00+00:00
+----------------------------------------
+
+Instruction #120:
+ Timestep: 119
+ Time: 2025-11-03 20:25:00+00:00
+ ID: ac0c2c3d-2cab-45cf-be6c-debfac711c69
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 20:25:00+00:00
+----------------------------------------
+
+Instruction #121:
+ Timestep: 120
+ Time: 2025-11-03 20:30:00+00:00
+ ID: d920e3d2-9997-4d64-a061-321b5bfae462
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 20:30:00+00:00
+----------------------------------------
+
+Instruction #122:
+ Timestep: 121
+ Time: 2025-11-03 20:35:00+00:00
+ ID: c816236d-cb2f-4ce4-bd76-3908637b93d8
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 20:35:00+00:00
+----------------------------------------
+
+Instruction #123:
+ Timestep: 122
+ Time: 2025-11-03 20:40:00+00:00
+ ID: d7e27a2d-ff5c-4a55-9fd4-f590466a5e13
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 20:40:00+00:00
+----------------------------------------
+
+Instruction #124:
+ Timestep: 123
+ Time: 2025-11-03 20:45:00+00:00
+ ID: f98b4c43-3d5f-4aeb-af82-58e6363fa07a
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 20:45:00+00:00
+----------------------------------------
+
+Instruction #125:
+ Timestep: 124
+ Time: 2025-11-03 20:50:00+00:00
+ ID: 6eaada6a-6159-4cc3-8356-733c571f4c71
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 20:50:00+00:00
+----------------------------------------
+
+Instruction #126:
+ Timestep: 125
+ Time: 2025-11-03 20:55:00+00:00
+ ID: d599219a-5adc-4bd2-a0f3-ea83b70f9100
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 20:55:00+00:00
+----------------------------------------
+
+Instruction #127:
+ Timestep: 126
+ Time: 2025-11-03 21:00:00+00:00
+ ID: 46526216-0680-4700-a02e-f921becda746
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 21:00:00+00:00
+----------------------------------------
+
+Instruction #128:
+ Timestep: 127
+ Time: 2025-11-03 21:05:00+00:00
+ ID: b15e0c87-a7e0-49f6-905c-ce8bf3eb0c63
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 21:05:00+00:00
+----------------------------------------
+
+Instruction #129:
+ Timestep: 128
+ Time: 2025-11-03 21:10:00+00:00
+ ID: 695792d3-201a-418f-af31-31aaa6f103ea
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 21:10:00+00:00
+----------------------------------------
+
+Instruction #130:
+ Timestep: 129
+ Time: 2025-11-03 21:15:00+00:00
+ ID: 8be1e41c-cfa1-4094-b5f9-e2b6ce33f2d8
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 21:15:00+00:00
+----------------------------------------
+
+Instruction #131:
+ Timestep: 130
+ Time: 2025-11-03 21:20:00+00:00
+ ID: f1d2f789-5ca4-41f5-9d5f-e9ad9cfc0223
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 21:20:00+00:00
+----------------------------------------
+
+Instruction #132:
+ Timestep: 131
+ Time: 2025-11-03 21:25:00+00:00
+ ID: b03653a1-039d-4897-8dbe-c5e3f7782161
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 21:25:00+00:00
+----------------------------------------
+
+Instruction #133:
+ Timestep: 132
+ Time: 2025-11-03 21:30:00+00:00
+ ID: d0d3199f-a299-4fc8-916c-c2bef51cdf46
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 21:30:00+00:00
+----------------------------------------
+
+Instruction #134:
+ Timestep: 133
+ Time: 2025-11-03 21:35:00+00:00
+ ID: 2605ccfb-516c-463d-a9cd-ec6510138ddd
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 21:35:00+00:00
+----------------------------------------
+
+Instruction #135:
+ Timestep: 134
+ Time: 2025-11-03 21:40:00+00:00
+ ID: 72b784e3-8657-4360-8d05-e1a30d373633
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 21:40:00+00:00
+----------------------------------------
+
+Instruction #136:
+ Timestep: 135
+ Time: 2025-11-03 21:45:00+00:00
+ ID: 4418900d-bb8f-430f-941c-217a34ab905c
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 21:45:00+00:00
+----------------------------------------
+
+Instruction #137:
+ Timestep: 136
+ Time: 2025-11-03 21:50:00+00:00
+ ID: a441b19e-9a95-463b-ad50-ad50f49e39d4
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 21:50:00+00:00
+----------------------------------------
+
+Instruction #138:
+ Timestep: 137
+ Time: 2025-11-03 21:55:00+00:00
+ ID: 56f6d6dd-309b-4a09-a774-741e3acd35fa
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 21:55:00+00:00
+----------------------------------------
+
+Instruction #139:
+ Timestep: 138
+ Time: 2025-11-03 22:00:00+00:00
+ ID: 5a5b27e7-a038-4073-bf7a-eb0ec57c1bac
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 22:00:00+00:00
+----------------------------------------
+
+Instruction #140:
+ Timestep: 139
+ Time: 2025-11-03 22:05:00+00:00
+ ID: e7333faf-7371-4261-a21c-9fa5c40779f8
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 22:05:00+00:00
+----------------------------------------
+
+Instruction #141:
+ Timestep: 140
+ Time: 2025-11-03 22:10:00+00:00
+ ID: 3e23a96c-d724-4169-a61a-05e2ecebc369
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 22:10:00+00:00
+----------------------------------------
+
+Instruction #142:
+ Timestep: 141
+ Time: 2025-11-03 22:15:00+00:00
+ ID: 0875fa60-ae46-42ad-9229-1e8080f88879
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 22:15:00+00:00
+----------------------------------------
+
+Instruction #143:
+ Timestep: 142
+ Time: 2025-11-03 22:20:00+00:00
+ ID: f75bde96-bef2-4a2a-84ea-50e704a68697
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 22:20:00+00:00
+----------------------------------------
+
+Instruction #144:
+ Timestep: 143
+ Time: 2025-11-03 22:25:00+00:00
+ ID: cfce273d-7a9f-4709-8af9-13c7f81f6e8a
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 22:25:00+00:00
+----------------------------------------
+
+Instruction #145:
+ Timestep: 144
+ Time: 2025-11-03 22:30:00+00:00
+ ID: a1f4a6f6-9293-4e5d-8721-b96ee277c290
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 22:30:00+00:00
+----------------------------------------
+
+Instruction #146:
+ Timestep: 145
+ Time: 2025-11-03 22:35:00+00:00
+ ID: aee2ed03-237c-415e-8670-e42439ef5983
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 22:35:00+00:00
+----------------------------------------
+
+Instruction #147:
+ Timestep: 146
+ Time: 2025-11-03 22:40:00+00:00
+ ID: dea22692-edd9-4716-89ad-824df281885d
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 22:40:00+00:00
+----------------------------------------
+
+Instruction #148:
+ Timestep: 147
+ Time: 2025-11-03 22:45:00+00:00
+ ID: b89805db-621c-4d16-b616-b1ff05fc26e6
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 22:45:00+00:00
+----------------------------------------
+
+Instruction #149:
+ Timestep: 148
+ Time: 2025-11-03 22:50:00+00:00
+ ID: b88e6db6-665f-4407-803d-93e52917a97c
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 22:50:00+00:00
+----------------------------------------
+
+Instruction #150:
+ Timestep: 149
+ Time: 2025-11-03 22:55:00+00:00
+ ID: 26224527-73ef-4402-859e-4471a01f6b17
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 22:55:00+00:00
+----------------------------------------
+
+Instruction #151:
+ Timestep: 150
+ Time: 2025-11-03 23:00:00+00:00
+ ID: 824525f0-d4c8-446a-9727-c83b60cdbf26
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 23:00:00+00:00
+----------------------------------------
+
+Instruction #152:
+ Timestep: 151
+ Time: 2025-11-03 23:05:00+00:00
+ ID: cc680480-4b0f-426c-85f1-4a0de8c96609
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 23:05:00+00:00
+----------------------------------------
+
+Instruction #153:
+ Timestep: 152
+ Time: 2025-11-03 23:10:00+00:00
+ ID: 01ec1362-39a4-43d3-a423-354b8b8300e0
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 23:10:00+00:00
+----------------------------------------
+
+Instruction #154:
+ Timestep: 153
+ Time: 2025-11-03 23:15:00+00:00
+ ID: 948245c8-9afa-43bd-9f46-7e63811743d4
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 23:15:00+00:00
+----------------------------------------
+
+Instruction #155:
+ Timestep: 154
+ Time: 2025-11-03 23:20:00+00:00
+ ID: 7c1c126b-4a66-434d-9353-85dde83ec835
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 23:20:00+00:00
+----------------------------------------
+
+Instruction #156:
+ Timestep: 155
+ Time: 2025-11-03 23:25:00+00:00
+ ID: 2ec1d4f4-eb19-4a00-bbba-16abcd028569
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 23:25:00+00:00
+----------------------------------------
+
+Instruction #157:
+ Timestep: 156
+ Time: 2025-11-03 23:30:00+00:00
+ ID: 53cae277-3262-420a-bbcd-f7e4dcaa1d8e
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 23:30:00+00:00
+----------------------------------------
+
+Instruction #158:
+ Timestep: 157
+ Time: 2025-11-03 23:35:00+00:00
+ ID: f77e3534-93fd-4428-9368-f8b17d9bad71
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 23:35:00+00:00
+----------------------------------------
+
+Instruction #159:
+ Timestep: 158
+ Time: 2025-11-03 23:40:00+00:00
+ ID: d389f7ba-970d-4dbf-864d-5a52cb75412a
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 23:40:00+00:00
+----------------------------------------
+
+Instruction #160:
+ Timestep: 159
+ Time: 2025-11-03 23:45:00+00:00
+ ID: 3ef93978-45e6-4e7f-a418-99023cd726a7
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 23:45:00+00:00
+----------------------------------------
+
+Instruction #161:
+ Timestep: 160
+ Time: 2025-11-03 23:50:00+00:00
+ ID: 5afbf63f-30f0-44bf-bf7e-3d4775090884
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 23:50:00+00:00
+----------------------------------------
+
+Instruction #162:
+ Timestep: 161
+ Time: 2025-11-03 23:55:00+00:00
+ ID: 537eae5f-eb0e-473d-9d1c-9b66eb987861
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-03 23:55:00+00:00
+----------------------------------------
+
+Instruction #163:
+ Timestep: 162
+ Time: 2025-11-04 00:00:00+00:00
+ ID: 18c9c090-f440-40ad-9790-47f638477df8
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 00:00:00+00:00
+----------------------------------------
+
+Instruction #164:
+ Timestep: 163
+ Time: 2025-11-04 00:05:00+00:00
+ ID: eead63e8-3a39-4755-95aa-2c72829d5645
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 00:05:00+00:00
+----------------------------------------
+
+Instruction #165:
+ Timestep: 164
+ Time: 2025-11-04 00:10:00+00:00
+ ID: 1466c5e2-fe29-4310-b74d-d2cca0cde2c8
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 00:10:00+00:00
+----------------------------------------
+
+Instruction #166:
+ Timestep: 165
+ Time: 2025-11-04 00:15:00+00:00
+ ID: 9fd25440-e42a-43a2-96a6-26054a76af0a
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 00:15:00+00:00
+----------------------------------------
+
+Instruction #167:
+ Timestep: 166
+ Time: 2025-11-04 00:20:00+00:00
+ ID: ac14b5d4-158a-4b1c-b4e8-946f2609d45a
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 00:20:00+00:00
+----------------------------------------
+
+Instruction #168:
+ Timestep: 167
+ Time: 2025-11-04 00:25:00+00:00
+ ID: 9f19f4be-1512-4f13-ab1c-23bf9cbaea67
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 00:25:00+00:00
+----------------------------------------
+
+Instruction #169:
+ Timestep: 168
+ Time: 2025-11-04 00:30:00+00:00
+ ID: 81edbe7a-ca2a-48ad-8e8c-342e3f7b5878
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 00:30:00+00:00
+----------------------------------------
+
+Instruction #170:
+ Timestep: 169
+ Time: 2025-11-04 00:35:00+00:00
+ ID: 80acf276-9eb3-4140-9139-2506898117da
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 00:35:00+00:00
+----------------------------------------
+
+Instruction #171:
+ Timestep: 170
+ Time: 2025-11-04 00:40:00+00:00
+ ID: 813bbd0c-326e-4b0d-98ee-503cc654b2dd
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 00:40:00+00:00
+----------------------------------------
+
+Instruction #172:
+ Timestep: 171
+ Time: 2025-11-04 00:45:00+00:00
+ ID: 81074676-dd44-46b6-aaa8-bf2ab45992f4
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 00:45:00+00:00
+----------------------------------------
+
+Instruction #173:
+ Timestep: 172
+ Time: 2025-11-04 00:50:00+00:00
+ ID: 5103c1ab-9d44-4416-982f-e90aac761632
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 00:50:00+00:00
+----------------------------------------
+
+Instruction #174:
+ Timestep: 173
+ Time: 2025-11-04 00:55:00+00:00
+ ID: b6b318ee-dd80-40fd-bb17-6c4ac18d4125
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 00:55:00+00:00
+----------------------------------------
+
+Instruction #175:
+ Timestep: 174
+ Time: 2025-11-04 01:00:00+00:00
+ ID: 434b6426-8603-4e6d-802d-2830b3fbb9dc
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 01:00:00+00:00
+----------------------------------------
+
+Instruction #176:
+ Timestep: 175
+ Time: 2025-11-04 01:05:00+00:00
+ ID: bf0ae72f-251a-4653-99d5-22a1372f6afe
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 01:05:00+00:00
+----------------------------------------
+
+Instruction #177:
+ Timestep: 176
+ Time: 2025-11-04 01:10:00+00:00
+ ID: a8f5c3ce-58e1-48f8-be21-e2d6f53b2d0b
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 01:10:00+00:00
+----------------------------------------
+
+Instruction #178:
+ Timestep: 177
+ Time: 2025-11-04 01:15:00+00:00
+ ID: af6f2482-b58d-4e57-903a-01def0259f8a
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 01:15:00+00:00
+----------------------------------------
+
+Instruction #179:
+ Timestep: 178
+ Time: 2025-11-04 01:20:00+00:00
+ ID: c2374a18-4ad3-40d2-a44b-ebe4dbd25ef2
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 01:20:00+00:00
+----------------------------------------
+
+Instruction #180:
+ Timestep: 179
+ Time: 2025-11-04 01:25:00+00:00
+ ID: 8a53ae5b-692b-474e-ad56-9a088926de4d
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 01:25:00+00:00
+----------------------------------------
+
+Instruction #181:
+ Timestep: 180
+ Time: 2025-11-04 01:30:00+00:00
+ ID: a044f5f8-126d-41ea-8941-cc489c471a30
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 01:30:00+00:00
+----------------------------------------
+
+Instruction #182:
+ Timestep: 181
+ Time: 2025-11-04 01:35:00+00:00
+ ID: 1387cbad-4016-468a-9241-8fd4908b188f
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 01:35:00+00:00
+----------------------------------------
+
+Instruction #183:
+ Timestep: 182
+ Time: 2025-11-04 01:40:00+00:00
+ ID: 1a9813e5-5d60-4e31-91c4-20d0aa5de49b
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 01:40:00+00:00
+----------------------------------------
+
+Instruction #184:
+ Timestep: 183
+ Time: 2025-11-04 01:45:00+00:00
+ ID: bfb8b504-b878-4432-bbae-adaa68cbcfa7
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 01:45:00+00:00
+----------------------------------------
+
+Instruction #185:
+ Timestep: 184
+ Time: 2025-11-04 01:50:00+00:00
+ ID: 51e36336-bf40-4916-8870-f914467cb04a
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 01:50:00+00:00
+----------------------------------------
+
+Instruction #186:
+ Timestep: 185
+ Time: 2025-11-04 01:55:00+00:00
+ ID: e55c1c10-8d4f-4265-8a25-aca73942deb7
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 01:55:00+00:00
+----------------------------------------
+
+Instruction #187:
+ Timestep: 186
+ Time: 2025-11-04 02:00:00+00:00
+ ID: 9c4ba964-13a9-47a5-9729-ef39853b785b
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 02:00:00+00:00
+----------------------------------------
+
+Instruction #188:
+ Timestep: 187
+ Time: 2025-11-04 02:05:00+00:00
+ ID: 572cce0e-55a9-430c-847d-dffc3178ec49
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 02:05:00+00:00
+----------------------------------------
+
+Instruction #189:
+ Timestep: 188
+ Time: 2025-11-04 02:10:00+00:00
+ ID: 1f9cb64a-f6c1-4d63-81c0-d9651292cb35
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 02:10:00+00:00
+----------------------------------------
+
+Instruction #190:
+ Timestep: 189
+ Time: 2025-11-04 02:15:00+00:00
+ ID: abbc9372-0ef4-4a77-8be1-4438fd20d8d0
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 02:15:00+00:00
+----------------------------------------
+
+Instruction #191:
+ Timestep: 190
+ Time: 2025-11-04 02:20:00+00:00
+ ID: 6eafe9c5-79ad-41ad-95e0-e9908c829e1b
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 02:20:00+00:00
+----------------------------------------
+
+Instruction #192:
+ Timestep: 191
+ Time: 2025-11-04 02:25:00+00:00
+ ID: 9462b4a2-b1a8-4e44-9e53-cbc99d245eb2
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 02:25:00+00:00
+----------------------------------------
+
+Instruction #193:
+ Timestep: 192
+ Time: 2025-11-04 02:30:00+00:00
+ ID: 729e06fa-6a01-44e0-b4fa-6c0c41fed841
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 02:30:00+00:00
+----------------------------------------
+
+Instruction #194:
+ Timestep: 193
+ Time: 2025-11-04 02:35:00+00:00
+ ID: c7cf5410-a783-4dd1-a5e8-3c7d02de4240
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 02:35:00+00:00
+----------------------------------------
+
+Instruction #195:
+ Timestep: 194
+ Time: 2025-11-04 02:40:00+00:00
+ ID: 5884609e-63c2-496e-a887-4a2278232d04
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 02:40:00+00:00
+----------------------------------------
+
+Instruction #196:
+ Timestep: 195
+ Time: 2025-11-04 02:45:00+00:00
+ ID: a4eb26c2-af2f-4e68-8ad7-4cc90cce8900
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 02:45:00+00:00
+----------------------------------------
+
+Instruction #197:
+ Timestep: 196
+ Time: 2025-11-04 02:50:00+00:00
+ ID: c5d22d2a-7a0d-4568-b58d-f84153cd36c0
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 02:50:00+00:00
+----------------------------------------
+
+Instruction #198:
+ Timestep: 197
+ Time: 2025-11-04 02:55:00+00:00
+ ID: 2b5f3bb1-65a0-457a-ba76-e72580c966c5
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 02:55:00+00:00
+----------------------------------------
+
+Instruction #199:
+ Timestep: 198
+ Time: 2025-11-04 03:00:00+00:00
+ ID: 3f8346d3-5354-45d7-a8d5-a7d6baedfb3e
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 03:00:00+00:00
+----------------------------------------
+
+Instruction #200:
+ Timestep: 199
+ Time: 2025-11-04 03:05:00+00:00
+ ID: eb54f402-19da-4883-bfd9-903adf5045bb
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 03:05:00+00:00
+----------------------------------------
+
+Instruction #201:
+ Timestep: 200
+ Time: 2025-11-04 03:10:00+00:00
+ ID: 8cf4e378-830f-489d-a9d7-e5babafd08de
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 03:10:00+00:00
+----------------------------------------
+
+Instruction #202:
+ Timestep: 201
+ Time: 2025-11-04 03:15:00+00:00
+ ID: 33e64dbb-afcc-416d-8839-14d29a5b7beb
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 03:15:00+00:00
+----------------------------------------
+
+Instruction #203:
+ Timestep: 202
+ Time: 2025-11-04 03:20:00+00:00
+ ID: a392c38a-cb05-42bc-977b-b234c1b564a2
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 03:20:00+00:00
+----------------------------------------
+
+Instruction #204:
+ Timestep: 203
+ Time: 2025-11-04 03:25:00+00:00
+ ID: 8697b229-5882-4e59-87b4-f1d5cb364ecd
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 03:25:00+00:00
+----------------------------------------
+
+Instruction #205:
+ Timestep: 204
+ Time: 2025-11-04 03:30:00+00:00
+ ID: 6ba3b8df-8bbf-4696-80fc-18fa2c0cf29c
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 03:30:00+00:00
+----------------------------------------
+
+Instruction #206:
+ Timestep: 205
+ Time: 2025-11-04 03:35:00+00:00
+ ID: 3baa2ec9-6255-4af8-a3e0-55701ca195b6
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 03:35:00+00:00
+----------------------------------------
+
+Instruction #207:
+ Timestep: 206
+ Time: 2025-11-04 03:40:00+00:00
+ ID: 97a0ec67-f19a-4616-99b9-be037901d9bd
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 03:40:00+00:00
+----------------------------------------
+
+Instruction #208:
+ Timestep: 207
+ Time: 2025-11-04 03:45:00+00:00
+ ID: 83d06faa-3e56-41ea-add7-8a760ef74eb8
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 03:45:00+00:00
+----------------------------------------
+
+Instruction #209:
+ Timestep: 208
+ Time: 2025-11-04 03:50:00+00:00
+ ID: 03a73048-9993-4d2a-86ca-4f6c3b851807
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 03:50:00+00:00
+----------------------------------------
+
+Instruction #210:
+ Timestep: 209
+ Time: 2025-11-04 03:55:00+00:00
+ ID: a00a0e5f-e703-4592-887b-57c4d2032244
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 03:55:00+00:00
+----------------------------------------
+
+Instruction #211:
+ Timestep: 210
+ Time: 2025-11-04 04:00:00+00:00
+ ID: eef31d3b-fedf-4cd8-83ff-b3717f6bb645
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 04:00:00+00:00
+----------------------------------------
+
+Instruction #212:
+ Timestep: 211
+ Time: 2025-11-04 04:05:00+00:00
+ ID: f2065747-e1de-4bf6-a5dd-14fb66969aa1
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 04:05:00+00:00
+----------------------------------------
+
+Instruction #213:
+ Timestep: 212
+ Time: 2025-11-04 04:10:00+00:00
+ ID: d2e52154-da53-4417-87f8-06c7b96c30ea
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 04:10:00+00:00
+----------------------------------------
+
+Instruction #214:
+ Timestep: 213
+ Time: 2025-11-04 04:15:00+00:00
+ ID: 93c3ebc6-6b51-4971-871a-6fe6e9829102
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 04:15:00+00:00
+----------------------------------------
+
+Instruction #215:
+ Timestep: 214
+ Time: 2025-11-04 04:20:00+00:00
+ ID: 4d29a5e1-4f84-4cac-82c8-b45d1a7af2ad
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 04:20:00+00:00
+----------------------------------------
+
+Instruction #216:
+ Timestep: 215
+ Time: 2025-11-04 04:25:00+00:00
+ ID: d39c9f7d-88bd-4cee-b65d-3c4a81900d64
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 04:25:00+00:00
+----------------------------------------
+
+Instruction #217:
+ Timestep: 216
+ Time: 2025-11-04 04:30:00+00:00
+ ID: 3479a9ba-5943-45b2-9d91-0ba208f2609c
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 04:30:00+00:00
+----------------------------------------
+
+Instruction #218:
+ Timestep: 217
+ Time: 2025-11-04 04:35:00+00:00
+ ID: f05cfb99-179a-4d75-9e90-1b7b5c159754
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 04:35:00+00:00
+----------------------------------------
+
+Instruction #219:
+ Timestep: 218
+ Time: 2025-11-04 04:40:00+00:00
+ ID: e277eceb-2fca-4423-8418-57892293c929
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 04:40:00+00:00
+----------------------------------------
+
+Instruction #220:
+ Timestep: 219
+ Time: 2025-11-04 04:45:00+00:00
+ ID: 3eee9159-d9e2-4e06-80dd-7add4cbf2fd2
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 04:45:00+00:00
+----------------------------------------
+
+Instruction #221:
+ Timestep: 220
+ Time: 2025-11-04 04:50:00+00:00
+ ID: 23f6e9e1-d962-4ed3-b55b-d27bc57f7395
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 04:50:00+00:00
+----------------------------------------
+
+Instruction #222:
+ Timestep: 221
+ Time: 2025-11-04 04:55:00+00:00
+ ID: 6f5d53c6-8051-4284-aa8a-00bb75f6e534
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 04:55:00+00:00
+----------------------------------------
+
+Instruction #223:
+ Timestep: 222
+ Time: 2025-11-04 05:00:00+00:00
+ ID: 9fbeebaf-19f8-47a0-a3d9-e26bb5d1d201
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 05:00:00+00:00
+----------------------------------------
+
+Instruction #224:
+ Timestep: 223
+ Time: 2025-11-04 05:05:00+00:00
+ ID: d149a0f7-5a43-4491-8a98-35f4e653f11f
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 05:05:00+00:00
+----------------------------------------
+
+Instruction #225:
+ Timestep: 224
+ Time: 2025-11-04 05:10:00+00:00
+ ID: e054f7cb-ac01-42c7-94d4-8bbff1b28f59
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 05:10:00+00:00
+----------------------------------------
+
+Instruction #226:
+ Timestep: 225
+ Time: 2025-11-04 05:15:00+00:00
+ ID: 15fbb051-3bb9-4e7d-9c5c-b8bb24659193
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 05:15:00+00:00
+----------------------------------------
+
+Instruction #227:
+ Timestep: 226
+ Time: 2025-11-04 05:20:00+00:00
+ ID: c6b0a3eb-795d-403b-b206-a6f8f35fddbb
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 05:20:00+00:00
+----------------------------------------
+
+Instruction #228:
+ Timestep: 227
+ Time: 2025-11-04 05:25:00+00:00
+ ID: e0f56280-0553-4974-b10d-81a35f040793
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 05:25:00+00:00
+----------------------------------------
+
+Instruction #229:
+ Timestep: 228
+ Time: 2025-11-04 05:30:00+00:00
+ ID: 6376942c-023f-4711-acf1-b792b80c3944
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 05:30:00+00:00
+----------------------------------------
+
+Instruction #230:
+ Timestep: 229
+ Time: 2025-11-04 05:35:00+00:00
+ ID: 8ef61a4c-f597-4077-a8c7-ed57f912abc9
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 05:35:00+00:00
+----------------------------------------
+
+Instruction #231:
+ Timestep: 230
+ Time: 2025-11-04 05:40:00+00:00
+ ID: 9cc2e6e9-1ea1-4ff7-8b24-2eae63ce4012
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 05:40:00+00:00
+----------------------------------------
+
+Instruction #232:
+ Timestep: 231
+ Time: 2025-11-04 05:45:00+00:00
+ ID: 922691d4-c9ec-4bdf-83fe-c75e7673fe16
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 05:45:00+00:00
+----------------------------------------
+
+Instruction #233:
+ Timestep: 232
+ Time: 2025-11-04 05:50:00+00:00
+ ID: 25ad2dde-0384-4887-9a9e-26201a65fa8b
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 05:50:00+00:00
+----------------------------------------
+
+Instruction #234:
+ Timestep: 233
+ Time: 2025-11-04 05:55:00+00:00
+ ID: 307d928e-6dec-430d-b1c8-c2251496ba70
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 05:55:00+00:00
+----------------------------------------
+
+Instruction #235:
+ Timestep: 234
+ Time: 2025-11-04 06:00:00+00:00
+ ID: 4172fc61-e3e3-437f-bfcc-d78f4359dbfb
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 06:00:00+00:00
+----------------------------------------
+
+Instruction #236:
+ Timestep: 235
+ Time: 2025-11-04 06:05:00+00:00
+ ID: 01fe5f02-b9e1-421c-b5be-29b975fec0be
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 06:05:00+00:00
+----------------------------------------
+
+Instruction #237:
+ Timestep: 236
+ Time: 2025-11-04 06:10:00+00:00
+ ID: 765fb36c-ba62-4617-9816-8a0d64e06150
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 06:10:00+00:00
+----------------------------------------
+
+Instruction #238:
+ Timestep: 237
+ Time: 2025-11-04 06:15:00+00:00
+ ID: 041c6160-ac0e-4a76-b8ce-b05ad2f48ffa
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 06:15:00+00:00
+----------------------------------------
+
+Instruction #239:
+ Timestep: 238
+ Time: 2025-11-04 06:20:00+00:00
+ ID: d32f5531-f2d3-4331-a690-14c77341bcfe
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 06:20:00+00:00
+----------------------------------------
+
+Instruction #240:
+ Timestep: 239
+ Time: 2025-11-04 06:25:00+00:00
+ ID: ea294527-8643-491a-9a07-964227510275
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 06:25:00+00:00
+----------------------------------------
+
+Instruction #241:
+ Timestep: 240
+ Time: 2025-11-04 06:30:00+00:00
+ ID: c713e1df-41f4-4ed8-8f12-123fe078c779
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 06:30:00+00:00
+----------------------------------------
+
+Instruction #242:
+ Timestep: 241
+ Time: 2025-11-04 06:35:00+00:00
+ ID: 485533d8-69e3-46fa-a0ba-a9e90c7caaa3
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 06:35:00+00:00
+----------------------------------------
+
+Instruction #243:
+ Timestep: 242
+ Time: 2025-11-04 06:40:00+00:00
+ ID: 3b267d9d-3aca-4c24-a947-4341e59b53a6
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 06:40:00+00:00
+----------------------------------------
+
+Instruction #244:
+ Timestep: 243
+ Time: 2025-11-04 06:45:00+00:00
+ ID: ec243dc6-147a-48b2-8cd6-9fd1b9b8832e
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 06:45:00+00:00
+----------------------------------------
+
+Instruction #245:
+ Timestep: 244
+ Time: 2025-11-04 06:50:00+00:00
+ ID: 1e7de924-e3e1-4df5-a3c9-8a8e483f108c
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 06:50:00+00:00
+----------------------------------------
+
+Instruction #246:
+ Timestep: 245
+ Time: 2025-11-04 06:55:00+00:00
+ ID: ef035bfd-8b42-49ae-b1b3-852a7861546e
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 06:55:00+00:00
+----------------------------------------
+
+Instruction #247:
+ Timestep: 246
+ Time: 2025-11-04 07:00:00+00:00
+ ID: 725ad818-ff23-4342-b0ff-82391666f7c9
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 07:00:00+00:00
+----------------------------------------
+
+Instruction #248:
+ Timestep: 247
+ Time: 2025-11-04 07:05:00+00:00
+ ID: 643acfb7-2325-4543-8f05-c74c81128f9b
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 07:05:00+00:00
+----------------------------------------
+
+Instruction #249:
+ Timestep: 248
+ Time: 2025-11-04 07:10:00+00:00
+ ID: b2210f98-f4d7-4121-ab82-0fedacef81eb
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 07:10:00+00:00
+----------------------------------------
+
+Instruction #250:
+ Timestep: 249
+ Time: 2025-11-04 07:15:00+00:00
+ ID: ae9adb54-f49b-4d35-8f3f-b90e7f919a69
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 07:15:00+00:00
+----------------------------------------
+
+Instruction #251:
+ Timestep: 250
+ Time: 2025-11-04 07:20:00+00:00
+ ID: cac19eca-04f0-4a5a-abc5-cb4b49a0d4de
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 07:20:00+00:00
+----------------------------------------
+
+Instruction #252:
+ Timestep: 251
+ Time: 2025-11-04 07:25:00+00:00
+ ID: 7556ba0e-5d96-42f3-86fb-0f7352f8bd41
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 07:25:00+00:00
+----------------------------------------
+
+Instruction #253:
+ Timestep: 252
+ Time: 2025-11-04 07:30:00+00:00
+ ID: f289a7f2-bd2f-4dec-8d28-a4c8e76fc614
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 07:30:00+00:00
+----------------------------------------
+
+Instruction #254:
+ Timestep: 253
+ Time: 2025-11-04 07:35:00+00:00
+ ID: 021415e8-4ca4-478c-a262-370b6e6e7e13
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 07:35:00+00:00
+----------------------------------------
+
+Instruction #255:
+ Timestep: 254
+ Time: 2025-11-04 07:40:00+00:00
+ ID: 3c0a8221-7c5f-43c2-bf7d-b26a6544109e
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 07:40:00+00:00
+----------------------------------------
+
+Instruction #256:
+ Timestep: 255
+ Time: 2025-11-04 07:45:00+00:00
+ ID: 5548d4d0-ba0d-401b-9e6e-05ebbdb1dd59
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 07:45:00+00:00
+----------------------------------------
+
+Instruction #257:
+ Timestep: 256
+ Time: 2025-11-04 07:50:00+00:00
+ ID: 6cf773f2-9a6c-45e8-9d4f-ae796c72e83b
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 07:50:00+00:00
+----------------------------------------
+
+Instruction #258:
+ Timestep: 257
+ Time: 2025-11-04 07:55:00+00:00
+ ID: ec21f19c-126c-44b2-800a-1b7f778f6500
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 07:55:00+00:00
+----------------------------------------
+
+Instruction #259:
+ Timestep: 258
+ Time: 2025-11-04 08:00:00+00:00
+ ID: 33b51205-4228-484f-9c97-8d616afa927c
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 08:00:00+00:00
+----------------------------------------
+
+Instruction #260:
+ Timestep: 259
+ Time: 2025-11-04 08:05:00+00:00
+ ID: b28f065d-fb8d-471f-a111-079d78e80aca
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 08:05:00+00:00
+----------------------------------------
+
+Instruction #261:
+ Timestep: 260
+ Time: 2025-11-04 08:10:00+00:00
+ ID: 54d473db-f4ea-4cb9-add1-baff3f27e0bc
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 08:10:00+00:00
+----------------------------------------
+
+Instruction #262:
+ Timestep: 261
+ Time: 2025-11-04 08:15:00+00:00
+ ID: 839f98d2-d403-40d7-b2fa-12fbd504d1fb
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 08:15:00+00:00
+----------------------------------------
+
+Instruction #263:
+ Timestep: 262
+ Time: 2025-11-04 08:20:00+00:00
+ ID: 04c110e1-f623-4542-a00b-d6010ebeb424
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 08:20:00+00:00
+----------------------------------------
+
+Instruction #264:
+ Timestep: 263
+ Time: 2025-11-04 08:25:00+00:00
+ ID: dce1acb1-d4f7-4108-89b7-15213acb5f2e
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 08:25:00+00:00
+----------------------------------------
+
+Instruction #265:
+ Timestep: 264
+ Time: 2025-11-04 08:30:00+00:00
+ ID: cb9b0b10-6d10-4426-8a3c-e376f11cb942
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 08:30:00+00:00
+----------------------------------------
+
+Instruction #266:
+ Timestep: 265
+ Time: 2025-11-04 08:35:00+00:00
+ ID: e474ddc0-0572-4f4f-aa6a-abcb4a9c7188
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 08:35:00+00:00
+----------------------------------------
+
+Instruction #267:
+ Timestep: 266
+ Time: 2025-11-04 08:40:00+00:00
+ ID: e52f66e8-1cad-4efa-8993-7fb2532eaddb
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 08:40:00+00:00
+----------------------------------------
+
+Instruction #268:
+ Timestep: 267
+ Time: 2025-11-04 08:45:00+00:00
+ ID: fab0a59e-c67c-47cd-b331-0c6682d5235e
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 08:45:00+00:00
+----------------------------------------
+
+Instruction #269:
+ Timestep: 268
+ Time: 2025-11-04 08:50:00+00:00
+ ID: f9b712c7-8b94-4ec8-8d7a-a9ca4c9f5837
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 08:50:00+00:00
+----------------------------------------
+
+Instruction #270:
+ Timestep: 269
+ Time: 2025-11-04 08:55:00+00:00
+ ID: a41cc9e2-7a35-4b40-b42f-8af19f4a9152
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 08:55:00+00:00
+----------------------------------------
+
+Instruction #271:
+ Timestep: 270
+ Time: 2025-11-04 09:00:00+00:00
+ ID: 96f2afed-af96-4cf4-8cd0-6a60ac2c2763
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 09:00:00+00:00
+----------------------------------------
+
+Instruction #272:
+ Timestep: 271
+ Time: 2025-11-04 09:05:00+00:00
+ ID: c826efce-997f-4529-a4d1-1b06c0db10e4
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 09:05:00+00:00
+----------------------------------------
+
+Instruction #273:
+ Timestep: 272
+ Time: 2025-11-04 09:10:00+00:00
+ ID: b9ed7ad8-e786-47d0-bb76-a07af225d3b0
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 09:10:00+00:00
+----------------------------------------
+
+Instruction #274:
+ Timestep: 273
+ Time: 2025-11-04 09:15:00+00:00
+ ID: d4773698-3353-4d42-9840-1e4af3aba7db
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 09:15:00+00:00
+----------------------------------------
+
+Instruction #275:
+ Timestep: 274
+ Time: 2025-11-04 09:20:00+00:00
+ ID: b6792925-3f3e-4095-828a-b2ff075241b1
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 09:20:00+00:00
+----------------------------------------
+
+Instruction #276:
+ Timestep: 275
+ Time: 2025-11-04 09:25:00+00:00
+ ID: 45c7eb47-37bd-465b-b50e-c31e85299ccb
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 09:25:00+00:00
+----------------------------------------
+
+Instruction #277:
+ Timestep: 276
+ Time: 2025-11-04 09:30:00+00:00
+ ID: 2e632794-7d6a-4610-bc41-b392db5f9bec
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 09:30:00+00:00
+----------------------------------------
+
+Instruction #278:
+ Timestep: 277
+ Time: 2025-11-04 09:35:00+00:00
+ ID: 4b16fcb6-b824-4a1c-913e-eedc0ec7d61b
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 09:35:00+00:00
+----------------------------------------
+
+Instruction #279:
+ Timestep: 278
+ Time: 2025-11-04 09:40:00+00:00
+ ID: 599ef653-3516-4641-9242-5ad426238e5b
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 09:40:00+00:00
+----------------------------------------
+
+Instruction #280:
+ Timestep: 279
+ Time: 2025-11-04 09:45:00+00:00
+ ID: 59569fe4-a58d-4c88-ba2f-6a2c0a65ea7b
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 09:45:00+00:00
+----------------------------------------
+
+Instruction #281:
+ Timestep: 280
+ Time: 2025-11-04 09:50:00+00:00
+ ID: 3feeaa47-f41e-4d61-a1ca-50f0ea6a86fc
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 09:50:00+00:00
+----------------------------------------
+
+Instruction #282:
+ Timestep: 281
+ Time: 2025-11-04 09:55:00+00:00
+ ID: 762e2e57-b1f2-4cbe-ac65-794e99afa096
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 09:55:00+00:00
+----------------------------------------
+
+Instruction #283:
+ Timestep: 282
+ Time: 2025-11-04 10:00:00+00:00
+ ID: e3aa8b20-3055-4546-9992-5d0d006f6995
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 10:00:00+00:00
+----------------------------------------
+
+Instruction #284:
+ Timestep: 283
+ Time: 2025-11-04 10:05:00+00:00
+ ID: ba785dc6-e01a-435b-ad24-ebe2f7f4b583
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 10:05:00+00:00
+----------------------------------------
+
+Instruction #285:
+ Timestep: 284
+ Time: 2025-11-04 10:10:00+00:00
+ ID: 265ca424-dd48-4ae1-b14d-c10321f8345f
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 10:10:00+00:00
+----------------------------------------
+
+Instruction #286:
+ Timestep: 285
+ Time: 2025-11-04 10:15:00+00:00
+ ID: c0c33d62-2629-4707-a41e-0599bcf070d3
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 10:15:00+00:00
+----------------------------------------
+
+Instruction #287:
+ Timestep: 286
+ Time: 2025-11-04 10:20:00+00:00
+ ID: 33892850-c152-4f8e-a469-0e0b6440cdbc
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 10:20:00+00:00
+----------------------------------------
+
+Instruction #288:
+ Timestep: 287
+ Time: 2025-11-04 10:25:00+00:00
+ ID: 610ad97b-41f4-44b5-8b85-552d050a5de8
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-04 10:25:00+00:00
+----------------------------------------
diff --git a/flexmeasures_s2/profile_steering/examples/instructions/itho_instructions_filltarget_only_D=1_B=200_S=30_T=288.txt b/flexmeasures_s2/profile_steering/examples/instructions/itho_instructions_filltarget_only_D=1_B=200_S=30_T=288.txt
new file mode 100644
index 0000000..ab013ac
--- /dev/null
+++ b/flexmeasures_s2/profile_steering/examples/instructions/itho_instructions_filltarget_only_D=1_B=200_S=30_T=288.txt
@@ -0,0 +1,3218 @@
+================================================================================
+ITHO DHW Heat Pump Instructions Filltarget Only
+================================================================================
+Planning start time: 2025-11-19 10:00:00+00:00
+Planning window: 1 days 00:00:00
+Resolution: 0 days 00:05:00
+Total timesteps: 288
+================================================================================
+
+Device: itho_dhw_device_005
+Number of instructions: 288
+--------------------------------------------------------------------------------
+
+Instruction Timeline:
+--------------------------------------------------------------------------------
+ Mode: DHW_ON
+ Start: 2025-11-19 10:00:00+00:00 (timestep 0)
+ Duration: 7 timesteps (35.0 minutes)
+ Operation mode factor: 0.0
+----------------------------------------
+ Mode: DHW_OFF
+ Start: 2025-11-19 10:35:00+00:00 (timestep 7)
+ Duration: 25 timesteps (125.0 minutes)
+ Operation mode factor: 0.0
+----------------------------------------
+ Mode: DHW_ON
+ Start: 2025-11-19 12:40:00+00:00 (timestep 32)
+ Duration: 53 timesteps (265.0 minutes)
+ Operation mode factor: 0.0
+----------------------------------------
+ Mode: DHW_OFF
+ Start: 2025-11-19 17:05:00+00:00 (timestep 85)
+ Duration: 7 timesteps (35.0 minutes)
+ Operation mode factor: 0.0
+----------------------------------------
+ Mode: DHW_ON
+ Start: 2025-11-19 17:40:00+00:00 (timestep 92)
+ Duration: 6 timesteps (30.0 minutes)
+ Operation mode factor: 0.0
+----------------------------------------
+ Mode: DHW_OFF
+ Start: 2025-11-19 18:10:00+00:00 (timestep 98)
+ Duration: 190 timesteps (950.0 minutes)
+----------------------------------------
+
+Total operation mode changes: 5
+================================================================================
+
+Detailed Instruction List:
+================================================================================
+
+Instruction #1:
+ Timestep: 0
+ Time: 2025-11-19 10:00:00+00:00
+ ID: cca769e9-d53f-4e80-b3b5-acef02920bb7
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 10:00:00+00:00
+----------------------------------------
+
+Instruction #2:
+ Timestep: 1
+ Time: 2025-11-19 10:05:00+00:00
+ ID: ddeabad5-43a6-4cb9-9ed6-1cd5fe44c016
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 10:05:00+00:00
+----------------------------------------
+
+Instruction #3:
+ Timestep: 2
+ Time: 2025-11-19 10:10:00+00:00
+ ID: 2cdb5869-b156-4c88-836a-ad6fcb5ff05e
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 10:10:00+00:00
+----------------------------------------
+
+Instruction #4:
+ Timestep: 3
+ Time: 2025-11-19 10:15:00+00:00
+ ID: 63c672fb-02ce-4372-afc6-2bc34490d9e3
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 10:15:00+00:00
+----------------------------------------
+
+Instruction #5:
+ Timestep: 4
+ Time: 2025-11-19 10:20:00+00:00
+ ID: 15cdaaae-67b6-4edb-9ab3-f14a5b6a296f
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 10:20:00+00:00
+----------------------------------------
+
+Instruction #6:
+ Timestep: 5
+ Time: 2025-11-19 10:25:00+00:00
+ ID: 01bf3db9-e78b-4bad-8302-d3bc05e4412d
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 10:25:00+00:00
+----------------------------------------
+
+Instruction #7:
+ Timestep: 6
+ Time: 2025-11-19 10:30:00+00:00
+ ID: 19b954f6-f819-45a3-8bf2-950d00e22d17
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 10:30:00+00:00
+----------------------------------------
+
+Instruction #8:
+ Timestep: 7
+ Time: 2025-11-19 10:35:00+00:00
+ ID: 3a5c8a64-376f-4055-9864-57706504ce02
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 10:35:00+00:00
+----------------------------------------
+
+Instruction #9:
+ Timestep: 8
+ Time: 2025-11-19 10:40:00+00:00
+ ID: f178e768-804e-455d-ba48-f192685e8fd4
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 10:40:00+00:00
+----------------------------------------
+
+Instruction #10:
+ Timestep: 9
+ Time: 2025-11-19 10:45:00+00:00
+ ID: 723b1efd-160f-47e0-ac40-3cbf327480d7
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 10:45:00+00:00
+----------------------------------------
+
+Instruction #11:
+ Timestep: 10
+ Time: 2025-11-19 10:50:00+00:00
+ ID: 6f311b75-26d2-498e-82c5-5b38a9560af4
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 10:50:00+00:00
+----------------------------------------
+
+Instruction #12:
+ Timestep: 11
+ Time: 2025-11-19 10:55:00+00:00
+ ID: 2451a8e4-fdb0-4248-8b46-b2efc7c6d98a
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 10:55:00+00:00
+----------------------------------------
+
+Instruction #13:
+ Timestep: 12
+ Time: 2025-11-19 11:00:00+00:00
+ ID: 09dae461-e56b-4dc0-a306-cc1a9a154b35
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 11:00:00+00:00
+----------------------------------------
+
+Instruction #14:
+ Timestep: 13
+ Time: 2025-11-19 11:05:00+00:00
+ ID: e3fcc514-18a0-447f-9d9b-e5d5031fa0d2
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 11:05:00+00:00
+----------------------------------------
+
+Instruction #15:
+ Timestep: 14
+ Time: 2025-11-19 11:10:00+00:00
+ ID: 4f9d2a44-bb14-45a4-97b6-df3322c9771a
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 11:10:00+00:00
+----------------------------------------
+
+Instruction #16:
+ Timestep: 15
+ Time: 2025-11-19 11:15:00+00:00
+ ID: a6a18fba-17fb-46be-87bf-01d5bfc65365
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 11:15:00+00:00
+----------------------------------------
+
+Instruction #17:
+ Timestep: 16
+ Time: 2025-11-19 11:20:00+00:00
+ ID: 0b166ef7-cc67-4758-a6cb-0f1c3b32047c
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 11:20:00+00:00
+----------------------------------------
+
+Instruction #18:
+ Timestep: 17
+ Time: 2025-11-19 11:25:00+00:00
+ ID: 788768f8-e972-4288-8789-e50a78861966
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 11:25:00+00:00
+----------------------------------------
+
+Instruction #19:
+ Timestep: 18
+ Time: 2025-11-19 11:30:00+00:00
+ ID: bd1c4fea-4613-4654-9f94-b78e36ec2de3
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 11:30:00+00:00
+----------------------------------------
+
+Instruction #20:
+ Timestep: 19
+ Time: 2025-11-19 11:35:00+00:00
+ ID: 413f5f3f-d72a-47ed-ac91-098a0621f926
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 11:35:00+00:00
+----------------------------------------
+
+Instruction #21:
+ Timestep: 20
+ Time: 2025-11-19 11:40:00+00:00
+ ID: 579d588b-42d7-4260-9505-e7c1ba9749fd
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 11:40:00+00:00
+----------------------------------------
+
+Instruction #22:
+ Timestep: 21
+ Time: 2025-11-19 11:45:00+00:00
+ ID: 173a52a4-745d-4815-b90c-a36f646db8b5
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 11:45:00+00:00
+----------------------------------------
+
+Instruction #23:
+ Timestep: 22
+ Time: 2025-11-19 11:50:00+00:00
+ ID: 28b2d057-36ce-4651-aa92-d5029355d581
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 11:50:00+00:00
+----------------------------------------
+
+Instruction #24:
+ Timestep: 23
+ Time: 2025-11-19 11:55:00+00:00
+ ID: f2671ccc-d508-44f4-b591-778839d7759b
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 11:55:00+00:00
+----------------------------------------
+
+Instruction #25:
+ Timestep: 24
+ Time: 2025-11-19 12:00:00+00:00
+ ID: 515883e9-0dcf-42cb-910b-7d6f7f4403a6
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 12:00:00+00:00
+----------------------------------------
+
+Instruction #26:
+ Timestep: 25
+ Time: 2025-11-19 12:05:00+00:00
+ ID: a90b5ecf-ff59-4a55-8fc5-a7645bcf7bf5
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 12:05:00+00:00
+----------------------------------------
+
+Instruction #27:
+ Timestep: 26
+ Time: 2025-11-19 12:10:00+00:00
+ ID: e62b7740-ce9f-402c-876f-43afbb347110
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 12:10:00+00:00
+----------------------------------------
+
+Instruction #28:
+ Timestep: 27
+ Time: 2025-11-19 12:15:00+00:00
+ ID: 5ddc637b-385f-44ef-ab90-57e996f46270
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 12:15:00+00:00
+----------------------------------------
+
+Instruction #29:
+ Timestep: 28
+ Time: 2025-11-19 12:20:00+00:00
+ ID: bdb687c9-1c78-423c-9c15-e846bfa50f6f
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 12:20:00+00:00
+----------------------------------------
+
+Instruction #30:
+ Timestep: 29
+ Time: 2025-11-19 12:25:00+00:00
+ ID: 5229bf77-0d28-404c-9295-e3c87322c2a6
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 12:25:00+00:00
+----------------------------------------
+
+Instruction #31:
+ Timestep: 30
+ Time: 2025-11-19 12:30:00+00:00
+ ID: 13b6266f-54cd-41c1-8a78-63edaeb29e91
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 12:30:00+00:00
+----------------------------------------
+
+Instruction #32:
+ Timestep: 31
+ Time: 2025-11-19 12:35:00+00:00
+ ID: c05970fb-d717-4e9d-abc5-9b639c3b7cfe
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 12:35:00+00:00
+----------------------------------------
+
+Instruction #33:
+ Timestep: 32
+ Time: 2025-11-19 12:40:00+00:00
+ ID: 774fd218-bb5a-4331-8b9b-6ff80775b11f
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 12:40:00+00:00
+----------------------------------------
+
+Instruction #34:
+ Timestep: 33
+ Time: 2025-11-19 12:45:00+00:00
+ ID: 68c2964d-6c38-43cb-a067-5bb58054a988
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 12:45:00+00:00
+----------------------------------------
+
+Instruction #35:
+ Timestep: 34
+ Time: 2025-11-19 12:50:00+00:00
+ ID: ab0f453e-e662-4d65-b0c3-04e8d53912e2
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 12:50:00+00:00
+----------------------------------------
+
+Instruction #36:
+ Timestep: 35
+ Time: 2025-11-19 12:55:00+00:00
+ ID: eefb9164-84d3-420a-be2e-47c8bbe5d703
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 12:55:00+00:00
+----------------------------------------
+
+Instruction #37:
+ Timestep: 36
+ Time: 2025-11-19 13:00:00+00:00
+ ID: 8f52e188-bcce-403a-86a0-3327e7121b08
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 13:00:00+00:00
+----------------------------------------
+
+Instruction #38:
+ Timestep: 37
+ Time: 2025-11-19 13:05:00+00:00
+ ID: e41cb4b5-5c2b-4604-92ea-fd2b959f19e9
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 13:05:00+00:00
+----------------------------------------
+
+Instruction #39:
+ Timestep: 38
+ Time: 2025-11-19 13:10:00+00:00
+ ID: afa53aaf-6b71-46ff-b8dc-28f228d64d4f
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 13:10:00+00:00
+----------------------------------------
+
+Instruction #40:
+ Timestep: 39
+ Time: 2025-11-19 13:15:00+00:00
+ ID: a35a011e-8e1f-47ff-9501-7927829a8a3b
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 13:15:00+00:00
+----------------------------------------
+
+Instruction #41:
+ Timestep: 40
+ Time: 2025-11-19 13:20:00+00:00
+ ID: 5d947674-877e-4d6a-8509-65b17e954c73
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 13:20:00+00:00
+----------------------------------------
+
+Instruction #42:
+ Timestep: 41
+ Time: 2025-11-19 13:25:00+00:00
+ ID: 12d85158-e038-41ba-96b8-d36a807162a7
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 13:25:00+00:00
+----------------------------------------
+
+Instruction #43:
+ Timestep: 42
+ Time: 2025-11-19 13:30:00+00:00
+ ID: 1dc0457d-f4c8-4c73-8d8a-9f5d2162fe2e
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 13:30:00+00:00
+----------------------------------------
+
+Instruction #44:
+ Timestep: 43
+ Time: 2025-11-19 13:35:00+00:00
+ ID: cf1f223b-458f-4b50-87f8-81f1dab76d37
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 13:35:00+00:00
+----------------------------------------
+
+Instruction #45:
+ Timestep: 44
+ Time: 2025-11-19 13:40:00+00:00
+ ID: fe20efd4-104d-4e08-8366-25d8b3cffe40
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 13:40:00+00:00
+----------------------------------------
+
+Instruction #46:
+ Timestep: 45
+ Time: 2025-11-19 13:45:00+00:00
+ ID: e0737971-4c35-4b74-949b-e4b48eda300d
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 13:45:00+00:00
+----------------------------------------
+
+Instruction #47:
+ Timestep: 46
+ Time: 2025-11-19 13:50:00+00:00
+ ID: 46e89ce3-ec2d-475d-909f-51393dab49a0
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 13:50:00+00:00
+----------------------------------------
+
+Instruction #48:
+ Timestep: 47
+ Time: 2025-11-19 13:55:00+00:00
+ ID: 2bb59cb8-aa61-437e-8ad2-89a213574b2b
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 13:55:00+00:00
+----------------------------------------
+
+Instruction #49:
+ Timestep: 48
+ Time: 2025-11-19 14:00:00+00:00
+ ID: fe377764-4ac2-4f79-87a5-983f2a1ad8db
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 14:00:00+00:00
+----------------------------------------
+
+Instruction #50:
+ Timestep: 49
+ Time: 2025-11-19 14:05:00+00:00
+ ID: 3c6930f1-43b1-4fc2-ab28-18c0a97f576e
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 14:05:00+00:00
+----------------------------------------
+
+Instruction #51:
+ Timestep: 50
+ Time: 2025-11-19 14:10:00+00:00
+ ID: 8e6cb726-7305-4841-84d0-d123dfea9d7c
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 14:10:00+00:00
+----------------------------------------
+
+Instruction #52:
+ Timestep: 51
+ Time: 2025-11-19 14:15:00+00:00
+ ID: 72bf6210-844e-4e5e-83d8-fee38ef88a88
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 14:15:00+00:00
+----------------------------------------
+
+Instruction #53:
+ Timestep: 52
+ Time: 2025-11-19 14:20:00+00:00
+ ID: b30316ba-5715-49f9-b1eb-19ed3bab448a
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 14:20:00+00:00
+----------------------------------------
+
+Instruction #54:
+ Timestep: 53
+ Time: 2025-11-19 14:25:00+00:00
+ ID: 4c1dec9e-1b66-471a-a924-cfb3213bc35a
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 14:25:00+00:00
+----------------------------------------
+
+Instruction #55:
+ Timestep: 54
+ Time: 2025-11-19 14:30:00+00:00
+ ID: fa0fa8a6-8552-4e02-bf74-191fdede17f9
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 14:30:00+00:00
+----------------------------------------
+
+Instruction #56:
+ Timestep: 55
+ Time: 2025-11-19 14:35:00+00:00
+ ID: b760fdd9-f574-490a-af1b-661b0bdff2c7
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 14:35:00+00:00
+----------------------------------------
+
+Instruction #57:
+ Timestep: 56
+ Time: 2025-11-19 14:40:00+00:00
+ ID: dd2a4c9d-5ec2-40ac-9805-2cb2207c534e
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 14:40:00+00:00
+----------------------------------------
+
+Instruction #58:
+ Timestep: 57
+ Time: 2025-11-19 14:45:00+00:00
+ ID: a9a9c3af-ff19-44ed-bb55-ced2297c0cdf
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 14:45:00+00:00
+----------------------------------------
+
+Instruction #59:
+ Timestep: 58
+ Time: 2025-11-19 14:50:00+00:00
+ ID: ecfae9e6-f25e-4872-be52-464727cf0e66
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 14:50:00+00:00
+----------------------------------------
+
+Instruction #60:
+ Timestep: 59
+ Time: 2025-11-19 14:55:00+00:00
+ ID: 9b13794f-6728-4658-a211-cc6101cfc172
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 14:55:00+00:00
+----------------------------------------
+
+Instruction #61:
+ Timestep: 60
+ Time: 2025-11-19 15:00:00+00:00
+ ID: 01e0e254-3200-472b-a67b-5245e57f2afd
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 15:00:00+00:00
+----------------------------------------
+
+Instruction #62:
+ Timestep: 61
+ Time: 2025-11-19 15:05:00+00:00
+ ID: cf4bf369-5685-49ec-bee4-4ba106c3b1c4
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 15:05:00+00:00
+----------------------------------------
+
+Instruction #63:
+ Timestep: 62
+ Time: 2025-11-19 15:10:00+00:00
+ ID: 5e2a706f-1d43-4418-b7d9-69c29a511412
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 15:10:00+00:00
+----------------------------------------
+
+Instruction #64:
+ Timestep: 63
+ Time: 2025-11-19 15:15:00+00:00
+ ID: a4d21b32-e5b1-4b5f-abd0-3acc7919aaae
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 15:15:00+00:00
+----------------------------------------
+
+Instruction #65:
+ Timestep: 64
+ Time: 2025-11-19 15:20:00+00:00
+ ID: 4980f494-e488-45a8-859f-0c8649a6ecf0
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 15:20:00+00:00
+----------------------------------------
+
+Instruction #66:
+ Timestep: 65
+ Time: 2025-11-19 15:25:00+00:00
+ ID: b0c69a30-6183-428b-85c0-294c1ddf522c
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 15:25:00+00:00
+----------------------------------------
+
+Instruction #67:
+ Timestep: 66
+ Time: 2025-11-19 15:30:00+00:00
+ ID: 79d9a30d-c087-4ae8-af6a-4c30d7c5d41e
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 15:30:00+00:00
+----------------------------------------
+
+Instruction #68:
+ Timestep: 67
+ Time: 2025-11-19 15:35:00+00:00
+ ID: 3d0789a2-0560-49aa-833b-731ab6b659d7
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 15:35:00+00:00
+----------------------------------------
+
+Instruction #69:
+ Timestep: 68
+ Time: 2025-11-19 15:40:00+00:00
+ ID: d4be47ef-ce21-42cd-a353-57431a72c54e
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 15:40:00+00:00
+----------------------------------------
+
+Instruction #70:
+ Timestep: 69
+ Time: 2025-11-19 15:45:00+00:00
+ ID: b56e0880-9225-4598-8682-bfe4fb9e9f7b
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 15:45:00+00:00
+----------------------------------------
+
+Instruction #71:
+ Timestep: 70
+ Time: 2025-11-19 15:50:00+00:00
+ ID: aa3002b0-9bbe-4cb9-ab9f-c21c6b1418fe
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 15:50:00+00:00
+----------------------------------------
+
+Instruction #72:
+ Timestep: 71
+ Time: 2025-11-19 15:55:00+00:00
+ ID: 5739b9e9-e188-406d-b33a-c788bdb20dfb
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 15:55:00+00:00
+----------------------------------------
+
+Instruction #73:
+ Timestep: 72
+ Time: 2025-11-19 16:00:00+00:00
+ ID: dafd175e-1780-41ad-9a69-035ca94d5ffb
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 16:00:00+00:00
+----------------------------------------
+
+Instruction #74:
+ Timestep: 73
+ Time: 2025-11-19 16:05:00+00:00
+ ID: c1fba3a6-a0fa-415b-953f-688cf8505ec1
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 16:05:00+00:00
+----------------------------------------
+
+Instruction #75:
+ Timestep: 74
+ Time: 2025-11-19 16:10:00+00:00
+ ID: 60f856d0-0866-43ef-80af-9c42eb3e923a
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 16:10:00+00:00
+----------------------------------------
+
+Instruction #76:
+ Timestep: 75
+ Time: 2025-11-19 16:15:00+00:00
+ ID: f34ad8e9-dfdb-45b1-b42c-f32b824ec324
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 16:15:00+00:00
+----------------------------------------
+
+Instruction #77:
+ Timestep: 76
+ Time: 2025-11-19 16:20:00+00:00
+ ID: 8ec5c67a-6866-4b35-ada8-6e1826c0ad5f
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 16:20:00+00:00
+----------------------------------------
+
+Instruction #78:
+ Timestep: 77
+ Time: 2025-11-19 16:25:00+00:00
+ ID: feccaee2-2b3c-4ab2-9616-ad4811904fde
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 16:25:00+00:00
+----------------------------------------
+
+Instruction #79:
+ Timestep: 78
+ Time: 2025-11-19 16:30:00+00:00
+ ID: a739b40d-3147-4bf3-bc65-b9df1c0849fc
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 16:30:00+00:00
+----------------------------------------
+
+Instruction #80:
+ Timestep: 79
+ Time: 2025-11-19 16:35:00+00:00
+ ID: 6c788ed8-7844-4c18-9f91-0f4b1feaa83f
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 16:35:00+00:00
+----------------------------------------
+
+Instruction #81:
+ Timestep: 80
+ Time: 2025-11-19 16:40:00+00:00
+ ID: ed8b6e3f-1d6b-49fb-bd52-53e51821b7e5
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 16:40:00+00:00
+----------------------------------------
+
+Instruction #82:
+ Timestep: 81
+ Time: 2025-11-19 16:45:00+00:00
+ ID: 7ec07b7d-d146-44d8-879a-63313d8cea41
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 16:45:00+00:00
+----------------------------------------
+
+Instruction #83:
+ Timestep: 82
+ Time: 2025-11-19 16:50:00+00:00
+ ID: dcc4a1be-e021-44df-9bd4-5fd72cfbe393
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 16:50:00+00:00
+----------------------------------------
+
+Instruction #84:
+ Timestep: 83
+ Time: 2025-11-19 16:55:00+00:00
+ ID: b3e437b1-8e65-461b-ac01-b9659ddeec13
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 16:55:00+00:00
+----------------------------------------
+
+Instruction #85:
+ Timestep: 84
+ Time: 2025-11-19 17:00:00+00:00
+ ID: 4cc72c9c-9939-4e70-b1a2-9d9afa6e0f2e
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 17:00:00+00:00
+----------------------------------------
+
+Instruction #86:
+ Timestep: 85
+ Time: 2025-11-19 17:05:00+00:00
+ ID: f1324c66-803a-4348-ad90-fb33ccc1c10c
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 17:05:00+00:00
+----------------------------------------
+
+Instruction #87:
+ Timestep: 86
+ Time: 2025-11-19 17:10:00+00:00
+ ID: 15ae7371-ecf9-4679-89bb-38505b30b2d7
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 17:10:00+00:00
+----------------------------------------
+
+Instruction #88:
+ Timestep: 87
+ Time: 2025-11-19 17:15:00+00:00
+ ID: 6f345862-930f-4c33-8061-197f9c4fc4bc
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 17:15:00+00:00
+----------------------------------------
+
+Instruction #89:
+ Timestep: 88
+ Time: 2025-11-19 17:20:00+00:00
+ ID: 389ffb93-a214-4054-852f-107a96f113c3
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 17:20:00+00:00
+----------------------------------------
+
+Instruction #90:
+ Timestep: 89
+ Time: 2025-11-19 17:25:00+00:00
+ ID: 0443e3b9-402e-4d75-9157-d310348c358d
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 17:25:00+00:00
+----------------------------------------
+
+Instruction #91:
+ Timestep: 90
+ Time: 2025-11-19 17:30:00+00:00
+ ID: a5d2b9da-a963-4fb0-943e-171a67319700
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 17:30:00+00:00
+----------------------------------------
+
+Instruction #92:
+ Timestep: 91
+ Time: 2025-11-19 17:35:00+00:00
+ ID: 724456d8-12b9-410f-8a4c-f1b7149f8789
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 17:35:00+00:00
+----------------------------------------
+
+Instruction #93:
+ Timestep: 92
+ Time: 2025-11-19 17:40:00+00:00
+ ID: 00305d0a-b0bc-4e0a-b78c-29e894bbc710
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 17:40:00+00:00
+----------------------------------------
+
+Instruction #94:
+ Timestep: 93
+ Time: 2025-11-19 17:45:00+00:00
+ ID: 7948c68a-2b56-4dd1-a160-b62bd38bbc05
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 17:45:00+00:00
+----------------------------------------
+
+Instruction #95:
+ Timestep: 94
+ Time: 2025-11-19 17:50:00+00:00
+ ID: 42692dd6-17e4-4ff4-8bd1-135e0fc92950
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 17:50:00+00:00
+----------------------------------------
+
+Instruction #96:
+ Timestep: 95
+ Time: 2025-11-19 17:55:00+00:00
+ ID: 095b3741-8605-4522-8d17-f09b942bde6f
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 17:55:00+00:00
+----------------------------------------
+
+Instruction #97:
+ Timestep: 96
+ Time: 2025-11-19 18:00:00+00:00
+ ID: 631b32b2-77d6-4acc-9daa-3026f5ef6f1f
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 18:00:00+00:00
+----------------------------------------
+
+Instruction #98:
+ Timestep: 97
+ Time: 2025-11-19 18:05:00+00:00
+ ID: ae63f82e-4e15-418a-b2d3-e42376a36781
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 18:05:00+00:00
+----------------------------------------
+
+Instruction #99:
+ Timestep: 98
+ Time: 2025-11-19 18:10:00+00:00
+ ID: 407df5db-4b82-4368-b698-ecb2782caca5
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 18:10:00+00:00
+----------------------------------------
+
+Instruction #100:
+ Timestep: 99
+ Time: 2025-11-19 18:15:00+00:00
+ ID: b026012a-4238-424a-a9ae-ccbf20f135d5
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 18:15:00+00:00
+----------------------------------------
+
+Instruction #101:
+ Timestep: 100
+ Time: 2025-11-19 18:20:00+00:00
+ ID: 803b27a9-d64d-4771-a985-d3debb0e24f3
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 18:20:00+00:00
+----------------------------------------
+
+Instruction #102:
+ Timestep: 101
+ Time: 2025-11-19 18:25:00+00:00
+ ID: 901adf12-95f5-48a9-8e50-927ebf1f0da1
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 18:25:00+00:00
+----------------------------------------
+
+Instruction #103:
+ Timestep: 102
+ Time: 2025-11-19 18:30:00+00:00
+ ID: 78be0c7e-9810-4427-9d60-f36438682029
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 18:30:00+00:00
+----------------------------------------
+
+Instruction #104:
+ Timestep: 103
+ Time: 2025-11-19 18:35:00+00:00
+ ID: 7a290227-eeb9-4816-952c-91f036f253c0
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 18:35:00+00:00
+----------------------------------------
+
+Instruction #105:
+ Timestep: 104
+ Time: 2025-11-19 18:40:00+00:00
+ ID: 927a70ef-e6a9-4875-99f6-206d03cb1dd0
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 18:40:00+00:00
+----------------------------------------
+
+Instruction #106:
+ Timestep: 105
+ Time: 2025-11-19 18:45:00+00:00
+ ID: 034c9b08-696e-4663-a03b-cc9aa441a95d
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 18:45:00+00:00
+----------------------------------------
+
+Instruction #107:
+ Timestep: 106
+ Time: 2025-11-19 18:50:00+00:00
+ ID: cac4889e-de08-45b0-8795-8f05820f22b6
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 18:50:00+00:00
+----------------------------------------
+
+Instruction #108:
+ Timestep: 107
+ Time: 2025-11-19 18:55:00+00:00
+ ID: cfbf031d-f332-487c-ad0f-5507d2a133c5
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 18:55:00+00:00
+----------------------------------------
+
+Instruction #109:
+ Timestep: 108
+ Time: 2025-11-19 19:00:00+00:00
+ ID: 9ed1e178-1108-419a-b460-131ceb640cf8
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 19:00:00+00:00
+----------------------------------------
+
+Instruction #110:
+ Timestep: 109
+ Time: 2025-11-19 19:05:00+00:00
+ ID: ab85b788-5f5f-442c-9076-866114148e4d
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 19:05:00+00:00
+----------------------------------------
+
+Instruction #111:
+ Timestep: 110
+ Time: 2025-11-19 19:10:00+00:00
+ ID: 7ec8d1d3-ff0c-469c-bf0d-4918e4d2f2e5
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 19:10:00+00:00
+----------------------------------------
+
+Instruction #112:
+ Timestep: 111
+ Time: 2025-11-19 19:15:00+00:00
+ ID: 33a18bad-1a27-4b8e-bdeb-0912f0edea6b
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 19:15:00+00:00
+----------------------------------------
+
+Instruction #113:
+ Timestep: 112
+ Time: 2025-11-19 19:20:00+00:00
+ ID: 5e461372-0662-40ba-88c8-147d6aa8f182
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 19:20:00+00:00
+----------------------------------------
+
+Instruction #114:
+ Timestep: 113
+ Time: 2025-11-19 19:25:00+00:00
+ ID: 2e4dde51-929f-46e5-be45-42f1b2276f4b
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 19:25:00+00:00
+----------------------------------------
+
+Instruction #115:
+ Timestep: 114
+ Time: 2025-11-19 19:30:00+00:00
+ ID: 69a55912-ee12-40f2-ad5c-23740ae14b20
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 19:30:00+00:00
+----------------------------------------
+
+Instruction #116:
+ Timestep: 115
+ Time: 2025-11-19 19:35:00+00:00
+ ID: 35bd807f-2a97-4566-ac0f-2af8f1daf3d3
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 19:35:00+00:00
+----------------------------------------
+
+Instruction #117:
+ Timestep: 116
+ Time: 2025-11-19 19:40:00+00:00
+ ID: 01ec4530-8b72-4875-97a9-7de3815eab05
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 19:40:00+00:00
+----------------------------------------
+
+Instruction #118:
+ Timestep: 117
+ Time: 2025-11-19 19:45:00+00:00
+ ID: 36dbc46e-2ed3-4a21-a996-1408d4fbabb6
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 19:45:00+00:00
+----------------------------------------
+
+Instruction #119:
+ Timestep: 118
+ Time: 2025-11-19 19:50:00+00:00
+ ID: c058409e-bcc3-44ee-8b35-475af162a906
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 19:50:00+00:00
+----------------------------------------
+
+Instruction #120:
+ Timestep: 119
+ Time: 2025-11-19 19:55:00+00:00
+ ID: 85d3734a-8fd3-4551-a735-e4a12f87236d
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 19:55:00+00:00
+----------------------------------------
+
+Instruction #121:
+ Timestep: 120
+ Time: 2025-11-19 20:00:00+00:00
+ ID: 92bce3ab-0c16-43ea-8f0f-2e9bee537402
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 20:00:00+00:00
+----------------------------------------
+
+Instruction #122:
+ Timestep: 121
+ Time: 2025-11-19 20:05:00+00:00
+ ID: 6d5055a3-2bee-4dd8-a1c9-f16076a8b32e
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 20:05:00+00:00
+----------------------------------------
+
+Instruction #123:
+ Timestep: 122
+ Time: 2025-11-19 20:10:00+00:00
+ ID: a4b836a6-7767-4bc8-9c7c-473733df640d
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 20:10:00+00:00
+----------------------------------------
+
+Instruction #124:
+ Timestep: 123
+ Time: 2025-11-19 20:15:00+00:00
+ ID: 61d50ab3-bb1e-4f84-ad58-3820dd47bdf1
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 20:15:00+00:00
+----------------------------------------
+
+Instruction #125:
+ Timestep: 124
+ Time: 2025-11-19 20:20:00+00:00
+ ID: f5057804-a544-4479-a298-9ee63c58e8d8
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 20:20:00+00:00
+----------------------------------------
+
+Instruction #126:
+ Timestep: 125
+ Time: 2025-11-19 20:25:00+00:00
+ ID: 2be36061-309a-4611-9cf8-43438b39d496
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 20:25:00+00:00
+----------------------------------------
+
+Instruction #127:
+ Timestep: 126
+ Time: 2025-11-19 20:30:00+00:00
+ ID: e812a3b3-8e26-4699-9081-8419e38e3261
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 20:30:00+00:00
+----------------------------------------
+
+Instruction #128:
+ Timestep: 127
+ Time: 2025-11-19 20:35:00+00:00
+ ID: 0b3179dc-8d6a-4d2a-a155-a892ee6497ce
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 20:35:00+00:00
+----------------------------------------
+
+Instruction #129:
+ Timestep: 128
+ Time: 2025-11-19 20:40:00+00:00
+ ID: b9035e27-884c-41c0-ad46-8379d49e97b6
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 20:40:00+00:00
+----------------------------------------
+
+Instruction #130:
+ Timestep: 129
+ Time: 2025-11-19 20:45:00+00:00
+ ID: cb40cb64-0ab0-422b-aa0f-621c8b681015
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 20:45:00+00:00
+----------------------------------------
+
+Instruction #131:
+ Timestep: 130
+ Time: 2025-11-19 20:50:00+00:00
+ ID: 146a2acc-ae78-404f-90ba-8e270101ef05
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 20:50:00+00:00
+----------------------------------------
+
+Instruction #132:
+ Timestep: 131
+ Time: 2025-11-19 20:55:00+00:00
+ ID: 26934c57-e9d0-4724-97fa-2bbe7a4beecf
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 20:55:00+00:00
+----------------------------------------
+
+Instruction #133:
+ Timestep: 132
+ Time: 2025-11-19 21:00:00+00:00
+ ID: b5d44fac-ce73-456e-864d-e27449666098
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 21:00:00+00:00
+----------------------------------------
+
+Instruction #134:
+ Timestep: 133
+ Time: 2025-11-19 21:05:00+00:00
+ ID: 2403af44-fc07-4f97-88cb-2ceff410951e
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 21:05:00+00:00
+----------------------------------------
+
+Instruction #135:
+ Timestep: 134
+ Time: 2025-11-19 21:10:00+00:00
+ ID: f63d55bf-0bee-41c4-8e93-2c493ac9962b
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 21:10:00+00:00
+----------------------------------------
+
+Instruction #136:
+ Timestep: 135
+ Time: 2025-11-19 21:15:00+00:00
+ ID: bcd63a41-b375-4f62-b513-96839852c33d
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 21:15:00+00:00
+----------------------------------------
+
+Instruction #137:
+ Timestep: 136
+ Time: 2025-11-19 21:20:00+00:00
+ ID: 05b28cc0-b4b4-423e-aa2d-f6e976b961c4
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 21:20:00+00:00
+----------------------------------------
+
+Instruction #138:
+ Timestep: 137
+ Time: 2025-11-19 21:25:00+00:00
+ ID: 6f194b71-f80f-4bdf-b143-b9f1550b5360
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 21:25:00+00:00
+----------------------------------------
+
+Instruction #139:
+ Timestep: 138
+ Time: 2025-11-19 21:30:00+00:00
+ ID: e3df84ce-ea85-42dd-93d4-e38a2fde2296
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 21:30:00+00:00
+----------------------------------------
+
+Instruction #140:
+ Timestep: 139
+ Time: 2025-11-19 21:35:00+00:00
+ ID: ce5d749b-7fcf-44d0-8adb-12ed7662e72b
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 21:35:00+00:00
+----------------------------------------
+
+Instruction #141:
+ Timestep: 140
+ Time: 2025-11-19 21:40:00+00:00
+ ID: 07f86d66-2a46-46f6-a989-0482dea67064
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 21:40:00+00:00
+----------------------------------------
+
+Instruction #142:
+ Timestep: 141
+ Time: 2025-11-19 21:45:00+00:00
+ ID: 93c4ce73-6805-41f9-850a-90c6e4501ed1
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 21:45:00+00:00
+----------------------------------------
+
+Instruction #143:
+ Timestep: 142
+ Time: 2025-11-19 21:50:00+00:00
+ ID: ff2af496-efb4-4c6f-a0bb-9e7495c456d6
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 21:50:00+00:00
+----------------------------------------
+
+Instruction #144:
+ Timestep: 143
+ Time: 2025-11-19 21:55:00+00:00
+ ID: 2eaa33ff-07e2-4940-9ea7-55a3384ca827
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 21:55:00+00:00
+----------------------------------------
+
+Instruction #145:
+ Timestep: 144
+ Time: 2025-11-19 22:00:00+00:00
+ ID: 56f7c606-4d74-4577-adb7-3701ea38118c
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 22:00:00+00:00
+----------------------------------------
+
+Instruction #146:
+ Timestep: 145
+ Time: 2025-11-19 22:05:00+00:00
+ ID: 1c64bc95-2aec-43df-89f5-e9ee03473796
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 22:05:00+00:00
+----------------------------------------
+
+Instruction #147:
+ Timestep: 146
+ Time: 2025-11-19 22:10:00+00:00
+ ID: c8c6433b-2820-4d9c-9879-c5563f542030
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 22:10:00+00:00
+----------------------------------------
+
+Instruction #148:
+ Timestep: 147
+ Time: 2025-11-19 22:15:00+00:00
+ ID: 42a66fa3-2c42-454a-9724-488027355580
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 22:15:00+00:00
+----------------------------------------
+
+Instruction #149:
+ Timestep: 148
+ Time: 2025-11-19 22:20:00+00:00
+ ID: 3748f427-6d64-4572-bb00-6b453b697df2
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 22:20:00+00:00
+----------------------------------------
+
+Instruction #150:
+ Timestep: 149
+ Time: 2025-11-19 22:25:00+00:00
+ ID: 3f5440ea-071e-4428-a880-3b07047ae281
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 22:25:00+00:00
+----------------------------------------
+
+Instruction #151:
+ Timestep: 150
+ Time: 2025-11-19 22:30:00+00:00
+ ID: ffe9d2ba-f0ea-47f7-92e5-28e50752dff2
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 22:30:00+00:00
+----------------------------------------
+
+Instruction #152:
+ Timestep: 151
+ Time: 2025-11-19 22:35:00+00:00
+ ID: 440e157b-bae8-464f-b116-32b11b14889d
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 22:35:00+00:00
+----------------------------------------
+
+Instruction #153:
+ Timestep: 152
+ Time: 2025-11-19 22:40:00+00:00
+ ID: 723ace7e-dc13-4a4e-96cc-65f8d87bbbc5
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 22:40:00+00:00
+----------------------------------------
+
+Instruction #154:
+ Timestep: 153
+ Time: 2025-11-19 22:45:00+00:00
+ ID: d20998b4-eb9a-47d3-9fe3-f496fcaf9e8c
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 22:45:00+00:00
+----------------------------------------
+
+Instruction #155:
+ Timestep: 154
+ Time: 2025-11-19 22:50:00+00:00
+ ID: 6b00bbc8-e1fe-4100-8a41-e60bf5829883
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 22:50:00+00:00
+----------------------------------------
+
+Instruction #156:
+ Timestep: 155
+ Time: 2025-11-19 22:55:00+00:00
+ ID: 4df02ebc-5ffa-4b39-8888-676d18e99a9b
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 22:55:00+00:00
+----------------------------------------
+
+Instruction #157:
+ Timestep: 156
+ Time: 2025-11-19 23:00:00+00:00
+ ID: d5c8b139-36a9-4721-b692-0d0a625ce42d
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 23:00:00+00:00
+----------------------------------------
+
+Instruction #158:
+ Timestep: 157
+ Time: 2025-11-19 23:05:00+00:00
+ ID: d401f481-73ab-44d0-b413-fc29acb76922
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 23:05:00+00:00
+----------------------------------------
+
+Instruction #159:
+ Timestep: 158
+ Time: 2025-11-19 23:10:00+00:00
+ ID: aad8cd17-858b-4fe9-87fd-13af65dcaac4
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 23:10:00+00:00
+----------------------------------------
+
+Instruction #160:
+ Timestep: 159
+ Time: 2025-11-19 23:15:00+00:00
+ ID: 98a3ed11-bd56-46a5-ac5f-b32419e673ac
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 23:15:00+00:00
+----------------------------------------
+
+Instruction #161:
+ Timestep: 160
+ Time: 2025-11-19 23:20:00+00:00
+ ID: 55d88eec-ddc7-4a77-a8ab-60743df96c4a
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 23:20:00+00:00
+----------------------------------------
+
+Instruction #162:
+ Timestep: 161
+ Time: 2025-11-19 23:25:00+00:00
+ ID: b11d4779-55de-473b-8762-50e156a6cd70
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 23:25:00+00:00
+----------------------------------------
+
+Instruction #163:
+ Timestep: 162
+ Time: 2025-11-19 23:30:00+00:00
+ ID: 2e749bef-bbfc-42ea-abaa-8ac9757af31e
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 23:30:00+00:00
+----------------------------------------
+
+Instruction #164:
+ Timestep: 163
+ Time: 2025-11-19 23:35:00+00:00
+ ID: 1fb75821-072c-449b-8fb2-41bbc174f6a5
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 23:35:00+00:00
+----------------------------------------
+
+Instruction #165:
+ Timestep: 164
+ Time: 2025-11-19 23:40:00+00:00
+ ID: c5aa0d82-fe72-48d6-99bf-6dd2897e3e0c
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 23:40:00+00:00
+----------------------------------------
+
+Instruction #166:
+ Timestep: 165
+ Time: 2025-11-19 23:45:00+00:00
+ ID: 3e77957f-c0af-4acf-b443-238c05cf256b
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 23:45:00+00:00
+----------------------------------------
+
+Instruction #167:
+ Timestep: 166
+ Time: 2025-11-19 23:50:00+00:00
+ ID: bd239563-bd68-4498-9b51-482bf978a807
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 23:50:00+00:00
+----------------------------------------
+
+Instruction #168:
+ Timestep: 167
+ Time: 2025-11-19 23:55:00+00:00
+ ID: 585e91f1-865f-4498-a0d8-e27e878ab820
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 23:55:00+00:00
+----------------------------------------
+
+Instruction #169:
+ Timestep: 168
+ Time: 2025-11-20 00:00:00+00:00
+ ID: 5d18a339-cb42-4aba-aadf-50a23362f9f7
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 00:00:00+00:00
+----------------------------------------
+
+Instruction #170:
+ Timestep: 169
+ Time: 2025-11-20 00:05:00+00:00
+ ID: a0dad660-892e-441f-9950-479f40939605
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 00:05:00+00:00
+----------------------------------------
+
+Instruction #171:
+ Timestep: 170
+ Time: 2025-11-20 00:10:00+00:00
+ ID: 77c9b34f-1dd0-4633-ab91-35d09fe6edd0
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 00:10:00+00:00
+----------------------------------------
+
+Instruction #172:
+ Timestep: 171
+ Time: 2025-11-20 00:15:00+00:00
+ ID: ba5e4a22-c565-4e76-8e9f-453ce97546a5
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 00:15:00+00:00
+----------------------------------------
+
+Instruction #173:
+ Timestep: 172
+ Time: 2025-11-20 00:20:00+00:00
+ ID: e0725995-cda6-4b94-b2ef-e65674f1e159
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 00:20:00+00:00
+----------------------------------------
+
+Instruction #174:
+ Timestep: 173
+ Time: 2025-11-20 00:25:00+00:00
+ ID: ef0e5de9-698d-469c-aded-e4cc9c8f6495
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 00:25:00+00:00
+----------------------------------------
+
+Instruction #175:
+ Timestep: 174
+ Time: 2025-11-20 00:30:00+00:00
+ ID: cb8db03e-0da1-45bf-abd7-2b3e0c2373e3
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 00:30:00+00:00
+----------------------------------------
+
+Instruction #176:
+ Timestep: 175
+ Time: 2025-11-20 00:35:00+00:00
+ ID: 89d1f492-82ab-4589-a14a-434a33fb730c
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 00:35:00+00:00
+----------------------------------------
+
+Instruction #177:
+ Timestep: 176
+ Time: 2025-11-20 00:40:00+00:00
+ ID: 4fd2f06b-4866-4db4-85d6-9eccee09a5b2
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 00:40:00+00:00
+----------------------------------------
+
+Instruction #178:
+ Timestep: 177
+ Time: 2025-11-20 00:45:00+00:00
+ ID: 7149a94b-42bd-4551-b120-f868a618d660
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 00:45:00+00:00
+----------------------------------------
+
+Instruction #179:
+ Timestep: 178
+ Time: 2025-11-20 00:50:00+00:00
+ ID: feb713d3-2e40-4027-a5d3-7422f011cdb7
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 00:50:00+00:00
+----------------------------------------
+
+Instruction #180:
+ Timestep: 179
+ Time: 2025-11-20 00:55:00+00:00
+ ID: f0464679-c3fa-411e-839f-c323410fec6f
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 00:55:00+00:00
+----------------------------------------
+
+Instruction #181:
+ Timestep: 180
+ Time: 2025-11-20 01:00:00+00:00
+ ID: 203d7ffc-1808-44cc-8e85-0801f9e3efd1
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 01:00:00+00:00
+----------------------------------------
+
+Instruction #182:
+ Timestep: 181
+ Time: 2025-11-20 01:05:00+00:00
+ ID: d3ea4358-62f6-40db-95a0-216dd5adcfd4
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 01:05:00+00:00
+----------------------------------------
+
+Instruction #183:
+ Timestep: 182
+ Time: 2025-11-20 01:10:00+00:00
+ ID: edaec515-d7ad-401a-bc22-ffd305ed7695
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 01:10:00+00:00
+----------------------------------------
+
+Instruction #184:
+ Timestep: 183
+ Time: 2025-11-20 01:15:00+00:00
+ ID: 3466819d-9770-4719-a6f8-ed0df52a7f38
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 01:15:00+00:00
+----------------------------------------
+
+Instruction #185:
+ Timestep: 184
+ Time: 2025-11-20 01:20:00+00:00
+ ID: 808c1d0f-7e5d-4b3d-96de-f9a0811b8826
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 01:20:00+00:00
+----------------------------------------
+
+Instruction #186:
+ Timestep: 185
+ Time: 2025-11-20 01:25:00+00:00
+ ID: 614af99a-52ba-4ab3-98a4-a571c4fd11ec
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 01:25:00+00:00
+----------------------------------------
+
+Instruction #187:
+ Timestep: 186
+ Time: 2025-11-20 01:30:00+00:00
+ ID: 94ef6fc0-d19b-424f-b642-a78afcc32c6f
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 01:30:00+00:00
+----------------------------------------
+
+Instruction #188:
+ Timestep: 187
+ Time: 2025-11-20 01:35:00+00:00
+ ID: 07fd219f-d127-4ef1-b414-1331e23da7e5
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 01:35:00+00:00
+----------------------------------------
+
+Instruction #189:
+ Timestep: 188
+ Time: 2025-11-20 01:40:00+00:00
+ ID: 6a1c8dab-3967-4193-8aaf-ae6a5cbe0247
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 01:40:00+00:00
+----------------------------------------
+
+Instruction #190:
+ Timestep: 189
+ Time: 2025-11-20 01:45:00+00:00
+ ID: 575dd889-1c48-416c-9dce-978a66366324
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 01:45:00+00:00
+----------------------------------------
+
+Instruction #191:
+ Timestep: 190
+ Time: 2025-11-20 01:50:00+00:00
+ ID: 59048ff7-8c64-455b-8393-33bd65eff843
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 01:50:00+00:00
+----------------------------------------
+
+Instruction #192:
+ Timestep: 191
+ Time: 2025-11-20 01:55:00+00:00
+ ID: f438d6c6-11a3-44c1-b83e-ef006fe0a5c7
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 01:55:00+00:00
+----------------------------------------
+
+Instruction #193:
+ Timestep: 192
+ Time: 2025-11-20 02:00:00+00:00
+ ID: 86bf1792-448c-463b-ac95-27e10e0493e9
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 02:00:00+00:00
+----------------------------------------
+
+Instruction #194:
+ Timestep: 193
+ Time: 2025-11-20 02:05:00+00:00
+ ID: 84c5f5b2-94f7-4923-9ddd-1c2151bbcfd2
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 02:05:00+00:00
+----------------------------------------
+
+Instruction #195:
+ Timestep: 194
+ Time: 2025-11-20 02:10:00+00:00
+ ID: 3c7145d4-d0a0-4329-aa1e-0a9027db1501
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 02:10:00+00:00
+----------------------------------------
+
+Instruction #196:
+ Timestep: 195
+ Time: 2025-11-20 02:15:00+00:00
+ ID: 4ee3c2a0-e5c5-4ded-a81c-618ba6afdf65
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 02:15:00+00:00
+----------------------------------------
+
+Instruction #197:
+ Timestep: 196
+ Time: 2025-11-20 02:20:00+00:00
+ ID: 554ca15a-9d19-4f74-b93d-524c1c4017d8
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 02:20:00+00:00
+----------------------------------------
+
+Instruction #198:
+ Timestep: 197
+ Time: 2025-11-20 02:25:00+00:00
+ ID: 3c0c7045-0bbc-4545-85e7-5b6b4a55d89b
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 02:25:00+00:00
+----------------------------------------
+
+Instruction #199:
+ Timestep: 198
+ Time: 2025-11-20 02:30:00+00:00
+ ID: 447a31ba-3aed-4439-829b-72a41205d1d7
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 02:30:00+00:00
+----------------------------------------
+
+Instruction #200:
+ Timestep: 199
+ Time: 2025-11-20 02:35:00+00:00
+ ID: b8356de4-d80b-4533-8584-248756b49f44
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 02:35:00+00:00
+----------------------------------------
+
+Instruction #201:
+ Timestep: 200
+ Time: 2025-11-20 02:40:00+00:00
+ ID: f250c9ff-6cb6-47b3-bf7d-01bdf98b4d70
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 02:40:00+00:00
+----------------------------------------
+
+Instruction #202:
+ Timestep: 201
+ Time: 2025-11-20 02:45:00+00:00
+ ID: b8fb013e-ec61-481c-b717-7bb8839d416d
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 02:45:00+00:00
+----------------------------------------
+
+Instruction #203:
+ Timestep: 202
+ Time: 2025-11-20 02:50:00+00:00
+ ID: 31354d50-b08c-4ae8-8c97-8fc219542103
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 02:50:00+00:00
+----------------------------------------
+
+Instruction #204:
+ Timestep: 203
+ Time: 2025-11-20 02:55:00+00:00
+ ID: d3c8f9a9-53dc-44de-a24c-6ef8c637d818
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 02:55:00+00:00
+----------------------------------------
+
+Instruction #205:
+ Timestep: 204
+ Time: 2025-11-20 03:00:00+00:00
+ ID: 2a951855-ae73-4fa7-a7a7-909768dd9ee1
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 03:00:00+00:00
+----------------------------------------
+
+Instruction #206:
+ Timestep: 205
+ Time: 2025-11-20 03:05:00+00:00
+ ID: e7bea64c-9610-4a7c-a4e0-5d0e5f15a041
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 03:05:00+00:00
+----------------------------------------
+
+Instruction #207:
+ Timestep: 206
+ Time: 2025-11-20 03:10:00+00:00
+ ID: ba7ee545-8361-4409-8220-e3745593795f
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 03:10:00+00:00
+----------------------------------------
+
+Instruction #208:
+ Timestep: 207
+ Time: 2025-11-20 03:15:00+00:00
+ ID: a741032e-737f-4390-97ef-7fce241b7463
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 03:15:00+00:00
+----------------------------------------
+
+Instruction #209:
+ Timestep: 208
+ Time: 2025-11-20 03:20:00+00:00
+ ID: 38809127-be9c-4bdb-ac74-12b80f3317aa
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 03:20:00+00:00
+----------------------------------------
+
+Instruction #210:
+ Timestep: 209
+ Time: 2025-11-20 03:25:00+00:00
+ ID: d8f398de-9257-4d18-9415-3ed41786c845
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 03:25:00+00:00
+----------------------------------------
+
+Instruction #211:
+ Timestep: 210
+ Time: 2025-11-20 03:30:00+00:00
+ ID: f805a453-9766-46c9-a54f-89bfb2353628
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 03:30:00+00:00
+----------------------------------------
+
+Instruction #212:
+ Timestep: 211
+ Time: 2025-11-20 03:35:00+00:00
+ ID: c0faf3a1-899c-4b30-be0c-6de50e226b8e
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 03:35:00+00:00
+----------------------------------------
+
+Instruction #213:
+ Timestep: 212
+ Time: 2025-11-20 03:40:00+00:00
+ ID: 4939a2ff-2d56-4fcb-98dd-5c4f53d3949f
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 03:40:00+00:00
+----------------------------------------
+
+Instruction #214:
+ Timestep: 213
+ Time: 2025-11-20 03:45:00+00:00
+ ID: 9830ecc4-8e59-4652-b50f-3e4e3aaea52b
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 03:45:00+00:00
+----------------------------------------
+
+Instruction #215:
+ Timestep: 214
+ Time: 2025-11-20 03:50:00+00:00
+ ID: afc37158-3292-470e-a8b3-e76670036b04
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 03:50:00+00:00
+----------------------------------------
+
+Instruction #216:
+ Timestep: 215
+ Time: 2025-11-20 03:55:00+00:00
+ ID: e1b5313e-489d-4873-83b5-964620b595a3
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 03:55:00+00:00
+----------------------------------------
+
+Instruction #217:
+ Timestep: 216
+ Time: 2025-11-20 04:00:00+00:00
+ ID: 9f994e57-a96b-432d-a658-06dce044f881
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 04:00:00+00:00
+----------------------------------------
+
+Instruction #218:
+ Timestep: 217
+ Time: 2025-11-20 04:05:00+00:00
+ ID: 07f77fca-308d-497f-895e-d896dfc21f17
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 04:05:00+00:00
+----------------------------------------
+
+Instruction #219:
+ Timestep: 218
+ Time: 2025-11-20 04:10:00+00:00
+ ID: 558d410a-fdb0-4b24-b4db-fbd3d357a277
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 04:10:00+00:00
+----------------------------------------
+
+Instruction #220:
+ Timestep: 219
+ Time: 2025-11-20 04:15:00+00:00
+ ID: 4f8a87de-ad3b-4e0f-aee7-7de0f5ec4df0
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 04:15:00+00:00
+----------------------------------------
+
+Instruction #221:
+ Timestep: 220
+ Time: 2025-11-20 04:20:00+00:00
+ ID: 878f031b-7730-4286-94e3-0a3188fb9204
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 04:20:00+00:00
+----------------------------------------
+
+Instruction #222:
+ Timestep: 221
+ Time: 2025-11-20 04:25:00+00:00
+ ID: ee8bcb8a-eb3f-4973-b80a-f38b48052d5e
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 04:25:00+00:00
+----------------------------------------
+
+Instruction #223:
+ Timestep: 222
+ Time: 2025-11-20 04:30:00+00:00
+ ID: f76696f7-64ab-4ad3-affc-bfad2f5c78d4
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 04:30:00+00:00
+----------------------------------------
+
+Instruction #224:
+ Timestep: 223
+ Time: 2025-11-20 04:35:00+00:00
+ ID: 9efdd12c-c92e-4270-a19f-69798d5e99e8
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 04:35:00+00:00
+----------------------------------------
+
+Instruction #225:
+ Timestep: 224
+ Time: 2025-11-20 04:40:00+00:00
+ ID: 47698f42-7367-4b48-99d5-d2b52dff217e
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 04:40:00+00:00
+----------------------------------------
+
+Instruction #226:
+ Timestep: 225
+ Time: 2025-11-20 04:45:00+00:00
+ ID: 61e2ab37-aae1-4586-81db-378d440ae1cd
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 04:45:00+00:00
+----------------------------------------
+
+Instruction #227:
+ Timestep: 226
+ Time: 2025-11-20 04:50:00+00:00
+ ID: dd4d6df6-144f-40df-a291-6713789a343a
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 04:50:00+00:00
+----------------------------------------
+
+Instruction #228:
+ Timestep: 227
+ Time: 2025-11-20 04:55:00+00:00
+ ID: 74faf011-b4d9-4bc0-b8b0-443315a9f332
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 04:55:00+00:00
+----------------------------------------
+
+Instruction #229:
+ Timestep: 228
+ Time: 2025-11-20 05:00:00+00:00
+ ID: 820b0a7c-0782-458a-975e-8eee4d6466b4
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 05:00:00+00:00
+----------------------------------------
+
+Instruction #230:
+ Timestep: 229
+ Time: 2025-11-20 05:05:00+00:00
+ ID: 9dfd830b-59a9-4230-ac81-769afa2fb12e
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 05:05:00+00:00
+----------------------------------------
+
+Instruction #231:
+ Timestep: 230
+ Time: 2025-11-20 05:10:00+00:00
+ ID: 901ac14e-c33e-40dd-960f-ffbf5961d98d
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 05:10:00+00:00
+----------------------------------------
+
+Instruction #232:
+ Timestep: 231
+ Time: 2025-11-20 05:15:00+00:00
+ ID: 4b02a55f-d065-47d5-889f-b896a131bf7c
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 05:15:00+00:00
+----------------------------------------
+
+Instruction #233:
+ Timestep: 232
+ Time: 2025-11-20 05:20:00+00:00
+ ID: 3f828ad2-54c4-439e-9505-a1f64dba4566
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 05:20:00+00:00
+----------------------------------------
+
+Instruction #234:
+ Timestep: 233
+ Time: 2025-11-20 05:25:00+00:00
+ ID: 82f4bfe1-56eb-44cb-94a5-a2fd867c54eb
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 05:25:00+00:00
+----------------------------------------
+
+Instruction #235:
+ Timestep: 234
+ Time: 2025-11-20 05:30:00+00:00
+ ID: b4556c81-08e2-44eb-ba5c-f023fea2a3d7
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 05:30:00+00:00
+----------------------------------------
+
+Instruction #236:
+ Timestep: 235
+ Time: 2025-11-20 05:35:00+00:00
+ ID: e23b08fc-d00d-4981-ad41-3fefc1a34312
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 05:35:00+00:00
+----------------------------------------
+
+Instruction #237:
+ Timestep: 236
+ Time: 2025-11-20 05:40:00+00:00
+ ID: 110f51cb-269b-4456-bbe2-a4a03fbfc279
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 05:40:00+00:00
+----------------------------------------
+
+Instruction #238:
+ Timestep: 237
+ Time: 2025-11-20 05:45:00+00:00
+ ID: d2b52303-28c2-4c29-8f78-381ac8c647ea
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 05:45:00+00:00
+----------------------------------------
+
+Instruction #239:
+ Timestep: 238
+ Time: 2025-11-20 05:50:00+00:00
+ ID: 292f00c9-4ba3-4521-82db-43f036a41d4f
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 05:50:00+00:00
+----------------------------------------
+
+Instruction #240:
+ Timestep: 239
+ Time: 2025-11-20 05:55:00+00:00
+ ID: c0f15adc-fb4f-4b14-9151-8ac6e797aec4
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 05:55:00+00:00
+----------------------------------------
+
+Instruction #241:
+ Timestep: 240
+ Time: 2025-11-20 06:00:00+00:00
+ ID: 5e6b85b7-e7be-4f2c-95be-260b3b06fe11
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 06:00:00+00:00
+----------------------------------------
+
+Instruction #242:
+ Timestep: 241
+ Time: 2025-11-20 06:05:00+00:00
+ ID: d49665e3-6aab-42ff-b534-29cfb259d19b
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 06:05:00+00:00
+----------------------------------------
+
+Instruction #243:
+ Timestep: 242
+ Time: 2025-11-20 06:10:00+00:00
+ ID: 09c660a3-6e7a-42b8-91c9-b7837262078d
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 06:10:00+00:00
+----------------------------------------
+
+Instruction #244:
+ Timestep: 243
+ Time: 2025-11-20 06:15:00+00:00
+ ID: 62e6fa58-05d0-4cae-bab2-f30e92925573
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 06:15:00+00:00
+----------------------------------------
+
+Instruction #245:
+ Timestep: 244
+ Time: 2025-11-20 06:20:00+00:00
+ ID: c8f61400-58b7-4035-819a-eeaa66f11763
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 06:20:00+00:00
+----------------------------------------
+
+Instruction #246:
+ Timestep: 245
+ Time: 2025-11-20 06:25:00+00:00
+ ID: 537c9937-8579-47a0-8c42-6d2d81957b5a
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 06:25:00+00:00
+----------------------------------------
+
+Instruction #247:
+ Timestep: 246
+ Time: 2025-11-20 06:30:00+00:00
+ ID: 91378f95-efe8-4957-9f84-0f0b67371bf7
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 06:30:00+00:00
+----------------------------------------
+
+Instruction #248:
+ Timestep: 247
+ Time: 2025-11-20 06:35:00+00:00
+ ID: 4b848139-c0d3-4046-8741-8e1ee1d05ba8
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 06:35:00+00:00
+----------------------------------------
+
+Instruction #249:
+ Timestep: 248
+ Time: 2025-11-20 06:40:00+00:00
+ ID: 8a298f67-1941-4c54-a935-b623bd2b16da
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 06:40:00+00:00
+----------------------------------------
+
+Instruction #250:
+ Timestep: 249
+ Time: 2025-11-20 06:45:00+00:00
+ ID: a4661135-9abf-4af6-9a7d-423fdd58284c
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 06:45:00+00:00
+----------------------------------------
+
+Instruction #251:
+ Timestep: 250
+ Time: 2025-11-20 06:50:00+00:00
+ ID: 4bbfce99-12a5-44e1-8156-1565cc3f305c
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 06:50:00+00:00
+----------------------------------------
+
+Instruction #252:
+ Timestep: 251
+ Time: 2025-11-20 06:55:00+00:00
+ ID: 6c818ed5-ebda-4a18-b15c-292f8a834465
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 06:55:00+00:00
+----------------------------------------
+
+Instruction #253:
+ Timestep: 252
+ Time: 2025-11-20 07:00:00+00:00
+ ID: 9b8ebdce-3476-4165-89d0-63b68b7c84d7
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 07:00:00+00:00
+----------------------------------------
+
+Instruction #254:
+ Timestep: 253
+ Time: 2025-11-20 07:05:00+00:00
+ ID: 5d40ef96-c222-432a-8dec-bcecf26bfbd1
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 07:05:00+00:00
+----------------------------------------
+
+Instruction #255:
+ Timestep: 254
+ Time: 2025-11-20 07:10:00+00:00
+ ID: 92bcc3b6-7872-4371-a26e-ab3a0f13a849
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 07:10:00+00:00
+----------------------------------------
+
+Instruction #256:
+ Timestep: 255
+ Time: 2025-11-20 07:15:00+00:00
+ ID: cb262382-b866-4140-8fdf-69a4a86fd95a
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 07:15:00+00:00
+----------------------------------------
+
+Instruction #257:
+ Timestep: 256
+ Time: 2025-11-20 07:20:00+00:00
+ ID: 5ff0ac4c-c64d-473a-a2ef-3d25c8052cc8
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 07:20:00+00:00
+----------------------------------------
+
+Instruction #258:
+ Timestep: 257
+ Time: 2025-11-20 07:25:00+00:00
+ ID: 1b9c4fb6-a7a3-4778-9402-bd332a05c60c
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 07:25:00+00:00
+----------------------------------------
+
+Instruction #259:
+ Timestep: 258
+ Time: 2025-11-20 07:30:00+00:00
+ ID: 2df1d3d5-e930-4e94-bfb4-d1562ebacb40
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 07:30:00+00:00
+----------------------------------------
+
+Instruction #260:
+ Timestep: 259
+ Time: 2025-11-20 07:35:00+00:00
+ ID: 4e1289d2-896c-470b-ade9-9c95bb5de697
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 07:35:00+00:00
+----------------------------------------
+
+Instruction #261:
+ Timestep: 260
+ Time: 2025-11-20 07:40:00+00:00
+ ID: ef556afe-2348-482d-9545-e7bfba9c7806
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 07:40:00+00:00
+----------------------------------------
+
+Instruction #262:
+ Timestep: 261
+ Time: 2025-11-20 07:45:00+00:00
+ ID: 8d7eaf43-d6dc-4983-b050-470f144bd2af
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 07:45:00+00:00
+----------------------------------------
+
+Instruction #263:
+ Timestep: 262
+ Time: 2025-11-20 07:50:00+00:00
+ ID: 86ce0d05-df0e-467e-b296-c5a1b21c4045
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 07:50:00+00:00
+----------------------------------------
+
+Instruction #264:
+ Timestep: 263
+ Time: 2025-11-20 07:55:00+00:00
+ ID: 6726097c-b7b4-4cd6-a045-50e48763d6bd
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 07:55:00+00:00
+----------------------------------------
+
+Instruction #265:
+ Timestep: 264
+ Time: 2025-11-20 08:00:00+00:00
+ ID: 4a62dc29-d054-42f2-bcfa-dc436bfd3662
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 08:00:00+00:00
+----------------------------------------
+
+Instruction #266:
+ Timestep: 265
+ Time: 2025-11-20 08:05:00+00:00
+ ID: 995097c0-fbc8-45ec-ad96-b4d3894ccc8a
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 08:05:00+00:00
+----------------------------------------
+
+Instruction #267:
+ Timestep: 266
+ Time: 2025-11-20 08:10:00+00:00
+ ID: a711dfb0-3231-4427-bd65-19a712df01c7
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 08:10:00+00:00
+----------------------------------------
+
+Instruction #268:
+ Timestep: 267
+ Time: 2025-11-20 08:15:00+00:00
+ ID: 08ceec3e-91bd-46c0-8936-f39444da0abe
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 08:15:00+00:00
+----------------------------------------
+
+Instruction #269:
+ Timestep: 268
+ Time: 2025-11-20 08:20:00+00:00
+ ID: 7775b2e0-687e-4341-8541-44c4198f6042
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 08:20:00+00:00
+----------------------------------------
+
+Instruction #270:
+ Timestep: 269
+ Time: 2025-11-20 08:25:00+00:00
+ ID: c521d12f-0f37-4d4e-95c5-7c46dd5b1149
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 08:25:00+00:00
+----------------------------------------
+
+Instruction #271:
+ Timestep: 270
+ Time: 2025-11-20 08:30:00+00:00
+ ID: 02481576-83d2-42d6-afbe-f15afc1312af
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 08:30:00+00:00
+----------------------------------------
+
+Instruction #272:
+ Timestep: 271
+ Time: 2025-11-20 08:35:00+00:00
+ ID: 21820305-3d99-4c65-9583-9bfeb9740a37
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 08:35:00+00:00
+----------------------------------------
+
+Instruction #273:
+ Timestep: 272
+ Time: 2025-11-20 08:40:00+00:00
+ ID: fe62914b-fbfd-4a39-a20e-45cc24c51083
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 08:40:00+00:00
+----------------------------------------
+
+Instruction #274:
+ Timestep: 273
+ Time: 2025-11-20 08:45:00+00:00
+ ID: b3f42212-a3f4-47cc-b0b7-7ecf763d60f5
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 08:45:00+00:00
+----------------------------------------
+
+Instruction #275:
+ Timestep: 274
+ Time: 2025-11-20 08:50:00+00:00
+ ID: f307e66d-d6a4-4c74-a8d7-0caed7663a8a
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 08:50:00+00:00
+----------------------------------------
+
+Instruction #276:
+ Timestep: 275
+ Time: 2025-11-20 08:55:00+00:00
+ ID: 0b666012-7341-4098-a06d-7773bd472f36
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 08:55:00+00:00
+----------------------------------------
+
+Instruction #277:
+ Timestep: 276
+ Time: 2025-11-20 09:00:00+00:00
+ ID: e67f89e4-3d4c-4bc5-a18b-b96a45620d8e
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 09:00:00+00:00
+----------------------------------------
+
+Instruction #278:
+ Timestep: 277
+ Time: 2025-11-20 09:05:00+00:00
+ ID: 45850660-3b98-4199-9e81-08f019b24783
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 09:05:00+00:00
+----------------------------------------
+
+Instruction #279:
+ Timestep: 278
+ Time: 2025-11-20 09:10:00+00:00
+ ID: b99bd11e-60c8-4c01-83ca-e5b948be56f0
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 09:10:00+00:00
+----------------------------------------
+
+Instruction #280:
+ Timestep: 279
+ Time: 2025-11-20 09:15:00+00:00
+ ID: f26ae8fc-c07a-4c2c-9c03-5eb023565549
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 09:15:00+00:00
+----------------------------------------
+
+Instruction #281:
+ Timestep: 280
+ Time: 2025-11-20 09:20:00+00:00
+ ID: a035e49b-e0b4-4c18-87ec-f32bec2b6375
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 09:20:00+00:00
+----------------------------------------
+
+Instruction #282:
+ Timestep: 281
+ Time: 2025-11-20 09:25:00+00:00
+ ID: 59940f45-119b-4be8-9cb0-f44ab32ec3fe
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 09:25:00+00:00
+----------------------------------------
+
+Instruction #283:
+ Timestep: 282
+ Time: 2025-11-20 09:30:00+00:00
+ ID: abfb88b8-8b30-4272-a49c-ff5fa43824b5
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 09:30:00+00:00
+----------------------------------------
+
+Instruction #284:
+ Timestep: 283
+ Time: 2025-11-20 09:35:00+00:00
+ ID: 345b9687-eb43-45ca-a850-ca6c2e81f1ce
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 09:35:00+00:00
+----------------------------------------
+
+Instruction #285:
+ Timestep: 284
+ Time: 2025-11-20 09:40:00+00:00
+ ID: 8e03482c-1f6f-499f-927e-03950d45955a
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 09:40:00+00:00
+----------------------------------------
+
+Instruction #286:
+ Timestep: 285
+ Time: 2025-11-20 09:45:00+00:00
+ ID: 0246f49a-f36d-4679-b044-971c9eb91107
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 09:45:00+00:00
+----------------------------------------
+
+Instruction #287:
+ Timestep: 286
+ Time: 2025-11-20 09:50:00+00:00
+ ID: 8b878275-ee24-46ee-97b2-b08dcee17085
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 09:50:00+00:00
+----------------------------------------
+
+Instruction #288:
+ Timestep: 287
+ Time: 2025-11-20 09:55:00+00:00
+ ID: 71264dbe-5efa-4839-a7a3-97f2c477327d
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 09:55:00+00:00
+----------------------------------------
diff --git a/flexmeasures_s2/profile_steering/examples/instructions/itho_instructions_flask_filltarget_only_D=1_B=200_S=30_T=288.txt b/flexmeasures_s2/profile_steering/examples/instructions/itho_instructions_flask_filltarget_only_D=1_B=200_S=30_T=288.txt
new file mode 100644
index 0000000..b6f5d25
--- /dev/null
+++ b/flexmeasures_s2/profile_steering/examples/instructions/itho_instructions_flask_filltarget_only_D=1_B=200_S=30_T=288.txt
@@ -0,0 +1,177 @@
+================================================================================
+ITHO DHW Heat Pump Instructions Flask Filltarget Only
+================================================================================
+Planning start time: 2025-11-19 09:55:00+00:00
+Planning window: 1 days 00:00:00
+Resolution: 0 days 00:05:00
+Total timesteps: 288
+================================================================================
+
+Device: itho_dhw_device_flask_001
+Number of instructions: 288
+--------------------------------------------------------------------------------
+
+Instruction Timeline:
+--------------------------------------------------------------------------------
+ Mode: DHW_ON
+ Start: 2025-11-19 09:55:00+00:00 (timestep 0)
+ Duration: 85 timesteps (425.0 minutes)
+ Operation mode factor: 0.0
+----------------------------------------
+ Mode: DHW_OFF
+ Start: 2025-11-19 17:00:00+00:00 (timestep 85)
+ Duration: 198 timesteps (990.0 minutes)
+ Operation mode factor: 0.0
+----------------------------------------
+ Mode: DHW_ON
+ Start: 2025-11-20 09:30:00+00:00 (timestep 283)
+ Duration: 5 timesteps (25.0 minutes)
+----------------------------------------
+
+Total operation mode changes: 2
+================================================================================
+
+Detailed Instruction List:
+================================================================================
+
+Instruction #1:
+ Timestep: 0
+ Time: 2025-11-19 09:55:00+00:00
+ Operation mode: e7eeb83d-5155-4f05-acec-7b6db49b854c
+ Factor: 0.0
+ Execution time: 2025-11-19 09:40:00+00:00
+
+Instruction #2:
+ Timestep: 1
+ Time: 2025-11-19 10:00:00+00:00
+ Operation mode: e7eeb83d-5155-4f05-acec-7b6db49b854c
+ Factor: 0.0
+ Execution time: 2025-11-19 09:45:00+00:00
+
+Instruction #3:
+ Timestep: 2
+ Time: 2025-11-19 10:05:00+00:00
+ Operation mode: e7eeb83d-5155-4f05-acec-7b6db49b854c
+ Factor: 0.0
+ Execution time: 2025-11-19 09:50:00+00:00
+
+Instruction #4:
+ Timestep: 3
+ Time: 2025-11-19 10:10:00+00:00
+ Operation mode: e7eeb83d-5155-4f05-acec-7b6db49b854c
+ Factor: 0.0
+ Execution time: 2025-11-19 09:55:00+00:00
+
+Instruction #5:
+ Timestep: 4
+ Time: 2025-11-19 10:15:00+00:00
+ Operation mode: e7eeb83d-5155-4f05-acec-7b6db49b854c
+ Factor: 0.0
+ Execution time: 2025-11-19 10:00:00+00:00
+
+Instruction #6:
+ Timestep: 5
+ Time: 2025-11-19 10:20:00+00:00
+ Operation mode: e7eeb83d-5155-4f05-acec-7b6db49b854c
+ Factor: 0.0
+ Execution time: 2025-11-19 10:05:00+00:00
+
+Instruction #7:
+ Timestep: 6
+ Time: 2025-11-19 10:25:00+00:00
+ Operation mode: e7eeb83d-5155-4f05-acec-7b6db49b854c
+ Factor: 0.0
+ Execution time: 2025-11-19 10:10:00+00:00
+
+Instruction #8:
+ Timestep: 7
+ Time: 2025-11-19 10:30:00+00:00
+ Operation mode: e7eeb83d-5155-4f05-acec-7b6db49b854c
+ Factor: 0.0
+ Execution time: 2025-11-19 10:15:00+00:00
+
+Instruction #9:
+ Timestep: 8
+ Time: 2025-11-19 10:35:00+00:00
+ Operation mode: e7eeb83d-5155-4f05-acec-7b6db49b854c
+ Factor: 0.0
+ Execution time: 2025-11-19 10:20:00+00:00
+
+Instruction #10:
+ Timestep: 9
+ Time: 2025-11-19 10:40:00+00:00
+ Operation mode: e7eeb83d-5155-4f05-acec-7b6db49b854c
+ Factor: 0.0
+ Execution time: 2025-11-19 10:25:00+00:00
+
+Instruction #11:
+ Timestep: 10
+ Time: 2025-11-19 10:45:00+00:00
+ Operation mode: e7eeb83d-5155-4f05-acec-7b6db49b854c
+ Factor: 0.0
+ Execution time: 2025-11-19 10:30:00+00:00
+
+Instruction #12:
+ Timestep: 11
+ Time: 2025-11-19 10:50:00+00:00
+ Operation mode: e7eeb83d-5155-4f05-acec-7b6db49b854c
+ Factor: 0.0
+ Execution time: 2025-11-19 10:35:00+00:00
+
+Instruction #13:
+ Timestep: 12
+ Time: 2025-11-19 10:55:00+00:00
+ Operation mode: e7eeb83d-5155-4f05-acec-7b6db49b854c
+ Factor: 0.0
+ Execution time: 2025-11-19 10:40:00+00:00
+
+Instruction #14:
+ Timestep: 13
+ Time: 2025-11-19 11:00:00+00:00
+ Operation mode: e7eeb83d-5155-4f05-acec-7b6db49b854c
+ Factor: 0.0
+ Execution time: 2025-11-19 10:45:00+00:00
+
+Instruction #15:
+ Timestep: 14
+ Time: 2025-11-19 11:05:00+00:00
+ Operation mode: e7eeb83d-5155-4f05-acec-7b6db49b854c
+ Factor: 0.0
+ Execution time: 2025-11-19 10:50:00+00:00
+
+Instruction #16:
+ Timestep: 15
+ Time: 2025-11-19 11:10:00+00:00
+ Operation mode: e7eeb83d-5155-4f05-acec-7b6db49b854c
+ Factor: 0.0
+ Execution time: 2025-11-19 10:55:00+00:00
+
+Instruction #17:
+ Timestep: 16
+ Time: 2025-11-19 11:15:00+00:00
+ Operation mode: e7eeb83d-5155-4f05-acec-7b6db49b854c
+ Factor: 0.0
+ Execution time: 2025-11-19 11:00:00+00:00
+
+Instruction #18:
+ Timestep: 17
+ Time: 2025-11-19 11:20:00+00:00
+ Operation mode: e7eeb83d-5155-4f05-acec-7b6db49b854c
+ Factor: 0.0
+ Execution time: 2025-11-19 11:05:00+00:00
+
+Instruction #19:
+ Timestep: 18
+ Time: 2025-11-19 11:25:00+00:00
+ Operation mode: e7eeb83d-5155-4f05-acec-7b6db49b854c
+ Factor: 0.0
+ Execution time: 2025-11-19 11:10:00+00:00
+
+Instruction #20:
+ Timestep: 19
+ Time: 2025-11-19 11:30:00+00:00
+ Operation mode: e7eeb83d-5155-4f05-acec-7b6db49b854c
+ Factor: 0.0
+ Execution time: 2025-11-19 11:15:00+00:00
+
+... and 268 more instructions
diff --git a/flexmeasures_s2/profile_steering/examples/instructions/itho_instructions_flask_scheduler_D=1_B=200_S=30_T=288.txt b/flexmeasures_s2/profile_steering/examples/instructions/itho_instructions_flask_scheduler_D=1_B=200_S=30_T=288.txt
new file mode 100644
index 0000000..f67844b
--- /dev/null
+++ b/flexmeasures_s2/profile_steering/examples/instructions/itho_instructions_flask_scheduler_D=1_B=200_S=30_T=288.txt
@@ -0,0 +1,177 @@
+================================================================================
+ITHO DHW Heat Pump Instructions Flask Scheduler
+================================================================================
+Planning start time: 2025-11-18 11:15:00+00:00
+Planning window: 1 days 00:00:00
+Resolution: 0 days 00:05:00
+Total timesteps: 288
+================================================================================
+
+Device: itho_dhw_device_flask_001
+Number of instructions: 288
+--------------------------------------------------------------------------------
+
+Instruction Timeline:
+--------------------------------------------------------------------------------
+ Mode: DHW
+ Start: 2025-11-18 11:15:00+00:00 (timestep 0)
+ Duration: 6 timesteps (30.0 minutes)
+ Operation mode factor: 0.0
+----------------------------------------
+ Mode: OFF
+ Start: 2025-11-18 11:45:00+00:00 (timestep 6)
+ Duration: 269 timesteps (1345.0 minutes)
+ Operation mode factor: 0.0
+----------------------------------------
+ Mode: DHW
+ Start: 2025-11-19 10:10:00+00:00 (timestep 275)
+ Duration: 13 timesteps (65.0 minutes)
+----------------------------------------
+
+Total operation mode changes: 2
+================================================================================
+
+Detailed Instruction List:
+================================================================================
+
+Instruction #1:
+ Timestep: 0
+ Time: 2025-11-18 11:15:00+00:00
+ Operation mode: f93ae9b2-820d-471a-a9db-c5d4180c5370
+ Factor: 0.0
+ Execution time: 2025-11-18 11:00:00+00:00
+
+Instruction #2:
+ Timestep: 1
+ Time: 2025-11-18 11:20:00+00:00
+ Operation mode: f93ae9b2-820d-471a-a9db-c5d4180c5370
+ Factor: 0.0
+ Execution time: 2025-11-18 11:05:00+00:00
+
+Instruction #3:
+ Timestep: 2
+ Time: 2025-11-18 11:25:00+00:00
+ Operation mode: f93ae9b2-820d-471a-a9db-c5d4180c5370
+ Factor: 0.0
+ Execution time: 2025-11-18 11:10:00+00:00
+
+Instruction #4:
+ Timestep: 3
+ Time: 2025-11-18 11:30:00+00:00
+ Operation mode: f93ae9b2-820d-471a-a9db-c5d4180c5370
+ Factor: 0.0
+ Execution time: 2025-11-18 11:15:00+00:00
+
+Instruction #5:
+ Timestep: 4
+ Time: 2025-11-18 11:35:00+00:00
+ Operation mode: f93ae9b2-820d-471a-a9db-c5d4180c5370
+ Factor: 0.0
+ Execution time: 2025-11-18 11:20:00+00:00
+
+Instruction #6:
+ Timestep: 5
+ Time: 2025-11-18 11:40:00+00:00
+ Operation mode: f93ae9b2-820d-471a-a9db-c5d4180c5370
+ Factor: 0.0
+ Execution time: 2025-11-18 11:25:00+00:00
+
+Instruction #7:
+ Timestep: 6
+ Time: 2025-11-18 11:45:00+00:00
+ Operation mode: 1cbe1d95-5313-48c5-acb5-4e208195006c
+ Factor: 0.0
+ Execution time: 2025-11-18 11:30:00+00:00
+
+Instruction #8:
+ Timestep: 7
+ Time: 2025-11-18 11:50:00+00:00
+ Operation mode: 1cbe1d95-5313-48c5-acb5-4e208195006c
+ Factor: 0.0
+ Execution time: 2025-11-18 11:35:00+00:00
+
+Instruction #9:
+ Timestep: 8
+ Time: 2025-11-18 11:55:00+00:00
+ Operation mode: 1cbe1d95-5313-48c5-acb5-4e208195006c
+ Factor: 0.0
+ Execution time: 2025-11-18 11:40:00+00:00
+
+Instruction #10:
+ Timestep: 9
+ Time: 2025-11-18 12:00:00+00:00
+ Operation mode: 1cbe1d95-5313-48c5-acb5-4e208195006c
+ Factor: 0.0
+ Execution time: 2025-11-18 11:45:00+00:00
+
+Instruction #11:
+ Timestep: 10
+ Time: 2025-11-18 12:05:00+00:00
+ Operation mode: 1cbe1d95-5313-48c5-acb5-4e208195006c
+ Factor: 0.0
+ Execution time: 2025-11-18 11:50:00+00:00
+
+Instruction #12:
+ Timestep: 11
+ Time: 2025-11-18 12:10:00+00:00
+ Operation mode: 1cbe1d95-5313-48c5-acb5-4e208195006c
+ Factor: 0.0
+ Execution time: 2025-11-18 11:55:00+00:00
+
+Instruction #13:
+ Timestep: 12
+ Time: 2025-11-18 12:15:00+00:00
+ Operation mode: 1cbe1d95-5313-48c5-acb5-4e208195006c
+ Factor: 0.0
+ Execution time: 2025-11-18 12:00:00+00:00
+
+Instruction #14:
+ Timestep: 13
+ Time: 2025-11-18 12:20:00+00:00
+ Operation mode: 1cbe1d95-5313-48c5-acb5-4e208195006c
+ Factor: 0.0
+ Execution time: 2025-11-18 12:05:00+00:00
+
+Instruction #15:
+ Timestep: 14
+ Time: 2025-11-18 12:25:00+00:00
+ Operation mode: 1cbe1d95-5313-48c5-acb5-4e208195006c
+ Factor: 0.0
+ Execution time: 2025-11-18 12:10:00+00:00
+
+Instruction #16:
+ Timestep: 15
+ Time: 2025-11-18 12:30:00+00:00
+ Operation mode: 1cbe1d95-5313-48c5-acb5-4e208195006c
+ Factor: 0.0
+ Execution time: 2025-11-18 12:15:00+00:00
+
+Instruction #17:
+ Timestep: 16
+ Time: 2025-11-18 12:35:00+00:00
+ Operation mode: 1cbe1d95-5313-48c5-acb5-4e208195006c
+ Factor: 0.0
+ Execution time: 2025-11-18 12:20:00+00:00
+
+Instruction #18:
+ Timestep: 17
+ Time: 2025-11-18 12:40:00+00:00
+ Operation mode: 1cbe1d95-5313-48c5-acb5-4e208195006c
+ Factor: 0.0
+ Execution time: 2025-11-18 12:25:00+00:00
+
+Instruction #19:
+ Timestep: 18
+ Time: 2025-11-18 12:45:00+00:00
+ Operation mode: 1cbe1d95-5313-48c5-acb5-4e208195006c
+ Factor: 0.0
+ Execution time: 2025-11-18 12:30:00+00:00
+
+Instruction #20:
+ Timestep: 19
+ Time: 2025-11-18 12:50:00+00:00
+ Operation mode: 1cbe1d95-5313-48c5-acb5-4e208195006c
+ Factor: 0.0
+ Execution time: 2025-11-18 12:35:00+00:00
+
+... and 268 more instructions
diff --git a/flexmeasures_s2/profile_steering/examples/instructions/itho_instructions_flask_usage_only_D=1_B=200_S=30_T=288.txt b/flexmeasures_s2/profile_steering/examples/instructions/itho_instructions_flask_usage_only_D=1_B=200_S=30_T=288.txt
new file mode 100644
index 0000000..2e18c52
--- /dev/null
+++ b/flexmeasures_s2/profile_steering/examples/instructions/itho_instructions_flask_usage_only_D=1_B=200_S=30_T=288.txt
@@ -0,0 +1,177 @@
+================================================================================
+ITHO DHW Heat Pump Instructions Flask Usage Only
+================================================================================
+Planning start time: 2025-11-19 09:55:00+00:00
+Planning window: 1 days 00:00:00
+Resolution: 0 days 00:05:00
+Total timesteps: 288
+================================================================================
+
+Device: itho_dhw_device_flask_001
+Number of instructions: 288
+--------------------------------------------------------------------------------
+
+Instruction Timeline:
+--------------------------------------------------------------------------------
+ Mode: DHW_ON
+ Start: 2025-11-19 09:55:00+00:00 (timestep 0)
+ Duration: 17 timesteps (85.0 minutes)
+ Operation mode factor: 0.0
+----------------------------------------
+ Mode: DHW_OFF
+ Start: 2025-11-19 11:20:00+00:00 (timestep 17)
+ Duration: 188 timesteps (940.0 minutes)
+ Operation mode factor: 0.0
+----------------------------------------
+ Mode: DHW_ON
+ Start: 2025-11-20 03:00:00+00:00 (timestep 205)
+ Duration: 83 timesteps (415.0 minutes)
+----------------------------------------
+
+Total operation mode changes: 2
+================================================================================
+
+Detailed Instruction List:
+================================================================================
+
+Instruction #1:
+ Timestep: 0
+ Time: 2025-11-19 09:55:00+00:00
+ Operation mode: 7bfbf58c-7bd5-4bdc-b419-f0f9bdf3e560
+ Factor: 0.0
+ Execution time: 2025-11-19 09:40:00+00:00
+
+Instruction #2:
+ Timestep: 1
+ Time: 2025-11-19 10:00:00+00:00
+ Operation mode: 7bfbf58c-7bd5-4bdc-b419-f0f9bdf3e560
+ Factor: 0.0
+ Execution time: 2025-11-19 09:45:00+00:00
+
+Instruction #3:
+ Timestep: 2
+ Time: 2025-11-19 10:05:00+00:00
+ Operation mode: 7bfbf58c-7bd5-4bdc-b419-f0f9bdf3e560
+ Factor: 0.0
+ Execution time: 2025-11-19 09:50:00+00:00
+
+Instruction #4:
+ Timestep: 3
+ Time: 2025-11-19 10:10:00+00:00
+ Operation mode: 7bfbf58c-7bd5-4bdc-b419-f0f9bdf3e560
+ Factor: 0.0
+ Execution time: 2025-11-19 09:55:00+00:00
+
+Instruction #5:
+ Timestep: 4
+ Time: 2025-11-19 10:15:00+00:00
+ Operation mode: 7bfbf58c-7bd5-4bdc-b419-f0f9bdf3e560
+ Factor: 0.0
+ Execution time: 2025-11-19 10:00:00+00:00
+
+Instruction #6:
+ Timestep: 5
+ Time: 2025-11-19 10:20:00+00:00
+ Operation mode: 7bfbf58c-7bd5-4bdc-b419-f0f9bdf3e560
+ Factor: 0.0
+ Execution time: 2025-11-19 10:05:00+00:00
+
+Instruction #7:
+ Timestep: 6
+ Time: 2025-11-19 10:25:00+00:00
+ Operation mode: 7bfbf58c-7bd5-4bdc-b419-f0f9bdf3e560
+ Factor: 0.0
+ Execution time: 2025-11-19 10:10:00+00:00
+
+Instruction #8:
+ Timestep: 7
+ Time: 2025-11-19 10:30:00+00:00
+ Operation mode: 7bfbf58c-7bd5-4bdc-b419-f0f9bdf3e560
+ Factor: 0.0
+ Execution time: 2025-11-19 10:15:00+00:00
+
+Instruction #9:
+ Timestep: 8
+ Time: 2025-11-19 10:35:00+00:00
+ Operation mode: 7bfbf58c-7bd5-4bdc-b419-f0f9bdf3e560
+ Factor: 0.0
+ Execution time: 2025-11-19 10:20:00+00:00
+
+Instruction #10:
+ Timestep: 9
+ Time: 2025-11-19 10:40:00+00:00
+ Operation mode: 7bfbf58c-7bd5-4bdc-b419-f0f9bdf3e560
+ Factor: 0.0
+ Execution time: 2025-11-19 10:25:00+00:00
+
+Instruction #11:
+ Timestep: 10
+ Time: 2025-11-19 10:45:00+00:00
+ Operation mode: 7bfbf58c-7bd5-4bdc-b419-f0f9bdf3e560
+ Factor: 0.0
+ Execution time: 2025-11-19 10:30:00+00:00
+
+Instruction #12:
+ Timestep: 11
+ Time: 2025-11-19 10:50:00+00:00
+ Operation mode: 7bfbf58c-7bd5-4bdc-b419-f0f9bdf3e560
+ Factor: 0.0
+ Execution time: 2025-11-19 10:35:00+00:00
+
+Instruction #13:
+ Timestep: 12
+ Time: 2025-11-19 10:55:00+00:00
+ Operation mode: 7bfbf58c-7bd5-4bdc-b419-f0f9bdf3e560
+ Factor: 0.0
+ Execution time: 2025-11-19 10:40:00+00:00
+
+Instruction #14:
+ Timestep: 13
+ Time: 2025-11-19 11:00:00+00:00
+ Operation mode: 7bfbf58c-7bd5-4bdc-b419-f0f9bdf3e560
+ Factor: 0.0
+ Execution time: 2025-11-19 10:45:00+00:00
+
+Instruction #15:
+ Timestep: 14
+ Time: 2025-11-19 11:05:00+00:00
+ Operation mode: 7bfbf58c-7bd5-4bdc-b419-f0f9bdf3e560
+ Factor: 0.0
+ Execution time: 2025-11-19 10:50:00+00:00
+
+Instruction #16:
+ Timestep: 15
+ Time: 2025-11-19 11:10:00+00:00
+ Operation mode: 7bfbf58c-7bd5-4bdc-b419-f0f9bdf3e560
+ Factor: 0.0
+ Execution time: 2025-11-19 10:55:00+00:00
+
+Instruction #17:
+ Timestep: 16
+ Time: 2025-11-19 11:15:00+00:00
+ Operation mode: 7bfbf58c-7bd5-4bdc-b419-f0f9bdf3e560
+ Factor: 0.0
+ Execution time: 2025-11-19 11:00:00+00:00
+
+Instruction #18:
+ Timestep: 17
+ Time: 2025-11-19 11:20:00+00:00
+ Operation mode: f397493b-be70-49ee-8d28-08e07c6395aa
+ Factor: 0.0
+ Execution time: 2025-11-19 11:05:00+00:00
+
+Instruction #19:
+ Timestep: 18
+ Time: 2025-11-19 11:25:00+00:00
+ Operation mode: f397493b-be70-49ee-8d28-08e07c6395aa
+ Factor: 0.0
+ Execution time: 2025-11-19 11:10:00+00:00
+
+Instruction #20:
+ Timestep: 19
+ Time: 2025-11-19 11:30:00+00:00
+ Operation mode: f397493b-be70-49ee-8d28-08e07c6395aa
+ Factor: 0.0
+ Execution time: 2025-11-19 11:15:00+00:00
+
+... and 268 more instructions
diff --git a/flexmeasures_s2/profile_steering/examples/instructions/itho_instructions_usage_only_D=1_B=200_S=30_T=288.txt b/flexmeasures_s2/profile_steering/examples/instructions/itho_instructions_usage_only_D=1_B=200_S=30_T=288.txt
new file mode 100644
index 0000000..1772df4
--- /dev/null
+++ b/flexmeasures_s2/profile_steering/examples/instructions/itho_instructions_usage_only_D=1_B=200_S=30_T=288.txt
@@ -0,0 +1,3208 @@
+================================================================================
+ITHO DHW Heat Pump Instructions Usage Only
+================================================================================
+Planning start time: 2025-11-19 10:00:00+00:00
+Planning window: 1 days 00:00:00
+Resolution: 0 days 00:05:00
+Total timesteps: 288
+================================================================================
+
+Device: itho_dhw_device_004
+Number of instructions: 288
+--------------------------------------------------------------------------------
+
+Instruction Timeline:
+--------------------------------------------------------------------------------
+ Mode: DHW_ON
+ Start: 2025-11-19 10:00:00+00:00 (timestep 0)
+ Duration: 17 timesteps (85.0 minutes)
+ Operation mode factor: 0.0
+----------------------------------------
+ Mode: DHW_OFF
+ Start: 2025-11-19 11:25:00+00:00 (timestep 17)
+ Duration: 15 timesteps (75.0 minutes)
+ Operation mode factor: 0.0
+----------------------------------------
+ Mode: DHW_ON
+ Start: 2025-11-19 12:40:00+00:00 (timestep 32)
+ Duration: 6 timesteps (30.0 minutes)
+ Operation mode factor: 0.0
+----------------------------------------
+ Mode: DHW_OFF
+ Start: 2025-11-19 13:10:00+00:00 (timestep 38)
+ Duration: 250 timesteps (1250.0 minutes)
+----------------------------------------
+
+Total operation mode changes: 3
+================================================================================
+
+Detailed Instruction List:
+================================================================================
+
+Instruction #1:
+ Timestep: 0
+ Time: 2025-11-19 10:00:00+00:00
+ ID: 9210300e-20af-4ea8-84b0-98f2b44bdbe6
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 10:00:00+00:00
+----------------------------------------
+
+Instruction #2:
+ Timestep: 1
+ Time: 2025-11-19 10:05:00+00:00
+ ID: 4059ad4c-c363-4eab-a8d3-3b3a10265ae3
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 10:05:00+00:00
+----------------------------------------
+
+Instruction #3:
+ Timestep: 2
+ Time: 2025-11-19 10:10:00+00:00
+ ID: 977921e2-c076-43e7-9c04-99f5e1ee1736
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 10:10:00+00:00
+----------------------------------------
+
+Instruction #4:
+ Timestep: 3
+ Time: 2025-11-19 10:15:00+00:00
+ ID: 99777816-b85d-4a3b-b6ca-2c75dc33cfc2
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 10:15:00+00:00
+----------------------------------------
+
+Instruction #5:
+ Timestep: 4
+ Time: 2025-11-19 10:20:00+00:00
+ ID: 1acc320f-6389-4238-a164-fa2d055dc9df
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 10:20:00+00:00
+----------------------------------------
+
+Instruction #6:
+ Timestep: 5
+ Time: 2025-11-19 10:25:00+00:00
+ ID: 86220aeb-e807-4cc1-bc17-3b2d073e5974
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 10:25:00+00:00
+----------------------------------------
+
+Instruction #7:
+ Timestep: 6
+ Time: 2025-11-19 10:30:00+00:00
+ ID: 10675e35-b022-4a42-9162-943a59e1baf7
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 10:30:00+00:00
+----------------------------------------
+
+Instruction #8:
+ Timestep: 7
+ Time: 2025-11-19 10:35:00+00:00
+ ID: c8ca41dc-6ee2-4e48-8fc3-edf529e48439
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 10:35:00+00:00
+----------------------------------------
+
+Instruction #9:
+ Timestep: 8
+ Time: 2025-11-19 10:40:00+00:00
+ ID: f0f27981-eb5b-4851-99eb-196fd48ea155
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 10:40:00+00:00
+----------------------------------------
+
+Instruction #10:
+ Timestep: 9
+ Time: 2025-11-19 10:45:00+00:00
+ ID: 8797cb86-c7fb-4354-b651-e6f083ee3050
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 10:45:00+00:00
+----------------------------------------
+
+Instruction #11:
+ Timestep: 10
+ Time: 2025-11-19 10:50:00+00:00
+ ID: 31e13ee8-d21e-4691-b074-abb955006140
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 10:50:00+00:00
+----------------------------------------
+
+Instruction #12:
+ Timestep: 11
+ Time: 2025-11-19 10:55:00+00:00
+ ID: 31314468-fd14-4ce8-87b2-c6fbab18aafd
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 10:55:00+00:00
+----------------------------------------
+
+Instruction #13:
+ Timestep: 12
+ Time: 2025-11-19 11:00:00+00:00
+ ID: 409e29a8-3a84-4048-8db0-7a703f7b657a
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 11:00:00+00:00
+----------------------------------------
+
+Instruction #14:
+ Timestep: 13
+ Time: 2025-11-19 11:05:00+00:00
+ ID: 041ed3ed-fa45-4152-be44-a932472b1fcb
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 11:05:00+00:00
+----------------------------------------
+
+Instruction #15:
+ Timestep: 14
+ Time: 2025-11-19 11:10:00+00:00
+ ID: 707438d2-59d7-4b3e-aca1-c4359f757dc7
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 11:10:00+00:00
+----------------------------------------
+
+Instruction #16:
+ Timestep: 15
+ Time: 2025-11-19 11:15:00+00:00
+ ID: b33c7bf1-1289-4537-a89e-6f8f058a103e
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 11:15:00+00:00
+----------------------------------------
+
+Instruction #17:
+ Timestep: 16
+ Time: 2025-11-19 11:20:00+00:00
+ ID: d2aa6044-e4ea-489d-996c-80349b5e1220
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 11:20:00+00:00
+----------------------------------------
+
+Instruction #18:
+ Timestep: 17
+ Time: 2025-11-19 11:25:00+00:00
+ ID: 45d6ca31-828f-48cf-bcb6-d1eafbd83ca8
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 11:25:00+00:00
+----------------------------------------
+
+Instruction #19:
+ Timestep: 18
+ Time: 2025-11-19 11:30:00+00:00
+ ID: 765b9ea9-7701-4831-b14e-5f8538a840ef
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 11:30:00+00:00
+----------------------------------------
+
+Instruction #20:
+ Timestep: 19
+ Time: 2025-11-19 11:35:00+00:00
+ ID: 76cde4c5-1a20-40b4-9252-2ba2262f0cc5
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 11:35:00+00:00
+----------------------------------------
+
+Instruction #21:
+ Timestep: 20
+ Time: 2025-11-19 11:40:00+00:00
+ ID: 7640671f-5f68-46cc-811c-f712887bc019
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 11:40:00+00:00
+----------------------------------------
+
+Instruction #22:
+ Timestep: 21
+ Time: 2025-11-19 11:45:00+00:00
+ ID: a3aeec59-5cfe-4745-bbdc-a4b6540d26af
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 11:45:00+00:00
+----------------------------------------
+
+Instruction #23:
+ Timestep: 22
+ Time: 2025-11-19 11:50:00+00:00
+ ID: 82193bfe-a52e-4737-87c8-b51552e53a06
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 11:50:00+00:00
+----------------------------------------
+
+Instruction #24:
+ Timestep: 23
+ Time: 2025-11-19 11:55:00+00:00
+ ID: 7edc56c7-987b-4c24-82e4-7f77d39aab80
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 11:55:00+00:00
+----------------------------------------
+
+Instruction #25:
+ Timestep: 24
+ Time: 2025-11-19 12:00:00+00:00
+ ID: 95fd45d0-d7ae-4a78-a9e1-96eeeb2e9529
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 12:00:00+00:00
+----------------------------------------
+
+Instruction #26:
+ Timestep: 25
+ Time: 2025-11-19 12:05:00+00:00
+ ID: 8945fd46-4acd-4a4d-b9c3-aacaa21121ff
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 12:05:00+00:00
+----------------------------------------
+
+Instruction #27:
+ Timestep: 26
+ Time: 2025-11-19 12:10:00+00:00
+ ID: 77dfabb1-3d50-469a-89aa-1b2d406b799c
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 12:10:00+00:00
+----------------------------------------
+
+Instruction #28:
+ Timestep: 27
+ Time: 2025-11-19 12:15:00+00:00
+ ID: c23f1128-7526-4d1c-8dde-8a930cea194f
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 12:15:00+00:00
+----------------------------------------
+
+Instruction #29:
+ Timestep: 28
+ Time: 2025-11-19 12:20:00+00:00
+ ID: 5283dc15-1dd3-4250-adb4-6c512671076f
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 12:20:00+00:00
+----------------------------------------
+
+Instruction #30:
+ Timestep: 29
+ Time: 2025-11-19 12:25:00+00:00
+ ID: a9732ff9-06b9-4bf2-82bf-9fe651a7aaca
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 12:25:00+00:00
+----------------------------------------
+
+Instruction #31:
+ Timestep: 30
+ Time: 2025-11-19 12:30:00+00:00
+ ID: 1c1fd4d2-ce32-4663-8b3d-4d96a9ec1cd7
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 12:30:00+00:00
+----------------------------------------
+
+Instruction #32:
+ Timestep: 31
+ Time: 2025-11-19 12:35:00+00:00
+ ID: 9e3080d9-d60a-4fd4-9c58-f36401976560
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 12:35:00+00:00
+----------------------------------------
+
+Instruction #33:
+ Timestep: 32
+ Time: 2025-11-19 12:40:00+00:00
+ ID: e579033b-d0b1-45d3-bbfc-cab74caa5fb9
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 12:40:00+00:00
+----------------------------------------
+
+Instruction #34:
+ Timestep: 33
+ Time: 2025-11-19 12:45:00+00:00
+ ID: 6f37e2bc-b682-43f1-82c1-61827110a076
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 12:45:00+00:00
+----------------------------------------
+
+Instruction #35:
+ Timestep: 34
+ Time: 2025-11-19 12:50:00+00:00
+ ID: 45906d97-e6e7-4a0f-8fd8-e75c224a8d54
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 12:50:00+00:00
+----------------------------------------
+
+Instruction #36:
+ Timestep: 35
+ Time: 2025-11-19 12:55:00+00:00
+ ID: b3808754-d740-4a6f-9a04-fec1637bd2c0
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 12:55:00+00:00
+----------------------------------------
+
+Instruction #37:
+ Timestep: 36
+ Time: 2025-11-19 13:00:00+00:00
+ ID: 6edfcd40-efd5-44be-81e3-adc0439b48bd
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 13:00:00+00:00
+----------------------------------------
+
+Instruction #38:
+ Timestep: 37
+ Time: 2025-11-19 13:05:00+00:00
+ ID: 7dfd92cc-f2ed-4041-a691-0909ea6ea07a
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_ON
+ Operation Mode ID: 31314092-4456-43f8-89dd-0421194bc821
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 13:05:00+00:00
+----------------------------------------
+
+Instruction #39:
+ Timestep: 38
+ Time: 2025-11-19 13:10:00+00:00
+ ID: 75c20e15-03cd-44c1-a629-6d4417b4b712
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 13:10:00+00:00
+----------------------------------------
+
+Instruction #40:
+ Timestep: 39
+ Time: 2025-11-19 13:15:00+00:00
+ ID: b4d57365-3e73-497e-b111-657f31257aab
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 13:15:00+00:00
+----------------------------------------
+
+Instruction #41:
+ Timestep: 40
+ Time: 2025-11-19 13:20:00+00:00
+ ID: 39e994e7-fbff-4caa-ab87-adb7e9db69bf
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 13:20:00+00:00
+----------------------------------------
+
+Instruction #42:
+ Timestep: 41
+ Time: 2025-11-19 13:25:00+00:00
+ ID: 01e4629d-5c44-475b-9e79-2326d34d6b32
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 13:25:00+00:00
+----------------------------------------
+
+Instruction #43:
+ Timestep: 42
+ Time: 2025-11-19 13:30:00+00:00
+ ID: 69bead87-f6b0-496f-a89a-d6bdb0cfd125
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 13:30:00+00:00
+----------------------------------------
+
+Instruction #44:
+ Timestep: 43
+ Time: 2025-11-19 13:35:00+00:00
+ ID: 9f84e73d-fc02-472e-b66f-575343057209
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 13:35:00+00:00
+----------------------------------------
+
+Instruction #45:
+ Timestep: 44
+ Time: 2025-11-19 13:40:00+00:00
+ ID: 076841fb-6ae3-40b6-b200-e5c67b7762fe
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 13:40:00+00:00
+----------------------------------------
+
+Instruction #46:
+ Timestep: 45
+ Time: 2025-11-19 13:45:00+00:00
+ ID: 0eda40ab-00b9-48bb-978a-8b18d9199f41
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 13:45:00+00:00
+----------------------------------------
+
+Instruction #47:
+ Timestep: 46
+ Time: 2025-11-19 13:50:00+00:00
+ ID: 5b0b3616-5be3-4b6d-8cbe-cb133c92aef8
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 13:50:00+00:00
+----------------------------------------
+
+Instruction #48:
+ Timestep: 47
+ Time: 2025-11-19 13:55:00+00:00
+ ID: ea50f8ae-a1f4-4153-a209-0ebf7bb8deaf
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 13:55:00+00:00
+----------------------------------------
+
+Instruction #49:
+ Timestep: 48
+ Time: 2025-11-19 14:00:00+00:00
+ ID: 7b24fb76-f45e-416e-8858-e0cc51481e56
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 14:00:00+00:00
+----------------------------------------
+
+Instruction #50:
+ Timestep: 49
+ Time: 2025-11-19 14:05:00+00:00
+ ID: 66eaf0ca-8500-4d05-81b5-242de971dcf2
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 14:05:00+00:00
+----------------------------------------
+
+Instruction #51:
+ Timestep: 50
+ Time: 2025-11-19 14:10:00+00:00
+ ID: c798beac-9d93-47a4-856e-a06dcbf2dc68
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 14:10:00+00:00
+----------------------------------------
+
+Instruction #52:
+ Timestep: 51
+ Time: 2025-11-19 14:15:00+00:00
+ ID: a3cc6b89-9320-42d9-80d8-e8bb760468e3
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 14:15:00+00:00
+----------------------------------------
+
+Instruction #53:
+ Timestep: 52
+ Time: 2025-11-19 14:20:00+00:00
+ ID: 6ef6e30e-052a-405d-a71a-f5b85ab638bd
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 14:20:00+00:00
+----------------------------------------
+
+Instruction #54:
+ Timestep: 53
+ Time: 2025-11-19 14:25:00+00:00
+ ID: cc73f965-535f-4a14-98bd-04bfa0b6fa43
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 14:25:00+00:00
+----------------------------------------
+
+Instruction #55:
+ Timestep: 54
+ Time: 2025-11-19 14:30:00+00:00
+ ID: 00630800-a52a-4c2e-b534-fe27d96e0945
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 14:30:00+00:00
+----------------------------------------
+
+Instruction #56:
+ Timestep: 55
+ Time: 2025-11-19 14:35:00+00:00
+ ID: 67e9cd1c-c075-4758-92bc-175a9b8854e0
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 14:35:00+00:00
+----------------------------------------
+
+Instruction #57:
+ Timestep: 56
+ Time: 2025-11-19 14:40:00+00:00
+ ID: 24290fee-7282-40e7-84a5-ca3ad3a7809a
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 14:40:00+00:00
+----------------------------------------
+
+Instruction #58:
+ Timestep: 57
+ Time: 2025-11-19 14:45:00+00:00
+ ID: 1d050ac4-c369-44b5-a167-76f1c13a3b0a
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 14:45:00+00:00
+----------------------------------------
+
+Instruction #59:
+ Timestep: 58
+ Time: 2025-11-19 14:50:00+00:00
+ ID: 36be74c4-7a08-428e-8922-d3ee304e3f6a
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 14:50:00+00:00
+----------------------------------------
+
+Instruction #60:
+ Timestep: 59
+ Time: 2025-11-19 14:55:00+00:00
+ ID: 73e336d4-9443-4afe-849e-109393763de7
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 14:55:00+00:00
+----------------------------------------
+
+Instruction #61:
+ Timestep: 60
+ Time: 2025-11-19 15:00:00+00:00
+ ID: 755db1fc-8f4c-4872-9859-dd79b293cd1b
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 15:00:00+00:00
+----------------------------------------
+
+Instruction #62:
+ Timestep: 61
+ Time: 2025-11-19 15:05:00+00:00
+ ID: 30dbe11d-48b8-4609-a89d-b35181c7a7d1
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 15:05:00+00:00
+----------------------------------------
+
+Instruction #63:
+ Timestep: 62
+ Time: 2025-11-19 15:10:00+00:00
+ ID: a0887362-c6d4-4f21-8e46-4a2f2fb5f1a0
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 15:10:00+00:00
+----------------------------------------
+
+Instruction #64:
+ Timestep: 63
+ Time: 2025-11-19 15:15:00+00:00
+ ID: 3dcc752d-9d0a-46d9-90d8-b2d03a6b7f43
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 15:15:00+00:00
+----------------------------------------
+
+Instruction #65:
+ Timestep: 64
+ Time: 2025-11-19 15:20:00+00:00
+ ID: 76c2751c-37d4-4db1-b031-f0f10d5a7017
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 15:20:00+00:00
+----------------------------------------
+
+Instruction #66:
+ Timestep: 65
+ Time: 2025-11-19 15:25:00+00:00
+ ID: 77a455f3-aad8-495d-80bd-61e9feaf1ebe
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 15:25:00+00:00
+----------------------------------------
+
+Instruction #67:
+ Timestep: 66
+ Time: 2025-11-19 15:30:00+00:00
+ ID: c4344a68-18a7-4bf1-a415-b94266f6c018
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 15:30:00+00:00
+----------------------------------------
+
+Instruction #68:
+ Timestep: 67
+ Time: 2025-11-19 15:35:00+00:00
+ ID: 46863a9f-d87b-48a3-9cc3-5df31ba1ccc3
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 15:35:00+00:00
+----------------------------------------
+
+Instruction #69:
+ Timestep: 68
+ Time: 2025-11-19 15:40:00+00:00
+ ID: d6e7acdf-130c-4176-9163-c175550c46c0
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 15:40:00+00:00
+----------------------------------------
+
+Instruction #70:
+ Timestep: 69
+ Time: 2025-11-19 15:45:00+00:00
+ ID: 67f35824-b753-41bf-a8f1-b642328cf06b
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 15:45:00+00:00
+----------------------------------------
+
+Instruction #71:
+ Timestep: 70
+ Time: 2025-11-19 15:50:00+00:00
+ ID: c86dace6-d7c1-49f6-96ef-4ff71ceaeeb4
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 15:50:00+00:00
+----------------------------------------
+
+Instruction #72:
+ Timestep: 71
+ Time: 2025-11-19 15:55:00+00:00
+ ID: c6bfd3ac-12fe-4111-b5f1-34453a050e0f
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 15:55:00+00:00
+----------------------------------------
+
+Instruction #73:
+ Timestep: 72
+ Time: 2025-11-19 16:00:00+00:00
+ ID: 9c76e2ff-a5e4-4d3a-9be0-3321f37ee50d
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 16:00:00+00:00
+----------------------------------------
+
+Instruction #74:
+ Timestep: 73
+ Time: 2025-11-19 16:05:00+00:00
+ ID: d94e15ca-ad39-41b8-994b-ab4f41bd68cb
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 16:05:00+00:00
+----------------------------------------
+
+Instruction #75:
+ Timestep: 74
+ Time: 2025-11-19 16:10:00+00:00
+ ID: 91073d42-06fb-4934-b4a8-6d8e5df8a4c6
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 16:10:00+00:00
+----------------------------------------
+
+Instruction #76:
+ Timestep: 75
+ Time: 2025-11-19 16:15:00+00:00
+ ID: 62166a3b-42f8-435d-bf63-9640828e0b1c
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 16:15:00+00:00
+----------------------------------------
+
+Instruction #77:
+ Timestep: 76
+ Time: 2025-11-19 16:20:00+00:00
+ ID: 5220dc08-2673-4e8a-9f2c-3df126052997
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 16:20:00+00:00
+----------------------------------------
+
+Instruction #78:
+ Timestep: 77
+ Time: 2025-11-19 16:25:00+00:00
+ ID: 7510a829-3b00-40af-a916-b99979eba4ff
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 16:25:00+00:00
+----------------------------------------
+
+Instruction #79:
+ Timestep: 78
+ Time: 2025-11-19 16:30:00+00:00
+ ID: 47a52e33-bdac-47f2-963f-99fb8ea5d4f4
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 16:30:00+00:00
+----------------------------------------
+
+Instruction #80:
+ Timestep: 79
+ Time: 2025-11-19 16:35:00+00:00
+ ID: a9a34991-3b77-4d87-87f0-cdd913c91c10
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 16:35:00+00:00
+----------------------------------------
+
+Instruction #81:
+ Timestep: 80
+ Time: 2025-11-19 16:40:00+00:00
+ ID: 0660eb10-b086-4bb6-b13d-75d3b2ca3001
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 16:40:00+00:00
+----------------------------------------
+
+Instruction #82:
+ Timestep: 81
+ Time: 2025-11-19 16:45:00+00:00
+ ID: 91afb8c7-c439-4472-b8e2-a17ac42e703b
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 16:45:00+00:00
+----------------------------------------
+
+Instruction #83:
+ Timestep: 82
+ Time: 2025-11-19 16:50:00+00:00
+ ID: f4e1ca4e-3b33-4446-9c7e-39bf7acfea46
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 16:50:00+00:00
+----------------------------------------
+
+Instruction #84:
+ Timestep: 83
+ Time: 2025-11-19 16:55:00+00:00
+ ID: 672b3f40-6ea4-4650-87f9-179257c2371c
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 16:55:00+00:00
+----------------------------------------
+
+Instruction #85:
+ Timestep: 84
+ Time: 2025-11-19 17:00:00+00:00
+ ID: 301560eb-d6da-4af0-a81c-5749b5895e9d
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 17:00:00+00:00
+----------------------------------------
+
+Instruction #86:
+ Timestep: 85
+ Time: 2025-11-19 17:05:00+00:00
+ ID: b96ffb31-e65d-4dc7-a0d5-cbc4aece1ad0
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 17:05:00+00:00
+----------------------------------------
+
+Instruction #87:
+ Timestep: 86
+ Time: 2025-11-19 17:10:00+00:00
+ ID: 263ea792-7943-4a63-ab93-ed48e9f80ae2
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 17:10:00+00:00
+----------------------------------------
+
+Instruction #88:
+ Timestep: 87
+ Time: 2025-11-19 17:15:00+00:00
+ ID: 7f5a7807-9bc5-4a8b-ba41-d980a7750a9c
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 17:15:00+00:00
+----------------------------------------
+
+Instruction #89:
+ Timestep: 88
+ Time: 2025-11-19 17:20:00+00:00
+ ID: 31510eb4-bbce-4625-9934-1c853bd16743
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 17:20:00+00:00
+----------------------------------------
+
+Instruction #90:
+ Timestep: 89
+ Time: 2025-11-19 17:25:00+00:00
+ ID: 2e33f3aa-92df-4dbf-8472-4253192c8aff
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 17:25:00+00:00
+----------------------------------------
+
+Instruction #91:
+ Timestep: 90
+ Time: 2025-11-19 17:30:00+00:00
+ ID: 0018eb46-db09-411f-a451-e6b940db64ed
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 17:30:00+00:00
+----------------------------------------
+
+Instruction #92:
+ Timestep: 91
+ Time: 2025-11-19 17:35:00+00:00
+ ID: e8f7137c-3a67-4045-b781-0e38ec62205a
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 17:35:00+00:00
+----------------------------------------
+
+Instruction #93:
+ Timestep: 92
+ Time: 2025-11-19 17:40:00+00:00
+ ID: 149df643-4aa8-4d1a-8764-ec0b3e9dcc62
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 17:40:00+00:00
+----------------------------------------
+
+Instruction #94:
+ Timestep: 93
+ Time: 2025-11-19 17:45:00+00:00
+ ID: a349f4c1-fc8b-4fb0-8892-1a7ed0dcbce6
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 17:45:00+00:00
+----------------------------------------
+
+Instruction #95:
+ Timestep: 94
+ Time: 2025-11-19 17:50:00+00:00
+ ID: faa6263e-395f-42e6-9cb3-e87686d438d8
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 17:50:00+00:00
+----------------------------------------
+
+Instruction #96:
+ Timestep: 95
+ Time: 2025-11-19 17:55:00+00:00
+ ID: 1b4218c9-08d5-43c2-b423-8f72de72feb1
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 17:55:00+00:00
+----------------------------------------
+
+Instruction #97:
+ Timestep: 96
+ Time: 2025-11-19 18:00:00+00:00
+ ID: 535fe40d-5f9a-4d94-8d52-8395f6b112e8
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 18:00:00+00:00
+----------------------------------------
+
+Instruction #98:
+ Timestep: 97
+ Time: 2025-11-19 18:05:00+00:00
+ ID: df370064-b0f3-4707-a23e-56a212506b88
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 18:05:00+00:00
+----------------------------------------
+
+Instruction #99:
+ Timestep: 98
+ Time: 2025-11-19 18:10:00+00:00
+ ID: 2767d481-1ad9-4e9f-aca4-dc70ee6f3916
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 18:10:00+00:00
+----------------------------------------
+
+Instruction #100:
+ Timestep: 99
+ Time: 2025-11-19 18:15:00+00:00
+ ID: 1e409c35-c530-487d-aafd-b3700bda192e
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 18:15:00+00:00
+----------------------------------------
+
+Instruction #101:
+ Timestep: 100
+ Time: 2025-11-19 18:20:00+00:00
+ ID: 8e0d150a-0f6f-40b1-9966-4055e16cf524
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 18:20:00+00:00
+----------------------------------------
+
+Instruction #102:
+ Timestep: 101
+ Time: 2025-11-19 18:25:00+00:00
+ ID: 1fd45823-eb20-4fea-b26d-2e153d3aa403
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 18:25:00+00:00
+----------------------------------------
+
+Instruction #103:
+ Timestep: 102
+ Time: 2025-11-19 18:30:00+00:00
+ ID: a69ebd32-8616-47bb-83b0-95083e7557d1
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 18:30:00+00:00
+----------------------------------------
+
+Instruction #104:
+ Timestep: 103
+ Time: 2025-11-19 18:35:00+00:00
+ ID: 8407a06e-7d17-4d86-9f80-94b49c9aab0f
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 18:35:00+00:00
+----------------------------------------
+
+Instruction #105:
+ Timestep: 104
+ Time: 2025-11-19 18:40:00+00:00
+ ID: c50bc82e-c524-4682-b6f8-a45b50c97ff3
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 18:40:00+00:00
+----------------------------------------
+
+Instruction #106:
+ Timestep: 105
+ Time: 2025-11-19 18:45:00+00:00
+ ID: 5dabc8d2-fcba-4186-b7a1-ae1d24ec34c5
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 18:45:00+00:00
+----------------------------------------
+
+Instruction #107:
+ Timestep: 106
+ Time: 2025-11-19 18:50:00+00:00
+ ID: b9f7cc75-0be2-442d-af50-abc8f738ccb4
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 18:50:00+00:00
+----------------------------------------
+
+Instruction #108:
+ Timestep: 107
+ Time: 2025-11-19 18:55:00+00:00
+ ID: cf073841-79cb-465d-a2b0-40af534114ca
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 18:55:00+00:00
+----------------------------------------
+
+Instruction #109:
+ Timestep: 108
+ Time: 2025-11-19 19:00:00+00:00
+ ID: 08646e9d-e2f9-4035-b98c-c93afa534bd6
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 19:00:00+00:00
+----------------------------------------
+
+Instruction #110:
+ Timestep: 109
+ Time: 2025-11-19 19:05:00+00:00
+ ID: fdd90049-a275-4ee2-b3f9-7ac91a9e6e6a
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 19:05:00+00:00
+----------------------------------------
+
+Instruction #111:
+ Timestep: 110
+ Time: 2025-11-19 19:10:00+00:00
+ ID: 77e23c7e-7302-4e31-b902-e123eb1cc469
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 19:10:00+00:00
+----------------------------------------
+
+Instruction #112:
+ Timestep: 111
+ Time: 2025-11-19 19:15:00+00:00
+ ID: ad376fd2-a736-4be4-b442-bb0ccacbb2da
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 19:15:00+00:00
+----------------------------------------
+
+Instruction #113:
+ Timestep: 112
+ Time: 2025-11-19 19:20:00+00:00
+ ID: ca2aac54-4480-4779-844f-94e026d182e7
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 19:20:00+00:00
+----------------------------------------
+
+Instruction #114:
+ Timestep: 113
+ Time: 2025-11-19 19:25:00+00:00
+ ID: 0d1cd9c9-f9e1-4bbd-84ae-d53d4e29d424
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 19:25:00+00:00
+----------------------------------------
+
+Instruction #115:
+ Timestep: 114
+ Time: 2025-11-19 19:30:00+00:00
+ ID: f4548bd0-d9c0-4ca8-b7ef-2e376f5b2b49
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 19:30:00+00:00
+----------------------------------------
+
+Instruction #116:
+ Timestep: 115
+ Time: 2025-11-19 19:35:00+00:00
+ ID: 952c1ad8-9f75-47bf-b69e-0a1d0eb13b52
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 19:35:00+00:00
+----------------------------------------
+
+Instruction #117:
+ Timestep: 116
+ Time: 2025-11-19 19:40:00+00:00
+ ID: 38828f83-3278-44be-bbfe-a3d03fa4c309
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 19:40:00+00:00
+----------------------------------------
+
+Instruction #118:
+ Timestep: 117
+ Time: 2025-11-19 19:45:00+00:00
+ ID: cdb58087-e002-47f2-bef2-235a8105b659
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 19:45:00+00:00
+----------------------------------------
+
+Instruction #119:
+ Timestep: 118
+ Time: 2025-11-19 19:50:00+00:00
+ ID: bd6e0965-b6af-46ae-aac5-c7e9718df3e6
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 19:50:00+00:00
+----------------------------------------
+
+Instruction #120:
+ Timestep: 119
+ Time: 2025-11-19 19:55:00+00:00
+ ID: e5a739e4-c85e-4f71-af4b-0031d8f8302e
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 19:55:00+00:00
+----------------------------------------
+
+Instruction #121:
+ Timestep: 120
+ Time: 2025-11-19 20:00:00+00:00
+ ID: 47fd802a-f4bf-44aa-9271-4ebe6c62a307
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 20:00:00+00:00
+----------------------------------------
+
+Instruction #122:
+ Timestep: 121
+ Time: 2025-11-19 20:05:00+00:00
+ ID: 59eab968-40f7-42c9-ac7c-84c64ca805d0
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 20:05:00+00:00
+----------------------------------------
+
+Instruction #123:
+ Timestep: 122
+ Time: 2025-11-19 20:10:00+00:00
+ ID: 3904cfbf-b5c8-4538-a22e-f978222815f6
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 20:10:00+00:00
+----------------------------------------
+
+Instruction #124:
+ Timestep: 123
+ Time: 2025-11-19 20:15:00+00:00
+ ID: dcd50e9f-e72d-4796-ba6c-d2f1b0cde1a1
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 20:15:00+00:00
+----------------------------------------
+
+Instruction #125:
+ Timestep: 124
+ Time: 2025-11-19 20:20:00+00:00
+ ID: aa05aa60-97b0-4c18-abae-40bf4103548c
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 20:20:00+00:00
+----------------------------------------
+
+Instruction #126:
+ Timestep: 125
+ Time: 2025-11-19 20:25:00+00:00
+ ID: 8873b43d-4dcc-40bf-953a-b3a53c0c1ea1
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 20:25:00+00:00
+----------------------------------------
+
+Instruction #127:
+ Timestep: 126
+ Time: 2025-11-19 20:30:00+00:00
+ ID: 7f0d47e6-ddd2-4eed-a5b3-e2f8ade1aee9
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 20:30:00+00:00
+----------------------------------------
+
+Instruction #128:
+ Timestep: 127
+ Time: 2025-11-19 20:35:00+00:00
+ ID: 436ffb11-8751-4aaa-9ba4-00955c2bab01
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 20:35:00+00:00
+----------------------------------------
+
+Instruction #129:
+ Timestep: 128
+ Time: 2025-11-19 20:40:00+00:00
+ ID: 78ecc15c-15d1-42e7-af0a-9f96af624c0e
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 20:40:00+00:00
+----------------------------------------
+
+Instruction #130:
+ Timestep: 129
+ Time: 2025-11-19 20:45:00+00:00
+ ID: 3b1ef0bb-39f1-47b0-91a1-7fc73fa00f55
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 20:45:00+00:00
+----------------------------------------
+
+Instruction #131:
+ Timestep: 130
+ Time: 2025-11-19 20:50:00+00:00
+ ID: f31594b4-1022-4f76-a2c2-d63fbb90ab6c
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 20:50:00+00:00
+----------------------------------------
+
+Instruction #132:
+ Timestep: 131
+ Time: 2025-11-19 20:55:00+00:00
+ ID: 78123622-f64b-467a-9110-94d4b0068750
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 20:55:00+00:00
+----------------------------------------
+
+Instruction #133:
+ Timestep: 132
+ Time: 2025-11-19 21:00:00+00:00
+ ID: f426f67b-c929-4405-b79f-2e1399ef203c
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 21:00:00+00:00
+----------------------------------------
+
+Instruction #134:
+ Timestep: 133
+ Time: 2025-11-19 21:05:00+00:00
+ ID: ad55692e-7261-47c0-b635-868072115939
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 21:05:00+00:00
+----------------------------------------
+
+Instruction #135:
+ Timestep: 134
+ Time: 2025-11-19 21:10:00+00:00
+ ID: 6f63545f-4b18-4906-a532-83cadffc6a25
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 21:10:00+00:00
+----------------------------------------
+
+Instruction #136:
+ Timestep: 135
+ Time: 2025-11-19 21:15:00+00:00
+ ID: a82b5887-6d4a-423f-ab9d-6a8786b480ad
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 21:15:00+00:00
+----------------------------------------
+
+Instruction #137:
+ Timestep: 136
+ Time: 2025-11-19 21:20:00+00:00
+ ID: 6a341c5c-2c6a-4d54-b79d-0ad07ad96ff0
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 21:20:00+00:00
+----------------------------------------
+
+Instruction #138:
+ Timestep: 137
+ Time: 2025-11-19 21:25:00+00:00
+ ID: 7401e0ba-665e-4050-8fa6-b833502a092a
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 21:25:00+00:00
+----------------------------------------
+
+Instruction #139:
+ Timestep: 138
+ Time: 2025-11-19 21:30:00+00:00
+ ID: 6efd4c2a-fcc6-4e3d-8446-5bebec39af05
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 21:30:00+00:00
+----------------------------------------
+
+Instruction #140:
+ Timestep: 139
+ Time: 2025-11-19 21:35:00+00:00
+ ID: 0b4691f3-2b77-49a6-9bd7-b2cfbf9708bf
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 21:35:00+00:00
+----------------------------------------
+
+Instruction #141:
+ Timestep: 140
+ Time: 2025-11-19 21:40:00+00:00
+ ID: ae4d28fa-927b-41cc-b0fe-b804c4cf2245
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 21:40:00+00:00
+----------------------------------------
+
+Instruction #142:
+ Timestep: 141
+ Time: 2025-11-19 21:45:00+00:00
+ ID: 03e52894-fc3c-4c4d-af27-9504171a2b6e
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 21:45:00+00:00
+----------------------------------------
+
+Instruction #143:
+ Timestep: 142
+ Time: 2025-11-19 21:50:00+00:00
+ ID: 6c21c1ca-d09f-465b-9582-e561d847c47b
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 21:50:00+00:00
+----------------------------------------
+
+Instruction #144:
+ Timestep: 143
+ Time: 2025-11-19 21:55:00+00:00
+ ID: af35755a-3dd4-4379-a2ef-83ff04162870
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 21:55:00+00:00
+----------------------------------------
+
+Instruction #145:
+ Timestep: 144
+ Time: 2025-11-19 22:00:00+00:00
+ ID: cbda5384-61dd-4908-b389-5cf4a39d7b76
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 22:00:00+00:00
+----------------------------------------
+
+Instruction #146:
+ Timestep: 145
+ Time: 2025-11-19 22:05:00+00:00
+ ID: 656f1df1-85b0-41c3-aba5-0dfd6d1ef278
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 22:05:00+00:00
+----------------------------------------
+
+Instruction #147:
+ Timestep: 146
+ Time: 2025-11-19 22:10:00+00:00
+ ID: 7e5bbb14-f8f5-49a7-8221-238484bff64a
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 22:10:00+00:00
+----------------------------------------
+
+Instruction #148:
+ Timestep: 147
+ Time: 2025-11-19 22:15:00+00:00
+ ID: a150a5d9-74b2-40b4-8d50-b25591d4bee9
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 22:15:00+00:00
+----------------------------------------
+
+Instruction #149:
+ Timestep: 148
+ Time: 2025-11-19 22:20:00+00:00
+ ID: 324822a1-90df-4238-bacc-a6bc66c2293c
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 22:20:00+00:00
+----------------------------------------
+
+Instruction #150:
+ Timestep: 149
+ Time: 2025-11-19 22:25:00+00:00
+ ID: c4c27988-d4fa-408e-9dce-0348884b3061
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 22:25:00+00:00
+----------------------------------------
+
+Instruction #151:
+ Timestep: 150
+ Time: 2025-11-19 22:30:00+00:00
+ ID: fed2a1d2-3446-48de-9003-beb869268bc3
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 22:30:00+00:00
+----------------------------------------
+
+Instruction #152:
+ Timestep: 151
+ Time: 2025-11-19 22:35:00+00:00
+ ID: f0eb9994-45d1-459d-9562-661af0a083fb
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 22:35:00+00:00
+----------------------------------------
+
+Instruction #153:
+ Timestep: 152
+ Time: 2025-11-19 22:40:00+00:00
+ ID: 0059c104-938f-436f-8719-b951304d9fb7
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 22:40:00+00:00
+----------------------------------------
+
+Instruction #154:
+ Timestep: 153
+ Time: 2025-11-19 22:45:00+00:00
+ ID: 9159f26b-76f8-4622-8362-71b2b54270f7
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 22:45:00+00:00
+----------------------------------------
+
+Instruction #155:
+ Timestep: 154
+ Time: 2025-11-19 22:50:00+00:00
+ ID: efebfaa1-a3bd-4a5d-a8a6-24b0f5ef06e0
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 22:50:00+00:00
+----------------------------------------
+
+Instruction #156:
+ Timestep: 155
+ Time: 2025-11-19 22:55:00+00:00
+ ID: 0ba8afc1-8abd-47d0-8dd4-98de4de0259e
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 22:55:00+00:00
+----------------------------------------
+
+Instruction #157:
+ Timestep: 156
+ Time: 2025-11-19 23:00:00+00:00
+ ID: 1132acdc-1205-4e80-9379-cc6c76180a90
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 23:00:00+00:00
+----------------------------------------
+
+Instruction #158:
+ Timestep: 157
+ Time: 2025-11-19 23:05:00+00:00
+ ID: 0ac6b656-6673-4c1c-bde6-cd637a02c603
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 23:05:00+00:00
+----------------------------------------
+
+Instruction #159:
+ Timestep: 158
+ Time: 2025-11-19 23:10:00+00:00
+ ID: 57ac78f5-13bf-4c6e-b7e8-3eeade269852
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 23:10:00+00:00
+----------------------------------------
+
+Instruction #160:
+ Timestep: 159
+ Time: 2025-11-19 23:15:00+00:00
+ ID: 40fef557-cf77-4356-addb-9583dfe641ad
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 23:15:00+00:00
+----------------------------------------
+
+Instruction #161:
+ Timestep: 160
+ Time: 2025-11-19 23:20:00+00:00
+ ID: d4170bf5-61d4-452e-a7bb-9bf8300db562
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 23:20:00+00:00
+----------------------------------------
+
+Instruction #162:
+ Timestep: 161
+ Time: 2025-11-19 23:25:00+00:00
+ ID: bde32bee-6d9b-4f7f-a351-339a6f31d72a
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 23:25:00+00:00
+----------------------------------------
+
+Instruction #163:
+ Timestep: 162
+ Time: 2025-11-19 23:30:00+00:00
+ ID: 461774af-451d-46d2-90c8-afbbc1d5e99b
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 23:30:00+00:00
+----------------------------------------
+
+Instruction #164:
+ Timestep: 163
+ Time: 2025-11-19 23:35:00+00:00
+ ID: 78f6a372-cbfd-4e70-89d9-578a2fad2c85
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 23:35:00+00:00
+----------------------------------------
+
+Instruction #165:
+ Timestep: 164
+ Time: 2025-11-19 23:40:00+00:00
+ ID: 567f815f-fca3-4cc5-b7a2-d9b9f6f4e39f
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 23:40:00+00:00
+----------------------------------------
+
+Instruction #166:
+ Timestep: 165
+ Time: 2025-11-19 23:45:00+00:00
+ ID: 83d8cf28-a468-4c34-8091-6c9f7019fc3d
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 23:45:00+00:00
+----------------------------------------
+
+Instruction #167:
+ Timestep: 166
+ Time: 2025-11-19 23:50:00+00:00
+ ID: 2d15a48c-b0a5-4ecf-b577-e75b1aedae4e
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 23:50:00+00:00
+----------------------------------------
+
+Instruction #168:
+ Timestep: 167
+ Time: 2025-11-19 23:55:00+00:00
+ ID: 49bf1b60-be50-4be9-b3f9-e7c49efd745c
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-19 23:55:00+00:00
+----------------------------------------
+
+Instruction #169:
+ Timestep: 168
+ Time: 2025-11-20 00:00:00+00:00
+ ID: 6b93ed99-02a7-4c81-971e-9f0d44ea6ae7
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 00:00:00+00:00
+----------------------------------------
+
+Instruction #170:
+ Timestep: 169
+ Time: 2025-11-20 00:05:00+00:00
+ ID: ac9fcc84-948b-4678-9978-57ce115e37f8
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 00:05:00+00:00
+----------------------------------------
+
+Instruction #171:
+ Timestep: 170
+ Time: 2025-11-20 00:10:00+00:00
+ ID: 58fd2ccc-aa13-4af4-adbd-c8c2a9952170
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 00:10:00+00:00
+----------------------------------------
+
+Instruction #172:
+ Timestep: 171
+ Time: 2025-11-20 00:15:00+00:00
+ ID: 2a83ef49-7859-48c7-a444-119ad36fbe3a
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 00:15:00+00:00
+----------------------------------------
+
+Instruction #173:
+ Timestep: 172
+ Time: 2025-11-20 00:20:00+00:00
+ ID: da9aec62-6d81-4d89-b1fe-2ecccbc320d8
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 00:20:00+00:00
+----------------------------------------
+
+Instruction #174:
+ Timestep: 173
+ Time: 2025-11-20 00:25:00+00:00
+ ID: 0ca85ae0-185f-43ca-a28d-6f6f6f244e59
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 00:25:00+00:00
+----------------------------------------
+
+Instruction #175:
+ Timestep: 174
+ Time: 2025-11-20 00:30:00+00:00
+ ID: 731f786e-7f89-4206-a388-2ef97571d66c
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 00:30:00+00:00
+----------------------------------------
+
+Instruction #176:
+ Timestep: 175
+ Time: 2025-11-20 00:35:00+00:00
+ ID: 472b0605-05b6-4268-b453-dd24feb13c75
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 00:35:00+00:00
+----------------------------------------
+
+Instruction #177:
+ Timestep: 176
+ Time: 2025-11-20 00:40:00+00:00
+ ID: 998e3115-5f36-49b5-9f2a-70c3882a8e94
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 00:40:00+00:00
+----------------------------------------
+
+Instruction #178:
+ Timestep: 177
+ Time: 2025-11-20 00:45:00+00:00
+ ID: c948ee13-2efd-466c-a023-9631d49af0ae
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 00:45:00+00:00
+----------------------------------------
+
+Instruction #179:
+ Timestep: 178
+ Time: 2025-11-20 00:50:00+00:00
+ ID: e3f3b401-1eb2-40bc-90d8-024b3f98b924
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 00:50:00+00:00
+----------------------------------------
+
+Instruction #180:
+ Timestep: 179
+ Time: 2025-11-20 00:55:00+00:00
+ ID: 2bb32d70-b88f-4f83-8171-be0b579d9579
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 00:55:00+00:00
+----------------------------------------
+
+Instruction #181:
+ Timestep: 180
+ Time: 2025-11-20 01:00:00+00:00
+ ID: cfc0bc0c-60a2-4d60-a49c-b554d7f25015
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 01:00:00+00:00
+----------------------------------------
+
+Instruction #182:
+ Timestep: 181
+ Time: 2025-11-20 01:05:00+00:00
+ ID: 6e2f7bcb-33d0-4efc-acc1-de1d6d4f80cc
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 01:05:00+00:00
+----------------------------------------
+
+Instruction #183:
+ Timestep: 182
+ Time: 2025-11-20 01:10:00+00:00
+ ID: e1ce7efb-3b7c-4832-9478-f2c4535491d0
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 01:10:00+00:00
+----------------------------------------
+
+Instruction #184:
+ Timestep: 183
+ Time: 2025-11-20 01:15:00+00:00
+ ID: 6799e5cf-3286-4811-a91e-9a38ba24139a
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 01:15:00+00:00
+----------------------------------------
+
+Instruction #185:
+ Timestep: 184
+ Time: 2025-11-20 01:20:00+00:00
+ ID: 9cb10857-fff9-43e1-909b-0dfba950506c
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 01:20:00+00:00
+----------------------------------------
+
+Instruction #186:
+ Timestep: 185
+ Time: 2025-11-20 01:25:00+00:00
+ ID: b8e7aed7-ec6c-4247-956a-bbd7243af597
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 01:25:00+00:00
+----------------------------------------
+
+Instruction #187:
+ Timestep: 186
+ Time: 2025-11-20 01:30:00+00:00
+ ID: 6164eec7-5b9f-418a-8f2e-c5485ade4f53
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 01:30:00+00:00
+----------------------------------------
+
+Instruction #188:
+ Timestep: 187
+ Time: 2025-11-20 01:35:00+00:00
+ ID: f0c55cd1-4550-4ee3-850f-b9882cbd029e
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 01:35:00+00:00
+----------------------------------------
+
+Instruction #189:
+ Timestep: 188
+ Time: 2025-11-20 01:40:00+00:00
+ ID: aa52bf5a-011f-48ab-b1b5-c8e0b1079ae1
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 01:40:00+00:00
+----------------------------------------
+
+Instruction #190:
+ Timestep: 189
+ Time: 2025-11-20 01:45:00+00:00
+ ID: 8e3b67ab-823d-4cc6-8500-2acdc8bdea80
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 01:45:00+00:00
+----------------------------------------
+
+Instruction #191:
+ Timestep: 190
+ Time: 2025-11-20 01:50:00+00:00
+ ID: 65b63db5-e6e2-44bd-a064-b149b648fb99
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 01:50:00+00:00
+----------------------------------------
+
+Instruction #192:
+ Timestep: 191
+ Time: 2025-11-20 01:55:00+00:00
+ ID: eb479956-4a35-455c-9022-5ea7489a34ed
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 01:55:00+00:00
+----------------------------------------
+
+Instruction #193:
+ Timestep: 192
+ Time: 2025-11-20 02:00:00+00:00
+ ID: 8e1f70a0-638d-4e2b-bb9d-c5e78b346be2
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 02:00:00+00:00
+----------------------------------------
+
+Instruction #194:
+ Timestep: 193
+ Time: 2025-11-20 02:05:00+00:00
+ ID: 6d2079df-551d-4c0c-bd09-dde7f8c57e99
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 02:05:00+00:00
+----------------------------------------
+
+Instruction #195:
+ Timestep: 194
+ Time: 2025-11-20 02:10:00+00:00
+ ID: fca0b279-68d0-44fa-a7a3-41cc36072241
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 02:10:00+00:00
+----------------------------------------
+
+Instruction #196:
+ Timestep: 195
+ Time: 2025-11-20 02:15:00+00:00
+ ID: 13942e1a-6508-48d6-b7ef-3481710d4bb5
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 02:15:00+00:00
+----------------------------------------
+
+Instruction #197:
+ Timestep: 196
+ Time: 2025-11-20 02:20:00+00:00
+ ID: 145e72f5-15ef-4343-8369-237bd75a6713
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 02:20:00+00:00
+----------------------------------------
+
+Instruction #198:
+ Timestep: 197
+ Time: 2025-11-20 02:25:00+00:00
+ ID: 8e05bb42-7499-4be5-ba2c-f48dd907708c
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 02:25:00+00:00
+----------------------------------------
+
+Instruction #199:
+ Timestep: 198
+ Time: 2025-11-20 02:30:00+00:00
+ ID: d34411d6-3636-4899-9dca-854f3ca7bb92
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 02:30:00+00:00
+----------------------------------------
+
+Instruction #200:
+ Timestep: 199
+ Time: 2025-11-20 02:35:00+00:00
+ ID: 7479cda1-2bde-4fab-8ecf-f9223d06b185
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 02:35:00+00:00
+----------------------------------------
+
+Instruction #201:
+ Timestep: 200
+ Time: 2025-11-20 02:40:00+00:00
+ ID: 0e665ea8-9c1b-4026-b7a4-85e5a68c846e
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 02:40:00+00:00
+----------------------------------------
+
+Instruction #202:
+ Timestep: 201
+ Time: 2025-11-20 02:45:00+00:00
+ ID: b0aea9bd-982c-4279-966d-17fabedc46f3
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 02:45:00+00:00
+----------------------------------------
+
+Instruction #203:
+ Timestep: 202
+ Time: 2025-11-20 02:50:00+00:00
+ ID: c0bca062-1d88-4a02-806b-59c4ee6aa8e9
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 02:50:00+00:00
+----------------------------------------
+
+Instruction #204:
+ Timestep: 203
+ Time: 2025-11-20 02:55:00+00:00
+ ID: 683aca55-f951-47de-885d-e0da2455ff26
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 02:55:00+00:00
+----------------------------------------
+
+Instruction #205:
+ Timestep: 204
+ Time: 2025-11-20 03:00:00+00:00
+ ID: 09489495-3288-484c-b43b-2c0d42464a72
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 03:00:00+00:00
+----------------------------------------
+
+Instruction #206:
+ Timestep: 205
+ Time: 2025-11-20 03:05:00+00:00
+ ID: 6b41e4f3-b174-4842-972e-9d0ae17b3342
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 03:05:00+00:00
+----------------------------------------
+
+Instruction #207:
+ Timestep: 206
+ Time: 2025-11-20 03:10:00+00:00
+ ID: 10f1dbb2-e2c9-4fb7-80e3-9cb9a1ff7c3b
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 03:10:00+00:00
+----------------------------------------
+
+Instruction #208:
+ Timestep: 207
+ Time: 2025-11-20 03:15:00+00:00
+ ID: a3567989-8755-4c67-bb4d-0902f1f44a17
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 03:15:00+00:00
+----------------------------------------
+
+Instruction #209:
+ Timestep: 208
+ Time: 2025-11-20 03:20:00+00:00
+ ID: db1cf121-9ceb-4836-a77d-ca5c1203b2d5
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 03:20:00+00:00
+----------------------------------------
+
+Instruction #210:
+ Timestep: 209
+ Time: 2025-11-20 03:25:00+00:00
+ ID: 51884a8e-8ce3-49f4-b1b1-bbbf9f5d3a88
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 03:25:00+00:00
+----------------------------------------
+
+Instruction #211:
+ Timestep: 210
+ Time: 2025-11-20 03:30:00+00:00
+ ID: e9fa88c3-6ec1-4492-9a57-7673c77b3b7a
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 03:30:00+00:00
+----------------------------------------
+
+Instruction #212:
+ Timestep: 211
+ Time: 2025-11-20 03:35:00+00:00
+ ID: 72885f58-f71a-4487-9cd5-2e35f7d4b632
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 03:35:00+00:00
+----------------------------------------
+
+Instruction #213:
+ Timestep: 212
+ Time: 2025-11-20 03:40:00+00:00
+ ID: 4862ecf1-e8f9-42ca-b449-3f9417f757de
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 03:40:00+00:00
+----------------------------------------
+
+Instruction #214:
+ Timestep: 213
+ Time: 2025-11-20 03:45:00+00:00
+ ID: fa294000-5676-4cf2-8226-5e2f720d7c81
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 03:45:00+00:00
+----------------------------------------
+
+Instruction #215:
+ Timestep: 214
+ Time: 2025-11-20 03:50:00+00:00
+ ID: ac5ef310-b3fc-458c-8bc6-2c4ff9172e1b
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 03:50:00+00:00
+----------------------------------------
+
+Instruction #216:
+ Timestep: 215
+ Time: 2025-11-20 03:55:00+00:00
+ ID: d53bf796-cabc-4ae9-80b7-2ba51bbbac86
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 03:55:00+00:00
+----------------------------------------
+
+Instruction #217:
+ Timestep: 216
+ Time: 2025-11-20 04:00:00+00:00
+ ID: 79f55983-11fe-416c-82e8-0699f011a285
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 04:00:00+00:00
+----------------------------------------
+
+Instruction #218:
+ Timestep: 217
+ Time: 2025-11-20 04:05:00+00:00
+ ID: 1b7dfc58-8aa4-4099-ad4d-86df284b598f
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 04:05:00+00:00
+----------------------------------------
+
+Instruction #219:
+ Timestep: 218
+ Time: 2025-11-20 04:10:00+00:00
+ ID: 57c20d65-af40-4f33-9f84-159647791e0a
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 04:10:00+00:00
+----------------------------------------
+
+Instruction #220:
+ Timestep: 219
+ Time: 2025-11-20 04:15:00+00:00
+ ID: 55f50815-69ce-43ab-bbb7-e57b9f74a742
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 04:15:00+00:00
+----------------------------------------
+
+Instruction #221:
+ Timestep: 220
+ Time: 2025-11-20 04:20:00+00:00
+ ID: e0e67073-6985-484f-be71-8036e55337fd
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 04:20:00+00:00
+----------------------------------------
+
+Instruction #222:
+ Timestep: 221
+ Time: 2025-11-20 04:25:00+00:00
+ ID: 7bbaf451-a2f1-4d6a-965b-86b8a75375be
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 04:25:00+00:00
+----------------------------------------
+
+Instruction #223:
+ Timestep: 222
+ Time: 2025-11-20 04:30:00+00:00
+ ID: 9b5a87c8-4f0e-4e06-9395-c4e9454021e7
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 04:30:00+00:00
+----------------------------------------
+
+Instruction #224:
+ Timestep: 223
+ Time: 2025-11-20 04:35:00+00:00
+ ID: 201d3b1f-0c88-4678-a037-ed1f4bf50bfb
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 04:35:00+00:00
+----------------------------------------
+
+Instruction #225:
+ Timestep: 224
+ Time: 2025-11-20 04:40:00+00:00
+ ID: 45b1b5e3-68bf-42e2-9636-8f588ef22579
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 04:40:00+00:00
+----------------------------------------
+
+Instruction #226:
+ Timestep: 225
+ Time: 2025-11-20 04:45:00+00:00
+ ID: 8cf83935-24d8-4f9f-af89-189716716db0
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 04:45:00+00:00
+----------------------------------------
+
+Instruction #227:
+ Timestep: 226
+ Time: 2025-11-20 04:50:00+00:00
+ ID: 17e354ca-3f49-41d0-8260-7ef841cb4834
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 04:50:00+00:00
+----------------------------------------
+
+Instruction #228:
+ Timestep: 227
+ Time: 2025-11-20 04:55:00+00:00
+ ID: c1ab8db7-5bd2-411f-8fc8-cbc9901dd598
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 04:55:00+00:00
+----------------------------------------
+
+Instruction #229:
+ Timestep: 228
+ Time: 2025-11-20 05:00:00+00:00
+ ID: 4509ab25-74a4-4bb5-8a1c-b54d657320f1
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 05:00:00+00:00
+----------------------------------------
+
+Instruction #230:
+ Timestep: 229
+ Time: 2025-11-20 05:05:00+00:00
+ ID: 11e3b4e9-9276-4de0-a0c2-556b0356d430
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 05:05:00+00:00
+----------------------------------------
+
+Instruction #231:
+ Timestep: 230
+ Time: 2025-11-20 05:10:00+00:00
+ ID: 122e848b-6f69-4235-91f2-4df3ad3a1016
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 05:10:00+00:00
+----------------------------------------
+
+Instruction #232:
+ Timestep: 231
+ Time: 2025-11-20 05:15:00+00:00
+ ID: dd615fb1-7035-489a-83ee-768710b4ae6a
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 05:15:00+00:00
+----------------------------------------
+
+Instruction #233:
+ Timestep: 232
+ Time: 2025-11-20 05:20:00+00:00
+ ID: 9b04fe07-c2ed-4fbf-afe2-6eb067fd644b
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 05:20:00+00:00
+----------------------------------------
+
+Instruction #234:
+ Timestep: 233
+ Time: 2025-11-20 05:25:00+00:00
+ ID: 4437dc96-3f8f-4ae1-bacc-f4b90886db0e
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 05:25:00+00:00
+----------------------------------------
+
+Instruction #235:
+ Timestep: 234
+ Time: 2025-11-20 05:30:00+00:00
+ ID: 269da291-0b0d-4fac-a8df-2c0cf5a01f65
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 05:30:00+00:00
+----------------------------------------
+
+Instruction #236:
+ Timestep: 235
+ Time: 2025-11-20 05:35:00+00:00
+ ID: 9da4ab8e-5153-49ba-ad1e-d612046a6431
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 05:35:00+00:00
+----------------------------------------
+
+Instruction #237:
+ Timestep: 236
+ Time: 2025-11-20 05:40:00+00:00
+ ID: 0e49ba1b-a92e-4131-aa0b-c7725b15ce14
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 05:40:00+00:00
+----------------------------------------
+
+Instruction #238:
+ Timestep: 237
+ Time: 2025-11-20 05:45:00+00:00
+ ID: afee8542-bdec-49ed-87fb-34a27669f892
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 05:45:00+00:00
+----------------------------------------
+
+Instruction #239:
+ Timestep: 238
+ Time: 2025-11-20 05:50:00+00:00
+ ID: 1a212380-c7d7-42c0-8322-614c86831bde
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 05:50:00+00:00
+----------------------------------------
+
+Instruction #240:
+ Timestep: 239
+ Time: 2025-11-20 05:55:00+00:00
+ ID: f0615fa9-38a6-42c3-9ce1-380e845dd617
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 05:55:00+00:00
+----------------------------------------
+
+Instruction #241:
+ Timestep: 240
+ Time: 2025-11-20 06:00:00+00:00
+ ID: 6738c7c0-b49e-4574-a105-b7d2fffbece4
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 06:00:00+00:00
+----------------------------------------
+
+Instruction #242:
+ Timestep: 241
+ Time: 2025-11-20 06:05:00+00:00
+ ID: a398619d-becb-4ffd-937e-93b50bbc16af
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 06:05:00+00:00
+----------------------------------------
+
+Instruction #243:
+ Timestep: 242
+ Time: 2025-11-20 06:10:00+00:00
+ ID: 7dee5cf4-2be2-4e02-859b-a88049faec3b
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 06:10:00+00:00
+----------------------------------------
+
+Instruction #244:
+ Timestep: 243
+ Time: 2025-11-20 06:15:00+00:00
+ ID: 9cffc18b-ef4f-4431-a875-abdd21b3dd3f
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 06:15:00+00:00
+----------------------------------------
+
+Instruction #245:
+ Timestep: 244
+ Time: 2025-11-20 06:20:00+00:00
+ ID: b11bc621-cbf8-4e60-b565-5586d719722d
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 06:20:00+00:00
+----------------------------------------
+
+Instruction #246:
+ Timestep: 245
+ Time: 2025-11-20 06:25:00+00:00
+ ID: 22e2da11-583b-4baa-8b2f-39f87e8336f4
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 06:25:00+00:00
+----------------------------------------
+
+Instruction #247:
+ Timestep: 246
+ Time: 2025-11-20 06:30:00+00:00
+ ID: a715fb3f-135b-4647-aa8d-a4ef855ad10f
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 06:30:00+00:00
+----------------------------------------
+
+Instruction #248:
+ Timestep: 247
+ Time: 2025-11-20 06:35:00+00:00
+ ID: 6994bc73-b446-45d5-ba4c-62f7798affc5
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 06:35:00+00:00
+----------------------------------------
+
+Instruction #249:
+ Timestep: 248
+ Time: 2025-11-20 06:40:00+00:00
+ ID: 8c520588-3ee7-4f62-8c2d-c162349c0cf9
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 06:40:00+00:00
+----------------------------------------
+
+Instruction #250:
+ Timestep: 249
+ Time: 2025-11-20 06:45:00+00:00
+ ID: 1aae9a40-cfdc-44eb-9e66-35e574eb1305
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 06:45:00+00:00
+----------------------------------------
+
+Instruction #251:
+ Timestep: 250
+ Time: 2025-11-20 06:50:00+00:00
+ ID: fcae4b23-8ec1-4a28-8a03-77d8ede3f898
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 06:50:00+00:00
+----------------------------------------
+
+Instruction #252:
+ Timestep: 251
+ Time: 2025-11-20 06:55:00+00:00
+ ID: d48ff10e-dcc9-4a0e-b04c-22025a67edc5
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 06:55:00+00:00
+----------------------------------------
+
+Instruction #253:
+ Timestep: 252
+ Time: 2025-11-20 07:00:00+00:00
+ ID: 8287ab62-0fb0-48f5-95a6-2a03a404f150
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 07:00:00+00:00
+----------------------------------------
+
+Instruction #254:
+ Timestep: 253
+ Time: 2025-11-20 07:05:00+00:00
+ ID: a372f94b-55cc-4282-8581-3cef77459ba0
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 07:05:00+00:00
+----------------------------------------
+
+Instruction #255:
+ Timestep: 254
+ Time: 2025-11-20 07:10:00+00:00
+ ID: 6c769e05-31df-4156-8615-e32e33f21227
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 07:10:00+00:00
+----------------------------------------
+
+Instruction #256:
+ Timestep: 255
+ Time: 2025-11-20 07:15:00+00:00
+ ID: 0eb99564-4abc-44be-a12e-5b53511caf4b
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 07:15:00+00:00
+----------------------------------------
+
+Instruction #257:
+ Timestep: 256
+ Time: 2025-11-20 07:20:00+00:00
+ ID: 0201dd39-5e1a-4478-a08c-da2cb0710bfd
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 07:20:00+00:00
+----------------------------------------
+
+Instruction #258:
+ Timestep: 257
+ Time: 2025-11-20 07:25:00+00:00
+ ID: 3eb97923-073d-48ba-a547-a3bbf48dc45d
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 07:25:00+00:00
+----------------------------------------
+
+Instruction #259:
+ Timestep: 258
+ Time: 2025-11-20 07:30:00+00:00
+ ID: 0cc18bec-fbd6-4a4d-ae99-15aebd79e8dd
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 07:30:00+00:00
+----------------------------------------
+
+Instruction #260:
+ Timestep: 259
+ Time: 2025-11-20 07:35:00+00:00
+ ID: e3e79ea0-6d30-459e-a216-6b9c859d2826
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 07:35:00+00:00
+----------------------------------------
+
+Instruction #261:
+ Timestep: 260
+ Time: 2025-11-20 07:40:00+00:00
+ ID: c954196f-9978-4cb5-9d92-edc1ad2e7f03
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 07:40:00+00:00
+----------------------------------------
+
+Instruction #262:
+ Timestep: 261
+ Time: 2025-11-20 07:45:00+00:00
+ ID: 4528ec70-d0d9-4351-8c43-56179f6c5372
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 07:45:00+00:00
+----------------------------------------
+
+Instruction #263:
+ Timestep: 262
+ Time: 2025-11-20 07:50:00+00:00
+ ID: 93d884b5-b359-47ac-91a7-3c2ca97d83a5
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 07:50:00+00:00
+----------------------------------------
+
+Instruction #264:
+ Timestep: 263
+ Time: 2025-11-20 07:55:00+00:00
+ ID: fbce642a-0e1b-4c7d-a104-771cb743479f
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 07:55:00+00:00
+----------------------------------------
+
+Instruction #265:
+ Timestep: 264
+ Time: 2025-11-20 08:00:00+00:00
+ ID: 0da438fc-cfcb-4ae0-860e-b42a804161b6
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 08:00:00+00:00
+----------------------------------------
+
+Instruction #266:
+ Timestep: 265
+ Time: 2025-11-20 08:05:00+00:00
+ ID: 914ef2c2-f573-429b-95e0-57e3adedda4d
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 08:05:00+00:00
+----------------------------------------
+
+Instruction #267:
+ Timestep: 266
+ Time: 2025-11-20 08:10:00+00:00
+ ID: 4b1ee423-a543-4fe0-b46d-a9faa5748d40
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 08:10:00+00:00
+----------------------------------------
+
+Instruction #268:
+ Timestep: 267
+ Time: 2025-11-20 08:15:00+00:00
+ ID: 70c078db-ae3d-476f-b585-f27bcd91620f
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 08:15:00+00:00
+----------------------------------------
+
+Instruction #269:
+ Timestep: 268
+ Time: 2025-11-20 08:20:00+00:00
+ ID: bbcc7c7e-4aff-4666-b51a-19fb9402fa47
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 08:20:00+00:00
+----------------------------------------
+
+Instruction #270:
+ Timestep: 269
+ Time: 2025-11-20 08:25:00+00:00
+ ID: 3e4c5682-84d3-43e9-946f-f77a5a7bd4e8
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 08:25:00+00:00
+----------------------------------------
+
+Instruction #271:
+ Timestep: 270
+ Time: 2025-11-20 08:30:00+00:00
+ ID: 80396301-9dce-4490-b1a1-7d19182da769
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 08:30:00+00:00
+----------------------------------------
+
+Instruction #272:
+ Timestep: 271
+ Time: 2025-11-20 08:35:00+00:00
+ ID: ff56dc47-2c2d-466e-8d10-23da0d2f2c4d
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 08:35:00+00:00
+----------------------------------------
+
+Instruction #273:
+ Timestep: 272
+ Time: 2025-11-20 08:40:00+00:00
+ ID: e9af8b18-241c-4009-8415-c2dbb66355ae
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 08:40:00+00:00
+----------------------------------------
+
+Instruction #274:
+ Timestep: 273
+ Time: 2025-11-20 08:45:00+00:00
+ ID: 9e8d2adc-d2cf-4dc2-8795-693f1ffa1313
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 08:45:00+00:00
+----------------------------------------
+
+Instruction #275:
+ Timestep: 274
+ Time: 2025-11-20 08:50:00+00:00
+ ID: 7c959381-7e1f-40b5-9f6f-abd6dae934c8
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 08:50:00+00:00
+----------------------------------------
+
+Instruction #276:
+ Timestep: 275
+ Time: 2025-11-20 08:55:00+00:00
+ ID: 54e87ae1-e5ad-4023-a6f4-22bde56f2b5f
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 08:55:00+00:00
+----------------------------------------
+
+Instruction #277:
+ Timestep: 276
+ Time: 2025-11-20 09:00:00+00:00
+ ID: e98b4546-35f1-402b-a580-0744eb63fbb2
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 09:00:00+00:00
+----------------------------------------
+
+Instruction #278:
+ Timestep: 277
+ Time: 2025-11-20 09:05:00+00:00
+ ID: 6aba851a-33c0-4539-8837-6af92e866766
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 09:05:00+00:00
+----------------------------------------
+
+Instruction #279:
+ Timestep: 278
+ Time: 2025-11-20 09:10:00+00:00
+ ID: bb7083f6-df53-4c64-8184-f6e79036ac77
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 09:10:00+00:00
+----------------------------------------
+
+Instruction #280:
+ Timestep: 279
+ Time: 2025-11-20 09:15:00+00:00
+ ID: 3ec0fc2a-1827-4943-9a0c-35d36852150b
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 09:15:00+00:00
+----------------------------------------
+
+Instruction #281:
+ Timestep: 280
+ Time: 2025-11-20 09:20:00+00:00
+ ID: 8889d42b-8f01-4668-a46f-8cd65cf6e3ee
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 09:20:00+00:00
+----------------------------------------
+
+Instruction #282:
+ Timestep: 281
+ Time: 2025-11-20 09:25:00+00:00
+ ID: d313c40e-74ad-4881-95db-56bb2e175118
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 09:25:00+00:00
+----------------------------------------
+
+Instruction #283:
+ Timestep: 282
+ Time: 2025-11-20 09:30:00+00:00
+ ID: 0812cbaa-02c0-4779-bee7-130332da7dea
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 09:30:00+00:00
+----------------------------------------
+
+Instruction #284:
+ Timestep: 283
+ Time: 2025-11-20 09:35:00+00:00
+ ID: 1af91293-3b2a-4ac0-9082-14c62c0a560d
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 09:35:00+00:00
+----------------------------------------
+
+Instruction #285:
+ Timestep: 284
+ Time: 2025-11-20 09:40:00+00:00
+ ID: a91a93f0-fb68-434d-9e6f-44153fd58331
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 09:40:00+00:00
+----------------------------------------
+
+Instruction #286:
+ Timestep: 285
+ Time: 2025-11-20 09:45:00+00:00
+ ID: f5f79c47-e341-404d-9466-fc9d1eb89486
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 09:45:00+00:00
+----------------------------------------
+
+Instruction #287:
+ Timestep: 286
+ Time: 2025-11-20 09:50:00+00:00
+ ID: e126bdba-e3b5-4e71-82ad-ab95b00ab4a5
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 09:50:00+00:00
+----------------------------------------
+
+Instruction #288:
+ Timestep: 287
+ Time: 2025-11-20 09:55:00+00:00
+ ID: 0aff01a8-fb77-4009-99eb-99d5f47a97a0
+ Actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+ Operation Mode: DHW_OFF
+ Operation Mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+ Operation Mode Factor: 0.0
+ Execution Time: 2025-11-20 09:55:00+00:00
+----------------------------------------
diff --git a/flexmeasures_s2/profile_steering/examples/itho.yaml b/flexmeasures_s2/profile_steering/examples/itho.yaml
new file mode 100644
index 0000000..15e96a0
--- /dev/null
+++ b/flexmeasures_s2/profile_steering/examples/itho.yaml
@@ -0,0 +1,192 @@
+ INFO: 📋 FRBC.SystemDescription
+[FLEXMEASURES][2025-11-03 08:13:02,259] DEBUG: {"message_type":"FRBC.SystemDescription","message_id":"56b1c93f-7bf9-4f40-a0c2-8996309bad87","valid_from":"2025-11-03T08:12:42.561432Z","actuators":[{"id":"189f6a58-e78a-4c3f-bcda-6cd151342285","diagnostic_label":"Heat Pump (DHW)","supported_commodities":["ELECTRICITY"],"operation_modes":[{"id":"959eb2c3-3d92-4ca1-8005-1d9caab972a5","diagnostic_label":"DHW_OFF","elements":[{"fill_level_range":{"start_of_range":0.0,"end_of_range":100.0},"fill_rate":{"start_of_range":0.0,"end_of_range":0.0},"power_ranges":[{"start_of_range":0.0,"end_of_range":0.0,"commodity_quantity":"ELECTRIC.POWER.L1"}],"running_costs":{"start_of_range":0.0,"end_of_range":0.0}}],"abnormal_condition_only":false},{"id":"31314092-4456-43f8-89dd-0421194bc821","diagnostic_label":"DHW_ON","elements":[{"fill_level_range":{"start_of_range":10.0,"end_of_range":61.5},"fill_rate":{"start_of_range":0.002031807896078361,"end_of_range":0.002031807896078361},"power_ranges":[{"start_of_range":1100.0,"end_of_range":1100.0,"commodity_quantity":"ELECTRIC.POWER.L1"}]}],"abnormal_condition_only":false}],"transitions":[{"id":"4359a2e3-8782-4192-9775-2a29b828c45e","from":"959eb2c3-3d92-4ca1-8005-1d9caab972a5","to":"31314092-4456-43f8-89dd-0421194bc821","start_timers":["702cd17f-ac40-429e-93d4-ce69c8eb8040"],"blocking_timers":["b27ef8c9-c440-44cb-8deb-f7f85f491a92"],"transition_duration":300000,"abnormal_condition_only":false},{"id":"8d3bdeae-5052-4579-a62b-a36b427df63a","from":"31314092-4456-43f8-89dd-0421194bc821","to":"959eb2c3-3d92-4ca1-8005-1d9caab972a5","start_timers":["b27ef8c9-c440-44cb-8deb-f7f85f491a92"],"blocking_timers":["702cd17f-ac40-429e-93d4-ce69c8eb8040"],"transition_duration":180000,"abnormal_condition_only":false}],"timers":[{"id":"702cd17f-ac40-429e-93d4-ce69c8eb8040","diagnostic_label":"DHW Minimum On Time","duration":1800000},{"id":"b27ef8c9-c440-44cb-8deb-f7f85f491a92","diagnostic_label":"DHW Minimum Off Time","duration":600000}]}],"storage":{"diagnostic_label":"DHW Buffer","fill_level_label":"Temperature in degrees Celcius","provides_leakage_behaviour":true,"provides_fill_level_target_profile":true,"provides_usage_forecast":true,"fill_level_range":{"start_of_range":10.0,"end_of_range":61.5}}}
+[FLEXMEASURES][2025-11-03 08:13:02,264] DEBUG: ✅ ReceptionStatus: OK
+[FLEXMEASURES][2025-11-03 08:13:02,302] DEBUG: ⚙️ Actuator 189f6a58...: 2 modes, 2 transitions, 2 timers
+[FLEXMEASURES][2025-11-03 08:13:02,310] DEBUG: 💾 Storage: 10.0-61.5 Temperature in degrees Celcius
+[FLEXMEASURES][2025-11-03 08:13:02,311] INFO: 📋 SystemDescription: 1 actuator(s)
+[FLEXMEASURES][2025-11-03 08:13:02,311] DEBUG: ⏳ Missing actuator status for: 189f6a58-e78a-4c3f-bcda-6cd151342285
+[FLEXMEASURES][2025-11-03 08:13:02,312] DEBUG: 📊 Device readiness: ✅ SystemDescription | ❌ FillLevelTargetProfile | ❌ StorageStatus | ❌ ActuatorStatus (0/1 received)
+[FLEXMEASURES][2025-11-03 08:13:02,312] INFO: ⏳ Waiting for: FillLevelTargetProfile, StorageStatus, ActuatorStatus
+[FLEXMEASURES][2025-11-03 08:13:03,996] INFO: 📋 FRBC.SystemDescription
+[FLEXMEASURES][2025-11-03 08:13:04,006] DEBUG: {"message_type":"FRBC.SystemDescription","message_id":"ae668e82-c315-48a3-80be-7d3389b3e4b4","valid_from":"2025-11-03T08:12:44.399427Z","actuators":[{"id":"189f6a58-e78a-4c3f-bcda-6cd151342285","diagnostic_label":"Heat Pump (DHW)","supported_commodities":["ELECTRICITY"],"operation_modes":[{"id":"959eb2c3-3d92-4ca1-8005-1d9caab972a5","diagnostic_label":"DHW_OFF","elements":[{"fill_level_range":{"start_of_range":0.0,"end_of_range":100.0},"fill_rate":{"start_of_range":0.0,"end_of_range":0.0},"power_ranges":[{"start_of_range":0.0,"end_of_range":0.0,"commodity_quantity":"ELECTRIC.POWER.3_PHASE_SYMMETRIC"}],"running_costs":{"start_of_range":0.0,"end_of_range":0.0}}],"abnormal_condition_only":false},{"id":"31314092-4456-43f8-89dd-0421194bc821","diagnostic_label":"DHW_ON","elements":[{"fill_level_range":{"start_of_range":10.0,"end_of_range":61.5},"fill_rate":{"start_of_range":0.010397806827295799,"end_of_range":0.010397806827295799},"power_ranges":[{"start_of_range":2060.9,"end_of_range":2060.9,"commodity_quantity":"ELECTRIC.POWER.3_PHASE_SYMMETRIC"}]}],"abnormal_condition_only":false}],"transitions":[{"id":"4359a2e3-8782-4192-9775-2a29b828c45e","from":"959eb2c3-3d92-4ca1-8005-1d9caab972a5","to":"31314092-4456-43f8-89dd-0421194bc821","start_timers":["702cd17f-ac40-429e-93d4-ce69c8eb8040"],"blocking_timers":["b27ef8c9-c440-44cb-8deb-f7f85f491a92"],"transition_duration":300000,"abnormal_condition_only":false},{"id":"8d3bdeae-5052-4579-a62b-a36b427df63a","from":"31314092-4456-43f8-89dd-0421194bc821","to":"959eb2c3-3d92-4ca1-8005-1d9caab972a5","start_timers":["b27ef8c9-c440-44cb-8deb-f7f85f491a92"],"blocking_timers":["702cd17f-ac40-429e-93d4-ce69c8eb8040"],"transition_duration":180000,"abnormal_condition_only":false}],"timers":[{"id":"702cd17f-ac40-429e-93d4-ce69c8eb8040","diagnostic_label":"DHW Minimum On Time","duration":1800000},{"id":"b27ef8c9-c440-44cb-8deb-f7f85f491a92","diagnostic_label":"DHW Minimum Off Time","duration":600000}]}],"storage":{"diagnostic_label":"DHW Buffer","fill_level_label":"Temperature in degrees Celcius","provides_leakage_behaviour":true,"provides_fill_level_target_profile":true,"provides_usage_forecast":true,"fill_level_range":{"start_of_range":10.0,"end_of_range":61.5}}}
+[FLEXMEASURES][2025-11-03 08:13:04,008] DEBUG: ✅ ReceptionStatus: OK
+[FLEXMEASURES][2025-11-03 08:13:04,034] INFO: 📋 FRBC.SystemDescription
+[FLEXMEASURES][2025-11-03 08:13:04,037] DEBUG: {"message_type":"FRBC.SystemDescription","message_id":"d8d28734-7073-4a7a-967c-6047f4f35e1f","valid_from":"2025-11-03T08:12:44.405821Z","actuators":[{"id":"189f6a58-e78a-4c3f-bcda-6cd151342285","diagnostic_label":"Heat Pump (DHW)","supported_commodities":["ELECTRICITY"],"operation_modes":[{"id":"959eb2c3-3d92-4ca1-8005-1d9caab972a5","diagnostic_label":"DHW_OFF","elements":[{"fill_level_range":{"start_of_range":0.0,"end_of_range":100.0},"fill_rate":{"start_of_range":0.0,"end_of_range":0.0},"power_ranges":[{"start_of_range":0.0,"end_of_range":0.0,"commodity_quantity":"ELECTRIC.POWER.3_PHASE_SYMMETRIC"}],"running_costs":{"start_of_range":0.0,"end_of_range":0.0}}],"abnormal_condition_only":false},{"id":"31314092-4456-43f8-89dd-0421194bc821","diagnostic_label":"DHW_ON","elements":[{"fill_level_range":{"start_of_range":10.0,"end_of_range":45.0},"fill_rate":{"start_of_range":0.010657432945423886,"end_of_range":0.010657432945423886},"power_ranges":[{"start_of_range":1596.1,"end_of_range":1596.1,"commodity_quantity":"ELECTRIC.POWER.3_PHASE_SYMMETRIC"}]}],"abnormal_condition_only":false}],"transitions":[{"id":"4359a2e3-8782-4192-9775-2a29b828c45e","from":"959eb2c3-3d92-4ca1-8005-1d9caab972a5","to":"31314092-4456-43f8-89dd-0421194bc821","start_timers":["702cd17f-ac40-429e-93d4-ce69c8eb8040"],"blocking_timers":["b27ef8c9-c440-44cb-8deb-f7f85f491a92"],"transition_duration":300000,"abnormal_condition_only":false},{"id":"8d3bdeae-5052-4579-a62b-a36b427df63a","from":"31314092-4456-43f8-89dd-0421194bc821","to":"959eb2c3-3d92-4ca1-8005-1d9caab972a5","start_timers":["b27ef8c9-c440-44cb-8deb-f7f85f491a92"],"blocking_timers":["702cd17f-ac40-429e-93d4-ce69c8eb8040"],"transition_duration":180000,"abnormal_condition_only":false}],"timers":[{"id":"702cd17f-ac40-429e-93d4-ce69c8eb8040","diagnostic_label":"DHW Minimum On Time","duration":1800000},{"id":"b27ef8c9-c440-44cb-8deb-f7f85f491a92","diagnostic_label":"DHW Minimum Off Time","duration":600000}]}],"storage":{"diagnostic_label":"DHW Buffer","fill_level_label":"Temperature in degrees Celcius","provides_leakage_behaviour":true,"provides_fill_level_target_profile":true,"provides_usage_forecast":true,"fill_level_range":{"start_of_range":10.0,"end_of_range":45.0}}}
+[FLEXMEASURES][2025-11-03 08:13:04,037] DEBUG: ✅ ReceptionStatus: OK
+[FLEXMEASURES][2025-11-03 08:13:04,043] DEBUG: ⚙️ Actuator 189f6a58...: 2 modes, 2 transitions, 2 timers
+[FLEXMEASURES][2025-11-03 08:13:04,052] DEBUG: 💾 Storage: 10.0-61.5 Temperature in degrees Celcius
+[FLEXMEASURES][2025-11-03 08:13:04,053] DEBUG: ⚙️ Actuator 189f6a58...: 2 modes, 2 transitions, 2 timers
+[FLEXMEASURES][2025-11-03 08:13:04,053] INFO: 📋 SystemDescription: 1 actuator(s)
+[FLEXMEASURES][2025-11-03 08:13:04,054] DEBUG: ⏳ Missing actuator status for: 189f6a58-e78a-4c3f-bcda-6cd151342285
+[FLEXMEASURES][2025-11-03 08:13:04,054] DEBUG: 📊 Device readiness: ✅ SystemDescription | ❌ FillLevelTargetProfile | ❌ StorageStatus | ❌ ActuatorStatus (0/1 received)
+[FLEXMEASURES][2025-11-03 08:13:04,054] INFO: ⏳ Waiting for: FillLevelTargetProfile, StorageStatus, ActuatorStatus
+[FLEXMEASURES][2025-11-03 08:13:04,064] DEBUG: 💾 Storage: 10.0-45.0 Temperature in degrees Celcius
+[FLEXMEASURES][2025-11-03 08:13:04,065] INFO: 📋 SystemDescription: 1 actuator(s)
+[FLEXMEASURES][2025-11-03 08:13:04,065] DEBUG: ⏳ Missing actuator status for: 189f6a58-e78a-4c3f-bcda-6cd151342285
+[FLEXMEASURES][2025-11-03 08:13:04,065] DEBUG: 📊 Device readiness: ✅ SystemDescription | ❌ FillLevelTargetProfile | ❌ StorageStatus | ❌ ActuatorStatus (0/1 received)
+[FLEXMEASURES][2025-11-03 08:13:04,066] INFO: ⏳ Waiting for: FillLevelTargetProfile, StorageStatus, ActuatorStatus
+[FLEXMEASURES][2025-11-03 08:13:23,415] INFO: 📥 PowerMeasurement
+[FLEXMEASURES][2025-11-03 08:13:26,546] DEBUG: {"message_type":"PowerMeasurement","message_id":"51a2cb16-7482-4d83-b821-4650ad7ac4e4","measurement_timestamp":"2025-11-03T08:13:03.688588Z","values":[{"commodity_quantity":"ELECTRIC.POWER.L1","value":1100.0}]}
+[FLEXMEASURES][2025-11-03 08:13:27,227] INFO: 📥 PowerMeasurement
+[FLEXMEASURES][2025-11-03 08:13:28,343] INFO: 📥 PowerMeasurement
+[FLEXMEASURES][2025-11-03 08:13:28,484] DEBUG: ✅ ReceptionStatus: OK
+[FLEXMEASURES][2025-11-03 08:13:31,850] DEBUG: {"message_type":"PowerMeasurement","message_id":"9df1cb51-2a02-467b-8491-2eed109fa8b9","measurement_timestamp":"2025-11-03T08:13:05.588639Z","values":[{"commodity_quantity":"ELECTRIC.POWER.3_PHASE_SYMMETRIC","value":2.2}]}
+[FLEXMEASURES][2025-11-03 08:13:32,776] DEBUG: ✅ ReceptionStatus: OK
+[FLEXMEASURES][2025-11-03 08:13:32,593] DEBUG: {"message_type":"PowerMeasurement","message_id":"c8d6c04a-eb31-46d6-a307-f5de1c6e8375","measurement_timestamp":"2025-11-03T08:13:05.725115Z","values":[{"commodity_quantity":"ELECTRIC.POWER.3_PHASE_SYMMETRIC","value":2.8}]}
+[FLEXMEASURES][2025-11-03 08:13:34,551] DEBUG: ✅ ReceptionStatus: OK
+[FLEXMEASURES][2025-11-03 08:13:36,544] INFO: ⚙️ FRBC.ActuatorStatus
+[FLEXMEASURES][2025-11-03 08:13:37,444] INFO: ⚙️ FRBC.ActuatorStatus
+[FLEXMEASURES][2025-11-03 08:13:37,447] DEBUG: ✅ ReceptionStatus: OK
+[FLEXMEASURES][2025-11-03 08:13:37,449] DEBUG: ✅ ReceptionStatus: OK
+[FLEXMEASURES][2025-11-03 08:13:37,450] INFO: ⚙️ FRBC.ActuatorStatus
+[FLEXMEASURES][2025-11-03 08:13:37,450] DEBUG: ✅ ReceptionStatus: OK
+[FLEXMEASURES][2025-11-03 08:13:37,548] DEBUG: ⚙️ ActuatorStatus: factor=0.0
+[FLEXMEASURES][2025-11-03 08:13:37,553] DEBUG: 📊 Device readiness: ✅ SystemDescription | ❌ FillLevelTargetProfile | ❌ StorageStatus | ✅ ActuatorStatus (all 1 received)
+[FLEXMEASURES][2025-11-03 08:13:37,553] INFO: ⏳ Waiting for: FillLevelTargetProfile, StorageStatus
+[FLEXMEASURES][2025-11-03 08:13:37,553] DEBUG: ⚙️ ActuatorStatus: factor=0.0
+[FLEXMEASURES][2025-11-03 08:13:37,554] DEBUG: ⚙️ ActuatorStatus: factor=1.0
+[FLEXMEASURES][2025-11-03 08:13:37,554] DEBUG: 📊 Device readiness: ✅ SystemDescription | ❌ FillLevelTargetProfile | ❌ StorageStatus | ✅ ActuatorStatus (all 1 received)
+[FLEXMEASURES][2025-11-03 08:13:37,555] INFO: ⏳ Waiting for: FillLevelTargetProfile, StorageStatus
+[FLEXMEASURES][2025-11-03 08:13:37,554] DEBUG: 📊 Device readiness: ✅ SystemDescription | ❌ FillLevelTargetProfile | ❌ StorageStatus | ✅ ActuatorStatus (all 1 received)
+[FLEXMEASURES][2025-11-03 08:13:37,555] INFO: ⏳ Waiting for: FillLevelTargetProfile, StorageStatus
+[FLEXMEASURES][2025-11-03 08:13:55,822] ERROR: NotFound:"The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again." [occurred at /usr/local/lib/python3.10/dist-packages/werkzeug/routing/map.py(match):629,URL was: /cgi-bin/luci/;stok=/locale]
+Traceback (most recent call last):
+ File "/usr/local/lib/python3.10/dist-packages/flask/app.py", line 880, in full_dispatch_request
+ rv = self.dispatch_request()
+ File "/usr/local/lib/python3.10/dist-packages/flask/app.py", line 854, in dispatch_request
+ self.raise_routing_exception(req)
+ File "/usr/local/lib/python3.10/dist-packages/flask/app.py", line 463, in raise_routing_exception
+ raise request.routing_exception # type: ignore[misc]
+ File "/usr/local/lib/python3.10/dist-packages/flask/ctx.py", line 362, in match_request
+ result = self.url_adapter.match(return_rule=True) # type: ignore
+ File "/usr/local/lib/python3.10/dist-packages/werkzeug/routing/map.py", line 629, in match
+ raise NotFound() from None
+werkzeug.exceptions.NotFound: 404 Not Found: The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.
+[FLEXMEASURES][2025-11-03 08:14:02,291] INFO: ⚙️ FRBC.ActuatorStatus
+[FLEXMEASURES][2025-11-03 08:14:02,297] DEBUG: ✅ ReceptionStatus: OK
+[FLEXMEASURES][2025-11-03 08:14:02,321] DEBUG: ⚙️ ActuatorStatus: factor=1.0
+[FLEXMEASURES][2025-11-03 08:14:02,322] DEBUG: 📊 Device readiness: ✅ SystemDescription | ❌ FillLevelTargetProfile | ❌ StorageStatus | ✅ ActuatorStatus (all 1 received)
+[FLEXMEASURES][2025-11-03 08:14:02,322] INFO: ⏳ Waiting for: FillLevelTargetProfile, StorageStatus
+[FLEXMEASURES][2025-11-03 08:14:02,323] INFO: 📥 FRBC.LeakageBehaviour
+[FLEXMEASURES][2025-11-03 08:14:02,324] DEBUG: {"message_type":"FRBC.LeakageBehaviour","message_id":"34c000d1-0140-4589-988f-12c80902169f","valid_from":"2025-11-03T08:13:42.632614Z","elements":[{"fill_level_range":{"start_of_range":10.0,"end_of_range":61.5},"leakage_rate":0.00004464134898917508}]}
+[FLEXMEASURES][2025-11-03 08:14:02,325] DEBUG: ✅ ReceptionStatus: OK
+[FLEXMEASURES][2025-11-03 08:14:04,030] INFO: ⚙️ FRBC.ActuatorStatus
+[FLEXMEASURES][2025-11-03 08:14:04,037] DEBUG: ✅ ReceptionStatus: OK
+[FLEXMEASURES][2025-11-03 08:14:04,057] DEBUG: ⚙️ ActuatorStatus: factor=0.0
+[FLEXMEASURES][2025-11-03 08:14:04,057] DEBUG: 📊 Device readiness: ✅ SystemDescription | ❌ FillLevelTargetProfile | ❌ StorageStatus | ✅ ActuatorStatus (all 1 received)
+[FLEXMEASURES][2025-11-03 08:14:04,058] INFO: ⏳ Waiting for: FillLevelTargetProfile, StorageStatus
+[FLEXMEASURES][2025-11-03 08:14:04,060] INFO: 📥 FRBC.LeakageBehaviour
+[FLEXMEASURES][2025-11-03 08:14:04,061] DEBUG: {"message_type":"FRBC.LeakageBehaviour","message_id":"301c976d-a646-4711-95af-37a69bf14b1b","valid_from":"2025-11-03T08:13:44.451202Z","elements":[{"fill_level_range":{"start_of_range":10.0,"end_of_range":61.5},"leakage_rate":0.00004222830309786831}]}
+[FLEXMEASURES][2025-11-03 08:14:04,061] DEBUG: ✅ ReceptionStatus: OK
+[FLEXMEASURES][2025-11-03 08:14:04,091] INFO: ⚙️ FRBC.ActuatorStatus
+[FLEXMEASURES][2025-11-03 08:14:04,091] DEBUG: ✅ ReceptionStatus: OK
+[FLEXMEASURES][2025-11-03 08:14:04,100] DEBUG: ⚙️ ActuatorStatus: factor=0.0
+[FLEXMEASURES][2025-11-03 08:14:04,100] DEBUG: 📊 Device readiness: ✅ SystemDescription | ❌ FillLevelTargetProfile | ❌ StorageStatus | ✅ ActuatorStatus (all 1 received)
+[FLEXMEASURES][2025-11-03 08:14:04,100] INFO: ⏳ Waiting for: FillLevelTargetProfile, StorageStatus
+[FLEXMEASURES][2025-11-03 08:14:04,102] INFO: 📥 FRBC.LeakageBehaviour
+[FLEXMEASURES][2025-11-03 08:14:04,103] DEBUG: {"message_type":"FRBC.LeakageBehaviour","message_id":"80a4c0a4-8814-4e21-9f1f-5cd038d4de6d","valid_from":"2025-11-03T08:13:44.479542Z","elements":[{"fill_level_range":{"start_of_range":10.0,"end_of_range":45.0},"leakage_rate":0.00004464134898917508}]}
+[FLEXMEASURES][2025-11-03 08:14:04,103] DEBUG: ✅ ReceptionStatus: OK
+[FLEXMEASURES][2025-11-03 08:15:14,782] INFO: 🔋 FRBC.StorageStatus
+[FLEXMEASURES][2025-11-03 08:15:19,702] INFO: 🔋 FRBC.StorageStatus
+[FLEXMEASURES][2025-11-03 08:15:19,700] INFO: 🔋 FRBC.StorageStatus
+[FLEXMEASURES][2025-11-03 08:15:19,798] DEBUG: {"message_type":"FRBC.StorageStatus","message_id":"d0970b64-b069-4738-96c5-b4318e6ce5cf","present_fill_level":42.4669997}
+[FLEXMEASURES][2025-11-03 08:15:19,858] DEBUG: ✅ ReceptionStatus: OK
+[FLEXMEASURES][2025-11-03 08:15:19,861] DEBUG: {"message_type":"FRBC.StorageStatus","message_id":"e101ac35-566d-4a27-8055-625ea344f50b","present_fill_level":16.182}
+[FLEXMEASURES][2025-11-03 08:15:19,902] DEBUG: {"message_type":"FRBC.StorageStatus","message_id":"fdef46a5-c812-4167-a3db-22518aa18654","present_fill_level":29.5889991}
+[FLEXMEASURES][2025-11-03 08:15:19,900] DEBUG: ✅ ReceptionStatus: OK
+[FLEXMEASURES][2025-11-03 08:15:19,939] DEBUG: ✅ ReceptionStatus: OK
+[FLEXMEASURES][2025-11-03 08:15:20,022] WARNING: Resource could not be saved as an asset: This session is provisioning a new connection; concurrent operations are not permitted (Background on this error at: https://sqlalche.me/e/20/isce)
+/usr/local/lib/python3.10/dist-packages/timely_beliefs/beliefs/classes.py:633: SAWarning: Object of type not in session, add operation along 'Sensor.beliefs' will not proceed (This warning originated from the Session 'autoflush' process, which was invoked automatically in response to a user-initiated operation.)
+ df = pd.DataFrame(session.execute(q))
+/usr/local/lib/python3.10/dist-packages/timely_beliefs/beliefs/classes.py:633: SAWarning: Object of type not in session, add operation along 'DataSource.beliefs' will not proceed (This warning originated from the Session 'autoflush' process, which was invoked automatically in response to a user-initiated operation.)
+ df = pd.DataFrame(session.execute(q))
+/usr/local/lib/python3.10/dist-packages/timely_beliefs/beliefs/classes.py:633: SAWarning: Object of type not in session, add operation along 'Sensor.beliefs' will not proceed (This warning originated from the Session 'autoflush' process, which was invoked automatically in response to a user-initiated operation.)
+ df = pd.DataFrame(session.execute(q))
+/usr/local/lib/python3.10/dist-packages/timely_beliefs/beliefs/classes.py:633: SAWarning: Object of type not in session, add operation along 'DataSource.beliefs' will not proceed (This warning originated from the Session 'autoflush' process, which was invoked automatically in response to a user-initiated operation.)
+ df = pd.DataFrame(session.execute(q))
+[FLEXMEASURES][2025-11-03 08:15:21,233] INFO: SAVING TO DB...
+/usr/local/lib/python3.10/dist-packages/timely_beliefs/beliefs/classes.py:332: SAWarning: Object of type not in session, add operation along 'Sensor.beliefs' will not proceed (This warning originated from the Session 'autoflush' process, which was invoked automatically in response to a user-initiated operation.)
+ session.execute(smt)
+/usr/local/lib/python3.10/dist-packages/timely_beliefs/beliefs/classes.py:332: SAWarning: Object of type not in session, add operation along 'DataSource.beliefs' will not proceed (This warning originated from the Session 'autoflush' process, which was invoked automatically in response to a user-initiated operation.)
+ session.execute(smt)
+[FLEXMEASURES][2025-11-03 08:15:21,464] INFO: 🔋 StorageStatus: 16.2%
+[FLEXMEASURES][2025-11-03 08:15:21,471] DEBUG: 📊 Device readiness: ✅ SystemDescription | ❌ FillLevelTargetProfile | ✅ StorageStatus | ✅ ActuatorStatus (all 1 received)
+[FLEXMEASURES][2025-11-03 08:15:21,471] INFO: ⏳ Waiting for: FillLevelTargetProfile
+[FLEXMEASURES][2025-11-03 08:15:21,516] INFO: 🎯 FRBC.FillLevelTargetProfile
+[FLEXMEASURES][2025-11-03 08:15:21,528] DEBUG: {"message_type":"FRBC.FillLevelTargetProfile","message_id":"a9fbad67-2b93-493e-8c95-11098f9ba85a","start_time":"2025-11-02T23:00:00Z","elements":[{"duration":23400000,"fill_level_range":{"start_of_range":10.0,"end_of_range":55.0}},{"duration":5400000,"fill_level_range":{"start_of_range":51.5,"end_of_range":55.0}},{"duration":32400000,"fill_level_range":{"start_of_range":10.0,"end_of_range":55.0}},{"duration":10800000,"fill_level_range":{"start_of_range":51.5,"end_of_range":55.0}},{"duration":14400000,"fill_level_range":{"start_of_range":10.0,"end_of_range":55.0}},{"duration":23400000,"fill_level_range":{"start_of_range":10.0,"end_of_range":55.0}},{"duration":5400000,"fill_level_range":{"start_of_range":51.5,"end_of_range":55.0}},{"duration":32400000,"fill_level_range":{"start_of_range":10.0,"end_of_range":55.0}},{"duration":10800000,"fill_level_range":{"start_of_range":51.5,"end_of_range":55.0}},{"duration":14400000,"fill_level_range":{"start_of_range":10.0,"end_of_range":55.0}}]}
+[FLEXMEASURES][2025-11-03 08:15:21,531] DEBUG: ✅ ReceptionStatus: OK
+[FLEXMEASURES][2025-11-03 08:15:21,587] DEBUG: 🎯 Start: 23:00:00
+[FLEXMEASURES][2025-11-03 08:15:21,591] INFO: 🎯 TargetProfile: 10 element(s)
+[FLEXMEASURES][2025-11-03 08:15:21,592] DEBUG: 📊 Device readiness: ✅ SystemDescription | ✅ FillLevelTargetProfile | ✅ StorageStatus | ✅ ActuatorStatus (all 1 received)
+[FLEXMEASURES][2025-11-03 08:15:21,594] INFO: 🎯 Generating instructions for 3c4cdc54...
+[FLEXMEASURES][2025-11-03 08:15:21,595] DEBUG: 🔧 Creating device state for 3c4cdc54...
+[FLEXMEASURES][2025-11-03 08:15:21,636] DEBUG: Storage status used for scheduler: message_type='FRBC.StorageStatus' message_id=UUID('e101ac35-566d-4a27-8055-625ea344f50b') present_fill_level=16.182
+[FLEXMEASURES][2025-11-03 08:15:21,642] DEBUG: Present fill level used for scheduler: 16.182
+[FLEXMEASURES][2025-11-03 08:15:21,642] DEBUG: ✅ Device state ready: 3c4cdc54...
+[FLEXMEASURES][2025-11-03 08:15:21,644] INFO: 🧮 S2FlaskScheduler started
+[FLEXMEASURES][2025-11-03 08:15:21,646] DEBUG: 🔧 Creating device state for 3c4cdc54...
+[FLEXMEASURES][2025-11-03 08:15:21,649] DEBUG: Storage status used for scheduler: message_type='FRBC.StorageStatus' message_id=UUID('e101ac35-566d-4a27-8055-625ea344f50b') present_fill_level=16.182
+[FLEXMEASURES][2025-11-03 08:15:21,649] DEBUG: Present fill level used for scheduler: 16.182
+[FLEXMEASURES][2025-11-03 08:15:21,649] DEBUG: ✅ Device state ready: 3c4cdc54...
+[FLEXMEASURES][2025-11-03 08:15:21,814] INFO: SAVING TO DB...
+[FLEXMEASURES][2025-11-03 08:15:21,880] WARNING: ⚠️ All prices missing, assuming 1 EUR/MWh
+[FLEXMEASURES][2025-11-03 08:15:21,923] DEBUG: 📊 Cluster: 1 device(s) in state
+[FLEXMEASURES][2025-11-03 08:15:21,928] INFO: 📊 Planning: 24.0h window (08:15 → 08:15)
+[FLEXMEASURES][2025-11-03 08:15:21,932] DEBUG: ⏱️ Resolution: 0:05:00, Belief time: 17:05:00
+[FLEXMEASURES][2025-11-03 08:15:21,932] DEBUG: 🧮 Running optimization algorithm...
+[FLEXMEASURES][2025-11-03 08:15:21,932] WARNING: CongestionPoint without target! CongestionPoint: . Generating empty target.
+[FLEXMEASURES][2025-11-03 08:15:21,934] DEBUG: S2 FRBC planner created!
+[FLEXMEASURES][2025-11-03 08:15:22,005] INFO: SAVING TO DB...
+/usr/local/lib/python3.10/dist-packages/timely_beliefs/beliefs/classes.py:332: SAWarning: Object of type not in session, add operation along 'Sensor.beliefs' will not proceed (This warning originated from the Session 'autoflush' process, which was invoked automatically in response to a user-initiated operation.)
+ session.execute(smt)
+/usr/local/lib/python3.10/dist-packages/timely_beliefs/beliefs/classes.py:332: SAWarning: Object of type not in session, add operation along 'DataSource.beliefs' will not proceed (This warning originated from the Session 'autoflush' process, which was invoked automatically in response to a user-initiated operation.)
+ session.execute(smt)
+[FLEXMEASURES][2025-11-03 08:24:25,249] INFO: 🔋 StorageStatus: 42.5%
+[FLEXMEASURES][2025-11-03 08:24:25,252] INFO: 🔋 StorageStatus: 29.6%
+[FLEXMEASURES][2025-11-03 08:24:25,258] DEBUG: 📊 Device readiness: ✅ SystemDescription | ❌ FillLevelTargetProfile | ✅ StorageStatus | ✅ ActuatorStatus (all 1 received)
+[FLEXMEASURES][2025-11-03 08:24:25,258] INFO: ⏳ Waiting for: FillLevelTargetProfile
+[FLEXMEASURES][2025-11-03 08:24:25,259] DEBUG: 📊 Device readiness: ✅ SystemDescription | ❌ FillLevelTargetProfile | ✅ StorageStatus | ✅ ActuatorStatus (all 1 received)
+[FLEXMEASURES][2025-11-03 08:24:25,259] INFO: ⏳ Waiting for: FillLevelTargetProfile
+[FLEXMEASURES][2025-11-03 08:24:25,327] INFO: 🎯 FRBC.FillLevelTargetProfile
+[FLEXMEASURES][2025-11-03 08:24:25,326] INFO: 🎯 FRBC.FillLevelTargetProfile
+[FLEXMEASURES][2025-11-03 08:24:25,335] DEBUG: {"message_type":"FRBC.FillLevelTargetProfile","message_id":"c208fb58-c2ac-4771-a6cd-77e9a8e4ac86","start_time":"2025-11-02T23:00:00Z","elements":[{"duration":23400000,"fill_level_range":{"start_of_range":10.0,"end_of_range":59.0}},{"duration":5400000,"fill_level_range":{"start_of_range":55.5,"end_of_range":59.0}},{"duration":32400000,"fill_level_range":{"start_of_range":10.0,"end_of_range":59.0}},{"duration":10800000,"fill_level_range":{"start_of_range":55.5,"end_of_range":59.0}},{"duration":14400000,"fill_level_range":{"start_of_range":10.0,"end_of_range":59.0}},{"duration":23400000,"fill_level_range":{"start_of_range":10.0,"end_of_range":59.0}},{"duration":5400000,"fill_level_range":{"start_of_range":55.5,"end_of_range":59.0}},{"duration":32400000,"fill_level_range":{"start_of_range":10.0,"end_of_range":59.0}},{"duration":10800000,"fill_level_range":{"start_of_range":55.5,"end_of_range":59.0}},{"duration":14400000,"fill_level_range":{"start_of_range":10.0,"end_of_range":59.0}}]}
+[FLEXMEASURES][2025-11-03 08:24:25,336] DEBUG: {"message_type":"FRBC.FillLevelTargetProfile","message_id":"936aaf07-3a83-48c2-9e5c-e378f9548146","start_time":"2025-11-02T23:00:00Z","elements":[{"duration":23400000,"fill_level_range":{"start_of_range":10.0,"end_of_range":58.5}},{"duration":5400000,"fill_level_range":{"start_of_range":57.0,"end_of_range":58.5}},{"duration":32400000,"fill_level_range":{"start_of_range":10.0,"end_of_range":58.5}},{"duration":10800000,"fill_level_range":{"start_of_range":57.0,"end_of_range":58.5}},{"duration":14400000,"fill_level_range":{"start_of_range":10.0,"end_of_range":58.5}},{"duration":23400000,"fill_level_range":{"start_of_range":10.0,"end_of_range":58.5}},{"duration":5400000,"fill_level_range":{"start_of_range":57.0,"end_of_range":58.5}},{"duration":32400000,"fill_level_range":{"start_of_range":10.0,"end_of_range":58.5}},{"duration":10800000,"fill_level_range":{"start_of_range":57.0,"end_of_range":58.5}},{"duration":14400000,"fill_level_range":{"start_of_range":10.0,"end_of_range":58.5}}]}
+[FLEXMEASURES][2025-11-03 08:24:25,338] DEBUG: ✅ ReceptionStatus: OK
+[FLEXMEASURES][2025-11-03 08:24:25,338] WARNING: ⚠️ Connection closed during response
+[FLEXMEASURES][2025-11-03 08:24:25,337] DEBUG: ✅ ReceptionStatus: OK
+[FLEXMEASURES][2025-11-03 08:24:25,405] WARNING: ⚠️ Connection closed during response
+[FLEXMEASURES][2025-11-03 08:24:25,591] DEBUG: 🎯 Start: 23:00:00
+[FLEXMEASURES][2025-11-03 08:24:25,593] DEBUG: 🎯 Start: 23:00:00
+[FLEXMEASURES][2025-11-03 08:24:25,594] INFO: 🎯 TargetProfile: 10 element(s)
+[FLEXMEASURES][2025-11-03 08:24:25,594] INFO: 🎯 TargetProfile: 10 element(s)
+[FLEXMEASURES][2025-11-03 08:24:25,596] DEBUG: 📊 Device readiness: ✅ SystemDescription | ✅ FillLevelTargetProfile | ✅ StorageStatus | ✅ ActuatorStatus (all 1 received)
+[FLEXMEASURES][2025-11-03 08:24:25,599] INFO: 🎯 Generating instructions for c88e6893...
+[FLEXMEASURES][2025-11-03 08:24:25,600] DEBUG: 📊 Device readiness: ✅ SystemDescription | ✅ FillLevelTargetProfile | ✅ StorageStatus | ✅ ActuatorStatus (all 1 received)
+[FLEXMEASURES][2025-11-03 08:24:25,602] DEBUG: 🔧 Creating device state for c88e6893...
+[FLEXMEASURES][2025-11-03 08:24:25,602] INFO: 🎯 Generating instructions for 9ec19d27...
+[FLEXMEASURES][2025-11-03 08:24:25,634] DEBUG: Storage status used for scheduler: message_type='FRBC.StorageStatus' message_id=UUID('fdef46a5-c812-4167-a3db-22518aa18654') present_fill_level=29.5889991
+[FLEXMEASURES][2025-11-03 08:24:25,636] DEBUG: Present fill level used for scheduler: 29.5889991
+[FLEXMEASURES][2025-11-03 08:24:25,636] DEBUG: ✅ Device state ready: c88e6893...
+[FLEXMEASURES][2025-11-03 08:24:25,636] DEBUG: 🔧 Creating device state for 9ec19d27...
+[FLEXMEASURES][2025-11-03 08:24:25,637] INFO: 🧮 S2FlaskScheduler started
+[FLEXMEASURES][2025-11-03 08:24:25,639] DEBUG: 🔧 Creating device state for 9ec19d27...
+[FLEXMEASURES][2025-11-03 08:24:25,640] DEBUG: Storage status used for scheduler: message_type='FRBC.StorageStatus' message_id=UUID('d0970b64-b069-4738-96c5-b4318e6ce5cf') present_fill_level=42.4669997
+[FLEXMEASURES][2025-11-03 08:24:25,640] DEBUG: Present fill level used for scheduler: 42.4669997
+[FLEXMEASURES][2025-11-03 08:24:25,641] DEBUG: ✅ Device state ready: 9ec19d27...
+[FLEXMEASURES][2025-11-03 08:24:25,639] DEBUG: Storage status used for scheduler: message_type='FRBC.StorageStatus' message_id=UUID('d0970b64-b069-4738-96c5-b4318e6ce5cf') present_fill_level=42.4669997
+[FLEXMEASURES][2025-11-03 08:24:25,672] DEBUG: Present fill level used for scheduler: 42.4669997
+[FLEXMEASURES][2025-11-03 08:24:25,673] DEBUG: ✅ Device state ready: 9ec19d27...
+[FLEXMEASURES][2025-11-03 08:24:25,673] INFO: 🧮 S2FlaskScheduler started
+[FLEXMEASURES][2025-11-03 08:24:25,680] DEBUG: 🔧 Creating device state for 9ec19d27...
+[FLEXMEASURES][2025-11-03 08:24:25,712] DEBUG: Storage status used for scheduler: message_type='FRBC.StorageStatus' message_id=UUID('d0970b64-b069-4738-96c5-b4318e6ce5cf') present_fill_level=42.4669997
+[FLEXMEASURES][2025-11-03 08:24:25,720] DEBUG: Present fill level used for scheduler: 42.4669997
+[FLEXMEASURES][2025-11-03 08:24:25,720] DEBUG: ✅ Device state ready: 9ec19d27...
+Current planning is within the congestion target range. Returning it.
+difference_profile has no Joule elements, using the target directly
+Improving------------------------->
+diff_to_global_target has no Joule elements, using the target directly
+CP '': Selected best controller 'FRBC Device 3c4cdc54-1852-4fe8-963a-57c03499058f' with improvement of 0.
+Root controller: selected best controller 'FRBC Device 3c4cdc54-1852-4fe8-963a-57c03499058f' with global energy impr 0, cost impr [0.], CP impr 0.0. Accepted 1 proposal(s). Iteration 1.
+Optimizing priority class 1 was done after 1 iterations.
+[FLEXMEASURES][2025-11-03 08:24:25,763] DEBUG: {'device_id': '3c4cdc54-1852-4fe8-963a-57c03499058f', 'device_name': 'FRBC Device 3c4cdc54-1852-4fe8-963a-57c03499058f', 'connection_id': '3c4cdc54-1852-4fe8-963a-57c03499058f_connection', 'energy_profile': , 'fill_level_profile': , 'instruction_profile': }
+[FLEXMEASURES][2025-11-03 08:24:25,789] INFO: Generated new plan in 543854.231 ms
diff --git a/flexmeasures_s2/profile_steering/examples/itho_example.txt b/flexmeasures_s2/profile_steering/examples/itho_example.txt
new file mode 100644
index 0000000..7b2cbb5
--- /dev/null
+++ b/flexmeasures_s2/profile_steering/examples/itho_example.txt
@@ -0,0 +1,129 @@
+[FLEXMEASURES][2025-10-29 10:15:02,774] INFO: 🤝 Handshake
+[FLEXMEASURES][2025-10-29 10:15:02,775] DEBUG: {"message_type":"Handshake","message_id":"946e9f1c-8811-4146-a777-dc30fc53e370","role":"RM","supported_protocol_versions":["0.0.2-beta"]}
+[FLEXMEASURES][2025-10-29 10:15:02,775] DEBUG: ✅ ReceptionStatus: OK
+[FLEXMEASURES][2025-10-29 10:15:02,777] DEBUG: {"message_type":"Handshake","message_id":"946e9f1c-8811-4146-a777-dc30fc53e370","role":"RM","supported_protocol_versions":["0.0.2-beta"]}
+[FLEXMEASURES][2025-10-29 10:15:02,778] INFO: 🤝 Handshake complete (protocol 0.0.2-beta)
+[FLEXMEASURES][2025-10-29 10:15:02,779] INFO: 📤 SelectControlType: FRBC
+[FLEXMEASURES][2025-10-29 10:15:02,825] INFO: 📥 ReceptionStatus
+[FLEXMEASURES][2025-10-29 10:15:02,827] DEBUG: {"message_type":"ReceptionStatus","subject_message_id":"9a26b47c-4b5f-4671-a0e9-6ef16d5890a6","status":"OK","diagnostic_label":"Processed okay."}
+[FLEXMEASURES][2025-10-29 10:15:02,827] DEBUG: {"message_type":"ReceptionStatus","subject_message_id":"9a26b47c-4b5f-4671-a0e9-6ef16d5890a6","status":"OK","diagnostic_label":"Processed okay."}
+[FLEXMEASURES][2025-10-29 10:15:02,830] INFO: 📝 ResourceManagerDetails
+[FLEXMEASURES][2025-10-29 10:15:02,831] DEBUG: {"message_type":"ResourceManagerDetails","message_id":"539400ba-db11-4267-bcfd-0674262de7f2","resource_id":"9ec19d27-6b5b-44cf-a43c-9d22b16d3a6a","name":"WPU 5G #WPUTESTKASTS2","roles":[{"role":"ENERGY_CONSUMER","commodity":"ELECTRICITY"}],"manufacturer":"Itho Daalderop","model":"5G - 47177","serial_number":"#WPUTESTKASTS2","firmware_version":"83.0.0","instruction_processing_delay":150000,"available_control_types":["FILL_RATE_BASED_CONTROL"],"provides_forecast":true,"provides_power_measurement_types":["ELECTRIC.POWER.L1"]}
+[FLEXMEASURES][2025-10-29 10:15:02,832] DEBUG: ✅ ReceptionStatus: OK
+[FLEXMEASURES][2025-10-29 10:15:02,832] INFO: 📝 RM registered: 9ec19d27... (WPU 5G #WPUTESTKASTS2)
+[FLEXMEASURES][2025-10-29 10:15:02,865] INFO: 📥 ReceptionStatus
+[FLEXMEASURES][2025-10-29 10:15:02,867] DEBUG: {"message_type":"ReceptionStatus","subject_message_id":"03ddcf93-e5be-4068-bafb-84f7eea57b62","status":"OK","diagnostic_label":"Processed okay."}
+[FLEXMEASURES][2025-10-29 10:15:02,867] DEBUG: {"message_type":"ReceptionStatus","subject_message_id":"03ddcf93-e5be-4068-bafb-84f7eea57b62","status":"OK","diagnostic_label":"Processed okay."}
+[FLEXMEASURES][2025-10-29 10:15:22,775] INFO: 📥 PowerMeasurement
+[FLEXMEASURES][2025-10-29 10:15:22,776] DEBUG: {"message_type":"PowerMeasurement","message_id":"5b836dc1-1ab9-496e-83b0-533ce79f9753","measurement_timestamp":"2025-10-29T10:15:04.430142Z","values":[{"commodity_quantity":"ELECTRIC.POWER.L1","value":1100.0}]}
+[FLEXMEASURES][2025-10-29 10:15:22,776] DEBUG: ✅ ReceptionStatus: OK
+[FLEXMEASURES][2025-10-29 10:15:22,778] INFO: 📋 FRBC.SystemDescription
+[FLEXMEASURES][2025-10-29 10:15:22,779] DEBUG: {"message_type":"FRBC.SystemDescription","message_id":"b155d7ed-8a46-4511-8c5a-1aa88e66b7b3","valid_from":"2025-10-29T10:15:04.774941Z","actuators":[{"id":"189f6a58-e78a-4c3f-bcda-6cd151342285","diagnostic_label":"Heat Pump (DHW)","supported_commodities":["ELECTRICITY"],"operation_modes":[{"id":"959eb2c3-3d92-4ca1-8005-1d9caab972a5","diagnostic_label":"Off","elements":[{"fill_level_range":{"start_of_range":0.0,"end_of_range":100.0},"fill_rate":{"start_of_range":0.0,"end_of_range":0.0},"power_ranges":[{"start_of_range":0.0,"end_of_range":0.0,"commodity_quantity":"ELECTRIC.POWER.L1"}],"running_costs":{"start_of_range":0.0,"end_of_range":0.0}}],"abnormal_condition_only":false},{"id":"31314092-4456-43f8-89dd-0421194bc821","diagnostic_label":"DHW","elements":[{"fill_level_range":{"start_of_range":10.0,"end_of_range":61.5},"fill_rate":{"start_of_range":0.0024383534802858943,"end_of_range":0.0024383534802858943},"power_ranges":[{"start_of_range":1100.0,"end_of_range":1100.0,"commodity_quantity":"ELECTRIC.POWER.L1"}]}],"abnormal_condition_only":false}],"transitions":[{"id":"4359a2e3-8782-4192-9775-2a29b828c45e","from":"959eb2c3-3d92-4ca1-8005-1d9caab972a5","to":"31314092-4456-43f8-89dd-0421194bc821","start_timers":["702cd17f-ac40-429e-93d4-ce69c8eb8040"],"blocking_timers":["b27ef8c9-c440-44cb-8deb-f7f85f491a92"],"transition_duration":300000,"abnormal_condition_only":false},{"id":"8d3bdeae-5052-4579-a62b-a36b427df63a","from":"31314092-4456-43f8-89dd-0421194bc821","to":"959eb2c3-3d92-4ca1-8005-1d9caab972a5","start_timers":["b27ef8c9-c440-44cb-8deb-f7f85f491a92"],"blocking_timers":["702cd17f-ac40-429e-93d4-ce69c8eb8040"],"transition_duration":180000,"abnormal_condition_only":false}],"timers":[{"id":"702cd17f-ac40-429e-93d4-ce69c8eb8040","diagnostic_label":"DHW Minimum On Time","duration":1800000},{"id":"b27ef8c9-c440-44cb-8deb-f7f85f491a92","diagnostic_label":"DHW Minimum Off Time","duration":600000}]}],"storage":{"diagnostic_label":"DHW Buffer","fill_level_label":"Temperature in degrees Celcius","provides_leakage_behaviour":true,"provides_fill_level_target_profile":true,"provides_usage_forecast":true,"fill_level_range":{"start_of_range":10.0,"end_of_range":61.5}}}
+[FLEXMEASURES][2025-10-29 10:15:22,779] DEBUG: ✅ ReceptionStatus: OK
+[FLEXMEASURES][2025-10-29 10:15:22,791] DEBUG: ⚙️ Actuator 189f6a58...: 2 modes, 2 transitions, 2 timers
+[FLEXMEASURES][2025-10-29 10:15:22,797] DEBUG: 💾 Storage: 10.0-61.5 Temperature in degrees Celcius
+[FLEXMEASURES][2025-10-29 10:15:22,798] INFO: 📋 SystemDescription: 1 actuator(s)
+[FLEXMEASURES][2025-10-29 10:15:22,799] DEBUG: ⏳ Missing actuator status for: 189f6a58-e78a-4c3f-bcda-6cd151342285
+[FLEXMEASURES][2025-10-29 10:15:22,799] DEBUG: 📊 Device readiness: ✅ SystemDescription | ❌ FillLevelTargetProfile | ❌ StorageStatus | ❌ ActuatorStatus (0/1 received)
+[FLEXMEASURES][2025-10-29 10:15:22,799] INFO: ⏳ Waiting for: FillLevelTargetProfile, StorageStatus, ActuatorStatus
+[FLEXMEASURES][2025-10-29 10:15:22,863] INFO: 📥 FRBC.LeakageBehaviour
+[FLEXMEASURES][2025-10-29 10:15:22,865] DEBUG: {"message_type":"FRBC.LeakageBehaviour","message_id":"49a61e0c-9a09-4902-b5a0-61a1f83240a3","valid_from":"2025-10-29T10:15:03.117182Z","elements":[{"fill_level_range":{"start_of_range":10.0,"end_of_range":61.5},"leakage_rate":0.00004464134898917508}]}
+[FLEXMEASURES][2025-10-29 10:15:22,865] DEBUG: ✅ ReceptionStatus: OK
+[FLEXMEASURES][2025-10-29 10:15:22,867] INFO: 🔋 FRBC.StorageStatus
+[FLEXMEASURES][2025-10-29 10:15:22,868] DEBUG: {"message_type":"FRBC.StorageStatus","message_id":"4edd0d97-e461-41ec-acd2-d4f0da6f09d8","present_fill_level":24.433}
+[FLEXMEASURES][2025-10-29 10:15:22,868] DEBUG: ✅ ReceptionStatus: OK
+/usr/local/lib/python3.10/dist-packages/timely_beliefs/beliefs/classes.py:633: SAWarning: Object of type not in session, add operation along 'Sensor.beliefs' will not proceed (This warning originated from the Session 'autoflush' process, which was invoked automatically in response to a user-initiated operation.)
+ df = pd.DataFrame(session.execute(q))
+/usr/local/lib/python3.10/dist-packages/timely_beliefs/beliefs/classes.py:633: SAWarning: Object of type not in session, add operation along 'DataSource.beliefs' will not proceed (This warning originated from the Session 'autoflush' process, which was invoked automatically in response to a user-initiated operation.)
+ df = pd.DataFrame(session.execute(q))
+[FLEXMEASURES][2025-10-29 10:15:22,989] INFO: SAVING TO DB...
+/usr/local/lib/python3.10/dist-packages/timely_beliefs/beliefs/classes.py:332: SAWarning: Object of type not in session, add operation along 'Sensor.beliefs' will not proceed (This warning originated from the Session 'autoflush' process, which was invoked automatically in response to a user-initiated operation.)
+ session.execute(smt)
+/usr/local/lib/python3.10/dist-packages/timely_beliefs/beliefs/classes.py:332: SAWarning: Object of type not in session, add operation along 'DataSource.beliefs' will not proceed (This warning originated from the Session 'autoflush' process, which was invoked automatically in response to a user-initiated operation.)
+ session.execute(smt)
+[FLEXMEASURES][2025-10-29 10:15:23,015] INFO: 🔋 StorageStatus: 24.4%
+[FLEXMEASURES][2025-10-29 10:15:23,015] DEBUG: 📊 Device readiness: ✅ SystemDescription | ❌ FillLevelTargetProfile | ✅ StorageStatus | ✅ ActuatorStatus (all 1 received)
+[FLEXMEASURES][2025-10-29 10:15:23,016] INFO: ⏳ Waiting for: FillLevelTargetProfile
+[FLEXMEASURES][2025-10-29 10:15:26,100] INFO: 📊 InstructionStatusUpdate
+[FLEXMEASURES][2025-10-29 10:15:26,102] DEBUG: {"message_type":"InstructionStatusUpdate","message_id":"0f7e0a49-a9c8-43c3-89b9-4e8434d52dd0","instruction_id":"25f18935-3bb7-4355-85db-36b389fb63f3","status_type":"ACCEPTED","timestamp":"2025-10-29T10:15:06.243494Z"}
+[FLEXMEASURES][2025-10-29 10:15:26,102] DEBUG: ✅ ReceptionStatus: OK
+[FLEXMEASURES][2025-10-29 10:15:26,103] INFO: ✅ Instruction 25f18935... → ACCEPTED
+[FLEXMEASURES][2025-10-29 10:15:26,104] DEBUG: 📋 Full instruction ID: 25f18935-3bb7-4355-85db-36b389fb63f3
+[FLEXMEASURES][2025-10-29 10:15:26,105] INFO: 📊 InstructionStatusUpdate
+[FLEXMEASURES][2025-10-29 10:15:26,106] DEBUG: {"message_type":"InstructionStatusUpdate","message_id":"d7c7c494-67e7-48b5-8866-afde8e70a3db","instruction_id":"e70f62ee-8585-4bb8-9a96-a2553d2c0c8d","status_type":"STARTED","timestamp":"2025-10-29T10:15:08.583918Z"}
+[FLEXMEASURES][2025-10-29 10:15:26,106] DEBUG: ✅ ReceptionStatus: OK
+[FLEXMEASURES][2025-10-29 10:15:26,106] INFO: ▶️ Instruction e70f62ee... → STARTED
+[FLEXMEASURES][2025-10-29 10:15:26,106] DEBUG: 📋 Full instruction ID: e70f62ee-8585-4bb8-9a96-a2553d2c0c8d
+[FLEXMEASURES][2025-10-29 10:15:26,107] DEBUG: 🗑️ Removed e70f62ee... from memory
+[FLEXMEASURES][2025-10-29 10:16:22,819] INFO: ⚙️ FRBC.ActuatorStatus
+[FLEXMEASURES][2025-10-29 10:16:22,820] DEBUG: ✅ ReceptionStatus: OK
+[FLEXMEASURES][2025-10-29 10:16:22,832] DEBUG: ⚙️ ActuatorStatus: factor=1.0
+[FLEXMEASURES][2025-10-29 10:16:22,833] DEBUG: 📊 Device readiness: ✅ SystemDescription | ❌ FillLevelTargetProfile | ❌ StorageStatus | ✅ ActuatorStatus (all 1 received)
+[FLEXMEASURES][2025-10-29 10:16:22,833] INFO: ⏳ Waiting for: FillLevelTargetProfile, StorageStatus
+[FLEXMEASURES][2025-10-29 10:16:22,836] INFO: ⚙️ FRBC.ActuatorStatus
+[FLEXMEASURES][2025-10-29 10:16:22,837] DEBUG: ✅ ReceptionStatus: OK
+[FLEXMEASURES][2025-10-29 10:16:22,854] DEBUG: ⚙️ ActuatorStatus: factor=1.0
+[FLEXMEASURES][2025-10-29 10:16:22,854] DEBUG: 📊 Device readiness: ✅ SystemDescription | ❌ FillLevelTargetProfile | ❌ StorageStatus | ✅ ActuatorStatus (all 1 received)
+[FLEXMEASURES][2025-10-29 10:16:22,855] INFO: ⏳ Waiting for: FillLevelTargetProfile, StorageStatus
+[FLEXMEASURES][2025-10-29 10:16:22,920] INFO: 🎯 FRBC.FillLevelTargetProfile
+[FLEXMEASURES][2025-10-29 10:16:22,920] DEBUG: {"message_type":"FRBC.FillLevelTargetProfile","message_id":"0f7c701c-2d9f-44f0-8a4f-942afdf8d279","start_time":"2025-10-28T23:00:00Z","elements":[{"duration":23400000,"fill_level_range":{"start_of_range":10.0,"end_of_range":59.0}},{"duration":5400000,"fill_level_range":{"start_of_range":55.5,"end_of_range":59.0}},{"duration":32400000,"fill_level_range":{"start_of_range":10.0,"end_of_range":59.0}},{"duration":10800000,"fill_level_range":{"start_of_range":55.5,"end_of_range":59.0}},{"duration":14400000,"fill_level_range":{"start_of_range":10.0,"end_of_range":59.0}},{"duration":23400000,"fill_level_range":{"start_of_range":10.0,"end_of_range":59.0}},{"duration":5400000,"fill_level_range":{"start_of_range":55.5,"end_of_range":59.0}},{"duration":32400000,"fill_level_range":{"start_of_range":10.0,"end_of_range":59.0}},{"duration":10800000,"fill_level_range":{"start_of_range":55.5,"end_of_range":59.0}},{"duration":14400000,"fill_level_range":{"start_of_range":10.0,"end_of_range":59.0}}]}
+[FLEXMEASURES][2025-10-29 10:16:22,921] DEBUG: ✅ ReceptionStatus: OK
+[FLEXMEASURES][2025-10-29 10:16:22,938] DEBUG: 🎯 Start: 23:00:00
+[FLEXMEASURES][2025-10-29 10:16:22,938] INFO: 🎯 TargetProfile: 10 element(s)
+[FLEXMEASURES][2025-10-29 10:16:22,938] DEBUG: 📊 Device readiness: ✅ SystemDescription | ✅ FillLevelTargetProfile | ✅ StorageStatus | ✅ ActuatorStatus (all 1 received)
+[FLEXMEASURES][2025-10-29 10:16:22,940] INFO: 🎯 Generating instructions for c88e6893...
+[FLEXMEASURES][2025-10-29 10:16:22,941] DEBUG: 🔧 Creating device state for c88e6893...
+[FLEXMEASURES][2025-10-29 10:16:22,941] DEBUG: ✅ Device state ready: c88e6893...
+[FLEXMEASURES][2025-10-29 10:16:22,942] INFO: 🧮 S2FlaskScheduler started
+[FLEXMEASURES][2025-10-29 10:16:22,943] DEBUG: 🔧 Creating device state for c88e6893...
+[FLEXMEASURES][2025-10-29 10:16:22,943] DEBUG: ✅ Device state ready: c88e6893...
+[FLEXMEASURES][2025-10-29 10:16:23,026] DEBUG: Forward filling 135 missing prices
+[FLEXMEASURES][2025-10-29 10:16:23,027] DEBUG: 📊 Cluster: 1 device(s) in state
+[FLEXMEASURES][2025-10-29 10:16:23,027] INFO: Planning service initialized
+[FLEXMEASURES][2025-10-29 10:16:23,028] DEBUG: ⚙️ Planning service initialized
+[FLEXMEASURES][2025-10-29 10:16:23,028] INFO: 📊 Planning: 24.0h window (10:15 → 10:15)
+[FLEXMEASURES][2025-10-29 10:16:23,028] DEBUG: ⏱️ Resolution: 0:05:00, Belief time: 10:25:00
+[FLEXMEASURES][2025-10-29 10:16:23,028] DEBUG: 🧮 Running optimization algorithm...
+[FLEXMEASURES][2025-10-29 10:16:23,028] WARNING: CongestionPoint without target! CongestionPoint: . Generating empty target.
+[FLEXMEASURES][2025-10-29 10:16:23,029] DEBUG: S2 FRBC planner created!
+Current planning is within the congestion target range. Returning it.
+difference_profile has no Joule elements, using the target directly
+Improving------------------------->
+diff_to_global_target has no Joule elements, using the target directly
+CP '': Selected best controller 'FRBC Device c88e6893-f2f2-4125-893f-5a3316505152' with improvement of 0.
+Root controller: selected best controller 'FRBC Device c88e6893-f2f2-4125-893f-5a3316505152' with global energy impr 0, cost impr [0.], CP impr 0.0. Accepted 1 proposal(s). Iteration 1.
+Optimizing priority class 1 was done after 1 iterations.
+[FLEXMEASURES][2025-10-29 10:16:23,711] DEBUG: {'device_id': 'c88e6893-f2f2-4125-893f-5a3316505152', 'device_name': 'FRBC Device c88e6893-f2f2-4125-893f-5a3316505152', 'connection_id': 'c88e6893-f2f2-4125-893f-5a3316505152_connection', 'energy_profile': , 'fill_level_profile': , 'instruction_profile': }
+[FLEXMEASURES][2025-10-29 10:16:23,719] INFO: Generated new plan in 691.228 ms
+[FLEXMEASURES][2025-10-29 10:16:23,720] DEBUG: 🔄 Converting cluster plan to instructions...
+[FLEXMEASURES][2025-10-29 10:16:23,720] DEBUG: 🔧 Device c88e6893...: converting 288 FRBC instructions
+[FLEXMEASURES][2025-10-29 10:16:23,720] INFO: ✅ Generated 288 FRBC instruction(s)
+[FLEXMEASURES][2025-10-29 10:16:23,721] DEBUG: 📊 Mode 31314092...: 6 instruction(s)
+[FLEXMEASURES][2025-10-29 10:16:23,721] DEBUG: 📊 Mode 959eb2c3...: 282 instruction(s)
+[FLEXMEASURES][2025-10-29 10:16:23,721] DEBUG: 📊 Processing energy profiles for 1 device(s)
+[FLEXMEASURES][2025-10-29 10:16:23,721] DEBUG: 💡 Device c88e6893...: 288 energy values
+[FLEXMEASURES][2025-10-29 10:16:23,722] INFO: 🔽 Filtered: 288 → 2 instructions (skipped 286 duplicate modes)
+[FLEXMEASURES][2025-10-29 10:16:23,722] INFO: 📤 Sending 2 instruction(s):
+[FLEXMEASURES][2025-10-29 10:16:23,723] INFO: 1. 286cc6a8... | mode: 31314092... | factor: 0.00 | actuator: 189f6a58... | exec: 10:15:00
+[FLEXMEASURES][2025-10-29 10:16:23,723] DEBUG: 📋 Full instruction ID: 286cc6a8-31a1-42b1-9f1a-4216cfd853d3
+[FLEXMEASURES][2025-10-29 10:16:23,723] DEBUG: 🔧 Full operation mode ID: 31314092-4456-43f8-89dd-0421194bc821
+[FLEXMEASURES][2025-10-29 10:16:23,723] DEBUG: ⚙️ Full actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+[FLEXMEASURES][2025-10-29 10:16:23,723] INFO: 2. 5e1dd26b... | mode: 959eb2c3... | factor: 0.00 | actuator: 189f6a58... | exec: 10:45:00
+[FLEXMEASURES][2025-10-29 10:16:23,724] DEBUG: 📋 Full instruction ID: 5e1dd26b-0678-4196-bdb5-377eb8d9a164
+[FLEXMEASURES][2025-10-29 10:16:23,724] DEBUG: 🔧 Full operation mode ID: 959eb2c3-3d92-4ca1-8005-1d9caab972a5
+[FLEXMEASURES][2025-10-29 10:16:23,724] DEBUG: ⚙️ Full actuator ID: 189f6a58-e78a-4c3f-bcda-6cd151342285
+[FLEXMEASURES][2025-10-29 10:16:23,724] DEBUG: 💾 Saving 288 energy values for device c88e6893... (J)
+[FLEXMEASURES][2025-10-29 10:16:24,018] INFO: SAVING TO DB...
+/usr/local/lib/python3.10/dist-packages/timely_beliefs/beliefs/classes.py:332: SAWarning: Object of type not in session, add operation along 'Sensor.beliefs' will not proceed (This warning originated from the Session 'autoflush' process, which was invoked automatically in response to a user-initiated operation.)
+ session.execute(smt)
+/usr/local/lib/python3.10/dist-packages/timely_beliefs/beliefs/classes.py:332: SAWarning: Object of type not in session, add operation along 'DataSource.beliefs' will not proceed (This warning originated from the Session 'autoflush' process, which was invoked automatically in response to a user-initiated operation.)
+ session.execute(smt)
+[FLEXMEASURES][2025-10-29 10:16:24,038] INFO: 💾 Saved energy data for 1 device(s)
+[FLEXMEASURES][2025-10-29 10:16:24,052] INFO: 📥 FRBC.TimerStatus
+[FLEXMEASURES][2025-10-29 10:16:24,053] DEBUG: {"message_type":"FRBC.TimerStatus","message_id":"9eb39270-1440-47f7-b5d0-f0e15fda5be5","timer_id":"702cd17f-ac40-429e-93d4-ce69c8eb8040","actuator_id":"189f6a58-e78a-4c3f-bcda-6cd151342285","finished_at":"2025-10-29T10:00:47.087326Z"}
+[FLEXMEASURES][2025-10-29 10:16:24,053] DEBUG: ✅ ReceptionStatus: OK
+[FLEXMEASURES][2025-10-29 10:16:24,055] INFO: 📥 ReceptionStatus
+[FLEXMEASURES][2025-10-29 10:16:24,056] DEBUG: {"message_type":"ReceptionStatus","subject_message_id":"286cc6a8-31a1-42b1-9f1a-4216cfd853d3","status":"OK","diagnostic_label":"Processed okay."}
+[FLEXMEASURES][2025-10-29 10:16:24,056] DEBUG: {"message_type":"ReceptionStatus","subject_message_id":"286cc6a8-31a1-42b1-9f1a-4216cfd853d3","status":"OK","diagnostic_label":"Processed okay."}
+[FLEXMEASURES][2025-10-29 10:16:24,057] INFO: 📥 ReceptionStatus
+[FLEXMEASURES][2025-10-29 10:16:24,058] DEBUG: {"message_type":"ReceptionStatus","subject_message_id":"5e1dd26b-0678-4196-bdb5-377eb8d9a164","status":"OK","diagnostic_label":"Processed okay."}
diff --git a/flexmeasures_s2/profile_steering/planning_service_impl.py b/flexmeasures_s2/profile_steering/planning_service_impl.py
new file mode 100644
index 0000000..5c35318
--- /dev/null
+++ b/flexmeasures_s2/profile_steering/planning_service_impl.py
@@ -0,0 +1,271 @@
+from datetime import datetime
+from typing import Any
+
+# Core profile steering imports
+from flexmeasures_s2.profile_steering.root_planner import RootPlanner
+from flexmeasures_s2.profile_steering.congestion_point_planner import (
+ CongestionPointPlanner,
+)
+
+# Common data types
+from flexmeasures_s2.profile_steering.common.joule_range_profile import (
+ JouleRangeProfile,
+)
+
+# Import from common data structures to avoid circular imports
+from flexmeasures_s2.profile_steering.common_data_structures import (
+ ClusterState,
+)
+
+# Import cluster related classes
+from flexmeasures_s2.profile_steering.cluster_plan import ClusterPlan, ClusterPlanData
+from flexmeasures_s2.profile_steering.cluster_target import ClusterTarget
+
+# Device planner imports
+from flexmeasures_s2.profile_steering.device_planner.frbc.s2_frbc_device_planner import (
+ S2FrbcDevicePlanner,
+)
+from flexmeasures_s2.profile_steering.device_planner.frbc.s2_frbc_device_state import (
+ S2FrbcDeviceState,
+)
+from flexmeasures_s2.profile_steering.device_planner.nocontrol.s2_nocontrol_device_planner import (
+ S2NoControlDevicePlanner,
+)
+from flexmeasures_s2.profile_steering.device_planner.nocontrol.s2_nocontrol_device_state import (
+ S2NoControlDeviceState,
+)
+
+# Logger setup
+import logging
+
+logger = logging.getLogger(__name__)
+
+
+class PlanningServiceConfig:
+ """Configuration for the planning service."""
+
+ def __init__(
+ self,
+ energy_improvement_criterion: float = 0.01,
+ cost_improvement_criterion: float = 0.01,
+ congestion_retry_iterations: int = 5,
+ multithreaded: bool = False,
+ ):
+ self._energy_improvement_criterion = energy_improvement_criterion
+ self._cost_improvement_criterion = cost_improvement_criterion
+ self._congestion_retry_iterations = congestion_retry_iterations
+ self._multithreaded = multithreaded
+
+ def energy_improvement_criterion(self) -> float:
+ return self._energy_improvement_criterion
+
+ def cost_improvement_criterion(self) -> float:
+ return self._cost_improvement_criterion
+
+ def congestion_retry_iterations(self) -> int:
+ return self._congestion_retry_iterations
+
+ def multithreaded(self) -> bool:
+ return self._multithreaded
+
+
+class PlanningService:
+ """Interface for planning services."""
+
+ def plan(
+ self,
+ state: ClusterState,
+ target: ClusterTarget,
+ planning_window: int,
+ reason: str,
+ plan_due_by_date: datetime,
+ optimize_for_target: bool,
+ max_priority_class: int,
+ ) -> ClusterPlan:
+ """Create a plan for the cluster."""
+ raise NotImplementedError("Subclasses must implement this method")
+
+
+class PlanningServiceImpl(PlanningService):
+ """Implementation of the planning service."""
+
+ DEFAULT_CONGESTION_POINT = ""
+
+ def __init__(self, config: PlanningServiceConfig, context: Any = None):
+ """Initialize the planning service.
+
+ Args:
+ config: Configuration for the planning service
+ context: Execution context (used for multithreading)
+ """
+ self.config = config
+ self.context = context
+ # logger.info("Planning service initialized")
+
+ def get_congestion_point(
+ self, cluster_state: ClusterState, connection_id: str
+ ) -> str:
+ """Get the congestion point for a connection ID.
+
+ Args:
+ cluster_state: The state of the cluster
+ connection_id: The connection ID to get the congestion point for
+
+ Returns:
+ The congestion point ID, or DEFAULT_CONGESTION_POINT if none is assigned
+ """
+ congestion_point = cluster_state.get_congestion_point(connection_id)
+ if congestion_point is None:
+ # This can happen if a device has no congestion point assigned yet.
+ # We handle this by giving them all the empty congestion point.
+ return self.DEFAULT_CONGESTION_POINT
+ return congestion_point
+
+ def create_controller_tree(
+ self,
+ cluster_state: ClusterState,
+ target: ClusterTarget,
+ plan_due_by_date: datetime,
+ ) -> RootPlanner:
+ """Create a tree of controllers for planning.
+
+ Args:
+ cluster_state: The state of the cluster
+ target: The target for the cluster
+ plan_due_by_date: The date by which planning must be completed
+
+ Returns:
+ A RootPlanner with appropriate device planners added
+ """
+ # Always accepting all targets is NOT possible if there is an energy target
+ always_accept_all_proposals = not target.contains_energy_target()
+
+ root_planner = RootPlanner(
+ target.get_global_target_profile(),
+ self.config.energy_improvement_criterion(),
+ self.config.cost_improvement_criterion(),
+ always_accept_all_proposals,
+ self.context,
+ )
+
+ for device_id, device_state in cluster_state.get_device_states().items():
+ congestion_point = self.get_congestion_point(
+ cluster_state, device_state.connection_id
+ )
+ cpc = root_planner.get_congestion_point_controller(congestion_point)
+
+ if cpc is None:
+ # Create a new congestion point controller if one doesn't exist yet
+ congestion_point_target = target.get_congestion_point_target(
+ congestion_point
+ )
+ if congestion_point == self.DEFAULT_CONGESTION_POINT:
+ # This is a dummy congestion point. We will give it an empty profile.
+ congestion_point_target = JouleRangeProfile(
+ target.get_global_target_profile().metadata,
+ elements=congestion_point_target.elements, # type: ignore[union-attr]
+ )
+
+ cpc = CongestionPointPlanner(congestion_point, congestion_point_target, self.config.multithreaded()) # type: ignore[arg-type]
+ root_planner.add_congestion_point_controller(cpc)
+
+ # Add the appropriate device planner based on the device state type
+ if isinstance(device_state, S2FrbcDeviceState):
+ # logger.debug("S2 FRBC planner created!")
+ cpc.add_device_controller(
+ S2FrbcDevicePlanner(
+ device_state,
+ target.metadata,
+ plan_due_by_date,
+ congestion_point,
+ )
+ )
+ elif isinstance(device_state, S2NoControlDeviceState):
+ logger.debug("S2 NoControl planner created!")
+ cpc.add_device_controller(
+ S2NoControlDevicePlanner(
+ device_state,
+ target.metadata,
+ congestion_point,
+ )
+ )
+ # Add other device types here as needed
+ else:
+ logger.warning(
+ f"Unknown device! No device planner added for {device_state}"
+ )
+
+ return root_planner
+
+ def plan(
+ self,
+ state: ClusterState,
+ target: ClusterTarget,
+ planning_window: int,
+ reason: str,
+ plan_due_by_date: datetime,
+ optimize_for_target: bool,
+ max_priority_class: int,
+ ) -> ClusterPlan:
+ """Create a plan for the cluster.
+
+ Args:
+ state: The state of the cluster
+ target: The target for the cluster
+ planning_window: The planning window in seconds
+ reason: The reason for planning
+ plan_due_by_date: The date by which planning must be completed
+ optimize_for_target: Whether to optimize for the target
+ max_priority_class: The maximum priority class to optimize
+
+ Returns:
+ A ClusterPlan for the cluster
+ """
+ # start_time = datetime.now()
+
+ # Make sure that all congestion points in the ClusterState have a target:
+ for cp in state.get_congestion_points():
+ congestion_point_target = target.get_congestion_point_target(cp)
+ if congestion_point_target is None and cp is not None:
+ # We don't have a target for the congestion point
+ logger.warning(
+ f"CongestionPoint without target! CongestionPoint: {cp}. Generating empty target."
+ )
+ target.set_congestion_point_target(
+ congestion_point_id=cp,
+ congestion_point_target=JouleRangeProfile(
+ profile_start=target.get_global_target_profile().metadata,
+ ),
+ )
+
+ # Create a tree of controllers and run the planning algorithm
+ root_controller = self.create_controller_tree(state, target, plan_due_by_date)
+
+ try:
+ root_controller.plan(
+ plan_due_by_date,
+ optimize_for_target,
+ max_priority_class,
+ self.config.multithreaded(),
+ )
+
+ # Collect device plans
+ device_plans = []
+ for cpc in root_controller.cp_controllers:
+ for device in cpc.devices:
+ device_plans.append(device.get_device_plan())
+
+ # Create and return the cluster plan
+ plan_data = ClusterPlanData(device_plans, target.metadata) # type: ignore[arg-type]
+ plan = ClusterPlan(state, target, plan_data, reason, plan_due_by_date, None)
+
+ # end_time = datetime.now()
+ # execution_time = (
+ # end_time - start_time
+ # ).total_seconds() * 1000 # Convert to milliseconds
+ # logger.info(f"Generated new plan in {execution_time} ms")
+
+ return plan
+ except Exception as e:
+ logger.error(f"Error during planning: {e}")
+ raise
diff --git a/flexmeasures_s2/profile_steering/root_planner.py b/flexmeasures_s2/profile_steering/root_planner.py
new file mode 100644
index 0000000..980afc7
--- /dev/null
+++ b/flexmeasures_s2/profile_steering/root_planner.py
@@ -0,0 +1,200 @@
+from typing import List, Any, Optional
+from datetime import datetime
+from flexmeasures_s2.profile_steering.common.joule_profile import JouleProfile
+from .congestion_point_planner import CongestionPointPlanner
+from flexmeasures_s2.profile_steering.common.target_profile import TargetProfile
+
+
+class RootPlanner:
+ MAX_ITERATIONS = 1000
+
+ def __init__(
+ self,
+ target: TargetProfile,
+ energy_iteration_criterion: float,
+ cost_iteration_criterion: float,
+ always_accept_all_proposals: bool,
+ context: Any,
+ ):
+ """
+ target: An object that provides a profile.
+ It must support a method get_profile_start(),
+ have attributes timestep_duration and nr_of_timesteps,
+ and (for optimization purposes) support a subtract() method.
+ always_accept_all_proposals: If True, accept all valid proposals (for pure cost targets)
+ context: In a full implementation, this might be an executor or similar. Here it is passed along.
+ """
+ self.context = context
+ self.target = self.remove_null_values(target)
+ self.energy_iteration_criterion = energy_iteration_criterion
+ self.cost_iteration_criterion = cost_iteration_criterion
+ self.always_accept_all_proposals = always_accept_all_proposals
+ # Create an empty JouleProfile.
+ # We assume that target exposes get_profile_start(), timestep_duration and nr_of_timesteps.
+ self.empty_profile = JouleProfile(
+ self.target.metadata.profile_start,
+ self.target.metadata.timestep_duration,
+ elements=[0] * self.target.metadata.nr_of_timesteps,
+ )
+ self.cp_controllers: List[CongestionPointPlanner] = []
+ self.root_ctrl_planning = self.empty_profile
+
+ def remove_null_values(self, target: Any) -> Any:
+ # TODO: Stub: simply return the target.
+ # In a full implementation, you would remove or replace null elements.
+ return target
+
+ def add_congestion_point_controller(self, cpc: CongestionPointPlanner):
+ self.cp_controllers.append(cpc)
+
+ def get_congestion_point_controller(
+ self, cp_id: str
+ ) -> Optional[CongestionPointPlanner]:
+ for cp in self.cp_controllers:
+ if cp.congestion_point_id == cp_id:
+ return cp
+ return None
+
+ def plan( # noqa: C901
+ self,
+ plan_due_by_date: datetime,
+ optimize_for_target: bool,
+ max_priority_class_external: int,
+ multithreaded: bool = False,
+ ):
+ # Compute an initial plan by summing each congestion point's initial planning.
+ self.root_ctrl_planning = self.empty_profile
+ for cpc in self.cp_controllers:
+ initial_plan = cpc.create_initial_planning(plan_due_by_date)
+ self.root_ctrl_planning = self.root_ctrl_planning.add(initial_plan)
+
+ if not optimize_for_target:
+ return
+
+ if not self.cp_controllers:
+ return
+
+ # Determine maximum and minimum priority classes across congestion points.
+ max_priority_class = max(
+ cpc.max_priority_class() for cpc in self.cp_controllers
+ )
+ min_priority_class = min(
+ cpc.min_priority_class() for cpc in self.cp_controllers
+ )
+
+ # Iterate over the priority classes.
+ for priority_class in range(
+ min_priority_class, min(max_priority_class, max_priority_class_external) + 1
+ ):
+ i = 0
+ best_proposal = None
+
+ # Simulate a do-while loop: we run at least once.
+ while True:
+ # Compute the difference profile
+ if self.target.nr_of_joule_target_elements() != 0:
+ difference_profile: TargetProfile = self.target.subtract(
+ self.root_ctrl_planning
+ )
+ else:
+ difference_profile = self.target
+ # print(
+ # "difference_profile has no Joule elements, using the target directly"
+ # )
+
+ best_proposal = None
+
+ # Get proposals from each congestion point controller
+ proposals = []
+ for cpc in self.cp_controllers:
+ # print("Improving------------------------->")
+ try:
+ proposal = cpc.create_improved_planning(
+ difference_profile,
+ priority_class,
+ plan_due_by_date,
+ )
+ if proposal is not None:
+ proposals.append(proposal)
+ if best_proposal is None or proposal.is_preferred_to(
+ best_proposal
+ ):
+ best_proposal = proposal
+ except Exception:
+ # print(f"Error getting proposal from controller: {e}")
+ continue
+
+ if best_proposal is None:
+ # No proposal could be generated; exit inner loop.
+ break
+
+ # Update root controller's own planning
+ if self.always_accept_all_proposals:
+ # If we're NOT optimizing for a Joule target or congestion target, we can
+ # accept all proposals for efficiency
+ for proposal in proposals:
+ self._accept_proposal(proposal)
+ else:
+ # If we're optimizing for a Joule target or congestion target we only accept
+ # one proposal
+ self._accept_proposal(best_proposal)
+
+ i += 1
+ # print(
+ # f"Root controller: selected best controller '{best_proposal.origin.device_name}' with global energy impr {best_proposal.get_global_improvement_value()}, cost impr {best_proposal.get_cost_improvement_value()}, CP impr {best_proposal.get_congestion_improvement_value()}. Accepted {accepted_proposals} proposal(s). Iteration {i}."
+ # )
+
+ # Check stopping criteria: if improvement values are below thresholds or max iterations reached.
+ if self.always_accept_all_proposals:
+ # For pure cost targets, check cost improvement
+ if (
+ best_proposal.get_cost_improvement_value()
+ <= self.cost_iteration_criterion
+ ) or i >= self.MAX_ITERATIONS:
+ break
+ else:
+ # For energy targets, check energy improvement (original behavior)
+ if (
+ best_proposal.get_global_improvement_value()
+ <= self.energy_iteration_criterion
+ ) or i >= self.MAX_ITERATIONS:
+ break
+
+ # print(
+ # f"Optimizing priority class {priority_class} was done after {i} iterations."
+ # )
+ if i >= self.MAX_ITERATIONS:
+ # print(
+ # f"Warning: Optimization stopped due to iteration limit. Priority class: {priority_class}, Iterations: {i}"
+ # )
+ pass
+
+ def _accept_proposal(self, proposal):
+ """Accept a proposal and update the planning accordingly."""
+ # Update the root controller's planning based on the proposal.
+ self.root_ctrl_planning = self.root_ctrl_planning.subtract(proposal.old_plan)
+ self.root_ctrl_planning = self.root_ctrl_planning.add(proposal.proposed_plan)
+
+ # Find the real device object and explicitly update its state.
+ # This is crucial for parallel execution as proposal.origin is a copy.
+ cp_id_of_origin = proposal.origin.congestion_point_id
+ congestion_point_controller = self.get_congestion_point_controller(
+ cp_id_of_origin
+ )
+ if congestion_point_controller is None:
+ raise Exception(
+ f"Could not find CongestionPointController with id {cp_id_of_origin}"
+ )
+
+ real_device = None
+ for dev in congestion_point_controller.devices:
+ if dev.device_id == proposal.origin.device_id:
+ real_device = dev
+ break
+
+ if real_device is None:
+ raise Exception(
+ f"Could not find device with id {proposal.origin.device_id} in CP {cp_id_of_origin}"
+ )
+ plan_to_accept = proposal.origin.get_latest_plan()
+ real_device.set_accepted_plan(plan_to_accept)
diff --git a/flexmeasures_s2/profile_steering/s2_utils/__init__.py b/flexmeasures_s2/profile_steering/s2_utils/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/flexmeasures_s2/profile_steering/s2_utils/number_range_wrapper.py b/flexmeasures_s2/profile_steering/s2_utils/number_range_wrapper.py
new file mode 100644
index 0000000..a6311b2
--- /dev/null
+++ b/flexmeasures_s2/profile_steering/s2_utils/number_range_wrapper.py
@@ -0,0 +1,18 @@
+class NumberRangeWrapper:
+ def __init__(self, start_of_range, end_of_range):
+ self.start_of_range = start_of_range
+ self.end_of_range = end_of_range
+
+ def __eq__(self, other):
+ if not isinstance(other, NumberRangeWrapper):
+ return False
+ return (
+ self.start_of_range == other.start_of_range
+ and self.end_of_range == other.end_of_range
+ )
+
+ def __hash__(self):
+ return hash((self.start_of_range, self.end_of_range))
+
+ def __str__(self):
+ return f"NumberRangeWrapper(startOfRange={self.start_of_range}, endOfRange={self.end_of_range})"
diff --git a/flexmeasures_s2/pytest.ini b/flexmeasures_s2/pytest.ini
new file mode 100644
index 0000000..c0511c0
--- /dev/null
+++ b/flexmeasures_s2/pytest.ini
@@ -0,0 +1,2 @@
+[pytest]
+log_cli = true
diff --git a/flexmeasures_s2/scheduler/s2_frbc_device_state.py b/flexmeasures_s2/scheduler/s2_frbc_device_state.py
new file mode 100644
index 0000000..2a93613
--- /dev/null
+++ b/flexmeasures_s2/scheduler/s2_frbc_device_state.py
@@ -0,0 +1,11 @@
+from s2python.frbc import (
+ FRBCSystemDescription,
+)
+
+from typing import List
+from flexmeasures_s2.profile_steering.common.pydantic_base import FlexMeasuresBaseModel
+
+
+class S2FrbcDeviceState(FlexMeasuresBaseModel):
+
+ system_descriptions: List[FRBCSystemDescription]
diff --git a/flexmeasures_s2/scheduler/scheduler_flask.py b/flexmeasures_s2/scheduler/scheduler_flask.py
new file mode 100644
index 0000000..5018b98
--- /dev/null
+++ b/flexmeasures_s2/scheduler/scheduler_flask.py
@@ -0,0 +1,998 @@
+from datetime import datetime, timezone
+from typing import Any, Dict
+import uuid
+
+from flask import current_app as app
+import pandas as pd
+
+from flexmeasures import Scheduler, Sensor
+from flexmeasures.data import db
+from flexmeasures.data.models.planning.utils import initialize_index
+from flexmeasures.data.queries.utils import simplify_index
+from flexmeasures.utils.flexmeasures_inflection import pluralize
+
+# Profile steering imports
+from flexmeasures_s2.profile_steering.common_data_structures import ClusterState
+from flexmeasures_s2.profile_steering.cluster_plan import ClusterPlan
+from flexmeasures_s2.profile_steering.cluster_target import ClusterTarget
+from flexmeasures_s2.profile_steering.device_planner.frbc.s2_frbc_device_state import (
+ S2FrbcDeviceState,
+)
+from flexmeasures_s2.profile_steering.device_planner.ddbc.s2_ddbc_device_state import (
+ S2DdbcDeviceState,
+)
+from flexmeasures_s2.profile_steering.common.profile_metadata import ProfileMetadata
+from flexmeasures_s2.profile_steering.common.target_profile import TargetProfile
+from s2python.frbc import (
+ FRBCInstruction,
+ FRBCFillLevelTargetProfile,
+ FRBCFillLevelTargetProfileElement,
+ FRBCUsageForecast,
+ FRBCUsageForecastElement,
+)
+from s2python.common import Duration
+
+from flexmeasures_s2.scheduler.schedulers import (
+ PlanningServiceConfig,
+ PlanningServiceImpl,
+)
+
+
+def adjust_fill_level_target_profile_for_current_time(
+ fill_level_target_profile: FRBCFillLevelTargetProfile,
+ current_time: datetime,
+) -> FRBCFillLevelTargetProfile:
+ """
+ Adjust fill level target profile when current time is ahead of profile start_time.
+
+ Removes elements that have already passed and adjusts the start_time to current_time.
+ This keeps fill_level_ranges at fixed positions in time regardless of when scheduling happens.
+
+ Args:
+ fill_level_target_profile: The original fill level target profile
+ current_time: The current scheduling time (when planning actually happens)
+
+ Returns:
+ Adjusted fill level target profile with start_time set to current_time
+ """
+ if fill_level_target_profile is None:
+ return None
+
+ profile_start = fill_level_target_profile.start_time
+ if isinstance(profile_start, datetime):
+ profile_start = (
+ profile_start.replace(tzinfo=timezone.utc)
+ if profile_start.tzinfo is None
+ else profile_start
+ )
+ else:
+ profile_start = profile_start.astimezone(timezone.utc)
+
+ current_time = (
+ current_time.replace(tzinfo=timezone.utc)
+ if current_time.tzinfo is None
+ else current_time
+ )
+
+ # If current time is not ahead of profile start, just update start_time
+ if current_time <= profile_start:
+ return fill_level_target_profile.model_copy(update={"start_time": current_time})
+
+ # Calculate elapsed time since profile start
+ elapsed_time_ms = int((current_time - profile_start).total_seconds() * 1000)
+
+ # Helper function to extract duration in milliseconds
+ def get_duration_ms(elem):
+ duration = elem.duration
+ if isinstance(duration, (int, float)):
+ return int(duration)
+ elif hasattr(duration, "root"):
+ return int(duration.root)
+ else:
+ return int(duration)
+
+ # Find which elements have passed and which we're currently in
+ remaining_elements = []
+ cumulative_duration_ms = 0
+
+ for element in fill_level_target_profile.elements:
+ element_duration_ms = get_duration_ms(element)
+ element_end_ms = cumulative_duration_ms + element_duration_ms
+
+ if element_end_ms <= elapsed_time_ms:
+ # This element has completely passed, skip it
+ cumulative_duration_ms = element_end_ms
+ continue
+ elif cumulative_duration_ms < elapsed_time_ms:
+ # We're in the middle of this element, truncate it
+ remaining_duration_ms = element_end_ms - elapsed_time_ms
+ if remaining_duration_ms > 0:
+ remaining_elements.append(
+ FRBCFillLevelTargetProfileElement(
+ duration=Duration(root=remaining_duration_ms),
+ fill_level_range=element.fill_level_range,
+ )
+ )
+ cumulative_duration_ms = element_end_ms
+ else:
+ # This element is in the future, keep it as-is
+ remaining_elements.append(element)
+ cumulative_duration_ms = element_end_ms
+
+ # Create adjusted profile with current_time as start_time
+ adjusted_profile = FRBCFillLevelTargetProfile(
+ message_id=str(uuid.uuid4()),
+ start_time=current_time,
+ elements=remaining_elements,
+ )
+
+ return adjusted_profile
+
+
+def adjust_usage_forecast_for_current_time(
+ usage_forecast: FRBCUsageForecast,
+ current_time: datetime,
+) -> FRBCUsageForecast:
+ """
+ Adjust usage forecast when current time is ahead of forecast start_time.
+
+ Removes elements that have already passed and adjusts the start_time to current_time.
+ This keeps usage_rate values at fixed positions in time regardless of when scheduling happens.
+
+ Args:
+ usage_forecast: The original usage forecast
+ current_time: The current scheduling time (when planning actually happens)
+
+ Returns:
+ Adjusted usage forecast with start_time set to current_time
+ """
+ if usage_forecast is None:
+ return None
+
+ forecast_start = usage_forecast.start_time
+ if isinstance(forecast_start, datetime):
+ forecast_start = (
+ forecast_start.replace(tzinfo=timezone.utc)
+ if forecast_start.tzinfo is None
+ else forecast_start
+ )
+ else:
+ forecast_start = forecast_start.astimezone(timezone.utc)
+
+ current_time = (
+ current_time.replace(tzinfo=timezone.utc)
+ if current_time.tzinfo is None
+ else current_time
+ )
+
+ # If current time is not ahead of forecast start, just update start_time
+ if current_time <= forecast_start:
+ return usage_forecast.model_copy(update={"start_time": current_time})
+
+ # Calculate elapsed time since forecast start
+ elapsed_time_ms = int((current_time - forecast_start).total_seconds() * 1000)
+
+ # Helper function to extract duration in milliseconds
+ def get_duration_ms(elem):
+ duration = elem.duration
+ if isinstance(duration, (int, float)):
+ return int(duration)
+ elif hasattr(duration, "root"):
+ return int(duration.root)
+ else:
+ return int(duration)
+
+ # Find which elements have passed and which we're currently in
+ remaining_elements = []
+ cumulative_duration_ms = 0
+
+ for element in usage_forecast.elements:
+ element_duration_ms = get_duration_ms(element)
+ element_end_ms = cumulative_duration_ms + element_duration_ms
+
+ if element_end_ms <= elapsed_time_ms:
+ # This element has completely passed, skip it
+ cumulative_duration_ms = element_end_ms
+ continue
+ elif cumulative_duration_ms < elapsed_time_ms:
+ # We're in the middle of this element, truncate it
+ remaining_duration_ms = element_end_ms - elapsed_time_ms
+ if remaining_duration_ms > 0:
+ remaining_elements.append(
+ FRBCUsageForecastElement(
+ duration=Duration(root=remaining_duration_ms),
+ usage_rate_expected=element.usage_rate_expected,
+ usage_rate_upper_limit=element.usage_rate_upper_limit,
+ usage_rate_upper_95PPR=element.usage_rate_upper_95PPR,
+ usage_rate_upper_68PPR=element.usage_rate_upper_68PPR,
+ usage_rate_lower_68PPR=element.usage_rate_lower_68PPR,
+ usage_rate_lower_95PPR=element.usage_rate_lower_95PPR,
+ usage_rate_lower_limit=element.usage_rate_lower_limit,
+ )
+ )
+ cumulative_duration_ms = element_end_ms
+ else:
+ # This element is in the future, keep it as-is
+ remaining_elements.append(element)
+ cumulative_duration_ms = element_end_ms
+
+ # Create adjusted forecast with current_time as start_time
+ adjusted_forecast = FRBCUsageForecast(
+ message_id=str(uuid.uuid4()),
+ start_time=current_time,
+ elements=remaining_elements,
+ )
+
+ return adjusted_forecast
+
+
+class S2FlaskScheduler(Scheduler):
+ """
+ S2FlaskScheduler integrates the profile steering planning service with FlexMeasures.
+
+ This is the Flask-compatible version that uses app.logger and app.config
+ for integration within the FlexMeasures application context.
+
+ This scheduler uses the PlanningServiceImpl to create optimal energy profiles
+ for devices in a cluster based on targets and constraints.
+ """
+
+ __author__ = "TNO"
+ __version__ = "1"
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.planning_service = None
+ self.config_deserialized = False
+ self.frbc_device_data = None # Store FRBC device data from WebSocket
+ self.ddbc_device_data = None # Store DDBC device data from WebSocket
+
+ def compute(self, *args, **kwargs):
+ """
+ Compute the optimal energy profile using the S2 profile steering algorithm.
+
+ Returns:
+ List: List containing FRBCInstruction objects and metadata dicts
+ """
+ if not self.config_deserialized:
+ self.deserialize_config()
+
+ try:
+
+ app.logger.info("🧮 S2FlaskScheduler started")
+
+ # Check if we have either FRBC or DDBC device data
+ has_frbc = (
+ hasattr(self, "frbc_device_data") and self.frbc_device_data is not None
+ )
+ has_ddbc = (
+ hasattr(self, "ddbc_device_data") and self.ddbc_device_data is not None
+ )
+
+ if not has_frbc and not has_ddbc:
+ app.logger.error("❌ No device data available (neither FRBC nor DDBC)")
+ return []
+
+ # Create device states from available data
+ device_states, cluster_target = self._create_cluster_state_and_target()
+
+ if not device_states:
+ app.logger.warning("⚠️ No device states created")
+ return []
+
+ # Create cluster state
+ cluster_state = ClusterState(
+ timestamp=datetime.now(),
+ device_states=device_states,
+ congestion_points_by_connection_id={
+ device_id: "" for device_id in device_states.keys()
+ },
+ )
+
+ app.logger.debug(f"📊 Cluster: {len(device_states)} device(s) in state")
+
+ # Get planning service
+ planning_service = self._get_planning_service()
+
+ # Calculate planning window
+ planning_window_seconds = (self.end - self.start).total_seconds()
+ planning_hours = planning_window_seconds / 3600
+
+ app.logger.info(
+ f"📊 Planning: {planning_hours:.1f}h window ({self.start.strftime('%H:%M')} → {self.end.strftime('%H:%M')})"
+ )
+ app.logger.debug(
+ f" ⏱️ Resolution: {self.resolution}, Belief time: {self.belief_time.strftime('%H:%M:%S') if self.belief_time else 'N/A'}"
+ )
+
+ # Generate plan
+ app.logger.debug("🧮 Running optimization algorithm...")
+ cluster_plan = planning_service.plan(
+ state=cluster_state,
+ target=cluster_target,
+ planning_window=planning_window_seconds,
+ reason="S2 WebSocket planning request",
+ plan_due_by_date=self.start,
+ optimize_for_target=True,
+ max_priority_class=1,
+ )
+
+ if cluster_plan is None:
+ app.logger.error("❌ Planning failed: returned None")
+ return []
+
+ # Convert cluster plan to instructions
+ app.logger.debug("🔄 Converting cluster plan to instructions...")
+ instructions = self._convert_cluster_plan_to_instructions(cluster_plan)
+ n_frbc_instructions = sum(
+ 1 for i in instructions if isinstance(i, FRBCInstruction)
+ )
+
+ # Log instruction details
+ if n_frbc_instructions > 0:
+ frbc_only = [i for i in instructions if isinstance(i, FRBCInstruction)]
+ # Group by operation mode
+ mode_counts = {}
+ for instr in frbc_only:
+ mode_id = str(instr.operation_mode)[:8]
+ mode_counts[mode_id] = mode_counts.get(mode_id, 0) + 1
+
+ app.logger.info(
+ f"✅ Generated {n_frbc_instructions} FRBC instruction(s)"
+ )
+ for mode_id, count in mode_counts.items():
+ app.logger.debug(f" 📊 Mode {mode_id}...: {count} instruction(s)")
+
+ # Add energy data entry for potential storage
+ try:
+ device_plans = cluster_plan.get_plan_data().get_device_plans()
+ n_devices = len(device_plans) if device_plans else 0
+ if n_devices > 0:
+ app.logger.debug(
+ f"📊 Processing energy profiles for {n_devices} device(s)"
+ )
+
+ for device_plan in device_plans:
+ n_energy_values = (
+ len(device_plan.energy_profile.elements)
+ if device_plan.energy_profile
+ else 0
+ )
+ app.logger.debug(
+ f" 💡 Device {device_plan.device_id[:8]}...: {n_energy_values} energy values"
+ )
+ instructions.append(
+ {
+ "device": device_plan.device_id,
+ "data": pd.Series(
+ device_plan.energy_profile.elements,
+ index=pd.date_range(
+ self.start,
+ self.end,
+ freq=self.resolution,
+ inclusive="left",
+ ),
+ ),
+ "unit": "J",
+ }
+ )
+ instructions.append(
+ {
+ "fill level": device_plan.device_id,
+ "data": pd.Series(
+ device_plan.fill_level_profile.elements,
+ index=pd.date_range(
+ self.start,
+ self.end,
+ freq=self.resolution,
+ inclusive="left",
+ ),
+ ),
+ "unit": "",
+ }
+ )
+ except Exception as exc:
+ app.logger.warning(f"⚠️ Energy profile retrieval failed: {str(exc)}")
+
+ return instructions
+
+ except Exception as e:
+ app.logger.error(f"❌ Scheduler error: {e}")
+ import traceback
+
+ app.logger.debug(f"Traceback: {traceback.format_exc()}")
+ return []
+
+ def _get_planning_service(self) -> PlanningServiceImpl:
+ """Get or create the planning service instance."""
+ if not hasattr(self, "planning_service") or self.planning_service is None:
+ config = PlanningServiceConfig(
+ energy_improvement_criterion=10.0,
+ cost_improvement_criterion=1.0,
+ congestion_retry_iterations=10,
+ multithreaded=False,
+ )
+ self.planning_service = PlanningServiceImpl(config)
+ app.logger.debug("⚙️ Planning service initialized")
+ return self.planning_service
+
+ def _create_cluster_state_and_target(self):
+ """Create cluster state and target from FRBC or DDBC device data."""
+ device_states = {}
+
+ # Try to create device states from FRBC data
+ if hasattr(self, "frbc_device_data") and self.frbc_device_data is not None:
+ frbc_states = self.create_device_states_from_frbc_data()
+ device_states.update(frbc_states)
+ app.logger.debug(f"Created {len(frbc_states)} FRBC device state(s)")
+
+ # Try to create device states from DDBC data
+ if hasattr(self, "ddbc_device_data") and self.ddbc_device_data is not None:
+ ddbc_states = self.create_device_states_from_ddbc_data()
+ device_states.update(ddbc_states)
+ app.logger.debug(f"Created {len(ddbc_states)} DDBC device state(s)")
+
+ if not device_states:
+ return device_states, None
+
+ # Create cluster target
+ cluster_target = self._create_cluster_target()
+
+ return device_states, cluster_target
+
+ def _create_cluster_target(self) -> ClusterTarget:
+ """Create cluster target profile."""
+
+ # Calculate profile metadata
+ profile_metadata = ProfileMetadata(
+ profile_start=self.start,
+ timestep_duration=self.resolution,
+ nr_of_timesteps=int((self.end - self.start) / self.resolution),
+ )
+
+ # Get target elements (this could be from sensor data or configuration)
+ target_elements = self._get_target_elements(profile_metadata.nr_of_timesteps)
+
+ # Type ignore: TargetProfile constructor accepts List[int] and converts to JouleElement
+ target_mode = app.config.get("FLEXMEASURES_S2_TARGET_MODE", "costs")
+ if target_mode == "energy":
+ global_target_profile = TargetProfile(
+ profile_start=profile_metadata.profile_start,
+ timestep_duration=profile_metadata.timestep_duration,
+ elements=target_elements, # type: ignore[arg-type]
+ )
+ else: # target_mode == "costs"
+ price_sensor_id = app.config.get("FLEXMEASURES_S2_PRICE_SENSOR", 2)
+ price_sensor = db.session.get(Sensor, price_sensor_id)
+ if price_sensor is None:
+ app.logger.warning("⚠️ No price sensor found, using energy target")
+ global_target_profile = TargetProfile(
+ profile_start=profile_metadata.profile_start,
+ timestep_duration=profile_metadata.timestep_duration,
+ elements=target_elements, # type: ignore[arg-type]
+ )
+ else:
+ # Query tariff data for the planning period
+ tariffs = price_sensor.search_beliefs(
+ event_starts_after=self.start,
+ event_ends_before=self.end,
+ resolution=self.resolution,
+ beliefs_before=self.belief_time,
+ most_recent_beliefs_only=True,
+ )
+
+ if (
+ n_missing_prices := (self.end - self.start) // self.resolution
+ - len(tariffs)
+ ) > 0:
+ tariffs = simplify_index(tariffs)
+ tariffs = tariffs.reindex(
+ initialize_index(
+ start=self.start, end=self.end, resolution=self.resolution
+ )
+ )
+ if n_missing_prices == len(tariffs):
+ app.logger.warning("⚠️ All prices missing, assuming 1 EUR/MWh")
+ tariffs = tariffs.fillna(1)
+ else:
+ app.logger.debug(
+ f"Forward filling {n_missing_prices} missing {pluralize('price', n_missing_prices)}"
+ )
+ tariffs = tariffs.ffill()
+
+ global_target_profile = TargetProfile.from_tariff_values(
+ metadata=profile_metadata,
+ tariff_values=tariffs.values,
+ )
+
+ # Create cluster target
+ cluster_target = ClusterTarget(
+ generated_at=datetime.now(),
+ parent_id=None,
+ generated_by=None,
+ global_target_profile=global_target_profile,
+ congestion_point_targets={},
+ )
+
+ return cluster_target
+
+ def _get_target_elements(self, num_elements: int) -> list[int]:
+ """Get target elements for the planning horizon."""
+ # This could be configured via app.config or calculated based on business logic
+ # For now, use a simple pattern similar to the example
+ target_elements = []
+
+ # Simple pattern: low consumption at night, higher during day
+ for i in range(num_elements):
+ hour_of_day = (i * (self.resolution.total_seconds() / 3600)) % 24
+ if 0 <= hour_of_day < 6: # Night: 0 consumption
+ target_elements.append(0)
+ elif 6 <= hour_of_day < 9: # Morning: moderate consumption
+ target_elements.append(4200000) # 4.2 MJ
+ elif 9 <= hour_of_day < 17: # Day: no specific target
+ target_elements.append(0)
+ elif 17 <= hour_of_day < 21: # Evening: moderate consumption
+ target_elements.append(4200000) # 4.2 MJ
+ else: # Late evening: high consumption
+ target_elements.append(8800000) # 8.8 MJ
+
+ return target_elements
+
+ def _convert_cluster_plan_to_instructions(self, cluster_plan: ClusterPlan) -> list:
+ """Convert cluster plan to FRBC instructions."""
+ instructions = []
+
+ # Get device plans from cluster plan
+ device_plans = cluster_plan.get_plan_data().get_device_plans()
+
+ for device_plan in device_plans:
+ if device_plan is None:
+ continue
+
+ try:
+ # Convert device plan to instructions
+ if device_plan.instruction_profile is None:
+ continue
+
+ device_instructions = device_plan.instruction_profile.elements
+
+ frbc_count = sum(
+ 1 for i in device_instructions if isinstance(i, FRBCInstruction)
+ )
+ app.logger.debug(
+ f"🔧 Device {device_plan.device_id[:8]}...: converting {frbc_count} FRBC instructions"
+ )
+
+ for instruction in device_instructions:
+ if isinstance(instruction, FRBCInstruction):
+ instructions.append(instruction)
+
+ # Add metadata about the plan
+ instructions.append(
+ {
+ "device_id": device_plan.device_id,
+ "plan_type": "FRBC",
+ "num_instructions": len(device_instructions),
+ }
+ )
+
+ except Exception as e:
+ app.logger.error(
+ f"❌ Conversion failed for device {device_plan.device_id[:8] if hasattr(device_plan, 'device_id') else 'unknown'}...: {e}"
+ )
+ continue
+
+ return instructions
+
+ def set_frbc_device_data(self, device_data: Any):
+ """Set FRBC device data for planning."""
+ self.frbc_device_data = device_data
+ if hasattr(device_data, "resource_id"):
+ app.logger.debug(
+ f"FRBC device data set for device {device_data.resource_id}"
+ )
+ elif isinstance(device_data, dict):
+ app.logger.debug(f"FRBC device data set for {len(device_data)} devices")
+ else:
+ app.logger.debug(f"FRBC device data set: {type(device_data)}")
+
+ def create_device_states_from_frbc_data(self) -> Dict[str, Any]:
+ """
+ Create device states from received FRBC data.
+
+ Returns:
+ Dict[str, Any]: Dictionary of device states created from FRBC data
+ """
+ from s2python.common import PowerValue, CommodityQuantity
+
+ if not hasattr(self, "frbc_device_data") or self.frbc_device_data is None:
+ app.logger.warning("⚠️ No FRBC device data")
+ return {}
+
+ device_states = {}
+
+ # Handle single FRBCDeviceData object from WebSocket
+ if hasattr(self.frbc_device_data, "resource_id"):
+ frbc_data = self.frbc_device_data
+ device_id = frbc_data.resource_id or "frbc_device_1"
+
+ try:
+ app.logger.debug(f"🔧 Creating device state for {device_id[:8]}...")
+
+ # Extract information from received FRBC data object
+ system_desc = frbc_data.system_description
+ storage_status = frbc_data.storage_status
+ actuator_statuses = (
+ list(frbc_data.actuator_statuses.values())
+ if hasattr(frbc_data, "actuator_statuses")
+ else []
+ )
+ fill_level_target_profile = (
+ frbc_data.fill_level_target_profile
+ if hasattr(frbc_data, "fill_level_target_profile")
+ else None
+ )
+ usage_forecast = (
+ frbc_data.usage_forecast
+ if hasattr(frbc_data, "usage_forecast")
+ else None
+ )
+ leakage_behaviour = (
+ frbc_data.leakage_behaviour
+ if hasattr(frbc_data, "leakage_behaviour")
+ else None
+ )
+
+ # Adjust fill level target profile for scheduler start time
+ # This removes elements that have already passed and adjusts start_time
+ if fill_level_target_profile:
+ original_start = fill_level_target_profile.start_time
+ n_original = (
+ len(fill_level_target_profile.elements)
+ if fill_level_target_profile.elements
+ else 0
+ )
+ adjusted_profile = (
+ adjust_fill_level_target_profile_for_current_time(
+ fill_level_target_profile, self.start
+ )
+ )
+ if adjusted_profile:
+ fill_level_target_profile = adjusted_profile
+ if original_start != self.start:
+ n_adjusted = (
+ len(adjusted_profile.elements)
+ if adjusted_profile.elements
+ else 0
+ )
+ app.logger.debug(
+ f"Adjusted fill level target profile: "
+ f"{original_start} → {self.start}, "
+ f"elements: {n_original} → {n_adjusted}"
+ )
+
+ # Adjust usage forecast for scheduler start time
+ # This removes elements that have already passed and adjusts start_time
+ if usage_forecast:
+ original_start = usage_forecast.start_time
+ n_original = (
+ len(usage_forecast.elements) if usage_forecast.elements else 0
+ )
+ adjusted_forecast = adjust_usage_forecast_for_current_time(
+ usage_forecast, self.start
+ )
+ if adjusted_forecast:
+ usage_forecast = adjusted_forecast
+ if original_start != self.start:
+ n_adjusted = (
+ len(adjusted_forecast.elements)
+ if adjusted_forecast.elements
+ else 0
+ )
+ app.logger.debug(
+ f"Adjusted usage forecast: "
+ f"{original_start} → {self.start}, "
+ f"elements: {n_original} → {n_adjusted}"
+ )
+
+ # Create device state using the received FRBC data
+ device_state = S2FrbcDeviceState(
+ device_id=device_id,
+ device_name=f"FRBC Device {device_id}",
+ connection_id=f"{device_id}_connection",
+ priority_class=1,
+ timestamp=self.start,
+ energy_in_current_timestep=PowerValue(
+ value=0, commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1
+ ),
+ is_online=True,
+ power_forecast=None,
+ system_descriptions=[system_desc] if system_desc else [],
+ leakage_behaviours=[leakage_behaviour] if leakage_behaviour else [],
+ usage_forecasts=[usage_forecast] if usage_forecast else [],
+ fill_level_target_profiles=[fill_level_target_profile]
+ if fill_level_target_profile
+ else [],
+ computational_parameters=S2FrbcDeviceState.ComputationalParameters(
+ 100, 20
+ ),
+ actuator_statuses=actuator_statuses,
+ storage_status=storage_status, # Single object, not a list
+ )
+ app.logger.debug(f"Storage status used for scheduler: {storage_status}")
+ if storage_status:
+ app.logger.debug(
+ f"Present fill level used for scheduler: {storage_status.present_fill_level}"
+ )
+
+ # Log which profiles are being used
+ if fill_level_target_profile:
+ n_elements = (
+ len(fill_level_target_profile.elements)
+ if fill_level_target_profile.elements
+ else 0
+ )
+ app.logger.debug(
+ f"Fill level target profile: {n_elements} element(s)"
+ )
+ else:
+ app.logger.debug("Fill level target profile: NOT PROVIDED")
+
+ if usage_forecast:
+ n_elements = (
+ len(usage_forecast.elements) if usage_forecast.elements else 0
+ )
+ app.logger.debug(f"Usage forecast: {n_elements} element(s)")
+ else:
+ app.logger.debug("Usage forecast: NOT PROVIDED")
+
+ if leakage_behaviour:
+ n_elements = (
+ len(leakage_behaviour.elements)
+ if leakage_behaviour.elements
+ else 0
+ )
+ app.logger.debug(f"Leakage behaviour: {n_elements} element(s)")
+ else:
+ app.logger.debug("Leakage behaviour: NOT PROVIDED")
+
+ device_states[device_id] = device_state
+ app.logger.debug(f"✅ Device state ready: {device_id[:8]}...")
+
+ except Exception as e:
+ app.logger.error(
+ f"❌ Device state creation failed for {device_id[:8]}...: {e}"
+ )
+
+ # Handle dictionary of device data (backward compatibility)
+ elif isinstance(self.frbc_device_data, dict):
+ for device_id, frbc_data in self.frbc_device_data.items():
+ try:
+ app.logger.debug(f"🔧 Creating device state for {device_id[:8]}...")
+
+ # Extract information from received FRBC data
+ system_desc = frbc_data.get("system_description")
+ storage_status = frbc_data.get("storage_status")
+ actuator_statuses = (
+ list(frbc_data.get("actuator_statuses", {}).values())
+ if "actuator_statuses" in frbc_data
+ else [frbc_data.get("actuator_status")]
+ if frbc_data.get("actuator_status")
+ else []
+ )
+ fill_level_target_profile = frbc_data.get(
+ "fill_level_target_profile"
+ )
+ usage_forecast = frbc_data.get("usage_forecast")
+ leakage_behaviour = frbc_data.get("leakage_behaviour")
+
+ # Adjust fill level target profile for scheduler start time
+ # This removes elements that have already passed and adjusts start_time
+ if fill_level_target_profile and hasattr(
+ fill_level_target_profile, "start_time"
+ ):
+ original_start = fill_level_target_profile.start_time
+ n_original = (
+ len(fill_level_target_profile.elements)
+ if fill_level_target_profile.elements
+ else 0
+ )
+ adjusted_profile = (
+ adjust_fill_level_target_profile_for_current_time(
+ fill_level_target_profile, self.start
+ )
+ )
+ if adjusted_profile:
+ fill_level_target_profile = adjusted_profile
+ if original_start != self.start:
+ n_adjusted = (
+ len(adjusted_profile.elements)
+ if adjusted_profile.elements
+ else 0
+ )
+ app.logger.debug(
+ f"Adjusted fill level target profile: "
+ f"{original_start} → {self.start}, "
+ f"elements: {n_original} → {n_adjusted}"
+ )
+
+ # Adjust usage forecast for scheduler start time
+ # This removes elements that have already passed and adjusts start_time
+ if usage_forecast and hasattr(usage_forecast, "start_time"):
+ original_start = usage_forecast.start_time
+ n_original = (
+ len(usage_forecast.elements)
+ if usage_forecast.elements
+ else 0
+ )
+ adjusted_forecast = adjust_usage_forecast_for_current_time(
+ usage_forecast, self.start
+ )
+ if adjusted_forecast:
+ usage_forecast = adjusted_forecast
+ if original_start != self.start:
+ n_adjusted = (
+ len(adjusted_forecast.elements)
+ if adjusted_forecast.elements
+ else 0
+ )
+ app.logger.debug(
+ f"Adjusted usage forecast: "
+ f"{original_start} → {self.start}, "
+ f"elements: {n_original} → {n_adjusted}"
+ )
+
+ # Create device state using the received FRBC data
+ device_state = S2FrbcDeviceState(
+ device_id=device_id,
+ device_name=f"FRBC Device {device_id}",
+ connection_id=f"{device_id}_connection",
+ priority_class=1,
+ timestamp=self.start,
+ energy_in_current_timestep=PowerValue(
+ value=0,
+ commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1,
+ ),
+ is_online=True,
+ power_forecast=None,
+ system_descriptions=[system_desc] if system_desc else [],
+ leakage_behaviours=[leakage_behaviour]
+ if leakage_behaviour
+ else [],
+ usage_forecasts=[usage_forecast] if usage_forecast else [],
+ fill_level_target_profiles=[fill_level_target_profile]
+ if fill_level_target_profile
+ else [],
+ computational_parameters=S2FrbcDeviceState.ComputationalParameters(
+ 100, 20
+ ),
+ actuator_statuses=actuator_statuses,
+ storage_status=storage_status, # Single object, not a list
+ )
+
+ device_states[device_id] = device_state
+ app.logger.debug(f"✅ Device state ready: {device_id[:8]}...")
+
+ except Exception as e:
+ app.logger.error(
+ f"❌ Device state creation failed for {device_id[:8]}...: {e}"
+ )
+ continue
+ else:
+ app.logger.error(
+ f"❌ Unexpected FRBC data type: {type(self.frbc_device_data)}"
+ )
+
+ return device_states
+
+ def create_device_states_from_ddbc_data(self) -> Dict[str, Any]:
+ """
+ Create device states from received DDBC data.
+
+ Returns:
+ Dict[str, Any]: Dictionary of device states created from DDBC data
+ """
+ if not hasattr(self, "ddbc_device_data") or self.ddbc_device_data is None:
+ app.logger.warning("⚠️ No DDBC device data")
+ return {}
+
+ device_states = {}
+
+ # Handle single DDBCDeviceData object
+ if hasattr(self.ddbc_device_data, "resource_id"):
+ ddbc_data = self.ddbc_device_data
+ device_id = ddbc_data.resource_id or "ddbc_device_1"
+
+ try:
+ app.logger.debug(f"🔧 Creating DDBC device state for {device_id[:8]}...")
+
+ # Extract information from received DDBC data object
+ system_desc = ddbc_data.system_description
+ demand_forecasts = (
+ ddbc_data.demand_forecasts
+ if hasattr(ddbc_data, "demand_forecasts")
+ else []
+ )
+ actuator_statuses = (
+ ddbc_data.actuator_statuses
+ if hasattr(ddbc_data, "actuator_statuses")
+ else {}
+ )
+
+ # Create device state using the received DDBC data
+ device_state = S2DdbcDeviceState(
+ device_id=device_id,
+ device_name=f"DDBC Device {device_id}",
+ connection_id=f"{device_id}_connection",
+ priority_class=0,
+ timestamp=self.start,
+ energy_in_current_timestep=0.0,
+ is_online=True,
+ power_forecast=None,
+ system_descriptions=[system_desc] if system_desc else [],
+ demand_forecasts=demand_forecasts,
+ actuator_statuses=actuator_statuses,
+ gas_price_per_m3=2.0, # €2 per m3 of gas
+ )
+
+ device_states[device_id] = device_state
+ app.logger.debug(f"✅ DDBC device state ready: {device_id[:8]}...")
+
+ except Exception as e:
+ app.logger.error(
+ f"❌ DDBC device state creation failed for {device_id[:8]}...: {e}"
+ )
+ import traceback
+
+ app.logger.debug(traceback.format_exc())
+
+ # Handle dictionary of device data (backward compatibility)
+ elif isinstance(self.ddbc_device_data, dict):
+ for device_id, ddbc_data in self.ddbc_device_data.items():
+ try:
+ app.logger.debug(
+ f"🔧 Creating DDBC device state for {device_id[:8]}..."
+ )
+
+ # Extract information from received DDBC data
+ system_desc = ddbc_data.get("system_description")
+ demand_forecasts = ddbc_data.get("demand_forecasts", [])
+ actuator_statuses = ddbc_data.get("actuator_statuses", {})
+
+ # Create device state using the received DDBC data
+ device_state = S2DdbcDeviceState(
+ device_id=device_id,
+ device_name=f"DDBC Device {device_id}",
+ connection_id=f"{device_id}_connection",
+ priority_class=0,
+ timestamp=self.start,
+ energy_in_current_timestep=0.0,
+ is_online=True,
+ power_forecast=None,
+ system_descriptions=[system_desc] if system_desc else [],
+ demand_forecasts=demand_forecasts,
+ actuator_statuses=actuator_statuses,
+ gas_price_per_m3=2.0, # €2 per m3 of gas
+ )
+
+ device_states[device_id] = device_state
+ app.logger.debug(f"✅ DDBC device state ready: {device_id[:8]}...")
+
+ except Exception as e:
+ app.logger.error(
+ f"❌ DDBC device state creation failed for {device_id[:8]}...: {e}"
+ )
+ continue
+ else:
+ app.logger.error(
+ f"❌ Unexpected DDBC data type: {type(self.ddbc_device_data)}"
+ )
+
+ return device_states
+
+ def deserialize_config(self):
+ """Deserialize the flex_model configuration."""
+ # For now, we use default configuration
+ # This could be extended to read from self.flex_model if needed
+ self.config_deserialized = True
+ app.logger.debug("⚙️ Config deserialized")
diff --git a/flexmeasures_s2/scheduler/schedulers.py b/flexmeasures_s2/scheduler/schedulers.py
index 7ab3080..a2e8bc7 100644
--- a/flexmeasures_s2/scheduler/schedulers.py
+++ b/flexmeasures_s2/scheduler/schedulers.py
@@ -1,24 +1,800 @@
import pandas as pd
+from datetime import datetime
+from typing import Any, Dict, Optional
+import logging
+
from flexmeasures import Scheduler
+# Profile steering imports
+from flexmeasures_s2.profile_steering.root_planner import RootPlanner
+from flexmeasures_s2.profile_steering.congestion_point_planner import (
+ CongestionPointPlanner,
+)
+from flexmeasures_s2.profile_steering.common.joule_range_profile import (
+ JouleRangeProfile,
+)
+from flexmeasures_s2.profile_steering.common_data_structures import (
+ ClusterState,
+)
+from flexmeasures_s2.profile_steering.cluster_plan import ClusterPlan, ClusterPlanData
+from flexmeasures_s2.profile_steering.cluster_target import ClusterTarget
+from flexmeasures_s2.profile_steering.device_planner.frbc.s2_frbc_device_planner import (
+ S2FrbcDevicePlanner,
+)
+from flexmeasures_s2.profile_steering.device_planner.frbc.s2_frbc_device_state import (
+ S2FrbcDeviceState,
+)
+from flexmeasures_s2.profile_steering.device_planner.nocontrol.s2_nocontrol_device_planner import (
+ S2NoControlDevicePlanner,
+)
+from flexmeasures_s2.profile_steering.device_planner.nocontrol.s2_nocontrol_device_state import (
+ S2NoControlDeviceState,
+)
+
+# NoControl planner is on a different branch
+# from flexmeasures_s2.profile_steering.device_planner.nocontrol.s2_nocontrol_device_planner import (
+# S2NoControlDevicePlanner,
+# )
+# from flexmeasures_s2.profile_steering.device_planner.nocontrol.s2_nocontrol_device_state import (
+# S2NoControlDeviceState,
+# )
+from flexmeasures_s2.profile_steering.device_planner.ddbc.s2_ddbc_device_planner import (
+ S2DdbcDevicePlanner,
+)
+from flexmeasures_s2.profile_steering.device_planner.ddbc.s2_ddbc_device_state import (
+ S2DdbcDeviceState,
+)
+
+# Schema imports
+from flexmeasures_s2.scheduler.schemas import S2FlexModelSchema, TNOFlexContextSchema
+
+# Logger setup
+logger = logging.getLogger(__name__)
+
+
+class PlanningServiceConfig:
+ """Configuration for the planning service."""
+
+ def __init__(
+ self,
+ energy_improvement_criterion: float = 0.01,
+ cost_improvement_criterion: float = 0.01,
+ congestion_retry_iterations: int = 5,
+ multithreaded: bool = False,
+ ):
+ self._energy_improvement_criterion = energy_improvement_criterion
+ self._cost_improvement_criterion = cost_improvement_criterion
+ self._congestion_retry_iterations = congestion_retry_iterations
+ self._multithreaded = multithreaded
+
+ def energy_improvement_criterion(self) -> float:
+ return self._energy_improvement_criterion
+
+ def cost_improvement_criterion(self) -> float:
+ return self._cost_improvement_criterion
+
+ def congestion_retry_iterations(self) -> int:
+ return self._congestion_retry_iterations
+
+ def multithreaded(self) -> bool:
+ return self._multithreaded
+
+
+class PlanningService:
+ """Interface for planning services."""
+
+ def plan(
+ self,
+ state: ClusterState,
+ target: ClusterTarget,
+ planning_window: int,
+ reason: str,
+ plan_due_by_date: datetime,
+ optimize_for_target: bool,
+ max_priority_class: int,
+ ) -> ClusterPlan:
+ """Create a plan for the cluster."""
+ raise NotImplementedError("Subclasses must implement this method")
+
+
+class PlanningServiceImpl(PlanningService):
+ """Implementation of the planning service."""
+
+ DEFAULT_CONGESTION_POINT = ""
+
+ def __init__(self, config: PlanningServiceConfig, context: Any = None):
+ """Initialize the planning service.
+
+ Args:
+ config: Configuration for the planning service
+ context: Execution context (used for multithreading)
+ """
+ self.config = config
+ self.context = context
+ logger.info("Planning service initialized")
+
+ def get_congestion_point(
+ self, cluster_state: ClusterState, connection_id: str
+ ) -> str:
+ """Get the congestion point for a connection ID.
+
+ Args:
+ cluster_state: The state of the cluster
+ connection_id: The connection ID to get the congestion point for
+
+ Returns:
+ The congestion point ID, or DEFAULT_CONGESTION_POINT if none is assigned
+ """
+ congestion_point = cluster_state.get_congestion_point(connection_id)
+ if congestion_point is None:
+ # This can happen if a device has no congestion point assigned yet.
+ # We handle this by giving them all the empty congestion point.
+ return self.DEFAULT_CONGESTION_POINT
+ return congestion_point
+
+ def create_controller_tree(
+ self,
+ cluster_state: ClusterState,
+ target: ClusterTarget,
+ plan_due_by_date: datetime,
+ ) -> RootPlanner:
+ """Create a tree of controllers for planning.
+
+ Args:
+ cluster_state: The state of the cluster
+ target: The target for the cluster
+ plan_due_by_date: The date by which planning must be completed
+
+ Returns:
+ A RootPlanner with appropriate device planners added
+ """
+ # Always accepting all targets is NOT possible if there is an energy target
+ always_accept_all_proposals = not target.contains_energy_target()
+
+ root_planner = RootPlanner(
+ target.get_global_target_profile(),
+ self.config.energy_improvement_criterion(),
+ self.config.cost_improvement_criterion(),
+ always_accept_all_proposals,
+ self.context,
+ )
+
+ for device_id, device_state in cluster_state.get_device_states().items():
+ congestion_point = self.get_congestion_point(
+ cluster_state, device_state.connection_id
+ )
+ cpc = root_planner.get_congestion_point_controller(congestion_point)
+
+ if cpc is None:
+ # Create a new congestion point controller if one doesn't exist yet
+ congestion_point_target = target.get_congestion_point_target(
+ congestion_point
+ )
+ if congestion_point == self.DEFAULT_CONGESTION_POINT:
+ # This is a dummy congestion point. We will give it an empty profile.
+ congestion_point_target = JouleRangeProfile(
+ target.get_global_target_profile().metadata,
+ elements=congestion_point_target.elements
+ if congestion_point_target is not None
+ else [],
+ )
+
+ cpc = CongestionPointPlanner(congestion_point, congestion_point_target, self.config.multithreaded()) # type: ignore[arg-type]
+ root_planner.add_congestion_point_controller(cpc)
+
+ # Add the appropriate device planner based on the device state type
+ if isinstance(device_state, S2FrbcDeviceState):
+ logger.debug("S2 FRBC planner created!")
+ cpc.add_device_controller(
+ S2FrbcDevicePlanner(
+ device_state,
+ target.metadata,
+ plan_due_by_date,
+ congestion_point,
+ )
+ )
+ # elif isinstance(device_state, S2NoControlDeviceState):
+ # logger.debug("S2 NoControl planner created!")
+ # cpc.add_device_controller(
+ # S2NoControlDevicePlanner(
+ # device_state,
+ # target.metadata,
+ # congestion_point,
+ # )
+ # )
+ elif isinstance(device_state, S2DdbcDeviceState):
+ logger.debug("S2 DDBC planner created!")
+ cpc.add_device_controller(
+ S2DdbcDevicePlanner(
+ device_state,
+ target.metadata,
+ plan_due_by_date,
+ congestion_point,
+ )
+ )
+ # Add other device types here as needed
+ else:
+ logger.warning(
+ f"Unknown device! No device planner added for {device_state}"
+ )
+
+ return root_planner
+
+ def plan(
+ self,
+ state: ClusterState,
+ target: ClusterTarget,
+ planning_window: int,
+ reason: str,
+ plan_due_by_date: datetime,
+ optimize_for_target: bool,
+ max_priority_class: int,
+ ) -> ClusterPlan:
+ """Create a plan for the cluster.
+
+ Args:
+ state: The state of the cluster
+ target: The target for the cluster
+ planning_window: The planning window in seconds
+ reason: The reason for planning
+ plan_due_by_date: The date by which planning must be completed
+ optimize_for_target: Whether to optimize for the target
+ max_priority_class: The maximum priority class to optimize
+
+ Returns:
+ A ClusterPlan for the cluster
+ """
+ start_time = datetime.now()
+
+ # Make sure that all congestion points in the ClusterState have a target:
+ for cp in state.get_congestion_points():
+ congestion_point_target = target.get_congestion_point_target(cp)
+ if congestion_point_target is None and cp is not None:
+ # We don't have a target for the congestion point
+ logger.warning(
+ f"CongestionPoint without target! CongestionPoint: {cp}. Generating empty target."
+ )
+ target.set_congestion_point_target(
+ congestion_point_id=cp,
+ congestion_point_target=JouleRangeProfile(
+ profile_start=target.get_global_target_profile().metadata,
+ ),
+ )
+
+ # Create a tree of controllers and run the planning algorithm
+ root_controller = self.create_controller_tree(state, target, plan_due_by_date)
+
+ try:
+ root_controller.plan(
+ plan_due_by_date,
+ optimize_for_target,
+ max_priority_class,
+ self.config.multithreaded(),
+ )
+
+ # Collect device plans
+ device_plans = []
+ for cpc in root_controller.cp_controllers:
+ for device in cpc.devices:
+ device_plans.append(device.get_device_plan())
+
+ # Create and return the cluster plan
+ plan_data = ClusterPlanData(device_plans, target.metadata) # type: ignore[arg-type]
+ plan = ClusterPlan(state, target, plan_data, reason, plan_due_by_date, None)
+
+ end_time = datetime.now()
+ execution_time = (
+ end_time - start_time
+ ).total_seconds() * 1000 # Convert to milliseconds
+ logger.info(f"Generated new plan in {execution_time} ms")
+
+ return plan
+ except Exception as e:
+ logger.error(f"Error during planning: {e}")
+ raise
+
class S2Scheduler(Scheduler):
+ """
+ S2Scheduler integrates the profile steering planning service with FlexMeasures.
+
+ This scheduler uses the PlanningServiceImpl to create optimal energy profiles
+ for devices in a cluster based on targets and constraints.
+ """
__author__ = "TNO"
__version__ = "1"
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.planning_service = None
+ self.config_deserialized = False
+ self.frbc_device_data = None # Store FRBC device data from WebSocket
+
def compute(self, *args, **kwargs):
"""
- Just a dummy scheduler that always plans to consume at maximum capacity.
- (Schedulers return positive values for consumption, and negative values for production)
+ Compute the optimal energy profile using the S2 profile steering algorithm.
+
+ Returns:
+ List: List containing FRBCInstruction objects and metadata dicts
"""
- return pd.Series(
- self.sensor.get_attribute("capacity_in_mw"),
- index=pd.date_range(
- self.start, self.end, freq=self.resolution, inclusive="left"
- ),
- )
+ if not self.config_deserialized:
+ self.deserialize_config()
+
+ try:
+ # Create planning service
+ config = PlanningServiceConfig()
+ planning_service = PlanningServiceImpl(config)
+
+ # Use FRBC device data if available, otherwise use mock data
+ if self.frbc_device_data is not None:
+ logger.info("Creating device states and cluster target from FRBC data")
+ device_states = self.create_device_states_from_frbc_data()
+ cluster_target = self.create_cluster_target_from_frbc_data()
+ else:
+ # Fallback to mock data for testing
+ logger.info("Using mock device states and cluster target")
+ device_states = self.create_mock_device_states()
+ cluster_target = self.create_mock_cluster_target()
+
+ logger.info(f"Created cluster state with {len(device_states)} devices")
+ cluster_state = ClusterState(self.start, device_states, {})
+
+ # Generate plan
+ logger.info("Starting planning service...")
+ try:
+ cluster_plan = planning_service.plan(
+ state=cluster_state,
+ target=cluster_target,
+ planning_window=int((self.end - self.start).total_seconds()),
+ reason="S2 scheduling",
+ plan_due_by_date=self.start + pd.Timedelta(seconds=10),
+ optimize_for_target=True,
+ max_priority_class=1,
+ )
+ logger.info("Planning service completed successfully")
+ except Exception as planning_error:
+ logger.error(f"Planning service failed: {planning_error}")
+ # Generate instructions directly without planning
+ if self.frbc_device_data is not None:
+ logger.info("Falling back to simple FRBC instruction generation")
+ instructions = self._generate_simple_frbc_instructions()
+ else:
+ instructions = []
+
+ # Create empty energy data
+ num_timesteps = int((self.end - self.start) / self.resolution)
+ energy_data = {
+ "sensor": self.sensor,
+ "data": pd.Series(
+ [0] * num_timesteps,
+ index=pd.date_range(
+ self.start, self.end, freq=self.resolution, inclusive="left"
+ ),
+ ),
+ }
+ return instructions + [energy_data]
+
+ # Extract FRBCInstructions from device plans
+ instructions = []
+ device_plans = cluster_plan.get_plan_data().get_device_plans()
+
+ for device_plan in device_plans:
+ if device_plan and device_plan.instruction_profile:
+ instructions.extend(device_plan.instruction_profile.elements)
+
+ # If we have FRBC device data but no instructions from planning,
+ # generate simple instructions based on the received data
+ if not instructions and self.frbc_device_data is not None:
+ logger.info(
+ "No instructions from planning service, generating simple FRBC instructions"
+ )
+ instructions = self._generate_simple_frbc_instructions()
+
+ # Add energy data entry for potential storage
+ try:
+ energy_profile = cluster_plan.get_joule_profile()
+ if energy_profile is not None and energy_profile.elements is not None:
+ energy_data = {
+ "sensor": self.sensor,
+ "data": pd.Series(
+ energy_profile.elements,
+ index=pd.date_range(
+ self.start,
+ self.end,
+ freq=self.resolution,
+ inclusive="left",
+ ),
+ ),
+ }
+ else:
+ # Create empty energy data if profile is None or has no elements
+ num_timesteps = int((self.end - self.start) / self.resolution)
+ energy_data = {
+ "sensor": self.sensor,
+ "data": pd.Series(
+ [0] * num_timesteps,
+ index=pd.date_range(
+ self.start,
+ self.end,
+ freq=self.resolution,
+ inclusive="left",
+ ),
+ ),
+ }
+ except Exception as profile_error:
+ logger.warning(
+ f"Failed to get energy profile: {profile_error}, using fallback"
+ )
+ # Create empty energy data as fallback
+ num_timesteps = int((self.end - self.start) / self.resolution)
+ energy_data = {
+ "sensor": self.sensor,
+ "data": pd.Series(
+ [0] * num_timesteps,
+ index=pd.date_range(
+ self.start, self.end, freq=self.resolution, inclusive="left"
+ ),
+ ),
+ }
+
+ return instructions + [energy_data]
+
+ except Exception as e:
+ logger.error(f"Error in S2Scheduler.compute(): {e}")
+ import traceback
+
+ logger.debug(f"Traceback: {traceback.format_exc()}")
+ # Return empty list on error
+ return []
def deserialize_config(self):
- """Do not care about any flex config sent in."""
+ """Deserialize the flex configuration from asset attributes."""
+ # Find flex-model in asset attributes
+ self.flex_model = self.asset.attributes.get("flex-model", {})
+
+ self.deserialize_flex_config()
self.config_deserialized = True
+
+ def deserialize_flex_config(self):
+ """Deserialize flex-model and flex-context"""
+ # Deserialize flex-model
+ self.flex_model = S2FlexModelSchema().load(self.flex_model)
+
+ # Deserialize self.flex_context
+ self.flex_context = TNOFlexContextSchema().load(self.flex_context)
+
+ def create_planning_service(
+ self, config: Optional[PlanningServiceConfig] = None
+ ) -> PlanningServiceImpl:
+ """
+ Create a planning service instance with the given configuration.
+
+ Args:
+ config: Planning service configuration. If None, uses default values.
+
+ Returns:
+ PlanningServiceImpl: Configured planning service instance
+ """
+ if config is None:
+ config = PlanningServiceConfig()
+
+ return PlanningServiceImpl(config)
+
+ def create_cluster_state(self, device_states: Dict[str, Any]) -> ClusterState:
+ """
+ Create a ClusterState from device states.
+
+ Args:
+ device_states: Dictionary of device states
+
+ Returns:
+ ClusterState: Cluster state for planning
+ """
+ # TODO: Implement conversion from FlexMeasures device data to ClusterState
+ raise NotImplementedError("create_cluster_state() needs to be implemented")
+
+ def create_cluster_target(self, target_profile: Any) -> ClusterTarget:
+ """
+ Create a ClusterTarget from target profile data.
+
+ Args:
+ target_profile: Target profile data from flex_model
+
+ Returns:
+ ClusterTarget: Cluster target for planning
+ """
+ # TODO: Implement conversion from flex_model to ClusterTarget
+ raise NotImplementedError("create_cluster_target() needs to be implemented")
+
+ def create_mock_device_states(self) -> Dict[str, Any]:
+ """
+ Create mock device states for testing.
+
+ Returns:
+ Dict[str, Any]: Dictionary of mock device states
+ """
+ from flexmeasures_s2.profile_steering.device_planner.frbc.s2_frbc_device_state import (
+ S2FrbcDeviceState,
+ )
+ from s2python.common import PowerValue, CommodityQuantity
+
+ # Create a simple mock device state for testing
+ mock_device_state = S2FrbcDeviceState(
+ device_id="mock_device_1",
+ device_name="Mock Device 1",
+ connection_id="mock_connection_1",
+ priority_class=1,
+ timestamp=self.start,
+ energy_in_current_timestep=PowerValue(
+ value=0, commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1
+ ),
+ is_online=True,
+ power_forecast=None,
+ system_descriptions=[],
+ leakage_behaviours=[],
+ usage_forecasts=[],
+ fill_level_target_profiles=[],
+ computational_parameters=S2FrbcDeviceState.ComputationalParameters(100, 20),
+ actuator_statuses=[],
+ storage_status=[],
+ )
+
+ return {"mock_device_1": mock_device_state}
+
+ def create_mock_cluster_target(self) -> ClusterTarget:
+ """
+ Create a mock cluster target for testing.
+
+ Returns:
+ ClusterTarget: Mock cluster target
+ """
+ from flexmeasures_s2.profile_steering.common.target_profile import TargetProfile
+ from flexmeasures_s2.profile_steering.common.profile_metadata import (
+ ProfileMetadata,
+ )
+
+ # Create mock profile metadata
+ profile_metadata = ProfileMetadata(
+ profile_start=self.start,
+ timestep_duration=self.resolution,
+ nr_of_timesteps=int((self.end - self.start) / self.resolution),
+ )
+
+ # Create a simple target profile with zeros
+ target_elements = [0] * profile_metadata.nr_of_timesteps
+ # Type ignore: TargetProfile constructor accepts List[int] and converts to JouleElement
+ global_target_profile = TargetProfile(
+ profile_start=profile_metadata.profile_start,
+ timestep_duration=profile_metadata.timestep_duration,
+ elements=target_elements, # type: ignore[arg-type]
+ )
+
+ return ClusterTarget(
+ generated_at=self.start,
+ parent_id=None,
+ generated_by=None,
+ global_target_profile=global_target_profile,
+ congestion_point_targets={},
+ )
+
+ def create_device_states_from_frbc_data(self) -> Dict[str, Any]:
+ """
+ Create device states from received FRBC data.
+
+ Returns:
+ Dict[str, Any]: Dictionary of device states created from FRBC data
+ """
+ from flexmeasures_s2.profile_steering.device_planner.frbc.s2_frbc_device_state import (
+ S2FrbcDeviceState,
+ )
+ from s2python.common import PowerValue, CommodityQuantity
+
+ if self.frbc_device_data is None:
+ return self.create_mock_device_states()
+
+ # Extract information from received FRBC data
+ device_id = self.frbc_device_data.resource_id or "frbc_device_1"
+ system_desc = self.frbc_device_data.system_description
+ storage_status = self.frbc_device_data.storage_status
+
+ # Create device state using the received FRBC data
+ device_state = S2FrbcDeviceState(
+ device_id=device_id,
+ device_name=f"FRBC Device {device_id}",
+ connection_id=f"{device_id}_connection",
+ priority_class=1,
+ timestamp=self.start,
+ energy_in_current_timestep=PowerValue(
+ value=0, commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1
+ ),
+ is_online=True,
+ power_forecast=None,
+ system_descriptions=[system_desc] if system_desc else [],
+ leakage_behaviours=[],
+ usage_forecasts=[],
+ fill_level_target_profiles=[self.frbc_device_data.fill_level_target_profile]
+ if self.frbc_device_data.fill_level_target_profile
+ else [],
+ computational_parameters=S2FrbcDeviceState.ComputationalParameters(100, 20),
+ actuator_statuses=list(self.frbc_device_data.actuator_statuses.values())
+ if hasattr(self.frbc_device_data, "actuator_statuses")
+ else [],
+ storage_status=[storage_status] if storage_status else [],
+ )
+
+ return {device_id: device_state}
+
+ def create_cluster_target_from_frbc_data(self) -> ClusterTarget:
+ """
+ Create a cluster target from FRBC device data.
+
+ Returns:
+ ClusterTarget: Cluster target based on FRBC data
+ """
+ from flexmeasures_s2.profile_steering.common.target_profile import TargetProfile
+ from flexmeasures_s2.profile_steering.common.profile_metadata import (
+ ProfileMetadata,
+ )
+
+ if (
+ self.frbc_device_data is None
+ or self.frbc_device_data.fill_level_target_profile is None
+ ):
+ return self.create_mock_cluster_target()
+
+ # Create profile metadata
+ profile_metadata = ProfileMetadata(
+ profile_start=self.start,
+ timestep_duration=self.resolution,
+ nr_of_timesteps=int((self.end - self.start) / self.resolution),
+ )
+
+ # Convert FRBC fill level target to energy target
+ # This is a simplified conversion - in practice you'd need more sophisticated logic
+ target_elements = self._convert_frbc_fill_level_to_energy_target(
+ self.frbc_device_data.fill_level_target_profile,
+ profile_metadata.nr_of_timesteps,
+ )
+
+ # Type ignore: TargetProfile constructor accepts List[int] and converts to JouleElement
+ target_mode = "costs" # Default mode when running outside Flask context
+ logging.debug(f"Target mode = {target_mode}")
+ if target_mode == "energy":
+ global_target_profile = TargetProfile(
+ profile_start=profile_metadata.profile_start,
+ timestep_duration=profile_metadata.timestep_duration,
+ elements=target_elements, # type: ignore[arg-type]
+ )
+ elif target_mode == "costs":
+ # For standalone operation, fall back to energy target mode
+ logging.warning(
+ "Cost target mode requires Flask context, falling back to energy target mode"
+ )
+ # Fall back to creating a simple energy target
+ global_target_profile = TargetProfile(
+ profile_start=profile_metadata.profile_start,
+ timestep_duration=profile_metadata.timestep_duration,
+ elements=target_elements, # type: ignore[arg-type]
+ )
+ else:
+ raise ValueError(f"Unknown FLEXMEASURES_S2_TARGET_MODE='{target_mode}'")
+
+ return ClusterTarget(
+ generated_at=self.start,
+ parent_id=None,
+ generated_by=None,
+ global_target_profile=global_target_profile,
+ congestion_point_targets={},
+ )
+
+ def _convert_frbc_fill_level_to_energy_target(
+ self, fill_level_profile, nr_timesteps: int
+ ) -> list:
+ """
+ Convert FRBC fill level target profile to energy target profile.
+
+ This is a simplified conversion for demonstration.
+ """
+ # Simple conversion: aim for charging when fill level targets are higher
+ target_elements = []
+
+ if fill_level_profile and fill_level_profile.elements:
+ # For each target element, determine if we need to charge/discharge
+ current_timestep = 0
+
+ for element in fill_level_profile.elements:
+ duration_seconds = (
+ getattr(element.duration, "value", 0) / 1000
+ ) # Convert ms to seconds
+ timesteps_for_element = max(
+ 1, int(duration_seconds / self.resolution.total_seconds())
+ )
+
+ # Simple logic: if target range is high, we want to charge (positive energy)
+ target_range_avg = (
+ element.fill_level_range.start_of_range
+ + element.fill_level_range.end_of_range
+ ) / 2
+
+ if target_range_avg > 50: # Above 50% - charging target
+ energy_target = 10000 # 10kJ per timestep
+ else: # Below 50% - minimal energy
+ energy_target = 1000 # 1kJ per timestep
+
+ # Add target for this duration
+ for _ in range(
+ min(timesteps_for_element, nr_timesteps - current_timestep)
+ ):
+ target_elements.append(energy_target)
+ current_timestep += 1
+
+ if current_timestep >= nr_timesteps:
+ break
+
+ # Fill remaining timesteps with zeros
+ while len(target_elements) < nr_timesteps:
+ target_elements.append(0)
+
+ return target_elements[:nr_timesteps]
+
+ def _generate_simple_frbc_instructions(self) -> list:
+ """
+ Generate simple FRBC instructions based on received device data.
+
+ Returns:
+ List of FRBCInstruction objects
+ """
+ from s2python.frbc import FRBCInstruction
+ from datetime import timezone
+ import uuid
+
+ if (
+ self.frbc_device_data is None
+ or self.frbc_device_data.system_description is None
+ ):
+ return []
+
+ instructions = []
+ system_desc = self.frbc_device_data.system_description
+ fill_level_target = self.frbc_device_data.fill_level_target_profile
+
+ # Extract actuator and operation mode information
+ if system_desc.actuators:
+ actuator = system_desc.actuators[0] # Use first actuator
+ if actuator.operation_modes:
+ operation_mode = actuator.operation_modes[0] # Use first operation mode
+
+ # Determine operation mode factor based on fill level targets
+ operation_mode_factor = 0.5 # Default
+
+ if fill_level_target and fill_level_target.elements:
+ # Simple logic: higher target ranges get higher factors
+ for element in fill_level_target.elements:
+ target_avg = (
+ element.fill_level_range.start_of_range
+ + element.fill_level_range.end_of_range
+ ) / 2
+ if target_avg > 50:
+ operation_mode_factor = 0.8 # Higher factor for charging
+ else:
+ operation_mode_factor = 0.2 # Lower factor for maintaining
+ break # Use first element for simplicity
+
+ # Generate instruction for near future
+ execution_time = self.start.replace(tzinfo=timezone.utc)
+
+ instruction = FRBCInstruction(
+ message_id=str(uuid.uuid4()),
+ id=str(uuid.uuid4()),
+ actuator_id=str(actuator.id),
+ operation_mode=str(operation_mode.id),
+ operation_mode_factor=operation_mode_factor,
+ execution_time=execution_time,
+ abnormal_condition=False,
+ )
+
+ instructions.append(instruction)
+ logger.info(f"Generated FRBC instruction: {instruction.to_json()}")
+
+ return instructions
diff --git a/flexmeasures_s2/scheduler/schemas.py b/flexmeasures_s2/scheduler/schemas.py
index 9e42dfa..6cc8c07 100644
--- a/flexmeasures_s2/scheduler/schemas.py
+++ b/flexmeasures_s2/scheduler/schemas.py
@@ -1,5 +1,34 @@
-from marshmallow import Schema
+from __future__ import annotations
+from pydantic import BaseModel
-class S2FlexModelSchema(Schema):
- ...
+class PowerValue(BaseModel):
+ value: float
+
+
+class Schedule(BaseModel):
+ values: list[PowerValue]
+
+
+class S2FlexModelSchema(BaseModel):
+ """Schema for S2 Flex Model validation."""
+
+ def load(self, data):
+ """Load and validate data."""
+ return data # Pass-through for now
+
+
+class TNOFlexContextSchema(BaseModel):
+ """Schema for TNO Flex Context validation."""
+
+ def load(self, data):
+ """Load and validate data."""
+ return data # Pass-through for now
+
+
+class TNOTargetProfile(BaseModel):
+ """Schema for TNO Target Profile validation."""
+
+ def load(self, data):
+ """Load and validate data."""
+ return data # Pass-through for now
diff --git a/flexmeasures_s2/scheduler/test_frbc_device.json b/flexmeasures_s2/scheduler/test_frbc_device.json
new file mode 100644
index 0000000..ef38b22
--- /dev/null
+++ b/flexmeasures_s2/scheduler/test_frbc_device.json
@@ -0,0 +1,123 @@
+{
+ "system_descriptions": [
+ {
+ "message_type": "FRBC.SystemDescription",
+ "message_id": "f6769b77-3d85-4d61-8a28-3bd70fc5cef4",
+ "valid_from": "2025-01-21 17:52:08.121054+00:00",
+ "actuators": [
+ {
+ "id": "1ebd596b-3031-403f-95a1-9520550b161b",
+ "diagnostic_label": "charge",
+ "supported_commodities": [
+ "Commodity.ELECTRICITY"
+ ],
+ "operation_modes": [
+ {
+ "id": "charge.on",
+ "diagnostic_label": "charge.on",
+ "elements": [
+ {
+ "fill_level_range": {
+ "start_of_range": 0.0,
+ "end_of_range": 100.0
+ },
+ "fill_rate": {
+ "start_of_range": 0.0054012349,
+ "end_of_range": 0.0054012349
+ },
+ "power_ranges": [
+ {
+ "start_of_range": 28000.0,
+ "end_of_range": 28000.0,
+ "commodity_quantity": "CommodityQuantity.ELECTRIC_POWER_L1"
+ }
+ ],
+ "running_costs": null
+ }
+ ],
+ "abnormal_condition_only": false
+ },
+ {
+ "id": "charge.off",
+ "diagnostic_label": "charge.off",
+ "elements": [
+ {
+ "fill_level_range": {
+ "start_of_range": 0.0,
+ "end_of_range": 100.0
+ },
+ "fill_rate": {
+ "start_of_range": 0.0,
+ "end_of_range": 0.0
+ },
+ "power_ranges": [
+ {
+ "start_of_range": 0.0,
+ "end_of_range": 0.0,
+ "commodity_quantity": "CommodityQuantity.ELECTRIC_POWER_L1"
+ }
+ ],
+ "running_costs": null
+ }
+ ],
+ "abnormal_condition_only": false
+ }
+ ],
+ "transitions": [
+ {
+ "id": "off.to.on",
+ "from_": "charge.off",
+ "to": "charge.on",
+ "start_timers": [
+ "on.to.off.timer"
+ ],
+ "blocking_timers": [
+ "off.to.on.timer"
+ ],
+ "transition_costs": null,
+ "transition_duration": null,
+ "abnormal_condition_only": false
+ },
+ {
+ "id": "on.to.off",
+ "from_": "charge.on",
+ "to": "charge.off",
+ "start_timers": [
+ "off.to.on.timer"
+ ],
+ "blocking_timers": [
+ "on.to.off.timer"
+ ],
+ "transition_costs": null,
+ "transition_duration": null,
+ "abnormal_condition_only": false
+ }
+ ],
+ "timers": [
+ {
+ "id": "on.to.off.timer",
+ "diagnostic_label": "on.to.off.timer",
+ "duration": 30
+ },
+ {
+ "id": "off.to.on.timer",
+ "diagnostic_label": "off.to.on.timer",
+ "duration": 30
+ }
+ ]
+ }
+ ],
+ "storage": {
+ "diagnostic_label": "battery",
+ "fill_level_label": "SoC %",
+ "provides_leakage_behaviour": false,
+ "provides_fill_level_target_profile": true,
+ "provides_usage_forecast": false,
+ "fill_level_range": {
+ "start_of_range": 0.0,
+ "end_of_range": 100.0
+ }
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/flexmeasures_s2/scheduler/tests/__init__.py b/flexmeasures_s2/scheduler/tests/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/flexmeasures_s2/scheduler/tests/test_schemas.py b/flexmeasures_s2/scheduler/tests/test_schemas.py
new file mode 100644
index 0000000..0f61a4d
--- /dev/null
+++ b/flexmeasures_s2/scheduler/tests/test_schemas.py
@@ -0,0 +1,20 @@
+import pytest
+
+from flexmeasures_s2.scheduler.schemas import TNOTargetProfile
+
+
+@pytest.mark.parametrize(
+ "target_profile",
+ [
+ # todo: port test cases from Java test
+ {},
+ {
+ "start": "2025-01-20T13:00+01",
+ "duration": "PT3H",
+ "values": [0, 1, 2],
+ },
+ ],
+)
+def test_tno_profile_schema(target_profile: dict):
+ """Check whether the profile schema"""
+ TNOTargetProfile().load(target_profile)
diff --git a/profiling_res.txt b/profiling_res.txt
new file mode 100644
index 0000000..e69de29
diff --git a/requirements/app.in b/requirements/app.in
index 3a17a94..b4916df 100644
--- a/requirements/app.in
+++ b/requirements/app.in
@@ -1,2 +1,4 @@
-flexmeasures>=0.24.0
+flexmeasures>=0.27.0
s2-python
+cryptography>=44.0.2
+inflect>=7.5.0
diff --git a/requirements/app.txt b/requirements/app.txt
index 67d7209..a410a53 100644
--- a/requirements/app.txt
+++ b/requirements/app.txt
@@ -1,5 +1,5 @@
#
-# This file is autogenerated by pip-compile with Python 3.11
+# This file is autogenerated by pip-compile with Python 3.12
# by the following command:
#
# pip-compile --output-file=requirements/app.txt requirements/app.in
@@ -14,6 +14,8 @@ argon2-cffi-bindings==21.2.0
# via argon2-cffi
arrow==1.3.0
# via rq-dashboard
+asn1crypto==1.5.1
+ # via webauthn
attrs==24.3.0
# via
# jsonschema
@@ -28,12 +30,16 @@ blinker==1.9.0
# flask-mail
# flask-principal
# sentry-sdk
+cbor2==5.6.5
+ # via webauthn
certifi==2024.12.14
# via
# requests
# sentry-sdk
cffi==1.17.1
- # via argon2-cffi-bindings
+ # via
+ # argon2-cffi-bindings
+ # cryptography
charset-normalizer==3.4.1
# via requests
click==8.1.8
@@ -49,6 +55,12 @@ contourpy==1.3.1
# via matplotlib
convertdate==2.4.0
# via workalendar
+cryptography==45.0.6
+ # via
+ # -r requirements/app.in
+ # flask-security-too
+ # pyopenssl
+ # webauthn
cycler==0.12.1
# via matplotlib
dill==0.3.9
@@ -96,11 +108,14 @@ flask-migrate==4.1.0
# via flexmeasures
flask-principal==0.4.0
# via flask-security-too
-flask-security-too==5.5.2
- # via flexmeasures
+flask-security-too[fsqla,mfa]==5.5.2
+ # via
+ # flask-security-too
+ # flexmeasures
flask-sqlalchemy==3.1.1
# via
# flask-migrate
+ # flask-security-too
# flexmeasures
flask-sslify==0.1.5
# via flexmeasures
@@ -110,14 +125,12 @@ flask-wtf==1.2.2
# flexmeasures
flexcache==0.3
# via pint
-flexmeasures==0.24.0
+flexmeasures==0.27.0
# via -r requirements/app.in
flexparser==0.4
# via pint
fonttools==4.55.3
# via matplotlib
-greenlet==3.1.1
- # via sqlalchemy
humanize==4.11.0
# via flexmeasures
idna==3.10
@@ -221,6 +234,8 @@ passlib==1.7.4
# via flask-security-too
patsy==1.0.1
# via statsmodels
+phonenumberslite==9.0.11
+ # via flask-security-too
pillow==11.1.0
# via
# flexmeasures
@@ -245,6 +260,7 @@ pycparser==2.22
# via cffi
pydantic==1.10.20
# via
+ # -r requirements/app.in
# flexmeasures
# inflect
# s2-python
@@ -254,6 +270,8 @@ pymeeus==0.5.12
# via convertdate
pyomo==6.8.2
# via flexmeasures
+pyopenssl==25.1.0
+ # via webauthn
pyparsing==3.2.1
# via matplotlib
python-dateutil==2.9.0.post0
@@ -274,6 +292,8 @@ pytz==2024.2
# timetomodel
pyyaml==6.0.2
# via flexmeasures
+qrcode==8.2
+ # via flask-security-too
redis==5.2.1
# via
# flexmeasures
@@ -327,18 +347,22 @@ sktime==0.35.0
sqlalchemy==2.0.37
# via
# alembic
+ # flask-security-too
# flask-sqlalchemy
# flexmeasures
# marshmallow-sqlalchemy
+ # sqlalchemy-utils
# timely-beliefs
# timetomodel
+sqlalchemy-utils==0.41.2
+ # via flask-security-too
statsmodels==0.14.4
# via timetomodel
tabulate==0.9.0
# via flexmeasures
threadpoolctl==3.5.0
# via scikit-learn
-timely-beliefs[forecast]==3.1.0
+timely-beliefs[forecast]==3.2.1
# via flexmeasures
timetomodel==0.7.3
# via flexmeasures
@@ -355,6 +379,7 @@ typing-extensions==4.12.2
# pint
# py-moneyed
# pydantic
+ # pyopenssl
# sqlalchemy
tzdata==2024.2
# via pandas
@@ -368,6 +393,8 @@ vl-convert-python==1.7.0
# via flexmeasures
webargs==8.6.0
# via flexmeasures
+webauthn==2.6.0
+ # via flask-security-too
werkzeug==3.1.3
# via
# flask
diff --git a/requirements/testi.txt b/requirements/testi.txt
new file mode 100644
index 0000000..c481ebb
--- /dev/null
+++ b/requirements/testi.txt
@@ -0,0 +1,25 @@
+test_frbc_device.py::test_connexxion_ev_bus_baseline_byd_225
+-------------------------------- live log call ---------------------------------
+DEBUG root:test_frbc_device.py:189 id_on_operation_mode: 8c46733e-0c68-4175-85db-4e28ef11ecf9
+DEBUG root:test_frbc_device.py:209 id_off_operation_mode: bbc4e2c2-599b-411b-b987-858d148846a4
+DEBUG root:test_frbc_device.py:219 id_on_to_off_timer: bcafc296-929e-4973-b447-bc18f7d3ceb3
+DEBUG root:test_frbc_device.py:227 id_off_to_on_timer: 412f76c3-6bb1-4654-b9c2-d31e50492987
+DEBUG root:test_frbc_device.py:235 transition_id_from_on_to_off: 5b286161-3f7f-43f6-b019-b3c808c2d376
+DEBUG root:test_frbc_device.py:247 transition_id_from_off_to_on: 24e5c3d3-4d7f-4c7e-a598-7c096f27083b
+DEBUG root:test_frbc_device.py:259 charge_actuator_id: 91b16e98-47b1-463a-b679-f5baf6ee8cb7
+DEBUG root:test_frbc_device.py:277 storage_status_id: 41f861c7-f9b7-46bb-94c0-1e131170bcbd
+DEBUG root:test_frbc_device.py:305 leakage_id: 14dac666-eb11-4acb-a5ec-400a051cd1b4
+DEBUG root:test_frbc_device.py:324 usage_id: 351fb97b-67e7-4901-9fcd-485cef127b0c
+DEBUG root:test_frbc_device.py:351 fill_level_id: 632d6d5f-efb9-48dd-b22e-f08db900d956
+DEBUG root:test_frbc_device.py:375 id_off_operation_mode_driving: b99c327d-8866-46be-9514-32851ccb50ef
+DEBUG root:test_frbc_device.py:384 off_actuator_id_driving: 8e1dc01b-4b8b-426b-a98a-d9f0cabe3503
+DEBUG root:test_frbc_device.py:189 id_on_operation_mode: 74e2d319-6fe4-4a0f-add4-e2709f2e21e0
+DEBUG root:test_frbc_device.py:209 id_off_operation_mode: 4a7f2a5a-d519-4803-8719-7f3d22ade46c
+DEBUG root:test_frbc_device.py:219 id_on_to_off_timer: 5c33e567-213a-4b2f-8575-808ea27b79f7
+DEBUG root:test_frbc_device.py:227 id_off_to_on_timer: 83962f5e-5e0f-4173-88de-2ec1e0eabcd0
+DEBUG root:test_frbc_device.py:235 transition_id_from_on_to_off: a65929ed-f7cd-4e7b-860d-9bfd68be9d6b
+DEBUG root:test_frbc_device.py:247 transition_id_from_off_to_on: 5c3e73cc-1b8e-4c6c-87d6-1ba076daeacb
+DEBUG root:test_frbc_device.py:259 charge_actuator_id: 3e728f7a-82c7-4a44-a1ea-7f8a220b8ed7
+DEBUG root:test_frbc_device.py:277 storage_status_id: 38abda5b-6774-439d-a319-2a50671595bb
+DEBUG root:test_frbc_device.py:305 leakage_id: 2ba72dcf-546d-45a2-9c87-1492437dd2c9
+DEBUG root:test_frbc_device.py:324 usage_id: d3132d10-e226-4763-adae-bd9492f8251d
\ No newline at end of file
diff --git a/run_mypy.sh b/run_mypy.sh
index f7b3c44..52258c9 100755
--- a/run_mypy.sh
+++ b/run_mypy.sh
@@ -2,5 +2,5 @@
set -e
pip install mypy
pip install types-pytz types-requests types-Flask types-click types-redis types-tzlocal types-python-dateutil types-setuptools
-files=$(find . -name \*.py -not -path "./venv/*" -not -path ".egg/*" -not -path "./build/*" -not -path "./dist/*")
+files=$(find . -name \*.py -not -path "./venv/*" -not -path "./.eggs/*" -not -path "./build/*" -not -path "./dist/*" -not -path "./.mypy_cache/*")
mypy --follow-imports skip --ignore-missing-imports $files
diff --git a/setup.cfg b/setup.cfg
index 72b2bac..291efbb 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -7,5 +7,5 @@ exclude = .git,__pycache__,documentation
max-line-length = 160
max-complexity = 13
select = B,C,E,F,W,B9
-ignore = E501, W503, E203
+ignore = E501, W503, E203, C901, E231