DLMS-SPODES-client 0.19.13__py3-none-any.whl → 0.19.14__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.
@@ -86,19 +86,21 @@ class Base[T: result.Result](Protocol):
86
86
  ):
87
87
  if isinstance(res := await init_type.data_link(c), result.Error):
88
88
  return res.with_msg("PH_connect")
89
- await c.close() # todo: change to DiscRequest, or make not closed or reconnect !!!
89
+ if isinstance(res_close := await c.close(), result.Error): # todo: change to DiscRequest, or make not closed or reconnect !!!
90
+ return res_close
90
91
  return result.OK
91
92
 
92
93
  @staticmethod
93
- async def physical_t(c: Client) -> None:
94
- await c.close()
94
+ async def physical_t(c: Client) -> result.Ok | result.Error:
95
+ return await c.close()
95
96
 
96
97
  async def physical(self, c: Client) -> T | result.Error:
97
98
  if OSI.PHYSICAL not in c.level:
98
99
  if isinstance((res := await self.PH_connect(c)), result.Error):
99
100
  return res
100
101
  ret = await self.data_link(c)
101
- await self.physical_t(c)
102
+ if isinstance(res_terminate := await self.physical_t(c), result.Error):
103
+ return res_terminate
102
104
  return ret
103
105
 
104
106
  async def DL_connect(self, c: Client) -> None:
@@ -113,8 +115,6 @@ class Base[T: result.Result](Protocol):
113
115
  c.SA = frame.Address(upper_address=int(c.SAP))
114
116
  c.log(logL.INFO, F"{c.SA=} {c.DA=}")
115
117
  # initialize connection
116
- c.reply.clear()
117
- # replace this
118
118
  if c.settings.cipher.security != Security.NONE:
119
119
  c.log(logL.DEB, F"Security: {c.settings.cipher.security}/n"
120
120
  F"System title: {c.settings.cipher.systemTitle.hex()}"
@@ -124,9 +124,9 @@ class Base[T: result.Result](Protocol):
124
124
  c.log(logL.DEB, F"Dedicated key: {c.settings.cipher.dedicatedKey.hex()}")
125
125
  # SNRM
126
126
  c.get_SNRM_request()
127
- await c.read_data_block()
127
+ if isinstance(res_pdu := await c.read_data_block(), result.Error):
128
+ return res_pdu
128
129
  c.level |= OSI.DATA_LINK
129
- c.reply.clear()
130
130
 
131
131
  async def data_link(self, c: Client) -> T | result.Error:
132
132
  if OSI.DATA_LINK not in c.level:
@@ -134,7 +134,7 @@ class Base[T: result.Result](Protocol):
134
134
  # todo: make tile
135
135
  return await self.application(c)
136
136
 
137
- async def AA(self, c: Client) -> None:
137
+ async def AA(self, c: Client) -> result.Ok | result.Error:
138
138
  """Application Associate"""
139
139
  if c.invocationCounter and c.settings.cipher is not None and c.settings.cipher.security != Security.NONE:
140
140
  # create IC object. TODO: remove it after close connection, maybe???
@@ -149,17 +149,19 @@ class Base[T: result.Result](Protocol):
149
149
  challenge = c.settings.ctoSChallenge
150
150
  try:
151
151
  c.aarqRequest(c.m_id)
152
- await c.read_data_block()
153
- ret = c.parseAareResponse(c.reply.data)
152
+ if isinstance(res_pdu := await c.read_data_block(), result.Error):
153
+ return res_pdu
154
+ ret = c.parseAareResponse(res_pdu.value)
154
155
  c.level |= OSI.APPLICATION # todo: it's must be result of <ret> look down
155
- c.reply.clear()
156
- await c.read_attribute(IC, 2)
157
- c.settings.cipher.invocationCounter = 1 + int(IC.value)
156
+ if isinstance(res_ic := await ReadObjAttr(IC, 2).exchange(c), result.Error):
157
+ return res_ic
158
+ c.settings.cipher.invocationCounter = 1 + int(res_ic.value)
158
159
  c.log(logL.DEB, "Invocation counter: " + str(c.settings.cipher.invocationCounter))
159
160
  # disconnect
160
161
  if c.media and c.media.is_open():
161
162
  c.log(logL.DEB, "DisconnectRequest")
162
- await c.disconnect_request()
163
+ if isinstance(res_disc_req := await c.disconnect_request(), result.Error):
164
+ return res_disc_req
163
165
  finally:
164
166
  c.SAP = tmp_client_SAP
165
167
  c.settings.useCustomChallenge = challenge is not None
@@ -206,40 +208,41 @@ class Base[T: result.Result](Protocol):
206
208
  # self.client.ctoSChallenge = challenge
207
209
 
208
210
  c.aarqRequest(c.m_id)
209
- await c.read_data_block()
211
+ if isinstance(res_pdu := await c.read_data_block(), result.Error):
212
+ return res_pdu
210
213
  # await c.read_attr(ut.CosemAttributeDescriptor((collection.ClassID.ASSOCIATION_LN, ut.CosemObjectInstanceId("0.0.40.0.0.255"), ut.CosemObjectAttributeId(6)))) # for test only
211
- match c.parseAareResponse(c.reply.data):
214
+ match c.parseAareResponse(res_pdu.value):
212
215
  case AcseServiceUser.NULL:
213
- c.log(logL.INFO, 'Authentication success')
216
+ c.log(logL.INFO, "Authentication success")
214
217
  c.level |= OSI.APPLICATION
215
218
  case AcseServiceUser.AUTHENTICATION_REQUIRED:
216
- c.reply.clear()
217
219
  c.getApplicationAssociationRequest()
218
- await c.read_data_block()
219
- c.parseApplicationAssociationResponse()
220
+ if isinstance(res_pdu := await c.read_data_block(), result.Error):
221
+ return res_pdu
222
+ c.parseApplicationAssociationResponse(res_pdu.value)
220
223
  case _ as diagnostic:
221
- raise exc.AssociationResultError(diagnostic)
224
+ return result.Error.from_e(exc.AssociationResultError(diagnostic))
222
225
  if c._objects is not None:
223
- c.get_get_request_normal(c._objects.LDN.get_attr_descriptor(2))
224
- await c.read_data_block()
225
226
  if c.is_universal():
226
227
  await change_ldn.exchange(c)
227
228
  else:
228
229
  await match_ldn.exchange(c)
230
+ return result.OK
229
231
 
230
232
  async def application(self, c: Client) -> T | result.Error:
231
233
  if OSI.APPLICATION not in c.level:
232
- await self.AA(c)
234
+ if isinstance(res := await self.AA(c), result.Error):
235
+ return res
233
236
  # no tile
234
237
  return await self.exchange(c)
235
238
 
236
239
  async def exchange(self, c: Client) -> T | result.Error:
237
240
  """application level exchange"""
238
241
 
239
- async def connect(self, c: Client) -> None:
242
+ async def connect(self, c: Client) -> result.Ok | result.Error:
240
243
  await self.PH_connect(c)
241
244
  await self.DL_connect(c)
242
- await self.AA(c)
245
+ return await self.AA(c)
243
246
 
244
247
 
245
248
  class SimpleCopy:
@@ -328,9 +331,11 @@ class TestDataLink(SimpleCopy, OK):
328
331
  )
