47a641c18e63015d4133d13d5f55c526bd091fc4
[libsigrokdecode.git] / decoders / morse / pd.py
1 ##
2 ## This file is part of the libsigrokdecode project.
3 ##
4 ## Copyright (C) 2017 Christoph Rackwitz <christoph.rackwitz@rwth-aachen.de>
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 def decode_ditdah(s):
23     return tuple({'-': 3, '.': 1}[c] for c in s)
24
25 def encode_ditdah(tpl):
26     return ''.join({1: '.', 3: '-'}[c] for c in tpl)
27
28 # https://www.itu.int/dms_pubrec/itu-r/rec/m/R-REC-M.1677-1-200910-I!!PDF-E.pdf
29 # Recommendation ITU-R M.1677-1
30 # (10/2009)
31 # International Morse code
32 alphabet = {
33     # 1.1.1 Letters
34     '.-':       'a',
35     '-...':     'b',
36     '-.-.':     'c',
37     '-..':      'd',
38     '.':        'e',
39     '..-..':    'é', # "accented"
40     '..-.':     'f',
41     '--.':      'g',
42     '....':     'h',
43     '..':       'i',
44     '.---':     'j',
45     '-.-':      'k',
46     '.-..':     'l',
47     '--':       'm',
48     '-.':       'n',
49     '---':      'o',
50     '.--.':     'p',
51     '--.-':     'q',
52     '.-.':      'r',
53     '...':      's',
54     '-':        't',
55     '..-':      'u',
56     '...-':     'v',
57     '.--':      'w',
58     '-..-':     'x',
59     '-.--':     'y',
60     '--..':     'z',
61
62     # 1.1.2 Figures
63     '.----':    '1',
64     '..---':    '2',
65     '...--':    '3',
66     '....-':    '4',
67     '.....':    '5',
68     '-....':    '6',
69     '--...':    '7',
70     '---..':    '8',
71     '----.':    '9',
72     '-----':    '0',
73
74     # 1.1.3 Punctuation marks and miscellaneous signs
75     '.-.-.-':   '.',          # Full stop (period)
76     '--..--':   ',',          # Comma
77     '---...':   ':',          # Colon or division sign
78     '..--..':   '?',          # Question mark (note of interrogation or request for repetition of a transmission not understood)
79     '.----.':   '’',          # Apostrophe
80     '-....-':   '-',          # Hyphen or dash or subtraction sign
81     '-..-.':    '/',          # Fraction bar or division sign
82     '-.--.':    '(',          # Left-hand bracket (parenthesis)
83     '-.--.-':   ')',          # Right-hand bracket (parenthesis)
84     '.-..-.':   '“ ”',        # Inverted commas (quotation marks) (before and after the words)
85     '-...-':    '=',          # Double hyphen
86     '...-.':    'UNDERSTOOD', # Understood
87     '........': 'ERROR',      # Error (eight dots)
88     '.-.-.':    '+',          # Cross or addition sign
89     '.-...':    'WAIT',       # Wait
90     '...-.-':   'EOW',        # End of work
91     '-.-.-':    'START',      # Starting signal (to precede every transmission)
92     '.--.-.':   '@',          # Commercial at
93
94     #'-.-':      'ITT',        # K: Invitation to transmit
95
96     # 3.2.1 For the multiplication sign, the signal corresponding to the letter X shall be transmitted.
97     #'-..-':     '×',          # Multiplication sign
98 }
99
100 alphabet = {decode_ditdah(k): v for k, v in alphabet.items()}
101
102 # 2 Spacing and length of the signals (right side is just for printing).
103 symbols = { # level, time units
104     # 2.1 A dash is equal to three dots.
105     (1, 1): '*',
106     (1, 3): '===',
107     # 2.2 The space between the signals forming the same letter is equal to one dot.
108     (0, 1): '_',
109     # 2.3 The space between two letters is equal to three dots.
110     (0, 3): '__',
111     # 2.4 The space between two words is equal to seven dots.
112     (0, 7): '___',
113 }
114
115 class Decoder(srd.Decoder):
116     api_version = 3
117     id = 'morse'
118     name = 'Morse'
119     longname = 'Morse code'
120     desc = 'Demodulated morse code protocol.'
121     license = 'gplv2+'
122     inputs = ['logic']
123     outputs = ['morse']
124     tags = ['Encoding']
125     channels = (
126         {'id': 'data', 'name': 'Data', 'desc': 'Data line'},
127     )
128     options = (
129         {'id': 'timeunit', 'desc': 'Time unit (guess)', 'default': 0.1},
130     )
131     annotations = (
132         ('time', 'Time'),
133         ('units', 'Units'),
134         ('symbol', 'Symbol'),
135         ('letter', 'Letter'),
136         ('word', 'Word'),
137     )
138     annotation_rows = tuple((u, v, (i,)) for i, (u, v) in enumerate(annotations))
139
140     def __init__(self):
141         self.reset()
142
143     def reset(self):
144         self.samplerate = None
145
146     def metadata(self, key, value):
147         if key == srd.SRD_CONF_SAMPLERATE:
148             self.samplerate = value
149
150     def start(self):
151         self.out_ann = self.register(srd.OUTPUT_ANN)
152         self.out_binary = self.register(srd.OUTPUT_BINARY)
153
154     def decode_symbols(self):
155         # Annotate symbols, emit symbols, handle timeout via token.
156
157         timeunit = self.options['timeunit']
158
159         self.wait({0: 'r'})
160         prevtime = self.samplenum # Time of an actual edge.
161
162         while True:
163             (val,) = self.wait([{0: 'e'}, {'skip': int(5 * self.samplerate * timeunit)}])
164
165             pval = 1 - val
166             curtime = self.samplenum
167             dt = (curtime - prevtime) / self.samplerate
168             units = dt / timeunit
169             iunits = int(max(1, round(units)))
170             error = abs(units - iunits)
171
172             symbol = (pval, iunits)
173
174             if self.matched[1]:
175                 yield None # Flush word.
176                 continue
177
178             self.put(prevtime, curtime, self.out_ann, [0, ['{:.3g}'.format(dt)]])
179
180             if symbol in symbols:
181                 self.put(prevtime, curtime, self.out_ann, [1, ['{:.1f}*{:.3g}'.format(units, timeunit)]])
182                 yield (prevtime, curtime, symbol)
183             else:
184                 self.put(prevtime, curtime, self.out_ann, [1, ['!! {:.1f}*{:.3g} !!'.format(units, timeunit)]])
185
186             prevtime = curtime
187
188             thisunit = dt / iunits
189             timeunit += (thisunit - timeunit) * 0.2 * max(0, 1 - 2*error) # Adapt.
190
191     def decode_morse(self):
192         # Group symbols into letters.
193         sequence = ()
194         s0 = s1 = None
195
196         for item in self.decode_symbols():
197             do_yield = False
198             if item is not None: # Level + width.
199                 (t0, t1, symbol) = item
200                 (sval, sunits) = symbol
201                 if sval == 1:
202                     if s0 is None:
203                         s0 = t0
204                     s1 = t1
205                     sequence += (sunits,)
206                 else:
207                     # Generate "flush" for end of letter, end of word.
208                     if sunits >= 3:
209                         do_yield = True
210             else:
211                 do_yield = True
212             if do_yield:
213                 if sequence:
214                     yield (s0, s1, alphabet.get(sequence, encode_ditdah(sequence)))
215                     sequence = ()
216                     s0 = s1 = None
217             if item is None:
218                 yield None # Pass through flush of 5+ space.
219
220     def decode(self):
221
222         # Strictly speaking there is no point in running this decoder
223         # when the sample rate is unknown or zero. But the previous
224         # implementation already fell back to a rate of 1 in that case.
225         # We stick with this approach, to not introduce new constraints
226         # for existing use scenarios.
227         if not self.samplerate:
228             self.samplerate = 1.0
229
230         # Annotate letters, group into words.
231         s0 = s1 = None
232         word = ''
233         for item in self.decode_morse():
234             do_yield = False
235
236             if item is not None: # Append letter.
237                 (t0, t1, letter) = item
238                 self.put(t0, t1, self.out_ann, [3, [letter]])
239                 if s0 is None:
240                     s0 = t0
241                 s1 = t1
242                 word += letter
243             else:
244                 do_yield = True
245
246             if do_yield: # Flush of word.
247                 if word:
248                     self.put(s0, s1, self.out_ann, [4, [word]])
249                     word = ''
250                     s0 = s1 = None