--- /dev/null
+__pycache__
+_test*.sh
--- /dev/null
+SaveMySugar
+===========
+
+SaveMySugar is an experiment about transmitting and receiving messages for free
+using phone call rings to encode Morse code.
+
+The name SaveMySugar is a joke on the price per bit of SMSs (Short Message
+Service) which is quite high, and the distress signal Save Our Soul used in
+Morse communications.
+
+See [http://savemysugar.ao2.it](http://savemysugar.ao2.it) for further details.
+
+Tutorial
+--------
+
+The "measure" pass in 1. and 2. assumes that there are two serial modems
+attached to the same host connected to two independent phone lines, so that
+a single program can control both the transmitter and the receiver device.
+
+A possible setup is to have two cell phones providing serial ports over which
+the programs can send AT commands and receive RING notifications.
+
+1. Measure the call setup time using `src/measure_call_setup_time.py` and take
+ note of `Max call setup time` and `Call setup time uncertainty`.
+
+2. Measure the distance between rings in the same call using
+ `src/measure_ring_distance.py` and take note of `max_distance` and
+ `uncerainty`.
+
+3. Adjust the parameters in `src/savemysugar/CallDistanceTransceiver.py` using
+ the values measured before, possibly approximated by excess.
+
+4. Transmit and/or receive a message using `src/transmit.py` or `src/receive.py`
+
+The sender and the receiver must use the same parameters.
+
+In a future version it should be possible to pass the parameters as command line
+arguments to `transmit.py` and `receive.py`, but for now these values have to be
+hardcoded.
+
+Dependencies
+------------
+
+The only dependencies are:
+
+* python3
+* python3-serial
+
+Authors
+-------
+
+Written by Antonio Ospite <ao2@ao2.it> starting from an idea by Corrado Rubera.
--- /dev/null
+- Consider making Modem.py and MorseTranslator.py independent packages.
+- Add a better command line argument parsing for the transmit.py and
+ receive.py programs.
+- Do not use Modem.py directly in the transceiver class but instead use
+ a generic transmitting/receiving back-end in order to support other
+ transmission mechanisms or telephony frameworks with python bindings (e.g.
+ freesmartphone.org).
--- /dev/null
+1449670918.188733 DEBUG:send_command AT command: 'ATZ\r\n'
+1449670918.198112 DEBUG:send_command AT command: 'ATX3\r\n'
+1449670918.201892 DEBUG:__init__ dot time: transmit: 14.70 receive: (13.20, 16.20)
+1449670918.203712 DEBUG:__init__ dash time: transmit: 19.90 receive: (18.40, 21.40)
+1449670918.203933 DEBUG:__init__ signalspace time: transmit: 25.10 receive: (23.60, 26.60)
+1449670918.204020 DEBUG:__init__ wordspace time: transmit: 30.30 receive: (28.80, 31.80)
+1449670918.204102 DEBUG:__init__ EOM time: transmit: 35.50 receive: (34.00, +inf)
+1449670994.957390 INFO:log_symbol distance: 0.00 Received "" (The very first ring)
+1449670994.957713 DEBUG:receive_loop Current message:
+1449671014.664847 DEBUG:receive_character RINGs distance: 19.71
+1449671014.665521 INFO:log_symbol distance: 19.71 Received "-"
+1449671014.665703 DEBUG:receive_loop Current message: -
+1449671030.670222 DEBUG:receive_character RINGs distance: 16.01
+1449671030.670410 INFO:log_symbol distance: 16.01 Received "."
+1449671030.670524 DEBUG:receive_loop Current message: -.
+1449671050.785801 DEBUG:receive_character RINGs distance: 20.12
+1449671050.785999 INFO:log_symbol distance: 20.12 Received "-"
+1449671050.786084 DEBUG:receive_loop Current message: -.-
+1449671066.069670 DEBUG:receive_character RINGs distance: 15.28
+1449671066.070046 INFO:log_symbol distance: 15.28 Received "."
+1449671066.070279 DEBUG:receive_loop Current message: -.-.
+1449671091.500801 DEBUG:receive_character RINGs distance: 25.43
+1449671091.500978 INFO:log_symbol distance: 25.43 Received " " got "c"
+1449671091.501050 DEBUG:receive_loop Current message: -.-.
+1449671112.640585 DEBUG:receive_character RINGs distance: 21.14
+1449671112.640955 INFO:log_symbol distance: 21.14 Received "-"
+1449671112.641135 DEBUG:receive_loop Current message: -.-. -
+1449671132.990194 DEBUG:receive_character RINGs distance: 20.35
+1449671132.990636 INFO:log_symbol distance: 20.35 Received "-"
+1449671132.990819 DEBUG:receive_loop Current message: -.-. --
+1449671153.426853 DEBUG:receive_character RINGs distance: 20.44
+1449671153.427221 INFO:log_symbol distance: 20.44 Received "-"
+1449671153.427400 DEBUG:receive_loop Current message: -.-. ---
+1449671179.055193 DEBUG:receive_character RINGs distance: 25.63
+1449671179.055582 INFO:log_symbol distance: 25.63 Received " " got "o"
+1449671179.055764 DEBUG:receive_loop Current message: -.-. ---
+1449671199.486396 DEBUG:receive_character RINGs distance: 20.43
+1449671199.486596 INFO:log_symbol distance: 20.43 Received "-"
+1449671199.486665 DEBUG:receive_loop Current message: -.-. --- -
+1449671215.024084 DEBUG:receive_character RINGs distance: 15.54
+1449671215.024292 INFO:log_symbol distance: 15.54 Received "."
+1449671215.024386 DEBUG:receive_loop Current message: -.-. --- -.
+1449671230.094768 DEBUG:receive_character RINGs distance: 15.07
+1449671230.094973 INFO:log_symbol distance: 15.07 Received "."
+1449671230.095073 DEBUG:receive_loop Current message: -.-. --- -..
+1449671255.788444 DEBUG:receive_character RINGs distance: 25.69
+1449671255.788843 INFO:log_symbol distance: 25.69 Received " " got "d"
+1449671255.789009 DEBUG:receive_loop Current message: -.-. --- -..
+1449671271.266149 DEBUG:receive_character RINGs distance: 15.48
+1449671271.266586 INFO:log_symbol distance: 15.48 Received "."
+1449671271.266758 DEBUG:receive_loop Current message: -.-. --- -.. .
+1449671296.885521 DEBUG:receive_character RINGs distance: 25.62
+1449671296.885915 INFO:log_symbol distance: 25.62 Received " " got "e"
+1449671296.886115 DEBUG:receive_loop Current message: -.-. --- -.. .
+1449671316.965795 DEBUG:receive_character RINGs distance: 20.08
+1449671316.966002 INFO:log_symbol distance: 20.08 Received "-"
+1449671316.966103 DEBUG:receive_loop Current message: -.-. --- -.. . -
+1449671332.817696 DEBUG:receive_character RINGs distance: 15.85
+1449671332.818059 INFO:log_symbol distance: 15.85 Received "."
+1449671332.818241 DEBUG:receive_loop Current message: -.-. --- -.. . -.
+1449671347.603125 DEBUG:receive_character RINGs distance: 14.79
+1449671347.603330 INFO:log_symbol distance: 14.79 Received "."
+1449671347.603422 DEBUG:receive_loop Current message: -.-. --- -.. . -..
+1449671368.286672 DEBUG:receive_character RINGs distance: 20.68
+1449671368.286869 INFO:log_symbol distance: 20.68 Received "-"
+1449671368.286952 DEBUG:receive_loop Current message: -.-. --- -.. . -..-
+1449671404.531024 DEBUG:receive_character RINGs distance: 36.24
+1449671404.531392 INFO:log_symbol distance: 36.24 Received "EOM" got "x"
+1449671404.531572 DEBUG:receive_loop Current message: -.-. --- -.. . -..-
+
+Message received!
+codex
--- /dev/null
+Script started on mer 09 dic 2015 15:23:05 CET
++ OUTGOING_PORT=/dev/ttyS0
++ DESTINATION_NUMBER=555402402
++ MESSAGE=CODEX
++ ./src/transmit.py /dev/ttyS0 555402402 CODEX
+Available hardware serial ports:
+ - /dev/ttyS0
+
+1449670985.587252 DEBUG:send_command AT command: 'ATZ\r\n'
+1449670986.534742 DEBUG:send_command AT command: 'ATX3\r\n'
+1449670986.553606 DEBUG:__init__ dot time: transmit: 14.70 receive: (13.20, 16.20)
+1449670986.553924 DEBUG:__init__ dash time: transmit: 19.90 receive: (18.40, 21.40)
+1449670986.554089 DEBUG:__init__ signalspace time: transmit: 25.10 receive: (23.60, 26.60)
+1449670986.554257 DEBUG:__init__ wordspace time: transmit: 30.30 receive: (28.80, 31.80)
+1449670986.554419 DEBUG:__init__ EOM time: transmit: 35.50 receive: (34.00, +inf)
+1449670986.555578 DEBUG:estimate_transmit_duration ['-.-.', '---', '-..', '.', '-..-']
+1449670986.555808 DEBUG:estimate_transmit_duration signal: -.-.
+1449670986.556016 DEBUG:estimate_transmit_duration signal: ---
+1449670986.556195 DEBUG:estimate_transmit_duration signal: -..
+1449670986.556372 DEBUG:estimate_transmit_duration signal: .
+1449670986.556543 DEBUG:estimate_transmit_duration signal: -..-
+1449670986.556720 DEBUG:estimate_transmit_duration Estimated transmitting time: 573 seconds
+1449670986.557297 DEBUG:transmit Starting the trasmission
+1449670986.557462 DEBUG:transmit Transmitting 'C' as '-.-.'
+1449670986.557620 DEBUG:transmit_signal Transmitting signal: -.-.
+1449670986.557800 INFO:transmit_symbol Dial and wait 19.90 seconds (transmitting '-')
+1449670986.558138 DEBUG:send_command AT command: 'ATDT555402402\r\n'
+1449670996.064090 DEBUG:send_command AT command: 'ATH\r\n'
+1449671007.066112 INFO:transmit_symbol Dial and wait 14.70 seconds (transmitting '.')
+1449671007.066549 DEBUG:send_command AT command: 'ATDT555402402\r\n'
+1449671016.574705 DEBUG:send_command AT command: 'ATH\r\n'
+1449671022.369606 INFO:transmit_symbol Dial and wait 19.90 seconds (transmitting '-')
+1449671022.369991 DEBUG:send_command AT command: 'ATDT555402402\r\n'
+1449671031.873174 DEBUG:send_command AT command: 'ATH\r\n'
+1449671042.877515 INFO:transmit_symbol Dial and wait 14.70 seconds (transmitting '.')
+1449671042.877715 DEBUG:send_command AT command: 'ATDT555402402\r\n'
+1449671052.378261 DEBUG:send_command AT command: 'ATH\r\n'
+1449671058.174628 INFO:transmit_symbol Dial and wait 25.10 seconds (transmitting ' ')
+1449671058.175008 DEBUG:send_command AT command: 'ATDT555402402\r\n'
+1449671067.682544 DEBUG:send_command AT command: 'ATH\r\n'
+1449671083.885474 DEBUG:transmit Transmitting 'O' as '---'
+1449671083.885724 DEBUG:transmit_signal Transmitting signal: ---
+1449671083.885830 INFO:transmit_symbol Dial and wait 19.90 seconds (transmitting '-')
+1449671083.885926 DEBUG:send_command AT command: 'ATDT555402402\r\n'
+1449671093.394557 DEBUG:send_command AT command: 'ATH\r\n'
+1449671104.398561 INFO:transmit_symbol Dial and wait 19.90 seconds (transmitting '-')
+1449671104.398755 DEBUG:send_command AT command: 'ATDT555402402\r\n'
+1449671113.908509 DEBUG:send_command AT command: 'ATH\r\n'
+1449671124.910546 INFO:transmit_symbol Dial and wait 19.90 seconds (transmitting '-')
+1449671124.910772 DEBUG:send_command AT command: 'ATDT555402402\r\n'
+1449671134.420646 DEBUG:send_command AT command: 'ATH\r\n'
+1449671145.422644 INFO:transmit_symbol Dial and wait 25.10 seconds (transmitting ' ')
+1449671145.423032 DEBUG:send_command AT command: 'ATDT555402402\r\n'
+1449671154.930423 DEBUG:send_command AT command: 'ATH\r\n'
+1449671171.138666 DEBUG:transmit Transmitting 'D' as '-..'
+1449671171.139023 DEBUG:transmit_signal Transmitting signal: -..
+1449671171.139232 INFO:transmit_symbol Dial and wait 19.90 seconds (transmitting '-')
+1449671171.139414 DEBUG:send_command AT command: 'ATDT555402402\r\n'
+1449671180.645330 DEBUG:send_command AT command: 'ATH\r\n'
+1449671191.649726 INFO:transmit_symbol Dial and wait 14.70 seconds (transmitting '.')
+1449671191.650113 DEBUG:send_command AT command: 'ATDT555402402\r\n'
+1449671201.159920 DEBUG:send_command AT command: 'ATH\r\n'
+1449671206.955485 INFO:transmit_symbol Dial and wait 14.70 seconds (transmitting '.')
+1449671206.955890 DEBUG:send_command AT command: 'ATDT555402402\r\n'
+1449671216.465874 DEBUG:send_command AT command: 'ATH\r\n'
+1449671222.262631 INFO:transmit_symbol Dial and wait 25.10 seconds (transmitting ' ')
+1449671222.263016 DEBUG:send_command AT command: 'ATDT555402402\r\n'
+1449671231.770671 DEBUG:send_command AT command: 'ATH\r\n'
+1449671247.979372 DEBUG:transmit Transmitting 'E' as '.'
+1449671247.979575 DEBUG:transmit_signal Transmitting signal: .
+1449671247.979692 INFO:transmit_symbol Dial and wait 14.70 seconds (transmitting '.')
+1449671247.979795 DEBUG:send_command AT command: 'ATDT555402402\r\n'
+1449671257.488091 DEBUG:send_command AT command: 'ATH\r\n'
+1449671263.281866 INFO:transmit_symbol Dial and wait 25.10 seconds (transmitting ' ')
+1449671263.282084 DEBUG:send_command AT command: 'ATDT555402402\r\n'
+1449671272.782675 DEBUG:send_command AT command: 'ATH\r\n'
+1449671288.990560 DEBUG:transmit Transmitting 'X' as '-..-'
+1449671288.990759 DEBUG:transmit_signal Transmitting signal: -..-
+1449671288.990876 INFO:transmit_symbol Dial and wait 19.90 seconds (transmitting '-')
+1449671288.990978 DEBUG:send_command AT command: 'ATDT555402402\r\n'
+1449671298.499601 DEBUG:send_command AT command: 'ATH\r\n'
+1449671309.502568 INFO:transmit_symbol Dial and wait 14.70 seconds (transmitting '.')
+1449671309.502792 DEBUG:send_command AT command: 'ATDT555402402\r\n'
+1449671319.010559 DEBUG:send_command AT command: 'ATH\r\n'
+1449671324.806638 INFO:transmit_symbol Dial and wait 14.70 seconds (transmitting '.')
+1449671324.807027 DEBUG:send_command AT command: 'ATDT555402402\r\n'
+1449671334.314691 DEBUG:send_command AT command: 'ATH\r\n'
+1449671340.113764 INFO:transmit_symbol Dial and wait 19.90 seconds (transmitting '-')
+1449671340.113983 DEBUG:send_command AT command: 'ATDT555402402\r\n'
+1449671349.622691 DEBUG:send_command AT command: 'ATH\r\n'
+1449671360.626544 INFO:transmit_symbol Dial and wait 35.50 seconds (transmitting 'EOM')
+1449671360.626760 DEBUG:send_command AT command: 'ATDT555402402\r\n'
+1449671370.136553 DEBUG:send_command AT command: 'ATH\r\n'
+1449671396.756532 INFO:transmit_symbol Dial and wait 9.50 seconds (transmitting 'None')
+1449671396.756752 DEBUG:send_command AT command: 'ATDT555402402\r\n'
+1449671406.261547 DEBUG:send_command AT command: 'ATH\r\n'
--- /dev/null
+Attempt: 1
+First ring
+After hangup sleeping 5.20 sec...
+Call setup time: 8.495060
+Min call setup time: 8.495060
+Max call setup time: 8.495060
+Average call setup time: 8.495060
+Call setup time uncertainty: 0.000000
+
+Total call time: 9.088812
+Min call time: 9.088812
+Max call time: 9.088812
+
+
+Attempt: 2
+First ring
+After hangup sleeping 5.20 sec...
+Call setup time: 7.734905
+Min call setup time: 7.734905
+Max call setup time: 8.495060
+Average call setup time: 8.114982
+Call setup time uncertainty: 0.380077
+
+Total call time: 8.328592
+Min call time: 8.328592
+Max call time: 9.088812
+
+
+Attempt: 3
+First ring
+After hangup sleeping 5.20 sec...
+Call setup time: 7.631182
+Min call setup time: 7.631182
+Max call setup time: 8.495060
+Average call setup time: 7.953716
+Call setup time uncertainty: 0.431939
+
+Total call time: 8.224887
+Min call time: 8.224887
+Max call time: 9.088812
+
+
+Attempt: 4
+First ring
+After hangup sleeping 5.20 sec...
+Call setup time: 8.416898
+Min call setup time: 7.631182
+Max call setup time: 8.495060
+Average call setup time: 8.069511
+Call setup time uncertainty: 0.431939
+
+Total call time: 9.010716
+Min call time: 8.224887
+Max call time: 9.088812
+
+
+Attempt: 5
+First ring
+After hangup sleeping 5.20 sec...
+Call setup time: 7.746990
+Min call setup time: 7.631182
+Max call setup time: 8.495060
+Average call setup time: 8.005007
+Call setup time uncertainty: 0.431939
+
+Total call time: 8.340551
+Min call time: 8.224887
+Max call time: 9.088812
--- /dev/null
+First ring
+
+Other ring
+distance: 5.011862
+min_distance: 5.011862
+max_distance: 5.011862
+average_distance: 5.011862
+uncertainty: 0.000000
+
+Other ring
+distance: 4.978777
+min_distance: 4.978777
+max_distance: 5.011862
+average_distance: 4.995319
+uncertainty: 0.016542
+
+Other ring
+distance: 5.181837
+min_distance: 4.978777
+max_distance: 5.181837
+average_distance: 5.057492
+uncertainty: 0.101530
+
+Other ring
+distance: 4.829713
+min_distance: 4.829713
+max_distance: 5.181837
+average_distance: 5.000547
+uncertainty: 0.176062
+
+Other ring
+distance: 5.144908
+min_distance: 4.829713
+max_distance: 5.181837
+average_distance: 5.029419
+uncertainty: 0.176062
+
+Other ring
+distance: 4.868851
+min_distance: 4.829713
+max_distance: 5.181837
+average_distance: 5.002658
+uncertainty: 0.176062
+
+Other ring
+distance: 5.055723
+min_distance: 4.829713
+max_distance: 5.181837
+average_distance: 5.010239
+uncertainty: 0.176062
+
+Other ring
+distance: 4.964905
+min_distance: 4.829713
+max_distance: 5.181837
+average_distance: 5.004572
+uncertainty: 0.176062
+
+Other ring
+distance: 5.034827
+min_distance: 4.829713
+max_distance: 5.181837
+average_distance: 5.007934
+uncertainty: 0.176062
--- /dev/null
+#!/usr/bin/gnuplot -persist
+#
+#
+# G N U P L O T
+# Version 5.0 patchlevel 1 last modified 2015-06-07
+#
+# Copyright (C) 1986-1993, 1998, 2004, 2007-2015
+# Thomas Williams, Colin Kelley and many others
+#
+# gnuplot home: http://www.gnuplot.info
+# faq, bugs, etc: type "help FAQ"
+# immediate help: type "help" (plot window: hit 'h')
+
+set border 1
+set nogrid
+set key autotitles nobox reverse Left enhanced vert left at graph 0, screen 0.98
+
+set title "Morse code sent using phone rings and pulse-distance modulation"
+set xlabel "time since the first call (seconds)"
+
+set noytics
+
+set xtics 20 out nomirror rotate by -45
+
+set yrange[0:0.7]
+set size ratio 0.3
+
+set style arrow 1 heads size 2,90 front ls 201 lc 'gray'
+
+_w = 1280
+_h = _w * 0.39
+
+# a helper function
+min(a, b) = (a < b) ? a : b
+
+# The data files
+transmit_data="codex_transmit.log"
+receive_data="codex_receive.log"
+#transmit_data="savemysugar_transmit.log"
+#receive_data="savemysugar_receive.log"
+
+set terminal unknown
+
+plot transmit_data using 1:(1)
+sender_t0 = GPVAL_DATA_X_MIN
+
+plot receive_data using 1:(1)
+receiver_t0 = GPVAL_DATA_X_MIN
+
+t0 = min(sender_t0, receiver_t0)
+
+call_setup_time = receiver_t0 - sender_t0
+call_time_error = 1.2
+call_time = call_setup_time + call_time_error
+
+morse_symbol(symbol) = symbol eq " " ? "sep" : symbol eq "/" ? "SEP" : symbol
+end_of_signal(symbol) = (symbol eq " ") || (symbol eq "/") || (symbol eq "EOM")
+text_character(symbol, character) = symbol eq "/" ? character . "␣" : character
+
+set terminal qt 0 enhanced font "Georgia,14" size _w,_h
+#set terminal pngcairo notransparent nocrop truecolor rounded enhanced font "Georgia,14" fontscale 1.0 size _w,_h
+#set output 'pulse_distance_modulation.png'
+
+plot \
+ transmit_data using (0):(0):($1 - t0):($1 - t0 + call_time):(0):(0.5) with boxxy \
+ fs solid noborder lc "#90caf9" title 'outgoing calls', \
+ receive_data using ($1 - t0):(0.5) with impulses lw 2 lc '#ff3d00' title 'ingoing rings', \
+ receive_data using ($4 > 0 ? $1 - t0 : 1/0):(0.53):(-$4):(0) with vectors arrowstyle 1 notitle, \
+ receive_data using ($4 > 0 ? ($1 - t0) - ($4 / 2) : 1/0):(0.56):(morse_symbol(strcol(6))) with labels notitle, \
+ receive_data using (end_of_signal(strcol(6)) ? ($1 - t0) : 1/0):(0.61):(text_character(strcol(6), strcol(8))) with labels font "Courier,14" notitle, \
--- /dev/null
+1449670994.957390 INFO:log_symbol distance: 0.00 Received "" (The very first ring)
+1449671014.665521 INFO:log_symbol distance: 19.71 Received "-"
+1449671030.670410 INFO:log_symbol distance: 16.01 Received "."
+1449671050.785999 INFO:log_symbol distance: 20.12 Received "-"
+1449671066.070046 INFO:log_symbol distance: 15.28 Received "."
+1449671091.500978 INFO:log_symbol distance: 25.43 Received " " got "c"
+1449671112.640955 INFO:log_symbol distance: 21.14 Received "-"
+1449671132.990636 INFO:log_symbol distance: 20.35 Received "-"
+1449671153.427221 INFO:log_symbol distance: 20.44 Received "-"
+1449671179.055582 INFO:log_symbol distance: 25.63 Received " " got "o"
+1449671199.486596 INFO:log_symbol distance: 20.43 Received "-"
+1449671215.024292 INFO:log_symbol distance: 15.54 Received "."
+1449671230.094973 INFO:log_symbol distance: 15.07 Received "."
+1449671255.788843 INFO:log_symbol distance: 25.69 Received " " got "d"
+1449671271.266586 INFO:log_symbol distance: 15.48 Received "."
+1449671296.885915 INFO:log_symbol distance: 25.62 Received " " got "e"
+1449671316.966002 INFO:log_symbol distance: 20.08 Received "-"
+1449671332.818059 INFO:log_symbol distance: 15.85 Received "."
+1449671347.603330 INFO:log_symbol distance: 14.79 Received "."
+1449671368.286869 INFO:log_symbol distance: 20.68 Received "-"
+1449671404.531392 INFO:log_symbol distance: 36.24 Received "EOM" got "x"
--- /dev/null
+1449670986.557800 INFO:transmit_symbol Dial and wait 19.90 seconds (transmitting '-')
+1449671007.066112 INFO:transmit_symbol Dial and wait 14.70 seconds (transmitting '.')
+1449671022.369606 INFO:transmit_symbol Dial and wait 19.90 seconds (transmitting '-')
+1449671042.877515 INFO:transmit_symbol Dial and wait 14.70 seconds (transmitting '.')
+1449671058.174628 INFO:transmit_symbol Dial and wait 25.10 seconds (transmitting ' ')
+1449671083.885830 INFO:transmit_symbol Dial and wait 19.90 seconds (transmitting '-')
+1449671104.398561 INFO:transmit_symbol Dial and wait 19.90 seconds (transmitting '-')
+1449671124.910546 INFO:transmit_symbol Dial and wait 19.90 seconds (transmitting '-')
+1449671145.422644 INFO:transmit_symbol Dial and wait 25.10 seconds (transmitting ' ')
+1449671171.139232 INFO:transmit_symbol Dial and wait 19.90 seconds (transmitting '-')
+1449671191.649726 INFO:transmit_symbol Dial and wait 14.70 seconds (transmitting '.')
+1449671206.955485 INFO:transmit_symbol Dial and wait 14.70 seconds (transmitting '.')
+1449671222.262631 INFO:transmit_symbol Dial and wait 25.10 seconds (transmitting ' ')
+1449671247.979692 INFO:transmit_symbol Dial and wait 14.70 seconds (transmitting '.')
+1449671263.281866 INFO:transmit_symbol Dial and wait 25.10 seconds (transmitting ' ')
+1449671288.990876 INFO:transmit_symbol Dial and wait 19.90 seconds (transmitting '-')
+1449671309.502568 INFO:transmit_symbol Dial and wait 14.70 seconds (transmitting '.')
+1449671324.806638 INFO:transmit_symbol Dial and wait 14.70 seconds (transmitting '.')
+1449671340.113764 INFO:transmit_symbol Dial and wait 19.90 seconds (transmitting '-')
+1449671360.626544 INFO:transmit_symbol Dial and wait 35.50 seconds (transmitting 'EOM')
+1449671396.756532 INFO:transmit_symbol Dial and wait 9.50 seconds (transmitting 'None')
--- /dev/null
+1449767078.298480 INFO:log_symbol distance: 0.00 Received "" (The very first ring)
+1449767093.801842 INFO:log_symbol distance: 15.50 Received "."
+1449767109.398245 INFO:log_symbol distance: 15.60 Received "."
+1449767124.998858 INFO:log_symbol distance: 15.60 Received "."
+1449767151.817434 INFO:log_symbol distance: 26.82 Received " " got "s"
+1449767167.063156 INFO:log_symbol distance: 15.25 Received "."
+1449767188.460764 INFO:log_symbol distance: 21.40 Received "-"
+1449767214.667315 INFO:log_symbol distance: 26.21 Received " " got "a"
+1449767229.979030 INFO:log_symbol distance: 15.31 Received "."
+1449767246.120717 INFO:log_symbol distance: 16.14 Received "."
+1449767262.340340 INFO:log_symbol distance: 16.22 Received "."
+1449767283.399982 INFO:log_symbol distance: 21.06 Received "-"
+1449767309.184571 INFO:log_symbol distance: 25.78 Received " " got "v"
+1449767325.035770 INFO:log_symbol distance: 15.85 Received "."
+1449767357.800701 INFO:log_symbol distance: 32.76 Received "/" got "e"
+1449767377.463256 INFO:log_symbol distance: 19.66 Received "-"
+1449767398.644859 INFO:log_symbol distance: 21.18 Received "-"
+1449767424.584395 INFO:log_symbol distance: 25.94 Received " " got "m"
+1449767446.529010 INFO:log_symbol distance: 21.94 Received "-"
+1449767461.247696 INFO:log_symbol distance: 14.72 Received "."
+1449767482.329433 INFO:log_symbol distance: 21.08 Received "-"
+1449767503.725013 INFO:log_symbol distance: 21.40 Received "-"
+1449767535.224446 INFO:log_symbol distance: 31.50 Received "/" got "y"
+1449767551.420112 INFO:log_symbol distance: 16.20 Received "."
+1449767566.244822 INFO:log_symbol distance: 14.82 Received "."
+1449767583.211545 INFO:log_symbol distance: 16.97 Received "."
+1449767608.362101 INFO:log_symbol distance: 25.15 Received " " got "s"
+1449767625.392787 INFO:log_symbol distance: 17.03 Received "."
+1449767640.819488 INFO:log_symbol distance: 15.43 Received "."
+1449767660.902081 INFO:log_symbol distance: 20.08 Received "-"
+1449767688.683652 INFO:log_symbol distance: 27.78 Received " " got "u"
+1449767709.581140 INFO:log_symbol distance: 20.90 Received "-"
+1449767730.097842 INFO:log_symbol distance: 20.52 Received "-"
+1449767745.651483 INFO:log_symbol distance: 15.55 Received "."
+1449767771.709129 INFO:log_symbol distance: 26.06 Received " " got "g"
+1449767788.010725 INFO:log_symbol distance: 16.30 Received "."
+1449767808.022360 INFO:log_symbol distance: 20.01 Received "-"
+1449767834.800986 INFO:log_symbol distance: 26.78 Received " " got "a"
+1449767850.402655 INFO:log_symbol distance: 15.60 Received "."
+1449767871.284281 INFO:log_symbol distance: 20.88 Received "-"
+1449767887.303974 INFO:log_symbol distance: 16.02 Received "."
+1449767924.244262 INFO:log_symbol distance: 36.94 Received "EOM" got "r"
--- /dev/null
+1449767070.136624 INFO:transmit_symbol Dial and wait 15.20 seconds (transmitting '.')
+1449767085.946654 INFO:transmit_symbol Dial and wait 15.20 seconds (transmitting '.')
+1449767101.756635 INFO:transmit_symbol Dial and wait 15.20 seconds (transmitting '.')
+1449767117.564704 INFO:transmit_symbol Dial and wait 25.60 seconds (transmitting ' ')
+1449767143.780804 INFO:transmit_symbol Dial and wait 15.20 seconds (transmitting '.')
+1449767159.588935 INFO:transmit_symbol Dial and wait 20.40 seconds (transmitting '-')
+1449767180.604049 INFO:transmit_symbol Dial and wait 25.60 seconds (transmitting ' ')
+1449767206.825262 INFO:transmit_symbol Dial and wait 15.20 seconds (transmitting '.')
+1449767222.635230 INFO:transmit_symbol Dial and wait 15.20 seconds (transmitting '.')
+1449767238.445194 INFO:transmit_symbol Dial and wait 15.20 seconds (transmitting '.')
+1449767254.255166 INFO:transmit_symbol Dial and wait 20.40 seconds (transmitting '-')
+1449767275.269486 INFO:transmit_symbol Dial and wait 25.60 seconds (transmitting ' ')
+1449767301.482446 INFO:transmit_symbol Dial and wait 15.20 seconds (transmitting '.')
+1449767317.292965 INFO:transmit_symbol Dial and wait 30.80 seconds (transmitting '/')
+1449767348.716438 INFO:transmit_symbol Dial and wait 20.40 seconds (transmitting '-')
+1449767369.731551 INFO:transmit_symbol Dial and wait 20.40 seconds (transmitting '-')
+1449767390.746612 INFO:transmit_symbol Dial and wait 25.60 seconds (transmitting ' ')
+1449767416.952798 INFO:transmit_symbol Dial and wait 20.40 seconds (transmitting '-')
+1449767437.967617 INFO:transmit_symbol Dial and wait 15.20 seconds (transmitting '.')
+1449767453.773552 INFO:transmit_symbol Dial and wait 20.40 seconds (transmitting '-')
+1449767474.785972 INFO:transmit_symbol Dial and wait 20.40 seconds (transmitting '-')
+1449767495.794459 INFO:transmit_symbol Dial and wait 30.80 seconds (transmitting '/')
+1449767527.220392 INFO:transmit_symbol Dial and wait 15.20 seconds (transmitting '.')
+1449767543.029056 INFO:transmit_symbol Dial and wait 15.20 seconds (transmitting '.')
+1449767558.839412 INFO:transmit_symbol Dial and wait 15.20 seconds (transmitting '.')
+1449767574.639571 INFO:transmit_symbol Dial and wait 25.60 seconds (transmitting ' ')
+1449767600.857857 INFO:transmit_symbol Dial and wait 15.20 seconds (transmitting '.')
+1449767616.664918 INFO:transmit_symbol Dial and wait 15.20 seconds (transmitting '.')
+1449767632.470437 INFO:transmit_symbol Dial and wait 20.40 seconds (transmitting '-')
+1449767653.485939 INFO:transmit_symbol Dial and wait 25.60 seconds (transmitting ' ')
+1449767679.703824 INFO:transmit_symbol Dial and wait 20.40 seconds (transmitting '-')
+1449767700.718621 INFO:transmit_symbol Dial and wait 20.40 seconds (transmitting '-')
+1449767721.733658 INFO:transmit_symbol Dial and wait 15.20 seconds (transmitting '.')
+1449767737.538787 INFO:transmit_symbol Dial and wait 25.60 seconds (transmitting ' ')
+1449767763.753572 INFO:transmit_symbol Dial and wait 15.20 seconds (transmitting '.')
+1449767779.563234 INFO:transmit_symbol Dial and wait 20.40 seconds (transmitting '-')
+1449767800.575923 INFO:transmit_symbol Dial and wait 25.60 seconds (transmitting ' ')
+1449767826.789688 INFO:transmit_symbol Dial and wait 15.20 seconds (transmitting '.')
+1449767842.599629 INFO:transmit_symbol Dial and wait 20.40 seconds (transmitting '-')
+1449767863.614820 INFO:transmit_symbol Dial and wait 15.20 seconds (transmitting '.')
+1449767879.418044 INFO:transmit_symbol Dial and wait 36.00 seconds (transmitting 'EOM')
+1449767916.043168 INFO:transmit_symbol Dial and wait 10.00 seconds (transmitting 'None')
--- /dev/null
+#!/usr/bin/env python3
+#
+# measure_call_setup_time - measure time between dialing out and ringing in
+#
+# Copyright (C) 2015 Antonio Ospite <ao2@ao2.it>
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+from savemysugar.cumulative_average import cumulative_average
+from savemysugar.Modem import Modem
+import time
+
+
+class Ring(object):
+ time = -1
+
+
+def on_ring():
+ if Ring.time == -1:
+ print("First ring")
+ Ring.time = time.time()
+ raise StopIteration("We just want the first ring")
+
+
+def measure_call_setup_time(ingoing_port, outgoing_port, destination_number):
+ ingoing_modem = Modem(ingoing_port)
+ ingoing_modem.register_callback("RING", on_ring)
+
+ outgoing_modem = Modem(outgoing_port)
+
+ min_call_time = 10000
+ max_call_time = -1
+ min_call_setup_time = 10000
+ max_call_setup_time = -1
+
+ avg_call_setup_time = 0
+
+ for i in range(5):
+ print()
+ print("Attempt: %s" % (i + 1))
+ outgoing_modem.send_command("ATDT" + destination_number)
+ dial_time = time.time()
+
+ # Wait for the receiver to get a ring before terminating the call
+ try:
+ ingoing_modem.get_response_loop()
+ except (StopIteration, KeyboardInterrupt):
+ outgoing_modem.send_command("ATH")
+ outgoing_modem.get_response()
+
+ hangup_time = time.time()
+
+ # When dialing with an analog modem I noticed that if calls are
+ # separated one from another they take less time to set up, this
+ # may be due to how an analog modem works: getting it on-hook and
+ # off-hook takes quite some time.
+ inter_call_sleep = 5.2
+ print("After hangup sleeping %.2f sec..." % inter_call_sleep)
+ time.sleep(inter_call_sleep)
+
+ call_setup_time = Ring.time - dial_time
+ min_call_setup_time = min(min_call_setup_time, call_setup_time)
+ max_call_setup_time = max(max_call_setup_time, call_setup_time)
+ avg_call_setup_time = cumulative_average(avg_call_setup_time,
+ i + 1,
+ call_setup_time)
+ uncertainty = (max_call_setup_time - min_call_setup_time) / 2.
+ print("Call setup time: %f" % call_setup_time)
+ print("Min call setup time: %f" % min_call_setup_time)
+ print("Max call setup time: %f" % max_call_setup_time)
+ print("Average call setup time: %f" % avg_call_setup_time)
+ print("Call setup time uncertainty: %f" % uncertainty)
+ print()
+
+ call_time = hangup_time - dial_time
+ min_call_time = min(min_call_time, call_time)
+ max_call_time = max(max_call_time, call_time)
+ print("Total call time: %f" % call_time)
+ print("Min call time: %f" % min_call_time)
+ print("Max call time: %f" % max_call_time)
+ print()
+ Ring.time = -1
+
+
+def main():
+ import sys
+ if len(sys.argv) != 4:
+ print("usage: %s" % sys.argv[0],
+ "<ingoing serial port> <outgoing serial port>",
+ "<destination number>")
+ sys.exit(1)
+
+ measure_call_setup_time(sys.argv[1], sys.argv[2], sys.argv[3])
+
+
+if __name__ == "__main__":
+ main()
--- /dev/null
+#!/usr/bin/env python3
+#
+# measure_ring_distance - measure the time between two rings in the same call
+#
+# Copyright (C) 2015 Antonio Ospite <ao2@ao2.it>
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+from savemysugar.cumulative_average import cumulative_average
+from savemysugar.Modem import Modem
+import time
+
+
+class Rings(object):
+ previous_time = -1
+ average_distance = 0
+ min_distance = 10000
+ max_distance = -1
+ count = 0
+
+
+def on_ring():
+ Rings.count += 1
+
+ if Rings.previous_time == -1:
+ print("First ring")
+ Rings.previous_time = time.time()
+ else:
+ new_time = time.time()
+ distance = new_time - Rings.previous_time
+ Rings.previous_time = new_time
+ Rings.min_distance = min(Rings.min_distance, distance)
+ Rings.max_distance = max(Rings.max_distance, distance)
+ Rings.average_distance = cumulative_average(Rings.average_distance,
+ Rings.count - 1, distance)
+ print()
+ print("Other ring")
+ print("distance: %f" % distance)
+ print("min_distance: %f" % Rings.min_distance)
+ print("max_distance: %f" % Rings.max_distance)
+ print("average_distance: %f" % Rings.average_distance)
+ uncertainty = (Rings.max_distance - Rings.min_distance) / 2.
+ print("uncertainty: %f" % uncertainty)
+
+
+def measure_ring_distance(ingoing_port, outgoing_port, destination_number):
+ ingoing_modem = Modem(ingoing_port)
+ ingoing_modem.register_callback("RING", on_ring)
+ outgoing_modem = Modem(outgoing_port)
+
+ outgoing_modem.send_command("ATDT" + destination_number)
+
+ try:
+ ingoing_modem.get_response_loop()
+ except KeyboardInterrupt:
+ outgoing_modem.send_command("ATH")
+ outgoing_modem.get_response()
+
+
+def main():
+ import sys
+ if len(sys.argv) != 4:
+ print("usage: %s" % sys.argv[0],
+ "<ingoing serial port> <outgoing serial port>",
+ "<destination number>")
+ sys.exit(1)
+
+ measure_ring_distance(sys.argv[1], sys.argv[2], sys.argv[3])
+
+
+if __name__ == "__main__":
+ main()
--- /dev/null
+#!/usr/bin/env python3
+#
+# SaveMySugar - receive messages using Morse code via phone rings
+#
+# Copyright (C) 2015 Antonio Ospite <ao2@ao2.it>
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+from savemysugar.CallDistanceTransceiver import CallDistanceTransceiver
+from savemysugar.Modem import Modem
+import logging
+
+# TODO: set the logging level from the command line
+FORMAT = "%(created)f %(levelname)s:%(funcName)s %(message)s"
+logging.basicConfig(level=logging.DEBUG, format=FORMAT)
+
+
+def receive(port):
+ modem = Modem(port)
+
+ receiver = CallDistanceTransceiver(modem)
+
+ while True:
+ receiver.receive_loop()
+ print()
+ print("Message received!")
+ print((receiver.get_text()))
+
+
+def main():
+ print("Available hardware serial ports:")
+ for port in Modem.scan():
+ print((" - " + port))
+ print()
+
+ import sys
+ if len(sys.argv) != 2:
+ print("usage: %s <serial port>" % sys.argv[0])
+ sys.exit(1)
+
+ receive(sys.argv[1])
+
+
+if __name__ == "__main__":
+ main()
--- /dev/null
+#!/usr/bin/env python3
+#
+# CallDistanceTransceiver - send and receive Morse using phone calls distance
+#
+# Copyright (C) 2015 Antonio Ospite <ao2@ao2.it>
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+
+# This hack allows MorseTranslator to be imported also when
+# __name__ == "__main__"
+try:
+ from .MorseTranslator import MorseTranslator
+except SystemError:
+ from MorseTranslator import MorseTranslator
+
+import logging
+import time
+
+
+class CallDistanceTransceiver(object):
+ """Transmit Morse messages using the distance between calls.
+
+ This is basically a pulse-distance modulation (PDM).
+
+ A RING is a pulse and the Morse symbols are encoded in the pause between
+ the first ring of the previous call and the first ring of a new call.
+
+ This strategy is very slow but it can even be used with ancient analog
+ modems which don't have call progress notifications for outgoing calls, and
+ make it also hard to count multiple rings in the same call on the receiving
+ side because there is no explicit notification on the receiving side of
+ when the caller ends a calls.
+
+ For GSM modems, which have a more sophisticate call report signalling,
+ a more efficient encoding can be used (for example using ring counts to
+ encode Morse symbols, i.e. a pulse-length modulation), but for
+ a proof-of-concept, a slow encoding covering the most general setup is
+ fine.
+
+ Plus, supporting communications between analog modems is cool :)
+
+ """
+
+ def __init__(self, modem,
+ call_setup_average=8.5, call_setup_uncertainty=1.5,
+ ring_distance_average=5, ring_uncertainty=0.2):
+ """Encode the Morse symbols using the distance between calls.
+
+ Args:
+ call_setup_average: the time between when the transmitter dials
+ the number and the receiver reports RING notifications. This is
+ needed in order to be sure than the receiver has received
+ _at_least_ one ring. The default chosen here should work fine
+ even with old analog modems, provided that there is some more
+ distance between two calls.
+ call_setup_uncertainty: uncertainty of call_setup_average,
+ a tolerance value
+ ring_distance_average: the time between two consecutive RINGs
+ in the same call, the standard interval is about 5 seconds, but
+ some tolerance is needed to account for line delays.
+ This is needed in order to ignore rings in the same call, and
+ differentiate two different calls.
+ ring_uncertainty: uncertainty of ring_distance_average, a tolerance
+ value
+ """
+
+ self.modem = modem
+ self.translator = MorseTranslator()
+
+ self.destination_number = ""
+
+ self.call_setup_time = call_setup_average + call_setup_uncertainty
+ self.rings_distance = ring_distance_average + ring_uncertainty
+
+ # In theory the symbol distance, the distance between calls which
+ # represent symbols, can be arbitrary, but in practice it's better to
+ # wait at least the duration of a ring between terminating one call and
+ # initiating the next call, as pick-up and hang-up can take some time
+ # with old analog modems.
+ symbol_distance = self.rings_distance
+
+ def symbol_time(multiplier):
+ return self.call_setup_time + symbol_distance * multiplier
+
+ self.dot_time = symbol_time(1)
+ self.dash_time = symbol_time(2)
+ self.signalspace_time = symbol_time(3)
+ self.wordspace_time = symbol_time(4)
+ self.eom_time = symbol_time(5)
+
+ self.ring_uncertainty = ring_uncertainty
+ self.symbol_uncertainty = symbol_distance / 2.
+
+ logging.debug("dot time: transmit: %.2f receive: (%.2f, %.2f)",
+ self.dot_time,
+ (self.dot_time - self.symbol_uncertainty),
+ (self.dot_time + self.symbol_uncertainty))
+ logging.debug("dash time: transmit: %.2f receive: (%.2f, %.2f)",
+ self.dash_time,
+ (self.dash_time - self.symbol_uncertainty),
+ (self.dash_time + self.symbol_uncertainty))
+ logging.debug("signalspace time: transmit: %.2f receive: (%.2f, %.2f)",
+ self.signalspace_time,
+ (self.signalspace_time - self.symbol_uncertainty),
+ (self.signalspace_time + self.symbol_uncertainty))
+ logging.debug("wordspace time: transmit: %.2f receive: (%.2f, %.2f)",
+ self.wordspace_time,
+ (self.wordspace_time - self.symbol_uncertainty),
+ (self.wordspace_time + self.symbol_uncertainty))
+ logging.debug("EOM time: transmit: %.2f receive: (%.2f, +inf)",
+ self.eom_time,
+ (self.eom_time - self.symbol_uncertainty))
+
+ self.previous_ring_time = -1
+ self.previous_call_time = -1
+
+ self.morse_message = ""
+ self.text_message = ""
+
+ self.end_of_message = False
+
+ def log_symbol(self, distance, symbol, extra_info=""):
+ logging.info("distance: %.2f Received \"%s\" %s", distance, symbol,
+ extra_info)
+
+ def receive_character(self):
+ current_ring_time = time.time()
+
+ if self.previous_ring_time == -1:
+ self.previous_ring_time = current_ring_time
+ self.previous_call_time = current_ring_time
+ self.log_symbol(0, "", "(The very first ring)")
+ return
+ else:
+ ring_distance = current_ring_time - self.previous_ring_time
+ logging.debug("RINGs distance: %.2f", ring_distance)
+ self.previous_ring_time = current_ring_time
+
+ # Ignore multiple rings in the same call
+ if abs(ring_distance - self.rings_distance) < self.ring_uncertainty:
+ logging.debug("multiple rings in the same call, distance: %.2f",
+ ring_distance)
+ return
+
+ call_distance = current_ring_time - self.previous_call_time
+ self.previous_call_time = current_ring_time
+
+ if abs(call_distance - self.dot_time) < self.symbol_uncertainty:
+ self.log_symbol(call_distance, '.')
+ self.morse_message += "."
+ return
+
+ if abs(call_distance - self.dash_time) < self.symbol_uncertainty:
+ self.log_symbol(call_distance, '-')
+ self.morse_message += "-"
+ return
+
+ if abs(call_distance - self.signalspace_time) < self.symbol_uncertainty:
+ signal = self.morse_message.strip().split(' ')[-1]
+ character = self.translator.signal_to_character(signal)
+ self.log_symbol(call_distance, ' ', "got \"%s\"" % character)
+ self.morse_message += " "
+ return
+
+ if abs(call_distance - self.wordspace_time) < self.symbol_uncertainty:
+ signal = self.morse_message.strip().split(' ')[-1]
+ character = self.translator.signal_to_character(signal)
+ self.log_symbol(call_distance, '/', "got \"%s\"" % character)
+ self.morse_message += " / "
+ return
+
+ if call_distance >= self.eom_time - self.symbol_uncertainty:
+ signal = self.morse_message.strip().split(' ')[-1]
+ character = self.translator.signal_to_character(signal)
+ self.log_symbol(call_distance, 'EOM', "got \"%s\"" % character)
+ self.end_of_message = True
+ self.previous_ring_time = -1
+ self.previous_call_time = -1
+ return
+
+ # if the code made it up to here, something fishy is going on
+ logging.error("Unexpected distance: %.2f", ring_distance)
+ logging.error("Check the transmitter and receiver parameters")
+
+ def receive_loop(self):
+ while not self.end_of_message:
+ self.modem.get_response("RING")
+ self.receive_character()
+ logging.debug("Current message: %s", self.morse_message)
+
+ self.end_of_message = False
+ self.text_message = self.translator.morse_to_text(self.morse_message)
+ self.morse_message = ""
+
+ def get_text(self):
+ return self.text_message
+
+ def transmit_symbol(self, symbol):
+ if symbol == ".":
+ sleep_time = self.dot_time
+ elif symbol == "-":
+ sleep_time = self.dash_time
+ elif symbol == " ":
+ sleep_time = self.signalspace_time
+ elif symbol == "/":
+ sleep_time = self.wordspace_time
+ elif symbol == "EOM":
+ sleep_time = self.eom_time
+ elif symbol is None:
+ # To terminate the transmission just call and hangup, with no extra
+ # distance
+ sleep_time = self.call_setup_time
+
+ logging.info("Dial and wait %.2f seconds (transmitting '%s')",
+ sleep_time, symbol)
+
+ # Dial, then wait self.call_setup_time to make sure the receiver gets
+ # at least one RING, and then hangup and sleep the remaining time
+ self.modem.send_command("ATDT" + self.destination_number)
+ time.sleep(self.call_setup_time)
+ self.modem.send_command("ATH")
+ self.modem.get_response()
+ time.sleep(sleep_time - self.call_setup_time)
+
+ def transmit_signal(self, signal):
+ logging.debug("Transmitting signal: %s", signal)
+ for symbol in signal:
+ self.transmit_symbol(symbol)
+
+ def transmit(self, message, destination_number):
+ self.destination_number = destination_number
+
+ morse_message = self.translator.text_to_morse(message)
+ signals = morse_message.split()
+ print(signals)
+
+ logging.debug("Starting the transmission")
+ for i, signal in enumerate(signals):
+ logging.debug("Transmitting '%s' as '%s'", message[i], signal)
+ self.transmit_signal(signal)
+
+ # Transmit a signal separator only when strictly necessary:
+ # - after the last symbol, we are going to transmit an EOM
+ # anyway, and that will mark the end of the last symbol.
+ # - between words the word separator act as a symbol separator
+ # too.
+ if i != len(signals) - 1 and signals[i + 1] != "/" and signal != "/":
+ self.transmit_symbol(" ")
+
+ self.transmit_symbol("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.
+ self.transmit_symbol(None)
+
+ def estimate_transmit_duration(self, message):
+ morsemessage = self.translator.text_to_morse(message)
+ signals = morsemessage.split()
+
+ logging.debug(signals)
+
+ transmitting_time = 0
+ for i, signal in enumerate(signals):
+ logging.debug("signal: %s", signal)
+
+ for symbol in signal:
+ if symbol == ".":
+ transmitting_time += self.dot_time
+ elif symbol == "-":
+ transmitting_time += self.dash_time
+ elif symbol == "/":
+ transmitting_time += self.wordspace_time
+
+ if i != len(signals) - 1 and signals[i + 1] != "/" and symbol != "/":
+ transmitting_time += self.signalspace_time
+
+ transmitting_time += self.eom_time
+
+ logging.debug("Estimated transmitting time: %d seconds",
+ transmitting_time)
+
+
+def test_send_receive():
+ logging.basicConfig(level=logging.DEBUG)
+ call_setup_time = 2
+ call_setup_uncertainty = 0.4
+ ring_time = 1
+ ring_uncertainty = 0.3
+
+ class DummyModem(object):
+ """Always receive a '.' and then a '/', which result in 'E '."""
+
+ def __init__(self):
+ self.ring_count = 0
+
+ def send_command(self, command):
+ pass
+
+ def get_response(self, response):
+ if self.ring_count % 2:
+ # received a '.'
+ time.sleep(call_setup_time + (ring_time + ring_uncertainty))
+ else:
+ # received a '/'
+ time.sleep(call_setup_time + (ring_time + ring_uncertainty) * 4)
+
+ self.ring_count += 1
+
+ xcv = CallDistanceTransceiver(DummyModem(), call_setup_time,
+ call_setup_uncertainty, ring_time,
+ ring_uncertainty)
+ xcv.receive_loop()
+
+
+if __name__ == "__main__":
+ test_send_receive()
--- /dev/null
+#!/usr/bin/env python3
+#
+# Modem - encapsulate serial modem functionalities
+#
+# Copyright (C) 2015 Antonio Ospite <ao2@ao2.it>
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+import logging
+import re
+import serial
+
+
+class Modem(object):
+ def __init__(self, serialport, baudrate=9600):
+ self.timeout = 0.1
+ self.init_commands = ["Z", "X3"]
+
+ self.callbacks = {}
+
+ self.open(serialport, baudrate)
+
+ @staticmethod
+ def scan():
+ """Scan for available hardware serial ports.
+
+ The code does not scan for virtual serial ports (e.g. /dev/ttyACM*,
+ /dev/ttyUSB* on linux).
+
+ Return:
+ a dict {portname: num}
+ """
+ available = {}
+ for i in range(256):
+ try:
+ port = serial.Serial(i)
+ available[port.portstr] = i
+ port.close()
+ except (OSError, serial.SerialException):
+ pass
+ return available
+
+ def open(self, port, baudrate):
+ self.serial = serial.Serial(port, baudrate=baudrate,
+ timeout=self.timeout,
+ rtscts=0, xonxoff=0)
+ self.reset()
+
+ def close(self):
+ self.reset()
+ self.serial.close()
+
+ def reset(self):
+ for command in self.init_commands:
+ self.send_command(command)
+ self.get_response("OK")
+
+ def flush(self):
+ self.serial.flushInput()
+ self.serial.flushOutput()
+
+ def convert_cr_lf(self, command):
+ if command.endswith("\r\n"):
+ pass
+ elif command.endswith("\n"):
+ command = command[:-1] + "\r\n"
+ elif command.endswith("\r"):
+ command += "\n"
+ else:
+ command += "\r\n"
+
+ return command
+
+ def send_command(self, command):
+ if command.upper().startswith("AT"):
+ command = command[2:]
+
+ command = "AT" + command
+ command = self.convert_cr_lf(command)
+ logging.debug("AT command: %s", repr(command))
+
+ self.flush()
+ self.serial.write(bytes(command, 'UTF-8'))
+
+ def get_response(self, expected=None):
+ std_responses = ["OK", "RING", "ERROR", "BUSY", "NO CARRIER",
+ "NO DIALTONE"]
+
+ resultlist = []
+ line = ""
+ while 1:
+ data = self.serial.read(1)
+ if len(data):
+ line += data.decode('UTF-8')
+ if line.endswith("\r\n"):
+ # strip '\r', '\n' and other spaces
+ line = re.sub(r"\s+$", "", line)
+ line = re.sub(r"^\s+", "", line)
+
+ if len(line):
+ resultlist.append(line)
+
+ # Only react on std_responses when NOT asked explicitly
+ # for an expected response
+ if (expected and (line == expected)) or \
+ (not expected and (line in std_responses)):
+ if line in self.callbacks:
+ func = self.callbacks[line]
+ func()
+ return resultlist
+
+ line = ""
+
+ def get_response_loop(self):
+ while 1:
+ self.get_response()
+
+ def register_callback(self, response, callback_func):
+ self.callbacks[response] = callback_func
+
+
+def test(port, number):
+ logging.basicConfig(level=logging.DEBUG)
+
+ def on_ok():
+ print("Received an OK")
+
+ def on_ring():
+ print("Phone Ringing")
+
+ import time
+
+ def test_send(port, number):
+ modem = Modem(port)
+
+ modem.register_callback("OK", on_ok)
+
+ time1 = 0
+ time2 = 0
+
+ for i in range(3):
+ time_difference = time2 - time1
+ print("time difference: %.2f" % time_difference)
+ time1 = time.time()
+ modem.send_command("ATDT" + number)
+
+ call_setup_time = 9
+ inter_call_sleep = 5
+
+ print("sleep %.2f" % call_setup_time)
+ time.sleep(call_setup_time)
+
+ modem.send_command("ATH")
+ modem.get_response()
+
+ print("sleep %.2f" % inter_call_sleep)
+ time.sleep(inter_call_sleep)
+
+ time2 = time.time()
+
+ def test_receive(port):
+ modem = Modem(port)
+
+ modem.register_callback("OK", on_ok)
+
+ modem.send_command("I1")
+ response = modem.get_response()
+ print("response:", response)
+
+ modem.register_callback("RING", on_ring)
+ modem.get_response_loop()
+
+ test_send(port, number)
+ test_receive(port)
+
+
+if __name__ == "__main__":
+ import sys
+ print(Modem.scan())
+ if len(sys.argv) != 3:
+ print("usage: %s <serial port> <destination number>" % sys.argv[0])
+ sys.exit(1)
+ test(sys.argv[1], sys.argv[2])
--- /dev/null
+#!/usr/bin/env python3
+#
+# MorseTranslator - translate to and from Morse code
+#
+# Copyright (C) 2015 Antonio Ospite <ao2@ao2.it>
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+import re
+
+
+class MorseTranslator(object):
+ """International Morse Code translator.
+
+ The specification of the International Morse Code is in ITU-R M.1677-1
+ (10/2009), Annex 1.
+
+ The terminology used here may differ from the one used in some other
+ places, so here is some nomenclature:
+
+ symbol: one of . (dot), - (dash), ' ' (signal separator),
+ '/' (word separator)
+
+ character: a letter of the alphabet, a number, a punctuation mark, or
+ a ' ' (text word separator)
+
+ signal: a sequence of . and - symbols which encode a character,
+ or a '/' (Morse word separator)
+
+ word: a sequence of characters not containing a ' ', or
+ a sequence of signals not containing a '/'
+
+ text: a sequence of characters
+
+ morse: a sequence of signals separated by ' '
+
+ NOTE:
+ signals are separated by a ' ' (signal separator), characters are not
+ separated one from the other.
+
+ This class defines a subset of the signals in Section 1 of the
+ aforementioned specification, plus a word space, and it does not make
+ assumptions about their actual transmission.
+ """
+
+ def __init__(self):
+ self.signals_table = {}
+ self.characters_table = {}
+
+ # XXX the current code only handles single characters,
+ # so prosigns are not added to the tables below
+
+ # Letters
+ self.signals_table['a'] = ".-"
+ self.signals_table['b'] = "-..."
+ self.signals_table['c'] = "-.-."
+ self.signals_table['d'] = "-.."
+ self.signals_table['e'] = "."
+ self.signals_table['f'] = "..-."
+ self.signals_table['g'] = "--."
+ self.signals_table['h'] = "...."
+ self.signals_table['i'] = ".."
+ self.signals_table['j'] = ".---"
+ self.signals_table['k'] = "-.-"
+ self.signals_table['l'] = ".-.."
+ self.signals_table['m'] = "--"
+ self.signals_table['n'] = "-."
+ self.signals_table['o'] = "---"
+ self.signals_table['p'] = ".--."
+ self.signals_table['q'] = "--.-"
+ self.signals_table['r'] = ".-."
+ self.signals_table['s'] = "..."
+ self.signals_table['t'] = "-"
+ self.signals_table['u'] = "..-"
+ self.signals_table['v'] = "...-"
+ self.signals_table['w'] = ".--"
+ self.signals_table['x'] = "-..-"
+ self.signals_table['y'] = "-.--"
+ self.signals_table['z'] = "--.."
+ # Figures
+ self.signals_table['1'] = ".----"
+ self.signals_table['2'] = "..---"
+ self.signals_table['3'] = "...--"
+ self.signals_table['4'] = "....-"
+ self.signals_table['5'] = "....."
+ self.signals_table['6'] = "-...."
+ self.signals_table['7'] = "--..."
+ self.signals_table['8'] = "---.."
+ self.signals_table['9'] = "----."
+ self.signals_table['0'] = "-----"
+ # Punctuation marks and miscellaneous signs
+ self.signals_table['.'] = ".-.-.-"
+ self.signals_table[','] = "--..--"
+ self.signals_table[':'] = "---..."
+ self.signals_table['?'] = "..--.."
+ self.signals_table['\''] = ".----."
+ self.signals_table['-'] = "-....-"
+ self.signals_table['/'] = "-..-."
+ self.signals_table['('] = "-.--."
+ self.signals_table[')'] = "-.--.-"
+ self.signals_table['"'] = ".-..-."
+ self.signals_table['='] = "-...-"
+ self.signals_table['+'] = ".-.-."
+ self.signals_table['x'] = "-..-"
+ self.signals_table['@'] = ".--.-."
+
+ # Represent the word space as a signal with only one "/" symbol
+ self.signals_table[' '] = "/"
+
+ for key, value in self.signals_table.items():
+ self.characters_table[value] = key
+
+ def stats(self):
+ signal_length_sum = 0
+ for signal in self.signals_table.values():
+ signal_length_sum += len(signal)
+
+ average_signal_length = signal_length_sum / len(self.signals_table)
+
+ character_length_sum = 0
+ for character in self.characters_table.values():
+ character_length_sum += len(character)
+
+ average_char_length = character_length_sum / len(self.characters_table)
+
+ return average_signal_length, average_char_length
+
+ def sanitize_text(self, text):
+ sanitized = text.lower()
+ sanitized = re.sub(r"[^a-z0-9.,?\'\"/() \-=\+@]", "", sanitized)
+ sanitized = re.sub(r"\s+", " ", sanitized)
+ sanitized = re.sub(r"^\s+", "", sanitized)
+ sanitized = re.sub(r"\s+$", "", sanitized)
+ return sanitized
+
+ def char_to_signal(self, character):
+ char = character.lower()
+ if char in self.signals_table:
+ return self.signals_table[char]
+ else:
+ return ""
+
+ def text_to_morse(self, text, sanitize=True):
+ if sanitize:
+ text = self.sanitize_text(text)
+
+ signal = [self.char_to_signal(c) for c in text]
+ return str(" ").join(signal)
+
+ def sanitize_morse(self, morse):
+ sanitized = re.sub("_", "-", morse)
+ sanitized = re.sub(r"[^\-\.\/]", " ", sanitized)
+ sanitized = re.sub(r"\|", "/", sanitized)
+ sanitized = re.sub(r"\s+", " ", sanitized)
+ sanitized = re.sub(r"( ?/ ?)+", " / ", sanitized)
+ sanitized = re.sub(r"^[ /]+", "", sanitized)
+ sanitized = re.sub(r"[ /]+$", "", sanitized)
+ return sanitized
+
+ def signal_to_character(self, signal):
+ if signal in self.characters_table:
+ return self.characters_table[signal]
+ else:
+ return '*'
+
+ def morse_to_text(self, morse, sanitize=True):
+ if sanitize:
+ morse = self.sanitize_morse(morse)
+
+ signals = morse.split()
+ characters = [self.signal_to_character(signal) for signal in signals]
+ return str('').join(characters)
+
+
+def test():
+ translator = MorseTranslator()
+ avg_signal_length, avg_character_length = translator.stats()
+ print("Average signal length:", avg_signal_length)
+ print("Average character length:", avg_character_length)
+
+ text = "Hello, I am just some text."
+
+ print(text)
+
+ morse = translator.text_to_morse(text)
+ print(morse)
+
+ text = translator.morse_to_text(morse)
+ print(text)
+
+ print("\n\nTesting sanitizing functions")
+
+ print()
+ dirty_text = ' < >Hello::## this is dirty^^%% text! '
+ print(dirty_text)
+ print(translator.sanitize_text(dirty_text))
+ print(translator.text_to_morse(translator.sanitize_text(dirty_text)))
+
+ print()
+ dirty_morse = ' 009 .... . ._.. .-.. --- /34// / // // - .... .. ...' + \
+ ' / .. ... / -.. .. .-. - -.-- / - . -..- _ '
+ print(dirty_morse)
+ print(translator.sanitize_morse(dirty_morse))
+ print(translator.morse_to_text(translator.sanitize_morse(dirty_morse)))
+
+ print("\n\nTesting conversion on unsanitized strings")
+ print(dirty_text)
+ print(translator.text_to_morse(dirty_text))
+ print(translator.morse_to_text(translator.text_to_morse(dirty_text)))
+
+ print(dirty_morse)
+ print(translator.morse_to_text(dirty_morse))
+
+
+if __name__ == "__main__":
+ test()
--- /dev/null
+#!/usr/bin/env python3
+#
+# cumulative_average - compute the cumulative average
+#
+# Copyright (C) 2015 Antonio Ospite <ao2@ao2.it>
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+
+def cumulative_average(old_average, n_samples, new_sample):
+ return ((old_average * (n_samples - 1)) + new_sample) / n_samples
+
+
+def test():
+ values = [1, 2, 3, 4, 5, 6]
+ average = 0
+ for i, value in enumerate(values):
+ average = cumulative_average(average, i + 1, value)
+ print(average)
+
+if __name__ == "__main__":
+ test()
--- /dev/null
+#!/usr/bin/env python3
+#
+# SaveMySugar - transmit messages using Morse code via phone rings
+#
+# Copyright (C) 2015 Antonio Ospite <ao2@ao2.it>
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+from savemysugar.CallDistanceTransceiver import CallDistanceTransceiver
+from savemysugar.Modem import Modem
+import logging
+
+# TODO: set the logging level from the command line
+FORMAT = "%(created)f %(levelname)s:%(funcName)s %(message)s"
+logging.basicConfig(level=logging.DEBUG, format=FORMAT)
+
+
+def transmit(port, number, message):
+ modem = Modem(port)
+
+ transmitter = CallDistanceTransceiver(modem)
+ transmitter.estimate_transmit_duration(message)
+ transmitter.transmit(message, number)
+
+
+def main():
+ print("Available hardware serial ports:")
+ for port in Modem.scan():
+ print(" - " + port)
+ print()
+
+ import sys
+ if len(sys.argv) != 4:
+ print("usage: %s" % sys.argv[0],
+ "<serial port> <destination number> <message>")
+ sys.exit(1)
+ transmit(sys.argv[1], sys.argv[2], sys.argv[3])
+
+
+if __name__ == "__main__":
+ main()