]> sigrok.org Git - libsigrokdecode.git/blob - decoders/lin/pd.py
lin: calculate checksum on two consecutive UART idle frames
[libsigrokdecode.git] / decoders / lin / pd.py
1 ##
2 ## This file is part of the libsigrokdecode project.
3 ##
4 ## Copyright (C) 2018 Stephan Thiele <stephan.thiele@mailbox.org>
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
22 class LinFsm:
23     class State:
24         WaitForBreak = 'WAIT_FOR_BREAK'
25         Sync = 'SYNC'
26         Pid = 'PID'
27         Data = 'DATA'
28         Checksum = 'CHECKSUM'
29         Error = 'ERROR'
30
31     def transit(self, target_state):
32         if not self._transition_allowed(target_state):
33             return False
34         self.state = target_state
35         return True
36
37     def _transition_allowed(self, target_state):
38         if target_state == LinFsm.State.Error:
39             return True
40         return target_state in self.allowed_state[self.state]
41
42     def reset(self):
43         self.state = LinFsm.State.WaitForBreak
44         self.uart_idle_count = 0
45
46     def __init__(self):
47         a = dict()
48         a[LinFsm.State.WaitForBreak] = (LinFsm.State.Sync,)
49         a[LinFsm.State.Sync]         = (LinFsm.State.Pid,)
50         a[LinFsm.State.Pid]          = (LinFsm.State.Data,)
51         a[LinFsm.State.Data]         = (LinFsm.State.Data, LinFsm.State.Checksum)
52         a[LinFsm.State.Checksum]     = (LinFsm.State.WaitForBreak,)
53         a[LinFsm.State.Error]        = (LinFsm.State.Sync,)
54         self.allowed_state = a
55
56         self.state = None
57         self.uart_idle_count = 0
58         self.reset()
59
60 class Decoder(srd.Decoder):
61     api_version = 3
62     id = 'lin'
63     name = 'LIN'
64     longname = 'Local Interconnect Network'
65     desc = 'Local Interconnect Network (LIN) protocol.'
66     license = 'gplv2+'
67     inputs = ['uart']
68     outputs = []
69     tags = ['Automotive']
70     options = (
71         {'id': 'version', 'desc': 'Protocol version', 'default': 2, 'values': (1, 2)},
72     )
73     annotations = (
74         ('data', 'LIN data'),
75         ('control', 'Protocol info'),
76         ('error', 'Error descriptions'),
77         ('inline_error', 'Protocol violations and errors'),
78     )
79     annotation_rows = (
80         ('data', 'Data', (0, 1, 3)),
81         ('error', 'Error', (2,)),
82     )
83
84     def __init__(self):
85         self.reset()
86
87     def reset(self):
88         self.fsm = LinFsm()
89         self.lin_header = []
90         self.lin_rsp = []
91         self.lin_version = None
92         self.out_ann = None
93         self.ss_block = None
94         self.es_block = None
95
96     def start(self):
97         self.out_ann = self.register(srd.OUTPUT_ANN)
98         self.lin_version = self.options['version']
99
100     def putx(self, data):
101         self.put(self.ss_block, self.es_block, self.out_ann, data)
102
103     def wipe_break_null_byte(self, value):
104         # Upon a break condition a null byte is received which must be ignored.
105         if self.fsm.state not in (LinFsm.State.WaitForBreak, LinFsm.State.Error):
106             if len(self.lin_rsp):
107                 value = self.lin_rsp.pop()[2]
108             else:
109                 self.lin_header.pop()
110
111         if value != 0:
112             self.fsm.transit(LinFsm.State.Error)
113             self.handle_error(None)
114             return False
115
116         return True
117
118     def handle_uart_idle(self):
119         if self.fsm.state not in (LinFsm.State.WaitForBreak, LinFsm.State.Error):
120             self.fsm.uart_idle_count += 1
121
122             if self.fsm.uart_idle_count == 2:
123                 self.fsm.transit(LinFsm.State.Checksum)
124                 self.handle_checksum()
125                 self.fsm.reset()
126
127     def handle_wait_for_break(self, value):
128         self.wipe_break_null_byte(value)
129
130     def handle_break(self, value):
131         if self.fsm.state not in (LinFsm.State.WaitForBreak, LinFsm.State.Error):
132             if self.wipe_break_null_byte(value):
133                 self.fsm.transit(LinFsm.State.Checksum)
134                 self.handle_checksum()
135
136         self.fsm.reset()
137         self.fsm.transit(LinFsm.State.Sync)
138
139         self.putx([1, ['Break condition', 'Break', 'Brk', 'B']])
140
141     def handle_sync(self, value):
142         self.fsm.transit(LinFsm.State.Pid)
143         self.lin_header.append((self.ss_block, self.es_block, value))
144
145     def handle_pid(self, value):
146         self.fsm.transit(LinFsm.State.Data)
147         self.lin_header.append((self.ss_block, self.es_block, value))
148
149     def handle_data(self, value):
150         self.lin_rsp.append((self.ss_block, self.es_block, value))
151
152     def handle_checksum(self):
153         sync = self.lin_header.pop(0) if len(self.lin_header) else None
154
155         self.put(sync[0], sync[1], self.out_ann, [0, ['Sync', 'S']])
156
157         if sync[2] != 0x55:
158             self.put(sync[0], sync[1], self.out_ann,
159                      [2, ['Sync is not 0x55', 'Not 0x55', '!= 0x55']])
160
161         pid = self.lin_header.pop(0) if len(self.lin_header) else None
162         checksum = self.lin_rsp.pop() if len(self.lin_rsp) else None
163
164         if pid:
165             id_ = pid[2] & 0x3F
166             parity = pid[2] >> 6
167
168             expected_parity = self.calc_parity(pid[2])
169             parity_valid = parity == expected_parity
170
171             if not parity_valid:
172                 self.put(pid[0], pid[1], self.out_ann, [2, ['P != %d' % expected_parity]])
173
174             ann_class = 0 if parity_valid else 3
175             self.put(pid[0], pid[1], self.out_ann, [ann_class, [
176                 'ID: %02X Parity: %d (%s)' % (id_, parity, 'ok' if parity_valid else 'bad'),
177                 'ID: 0x%02X' % id_, 'I: %d' % id_
178             ]])
179
180         if len(self.lin_rsp):
181             checksum_valid = self.checksum_is_valid(pid[2], self.lin_rsp, checksum[2])
182
183             for b in self.lin_rsp:
184                 self.put(b[0], b[1], self.out_ann, [0, ['Data: 0x%02X' % b[2], 'D: 0x%02X' % b[2]]])
185
186             ann_class = 0 if checksum_valid else 3
187             self.put(checksum[0], checksum[1], self.out_ann,
188                  [ann_class, ['Checksum: 0x%02X' % checksum[2], 'Checksum', 'Chk', 'C']])
189
190             if not checksum_valid:
191                 self.put(checksum[0], checksum[1], self.out_ann, [2, ['Checksum invalid']])
192         else:
193             pass # No response.
194
195         self.lin_header.clear()
196         self.lin_rsp.clear()
197
198     def handle_error(self, dummy):
199         self.putx([3, ['Error', 'Err', 'E']])
200
201     def checksum_is_valid(self, pid, data, checksum):
202         if self.lin_version == 2:
203             id_ = pid & 0x3F
204
205             if id_ != 60 and id_ != 61:
206                 checksum += pid
207
208         for d in data:
209             checksum += d[2]
210
211         carry_bits = int(checksum / 256)
212         checksum += carry_bits
213
214         return checksum & 0xFF == 0xFF
215
216     @staticmethod
217     def calc_parity(pid):
218         id_ = [((pid & 0x3F) >> i) & 1 for i in range(8)]
219
220         p0 = id_[0] ^ id_[1] ^ id_[2] ^ id_[4]
221         p1 = not (id_[1] ^ id_[3] ^ id_[4] ^ id_[5])
222
223         return (p0 << 0) | (p1 << 1)
224
225     def decode(self, ss, es, data):
226         ptype, rxtx, pdata = data
227
228         self.ss_block, self.es_block = ss, es
229
230         # Ignore all UART packets except the actual data packets or BREAK.
231         if ptype == 'IDLE':
232             self.handle_uart_idle()
233         if ptype == 'BREAK':
234             self.handle_break(pdata)
235         if ptype != 'DATA':
236             return
237
238         # We're only interested in the byte value (not individual bits).
239         pdata = pdata[0]
240
241         # Short LIN overview:
242         #  - Message begins with a BREAK (0x00) for at least 13 bittimes.
243         #  - Break is always followed by a SYNC byte (0x55).
244         #  - Sync byte is followed by a PID byte (Protected Identifier).
245         #  - PID byte is followed by 1 - 8 data bytes and a final checksum byte.
246
247         handler = getattr(self, 'handle_%s' % self.fsm.state.lower())
248         handler(pdata)