Управление поворотным устройством при помощи GPredict
На прошлой неделе мы рассмотрели самодельное поворотное устройство для СВЧ-антенн. Данная поворотка может быть использована для приема сигналов с LEO-спутников. Только сначала ее нужно подружить с программой GPredict.
Первым делом добавим поворотку в GPredict. Открываем Edit → Preferences → Interfaces → Rotators и нажимаем Add New. Имя устройства вводим произвольное. В качестве хоста и порта пусть будут localhost и 4533. В выпадающем списке Az Type выбираем 0-180-360. Ниже в диапазонах наведения указываем 0°..360° для азимута и 0°..180° для элевации. Нажимаем OK. Поворотка добавлена.
GPredict будет ходить на указанные хост и порт по TCP и давать команды поворотке. Насколько я смог выяснить, протокол не документирован. А значит, изучать его предстоит по исходникам. К слову, реализация содержит ошибки. С сервером, запущенным на удаленной машине, GPredict работать не будет. Звучит, как отличная возможность законтрибьютить в open-source.
Был написан скрипт, который переводит протокол GPredict в команды, понятные ротатору:
#!/usr/bin/env python3
# antenna-rotator-control.py ~ (c) Alex R2AUK 2026
# https://eax.me/2026/2026-08-26-antenna-rotator-control.html
import argparse
import serial
import socket
import sys
import time
BUFFER_SIZE = 100
def uart_get_current(ser):
ser.write(b"get current\n")
response = ser.readline().decode('utf-8', errors='replace').strip()
print(f"uart_get_current(), response: {response}")
# expected: "AZ=45.0 EL=55.8"
parts = response.split()
az = float(parts[0].split('=')[1])
el = float(parts[1].split('=')[1])
return az, el
def uart_set_target(ser, az, el):
cmd = f"set target {az:.1f} {el:.1f}\n"
ser.write(cmd.encode('utf-8'))
response = ser.readline().decode('utf-8', errors='replace').strip()
print(f"uart_set_target(), response: {response}")
return response == "OK"
def uart_set_shortpath(ser, enabled):
cmd = b"set shortpath on\n" if enabled else b"set shortpath off\n"
ser.write(cmd)
response = ser.readline().decode('utf-8', errors='replace').strip()
print(f"uart_set_shortpath(), response: {response}")
return response == "OK"
parser = argparse.ArgumentParser(description='Antenna rotator control bridge')
parser.add_argument('-d', default='/dev/ttyUSB0', metavar='DEVICE',
help='Serial device (default: /dev/ttyUSB0)')
parser.add_argument('-b', default=9600, type=int, metavar='BAUDRATE',
help='Baud rate (default: 9600)')
parser.add_argument('-l', default='127.0.0.1', metavar='HOST',
help='TCP listen address (default: 127.0.0.1)')
parser.add_argument('-p', default=4533, type=int, metavar='PORT',
help='TCP listen port (default: 4533)')
parser.add_argument('-s', action='store_true', default=False,
help='Enable shortest path algorithm (default: off)')
args = parser.parse_args()
print(f"Opening serial port {args.d} at {args.b} baud...")
try:
ser = serial.Serial(args.d, args.b, timeout=2)
except serial.SerialException as e:
print(f"Error: {e}")
sys.exit(1)
# Discard any garbage data that may arrive right after connecting
time.sleep(2.0)
ser.reset_input_buffer()
print("Serial port opened successfully")
shortpath_str = 'on' if args.s else 'off'
print(f"Setting shortpath: {shortpath_str}")
if not uart_set_shortpath(ser, args.s):
print("Error: failed to set shortpath")
sys.exit(1)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((args.l, args.p))
s.listen(1)
while True:
print("Waiting for connection...")
conn, addr = s.accept()
print(f"Connection from: {addr}")
while True:
raw = conn.recv(BUFFER_SIZE)
if not raw:
break
data = raw.decode('utf-8', errors='replace').strip()
if data == "p":
az, el = uart_get_current(ser)
print(f"pos query: az={az} el={el}")
conn.send(f"{az}\n{el}\n".encode())
elif data.startswith("P "):
values = data.split(" ")
az = float(values[1])
el = float(values[2])
print(f"set target: az={az} el={el}")
uart_set_target(ser, az, el)
conn.send(b"RPRT 0\n")
elif data == "S":
print("stop command, doing nothing")
conn.send(b"RPRT 0\n")
elif data == "q":
print("close command")
break
else:
print(f"unknown command: {data}")
break
conn.close()
print("Connection closed")
Подключаем поворотку к компьютеру. Запускаем скрипт. В интерфейсе GPredict справа нажимаем на «сэндвич». В появившимся меню выбираем Configure. Для теста добавляем десяток-другой спутников Iridium. Так в GPredict постоянно будет хотя бы один пролетающий над нами спутник.
Далее в том же «сэндвиче» выбираем Antenna Control. В списке Target выбираем один из пролетающих спутников. Нажимаем Track. В списке Device выбираем ранее созданную поворотку. Нажимаем Engage:
В выводе скрипта видим, как он принимает команды TCP-протокола и переводит их в команды UART-протокола. Поворотное устройство наводится на спутник.
Обратите внимание на разницу между положением спутника (az 47.13°, el 74.37°) и поворотки (az 228.21°, el 105.52°). Это не ошибка, а крутая фича. GPredict управляет повороткой так, чтобы она не пересекала нулевой азимут в рамках прохода спутника. Таким образом, использовать shortpath в сочетании с GPredict не нужно и даже вредно. Он приведет лишь к наматыванию проводов.
Осталось сделать антенну, и можно принимать сигналы со спутников. Этим мы займемся в следующий раз.