]> sigrok.org Git - libsigrokdecode.git/commitdiff
Add a morse code protocol decoder.
authorChristoph Rackwitz <redacted>
Thu, 21 Sep 2017 01:28:33 +0000 (03:28 +0200)
committerUwe Hermann <redacted>
Sat, 9 Dec 2017 14:05:21 +0000 (15:05 +0100)
decoders/morse/__init__.py [new file with mode: 0644]
decoders/morse/pd.py [new file with mode: 0644]

diff --git a/decoders/morse/__init__.py b/decoders/morse/__init__.py
new file mode 100644 (file)
index 0000000..5d91624
--- /dev/null
@@ -0,0 +1,28 @@
+##
+## This file is part of the libsigrokdecode project.
+##
+## Copyright (C) 2017 Christoph Rackwitz <christoph.rackwitz@rwth-aachen.de>
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, see <http://www.gnu.org/licenses/>.
+##
+
+'''
+Morse code is a method of transmitting text information as a series of
+on-off tones.
+
+Details:
+https://en.wikipedia.org/wiki/Morse_code
+'''
+
+from .pd import Decoder
diff --git a/decoders/morse/pd.py b/decoders/morse/pd.py
new file mode 100644 (file)
index 0000000..588394d
--- /dev/null
@@ -0,0 +1,237 @@
+##
+## This file is part of the libsigrokdecode project.
+##
+## Copyright (C) 2017 Christoph Rackwitz <christoph.rackwitz@rwth-aachen.de>
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, see <http://www.gnu.org/licenses/>.
+##
+
+import sigrokdecode as srd
+
+def decode_ditdah(s):
+    return tuple({'-': 3, '.': 1}[c] for c in s)
+
+def encode_ditdah(tpl):
+    return ''.join({1: '.', 3: '-'}[c] for c in tpl)
+
+# https://www.itu.int/dms_pubrec/itu-r/rec/m/R-REC-M.1677-1-200910-I!!PDF-E.pdf
+# Recommendation ITU-R M.1677-1
+# (10/2009)
+# International Morse code
+alphabet = {
+    # 1.1.1 Letters
+    '.-':       'a',
+    '-...':     'b',
+    '-.-.':     'c',
+    '-..':      'd',
+    '.':        'e',
+    '..-..':    'é', # "accented"
+    '..-.':     'f',
+    '--.':      'g',
+    '....':     'h',
+    '..':       'i',
+    '.---':     'j',
+    '-.-':      'k',
+    '.-..':     'l',
+    '--':       'm',
+    '-.':       'n',
+    '---':      'o',
+    '.--.':     'p',
+    '--.-':     'q',
+    '.-.':      'r',
+    '...':      's',
+    '-':        't',
+    '..-':      'u',
+    '...-':     'v',
+    '.--':      'w',
+    '-..-':     'x',
+    '-.--':     'y',
+    '--..':     'z',
+
+    # 1.1.2 Figures
+    '.----':    '1',
+    '..---':    '2',
+    '...--':    '3',
+    '....-':    '4',
+    '.....':    '5',
+    '-....':    '6',
+    '--...':    '7',
+    '---..':    '8',
+    '----.':    '9',
+    '-----':    '0',
+
+    # 1.1.3 Punctuation marks and miscellaneous signs
+    '.-.-.-':   '.',          # Full stop (period)
+    '--..--':   ',',          # Comma
+    '---...':   ':',          # Colon or division sign
+    '..--..':   '?',          # Question mark (note of interrogation or request for repetition of a transmission not understood)
+    '.----.':   '’',          # Apostrophe
+    '-....-':   '-',          # Hyphen or dash or subtraction sign
+    '-..-.':    '/',          # Fraction bar or division sign
+    '-.--.':    '(',          # Left-hand bracket (parenthesis)
+    '-.--.-':   ')',          # Right-hand bracket (parenthesis)
+    '.-..-.':   '“ ”',        # Inverted commas (quotation marks) (before and after the words)
+    '-...-':    '=',          # Double hyphen
+    '...-.':    'UNDERSTOOD', # Understood
+    '........': 'ERROR',      # Error (eight dots)
+    '.-.-.':    '+',          # Cross or addition sign
+    '.-...':    'WAIT',       # Wait
+    '...-.-':   'EOW',        # End of work
+    '-.-.-':    'START',      # Starting signal (to precede every transmission)
+    '.--.-.':   '@',          # Commercial at
+
+    #'-.-':      'ITT',        # K: Invitation to transmit
+
+    # 3.2.1 For the multiplication sign, the signal corresponding to the letter X shall be transmitted.
+    #'-..-':     '×',          # Multiplication sign
+}
+
+alphabet = {decode_ditdah(k): v for k, v in alphabet.items()}
+
+# 2 Spacing and length of the signals (right side is just for printing).
+symbols = { # level, time units
+    # 2.1 A dash is equal to three dots.
+    (1, 1): '*',
+    (1, 3): '===',
+    # 2.2 The space between the signals forming the same letter is equal to one dot.
+    (0, 1): '_',
+    # 2.3 The space between two letters is equal to three dots.
+    (0, 3): '__',
+    # 2.4 The space between two words is equal to seven dots.
+    (0, 7): '___',
+}
+
+class Decoder(srd.Decoder):
+    api_version = 3
+    id = 'morse'
+    name = 'Morse'
+    longname = 'Morse code'
+    desc = 'Demodulated morse code protocol.'
+    license = 'gplv2+'
+    inputs = ['logic']
+    outputs = ['morse']
+    channels = (
+        {'id': 'data', 'name': 'Data', 'desc': 'Data line'},
+    )
+    options = (
+        {'id': 'timeunit', 'desc': 'Time unit (guess)', 'default': 0.1},
+    )
+    annotations = (
+        ('time', 'Time'),
+        ('units', 'Units'),
+        ('symbol', 'Symbol'),
+        ('letter', 'Letter'),
+        ('word', 'Word'),
+    )
+    annotation_rows = tuple((u, v, (i,)) for i, (u, v) in enumerate(annotations))
+
+    def __init__(self):
+        self.samplerate = None
+
+    def metadata(self, key, value):
+        if key == srd.SRD_CONF_SAMPLERATE:
+            self.samplerate = value
+
+    def start(self):
+        self.out_ann = self.register(srd.OUTPUT_ANN)
+        self.out_binary = self.register(srd.OUTPUT_BINARY)
+
+    def decode_symbols(self):
+        # Annotate symbols, emit symbols, handle timeout via token.
+
+        timeunit = self.options['timeunit']
+        if self.samplerate is None:
+            self.samplerate = 1.0
+
+        self.wait({0: 'r'})
+        prevtime = self.samplenum # Time of an actual edge.
+
+        while True:
+            (val,) = self.wait([{0: 'e'}, {'skip': int(5 * self.samplerate * timeunit)}])
+
+            pval = 1 - val
+            curtime = self.samplenum
+            dt = (curtime - prevtime) / self.samplerate
+            units = dt / timeunit
+            iunits = round(units)
+            error = abs(units - iunits)
+
+            symbol = (pval, iunits)
+
+            if self.matched[1]:
+                yield None # Flush word.
+                continue
+
+            self.put(prevtime, curtime, self.out_ann, [0, ['{:.3g}'.format(dt)]])
+            self.put(prevtime, curtime, self.out_ann, [1, ['{:.1f}*{:.3g}'.format(units, timeunit)]])
+
+            if symbol in symbols:
+                yield (prevtime, curtime, symbol)
+
+            prevtime = curtime
+
+            thisunit = dt / iunits
+            timeunit += (thisunit - timeunit) * 0.02 * iunits # Adapt.
+
+    def decode_morse(self):
+        # Group symbols into letters.
+        sequence = ()
+        s0 = s1 = None
+
+        for item in self.decode_symbols():
+            do_yield = False
+            if item is not None: # Level + width.
+                (t0, t1, symbol) = item
+                (sval, sunits) = symbol
+                if sval == 1:
+                    if s0 is None:
+                        s0 = t0
+                    s1 = t1
+                    sequence += (sunits,)
+                else:
+                    # Generate "flush" for end of letter, end of word.
+                    if sunits >= 3:
+                        do_yield = True
+            else:
+                do_yield = True
+            if do_yield:
+                if sequence:
+                    yield (s0, s1, alphabet.get(sequence, encode_ditdah(sequence)))
+                    sequence = ()
+                    s0 = s1 = None
+            if item is None:
+                yield None # Pass through flush of 5+ space.
+
+    def decode(self):
+        # Annotate letters, group into words.
+        s0 = s1 = None
+        word = ''
+        for item in self.decode_morse():
+            do_yield = False
+
+            if item is not None: # Append letter.
+                (t0, t1, letter) = item
+                self.put(t0, t1, self.out_ann, [3, [letter]])
+                if s0 is None:
+                    s0 = t0
+                s1 = t1
+                word += letter
+            else:
+                do_yield = True
+
+            if do_yield: # Flush of word.
+                if word:
+                    self.put(s0, s1, self.out_ann, [4, [word]])
+                    word = ''
+                    s0 = s1 = None