Skip to content

Commit 14fe7c4

Browse files
committed
Moved to attrs for messages and added protocol char. Fixes #23, #19
1 parent f09e86e commit 14fe7c4

File tree

2 files changed

+64
-111
lines changed

2 files changed

+64
-111
lines changed

iec62056_21/messages.py

+62-110
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import re
2-
import typing
2+
from typing import *
3+
import attr
34

45
from iec62056_21.exceptions import Iec6205621ParseError, ValidationError
56
from iec62056_21 import constants, utils
@@ -40,6 +41,7 @@ def from_bytes(cls, bytes_data):
4041
return cls.from_representation(bytes_data.decode(constants.ENCODING))
4142

4243

44+
@attr.s(auto_attribs=True)
4345
class DataSet(Iec6205621Data):
4446

4547
"""
@@ -50,13 +52,9 @@ class DataSet(Iec6205621Data):
5052

5153
EXCLUDE_CHARS = ["(", ")", "/", "!"]
5254

53-
def __init__(self, value: str, address: str = None, unit: str = None):
54-
55-
# TODO: in programming mode, protocol mode C the value can be up to 128 chars
56-
57-
self.address = address
58-
self.value = value
59-
self.unit = unit
55+
value: str
56+
address: Optional[str] = attr.ib(default=None)
57+
unit: Optional[str] = attr.ib(default=None)
6058

6159
def to_representation(self) -> str:
6260
if self.unit is not None and self.address is not None:
@@ -70,7 +68,7 @@ def to_representation(self) -> str:
7068
return f"({self.value})"
7169

7270
@classmethod
73-
def from_representation(cls, data_set_string):
71+
def from_representation(cls, data_set_string: str):
7472
just_value = regex_data_just_value.search(data_set_string)
7573

7674
if just_value:
@@ -92,30 +90,21 @@ def from_representation(cls, data_set_string):
9290
else:
9391
return cls(address=address, value=values_data, unit=None)
9492

95-
def __repr__(self):
96-
return (
97-
f"{self.__class__.__name__}("
98-
f"value={self.value!r}, "
99-
f"address={self.address!r}, "
100-
f"unit={self.unit!r}"
101-
f")"
102-
)
103-
10493

94+
@attr.s(auto_attribs=True)
10595
class DataLine(Iec6205621Data):
10696
"""
10797
A data line is a list of data sets.
10898
"""
10999

110-
def __init__(self, data_sets):
111-
self.data_sets: typing.List[DataSet] = data_sets
100+
data_sets: List[DataSet]
112101

113102
def to_representation(self):
114103
sets_representation = [_set.to_representation() for _set in self.data_sets]
115104
return "".join(sets_representation)
116105

117106
@classmethod
118-
def from_representation(cls, string_data):
107+
def from_representation(cls, string_data: str):
119108
"""
120109
Is a list of data sets id(value*unit)id(value*unit)
121110
need to split after each ")"
@@ -132,20 +121,17 @@ def from_representation(cls, string_data):
132121

133122
return cls(data_sets=data_sets)
134123

135-
def __repr__(self):
136-
return f"{self.__class__.__name__}(" f"data_sets={self.data_sets!r}" f")"
137-
138124

125+
@attr.s(auto_attribs=True)
139126
class DataBlock(Iec6205621Data):
140127
"""
141128
A data block is a list of DataLines, each ended with a the line end characters
142129
\n\r
143130
"""
144131

145-
def __init__(self, data_lines):
146-
self.data_lines = data_lines
132+
data_lines: List[DataLine]
147133

148-
def to_representation(self):
134+
def to_representation(self) -> str:
149135
lines_rep = [
150136
(line.to_representation() + constants.LINE_END) for line in self.data_lines
151137
]
@@ -157,15 +143,12 @@ def from_representation(cls, string_data: str):
157143
data_lines = [DataLine.from_representation(line) for line in lines]
158144
return cls(data_lines)
159145

160-
def __repr__(self):
161-
return f"{self.__class__.__name__}(data_lines={self.data_lines!r})"
162-
163146

147+
@attr.s(auto_attribs=True)
164148
class ReadoutDataMessage(Iec6205621Data):
165-
def __init__(self, data_block):
166-
self.data_block = data_block
149+
data_block: DataBlock
167150

