DLMS-SPODES-client 0.19.12__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 +309 -304
- DLMS_SPODES_client/gurux_dlms/GXByteBuffer.py +4 -4
- DLMS_SPODES_client/gurux_dlms/GXReplyData.py +44 -91
- DLMS_SPODES_client/task.py +198 -208
- {dlms_spodes_client-0.19.12.dist-info → dlms_spodes_client-0.19.14.dist-info}/METADATA +2 -2
- {dlms_spodes_client-0.19.12.dist-info → dlms_spodes_client-0.19.14.dist-info}/RECORD +9 -9
- {dlms_spodes_client-0.19.12.dist-info → dlms_spodes_client-0.19.14.dist-info}/WHEEL +0 -0
- {dlms_spodes_client-0.19.12.dist-info → dlms_spodes_client-0.19.14.dist-info}/entry_points.txt +0 -0
- {dlms_spodes_client-0.19.12.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:
|
|
@@ -291,11 +294,11 @@ class StrictOK(Base[result.StrictOk], Protocol):
|
|
|
291
294
|
async def exchange(self, c: Client) -> result.StrictOk | result.Error: ...
|
|
292
295
|
|
|
293
296
|
|
|
294
|
-
@dataclass
|
|
297
|
+
@dataclass(frozen=True)
|
|
295
298
|
class ClientBlocking(SimpleCopy, OK):
|
|
296
299
|
"""complete by time or abort"""
|
|
297
|
-
delay:
|
|
298
|
-
msg: str = ""
|
|
300
|
+
delay: float = field(default=99999999.0)
|
|
301
|
+
msg: str = "client blocking"
|
|
299
302
|
|
|
300
303
|
async def run(self, c: Client) -> result.Ok | result.Error:
|
|
301
304
|
try:
|
|
@@ -312,8 +315,9 @@ class ClientBlocking(SimpleCopy, OK):
|
|
|
312
315
|
|
|
313
316
|
|
|
314
317
|
# todo: make with <data_link>
|
|
318
|
+
@dataclass
|
|
315
319
|
class TestDataLink(SimpleCopy, OK):
|
|
316
|
-
msg: str = ""
|
|
320
|
+
msg: str = "test DLink"
|
|
317
321
|
|
|
318
322
|
async def physical(self, c: Client) -> result.Ok | result.Error:
|
|
319
323
|
if OSI.PHYSICAL not in c.level:
|
|
@@ -327,27 +331,29 @@ class TestDataLink(SimpleCopy, OK):
|
|
|
327
331
|
)
|
|
328
332
|
c.SA = frame.Address(upper_address=int(c.SAP))
|
|
329
333
|
c.get_SNRM_request()
|
|
330
|
-
await c.read_data_block()
|
|
334
|
+
if isinstance(res_pdu := await c.read_data_block(), result.Error):
|
|
335
|
+
return res_pdu
|
|
331
336
|
c.level |= OSI.DATA_LINK
|
|
332
|
-
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
|
|
333
339
|
return result.Ok
|
|
334
340
|
|
|
335
341
|
async def exchange(self, c: Client):
|
|
336
342
|
return result.OK
|
|
337
343
|
|
|
338
344
|
|
|
339
|
-
@dataclass
|
|
345
|
+
@dataclass(frozen=True)
|
|
340
346
|
class Dummy(SimpleCopy, OK):
|
|
341
|
-
msg: str = ""
|
|
347
|
+
msg: str = "dummy"
|
|
342
348
|
|
|
343
349
|
async def exchange(self, c: Client) -> result.Ok | result.Error:
|
|
344
350
|
""""""
|
|
345
351
|
return result.OK
|
|
346
352
|
|
|
347
353
|
|
|
348
|
-
@dataclass
|
|
354
|
+
@dataclass(frozen=True)
|
|
349
355
|
class HardwareDisconnect(SimpleCopy, OK):
|
|
350
|
-
msg: str = ""
|
|
356
|
+
msg: str = "hardware disconnect"
|
|
351
357
|
|
|
352
358
|
@override
|
|
353
359
|
async def exchange(self, c: Client) -> result.Ok | result.Error:
|
|
@@ -358,9 +364,9 @@ class HardwareDisconnect(SimpleCopy, OK):
|
|
|
358
364
|
return result.OK
|
|
359
365
|
|
|
360
366
|
|
|
361
|
-
@dataclass
|
|
367
|
+
@dataclass(frozen=True)
|
|
362
368
|
class HardwareReconnect(SimpleCopy, OK):
|
|
363
|
-
delay:
|
|
369
|
+
delay: float = 0.0
|
|
364
370
|
"""delay between disconnect and restore Application"""
|
|
365
371
|
msg: str = "reconnect media without response"
|
|
366
372
|
|
|
@@ -370,17 +376,18 @@ class HardwareReconnect(SimpleCopy, OK):
|
|
|
370
376
|
if self.delay != 0.0:
|
|
371
377
|
c.log(logL.INFO, F"delay({self.delay})")
|
|
372
378
|
await asyncio.sleep(self.delay)
|
|
373
|
-
await self.connect(c) # restore Application
|
|
379
|
+
if isinstance(res_connect := await self.connect(c), result.Error): # restore Application
|
|
380
|
+
return res_connect
|
|
374
381
|
return result.OK
|
|
375
382
|
|
|
376
383
|
|
|
377
|
-
@dataclass
|
|
384
|
+
@dataclass(frozen=True)
|
|
378
385
|
class Loop(OK):
|
|
379
386
|
task: Base[result.Result]
|
|
380
387
|
func: Callable[[Any], bool]
|
|
381
|
-
delay:
|
|
382
|
-
msg: str = ""
|
|
383
|
-
attempt_amount:
|
|
388
|
+
delay: int = 0.0
|
|
389
|
+
msg: str = "loop"
|
|
390
|
+
attempt_amount: int = 0
|
|
384
391
|
"""0 is never end loop"""
|
|
385
392
|
|
|
386
393
|
def copy(self) -> Self:
|
|
@@ -411,7 +418,7 @@ class ConditionalTask[T](Base):
|
|
|
411
418
|
comp_value: T
|
|
412
419
|
predicate: Callable[[T, T], bool]
|
|
413
420
|
main_task: Base[result.Result]
|
|
414
|
-
msg: str
|
|
421
|
+
msg: str = "conditional task"
|
|
415
422
|
|
|
416
423
|
async def exchange(self, c: Client) -> result.Result:
|
|
417
424
|
res = await self.precondition_task.exchange(c)
|
|
@@ -434,7 +441,7 @@ class Scheduler[T: result.Result](Base[T]):
|
|
|
434
441
|
repetition_delay_min: Final[int] = 1
|
|
435
442
|
repetition_delay_exponent: Final[int] = 100
|
|
436
443
|
repetition_delay_max: Final[int] = 100
|
|
437
|
-
msg: str = ""
|
|
444
|
+
msg: str = "sheduler"
|
|
438
445
|
|
|
439
446
|
def copy(self) -> "Scheduler[T]":
|
|
440
447
|
return Scheduler(
|
|
@@ -492,11 +499,11 @@ class Subtasks[U: Base[result.Result]](Protocol):
|
|
|
492
499
|
class List[T: result.Result, U: Base[result.Result]](Subtasks[U], _List[T]):
|
|
493
500
|
"""for exchange task sequence"""
|
|
494
501
|
__is_exchange: bool
|
|
495
|
-
msg: str
|
|
496
502
|
err_ignore: bool
|
|
503
|
+
msg: str
|
|
497
504
|
__current: Base[T]
|
|
498
505
|
|
|
499
|
-
def __init__(self, *tasks: Base[T], msg: str = "", err_ignore: bool =
|
|
506
|
+
def __init__(self, *tasks: Base[T], msg: str = "", err_ignore: bool = False):
|
|
500
507
|
self.tasks = list(tasks)
|
|
501
508
|
self.__current = self
|
|
502
509
|
self.__is_exchange = False
|
|
@@ -527,15 +534,9 @@ class List[T: result.Result, U: Base[result.Result]](Subtasks[U], _List[T]):
|
|
|
527
534
|
self.__is_exchange = True
|
|
528
535
|
for t in self.tasks:
|
|
529
536
|
self.__current = t
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
res.append(result.Error.from_e(e, msg=t.msg))
|
|
534
|
-
if (
|
|
535
|
-
not self.err_ignore
|
|
536
|
-
and not res.is_ok()
|
|
537
|
-
):
|
|
538
|
-
break
|
|
537
|
+
res.append(await t.exchange(c))
|
|
538
|
+
if not self.err_ignore:
|
|
539
|
+
return result.Error(res.err)
|
|
539
540
|
return res
|
|
540
541
|
|
|
541
542
|
|
|
@@ -546,7 +547,7 @@ class Sequence[*Ts](Subtasks[Base[result.Result]], _Sequence[*Ts]):
|
|
|
546
547
|
__current: "Base[result.Result] | Sequence[*Ts]"
|
|
547
548
|
tasks: tuple[Base[result.Result], ...]
|
|
548
549
|
|
|
549
|
-
def __init__(self, *tasks: Base[result.Result], msg: str = "", err_ignore: bool = False):
|
|
550
|
+
def __init__(self, *tasks: Base[result.Result], msg: str = "sequence", err_ignore: bool = False):
|
|
550
551
|
self.tasks = tasks
|
|
551
552
|
self.__current = self
|
|
552
553
|
self.msg = self.__class__.__name__ if msg == "" else msg
|
|
@@ -569,21 +570,19 @@ class Sequence[*Ts](Subtasks[Base[result.Result]], _Sequence[*Ts]):
|
|
|
569
570
|
res = result.Sequence()
|
|
570
571
|
for t in self.tasks:
|
|
571
572
|
self.__current = t
|
|
572
|
-
|
|
573
|
-
if
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
):
|
|
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
577
|
return res_one
|
|
578
|
-
|
|
579
|
-
except exc.ResultError as e: # todo: make without except
|
|
580
|
-
res.add(result.Error.from_e(e).with_msg(t.msg))
|
|
578
|
+
res = res.add(res_one)
|
|
581
579
|
return cast("result.Sequence[*Ts]", res)
|
|
582
580
|
|
|
583
581
|
|
|
582
|
+
@dataclass(frozen=True)
|
|
584
583
|
class SetLocalTime(SimpleCopy, OK):
|
|
585
584
|
"""without decide time transfer"""
|
|
586
|
-
msg: str
|
|
585
|
+
msg: str = "set local time"
|
|
587
586
|
|
|
588
587
|
async def exchange(self, c: Client) -> result.Ok | result.Error:
|
|
589
588
|
clock_obj: Clock = c.objects.get_object("0.0.1.0.0.255")
|
|
@@ -603,68 +602,81 @@ class SetLocalTime(SimpleCopy, OK):
|
|
|
603
602
|
return result.OK
|
|
604
603
|
|
|
605
604
|
|
|
605
|
+
@dataclass(frozen=True)
|
|
606
606
|
class GetFirmwareVersion(SimpleCopy, CDT):
|
|
607
|
-
"
|
|
608
|
-
msg: str
|
|
607
|
+
msg: str = "get firmware version"
|
|
609
608
|
|
|
610
609
|
async def exchange(self, c: Client) -> result.SimpleOrError[cdt.CommonDataType]:
|
|
611
610
|
return await Par2Data(Parameter(c.objects.id.f_ver.par[:6]).get_attr(c.objects.id.f_ver.par[6])).exchange(c)
|
|
612
611
|
|
|
613
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
|
+
|
|
623
|
+
@dataclass(frozen=True)
|
|
614
624
|
class FindFirmwareVersion(SimpleCopy, Simple[collection.ParameterValue]):
|
|
615
|
-
"
|
|
616
|
-
msg: str
|
|
625
|
+
msg: str = "try find COSEM server version, return: instance(B group), CDT"
|
|
617
626
|
|
|
618
627
|
async def exchange(self, c: Client) -> result.SimpleOrError[collection.ParameterValue]:
|
|
619
628
|
err = result.ErrorAccumulator()
|
|
620
629
|
for desc in (ut.CosemAttributeDescriptor((1, "0.0.0.2.1.255", 2)), ut.CosemAttributeDescriptor((1, "0.0.96.1.2.255", 2))):
|
|
621
|
-
|
|
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:
|
|
622
641
|
res = result.Simple(collection.ParameterValue(
|
|
623
642
|
par=desc.instance_id.contents + desc.attribute_id.contents,
|
|
624
|
-
value=
|
|
643
|
+
value=res_read.value
|
|
625
644
|
))
|
|
626
645
|
res.propagate_err(err)
|
|
627
646
|
return res
|
|
628
|
-
|
|
629
|
-
err.append_err(e)
|
|
630
|
-
if e.result == pdu.DataAccessResult.OBJECT_UNDEFINED:
|
|
631
|
-
"""try search in old object and set to new"""
|
|
632
|
-
else:
|
|
633
|
-
break
|
|
634
|
-
if isinstance((res := err.result), result.Error):
|
|
635
|
-
return res
|
|
636
|
-
raise RuntimeError(f"empty {err}")
|
|
647
|
+
return err.as_error()
|
|
637
648
|
|
|
638
649
|
|
|
650
|
+
@dataclass(frozen=True)
|
|
639
651
|
class FindFirmwareId(SimpleCopy, Simple[collection.ParameterValue]):
|
|
640
|
-
"""try find COSEM server identifier, return: FirmwareID"""
|
|
641
652
|
msg: str = "find firmaware Identifier"
|
|
642
653
|
|
|
643
654
|
async def exchange(self, c: Client) -> result.SimpleOrError[collection.ParameterValue]:
|
|
644
655
|
err = result.ErrorAccumulator()
|
|
645
656
|
for desc in (ut.CosemAttributeDescriptor((1, "0.0.0.2.0.255", 2)), ut.CosemAttributeDescriptor((1, "0.0.96.1.1.255", 2))):
|
|
646
|
-
|
|
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:
|
|
647
668
|
res = result.Simple(collection.ParameterValue(
|
|
648
669
|
par=desc.instance_id.contents + desc.attribute_id.contents,
|
|
649
|
-
value=
|
|
670
|
+
value=res_read.value
|
|
650
671
|
))
|
|
651
672
|
res.propagate_err(err)
|
|
652
673
|
return res
|
|
653
|
-
|
|
654
|
-
err.append_err(e)
|
|
655
|
-
if e.result==pdu.DataAccessResult.OBJECT_UNDEFINED:
|
|
656
|
-
"""try search in old object and set to new"""
|
|
657
|
-
else:
|
|
658
|
-
break
|
|
659
|
-
if isinstance((res := err.result), result.Error):
|
|
660
|
-
return res
|
|
661
|
-
raise RuntimeError(f"empty {err}")
|
|
674
|
+
return err.as_error()
|
|
662
675
|
|
|
663
676
|
|
|
664
|
-
@dataclass
|
|
677
|
+
@dataclass(frozen=True)
|
|
665
678
|
class KeepAlive(SimpleCopy, OK):
|
|
666
|
-
"
|
|
667
|
-
msg: str = "keep alive"
|
|
679
|
+
msg: str = "keep alive(read LND.ln)"
|
|
668
680
|
|
|
669
681
|
async def exchange(self, c: Client) -> result.Ok | result.Error:
|
|
670
682
|
if isinstance(res := await Par2Data(dlms_par.LDN.value).exchange(c), result.Error):
|
|
@@ -677,8 +689,9 @@ class GetLDN(SimpleCopy, CDT[octet_string.LDN]):
|
|
|
677
689
|
msg: str = "get LDN"
|
|
678
690
|
|
|
679
691
|
async def exchange(self, c: Client) -> result.SimpleOrError[octet_string.LDN]:
|
|
680
|
-
|
|
681
|
-
|
|
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))
|
|
682
695
|
|
|
683
696
|
|
|
684
697
|
# todo: possible implementation ConditionalTask
|
|
@@ -697,7 +710,7 @@ class GetLDN(SimpleCopy, CDT[octet_string.LDN]):
|
|
|
697
710
|
@dataclass
|
|
698
711
|
class MatchLDN(OK):
|
|
699
712
|
universal: bool = field(default=False)
|
|
700
|
-
msg: str = ""
|
|
713
|
+
msg: str = "matching LDN"
|
|
701
714
|
|
|
702
715
|
async def exchange(self, c: Client) -> result.Ok | result.Error:
|
|
703
716
|
if isinstance((res := await GetLDN().exchange(c)), result.Error):
|
|
@@ -748,11 +761,7 @@ class CreateType(SimpleCopy, Simple[collection.Collection]):
|
|
|
748
761
|
col: collection.Collection = field(init=False)
|
|
749
762
|
|
|
750
763
|
def __post_init__(self):
|
|
751
|
-
self.col = collection.Collection(id_=
|
|
752
|
-
man=self.col_id.man,
|
|
753
|
-
f_id=self.col_id.f_id,
|
|
754
|
-
f_ver=self.col_id.f_ver
|
|
755
|
-
))
|
|
764
|
+
self.col = collection.Collection(id_=self.col_id)
|
|
756
765
|
"""common collection"""
|
|
757
766
|
self.wait_list = Lock(name="wait <object list>")
|
|
758
767
|
|
|
@@ -761,7 +770,9 @@ class CreateType(SimpleCopy, Simple[collection.Collection]):
|
|
|
761
770
|
await self.wait_list.acquire(c)
|
|
762
771
|
try:
|
|
763
772
|
if self.obj_list is None:
|
|
764
|
-
|
|
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)
|
|
765
776
|
if len(self.col) == 0:
|
|
766
777
|
for country_olel in filter(lambda it: ln_pattern.COUNTRY_SPECIFIC == it.logical_name, self.obj_list):
|
|
767
778
|
self.col.set_country(collection.CountrySpecificIdentifiers(country_olel.logical_name.d))
|
|
@@ -775,9 +786,11 @@ class CreateType(SimpleCopy, Simple[collection.Collection]):
|
|
|
775
786
|
country_desc is not None
|
|
776
787
|
and next(filter(lambda it: country_desc.instance_id.contents == it.logical_name.contents, self.obj_list), False)
|
|
777
788
|
):
|
|
789
|
+
if isinstance(res_country_ver := await ReadByDescriptor(country_desc).exchange(c), result.Error):
|
|
790
|
+
return res_country_ver
|
|
778
791
|
self.col.set_country_ver(collection.ParameterValue(
|
|
779
792
|
par=country_desc.instance_id.contents + country_desc.attribute_id.contents,
|
|
780
|
-
value=
|
|
793
|
+
value=res_country_ver.value
|
|
781
794
|
))
|
|
782
795
|
c.log(logL.INFO, F"set country version: {self.col.country_ver}")
|
|
783
796
|
else:
|
|
@@ -945,7 +958,7 @@ class MapTypeCreator:
|
|
|
945
958
|
@dataclass
|
|
946
959
|
class InitType(SimpleCopy, Simple[collection.Collection]):
|
|
947
960
|
adapter: Adapter
|
|
948
|
-
msg: str = ""
|
|
961
|
+
msg: str = "initiate type"
|
|
949
962
|
|
|
950
963
|
async def exchange(self, c: Client) -> result.SimpleOrError[collection.Collection]:
|
|
951
964
|
if isinstance((res := await Sequence(
|
|
@@ -989,10 +1002,11 @@ get_adapter(gag) # Dummy Adapter
|
|
|
989
1002
|
|
|
990
1003
|
|
|
991
1004
|
@dataclass
|
|
1005
|
+
@deprecated("use <ReadObjAttr>")
|
|
992
1006
|
class ReadAttribute(SimpleCopy, CDT):
|
|
993
1007
|
ln: collection.LNContaining
|
|
994
1008
|
index: int
|
|
995
|
-
msg: str = ""
|
|
1009
|
+
msg: str = "read LN attribute"
|
|
996
1010
|
|
|
997
1011
|
async def exchange(self, c: Client) -> result.Simple[cdt.CommonDataType]:
|
|
998
1012
|
obj = c.objects.get_object(self.ln)
|
|
@@ -1003,7 +1017,7 @@ class ReadAttribute(SimpleCopy, CDT):
|
|
|
1003
1017
|
class ReadObjAttr(SimpleCopy, CDT):
|
|
1004
1018
|
obj: collection.InterfaceClass
|
|
1005
1019
|
index: int
|
|
1006
|
-
msg: str = ""
|
|
1020
|
+
msg: str = "read object attribute"
|
|
1007
1021
|
|
|
1008
1022
|
async def exchange(self, c: Client) -> result.SimpleOrError[cdt.CommonDataType]:
|
|
1009
1023
|
# TODO: check is_readable?
|
|
@@ -1012,10 +1026,11 @@ class ReadObjAttr(SimpleCopy, CDT):
|
|
|
1012
1026
|
value=self.index,
|
|
1013
1027
|
with_selection=bool(c.negotiated_conformance.selective_access)))
|
|
1014
1028
|
start_read_time: float = time.perf_counter()
|
|
1015
|
-
await c.read_data_block()
|
|
1029
|
+
if isinstance(res_pdu := await c.read_data_block(), result.Error):
|
|
1030
|
+
return res_pdu
|
|
1016
1031
|
c.last_transfer_time = datetime.timedelta(seconds=time.perf_counter()-start_read_time)
|
|
1017
1032
|
try:
|
|
1018
|
-
self.obj.set_attr(self.index,
|
|
1033
|
+
self.obj.set_attr(self.index, res_pdu.value)
|
|
1019
1034
|
return result.Simple(self.obj.get_attr(self.index))
|
|
1020
1035
|
except ValueError as e:
|
|
1021
1036
|
return result.Error.from_e(e)
|
|
@@ -1025,7 +1040,7 @@ class ReadObjAttr(SimpleCopy, CDT):
|
|
|
1025
1040
|
return result.Error.from_e(e)
|
|
1026
1041
|
|
|
1027
1042
|
|
|
1028
|
-
@dataclass
|
|
1043
|
+
@dataclass(frozen=True)
|
|
1029
1044
|
class Par2Data[T: cdt.CommonDataType](SimpleCopy, CDT[T]):
|
|
1030
1045
|
"""get CommonDataType by Parameter"""
|
|
1031
1046
|
par: Parameter
|
|
@@ -1056,13 +1071,13 @@ def is_empty(value: cdt.CommonDataType | None) -> bool:
|
|
|
1056
1071
|
return True if value is None else False
|
|
1057
1072
|
|
|
1058
1073
|
|
|
1059
|
-
@dataclass
|
|
1074
|
+
@dataclass(frozen=True)
|
|
1060
1075
|
class ReadAttributeIf(SimpleCopy, Base):
|
|
1061
1076
|
"""read if func with arg as value is True"""
|
|
1062
1077
|
ln: collection.LNContaining
|
|
1063
1078
|
index: int
|
|
1064
1079
|
func: AttrValueComp
|
|
1065
|
-
msg: str
|
|
1080
|
+
msg: str = "read attribute with condition"
|
|
1066
1081
|
|
|
1067
1082
|
async def exchange(self, c: Client) -> result.Simple[cdt.CommonDataType]:
|
|
1068
1083
|
# TODO: check is_readable
|
|
@@ -1075,7 +1090,7 @@ class ReadAttributeIf(SimpleCopy, Base):
|
|
|
1075
1090
|
return result.OK
|
|
1076
1091
|
|
|
1077
1092
|
|
|
1078
|
-
@dataclass
|
|
1093
|
+
@dataclass(frozen=True)
|
|
1079
1094
|
class ReadEmptyAttribute(SimpleCopy, Base):
|
|
1080
1095
|
ln: collection.LNContaining
|
|
1081
1096
|
index: int
|
|
@@ -1090,7 +1105,7 @@ class ReadEmptyAttribute(SimpleCopy, Base):
|
|
|
1090
1105
|
).exchange(c)
|
|
1091
1106
|
|
|
1092
1107
|
|
|
1093
|
-
@dataclass
|
|
1108
|
+
@dataclass(frozen=True)
|
|
1094
1109
|
class ReadWritableAttributes(SimpleCopy, Base):
|
|
1095
1110
|
ln: collection.LNContaining
|
|
1096
1111
|
indexes: tuple[int, ...]
|
|
@@ -1116,7 +1131,7 @@ class ReadWritableAttributes(SimpleCopy, Base):
|
|
|
1116
1131
|
|
|
1117
1132
|
|
|
1118
1133
|
# copy past from ReadWritableAttributes
|
|
1119
|
-
@dataclass
|
|
1134
|
+
@dataclass(frozen=True)
|
|
1120
1135
|
class ActualizeAttributes(SimpleCopy, OK):
|
|
1121
1136
|
ln: collection.LNContaining
|
|
1122
1137
|
indexes: tuple[int, ...]
|
|
@@ -1142,7 +1157,7 @@ class ActualizeAttributes(SimpleCopy, OK):
|
|
|
1142
1157
|
return result.OK
|
|
1143
1158
|
|
|
1144
1159
|
|
|
1145
|
-
@dataclass
|
|
1160
|
+
@dataclass(frozen=True)
|
|
1146
1161
|
class ReadAttributes(SimpleCopy, _List[cdt.CommonDataType]):
|
|
1147
1162
|
ln: collection.LNContaining
|
|
1148
1163
|
indexes: tuple[int, ...]
|
|
@@ -1181,7 +1196,8 @@ class Write(SimpleCopy, OK):
|
|
|
1181
1196
|
obj=res_obj.value,
|
|
1182
1197
|
attr_index=self.par_data.par.i,
|
|
1183
1198
|
value=enc)
|
|
1184
|
-
|
|
1199
|
+
if isinstance(res_pdu := await c.read_data_block(), result.Error):
|
|
1200
|
+
return res_pdu
|
|
1185
1201
|
return result.OK
|
|
1186
1202
|
|
|
1187
1203
|
|
|
@@ -1209,7 +1225,8 @@ class Write2(SimpleCopy, OK):
|
|
|
1209
1225
|
obj=res_obj.value,
|
|
1210
1226
|
attr_index=self.par.i,
|
|
1211
1227
|
value=enc)
|
|
1212
|
-
|
|
1228
|
+
if isinstance(res_pdu := await c.read_data_block(), result.Error):
|
|
1229
|
+
return res_pdu
|
|
1213
1230
|
return result.OK
|
|
1214
1231
|
|
|
1215
1232
|
|
|
@@ -1249,7 +1266,8 @@ class WriteParValue(SimpleCopy, OK):
|
|
|
1249
1266
|
obj=obj,
|
|
1250
1267
|
attr_index=self.par_value.par.i,
|
|
1251
1268
|
value=set_data.encoding)
|
|
1252
|
-
|
|
1269
|
+
if isinstance(res_pdu := await c.read_data_block(), result.Error):
|
|
1270
|
+
return res_pdu
|
|
1253
1271
|
return result.OK
|
|
1254
1272
|
|
|
1255
1273
|
|
|
@@ -1275,7 +1293,8 @@ class WriteAttribute(SimpleCopy, OK):
|
|
|
1275
1293
|
obj=obj,
|
|
1276
1294
|
attr_index=self.index,
|
|
1277
1295
|
value=enc)
|
|
1278
|
-
|
|
1296
|
+
if isinstance(res_pdu := await c.read_data_block(), result.Error):
|
|
1297
|
+
return res_pdu
|
|
1279
1298
|
return result.OK # todo: return Data-Access-Result
|
|
1280
1299
|
|
|
1281
1300
|
|
|
@@ -1327,7 +1346,8 @@ class Execute2(SimpleCopy, OK):
|
|
|
1327
1346
|
meth_desc=res.value.get_meth_descriptor(self.par.i),
|
|
1328
1347
|
method=self.data
|
|
1329
1348
|
)
|
|
1330
|
-
await c.read_data_block()
|
|
1349
|
+
if isinstance(res_pdu := await c.read_data_block(), result.Error):
|
|
1350
|
+
return res_pdu
|
|
1331
1351
|
return result.OK
|
|
1332
1352
|
except Exception as e:
|
|
1333
1353
|
return result.Error.from_e(e)
|
|
@@ -1340,8 +1360,9 @@ class WriteTime(SimpleCopy, Base):
|
|
|
1340
1360
|
try:
|
|
1341
1361
|
obj = c._objects.clock
|
|
1342
1362
|
c.get_get_request_normal(obj.get_attr_descriptor(3))
|
|
1343
|
-
await c.read_data_block()
|
|
1344
|
-
|
|
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)
|
|
1345
1366
|
res = await WriteAttribute(
|
|
1346
1367
|
ln=obj.logical_name,
|
|
1347
1368
|
index=2,
|
|
@@ -1361,7 +1382,7 @@ class ImageTransfer(SimpleCopy, StrictOK):
|
|
|
1361
1382
|
n_t_b: int = field(init=False, default=0)
|
|
1362
1383
|
"""not transferred block"""
|
|
1363
1384
|
n_blocks: int = field(init=False)
|
|
1364
|
-
msg: str = ""
|
|
1385
|
+
msg: str = "image transfer"
|
|
1365
1386
|
|
|
1366
1387
|
def __post_init__(self) -> None:
|
|
1367
1388
|
self.ITI = ImageTransferInitiate((
|
|
@@ -1560,62 +1581,42 @@ class ActivateImage(SimpleCopy, OK):
|
|
|
1560
1581
|
return result.OK
|
|
1561
1582
|
|
|
1562
1583
|
|
|
1563
|
-
|
|
1584
|
+
# todo: don't work with new API, remake
|
|
1585
|
+
class TestAll(OK):
|
|
1564
1586
|
"""read all attributes with check access""" # todo: add Write with access
|
|
1565
|
-
msg: str = ""
|
|
1587
|
+
msg: str = "test all"
|
|
1566
1588
|
|
|
1567
|
-
async def exchange(self, c: Client):
|
|
1589
|
+
async def exchange(self, c: Client) -> result.Ok | result.Error:
|
|
1568
1590
|
# todo: refactoring with <append_err>
|
|
1569
|
-
res = result.
|
|
1570
|
-
if
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
).exchange(c)
|
|
1587
|
-
if not is_readable:
|
|
1588
|
-
c.log(logL.ERR, F"{obj} with attr={i} must be unreadable")
|
|
1589
|
-
indexes.remove(i)
|
|
1590
|
-
c.log(logL.INFO, F"Get {obj}:{obj.get_attr_element(i)}")
|
|
1591
|
-
if res.value is None:
|
|
1592
|
-
c.log(logL.WARN, F"ValueError. For {obj} attr:{i} {res}")
|
|
1593
|
-
except exc.ResultError as e:
|
|
1594
|
-
if e.result == pdu.DataAccessResult.READ_WRITE_DENIED and not is_readable:
|
|
1595
|
-
c.log(logL.INFO, F"success ReadAccess TEST: {e}")
|
|
1596
|
-
indexes.remove(i)
|
|
1597
|
-
else:
|
|
1598
|
-
c.log(logL.ERR, F"result_error {e}")
|
|
1599
|
-
except exc.ITEApplication as e:
|
|
1600
|
-
c.log(logL.ERR, F"ITEApplication. For {obj} attr:{i} {e}")
|
|
1601
|
-
except Exception as e:
|
|
1602
|
-
c.log(logL.ERR, F"Unknown. For {obj} attr:{i} {e}")
|
|
1603
|
-
if len(indexes) == 0:
|
|
1604
|
-
c.log(logL.INFO, "all read success")
|
|
1605
|
-
break
|
|
1606
|
-
elif len(indexes) == len_indexes:
|
|
1607
|
-
c.log(logL.ERR, F"can't read attr={', '.join(map(str, indexes))}")
|
|
1608
|
-
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")
|
|
1609
1608
|
else:
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
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
|
|
1613
1614
|
|
|
1614
1615
|
|
|
1615
1616
|
@dataclass
|
|
1616
1617
|
class ApplyTemplate(Base):
|
|
1617
1618
|
template: collection.Template
|
|
1618
|
-
msg: str = ""
|
|
1619
|
+
msg: str = "apply template"
|
|
1619
1620
|
|
|
1620
1621
|
async def exchange(self, c: Client) -> result.Result:
|
|
1621
1622
|
# todo: search col
|
|
@@ -1645,7 +1646,7 @@ class ApplyTemplate(Base):
|
|
|
1645
1646
|
@dataclass
|
|
1646
1647
|
class ReadTemplate(OK):
|
|
1647
1648
|
template: collection.Template
|
|
1648
|
-
msg: str = ""
|
|
1649
|
+
msg: str = "read template"
|
|
1649
1650
|
|
|
1650
1651
|
async def exchange(self, c: Client) -> result.Ok | result.Error:
|
|
1651
1652
|
# todo: copypast from <ApplyTemplate>
|
|
@@ -1673,50 +1674,39 @@ class ReadTemplate(OK):
|
|
|
1673
1674
|
|
|
1674
1675
|
|
|
1675
1676
|
@dataclass
|
|
1676
|
-
class AccessValidate(
|
|
1677
|
+
class AccessValidate(OK):
|
|
1677
1678
|
"""check all access rights for current SAP"""
|
|
1678
1679
|
with_correct: bool = False
|
|
1679
|
-
msg: str = ""
|
|
1680
|
+
msg: str = "all access validate"
|
|
1680
1681
|
|
|
1681
1682
|
# todo: make with result.Error
|
|
1682
|
-
async def exchange(self, c: Client) -> result.Ok | result.
|
|
1683
|
+
async def exchange(self, c: Client) -> result.Ok | result.Error:
|
|
1684
|
+
res = result.ErrorAccumulator()
|
|
1683
1685
|
obj_l: ObjectListType
|
|
1684
1686
|
el: ObjectListElement
|
|
1685
1687
|
a_a_i: association_ln.abstract.AttributeAccessItem
|
|
1686
|
-
if c.
|
|
1687
|
-
return result.Error.from_e(exc.DLMSException("not find Collection"))
|
|
1688
|
-
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:
|
|
1689
1689
|
return result.Error.from_e(exc.EmptyObj(F"empty object_list for {c._objects.sap2association(c.SAP)}"))
|
|
1690
|
-
res = result.Simple(value=[])
|
|
1691
1690
|
for el in obj_l:
|
|
1692
1691
|
for a_a_i in el.access_rights.attribute_access:
|
|
1693
1692
|
if a_a_i.access_mode.is_readable():
|
|
1694
1693
|
i = int(a_a_i.attribute_id)
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
await WriteAttribute(
|
|
1702
|
-
ln=el.logical_name,
|
|
1703
|
-
index=i,
|
|
1704
|
-
value=res_.value.encoding
|
|
1705
|
-
).exchange(c)
|
|
1706
|
-
except exc.ResultError as e:
|
|
1707
|
-
res.append_e(e)
|
|
1708
|
-
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)
|
|
1709
1700
|
if self.with_correct:
|
|
1710
1701
|
a_a_i.access_mode.set(1) # todo: make better in future
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
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
|
|
1720
1710
|
|
|
1721
1711
|
|
|
1722
1712
|
@dataclass
|
|
@@ -1742,7 +1732,7 @@ class WriteList(SimpleCopy, _List[result.Ok]):
|
|
|
1742
1732
|
par_datas: tuple[tuple[Parameter, cdt.CommonDataType], ...]
|
|
1743
1733
|
err_ignore: bool
|
|
1744
1734
|
|
|
1745
|
-
def __init__(self, *par_datas: tuple[Parameter, cdt.CommonDataType], err_ignore: bool = False, msg: str = "") -> None:
|
|
1735
|
+
def __init__(self, *par_datas: tuple[Parameter, cdt.CommonDataType], err_ignore: bool = False, msg: str = "write list") -> None:
|
|
1746
1736
|
self.par_datas = par_datas
|
|
1747
1737
|
self.err_ignore = err_ignore
|
|
1748
1738
|
self.msg = msg
|
|
@@ -1769,7 +1759,7 @@ class WriteTranscript(SimpleCopy, OK):
|
|
|
1769
1759
|
async def exchange(self, c: Client) -> result.Ok | result.Error:
|
|
1770
1760
|
if isinstance((res := await Par2Data[cdt.CommonDataType](self.par).exchange(c)), result.Error):
|
|
1771
1761
|
return res
|
|
1772
|
-
if isinstance(
|
|
1762
|
+
if isinstance(res.value, cdt.Digital):
|
|
1773
1763
|
s_u = c.objects.par2su(self.par)
|
|
1774
1764
|
if isinstance(s_u, cdt.ScalUnitType):
|
|
1775
1765
|
if not isinstance(self.value, str):
|
|
@@ -1790,7 +1780,7 @@ class WriteTranscripts(SimpleCopy, _List[result.Ok]):
|
|
|
1790
1780
|
par_values: tuple[tuple[Parameter, cdt.Transcript], ...]
|
|
1791
1781
|
err_ignore: bool
|
|
1792
1782
|
|
|
1793
|
-
def __init__(self, *par_values: tuple[Parameter, cdt.Transcript], err_ignore: bool = False, msg: str =""):
|
|
1783
|
+
def __init__(self, *par_values: tuple[Parameter, cdt.Transcript], err_ignore: bool = False, msg: str ="write transcripts"):
|
|
1794
1784
|
self.par_values = par_values
|
|
1795
1785
|
self.err_ignore = err_ignore
|
|
1796
1786
|
self.msg = msg
|