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.
@@ -115,7 +115,7 @@ class Client:
115
115
  __universal: bool
116
116
  level: OSI
117
117
  log_file: TextIO
118
- media: base.Media | None
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 = Serial(port="COM3") if media is None else 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
- reply.complete = True
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
- try:
762
- await asyncio.wait_for( # todo: don't work exception!!!! Why????
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()
@@ -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
- await change_ldn.exchange(c)
228
- else:
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(exc.IDError(F"got {res.value=}, expected {c._objects.LDN.value}"))
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
- class WriteTime(SimpleCopy, Base):
1360
- msg: str = "write Clock.time depend from transfer time"
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
- try:
1364
- obj = c._objects.clock
1365
- c.get_get_request_normal(obj.get_attr_descriptor(3))
1366
- if isinstance(res_pdu := await c.read_data_block(), result.Error):
1367
- return res_pdu
1368
- tz = obj.get_attr_element(3).DATA_TYPE(res_pdu.value)
1369
- res = await WriteAttribute(
1370
- ln=obj.logical_name,
1371
- index=2,
1372
- value=(datetime.datetime.utcnow() + datetime.timedelta(minutes=int(tz)) + c.last_transfer_time)
1373
- ).exchange(c)
1374
- return res
1375
- except Exception as e:
1376
- # logger.info(F'ERROR: write Clock: attribute 2 {e}')
1377
- return result.Error.from_e(exc.DLMSException(F"write time: {e}"))
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.18
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.5,>=1.4.9
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=T_8nI6_tCWK6Us74gjIM_2I8EtETs4dQX4ygFmnV70A,112333
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=K2GyX4_DMV5_vYk2QhCwNkXK4gEqd9UmmcjHa46I49c,76378
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.18.dist-info/METADATA,sha256=hYlGdBkjzcYlM28DJTQnpUQ-lF83f9ozF7i2z1C1L_s,986
58
- dlms_spodes_client-0.19.18.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
59
- dlms_spodes_client-0.19.18.dist-info/entry_points.txt,sha256=Z6UTeQjjCf2k1Y3Bjs0s7yr-UYSWb-TvJMuG2K2MApw,70
60
- dlms_spodes_client-0.19.18.dist-info/top_level.txt,sha256=rh_3Uig5bc6J_lKni01btol7dX_IgIJulNtGjGehmBE,19
61
- dlms_spodes_client-0.19.18.dist-info/RECORD,,
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,,