168-
def to_representation(self):
151+
def to_representation(self) -> str:
169152
data = (
170153
f"{constants.STX}{self.data_block.to_representation()}{constants.END_CHAR}"
171154
f"{constants.LINE_END}{constants.ETX}"
@@ -186,27 +169,28 @@ def from_representation(cls, string_data: str):
186169

187170
return cls(data_block=data_block)
188171

189-
def __repr__(self):
190-
return f"{self.__class__.__name__}(data_block={self.data_block!r})"
191-
192172

173+
@attr.s(auto_attribs=True)
193174
class CommandMessage(Iec6205621Data):
194-
allowed_commands = ["P", "W", "R", "E", "B"]
195-
allowed_command_types = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
175+
allowed_commands: ClassVar[List[str]] = ["P", "W", "R", "E", "B"]
176+
allowed_command_types: ClassVar[List[str]] = [
177+
"0",
178+
"1",
179+
"2",
180+
"3",
181+
"4",
182+
"5",
183+
"6",
184+
"7",
185+
"8",
186+
"9",
187+
]
188+
189+
command: str
190+
command_type: str
191+
data_set: Optional[DataSet] = attr.ib(default=None)
196192

197-
def __init__(
198-
self, command: str, command_type: str, data_set: typing.Optional[DataSet]
199-
):
200-
self.command = command
201-
self.command_type = command_type
202-
self.data_set = data_set
203-
204-
if command not in self.allowed_commands:
205-
raise ValueError(f"{command} is not an allowed command")
206-
if command_type not in self.allowed_command_types:
207-
raise ValueError(f"{command_type} is not an allowed command type")
208-
209-
def to_representation(self):
193+
def to_representation(self) -> str:
210194
header = f"{constants.SOH}{self.command}{self.command_type}"
211195
if self.data_set:
212196
body = f"{constants.STX}{self.data_set.to_representation()}{constants.ETX}"
@@ -245,36 +229,28 @@ def for_single_write(cls, address, value):
245229
data_set = DataSet(value=value, address=address)
246230
return cls(command="W", command_type="1", data_set=data_set)
247231

248-
def __repr__(self):
249-
return (
250-
f"{self.__class__.__name__}("
251-
f"command={self.command!r}, "
252-
f"command_type={self.command_type!r}, "
253-
f"data_set={self.data_set!r}"
254-
f")"
255-
)
256-
257232

233+
@attr.s(auto_attribs=True)
258234
class AnswerDataMessage(Iec6205621Data):
259-
def __init__(self, data_block):
260-
self.data_block = data_block
261-
self._data = None
235+
236+
data_block: DataBlock
237+
cached_data: Optional[List[DataSet]] = attr.ib(default=None, init=False)
262238

263239
@property
264240
def data(self):
265-
if not self._data:
266-
self._get_all_data_sets()
241+
if not self.cached_data:
242+
self.get_all_data_sets()
267243

268-
return self._data
244+
return self.cached_data
269245

270-
def _get_all_data_sets(self):
246+
def get_all_data_sets(self):
271247
data_sets = list()
272248

273249
for line in self.data_block.data_lines:
274-
for set in line.data_sets:
275-
data_sets.append(set)
250+
for data_set in line.data_sets:
251+
data_sets.append(data_set)
276252

277-
self._data = data_sets
253+
self.cached_data = data_sets
278254

279255
def to_representation(self):
280256
# TODO: this is not valid in case reading out partial blocks.
@@ -295,15 +271,12 @@ def from_representation(cls, string_data):
295271

296272
return cls(data_block=data_block)
297273

298-
def __repr__(self):
299-
return f"{self.__class__.__name__}(data_block={self.data_block!r})"
300-
301274

275+
@attr.s(auto_attribs=True)
302276
class RequestMessage(Iec6205621Data):
303-
def __init__(self, device_address=""):
304-
self.device_address = device_address
277+
device_address: str = attr.ib(default="")
305278

306-
def to_representation(self):
279+
def to_representation(self) -> str:
307280
return (
308281
f"{constants.START_CHAR}{constants.REQUEST_CHAR}{self.device_address}"
309282
f"{constants.END_CHAR}{constants.LINE_END}"
@@ -314,44 +287,32 @@ def from_representation(cls, string_data):
314287
device_address = string_data[2:-3]
315288
return cls(device_address)
316289

317-
def __repr__(self):
318-
return f"{self.__class__.__name__}(device_address={self.device_address!r})"
319-
320290

291+
@attr.s(auto_attribs=True)
321292
class AckOptionSelectMessage(Iec6205621Data):
322-
"""
323-
Only support protocol mode 0: Normal
324-
"""
293+
""""""
325294

326-
def __init__(self, baud_char, mode_char):
327-
self.baud_char = baud_char
328-
self.mode_char = mode_char
295+
baud_char: str
296+
mode_char: str
297+
protocol_char: str = attr.ib(default="0")
329298

330299
def to_representation(self):
331-
return f"{constants.ACK}0{self.baud_char}{self.mode_char}{constants.LINE_END}"
300+
return f"{constants.ACK}{self.protocol_char}{self.baud_char}{self.mode_char}{constants.LINE_END}"
332301

333302
@classmethod
334303
def from_representation(cls, string_data):
304+
protocol_char = string_data[1]
335305
baud_char = string_data[2]
336306
mode_char = string_data[3]
337-
return cls(baud_char, mode_char)
338-
339-
def __repr__(self):
340-
return (
341-
f"{self.__class__.__name__}("
342-
f"baud_char={self.baud_char!r}, "
343-
f"mode_char={self.mode_char!r}"
344-
f")"
345-
)
307+
return cls(baud_char, mode_char, protocol_char=protocol_char)
346308

347309

310+
@attr.s(auto_attribs=True)
348311
class IdentificationMessage(Iec6205621Data):
349-
def __init__(
350-
self, identification: str, manufacturer: str, switchover_baudrate_char: str
351-
):
352-
self.identification: str = identification
353-
self.manufacturer: str = manufacturer
354-
self.switchover_baudrate_char: str = switchover_baudrate_char
312+
313+
identification: str
314+
manufacturer: str
315+
switchover_baudrate_char: str
355316

356317
def to_representation(self):
357318
return (
@@ -366,12 +327,3 @@ def from_representation(cls, string_data):
366327
identification = string_data[6:-2]
367328

368329
return cls(identification, manufacturer, switchover_baudrate_char)
369-
370-
def __repr__(self):
371-
return (
372-
f"{self.__class__.__name__}("
373-
f"identification={self.identification!r}, "
374-
f"manufacturer={self.manufacturer!r}, "
375-
f"switchover_baudrate_char={self.switchover_baudrate_char!r}"
376-
f")"
377-
)

requirements.txt

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
attrs==19.1.0
1+
attrs==21.2.0
2+
pyserial==3.5
23

34
#pytest
45
pytest==4.4.1

0 commit comments

Comments
 (0)