gridworks-scada-protocol 0.1.0.dev3__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 (73) hide show
  1. gridworks_scada_protocol-0.1.0.dev3/PKG-INFO +11 -0
  2. gridworks_scada_protocol-0.1.0.dev3/README.md +0 -0
  3. gridworks_scada_protocol-0.1.0.dev3/pyproject.toml +20 -0
  4. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/__init__.py +9 -0
  5. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/data_classes/__init__.py +0 -0
  6. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/data_classes/house_0_layout.py +307 -0
  7. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/data_classes/house_0_names.py +387 -0
  8. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/enums/__init__.py +59 -0
  9. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/enums/atomic_ally_state.py +30 -0
  10. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/enums/change_keep_send.py +31 -0
  11. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/enums/contract_status.py +47 -0
  12. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/enums/flow_manifold_variant.py +35 -0
  13. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/enums/home_alone_strategy.py +36 -0
  14. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/enums/home_alone_top_state.py +42 -0
  15. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/enums/hp_loop_keep_send.py +30 -0
  16. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/enums/hp_model.py +11 -0
  17. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/enums/log_level.py +53 -0
  18. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/enums/main_auto_event.py +44 -0
  19. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/enums/main_auto_state.py +38 -0
  20. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/enums/market_price_unit.py +34 -0
  21. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/enums/market_quantity_unit.py +36 -0
  22. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/enums/pico_cycler_event.py +50 -0
  23. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/enums/pico_cycler_state.py +46 -0
  24. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/enums/top_event.py +36 -0
  25. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/enums/top_state.py +36 -0
  26. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/enums/turn_hp_on_off.py +36 -0
  27. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/named_types/__init__.py +93 -0
  28. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/named_types/actuators_ready.py +12 -0
  29. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/named_types/admin_dispatch.py +13 -0
  30. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/named_types/admin_keep_alive.py +11 -0
  31. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/named_types/admin_release_control.py +12 -0
  32. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/named_types/ally_gives_up.py +11 -0
  33. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/named_types/atn_bid.py +38 -0
  34. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/named_types/atn_bid_001.py +152 -0
  35. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/named_types/channel_flatlined.py +27 -0
  36. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/named_types/dispatch_contract_go_dormant.py +21 -0
  37. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/named_types/dispatch_contract_go_live.py +26 -0
  38. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/named_types/energy_instruction.py +42 -0
  39. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/named_types/events.py +13 -0
  40. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/named_types/flo_params.py +37 -0
  41. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/named_types/flo_params_house0.py +64 -0
  42. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/named_types/fsm_event.py +58 -0
  43. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/named_types/glitch.py +18 -0
  44. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/named_types/go_dormant.py +11 -0
  45. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/named_types/ha1_params.py +22 -0
  46. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/named_types/hack_oil_off.py +10 -0
  47. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/named_types/hack_oil_on.py +10 -0
  48. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/named_types/heating_forecast.py +50 -0
  49. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/named_types/latest_price.py +17 -0
  50. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/named_types/layout_lite.py +86 -0
  51. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/named_types/market_maker_ack.py +123 -0
  52. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/named_types/new_command_tree.py +27 -0
  53. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/named_types/no_new_contract_warning.py +12 -0
  54. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/named_types/pico_missing.py +13 -0
  55. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/named_types/price_quantity_unitless.py +12 -0
  56. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/named_types/remaining_elec.py +13 -0
  57. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/named_types/reset_hp_keep_value.py +20 -0
  58. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/named_types/scada_params.py +26 -0
  59. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/named_types/send_layout.py +14 -0
  60. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/named_types/set_lwt_control_params.py +22 -0
  61. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/named_types/set_target_lwt.py +27 -0
  62. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/named_types/sieg_loop_endpoint_valve_adjustment.py +32 -0
  63. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/named_types/sieg_target_too_low.py +17 -0
  64. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/named_types/single_machine_state.py +30 -0
  65. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/named_types/slow_contract_heartbeat.py +158 -0
  66. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/named_types/slow_dispatch_contract.py +42 -0
  67. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/named_types/snapshot_spaceheat.py +30 -0
  68. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/named_types/start_listening_to_atn.py +11 -0
  69. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/named_types/stop_listening_to_atn.py +11 -0
  70. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/named_types/suit_up.py +15 -0
  71. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/named_types/wake_up.py +12 -0
  72. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/named_types/weather_forecast.py +53 -0
  73. gridworks_scada_protocol-0.1.0.dev3/src/gwsproto/py.typed +0 -0