329
332
  c.SA = frame.Address(upper_address=int(c.SAP))
330
333
  c.get_SNRM_request()
331
- await c.read_data_block()
334
+ if isinstance(res_pdu := await c.read_data_block(), result.Error):
335
+ return res_pdu
332
336
  c.level |= OSI.DATA_LINK
333
- await c.close() # todo: change to DiscRequest
337
+ if isinstance(res_close := await c.close(), result.Error): # todo: change to DiscRequest
338
+ return res_close
334
339
  return result.Ok
335
340
 
336
341
  async def exchange(self, c: Client):
@@ -371,7 +376,8 @@ class HardwareReconnect(SimpleCopy, OK):
371
376
  if self.delay != 0.0:
372
377
  c.log(logL.INFO, F"delay({self.delay})")
373
378
  await asyncio.sleep(self.delay)
374
- await self.connect(c) # restore Application
379
+ if isinstance(res_connect := await self.connect(c), result.Error): # restore Application
380
+ return res_connect
375
381
  return result.OK
376
382
 
377
383
 
@@ -497,7 +503,7 @@ class List[T: result.Result, U: Base[result.Result]](Subtasks[U], _List[T]):
497
503
  msg: str
498
504
  __current: Base[T]
499
505
 
500
- def __init__(self, *tasks: Base[T], msg: str = "", err_ignore: bool = True):
506
+ def __init__(self, *tasks: Base[T], msg: str = "", err_ignore: bool = False):
501
507
  self.tasks = list(tasks)
502
508
  self.__current = self
503
509
  self.__is_exchange = False
@@ -528,15 +534,9 @@ class List[T: result.Result, U: Base[result.Result]](Subtasks[U], _List[T]):
528
534
  self.__is_exchange = True
