hycon 0.7.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. hycon-0.7.1/LICENSE.txt +26 -0
  2. hycon-0.7.1/PKG-INFO +93 -0
  3. hycon-0.7.1/README.md +36 -0
  4. hycon-0.7.1/hycon/__init__.py +3 -0
  5. hycon-0.7.1/hycon/controllers/__init__.py +19 -0
  6. hycon-0.7.1/hycon/controllers/battery_controller.py +268 -0
  7. hycon-0.7.1/hycon/controllers/controller_base.py +68 -0
  8. hycon-0.7.1/hycon/controllers/hybrid_supervisory_controller.py +354 -0
  9. hycon-0.7.1/hycon/controllers/hydrogen_plant_controller.py +113 -0
  10. hycon-0.7.1/hycon/controllers/lookup_based_wake_steering_controller.py +115 -0
  11. hycon-0.7.1/hycon/controllers/solar_passthrough_controller.py +13 -0
  12. hycon-0.7.1/hycon/controllers/wake_steering_rosco_standin.py +41 -0
  13. hycon-0.7.1/hycon/controllers/wind_farm_power_tracking_controller.py +171 -0
  14. hycon-0.7.1/hycon/design_tools/wake_steering_design.py +703 -0
  15. hycon-0.7.1/hycon/design_tools/wake_steering_visualization.py +178 -0
  16. hycon-0.7.1/hycon/interfaces/__init__.py +10 -0
  17. hycon-0.7.1/hycon/interfaces/hercules_interface.py +201 -0
  18. hycon-0.7.1/hycon/interfaces/hercules_v1_interface.py +305 -0
  19. hycon-0.7.1/hycon/interfaces/interface_base.py +56 -0
  20. hycon-0.7.1/hycon/interfaces/python_interface_temp.py +18 -0
  21. hycon-0.7.1/hycon/interfaces/rosco_zmq_interface.py +161 -0
  22. hycon-0.7.1/hycon/utilities.py +8 -0
  23. hycon-0.7.1/hycon.egg-info/PKG-INFO +93 -0
  24. hycon-0.7.1/hycon.egg-info/SOURCES.txt +27 -0
  25. hycon-0.7.1/hycon.egg-info/dependency_links.txt +1 -0
  26. hycon-0.7.1/hycon.egg-info/requires.txt +15 -0
  27. hycon-0.7.1/hycon.egg-info/top_level.txt +1 -0
  28. hycon-0.7.1/pyproject.toml +124 -0
  29. hycon-0.7.1/setup.cfg +4 -0
