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