2 ## This file is part of the libsigrokdecode project.
4 ## Copyright (C) 2017 Christoph Rackwitz <christoph.rackwitz@rwth-aachen.de>
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.
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.
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/>.
20 import sigrokdecode as srd
23 return tuple({'-': 3, '.': 1}[c] for c in s)
25 def encode_ditdah(tpl):
26 return ''.join({1: '.', 3: '-'}[c] for c in tpl)
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
31 # International Morse code
39 '..-..': 'é', # "accented"
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
94 #'-.-': 'ITT', # K: Invitation to transmit
96 # 3.2.1 For the multiplication sign, the signal corresponding to the letter X shall be transmitted.
97 #'-..-': '×', # Multiplication sign
100 alphabet = {decode_ditdah(k): v for k, v in alphabet.items()}
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.
107 # 2.2 The space between the signals forming the same letter is equal to one dot.
109 # 2.3 The space between two letters is equal to three dots.
111 # 2.4 The space between two words is equal to seven dots.
115 class Decoder(srd.Decoder):
119 longname = 'Morse code'
120 desc = 'Demodulated morse code protocol.'
126 {'id': 'data', 'name': 'Data', 'desc': 'Data line'},
129 {'id': 'timeunit', 'desc': 'Time unit (guess)', 'default': 0.1},
134 ('symbol', 'Symbol'),
135 ('letter', 'Letter'),
138 annotation_rows = tuple((u + 's', v + 's', (i,)) for i, (u, v) in enumerate(annotations))
144 self.samplerate = None
146 def metadata(self, key, value):
147 if key == srd.SRD_CONF_SAMPLERATE:
148 self.samplerate = value
151 self.out_ann = self.register(srd.OUTPUT_ANN)
152 self.out_binary = self.register(srd.OUTPUT_BINARY)
154 def decode_symbols(self):
155 # Annotate symbols, emit symbols, handle timeout via token.
157 timeunit = self.options['timeunit']
160 prevtime = self.samplenum # Time of an actual edge.
163 (val,) = self.wait([{0: 'e'}, {'skip': int(5 * self.samplerate * timeunit)}])
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)
172 symbol = (pval, iunits)
175 yield None # Flush word.
178 self.put(prevtime, curtime, self.out_ann, [0, ['{:.3g}'.format(dt)]])
180 if symbol in symbols:
181 self.put(prevtime, curtime, self.out_ann, [1, ['{:.1f}*{:.3g}'.format(units, timeunit)]])
182 yield (prevtime, curtime, symbol)
184 self.put(prevtime, curtime, self.out_ann, [1, ['!! {:.1f}*{:.3g} !!'.format(units, timeunit)]])
188 thisunit = dt / iunits
189 timeunit += (thisunit - timeunit) * 0.2 * max(0, 1 - 2*error) # Adapt.
191 def decode_morse(self):
192 # Group symbols into letters.
196 for item in self.decode_symbols():
198 if item is not None: # Level + width.
199 (t0, t1, symbol) = item
200 (sval, sunits) = symbol
205 sequence += (sunits,)
207 # Generate "flush" for end of letter, end of word.
214 yield (s0, s1, alphabet.get(sequence, encode_ditdah(sequence)))
218 yield None # Pass through flush of 5+ space.
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
230 # Annotate letters, group into words.
233 for item in self.decode_morse():
236 if item is not None: # Append letter.
237 (t0, t1, letter) = item
238 self.put(t0, t1, self.out_ann, [3, [letter]])
246 if do_yield: # Flush of word.
248 self.put(s0, s1, self.out_ann, [4, [word]])