529
535
  for t in self.tasks:
530
536
  self.__current = t
531
- try:
532
- res.append(await t.exchange(c))
533
- except exc.ResultError as e: # todo: make without except
534
- res.append(result.Error.from_e(e, msg=t.msg))
535
- if (
536
- not self.err_ignore
537
- and not res.is_ok()
538
- ):
539
- break
537
+ res.append(await t.exchange(c))
538
+ if not self.err_ignore:
539
+ return result.Error(res.err)
540
540
  return res
541
541
 
542
542
 
@@ -570,15 +570,12 @@ class Sequence[*Ts](Subtasks[Base[result.Result]], _Sequence[*Ts]):
570
570
  res = result.Sequence()
571
571
  for t in self.tasks:
572
572
  self.__current = t
573
- try:
574
- if isinstance(res_one := await t.exchange(c), result.Error):
575
- if self.err_ignore:
576
- res_one = res_one.with_msg(self.msg)
577
- else:
578
- return res_one
579
- res = res.add(res_one)
580
- except exc.ResultError as e: # todo: make without except
581
- res = res.add(result.Error.from_e(e).with_msg(self.msg))
573
+ if isinstance(res_one := await t.exchange(c), result.Error):
574
+ if self.err_ignore:
575
+ res_one = res_one.with_msg(self.msg)
576
+ else:
577
+ return res_one
578
+ res = res.add(res_one)
582
579
  return cast("result.Sequence[*Ts]", res)
583
580
 
584
581
 
@@ -613,6 +610,16 @@ class GetFirmwareVersion(SimpleCopy, CDT):
613
610
  return await Par2Data(Parameter(c.objects.id.f_ver.par[:6]).get_attr(c.objects.id.f_ver.par[6])).exchange(c)
614
611
 
615
612
 
613
+ @dataclass(frozen=True)
614
+ class ReadByDescriptor(SimpleCopy, Simple[bytes]):
615
+ desc: ut.CosemMethodDescriptor
616
+ msg: str = "get encoding by Cosem-Attribute-Descriptor"
617
+
618
+ async def exchange(self, c: Client) -> result.SimpleOrError[bytes]:
619
+ c.get_get_request_normal(self.desc)
620
+ return await c.read_data_block()
621
+
622
+
616
623
  @dataclass(frozen=True)
617
624
  class FindFirmwareVersion(SimpleCopy, Simple[collection.ParameterValue]):
618
625
  msg: str = "try find COSEM server version, return: instance(B group), CDT"
@@ -620,22 +627,24 @@ class FindFirmwareVersion(SimpleCopy, Simple[collection.ParameterValue]):
620
627
  async def exchange(self, c: Client) -> result.SimpleOrError[collection.ParameterValue]:
621
628
  err = result.ErrorAccumulator()
622
629
  for desc in (ut.CosemAttributeDescriptor((1, "0.0.0.2.1.255", 2)), ut.CosemAttributeDescriptor((1, "0.0.96.1.2.255", 2))):
623
- try:
630
+ if isinstance(res_read := await ReadByDescriptor(desc).exchange(c), result.Error):
631
+ err.append_err(res_read.err)
632
+ in_e, out_e = res_read.err.split(exc.ResultError)
633
+ if (
634
+ out_e is None
635
+ and in_e.exceptions[0].result == pdu.DataAccessResult(4)
636
+ ):
637
+ continue
638
+ else:
639
+ return res_read
640
+ else:
624
641
  res = result.Simple(collection.ParameterValue(
625
642
  par=desc.instance_id.contents + desc.attribute_id.contents,
626
- value=await c.read_attr(desc)
643
+ value=res_read.value
627
644
  ))
628
645
  res.propagate_err(err)
629
646
  return res
630
- except exc.ResultError as e:
631
- err.append_err(e)
632
- if e.result == pdu.DataAccessResult.OBJECT_UNDEFINED:
633
- """try search in old object and set to new"""
634
- else:
635
- break
636
- if isinstance((res := err.result), result.Error):
637
- return res
638
- raise RuntimeError(f"empty {err}")
647
+ return err.as_error()
639
648
 
640
649
 
641
650
  @dataclass(frozen=True)
@@ -645,22 +654,24 @@ class FindFirmwareId(SimpleCopy, Simple[collection.ParameterValue]):
645
654
  async def exchange(self, c: Client) -> result.SimpleOrError[collection.ParameterValue]:
646
655
  err = result.ErrorAccumulator()
