ihcsdk 2.8.7__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 +213 -191
- ihcsdk/ihcconnection.py +66 -27
- ihcsdk/ihccontroller.py +65 -46
- ihcsdk/ihcsslconnection.py +20 -17
- {ihcsdk-2.8.7.dist-info → ihcsdk-2.8.11.dist-info}/METADATA +9 -2
- ihcsdk-2.8.11.dist-info/RECORD +12 -0
- {ihcsdk-2.8.7.dist-info → ihcsdk-2.8.11.dist-info}/WHEEL +1 -1
- ihcsdk-2.8.7.dist-info/RECORD +0 -12
- {ihcsdk-2.8.7.dist-info → ihcsdk-2.8.11.dist-info/licenses}/license.txt +0 -0
- {ihcsdk-2.8.7.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,25 +117,31 @@ 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
|
-
|
|
134
|
+
segment = self.get_project_segment(s, project_major, project_minor)
|
|
135
|
+
if segment is False:
|
|
136
|
+
return False
|
|
137
|
+
buffer.write(segment)
|
|
124
138
|
return zlib.decompress(buffer.getvalue(), 16 + zlib.MAX_WBITS).decode(
|
|
125
139
|
"ISO-8859-1"
|
|
126
140
|
)
|
|
127
141
|
return False
|
|
128
142
|
|
|
129
|
-
def get_project_info(self) -> dict:
|
|
130
|
-
"""
|
|
143
|
+
def get_project_info(self) -> dict[str, Any]:
|
|
144
|
+
"""Return dictionary of project info items."""
|
|
131
145
|
xdoc = self.connection.soap_action(
|
|
132
146
|
"/ws/ControllerService", "getProjectInfo", ""
|
|
133
147
|
)
|
|
@@ -142,7 +156,7 @@ class IHCSoapClient:
|
|
|
142
156
|
return False
|
|
143
157
|
|
|
144
158
|
def get_project_number_of_segments(self) -> int:
|
|
145
|
-
"""
|
|
159
|
+
"""Return the number of segments needed to fetch the current ihc-project."""
|
|
146
160
|
xdoc = self.connection.soap_action(
|
|
147
161
|
"/ws/ControllerService", "getIHCProjectNumberOfSegments", ""
|
|
148
162
|
)
|
|
@@ -155,20 +169,23 @@ class IHCSoapClient:
|
|
|
155
169
|
)
|
|
156
170
|
return False
|
|
157
171
|
|
|
158
|
-
def get_project_segment(
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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.
|
|
164
183
|
"""
|
|
165
|
-
payload = """
|
|
184
|
+
payload = f"""
|
|
166
185
|
<getIHCProjectSegment1 xmlns="utcs">{segment}</getIHCProjectSegment1>
|
|
167
|
-
<getIHCProjectSegment2 xmlns="utcs">{
|
|
168
|
-
<getIHCProjectSegment3 xmlns="utcs">{
|
|
169
|
-
"""
|
|
170
|
-
segment=segment, major=projectMajor, minor=projectMinor
|
|
171
|
-
)
|
|
186
|
+
<getIHCProjectSegment2 xmlns="utcs">{project_major}</getIHCProjectSegment2>
|
|
187
|
+
<getIHCProjectSegment3 xmlns="utcs">{project_minor}</getIHCProjectSegment3>
|
|
188
|
+
"""
|
|
172
189
|
xdoc = self.connection.soap_action(
|
|
173
190
|
"/ws/ControllerService", "getIHCProjectSegment", payload
|
|
174
191
|
)
|
|
@@ -179,29 +196,22 @@ class IHCSoapClient:
|
|
|
179
196
|
).text
|
|
180
197
|
if not base64:
|
|
181
198
|
return False
|
|
182
|
-
|
|
183
|
-
return compresseddata
|
|
199
|
+
return base64.b64decode(base64data)
|
|
184
200
|
return False
|
|
185
201
|
|
|
186
202
|
def set_runtime_value_bool(self, resourceid: int, value: bool) -> bool:
|
|
187
|
-
"""Set a boolean runtime value"""
|
|
188
|
-
if value
|
|
189
|
-
|
|
190
|
-
else:
|
|
191
|
-
boolvalue = "false"
|
|
192
|
-
|
|
193
|
-
payload = """
|
|
203
|
+
"""Set a boolean runtime value."""
|
|
204
|
+
boolvalue = "true" if value else "false"
|
|
205
|
+
payload = f"""
|
|
194
206
|
<setResourceValue1 xmlns=\"utcs\"
|
|
195
207
|
xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">
|
|
196
208
|
<value i:type=\"a:WSBooleanValue\" xmlns:a=\"utcs.values\">
|
|
197
|
-
<a:value>{
|
|
209
|
+
<a:value>{boolvalue}</a:value></value>
|
|
198
210
|
<typeString/>
|
|
199
|
-
<resourceID>{
|
|
211
|
+
<resourceID>{resourceid}</resourceID>
|
|
200
212
|
<isValueRuntime>true</isValueRuntime>
|
|
201
213
|
</setResourceValue1>
|
|
202
|
-
"""
|
|
203
|
-
id=resourceid, value=boolvalue
|
|
204
|
-
)
|
|
214
|
+
"""
|
|
205
215
|
xdoc = self.connection.soap_action(
|
|
206
216
|
"/ws/ResourceInteractionService", "setResourceValue", payload
|
|
207
217
|
)
|
|
@@ -212,20 +222,18 @@ class IHCSoapClient:
|
|
|
212
222
|
return result == "true"
|
|
213
223
|
return False
|
|
214
224
|
|
|
215
|
-
def set_runtime_value_int(self, resourceid: int, intvalue: int):
|
|
216
|
-
"""Set a integer runtime value"""
|
|
217
|
-
payload = """
|
|
225
|
+
def set_runtime_value_int(self, resourceid: int, intvalue: int) -> bool:
|
|
226
|
+
"""Set a integer runtime value."""
|
|
227
|
+
payload = f"""
|
|
218
228
|
<setResourceValue1 xmlns=\"utcs\"
|
|
219
229
|
xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">
|
|
220
230
|
<value i:type=\"a:WSIntegerValue\" xmlns:a=\"utcs.values\">
|
|
221
|
-
<a:integer>{
|
|
231
|
+
<a:integer>{intvalue}</a:integer></value>
|
|
222
232
|
<typeString/>
|
|
223
|
-
<resourceID>{
|
|
233
|
+
<resourceID>{resourceid}</resourceID>
|
|
224
234
|
<isValueRuntime>true</isValueRuntime>
|
|
225
235
|
</setResourceValue1>
|
|
226
|
-
"""
|
|
227
|
-
id=resourceid, value=intvalue
|
|
228
|
-
)
|
|
236
|
+
"""
|
|
229
237
|
xdoc = self.connection.soap_action(
|
|
230
238
|
"/ws/ResourceInteractionService", "setResourceValue", payload
|
|
231
239
|
)
|
|
@@ -236,21 +244,19 @@ class IHCSoapClient:
|
|
|
236
244
|
return result == "true"
|
|
237
245
|
return False
|
|
238
246
|
|
|
239
|
-
def set_runtime_value_float(self, resourceid: int, floatvalue: float):
|
|
240
|
-
"""Set a flot runtime value"""
|
|
241
|
-
payload = """
|
|
247
|
+
def set_runtime_value_float(self, resourceid: int, floatvalue: float) -> bool:
|
|
248
|
+
"""Set a flot runtime value."""
|
|
249
|
+
payload = f"""
|
|
242
250
|
<setResourceValue1 xmlns=\"utcs\"
|
|
243
251
|
xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">
|
|
244
252
|
<value i:type=\"a:WSFloatingPointValue\" xmlns:a=\"utcs.values\">
|
|
245
|
-
<a:floatingPointValue>{
|
|
253
|
+
<a:floatingPointValue>{floatvalue}</a:floatingPointValue></value>
|
|
246
254
|
<typeString/>
|
|
247
|
-
<resourceID>{
|
|
255
|
+
<resourceID>{resourceid}</resourceID>
|
|
248
256
|
<isValueRuntime>true</isValueRuntime>
|
|
249
257
|
</setResourceValue1>
|
|
250
258
|
</s:Body>
|
|
251
|
-
"""
|
|
252
|
-
id=resourceid, value=floatvalue
|
|
253
|
-
)
|
|
259
|
+
"""
|
|
254
260
|
xdoc = self.connection.soap_action(
|
|
255
261
|
"/ws/ResourceInteractionService", "setResourceValue", payload
|
|
256
262
|
)
|
|
@@ -261,21 +267,19 @@ class IHCSoapClient:
|
|
|
261
267
|
return result == "true"
|
|
262
268
|
return False
|
|
263
269
|
|
|
264
|
-
def set_runtime_value_timer(self, resourceid: int, timer: int):
|
|
265
|
-
"""Set a timer runtime value in milliseconds"""
|
|
266
|
-
payload = """
|
|
270
|
+
def set_runtime_value_timer(self, resourceid: int, timer: int) -> bool:
|
|
271
|
+
"""Set a timer runtime value in milliseconds."""
|
|
272
|
+
payload = f"""
|
|
267
273
|
<setResourceValue1 xmlns=\"utcs\"
|
|
268
274
|
xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">
|
|
269
275
|
<value i:type=\"a:WSTimerValue\" xmlns:a=\"utcs.values\">
|
|
270
|
-
<a:milliseconds>{
|
|
276
|
+
<a:milliseconds>{timer}</a:milliseconds></value>
|
|
271
277
|
<typeString/>
|
|
272
|
-
<resourceID>{
|
|
278
|
+
<resourceID>{resourceid}</resourceID>
|
|
273
279
|
<isValueRuntime>true</isValueRuntime>
|
|
274
280
|
</setResourceValue1>
|
|
275
281
|
</s:Body>
|
|
276
|
-
"""
|
|
277
|
-
id=resourceid, value=timer
|
|
278
|
-
)
|
|
282
|
+
"""
|
|
279
283
|
xdoc = self.connection.soap_action(
|
|
280
284
|
"/ws/ResourceInteractionService", "setResourceValue", payload
|
|
281
285
|
)
|
|
@@ -288,8 +292,8 @@ class IHCSoapClient:
|
|
|
288
292
|
|
|
289
293
|
def set_runtime_value_time(
|
|
290
294
|
self, resourceid: int, hours: int, minutes: int, seconds: int
|
|
291
|
-
):
|
|
292
|
-
"""Set a time runtime value in hours:minutes:seconds"""
|
|
295
|
+
) -> bool:
|
|
296
|
+
"""Set a time runtime value in hours:minutes:seconds."""
|
|
293
297
|
payload = f"""
|
|
294
298
|
<setResourceValue1 xmlns=\"utcs\"
|
|
295
299
|
xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">
|
|
@@ -314,15 +318,16 @@ class IHCSoapClient:
|
|
|
314
318
|
return result == "true"
|
|
315
319
|
return False
|
|
316
320
|
|
|
317
|
-
|
|
318
|
-
|
|
321
|
+
@staticmethod
|
|
322
|
+
def _get_time(resource_value: ET.Element) -> datetime.time:
|
|
319
323
|
hours = int(resource_value.find("./ns2:hours", IHCSoapClient.ihcns).text)
|
|
320
324
|
minutes = int(resource_value.find("./ns2:minutes", IHCSoapClient.ihcns).text)
|
|
321
325
|
seconds = int(resource_value.find("./ns2:seconds", IHCSoapClient.ihcns).text)
|
|
322
326
|
|
|
323
327
|
return datetime.time(hours, minutes, seconds)
|
|
324
328
|
|
|
325
|
-
|
|
329
|
+
@staticmethod
|
|
330
|
+
def _get_datetime(resource_value: ET.Element) -> datetime.datetime:
|
|
326
331
|
year = int(resource_value.find("./ns1:year", IHCSoapClient.ihcns).text)
|
|
327
332
|
month = int(
|
|
328
333
|
resource_value.find("./ns1:monthWithJanuaryAsOne", IHCSoapClient.ihcns).text
|
|
@@ -331,55 +336,74 @@ class IHCSoapClient:
|
|
|
331
336
|
hours = int(resource_value.find("./ns1:hours", IHCSoapClient.ihcns).text)
|
|
332
337
|
minutes = int(resource_value.find("./ns1:minutes", IHCSoapClient.ihcns).text)
|
|
333
338
|
seconds = int(resource_value.find("./ns1:seconds", IHCSoapClient.ihcns).text)
|
|
334
|
-
return datetime.datetime(year, month, day, hours, minutes, seconds)
|
|
339
|
+
return datetime.datetime(year, month, day, hours, minutes, seconds) # noqa: DTZ001
|
|
335
340
|
|
|
336
|
-
|
|
341
|
+
@staticmethod
|
|
342
|
+
def _get_date(resource_value: ET.Element) -> datetime.datetime:
|
|
337
343
|
year = int(resource_value.find("./ns2:year", IHCSoapClient.ihcns).text)
|
|
338
344
|
if year == 0:
|
|
339
|
-
year = datetime.datetime.today().year
|
|
345
|
+
year = datetime.datetime.today().year # noqa: DTZ002
|
|
340
346
|
month = int(resource_value.find("./ns2:month", IHCSoapClient.ihcns).text)
|
|
341
347
|
day = int(resource_value.find("./ns2:day", IHCSoapClient.ihcns).text)
|
|
342
|
-
return datetime.datetime(year, month, day)
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
348
|
+
return datetime.datetime(year, month, day) # noqa: DTZ001
|
|
349
|
+
|
|
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."""
|
|
355
|
+
if resource_value is None:
|
|
347
356
|
return None
|
|
348
357
|
valuetype = resource_value.attrib[
|
|
349
358
|
"{http://www.w3.org/2001/XMLSchema-instance}type"
|
|
350
359
|
].split(":")[1]
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
"WSBooleanValue":
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
"
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
360
|
+
result = resource_value.text
|
|
361
|
+
match valuetype:
|
|
362
|
+
case "WSBooleanValue":
|
|
363
|
+
result = (
|
|
364
|
+
resource_value.find("./ns2:value", IHCSoapClient.ihcns).text
|
|
365
|
+
== "true"
|
|
366
|
+
)
|
|
367
|
+
case "WSIntegerValue":
|
|
368
|
+
result = int(
|
|
369
|
+
resource_value.find("./ns2:integer", IHCSoapClient.ihcns).text
|
|
370
|
+
)
|
|
371
|
+
case "WSFloatingPointValue":
|
|
372
|
+
result = round(
|
|
373
|
+
float(
|
|
374
|
+
resource_value.find(
|
|
375
|
+
"./ns2:floatingPointValue", IHCSoapClient.ihcns
|
|
376
|
+
).text
|
|
377
|
+
),
|
|
378
|
+
2,
|
|
379
|
+
)
|
|
380
|
+
case "WSEnumValue":
|
|
381
|
+
result = resource_value.find("./ns2:enumName", IHCSoapClient.ihcns).text
|
|
382
|
+
case "WSTimerValue":
|
|
383
|
+
return int(
|
|
384
|
+
resource_value.find("./ns2:milliseconds", IHCSoapClient.ihcns).text
|
|
385
|
+
)
|
|
386
|
+
case "WSTimeValue":
|
|
387
|
+
result = IHCSoapClient._get_time(resource_value)
|
|
388
|
+
case "WSDate":
|
|
389
|
+
result = IHCSoapClient._get_datetime(resource_value)
|
|
390
|
+
case "WSDateValue":
|
|
391
|
+
result = IHCSoapClient._get_date(resource_value)
|
|
392
|
+
case "int":
|
|
393
|
+
result = int(resource_value.text)
|
|
372
394
|
return result
|
|
373
395
|
|
|
374
|
-
def get_runtime_value(
|
|
375
|
-
|
|
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
|
+
|
|
376
402
|
The returned value will be boolean, integer or float
|
|
377
403
|
Return None if resource cannot be found or on error
|
|
378
404
|
"""
|
|
379
|
-
payload = """<getRuntimeValue1 xmlns="utcs">{
|
|
380
|
-
"""
|
|
381
|
-
id=resourceid
|
|
382
|
-
)
|
|
405
|
+
payload = f"""<getRuntimeValue1 xmlns="utcs">{resourceid}</getRuntimeValue1>
|
|
406
|
+
"""
|
|
383
407
|
xdoc = self.connection.soap_action(
|
|
384
408
|
"/ws/ResourceInteractionService", "getResourceValue", payload
|
|
385
409
|
)
|
|
@@ -390,14 +414,17 @@ class IHCSoapClient:
|
|
|
390
414
|
)
|
|
391
415
|
return IHCSoapClient.__get_value(value)
|
|
392
416
|
|
|
393
|
-
def get_runtime_values(
|
|
394
|
-
|
|
395
|
-
|
|
417
|
+
def get_runtime_values(
|
|
418
|
+
self, resourceids: list[int]
|
|
419
|
+
) -> dict[int, Any] | Literal[False]:
|
|
396
420
|
"""
|
|
421
|
+
Get runtime values of specified resource ids.
|
|
397
422
|
|
|
423
|
+
Return None if resource cannot be found or on error
|
|
424
|
+
"""
|
|
398
425
|
idsarr = ""
|
|
399
426
|
for ihcid in resourceids:
|
|
400
|
-
idsarr += "<arrayItem>{
|
|
427
|
+
idsarr += f"<arrayItem>{ihcid}</arrayItem>"
|
|
401
428
|
payload = '<getRuntimeValues1 xmlns="utcs">' + idsarr + "</getRuntimeValues1>"
|
|
402
429
|
xdoc = self.connection.soap_action(
|
|
403
430
|
"/ws/ResourceInteractionService", "getResourceValues", payload
|
|
@@ -412,17 +439,19 @@ class IHCSoapClient:
|
|
|
412
439
|
ihcid = item.find("ns1:resourceID", IHCSoapClient.ihcns)
|
|
413
440
|
if ihcid is None:
|
|
414
441
|
continue
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
if
|
|
418
|
-
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
|
|
419
446
|
return changes
|
|
420
447
|
|
|
421
|
-
def cycle_bool_value(self, resourceid: int):
|
|
422
|
-
"""
|
|
448
|
+
def cycle_bool_value(self, resourceid: int) -> bool | None:
|
|
449
|
+
"""
|
|
450
|
+
Turn a booelan resource On and back Off.
|
|
451
|
+
|
|
423
452
|
Return None if resource cannot be found or on error
|
|
424
453
|
"""
|
|
425
|
-
|
|
454
|
+
set_bool = (
|
|
426
455
|
"<arrayItem>"
|
|
427
456
|
'<value xsi:type="ns1:WSBooleanValue">'
|
|
428
457
|
"<ns1:value>{value}</ns1:value>"
|
|
@@ -434,8 +463,8 @@ class IHCSoapClient:
|
|
|
434
463
|
)
|
|
435
464
|
payload = (
|
|
436
465
|
'<setResourceValues1 xmlns="utcs" xmlns:ns1="utcs.values">'
|
|
437
|
-
+
|
|
438
|
-
+
|
|
466
|
+
+ set_bool.format(value="true", id=resourceid)
|
|
467
|
+
+ set_bool.format(value="false", id=resourceid)
|
|
439
468
|
+ "</setResourceValues1>"
|
|
440
469
|
)
|
|
441
470
|
xdoc = self.connection.soap_action(
|
|
@@ -445,53 +474,53 @@ class IHCSoapClient:
|
|
|
445
474
|
return None
|
|
446
475
|
return True
|
|
447
476
|
|
|
448
|
-
def enable_runtime_notification(self, resourceid: int):
|
|
449
|
-
"""Enable notification for specified resource id"""
|
|
477
|
+
def enable_runtime_notification(self, resourceid: int) -> bool:
|
|
478
|
+
"""Enable notification for specified resource id."""
|
|
450
479
|
return self.enable_runtime_notifications([resourceid])
|
|
451
480
|
|
|
452
|
-
def enable_runtime_notifications(self, resourceids):
|
|
453
|
-
"""Enable notification for specified resource ids"""
|
|
481
|
+
def enable_runtime_notifications(self, resourceids: list[int]) -> bool:
|
|
482
|
+
"""Enable notification for specified resource ids."""
|
|
454
483
|
idsarr = ""
|
|
455
484
|
for ihcid in resourceids:
|
|
456
|
-
idsarr += "<a:arrayItem>{
|
|
485
|
+
idsarr += f"<a:arrayItem>{ihcid}</a:arrayItem>"
|
|
457
486
|
|
|
458
|
-
payload = """<enableRuntimeValueNotifications1 xmlns=\"utcs\"
|
|
487
|
+
payload = f"""<enableRuntimeValueNotifications1 xmlns=\"utcs\"
|
|
459
488
|
xmlns:a=\"http://www.w3.org/2001/XMLSchema\"
|
|
460
489
|
xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">
|
|
461
|
-
{
|
|
490
|
+
{idsarr}
|
|
462
491
|
</enableRuntimeValueNotifications1>
|
|
463
|
-
"""
|
|
464
|
-
arr=idsarr
|
|
465
|
-
)
|
|
492
|
+
"""
|
|
466
493
|
xdoc = self.connection.soap_action(
|
|
467
494
|
"/ws/ResourceInteractionService", "enableRuntimeValueNotifications", payload
|
|
468
495
|
)
|
|
469
496
|
return xdoc is not False
|
|
470
497
|
|
|
471
|
-
def wait_for_resource_value_changes(
|
|
498
|
+
def wait_for_resource_value_changes(
|
|
499
|
+
self, wait: int = 10
|
|
500
|
+
) -> dict[int, str] | Literal[False]:
|
|
472
501
|
"""
|
|
473
|
-
Long polling for changes
|
|
474
|
-
|
|
502
|
+
Long polling for changes.
|
|
503
|
+
|
|
504
|
+
And return a dictionary with resource:value for changes (Only last change)
|
|
475
505
|
"""
|
|
476
506
|
change_list = self.wait_for_resource_value_change_list(wait)
|
|
477
507
|
if change_list is False:
|
|
478
508
|
return False
|
|
479
|
-
|
|
480
|
-
for id, value in change_list:
|
|
481
|
-
last_changes[id] = value
|
|
482
|
-
return last_changes
|
|
509
|
+
return dict(change_list)
|
|
483
510
|
|
|
484
|
-
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]:
|
|
485
514
|
"""
|
|
486
|
-
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.
|
|
487
518
|
Return a list of tuples with the id,value
|
|
488
519
|
"""
|
|
489
520
|
changes = []
|
|
490
|
-
payload = """<waitForResourceValueChanges1
|
|
491
|
-
xmlns=\"utcs\">{
|
|
492
|
-
"""
|
|
493
|
-
timeout=wait
|
|
494
|
-
)
|
|
521
|
+
payload = f"""<waitForResourceValueChanges1
|
|
522
|
+
xmlns=\"utcs\">{wait}</waitForResourceValueChanges1>
|
|
523
|
+
"""
|
|
495
524
|
xdoc = self.connection.soap_action(
|
|
496
525
|
"/ws/ResourceInteractionService", "getResourceValue", payload
|
|
497
526
|
)
|
|
@@ -512,14 +541,12 @@ class IHCSoapClient:
|
|
|
512
541
|
changes.append((int(ihcid.text), value))
|
|
513
542
|
return changes
|
|
514
543
|
|
|
515
|
-
def get_user_log(self, language="da"):
|
|
516
|
-
"""Get the controller state"""
|
|
517
|
-
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" />
|
|
518
547
|
<getUserLog2 xmlns="utcs">0</getUserLog2>
|
|
519
548
|
<getUserLog3 xmlns="utcs">{language}</getUserLog3>
|
|
520
|
-
"""
|
|
521
|
-
language=language
|
|
522
|
-
)
|
|
549
|
+
"""
|
|
523
550
|
xdoc = self.connection.soap_action(
|
|
524
551
|
"/ws/ConfigurationService", "getUserLog", payload
|
|
525
552
|
)
|
|
@@ -532,52 +559,47 @@ class IHCSoapClient:
|
|
|
532
559
|
return base64.b64decode(base64data).decode("UTF-8")
|
|
533
560
|
return False
|
|
534
561
|
|
|
535
|
-
def clear_user_log(self):
|
|
536
|
-
"""Clear the user log in the controller"""
|
|
537
|
-
|
|
538
|
-
"/ws/ConfigurationService", "clearUserLog", ""
|
|
539
|
-
)
|
|
540
|
-
return
|
|
562
|
+
def clear_user_log(self) -> None:
|
|
563
|
+
"""Clear the user log in the controller."""
|
|
564
|
+
self.connection.soap_action("/ws/ConfigurationService", "clearUserLog", "")
|
|
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,15 +1,21 @@
|
|
|
1
|
-
"""Implements soap reqeust using the "requests" module"""
|
|
2
|
-
# pylint: disable=too-few-public-methods
|
|
3
|
-
import logging
|
|
4
|
-
import requests
|
|
5
|
-
import xml.etree.ElementTree
|
|
1
|
+
"""Implements soap reqeust using the "requests" module."""
|
|
6
2
|
|
|
3
|
+
import logging
|
|
4
|
+
import time
|
|
5
|
+
import xml.etree.ElementTree as ET
|
|
6
|
+
from http import HTTPStatus
|
|
7
|
+
from typing import Literal
|
|
7
8
|
from urllib.parse import urlparse
|
|
8
9
|
|
|
10
|
+
import requests
|
|
11
|
+
from requests.adapters import HTTPAdapter
|
|
12
|
+
from urllib3.util import Retry
|
|
13
|
+
|
|
9
14
|
_LOGGER = logging.getLogger(__name__)
|
|
10
15
|
|
|
11
|
-
|
|
12
|
-
|
|
16
|
+
|
|
17
|
+
class IHCConnection:
|
|
18
|
+
"""Implements a http connection to the controller."""
|
|
13
19
|
|
|
14
20
|
soapenvelope = """<?xml version=\"1.0\" encoding=\"UTF-8\"?>
|
|
15
21
|
<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\"
|
|
@@ -17,18 +23,37 @@ class IHCConnection(object):
|
|
|
17
23
|
xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">
|
|
18
24
|
<s:Body>{body}</s:Body></s:Envelope>"""
|
|
19
25
|
|
|
20
|
-
def __init__(self, url: str):
|
|
21
|
-
"""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."""
|
|
22
28
|
self.url = url
|
|
23
29
|
self.verify = False
|
|
24
30
|
self.last_exception = None
|
|
25
31
|
self.last_response = None
|
|
26
32
|
self.session = requests.Session()
|
|
33
|
+
self.retries = Retry(
|
|
34
|
+
total=3,
|
|
35
|
+
backoff_factor=0.2,
|
|
36
|
+
status_forcelist=[502, 503, 504],
|
|
37
|
+
allowed_methods={"POST"},
|
|
38
|
+
)
|
|
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
|
|
27
44
|
|
|
28
|
-
def
|
|
45
|
+
def close(self) -> None:
|
|
46
|
+
"""Close the connection."""
|
|
47
|
+
self.session.close()
|
|
48
|
+
self.session = None
|
|
49
|
+
|
|
50
|
+
def cert_verify(self) -> str | None:
|
|
51
|
+
"""Validate the certificate and return the cert file."""
|
|
29
52
|
return None
|
|
30
53
|
|
|
31
|
-
def soap_action(
|
|
54
|
+
def soap_action(
|
|
55
|
+
self, service: str, action: str, payloadbody: str
|
|
56
|
+
) -> ET.Element | Literal[False]:
|
|
32
57
|
"""Do a soap request."""
|
|
33
58
|
payload = self.soapenvelope.format(body=payloadbody).encode("utf-8")
|
|
34
59
|
headers = {
|
|
@@ -39,7 +64,8 @@ class IHCConnection(object):
|
|
|
39
64
|
"SOAPAction": action,
|
|
40
65
|
}
|
|
41
66
|
try:
|
|
42
|
-
|
|
67
|
+
self.rate_limit()
|
|
68
|
+
_LOGGER.debug("soap payload %s", payload)
|
|
43
69
|
self.last_exception = None
|
|
44
70
|
response = self.session.post(
|
|
45
71
|
url=self.url + service,
|
|
@@ -47,22 +73,35 @@ class IHCConnection(object):
|
|
|
47
73
|
data=payload,
|
|
48
74
|
verify=self.cert_verify(),
|
|
49
75
|
)
|
|
50
|
-
_LOGGER.debug(
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
self.last_response = response
|
|
57
|
-
return False
|
|
58
|
-
try:
|
|
59
|
-
_LOGGER.debug( "soap request response %s",response.text)
|
|
60
|
-
xdoc = xml.etree.ElementTree.fromstring(response.text)
|
|
76
|
+
_LOGGER.debug("soap request response status %d", response.status_code)
|
|
77
|
+
if response.status_code != HTTPStatus.OK:
|
|
78
|
+
self.last_response = response
|
|
79
|
+
return False
|
|
80
|
+
_LOGGER.debug("soap request response %s", response.text)
|
|
81
|
+
xdoc = ET.fromstring(response.text) # noqa: S314
|
|
61
82
|
if xdoc is None:
|
|
62
83
|
return False
|
|
63
|
-
except
|
|
64
|
-
_LOGGER.
|
|
84
|
+
except requests.exceptions.RequestException as exp:
|
|
85
|
+
_LOGGER.exception("soap request exception")
|
|
86
|
+
self.last_exception = exp
|
|
87
|
+
except ET.ParseError as exp:
|
|
88
|
+
_LOGGER.exception("soap request xml parse erro")
|
|
65
89
|
self.last_exception = exp
|
|
66
90
|
self.last_response = response
|
|
67
|
-
|
|
68
|
-
|
|
91
|
+
else:
|
|
92
|
+
return xdoc
|
|
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,47 +1,50 @@
|
|
|
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
|
|
3
4
|
import os
|
|
4
|
-
import requests
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
import requests
|
|
7
7
|
from cryptography.hazmat.backends import default_backend
|
|
8
8
|
from cryptography.hazmat.primitives import hashes
|
|
9
|
-
from
|
|
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
|
-
self.session.mount(
|
|
22
|
+
self.session.mount(
|
|
23
|
+
"https://",
|
|
24
|
+
CertAdapter(self.get_fingerprint_from_cert(), max_retries=self.retries),
|
|
25
|
+
)
|
|
23
26
|
|
|
24
|
-
def get_fingerprint_from_cert(self):
|
|
25
|
-
"""Get the fingerprint from the certificate"""
|
|
27
|
+
def get_fingerprint_from_cert(self) -> str:
|
|
28
|
+
"""Get the fingerprint from the certificate."""
|
|
26
29
|
pem = open(self.cert_file, "rb").read()
|
|
27
30
|
cert = load_pem_x509_certificate(pem, default_backend())
|
|
28
31
|
f = cert.fingerprint(hashes.SHA1())
|
|
29
32
|
return "".join("{:02x}".format(x) for x in f)
|
|
30
33
|
|
|
31
|
-
def cert_verify(self):
|
|
34
|
+
def cert_verify(self) -> str:
|
|
32
35
|
return self.cert_file
|
|
33
36
|
|
|
34
37
|
|
|
35
38
|
class CertAdapter(requests.adapters.HTTPAdapter):
|
|
36
|
-
"""A adapter for a specific certificate"""
|
|
39
|
+
"""A adapter for a specific certificate."""
|
|
37
40
|
|
|
38
|
-
def __init__(self, fingerprint, **kwargs):
|
|
39
|
-
"""
|
|
41
|
+
def __init__(self, fingerprint, **kwargs) -> None:
|
|
42
|
+
"""Store the fingerprint for use when creating the poolmanager."""
|
|
40
43
|
self.fingerprint = fingerprint
|
|
41
|
-
super(
|
|
44
|
+
super().__init__(**kwargs)
|
|
42
45
|
|
|
43
46
|
def init_poolmanager(self, connections, maxsize, block=False, **pool_kwargs):
|
|
44
|
-
"""Create a custom poolmanager"""
|
|
47
|
+
"""Create a custom poolmanager."""
|
|
45
48
|
CIPHERS = "DEFAULT:!DH"
|
|
46
49
|
context = create_urllib3_context(ciphers=CIPHERS)
|
|
47
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.7.dist-info/RECORD
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
ihcsdk/__init__.py,sha256=ocAoa28ZKxGgMlDEHWEsd7n00OtGB0R9N7XQfRlih9Q,28
|
|
2
|
-
ihcsdk/ihcclient.py,sha256=KCgat4CnR69Vrb-GdA9tfduS7tQl9ymwqEBf-EPDM1U,24117
|
|
3
|
-
ihcsdk/ihcconnection.py,sha256=qnOEEsgKulIUGRS223o9YSy9qmEVR1D6jNt2XxGFGho,2577
|
|
4
|
-
ihcsdk/ihccontroller.py,sha256=9D97RX8DrFMLMxDBcIrhoYY-QEeKNjbVv_PiRW0wNyU,9180
|
|
5
|
-
ihcsdk/ihcsslconnection.py,sha256=4IFVg1bpdJ25yN76tnFLndvL5Rl9-qbXrbENUjyTfzw,2056
|
|
6
|
-
ihcsdk/certs/ihc.crt,sha256=VYY_DiHrctlXBTNXGdJ2FN4TYuVnwnpVZ1i8t2_0cec,1002
|
|
7
|
-
ihcsdk/certs/ihc3.crt,sha256=Ka2L8zQ06A76W6fZc0ckScdrj1Cn-mhgqdhc61-cCrk,1398
|
|
8
|
-
ihcsdk-2.8.7.dist-info/METADATA,sha256=q8VRbllNSE39vfayX1ui00DurTtIACN24dL7WJ0XVCY,339
|
|
9
|
-
ihcsdk-2.8.7.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
10
|
-
ihcsdk-2.8.7.dist-info/license.txt,sha256=jOtLnuWt7d5Hsx6XXB2QxzrSe2sWWh3NgMfFRetluQM,35147
|
|
11
|
-
ihcsdk-2.8.7.dist-info/top_level.txt,sha256=QgKE7TWblC-uXe5-7MVO5JtzvrPiMoVmGgKKsVR43hU,7
|
|
12
|
-
ihcsdk-2.8.7.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|