ihcsdk 2.8.8__py3-none-any.whl → 2.8.11__py3-none-any.whl
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.
- ihcsdk/__init__.py +1 -1
- ihcsdk/ihcclient.py +194 -172
- ihcsdk/ihcconnection.py +48 -18
- ihcsdk/ihccontroller.py +65 -46
- ihcsdk/ihcsslconnection.py +15 -15
- {ihcsdk-2.8.8.dist-info → ihcsdk-2.8.11.dist-info}/METADATA +9 -2
- ihcsdk-2.8.11.dist-info/RECORD +12 -0
- {ihcsdk-2.8.8.dist-info → ihcsdk-2.8.11.dist-info}/WHEEL +1 -1
- ihcsdk-2.8.8.dist-info/RECORD +0 -12
- {ihcsdk-2.8.8.dist-info → ihcsdk-2.8.11.dist-info/licenses}/license.txt +0 -0
- {ihcsdk-2.8.8.dist-info → ihcsdk-2.8.11.dist-info}/top_level.txt +0 -0
ihcsdk/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"""Init file for ihcsdk
|
|
1
|
+
"""Init file for ihcsdk."""
|
ihcsdk/ihcclient.py
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Implements the connection to the ihc controller
|
|
3
|
-
"""
|
|
1
|
+
"""Implements the connection to the ihc controller."""
|
|
4
2
|
|
|
5
3
|
# pylint: disable=bare-except
|
|
6
4
|
import base64
|
|
7
5
|
import datetime
|
|
8
6
|
import io
|
|
7
|
+
import xml.etree.ElementTree as ET
|
|
9
8
|
import zlib
|
|
9
|
+
from typing import Any, ClassVar, Literal
|
|
10
|
+
|
|
10
11
|
from ihcsdk.ihcconnection import IHCConnection
|
|
11
12
|
from ihcsdk.ihcsslconnection import IHCSSLConnection
|
|
12
13
|
|
|
@@ -14,17 +15,17 @@ IHCSTATE_READY = "text.ctrl.state.ready"
|
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
class IHCSoapClient:
|
|
17
|
-
"""Implements a limited set of the soap request for the IHC controller"""
|
|
18
|
+
"""Implements a limited set of the soap request for the IHC controller."""
|
|
18
19
|
|
|
19
|
-
ihcns = {
|
|
20
|
+
ihcns: ClassVar[dict[str, str]] = {
|
|
20
21
|
"SOAP-ENV": "http://schemas.xmlsoap.org/soap/envelope/",
|
|
21
22
|
"ns1": "utcs",
|
|
22
23
|
"ns2": "utcs.values",
|
|
23
24
|
"ns3": "utcs.values",
|
|
24
25
|
}
|
|
25
26
|
|
|
26
|
-
def __init__(self, url: str):
|
|
27
|
-
"""Initialize the IIHCSoapClient with a url for the controller"""
|
|
27
|
+
def __init__(self, url: str) -> None:
|
|
28
|
+
"""Initialize the IIHCSoapClient with a url for the controller."""
|
|
28
29
|
self.url = url
|
|
29
30
|
self.username = ""
|
|
30
31
|
self.password = ""
|
|
@@ -33,10 +34,17 @@ class IHCSoapClient:
|
|
|
33
34
|
else:
|
|
34
35
|
self.connection = IHCConnection(url)
|
|
35
36
|
|
|
37
|
+
def close(self) -> None:
|
|
38
|
+
"""Close the connection."""
|
|
39
|
+
self.connection.close()
|
|
40
|
+
self.connection = None
|
|
41
|
+
|
|
36
42
|
def authenticate(self, username: str, password: str) -> bool:
|
|
37
|
-
"""
|
|
38
|
-
|
|
39
|
-
|
|
43
|
+
"""
|
|
44
|
+
Do an Authentricate request.
|
|
45
|
+
|
|
46
|
+
And save the cookie returned to be used on the following requests.
|
|
47
|
+
Return True if the request was successfull.
|
|
40
48
|
"""
|
|
41
49
|
self.username = username
|
|
42
50
|
self.password = password
|
|
@@ -61,7 +69,7 @@ class IHCSoapClient:
|
|
|
61
69
|
return False
|
|
62
70
|
|
|
63
71
|
def get_state(self) -> str:
|
|
64
|
-
"""Get the controller state"""
|
|
72
|
+
"""Get the controller state."""
|
|
65
73
|
xdoc = self.connection.soap_action("/ws/ControllerService", "getState", "")
|
|
66
74
|
if xdoc is not False:
|
|
67
75
|
return xdoc.find(
|
|
@@ -69,18 +77,16 @@ class IHCSoapClient:
|
|
|
69
77
|
).text
|
|
70
78
|
return False
|
|
71
79
|
|
|
72
|
-
def wait_for_state_change(self, state: str, waitsec) -> str:
|
|
73
|
-
"""Wait for controller state change and return state"""
|
|
74
|
-
payload = """<ns1:waitForControllerStateChange1
|
|
80
|
+
def wait_for_state_change(self, state: str, waitsec: int) -> str:
|
|
81
|
+
"""Wait for controller state change and return state."""
|
|
82
|
+
payload = f"""<ns1:waitForControllerStateChange1
|
|
75
83
|
xmlns:ns1=\"utcs\" xsi:type=\"ns1:WSControllerState\">
|
|
76
84
|
<ns1:state xsi:type=\"xsd:string\">{state}</ns1:state>
|
|
77
85
|
</ns1:waitForControllerStateChange1>
|
|
78
86
|
<ns2:waitForControllerStateChange2
|
|
79
87
|
xmlns:ns2=\"utcs\" xsi:type=\"xsd:int\">
|
|
80
|
-
{
|
|
81
|
-
"""
|
|
82
|
-
state=state, wait=waitsec
|
|
83
|
-
)
|
|
88
|
+
{waitsec}</ns2:waitForControllerStateChange2>
|
|
89
|
+
"""
|
|
84
90
|
xdoc = self.connection.soap_action(
|
|
85
91
|
"/ws/ControllerService", "waitForControllerStateChange", payload
|
|
86
92
|
)
|
|
@@ -92,9 +98,11 @@ class IHCSoapClient:
|
|
|
92
98
|
return False
|
|
93
99
|
|
|
94
100
|
def get_project(self) -> str:
|
|
95
|
-
"""
|
|
96
|
-
|
|
97
|
-
|
|
101
|
+
"""
|
|
102
|
+
Get the ihc project in single SOAP action.
|
|
103
|
+
|
|
104
|
+
You should use the get_project_in_segments to get the project in multiple
|
|
105
|
+
segments. This will stress the IHC controller less.
|
|
98
106
|
"""
|
|
99
107
|
xdoc = self.connection.soap_action("/ws/ControllerService", "getIHCProject", "")
|
|
100
108
|
if xdoc is not False:
|
|
@@ -109,18 +117,21 @@ class IHCSoapClient:
|
|
|
109
117
|
)
|
|
110
118
|
return False
|
|
111
119
|
|
|
112
|
-
def get_project_in_segments(self, info=None) -> str:
|
|
113
|
-
"""
|
|
114
|
-
|
|
120
|
+
def get_project_in_segments(self, info: dict[str, Any] | None = None) -> str:
|
|
121
|
+
"""
|
|
122
|
+
Get the ihc project per segments.
|
|
123
|
+
|
|
124
|
+
Param: info .. reuse existing project info.
|
|
125
|
+
If not provided, the get_project_info() is called internally.
|
|
115
126
|
"""
|
|
116
127
|
if info is None:
|
|
117
128
|
info = self.get_project_info()
|
|
118
129
|
if info:
|
|
119
|
-
|
|
120
|
-
|
|
130
|
+
project_major = info.get("projectMajorRevision", 0)
|
|
131
|
+
project_minor = info.get("projectMinorRevision", 0)
|
|
121
132
|
buffer = io.BytesIO()
|
|
122
133
|
for s in range(self.get_project_number_of_segments()):
|
|
123
|
-
segment = self.get_project_segment(s,
|
|
134
|
+
segment = self.get_project_segment(s, project_major, project_minor)
|
|
124
135
|
if segment is False:
|
|
125
136
|
return False
|
|
126
137
|
buffer.write(segment)
|
|
@@ -129,8 +140,8 @@ class IHCSoapClient:
|
|
|
129
140
|
)
|
|
130
141
|
return False
|
|
131
142
|
|
|
132
|
-
def get_project_info(self) -> dict:
|
|
133
|
-
"""
|
|
143
|
+
def get_project_info(self) -> dict[str, Any]:
|
|
144
|
+
"""Return dictionary of project info items."""
|
|
134
145
|
xdoc = self.connection.soap_action(
|
|
135
146
|
"/ws/ControllerService", "getProjectInfo", ""
|
|
136
147
|
)
|
|
@@ -145,7 +156,7 @@ class IHCSoapClient:
|
|
|
145
156
|
return False
|
|
146
157
|
|
|
147
158
|
def get_project_number_of_segments(self) -> int:
|
|
148
|
-
"""
|
|
159
|
+
"""Return the number of segments needed to fetch the current ihc-project."""
|
|
149
160
|
xdoc = self.connection.soap_action(
|
|
150
161
|
"/ws/ControllerService", "getIHCProjectNumberOfSegments", ""
|
|
151
162
|
)
|
|
@@ -158,20 +169,23 @@ class IHCSoapClient:
|
|
|
158
169
|
)
|
|
159
170
|
return False
|
|
160
171
|
|
|
161
|
-
def get_project_segment(
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
172
|
+
def get_project_segment(
|
|
173
|
+
self, segment: int, project_major: int, project_minor: int
|
|
174
|
+
) -> bytes:
|
|
175
|
+
"""
|
|
176
|
+
Return a segment of the ihc-project with the given number.
|
|
177
|
+
|
|
178
|
+
Returns null if the segment number increases above the number of segments
|
|
179
|
+
available. The segments are offset from 0.
|
|
180
|
+
The project-versions given as parameters are used to indentify the project that
|
|
181
|
+
should be fetched. That is, to make sure that you suddenly don't get segments
|
|
182
|
+
belonging to another project.
|
|
167
183
|
"""
|
|
168
|
-
payload = """
|
|
184
|
+
payload = f"""
|
|
169
185
|
<getIHCProjectSegment1 xmlns="utcs">{segment}</getIHCProjectSegment1>
|
|
170
|
-
<getIHCProjectSegment2 xmlns="utcs">{
|
|
171
|
-
<getIHCProjectSegment3 xmlns="utcs">{
|
|
172
|
-
"""
|
|
173
|
-
segment=segment, major=projectMajor, minor=projectMinor
|
|
174
|
-
)
|
|
186
|
+
<getIHCProjectSegment2 xmlns="utcs">{project_major}</getIHCProjectSegment2>
|
|
187
|
+
<getIHCProjectSegment3 xmlns="utcs">{project_minor}</getIHCProjectSegment3>
|
|
188
|
+
"""
|
|
175
189
|
xdoc = self.connection.soap_action(
|
|
176
190
|
"/ws/ControllerService", "getIHCProjectSegment", payload
|
|
177
191
|
)
|
|
@@ -182,29 +196,22 @@ class IHCSoapClient:
|
|
|
182
196
|
).text
|
|
183
197
|
if not base64:
|
|
184
198
|
return False
|
|
185
|
-
|
|
186
|
-
return compresseddata
|
|
199
|
+
return base64.b64decode(base64data)
|
|
187
200
|
return False
|
|
188
201
|
|
|
189
202
|
def set_runtime_value_bool(self, resourceid: int, value: bool) -> bool:
|
|
190
|
-
"""Set a boolean runtime value"""
|
|
191
|
-
if value
|
|
192
|
-
|
|
193
|
-
else:
|
|
194
|
-
boolvalue = "false"
|
|
195
|
-
|
|
196
|
-
payload = """
|
|
203
|
+
"""Set a boolean runtime value."""
|
|
204
|
+
boolvalue = "true" if value else "false"
|
|
205
|
+
payload = f"""
|
|
197
206
|
<setResourceValue1 xmlns=\"utcs\"
|
|
198
207
|
xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">
|
|
199
208
|
<value i:type=\"a:WSBooleanValue\" xmlns:a=\"utcs.values\">
|
|
200
|
-
<a:value>{
|
|
209
|
+
<a:value>{boolvalue}</a:value></value>
|
|
201
210
|
<typeString/>
|
|
202
|
-
<resourceID>{
|
|
211
|
+
<resourceID>{resourceid}</resourceID>
|
|
203
212
|
<isValueRuntime>true</isValueRuntime>
|
|
204
213
|
</setResourceValue1>
|
|
205
|
-
"""
|
|
206
|
-
id=resourceid, value=boolvalue
|
|
207
|
-
)
|
|
214
|
+
"""
|
|
208
215
|
xdoc = self.connection.soap_action(
|
|
209
216
|
"/ws/ResourceInteractionService", "setResourceValue", payload
|
|
210
217
|
)
|
|
@@ -215,20 +222,18 @@ class IHCSoapClient:
|
|
|
215
222
|
return result == "true"
|
|
216
223
|
return False
|
|
217
224
|
|
|
218
|
-
def set_runtime_value_int(self, resourceid: int, intvalue: int):
|
|
219
|
-
"""Set a integer runtime value"""
|
|
220
|
-
payload = """
|
|
225
|
+
def set_runtime_value_int(self, resourceid: int, intvalue: int) -> bool:
|
|
226
|
+
"""Set a integer runtime value."""
|
|
227
|
+
payload = f"""
|
|
221
228
|
<setResourceValue1 xmlns=\"utcs\"
|
|
222
229
|
xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">
|
|
223
230
|
<value i:type=\"a:WSIntegerValue\" xmlns:a=\"utcs.values\">
|
|
224
|
-
<a:integer>{
|
|
231
|
+
<a:integer>{intvalue}</a:integer></value>
|
|
225
232
|
<typeString/>
|
|
226
|
-
<resourceID>{
|
|
233
|
+
<resourceID>{resourceid}</resourceID>
|
|
227
234
|
<isValueRuntime>true</isValueRuntime>
|
|
228
235
|
</setResourceValue1>
|
|
229
|
-
"""
|
|
230
|
-
id=resourceid, value=intvalue
|
|
231
|
-
)
|
|
236
|
+
"""
|
|
232
237
|
xdoc = self.connection.soap_action(
|
|
233
238
|
"/ws/ResourceInteractionService", "setResourceValue", payload
|
|
234
239
|
)
|
|
@@ -239,21 +244,19 @@ class IHCSoapClient:
|
|
|
239
244
|
return result == "true"
|
|
240
245
|
return False
|
|
241
246
|
|
|
242
|
-
def set_runtime_value_float(self, resourceid: int, floatvalue: float):
|
|
243
|
-
"""Set a flot runtime value"""
|
|
244
|
-
payload = """
|
|
247
|
+
def set_runtime_value_float(self, resourceid: int, floatvalue: float) -> bool:
|
|
248
|
+
"""Set a flot runtime value."""
|
|
249
|
+
payload = f"""
|
|
245
250
|
<setResourceValue1 xmlns=\"utcs\"
|
|
246
251
|
xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">
|
|
247
252
|
<value i:type=\"a:WSFloatingPointValue\" xmlns:a=\"utcs.values\">
|
|
248
|
-
<a:floatingPointValue>{
|
|
253
|
+
<a:floatingPointValue>{floatvalue}</a:floatingPointValue></value>
|
|
249
254
|
<typeString/>
|
|
250
|
-
<resourceID>{
|
|
255
|
+
<resourceID>{resourceid}</resourceID>
|
|
251
256
|
<isValueRuntime>true</isValueRuntime>
|
|
252
257
|
</setResourceValue1>
|
|
253
258
|
</s:Body>
|
|
254
|
-
"""
|
|
255
|
-
id=resourceid, value=floatvalue
|
|
256
|
-
)
|
|
259
|
+
"""
|
|
257
260
|
xdoc = self.connection.soap_action(
|
|
258
261
|
"/ws/ResourceInteractionService", "setResourceValue", payload
|
|
259
262
|
)
|
|
@@ -264,21 +267,19 @@ class IHCSoapClient:
|
|
|
264
267
|
return result == "true"
|
|
265
268
|
return False
|
|
266
269
|
|
|
267
|
-
def set_runtime_value_timer(self, resourceid: int, timer: int):
|
|
268
|
-
"""Set a timer runtime value in milliseconds"""
|
|
269
|
-
payload = """
|
|
270
|
+
def set_runtime_value_timer(self, resourceid: int, timer: int) -> bool:
|
|
271
|
+
"""Set a timer runtime value in milliseconds."""
|
|
272
|
+
payload = f"""
|
|
270
273
|
<setResourceValue1 xmlns=\"utcs\"
|
|
271
274
|
xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">
|
|
272
275
|
<value i:type=\"a:WSTimerValue\" xmlns:a=\"utcs.values\">
|
|
273
|
-
<a:milliseconds>{
|
|
276
|
+
<a:milliseconds>{timer}</a:milliseconds></value>
|
|
274
277
|
<typeString/>
|
|
275
|
-
<resourceID>{
|
|
278
|
+
<resourceID>{resourceid}</resourceID>
|
|
276
279
|
<isValueRuntime>true</isValueRuntime>
|
|
277
280
|
</setResourceValue1>
|
|
278
281
|
</s:Body>
|
|
279
|
-
"""
|
|
280
|
-
id=resourceid, value=timer
|
|
281
|
-
)
|
|
282
|
+
"""
|
|
282
283
|
xdoc = self.connection.soap_action(
|
|
283
284
|
"/ws/ResourceInteractionService", "setResourceValue", payload
|
|
284
285
|
)
|
|
@@ -291,8 +292,8 @@ class IHCSoapClient:
|
|
|
291
292
|
|
|
292
293
|
def set_runtime_value_time(
|
|
293
294
|
self, resourceid: int, hours: int, minutes: int, seconds: int
|
|
294
|
-
):
|
|
295
|
-
"""Set a time runtime value in hours:minutes:seconds"""
|
|
295
|
+
) -> bool:
|
|
296
|
+
"""Set a time runtime value in hours:minutes:seconds."""
|
|
296
297
|
payload = f"""
|
|
297
298
|
<setResourceValue1 xmlns=\"utcs\"
|
|
298
299
|
xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">
|
|
@@ -317,15 +318,16 @@ class IHCSoapClient:
|
|
|
317
318
|
return result == "true"
|
|
318
319
|
return False
|
|
319
320
|
|
|
320
|
-
|
|
321
|
-
|
|
321
|
+
@staticmethod
|
|
322
|
+
def _get_time(resource_value: ET.Element) -> datetime.time:
|
|
322
323
|
hours = int(resource_value.find("./ns2:hours", IHCSoapClient.ihcns).text)
|
|
323
324
|
minutes = int(resource_value.find("./ns2:minutes", IHCSoapClient.ihcns).text)
|
|
324
325
|
seconds = int(resource_value.find("./ns2:seconds", IHCSoapClient.ihcns).text)
|
|
325
326
|
|
|
326
327
|
return datetime.time(hours, minutes, seconds)
|
|
327
328
|
|
|
328
|
-
|
|
329
|
+
@staticmethod
|
|
330
|
+
def _get_datetime(resource_value: ET.Element) -> datetime.datetime:
|
|
329
331
|
year = int(resource_value.find("./ns1:year", IHCSoapClient.ihcns).text)
|
|
330
332
|
month = int(
|
|
331
333
|
resource_value.find("./ns1:monthWithJanuaryAsOne", IHCSoapClient.ihcns).text
|
|
@@ -334,18 +336,22 @@ class IHCSoapClient:
|
|
|
334
336
|
hours = int(resource_value.find("./ns1:hours", IHCSoapClient.ihcns).text)
|
|
335
337
|
minutes = int(resource_value.find("./ns1:minutes", IHCSoapClient.ihcns).text)
|
|
336
338
|
seconds = int(resource_value.find("./ns1:seconds", IHCSoapClient.ihcns).text)
|
|
337
|
-
return datetime.datetime(year, month, day, hours, minutes, seconds)
|
|
339
|
+
return datetime.datetime(year, month, day, hours, minutes, seconds) # noqa: DTZ001
|
|
338
340
|
|
|
339
|
-
|
|
341
|
+
@staticmethod
|
|
342
|
+
def _get_date(resource_value: ET.Element) -> datetime.datetime:
|
|
340
343
|
year = int(resource_value.find("./ns2:year", IHCSoapClient.ihcns).text)
|
|
341
344
|
if year == 0:
|
|
342
|
-
year = datetime.datetime.today().year
|
|
345
|
+
year = datetime.datetime.today().year # noqa: DTZ002
|
|
343
346
|
month = int(resource_value.find("./ns2:month", IHCSoapClient.ihcns).text)
|
|
344
347
|
day = int(resource_value.find("./ns2:day", IHCSoapClient.ihcns).text)
|
|
345
|
-
return datetime.datetime(year, month, day)
|
|
348
|
+
return datetime.datetime(year, month, day) # noqa: DTZ001
|
|
346
349
|
|
|
347
|
-
|
|
348
|
-
|
|
350
|
+
@staticmethod
|
|
351
|
+
def __get_value(
|
|
352
|
+
resource_value: ET.Element,
|
|
353
|
+
) -> bool | int | float | str | datetime.datetime | None:
|
|
354
|
+
"""Get a runtime value from the xml base on the type in the xml."""
|
|
349
355
|
if resource_value is None:
|
|
350
356
|
return None
|
|
351
357
|
valuetype = resource_value.attrib[
|
|
@@ -354,34 +360,50 @@ class IHCSoapClient:
|
|
|
354
360
|
result = resource_value.text
|
|
355
361
|
match valuetype:
|
|
356
362
|
case "WSBooleanValue":
|
|
357
|
-
result =
|
|
363
|
+
result = (
|
|
364
|
+
resource_value.find("./ns2:value", IHCSoapClient.ihcns).text
|
|
365
|
+
== "true"
|
|
366
|
+
)
|
|
358
367
|
case "WSIntegerValue":
|
|
359
|
-
result =
|
|
368
|
+
result = int(
|
|
369
|
+
resource_value.find("./ns2:integer", IHCSoapClient.ihcns).text
|
|
370
|
+
)
|
|
360
371
|
case "WSFloatingPointValue":
|
|
361
|
-
result = round(
|
|
372
|
+
result = round(
|
|
373
|
+
float(
|
|
374
|
+
resource_value.find(
|
|
375
|
+
"./ns2:floatingPointValue", IHCSoapClient.ihcns
|
|
376
|
+
).text
|
|
377
|
+
),
|
|
378
|
+
2,
|
|
379
|
+
)
|
|
362
380
|
case "WSEnumValue":
|
|
363
381
|
result = resource_value.find("./ns2:enumName", IHCSoapClient.ihcns).text
|
|
364
382
|
case "WSTimerValue":
|
|
365
|
-
return int(
|
|
383
|
+
return int(
|
|
384
|
+
resource_value.find("./ns2:milliseconds", IHCSoapClient.ihcns).text
|
|
385
|
+
)
|
|
366
386
|
case "WSTimeValue":
|
|
367
|
-
result = IHCSoapClient.
|
|
387
|
+
result = IHCSoapClient._get_time(resource_value)
|
|
368
388
|
case "WSDate":
|
|
369
|
-
result = IHCSoapClient.
|
|
389
|
+
result = IHCSoapClient._get_datetime(resource_value)
|
|
370
390
|
case "WSDateValue":
|
|
371
|
-
result = IHCSoapClient.
|
|
391
|
+
result = IHCSoapClient._get_date(resource_value)
|
|
372
392
|
case "int":
|
|
373
393
|
result = int(resource_value.text)
|
|
374
394
|
return result
|
|
375
395
|
|
|
376
|
-
def get_runtime_value(
|
|
377
|
-
|
|
396
|
+
def get_runtime_value(
|
|
397
|
+
self, resourceid: int
|
|
398
|
+
) -> bool | int | float | str | datetime.datetime | None:
|
|
399
|
+
"""
|
|
400
|
+
Get runtime value of specified resource id.
|
|
401
|
+
|
|
378
402
|
The returned value will be boolean, integer or float
|
|
379
403
|
Return None if resource cannot be found or on error
|
|
380
404
|
"""
|
|
381
|
-
payload = """<getRuntimeValue1 xmlns="utcs">{
|
|
382
|
-
"""
|
|
383
|
-
id=resourceid
|
|
384
|
-
)
|
|
405
|
+
payload = f"""<getRuntimeValue1 xmlns="utcs">{resourceid}</getRuntimeValue1>
|
|
406
|
+
"""
|
|
385
407
|
xdoc = self.connection.soap_action(
|
|
386
408
|
"/ws/ResourceInteractionService", "getResourceValue", payload
|
|
387
409
|
)
|
|
@@ -392,14 +414,17 @@ class IHCSoapClient:
|
|
|
392
414
|
)
|
|
393
415
|
return IHCSoapClient.__get_value(value)
|
|
394
416
|
|
|
395
|
-
def get_runtime_values(
|
|
396
|
-
|
|
397
|
-
|
|
417
|
+
def get_runtime_values(
|
|
418
|
+
self, resourceids: list[int]
|
|
419
|
+
) -> dict[int, Any] | Literal[False]:
|
|
398
420
|
"""
|
|
421
|
+
Get runtime values of specified resource ids.
|
|
399
422
|
|
|
423
|
+
Return None if resource cannot be found or on error
|
|
424
|
+
"""
|
|
400
425
|
idsarr = ""
|
|
401
426
|
for ihcid in resourceids:
|
|
402
|
-
idsarr += "<arrayItem>{
|
|
427
|
+
idsarr += f"<arrayItem>{ihcid}</arrayItem>"
|
|
403
428
|
payload = '<getRuntimeValues1 xmlns="utcs">' + idsarr + "</getRuntimeValues1>"
|
|
404
429
|
xdoc = self.connection.soap_action(
|
|
405
430
|
"/ws/ResourceInteractionService", "getResourceValues", payload
|
|
@@ -414,17 +439,19 @@ class IHCSoapClient:
|
|
|
414
439
|
ihcid = item.find("ns1:resourceID", IHCSoapClient.ihcns)
|
|
415
440
|
if ihcid is None:
|
|
416
441
|
continue
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
if
|
|
420
|
-
changes[int(ihcid.text)] =
|
|
442
|
+
resource_value = item.find("./ns1:value", IHCSoapClient.ihcns)
|
|
443
|
+
item_value = IHCSoapClient.__get_value(resource_value)
|
|
444
|
+
if item_value is not None:
|
|
445
|
+
changes[int(ihcid.text)] = item_value
|
|
421
446
|
return changes
|
|
422
447
|
|
|
423
|
-
def cycle_bool_value(self, resourceid: int):
|
|
424
|
-
"""
|
|
448
|
+
def cycle_bool_value(self, resourceid: int) -> bool | None:
|
|
449
|
+
"""
|
|
450
|
+
Turn a booelan resource On and back Off.
|
|
451
|
+
|
|
425
452
|
Return None if resource cannot be found or on error
|
|
426
453
|
"""
|
|
427
|
-
|
|
454
|
+
set_bool = (
|
|
428
455
|
"<arrayItem>"
|
|
429
456
|
'<value xsi:type="ns1:WSBooleanValue">'
|
|
430
457
|
"<ns1:value>{value}</ns1:value>"
|
|
@@ -436,8 +463,8 @@ class IHCSoapClient:
|
|
|
436
463
|
)
|
|
437
464
|
payload = (
|
|
438
465
|
'<setResourceValues1 xmlns="utcs" xmlns:ns1="utcs.values">'
|
|
439
|
-
+
|
|
440
|
-
+
|
|
466
|
+
+ set_bool.format(value="true", id=resourceid)
|
|
467
|
+
+ set_bool.format(value="false", id=resourceid)
|
|
441
468
|
+ "</setResourceValues1>"
|
|
442
469
|
)
|
|
443
470
|
xdoc = self.connection.soap_action(
|
|
@@ -447,53 +474,53 @@ class IHCSoapClient:
|
|
|
447
474
|
return None
|
|
448
475
|
return True
|
|
449
476
|
|
|
450
|
-
def enable_runtime_notification(self, resourceid: int):
|
|
451
|
-
"""Enable notification for specified resource id"""
|
|
477
|
+
def enable_runtime_notification(self, resourceid: int) -> bool:
|
|
478
|
+
"""Enable notification for specified resource id."""
|
|
452
479
|
return self.enable_runtime_notifications([resourceid])
|
|
453
480
|
|
|
454
|
-
def enable_runtime_notifications(self, resourceids):
|
|
455
|
-
"""Enable notification for specified resource ids"""
|
|
481
|
+
def enable_runtime_notifications(self, resourceids: list[int]) -> bool:
|
|
482
|
+
"""Enable notification for specified resource ids."""
|
|
456
483
|
idsarr = ""
|
|
457
484
|
for ihcid in resourceids:
|
|
458
|
-
idsarr += "<a:arrayItem>{
|
|
485
|
+
idsarr += f"<a:arrayItem>{ihcid}</a:arrayItem>"
|
|
459
486
|
|
|
460
|
-
payload = """<enableRuntimeValueNotifications1 xmlns=\"utcs\"
|
|
487
|
+
payload = f"""<enableRuntimeValueNotifications1 xmlns=\"utcs\"
|
|
461
488
|
xmlns:a=\"http://www.w3.org/2001/XMLSchema\"
|
|
462
489
|
xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">
|
|
463
|
-
{
|
|
490
|
+
{idsarr}
|
|
464
491
|
</enableRuntimeValueNotifications1>
|
|
465
|
-
"""
|
|
466
|
-
arr=idsarr
|
|
467
|
-
)
|
|
492
|
+
"""
|
|
468
493
|
xdoc = self.connection.soap_action(
|
|
469
494
|
"/ws/ResourceInteractionService", "enableRuntimeValueNotifications", payload
|
|
470
495
|
)
|
|
471
496
|
return xdoc is not False
|
|
472
497
|
|
|
473
|
-
def wait_for_resource_value_changes(
|
|
498
|
+
def wait_for_resource_value_changes(
|
|
499
|
+
self, wait: int = 10
|
|
500
|
+
) -> dict[int, str] | Literal[False]:
|
|
474
501
|
"""
|
|
475
|
-
Long polling for changes
|
|
476
|
-
|
|
502
|
+
Long polling for changes.
|
|
503
|
+
|
|
504
|
+
And return a dictionary with resource:value for changes (Only last change)
|
|
477
505
|
"""
|
|
478
506
|
change_list = self.wait_for_resource_value_change_list(wait)
|
|
479
507
|
if change_list is False:
|
|
480
508
|
return False
|
|
481
|
-
|
|
482
|
-
for id, value in change_list:
|
|
483
|
-
last_changes[id] = value
|
|
484
|
-
return last_changes
|
|
509
|
+
return dict(change_list)
|
|
485
510
|
|
|
486
|
-
def wait_for_resource_value_change_list(
|
|
511
|
+
def wait_for_resource_value_change_list(
|
|
512
|
+
self, wait: int = 10
|
|
513
|
+
) -> list[(int, Any)] | Literal[False]:
|
|
487
514
|
"""
|
|
488
|
-
Long polling for changes
|
|
515
|
+
Long polling for changes.
|
|
516
|
+
|
|
517
|
+
And return a resource id dictionary with a list of all changes since last poll.
|
|
489
518
|
Return a list of tuples with the id,value
|
|
490
519
|
"""
|
|
491
520
|
changes = []
|
|
492
|
-
payload = """<waitForResourceValueChanges1
|
|
493
|
-
xmlns=\"utcs\">{
|
|
494
|
-
"""
|
|
495
|
-
timeout=wait
|
|
496
|
-
)
|
|
521
|
+
payload = f"""<waitForResourceValueChanges1
|
|
522
|
+
xmlns=\"utcs\">{wait}</waitForResourceValueChanges1>
|
|
523
|
+
"""
|
|
497
524
|
xdoc = self.connection.soap_action(
|
|
498
525
|
"/ws/ResourceInteractionService", "getResourceValue", payload
|
|
499
526
|
)
|
|
@@ -514,14 +541,12 @@ class IHCSoapClient:
|
|
|
514
541
|
changes.append((int(ihcid.text), value))
|
|
515
542
|
return changes
|
|
516
543
|
|
|
517
|
-
def get_user_log(self, language="da"):
|
|
518
|
-
"""Get the controller state"""
|
|
519
|
-
payload = """<getUserLog1 xmlns="utcs" />
|
|
544
|
+
def get_user_log(self, language: str = "da") -> str | Literal[False]:
|
|
545
|
+
"""Get the controller state."""
|
|
546
|
+
payload = f"""<getUserLog1 xmlns="utcs" />
|
|
520
547
|
<getUserLog2 xmlns="utcs">0</getUserLog2>
|
|
521
548
|
<getUserLog3 xmlns="utcs">{language}</getUserLog3>
|
|
522
|
-
"""
|
|
523
|
-
language=language
|
|
524
|
-
)
|
|
549
|
+
"""
|
|
525
550
|
xdoc = self.connection.soap_action(
|
|
526
551
|
"/ws/ConfigurationService", "getUserLog", payload
|
|
527
552
|
)
|
|
@@ -534,50 +559,47 @@ class IHCSoapClient:
|
|
|
534
559
|
return base64.b64decode(base64data).decode("UTF-8")
|
|
535
560
|
return False
|
|
536
561
|
|
|
537
|
-
def clear_user_log(self):
|
|
538
|
-
"""Clear the user log in the controller"""
|
|
562
|
+
def clear_user_log(self) -> None:
|
|
563
|
+
"""Clear the user log in the controller."""
|
|
539
564
|
self.connection.soap_action("/ws/ConfigurationService", "clearUserLog", "")
|
|
540
|
-
return
|
|
541
565
|
|
|
542
|
-
def get_system_info(self):
|
|
543
|
-
"""Get controller system info"""
|
|
566
|
+
def get_system_info(self) -> dict[str, str] | bool:
|
|
567
|
+
"""Get controller system info."""
|
|
544
568
|
xdoc = self.connection.soap_action(
|
|
545
569
|
"/ws/ConfigurationService", "getSystemInfo", ""
|
|
546
570
|
)
|
|
547
571
|
if xdoc is False:
|
|
548
572
|
return False
|
|
549
|
-
|
|
550
|
-
"uptime": IHCSoapClient.
|
|
551
|
-
"realtimeclock": IHCSoapClient.
|
|
552
|
-
"serial_number": IHCSoapClient.
|
|
553
|
-
"production_date": IHCSoapClient.
|
|
554
|
-
"brand": IHCSoapClient.
|
|
555
|
-
"version": IHCSoapClient.
|
|
556
|
-
"hw_revision": IHCSoapClient.
|
|
557
|
-
"sw_date": IHCSoapClient.
|
|
558
|
-
"dataline_version": IHCSoapClient.
|
|
559
|
-
|
|
560
|
-
),
|
|
561
|
-
"rf_module_software_version": IHCSoapClient.__extract_sysinfo(
|
|
573
|
+
return {
|
|
574
|
+
"uptime": IHCSoapClient._extract_sysinfo(xdoc, "uptime"),
|
|
575
|
+
"realtimeclock": IHCSoapClient._extract_sysinfo(xdoc, "realtimeclock"),
|
|
576
|
+
"serial_number": IHCSoapClient._extract_sysinfo(xdoc, "serialNumber"),
|
|
577
|
+
"production_date": IHCSoapClient._extract_sysinfo(xdoc, "productionDate"),
|
|
578
|
+
"brand": IHCSoapClient._extract_sysinfo(xdoc, "brand"),
|
|
579
|
+
"version": IHCSoapClient._extract_sysinfo(xdoc, "version"),
|
|
580
|
+
"hw_revision": IHCSoapClient._extract_sysinfo(xdoc, "hwRevision"),
|
|
581
|
+
"sw_date": IHCSoapClient._extract_sysinfo(xdoc, "swDate"),
|
|
582
|
+
"dataline_version": IHCSoapClient._extract_sysinfo(xdoc, "datalineVersion"),
|
|
583
|
+
"rf_module_software_version": IHCSoapClient._extract_sysinfo(
|
|
562
584
|
xdoc, "rfModuleSoftwareVersion"
|
|
563
585
|
),
|
|
564
|
-
"rf_module_serial_number": IHCSoapClient.
|
|
586
|
+
"rf_module_serial_number": IHCSoapClient._extract_sysinfo(
|
|
565
587
|
xdoc, "rfModuleSerialNumber"
|
|
566
588
|
),
|
|
567
|
-
"application_is_without_viewer": IHCSoapClient.
|
|
589
|
+
"application_is_without_viewer": IHCSoapClient._extract_sysinfo(
|
|
568
590
|
xdoc, "applicationIsWithoutViewer"
|
|
569
591
|
),
|
|
570
|
-
"sms_modem_software_version": IHCSoapClient.
|
|
592
|
+
"sms_modem_software_version": IHCSoapClient._extract_sysinfo(
|
|
571
593
|
xdoc, "smsModemSoftwareVersion"
|
|
572
594
|
),
|
|
573
|
-
"led_dimmer_software_version": IHCSoapClient.
|
|
595
|
+
"led_dimmer_software_version": IHCSoapClient._extract_sysinfo(
|
|
574
596
|
xdoc, "ledDimmerSoftwareVersion"
|
|
575
597
|
),
|
|
576
598
|
}
|
|
577
|
-
return info
|
|
578
599
|
|
|
579
|
-
|
|
580
|
-
|
|
600
|
+
@staticmethod
|
|
601
|
+
def _extract_sysinfo(xdoc: ET.Element, param: str) -> str:
|
|
602
|
+
"""Extract a parameter from system info."""
|
|
581
603
|
element = xdoc.find(
|
|
582
604
|
f"./SOAP-ENV:Body/ns1:getSystemInfo1/ns1:{param}", IHCSoapClient.ihcns
|
|
583
605
|
)
|
ihcsdk/ihcconnection.py
CHANGED
|
@@ -1,19 +1,21 @@
|
|
|
1
|
-
"""Implements soap reqeust using the "requests" module"""
|
|
1
|
+
"""Implements soap reqeust using the "requests" module."""
|
|
2
2
|
|
|
3
|
-
# pylint: disable=too-few-public-methods
|
|
4
3
|
import logging
|
|
5
|
-
import
|
|
6
|
-
import xml.etree.ElementTree
|
|
7
|
-
|
|
4
|
+
import time
|
|
5
|
+
import xml.etree.ElementTree as ET
|
|
6
|
+
from http import HTTPStatus
|
|
7
|
+
from typing import Literal
|
|
8
8
|
from urllib.parse import urlparse
|
|
9
|
-
|
|
9
|
+
|
|
10
|
+
import requests
|
|
10
11
|
from requests.adapters import HTTPAdapter
|
|
12
|
+
from urllib3.util import Retry
|
|
11
13
|
|
|
12
14
|
_LOGGER = logging.getLogger(__name__)
|
|
13
15
|
|
|
14
16
|
|
|
15
|
-
class IHCConnection
|
|
16
|
-
"""Implements a http connection to the controller"""
|
|
17
|
+
class IHCConnection:
|
|
18
|
+
"""Implements a http connection to the controller."""
|
|
17
19
|
|
|
18
20
|
soapenvelope = """<?xml version=\"1.0\" encoding=\"UTF-8\"?>
|
|
19
21
|
<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\"
|
|
@@ -21,8 +23,8 @@ class IHCConnection(object):
|
|
|
21
23
|
xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">
|
|
22
24
|
<s:Body>{body}</s:Body></s:Envelope>"""
|
|
23
25
|
|
|
24
|
-
def __init__(self, url: str):
|
|
25
|
-
"""Initialize the IHCConnection with a url for the controller"""
|
|
26
|
+
def __init__(self, url: str) -> None:
|
|
27
|
+
"""Initialize the IHCConnection with a url for the controller."""
|
|
26
28
|
self.url = url
|
|
27
29
|
self.verify = False
|
|
28
30
|
self.last_exception = None
|
|
@@ -35,11 +37,23 @@ class IHCConnection(object):
|
|
|
35
37
|
allowed_methods={"POST"},
|
|
36
38
|
)
|
|
37
39
|
self.session.mount("http://", HTTPAdapter(max_retries=self.retries))
|
|
40
|
+
# default minimum time between calls in seconds (0 will not rate limit)
|
|
41
|
+
self.min_interval: float = 0.0
|
|
42
|
+
self.last_call_time: float = 0
|
|
43
|
+
self.logtiming = False
|
|
44
|
+
|
|
45
|
+
def close(self) -> None:
|
|
46
|
+
"""Close the connection."""
|
|
47
|
+
self.session.close()
|
|
48
|
+
self.session = None
|
|
38
49
|
|
|
39
|
-
def cert_verify(self):
|
|
50
|
+
def cert_verify(self) -> str | None:
|
|
51
|
+
"""Validate the certificate and return the cert file."""
|
|
40
52
|
return None
|
|
41
53
|
|
|
42
|
-
def soap_action(
|
|
54
|
+
def soap_action(
|
|
55
|
+
self, service: str, action: str, payloadbody: str
|
|
56
|
+
) -> ET.Element | Literal[False]:
|
|
43
57
|
"""Do a soap request."""
|
|
44
58
|
payload = self.soapenvelope.format(body=payloadbody).encode("utf-8")
|
|
45
59
|
headers = {
|
|
@@ -50,6 +64,7 @@ class IHCConnection(object):
|
|
|
50
64
|
"SOAPAction": action,
|
|
51
65
|
}
|
|
52
66
|
try:
|
|
67
|
+
self.rate_limit()
|
|
53
68
|
_LOGGER.debug("soap payload %s", payload)
|
|
54
69
|
self.last_exception = None
|
|
55
70
|
response = self.session.post(
|
|
@@ -59,19 +74,34 @@ class IHCConnection(object):
|
|
|
59
74
|
verify=self.cert_verify(),
|
|
60
75
|
)
|
|
61
76
|
_LOGGER.debug("soap request response status %d", response.status_code)
|
|
62
|
-
if response.status_code !=
|
|
77
|
+
if response.status_code != HTTPStatus.OK:
|
|
63
78
|
self.last_response = response
|
|
64
79
|
return False
|
|
65
80
|
_LOGGER.debug("soap request response %s", response.text)
|
|
66
|
-
xdoc =
|
|
81
|
+
xdoc = ET.fromstring(response.text) # noqa: S314
|
|
67
82
|
if xdoc is None:
|
|
68
83
|
return False
|
|
69
|
-
return xdoc
|
|
70
84
|
except requests.exceptions.RequestException as exp:
|
|
71
|
-
_LOGGER.
|
|
85
|
+
_LOGGER.exception("soap request exception")
|
|
72
86
|
self.last_exception = exp
|
|
73
|
-
except
|
|
74
|
-
_LOGGER.
|
|
87
|
+
except ET.ParseError as exp:
|
|
88
|
+
_LOGGER.exception("soap request xml parse erro")
|
|
75
89
|
self.last_exception = exp
|
|
76
90
|
self.last_response = response
|
|
91
|
+
else:
|
|
92
|
+
return xdoc
|
|
77
93
|
return False
|
|
94
|
+
|
|
95
|
+
def rate_limit(self) -> None:
|
|
96
|
+
"""Rate limit the calls to this function."""
|
|
97
|
+
current_time: float = time.time()
|
|
98
|
+
time_since_last_call: float = current_time - self.last_call_time
|
|
99
|
+
if self.logtiming:
|
|
100
|
+
_LOGGER.warning("time since last call %f sec", time_since_last_call)
|
|
101
|
+
# If not enough time has passed, sleep for the remaining time
|
|
102
|
+
if time_since_last_call < self.min_interval:
|
|
103
|
+
sleep_time: float = self.min_interval - time_since_last_call
|
|
104
|
+
_LOGGER.debug("Ratelimiting for %f sec", sleep_time)
|
|
105
|
+
time.sleep(sleep_time)
|
|
106
|
+
# Update the last call time and call the function
|
|
107
|
+
self.last_call_time = time.time()
|
ihcsdk/ihccontroller.py
CHANGED
|
@@ -1,28 +1,36 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Wraps the ihcclient in a more user friendly interface to handle lost connection
|
|
2
|
+
Wraps the ihcclient in a more user friendly interface to handle lost connection.
|
|
3
|
+
|
|
3
4
|
Notify thread to handle change notifications
|
|
4
5
|
"""
|
|
5
6
|
|
|
6
7
|
# pylint: disable=invalid-name, bare-except, too-many-instance-attributes
|
|
7
|
-
from datetime import datetime, timedelta
|
|
8
8
|
import logging
|
|
9
|
-
import requests
|
|
10
9
|
import threading
|
|
11
10
|
import time
|
|
12
|
-
from
|
|
11
|
+
from collections.abc import Callable
|
|
12
|
+
from datetime import datetime, timedelta
|
|
13
|
+
from http import HTTPStatus
|
|
14
|
+
from typing import Any, Literal
|
|
15
|
+
|
|
16
|
+
import requests
|
|
17
|
+
|
|
18
|
+
from ihcsdk.ihcclient import IHCSTATE_READY, IHCSoapClient
|
|
13
19
|
|
|
14
20
|
_LOGGER = logging.getLogger(__name__)
|
|
15
21
|
|
|
16
22
|
|
|
17
23
|
class IHCController:
|
|
18
24
|
"""
|
|
19
|
-
Implements the notification thread
|
|
25
|
+
Implements the notification thread.
|
|
26
|
+
|
|
20
27
|
will re-authenticate if needed.
|
|
21
28
|
"""
|
|
22
29
|
|
|
23
30
|
_mutex = threading.Lock()
|
|
24
31
|
|
|
25
|
-
def __init__(self, url: str, username: str, password: str):
|
|
32
|
+
def __init__(self, url: str, username: str, password: str) -> None:
|
|
33
|
+
"""Initialize the IHC controller with connection data."""
|
|
26
34
|
self.client = IHCSoapClient(url)
|
|
27
35
|
self.reauthenticatetimeout = 30
|
|
28
36
|
self.retryinterval = 10
|
|
@@ -35,26 +43,26 @@ class IHCController:
|
|
|
35
43
|
self._newnotifyids = []
|
|
36
44
|
self._project = None
|
|
37
45
|
|
|
46
|
+
@staticmethod
|
|
38
47
|
def is_ihc_controller(url: str) -> bool:
|
|
39
48
|
"""Will return True if the url respods like an IHC controller."""
|
|
40
49
|
try:
|
|
41
50
|
client = IHCSoapClient(url)
|
|
42
51
|
response = client.connection.session.get(
|
|
43
|
-
f"{url}/wsdl/controller.wsdl", verify=
|
|
52
|
+
f"{url}/wsdl/controller.wsdl", verify=False
|
|
44
53
|
)
|
|
45
|
-
|
|
54
|
+
client.close()
|
|
55
|
+
if response.status_code != HTTPStatus.OK:
|
|
46
56
|
return False
|
|
47
57
|
if not response.headers["content-type"].startswith("text/xml"):
|
|
48
58
|
return False
|
|
49
|
-
|
|
50
|
-
return False
|
|
51
|
-
return True
|
|
59
|
+
return not response.text.find("getIHCProject") < 0
|
|
52
60
|
except requests.exceptions.RequestException as exp:
|
|
53
61
|
_LOGGER.warning("is_ihc_controller %s", exp)
|
|
54
62
|
return False
|
|
55
63
|
|
|
56
64
|
def authenticate(self) -> bool:
|
|
57
|
-
"""Authenticate and enable the registered notifications"""
|
|
65
|
+
"""Authenticate and enable the registered notifications."""
|
|
58
66
|
with IHCController._mutex:
|
|
59
67
|
_LOGGER.debug("Authenticating login on ihc controller")
|
|
60
68
|
if not self.client.authenticate(self._username, self._password):
|
|
@@ -65,30 +73,34 @@ class IHCController:
|
|
|
65
73
|
self.client.enable_runtime_notifications(self._ihcevents.keys())
|
|
66
74
|
return True
|
|
67
75
|
|
|
68
|
-
def disconnect(self):
|
|
69
|
-
"""Disconnect by stopping the notification thread
|
|
70
|
-
TODO call disconnect on ihcclient
|
|
71
|
-
"""
|
|
76
|
+
def disconnect(self) -> None:
|
|
77
|
+
"""Disconnect by stopping the notification thread. And closing the client."""
|
|
72
78
|
self._notifyrunning = False
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
79
|
+
# wait for notify thread to finish
|
|
80
|
+
while self._notifythread.is_alive():
|
|
81
|
+
time.sleep(0.1) # Optional sleep to prevent busy waiting
|
|
82
|
+
self.client.close()
|
|
83
|
+
|
|
84
|
+
def get_runtime_value(
|
|
85
|
+
self, ihcid: int
|
|
86
|
+
) -> bool | int | float | str | datetime | None:
|
|
87
|
+
"""Get runtime value with re-authenticate if needed."""
|
|
76
88
|
value = self.client.get_runtime_value(ihcid)
|
|
77
89
|
if value is not None:
|
|
78
90
|
return value
|
|
79
91
|
self.re_authenticate()
|
|
80
92
|
return self.client.get_runtime_value(ihcid)
|
|
81
93
|
|
|
82
|
-
def get_runtime_values(self, ihcids):
|
|
83
|
-
"""Get runtime value with re-authenticate if needed"""
|
|
94
|
+
def get_runtime_values(self, ihcids: list[int]) -> dict[int, Any] | Literal[False]:
|
|
95
|
+
"""Get runtime value with re-authenticate if needed."""
|
|
84
96
|
value = self.client.get_runtime_values(ihcids)
|
|
85
97
|
if value is not None:
|
|
86
98
|
return value
|
|
87
99
|
self.re_authenticate()
|
|
88
100
|
return self.client.get_runtime_values(ihcids)
|
|
89
101
|
|
|
90
|
-
def cycle_bool_value(self, resourceid: int):
|
|
91
|
-
"""Turn a booelan resource On and back Off"""
|
|
102
|
+
def cycle_bool_value(self, resourceid: int) -> bool | None:
|
|
103
|
+
"""Turn a booelan resource On and back Off."""
|
|
92
104
|
value = self.client.cycle_bool_value(resourceid)
|
|
93
105
|
if value is not None:
|
|
94
106
|
return value
|
|
@@ -96,28 +108,28 @@ class IHCController:
|
|
|
96
108
|
return self.client.cycle_bool_value(resourceid)
|
|
97
109
|
|
|
98
110
|
def set_runtime_value_bool(self, ihcid: int, value: bool) -> bool:
|
|
99
|
-
"""Set bool runtime value with re-authenticate if needed"""
|
|
111
|
+
"""Set bool runtime value with re-authenticate if needed."""
|
|
100
112
|
if self.client.set_runtime_value_bool(ihcid, value):
|
|
101
113
|
return True
|
|
102
114
|
self.re_authenticate()
|
|
103
115
|
return self.client.set_runtime_value_bool(ihcid, value)
|
|
104
116
|
|
|
105
117
|
def set_runtime_value_int(self, ihcid: int, value: int) -> bool:
|
|
106
|
-
"""Set integer runtime value with re-authenticate if needed"""
|
|
118
|
+
"""Set integer runtime value with re-authenticate if needed."""
|
|
107
119
|
if self.client.set_runtime_value_int(ihcid, value):
|
|
108
120
|
return True
|
|
109
121
|
self.re_authenticate()
|
|
110
122
|
return self.client.set_runtime_value_int(ihcid, value)
|
|
111
123
|
|
|
112
124
|
def set_runtime_value_float(self, ihcid: int, value: float) -> bool:
|
|
113
|
-
"""Set float runtime value with re-authenticate if needed"""
|
|
125
|
+
"""Set float runtime value with re-authenticate if needed."""
|
|
114
126
|
if self.client.set_runtime_value_float(ihcid, value):
|
|
115
127
|
return True
|
|
116
128
|
self.re_authenticate()
|
|
117
129
|
return self.client.set_runtime_value_float(ihcid, value)
|
|
118
130
|
|
|
119
131
|
def set_runtime_value_timer(self, ihcid: int, value: int) -> bool:
|
|
120
|
-
"""Set timer runtime value with re-authenticate if needed"""
|
|
132
|
+
"""Set timer runtime value with re-authenticate if needed."""
|
|
121
133
|
if self.client.set_runtime_value_timer(ihcid, value):
|
|
122
134
|
return True
|
|
123
135
|
self.re_authenticate()
|
|
@@ -126,14 +138,14 @@ class IHCController:
|
|
|
126
138
|
def set_runtime_value_time(
|
|
127
139
|
self, ihcid: int, hours: int, minutes: int, seconds: int
|
|
128
140
|
) -> bool:
|
|
129
|
-
"""Set time runtime value with re-authenticate if needed"""
|
|
141
|
+
"""Set time runtime value with re-authenticate if needed."""
|
|
130
142
|
if self.client.set_runtime_value_time(ihcid, hours, minutes):
|
|
131
143
|
return True
|
|
132
144
|
self.re_authenticate()
|
|
133
145
|
return self.client.set_runtime_value_time(ihcid, hours, minutes, seconds)
|
|
134
146
|
|
|
135
147
|
def get_project(self, insegments: bool = True) -> str:
|
|
136
|
-
"""Get the ihc project and make sure controller is ready before"""
|
|
148
|
+
"""Get the ihc project and make sure controller is ready before."""
|
|
137
149
|
with IHCController._mutex:
|
|
138
150
|
if self._project is None:
|
|
139
151
|
if self.client.get_state() != IHCSTATE_READY:
|
|
@@ -146,8 +158,15 @@ class IHCController:
|
|
|
146
158
|
self._project = self.client.get_project()
|
|
147
159
|
return self._project
|
|
148
160
|
|
|
149
|
-
def add_notify_event(
|
|
150
|
-
|
|
161
|
+
def add_notify_event(
|
|
162
|
+
self,
|
|
163
|
+
resourceid: int,
|
|
164
|
+
callback: Callable[[int, bool | float | str | datetime], None],
|
|
165
|
+
delayed: bool = False,
|
|
166
|
+
) -> bool:
|
|
167
|
+
"""
|
|
168
|
+
Add a notify callback for a specified resource id.
|
|
169
|
+
|
|
151
170
|
If delayed is set to true the enable request will be send from the
|
|
152
171
|
notofication thread
|
|
153
172
|
"""
|
|
@@ -158,17 +177,16 @@ class IHCController:
|
|
|
158
177
|
self._ihcevents[resourceid] = [callback]
|
|
159
178
|
if delayed:
|
|
160
179
|
self._newnotifyids.append(resourceid)
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
return False
|
|
180
|
+
elif not self.client.enable_runtime_notification(resourceid):
|
|
181
|
+
return False
|
|
164
182
|
if not self._notifyrunning:
|
|
165
183
|
self._notifyrunning = True
|
|
166
184
|
self._notifythread.start()
|
|
167
185
|
|
|
168
186
|
return True
|
|
169
187
|
|
|
170
|
-
def _notify_fn(self):
|
|
171
|
-
"""
|
|
188
|
+
def _notify_fn(self) -> None:
|
|
189
|
+
"""Notify thread function."""
|
|
172
190
|
_LOGGER.debug("Starting notify thread")
|
|
173
191
|
while self._notifyrunning:
|
|
174
192
|
try:
|
|
@@ -180,7 +198,7 @@ class IHCController:
|
|
|
180
198
|
|
|
181
199
|
changes = self.client.wait_for_resource_value_change_list()
|
|
182
200
|
if changes is False:
|
|
183
|
-
self.re_authenticate(True)
|
|
201
|
+
self.re_authenticate(notify=True)
|
|
184
202
|
continue
|
|
185
203
|
for ihcid, value in changes:
|
|
186
204
|
if ihcid in self._ihcevents:
|
|
@@ -191,32 +209,33 @@ class IHCController:
|
|
|
191
209
|
):
|
|
192
210
|
callback(ihcid, value)
|
|
193
211
|
self._ihcvalues[ihcid] = value
|
|
194
|
-
except Exception
|
|
195
|
-
_LOGGER.
|
|
196
|
-
self.re_authenticate(True)
|
|
212
|
+
except Exception:
|
|
213
|
+
_LOGGER.exception("Exception in notify thread")
|
|
214
|
+
self.re_authenticate(notify=True)
|
|
197
215
|
|
|
198
216
|
def re_authenticate(self, notify: bool = False) -> bool:
|
|
199
|
-
"""
|
|
217
|
+
"""
|
|
218
|
+
Authenticate again after failure.
|
|
219
|
+
|
|
200
220
|
Keep trying with 10 sec interval. If called from the notify thread
|
|
201
221
|
we will not have a timeout, but will end if the notify thread has
|
|
202
222
|
been cancled.
|
|
203
223
|
Will return True if authentication was successful.
|
|
204
224
|
"""
|
|
205
|
-
timeout = datetime.now() + timedelta(seconds=self.reauthenticatetimeout)
|
|
225
|
+
timeout = datetime.now() + timedelta(seconds=self.reauthenticatetimeout) # noqa: DTZ005
|
|
206
226
|
while True:
|
|
207
227
|
_LOGGER.debug("Reauthenticating login on ihc controller")
|
|
208
228
|
if self.authenticate():
|
|
209
229
|
return True
|
|
210
230
|
_LOGGER.debug(
|
|
211
|
-
"Authenticate failed
|
|
231
|
+
"Authenticate failed, reauthenticating login on ihc controller in 10s"
|
|
212
232
|
)
|
|
213
233
|
|
|
214
234
|
# if called from the notify and notify a cancled we do not want to retry
|
|
215
235
|
if notify:
|
|
216
236
|
if not self._notifyrunning:
|
|
217
237
|
return False
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
return False
|
|
238
|
+
elif timeout and datetime.now() > timeout: # noqa: DTZ005
|
|
239
|
+
return False
|
|
221
240
|
# wait before we try to authenticate again
|
|
222
241
|
time.sleep(self.retryinterval)
|
ihcsdk/ihcsslconnection.py
CHANGED
|
@@ -1,50 +1,50 @@
|
|
|
1
|
-
"""Implements soap reqeust using the "requests" module"""
|
|
1
|
+
"""Implements soap reqeust using the "requests" module."""
|
|
2
2
|
|
|
3
3
|
# pylint: disable=too-few-public-methods
|
|
4
4
|
import os
|
|
5
|
-
import requests
|
|
6
5
|
|
|
7
|
-
|
|
6
|
+
import requests
|
|
8
7
|
from cryptography.hazmat.backends import default_backend
|
|
9
8
|
from cryptography.hazmat.primitives import hashes
|
|
9
|
+
from cryptography.x509 import load_pem_x509_certificate
|
|
10
10
|
from requests.packages.urllib3.util.ssl_ import create_urllib3_context
|
|
11
11
|
|
|
12
12
|
from ihcsdk.ihcconnection import IHCConnection
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
class IHCSSLConnection(IHCConnection):
|
|
16
|
-
"""Implements a https connection to the controller"""
|
|
16
|
+
"""Implements a https connection to the controller."""
|
|
17
17
|
|
|
18
|
-
def __init__(self, url: str):
|
|
19
|
-
"""Initialize the IHCSSLConnection with a url for the controller"""
|
|
20
|
-
super(
|
|
18
|
+
def __init__(self, url: str) -> None:
|
|
19
|
+
"""Initialize the IHCSSLConnection with a url for the controller."""
|
|
20
|
+
super().__init__(url)
|
|
21
21
|
self.cert_file = os.path.dirname(__file__) + "/certs/ihc3.crt"
|
|
22
22
|
self.session.mount(
|
|
23
23
|
"https://",
|
|
24
24
|
CertAdapter(self.get_fingerprint_from_cert(), max_retries=self.retries),
|
|
25
25
|
)
|
|
26
26
|
|
|
27
|
-
def get_fingerprint_from_cert(self):
|
|
28
|
-
"""Get the fingerprint from the certificate"""
|
|
27
|
+
def get_fingerprint_from_cert(self) -> str:
|
|
28
|
+
"""Get the fingerprint from the certificate."""
|
|
29
29
|
pem = open(self.cert_file, "rb").read()
|
|
30
30
|
cert = load_pem_x509_certificate(pem, default_backend())
|
|
31
31
|
f = cert.fingerprint(hashes.SHA1())
|
|
32
32
|
return "".join("{:02x}".format(x) for x in f)
|
|
33
33
|
|
|
34
|
-
def cert_verify(self):
|
|
34
|
+
def cert_verify(self) -> str:
|
|
35
35
|
return self.cert_file
|
|
36
36
|
|
|
37
37
|
|
|
38
38
|
class CertAdapter(requests.adapters.HTTPAdapter):
|
|
39
|
-
"""A adapter for a specific certificate"""
|
|
39
|
+
"""A adapter for a specific certificate."""
|
|
40
40
|
|
|
41
|
-
def __init__(self, fingerprint, **kwargs):
|
|
42
|
-
"""
|
|
41
|
+
def __init__(self, fingerprint, **kwargs) -> None:
|
|
42
|
+
"""Store the fingerprint for use when creating the poolmanager."""
|
|
43
43
|
self.fingerprint = fingerprint
|
|
44
|
-
super(
|
|
44
|
+
super().__init__(**kwargs)
|
|
45
45
|
|
|
46
46
|
def init_poolmanager(self, connections, maxsize, block=False, **pool_kwargs):
|
|
47
|
-
"""Create a custom poolmanager"""
|
|
47
|
+
"""Create a custom poolmanager."""
|
|
48
48
|
CIPHERS = "DEFAULT:!DH"
|
|
49
49
|
context = create_urllib3_context(ciphers=CIPHERS)
|
|
50
50
|
pool_kwargs["ssl_context"] = context
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: ihcsdk
|
|
3
|
-
Version: 2.8.
|
|
3
|
+
Version: 2.8.11
|
|
4
4
|
Summary: IHC Python SDK
|
|
5
5
|
Home-page: https://github.com/dingusdk/PythonIhcSdk
|
|
6
6
|
Author: Jens Nielsen
|
|
@@ -8,5 +8,12 @@ License: GPL-3.0
|
|
|
8
8
|
License-File: license.txt
|
|
9
9
|
Requires-Dist: requests
|
|
10
10
|
Requires-Dist: cryptography
|
|
11
|
+
Dynamic: author
|
|
12
|
+
Dynamic: description
|
|
13
|
+
Dynamic: home-page
|
|
14
|
+
Dynamic: license
|
|
15
|
+
Dynamic: license-file
|
|
16
|
+
Dynamic: requires-dist
|
|
17
|
+
Dynamic: summary
|
|
11
18
|
|
|
12
19
|
SDK for connection to the LK IHC Controller. Made for interfacing to home assistant
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
ihcsdk/__init__.py,sha256=IFv4tTt5vulfQlTqcok2IJA8ms3rE7XR7WIsRM2az9g,28
|
|
2
|
+
ihcsdk/ihcclient.py,sha256=NyTKFaF_jz5x1_fz1bHna22ke7KRm4DukMqn3aE70nM,24970
|
|
3
|
+
ihcsdk/ihcconnection.py,sha256=ISruHcCqusn5KCVSBwE8LNJS-v0aIw89WPCxAQuIk4w,4133
|
|
4
|
+
ihcsdk/ihccontroller.py,sha256=wb-7y5J0arNrlCMi35I6CLYwtKgcJvLNmzaABHqW3Aw,9767
|
|
5
|
+
ihcsdk/ihcsslconnection.py,sha256=OjGo8eJGHFnhVc9nz0Krf6mCCVrOOWj9BpTDOTtxKqk,2063
|
|
6
|
+
ihcsdk/certs/ihc.crt,sha256=VYY_DiHrctlXBTNXGdJ2FN4TYuVnwnpVZ1i8t2_0cec,1002
|
|
7
|
+
ihcsdk/certs/ihc3.crt,sha256=Ka2L8zQ06A76W6fZc0ckScdrj1Cn-mhgqdhc61-cCrk,1398
|
|
8
|
+
ihcsdk-2.8.11.dist-info/licenses/license.txt,sha256=jOtLnuWt7d5Hsx6XXB2QxzrSe2sWWh3NgMfFRetluQM,35147
|
|
9
|
+
ihcsdk-2.8.11.dist-info/METADATA,sha256=qUKqMaLYOBUsmAhn7jIvlGydjIqFz6Ohu_63qUiaG9E,482
|
|
10
|
+
ihcsdk-2.8.11.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
11
|
+
ihcsdk-2.8.11.dist-info/top_level.txt,sha256=QgKE7TWblC-uXe5-7MVO5JtzvrPiMoVmGgKKsVR43hU,7
|
|
12
|
+
ihcsdk-2.8.11.dist-info/RECORD,,
|
ihcsdk-2.8.8.dist-info/RECORD
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
ihcsdk/__init__.py,sha256=ocAoa28ZKxGgMlDEHWEsd7n00OtGB0R9N7XQfRlih9Q,28
|
|
2
|
-
ihcsdk/ihcclient.py,sha256=LzcO0ZfIqfdQxF3TzoIfo2jVBU2u3QoZeZA4Fj3psqc,24302
|
|
3
|
-
ihcsdk/ihcconnection.py,sha256=qCFhU4cAWYnqgiXWF-xjUZj1TMBZ83caDj8jEE1qaoc,2889
|
|
4
|
-
ihcsdk/ihccontroller.py,sha256=9D97RX8DrFMLMxDBcIrhoYY-QEeKNjbVv_PiRW0wNyU,9180
|
|
5
|
-
ihcsdk/ihcsslconnection.py,sha256=q7gDchCN2_SXItp2T3dMvlXmHC7zVO5M0bqGnepiKiU,2079
|
|
6
|
-
ihcsdk/certs/ihc.crt,sha256=VYY_DiHrctlXBTNXGdJ2FN4TYuVnwnpVZ1i8t2_0cec,1002
|
|
7
|
-
ihcsdk/certs/ihc3.crt,sha256=Ka2L8zQ06A76W6fZc0ckScdrj1Cn-mhgqdhc61-cCrk,1398
|
|
8
|
-
ihcsdk-2.8.8.dist-info/METADATA,sha256=I2qpZJjWGY0Yad46vDMjNBljsRURFsopM0Ep8vgrn24,339
|
|
9
|
-
ihcsdk-2.8.8.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
10
|
-
ihcsdk-2.8.8.dist-info/license.txt,sha256=jOtLnuWt7d5Hsx6XXB2QxzrSe2sWWh3NgMfFRetluQM,35147
|
|
11
|
-
ihcsdk-2.8.8.dist-info/top_level.txt,sha256=QgKE7TWblC-uXe5-7MVO5JtzvrPiMoVmGgKKsVR43hU,7
|
|
12
|
-
ihcsdk-2.8.8.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|