From: Antonio Ospite Date: Thu, 10 Dec 2015 21:31:04 +0000 (+0100) Subject: Initial import X-Git-Url: https://git.ao2.it/SaveMySugar/python3-savemysugar.git/commitdiff_plain/88fce7811221c360966eb3af691a719930a2514d?ds=inline Initial import --- 88fce7811221c360966eb3af691a719930a2514d diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b1854c9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +__pycache__ +_test*.sh diff --git a/README.md b/README.md new file mode 100644 index 0000000..947bd19 --- /dev/null +++ b/README.md @@ -0,0 +1,52 @@ +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 starting from an idea by Corrado Rubera. diff --git a/TODO b/TODO new file mode 100644 index 0000000..9f26f6d --- /dev/null +++ b/TODO @@ -0,0 +1,7 @@ +- 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). diff --git a/logs/codex_receive.log b/logs/codex_receive.log new file mode 100644 index 0000000..bdc87a7 --- /dev/null +++ b/logs/codex_receive.log @@ -0,0 +1,72 @@ +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 diff --git a/logs/codex_transmit.log b/logs/codex_transmit.log new file mode 100644 index 0000000..f52bed2 --- /dev/null +++ b/logs/codex_transmit.log @@ -0,0 +1,96 @@ +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' diff --git a/logs/measure_call_setup_time_PSTN-to-GSM.log b/logs/measure_call_setup_time_PSTN-to-GSM.log new file mode 100644 index 0000000..8a9cc81 --- /dev/null +++ b/logs/measure_call_setup_time_PSTN-to-GSM.log @@ -0,0 +1,68 @@ +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 diff --git a/logs/measure_ring_distance_PSTN-to-GSM.log b/logs/measure_ring_distance_PSTN-to-GSM.log new file mode 100644 index 0000000..c4878e5 --- /dev/null +++ b/logs/measure_ring_distance_PSTN-to-GSM.log @@ -0,0 +1,64 @@ +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 diff --git a/plot/call_distance_modulation.gnuplot b/plot/call_distance_modulation.gnuplot new file mode 100755 index 0000000..207eedb --- /dev/null +++ b/plot/call_distance_modulation.gnuplot @@ -0,0 +1,70 @@ +#!/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, \ diff --git a/plot/codex_receive.log b/plot/codex_receive.log new file mode 100644 index 0000000..05db781 --- /dev/null +++ b/plot/codex_receive.log @@ -0,0 +1,21 @@ +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" diff --git a/plot/codex_transmit.log b/plot/codex_transmit.log new file mode 100644 index 0000000..254c1cf --- /dev/null +++ b/plot/codex_transmit.log @@ -0,0 +1,21 @@ +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') diff --git a/plot/savemysugar_receive.log b/plot/savemysugar_receive.log new file mode 100644 index 0000000..18e7843 --- /dev/null +++ b/plot/savemysugar_receive.log @@ -0,0 +1,42 @@ +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" diff --git a/plot/savemysugar_transmit.log b/plot/savemysugar_transmit.log new file mode 100644 index 0000000..6f2e32b --- /dev/null +++ b/plot/savemysugar_transmit.log @@ -0,0 +1,42 @@ +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') diff --git a/src/measure_call_setup_time.py b/src/measure_call_setup_time.py new file mode 100755 index 0000000..6268145 --- /dev/null +++ b/src/measure_call_setup_time.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python3 +# +# measure_call_setup_time - measure time between dialing out and ringing in +# +# 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 . + +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], + " ", + "") + sys.exit(1) + + measure_call_setup_time(sys.argv[1], sys.argv[2], sys.argv[3]) + + +if __name__ == "__main__": + main() diff --git a/src/measure_ring_distance.py b/src/measure_ring_distance.py new file mode 100755 index 0000000..87f6da3 --- /dev/null +++ b/src/measure_ring_distance.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 +# +# measure_ring_distance - measure the time between two rings in the same call +# +# 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 . + +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], + " ", + "") + sys.exit(1) + + measure_ring_distance(sys.argv[1], sys.argv[2], sys.argv[3]) + + +if __name__ == "__main__": + main() diff --git a/src/receive.py b/src/receive.py new file mode 100755 index 0000000..042ec63 --- /dev/null +++ b/src/receive.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 +# +# SaveMySugar - receive messages using Morse code via phone rings +# +# 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 . + +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 " % sys.argv[0]) + sys.exit(1) + + receive(sys.argv[1]) + + +if __name__ == "__main__": + main() diff --git a/src/savemysugar/CallDistanceTransceiver.py b/src/savemysugar/CallDistanceTransceiver.py new file mode 100755 index 0000000..fd60a1f --- /dev/null +++ b/src/savemysugar/CallDistanceTransceiver.py @@ -0,0 +1,329 @@ +#!/usr/bin/env python3 +# +# CallDistanceTransceiver - send and receive Morse using phone calls distance +# +# 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 . + + +# 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() diff --git a/src/savemysugar/Modem.py b/src/savemysugar/Modem.py new file mode 100755 index 0000000..e510382 --- /dev/null +++ b/src/savemysugar/Modem.py @@ -0,0 +1,194 @@ +#!/usr/bin/env python3 +# +# Modem - encapsulate serial modem functionalities +# +# 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 . + +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 " % sys.argv[0]) + sys.exit(1) + test(sys.argv[1], sys.argv[2]) diff --git a/src/savemysugar/MorseTranslator.py b/src/savemysugar/MorseTranslator.py new file mode 100755 index 0000000..8184829 --- /dev/null +++ b/src/savemysugar/MorseTranslator.py @@ -0,0 +1,227 @@ +#!/usr/bin/env python3 +# +# MorseTranslator - translate to and from Morse code +# +# 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 . + +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() diff --git a/src/savemysugar/__init__.py b/src/savemysugar/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/src/savemysugar/cumulative_average.py b/src/savemysugar/cumulative_average.py new file mode 100755 index 0000000..7200eb0 --- /dev/null +++ b/src/savemysugar/cumulative_average.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 +# +# cumulative_average - compute the cumulative average +# +# 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 . + + +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() diff --git a/src/transmit.py b/src/transmit.py new file mode 100755 index 0000000..2e8ef4c --- /dev/null +++ b/src/transmit.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 +# +# SaveMySugar - transmit messages using Morse code via phone rings +# +# 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 . + +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], + " ") + sys.exit(1) + transmit(sys.argv[1], sys.argv[2], sys.argv[3]) + + +if __name__ == "__main__": + main()