]> sigrok.org Git - libsigrokdecode.git/blame - decoders/modbus/pd.py
modbus: Add missing annotation class names.
[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
d4364948 25rxtx_channels = ('RX', 'TX')
db858a04
BW
26
27class 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
32class 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
39class 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
300class 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
582class 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
814class 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')