|
|
@ -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 · Up: {sv:.2f} kHz", onair)
|
|
|
|
writehtml(f"Frequency: {f}kHz {mode}<br>Tx Split · 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.
|
|
|
|