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.
- DLMS_SPODES_client/FCS16.py +39 -39
- DLMS_SPODES_client/__init__.py +12 -12
- DLMS_SPODES_client/client.py +2093 -2093
- DLMS_SPODES_client/gurux_common/enums/TraceLevel.py +21 -21
- DLMS_SPODES_client/gurux_dlms/AesGcmParameter.py +37 -37
- DLMS_SPODES_client/gurux_dlms/CountType.py +16 -16
- DLMS_SPODES_client/gurux_dlms/GXByteBuffer.py +545 -545
- DLMS_SPODES_client/gurux_dlms/GXCiphering.py +196 -196
- DLMS_SPODES_client/gurux_dlms/GXDLMS.py +426 -426
- DLMS_SPODES_client/gurux_dlms/GXDLMSChippering.py +237 -237
- DLMS_SPODES_client/gurux_dlms/GXDLMSChipperingStream.py +977 -977
- DLMS_SPODES_client/gurux_dlms/GXDLMSConfirmedServiceError.py +90 -90
- DLMS_SPODES_client/gurux_dlms/GXDLMSException.py +139 -139
- DLMS_SPODES_client/gurux_dlms/GXDLMSLNParameters.py +33 -33
- DLMS_SPODES_client/gurux_dlms/GXDLMSSNParameters.py +21 -21
- DLMS_SPODES_client/gurux_dlms/GXDLMSSettings.py +254 -254
- DLMS_SPODES_client/gurux_dlms/GXReplyData.py +87 -87
- DLMS_SPODES_client/gurux_dlms/HdlcControlFrame.py +9 -9
- DLMS_SPODES_client/gurux_dlms/MBusCommand.py +8 -8
- DLMS_SPODES_client/gurux_dlms/MBusEncryptionMode.py +27 -27
- DLMS_SPODES_client/gurux_dlms/ResponseType.py +8 -8
- DLMS_SPODES_client/gurux_dlms/SetResponseType.py +29 -29
- DLMS_SPODES_client/gurux_dlms/_HDLCInfo.py +9 -9
- DLMS_SPODES_client/gurux_dlms/__init__.py +75 -75
- DLMS_SPODES_client/gurux_dlms/enums/Access.py +12 -12
- DLMS_SPODES_client/gurux_dlms/enums/ApplicationReference.py +14 -14
- DLMS_SPODES_client/gurux_dlms/enums/Authentication.py +41 -41
- DLMS_SPODES_client/gurux_dlms/enums/BerType.py +35 -35
- DLMS_SPODES_client/gurux_dlms/enums/Command.py +285 -285
- DLMS_SPODES_client/gurux_dlms/enums/Definition.py +9 -9
- DLMS_SPODES_client/gurux_dlms/enums/ErrorCode.py +46 -46
- DLMS_SPODES_client/gurux_dlms/enums/ExceptionServiceError.py +12 -12
- DLMS_SPODES_client/gurux_dlms/enums/HardwareResource.py +10 -10
- DLMS_SPODES_client/gurux_dlms/enums/HdlcFrameType.py +9 -9
- DLMS_SPODES_client/gurux_dlms/enums/Initiate.py +10 -10
- DLMS_SPODES_client/gurux_dlms/enums/LoadDataSet.py +13 -13
- DLMS_SPODES_client/gurux_dlms/enums/ObjectType.py +306 -306
- DLMS_SPODES_client/gurux_dlms/enums/Priority.py +7 -7
- DLMS_SPODES_client/gurux_dlms/enums/RequestTypes.py +9 -9
- DLMS_SPODES_client/gurux_dlms/enums/Security.py +14 -14
- DLMS_SPODES_client/gurux_dlms/enums/Service.py +16 -16
- DLMS_SPODES_client/gurux_dlms/enums/ServiceClass.py +9 -9
- DLMS_SPODES_client/gurux_dlms/enums/ServiceError.py +8 -8
- DLMS_SPODES_client/gurux_dlms/enums/Standard.py +18 -18
- DLMS_SPODES_client/gurux_dlms/enums/StateError.py +7 -7
- DLMS_SPODES_client/gurux_dlms/enums/Task.py +10 -10
- DLMS_SPODES_client/gurux_dlms/enums/VdeStateError.py +10 -10
- DLMS_SPODES_client/gurux_dlms/enums/__init__.py +33 -33
- DLMS_SPODES_client/gurux_dlms/internal/_GXCommon.py +1673 -1673
- DLMS_SPODES_client/logger.py +56 -56
- DLMS_SPODES_client/services.py +90 -90
- DLMS_SPODES_client/session.py +363 -363
- DLMS_SPODES_client/settings.py +48 -48
- DLMS_SPODES_client/task.py +1891 -1884
- {dlms_spodes_client-0.19.36.dist-info → dlms_spodes_client-0.19.38.dist-info}/METADATA +29 -29
- dlms_spodes_client-0.19.38.dist-info/RECORD +61 -0
- {dlms_spodes_client-0.19.36.dist-info → dlms_spodes_client-0.19.38.dist-info}/WHEEL +1 -1
- dlms_spodes_client-0.19.36.dist-info/RECORD +0 -61
- {dlms_spodes_client-0.19.36.dist-info → dlms_spodes_client-0.19.38.dist-info}/entry_points.txt +0 -0
- {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
|