Skip to content

Instantly share code, notes, and snippets.

@mstyne
Last active October 16, 2022 00:55
Show Gist options
  • Save mstyne/dd6d3f19dc818818f04029fcc0d8d46d to your computer and use it in GitHub Desktop.
Save mstyne/dd6d3f19dc818818f04029fcc0d8d46d to your computer and use it in GitHub Desktop.
Python Script for EME control via WSJT-X and Rotctld, courtesy ON4KHG and ON4KDV
#!/usr/bin/python3
# coding: utf-8
# This script allows to rotate an antenna system either by manually typing in a heading (azimuth and/or elevation)
# you want the antenna to rotate to or by automatically tracking the moon azimuth and elevation.
# The Moon azimuth and elevation are extracted out of the file azel.dat provided by the WSJT-X suite.
# Beside WSJT-X, this script also makes use of the rotator controller daemon embedded in the Hamlib
# libraries. So, the prerequisites for this script to run are WSJT-X opened and running, and the
# Hamlib librairies installed on your Raspberry Pi.
# This script has been written in Python 3 by Didier, ON4KDV and Gaëtan, ON4KHG.
# My personnal system makes use of a Raspberry Pi 4B and an ERC-3D (DF9GR) antenna rotator controller.
# Both are linked with a USB (on the Raspberry Pi side) to RS232 serial (on the controller side) connection.
# First, import the time, system and subprocess functions needed in this script
import time
import os, sys
import subprocess
# Select the USB port of the Raspberry Pi onto which the rotator controller is connected.
# Normally, as WSJT-X is started beforehand, the USB0 port is already assigned for the CAT & PTT of WSJT-X.
# In my case, I usually select USB1, so, I type "1" below.
usb_port=(input("USB port to use (0-3) : "))
print()
if usb_port == "0":
# The Rotctld(aemon) is opened with the command "rotctld -m 601 -r /dev/ttyUSB0 &".
# -m 601 is related to the GS-232A rotator protocol.
# You could have to have to select another number according to the protocol and associated hardware in use on your side.
# The list of supported protocols can be obtained by typing "rotctld -l" in the terminal.
# ttyUSB0 is the serial port (USB0) of the Raspberry onto which the rotator controller is connected.
subprocess.call('rotctld -m 601 -r /dev/ttyUSB0 &', shell=True)
# And so on for the other USB ports.
elif usb_port == "1":
subprocess.call('rotctld -m 601 -r /dev/ttyUSB1 &', shell=True)
elif usb_port == "2":
subprocess.call('rotctld -m 601 -r /dev/ttyUSB2 &', shell=True)
elif usb_port == "3":
subprocess.call('rotctld -m 601 -r /dev/ttyUSB3 &', shell=True)
# Select the working mode : manual, automatic (moon tracking) or exit.
Mode=(input("MANUAL rotation/elevation, type m\nAUTOMATIC tracking, type a\nEXIT, type e\n\n"))
if Mode == "m":
# If "m" is chosen, the manual rotation mode is selected.
while (True):
# Then, select what you want to do : rotate/elevate the antenna, get its current position, stop the rotation in case
# of emergency or exit.
Submode=(input("\nROTATE antenna (azimuth only) and set elevation to 0°, type r\nROTATE and ELEVATE antenna (azimuth and elevation), type l\nGet current POSITION, type p\nSTOP, type s\nEXIT, type e\n\n"))
if Submode == "r":
# If "r" is selected, instruct towards which azimuth the antenna has to rotate. With this selection, if the antenna was previously elevated,
# it will be set back to 0° (no elevation) too.
az=(input("\nRotate antenna to azimuth (°) : "))
az=az.strip()
# Build and execute the command "set_pos". The default port used by the rotctld(aemon) is 4533.
command_az='echo "|\set_pos ' + az + ' 0" | nc -w 1 localhost 4533'
os.system(command_az)
elif Submode == "l":
# If "l" is selected, you can set whatever azimuth and elevation angle.
az=(input("\nRotate antenna to azimuth (°) : "))
az=az.strip()
el=(input("Elevate antenna to elevation (°) : "))
el=el.strip()
command_azel='echo "|\set_pos ' + az + ' ' + el + '" | nc -w 1 localhost 4533'
os.system(command_azel)
# If "p" to get the position of the antenne is selected, build and execute the command "get_pos".
elif Submode == "p":
command_azpos='echo "|\get_pos" | nc -w 1 localhost 4533'
os.system(command_azpos)
# If "s" to (emergency) stop is selected, build and execute the command "S" (Stop).
elif Submode == "s":
command_stop='echo S | nc -w 1 localhost 4533'
os.system(command_stop)
else:
# If none of the above is selected, then exit the script.
exit()
elif Mode == "a":
# If "a" is chosen, the automatic (moon tracking) mode is selected.
# Set the refresh rate in minutes of the moon azimuth and elevation.
# With narrow beamwidth antennas (microwaves), the refresh rate has to be fast.
# With wide beamwidth antennas, the refresh rate can be slower. On 2m, I chose 4 minutes.
# If you want a refresh time in seconds, remove the line "ref=ref*60" and rename "min" into "sec"
# in the line ref=int(input("\nRefresh duration (min) : ")).
ref=int(input("\nRefresh duration (min) : "))
ref=ref*60
while (True):
# Open the file azel.dat of WSJT-X in read mode. Make sure the path "/home/pi/.local/share/WSJT-X/azel.dat" is the same
# than the path defined in the settings of WSJT-X (see the WSJT-X user guide).
# The first line of the file is read and placed in the variable "txt" and then the file is closed.
f=open("/home/pi/.local/share/WSJT-X/azel.dat","r")
txt=f.readline()
f.close()
if ("Moon" in txt): # Check that the first line contains the word "Moon".
p=txt.find(",") # Search for the 1st comma (,).
if (p > 1): # If the 1st comma has been found
txt=txt[p+1:] # what is in front of the 1st comma is removed, including the comma.
p=txt.find(",") # Search for the 2nd comma.
if (p > 1): # If the 2nd comma has been found
az=txt[0:p] # the text in between the 1st and 2nd commas is saved in "az" (azimuth).
az=az.strip() # Spaces before and after are removed, only the figures/sign are kept.
print("Moon azimuth (°) :",az) # The moon azimuth is displayed.
txt=txt[p+1:] # What is in front of the 2nd comma is removed, including the comma.
p=txt.find(",") # Search for the 3rd comma.
if (p > 1): # If the 3rd comma has been found
el=txt[0:p] # the text in between the 2nd and 3rd commas is saved in "el" (elevation).
el=el.strip() # Spaces before and after are removed, only the figures/sign are kept.
print("Moon elevation (°) :",el) # The moon elevation is displayed.
# Build the command "set_pos" as from the az and el variables extracted above.
command_azel='echo "|\set_pos ' + az + ' ' + el + '" | nc -w 1 localhost 4533'
# Convert az and el from text to float (numerical) format.
f_az=float(az)
f_el=float(el)
# Execute the command only if the azimuth is between 0 and 360° and if the elevation is between 0 and 90°.
if(f_az >0 and f_az <361 and f_el >0 and f_el <91):
os.system(command_azel)
# Otherwise the moon is below the horizon and nothing is executed but displaying that the moon is below horizon.
else:
print('\nThe moon is below horizon, antenna parked !')
# We wait 5 seconds, the time for the antenna to rotate (it may actually take longer, depending on the position of
# the antenna at startup of the tracking) and then we display the position of the antenna.
# Finally, we indicate that we wait until the next update, according to the refresh duration (ref) defined above.
time.sleep(5)
print()
command_azelpos='echo "|\get_pos" | nc -w 1 localhost 4533'
os.system(command_azelpos)
print('\nWaiting for the next update or CTRL+C to exit\n')
time.sleep(ref)
# If none of the working mode (manual or automatic moon tracking) was selected, exit and close the script.
else:
exit()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment