]> sigrok.org Git - libsigrokdecode.git/blob - decoders/dmx512/pd.py
avr_isp: Add more parts
[libsigrokdecode.git] / decoders / dmx512 / pd.py
1 ##
2 ## This file is part of the libsigrokdecode project.
3 ##
4 ## Copyright (C) 2016 Fabian J. Stumpf <sigrok@fabianstumpf.de>
5 ## Copyright (C) 2019-2020 Gerhard Sittig <gerhard.sittig@gmx.net>
6 ##
7 ## This program is free software; you can redistribute it and/or modify
8 ## it under the terms of the GNU General Public License as published by
9 ## the Free Software Foundation; either version 2 of the License, or
10 ## (at your option) any later version.
11 ##
12 ## This program is distributed in the hope that it will be useful,
13 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
14 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 ## GNU General Public License for more details.
16 ##
17 ## You should have received a copy of the GNU General Public License
18 ## along with this program; if not, see <http://www.gnu.org/licenses/>.
19 ##
20
21 '''
22 OUTPUT_PYTHON format:
23
24 Packet:
25 [<ptype>, <pdata>]
26
27 This is the list of <ptype> codes and their respective <pdata> values:
28  - 'PACKET': The data is a list of tuples with the bytes' start and end
29    positions as well as a byte value and a validity flag. This output
30    represents a DMX packet. The sample numbers span the range beginning
31    at the start of the start code and ending at the end of the last data
32    byte in the packet. The start code value resides at index 0.
33
34 Developer notes on the DMX512 protocol:
35
36 See Wikipedia for an overview:
37   https://en.wikipedia.org/wiki/DMX512#Electrical (physics, transport)
38   https://en.wikipedia.org/wiki/DMX512#Protocol (UART frames, DMX frames)
39   RS-485 transport, differential thus either polarity (needs user spec)
40   8n2 UART frames at 250kbps, BREAK to start a new DMX frame
41   slot 0 carries start code, slot 1 up to 512 max carry data for peripherals
42   start code 0 for "boring lights", non-zero start code for extensions.
43
44 TODO
45 - Cover more DMX packet types beyond start code 0x00 (standard). See
46   https://en.wikipedia.org/wiki/DMX512#Protocol for a list (0x17 text,
47   0xcc RDM, 0xcf sysinfo) and a reference to the ESTA database. These
48   can either get added here or can get implemented in a stacked decoder.
49 - Run on more captures as these become available. Verify the min/max
50   BREAK, MARK, and RESET to RESET period checks. Add more conditions that
51   are worth checking to determine the health of the bus, see the (German)
52   http://www.soundlight.de/techtips/dmx512/dmx2000a.htm article for ideas.
53 - Is there a more user friendly way of having the DMX512 decoder configure
54   the UART decoder's parameters? Currently users need to setup the polarity
55   (which is acceptable, and an essential feature), but also the bitrate and
56   frame format (which may or may not be considered acceptable).
57 - (Not a DMX512 decoder TODO item) Current UART decoder implementation does
58   not handle two STOP bits, but DMX512 will transparently benefit when UART
59   gets adjusted. Until then the second STOP bit will be mistaken for a MARK
60   but that's just cosmetics, available data gets interpreted correctly.
61 '''
62
63 import sigrokdecode as srd
64
65 class Ann:
66     BREAK, MAB, INTERFRAME, INTERPACKET, STARTCODE, DATABYTE, CHANNEL_DATA, \
67     SLOT_DATA, RESET, WARN, ERROR = range(11)
68
69 class Decoder(srd.Decoder):
70     api_version = 3
71     id = 'dmx512'
72     name = 'DMX512'
73     longname = 'Digital MultipleX 512'
74     desc = 'Digital MultipleX 512 (DMX512) lighting protocol.'
75     license = 'gplv2+'
76     inputs = ['uart']
77     outputs = ['dmx512']
78     tags = ['Embedded/industrial', 'Lighting']
79     options = (
80         {'id': 'min_break', 'desc': 'Minimum BREAK length (us)', 'default': 88},
81         {'id': 'max_mark', 'desc': 'Maximum MARK length (us)', 'default': 1000000},
82         {'id': 'min_break_break', 'desc': 'Minimum BREAK to BREAK interval (us)',
83             'default': 1196},
84         {'id': 'max_reset_reset', 'desc': 'Maximum RESET to RESET interval (us)',
85          'default': 1250000},
86         {'id': 'show_zero', 'desc': 'Display all-zero set-point values',
87             'default': 'no', 'values': ('yes', 'no')},
88         {'id': 'format', 'desc': 'Data format', 'default': 'dec',
89             'values': ('dec', 'hex', 'bin')},
90     )
91     annotations = (
92         # Lowest layer (above UART): BREAK MARK ( FRAME [MARK] )*
93         # with MARK being after-break or inter-frame or inter-packet.
94         ('break', 'Break'),
95         ('mab', 'Mark after break'),
96         ('interframe', 'Interframe'),
97         ('interpacket', 'Interpacket'),
98         # Next layer: STARTCODE ( DATABYTE )*
99         ('startcode', 'Start code'),
100         ('databyte', 'Data byte'),
101         # Next layer: CHANNEL or SLOT values
102         ('chan_data', 'Channel data'),
103         ('slot_data', 'Slot data'),
104         # Next layer: RESET
105         ('reset', 'Reset sequence'),
106         # Warnings and errors.
107         ('warning', 'Warning'),
108         ('error', 'Error'),
109     )
110     annotation_rows = (
111         ('dmx_fields', 'Fields', (Ann.BREAK, Ann.MAB,
112             Ann.STARTCODE, Ann.INTERFRAME,
113             Ann.DATABYTE, Ann.INTERPACKET)),
114         ('chans_data', 'Channels data', (Ann.CHANNEL_DATA,)),
115         ('slots_data', 'Slots data', (Ann.SLOT_DATA,)),
116         ('resets', 'Reset sequences', (Ann.RESET,)),
117         ('warnings', 'Warnings', (Ann.WARN,)),
118         ('errors', 'Errors', (Ann.ERROR,)),
119     )
120
121     def __init__(self):
122         self.reset()
123
124     def reset(self):
125         self.samplerate = None
126         self.samples_per_usec = None
127         self.last_reset = None
128         self.last_break = None
129         self.packet = None
130         self.last_es = None
131         self.last_frame = None
132         self.start_code = None
133
134     def start(self):
135         self.out_ann = self.register(srd.OUTPUT_ANN)
136         self.out_python = self.register(srd.OUTPUT_PYTHON)
137
138     def metadata(self, key, value):
139         if key == srd.SRD_CONF_SAMPLERATE:
140             self.samplerate = value
141             self.samples_per_usec = value / 1000000
142
143     def have_samplerate(self):
144         return bool(self.samplerate)
145
146     def samples_to_usecs(self, count):
147         return count / self.samples_per_usec
148
149     def putg(self, ss, es, data):
150         self.put(ss, es, self.out_ann, data)
151
152     def putpy(self, ss, es, data):
153         self.put(ss, es, self.out_python, data)
154
155     def format_value(self, v):
156         fmt = self.options['format']
157         if fmt == 'dec':
158             return '{:d}'.format(v)
159         if fmt == 'hex':
160             return '{:02X}'.format(v)
161         if fmt == 'bin':
162             return '{:08b}'.format(v)
163         return '{}'.format(v)
164
165     def flush_packet(self):
166         if self.packet:
167             ss, es = self.packet[0][0], self.packet[-1][1]
168             self.putpy(ss, es, ['PACKET', self.packet])
169         self.packet = None
170
171     def flush_reset(self, ss, es):
172         if ss is not None and es is not None:
173             self.putg(ss, es, [Ann.RESET, ['RESET SEQUENCE', 'RESET', 'R']])
174             if self.last_reset and self.have_samplerate():
175                 duration = self.samples_to_usecs(es - self.last_reset)
176                 if duration > self.options['max_reset_reset']:
177                     txts = ['Excessive RESET to RESET interval', 'RESET to RESET', 'RESET']
178                     self.putg(self.last_reset, es, [Ann.WARN, txts])
179         self.last_reset = es
180
181     def flush_break(self, ss, es):
182         self.putg(ss, es, [Ann.BREAK, ['BREAK', 'B']])
183         if self.have_samplerate():
184             duration = self.samples_to_usecs(es - ss)
185             if duration < self.options['min_break']:
186                 txts = ['Short BREAK period', 'Short BREAK', 'BREAK']
187                 self.putg(ss, es, [Ann.WARN, txts])
188             if self.last_break:
189                 duration = self.samples_to_usecs(ss - self.last_break)
190                 if duration < self.options['min_break_break']:
191                     txts = ['Short BREAK to BREAK interval', 'Short BREAK to BREAK', 'BREAK']
192                     self.putg(ss, es, [Ann.WARN, txts])
193         self.last_break = ss
194         self.last_es = es
195
196     def flush_mark(self, ss, es, is_mab = False, is_if = False, is_ip = False):
197         '''Handle several kinds of MARK conditions.'''
198
199         if ss is None or es is None or ss >= es:
200             return
201
202         if is_mab:
203             ann = Ann.MAB
204             txts = ['MARK AFTER BREAK', 'MAB']
205         elif is_if:
206             ann = Ann.INTERFRAME
207             txts = ['INTER FRAME', 'IF']
208         elif is_ip:
209             ann = Ann.INTERPACKET
210             txts = ['INTER PACKET', 'IP']
211         else:
212             return
213         self.putg(ss, es, [ann, txts])
214
215         if self.have_samplerate():
216             duration = self.samples_to_usecs(es - ss)
217             if duration > self.options['max_mark']:
218                 txts = ['Excessive MARK length', 'MARK length', 'MARK']
219                 self.putg(ss, es, [Ann.ERROR, txts])
220
221     def flush_frame(self, ss, es, value, valid):
222         '''Handle UART frame content. Accumulate DMX packet.'''
223
224         if not valid:
225             txts = ['Invalid frame', 'Frame']
226             self.putg(ss, es, [Ann.ERROR, txts])
227
228         self.last_es = es
229
230         # Cease packet inspection before first BREAK.
231         if not self.last_break:
232             return
233
234         # Accumulate the sequence of bytes for the current DMX frame.
235         # Emit the annotation at the "DMX fields" level.
236         is_start = self.packet is None
237         if is_start:
238             self.packet = []
239         slot_nr = len(self.packet)
240         item = (ss, es, value, valid)
241         self.packet.append(item)
242         if is_start:
243             # Slot 0, the start code. Determines the DMX frame type.
244             self.start_code = value
245             ann = Ann.STARTCODE
246             val_text = self.format_value(value)
247             txts = [
248                 'STARTCODE {}'.format(val_text),
249                 'START {}'.format(val_text),
250                 '{}'.format(val_text),
251             ]
252         else:
253             # Slot 1+, the payload bytes.
254             ann = Ann.DATABYTE
255             val_text = self.format_value(value)
256             txts = [
257                 'DATABYTE {:d}: {}'.format(slot_nr, val_text),
258                 'DATA {:d}: {}'.format(slot_nr, val_text),
259                 'DATA {}'.format(val_text),
260                 '{}'.format(val_text),
261             ]
262         self.putg(ss, es, [ann, txts])
263
264         # Tell channel data for peripherals from arbitrary slot values.
265         # Can get extended for other start code types in case protocol
266         # extensions are handled here and not in stacked decoders.
267         if is_start:
268             ann = None
269         elif self.start_code == 0:
270             # Start code was 0. Slots carry values for channels.
271             # Optionally suppress zero-values to make used channels
272             # stand out, to help users focus their attention.
273             ann = Ann.CHANNEL_DATA
274             if value == 0 and self.options['show_zero'] == 'no':
275                 ann = None
276             else:
277                 val_text = self.format_value(value)
278                 txts = [
279                     'CHANNEL {:d}: {}'.format(slot_nr, val_text),
280                     'CH {:d}: {}'.format(slot_nr, val_text),
281                     'CH {}'.format(val_text),
282                     '{}'.format(val_text),
283                 ]
284         else:
285             # Unhandled start code. Provide "anonymous" values.
286             ann = Ann.SLOT_DATA
287             val_text = self.format_value(value)
288             txts = [
289                 'SLOT {:d}: {}'.format(slot_nr, val_text),
290                 'SL {:d}: {}'.format(slot_nr, val_text),
291                 'SL {}'.format(val_text),
292                 '{}'.format(val_text),
293             ]
294         if ann is not None:
295             self.putg(ss, es, [ann, txts])
296
297         if is_start and value == 0:
298             self.flush_reset(self.last_break, es)
299
300     def handle_break(self, ss, es):
301         '''Handle UART BREAK conditions.'''
302
303         # Check the last frame before BREAK if one was queued. It could
304         # have been "invalid" since the STOP bit check failed. If there
305         # is an invalid frame which happens to start at the start of the
306         # BREAK condition, then discard it. Otherwise flush its output.
307         last_frame = self.last_frame
308         self.last_frame = None
309         frame_invalid = last_frame and not last_frame[3]
310         frame_zero_data = last_frame and last_frame[2] == 0
311         frame_is_break = last_frame and last_frame[0] == ss
312         if frame_invalid and frame_zero_data and frame_is_break:
313             last_frame = None
314         if last_frame is not None:
315             self.flush_frame(*last_frame)
316
317         # Handle inter-packet MARK (works for zero length, too).
318         self.flush_mark(self.last_es, ss, is_ip = True)
319
320         # Handle accumulated packets.
321         self.flush_packet()
322         self.packet = None
323
324         # Annotate the BREAK condition. Start accumulation of a packet.
325         self.flush_break(ss, es)
326
327     def handle_frame(self, ss, es, value, valid):
328         '''Handle UART data frames.'''
329
330         # Flush previously deferred frame (if available). Can't have been
331         # BREAK if another data frame follows.
332         last_frame = self.last_frame
333         self.last_frame = None
334         if last_frame:
335             self.flush_frame(*last_frame)
336
337         # Handle inter-frame MARK (works for zero length, too).
338         is_mab = self.last_break and self.packet is None
339         is_if = self.packet
340         self.flush_mark(self.last_es, ss, is_mab = is_mab, is_if = is_if)
341
342         # Defer handling of invalid frames, because they may start a new
343         # BREAK which we will only learn about much later. Immediately
344         # annotate valid frames.
345         if valid:
346             self.flush_frame(ss, es, value, valid)
347         else:
348             self.last_frame = (ss, es, value, valid)
349
350     def decode(self, ss, es, data):
351         # Lack of a sample rate in the input capture only disables the
352         # optional warnings about exceeded timespans here at the DMX512
353         # decoder level. That the lower layer UART decoder depends on a
354         # sample rate is handled there, and is not relevant here.
355
356         ptype, rxtx, pdata = data
357         if ptype == 'BREAK':
358             self.handle_break(ss, es)
359         elif ptype == 'FRAME':
360             value, valid = pdata
361             self.handle_frame(ss, es, value, valid)