647
656
  for desc in (ut.CosemAttributeDescriptor((1, "0.0.0.2.0.255", 2)), ut.CosemAttributeDescriptor((1, "0.0.96.1.1.255", 2))):
648
- try:
657
+ if isinstance(res_read := await ReadByDescriptor(desc).exchange(c), result.Error):
658
+ err.append_err(res_read.err)
659
+ in_e, out_e = res_read.err.split(exc.ResultError)
660
+ if (
661
+ out_e is None
662
+ and in_e.exceptions[0].result == pdu.DataAccessResult(4)
663
+ ):
664
+ continue
665
+ else:
666
+ return res_read
667
+ else:
649
668
  res = result.Simple(collection.ParameterValue(
650
669
  par=desc.instance_id.contents + desc.attribute_id.contents,
651
- value=await c.read_attr(desc)
670
+ value=res_read.value
652
671
  ))
653
672
  res.propagate_err(err)
654
673
  return res
655
- except exc.ResultError as e:
656
- err.append_err(e)
657
- if e.result==pdu.DataAccessResult.OBJECT_UNDEFINED:
658
- """try search in old object and set to new"""
659
- else:
660
- break
661
- if isinstance((res := err.result), result.Error):
662
- return res
663
- raise RuntimeError(f"empty {err}")
674
+ return err.as_error()
664
675
 
665
676
 
666
677
  @dataclass(frozen=True)
@@ -678,8 +689,9 @@ class GetLDN(SimpleCopy, CDT[octet_string.LDN]):
678
689
  msg: str = "get LDN"
679
690
 
680
691
  async def exchange(self, c: Client) -> result.SimpleOrError[octet_string.LDN]:
681
- data = await c.read_attr(collection.AttrDesc.LDN_VALUE)
682
- return result.Simple(octet_string.LDN(data))
692
+ if isinstance(res := await ReadByDescriptor(collection.AttrDesc.LDN_VALUE).exchange(c), result.Error):
693
+ return res
694
+ return result.Simple(octet_string.LDN(res.value))
683
695
 
684
696
 
685
697
  # todo: possible implementation ConditionalTask
@@ -749,11 +761,7 @@ class CreateType(SimpleCopy, Simple[collection.Collection]):
749
761
  col: collection.Collection = field(init=False)
750
762
 
751
763
  def __post_init__(self):
752
- self.col = collection.Collection(id_=collection.ID(
753
- man=self.col_id.man,
754
- f_id=self.col_id.f_id,
755
- f_ver=self.col_id.f_ver
756
- ))
764
+ self.col = collection.Collection(id_=self.col_id)
757
765
  """common collection"""
758
766
  self.wait_list = Lock(name="wait <object list>")
759
767
 
@@ -762,7 +770,9 @@ class CreateType(SimpleCopy, Simple[collection.Collection]):
762
770
  await self.wait_list.acquire(c)
763
771
  try:
764
772
  if self.obj_list is None:
765
- self.obj_list = cdt.Array(await c.read_attr(collection.AttrDesc.OBJECT_LIST), type_=structs.ObjectListElement)
773
+ if isinstance(res_obj_list := await ReadByDescriptor(collection.AttrDesc.OBJECT_LIST).exchange(c), result.Error):
774
+ return res_obj_list
775
+ self.obj_list = cdt.Array(res_obj_list.value, type_=structs.ObjectListElement)
766
776
  if len(self.col) == 0:
767
777
  for country_olel in filter(lambda it: ln_pattern.COUNTRY_SPECIFIC == it.logical_name, self.obj_list):
768
778
  self.col.set_country(collection.CountrySpecificIdentifiers(country_olel.logical_name.d))
@@ -776,9 +786,11 @@ class CreateType(SimpleCopy, Simple[collection.Collection]):
776
786
  country_desc is not None
777
787
  and next(filter(lambda it: country_desc.instance_id.contents == it.logical_name.contents, self.obj_list), False)
778
788
  ):
789
+ if isinstance(res_country_ver := await ReadByDescriptor(country_desc).exchange(c), result.Error):
790
+ return res_country_ver
779
791
  self.col.set_country_ver(collection.ParameterValue(
780
792
  par=country_desc.instance_id.contents + country_desc.attribute_id.contents,
781
- value=await c.read_attr(country_desc)
793
+ value=res_country_ver.value
782
794
  ))
783
795
  c.log(logL.INFO, F"set country version: {self.col.country_ver}")
784
796
  else:
@@ -990,6 +1002,7 @@ get_adapter(gag) # Dummy Adapter
990
1002
 
991
1003
 
992
1004
  @dataclass
