Last active
October 16, 2022 00:55
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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