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.
- DLMS_SPODES_client/client.py +308 -304
- DLMS_SPODES_client/gurux_dlms/GXByteBuffer.py +4 -4
- DLMS_SPODES_client/gurux_dlms/GXReplyData.py +44 -91
- DLMS_SPODES_client/task.py +152 -163
- {dlms_spodes_client-0.19.13.dist-info → dlms_spodes_client-0.19.14.dist-info}/METADATA +2 -2
- {dlms_spodes_client-0.19.13.dist-info → dlms_spodes_client-0.19.14.dist-info}/RECORD +9 -9
- {dlms_spodes_client-0.19.13.dist-info → dlms_spodes_client-0.19.14.dist-info}/WHEEL +0 -0
- {dlms_spodes_client-0.19.13.dist-info → dlms_spodes_client-0.19.14.dist-info}/entry_points.txt +0 -0
- {dlms_spodes_client-0.19.13.dist-info → dlms_spodes_client-0.19.14.dist-info}/top_level.txt +0 -0
DLMS_SPODES_client/task.py
CHANGED
|
@@ -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) ->
|
|
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) ->
|
|
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
|
-
|
|
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
|
-
|
|
156
|
-
|
|
157
|
-
c.settings.cipher.invocationCounter = 1 + int(
|
|
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(
|
|
214
|
+
match c.parseAareResponse(res_pdu.value):
|
|
212
215
|
case AcseServiceUser.NULL:
|
|
213
|
-
c.log(logL.INFO,
|
|
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
|
-
|
|
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
|
-
|
|
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) ->
|
|
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 =
|
|
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
|
-
|
|
532
|
-
|
|
533
|
-
|
|
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
|
-
|
|
574
|
-
if
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
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
|
-
|
|
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=
|
|
643
|
+
value=res_read.value
|
|
627
644
|
))
|
|
628
645
|
res.propagate_err(err)
|
|
629
646
|
return res
|
|
630
|
-
|
|
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
|
-
|
|
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=
|
|
670
|
+
value=res_read.value
|
|
652
671
|
))
|
|
653
672
|
res.propagate_err(err)
|
|
654
673
|
return res
|
|
655
|
-
|
|
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
|
-
|
|
682
|
-
|
|
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_=
|
|
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
|
-
|
|
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=
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
1571
|
-
if
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
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
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
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(
|
|
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.
|
|
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.
|
|
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
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
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
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
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.
|
|
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.
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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.
|
|
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.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,,
|
|
File without changes
|
{dlms_spodes_client-0.19.13.dist-info → dlms_spodes_client-0.19.14.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|