1005
+ @deprecated("use <ReadObjAttr>")
993
1006
  class ReadAttribute(SimpleCopy, CDT):
994
1007
  ln: collection.LNContaining
995
1008
  index: int
@@ -1013,10 +1026,11 @@ class ReadObjAttr(SimpleCopy, CDT):
1013
1026
  value=self.index,
1014
1027
  with_selection=bool(c.negotiated_conformance.selective_access)))
1015
1028
  start_read_time: float = time.perf_counter()
1016
- await c.read_data_block()
1029
+ if isinstance(res_pdu := await c.read_data_block(), result.Error):
1030
+ return res_pdu
1017
1031
  c.last_transfer_time = datetime.timedelta(seconds=time.perf_counter()-start_read_time)
1018
1032
  try:
1019
- self.obj.set_attr(self.index, c.reply.data.get_data())
1033
+ self.obj.set_attr(self.index, res_pdu.value)
1020
1034
  return result.Simple(self.obj.get_attr(self.index))
1021
1035
  except ValueError as e:
1022
1036
  return result.Error.from_e(e)
@@ -1182,7 +1196,8 @@ class Write(SimpleCopy, OK):
1182
1196
  obj=res_obj.value,
1183
1197
  attr_index=self.par_data.par.i,
1184
1198
  value=enc)
1185
- ret = await c.read_data_block()
1199
+ if isinstance(res_pdu := await c.read_data_block(), result.Error):
1200
+ return res_pdu
1186
1201
  return result.OK
1187
1202
 
1188
1203
 
@@ -1210,7 +1225,8 @@ class Write2(SimpleCopy, OK):
1210
1225
  obj=res_obj.value,
1211
1226
  attr_index=self.par.i,
1212
1227
  value=enc)
1213
- ret = await c.read_data_block()
1228
+ if isinstance(res_pdu := await c.read_data_block(), result.Error):
1229
+ return res_pdu
1214
1230
  return result.OK
1215
1231
 
1216
1232
 
@@ -1250,7 +1266,8 @@ class WriteParValue(SimpleCopy, OK):
1250
1266
  obj=obj,
1251
1267
  attr_index=self.par_value.par.i,
1252
1268
  value=set_data.encoding)
1253
- ret = await c.read_data_block()
1269
+ if isinstance(res_pdu := await c.read_data_block(), result.Error):
1270
+ return res_pdu
1254
1271
  return result.OK
1255
1272
 
1256
1273
 
@@ -1276,7 +1293,8 @@ class WriteAttribute(SimpleCopy, OK):
1276
1293
  obj=obj,
1277
1294
  attr_index=self.index,
1278
1295
  value=enc)
1279
- ret = await c.read_data_block()
1296
+ if isinstance(res_pdu := await c.read_data_block(), result.Error):
1297
+ return res_pdu
1280
1298
  return result.OK # todo: return Data-Access-Result
1281
1299
 
1282
1300
 
@@ -1328,7 +1346,8 @@ class Execute2(SimpleCopy, OK):
1328
1346
  meth_desc=res.value.get_meth_descriptor(self.par.i),
1329
1347
  method=self.data
1330
1348
  )
1331
- await c.read_data_block()
1349
+ if isinstance(res_pdu := await c.read_data_block(), result.Error):
1350
+ return res_pdu
1332
1351
  return result.OK
1333
1352
  except Exception as e:
1334
1353
  return result.Error.from_e(e)
@@ -1341,8 +1360,9 @@ class WriteTime(SimpleCopy, Base):
1341
1360
  try:
1342
1361
  obj = c._objects.clock
1343
1362
  c.get_get_request_normal(obj.get_attr_descriptor(3))
