]> sigrok.org Git - libsigrokdecode.git/blame - decoders/morse/pd.py
decoders: Add/update tags for each PD.
[libsigrokdecode.git] / decoders / morse / pd.py
CommitLineData
500d5ff4
CR
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
20import sigrokdecode as srd
21
22def decode_ditdah(s):
23 return tuple({'-': 3, '.': 1}[c] for c in s)
24
25def 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
32alphabet = {
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
100alphabet = {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).
103symbols = { # 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
115class 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']
d6d8a8a4 124 tags = ['Encoding']
500d5ff4
CR
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):
10aeb8ea
GS
141 self.reset()
142
143 def reset(self):
500d5ff4
CR
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']
500d5ff4
CR
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
2d0ef8bd 169 iunits = int(max(1, round(units)))
500d5ff4
CR
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)]])
500d5ff4
CR
179
180 if symbol in symbols:
2d0ef8bd 181 self.put(prevtime, curtime, self.out_ann, [1, ['{:.1f}*{:.3g}'.format(units, timeunit)]])
500d5ff4 182 yield (prevtime, curtime, symbol)
2d0ef8bd
CR
183 else:
184 self.put(prevtime, curtime, self.out_ann, [1, ['!! {:.1f}*{:.3g} !!'.format(units, timeunit)]])
500d5ff4
CR
185
186 prevtime = curtime
187
188 thisunit = dt / iunits
2d0ef8bd 189 timeunit += (thisunit - timeunit) * 0.2 * max(0, 1 - 2*error) # Adapt.
500d5ff4
CR
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):
15a8a055
GS
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
500d5ff4
CR
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