Initial travler-rotor library scaffolding
Extract Gabe Emerson's Trav'ler rotor scripts into a proper Python library with firmware protocol abstraction (HAL 2.05 + HAL 0.0.00), Hamlib rotctld TCP server, Click CLI, and isolated leap-frog algorithm with the elevation copy-paste bug fixed.
This commit is contained in:
commit
c93bbef26d
17 changed files with 1374 additions and 0 deletions
159
Trav-ler-Rotor-For-HAL-2.05/travler_rotor.py
Normal file
159
Trav-ler-Rotor-For-HAL-2.05/travler_rotor.py
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
#Python program to control Winegard Trav'ler antenna as an AZ/EL Rotor
|
||||
#Version 2.0B (For HAL Version 2.05, NOT for HAL v0.0)
|
||||
#Gabe Emerson / Saveitforparts 2025, Email: gabe@saveitforparts.com
|
||||
|
||||
import serial
|
||||
import socket
|
||||
import regex as re
|
||||
|
||||
#initialize some variables
|
||||
current_az = 0.0
|
||||
current_el = 0.0
|
||||
move_nbr = 0
|
||||
|
||||
#define "antenna" as the serial port device to interface with
|
||||
antenna = serial.Serial(
|
||||
port='/dev/ttyUSB0', #pass this from command line in future?
|
||||
baudrate = 57600,
|
||||
parity=serial.PARITY_NONE,
|
||||
stopbits=serial.STOPBITS_ONE,
|
||||
bytesize=serial.EIGHTBITS,
|
||||
timeout=1)
|
||||
|
||||
print ('Antenna connected on ', antenna.port)
|
||||
|
||||
#Prep the Trav'ler firmware to accept commands
|
||||
antenna.write(bytes(b'q\r')) #go back to root menu in case firmware was left in a submenu
|
||||
antenna.write(bytes(b'\r')) #clear firmware prompt to avoid unknown command errors
|
||||
antenna.write(bytes(b'motor\r')) #enter motor submenu
|
||||
|
||||
#listen to local port for rotctld commands
|
||||
listen_ip = '127.0.0.1' #listen on localhost
|
||||
listen_port = 4533 #pass this from command line in future?
|
||||
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
client_socket.bind((listen_ip, listen_port))
|
||||
client_socket.listen(1)
|
||||
|
||||
while 1:
|
||||
print ('Listening for rotor commands on', listen_ip, ':', listen_port)
|
||||
print ('Ctrl-C to exit')
|
||||
conn, addr = client_socket.accept()
|
||||
print ('Connection from ',addr)
|
||||
|
||||
#pass rotor commands to antenna
|
||||
while 1:
|
||||
data = conn.recv(100) #get Gpredict's message
|
||||
if not data:
|
||||
break
|
||||
|
||||
cmd = data.decode("utf-8").strip().split(" ") #grab the incoming command
|
||||
|
||||
if cmd[0] == "p": #Gpredict is requesting position
|
||||
|
||||
#ask antenna for position
|
||||
antenna.write(bytes(b'a\r')) #get current Az/El/Sk
|
||||
antenna.flush()
|
||||
|
||||
#read current position from antenna
|
||||
reply1 = antenna.read(200).decode(errors='ignore').strip() #read dish response
|
||||
|
||||
#Regular expression patterns to find AZ and EL values
|
||||
az_pattern = r"AZ =(\s+\d+\.\d+)"
|
||||
el_pattern = r"EL =(\s+\d+\.\d+)"
|
||||
|
||||
#Use re.search to extract the values
|
||||
az_match = re.search(az_pattern, reply1)
|
||||
el_match = re.search(el_pattern, reply1)
|
||||
|
||||
#If matches are found, extract the numerical values
|
||||
if az_match and el_match:
|
||||
current_az = float(az_match.group(1))
|
||||
current_el = float(el_match.group(1))
|
||||
else:
|
||||
print("AZ or EL not found.")
|
||||
|
||||
print("Current position:", current_az,", ", current_el)
|
||||
|
||||
response = "{}\n{}\n".format(current_az, current_el) #put az & el into the format Gpredict expects
|
||||
conn.send(response.encode('utf-8')) #send response to Gpredict
|
||||
antenna.flush()
|
||||
|
||||
elif cmd[0] == "P": #Gpredict is sending desired position
|
||||
|
||||
target_az = float(cmd[1])
|
||||
target_el = float(cmd[2])
|
||||
print('Gpredict requesting move to:', target_az,', ', target_el)
|
||||
print('\n')
|
||||
|
||||
#Set movement a little farther than requested to "leap-frog" over position.
|
||||
#This helps avoid the antenna lagging behind the satellite in the middle of the pass
|
||||
if target_az - current_az > 2:
|
||||
target_az+=1
|
||||
elif target_az - current_az < -2:
|
||||
target_az-=1
|
||||
elif target_az - current_az > 1:
|
||||
target_az+=0.5
|
||||
elif target_az - current_az < -1:
|
||||
target_az-=0.5
|
||||
if target_el - current_el > 2:
|
||||
target_az+=1
|
||||
elif target_el - current_el < -2:
|
||||
target_az-=1
|
||||
elif target_el - current_el > 1:
|
||||
target_az+=0.5
|
||||
elif target_el - current_el < -1:
|
||||
target_az-=0.5
|
||||
|
||||
#tell Antenna to move to target position
|
||||
#Alternate AZ and EL motions to avoid one waiting on the other
|
||||
if (move_nbr % 2) == 0: #Even numbered step, send AZ first
|
||||
|
||||
command = ('a 0 ' + str(target_az) + '\r').encode('ascii') #move azimuth motor
|
||||
antenna.write(command)
|
||||
|
||||
#tell Antenna to move to target el
|
||||
if (target_el < 15): #HAL 2.05 won't reliably go below 15 degrees with this method
|
||||
target_el = 15
|
||||
command = ('a 1 ' + str(target_el) + '\r').encode('ascii') #move elevation motor
|
||||
antenna.write(command)
|
||||
|
||||
else: #Odd numbered step, send EL first
|
||||
|
||||
#tell Antenna to move to target el
|
||||
if (target_el < 15):
|
||||
target_el = 15
|
||||
command = ('a 1 ' + str(target_el) + '\r').encode('ascii') #move elevation motor
|
||||
antenna.write(command)
|
||||
|
||||
#tell Antenna to move to target az
|
||||
command = ('a 0 ' + str(target_az) + '\r').encode('ascii') #move azimuth motor
|
||||
antenna.write(command)
|
||||
|
||||
#Tell Gpredict things went correctly
|
||||
response="RPRT 0\n " #Everything's under control, situation normal
|
||||
conn.send(response.encode('utf-8'))
|
||||
move_nbr+=1 #increment step counter
|
||||
|
||||
|
||||
elif cmd[0] == "S": #Gpredict says to stop, so exit loop and listen for a new connection
|
||||
print('Gpredict disconnected!')
|
||||
move_nbr = 0
|
||||
conn.close()
|
||||
break
|
||||
|
||||
elif cmd[0] == "_": #Gpredict asks for model name (does it ever do this?)
|
||||
response = "Saveitforparts Winegard Trav'ler Interface v2.0"
|
||||
conn.send(response.encode('utf-8'))
|
||||
|
||||
else:
|
||||
print('Exiting.')
|
||||
antenna.write(bytes(b'q\r')) #go back to root menu
|
||||
antenna.write(bytes(b'\r'))
|
||||
conn.close()
|
||||
antenna.close()
|
||||
exit()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue