]>
Commit | Line | Data |
---|---|---|
db858a04 BW |
1 | ## |
2 | ## This file is part of the libsigrokdecode project. | |
3 | ## | |
4 | ## Copyright (C) 2015 Bart de Waal <bart@waalamo.com> | |
5 | ## | |
6 | ## This program is free software; you can redistribute it and/or modify | |
7 | ## it under the terms of the GNU General Public License as published by | |
8 | ## the Free Software Foundation; either version 3 of the License, or | |
9 | ## (at your option) any later version. | |
10 | ## | |
11 | ## This program is distributed in the hope that it will be useful, | |
12 | ## but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 | ## GNU General Public License for more details. | |
15 | ## | |
16 | ## You should have received a copy of the GNU General Public License | |
17 | ## along with this program; if not, see <http://www.gnu.org/licenses/>. | |
18 | ## | |
19 | ||
20 | import sigrokdecode as srd | |
21 | from math import ceil | |
22 | ||
23 | RX = 0 | |
24 | TX = 1 | |
d4364948 | 25 | rxtx_channels = ('RX', 'TX') |
db858a04 BW |
26 | |
27 | class No_more_data(Exception): | |
28 | '''This exception is a signal that we should stop parsing an ADU as there | |
29 | is no more data to parse.''' | |
30 | pass | |
31 | ||
32 | class Data: | |
33 | '''The Data class is used to hold the bytes from the serial decode.''' | |
34 | def __init__(self, start, end, data): | |
35 | self.start = start | |
36 | self.end = end | |
37 | self.data = data | |
38 | ||
39 | class Modbus_ADU: | |
40 | '''An Application Data Unit is what Modbus calls one message. | |
41 | Protocol decoders are supposed to keep track of state and then provide | |
42 | decoded data to the backend as it reads it. In Modbus' case, the state is | |
43 | the ADU up to that point. This class represents the state and writes the | |
44 | messages to the backend. | |
45 | This class is for the common infrastructure between CS and SC. It should | |
46 | not be used directly, only inhereted from.''' | |
47 | ||
48 | def __init__(self, parent, start, write_channel, annotation_prefix): | |
49 | self.data = [] # List of all the data received up to now | |
50 | self.parent = parent # Reference to the decoder object | |
51 | self.start = start | |
52 | self.last_read = start # The last moment parsed by this ADU object | |
53 | self.write_channel = write_channel | |
54 | self.last_byte_put = -1 | |
55 | self.annotation_prefix = annotation_prefix | |
56 | # Any Modbus message needs to be at least 4 bytes long. The Modbus | |
57 | # function may make this longer. | |
58 | self.minimum_length = 4 | |
59 | ||
60 | # This variable is used by an external function to determine when the | |
61 | # next frame should be started. | |
62 | self.startNewFrame = False | |
63 | ||
64 | # If there is an error in a frame, we'd like to highlight it. Keep | |
65 | # track of errors. | |
66 | self.hasError = False | |
67 | ||
68 | def add_data(self, start, end, data): | |
69 | '''Let the frame handle another piece of data. | |
70 | start: start of this data | |
71 | end: end of this data | |
72 | data: data as received from the UART decoder''' | |
73 | ptype, rxtx, pdata = data | |
74 | self.last_read = end | |
75 | if ptype == 'DATA': | |
76 | self.data.append(Data(start, end, pdata[0])) | |
77 | self.parse() # parse() is defined in the specific type of ADU. | |
78 | ||
79 | def puti(self, byte_to_put, annotation, message): | |
80 | '''This class keeps track of how much of the data has already been | |
81 | annotated. This function tells the parent class to write message, but | |
82 | only if it hasn't written about this bit before. | |
83 | byte_to_put: Only write if it hasn't yet written byte_to_put. It will | |
84 | write from the start of self.last_byte_put+1 to the end | |
85 | of byte_to_put. | |
86 | annotation: Annotation to write to, without annotation_prefix. | |
87 | message: Message to write.''' | |
88 | if byte_to_put > len(self.data) - 1: | |
89 | # If the byte_to_put hasn't been read yet. | |
90 | raise No_more_data | |
91 | ||
92 | if annotation == 'error': | |
93 | self.hasError = True | |
94 | ||
95 | if byte_to_put > self.last_byte_put: | |
96 | self.parent.puta( | |
97 | self.data[self.last_byte_put + 1].start, | |
98 | self.data[byte_to_put].end, | |
99 | self.annotation_prefix + annotation, | |
100 | message) | |
101 | self.last_byte_put = byte_to_put | |
102 | raise No_more_data | |
103 | ||
104 | def putl(self, annotation, message, maximum=None): | |
105 | '''Puts the last byte on the stack with message. The contents of the | |
106 | last byte will be applied to message using format.''' | |
107 | last_byte_address = len(self.data) - 1 | |
108 | if maximum is not None and last_byte_address > maximum: | |
109 | return | |
110 | self.puti(last_byte_address, annotation, | |
111 | message.format(self.data[-1].data)) | |
112 | ||
113 | def close(self, message_overflow): | |
114 | '''Function to be called when next message is started. As there is | |
115 | always space between one message and the next, we can use that space | |
116 | for errors at the end.''' | |
117 | # TODO: Figure out how to make this happen for last message. | |
118 | data = self.data | |
119 | if len(data) < self.minimum_length: | |
120 | if len(data) == 0: | |
121 | # Sometimes happens with noise, safe to ignore. | |
122 | return | |
123 | self.parent.puta( | |
124 | data[self.last_byte_put].end, message_overflow, | |
125 | self.annotation_prefix + 'error', | |
126 | 'Message too short or not finished') | |
127 | self.hasError = True | |
d4364948 AG |
128 | if self.hasError and self.parent.options['scchannel'] != self.parent.options['cschannel']: |
129 | # If we are decoding different channels (so client->server and | |
130 | # server->client messages can be separated) we like to mark blocks | |
131 | # containing errors. We don't do this when decoding the same | |
132 | # channel as both a client->server and server->client frame, and | |
db858a04 BW |
133 | # one of those is bound to contain an error, making highlighting |
134 | # frames useless. | |
135 | self.parent.puta(data[0].start, data[-1].end, | |
136 | 'error-indication', 'Frame contains error') | |
137 | if len(data) > 256: | |
138 | try: | |
d4364948 | 139 | self.puti(len(data) - 1, 'error', |
db858a04 BW |
140 | 'Modbus data frames are limited to 256 bytes') |
141 | except No_more_data: | |
142 | pass | |
143 | ||
144 | def check_crc(self, byte_to_put): | |
145 | '''Check the CRC code, data[byte_to_put] is the 2nd byte of the CRC.''' | |
146 | crc_byte1, crc_byte2 = self.calc_crc(byte_to_put) | |
147 | data = self.data | |
148 | if data[-2].data == crc_byte1 and data[-1].data == crc_byte2: | |
149 | self.puti(byte_to_put, 'crc', 'CRC correct') | |
150 | else: | |
151 | self.puti(byte_to_put, 'error', | |
152 | 'CRC should be {} {}'.format(crc_byte1, crc_byte2)) | |
153 | ||
154 | def half_word(self, start): | |
155 | '''Return the half word (16 bit) value starting at start bytes in. If | |
156 | it goes out of range it raises the usual errors.''' | |
157 | if (start + 1) > (len(self.data) - 1): | |
158 | # If there isn't enough length to access data[start + 1]. | |
159 | raise No_more_data | |
160 | return self.data[start].data * 0x100 + self.data[start + 1].data | |
161 | ||
162 | def calc_crc(self, last_byte): | |
163 | '''Calculate the CRC, as described in the spec. | |
164 | The last byte of the CRC should be data[last_byte].''' | |
165 | if last_byte < 3: | |
166 | # Every Modbus ADU should be as least 4 long, so we should never | |
167 | # have to calculate a CRC on something shorter. | |
168 | raise Exception('Could not calculate CRC: message too short') | |
169 | ||
170 | result = 0xFFFF | |
171 | magic_number = 0xA001 # As defined in the modbus specification. | |
172 | for byte in self.data[:last_byte - 1]: | |
173 | result = result ^ byte.data | |
174 | for i in range(8): | |
175 | LSB = result & 1 | |
176 | result = result >> 1 | |
177 | if (LSB): # If the LSB is true. | |
178 | result = result ^ magic_number | |
179 | byte1 = result & 0xFF | |
180 | byte2 = (result & 0xFF00) >> 8 | |
181 | return (byte1, byte2) | |
182 | ||
183 | def parse_write_single_coil(self): | |
184 | '''Parse function 5, write single coil.''' | |
185 | self.minimum_length = 8 | |
186 | ||
187 | self.puti(1, 'function', 'Function 5: Write Single Coil') | |
188 | ||
189 | address = self.half_word(2) | |
190 | self.puti(3, 'address', | |
191 | 'Address 0x{:X} / {:d}'.format(address, address + 10000)) | |
192 | ||
193 | raw_value = self.half_word(4) | |
194 | value = 'Invalid Coil Value' | |
195 | if raw_value == 0x0000: | |
196 | value = 'Coil Value OFF' | |
197 | elif raw_value == 0xFF00: | |
198 | value = 'Coil Value ON' | |
199 | self.puti(5, 'data', value) | |
200 | ||
201 | self.check_crc(7) | |
202 | ||
203 | def parse_write_single_register(self): | |
204 | '''Parse function 6, write single register.''' | |
205 | self.minimum_length = 8 | |
206 | ||
207 | self.puti(1, 'function', 'Function 6: Write Single Register') | |
208 | ||
209 | address = self.half_word(2) | |
210 | self.puti(3, 'address', | |
211 | 'Address 0x{:X} / {:d}'.format(address, address + 30000)) | |
212 | ||
213 | value = self.half_word(4) | |
214 | value_formatted = 'Register Value 0x{0:X} / {0:d}'.format(value) | |
215 | self.puti(5, 'data', value_formatted) | |
216 | ||
217 | self.check_crc(7) | |
218 | ||
219 | def parse_diagnostics(self): | |
220 | '''Parse function 8, diagnostics. This function has many subfunctions, | |
221 | but they are all more or less the same.''' | |
222 | self.minimum_length = 8 | |
223 | ||
224 | self.puti(1, 'function', 'Function 8: Diagnostics') | |
225 | ||
226 | diag_subfunction = { | |
227 | 0: 'Return Query data', | |
228 | 1: 'Restart Communications Option', | |
229 | 2: 'Return Diagnostics Register', | |
230 | 3: 'Change ASCII Input Delimiter', | |
231 | 4: 'Force Listen Only Mode', | |
232 | 10: 'Clear Counters and Diagnostic Register', | |
233 | 11: 'Return Bus Message Count', | |
234 | 12: 'Return Bus Communication Error Count', | |
235 | 13: 'Return Bus Exception Error Count', | |
236 | 14: 'Return Slave Message Count', | |
237 | 15: 'Return Slave No Response Count', | |
238 | 16: 'Return Slave NAK Count', | |
239 | 17: 'Return Slave Busy Count', | |
240 | 18: 'Return Bus Character Overrun Count', | |
241 | 20: 'Return Overrun Counter and Flag', | |
242 | } | |
243 | subfunction = self.half_word(2) | |
244 | subfunction_name = diag_subfunction.get(subfunction, | |
245 | 'Reserved subfunction') | |
246 | self.puti(3, 'data', | |
247 | 'Subfunction {}: {}'.format(subfunction, subfunction_name)) | |
248 | ||
249 | diagnostic_data = self.half_word(4) | |
250 | self.puti(5, 'data', | |
251 | 'Data Field: {0} / 0x{0:04X}'.format(diagnostic_data)) | |
252 | ||
253 | self.check_crc(7) | |
254 | ||
255 | def parse_mask_write_register(self): | |
256 | '''Parse function 22, Mask Write Register.''' | |
257 | self.minimum_length = 10 | |
258 | data = self.data | |
259 | ||
260 | self.puti(1, 'function', 'Function 22: Mask Write Register') | |
261 | ||
262 | address = self.half_word(2) | |
263 | self.puti(3, 'address', | |
264 | 'Address 0x{:X} / {:d}'.format(address, address + 30001)) | |
265 | ||
266 | self.half_word(4) # To make sure we don't oveflow data. | |
267 | and_mask_1 = data[4].data | |
268 | and_mask_2 = data[5].data | |
269 | self.puti(5, 'data', | |
270 | 'AND mask: {:08b} {:08b}'.format(and_mask_1, and_mask_2)) | |
271 | ||
272 | self.half_word(6) # To make sure we don't oveflow data. | |
273 | or_mask_1 = data[6].data | |
274 | or_mask_2 = data[7].data | |
275 | self.puti(7, 'data', | |
276 | 'OR mask: {:08b} {:08b}'.format(or_mask_1, or_mask_2)) | |
277 | ||
278 | self.check_crc(9) | |
279 | ||
280 | def parse_not_implemented(self): | |
281 | '''Explicitly mark certain functions as legal functions, but not | |
282 | implemented in this parser. This is due to the author not being able to | |
283 | find anything (hardware or software) that supports these functions.''' | |
284 | # TODO: Implement these functions. | |
285 | ||
286 | # Mentioning what function it is is no problem. | |
287 | function = self.data[1].data | |
288 | functionname = { | |
289 | 20: 'Read File Record', | |
290 | 21: 'Write File Record', | |
291 | 24: 'Read FIFO Queue', | |
292 | 43: 'Read Device Identification/Encapsulated Interface Transport', | |
293 | }[function] | |
294 | self.puti(1, 'function', | |
295 | 'Function {}: {} (not supported)'.format(function, functionname)) | |
296 | ||
297 | # From there on out we can keep marking it unsupported. | |
298 | self.putl('data', 'This function is not currently supported') | |
299 | ||
300 | class Modbus_ADU_SC(Modbus_ADU): | |
301 | '''SC stands for Server -> Client.''' | |
302 | def parse(self): | |
303 | '''Select which specific Modbus function we should parse.''' | |
304 | data = self.data | |
305 | ||
306 | # This try-catch is being used as flow control. | |
307 | try: | |
308 | server_id = data[0].data | |
309 | if 1 <= server_id <= 247: | |
310 | message = 'Slave ID: {}'.format(server_id) | |
311 | else: | |
312 | message = 'Slave ID {} is invalid' | |
313 | self.puti(0, 'server-id', message) | |
314 | ||
315 | function = data[1].data | |
316 | if function == 1 or function == 2: | |
317 | self.parse_read_bits() | |
318 | elif function == 3 or function == 4 or function == 23: | |
319 | self.parse_read_registers() | |
320 | elif function == 5: | |
321 | self.parse_write_single_coil() | |
322 | elif function == 6: | |
323 | self.parse_write_single_register() | |
324 | elif function == 7: | |
325 | self.parse_read_exception_status() | |
326 | elif function == 8: | |
327 | self.parse_diagnostics() | |
328 | elif function == 11: | |
329 | self.parse_get_comm_event_counter() | |
330 | elif function == 12: | |
331 | self.parse_get_comm_event_log() | |
332 | elif function == 15 or function == 16: | |
333 | self.parse_write_multiple() | |
334 | elif function == 17: | |
335 | self.parse_report_server_id() | |
336 | elif function == 22: | |
337 | self.parse_mask_write_register() | |
338 | elif function in {21, 21, 24, 43}: | |
339 | self.parse_not_implemented() | |
340 | elif function > 0x80: | |
341 | self.parse_error() | |
342 | else: | |
343 | self.puti(1, 'error', | |
344 | 'Unknown function: {}'.format(data[1].data)) | |
345 | self.putl('error', 'Unknown function') | |
346 | ||
347 | # If the message gets here without raising an exception, the | |
348 | # message goes on longer than it should. | |
349 | self.putl('error', 'Message too long') | |
350 | ||
351 | except No_more_data: | |
352 | # Just a message saying we don't need to parse anymore this round. | |
353 | pass | |
354 | ||
355 | def parse_read_bits(self): | |
356 | self.mimumum_length = 5 | |
357 | ||
358 | data = self.data | |
359 | function = data[1].data | |
360 | ||
361 | if function == 1: | |
362 | self.puti(1, 'function', 'Function 1: Read Coils') | |
363 | else: | |
364 | self.puti(1, 'function', 'Function 2: Read Discrete Inputs') | |
365 | ||
366 | bytecount = self.data[2].data | |
367 | self.minimum_length = 5 + bytecount # 3 before data, 2 CRC. | |
368 | self.puti(2, 'length', 'Byte count: {}'.format(bytecount)) | |
369 | ||
370 | # From here on out, we expect registers on 3 and 4, 5 and 6 etc. | |
371 | # So registers never start when the length is even. | |
372 | self.putl('data', '{:08b}', bytecount + 2) | |
373 | self.check_crc(bytecount + 4) | |
374 | ||
375 | def parse_read_registers(self): | |
376 | self.mimumum_length = 5 | |
377 | ||
378 | data = self.data | |
379 | ||
380 | function = data[1].data | |
381 | if function == 3: | |
382 | self.puti(1, 'function', 'Function 3: Read Holding Registers') | |
383 | elif function == 4: | |
384 | self.puti(1, 'function', 'Function 4: Read Input Registers') | |
385 | elif function == 23: | |
386 | self.puti(1, 'function', 'Function 23: Read/Write Multiple Registers') | |
387 | ||
388 | bytecount = self.data[2].data | |
389 | self.minimum_length = 5 + bytecount # 3 before data, 2 CRC. | |
390 | if bytecount % 2 == 0: | |
391 | self.puti(2, 'length', 'Byte count: {}'.format(bytecount)) | |
392 | else: | |
393 | self.puti(2, 'error', | |
394 | 'Error: Odd byte count ({})'.format(bytecount)) | |
395 | ||
396 | # From here on out, we expect registers on 3 and 4, 5 and 6 etc. | |
397 | # So registers never start when the length is even. | |
398 | if len(data) % 2 == 1: | |
399 | register_value = self.half_word(-2) | |
400 | self.putl('data', '0x{0:04X} / {0}'.format(register_value), | |
401 | bytecount + 2) | |
402 | else: | |
403 | raise No_more_data | |
404 | ||
405 | self.check_crc(bytecount + 4) | |
406 | ||
407 | def parse_read_exception_status(self): | |
408 | self.mimumum_length = 5 | |
409 | ||
410 | self.puti(1, 'function', 'Function 7: Read Exception Status') | |
411 | exception_status = self.data[2].data | |
412 | self.puti(2, 'data', | |
413 | 'Exception status: {:08b}'.format(exception_status)) | |
414 | self.check_crc(4) | |
415 | ||
416 | def parse_get_comm_event_counter(self): | |
417 | self.mimumum_length = 8 | |
418 | ||
419 | self.puti(1, 'function', 'Function 11: Get Comm Event Counter') | |
420 | ||
421 | status = self.half_word(2) | |
422 | if status == 0x0000: | |
423 | self.puti(3, 'data', 'Status: not busy') | |
424 | elif status == 0xFFFF: | |
425 | self.puti(3, 'data', 'Status: busy') | |
426 | else: | |
427 | self.puti(3, 'error', 'Bad status: 0x{:04X}'.format(status)) | |
428 | ||
429 | count = self.half_word(4) | |
430 | self.puti(5, 'data', 'Event Count: {}'.format(count)) | |
431 | self.check_crc(7) | |
432 | ||
433 | def parse_get_comm_event_log(self): | |
434 | self.mimumum_length = 11 | |
435 | self.puti(1, 'function', 'Function 12: Get Comm Event Log') | |
436 | ||
437 | data = self.data | |
438 | ||
439 | bytecount = data[2].data | |
440 | self.puti(2, 'length', 'Bytecount: {}'.format(bytecount)) | |
441 | # The bytecount is the length of everything except the slaveID, | |
442 | # function code, bytecount and CRC. | |
443 | self.mimumum_length = 5 + bytecount | |
444 | ||
445 | status = self.half_word(3) | |
446 | if status == 0x0000: | |
447 | self.puti(4, 'data', 'Status: not busy') | |
448 | elif status == 0xFFFF: | |
449 | self.puti(4, 'data', 'Status: busy') | |
450 | else: | |
451 | self.puti(4, 'error', 'Bad status: 0x{:04X}'.format(status)) | |
452 | ||
453 | event_count = self.half_word(5) | |
454 | self.puti(6, 'data', 'Event Count: {}'.format(event_count)) | |
455 | ||
456 | message_count = self.half_word(7) | |
457 | self.puti(8, 'data', 'Message Count: {}'.format(message_count)) | |
458 | ||
459 | self.putl('data', 'Event: 0x{:02X}'.format(data[-1].data), | |
460 | bytecount + 2) | |
461 | ||
462 | self.check_crc(bytecount + 4) | |
463 | ||
464 | def parse_write_multiple(self): | |
465 | '''Function 15 and 16 are almost the same, so we can parse them both | |
466 | using one function.''' | |
467 | self.mimumum_length = 8 | |
468 | ||
469 | function = self.data[1].data | |
470 | if function == 15: | |
471 | data_unit = 'Coils' | |
472 | max_outputs = 0x07B0 | |
473 | long_address_offset = 10001 | |
474 | elif function == 16: | |
475 | data_unit = 'Registers' | |
476 | max_outputs = 0x007B | |
477 | long_address_offset = 30001 | |
478 | ||
479 | self.puti(1, 'function', | |
480 | 'Function {}: Write Multiple {}'.format(function, data_unit)) | |
481 | ||
482 | starting_address = self.half_word(2) | |
483 | # Some instruction manuals use a long form name for addresses, this is | |
484 | # listed here for convienience. | |
485 | address_name = long_address_offset + starting_address | |
486 | self.puti(3, 'address', | |
487 | 'Start at address 0x{:X} / {:d}'.format(starting_address, | |
488 | address_name)) | |
489 | ||
490 | quantity_of_outputs = self.half_word(4) | |
491 | if quantity_of_outputs <= max_outputs: | |
492 | self.puti(5, 'data', | |
493 | 'Write {} {}'.format(quantity_of_outputs, data_unit)) | |
494 | else: | |
495 | self.puti(5, 'error', | |
496 | 'Bad value: {} {}. Max is {}'.format(quantity_of_outputs, | |
497 | data_unit, max_outputs)) | |
498 | ||
499 | self.check_crc(7) | |
500 | ||
501 | def parse_report_server_id(self): | |
502 | # Buildup of this function: | |
503 | # 1 byte serverID | |
504 | # 1 byte function (17) | |
505 | # 1 byte bytecount | |
506 | # 1 byte serverID (counts for bytecount) | |
507 | # 1 byte Run Indicator Status (counts for bytecount) | |
508 | # bytecount - 2 bytes of device specific data (counts for bytecount) | |
509 | # 2 bytes of CRC | |
510 | self.mimumum_length = 7 | |
511 | data = self.data | |
512 | self.puti(1, 'function', 'Function 17: Report Server ID') | |
513 | ||
514 | bytecount = data[2].data | |
515 | self.puti(2, 'length', 'Data is {} bytes long'.format(bytecount)) | |
516 | ||
517 | self.puti(3, 'data', 'serverID: {}'.format(data[3].data)) | |
518 | ||
519 | run_indicator_status = data[4].data | |
520 | if run_indicator_status == 0x00: | |
521 | self.puti(4, 'data', 'Run Indicator status: Off') | |
522 | elif run_indicator_status == 0xFF: | |
523 | self.puti(4, 'data', 'Run Indicator status: On') | |
524 | else: | |
525 | self.puti(4, 'error', | |
526 | 'Bad Run Indicator status: 0x{:X}'.format(run_indicator_status)) | |
527 | ||
528 | self.putl('data', 'Device specific data: {}, "{}"'.format(data[-1].data, | |
529 | chr(data[-1].data)), 2 + bytecount) | |
530 | ||
531 | self.check_crc(4 + bytecount) | |
532 | ||
533 | def parse_error(self): | |
534 | '''Parse a Modbus error message.''' | |
535 | self.mimumum_length = 5 | |
536 | # The function code of an error is always 0x80 above the function call | |
537 | # that caused it. | |
538 | functioncode = self.data[1].data - 0x80 | |
539 | ||
540 | functions = { | |
541 | 1: 'Read Coils', | |
542 | 2: 'Read Discrete Inputs', | |
543 | 3: 'Read Holding Registers', | |
544 | 4: 'Read Input Registers', | |
545 | 5: 'Write Single Coil', | |
546 | 6: 'Write Single Register', | |
547 | 7: 'Read Exception Status', | |
548 | 8: 'Diagnostic', | |
549 | 11: 'Get Com Event Counter', | |
550 | 12: 'Get Com Event Log', | |
551 | 15: 'Write Multiple Coils', | |
552 | 16: 'Write Multiple Registers', | |
553 | 17: 'Report Slave ID', | |
554 | 20: 'Read File Record', | |
555 | 21: 'Write File Record', | |
556 | 22: 'Mask Write Register', | |
557 | 23: 'Read/Write Multiple Registers', | |
558 | 24: 'Read FIFO Queue', | |
559 | 43: 'Read Device Identification/Encapsulated Interface Transport', | |
560 | } | |
561 | functionname = '{}: {}'.format(functioncode, | |
562 | functions.get(functioncode, 'Unknown function')) | |
563 | self.puti(1, 'function', | |
564 | 'Error for function {}'.format(functionname)) | |
565 | ||
566 | error = self.data[2].data | |
567 | errorcodes = { | |
568 | 1: 'Illegal Function', | |
569 | 2: 'Illegal Data Address', | |
570 | 3: 'Illegal Data Value', | |
571 | 4: 'Slave Device Failure', | |
572 | 5: 'Acknowledge', | |
573 | 6: 'Slave Device Busy', | |
574 | 8: 'Memory Parity Error', | |
575 | 10: 'Gateway Path Unavailable', | |
576 | 11: 'Gateway Target Device failed to respond', | |
577 | } | |
578 | errorname = '{}: {}'.format(error, errorcodes.get(error, 'Unknown')) | |
579 | self.puti(2, 'data', 'Error {}'.format(errorname)) | |
580 | self.check_crc(4) | |
581 | ||
582 | class Modbus_ADU_CS(Modbus_ADU): | |
583 | '''CS stands for Client -> Server.''' | |
584 | def parse(self): | |
585 | '''Select which specific Modbus function we should parse.''' | |
586 | data = self.data | |
587 | ||
588 | # This try-catch is being used as flow control. | |
589 | try: | |
590 | server_id = data[0].data | |
591 | message = '' | |
592 | if server_id == 0: | |
593 | message = 'Broadcast message' | |
594 | elif 1 <= server_id <= 247: | |
595 | message = 'Slave ID: {}'.format(server_id) | |
596 | elif 248 <= server_id <= 255: | |
597 | message = 'Slave ID: {} (reserved address)'.format(server_id) | |
598 | self.puti(0, 'server-id', message) | |
599 | ||
600 | function = data[1].data | |
601 | if function >= 1 and function <= 4: | |
602 | self.parse_read_data_command() | |
603 | if function == 5: | |
604 | self.parse_write_single_coil() | |
605 | if function == 6: | |
606 | self.parse_write_single_register() | |
607 | if function in {7, 11, 12, 17}: | |
608 | self.parse_single_byte_request() | |
609 | elif function == 8: | |
610 | self.parse_diagnostics() | |
611 | if function in {15, 16}: | |
612 | self.parse_write_multiple() | |
613 | elif function == 22: | |
614 | self.parse_mask_write_register() | |
615 | elif function == 23: | |
616 | self.parse_read_write_registers() | |
617 | elif function in {21, 21, 24, 43}: | |
618 | self.parse_not_implemented() | |
619 | else: | |
620 | self.puti(1, 'error', | |
621 | 'Unknown function: {}'.format(data[1].data)) | |
622 | self.putl('error', 'Unknown function') | |
623 | ||
624 | # If the message gets here without raising an exception, the | |
625 | # message goes on longer than it should. | |
626 | self.putl('error', 'Message too long') | |
627 | ||
628 | except No_more_data: | |
629 | # Just a message saying we don't need to parse anymore this round. | |
630 | pass | |
631 | ||
632 | def parse_read_data_command(self): | |
633 | '''Interpret a command to read x units of data starting at address, ie | |
634 | functions 1, 2, 3 and 4, and write the result to the annotations.''' | |
635 | data = self.data | |
636 | self.minimum_length = 8 | |
637 | ||
638 | function = data[1].data | |
639 | functionname = {1: 'Read Coils', | |
640 | 2: 'Read Discrete Inputs', | |
641 | 3: 'Read Holding Registers', | |
642 | 4: 'Read Input Registers', | |
643 | }[function] | |
644 | ||
645 | self.puti(1, 'function', | |
646 | 'Function {}: {}'.format(function, functionname)) | |
647 | ||
648 | starting_address = self.half_word(2) | |
649 | # Some instruction manuals use a long form name for addresses, this is | |
650 | # listed here for convienience. | |
651 | # Example: holding register 60 becomes 30061. | |
652 | address_name = 10000 * function + 1 + starting_address | |
653 | self.puti(3, 'address', | |
654 | 'Start at address 0x{:X} / {:d}'.format(starting_address, | |
655 | address_name)) | |
656 | ||
657 | self.puti(5, 'length', | |
658 | 'Read {:d} units of data'.format(self.half_word(4))) | |
659 | self.check_crc(7) | |
660 | ||
661 | def parse_single_byte_request(self): | |
662 | '''Some Modbus functions have no arguments, this parses those.''' | |
663 | function = self.data[1].data | |
664 | function_name = {7: 'Read Exception Status', | |
665 | 11: 'Get Comm Event Counter', | |
666 | 12: 'Get Comm Event Log', | |
667 | 17: 'Report Slave ID', | |
668 | }[function] | |
669 | self.puti(1, 'function', | |
670 | 'Function {}: {}'.format(function, function_name)) | |
671 | ||
672 | self.check_crc(3) | |
673 | ||
674 | def parse_write_multiple(self): | |
675 | '''Function 15 and 16 are almost the same, so we can parse them both | |
676 | using one function.''' | |
677 | self.mimumum_length = 9 | |
678 | ||
679 | function = self.data[1].data | |
680 | if function == 15: | |
681 | data_unit = 'Coils' | |
682 | max_outputs = 0x07B0 | |
683 | ratio_bytes_data = 1/8 | |
684 | long_address_offset = 10001 | |
685 | elif function == 16: | |
686 | data_unit = 'Registers' | |
687 | max_outputs = 0x007B | |
688 | ratio_bytes_data = 2 | |
689 | long_address_offset = 30001 | |
690 | ||
691 | self.puti(1, 'function', | |
692 | 'Function {}: Write Multiple {}'.format(function, data_unit)) | |
693 | ||
694 | starting_address = self.half_word(2) | |
695 | # Some instruction manuals use a long form name for addresses, this is | |
696 | # listed here for convienience. | |
697 | address_name = long_address_offset + starting_address | |
698 | self.puti(3, 'address', | |
699 | 'Start at address 0x{:X} / {:d}'.format(starting_address, | |
700 | address_name)) | |
701 | ||
702 | quantity_of_outputs = self.half_word(4) | |
703 | if quantity_of_outputs <= max_outputs: | |
704 | self.puti(5, 'length', | |
705 | 'Write {} {}'.format(quantity_of_outputs, data_unit)) | |
706 | else: | |
707 | self.puti(5, 'error', | |
708 | 'Bad value: {} {}. Max is {}'.format(quantity_of_outputs, | |
709 | data_unit, max_outputs)) | |
710 | proper_bytecount = ceil(quantity_of_outputs * ratio_bytes_data) | |
711 | ||
712 | bytecount = self.data[6].data | |
713 | if bytecount == proper_bytecount: | |
714 | self.puti(6, 'length', 'Byte count: {}'.format(bytecount)) | |
715 | else: | |
716 | self.puti(6, 'error', | |
717 | 'Bad byte count, is {}, should be {}'.format(bytecount, | |
718 | proper_bytecount)) | |
719 | self.mimumum_length = bytecount + 9 | |
720 | ||
721 | self.putl('data', 'Value 0x{:X}', 6 + bytecount) | |
722 | ||
723 | self.check_crc(bytecount + 8) | |
724 | ||
725 | def parse_read_file_record(self): | |
726 | self.puti(1, 'function', 'Function 20: Read file records') | |
727 | ||
728 | data = self.data | |
729 | ||
730 | bytecount = data[2].data | |
731 | ||
732 | self.minimum_length = 5 + bytecount | |
733 | # 1 for serverID, 1 for function, 1 for bytecount, 2 for CRC. | |
734 | ||
735 | if 0x07 <= bytecount <= 0xF5: | |
736 | self.puti(2, 'length', 'Request is {} bytes long'.format(bytecount)) | |
737 | else: | |
738 | self.puti(2, 'error', | |
739 | 'Request claims to be {} bytes long, legal values are between' | |
740 | ' 7 and 247'.format(bytecount)) | |
741 | ||
742 | current_byte = len(data) - 1 | |
743 | # Function 20 is a number of sub-requests, the first starting at 3, | |
744 | # the total length of the sub-requests is bytecount. | |
745 | if current_byte <= bytecount + 2: | |
746 | step = (current_byte - 3) % 7 | |
747 | if step == 0: | |
748 | if data[current_byte].data == 6: | |
749 | self.puti(current_byte, 'data', 'Start sub-request') | |
750 | else: | |
751 | self.puti(current_byte, 'error', | |
752 | 'First byte of subrequest should be 0x06') | |
753 | elif step == 1: | |
754 | raise No_more_data | |
755 | elif step == 2: | |
756 | file_number = self.half_word(current_byte - 1) | |
757 | self.puti(current_byte, 'data', | |
758 | 'Read File number {}'.format(file_number)) | |
759 | elif step == 3: | |
760 | raise No_more_data | |
761 | elif step == 4: | |
762 | record_number = self.half_word(current_byte - 1) | |
763 | self.puti(current_byte, 'address', | |
764 | 'Read from record number {}'.format(record_number)) | |
765 | # TODO: Check if within range. | |
766 | elif step == 5: | |
767 | raise No_more_data | |
768 | elif step == 6: | |
769 | records_to_read = self.half_word(current_byte - 1) | |
770 | self.puti(current_byte, 'length', | |
771 | 'Read {} records'.format(records_to_read)) | |
772 | self.check_crc(4 + bytecount) | |
773 | ||
774 | def parse_read_write_registers(self): | |
775 | '''Parse function 23: Read/Write multiple registers.''' | |
776 | self.minimum_length = 13 | |
777 | ||
778 | self.puti(1, 'function', 'Function 23: Read/Write Multiple Registers') | |
779 | ||
780 | starting_address = self.half_word(2) | |
781 | # Some instruction manuals use a long form name for addresses, this is | |
782 | # listed here for convienience. | |
783 | # Example: holding register 60 becomes 30061. | |
784 | address_name = 30001 + starting_address | |
785 | self.puti(3, 'address', | |
786 | 'Read starting at address 0x{:X} / {:d}'.format(starting_address, | |
787 | address_name)) | |
788 | ||
789 | self.puti(5, 'length', 'Read {:d} units of data'.format(self.half_word(4))) | |
790 | ||
791 | starting_address = self.half_word(6) | |
792 | self.puti(7, 'address', | |
793 | 'Write starting at address 0x{:X} / {:d}'.format(starting_address, | |
794 | address_name)) | |
795 | ||
796 | quantity_of_outputs = self.half_word(8) | |
797 | self.puti(9, 'length', | |
798 | 'Write {} registers'.format(quantity_of_outputs)) | |
799 | proper_bytecount = quantity_of_outputs * 2 | |
800 | ||
801 | bytecount = self.data[10].data | |
802 | if bytecount == proper_bytecount: | |
803 | self.puti(10, 'length', 'Byte count: {}'.format(bytecount)) | |
804 | else: | |
805 | self.puti(10, 'error', | |
806 | 'Bad byte count, is {}, should be {}'.format(bytecount, | |
807 | proper_bytecount)) | |
808 | self.mimumum_length = bytecount + 13 | |
809 | ||
810 | self.putl('data', 'Data, value 0x{:02X}', 10 + bytecount) | |
811 | ||
812 | self.check_crc(bytecount + 12) | |
813 | ||
814 | class Decoder(srd.Decoder): | |
b197383c | 815 | api_version = 3 |
db858a04 BW |
816 | id = 'modbus' |
817 | name = 'Modbus' | |
818 | longname = 'Modbus RTU over RS232/RS485' | |
819 | desc = 'Modbus RTU protocol for industrial applications.' | |
9eac0fe3 | 820 | license = 'gplv3+' |
db858a04 BW |
821 | inputs = ['uart'] |
822 | outputs = ['modbus'] | |
d6d8a8a4 | 823 | tags = ['Embedded/industrial'] |
db858a04 | 824 | annotations = ( |
79f5cbcf UH |
825 | ('sc-server-id', 'SC server ID'), |
826 | ('sc-function', 'SC function'), | |
827 | ('sc-crc', 'SC CRC'), | |
828 | ('sc-address', 'SC address'), | |
829 | ('sc-data', 'SC data'), | |
830 | ('sc-length', 'SC length'), | |
831 | ('sc-error', 'SC error'), | |
832 | ('cs-server-id', 'CS server ID'), | |
833 | ('cs-function', 'CS function'), | |
834 | ('cs-crc', 'CS CRC'), | |
835 | ('cs-address', 'CS address'), | |
836 | ('cs-data', 'CS data'), | |
837 | ('cs-length', 'CS length'), | |
838 | ('cs-error', 'CS error'), | |
839 | ('error-indication', 'Error indication'), | |
db858a04 BW |
840 | ) |
841 | annotation_rows = ( | |
842 | ('sc', 'Server->client', (0, 1, 2, 3, 4, 5, 6)), | |
843 | ('cs', 'Client->server', (7, 8, 9, 10, 11, 12, 13)), | |
e144452b | 844 | ('error-indicators', 'Errors in frame', (14,)), |
db858a04 BW |
845 | ) |
846 | options = ( | |
d4364948 AG |
847 | {'id': 'scchannel', 'desc': 'Server -> client channel', |
848 | 'default': rxtx_channels[0], 'values': rxtx_channels}, | |
849 | {'id': 'cschannel', 'desc': 'Client -> server channel', | |
850 | 'default': rxtx_channels[1], 'values': rxtx_channels}, | |
dcf1b50d | 851 | {'id': 'framegap', 'desc': 'Inter-frame bit gap', 'default': 28}, |
db858a04 BW |
852 | ) |
853 | ||
92b7b49f | 854 | def __init__(self): |
10aeb8ea GS |
855 | self.reset() |
856 | ||
857 | def reset(self): | |
db858a04 BW |
858 | self.ADUSc = None # Start off with empty slave -> client ADU. |
859 | self.ADUCs = None # Start off with empty client -> slave ADU. | |
860 | ||
861 | # The reason we have both (despite not supporting full duplex comms) is | |
862 | # because we want to be able to decode the message as both client -> | |
863 | # server and server -> client, and let the user see which of the two | |
864 | # the ADU was. | |
865 | ||
866 | self.bitlength = None # We will later test how long a bit is. | |
867 | ||
868 | def start(self): | |
869 | self.out_ann = self.register(srd.OUTPUT_ANN) | |
870 | ||
871 | def puta(self, start, end, ann_str, message): | |
872 | '''Put an annotation from start to end, with ann as a | |
873 | string. This means you don't have to know the ann's | |
874 | number to write annotations to it.''' | |
875 | ann = [s[0] for s in self.annotations].index(ann_str) | |
876 | self.put(start, end, self.out_ann, [ann, [message]]) | |
877 | ||
878 | def decode_adu(self, ss, es, data, direction): | |
879 | '''Decode the next byte or bit (depending on type) in the ADU. | |
880 | ss: Start time of the data | |
881 | es: End time of the data | |
882 | data: Data as passed from the UART decoder | |
883 | direction: Is this data for the Cs (client -> server) or Sc (server -> | |
884 | client) being decoded right now?''' | |
885 | ptype, rxtx, pdata = data | |
886 | ||
887 | # We don't have a nice way to get the baud rate from UART, so we have | |
888 | # to figure out how long a bit lasts. We do this by looking at the | |
889 | # length of (probably) the startbit. | |
890 | if self.bitlength is None: | |
891 | if ptype == 'STARTBIT' or ptype == 'STOPBIT': | |
892 | self.bitlength = es - ss | |
893 | else: | |
894 | # If we don't know the bitlength yet, we can't start decoding. | |
895 | return | |
896 | ||
897 | # Select the ADU, create the ADU if needed. | |
898 | # We set ADU.startNewFrame = True when we know the old one is over. | |
899 | if direction == 'Sc': | |
900 | if (self.ADUSc is None) or self.ADUSc.startNewFrame: | |
901 | self.ADUSc = Modbus_ADU_SC(self, ss, TX, 'sc-') | |
902 | ADU = self.ADUSc | |
903 | if direction == 'Cs': | |
904 | if self.ADUCs is None or self.ADUCs.startNewFrame: | |
905 | self.ADUCs = Modbus_ADU_CS(self, ss, TX, 'cs-') | |
906 | ADU = self.ADUCs | |
907 | ||
908 | # We need to determine if the last ADU is over. | |
909 | # According to the Modbus spec, there should be 3.5 characters worth of | |
910 | # space between each message. But if within a message there is a length | |
911 | # of more than 1.5 character, that's an error. For our purposes | |
912 | # somewhere between seems fine. | |
913 | # A character is 11 bits long, so (3.5 + 1.5)/2 * 11 ~= 28 | |
914 | # TODO: Display error for too short or too long. | |
dcf1b50d | 915 | if (ss - ADU.last_read) <= self.bitlength * self.options['framegap']: |
db858a04 BW |
916 | ADU.add_data(ss, es, data) |
917 | else: | |
918 | # It's been too long since the last part of the ADU! | |
919 | # If there is any data in the ADU we need to show it to the user | |
920 | if len(ADU.data) > 0: | |
921 | # Extend errors for 3 bits after last byte, we can guarantee | |
922 | # space. | |
923 | ADU.close(ADU.data[-1].end + self.bitlength * 3) | |
924 | ||
925 | ADU.startNewFrame = True | |
926 | # Restart this function, it will make a new ADU for us. | |
927 | self.decode_adu(ss, es, data, direction) | |
928 | ||
929 | def decode(self, ss, es, data): | |
930 | ptype, rxtx, pdata = data | |
931 | ||
7714a230 UH |
932 | # Ignore unknown/unsupported ptypes. |
933 | if ptype not in ('STARTBIT', 'DATA', 'STOPBIT'): | |
934 | return | |
935 | ||
db858a04 BW |
936 | # Decide what ADU(s) we need this packet to go to. |
937 | # Note that it's possible to go to both ADUs. | |
d4364948 | 938 | if rxtx_channels[rxtx] == self.options['scchannel']: |
db858a04 | 939 | self.decode_adu(ss, es, data, 'Sc') |
d4364948 AG |
940 | if rxtx_channels[rxtx] == self.options['cschannel']: |
941 | self.decode_adu(ss, es, data, 'Cs') |