]> sigrok.org Git - libsigrokdecode.git/blame - decoders/dmx512/pd.py
avr_isp: Add more parts
[libsigrokdecode.git] / decoders / dmx512 / pd.py
CommitLineData
dca19fbf
GS
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'''
22OUTPUT_PYTHON format:
23
24Packet:
25[<ptype>, <pdata>]
26
27This 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
34Developer notes on the DMX512 protocol:
35
36See 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
44TODO
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
63import sigrokdecode as srd
64
65class Ann:
66 BREAK, MAB, INTERFRAME, INTERPACKET, STARTCODE, DATABYTE, CHANNEL_DATA, \
67 SLOT_DATA, RESET, WARN, ERROR = range(11)
68
69class 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)