DLMS-SPODES-client 0.19.36__py3-none-any.whl → 0.19.38__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.
Files changed (60) hide show
  1. DLMS_SPODES_client/FCS16.py +39 -39
  2. DLMS_SPODES_client/__init__.py +12 -12
  3. DLMS_SPODES_client/client.py +2093 -2093
  4. DLMS_SPODES_client/gurux_common/enums/TraceLevel.py +21 -21
  5. DLMS_SPODES_client/gurux_dlms/AesGcmParameter.py +37 -37
  6. DLMS_SPODES_client/gurux_dlms/CountType.py +16 -16
  7. DLMS_SPODES_client/gurux_dlms/GXByteBuffer.py +545 -545
  8. DLMS_SPODES_client/gurux_dlms/GXCiphering.py +196 -196
  9. DLMS_SPODES_client/gurux_dlms/GXDLMS.py +426 -426
  10. DLMS_SPODES_client/gurux_dlms/GXDLMSChippering.py +237 -237
  11. DLMS_SPODES_client/gurux_dlms/GXDLMSChipperingStream.py +977 -977
  12. DLMS_SPODES_client/gurux_dlms/GXDLMSConfirmedServiceError.py +90 -90
  13. DLMS_SPODES_client/gurux_dlms/GXDLMSException.py +139 -139
  14. DLMS_SPODES_client/gurux_dlms/GXDLMSLNParameters.py +33 -33
  15. DLMS_SPODES_client/gurux_dlms/GXDLMSSNParameters.py +21 -21
  16. DLMS_SPODES_client/gurux_dlms/GXDLMSSettings.py +254 -254
  17. DLMS_SPODES_client/gurux_dlms/GXReplyData.py +87 -87
  18. DLMS_SPODES_client/gurux_dlms/HdlcControlFrame.py +9 -9
  19. DLMS_SPODES_client/gurux_dlms/MBusCommand.py +8 -8
  20. DLMS_SPODES_client/gurux_dlms/MBusEncryptionMode.py +27 -27
  21. DLMS_SPODES_client/gurux_dlms/ResponseType.py +8 -8
  22. DLMS_SPODES_client/gurux_dlms/SetResponseType.py +29 -29
  23. DLMS_SPODES_client/gurux_dlms/_HDLCInfo.py +9 -9
  24. DLMS_SPODES_client/gurux_dlms/__init__.py +75 -75
  25. DLMS_SPODES_client/gurux_dlms/enums/Access.py +12 -12
  26. DLMS_SPODES_client/gurux_dlms/enums/ApplicationReference.py +14 -14
  27. DLMS_SPODES_client/gurux_dlms/enums/Authentication.py +41 -41
  28. DLMS_SPODES_client/gurux_dlms/enums/BerType.py +35 -35
  29. DLMS_SPODES_client/gurux_dlms/enums/Command.py +285 -285
  30. DLMS_SPODES_client/gurux_dlms/enums/Definition.py +9 -9
  31. DLMS_SPODES_client/gurux_dlms/enums/ErrorCode.py +46 -46
  32. DLMS_SPODES_client/gurux_dlms/enums/ExceptionServiceError.py +12 -12
  33. DLMS_SPODES_client/gurux_dlms/enums/HardwareResource.py +10 -10
  34. DLMS_SPODES_client/gurux_dlms/enums/HdlcFrameType.py +9 -9
  35. DLMS_SPODES_client/gurux_dlms/enums/Initiate.py +10 -10
  36. DLMS_SPODES_client/gurux_dlms/enums/LoadDataSet.py +13 -13
  37. DLMS_SPODES_client/gurux_dlms/enums/ObjectType.py +306 -306
  38. DLMS_SPODES_client/gurux_dlms/enums/Priority.py +7 -7
  39. DLMS_SPODES_client/gurux_dlms/enums/RequestTypes.py +9 -9
  40. DLMS_SPODES_client/gurux_dlms/enums/Security.py +14 -14
  41. DLMS_SPODES_client/gurux_dlms/enums/Service.py +16 -16
  42. DLMS_SPODES_client/gurux_dlms/enums/ServiceClass.py +9 -9
  43. DLMS_SPODES_client/gurux_dlms/enums/ServiceError.py +8 -8
  44. DLMS_SPODES_client/gurux_dlms/enums/Standard.py +18 -18
  45. DLMS_SPODES_client/gurux_dlms/enums/StateError.py +7 -7
  46. DLMS_SPODES_client/gurux_dlms/enums/Task.py +10 -10
  47. DLMS_SPODES_client/gurux_dlms/enums/VdeStateError.py +10 -10
  48. DLMS_SPODES_client/gurux_dlms/enums/__init__.py +33 -33
  49. DLMS_SPODES_client/gurux_dlms/internal/_GXCommon.py +1673 -1673
  50. DLMS_SPODES_client/logger.py +56 -56
  51. DLMS_SPODES_client/services.py +90 -90
  52. DLMS_SPODES_client/session.py +363 -363
  53. DLMS_SPODES_client/settings.py +48 -48
  54. DLMS_SPODES_client/task.py +1891 -1884
  55. {dlms_spodes_client-0.19.36.dist-info → dlms_spodes_client-0.19.38.dist-info}/METADATA +29 -29
  56. dlms_spodes_client-0.19.38.dist-info/RECORD +61 -0
  57. {dlms_spodes_client-0.19.36.dist-info → dlms_spodes_client-0.19.38.dist-info}/WHEEL +1 -1
  58. dlms_spodes_client-0.19.36.dist-info/RECORD +0 -61
  59. {dlms_spodes_client-0.19.36.dist-info → dlms_spodes_client-0.19.38.dist-info}/entry_points.txt +0 -0
  60. {dlms_spodes_client-0.19.36.dist-info → dlms_spodes_client-0.19.38.dist-info}/top_level.txt +0 -0
