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