@@ -0,0 +1,26 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2025 Alliance for Energy Innovation, LLC and Colorado School of Mines.
4
+
5
+ Redistribution and use in source and binary forms, with or without modification, are permitted
6
+ provided that the following conditions are met:
7
+
8
+ * Redistributions of source code must retain the above copyright notice, this list of conditions
9
+ and the following disclaimer.
10
+
11
+ * Redistributions in binary form must reproduce the above copyright notice, this list of
12
+ conditions and the following disclaimer in the documentation and/or other materials provided
13
+ with the distribution.
14
+
15
+ * Neither the name of the copyright holder nor the names of its contributors may be used to
16
+ endorse or promote products derived from this software without specific prior written permission.
17
+
18
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
19
+ IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
20
+ AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER
21
+ OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
23
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
24
+ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
25
+ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26
+ POSSIBILITY OF SUCH DAMAGE.
hycon-0.7.1/PKG-INFO ADDED
@@ -0,0 +1,93 @@
1
+ Metadata-Version: 2.4
2
+ Name: hycon
3
+ Version: 0.7.1
4
+ Summary: Hybrid power plant controller.
5
+ Author-email: "Michael (Misha) Sinner" <michael.sinner@nlr.gov>
6
+ License: BSD 3-Clause License
7
+
8
+ Copyright (c) 2025 Alliance for Energy Innovation, LLC and Colorado School of Mines.
9
+
10
+ Redistribution and use in source and binary forms, with or without modification, are permitted
11
+ provided that the following conditions are met:
12
+
13
+ * Redistributions of source code must retain the above copyright notice, this list of conditions
14
+ and the following disclaimer.
15
+
16
+ * Redistributions in binary form must reproduce the above copyright notice, this list of
17
+ conditions and the following disclaimer in the documentation and/or other materials provided
18
+ with the distribution.
19
+
20
+ * Neither the name of the copyright holder nor the names of its contributors may be used to
21
+ endorse or promote products derived from this software without specific prior written permission.
22
+
23
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
24
+ IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
25
+ AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER
26
+ OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
27
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
28
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
29
+ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
30
+ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31
+ POSSIBILITY OF SUCH DAMAGE.
32
+ Project-URL: Homepage, https://github.com/NatLabRockies/hycon
33
+ Project-URL: Documentation, https://natlabrockies.github.io/hycon/intro.html
34
+ Keywords: hycon
35
+ Classifier: Development Status :: 5 - Production/Stable
36
+ Classifier: Intended Audience :: Developers
37
+ Classifier: License :: OSI Approved :: BSD License
38
+ Classifier: Natural Language :: English
39
+ Classifier: Programming Language :: Python :: 3
40
+ Requires-Python: >=3.10
41
+ Description-Content-Type: text/markdown
42
+ License-File: LICENSE.txt
43
+ Requires-Dist: numpy~=2.0
44
+ Requires-Dist: pandas~=2.0
45
+ Requires-Dist: matplotlib~=3.0
46
+ Requires-Dist: floris~=4.3
47
+ Requires-Dist: zmq
48
+ Provides-Extra: docs
49
+ Requires-Dist: jupyter-book==1.0.4; extra == "docs"
50
+ Requires-Dist: sphinx-book-theme; extra == "docs"
51
+ Provides-Extra: develop
52
+ Requires-Dist: pytest; extra == "develop"
53
+ Requires-Dist: pre-commit; extra == "develop"
54
+ Requires-Dist: ruff; extra == "develop"
55
+ Requires-Dist: isort; extra == "develop"
56
+ Dynamic: license-file
57
+
58
+ # Hycon
59
+
60
+ Hycon is a python-based tool for real-time hybrid power plant control.
61
+ Hycon will primarily be run in simulation, although we intend that it could be
62
+ used for physical plants in future.
63
+
64
+ Hycon provides plant-level (and hybrid plant-level) controls such as
65
+ wake steering control, spatial filtering/consensus, active power control,
66
+ and coordinated control of hybrid power plant assets;
67
+ and creates an entry point for the development of more advanced controllers.
68
+
69
+ Hycon will interface with various simulation testbeds and lower level
70
+ controllers, including:
71
+ - [Hercules](https://github.com/NatLabRockies/hercules)
72
+ - [FAST.Farm](https://github.com/OpenFAST/openfast)
73
+ - [ROSCO](https://github.com/NatLabRockies/rosco)
74
+
75
+ Hycon controllers will also call on design tools such as
76
+ [FLORIS](https://github.com/NatLabRockies/floris).
77
+
78
+ ## WETO software
79
+
80
+ Hycon is primarily developed with the support from the U.S. Department of Energy and
81
+ is part of the [WETO Software Stack](https://natlabrockies.github.io/WETOStack).
82
+ For more information and other integrated modeling software, see:
83
+
84
+ - [Portfolio Overview](https://natlabrockies.github.io/WETOStack/portfolio_analysis/overview.html)
85
+ - [Entry Guide](https://natlabrockies.github.io/WETOStack/_static/entry_guide/index.html)
86
+ - [Wind Farm Controls Workshop](https://www.youtube.com/watch?v=f-w6whxIBrA&list=PL6ksUtsZI1dwRXeWFCmJT6cEN1xijsHJz)
87
+
88
+ NLR's software record for Hycon is SWR-25-54.
89
+
90
+ ## Documentation
91
+
92
+ Documentation for Hycon, including installation instructions, can be found
93
+ [here](https://natlabrockies.github.io/hycon/intro.html).
hycon-0.7.1/README.md ADDED
@@ -0,0 +1,36 @@
1
+ # Hycon
2
+
3
+ Hycon is a python-based tool for real-time hybrid power plant control.
4
+ Hycon will primarily be run in simulation, although we intend that it could be
5
+ used for physical plants in future.
6
+
7
+ Hycon provides plant-level (and hybrid plant-level) controls such as
8
+ wake steering control, spatial filtering/consensus, active power control,
9
+ and coordinated control of hybrid power plant assets;
10
+ and creates an entry point for the development of more advanced controllers.
11
+
12
+ Hycon will interface with various simulation testbeds and lower level
13
+ controllers, including:
14
+ - [Hercules](https://github.com/NatLabRockies/hercules)
15
+ - [FAST.Farm](https://github.com/OpenFAST/openfast)
16
+ - [ROSCO](https://github.com/NatLabRockies/rosco)
17
+
18
+ Hycon controllers will also call on design tools such as
19
+ [FLORIS](https://github.com/NatLabRockies/floris).
20
+
21
+ ## WETO software
22
+
23
+ Hycon is primarily developed with the support from the U.S. Department of Energy and
24
+ is part of the [WETO Software Stack](https://natlabrockies.github.io/WETOStack).
25
+ For more information and other integrated modeling software, see:
26
+
27
+ - [Portfolio Overview](https://natlabrockies.github.io/WETOStack/portfolio_analysis/overview.html)
28
+ - [Entry Guide](https://natlabrockies.github.io/WETOStack/_static/entry_guide/index.html)
29
+ - [Wind Farm Controls Workshop](https://www.youtube.com/watch?v=f-w6whxIBrA&list=PL6ksUtsZI1dwRXeWFCmJT6cEN1xijsHJz)
30
+
31
+ NLR's software record for Hycon is SWR-25-54.
32
+
33
+ ## Documentation
34
+
35
+ Documentation for Hycon, including installation instructions, can be found
36
+ [here](https://natlabrockies.github.io/hycon/intro.html).
@@ -0,0 +1,3 @@
1
+ from importlib.metadata import version
2
+
3
+ __version__ = version("hycon")
@@ -0,0 +1,19 @@
1
+ from hycon.controllers.battery_controller import (
2
+ BatteryController,
3
+ BatteryPassthroughController,
4
+ BatteryPriceSOCController,
5
+ )
6
+ from hycon.controllers.hybrid_supervisory_controller import (
7
+ HybridSupervisoryControllerBaseline,
8
+ HybridSupervisoryControllerMultiRef,
9
+ )
10
+ from hycon.controllers.hydrogen_plant_controller import HydrogenPlantController
11
+ from hycon.controllers.lookup_based_wake_steering_controller import (
12
+ LookupBasedWakeSteeringController,
13
+ )
14
+ from hycon.controllers.solar_passthrough_controller import SolarPassthroughController
15
+ from hycon.controllers.wake_steering_rosco_standin import WakeSteeringROSCOStandin
16
+ from hycon.controllers.wind_farm_power_tracking_controller import (
17
+ WindFarmPowerDistributingController,
18
+ WindFarmPowerTrackingController,
19
+ )
@@ -0,0 +1,268 @@
1
+ import numpy as np
2
+
3
+ from hycon.controllers.controller_base import ControllerBase
4
+
5
+
6
+ class BatteryController(ControllerBase):
7
+ """
8
+ Modifies power reference to consider battery degradation for single battery.
9
+
10
+ In particular, ensures smoothness in battery reference signal to avoid rapid
11
+ changes in power reference, which can lead to degradation.
12
+ """
13
+
14
+ def __init__(self, interface, input_dict, controller_parameters={}, verbose=True):
15
+ """
16
+ Instantiate BatteryController.
17
+
18
+ Args:
19
+ interface (object): Interface object for communicating with simulator.
20
+ input_dict (dict): Dictionary of input parameters (e.g. from Hercules).
21
+ controller_parameters (dict): Dictionary of controller parameters k_batt and
22
+ clipping_thresholds. See set_controller_parameters for more details. If
23
+ controller parameters are provided both in input_dict and controller_parameters,
24
+ the latter will take precedence.
25
+ verbose (bool): If True, print debug information.
26
+ """
27
+ super().__init__(interface, verbose)
28
+
29
+ # Extract global parameters
30
+ self.dt = input_dict["dt"]
31
+
32
+ # Check that parameters are not specified both in input file
33
+ # and in controller_parameters
34
+ if "controller" in input_dict:
35
+ for cp in controller_parameters.keys():
36
+ if cp in input_dict["controller"]:
37
+ raise KeyError(
38
+ 'Found key "' + cp + '" in both input_dict["controller"] and'
39
+ " in controller_parameters."
40
+ )
41
+ controller_parameters = {**controller_parameters, **input_dict["controller"]}
42
+ self.set_controller_parameters(**controller_parameters)
43
+
44
+ # Initialize controller internal state
45
+ self.x = 0
46
+
47
+ def set_controller_parameters(
48
+ self,
49
+ k_batt=0.1,
50
+ clipping_thresholds=[0, 0, 1, 1],
51
+ **_, # <- Allows arbitrary additional parameters to be passed, which are ignored
52
+ ):
53
+ """
54
+ Set gains and threshold limits for BatteryController.
55
+
56
+ k_batt is the controller gain. The controller will be stable and slow to react for small
57
+ values of k_batt (e.g. k_batt=0.01), and will be fast to react (and eventually unstable)
58
+ for large values of k_batt (e.g. k_batt=1).
59
+
60
+ clipping_thresholds is a list of four values: [soc_min, soc_min_clip, soc_max_clip,
61
+ soc_max]. soc_min is the minimum allowable SOC value, below which the controller output
62
+ reference power will be zero. soc_min_clip is the SOC value below which the controller
63
+ applies clipping to the reference power (the reference power is clipped linearly between
64
+ soc_min and soc_min_clip). Similarly, soc_max_clip is the SOC value above which linear
65
+ clipping is applied, until soc_max, after which the output is zero. Between soc_min_clip
66
+ and soc_max_clip, the full reference power is used.
67
+
68
+ Args:
69
+ k_batt (float): Gain for controller.
70
+ clipping_thresholds (list): SOC thresholds for clipping reference power. Should be a
71
+ list of four values: [soc_min, soc_min_clip, soc_max_clip, soc_max].
72
+ """
73
+ zeta = 2
74
+ omega = 2 * np.pi * k_batt
75
+
76
+ # Discrete-time, first-order state-space model of controller
77
+ p = np.exp(-2 * zeta * omega * self.dt)
78
+ self.a = p
79
+ self.b = 1
80
+ self.c = omega / (2 * zeta) * (1 - p) / 2 * (p + 1)
81
+ self.d = omega / (2 * zeta) * (1 - p) / 2
82
+
83
+ self.clipping_thresholds = clipping_thresholds
84
+
85
+ def soc_clipping(self, soc, reference_power):
86
+ """
87
+ Clip the input reference based on the state of charge and clipping_thresholds.
88
+
89
+ Args:
90
+ soc (float): Current state of charge.
91
+ reference_power (float): Reference power to be clipped.
92
+
93
+ Returns:
94
+ float: Clipped reference power.
95
+ """
96
+ clip_fraction = np.interp(soc, self.clipping_thresholds, [0, 1, 1, 0], left=0, right=0)
97
+
98
+ r_charge = clip_fraction * self.plant_parameters["battery"]["charge_rate"]
99
+ r_discharge = clip_fraction * self.plant_parameters["battery"]["discharge_rate"]
100
+
101
+ return np.clip(reference_power, -r_discharge, r_charge)
102
+
103
+ def compute_controls(self, measurements_dict):
104
+ """
105
+ Main compute_controls method for BatteryController.
106
+ """
107
+ reference_power = measurements_dict["battery"]["power_reference"]
108
+ current_power = measurements_dict["battery"]["power"]
109
+ soc = measurements_dict["battery"]["state_of_charge"]
110
+
111
+ # Apply reference clipping
112
+ reference_power = self.soc_clipping(soc, reference_power)
113
+
114
+ e = reference_power - current_power
115
+
116
+ # Compute control
117
+ u = self.c * self.x + self.d * e
118
+
119
+ # Update controller internal state
120
+ self.x = self.a * self.x + self.b * e
121
+
122
+ controls_dict = {"power_setpoint": current_power + u}
123
+
124
+ return controls_dict
125
+
126
+
127
+ class BatteryPassthroughController(ControllerBase):
128
+ """
129
+ Simply passes power reference down to (single) battery.
130
+ """
131
+
132
+ def __init__(self, interface, input_dict, verbose=True):
133
+ """
134
+ Instantiate BatteryPassthroughController."
135
+ """
136
+ super().__init__(interface, verbose)
137
+
138
+ def compute_controls(self, measurements_dict):
139
+ """
140
+ Main compute_controls method for BatteryPassthroughController.
141
+ """
142
+ return {"power_setpoint": measurements_dict["battery"]["power_reference"]}
143
+
144
+
145
+ class BatteryPriceSOCController(ControllerBase):
146
+ """
147
+ Controller considers price and SOC to determine power setpoint.
148
+
149
+ This controller implements a price-arbitrage strategy that uses day-ahead (DA)
150
+ locational marginal prices (LMPs) and real-time (RT) LMPs to decide when to
151
+ charge or discharge the battery. The algorithm identifies the top and bottom
152
+ price hours of the day based on battery duration (e.g., for a 4-hour battery,
153
+ it targets the "top_d" = 4 highest and "bottom_d" = 4 lowest priced hours).
154
+
155
+ The decision logic is as follows:
156
+ 1. If RT price exceeds the highest DA price: discharge at full rate
157
+ (unconditionally).
158
+ 2. Else if RT price is in the top-d highest DA prices AND SOC > low_soc:
159
+ discharge at full rate.
160
+ 3. Else if RT price is below the lowest DA price: charge at full rate
161
+ (unconditionally).
162
+ 4. Else if RT price is in the bottom-d lowest DA prices AND SOC < high_soc:
163
+ charge at full rate.
164
+ 5. Otherwise: hold (power setpoint = 0).
165
+
166
+ The SOC thresholds (high_soc, low_soc) prevent over-charging or over-discharging
167
+ during moderate price signals, while still allowing full charge/discharge when
168
+ prices move outside the expected DA range.
169
+
170
+ Note:
171
+ Charging power is represented as negative values, matching the convention
172
+ used at the Hercules/hybrid_plant level.
173
+ """
174
+
175
+ def __init__(self, interface, input_dict, controller_parameters={}, verbose=True):
176
+ super().__init__(interface, verbose)
177
+
178
+ # Check that parameters are not specified both in input file
179
+ # and in controller_parameters
180
+ if "controller" in input_dict:
181
+ for cp in controller_parameters.keys():
182
+ if cp in input_dict["controller"]:
183
+ raise KeyError(
184
+ 'Found key "' + cp + '" in both input_dict["controller"] and'
185
+ " in controller_parameters."
186
+ )
187
+ controller_parameters = {**controller_parameters, **input_dict["controller"]}
188
+ self.set_controller_parameters(**controller_parameters)
189
+
190
+ self.rated_power_charging = input_dict["battery"]["charge_rate"]
191
+ self.rated_power_discharging = input_dict["battery"]["discharge_rate"]
192
+
193
+ # Save the duration rounded to nearest hour
194
+ self.duration = round(
195
+ interface.plant_parameters["battery"]["energy_capacity"]
196
+ / interface.plant_parameters["battery"]["power_capacity"]
197
+ )
198
+
199
+ # Raise if duration makes this controller implausible
200
+ if self.duration >= 12:
201
+ raise ValueError(
202
+ f"Battery duration is {self.duration} hours, which is not "
203
+ "supported by BatteryPriceSOCController."
204
+ " This controller is only intended for durations shorter than 12 hours."
205
+ )
206
+
207
+ if self.duration < 1:
208
+ raise ValueError(
209
+ f"Battery duration is {self.duration} hours, which is not "
210
+ "supported by BatteryPriceSOCController."
211
+ " This controller is only intended for durations of at least 1 hour."
212
+ )
213
+
214
+ def set_controller_parameters(
215
+ self,
216
+ high_soc=1.0,
217
+ low_soc=0.2,
218
+ **_, # <- Allows arbitrary additional parameters to be passed, which are ignored
219
+ ):
220
+ """
221
+ Set parameters for BatteryPriceSOCController.
222
+
223
+ high_soc is the SOC threshold above which the battery will only charge if the price is below
224
+ the lowest (hourly) DA price of the day. Defaults to 1.0.
225
+
226
+ low_soc is the SOC threshold below which the battery will only discharge if the price is
227
+ above the highest (hourly) DA price of the day. Defaults to 0.2.
228
+
229
+ high_soc defaults to 1.0 (effectively disabled) as experience suggests waiting for
230
+ very low prices is not worthwhile. low_soc defaults to 0.2 as experience suggests waiting
231
+ for very high prices is worthwhile.
232
+
233
+ Args:
234
+ high_soc (float): High SOC threshold (0 to 1). Defaults to 1.0.
235
+ low_soc (float): Low SOC threshold (0 to 1). Defaults to 0.2.
236
+ """
237
+ self.high_soc = high_soc
238
+ self.low_soc = low_soc
239
+
240
+ def compute_controls(self, measurements_dict):
241
+ day_ahead_lmps = np.array(measurements_dict["DA_LMP_24hours"])
242
+ sorted_day_ahead_lmps = np.sort(day_ahead_lmps)
243
+ real_time_lmp = measurements_dict["RT_LMP"]
244
+
245
+ # Extract limits
246
+ bottom_d = sorted_day_ahead_lmps[self.duration - 1]
247
+ top_d = sorted_day_ahead_lmps[-self.duration]
248
+ bottom_1 = sorted_day_ahead_lmps[0]
249
+ top_1 = sorted_day_ahead_lmps[-1]
250
+
251
+ # Access the state of charge and LMP in real-time
252
+ soc = measurements_dict["battery"]["state_of_charge"]
253
+
254
+ # Note that the convention is followed where charging is negative power
255
+ # This matches what is in place in the hercules/hybrid_plant level and
256
+ # will be inverted before passing into the battery modules
257
+ if real_time_lmp > top_1:
258
+ power_setpoint = self.rated_power_discharging
259
+ elif (real_time_lmp > top_d) & (soc > self.low_soc):
260
+ power_setpoint = self.rated_power_discharging
261
+ elif real_time_lmp < bottom_1:
262
+ power_setpoint = -self.rated_power_charging
263
+ elif (real_time_lmp < bottom_d) & (soc < self.high_soc):
264
+ power_setpoint = -self.rated_power_charging
265
+ else:
266
+ power_setpoint = 0.0
267
+
268
+ return {"power_setpoint": power_setpoint}
@@ -0,0 +1,68 @@
1
+ from abc import ABCMeta, abstractmethod
2
+
3
+
4
+ class ControllerBase(metaclass=ABCMeta):
5
+ def __init__(self, interface, verbose=True):
6
+ self._s = interface
7
+ self.verbose = verbose
8
+
9
+ # Initialize measurements and controls to send
10
+ self._measurements_dict = {}
11
+ self._controls_dict = {}
12
+
13
+ def _receive_measurements(self, input_dict=None):
14
+ # May need to eventually loop here, depending on server set up.
15
+ self._measurements_dict = self._s.get_measurements(input_dict)
16
+
17
+ return None
18
+
19
+ def _send_controls(self, input_dict=None):
20
+ self._s.check_controls(self._controls_dict)
21
+ output_dict = self._s.send_controls(input_dict, **self._controls_dict)
22
+
23
+ return output_dict
24
+
25
+ def step(self, input_dict=None):
26
+ # If not running with direct hercules integration, hercules_dict may simply be None
27
+ # throughout this method.
28
+ self._receive_measurements(input_dict)
29
+
30
+ self._controls_dict = self.compute_controls(self._measurements_dict)
31
+
32
+ output_dict = self._send_controls(input_dict)
33
+
34
+ return output_dict
35
+
36
+ @property
37
+ def controller_parameters(self):
38
+ return self._s.controller_parameters
39
+
40
+ @property
41
+ def plant_parameters(self):
42
+ return self._s.plant_parameters
43
+
44
+ @property
45
+ def dt(self):
46
+ return self._s.dt
47
+
48
+ @dt.setter
49
+ def dt(self, _):
50
+ print(
51
+ "Warning: Setting dt directly is deprecated. Use the interface's dt property instead."
52
+ )
53
+
54
+ @property
55
+ def cname(self):
56
+ if hasattr(self, "_cname"):
57
+ return self._cname
58
+ else:
59
+ return ValueError("cname has not been set for this controller.")
60
+
61
+ @cname.setter
62
+ def cname(self, value):
63
+ self._cname = value
64
+
65
+ @abstractmethod
66
+ def compute_controls(self, measurements_dict: dict) -> dict:
67
+ pass # Control algorithms should be implemented in the compute_controls
68
+ # method of the child class.