arcs

Amateur Radio Chat Server with a modern interface
git clone git://squid-tech.com/arcs.git
Log | Files | Refs | README

commit f4e42f6c53bb432f3ad437773153517eb4b6f2f2
parent 739a40f6f2248e06954e70f7b85ce3ee5685915f
Author: joshiemoore <jxm5210@g.rit.edu>
Date:   Thu, 19 Dec 2019 23:28:05 -0500

Implement server-side chat traffic routing

Diffstat:
MREADME.md | 2+-
Marcs.py | 152+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
2 files changed, 147 insertions(+), 7 deletions(-)

diff --git a/README.md b/README.md @@ -14,7 +14,7 @@ ARCS is based on "pure" python3, so no special setup is required. You also need 4. If you are running your own server, modify the server config section of `arcs.py` to match your setup. 5. Connect your radio to your computer, and set it to frequency 144.39 (national APRS frequency). 6. Run `direwolf -p`, making note of the path to the serial KISS TNC. -7. Start ARCS with `python3 arcs.py <serial/tnc/path> <baudrate> [options ...]` (If you are running your own server, include the `-server` flag when you run `arcs.py`) +7. Start ARCS with `python3 arcs.py <serial/tnc/path> 9600 [options ...]` (If you are running your own server, include the `-server` flag in the options field) 8. Open `index.html` in your web browser, and you're ready to go! ### Commands diff --git a/arcs.py b/arcs.py @@ -5,15 +5,20 @@ import sys import serial import threading import time +import random + +import socketserver # change this to your callsign MYCALL = 'KD2SIX' -### server config ### - # server callsign HOST = 'KD2SIX-1' + + +### server config ### + # server symbol SYMBOL = '/B' @@ -27,9 +32,16 @@ BEACON_MSG = 'Amateur Radio Chat Server - WIP' # how long to delay between beacon transmissions (in seconds) BEACON_DELAY = 1800 +# maximum message length +MAX_LEN = 128 + +# how many seconds before users are timed-out and disconnected +TIMEOUT = 300 + ##################### + KISS_FEND = 0xC0 KISS_FESC = 0xDB KISS_TFEND = 0xDC @@ -40,6 +52,35 @@ KISS_TFESC = 0xDD _SERVER = False +# dictionary of connected users (for server use) +# format is CALLSIGN: (timeHeard, lastMsg) +_USERS = {} + + +# list of local unread messages, for use by the web client +# format is (CALLSIGN, MSG) +_MSGS = [] + +_PATH = ["KD2AYD-3"] + + +# browser server host and port +CLIENTHOST, CLIENTPORT = "localhost", 8080 + + +""" +Handler for requests from the browser client. +""" +class BrowserHandler(socketserver.BaseRequestHandler): + def handle(self): + self.data = self.request.recv(1024).strip() + # output the data + print(self.data) + + # send back the same data, uppercase + self.request.sendall(self.data.upper()) + + """ Send command and data to the KISS TNC. data should be a byte array. @@ -96,7 +137,13 @@ def decodecall(call): if ssid != 0: decodedcall += "-" + str(ssid) - return decodedcall + return decodedcall + +""" +Pad a callsign for transmission. +""" +def padcall(call): + return call + " "*(9 - len(call)) """ Send an AX.25 packet from source to dest. @@ -165,12 +212,18 @@ def AX25parse(packet): return (dest, source, msg) """ +Send a message from one station to another. +""" +def sendMessage(port, source, dest, path, message): + AX25send(port, source, "CQ", path, ":" + padcall(dest) + ":" + message) + +""" Transmit the server's beacon message, position, and symbol. """ def beacon(port): print("BEACON") out = "=" + SERV_LAT + SYMBOL[0] + SERV_LONG + SYMBOL[1] + BEACON_MSG - AX25send(port, HOST, "APRS", ["WIDE2-2"], out) + AX25send(port, HOST, "APRS", _PATH, out) """ Automatically beacon on a set interval. @@ -180,6 +233,89 @@ def beacond(port): beacon(port) time.sleep(BEACON_DELAY) +""" +Send the message to all connected users, and disconnect timed-out users. +""" +def relay(port, msg): + # list of users to disconnect + timeouts = [] + + for k in _USERS: + if (time.time() - _USERS[k][0] > TIMEOUT): + # disconnect the user + timeouts.append(k) + else: + sendMessage(port, HOST, k, _PATH, msg) + + # remove all timed-out users + for k in timeouts: + print("TIMEOUT " + k) + _USERS.pop(k) + + +""" +Main server daemon that receives and routes messages. +""" +def serverd(port): + while True: + # receive and parse a packet + recv = AX25parse(AX25read(port)) + msg = recv[2] + + # clean up strange packets + # i'm not currently sure what to do with these or where they come from + if ":}" in msg: + msg = msg[msg.index(":}") + 2:] + + + # get the message recipient + dest = msg[1:10] + + # ignore the message if the server is not the recipient + if dest != padcall(HOST): + continue + + # trim the message + msg = msg[11:] if len(msg[11:]) < MAX_LEN else msg[11:MAX_LEN] + + # ignore duplicates + if recv[1] in _USERS and _USERS[recv[1]][1] == msg: + continue + + # update the user list + _USERS[recv[1]] = (int(time.time()), msg) + + #\TODO parse /commands + + dispmsg = recv[1] + ": " + msg + + # add message to local unread messages + _MSGS.append(dispmsg) + + # send message to all connected users + relay(port, dispmsg) + + print(dispmsg) + +""" +Test sending messages to the server +""" +def clientsTest(port): + count = 0 + + time.sleep(5) + sendMessage(port, MYCALL, HOST, _PATH, "initialize") + + while True: + time.sleep(random.randint(30, 60)) + + # callsign with random ssid + sendercall = MYCALL + "-" + str(random.randint(2, 5)) + + sendMessage(port, sendercall, HOST, _PATH, "testing" + str(count)) + count = count + 1 + + def main(): global _SERVER @@ -196,12 +332,16 @@ def main(): # initialize the KISS TNC KISSinit(ser) + # start the browser server + server = socketserver.TCPServer((CLIENTHOST, CLIENTPORT), BrowserHandler) + if _SERVER: # start beacon daemon threading.Thread(target=beacond, args=[ser]).start() - while True: - print(AX25parse(AX25read(ser))) + # start server daemon + threading.Thread(target=serverd, args=[ser]).start() + if __name__ == '__main__': main()