From: Christoph Rackwitz Date: Thu, 21 Sep 2017 01:28:33 +0000 (+0200) Subject: Add a morse code protocol decoder. X-Git-Url: https://sigrok.org/gitaction?a=commitdiff_plain;h=500d5ff479c2ee1c1e5531735485311f4952c368;p=libsigrokdecode.git Add a morse code protocol decoder. --- diff --git a/decoders/morse/__init__.py b/decoders/morse/__init__.py new file mode 100644 index 0000000..5d91624 --- /dev/null +++ b/decoders/morse/__init__.py @@ -0,0 +1,28 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2017 Christoph Rackwitz +## +## 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 . +## + +''' +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 index 0000000..588394d --- /dev/null +++ b/decoders/morse/pd.py @@ -0,0 +1,237 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2017 Christoph Rackwitz +## +## 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 . +## + +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