DLMS-SPODES-client 0.19.18__py3-none-any.whl → 0.19.25__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.
- DLMS_SPODES_client/client.py +17 -21
- DLMS_SPODES_client/task.py +67 -26
- {dlms_spodes_client-0.19.18.dist-info → dlms_spodes_client-0.19.25.dist-info}/METADATA +4 -2
- {dlms_spodes_client-0.19.18.dist-info → dlms_spodes_client-0.19.25.dist-info}/RECORD +7 -7
- {dlms_spodes_client-0.19.18.dist-info → dlms_spodes_client-0.19.25.dist-info}/WHEEL +0 -0
- {dlms_spodes_client-0.19.18.dist-info → dlms_spodes_client-0.19.25.dist-info}/entry_points.txt +0 -0
- {dlms_spodes_client-0.19.18.dist-info → dlms_spodes_client-0.19.25.dist-info}/top_level.txt +0 -0
DLMS_SPODES_client/client.py
CHANGED
|
@@ -115,7 +115,7 @@ class Client:
|
|
|
115
115
|
__universal: bool
|
|
116
116
|
level: OSI
|
|
117
117
|
log_file: TextIO
|
|
118
|
-
media: base.Media
|
|
118
|
+
media: base.Media
|
|
119
119
|
lock: asyncio.Lock
|
|
120
120
|
last_transfer_time: datetime.timedelta | None
|
|
121
121
|
connection_time_release: int
|
|
@@ -140,11 +140,11 @@ class Client:
|
|
|
140
140
|
state: State
|
|
141
141
|
|
|
142
142
|
def __init__(self,
|
|
143
|
+
media: base.Media,
|
|
143
144
|
SAP: int = 0x10,
|
|
144
145
|
secret: str | bytes = "",
|
|
145
146
|
conformance: str = None,
|
|
146
147
|
addr_size: int = -1,
|
|
147
|
-
media: base.Media = None,
|
|
148
148
|
id_: str | int = None,
|
|
149
149
|
m_id: int = 0,
|
|
150
150
|
universal: bool = False,
|
|
@@ -163,7 +163,7 @@ class Client:
|
|
|
163
163
|
self._objects = None
|
|
164
164
|
self.__sap = enums.ClientSAP(SAP)
|
|
165
165
|
"""Service Access Point. Default <Public>"""
|
|
166
|
-
self.media =
|
|
166
|
+
self.media = media
|
|
167
167
|
""" physical layer """
|
|
168
168
|
if com_profile is None:
|
|
169
169
|
self.com_profile = c_pf.HDLC()
|
|
@@ -260,12 +260,12 @@ class Client:
|
|
|
260
260
|
case _: raise ValueError(F"can't calculate channel index by media: {self.media}")
|
|
261
261
|
|
|
262
262
|
def get_frame(self, read_data: bytearray, reply: GXReplyData) -> frame.Frame | None:
|
|
263
|
+
reply.complete = False
|
|
263
264
|
while len(read_data) != 0:
|
|
264
265
|
new_frame = frame.Frame.try_from(read_data)
|
|
265
|
-
|
|
266
|
-
if new_frame is None:
|
|
267
|
-
reply.complete = False
|
|
266
|
+
if not isinstance(new_frame, frame.Frame):
|
|
268
267
|
return None
|
|
268
|
+
reply.complete = True
|
|
269
269
|
if new_frame.is_for_me(self.DA, self.SA):
|
|
270
270
|
self.received_frames.append(new_frame)
|
|
271
271
|
if new_frame.is_segmentation:
|
|
@@ -319,7 +319,7 @@ class Client:
|
|
|
319
319
|
len_ = _GXCommon.getObjectCount(reply.data)
|
|
320
320
|
if len_ > reply.data.size - reply.data.position:
|
|
321
321
|
reply.complete = False
|
|
322
|
-
return
|
|
322
|
+
return result.Error.from_e(RuntimeError("not enouth reply data size"))
|
|
323
323
|
GXDLMS.getDataFromBlock(reply.data, index)
|
|
324
324
|
if (bc & 0x80) == 0:
|
|
325
325
|
reply.moreData = (RequestTypes(reply.moreData | RequestTypes.GBT))
|
|
@@ -758,22 +758,18 @@ class Client:
|
|
|
758
758
|
self.log(logL.INFO, F"TX: {send_frame.content.hex(" ")}")
|
|
759
759
|
attempt: int = 1
|
|
760
760
|
while attempt < 3:
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
fut=self.media.receive(recv_buf),
|
|
764
|
-
timeout=self.com_profile.parameters.inactivity_time_out) # todo: only for HDLC
|
|
765
|
-
if self.__is_frame(notify, recv_buf, reply):
|
|
766
|
-
break
|
|
767
|
-
elif notify.data.size != 0:
|
|
768
|
-
if not notify.isMoreData():
|
|
769
|
-
notify.clear()
|
|
770
|
-
continue
|
|
771
|
-
else:
|
|
772
|
-
"""our frame not was found"""
|
|
773
|
-
except TimeoutError as e:
|
|
774
|
-
self.log(logL.WARN, F'Data send failed: {e}. Try to resend {attempt+1}/3. RX_buffer: {recv_buf.hex(" ")}')
|
|
761
|
+
if not await self.media.receive(recv_buf): # todo: make for BLE
|
|
762
|
+
self.log(logL.WARN, F'Data receive failed: Try to resend {attempt + 1}/3. RX_buffer: {recv_buf.hex(" ")}')
|
|
775
763
|
await self.media.send(send_frame.content)
|
|
776
764
|
attempt += 1
|
|
765
|
+
continue
|
|
766
|
+
if self.__is_frame(notify, recv_buf, reply):
|
|
767
|
+
await self.media.end_transaction()
|
|
768
|
+
break
|
|
769
|
+
if notify.data.size != 0:
|
|
770
|
+
if not notify.isMoreData():
|
|
771
|
+
notify.clear()
|
|
772
|
+
continue
|
|
777
773
|
else:
|
|
778
774
|
return result.Error.from_e(TimeoutError("Failed to receive reply from the device in given time"), "read data block")
|
|
779
775
|
recv_buf.clear()
|
DLMS_SPODES_client/task.py
CHANGED
|
@@ -76,9 +76,10 @@ class Base[T: result.Result](Protocol):
|
|
|
76
76
|
if c.media is None:
|
|
77
77
|
return result.Error.from_e(exc.NoPort("no media"), "PH_connect")
|
|
78
78
|
if not c.media.is_open():
|
|
79
|
-
await c.media.open()
|
|
79
|
+
if isinstance(res_open := await c.media.open(), result.Error):
|
|
80
|
+
return res_open
|
|
81
|
+
c.log(logL.INFO, F"Open port communication channel: {c.media} {res_open.value}sec")
|
|
80
82
|
c.level = OSI.PHYSICAL
|
|
81
|
-
c.log(logL.INFO, F"Open port communication channel: {c.media}")
|
|
82
83
|
# todo: replace to <data_link>
|
|
83
84
|
if (
|
|
84
85
|
c._objects is None
|
|
@@ -223,10 +224,9 @@ class Base[T: result.Result](Protocol):
|
|
|
223
224
|
case _ as diagnostic:
|
|
224
225
|
return result.Error.from_e(exc.AssociationResultError(diagnostic))
|
|
225
226
|
if c._objects is not None:
|
|
226
|
-
if c.is_universal()
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
await match_ldn.exchange(c)
|
|
227
|
+
matchLDN = change_ldn if c.is_universal() else match_ldn
|
|
228
|
+
if isinstance(res_match_ldn := await matchLDN.exchange(c), result.Error):
|
|
229
|
+
return res_match_ldn
|
|
230
230
|
return result.OK
|
|
231
231
|
|
|
232
232
|
async def application(self, c: Client) -> T | result.Error:
|
|
@@ -322,7 +322,8 @@ class TestDataLink(SimpleCopy, OK):
|
|
|
322
322
|
async def physical(self, c: Client) -> result.Ok | result.Error:
|
|
323
323
|
if OSI.PHYSICAL not in c.level:
|
|
324
324
|
if not c.media.is_open():
|
|
325
|
-
await c.media.open()
|
|
325
|
+
if isinstance(res_open := await c.media.open(), result.Error):
|
|
326
|
+
return res_open
|
|
326
327
|
c.level = OSI.PHYSICAL
|
|
327
328
|
c.DA = frame.Address(
|
|
328
329
|
upper_address=int(c.server_SAP),
|
|
@@ -726,7 +727,7 @@ class MatchLDN(OK):
|
|
|
726
727
|
c.log(logL.WARN, F"connected to other server, change LDN")
|
|
727
728
|
await init_type.exchange(c) # todo: maybe set spec?
|
|
728
729
|
else:
|
|
729
|
-
return result.Error
|
|
730
|
+
return result.Error.from_e(ValueError(F"got LDN: {res.value}, expected {c._objects.LDN.value}"))
|
|
730
731
|
return result.OK
|
|
731
732
|
|
|
732
733
|
|
|
@@ -1356,25 +1357,65 @@ class Execute2(SimpleCopy, OK):
|
|
|
1356
1357
|
return result.Error.from_e(e)
|
|
1357
1358
|
|
|
1358
1359
|
|
|
1359
|
-
|
|
1360
|
-
|
|
1360
|
+
@dataclass
|
|
1361
|
+
class GetTimeDelta(SimpleCopy, Simple[float]):
|
|
1362
|
+
"""Read and return <time delta> in second: """
|
|
1363
|
+
msg: str = "Read Clock.time"
|
|
1361
1364
|
|
|
1362
|
-
async def exchange(self, c: Client):
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1365
|
+
async def exchange(self, c: Client) -> result.SimpleOrError[float]:
|
|
1366
|
+
acc = result.ErrorAccumulator()
|
|
1367
|
+
obj = c._objects.clock
|
|
1368
|
+
if isinstance(
|
|
1369
|
+
res_read_tz := await ReadObjAttr(
|
|
1370
|
+
obj=obj,
|
|
1371
|
+
index=3
|
|
1372
|
+
).exchange(c),
|
|
1373
|
+
result.Error):
|
|
1374
|
+
return res_read_tz
|
|
1375
|
+
tz = datetime.timezone(datetime.timedelta(minutes=int(res_read_tz.value)))
|
|
1376
|
+
if isinstance(
|
|
1377
|
+
res_read := await ReadObjAttr(
|
|
1378
|
+
obj=obj,
|
|
1379
|
+
index=2
|
|
1380
|
+
).exchange(c),
|
|
1381
|
+
result.Error):
|
|
1382
|
+
return res_read
|
|
1383
|
+
value = datetime.datetime.now(tz=tz)
|
|
1384
|
+
value2 = res_read.value.to_datetime().replace(tzinfo=tz)
|
|
1385
|
+
return result.Simple((value2 - value).total_seconds()).append_err(acc.err)
|
|
1386
|
+
|
|
1387
|
+
|
|
1388
|
+
|
|
1389
|
+
@dataclass
|
|
1390
|
+
class WriteTime(SimpleCopy, Simple[float]):
|
|
1391
|
+
"""Write and return <record time> in second: """
|
|
1392
|
+
limit: float = 5.0
|
|
1393
|
+
number_of_retries: int = 10
|
|
1394
|
+
msg: str = "write Clock.time"
|
|
1395
|
+
|
|
1396
|
+
async def exchange(self, c: Client) -> result.SimpleOrError[float]:
|
|
1397
|
+
acc = result.ErrorAccumulator()
|
|
1398
|
+
obj = c._objects.clock
|
|
1399
|
+
c.get_get_request_normal(obj.get_attr_descriptor(3))
|
|
1400
|
+
if isinstance(res_pdu := await c.read_data_block(), result.Error):
|
|
1401
|
+
return res_pdu
|
|
1402
|
+
tz = obj.get_attr_element(3).DATA_TYPE(res_pdu.value)
|
|
1403
|
+
for i in range(self.number_of_retries):
|
|
1404
|
+
pre_time = time.time()
|
|
1405
|
+
if isinstance(
|
|
1406
|
+
res_write := await WriteAttribute(
|
|
1407
|
+
ln=obj.logical_name,
|
|
1408
|
+
index=2,
|
|
1409
|
+
value=(datetime.datetime.utcnow() + datetime.timedelta(minutes=int(tz)))).exchange(c),
|
|
1410
|
+
result.Error):
|
|
1411
|
+
return res_write
|
|
1412
|
+
rec_time = time.time() - pre_time
|
|
1413
|
+
if rec_time < self.limit:
|
|
1414
|
+
break
|
|
1415
|
+
acc.append_e(TimeoutError(f"can't write in {i} attemp in time"))
|
|
1416
|
+
else:
|
|
1417
|
+
return result.Error.from_e(TimeoutError(f"can't write time for limit: {self.limit} second"))
|
|
1418
|
+
return result.Simple(rec_time).append_err(acc.err)
|
|
1378
1419
|
|
|
1379
1420
|
|
|
1380
1421
|
@dataclass
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: DLMS_SPODES_client
|
|
3
|
-
Version: 0.19.
|
|
3
|
+
Version: 0.19.25
|
|
4
4
|
Summary: dlms-spodes
|
|
5
5
|
Author-email: Serj Kotilevski <youserj@outlook.com>
|
|
6
6
|
Project-URL: Source, https://github.com/youserj/SPODESclient_prj
|
|
@@ -14,7 +14,7 @@ Requires-Dist: pydantic>=2.11
|
|
|
14
14
|
Requires-Dist: pycryptodomex>=3.22.0
|
|
15
15
|
Requires-Dist: semver>=3.0
|
|
16
16
|
Requires-Dist: StructResult<0.10,>=0.9.2
|
|
17
|
-
Requires-Dist: DLMS-SPODES-communications<1.
|
|
17
|
+
Requires-Dist: DLMS-SPODES-communications<1.6,>=1.5.4
|
|
18
18
|
Requires-Dist: DLMS_SPODES<0.88,>=0.87.8
|
|
19
19
|
Requires-Dist: DLMSadapter<0.8,>=0.7.6
|
|
20
20
|
Requires-Dist: DLMSCommunicationProfile<0.2,>=0.1.11
|
|
@@ -23,5 +23,7 @@ Provides-Extra: dev
|
|
|
23
23
|
Requires-Dist: mypy>=1.5.0; extra == "dev"
|
|
24
24
|
Requires-Dist: ruff>=0.11; extra == "dev"
|
|
25
25
|
Requires-Dist: types-requests; extra == "dev"
|
|
26
|
+
Requires-Dist: build; extra == "dev"
|
|
27
|
+
Requires-Dist: twine; extra == "dev"
|
|
26
28
|
|
|
27
29
|
# SPODES-client
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
DLMS_SPODES_client/FCS16.py,sha256=RhoN4fX7eesKWfWCkRT_uWNfiQHqFd3T6lRwxfamUqw,2697
|
|
2
2
|
DLMS_SPODES_client/__init__.py,sha256=6wphXvqkodng7h4nKNmkfldbaxf--IDVGfT0yNbas-o,449
|
|
3
|
-
DLMS_SPODES_client/client.py,sha256=
|
|
3
|
+
DLMS_SPODES_client/client.py,sha256=pRkFriDGu2DKZQDDNWmNaYXTRvPeWWIcITjfHwYx3CE,112082
|
|
4
4
|
DLMS_SPODES_client/logger.py,sha256=zAbihLloMU99w8Sw3djQ0cwItzyGq0Fz8DI9_suazv4,1913
|
|
5
5
|
DLMS_SPODES_client/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
6
|
DLMS_SPODES_client/services.py,sha256=xM_-h322V1bGBcw9cJh7XOSUMTL3_E-GX89-z3YwIYw,3909
|
|
7
7
|
DLMS_SPODES_client/session.py,sha256=nPzXujpmGSTFFvhyZRjgH_RLX1DS9moRddUEZuf-QEE,12760
|
|
8
8
|
DLMS_SPODES_client/settings.py,sha256=6mitGe9UYeEgL61sf933MJ-S5N-ReoxvXqiI3agBGYE,1623
|
|
9
|
-
DLMS_SPODES_client/task.py,sha256=
|
|
9
|
+
DLMS_SPODES_client/task.py,sha256=wW6sjKKR31dc1FNKFptGTcob3mBRAVaOVa1h6tUWp10,78083
|
|
10
10
|
DLMS_SPODES_client/gurux_common/enums/TraceLevel.py,sha256=Ne0Rn3c9ACqQjmde_ksbiQxIUv6nXsPQRnhkGwIv3QI,521
|
|
11
11
|
DLMS_SPODES_client/gurux_common/enums/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
12
|
DLMS_SPODES_client/gurux_dlms/AesGcmParameter.py,sha256=HJt0uvxtkqKEkvfiqXTMNsiayN15AgPJa9_iMSSFZsQ,1429
|
|
@@ -54,8 +54,8 @@ DLMS_SPODES_client/gurux_dlms/enums/Task.py,sha256=chuOL6-IMxBvABUZtoFcaYaQQB4GZ
|
|
|
54
54
|
DLMS_SPODES_client/gurux_dlms/enums/VdeStateError.py,sha256=qT87LMbIYEs3TYPIp3N-dR2Tcg9KhKyiELwhVl5U-tw,233
|
|
55
55
|
DLMS_SPODES_client/gurux_dlms/enums/__init__.py,sha256=F_sgGwNmmdpbKvP1klJQUNiLXxU2BtZ-LgEI9e6xP8g,1314
|
|
56
56
|
DLMS_SPODES_client/gurux_dlms/internal/_GXCommon.py,sha256=7D9EYcfiZxwbk8sfpHv7s2nYqrbmGf-Tbwv2T-gqmgk,53226
|
|
57
|
-
dlms_spodes_client-0.19.
|
|
58
|
-
dlms_spodes_client-0.19.
|
|
59
|
-
dlms_spodes_client-0.19.
|
|
60
|
-
dlms_spodes_client-0.19.
|
|
61
|
-
dlms_spodes_client-0.19.
|
|
57
|
+
dlms_spodes_client-0.19.25.dist-info/METADATA,sha256=bn_R-U_lc1TyTuOOGkNrUZFYRc6oDiEcYF7IZgISCl0,1062
|
|
58
|
+
dlms_spodes_client-0.19.25.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
59
|
+
dlms_spodes_client-0.19.25.dist-info/entry_points.txt,sha256=Z6UTeQjjCf2k1Y3Bjs0s7yr-UYSWb-TvJMuG2K2MApw,70
|
|
60
|
+
dlms_spodes_client-0.19.25.dist-info/top_level.txt,sha256=rh_3Uig5bc6J_lKni01btol7dX_IgIJulNtGjGehmBE,19
|
|
61
|
+
dlms_spodes_client-0.19.25.dist-info/RECORD,,
|
|
File without changes
|
{dlms_spodes_client-0.19.18.dist-info → dlms_spodes_client-0.19.25.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|