@@ -1,426 +1,426 @@
1
- from __future__ import print_function
2
- from DLMS_SPODES.enums import ConfirmedServiceError, XDLMSAPDU, ReadResponse
3
- from ..FCS16 import get_fcs16
4
- from . import GXDLMSSettings
5
- from .GXByteBuffer import GXByteBuffer
6
- from .internal._GXCommon import _GXCommon
7
- from .enums import RequestTypes, Priority, ServiceClass
8
- from .enums import StateError, ExceptionServiceError, Security
9
- from .GXDLMSConfirmedServiceError import GXDLMSConfirmedServiceError
10
- from .MBusEncryptionMode import MBusEncryptionMode
11
- from .MBusCommand import MBusCommand
12
-
13
-
14
- class GXDLMS:
15
- """ GXDLMS implements methods to communicate with DLMS/COSEM metering devices. """
16
-
17
- _CIPHERING_HEADER_SIZE = 7 + 12 + 3
18
- _data_TYPE_OFFSET = 0xFF0000
19
-
20
- @classmethod
21
- def getInvokeIDPriority(cls, settings: GXDLMSSettings) -> int:
22
- value = 0
23
- if settings.priority == Priority.HIGH:
24
- value |= 0x80
25
- if settings.serviceClass == ServiceClass.CONFIRMED:
26
- value |= 0x40
27
- value |= settings.invokeId
28
- return value
29
-
30
- @classmethod
31
- def getLongInvokeIDPriority(cls, settings: GXDLMSSettings) -> int:
32
- """ Generates Invoke ID and priority. DLMS settings. Invoke ID and priority. """
33
- value = 0
34
- if settings.priority == Priority.HIGH:
35
- value = 0x80000000
36
- if settings.serviceClass == ServiceClass.CONFIRMED:
37
- value |= 0x40000000
38
- value |= int((settings.getLongInvokeID() & 0xFFFFFF))
39
- settings.setLongInvokeID(settings.getLongInvokeID() + 1)
40
- return value
41
-
42
- @classmethod
43
- def multipleBlocks(cls, p, reply, ciphering):
44
- """
45
- Check is all_ data fit to one data block.
46
-
47
- @param p
48
- LN parameters.
49
- @param reply
50
- Generated reply.
51
- """
52
-
53
- # Check is all_ data fit to one message if data is given.
54
- len_ = 0
55
- if p.data:
56
- if isinstance(p.data, bytes):
57
- len_ = len(p.data)
58
- else:
59
- len_ = len(p.data) - p.data.position
60
- if p.attributeDescriptor:
61
- len_ += p.attributeDescriptor.size
62
- if ciphering:
63
- len_ += cls._CIPHERING_HEADER_SIZE
64
- if not p.settings.is_multiple_block():
65
- # Add command type and invoke and priority.
66
- p.multipleBlocks = 2 + len(reply) + len_ > p.settings.maxPduSize
67
- if p.multipleBlocks:
68
- # Add command type and invoke and priority.
69
- p.lastBlock = not 8 + len(reply) + len_ > p.settings.maxPduSize
70
- if p.lastBlock:
71
- # Add command type and invoke and priority.
72
- p.lastBlock = not 8 + len(reply) + len_ > p.settings.maxPduSize
73
-
74
- @classmethod
75
- def appendMultipleSNBlocks(cls, p, reply):
76
- ciphering = p.settings.cipher and p.settings.cipher.security != Security.NONE
77
- hSize = len(reply) + 3
78
- if p.command == XDLMSAPDU.WRITE_REQUEST or p.command == XDLMSAPDU.READ_REQUEST:
79
- hSize += 1 + _GXCommon.getObjectCountSizeInBytes(p.getCount())
80
- maxSize = p.settings.maxPduSize - hSize
81
- if ciphering:
82
- maxSize -= cls._CIPHERING_HEADER_SIZE
83
- maxSize -= _GXCommon.getObjectCountSizeInBytes(maxSize)
84
- if reply.data.size - reply.data.position > maxSize:
85
- reply.setUInt8(0)
86
- else:
87
- reply.setUInt8(1)
88
- maxSize = reply.data.size - reply.data.position
89
- reply.setUInt16(p.blockIndex)
90
- if p.command == XDLMSAPDU.WRITE_REQUEST:
91
- p.setBlockIndex(p.blockIndex + 1)
92
- _GXCommon.setObjectCount(p.getCount(), reply)
93
- reply.setUInt8(DataType.OCTET_STRING)
94
- elif p.command == XDLMSAPDU.READ_REQUEST:
95
- p.setBlockIndex(p.blockIndex + 1)
96
- _GXCommon.setObjectCount(maxSize, reply)
97
- return maxSize
98
-
99
- @classmethod
100
- def getAddress(cls, value, size):
101
- if size < 2 and value < 0x80:
102
- return int((value << 1 | 1))
103
- if size < 4 and value < 0x4000:
104
- return int(((value & 0x3F80) << 2 | (value & 0x7F) << 1 | 1))
105
- if value < 0x10000000:
106
- return int(((value & 0xFE00000) << 4 | (value & 0x1FC000) << 3 | (value & 0x3F80) << 2 | (value & 0x7F) << 1 | 1))
107
- raise ValueError("Invalid address.")
108
-
109
- @classmethod
110
- def getAddressBytes(cls, value, size):
111
- tmp = cls.getAddress(value, size)
112
- bb = GXByteBuffer()
113
- if size == 1 or tmp < 0x100:
114
- bb.setUInt8(tmp)
115
- elif size == 2 or tmp < 0x10000:
116
- bb.setUInt16(tmp)
117
- elif size == 4 or tmp < 0x100000000:
118
- bb.setUInt32(tmp)
119
- else:
120
- raise ValueError("Invalid address type.")
121
- return bb.array()
122
-
123
- @classmethod
124
- def getWrapperFrame(cls, settings, command, data):
125
- bb = GXByteBuffer()
126
- bb.setUInt16(1)
127
- if settings.isServer:
128
- bb.setUInt16(settings.serverAddress)
129
- if settings.PushClientAddress != 0 and command in (XDLMSAPDU.DATA_NOTIFICATION, XDLMSAPDU.EVENT_NOTIFICATION_REQUEST):
130
- bb.setUInt16(settings.pushClientAddress)
131
- else:
132
- bb.setUInt16(settings.clientAddress)
133
- else:
134
- bb.setUInt16(settings.clientAddress)
135
- bb.setUInt16(settings.serverAddress)
136
- if data is None:
137
- bb.setUInt16(0)
138
- else:
139
- bb.setUInt16(len(data))
140
- bb.set(data)
141
- if settings.isServer:
142
- if len(data) == data.position:
143
- data.clear()
144
- else:
145
- data.move(data.position, 0, len(data) - data.position)
146
- data.position = 0
147
- return bb.array()
148
-
149
- @classmethod
150
- def getHdlcFrame(cls, settings, frame_, data):
151
- # pylint: disable=protected-access
152
- bb = GXByteBuffer()
153
- frameSize = 0
154
- len1 = 0
155
- primaryAddress = None
156
- secondaryAddress = None
157
- if settings.isServer:
158
- if frame_ == 0x13 and settings.pushClientAddress != 0:
159
- primaryAddress = cls.getAddressBytes(settings.pushClientAddress, 1)
160
- else:
161
- primaryAddress = cls.getAddressBytes(settings.clientAddress, 1)
162
- secondaryAddress = cls.getAddressBytes(settings.serverAddress, settings.serverAddressSize)
163
- else:
164
- primaryAddress = cls.getAddressBytes(settings.serverAddress, settings.serverAddressSize)
165
- secondaryAddress = cls.getAddressBytes(settings.clientAddress, 1)
166
- bb.setUInt8(_GXCommon.HDLC_FRAME_START_END)
167
- frameSize = settings.limits.maxInfoTX
168
- if data and data.position == 0:
169
- frameSize -= 3
170
- if not data:
171
- len1 = 0
172
- bb.setUInt8(0xA0)
173
- elif len(data) - data.position <= frameSize:
174
- len1 = len(data) - data.position
175
- bb.setUInt8(0xA0 | (((len(secondaryAddress) + len(primaryAddress) + len1) >> 8) & 0x7))
176
- else:
177
- len1 = frameSize
178
- bb.setUInt8(0xA8 | (((len(secondaryAddress) + len(primaryAddress) + len1) >> 8) & 0x7))
179
- if len1 == 0:
180
- bb.setUInt8(5 + len(secondaryAddress) + len(primaryAddress) + len1)
181
- else:
182
- bb.setUInt8(7 + len(secondaryAddress) + len(primaryAddress) + len1)
183
- bb.set(primaryAddress)
184
- bb.set(secondaryAddress)
185
- if frame_ == 0:
186
- bb.setUInt8(settings.getNextSend(True))
187
- else:
188
- bb.setUInt8(frame_)
189
- crc = get_fcs16(bb._data, 1, bb.size - 1)
190
- bb.setUInt16(crc)
191
- if len1 != 0:
192
- bb.set(data, data.position, len1)
193
- crc = get_fcs16(bb._data, 1, len(bb) - 1)
194
- bb.setUInt16(crc)
195
- bb.setUInt8(_GXCommon.HDLC_FRAME_START_END)
196
- if settings.isServer:
197
- if data:
198
- if len(data) == data.position:
199
- data.clear()
200
- else:
201
- data.move(data.position, 0, len(data) - data.position)
202
- data.position = 0
203
- return bb.array()
204
-
205
- @classmethod
206
- def getMBusData(cls, settings, buff, data):
207
- len_ = buff.getUInt8()
208
- if len(buff) < len_ - 1:
209
- data.complete = (False)
210
- buff.position = buff.position - 1
211
- else:
212
- if len(buff) < len_:
213
- len_ -= 1
214
- data.packetLength = len_
215
- data.complete = True
216
- cmd = buff.getUInt8()
217
- manufacturerID = buff.getUInt16()
218
- man = _GXCommon.decryptManufacturer(manufacturerID)
219
- #id =
220
- buff.getUInt32()
221
- meterVersion = buff.getUInt8()
222
- type_ = buff.getUInt8()
223
- ci = buff.getUInt8()
224
- #frameId =
225
- buff.getUInt8()
226
- #state =
227
- buff.getUInt8()
228
- configurationWord = buff.getUInt16()
229
- encryption = MBusEncryptionMode(configurationWord & 7)
230
- settings.clientAddress = buff.getUInt8()
231
- settings.serverAddress = buff.getUInt8()
232
-
233
- @classmethod
234
- def isMBusData(cls, buff):
235
- if len(buff) - buff.position < 2:
236
- return False
237
- cmd = buff.getUInt8(buff.position + 1)
238
- return cmd in (MBusCommand.SND_NR, MBusCommand.SND_UD2, MBusCommand.RSP_UD)
239
-
240
- @classmethod
241
- def handleReadResponse(cls, settings, reply, index):
242
- data = reply.data
243
- pos = 0
244
- cnt = reply.getTotalCount()
245
- first = cnt == 0 or reply.commandType == ReadResponse.DATA_BLOCK_RESULT
246
- if first:
247
- cnt = _GXCommon.getObjectCount(reply.data)
248
- reply.totalCount = cnt
249
- type_ = 0
250
- # values = None
251
- if cnt != 1:
252
- #Parse data after all data is received when readlist is used.
253
- if reply.isMoreData():
254
- cls.getDataFromBlock(reply.data, 0)
255
- return False
256
- if not first:
257
- reply.data.position = 0
258
- first = True
259
- # values = list()
260
- # if isinstance(reply.value, list):
261
- # values.append(reply.value)
262
- reply.value = None
263
- while pos != cnt:
264
- if first:
265
- type_ = data.getUInt8()
266
- reply.commandType = type_
267
- else:
268
- type_ = reply.commandType
269
- match type_:
270
- case ReadResponse.DATA:
271
- reply.error = 0
272
- if cnt == 1:
273
- cls.getDataFromBlock(reply.data, 0)
274
- else:
275
- reply.readPosition = data.position
276
- cls.getValueFromData(settings, reply)
277
- data.position = reply.readPosition
278
- # values.append(reply.value)
279
- # reply.value = None
280
- case ReadResponse.DATA_ACCESS_ERROR:
281
- reply.error = data.getUInt8()
282
- case ReadResponse.DATA_BLOCK_RESULT:
283
- reply.error = 0
284
- data = reply.data
285
- lastBlock = data.getUInt8()
286
- number = data.getUInt16()
287
- blockLength = _GXCommon.getObjectCount(data)
288
- if lastBlock == 0:
289
- reply.moreData = (RequestTypes(reply.moreData | RequestTypes.DATABLOCK))
290
- else:
291
- reply.moreData = (RequestTypes(reply.moreData & ~RequestTypes.DATABLOCK))
292
- if number != 1 and settings.blockIndex == 1:
293
- settings.setBlockIndex(number)
294
- expectedIndex = settings.blockIndex
295
- if number != expectedIndex:
296
- raise Exception("Invalid Block number. It is " + number + " and it should be " + expectedIndex + ".")
297
- if (reply.moreData & RequestTypes.FRAME) != 0:
298
- cls.getDataFromBlock(data, index)
299
- return False
300
- if blockLength != data.size - data.position:
301
- raise ValueError("Invalid block length.")
302
- reply.command = None
303
- cls.getDataFromBlock(reply.data, index)
304
- reply.setTotalCount(0)
305
- if reply.getMoreData() == RequestTypes.NONE:
306
- settings.resetBlockIndex()
307
- return True
308
- case ReadResponse.BLOCK_NUMBER:
309
- number = data.getUInt16()
310
- if number != settings.blockIndex:
311
- raise Exception("Invalid Block number. It is " + number + " and it should be " + settings.blockIndex + ".")
312
- settings.increaseBlockIndex()
313
- reply.moreData = (RequestTypes(reply.moreData | RequestTypes.DATABLOCK))
314
- case _:
315
- raise Exception("HandleReadResponse failed. Invalid tag.")
316
- pos += 1
317
- # if values:
318
- # reply.value = values
319
- return cnt == 1
320
-
321
- @classmethod
322
- def errorCodeToString(cls, type_, value):
323
- if type_ == TranslatorOutputType.STANDARD_XML:
324
- return TranslatorStandardTags.errorCodeToString(value)
325
- return TranslatorSimpleTags.errorCodeToString(value)
326
-
327
- @classmethod
328
- def handleDataNotification(cls, settings, reply):
329
- data = reply.data
330
- start = data.position - 1
331
- invokeId = data.getUInt32()
332
- reply.time = None
333
- len_ = data.getUInt8()
334
- tmp = None
335
- if len_ != 0:
336
- tmp = bytearray(len_)
337
- data.get(tmp)
338
- dt = DataType.DATETIME
339
- if len_ == 4:
340
- dt = DataType.TIME
341
- elif len_ == 5:
342
- dt = DataType.DATE
343
- info = _GXDataInfo()
344
- info.type_ = dt
345
- reply.time = _GXCommon.getData(settings, GXByteBuffer(tmp), info)
346
- cls.getDataFromBlock(reply.data, start)
347
- cls.getValueFromData(settings, reply)
348
-
349
- @classmethod
350
- def handleGetResponseWithList(cls, settings, reply):
351
- cnt = _GXCommon.getObjectCount(reply.data)
352
- # values = list([None] * cnt)
353
- pos = 0
354
- while pos != cnt:
355
- ch = reply.data.getUInt8()
356
- if ch != 0:
357
- reply.error = reply.data.getUInt8()
358
- else:
359
- reply.readPosition = reply.data.position
360
- cls.getValueFromData(settings, reply)
361
- reply.data.position = reply.readPosition
362
- # if values:
363
- # values[pos] = reply.value
364
- # reply.value = None
365
- pos += 1
366
- # reply.value = values
367
-
368
- @classmethod
369
- def handleExceptionResponse(cls, data):
370
- raise Exception(StateError(data.data.getUInt8() - 1), ExceptionServiceError(data.data.getUInt8() - 1))
371
-
372
- @classmethod
373
- def handleConfirmedServiceError(cls, data):
374
- service = ConfirmedServiceError(data.data.getUInt8())
375
- type_ = data.data.getUInt8()
376
- raise GXDLMSConfirmedServiceError(service, type_, data.data.getUInt8())
377
-
378
- @classmethod
379
- def getValueFromData(cls, settings, reply):
380
- data = reply.data
381
- info = _GXDataInfo()
382
- if isinstance(reply.value, list):
383
- info.type_ = DataType.ARRAY
384
- info.count = reply.totalCount
385
- info.index = reply.getCount()
386
- index = data.position
387
- data.position = reply.readPosition
388
- try:
389
- value = _GXCommon.getData(settings, data, info)
390
- if value is not None:
391
- if not isinstance(value, list):
392
- reply.valueType = DataType(info.type_)
393
- reply.value = value
394
- reply.totalCount = 0
395
- reply.readPosition = data.position
396
- else:
397
- if value:
398
- if reply.value is None:
399
- reply.value = value
400
- else:
401
- list_ = list()
402
- list_ += reply.value
403
- list_ += value
404
- reply.value = list_
405
- reply.readPosition = data.position
406
- reply.totalCount = info.count
407
- elif info.complete and reply.command == XDLMSAPDU.DATA_NOTIFICATION:
408
- reply.readPosition = data.position
409
- finally:
410
- data.position = index
411
- if reply.command != XDLMSAPDU.DATA_NOTIFICATION and info.complete and reply.moreData == RequestTypes.NONE:
412
- if settings:
413
- settings.resetBlockIndex()
414
- data.position = 0
415
-
416
- @classmethod
417
- def getDataFromBlock(cls, data: GXByteBuffer, index: int):
418
- # pylint: disable=protected-access
419
- if len(data) == data.position:
420
- data.clear()
421
- return 0
422
- len_ = data.position - index
423
- data._data[data.position - len_:data.position] = data._data[data.position: len(data)]
424
- data.position = data.position - len_
425
- data.size = len(data) - len_
426
- return len
1
+ from __future__ import print_function
2
+ from DLMS_SPODES.enums import ConfirmedServiceError, XDLMSAPDU, ReadResponse
3
+ from ..FCS16 import get_fcs16
4
+ from . import GXDLMSSettings
5
+ from .GXByteBuffer import GXByteBuffer
6
+ from .internal._GXCommon import _GXCommon
7
+ from .enums import RequestTypes, Priority, ServiceClass
8
+ from .enums import StateError, ExceptionServiceError, Security
9
+ from .GXDLMSConfirmedServiceError import GXDLMSConfirmedServiceError
10
+ from .MBusEncryptionMode import MBusEncryptionMode
11
+ from .MBusCommand import MBusCommand
12
+
13
+
14
+ class GXDLMS:
15
+ """ GXDLMS implements methods to communicate with DLMS/COSEM metering devices. """
16
+
17
+ _CIPHERING_HEADER_SIZE = 7 + 12 + 3
18
+ _data_TYPE_OFFSET = 0xFF0000
19
+
20
+ @classmethod
21
+ def getInvokeIDPriority(cls, settings: GXDLMSSettings) -> int:
22
+ value = 0
23
+ if settings.priority == Priority.HIGH:
24
+ value |= 0x80
25
+ if settings.serviceClass == ServiceClass.CONFIRMED:
26
+ value |= 0x40
27
+ value |= settings.invokeId
28
+ return value
29
+
30
+ @classmethod
31
+ def getLongInvokeIDPriority(cls, settings: GXDLMSSettings) -> int:
32
+ """ Generates Invoke ID and priority. DLMS settings. Invoke ID and priority. """
33
+ value = 0
34
+ if settings.priority == Priority.HIGH:
35
+ value = 0x80000000
36
+ if settings.serviceClass == ServiceClass.CONFIRMED:
37
+ value |= 0x40000000
38
+ value |= int((settings.getLongInvokeID() & 0xFFFFFF))
39
+ settings.setLongInvokeID(settings.getLongInvokeID() + 1)
40
+ return value
41
+
42
+ @classmethod
43
+ def multipleBlocks(cls, p, reply, ciphering):
44
+ """
45
+ Check is all_ data fit to one data block.
46
+
47
+ @param p
48
+ LN parameters.
49
+ @param reply
50
+ Generated reply.
51
+ """
52
+
53
+ # Check is all_ data fit to one message if data is given.
54
+ len_ = 0
55
+ if p.data:
56
+ if isinstance(p.data, bytes):
57
+ len_ = len(p.data)
58
+ else:
59
+ len_ = len(p.data) - p.data.position
60
+ if p.attributeDescriptor:
61
+ len_ += p.attributeDescriptor.size
62
+ if ciphering:
63
+ len_ += cls._CIPHERING_HEADER_SIZE
64
+ if not p.settings.is_multiple_block():
65
+ # Add command type and invoke and priority.
66
+ p.multipleBlocks = 2 + len(reply) + len_ > p.settings.maxPduSize
67
+ if p.multipleBlocks:
68
+ # Add command type and invoke and priority.
69
+ p.lastBlock = not 8 + len(reply) + len_ > p.settings.maxPduSize
70
+ if p.lastBlock:
71
+ # Add command type and invoke and priority.
72
+ p.lastBlock = not 8 + len(reply) + len_ > p.settings.maxPduSize
73
+
74
+ @classmethod
75
+ def appendMultipleSNBlocks(cls, p, reply):
76
+ ciphering = p.settings.cipher and p.settings.cipher.security != Security.NONE
77
+ hSize = len(reply) + 3
78
+ if p.command == XDLMSAPDU.WRITE_REQUEST or p.command == XDLMSAPDU.READ_REQUEST:
79
+ hSize += 1 + _GXCommon.getObjectCountSizeInBytes(p.getCount())
80
+ maxSize = p.settings.maxPduSize - hSize
81
+ if ciphering:
82
+ maxSize -= cls._CIPHERING_HEADER_SIZE
83
+ maxSize -= _GXCommon.getObjectCountSizeInBytes(maxSize)
84
+ if reply.data.size - reply.data.position > maxSize:
85
+ reply.setUInt8(0)
86
+ else:
87
+ reply.setUInt8(1)
88
+ maxSize = reply.data.size - reply.data.position
89
+ reply.setUInt16(p.blockIndex)
90
+ if p.command == XDLMSAPDU.WRITE_REQUEST:
91
+ p.setBlockIndex(p.blockIndex + 1)
92
+ _GXCommon.setObjectCount(p.getCount(), reply)
93
+ reply.setUInt8(DataType.OCTET_STRING)
94
+ elif p.command == XDLMSAPDU.READ_REQUEST:
95
+ p.setBlockIndex(p.blockIndex + 1)
96
+ _GXCommon.setObjectCount(maxSize, reply)
97
+ return maxSize
98
+
99
+ @classmethod
100
+ def getAddress(cls, value, size):
101
+ if size < 2 and value < 0x80:
102
+ return int((value << 1 | 1))
103
+ if size < 4 and value < 0x4000:
104
+ return int(((value & 0x3F80) << 2 | (value & 0x7F) << 1 | 1))
105
+ if value < 0x10000000:
106
+ return int(((value & 0xFE00000) << 4 | (value & 0x1FC000) << 3 | (value & 0x3F80) << 2 | (value & 0x7F) << 1 | 1))
107
+ raise ValueError("Invalid address.")
108
+
109
+ @classmethod
110
+ def getAddressBytes(cls, value, size):
111
+ tmp = cls.getAddress(value, size)
112
+ bb = GXByteBuffer()
113
+ if size == 1 or tmp < 0x100:
114
+ bb.setUInt8(tmp)
115
+ elif size == 2 or tmp < 0x10000:
116
+ bb.setUInt16(tmp)
117
+ elif size == 4 or tmp < 0x100000000:
118
+ bb.setUInt32(tmp)
119
+ else:
120
+ raise ValueError("Invalid address type.")
121
+ return bb.array()
122
+
123
+ @classmethod
124
+ def getWrapperFrame(cls, settings, command, data):
125
+ bb = GXByteBuffer()
126
+ bb.setUInt16(1)
127
+ if settings.isServer:
128
+ bb.setUInt16(settings.serverAddress)
129
+ if settings.PushClientAddress != 0 and command in (XDLMSAPDU.DATA_NOTIFICATION, XDLMSAPDU.EVENT_NOTIFICATION_REQUEST):
130
+ bb.setUInt16(settings.pushClientAddress)
131
+ else:
132
+ bb.setUInt16(settings.clientAddress)
133
+ else:
134
+ bb.setUInt16(settings.clientAddress)
135
+ bb.setUInt16(settings.serverAddress)
136
+ if data is None:
137
+ bb.setUInt16(0)
138
+ else:
139
+ bb.setUInt16(len(data))
140
+ bb.set(data)
141
+ if settings.isServer:
142
+ if len(data) == data.position:
143
+ data.clear()
144
+ else:
145
+ data.move(data.position, 0, len(data) - data.position)
146
+ data.position = 0
147
+ return bb.array()
148
+
149
+ @classmethod
150
+ def getHdlcFrame(cls, settings, frame_, data):
151
+ # pylint: disable=protected-access
152
+ bb = GXByteBuffer()
153
+ frameSize = 0
154
+ len1 = 0
155
+ primaryAddress = None
156
+ secondaryAddress = None
157
+ if settings.isServer:
158
+ if frame_ == 0x13 and settings.pushClientAddress != 0:
159
+ primaryAddress = cls.getAddressBytes(settings.pushClientAddress, 1)
160
+ else:
161
+ primaryAddress = cls.getAddressBytes(settings.clientAddress, 1)
162
+ secondaryAddress = cls.getAddressBytes(settings.serverAddress, settings.serverAddressSize)
163
+ else:
164
+ primaryAddress = cls.getAddressBytes(settings.serverAddress, settings.serverAddressSize)
165
+ secondaryAddress = cls.getAddressBytes(settings.clientAddress, 1)
166
+ bb.setUInt8(_GXCommon.HDLC_FRAME_START_END)
167
+ frameSize = settings.limits.maxInfoTX
168
+ if data and data.position == 0:
169
+ frameSize -= 3
170
+ if not data:
171
+ len1 = 0
172
+ bb.setUInt8(0xA0)
173
+ elif len(data) - data.position <= frameSize:
174
+ len1 = len(data) - data.position
175
+ bb.setUInt8(0xA0 | (((len(secondaryAddress) + len(primaryAddress) + len1) >> 8) & 0x7))
176
+ else:
177
+ len1 = frameSize
178
+ bb.setUInt8(0xA8 | (((len(secondaryAddress) + len(primaryAddress) + len1) >> 8) & 0x7))
179
+ if len1 == 0:
180
+ bb.setUInt8(5 + len(secondaryAddress) + len(primaryAddress) + len1)
181
+ else:
182
+ bb.setUInt8(7 + len(secondaryAddress) + len(primaryAddress) + len1)
183
+ bb.set(primaryAddress)
184
+ bb.set(secondaryAddress)
185
+ if frame_ == 0:
186
+ bb.setUInt8(settings.getNextSend(True))
187
+ else:
188
+ bb.setUInt8(frame_)
189
+ crc = get_fcs16(bb._data, 1, bb.size - 1)
190
+ bb.setUInt16(crc)
191
+ if len1 != 0:
192
+ bb.set(data, data.position, len1)
193
+ crc = get_fcs16(bb._data, 1, len(bb) - 1)
194
+ bb.setUInt16(crc)
195
+ bb.setUInt8(_GXCommon.HDLC_FRAME_START_END)
196
+ if settings.isServer:
197
+ if data:
198
+ if len(data) == data.position:
199
+ data.clear()
200
+ else:
201
+ data.move(data.position, 0, len(data) - data.position)
202
+ data.position = 0
203
+ return bb.array()
204
+
205
+ @classmethod
206
+ def getMBusData(cls, settings, buff, data):
207
+ len_ = buff.getUInt8()
208
+ if len(buff) < len_ - 1:
209
+ data.complete = (False)
210
+ buff.position = buff.position - 1
211
+ else:
212
+ if len(buff) < len_:
213
+ len_ -= 1
214
+ data.packetLength = len_
215
+ data.complete = True
216
+ cmd = buff.getUInt8()
217
+ manufacturerID = buff.getUInt16()
218
+ man = _GXCommon.decryptManufacturer(manufacturerID)
219
+ #id =
220
+ buff.getUInt32()
221
+ meterVersion = buff.getUInt8()
222
+ type_ = buff.getUInt8()
223
+ ci = buff.getUInt8()
224
+ #frameId =
225
+ buff.getUInt8()
226
+ #state =
227
+ buff.getUInt8()
228
+ configurationWord = buff.getUInt16()
229
+ encryption = MBusEncryptionMode(configurationWord & 7)
230
+ settings.clientAddress = buff.getUInt8()
231
+ settings.serverAddress = buff.getUInt8()
232
+
233
+ @classmethod
234
+ def isMBusData(cls, buff):
235
+ if len(buff) - buff.position < 2:
236
+ return False
237
+ cmd = buff.getUInt8(buff.position + 1)
238
+ return cmd in (MBusCommand.SND_NR, MBusCommand.SND_UD2, MBusCommand.RSP_UD)
239
+
240
+ @classmethod
241
+ def handleReadResponse(cls, settings, reply, index):
242
+ data = reply.data
243
+ pos = 0
244
+ cnt = reply.getTotalCount()
245
+ first = cnt == 0 or reply.commandType == ReadResponse.DATA_BLOCK_RESULT
246
+ if first:
247
+ cnt = _GXCommon.getObjectCount(reply.data)
248
+ reply.totalCount = cnt
249
+ type_ = 0
250
+ # values = None
251
+ if cnt != 1:
252
+ #Parse data after all data is received when readlist is used.
253
+ if reply.isMoreData():
254
+ cls.getDataFromBlock(reply.data, 0)
255
+ return False
256
+ if not first:
257
+ reply.data.position = 0
258
+ first = True
259
+ # values = list()
260
+ # if isinstance(reply.value, list):
261
+ # values.append(reply.value)
262
+ reply.value = None
263
+ while pos != cnt:
264
+ if first:
265
+ type_ = data.getUInt8()
266
+ reply.commandType = type_
267
+ else:
268
+ type_ = reply.commandType
269
+ match type_:
270
+ case ReadResponse.DATA:
271
+ reply.error = 0
272
+ if cnt == 1:
273
+ cls.getDataFromBlock(reply.data, 0)
274
+ else:
275
+ reply.readPosition = data.position
276
+ cls.getValueFromData(settings, reply)
277
+ data.position = reply.readPosition
278
+ # values.append(reply.value)
279
+ # reply.value = None
280
+ case ReadResponse.DATA_ACCESS_ERROR:
281
+ reply.error = data.getUInt8()
282
+ case ReadResponse.DATA_BLOCK_RESULT:
283
+ reply.error = 0
284
+ data = reply.data
285
+ lastBlock = data.getUInt8()
286
+ number = data.getUInt16()
287
+ blockLength = _GXCommon.getObjectCount(data)
288
+ if lastBlock == 0:
289
+ reply.moreData = (RequestTypes(reply.moreData | RequestTypes.DATABLOCK))
290
+ else:
291
+ reply.moreData = (RequestTypes(reply.moreData & ~RequestTypes.DATABLOCK))
292
+ if number != 1 and settings.blockIndex == 1:
293
+ settings.setBlockIndex(number)
294
+ expectedIndex = settings.blockIndex
295
+ if number != expectedIndex:
296
+ raise Exception("Invalid Block number. It is " + number + " and it should be " + expectedIndex + ".")
297
+ if (reply.moreData & RequestTypes.FRAME) != 0:
298
+ cls.getDataFromBlock(data, index)
299
+ return False
300
+ if blockLength != data.size - data.position:
301
+ raise ValueError("Invalid block length.")
302
+ reply.command = None
303
+ cls.getDataFromBlock(reply.data, index)
304
+ reply.setTotalCount(0)
305
+ if reply.getMoreData() == RequestTypes.NONE:
306
+ settings.resetBlockIndex()
307
+ return True
308
+ case ReadResponse.BLOCK_NUMBER:
309
+ number = data.getUInt16()
310
+ if number != settings.blockIndex:
311
+ raise Exception("Invalid Block number. It is " + number + " and it should be " + settings.blockIndex + ".")
312
+ settings.increaseBlockIndex()
313
+ reply.moreData = (RequestTypes(reply.moreData | RequestTypes.DATABLOCK))
314
+ case _:
315
+ raise Exception("HandleReadResponse failed. Invalid tag.")
316
+ pos += 1
317
+ # if values:
318
+ # reply.value = values
319
+ return cnt == 1
320
+
321
+ @classmethod
322
+ def errorCodeToString(cls, type_, value):
323
+ if type_ == TranslatorOutputType.STANDARD_XML:
324
+ return TranslatorStandardTags.errorCodeToString(value)
325
+ return TranslatorSimpleTags.errorCodeToString(value)
326
+
327
+ @classmethod
328
+ def handleDataNotification(cls, settings, reply):
329
+ data = reply.data
330
+ start = data.position - 1
331
+ invokeId = data.getUInt32()
332
+ reply.time = None
333
+ len_ = data.getUInt8()
334
+ tmp = None
335
+ if len_ != 0:
336
+ tmp = bytearray(len_)
337
+ data.get(tmp)
338
+ dt = DataType.DATETIME
339
+ if len_ == 4:
340
+ dt = DataType.TIME
341
+ elif len_ == 5:
342
+ dt = DataType.DATE
343
+ info = _GXDataInfo()
344
+ info.type_ = dt
345
+ reply.time = _GXCommon.getData(settings, GXByteBuffer(tmp), info)
346
+ cls.getDataFromBlock(reply.data, start)
347
+ cls.getValueFromData(settings, reply)
348
+
349
+ @classmethod
350
+ def handleGetResponseWithList(cls, settings, reply):
351
+ cnt = _GXCommon.getObjectCount(reply.data)
352
+ # values = list([None] * cnt)
353
+ pos = 0
354
+ while pos != cnt:
355
+ ch = reply.data.getUInt8()
356
+ if ch != 0:
357
+ reply.error = reply.data.getUInt8()
358
+ else:
359
+ reply.readPosition = reply.data.position
360
+ cls.getValueFromData(settings, reply)
361
+ reply.data.position = reply.readPosition
362
+ # if values:
363
+ # values[pos] = reply.value
364
+ # reply.value = None
365
+ pos += 1
366
+ # reply.value = values
367
+
368
+ @classmethod
369
+ def handleExceptionResponse(cls, data):
370
+ raise Exception(StateError(data.data.getUInt8() - 1), ExceptionServiceError(data.data.getUInt8() - 1))
371
+
372
+ @classmethod
373
+ def handleConfirmedServiceError(cls, data):
374
+ service = ConfirmedServiceError(data.data.getUInt8())
375
+ type_ = data.data.getUInt8()
376
+ raise GXDLMSConfirmedServiceError(service, type_, data.data.getUInt8())
377
+
378
+ @classmethod
379
+ def getValueFromData(cls, settings, reply):
380
+ data = reply.data
381
+ info = _GXDataInfo()
382
+ if isinstance(reply.value, list):
383
+ info.type_ = DataType.ARRAY
384
+ info.count = reply.totalCount
385
+ info.index = reply.getCount()
386
+ index = data.position
387
+ data.position = reply.readPosition
388
+ try:
389
+ value = _GXCommon.getData(settings, data, info)
390
+ if value is not None:
391
+ if not isinstance(value, list):
392
+ reply.valueType = DataType(info.type_)
393
+ reply.value = value
394
+ reply.totalCount = 0
395
+ reply.readPosition = data.position
396
+ else:
397
+ if value:
398
+ if reply.value is None:
399
+ reply.value = value
400
+ else:
401
+ list_ = list()
402
+ list_ += reply.value
403
+ list_ += value
404
+ reply.value = list_
405
+ reply.readPosition = data.position
406
+ reply.totalCount = info.count
407
+ elif info.complete and reply.command == XDLMSAPDU.DATA_NOTIFICATION:
408
+ reply.readPosition = data.position
409
+ finally:
410
+ data.position = index
411
+ if reply.command != XDLMSAPDU.DATA_NOTIFICATION and info.complete and reply.moreData == RequestTypes.NONE:
412
+ if settings:
413
+ settings.resetBlockIndex()
414
+ data.position = 0
415
+
416
+ @classmethod
417
+ def getDataFromBlock(cls, data: GXByteBuffer, index: int):
418
+ # pylint: disable=protected-access
419
+ if len(data) == data.position:
420
+ data.clear()
421
+ return 0
422
+ len_ = data.position - index
423
+ data._data[data.position - len_:data.position] = data._data[data.position: len(data)]
424
+ data.position = data.position - len_
425
+ data.size = len(data) - len_
426
+ return len