]> sigrok.org Git - libsigrokdecode.git/blob - decoders/cec/pd.py
Rename values that shadow built-in keywords
[libsigrokdecode.git] / decoders / cec / pd.py
1 ##
2 ## This file is part of the libsigrokdecode project.
3 ##
4 ## Copyright (C) 2018 Jorge Solla Rubiales <jorgesolla@gmail.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 2 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
20 import sigrokdecode as srd
21 from .protocoldata import *
22
23 # Pulse types
24 class Pulse:
25     INVALID, START, ZERO, ONE = range(4)
26
27 # Protocol stats
28 class Stat:
29     WAIT_START, GET_BITS, WAIT_EOM, WAIT_ACK = range(4)
30
31 # Pulse times in milliseconds
32 timing = {
33     Pulse.START: {
34         'low': { 'min': 3.5, 'max': 3.9 },
35         'total': { 'min': 4.3, 'max': 4.7 }
36     },
37     Pulse.ZERO: {
38         'low': { 'min': 1.3, 'max': 1.7 },
39         'total': { 'min': 2.05, 'max': 2.75 }
40     },
41     Pulse.ONE: {
42         'low': { 'min': 0.4, 'max': 0.8 },
43         'total': { 'min': 2.05, 'max': 2.75 }
44     }
45 }
46
47 class ChannelError(Exception):
48     pass
49
50 class Decoder(srd.Decoder):
51     api_version = 3
52     id = 'cec'
53     name = 'CEC'
54     longname = 'HDMI-CEC'
55     desc = 'HDMI Consumer Electronics Control (CEC) protocol.'
56     license = 'gplv2+'
57     inputs = ['logic']
58     outputs = ['cec']
59     channels = (
60         {'id': 'cec', 'name': 'CEC', 'desc': 'CEC bus data'},
61     )
62     annotations = (
63         ('st', 'Start'),
64         ('eom-0', 'End of message'),
65         ('eom-1', 'Message continued'),
66         ('nack', 'ACK not set'),
67         ('ack', 'ACK set'),
68         ('bits', 'Bits'),
69         ('bytes', 'Bytes'),
70         ('frames', 'Frames'),
71         ('sections', 'Sections'),
72         ('warnings', 'Warnings')
73     )
74     annotation_rows = (
75         ('bits', 'Bits', (0, 1, 2, 3, 4, 5)),
76         ('bytes', 'Bytes', (6,)),
77         ('frames', 'Frames', (7,)),
78         ('sections', 'Sections', (8,)),
79         ('warnings', 'Warnings', (9,))
80     )
81
82     def __init__(self):
83         self.reset()
84
85     def precalculate(self):
86         # Restrict max length of ACK/NACK labels to 2 BIT pulses.
87         bit_time = timing[Pulse.ZERO]['total']['min'] * 2
88         self.max_ack_len_samples = round((bit_time / 1000) * self.samplerate)
89
90     def reset(self):
91         self.stat = Stat.WAIT_START
92         self.samplerate = None
93         self.fall_start = None
94         self.fall_end = None
95         self.rise = None
96         self.reset_frame_vars()
97
98     def reset_frame_vars(self):
99         self.eom = None
100         self.bit_count = 0
101         self.byte_count = 0
102         self.byte = 0
103         self.byte_start = None
104         self.frame_start = None
105         self.frame_end = None
106         self.is_nack = 0
107         self.cmd_bytes = []
108
109     def metadata(self, key, value):
110         if key == srd.SRD_CONF_SAMPLERATE:
111             self.samplerate = value
112             self.precalculate()
113
114     def handle_frame(self, is_nack):
115         if self.fall_start is None or self.fall_end is None:
116             return
117
118         i = 0
119         string = ''
120         while i < len(self.cmd_bytes):
121             string += '{:02x}'.format(self.cmd_bytes[i]['val'])
122             if i != (len(self.cmd_bytes) - 1):
123                 string += ':'
124             i += 1
125
126         self.put(self.frame_start, self.frame_end, self.out_ann, [7, [string]])
127
128         i = 0
129         operands = 0
130         string = ''
131         while i < len(self.cmd_bytes):
132             if i == 0: # Parse header
133                 (src, dst) = decode_header(self.cmd_bytes[i]['val'])
134                 string = 'HDR: ' + src + ', ' + dst
135             elif i == 1: # Parse opcode
136                 string += ' | OPC: ' + opcodes.get(self.cmd_bytes[i]['val'], 'Invalid')
137             else: # Parse operands
138                 if operands == 0:
139                     string += ' | OPS: '
140                 operands += 1
141                 string += '0x{:02x}'.format(self.cmd_bytes[i]['val'])
142                 if i != len(self.cmd_bytes) - 1:
143                     string += ', '
144             i += 1
145
146         # Header only commands are PINGS
147         if i == 1:
148             string += ' | OPC: PING' if self.eom else ' | OPC: NONE. Aborted cmd'
149
150         # Add extra information (ack of the command from the destination)
151         string += ' | R: NACK' if is_nack else ' | R: ACK'
152
153         self.put(self.frame_start, self.frame_end, self.out_ann, [8, [string]])
154
155     def process(self):
156         zero_time = ((self.rise - self.fall_start) / self.samplerate) * 1000.0
157         total_time = ((self.fall_end - self.fall_start) / self.samplerate) * 1000.0
158         pulse = Pulse.INVALID
159
160         # VALIDATION: Identify pulse based on length of the low period
161         for key in timing:
162             if zero_time >= timing[key]['low']['min'] and zero_time <= timing[key]['low']['max']:
163                 pulse = key
164                 break
165
166         # VALIDATION: Invalid pulse
167         if pulse == Pulse.INVALID:
168             self.stat = Stat.WAIT_START
169             self.put(self.fall_start, self.fall_end, self.out_ann, [9, ['Invalid pulse: Wrong timing']])
170             return
171
172         # VALIDATION: If waiting for start, discard everything else
173         if self.stat == Stat.WAIT_START and pulse != Pulse.START:
174             self.put(self.fall_start, self.fall_end, self.out_ann, [9, ['Expected START: BIT found']])
175             return
176
177         # VALIDATION: If waiting for ACK or EOM, only BIT pulses (0/1) are expected
178         if (self.stat == Stat.WAIT_ACK or self.stat == Stat.WAIT_EOM) and pulse == Pulse.START:
179             self.put(self.fall_start, self.fall_end, self.out_ann, [9, ['Expected BIT: START received)']])
180             self.stat = Stat.WAIT_START
181
182         # VALIDATION: ACK bit pulse remains high till the next frame (if any): Validate only min time of the low period
183         if self.stat == Stat.WAIT_ACK and pulse != Pulse.START:
184             if total_time < timing[pulse]['total']['min']:
185                 pulse = Pulse.INVALID
186                 self.put(self.fall_start, self.fall_end, self.out_ann, [9, ['ACK pulse below minimun time']])
187                 self.stat = Stat.WAIT_START
188                 return
189
190         # VALIDATION / PING FRAME DETECTION: Initiator doesn't sets the EOM = 1 but stops sending when ack doesn't arrive
191         if self.stat == Stat.GET_BITS and pulse == Pulse.START:
192             # Make sure we received a complete byte to consider it a valid ping
193             if self.bit_count == 0:
194                 self.handle_frame(self.is_nack)
195             else:
196                 self.put(self.frame_start, self.samplenum, self.out_ann, [9, ['ERROR: Incomplete byte received']])
197
198             # Set wait start so we receive next frame
199             self.stat = Stat.WAIT_START
200
201         # VALIDATION: Check timing of the BIT (0/1) pulse in any other case (not waiting for ACK)
202         if self.stat != Stat.WAIT_ACK and pulse != Pulse.START:
203             if total_time < timing[pulse]['total']['min'] or total_time > timing[pulse]['total']['max']:
204                 self.put(self.fall_start, self.fall_end, self.out_ann, [9, ['Bit pulse exceeds total pulse timespan']])
205                 pulse = Pulse.INVALID
206                 self.stat = Stat.WAIT_START
207                 return
208
209         if pulse == Pulse.ZERO:
210             bit = 0
211         elif pulse == Pulse.ONE:
212             bit = 1
213
214         # STATE: WAIT START
215         if self.stat == Stat.WAIT_START:
216             self.stat = Stat.GET_BITS
217             self.reset_frame_vars()
218             self.put(self.fall_start, self.fall_end, self.out_ann, [0, ['ST']])
219
220         # STATE: GET BITS
221         elif self.stat == Stat.GET_BITS:
222             # Reset stats on first bit
223             if self.bit_count == 0:
224                 self.byte_start = self.fall_start
225                 self.byte = 0
226
227                 # If 1st byte of the datagram save its sample num
228                 if len(self.cmd_bytes) == 0:
229                     self.frame_start = self.fall_start
230
231             self.byte += (bit << (7 - self.bit_count))
232             self.bit_count += 1
233             self.put(self.fall_start, self.fall_end, self.out_ann, [5, [str(bit)]])
234
235             if self.bit_count == 8:
236                 self.bit_count = 0
237                 self.byte_count += 1
238                 self.stat = Stat.WAIT_EOM
239                 self.put(self.byte_start, self.samplenum, self.out_ann, [6, ['0x{:02x}'.format(self.byte)]])
240                 self.cmd_bytes.append({'st': self.byte_start, 'ed': self.samplenum, 'val': self.byte})
241
242         # STATE: WAIT EOM
243         elif self.stat == Stat.WAIT_EOM:
244             self.eom = bit
245             self.frame_end = self.fall_end
246
247             a = [2, ['EOM=Y']] if self.eom else [1, ['EOM=N']]
248             self.put(self.fall_start, self.fall_end, self.out_ann, a)
249
250             self.stat = Stat.WAIT_ACK
251
252         # STATE: WAIT ACK
253         elif self.stat == Stat.WAIT_ACK:
254             # If a frame with broadcast destination is being sent, the ACK is
255             # inverted: a 0 is considered a NACK, therefore we invert the value
256             # of the bit here, so we match the real meaning of it.
257             if (self.cmd_bytes[0]['val'] & 0x0F) == 0x0F:
258                 bit = ~bit & 0x01
259
260             if (self.fall_end - self.fall_start) > self.max_ack_len_samples:
261                 ann_end = self.fall_start + self.max_ack_len_samples
262             else:
263                 ann_end = self.fall_end
264
265             if bit:
266                 # Any NACK detected in the frame is enough to consider the
267                 # whole frame NACK'd.
268                 self.is_nack = 1
269                 self.put(self.fall_start, ann_end, self.out_ann, [3, ['NACK']])
270             else:
271                 self.put(self.fall_start, ann_end, self.out_ann, [4, ['ACK']])
272
273             # After ACK bit, wait for new datagram or continue reading current
274             # one based on EOM value.
275             if self.eom or self.is_nack:
276                 self.stat = Stat.WAIT_START
277                 self.handle_frame(self.is_nack)
278             else:
279                 self.stat = Stat.GET_BITS
280
281     def start(self):
282         self.out_ann = self.register(srd.OUTPUT_ANN)
283
284     def decode(self):
285         if not self.samplerate:
286             raise SamplerateError('Cannot decode without samplerate.')
287
288         # Wait for first falling edge.
289         self.wait({0: 'f'})
290         self.fall_end = self.samplenum
291
292         while True:
293             self.wait({0: 'r'})
294             self.rise = self.samplenum
295
296             if self.stat == Stat.WAIT_ACK:
297                 self.wait([{0: 'f'}, {'skip': self.max_ack_len_samples}])
298             else:
299                 self.wait([{0: 'f'}])
300
301             self.fall_start = self.fall_end
302             self.fall_end = self.samplenum
303             self.process()
304
305             # If there was a timeout while waiting for ACK: RESYNC.
306             # Note: This is an expected situation as no new falling edge will
307             # happen until next frame is transmitted.
308             if self.matched == (False, True):
309                 self.wait({0: 'f'})
310                 self.fall_end = self.samplenum