]> sigrok.org Git - libsigrokdecode.git/blame - decoders/modbus/pd.py
all decoders: introduce a reset() method
[libsigrokdecode.git] / decoders / modbus / pd.py
CommitLineData
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
20import sigrokdecode as srd
21from math import ceil
22
23RX = 0
24TX = 1
25
26class 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
31class 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
38class 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
299class 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
581class 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
813class 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')