├── requirements.txt ├── .DS_Store ├── TODO ├── m32-chat-server.code-workspace ├── __pycache__ ├── mopp.cpython-312.pyc └── config.cpython-312.pyc ├── README.md ├── .gitignore ├── Dockerfile ├── config.py ├── udp_client_transmitter.py ├── udp_client_receiver.py ├── mqtt_client_receiver.py ├── LICENSE ├── mqtt_client_transmitter.py ├── udp2mqtt_relais.py ├── beep.py ├── .github └── workflows │ └── docker.yml ├── udp_client_console.py ├── udp_chat_server.py └── mopp.py /requirements.txt: -------------------------------------------------------------------------------- 1 | paho-mqtt -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sp9wpn/m32_chat_server/HEAD/.DS_Store -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | - Split repos? 2 | - Merge with git@github.com:Morse-Code-over-IP/mopp.git 3 | -------------------------------------------------------------------------------- /m32-chat-server.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | } 6 | ] 7 | } -------------------------------------------------------------------------------- /__pycache__/mopp.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sp9wpn/m32_chat_server/HEAD/__pycache__/mopp.cpython-312.pyc -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Warning! This repo is obsolete. 2 | Please follow to https://github.com/Morse-Code-over-IP/chatserver-mopp-udp 3 | -------------------------------------------------------------------------------- /__pycache__/config.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sp9wpn/m32_chat_server/HEAD/__pycache__/config.cpython-312.pyc -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/mopp.cpython-311.pyc 2 | __pycache__/beep.cpython-311.pyc 3 | hivemq.py 4 | __pycache__/hivemq.cpython-311.pyc 5 | __pycache__/config.cpython-311.pyc 6 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3 2 | 3 | WORKDIR /usr/src/app 4 | COPY udp_chat_server.py ./ 5 | COPY mopp.py ./ 6 | COPY config.py ./ 7 | 8 | EXPOSE 7373/udp 9 | 10 | CMD [ "python3", "./udp_chat_server.py"] -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | # Configuration for UDP connections 2 | SERVER_IP = "0.0.0.0" 3 | UDP_PORT = 7373 4 | 5 | # Configuration for Chat Server 6 | CLIENT_TIMEOUT = 3000 7 | MAX_CLIENTS = 100 8 | KEEPALIVE = 10 9 | CHAT_WPM = 20 10 | 11 | # MQTT configuration 12 | MQTT_HOST = "broker.hivemq.com" 13 | MQTT_PORT = 1883 -------------------------------------------------------------------------------- /udp_client_transmitter.py: -------------------------------------------------------------------------------- 1 | # Simple transmitter 2 | import socket 3 | import logging 4 | from mopp import * 5 | import config 6 | 7 | logging.basicConfig(level=logging.DEBUG, format='%(message)s', ) 8 | 9 | mopp = Mopp() 10 | 11 | client_socket = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) 12 | client_socket.connect((config.SERVER_IP, config.UDP_PORT)) # connect to the server 13 | 14 | client_socket.send(mopp.mopp(20,'hi')) 15 | 16 | client_socket.send(mopp.mopp(30,' hello world. this is a small test.')) 17 | client_socket.send(mopp.mopp(10,'ok')) 18 | -------------------------------------------------------------------------------- /udp_client_receiver.py: -------------------------------------------------------------------------------- 1 | # Simple receiver 2 | 3 | import socket 4 | import logging 5 | import time 6 | from mopp import * 7 | from beep import * 8 | import config 9 | 10 | logging.basicConfig(level=logging.DEBUG, format='%(message)s', ) 11 | 12 | 13 | mopp = Mopp() 14 | 15 | client_socket = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) 16 | client_socket.connect((config.SERVER_IP, config.UDP_PORT)) # connect to the server 17 | 18 | client_socket.send(mopp.mopp(20,'hi')) 19 | 20 | last_r = {} # keep track of duplicate messages... 21 | 22 | while KeyboardInterrupt: 23 | time.sleep(0.2) # anti flood 24 | try: 25 | data_bytes, addr = client_socket.recvfrom(64) 26 | client = addr[0] + ':' + str(addr[1]) 27 | r = mopp.decode_message(data_bytes) 28 | print (r) 29 | 30 | # Beep if message received 31 | if not "Keepalive" in r: 32 | b = Beep(speed=r["Speed"]) 33 | if not last_r == r: 34 | b.beep_message(r["Message"]) 35 | last_r = r 36 | 37 | 38 | 39 | except (KeyboardInterrupt, SystemExit): 40 | client_socket.close() 41 | break 42 | pass 43 | -------------------------------------------------------------------------------- /mqtt_client_receiver.py: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/python3 2 | # Subscriber for MQTT with Morse Code Beep 3 | 4 | import paho.mqtt.client as paho 5 | import config 6 | 7 | from mopp import * 8 | from beep import * 9 | 10 | mopp = Mopp() 11 | 12 | def on_connect(mqttc, obj, flags, rc): 13 | print("rc: " + str(rc)) 14 | 15 | def on_message(mqttc, obj, msg): 16 | print(msg.topic + " " + str(msg.qos) + " " + str(msg.payload)) 17 | 18 | r = mopp.decode_message(msg.payload) 19 | print (r) 20 | 21 | # Beep if message received 22 | if not "Keepalive" in r: 23 | b = Beep(speed=r["Speed"]) 24 | b.beep_message(r["Message"]) 25 | 26 | def on_publish(mqttc, obj, mid): 27 | print("mid: " + str(mid)) 28 | 29 | def on_subscribe(mqttc, obj, mid, granted_qos): 30 | print("Subscribed: " + str(mid) + " " + str(granted_qos)) 31 | 32 | def on_log(mqttc, obj, level, string): 33 | print(string) 34 | 35 | 36 | mqttc = paho.Client() 37 | 38 | mqttc.on_message = on_message 39 | mqttc.on_connect = on_connect 40 | mqttc.on_publish = on_publish 41 | mqttc.on_subscribe = on_subscribe 42 | 43 | mqttc.connect(config.MQTT_HOST, config.MQTT_PORT, 60) 44 | mqttc.subscribe("m32_test", 0) 45 | 46 | mqttc.loop_forever() 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /mqtt_client_transmitter.py: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/python3 2 | # Simple transmitter 3 | 4 | import paho.mqtt.client as paho 5 | import logging 6 | from mopp import * 7 | import config 8 | 9 | logging.basicConfig(level=logging.DEBUG, format='%(message)s', ) 10 | 11 | mopp = Mopp() 12 | 13 | 14 | 15 | def on_connect(mqttc, obj, flags, rc): 16 | print("rc: " + str(rc)) 17 | 18 | def on_message(mqttc, obj, msg): 19 | print(msg.topic + " " + str(msg.qos) + " " + str(msg.payload)) 20 | 21 | r = mopp.decode_message(msg.payload) 22 | print (r) 23 | 24 | def on_publish(mqttc, obj, mid): 25 | print("mid: " + str(mid)) 26 | pass 27 | 28 | def on_subscribe(mqttc, obj, mid, granted_qos): 29 | print("Subscribed: " + str(mid) + " " + str(granted_qos)) 30 | 31 | def on_log(mqttc, obj, level, string): 32 | print(string) 33 | 34 | 35 | mqttc = paho.Client() 36 | 37 | mqttc.on_message = on_message 38 | mqttc.on_connect = on_connect 39 | mqttc.on_publish = on_publish 40 | mqttc.on_subscribe = on_subscribe 41 | 42 | mqttc.connect(config.MQTT_HOST, config.MQTT_PORT, 60) 43 | mqttc.loop_start() 44 | 45 | infot = mqttc.publish("m32_test", mopp.mopp(20,'hi'), qos=2) 46 | infot = mqttc.publish("m32_test", mopp.mopp(30,' hello world. this is a small test.'), qos=2) 47 | 48 | infot.wait_for_publish() 49 | 50 | -------------------------------------------------------------------------------- /udp2mqtt_relais.py: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/python3 2 | # Receiver for UDP and publish to MQTT 3 | import socket 4 | import logging 5 | import time 6 | from mopp import * 7 | from paho import mqtt 8 | import paho.mqtt.client as paho 9 | import paho.mqtt.publish as publish 10 | import config 11 | 12 | logging.basicConfig(level=logging.DEBUG, format='%(message)s', ) 13 | 14 | mopp = Mopp() 15 | 16 | client_socket = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) 17 | client_socket.connect((config.SERVER_IP, config.UDP_PORT)) # connect to the server 18 | 19 | client_socket.send(mopp.mopp(20,'hi')) 20 | 21 | def on_subscribe(client, userdata, mid, granted_qos): 22 | print("Subscribed: "+str(mid)+" "+str(granted_qos)) 23 | 24 | def on_message(client, userdata, msg): 25 | print(msg.topic+" "+str(msg.qos)+" "+str(msg.payload)) 26 | 27 | 28 | last_r = {} # keep track of duplicate messages... 29 | 30 | while KeyboardInterrupt: 31 | time.sleep(0.2) # anti flood 32 | try: 33 | data_bytes, addr = client_socket.recvfrom(64) 34 | client = addr[0] + ':' + str(addr[1]) 35 | r = mopp.decode_message(data_bytes) 36 | 37 | # Publish if message received 38 | if not "Keepalive" in r: 39 | if not last_r == r: 40 | print (r) 41 | msgs = [{'topic': "m32_test", 'payload': data_bytes}] 42 | publish.multiple(msgs, hostname=config.MQTT_HOST, port=config.MQTT_PORT, protocol=paho.MQTTv31) 43 | last_r = r 44 | 45 | except (KeyboardInterrupt, SystemExit): 46 | client_socket.close() 47 | break 48 | pass 49 | -------------------------------------------------------------------------------- /beep.py: -------------------------------------------------------------------------------- 1 | import pygame, numpy, pygame.sndarray 2 | 3 | class Beep: 4 | def __init__(self, speed = 20): 5 | 6 | # Ref timings: https://morsecode.world/international/timing.html#:~:text=It's%20clear%20that%20this%20makes,%22)%20which%20also%20makes%20sense. 7 | speed_wpm = speed 8 | self.dit_duration = int(60 / (50*speed_wpm)*1000) 9 | self.dah_duration = 3*self.dit_duration 10 | self.eoc_duration = 3*self.dit_duration 11 | self.eow_duration = 7*self.dit_duration 12 | 13 | # Ref for sound: https://gist.github.com/nekozing/5774628 14 | sampleRate = 44100 15 | # 44.1kHz, 16-bit signed, mono 16 | pygame.mixer.pre_init(sampleRate, -16, 1) 17 | pygame.init() 18 | # 4096 : the peak ; volume ; loudness 19 | # 440 : the frequency in hz 20 | # ?not so sure? if astype int16 not specified sound will get very noisy, because we have defined it as 16 bit mixer at mixer.pre_init() 21 | # ( probably due to int overflow resulting in non continuous sound data) 22 | arr = numpy.array([4096 * numpy.sin(2.0 * numpy.pi * 440 * x / sampleRate) for x in range(0, sampleRate)]).astype(numpy.int16) 23 | self.sound = pygame.sndarray.make_sound(arr) 24 | 25 | def _beep(self, symbol): 26 | if symbol == ".": 27 | self.sound.play(-1) 28 | pygame.time.delay(self.dit_duration) 29 | self.sound.stop() 30 | pygame.time.delay(self.dit_duration) 31 | elif symbol == "-": 32 | self.sound.play(-1) 33 | pygame.time.delay(self.dah_duration) 34 | self.sound.stop() 35 | pygame.time.delay(self.dit_duration) 36 | elif symbol == "C": # EOC 37 | pygame.time.delay(self.eoc_duration) 38 | elif symbol == "W": # EOW 39 | pygame.time.delay(self.eow_duration) 40 | 41 | def beep_message(self, message): 42 | for s in message: 43 | self._beep (s) 44 | 45 | -------------------------------------------------------------------------------- /.github/workflows/docker.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. 2 | # They are provided by a third-party and are governed by 3 | # separate terms of service, privacy policy, and support 4 | # documentation. 5 | 6 | name: Create and publish a Docker image 7 | 8 | on: 9 | push: 10 | branches: 11 | - 'master' 12 | - 'main' 13 | - 'dev' 14 | 15 | tags: 16 | - 'v*' 17 | - 'v*.*' 18 | - 'v*.*.*' 19 | - '*' 20 | - '*.*' 21 | - '*.*.*' 22 | pull_request: 23 | branches: 24 | - 'main' 25 | - 'dev' 26 | 27 | 28 | env: 29 | REGISTRY: ghcr.io 30 | IMAGE_NAME: ${{ github.repository }} 31 | 32 | jobs: 33 | build-and-push-image: 34 | runs-on: ubuntu-latest 35 | permissions: 36 | contents: read 37 | packages: write 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v3 42 | 43 | - name: Set up QEMU 44 | uses: docker/setup-qemu-action@v2 45 | 46 | - name: Set up Docker Buildx 47 | uses: docker/setup-buildx-action@v2 48 | 49 | - name: Log in to the Container registry 50 | uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9 51 | with: 52 | registry: ${{ env.REGISTRY }} 53 | username: ${{ github.actor }} 54 | password: ${{ secrets.GITHUB_TOKEN }} 55 | 56 | - name: Extract metadata (tags, labels) for Docker 57 | id: meta 58 | uses: docker/metadata-action@v4 59 | with: 60 | images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 61 | tags: | 62 | type=semver,pattern={{version}} 63 | type=semver,pattern={{major}}.{{minor}} 64 | type=semver,pattern={{major}} 65 | type=sha 66 | type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'main') }} 67 | 68 | - name: Build and push Docker image 69 | uses: docker/build-push-action@v4 70 | with: 71 | context: . 72 | push: true 73 | platforms: linux/amd64,linux/arm64 74 | tags: ${{ steps.meta.outputs.tags }} 75 | labels: ${{ steps.meta.outputs.labels }} 76 | -------------------------------------------------------------------------------- /udp_client_console.py: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/python3 2 | # The MOPP Chat Client (UDP) 3 | 4 | import socket 5 | import time 6 | import logging 7 | from mopp import * 8 | import argparse 9 | import threading 10 | import os 11 | 12 | logging.basicConfig(level=logging.DEBUG, format='%(message)s', ) 13 | 14 | argparser = argparse.ArgumentParser(description='MOPP - IP/UDP console client') 15 | argparser.add_argument('server', help='Server IP or hostname') 16 | argparser.add_argument('port', help='Server UDP port (default: 7373)', nargs='?', type=int, default=7373) 17 | 18 | args=argparser.parse_args() 19 | 20 | sock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) 21 | sock.settimeout(0.2) 22 | 23 | speed = 20 24 | 25 | mopp = Mopp() 26 | 27 | def transmit (data): 28 | if len(data) > 0: 29 | sock.sendto(data, (socket.gethostbyname(args.server), args.port)) 30 | 31 | 32 | def inputThread(): 33 | global speed 34 | 35 | while True: 36 | i = input().strip() 37 | if i == '#quit': 38 | transmit(mopp.mopp(speed, ':bye')) 39 | print("Quitting...") 40 | os._exit(0) 41 | break 42 | 43 | if i[0:1] == '#': 44 | try: 45 | _speed = int(i[1:]) 46 | if (_speed >= 5 and _speed <= 60): 47 | speed = _speed 48 | print("Speed set to %d wpm." % speed) 49 | else: 50 | print("Allowed speeds from 5 to 60 wpm.") 51 | 52 | except: 53 | pass 54 | 55 | continue 56 | 57 | for word in i.split(' '): 58 | if word != '': 59 | # print("Transmitting (%d wpm): %s" % (speed,word)) 60 | transmit(mopp.mopp(speed, word)) 61 | 62 | 63 | 64 | print("### MOPP - IP/UDP console client") 65 | print("### Speed is set to %d wpm. To change, type: #" % speed) 66 | print("### To exit the program, use #quit") 67 | print("") 68 | 69 | # Login with hi 70 | transmit(mopp.mopp(speed, 'hi')) 71 | 72 | 73 | iThread = threading.Thread(target=inputThread) 74 | iThread.daemon = True 75 | iThread.start() 76 | 77 | 78 | while KeyboardInterrupt: 79 | try: 80 | data_bytes, addr = sock.recvfrom(64) 81 | try: 82 | if len(data_bytes) > 0: 83 | r = mopp.decode_message(data_bytes) 84 | print ("Received (%d wpm): %s" % (mopp.received_speed(data_bytes), r['Text'])) 85 | except: 86 | raise 87 | pass 88 | 89 | except (KeyboardInterrupt, SystemExit): 90 | transmit(mopp.mopp(speed, ':bye')) 91 | sock.close() 92 | break 93 | pass 94 | 95 | except socket.timeout: 96 | pass 97 | 98 | -------------------------------------------------------------------------------- /udp_chat_server.py: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/python3 2 | # The MOPP Chat Server (UDP) 3 | 4 | import socket 5 | import time 6 | import logging 7 | from mopp import * 8 | import config 9 | 10 | logging.basicConfig(level=logging.DEBUG, format='%(message)s', ) 11 | 12 | serversock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) 13 | serversock.bind((config.SERVER_IP, config.UDP_PORT)) 14 | serversock.settimeout(config.KEEPALIVE) 15 | 16 | receivers = {} 17 | mopp = Mopp() 18 | 19 | def transmit (data, ip, port): 20 | serversock.sendto(data, (ip, int(port))) 21 | 22 | def broadcast(data,client): 23 | for c in receivers.keys(): 24 | if c == client: 25 | continue 26 | logging.debug("Sending to %s" % c) 27 | ip,port = c.split(':') 28 | 29 | transmit (data, ip, port) 30 | 31 | def welcome(client, speed): 32 | ip,port = client.split(':') 33 | welcome_msg = ':hi '+str(len(receivers)) 34 | transmit(mopp.mopp(speed, welcome_msg), ip, port) 35 | receivers[client] = time.time() 36 | logging.debug("New client: %s" % client) 37 | 38 | def reject(client, speed): 39 | ip,port = client.split(':') 40 | bye_msg = ':qrl' 41 | transmit(mopp.mopp(speed, bye_msg), ip, int(port)) 42 | 43 | while KeyboardInterrupt: 44 | time.sleep(0.2) # anti flood 45 | try: 46 | data_bytes, addr = serversock.recvfrom(64) 47 | client = addr[0] + ':' + str(addr[1]) 48 | speed = mopp.received_speed(data_bytes) 49 | logging.debug ("\nReceived %s from %s with %i wpm" % (mopp.received_data(data_bytes),client, speed)) 50 | r = mopp.decode_message(data_bytes) 51 | print (r) 52 | 53 | if client in receivers: 54 | if mopp.msg_strcmp(data_bytes, config.CHAT_WPM, ':bye'): 55 | serversock.sendto(mopp.mopp(speed,':bye'), addr) # FIXME 56 | del receivers[client] 57 | logging.debug ("Removing client %s on request" % client) 58 | else: 59 | broadcast (data_bytes, client) 60 | receivers[client] = time.time() 61 | else: 62 | if mopp.msg_strcmp(data_bytes, config.CHAT_WPM, 'hi'): 63 | if (len(receivers) < config.MAX_CLIENTS): 64 | receivers[client] = time.time() 65 | welcome(client, speed) 66 | else: 67 | reject(client, speed) 68 | logging.debug ("ERR: maximum clients reached") 69 | else: 70 | logging.debug ("-unknown client, ignoring-") 71 | 72 | except socket.timeout: 73 | # Send UDP keepalives 74 | for c in receivers.keys(): 75 | ip,port = c.split(':') 76 | serversock.sendto(bytes('','latin1'), (ip, int(port))) 77 | pass 78 | 79 | except (KeyboardInterrupt, SystemExit): 80 | serversock.close() 81 | break 82 | pass 83 | 84 | # clean clients list 85 | for c in list(receivers.items()): 86 | if c[1] + config.CLIENT_TIMEOUT < time.time(): 87 | ip,port = c[0].split(':') 88 | bye_msg = ':bye' 89 | transmit(mopp.mopp(config.CHAT_WPM, bye_msg), ip, int(port)) 90 | del receivers[c[0]] 91 | logging.debug ("Removing expired client %s" % c[0]) 92 | 93 | -------------------------------------------------------------------------------- /mopp.py: -------------------------------------------------------------------------------- 1 | # Module for MOPP protocol 2 | import logging 3 | from math import ceil 4 | 5 | class Mopp: 6 | serial = 1 7 | 8 | morse = { 9 | "0" : "-----", "1" : ".----", "2" : "..---", "3" : "...--", "4" : "....-", "5" : ".....", 10 | "6" : "-....", "7" : "--...", "8" : "---..", "9" : "----.", 11 | "a" : ".-", "b" : "-...", "c" : "-.-.", "d" : "-..", "e" : ".", "f" : "..-.", "g" : "--.", 12 | "h" : "....", "i" : "..", "j" : ".---", "k" : "-.-", "l" : ".-..", "m" : "--", "n" : "-.", 13 | "o" : "---", "p" : ".--.", "q" : "--.-", "r" : ".-.", "s" : "...", "t" : "-", "u" : "..-", 14 | "v" : "...-", "w" : ".--", "x" : "-..-", "y" : "-.--", "z" : "--..", "=" : "-...-", 15 | "/" : "-..-.", "+" : ".-.-.", "-" : "-....-", "." : ".-.-.-", "," : "--..--", "?" : "..--..", 16 | ":" : "---...", "!" : "-.-.--", "'" : ".----." 17 | } 18 | 19 | morse_inv = {v: k for k, v in morse.items()} 20 | 21 | 22 | def __init__(self, speed = 20): 23 | self.speed = speed 24 | return 25 | 26 | def _str2hex(self, bytes): 27 | hex = ":".join("{:02x}".format(c) for c in bytes) 28 | return hex 29 | 30 | def _str2bin(self, bytes): 31 | bincode = "".join("{:08b}".format(c) for c in bytes) 32 | return bincode 33 | 34 | def mopp(self, speed, msg, for_transmission = True): 35 | 36 | m = '01' # protocol version 37 | m += bin(self.serial)[2:].zfill(6) 38 | m += bin(speed)[2:].zfill(6) 39 | 40 | _txt = '' 41 | 42 | for c in msg: 43 | if c == " ": 44 | continue # spaces not supported by morserino! 45 | 46 | if c.lower() not in self.morse: 47 | continue # unknown character 48 | 49 | _txt += c.lower() 50 | for b in self.morse[c.lower()]: 51 | if b == '.': 52 | m += '01' 53 | else: 54 | m += '10' 55 | 56 | m += '00' # EOC 57 | 58 | if len(m) <= 14: # empty message 59 | return bytes('','latin_1') 60 | 61 | 62 | m = m[0:-2] + '11' # final EOW 63 | m = m.ljust(int(8*ceil(len(m)/8.0)),'0') 64 | 65 | #print (m, " ENCODER") # FIXME 66 | 67 | res = '' 68 | for i in range (0, len(m), 8): 69 | #print (m[i:i+8], bytes(chr(int(m[i:i+8],2)),"latin_1"), i, " ENCODER") 70 | res += chr(int(m[i:i+8],2)) 71 | 72 | if for_transmission: 73 | logging.debug("Encoding message with "+str(speed)+" wpm: "+str(_txt)) 74 | self.serial += 1 75 | if self.serial >= 64: 76 | self.serial = 0 77 | 78 | return bytes(res,'latin_1') # WATCH OUT: UNICODE MAKES MULTI-BYTES 79 | 80 | def _stripheader(self, msg): 81 | res = bytes(0x00) + bytes(msg[1] & 3) + msg[2:] 82 | return res 83 | 84 | def msg_strcmp (self, data_bytes, speed, msg): 85 | if self._stripheader(data_bytes) == self._stripheader(self.mopp(speed, msg, False)): 86 | return True 87 | else: 88 | return False 89 | 90 | def received_speed (self, data_bytes): # FIXME 91 | speed = data_bytes[1] >> 2 92 | return speed 93 | 94 | def received_serial (self, data_bytes): # FIXME 95 | #myserial = data_bytes[0] >> 2 96 | myserial = 0 # FIXME 97 | return myserial 98 | 99 | def received_data (self, data_bytes): # TODO 100 | return self._str2hex(data_bytes) 101 | 102 | def decode_message (self, data_bytes): 103 | if len(data_bytes) < 1: 104 | return {"Keepalive": True} 105 | 106 | speed = data_bytes[1] >> 2 107 | 108 | # Convert symbols to string of 0 and 1 again 109 | n = "" 110 | for l in [data_bytes[i:i+1] for i in range(len(data_bytes))]: 111 | n += "{:08b}".format(ord(l)) 112 | 113 | # list of bit pairs 01, 10, 11, 00 114 | sym = [n[i:i+2] for i in range(0, len(n), 2)] 115 | protocol = sym[0] 116 | serial = int("".join(sym[1:4]),2) 117 | 118 | # Extract message in format ./-/EOC/EOW 119 | msg = "" 120 | for i in range (14, len(n), 2): 121 | s = n[i:i+2] 122 | msg += self._mopp2morse(s) 123 | 124 | txt = self._morse2txt(msg) 125 | 126 | return {"Protocol": protocol, "Serial": serial, "Speed": speed, "Message": msg, "Text": txt} 127 | 128 | 129 | def _mopp2morse(self, sym): 130 | s = "" 131 | if sym == '01': 132 | s = '.' 133 | elif sym == '10': 134 | s = '-' 135 | elif sym == '00': 136 | s = 'EOC' 137 | elif sym == '11': 138 | s = 'EOW' 139 | else: 140 | logging.debug ("This should not happen: symbol ", s) 141 | return s 142 | 143 | def _morse2txt(self, ditcode): 144 | s = "" 145 | for word in ditcode.split("EOW"): 146 | if word == '': 147 | continue 148 | for letter in word.split("EOC"): 149 | if letter == '': 150 | continue 151 | if letter in self.morse_inv: 152 | s += self.morse_inv[letter] 153 | else: 154 | s += "<" + letter + ">" 155 | 156 | s += " " 157 | 158 | return s.strip() 159 | --------------------------------------------------------------------------------