Skip to content

Commit a90c973

Browse files
authored
Workaround issue when recevied serial packets are too large (#25)
Adds a workaround to trim the front off any packets that are larger than expected. This should help in the case the start of the packet was not correctly found. To turn off the workaround, specify `ser.TRIM_LARGE_PACKETS = False` where `ser` is `MateNETSerial`. This does not apply when using the PJON transport. Also adds packet length checking for known packet types, for robustness. Resolves #22
1 parent c4b0f9e commit a90c973

File tree

8 files changed

+41
-15
lines changed

8 files changed

+41
-15
lines changed

pymate/cstruct.py

+2
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ class _struct(object):
2626
_size = fmt.size
2727
_nfields = nfields
2828

29+
size = fmt.size
30+
2931
def __init__(self, *args, **kwargs):
3032
"""
3133
Initialize the struct's fields from the provided args.

pymate/matenet/flexnetdc.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ def get_status(self):
161161
def get_status_raw(self):
162162
data = ''
163163
for i in range(0x0A,0x0F+1):
164-
resp = self.send(MateNET.TYPE_STATUS, addr=i)
164+
resp = self.send(MateNET.TYPE_STATUS, addr=i, response_len=(13*6))
165165
if not resp:
166166
return None
167167
data += str(resp)

pymate/matenet/fx.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
class FXStatusPacket(object):
1616
fmt = Struct('>BBBBBBBBBhBB')
17+
size = fmt.size
1718

1819
def __init__(self, misc=None):
1920
self.raw = None
@@ -220,7 +221,7 @@ def get_status(self):
220221
Request a status packet from the inverter
221222
:return: A FXStatusPacket
222223
"""
223-
resp = self.send(MateNET.TYPE_STATUS, addr=1)
224+
resp = self.send(MateNET.TYPE_STATUS, addr=1, response_len=FXStatusPacket.size)
224225
if resp:
225226
status = FXStatusPacket.from_buffer(resp)
226227
self._is_230v = status.is_230v

pymate/matenet/matedevice.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ def __init__(self, matenet, port=0):
3737
def scan(self):
3838
return self.matenet.scan(self.port)
3939

40-
def send(self, ptype, addr, param=0):
41-
return self.matenet.send(ptype, addr, param=param, port=self.port)
40+
def send(self, ptype, addr, param=0, response_len=None):
41+
return self.matenet.send(ptype, addr, param=param, port=self.port, response_len=None)
4242

4343
def query(self, reg, param=0):
4444
return self.matenet.query(reg, param=param, port=self.port)

pymate/matenet/matenet.py

+9-5
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ def __init__(self, port, supports_spacemark=None, tap=None):
6666

6767
self.tap = tap
6868

69-
def send(self, ptype, addr, param=0, port=0):
69+
def send(self, ptype, addr, param=0, port=0, response_len=None):
7070
"""
7171
Send a MateNET packet to the bus (as if it was sent by a MATE unit) and return the response
7272
:param port: Port to send to, if a hub is present (0 if no hub or talking to the hub)
@@ -77,14 +77,17 @@ def send(self, ptype, addr, param=0, port=0):
7777
if self.log.isEnabledFor(logging.DEBUG):
7878
self.log.debug('Send [Port%d, Type=0x%.2x, Addr=0x%.4x, Param=0x%.4x]', port, ptype, addr, param)
7979

80+
if response_len is not None:
81+
response_len += 1 # Account for command ack byte
82+
8083
packet = MateNET.TxPacket(port, ptype, addr, param)
8184
data = None
8285
for i in range(self.RETRY_PACKET+1):
8386
try:
8487
txbuf = packet.to_buffer()
8588
self.port.send(txbuf)
8689

87-
rxbuf = self.port.recv()
90+
rxbuf = self.port.recv(response_len)
8891
if not rxbuf:
8992
self.log.debug('RETRY')
9093
continue # No response - try again
@@ -131,7 +134,7 @@ def query(self, reg, param=0, port=0):
131134
:param param: Optional parameter
132135
:return: The value (16-bit uint)
133136
"""
134-
resp = self.send(MateNET.TYPE_QUERY, addr=reg, param=param, port=port)
137+
resp = self.send(MateNET.TYPE_QUERY, addr=reg, param=param, port=port, response_len=MateNET.QueryResponse.size)
135138
if resp:
136139
response = MateNET.QueryResponse.from_buffer(resp)
137140
return response.value
@@ -144,7 +147,7 @@ def control(self, reg, value, port=0):
144147
:param port: Port (0-10)
145148
:return: ???
146149
"""
147-
resp = self.send(MateNET.TYPE_CONTROL, addr=reg, param=value, port=port)
150+
resp = self.send(MateNET.TYPE_CONTROL, addr=reg, param=value, port=port, response_len=MateNET.QueryResponse.size)
148151
if resp:
149152
return None # TODO: What kind of response do we get from a control packet?
150153

@@ -168,6 +171,7 @@ def scan(self, port=0):
168171
"""
169172
result = self.query(0x00, port=port)
170173
if result is not None:
174+
# TODO: Don't know what the upper byte is for, but it is seen on some MX units
171175
result = result & 0x00FF
172176
return result
173177

@@ -206,7 +210,7 @@ def find_device(self, device_type):
206210
mx = MateMXDevice(bus, port)
207211
"""
208212
for i in range(0,10):
209-
dtype = self.query(0x00, port=i)
213+
dtype = self.scan(port=i)
210214
if dtype and dtype == device_type:
211215
self.log.info('Found %s device at port %d',
212216
MateNET.DEVICE_TYPES[dtype],

pymate/matenet/matenet_pjon.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ def _recv_frame(self, timeout=1.0):
184184
self.log.info('RX TIMEOUT')
185185
return None
186186

187-
def recv(self, timeout=1.0):
187+
def recv(self, expected_len=None, timeout=1.0):
188188
"""
189189
Receive a packet from PJON bus
190190
:param timeout: seconds to wait until returning, 0 to return immediately, None to block indefinitely
@@ -262,6 +262,8 @@ def recv(self, timeout=1.0):
262262
if len(payload) == 1:
263263
raise RuntimeError("PJON error: Error returned from controller: %.2x" % (payload[0]))
264264

265+
# TODO: Validate payload length against expected_len
266+
265267
return ''.join(chr(c) for c in payload) # TODO: Hacky
266268

267269
if __name__ == "__main__":

pymate/matenet/matenet_ser.py

+18-3
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ def __init__(self, comport, supports_spacemark=None):
5757
# Amount of time with no communication that signifies the end of the packet
5858
self.END_OF_PACKET_TIMEOUT = 0.02 # seconds
5959

60+
# Set to true to workaround issue where some received packets are too large
61+
self.TRIM_LARGE_PACKETS = True
62+
6063
self.supports_spacemark = supports_spacemark
6164
if self.supports_spacemark is None:
6265
self.supports_spacemark = (
@@ -97,7 +100,7 @@ def _calc_checksum(data):
97100
return sum(ord(c) for c in data) % 0xFFFF
98101

99102
@staticmethod
100-
def _parse_packet(data):
103+
def _parse_packet(data, expected_len=None):
101104
"""
102105
Parse a MATE packet, validatin the length and checksum
103106
:param data: Raw string data
@@ -109,6 +112,12 @@ def _parse_packet(data):
109112
if len(data) < 3:
110113
raise RuntimeError("Error receiving mate packet - Received packet too small (%d bytes)" % (len(data)))
111114

115+
if expected_len is not None:
116+
if len(data) < expected_len:
117+
raise RuntimeError("Error receiving mate packet - Received packet too small (%d bytes, expected %d)" % (len(data), expected_len))
118+
if len(data) > expected_len:
119+
RuntimeError("Error receiving mate packet - Received packet too large (%d bytes, expected %d)" % (len(data), expected_len))
120+
112121
# Checksum
113122
packet = data[0:-2]
114123
expected_chksum = (ord(data[-2]) << 8) | ord(data[-1])
@@ -133,7 +142,7 @@ def send(self, data):
133142
# Rest of the bytes have bit8 cleared (data byte)
134143
self._write_9b(data[1:] + footer, 0)
135144

136-
def recv(self, timeout=1.0):
145+
def recv(self, expected_len=None, timeout=1.0):
137146
"""
138147
Receive a packet from the MateNET bus, waiting if necessary
139148
:param timeout: seconds to wait until returning, 0 to return immediately, None to block indefinitely
@@ -156,4 +165,10 @@ def recv(self, timeout=1.0):
156165
if self.log.isEnabledFor(logging.DEBUG):
157166
self.log.debug('RX: %s', (' '.join('%.2x' % ord(c) for c in rawdata)))
158167

159-
return MateNETSerial._parse_packet(rawdata)
168+
if expected_len is not None:
169+
expected_len += 2 # Account for checksum
170+
171+
if self.TRIM_LARGE_PACKETS and (len(rawdata) > expected_len):
172+
rawdata = rawdata[-expected_len:]
173+
174+
return MateNETSerial._parse_packet(rawdata, expected_len)

pymate/matenet/mx.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
class MXStatusPacket(object):
1515
fmt = Struct('>BbbbBBBBBHH')
16+
size = fmt.size
1617

1718
STATUS_SLEEPING = 0
1819
STATUS_FLOATING = 1
@@ -83,6 +84,7 @@ def __str__(self):
8384

8485
class MXLogPagePacket(object):
8586
fmt = Struct('>BBBBBBBBBBBBBB')
87+
size = fmt.size
8688

8789
def __init__(self):
8890
self.day = None
@@ -162,7 +164,7 @@ def get_status(self):
162164
Request a status packet from the controller
163165
:return: A MXStatusPacket
164166
"""
165-
resp = self.send(MateNET.TYPE_STATUS, addr=1, param=0x00)
167+
resp = self.send(MateNET.TYPE_STATUS, addr=1, param=0x00, response_len=MXStatusPacket.size)
166168
if resp:
167169
return MXStatusPacket.from_buffer(resp)
168170

@@ -172,7 +174,7 @@ def get_logpage(self, day):
172174
:param day: The day, counting backwards from today (0:Today, -1..-255)
173175
:return: A MXLogPagePacket
174176
"""
175-
resp = self.send(MateNET.TYPE_LOG, addr=0, param=-day)
177+
resp = self.send(MateNET.TYPE_LOG, addr=0, param=-day, response_len=MXLogPagePacket.size)
176178
if resp:
177179
return MXLogPagePacket.from_buffer(resp)
178180

0 commit comments

Comments
 (0)