X-Git-Url: https://git.ao2.it/SaveMySugar/python3-savemysugar.git/blobdiff_plain/3baafa27ab24bf277e27371a6205884651abb964..be2f5f341d712399244a784a9114609826ae1974:/src/savemysugar/MorseDistanceModulator.py diff --git a/src/savemysugar/MorseDistanceModulator.py b/src/savemysugar/MorseDistanceModulator.py new file mode 100755 index 0000000..417c5f9 --- /dev/null +++ b/src/savemysugar/MorseDistanceModulator.py @@ -0,0 +1,167 @@ +#!/usr/bin/env python3 +# +# MorseDistanceModulator - represent Morse symbols using Distance Modulation +# +# Copyright (C) 2015 Antonio Ospite +# +# 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 3 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 . + + +class SymbolTime(object): + """ + In theory the symbol distance (the distance to discriminate symbols) can + be arbitrary, and it's only bounded below by the stability of the period + time, but in practice it can be necessary to wait a predefined minimum + amount of time between periods because of technological limits of the + transmitting devices, we call this time the "minimum inter-symbol + distance". + """ + + # pylint: disable=too-few-public-methods + def __init__(self, period_min, period_max, multiplier, + min_inter_symbol_distance=0.0): + assert multiplier >= 0 + if (period_min == period_max) and (min_inter_symbol_distance == 0): + raise ValueError("If (period_min == period_max) a non-zero", + "inter-symbol distance MUST be specified") + + symbol_distance = 2 * (period_max - period_min) + if symbol_distance == 0: + symbol_distance = min_inter_symbol_distance + + # The time the transmitter has to wait to disambiguate between this + # symbol and a different one. + self.dist = min_inter_symbol_distance + symbol_distance * multiplier + + # The minimum time which represents the symbol at the receiving end. + self.min = min_inter_symbol_distance + period_min + \ + symbol_distance * multiplier + + # The maximum time which represents the symbol at the receiving end + self.max = min_inter_symbol_distance + period_min + \ + symbol_distance * (multiplier + 1) + + +class MorseDistanceModulator(object): + def __init__(self, period_min, period_max, pulse_min, pulse_max, + inter_symbol_distance): + self.set_parameters(period_min, period_max, + pulse_min, pulse_max, + inter_symbol_distance) + + def set_parameters(self, period_min, period_max, pulse_min, pulse_max, + inter_symbol_distance): + self.period_min = period_min + self.period_max = period_max + self.pulse_min = pulse_min + self.pulse_max = pulse_max + self.inter_symbol_distance = inter_symbol_distance + + self.dot_time = SymbolTime(period_min, + period_max, 0, + inter_symbol_distance) + self.dash_time = SymbolTime(period_min, + period_max, 1, + inter_symbol_distance) + self.signalspace_time = SymbolTime(period_min, + period_max, 2, + inter_symbol_distance) + self.wordspace_time = SymbolTime(period_min, + period_max, 3, + inter_symbol_distance) + self.eom_time = SymbolTime(period_min, + period_max, 4, + inter_symbol_distance) + + def symbol_to_distance(self, symbol): + if symbol == ".": + return self.dot_time.dist + elif symbol == "-": + return self.dash_time.dist + elif symbol == " ": + return self.signalspace_time.dist + elif symbol == "/" or symbol == " / ": + return self.wordspace_time.dist + elif symbol == "EOM": + return self.eom_time.dist + + raise ValueError("Unexpected symbol %s" % symbol) + + def is_same_period(self, distance): + return distance > self.pulse_min and distance <= self.pulse_max + + def distance_to_symbol(self, distance): + if distance > self.dot_time.min and \ + distance <= self.dot_time.max: + return "." + + if distance > self.dash_time.min and \ + distance <= self.dash_time.max: + return "-" + + if distance > self.signalspace_time.min and \ + distance <= self.signalspace_time.max: + return " " + + if distance > self.wordspace_time.min and \ + distance <= self.wordspace_time.max: + return "/" + + if distance > self.eom_time.min: + return "EOM" + + raise ValueError("Unexpected distance %.2f" % distance) + + def modulate(self, morse): + signals = morse.split(' ') + distances = [] + for i, signal in enumerate(signals): + for symbol in signal: + distances.append(self.symbol_to_distance(symbol)) + + # Transmit a signal separator only when strictly necessary. + # + # Avoid it in these cases: + # - after the last symbol, because EOM is going to ne transmitted + # anyway and that will mark the end of the last symbol. + # - between words, because the word separator act as a symbol + # separator too. + if i != len(signals) - 1 and signals[i + 1] != "/" and signal != "/": + distances.append(self.symbol_to_distance(" ")) + + distances.append(self.symbol_to_distance("EOM")) + + # Since the Morse signals are encoded in the distance between calls, an + # extra call is needed in order for receiver actually get the EOM and + # see that the transmission has terminated. + distances.append(0) + + return distances + + +def test_modulate(): + period_min = 7 + period_max = 15 + pulse_min = 4.9 + pulse_max = 5.2 + min_inter_symbol_distance = pulse_max + morse = "... -- ..." + modulator = MorseDistanceModulator(period_min, period_max, + pulse_min, pulse_max, + min_inter_symbol_distance) + + print(modulator.modulate(morse)) + +if __name__ == "__main__": + test_modulate()