@@ -0,0 +1,11 @@
1
+ Metadata-Version: 2.3
2
+ Name: gridworks-scada-protocol
3
+ Version: 0.1.0.dev3
4
+ Summary: Add your description here
5
+ Author: Jessica Millar
6
+ Author-email: Jessica Millar <jmillar@gridworks-consulting.com>
7
+ Requires-Dist: gridworks-protocol>=1.2.9
8
+ Requires-Dist: transitions>=0.9.3
9
+ Requires-Python: >=3.11
10
+ Description-Content-Type: text/markdown
11
+
File without changes
@@ -0,0 +1,20 @@
1
+ [project]
2
+ name = "gridworks-scada-protocol"
3
+ version = "0.1.0.dev3"
4
+ description = "Add your description here"
5
+ readme = "README.md"
6
+ authors = [
7
+ { name = "Jessica Millar", email = "jmillar@gridworks-consulting.com" }
8
+ ]
9
+ requires-python = ">=3.11"
10
+ dependencies = [
11
+ "gridworks-protocol>=1.2.9",
12
+ "transitions>=0.9.3",
13
+ ]
14
+
15
+ [tool.uv.build-backend]
16
+ module-name = "gwsproto"
17
+
18
+ [build-system]
19
+ requires = ["uv_build>=0.8.8,<0.9.0"]
20
+ build-backend = "uv_build"
@@ -0,0 +1,9 @@
1
+ from gwsproto import data_classes
2
+ from gwsproto import named_types
3
+ from gwsproto import enums
4
+
5
+ __all__ = [
6
+ "data_classes",
7
+ "named_types",
8
+ "enums",
9
+ ]
@@ -0,0 +1,307 @@
1
+ import json
2
+ from pathlib import Path
3
+ from typing import Any, List, Literal, Optional
4
+
5
+ from gw.errors import DcError
6
+ from gwproto.enums import ActorClass
7
+ from gwproto.data_classes.components import Component
8
+ from gwproto.data_classes.data_channel import DataChannel
9
+ from gwproto.data_classes.hardware_layout import (
10
+ HardwareLayout,
11
+ LoadArgs,
12
+ LoadError,
13
+ )
14
+
15
+ from gwsproto.data_classes.house_0_names import H0CN, H0N
16
+ from gwsproto.enums import FlowManifoldVariant, HomeAloneStrategy
17
+
18
+ from gwproto.data_classes.sh_node import ShNode
19
+ from gwproto.data_classes.synth_channel import SynthChannel
20
+ from gwproto.default_decoders import (
21
+ CacDecoder,
22
+ ComponentDecoder,
23
+ )
24
+ from gwproto.named_types import ComponentAttributeClassGt
25
+
26
+ class House0LoadArgs(LoadArgs):
27
+ flow_manifold_variant: FlowManifoldVariant
28
+ use_sieg_loop: bool
29
+ class House0Layout(HardwareLayout):
30
+ zone_list: List[str]
31
+ total_store_tanks: int
32
+
33
+
34
+ def __init__( # noqa: PLR0913
35
+ self,
36
+ layout: dict[Any, Any],
37
+ *,
38
+ cacs: dict[str, ComponentAttributeClassGt], # by id
39
+ components: dict[str, Component], # by id
40
+ nodes: dict[str, ShNode], # by name
41
+ data_channels: dict[str, DataChannel], # by name
42
+ synth_channels: dict[str, SynthChannel],
43
+ flow_manifold_variant: FlowManifoldVariant = FlowManifoldVariant.House0,
44
+ use_sieg_loop: bool = False,
45
+ ) -> None:
46
+ super().__init__(
47
+ layout=layout,
48
+ cacs=cacs,
49
+ components=components,
50
+ nodes=nodes,
51
+ data_channels=data_channels,
52
+ synth_channels=synth_channels,
53
+ )
54
+ self.flow_manifold_variant = flow_manifold_variant
55
+ self.use_sieg_loop = use_sieg_loop
56
+
57
+ # Bolted on right now
58
+ required_keys = ["ZoneList", "TotalStoreTanks"]
59
+ for key in required_keys:
60
+ if key not in layout:
61
+ raise DcError(f"House0 requires {key}!")
62
+
63
+
64
+ self.zone_list = layout["ZoneList"]
65
+ self.total_store_tanks = layout["TotalStoreTanks"]
66
+
67
+ self.channel_names = H0CN(self.total_store_tanks, self.zone_list)
68
+ if not isinstance(self.total_store_tanks, int):
69
+ raise TypeError("TotalStoreTanks must be an integer")
70
+ if not 1 <= self.total_store_tanks <= 6:
71
+ raise ValueError("Must have between 1 and 6 store tanks")
72
+ if not isinstance(self.zone_list, List):
73
+ raise TypeError("ZoneList must be a list")
74
+ if not 1 <= len(self.zone_list) <= 6:
75
+ raise ValueError("Must have between 1 and 6 store zones")
76
+ self.h0n = H0N(self.total_store_tanks, self.zone_list)
77
+
78
+ @classmethod
79
+ def validate_house0( # noqa: C901
80
+ cls,
81
+ load_args: House0LoadArgs,
82
+ *,
83
+ raise_errors: bool,
84
+ errors: Optional[list[LoadError]] = None,
85
+ ) -> None:
86
+ nodes = load_args["nodes"]
87
+ data_channels = load_args["data_channels"]
88
+ errors_caught = []
89
+ flow_manifold_variant = load_args["flow_manifold_variant"]
90
+ use_sieg_loop = load_args["use_sieg_loop"]
91
+
92
+ # Can't use the siegenthaler loop in the code if it isn't in the plumbing
93
+ if use_sieg_loop and flow_manifold_variant != FlowManifoldVariant.House0Sieg:
94
+ raise DcError("Cannot use Sieg Loop when FlowManifoldVariant is not House0Sieg!")
95
+
96
+ # Make sure sieg relays, sieg flow and sieg temp nodes and channels exist
97
+ if flow_manifold_variant == FlowManifoldVariant.House0Sieg:
98
+ try:
99
+ cls.check_house0_sieg_manifold(data_channels)
100
+ except Exception as e:
101
+ if raise_errors:
102
+ raise
103
+ errors_caught.append(LoadError("hardware.layout", nodes, e))
104
+
105
+
106
+ if use_sieg_loop: # HpBoss and SiegLoop need to be actors
107
+ try:
108
+ cls.check_actors_when_using_sieg_loop(nodes)
109
+ except Exception as e:
110
+ if raise_errors:
111
+ raise
112
+ errors_caught.append(LoadError("hardware.layout", nodes, e))
113
+ else: # HpBoss and SiegLoop should NOT be actors
114
+ try:
115
+ cls.check_actors_when_not_using_sieg_loop(nodes)
116
+ except Exception as e:
117
+ if raise_errors:
118
+ raise
119
+ errors_caught.append(LoadError("hardware.layout", nodes, e))
120
+ @classmethod
121
+ def check_house0_sieg_manifold(cls, channels: dict[str, DataChannel]) -> None:
122
+ # if H0CN.sieg_cold not in channels.keys():
123
+ # raise DcError(f"Need {H0CN.sieg_cold} channel with House0Sieg flow manifold variant")
124
+ # if H0CN.sieg_flow not in channels.keys():
125
+ # raise DcError(f"Need {H0CN.sieg_flow} channel with House0Sieg flow manifold variant")
126
+ if H0CN.hp_loop_on_off_relay_state not in channels.keys():
127
+ raise DcError(f"Need {H0CN.hp_loop_on_off_relay_state} channel with House0Sieg flow manifold variant")
128
+ if H0CN.hp_loop_keep_send_relay_state not in channels.keys():
129
+ raise DcError(f"Need {H0CN.hp_loop_keep_send_relay_state} channel with House0Sieg flow manifold variant")
130
+
131
+ @classmethod
132
+ def check_actors_when_using_sieg_loop(cls, nodes: dict[str, ShNode]) -> None:
133
+ if H0N.sieg_loop not in nodes.keys():
134
+ raise DcError(f"Need a SiegLoop actor when using sieg loop!")
135
+ sieg_loop = nodes[H0N.sieg_loop]
136
+ if sieg_loop.actor_class != ActorClass.SiegLoop:
137
+ raise DcError(f"SiegLoop actor {sieg_loop.name} shoud have actor class SiegLoop, not {sieg_loop.actor_class}")
138
+ if H0N.hp_boss not in nodes.keys():
139
+ raise DcError(f"Need HpBoss actor when using sieg loop!")
140
+ hp_boss = nodes[H0N.hp_boss]
141
+ if hp_boss.actor_class != ActorClass.HpBoss:
142
+ raise DcError(f"HpBoss actor {hp_boss.name} shoud have actor class HpBoss, not {hp_boss.actor_class}")
143
+
144
+ @classmethod
145
+ def check_actors_when_not_using_sieg_loop(cls, nodes: dict[str, ShNode]) -> None:
146
+ if H0N.sieg_loop in nodes.keys():
147
+ raise DcError(f"If not using sieg loop, should not have node {H0N.sieg_loop}!")
148
+
149
+ @property
150
+ def ha_strategy(self) -> str:
151
+ """Returns the current home alone strategy"""
152
+ # Could be stored as a property or derived from a node
153
+ ha_node = self.nodes.get(H0N.home_alone)
154
+ return HomeAloneStrategy(HomeAloneStrategy(getattr(ha_node, "Strategy", None)))
155
+
156
+ @property
157
+ def actuators(self) -> List[ShNode]:
158
+ return self.relays + self.zero_tens
159
+
160
+ @property
161
+ def relays(self) -> List[ShNode]:
162
+ return [
163
+ node for node in self.nodes.values()
164
+ if node.ActorClass == ActorClass.Relay
165
+ ]
166
+
167
+ @property
168
+ def zero_tens(self) -> List[ShNode]:
169
+ return [
170
+ node for node in self.nodes.values()
171
+ if node.ActorClass == ActorClass.ZeroTenOutputer
172
+ ]
173
+
174
+
175
+ # overwrites base class to return correct object
176
+ @classmethod
177
+ def load( # noqa: PLR0913
178
+ cls,
179
+ layout_path: Path | str,
180
+ *,
181
+ included_node_names: Optional[set[str]] = None,
182
+ raise_errors: bool = True,
183
+ errors: Optional[list[LoadError]] = None,
184
+ cac_decoder: Optional[CacDecoder] = None,
185
+ component_decoder: Optional[ComponentDecoder] = None,
186
+ ) -> "House0Layout":
187
+ with Path(layout_path).open() as f:
188
+ layout = json.loads(f.read())
189
+ return cls.load_dict(
190
+ layout,
191
+ included_node_names=included_node_names,
192
+ raise_errors=raise_errors,
193
+ errors=errors,
194
+ cac_decoder=cac_decoder,
195
+ component_decoder=component_decoder,
196
+ )
197
+
198
+ # overwrites base class to return correct object
199
+ @classmethod
200
+ def load_dict( # noqa: PLR0913
201
+ cls,
202
+ layout: dict[Any, Any],
203
+ *,
204
+ included_node_names: Optional[set[str]] = None,
205
+ raise_errors: bool = True,
206
+ errors: Optional[list[LoadError]] = None,
207
+ cac_decoder: Optional[CacDecoder] = None,
208
+ component_decoder: Optional[ComponentDecoder] = None,
209
+ ) -> "House0Layout":
210
+ if errors is None:
211
+ errors = []
212
+ cacs = cls.load_cacs(
213
+ layout=layout,
214
+ raise_errors=raise_errors,
215
+ errors=errors,
216
+ cac_decoder=cac_decoder,
217
+ )
218
+ components = cls.load_components(
219
+ layout=layout,
220
+ cacs=cacs,
221
+ raise_errors=raise_errors,
222
+ errors=errors,
223
+ component_decoder=component_decoder,
224
+ )
225
+ nodes = cls.load_nodes(
226
+ layout=layout,
227
+ components=components,
228
+ raise_errors=raise_errors,
229
+ errors=errors,
230
+ included_node_names=included_node_names,
231
+ )
232
+ data_channels = cls.load_data_channels(
233
+ layout=layout,
234
+ nodes=nodes,
235
+ raise_errors=raise_errors,
236
+ errors=errors,
237
+ )
238
+ synth_channels = cls.load_synth_channels(
239
+ layout=layout,
240
+ nodes=nodes,
241
+ raise_errors=raise_errors,
242
+ errors=errors,
243
+ )
244
+ load_args: House0LoadArgs = {
245
+ "cacs": cacs,
246
+ "components": components,
247
+ "nodes": nodes,
248
+ "data_channels": data_channels,
249
+ "synth_channels": synth_channels,
250
+ "flow_manifold_variant": FlowManifoldVariant(layout.get("FlowManifoldVariant", "House0")),
251
+ "use_sieg_loop": bool(layout.get("UseSiegLoop", False))
252
+ }
253
+ cls.resolve_links(
254
+ load_args["nodes"],
255
+ load_args["components"],
256
+ raise_errors=raise_errors,
257
+ errors=errors,
258
+ )
259
+ cls.validate_layout(load_args, raise_errors=raise_errors, errors=errors)
260
+ cls.validate_house0(load_args, raise_errors=raise_errors, errors=errors)
261
+ return House0Layout(layout, **load_args)
262
+
263
+ @property
264
+ def home_alone(self) -> ShNode:
265
+ return self.node(H0N.home_alone)
266
+
267
+ @property
268
+ def auto_node(self) -> ShNode:
269
+ return self.node(H0N.auto)
270
+
271
+ @property
272
+ def atomic_ally(self) -> ShNode:
273
+ return self.node(H0N.atomic_ally)
274
+
275
+ @property
276
+ def atn(self) -> ShNode:
277
+ return self.node(H0N.atn)
278
+
279
+ @property
280
+ def pico_cycler(self) -> ShNode:
281
+ return self.node(H0N.pico_cycler)
282
+
283
+ @property
284
+ def vdc_relay(self) -> ShNode:
285
+ return self.node(H0N.vdc_relay)
286
+
287
+ @property
288
+ def tstat_common_relay(self) -> ShNode:
289
+ return self.node(H0N.tstat_common_relay)
290
+
291
+ @property
292
+ def charge_discharge_relay(self) -> ShNode:
293
+ return self.node(H0N.store_charge_discharge_relay)#
294
+
295
+ def scada2_gnode_name(self) -> str:
296
+ return f"{self.scada_g_node_alias}.{H0N.secondary_scada}"
297
+
298
+ def deserialize_house0_load_args(data: dict) -> House0LoadArgs:
299
+ valid_keys = set(House0LoadArgs.__annotations__.keys())
300
+
301
+ # Validate the FlowManifoldVariant
302
+ data["FlowManifoldVariant"] = FlowManifoldVariant(data.get("FlowManifoldVariant", "House0"))
303
+ # Validate use_sieg_loop
304
+ data["UseSiegLoop"] = bool(data.get("UseSiegLoop", False))
305
+ # TypedDict expects a regular dictionary, so we just pass it in
306
+ return House0LoadArgs(**data)
307
+