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/client.py
CHANGED
|
@@ -14,6 +14,7 @@ import datetime
|
|
|
14
14
|
import os
|
|
15
15
|
import hashlib
|
|
16
16
|
from Cryptodome.Cipher import AES
|
|
17
|
+
from StructResult import result
|
|
17
18
|
from DLMS_SPODES_communications import Network, Serial, RS485, BLEKPZ, base
|
|
18
19
|
from DLMS_SPODES.cosem_interface_classes import overview
|
|
19
20
|
from DLMS_SPODES.cosem_interface_classes.collection import Collection, InterfaceClass, ic, cdt, ut, Data, AssociationLN
|
|
@@ -192,9 +193,6 @@ class Client:
|
|
|
192
193
|
self.connection_time_release = 10
|
|
193
194
|
""" number of second for port release after inactivity """
|
|
194
195
|
|
|
195
|
-
self.reply = GXReplyData()
|
|
196
|
-
""" use gurux reply class now """
|
|
197
|
-
|
|
198
196
|
self.received_frames = deque()
|
|
199
197
|
""" HDLC frames container from server """
|
|
200
198
|
|
|
@@ -261,19 +259,19 @@ class Client:
|
|
|
261
259
|
case BLEKPZ(): return 3
|
|
262
260
|
case _: raise ValueError(F"can't calculate channel index by media: {self.media}")
|
|
263
261
|
|
|
264
|
-
def get_frame(self, read_data: bytearray) -> frame.Frame | None:
|
|
262
|
+
def get_frame(self, read_data: bytearray, reply: GXReplyData) -> frame.Frame | None:
|
|
265
263
|
while len(read_data) != 0:
|
|
266
264
|
new_frame = frame.Frame.try_from(read_data)
|
|
267
|
-
|
|
265
|
+
reply.complete = True
|
|
268
266
|
if new_frame is None:
|
|
269
|
-
|
|
267
|
+
reply.complete = False
|
|
270
268
|
return None
|
|
271
269
|
if new_frame.is_for_me(self.DA, self.SA):
|
|
272
270
|
self.received_frames.append(new_frame)
|
|
273
271
|
if new_frame.is_segmentation:
|
|
274
|
-
|
|
272
|
+
reply.moreData |= RequestTypes.FRAME
|
|
275
273
|
else:
|
|
276
|
-
|
|
274
|
+
reply.moreData &= ~RequestTypes.FRAME
|
|
277
275
|
# check control TODO: rewrite it
|
|
278
276
|
if new_frame.control.is_unnumbered():
|
|
279
277
|
if new_frame.control in (frame.Control.UA_F, frame.Control.SNRM_P):
|
|
@@ -306,234 +304,244 @@ class Client:
|
|
|
306
304
|
self.log(logL.WARN, F"ALIEN frame {new_frame}, expect with SA:{self.SA}")
|
|
307
305
|
# FROM GURUX - if new_frame.control == frame.Control.UI_PF: # search next frame in read_data
|
|
308
306
|
|
|
309
|
-
def handleGbt(self):
|
|
310
|
-
index =
|
|
311
|
-
|
|
312
|
-
bc =
|
|
313
|
-
|
|
307
|
+
def handleGbt(self, reply: GXReplyData) -> result.Ok | result.Error:
|
|
308
|
+
index = reply.data.position - 1
|
|
309
|
+
reply.windowSize = self.settings.windowSize
|
|
310
|
+
bc = reply.data.getUInt8()
|
|
311
|
+
reply.streaming = (bc & 0x40) != 0
|
|
314
312
|
windowSize = int(bc & 0x3F)
|
|
315
|
-
bn =
|
|
316
|
-
bna =
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
self.settings.blockNumberAck =
|
|
320
|
-
|
|
321
|
-
len_ = _GXCommon.getObjectCount(
|
|
322
|
-
if len_ >
|
|
323
|
-
|
|
313
|
+
bn = reply.data.getUInt16()
|
|
314
|
+
bna = reply.data.getUInt16()
|
|
315
|
+
reply.blockNumber = bn
|
|
316
|
+
reply.blockNumberAck = bna
|
|
317
|
+
self.settings.blockNumberAck = reply.blockNumber
|
|
318
|
+
reply.command = None
|
|
319
|
+
len_ = _GXCommon.getObjectCount(reply.data)
|
|
320
|
+
if len_ > reply.data.size - reply.data.position:
|
|
321
|
+
reply.complete = False
|
|
324
322
|
return
|
|
325
|
-
GXDLMS.getDataFromBlock(
|
|
323
|
+
GXDLMS.getDataFromBlock(reply.data, index)
|
|
326
324
|
if (bc & 0x80) == 0:
|
|
327
|
-
|
|
325
|
+
reply.moreData = (RequestTypes(reply.moreData | RequestTypes.GBT))
|
|
328
326
|
else:
|
|
329
|
-
|
|
330
|
-
if
|
|
331
|
-
|
|
332
|
-
self.getPdu()
|
|
333
|
-
|
|
334
|
-
#
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
327
|
+
reply.moreData = (RequestTypes(reply.moreData & ~RequestTypes.GBT))
|
|
328
|
+
if reply.data.size != 0:
|
|
329
|
+
reply.data.position = 0
|
|
330
|
+
if isinstance(res_pdu := self.getPdu(), result.Error):
|
|
331
|
+
return res_pdu.with_msg("handle GBT")
|
|
332
|
+
# if reply.data.position != reply.data.size and (reply.command == XDLMSAPDU.READ_RESPONSE or reply.command == XDLMSAPDU.GET_RESPONSE) and (reply.moreData == RequestTypes.NONE or reply.peek):
|
|
333
|
+
# reply.data.position = 0
|
|
334
|
+
# cls.getValueFromData(settings, reply)
|
|
335
|
+
return result.OK
|
|
336
|
+
|
|
337
|
+
def getPdu(self, reply: GXReplyData) -> result.Ok | result.Error:
|
|
338
338
|
# TODO: make return pdu
|
|
339
|
-
if
|
|
340
|
-
if
|
|
341
|
-
|
|
342
|
-
index =
|
|
343
|
-
|
|
344
|
-
match
|
|
339
|
+
if reply.command is None:
|
|
340
|
+
if reply.data.size - reply.data.position == 0:
|
|
341
|
+
return result.Error(ValueError("Invalid PDU"), "getpdu")
|
|
342
|
+
index = reply.data.position
|
|
343
|
+
reply.command = XDLMSAPDU(reply.data.getUInt8())
|
|
344
|
+
match reply.command:
|
|
345
345
|
case XDLMSAPDU.GET_RESPONSE:
|
|
346
|
-
response_type: int =
|
|
347
|
-
invoke_id_and_priority =
|
|
346
|
+
response_type: int = reply.data.getUInt8()
|
|
347
|
+
invoke_id_and_priority = reply.data.getUInt8() # TODO: matching with setting params
|
|
348
348
|
match response_type:
|
|
349
349
|
case pdu.GetResponse.NORMAL:
|
|
350
|
-
match
|
|
351
|
-
case 0:
|
|
350
|
+
match reply.data.getUInt8(): # Get-Data-Result[0]
|
|
351
|
+
case 0:
|
|
352
|
+
GXDLMS.getDataFromBlock(reply.data, 0)
|
|
352
353
|
case 1:
|
|
353
|
-
|
|
354
|
-
if
|
|
355
|
-
|
|
356
|
-
case err:
|
|
357
|
-
|
|
354
|
+
reply.error = pdu.DataAccessResult(reply.data.getUInt8())
|
|
355
|
+
if reply.error != 0:
|
|
356
|
+
return result.Error.from_e(exc.ResultError(reply.error), "get pdu")
|
|
357
|
+
case err:
|
|
358
|
+
return result.Error.from_e(ValueError(F'Got Get-Data-Result[0] {err}, expect 0 or 1'), "get pdu")
|
|
359
|
+
GXDLMS.getDataFromBlock(reply.data, 0)
|
|
358
360
|
case pdu.GetResponse.WITH_DATABLOCK:
|
|
359
|
-
last_block =
|
|
361
|
+
last_block = reply.data.getUInt8()
|
|
360
362
|
if last_block == 0:
|
|
361
|
-
|
|
363
|
+
reply.moreData |= RequestTypes.DATABLOCK
|
|
362
364
|
else:
|
|
363
|
-
|
|
364
|
-
block_number =
|
|
365
|
+
reply.moreData &= ~RequestTypes.DATABLOCK
|
|
366
|
+
block_number = reply.data.getUInt32()
|
|
365
367
|
if block_number == 0 and self.settings.blockIndex == 1: # if start block_index == 0
|
|
366
368
|
self.settings.setBlockIndex(0)
|
|
367
369
|
if block_number != self.settings.blockIndex:
|
|
368
|
-
|
|
369
|
-
match
|
|
370
|
+
return result.Error.from_e(ValueError(F"Invalid Block number. It is {block_number} and it should be {self.settings.blockIndex}."), "get pdu")
|
|
371
|
+
match reply.data.getUInt8(): # DataBlock-G.result,
|
|
370
372
|
case 0:
|
|
371
|
-
if
|
|
372
|
-
block_length = _GXCommon.getObjectCount(
|
|
373
|
-
if (
|
|
374
|
-
if block_length > len(
|
|
375
|
-
|
|
376
|
-
|
|
373
|
+
if reply.data.position != len(reply.data):
|
|
374
|
+
block_length = _GXCommon.getObjectCount(reply.data)
|
|
375
|
+
if (reply.moreData & RequestTypes.FRAME) == 0:
|
|
376
|
+
if block_length > len(reply.data) - reply.data.position:
|
|
377
|
+
return result.Error.from_e(ValueError("Invalid block length."), "get pdu")
|
|
378
|
+
reply.command = None
|
|
377
379
|
if block_length == 0:
|
|
378
|
-
|
|
380
|
+
reply.data.size = index
|
|
379
381
|
else:
|
|
380
|
-
GXDLMS.getDataFromBlock(
|
|
381
|
-
if
|
|
382
|
-
if not
|
|
383
|
-
|
|
382
|
+
GXDLMS.getDataFromBlock(reply.data, index)
|
|
383
|
+
if reply.moreData == RequestTypes.NONE:
|
|
384
|
+
if not reply.peek:
|
|
385
|
+
reply.data.position = 0
|
|
384
386
|
self.settings.resetBlockIndex()
|
|
385
|
-
if
|
|
387
|
+
if reply.moreData == RequestTypes.NONE and self.settings and self.settings.command == XDLMSAPDU.GET_REQUEST \
|
|
386
388
|
and self.settings.commandType == pdu.GetResponse.WITH_LIST:
|
|
387
|
-
GXDLMS.handleGetResponseWithList(self.settings,
|
|
388
|
-
return
|
|
389
|
+
GXDLMS.handleGetResponseWithList(self.settings, reply)
|
|
390
|
+
return result.OK
|
|
389
391
|
case 1:
|
|
390
|
-
|
|
391
|
-
if
|
|
392
|
-
|
|
393
|
-
case err:
|
|
392
|
+
reply.error = pdu.DataAccessResult(reply.data.getUInt8())
|
|
393
|
+
if reply.error != 0:
|
|
394
|
+
return result.Error.from_e(exc.ResultError(reply.error), "get pdu")
|
|
395
|
+
case err:
|
|
396
|
+
return result.Error.from_e(ValueError(F'Got DataBlock-G.result {err}, expect 0 or 1'), "get pdu")
|
|
394
397
|
case pdu.GetResponse.WITH_LIST:
|
|
395
|
-
GXDLMS.handleGetResponseWithList(self.settings,
|
|
396
|
-
return
|
|
397
|
-
case err:
|
|
398
|
+
GXDLMS.handleGetResponseWithList(self.settings, reply)
|
|
399
|
+
return result.OK
|
|
400
|
+
case err:
|
|
401
|
+
return result.Error.from_e(ValueError(F"Got Invalid Get response {err}, expect {', '.join(map(lambda it: F'{it.name} = {it.value}', pdu.GetResponse))}"), "get pdu")
|
|
398
402
|
case XDLMSAPDU.READ_RESPONSE:
|
|
399
|
-
if not GXDLMS.handleReadResponse(self.settings,
|
|
400
|
-
return
|
|
403
|
+
if not GXDLMS.handleReadResponse(self.settings, reply, index):
|
|
404
|
+
return result.OK
|
|
401
405
|
case XDLMSAPDU.SET_RESPONSE:
|
|
402
|
-
response_type: int =
|
|
403
|
-
invoke_id_and_priority =
|
|
406
|
+
response_type: int = reply.data.getUInt8()
|
|
407
|
+
invoke_id_and_priority = reply.data.getUInt8() # TODO: matching with setting params
|
|
404
408
|
match response_type:
|
|
405
409
|
case pdu.SetResponse.NORMAL:
|
|
406
|
-
|
|
407
|
-
if
|
|
408
|
-
|
|
410
|
+
reply.error = pdu.DataAccessResult(reply.data.getUInt8())
|
|
411
|
+
if reply.error != 0:
|
|
412
|
+
return result.Error.from_e(exc.ResultError(reply.error), "get pdu")
|
|
409
413
|
case pdu.SetResponse.DATABLOCK:
|
|
410
|
-
block_number =
|
|
414
|
+
block_number = reply.data.getUInt32()
|
|
411
415
|
case pdu.SetResponse.LAST_DATABLOCK:
|
|
412
|
-
|
|
413
|
-
if
|
|
414
|
-
|
|
415
|
-
block_number =
|
|
416
|
+
reply.error = pdu.DataAccessResult(reply.data.getUInt8())
|
|
417
|
+
if reply.error != 0:
|
|
418
|
+
return result.Error.from_e(exc.ResultError(reply.error), "get pdu")
|
|
419
|
+
block_number = reply.data.getUInt32()
|
|
416
420
|
case pdu.SetResponse.LAST_DATABLOCK_WITH_LIST:
|
|
417
|
-
raise
|
|
421
|
+
raise RuntimeError("Not released in Client")
|
|
418
422
|
case pdu.SetResponse.WITH_LIST:
|
|
419
|
-
cnt = _GXCommon.getObjectCount(
|
|
423
|
+
cnt = _GXCommon.getObjectCount(reply.data)
|
|
420
424
|
pos = 0
|
|
421
425
|
while pos != cnt:
|
|
422
|
-
|
|
423
|
-
if
|
|
424
|
-
|
|
426
|
+
reply.error = pdu.DataAccessResult(reply.data.getUInt8())
|
|
427
|
+
if reply.error != 0:
|
|
428
|
+
return result.Error.from_e(exc.ResultError(reply.error), "get pdu")
|
|
425
429
|
pos += 1
|
|
426
|
-
case err:
|
|
430
|
+
case err:
|
|
431
|
+
return result.Error.from_e(ValueError(F"Got Invalid Set response {err}, expect {', '.join(map(lambda it: F'{it.name} = {it.value}', pdu.SetResponse))}"), "get pdu")
|
|
427
432
|
case XDLMSAPDU.WRITE_RESPONSE:
|
|
428
|
-
cnt = _GXCommon.getObjectCount(
|
|
433
|
+
cnt = _GXCommon.getObjectCount(reply.data)
|
|
429
434
|
pos = 0
|
|
430
435
|
while pos != cnt:
|
|
431
|
-
ret =
|
|
436
|
+
ret = reply.data.getUInt8()
|
|
432
437
|
if ret != 0:
|
|
433
|
-
|
|
438
|
+
reply.error = reply.data.getUInt8()
|
|
434
439
|
pos += 1
|
|
435
440
|
case XDLMSAPDU.ACTION_RESPONSE:
|
|
436
|
-
action_response =
|
|
437
|
-
invoke_id_and_priority =
|
|
441
|
+
action_response = reply.data.getUInt8()
|
|
442
|
+
invoke_id_and_priority = reply.data.getUInt8()
|
|
438
443
|
match action_response:
|
|
439
444
|
case pdu.ActionResponse.NORMAL:
|
|
440
|
-
|
|
441
|
-
if
|
|
442
|
-
|
|
443
|
-
if
|
|
444
|
-
ret =
|
|
445
|
+
reply.error = pdu.ActionResult(reply.data.getUInt8())
|
|
446
|
+
if reply.error != 0:
|
|
447
|
+
return result.Error(exc.ResultError(reply.error), "get pdu")
|
|
448
|
+
if reply.data.position < reply.data.size:
|
|
449
|
+
ret = reply.data.getUInt8()
|
|
445
450
|
if ret == 0:
|
|
446
|
-
GXDLMS.getDataFromBlock(
|
|
451
|
+
GXDLMS.getDataFromBlock(reply.data, 0)
|
|
447
452
|
elif ret == 1:
|
|
448
|
-
ret = int(
|
|
453
|
+
ret = int(reply.data.getUInt8())
|
|
449
454
|
if ret != 0:
|
|
450
|
-
|
|
451
|
-
if ret == 9 and
|
|
452
|
-
|
|
453
|
-
GXDLMS.getDataFromBlock(
|
|
454
|
-
|
|
455
|
+
reply.error = reply.data.getUInt8()
|
|
456
|
+
if ret == 9 and reply.error == 16:
|
|
457
|
+
reply.data.position = reply.data.position - 2
|
|
458
|
+
GXDLMS.getDataFromBlock(reply.data, 0)
|
|
459
|
+
reply.error = 0
|
|
455
460
|
ret = 0
|
|
456
461
|
else:
|
|
457
|
-
GXDLMS.getDataFromBlock(
|
|
462
|
+
GXDLMS.getDataFromBlock(reply.data, 0)
|
|
458
463
|
else:
|
|
459
|
-
|
|
464
|
+
return result.Error.from_e(Exception("HandleActionResponseNormal failed. " + "Invalid tag."), "get pdu")
|
|
460
465
|
case pdu.ActionResponse.WITH_PBLOCK:
|
|
461
|
-
raise
|
|
466
|
+
raise RuntimeError("Not released in Client")
|
|
462
467
|
case pdu.ActionResponse.WITH_LIST:
|
|
463
|
-
raise
|
|
468
|
+
raise RuntimeError("Not released in Client")
|
|
464
469
|
case pdu.ActionResponse.NEXT_PBLOCK:
|
|
465
|
-
raise
|
|
470
|
+
raise RuntimeError("Not released in Client")
|
|
466
471
|
case err:
|
|
467
|
-
|
|
472
|
+
return result.Error.from_e(ValueError(F"got {pdu.ActionResponse}: {err}, expect {', '.join(map(lambda it: F'{it.name} = {it.value}', pdu.ActionResponse))}"), "get pdu")
|
|
468
473
|
case XDLMSAPDU.ACCESS_RESPONSE:
|
|
469
|
-
data =
|
|
470
|
-
invokeId =
|
|
471
|
-
len_ =
|
|
474
|
+
data = reply.data
|
|
475
|
+
invokeId = reply.data.getUInt32()
|
|
476
|
+
len_ = reply.data.getUInt8()
|
|
472
477
|
tmp = None
|
|
473
478
|
if len_ != 0:
|
|
474
479
|
tmp = bytearray(len_)
|
|
475
480
|
data.get(tmp)
|
|
476
|
-
|
|
481
|
+
reply.time = _GXCommon.changeType(self.settings, tmp, DataType.DATETIME)
|
|
477
482
|
data.getUInt8()
|
|
478
483
|
case XDLMSAPDU.GENERAL_BLOCK_TRANSFER:
|
|
479
|
-
if not self.settings.isServer and (
|
|
480
|
-
self.handleGbt()
|
|
484
|
+
if not self.settings.isServer and (reply.moreData & RequestTypes.FRAME) == 0:
|
|
485
|
+
if isinstance(res_gbt := self.handleGbt(reply), result.Error):
|
|
486
|
+
return res_gbt
|
|
481
487
|
case ACSEAPDU.AARQ | ACSEAPDU.AARE:
|
|
482
488
|
# This is parsed later.
|
|
483
|
-
|
|
489
|
+
reply.data.position = reply.data.position - 1
|
|
484
490
|
case ACSEAPDU.RLRE | ACSEAPDU.RLRQ:
|
|
485
491
|
pass
|
|
486
492
|
case XDLMSAPDU.CONFIRMED_SERVICE_ERROR:
|
|
487
|
-
GXDLMS.handleConfirmedServiceError(
|
|
493
|
+
GXDLMS.handleConfirmedServiceError(reply)
|
|
488
494
|
case XDLMSAPDU.EXCEPTION_RESPONSE:
|
|
489
|
-
GXDLMS.handleExceptionResponse(
|
|
495
|
+
GXDLMS.handleExceptionResponse(reply)
|
|
490
496
|
case XDLMSAPDU.GET_REQUEST | XDLMSAPDU.READ_REQUEST | XDLMSAPDU.WRITE_REQUEST | XDLMSAPDU.SET_REQUEST | XDLMSAPDU.ACTION_REQUEST:
|
|
491
497
|
pass
|
|
492
498
|
case XDLMSAPDU.GLO_READ_REQUEST | XDLMSAPDU.GLO_WRITE_REQUEST | XDLMSAPDU.GLO_GET_REQUEST | XDLMSAPDU.GLO_SET_REQUEST | XDLMSAPDU.GLO_ACTION_REQUEST | \
|
|
493
499
|
XDLMSAPDU.DED_GET_REQUEST | XDLMSAPDU.DED_SET_REQUEST | XDLMSAPDU.DED_ACTION_REQUEST:
|
|
494
500
|
if self.settings.cipher is None:
|
|
495
|
-
|
|
496
|
-
if (
|
|
497
|
-
|
|
501
|
+
return result.Error.from_e(ServiceError("Secure connection is not supported."), "get pdu")
|
|
502
|
+
if (reply.moreData & RequestTypes.FRAME) == 0:
|
|
503
|
+
reply.data.position = reply.data.position - 1
|
|
498
504
|
p = None
|
|
499
505
|
if self.settings.cipher.dedicatedKey and (OSI.APPLICATION in self.level):
|
|
500
506
|
p = AesGcmParameter(self.settings.sourceSystemTitle, self.settings.cipher.dedicatedKey, self.settings.cipher.authenticationKey)
|
|
501
507
|
else:
|
|
502
508
|
p = AesGcmParameter(self.settings.sourceSystemTitle, self.settings.cipher.blockCipherKey, self.settings.cipher.authenticationKey)
|
|
503
|
-
tmp = GXCiphering.decrypt(self.settings.cipher, p,
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
if
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
self.getPdu()
|
|
509
|
+
tmp = GXCiphering.decrypt(self.settings.cipher, p, reply.data)
|
|
510
|
+
reply.data.clear()
|
|
511
|
+
reply.data.set(tmp)
|
|
512
|
+
reply.command = XDLMSAPDU(reply.data.getUInt8())
|
|
513
|
+
if reply.command == XDLMSAPDU.DATA_NOTIFICATION or reply.command == XDLMSAPDU.INFORMATION_REPORT_REQUEST:
|
|
514
|
+
reply.command = None
|
|
515
|
+
reply.data.position = reply.data.position - 1
|
|
516
|
+
if isinstance(res_pdu := self.getPdu(reply), result.Error):
|
|
517
|
+
return res_pdu
|
|
511
518
|
else:
|
|
512
|
-
|
|
519
|
+
reply.data.position = reply.data.position - 1
|
|
513
520
|
case XDLMSAPDU.GLO_READ_RESPONSE | XDLMSAPDU.GLO_WRITE_RESPONSE | XDLMSAPDU.GLO_GET_RESPONSE | XDLMSAPDU.GLO_SET_RESPONSE | XDLMSAPDU.GLO_ACTION_RESPONSE | \
|
|
514
521
|
XDLMSAPDU.GENERAL_GLO_CIPHERING | XDLMSAPDU.GLO_EVENT_NOTIFICATION_REQUEST | XDLMSAPDU.DED_GET_RESPONSE | XDLMSAPDU.DED_SET_RESPONSE | \
|
|
515
522
|
XDLMSAPDU.DED_ACTION_RESPONSE | XDLMSAPDU.GENERAL_DED_CIPHERING | XDLMSAPDU.DED_EVENT_NOTIFICATION_REQUEST:
|
|
516
523
|
if self.settings.cipher is None:
|
|
517
|
-
|
|
518
|
-
if (
|
|
519
|
-
|
|
520
|
-
bb = GXByteBuffer(
|
|
521
|
-
|
|
524
|
+
return result.Error.from_e(ServiceError("Secure connection is not supported."), "get pdu")
|
|
525
|
+
if (reply.moreData & RequestTypes.FRAME) == 0:
|
|
526
|
+
reply.data.position = reply.data.position - 1
|
|
527
|
+
bb = GXByteBuffer(reply.data)
|
|
528
|
+
reply.data.size = reply.data.position = index
|
|
522
529
|
p = None
|
|
523
530
|
if self.settings.cipher.dedicatedKey and (OSI.APPLICATION in self.level):
|
|
524
531
|
p = AesGcmParameter(0, self.settings.sourceSystemTitle, self.settings.cipher.dedicatedKey, self.settings.cipher.authenticationKey)
|
|
525
532
|
else:
|
|
526
533
|
p = AesGcmParameter(0, self.settings.sourceSystemTitle, self.settings.cipher.blockCipherKey, self.settings.cipher.authenticationKey)
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
self.getPdu()
|
|
530
|
-
|
|
534
|
+
reply.data.set(GXCiphering.decrypt(self.settings.cipher, p, bb))
|
|
535
|
+
reply.command = None
|
|
536
|
+
if isinstance(res_pdu := self.getPdu(reply), result.Error):
|
|
537
|
+
return res_pdu
|
|
538
|
+
reply.cipherIndex = reply.data.size
|
|
531
539
|
case XDLMSAPDU.DATA_NOTIFICATION:
|
|
532
|
-
GXDLMS.handleDataNotification(self.settings,
|
|
533
|
-
data =
|
|
540
|
+
GXDLMS.handleDataNotification(self.settings, reply)
|
|
541
|
+
data = reply.data
|
|
534
542
|
start = data.position - 1
|
|
535
543
|
invokeId = data.getUInt32()
|
|
536
|
-
|
|
544
|
+
reply.time = None
|
|
537
545
|
len_ = data.getUInt8()
|
|
538
546
|
tmp = None
|
|
539
547
|
if len_ != 0:
|
|
@@ -546,103 +554,121 @@ class Client:
|
|
|
546
554
|
dt = DataType.DATE
|
|
547
555
|
info = _GXDataInfo()
|
|
548
556
|
info.type_ = dt
|
|
549
|
-
|
|
550
|
-
GXDLMS.getDataFromBlock(
|
|
551
|
-
GXDLMS.getValueFromData(self.settings,
|
|
557
|
+
reply.time = _GXCommon.getData(self.settings, GXByteBuffer(tmp), info)
|
|
558
|
+
GXDLMS.getDataFromBlock(reply.data, start)
|
|
559
|
+
GXDLMS.getValueFromData(self.settings, reply)
|
|
552
560
|
case XDLMSAPDU.EVENT_NOTIFICATION_REQUEST:
|
|
553
561
|
pass
|
|
554
562
|
case XDLMSAPDU.INFORMATION_REPORT_REQUEST:
|
|
555
563
|
pass
|
|
556
564
|
case XDLMSAPDU.GENERAL_CIPHERING:
|
|
557
565
|
if self.settings.cipher is None:
|
|
558
|
-
|
|
559
|
-
if (
|
|
560
|
-
|
|
566
|
+
return result.Error.from_e(ServiceError("Secure connection is not supported."), "get pdu")
|
|
567
|
+
if (reply.moreData & RequestTypes.FRAME) == 0:
|
|
568
|
+
reply.data.position = reply.data.position - 1
|
|
561
569
|
p = AesGcmParameter(0, self.settings.sourceSystemTitle, self.settings.cipher.blockCipherKey, self.settings.cipher.authenticationKey)
|
|
562
|
-
tmp = GXCiphering.decrypt(self.settings.cipher, p,
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
570
|
+
tmp = GXCiphering.decrypt(self.settings.cipher, p, reply.data)
|
|
571
|
+
reply.data.clear()
|
|
572
|
+
reply.data.set(tmp)
|
|
573
|
+
reply.command = None
|
|
566
574
|
if p.security:
|
|
567
|
-
self.getPdu()
|
|
575
|
+
if isinstance(res_pdu := self.getPdu(reply), result.Error):
|
|
576
|
+
return res_pdu
|
|
568
577
|
case XDLMSAPDU.GATEWAY_REQUEST:
|
|
569
578
|
pass
|
|
570
579
|
case XDLMSAPDU.GATEWAY_RESPONSE:
|
|
571
|
-
|
|
572
|
-
len_ = _GXCommon.getObjectCount(
|
|
580
|
+
reply.data.getUInt8()
|
|
581
|
+
len_ = _GXCommon.getObjectCount(reply.data)
|
|
573
582
|
pda = bytearray(len_)
|
|
574
|
-
|
|
575
|
-
GXDLMS.getDataFromBlock(
|
|
576
|
-
|
|
577
|
-
self.getPdu()
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
+
reply.data.get(pda)
|
|
584
|
+
GXDLMS.getDataFromBlock(reply, index)
|
|
585
|
+
reply.command = None
|
|
586
|
+
if isinstance(res_pdu := self.getPdu(reply), result.Error):
|
|
587
|
+
return res_pdu
|
|
588
|
+
case _:
|
|
589
|
+
return result.Error.from_e(ValueError("Invalid PDU Command."), "get pdu")
|
|
590
|
+
elif (reply.moreData & RequestTypes.FRAME) == 0:
|
|
591
|
+
if not reply.peek and reply.moreData == RequestTypes.NONE:
|
|
592
|
+
if reply.command == ACSEAPDU.AARE or reply.command == ACSEAPDU.AARQ:
|
|
593
|
+
reply.data.position = 0
|
|
583
594
|
else:
|
|
584
|
-
|
|
585
|
-
if
|
|
586
|
-
|
|
587
|
-
self.handleGbt()
|
|
588
|
-
|
|
589
|
-
|
|
595
|
+
reply.data.position = 1
|
|
596
|
+
if reply.command == XDLMSAPDU.GENERAL_BLOCK_TRANSFER:
|
|
597
|
+
reply.data.position = reply.cipherIndex + 1
|
|
598
|
+
if isinstance(res_gbt := self.handleGbt(reply), result.Error):
|
|
599
|
+
return res_gbt
|
|
600
|
+
reply.cipherIndex = reply.data.size
|
|
601
|
+
reply.command = None
|
|
590
602
|
elif self.settings.isServer:
|
|
591
|
-
if
|
|
603
|
+
if reply.command in (
|
|
592
604
|
XDLMSAPDU.GLO_READ_REQUEST, XDLMSAPDU.GLO_WRITE_REQUEST, XDLMSAPDU.GLO_GET_REQUEST, XDLMSAPDU.GLO_SET_REQUEST, XDLMSAPDU.GLO_ACTION_REQUEST,
|
|
593
605
|
XDLMSAPDU.GLO_EVENT_NOTIFICATION_REQUEST, XDLMSAPDU.DED_GET_REQUEST, XDLMSAPDU.DED_SET_REQUEST, XDLMSAPDU.DED_ACTION_REQUEST,
|
|
594
606
|
XDLMSAPDU.DED_EVENT_NOTIFICATION_REQUEST):
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
self.getPdu()
|
|
607
|
+
reply.command = None
|
|
608
|
+
reply.data.position = reply.getCipherIndex()
|
|
609
|
+
if isinstance(res_pdu := self.getPdu(reply), result.Error):
|
|
610
|
+
return res_pdu
|
|
598
611
|
else:
|
|
599
|
-
|
|
600
|
-
if
|
|
601
|
-
XDLMSAPDU.GLO_READ_RESPONSE,
|
|
602
|
-
XDLMSAPDU.
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
612
|
+
reply.command = None
|
|
613
|
+
if reply.command in (
|
|
614
|
+
XDLMSAPDU.GLO_READ_RESPONSE,
|
|
615
|
+
XDLMSAPDU.GLO_WRITE_RESPONSE,
|
|
616
|
+
XDLMSAPDU.GLO_GET_RESPONSE,
|
|
617
|
+
XDLMSAPDU.GLO_SET_RESPONSE,
|
|
618
|
+
XDLMSAPDU.GLO_ACTION_RESPONSE,
|
|
619
|
+
XDLMSAPDU.DED_GET_RESPONSE,
|
|
620
|
+
XDLMSAPDU.DED_SET_RESPONSE,
|
|
621
|
+
XDLMSAPDU.DED_ACTION_RESPONSE,
|
|
622
|
+
XDLMSAPDU.GENERAL_GLO_CIPHERING,
|
|
623
|
+
XDLMSAPDU.GENERAL_DED_CIPHERING
|
|
624
|
+
):
|
|
625
|
+
reply.data.position = reply.cipherIndex
|
|
626
|
+
if isinstance(res_pdu := self.getPdu(reply), result.Error):
|
|
627
|
+
return res_pdu
|
|
628
|
+
if (
|
|
629
|
+
reply.command == XDLMSAPDU.READ_RESPONSE
|
|
630
|
+
and reply.totalCount > 1
|
|
631
|
+
):
|
|
632
|
+
if not GXDLMS.handleReadResponse(self.settings, reply, 0):
|
|
633
|
+
return result.OK
|
|
608
634
|
|
|
609
635
|
if (
|
|
610
|
-
|
|
611
|
-
and
|
|
612
|
-
and (
|
|
636
|
+
reply.command == XDLMSAPDU.READ_RESPONSE
|
|
637
|
+
and reply.commandType == ReadResponse.DATA_BLOCK_RESULT
|
|
638
|
+
and (reply.moreData & RequestTypes.FRAME) != 0
|
|
613
639
|
):
|
|
614
|
-
return
|
|
640
|
+
return result.OK
|
|
615
641
|
if (
|
|
616
|
-
|
|
642
|
+
reply.data.position != reply.data.size
|
|
617
643
|
and (
|
|
618
|
-
|
|
619
|
-
or
|
|
620
|
-
and
|
|
644
|
+
reply.moreData == RequestTypes.NONE
|
|
645
|
+
or reply.peek)
|
|
646
|
+
and reply.command in (
|
|
621
647
|
XDLMSAPDU.READ_RESPONSE,
|
|
622
648
|
XDLMSAPDU.GET_RESPONSE,
|
|
623
649
|
XDLMSAPDU.ACTION_RESPONSE,
|
|
624
650
|
XDLMSAPDU.DATA_NOTIFICATION)
|
|
625
651
|
):
|
|
626
|
-
|
|
627
|
-
# GXDLMS.getValueFromData(self.settings,
|
|
652
|
+
return result.OK
|
|
653
|
+
# GXDLMS.getValueFromData(self.settings, reply)
|
|
628
654
|
|
|
629
|
-
def __is_frame(self, notify, read_data: bytearray) -> bool:
|
|
655
|
+
def __is_frame(self, notify, read_data: bytearray, reply_: GXReplyData) -> bool:
|
|
630
656
|
reply = GXByteBuffer(read_data)
|
|
631
657
|
is_notify: bool = False
|
|
632
658
|
match self.com_profile:
|
|
633
659
|
case c_pf.HDLC():
|
|
634
|
-
recv_frame = self.get_frame(read_data)
|
|
660
|
+
recv_frame = self.get_frame(read_data, reply_)
|
|
635
661
|
if recv_frame is not None:
|
|
636
662
|
self.log(logL.INFO, F"RX: {recv_frame.content.hex(' ')}")
|
|
637
663
|
if recv_frame.control == frame.Control.UI_PF:
|
|
638
|
-
target = notify # use instead of
|
|
664
|
+
target = notify # use instead of reply_ in getPdu(target). see in Gurux to do
|
|
639
665
|
is_notify = True
|
|
640
|
-
|
|
666
|
+
reply_.frameId = recv_frame.control
|
|
641
667
|
else: # TODO: GURUX redundant
|
|
642
668
|
# self.write_trace(F"RX {self.id}: {get_os_time()} {read_data}", TraceLevel.ERROR)
|
|
643
|
-
|
|
669
|
+
reply_.frameId = frame.Control(0)
|
|
644
670
|
case c_pf.TCPUDPIP(): # getTcpData TODO: check it
|
|
645
|
-
target =
|
|
671
|
+
target = reply_
|
|
646
672
|
if len(reply) - reply.position < 8:
|
|
647
673
|
target.complete = False
|
|
648
674
|
return True
|
|
@@ -690,46 +716,44 @@ class Client:
|
|
|
690
716
|
break
|
|
691
717
|
else:
|
|
692
718
|
reply.position = reply.position - 1
|
|
693
|
-
if target is not
|
|
719
|
+
if target is not reply_:
|
|
694
720
|
is_notify = True
|
|
695
721
|
case c_pf.MBUS(): # not realised see how
|
|
696
|
-
GXDLMS.getMBusData(self.settings, reply,
|
|
722
|
+
GXDLMS.getMBusData(self.settings, reply, reply_)
|
|
697
723
|
case _: raise ValueError("Invalid Interface type.")
|
|
698
|
-
if not
|
|
724
|
+
if not reply_.complete:
|
|
699
725
|
return False
|
|
700
726
|
|
|
701
727
|
# TODO: relocate notify to read_data_type
|
|
702
728
|
if notify and not is_notify:
|
|
703
729
|
#Check command to make sure it's not notify message.
|
|
704
|
-
if
|
|
730
|
+
if reply_.command in (XDLMSAPDU.DATA_NOTIFICATION,
|
|
705
731
|
XDLMSAPDU.GLO_EVENT_NOTIFICATION_REQUEST,
|
|
706
732
|
XDLMSAPDU.INFORMATION_REPORT_REQUEST,
|
|
707
733
|
XDLMSAPDU.EVENT_NOTIFICATION_REQUEST,
|
|
708
734
|
XDLMSAPDU.DED_INFORMATION_REPORT_REQUEST,
|
|
709
735
|
XDLMSAPDU.DED_EVENT_NOTIFICATION_REQUEST):
|
|
710
736
|
is_notify = True
|
|
711
|
-
notify.complete =
|
|
712
|
-
notify.command =
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
notify.
|
|
716
|
-
# notify.value =
|
|
717
|
-
|
|
737
|
+
notify.complete = reply_.complete
|
|
738
|
+
notify.command = reply_.command
|
|
739
|
+
reply_.command = None
|
|
740
|
+
reply_.time = None
|
|
741
|
+
notify.reply_.set(reply_.data)
|
|
742
|
+
# notify.value = reply_.value
|
|
743
|
+
reply_.data.trim()
|
|
718
744
|
if is_notify:
|
|
719
745
|
return False
|
|
720
746
|
return True
|
|
721
747
|
|
|
722
|
-
async def read_data_block(self) ->
|
|
723
|
-
ret = None
|
|
748
|
+
async def read_data_block(self) -> result.SimpleOrError[bytes]: # todo: make depend from CommunicationProfile
|
|
724
749
|
self.received_frames.clear()
|
|
725
|
-
|
|
750
|
+
reply = GXReplyData()
|
|
726
751
|
while self.send_frames:
|
|
727
|
-
# TODO: see how remove self.reply here !!!
|
|
728
752
|
send_frame = self.send_frames.popleft()
|
|
729
753
|
notify = GXReplyData()
|
|
730
|
-
|
|
754
|
+
reply.error = 0
|
|
731
755
|
recv_buf: bytearray = bytearray()
|
|
732
|
-
if not
|
|
756
|
+
if not reply.isStreaming():
|
|
733
757
|
await self.media.send(send_frame.content)
|
|
734
758
|
self.log(logL.INFO, F"TX: {send_frame.content.hex(" ")}")
|
|
735
759
|
attempt: int = 1
|
|
@@ -738,7 +762,7 @@ class Client:
|
|
|
738
762
|
await asyncio.wait_for( # todo: don't work exception!!!! Why????
|
|
739
763
|
fut=self.media.receive(recv_buf),
|
|
740
764
|
timeout=self.com_profile.parameters.inactivity_time_out) # todo: only for HDLC
|
|
741
|
-
if self.__is_frame(notify, recv_buf):
|
|
765
|
+
if self.__is_frame(notify, recv_buf, reply):
|
|
742
766
|
break
|
|
743
767
|
elif notify.data.size != 0:
|
|
744
768
|
if not notify.isMoreData():
|
|
@@ -751,37 +775,40 @@ class Client:
|
|
|
751
775
|
await self.media.send(send_frame.content)
|
|
752
776
|
attempt += 1
|
|
753
777
|
else:
|
|
754
|
-
|
|
778
|
+
return result.Error.from_e(TimeoutError("Failed to receive reply from the device in given time"), "read data block")
|
|
755
779
|
recv_buf.clear()
|
|
756
|
-
match
|
|
757
|
-
case 0:
|
|
758
|
-
|
|
759
|
-
case
|
|
780
|
+
match reply.error:
|
|
781
|
+
case 0:
|
|
782
|
+
"""errors is absence"""
|
|
783
|
+
case 4:
|
|
784
|
+
return result.Error.from_e(exc.NoObject(), "read data block")
|
|
785
|
+
case _:
|
|
786
|
+
return result.Error.from_e(GXDLMSException(reply.error), "read data block")
|
|
760
787
|
if self.received_frames[-1].control.is_info() or self.received_frames[-1].control == frame.Control.UI_PF:
|
|
761
788
|
if self.received_frames[-1].is_segmentation:
|
|
762
789
|
"""pass handle frame. wait all information"""
|
|
763
790
|
else:
|
|
764
791
|
llc = sub_layer.LLC(frame.Frame.join_info(self.received_frames))
|
|
765
792
|
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
793
|
+
reply.data.position = len(reply.data)
|
|
794
|
+
reply.data.set(llc.info)
|
|
795
|
+
if isinstance(res_pdu := self.getPdu(reply), result.Error):
|
|
796
|
+
return res_pdu
|
|
770
797
|
# TODO: LLC to PDU
|
|
771
798
|
else:
|
|
772
799
|
received_frame = self.received_frames.popleft()
|
|
773
800
|
if send_frame.control == frame.Control.SNRM_P:
|
|
774
801
|
self.com_profile.negotiation.set_from_UA(received_frame.info)
|
|
775
802
|
self.log(logL.INFO, F"negotiation setup: {self.com_profile.negotiation}")
|
|
776
|
-
if
|
|
777
|
-
if
|
|
803
|
+
if reply.isMoreData():
|
|
804
|
+
if reply.isStreaming():
|
|
778
805
|
data = None
|
|
779
806
|
else:
|
|
780
807
|
# Generates an acknowledgment message, with which the server is informed to send next packets. Frame type. Acknowledgment message as byte array
|
|
781
|
-
if
|
|
782
|
-
|
|
808
|
+
if reply.moreData == RequestTypes.NONE:
|
|
809
|
+
return result.Error.from_e(ValueError("Invalid receiverReady RequestTypes parameter."), msg="read data block")
|
|
783
810
|
# Get next frame.
|
|
784
|
-
if (
|
|
811
|
+
if (reply.moreData & RequestTypes.FRAME) != 0:
|
|
785
812
|
id_ = self.settings.getReceiverReady()
|
|
786
813
|
# return GXDLMS.getHdlcFrame(settings, id_, None)
|
|
787
814
|
self.add_frames_to_queue(frame.Control(id_))
|
|
@@ -796,13 +823,13 @@ class Client:
|
|
|
796
823
|
cmd = XDLMSAPDU.READ_RESPONSE
|
|
797
824
|
else:
|
|
798
825
|
cmd = XDLMSAPDU.READ_REQUEST
|
|
799
|
-
if
|
|
826
|
+
if reply.moreData == RequestTypes.GBT:
|
|
800
827
|
p = GXDLMSLNParameters(self.settings, 0, XDLMSAPDU.GENERAL_BLOCK_TRANSFER, 0, None, None, 0xff)
|
|
801
|
-
p.WindowSize =
|
|
802
|
-
p.blockNumberAck =
|
|
803
|
-
p.blockIndex =
|
|
828
|
+
p.WindowSize = reply.windowSize
|
|
829
|
+
p.blockNumberAck = reply.blockNumberAck
|
|
830
|
+
p.blockIndex = reply.blockNumber
|
|
804
831
|
p.Streaming = False
|
|
805
|
-
|
|
832
|
+
messages = self.getLnMessages(p) # TODO: test it
|
|
806
833
|
else:
|
|
807
834
|
# Get next block.
|
|
808
835
|
bb = GXByteBuffer(4)
|
|
@@ -813,12 +840,12 @@ class Client:
|
|
|
813
840
|
self.settings.increaseBlockIndex()
|
|
814
841
|
if self.settings.getUseLogicalNameReferencing():
|
|
815
842
|
p = GXDLMSLNParameters(self.settings, 0, cmd, pdu.GetResponse.WITH_DATABLOCK, bb, None, 0xff)
|
|
816
|
-
|
|
843
|
+
messages = self.getLnMessages(p)
|
|
817
844
|
else:
|
|
818
845
|
p = GXDLMSSNParameters(self.settings, cmd, 1, VariableAccessSpecification.BLOCK_NUMBER_ACCESS, bb, None)
|
|
819
|
-
|
|
820
|
-
data =
|
|
821
|
-
return
|
|
846
|
+
messages = self.getSnMessages(p)
|
|
847
|
+
data = messages
|
|
848
|
+
return result.Simple(reply.data.get_data())
|
|
822
849
|
|
|
823
850
|
def getSnMessages(self, p: GXDLMSSNParameters):
|
|
824
851
|
reply = GXByteBuffer()
|
|
@@ -864,8 +891,8 @@ class Client:
|
|
|
864
891
|
if p.settings.is_multiple_block():
|
|
865
892
|
reply.size = 0
|
|
866
893
|
if (
|
|
867
|
-
|
|
868
|
-
|
|
894
|
+
not ciphering
|
|
895
|
+
and isinstance(self.com_profile, c_pf.HDLC)
|
|
869
896
|
):
|
|
870
897
|
if p.settings.isServer:
|
|
871
898
|
reply.set(_GXCommon.LLC_REPLY_BYTES)
|
|
@@ -970,36 +997,31 @@ class Client:
|
|
|
970
997
|
def set_params(self, field: str, value: str):
|
|
971
998
|
self.__dict__[field] = eval(value)
|
|
972
999
|
|
|
973
|
-
async def close(self) ->
|
|
1000
|
+
async def close(self) -> result.StrictOk | result.Error:
|
|
974
1001
|
"""close , media is open"""
|
|
1002
|
+
res = result.StrictOk()
|
|
975
1003
|
self.log(logL.DEB, "close")
|
|
976
1004
|
if self.level > OSI.DATA_LINK:
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
if (
|
|
1005
|
+
# Release is call only for secured connections. All meters are not supporting Release and it's causing problems.
|
|
1006
|
+
if (
|
|
980
1007
|
isinstance(self.com_profile, c_pf.TCPUDPIP)
|
|
981
1008
|
or (
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
# All meters don't support release.
|
|
992
|
-
finally:
|
|
993
|
-
self.level = OSI.DATA_LINK
|
|
1009
|
+
isinstance(self.com_profile, c_pf.HDLC)
|
|
1010
|
+
and self.settings.cipher.security != Security.NONE
|
|
1011
|
+
)
|
|
1012
|
+
):
|
|
1013
|
+
self.releaseRequest()
|
|
1014
|
+
if isinstance(res_rdb := await self.read_data_block(), result.Error):
|
|
1015
|
+
res.append_err(res_rdb.err)
|
|
1016
|
+
self.log(logL.WARN, "don't support release ReleaseRequest")
|
|
1017
|
+
self.level = OSI.DATA_LINK
|
|
994
1018
|
# hdlc close
|
|
995
|
-
|
|
996
|
-
res
|
|
997
|
-
# todo: handle res
|
|
998
|
-
except Exception as e:
|
|
999
|
-
self.log(logL.ERR, F'Disconnect request ERROR: {e.args[0]}')
|
|
1019
|
+
if isinstance(res_diconnect_req := await self.disconnect_request(), result.Error):
|
|
1020
|
+
res.append_err(res_diconnect_req.err)
|
|
1000
1021
|
self.level -= OSI.DATA_LINK
|
|
1022
|
+
return res
|
|
1001
1023
|
|
|
1002
|
-
async def disconnect_request(self):
|
|
1024
|
+
async def disconnect_request(self) -> result.Ok | result.Error:
|
|
1003
1025
|
""" Sent to server DISC """
|
|
1004
1026
|
if isinstance(self.com_profile, c_pf.HDLC):
|
|
1005
1027
|
self.add_frames_to_queue(frame.Control.DISC_P)
|
|
@@ -1026,10 +1048,10 @@ class Client:
|
|
|
1026
1048
|
return ret
|
|
1027
1049
|
|
|
1028
1050
|
# TODO: remove in future
|
|
1029
|
-
def parseApplicationAssociationResponse(self):
|
|
1051
|
+
def parseApplicationAssociationResponse(self, data: bytes):
|
|
1030
1052
|
""" Parse server's challenge if HLS authentication is used. Received reply from the server. todo: refactoring here """
|
|
1031
1053
|
ic = 0
|
|
1032
|
-
value = cdt.OctetString(
|
|
1054
|
+
value = cdt.OctetString(data)
|
|
1033
1055
|
match self.m_id:
|
|
1034
1056
|
case mechanism_id.HIGH_GMAC:
|
|
1035
1057
|
secret = self.settings.sourceSystemTitle
|
|
@@ -1135,8 +1157,9 @@ class Client:
|
|
|
1135
1157
|
else:
|
|
1136
1158
|
return self.method2(0xFA00, 12, 8, challenge, cdt.OctetString.TAG) # TODO: rewrite old client.method
|
|
1137
1159
|
|
|
1138
|
-
def parseAARE(self,
|
|
1160
|
+
def parseAARE(self, pdu: bytes) -> AcseServiceUser:
|
|
1139
1161
|
# Get AARE tag and length
|
|
1162
|
+
buff = GXByteBuffer(pdu)
|
|
1140
1163
|
tag = buff.getUInt8()
|
|
1141
1164
|
if self.settings.isServer:
|
|
1142
1165
|
if tag != (BerType.APPLICATION | BerType.CONSTRUCTED | AARQapdu.PROTOCOL_VERSION):
|
|
@@ -1452,11 +1475,11 @@ class Client:
|
|
|
1452
1475
|
# raise exc.AssociationResultError(resultComponent, resultDiagnosticValue)
|
|
1453
1476
|
return resultDiagnosticValue
|
|
1454
1477
|
|
|
1455
|
-
def parseAareResponse(self,
|
|
1478
|
+
def parseAareResponse(self, pdu: bytes) -> AcseServiceUser:
|
|
1456
1479
|
""" TODO: need refactoring. Parses the AARE response. Parse method will update the following data: DLMSVersion, MaxReceivePDUSize, UseLogicalNameReferencing, LNSettings or SNSettings,
|
|
1457
1480
|
LNSettings or SNSettings will be updated, depending on the referencing, Logical name or Short name.
|
|
1458
1481
|
Received data. GXDLMSClient#aarqRequest GXDLMSClient#useLogicalNameReferencing GXDLMSClient#negotiatedConformance GXDLMSClient#proposedConformance """
|
|
1459
|
-
if (ret := self.parseAARE(
|
|
1482
|
+
if (ret := self.parseAARE(pdu)) != AcseServiceUser.AUTHENTICATION_REQUIRED:
|
|
1460
1483
|
self.level |= OSI.APPLICATION
|
|
1461
1484
|
if self.settings.dlmsVersion != 6:
|
|
1462
1485
|
raise ValueError("Invalid DLMS version number.")
|
|
@@ -1581,7 +1604,7 @@ class Client:
|
|
|
1581
1604
|
|
|
1582
1605
|
def getLnMessages(self, p: GXDLMSLNParameters):
|
|
1583
1606
|
reply = GXByteBuffer()
|
|
1584
|
-
messages =
|
|
1607
|
+
messages = []
|
|
1585
1608
|
frame_ = 0
|
|
1586
1609
|
if (
|
|
1587
1610
|
p.command == XDLMSAPDU.DATA_NOTIFICATION
|
|
@@ -2037,6 +2060,7 @@ class Client:
|
|
|
2037
2060
|
else:
|
|
2038
2061
|
return 'недоступен'
|
|
2039
2062
|
|
|
2063
|
+
@deprecated("<use ReadObjAttr>")
|
|
2040
2064
|
async def read_attribute(self, obj: ic.COSEMInterfaceClasses | str,
|
|
2041
2065
|
attr_index: int):
|
|
2042
2066
|
# TODO: redundant, use read_attr?
|
|
@@ -2046,44 +2070,25 @@ class Client:
|
|
|
2046
2070
|
value=attr_index,
|
|
2047
2071
|
with_selection=bool(self.negotiated_conformance.selective_access)))
|
|
2048
2072
|
start_read_time: float = time.perf_counter()
|
|
2049
|
-
await self.read_data_block()
|
|
2073
|
+
data = (await self.read_data_block()).unwrap()
|
|
2050
2074
|
self.last_transfer_time = datetime.timedelta(seconds=time.perf_counter()-start_read_time)
|
|
2051
|
-
obj.set_attr(attr_index,
|
|
2052
|
-
|
|
2053
|
-
async def read_attr(self, attr_desc: ut.CosemAttributeDescriptor) -> bytes:
|
|
2054
|
-
"""instead <read_attribute> in future. Read and return encoding"""
|
|
2055
|
-
self.get_get_request_normal(attr_desc)
|
|
2056
|
-
await self.read_data_block()
|
|
2057
|
-
return self.reply.data.get_data()
|
|
2058
|
-
|
|
2059
|
-
def recv_attr_value(self, attr_desc: ut.CosemAttributeDescriptor) -> cdt.CommonDataTypes:
|
|
2060
|
-
"""receive attribute value from server"""
|
|
2061
|
-
obj = self.objects.get_object(attr_desc)
|
|
2062
|
-
index = int(attr_desc.attribute_id)
|
|
2063
|
-
obj.set_attr(index=index,
|
|
2064
|
-
value=self.read_attr(attr_desc))
|
|
2065
|
-
return obj.get_attr(index)
|
|
2066
|
-
|
|
2067
|
-
async def write_attr2(self, attr_desc: ut.CosemAttributeDescriptor, value: cdt.CommonDataTypes):
|
|
2068
|
-
data = self.get_set_request_normal2(attr_desc, value)
|
|
2069
|
-
await self.read_data_block()
|
|
2075
|
+
obj.set_attr(attr_index, data)
|
|
2070
2076
|
|
|
2071
2077
|
@deprecated("use execute_method2")
|
|
2072
|
-
async def execute_method(self, meth_desc: ut.CosemMethodDescriptor):
|
|
2078
|
+
async def execute_method(self, meth_desc: ut.CosemMethodDescriptor) -> result.Ok | result.Error:
|
|
2073
2079
|
data = self.get_action_request_normal_old(meth_desc)
|
|
2074
|
-
await self.read_data_block()
|
|
2080
|
+
return await self.read_data_block()
|
|
2075
2081
|
|
|
2076
|
-
async def execute_method2(self, obj: ic.COSEMInterfaceClasses, i: int, mip=None):
|
|
2082
|
+
async def execute_method2(self, obj: ic.COSEMInterfaceClasses, i: int, mip=None) -> result.Ok | result.Error:
|
|
2077
2083
|
data = self.get_action_request_normal(
|
|
2078
2084
|
meth_desc=obj.get_meth_descriptor(i),
|
|
2079
2085
|
method=obj.get_meth_element(i).DATA_TYPE() if mip is None else mip)
|
|
2080
|
-
await self.read_data_block()
|
|
2086
|
+
return await self.read_data_block()
|
|
2081
2087
|
|
|
2082
2088
|
async def is_equal_attribute(self, obj: ic.COSEMInterfaceClasses, attr_index: int | str, with_time: bool | datetime.datetime = False) -> bool:
|
|
2083
2089
|
self.get_get_request_normal(obj.get_attr_descriptor(attr_index))
|
|
2084
|
-
await self.read_data_block()
|
|
2085
|
-
if obj.get_attr(attr_index).encoding ==
|
|
2090
|
+
data = (await self.read_data_block()).unwrap()
|
|
2091
|
+
if obj.get_attr(attr_index).encoding == data:
|
|
2086
2092
|
return True
|
|
2087
2093
|
else:
|
|
2088
|
-
# self.set_error(Application.VALUE_ERROR, F'{obj} attr-{attr_index}: got {self.reply.data.get_data()} must {obj[attr_index]}')
|
|
2089
2094
|
return False
|