]>
Commit | Line | Data |
---|---|---|
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 | ''' | |
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) |