'Back On The Air', Code Cleanup, Split Exemptions

master
Jay Moore/NQ4T 6 months ago
parent 541c02a2dd
commit 3994e46505

@ -5,8 +5,21 @@ continually update this file as messages come in. It requires you to let Log4OM
## Usage ## Usage
Place the script on a system that can hear UDP from Log4OM and has a way of putting the resulting HTML file on a webserver This is not something you can just toss on your computer and get it to work. It's designed to run on a web-server; and
as fast as possible. Edit the script as required (IP, port, output location, HTML formatting). even worse the environment I designed it for has a public httpd running on my local LAN.
The script takes UDP packets from Log4OM and will then write an HTML file. So there are two requirements:
- Send/recieve UDP messages to/from Log4OM
- Serve the HTML file.
There are a number of ways this can be accomplished. You can run it on a local machine and upload the HTML file
to your webserver every few seconds, or minutes. You can run it on your hosted server and just blast the packets over
the internet every 5 seconds.
I have plans to write a version that's less real-time for cases where you want to send the UDP over the internet. There
are also ideas for hacking together an SSH tunneling solution...maybe. I would love for as many people to run this; but
at the end of the day, getting this to work will require a higher-than-average skill level.
## Basic Operation ## Basic Operation
@ -21,11 +34,19 @@ the following in a very basic way:
This now will determine if the radio is off or if Log4OM is not loaded by trying to request the Alive command over remote This now will determine if the radio is off or if Log4OM is not loaded by trying to request the Alive command over remote
control. It will assue Log4OM is active if a response is received. In an effort to make the thing a bit less chatty, it control. It will assue Log4OM is active if a response is received. In an effort to make the thing a bit less chatty, it
backs down to 60 second checks when not receiving data automatically. backs down to 60 second checks when not receiving data automatically. Some modes (FT8, JS8, FT4) might use split, but who
cares if you say it; it's almost implied. So now we just ignore split for those modes.
## Running As A Service
Code could likely use more optimization. ChatGPT has been used to some degree but currently has issues giving complete I have run this as a systemd service to great success. For the 11 months I was off the air it largely sat in the background
output. trying to ask my PC if Log4OM was alive. Granted I rebooted the thing about 40 times in that 11 months; I forgot to deactivate
and turn it off. During that 11 months I saw absolutely no negative impacts on my system. It consumed almost no CPU and just
made a lot of UDP requests to my IP. After getting back on the air, updating Log4OM, and getting CAT control working; it
immedately began updating like nothing had happened.
I've included a systemd service file, as that's what I'm using. If you write one for something else; please submit it given
you will allow it to be released and distributed under the FBSD3 license.
## Examples ## Examples
This has been implemented on the sidebar/menu of [nq4t.com](https://nq4t.com). The actual webpage that's updated is served This has been implemented on the sidebar/menu of [nq4t.com](https://nq4t.com). The actual webpage that's updated is served
@ -36,6 +57,7 @@ from my [QTH's webserver](https://log.nq4t.com/radio.html).
``` ```
02-FEB-2023: Initial Version. Shows status and basic offline message. 02-FEB-2023: Initial Version. Shows status and basic offline message.
04-APR-2023: Second Version. Now shows more percise offline message. Removes threads. 04-APR-2023: Second Version. Now shows more percise offline message. Removes threads.
08-JUN-2024: "Back On The Air" Update. Minor cleanup. Code commenting. Split exclusion for FT/JT modes.
``` ```
## License ## License
@ -43,7 +65,7 @@ from my [QTH's webserver](https://log.nq4t.com/radio.html).
``` ```
BSD 3-Clause License BSD 3-Clause License
Copyright (c) 2023, Jay Moore Copyright (c) 2024, Jay Moore
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met: modification, are permitted provided that the following conditions are met:

@ -1,27 +1,40 @@
#!/usr/bin/env python3
# LOG4OM Web Statuts # LOG4OM Web Statuts
# Version: 5-APR-2023 - Jay Moore/NQ4T # Version: 8-JUN-2024 - Jay Moore/NQ4T
# https://git.pickmy.org/nq4t/log4om-webstatus # https://git.pickmy.org/nq4t/log4om-webstatus
# https://nq4t.com/software/log4omudp/ # https://nq4t.com/software/log4omudp/
# FreeBSD 3-Clause License (see LICENSE) # FreeBSD 3-Clause License (see LICENSE)
# Please read comments for configuration.
# Script must be configured for your setup.
import time import time
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
import socket import socket
import select import select
UDP_IP = "0.0.0.0" UDP_IP = "0.0.0.0" # Binds to "Any" For Broadcast Packets
UDP_PORT = 2242 # Default Log4OM UDP Out Port UDP_PORT = 2242 # Default Log4OM UDP Out Port
UDP_C_IP = "192.168.1.70" # Set to IP running Log4OM UDP_C_IP = "0.0.0.0" # Set to IP running Log4OM
UDP_C_PORT = 2241 UDP_C_PORT = 2241 # Default Log4OM Remote Port
tot = 1 # Global Variable Definitions and Initializations
tot = time.time()
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind((UDP_IP, UDP_PORT)) sock.bind((UDP_IP, UDP_PORT))
check = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) check = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
def checkrig(): # These modes should ignore split mode indication
ignored_modes = ["FT8", "FT4", "JT65", "JT9", "JS8", "MSK144"]
# Script Functions
def checkrig(): # Checks if it's radio off or Log4OM off
check = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) check = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
check.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) check.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
check.bind((UDP_IP, UDP_C_PORT)) check.bind((UDP_IP, UDP_C_PORT))
@ -32,40 +45,67 @@ def checkrig():
</RemoteControlRequest>""" </RemoteControlRequest>"""
check.sendto(msg.encode(), (UDP_C_IP, UDP_C_PORT)) check.sendto(msg.encode(), (UDP_C_IP, UDP_C_PORT))
status = select.select([check], [], [], 2) status = select.select([check], [], [], 2)
# It's simple; we either get a response, or we don't.
# It doesn't matter what it is.
if status[0]: if status[0]:
writehtml(f"Radio Off", False) writehtml(f"Radio Off", False)
else: else:
writehtml(f"Log4OM Down", False) writehtml(f"Log4OM Down", False)
check.close() check.close()
def writehtml(rs, t = "true"): def writehtml(rs, t = "true"): # Takes data as argument from loop, writes HTML file.
header = """<html>\n<head>\n<title>FT-1000MP Status</title> header = """<html>\n<head>\n<title>FT-1000MP Status</title>
<meta http-equiv=\"refresh\" content=\"5\">\n</head>\n""" <meta http-equiv=\"refresh\" content=\"5\">\n</head>\n"""
# if you use CSS then modify for your stylesheet URI # if you use CSS then modify for your stylesheet URI
css = """<link rel=\"stylesheet\" href=\"poole.css\"> css = """<link rel=\"stylesheet\" href=\"poole.css\">
<link rel=\"stylesheet\" href=\"hyde.css\"> <link rel=\"stylesheet\" href=\"hyde.css\">
<link rel=\"stylesheet\" href=\"https://fonts.googleapis.com/css?family=PT+Sans:400,400italic,700|Abril+Fatface\">""" <link rel=\"stylesheet\" href=\"https://fonts.googleapis.com/css?family=PT+Sans:400,400italic,700|Abril+Fatface\">"""
# you may not need this div portion, it's part of nq4t.com's integration
div = "<body><body class=\"theme-base-0d\"><div class=\"sidebar\"><div class=\"sidebar-about\">" div = "<body><body class=\"theme-base-0d\"><div class=\"sidebar\"><div class=\"sidebar-about\">"
# you will need the footer though
footer = "\n</div></div></body>\n</html>" footer = "\n</div></div></body>\n</html>"
# you can change it to "BEING A LID" if you want
isonair = "ON THE AIR<br>\n" isonair = "ON THE AIR<br>\n"
with open("/var/www/log/radio.html", "w") as html: # Modifiy file location as needed. # HTML Output: Modify the file path. Also modify html.write to remove css or div if not needed.
if t == True: # Checks for on-air # isonair can be placed before or after 'rs'
html.write(header + css + div + isonair + rs + footer) # If not using CSS, remove it. with open("/var/www/log/radio.html", "w") as html:
if t == True:
html.write(header + css + div + isonair + rs + footer)
else: else:
html.write(header + css + div + rs + footer) html.write(header + css + div + rs + footer)
checkrig() # Run this once to ensure things are initalized.
# Script Loop
# Log4OM sends it's packets every five seconds. So we wait six to see if a packet comes in.
# If it does, we parse the XML and send it as arguments to writehtml()
#
# If it doesn't....
#
# Then we create the sleepy variable by subtracting tot from time.time(). If it's more than
# a minute; we run checkrig(). If it's not, we go away. After a minute has passed, we check
# again and reset the counter.
#
# This largely is done so we can daemon the script and have it pick back up when things change.
#
# You also probably shouldn't modify anything here unless you know what you're doing.
while True: while True:
ready = select.select([sock], [], [], 6) # Just give it an extra second ready = select.select([sock], [], [], 6)
if ready[0]: if ready[0]:
data, addr = sock.recvfrom(1024) data, addr = sock.recvfrom(1024)
root = ET.fromstring(data.decode("utf-8")) root = ET.fromstring(data.decode("utf-8"))
freq = int(root.find("Freq").text) freq = int(root.find("Freq").text) # Frequency
tx_freq = int(root.find("TXFreq").text) tx_freq = int(root.find("TXFreq").text) # TX Frequency
mode = root.find("Mode").text mode = root.find("Mode").text # Operating Mode
onair = (root.find("IsTransmitting").text == "true") onair = (root.find("IsTransmitting").text == "true") # TX Status
f = freq / 100 f = freq / 100
tf = tx_freq / 100 tf = tx_freq / 100
sv = tf - f # Determines split value if mode in ignored_modes:
sv = 0
else:
sv = tf - f
if sv > 0: if sv > 0:
writehtml(f"Frequency: {f}kHz {mode}<br>Tx Split &middot; Up: {sv:.2f} kHz", onair) writehtml(f"Frequency: {f}kHz {mode}<br>Tx Split &middot; Up: {sv:.2f} kHz", onair)
elif sv < 0: elif sv < 0:
@ -78,4 +118,4 @@ while True:
sleepy = time.time() - tot sleepy = time.time() - tot
if sleepy > 60: # How often to check in Off/Down state if sleepy > 60: # How often to check in Off/Down state
checkrig() checkrig()
tot = time.time() tot = time.time() # Reset tot.

@ -0,0 +1,13 @@
[Unit]
Description=Log4OM UDP Status Display
After=network.target
StartLimitIntervalSec=0
[Service]
Type=simple
Restart=always
RestartSec=1
User=dewdude
ExecStart=/usr/bin/python3 /path/to/log4om.py
[Install]
WantedBy=multi-user.target
Loading…
Cancel
Save