1344
- await c.read_data_block()
1345
- tz = obj.get_attr_element(3).DATA_TYPE(c.reply.data.get_data())
1363
+ if isinstance(res_pdu := await c.read_data_block(), result.Error):
1364
+ return res_pdu
1365
+ tz = obj.get_attr_element(3).DATA_TYPE(res_pdu.value)
1346
1366
  res = await WriteAttribute(
1347
1367
  ln=obj.logical_name,
1348
1368
  index=2,
@@ -1561,56 +1581,36 @@ class ActivateImage(SimpleCopy, OK):
1561
1581
  return result.OK
1562
1582
 
1563
1583
 
1564
- class TestAll(Base):
1584
+ # todo: don't work with new API, remake
1585
+ class TestAll(OK):
1565
1586
  """read all attributes with check access""" # todo: add Write with access
1566
1587
  msg: str = "test all"
1567
1588
 
1568
- async def exchange(self, c: Client):
1589
+ async def exchange(self, c: Client) -> result.Ok | result.Error:
1569
1590
  # todo: refactoring with <append_err>
1570
- res = result.Simple()
1571
- if await init_type.exchange(c):
1572
- ass: collection.AssociationLN = c._objects.sap2association(c.SAP)
1573
- for obj in tuple(c._objects):
1574
- indexes: list[int] = [i for i, _ in obj.get_index_with_attributes()]
1575
- attempt = count(1)
1576
- while True:
1577
- c.log(logL.INFO, F"start read {obj} attr: {', '.join(map(str, indexes))}. attempt={next(attempt)}")
1578
- len_indexes: int = len(indexes)
1579
- for i in tuple(indexes):
1580
- is_readable = ass.is_readable(
1581
- ln=obj.logical_name,
1582
- index=i)
1583
- try:
1584
- await ReadAttribute(
1585
- ln=obj.logical_name,
1586
- index=i
1587
- ).exchange(c)
1588
- if not is_readable:
1589
- c.log(logL.ERR, F"{obj} with attr={i} must be unreadable")
1590
- indexes.remove(i)
1591
- c.log(logL.INFO, F"Get {obj}:{obj.get_attr_element(i)}")
1592
- if res.value is None:
1593
- c.log(logL.WARN, F"ValueError. For {obj} attr:{i} {res}")
1594
- except exc.ResultError as e:
1595
- if e.result == pdu.DataAccessResult.READ_WRITE_DENIED and not is_readable:
1596
- c.log(logL.INFO, F"success ReadAccess TEST: {e}")
1597
- indexes.remove(i)
1598
- else:
1599
- c.log(logL.ERR, F"result_error {e}")
1600
- except exc.ITEApplication as e:
1601
- c.log(logL.ERR, F"ITEApplication. For {obj} attr:{i} {e}")
1602
- except Exception as e:
1603
- c.log(logL.ERR, F"Unknown. For {obj} attr:{i} {e}")
1604
- if len(indexes) == 0:
1605
- c.log(logL.INFO, "all read success")
1606
- break
1607
- elif len(indexes) == len_indexes:
1608
- c.log(logL.ERR, F"can't read attr={', '.join(map(str, indexes))}")
1609
- break
1591
+ res = result.ErrorAccumulator()
1592
+ if isinstance(res_objects := c.objects.sap2objects(c.SAP), result.Error):
1593
+ return res_objects
1594
+ ass: collection.AssociationLN = c.objects.sap2association(c.SAP)
1595
+ for obj in res_objects.value:
1596
+ indexes: list[int] = [i for i, _ in obj.get_index_with_attributes()]
1597
+ c.log(logL.INFO, F"start read {obj} attr: {', '.join(map(str, indexes))}")
1598
+ for i in indexes:
1599
+ is_readable = ass.is_readable(
1600
+ ln=obj.logical_name,
1601
+ index=i)
1602
+ if isinstance(res_read := await ReadObjAttr(obj, i).exchange(c), result.Error):
1603
+ if (
1604
+ res_read.has(pdu.DataAccessResult.READ_WRITE_DENIED, exc.ResultError)
1605
+ and not is_readable
1606
+ ):
1607
+ c.log(logL.INFO, F"success ReadAccess TEST")
1610
1608
  else:
1611
- c.log(logL.WARN, F"has a errors in attr={', '.join(map(str, indexes))}. Need more attempt")
1612
- # todo: check write problem - if send_frame clear then counter is wrong, need fix in client it
1613
- return res
1609
+ res.append_err(res_read.err)
1610
+ elif not is_readable:
1611
+ res.append_e(PermissionError(f"{obj} with attr={i} must be unreadable"))
1612
+ indexes.remove(i)
1613
+ return res.result
1614
1614
 
1615
1615
 
1616
1616
  @dataclass
@@ -1674,50 +1674,39 @@ class ReadTemplate(OK):
1674
1674
 
1675
1675
 
1676
1676
  @dataclass
1677
- class AccessValidate(Base[result.Ok | result.Simple[list[Parameter]]]):
1677
+ class AccessValidate(OK):
1678
1678
  """check all access rights for current SAP"""
1679
1679
  with_correct: bool = False
1680
1680
  msg: str = "all access validate"
1681
1681
 
1682
1682
  # todo: make with result.Error
1683
- async def exchange(self, c: Client) -> result.Ok | result.Simple[list[Parameter]] | result.Error:
1683
+ async def exchange(self, c: Client) -> result.Ok | result.Error:
1684
+ res = result.ErrorAccumulator()
1684
1685
  obj_l: ObjectListType
1685
1686
  el: ObjectListElement
1686
1687
  a_a_i: association_ln.abstract.AttributeAccessItem
1687
- if c._objects is None:
1688
- return result.Error.from_e(exc.DLMSException("not find Collection"))
1689
- elif (obj_l := c._objects.sap2association(c.SAP).object_list) is None:
1688
+ if (obj_l := c.objects.sap2association(c.SAP).object_list) is None:
1690
1689
  return result.Error.from_e(exc.EmptyObj(F"empty object_list for {c._objects.sap2association(c.SAP)}"))
1691
- res = result.Simple(value=[])
1692
1690
  for el in obj_l:
1693
1691
  for a_a_i in el.access_rights.attribute_access:
1694
1692
  if a_a_i.access_mode.is_readable():
1695
1693
  i = int(a_a_i.attribute_id)
1696
- try:
1697
- res_ = await ReadAttribute(
1698
- ln=el.logical_name,
1699
- index=int(a_a_i.attribute_id)
1700
- ).exchange(c)
1701
- if a_a_i.access_mode.is_writable():
1702
- await WriteAttribute(
1703
- ln=el.logical_name,
1704
- index=i,
1705
- value=res_.value.encoding
1706
- ).exchange(c)
1707
- except exc.ResultError as e:
1708
- res.append_e(e)
1709
- res.value.append(Parameter(el.logical_name.contents).set_i(i))
1694
+ if isinstance(res_read :=await ReadByDescriptor(ut.CosemAttributeDescriptor((
1695
+ int(el.class_id),
1696
+ el.logical_name.contents,
1697
+ i
1698
+ ))).exchange(c), result.Error):
1699
+ res.append_err(res_read.err)
1710
1700
  if self.with_correct:
1711
1701
  a_a_i.access_mode.set(1) # todo: make better in future
1712
- except exc.Timeout as e:
1713
- c.log(logL.ERR, F"for {el.logical_name}: {i} - {e}")
1714
- await HardwareReconnect().exchange(c)
1715
- except Exception as e:
1716
- res.append_e(e)
1717
- res.value.append(Parameter(el.logical_name.contents).set_i(i))
1718
- if not res.is_ok():
1719
- return res
1720
- return result.OK
1702
+ elif a_a_i.access_mode.is_writable():
1703
+ if isinstance(res_write :=await WriteAttribute(
1704
+ ln=el.logical_name,
1705
+ index=i,
1706
+ value=res_read.value
1707
+ ).exchange(c), result.Error):
1708
+ res.append_err(res_write.err)
1709
+ return res.result
1721
1710
 
1722
1711
 
1723
1712
  @dataclass
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: DLMS_SPODES_client
3
- Version: 0.19.13
3
+ Version: 0.19.14
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
@@ -15,7 +15,7 @@ Requires-Dist: pycryptodomex>=3.22.0
15
15
  Requires-Dist: semver>=3.0
16
16
  Requires-Dist: StructResult<0.10,>=0.9.2
17
17
  Requires-Dist: DLMS-SPODES-communications<1.5,>=1.4.9
18
- Requires-Dist: DLMS_SPODES<0.88,>=0.87.7
18
+ Requires-Dist: DLMS_SPODES<0.88,>=0.87.8
19
19
  Requires-Dist: DLMSadapter<0.8,>=0.7.5
20
20
  Requires-Dist: DLMSCommunicationProfile<0.2,>=0.1.11
21
21
  Requires-Dist: numpy>=2.3.2
@@ -1,17 +1,17 @@
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=WuWg9OW3uRMQ4EtcWPEqUuG9QQLjNyL-JR2gZSTUifo,112222
3
+ DLMS_SPODES_client/client.py,sha256=T_8nI6_tCWK6Us74gjIM_2I8EtETs4dQX4ygFmnV70A,112333
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=1sa4wJny2xwDqL3DXpfYTS7mZcabVdC5fW1H60VB5QA,12681
8
8
  DLMS_SPODES_client/settings.py,sha256=6mitGe9UYeEgL61sf933MJ-S5N-ReoxvXqiI3agBGYE,1623
9
- DLMS_SPODES_client/task.py,sha256=xgiEk7Fu3pp24rgVcRQ_R2asK9rZwA0KhtZXfvZYi6Q,76509
9
+ DLMS_SPODES_client/task.py,sha256=W-tlB3iqkz1ui_QmvXq_ON90Xhiln1Y1a0PgZI3Mtb8,76260
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
13
13
  DLMS_SPODES_client/gurux_dlms/CountType.py,sha256=VtpybVSaPpCq6Me3WkYk3SLmIPgi6Yp32IUjuJ-7fAo,264
14
- DLMS_SPODES_client/gurux_dlms/GXByteBuffer.py,sha256=qoq5h8HlBltIwkvWyK42lRL6eV_KDFXjlZT0OMhNgTo,17658
14
+ DLMS_SPODES_client/gurux_dlms/GXByteBuffer.py,sha256=MiX-fegn-Ptfu8jHzocckDFhf4oZSNCpNOMONgZ1d44,17708
15
15
  DLMS_SPODES_client/gurux_dlms/GXCiphering.py,sha256=Py0PFm4PVUrxIZEN4UmBAok0UZ0nH1JpGGLes0aOhjk,5909
16
16
  DLMS_SPODES_client/gurux_dlms/GXDLMS.py,sha256=Up5hQpoS2MPbycDW1vjQprbKWGOC5xBYuvnvpAdLBrk,17458
17
17
  DLMS_SPODES_client/gurux_dlms/GXDLMSChippering.py,sha256=HxOtiyOUrdFreEcUHNCU8oq9dBgtvX8g65q932cDb1w,10122
@@ -21,7 +21,7 @@ DLMS_SPODES_client/gurux_dlms/GXDLMSException.py,sha256=SME3os97MbZ-b65z2HQvU40b
21
21
  DLMS_SPODES_client/gurux_dlms/GXDLMSLNParameters.py,sha256=JxFiv8Xh8VC2VcogoprKsun6iTZX_kyz3Qho8MdO-GM,1347
22
22
  DLMS_SPODES_client/gurux_dlms/GXDLMSSNParameters.py,sha256=dKDVbtTUsG3Y_39TUVZXVvNcsyxpPVWvdNsXAFgxzGY,699
23
23
  DLMS_SPODES_client/gurux_dlms/GXDLMSSettings.py,sha256=fCXt2j1smev-r_iW-hP_PyCJzHEZzXF87_1LARse8GE,8146
24
- DLMS_SPODES_client/gurux_dlms/GXReplyData.py,sha256=Aq8G5aAK__NzNzAh6YIZ-KZPeu0HtqTKi3aTJ7UfOMQ,4476
24
+ DLMS_SPODES_client/gurux_dlms/GXReplyData.py,sha256=XDddMjyzlCmyQJ5XOMjz1lstAbk5Xu0DKYx1_DyFkCI,3057
25
25
  DLMS_SPODES_client/gurux_dlms/HdlcControlFrame.py,sha256=uB_lZfutg0xj8of7Rjhg4iYEFb08Z4BR3KLCMj0ih4g,193
26
26
  DLMS_SPODES_client/gurux_dlms/MBusCommand.py,sha256=VlSbupvSReAb-n9QeeQcksjdQ_hFRtpwiPCOZo4k2u0,143
27
27
  DLMS_SPODES_client/gurux_dlms/MBusEncryptionMode.py,sha256=InIXEUNhq_IENiXfxHO-PRnDAF7PgZ_yNNNBAssXQgo,623
@@ -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.13.dist-info/METADATA,sha256=T6U2znlwbvDl53LDxlksEmmOXJKGett3mwG5QeR08UI,986
58
- dlms_spodes_client-0.19.13.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
59
- dlms_spodes_client-0.19.13.dist-info/entry_points.txt,sha256=Z6UTeQjjCf2k1Y3Bjs0s7yr-UYSWb-TvJMuG2K2MApw,70
60
- dlms_spodes_client-0.19.13.dist-info/top_level.txt,sha256=rh_3Uig5bc6J_lKni01btol7dX_IgIJulNtGjGehmBE,19
61
- dlms_spodes_client-0.19.13.dist-info/RECORD,,
57
+ dlms_spodes_client-0.19.14.dist-info/METADATA,sha256=2JnJR8pivvNowQChuQcYsa9nyTjw5ZFBGWUpeoRyZCk,986
58
+ dlms_spodes_client-0.19.14.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
59
+ dlms_spodes_client-0.19.14.dist-info/entry_points.txt,sha256=Z6UTeQjjCf2k1Y3Bjs0s7yr-UYSWb-TvJMuG2K2MApw,70
60
+ dlms_spodes_client-0.19.14.dist-info/top_level.txt,sha256=rh_3Uig5bc6J_lKni01btol7dX_IgIJulNtGjGehmBE,19
61
+ dlms_spodes_client-0.19.14.dist-info/RECORD,,