ARK has recently collected all rotor controllers on a single Raspberry Pi-device and made these available for software control from all Linux machines on the local network. We’ve also enabled rotor control from N1MM on Windows. This post outlines how we did it.


LA1K has a large selection of different rotors and rotor controllers: One rotor to control the HF-beam in the azimuth direction, one rotor to control the 6m and 4m antennas in the azimuth direction, one rotor to control the 2m satellite array in azimuth and elevation directions, and recently, one rotor to control the new satellite dish in azimuth and elevation directions.

Rotor controllers. Top left: 4m/6m-array, top right: satellite dish, bottom left: 2m array, bottom right: HF rotor

Software control over all these rotor controllers is either very simple or extremely complicated. In Linux, software control is typically enabled through the use of Hamlib, either direct control through the C API or by setting up rotctld to accept rotor commands over the network. The latter is usually the preferred solution. Applications like gpredict, SatNOGS and flyby enable satellite tracking through client connections to existing rotctld services. This is a clean and simple solution where the application would need to only implement simple socket communications, without having to drag in any special rotor control libraries. The rotor is made available for several applications and computers at the same time. This also has additional conveniences, as it becomes possible to remotely control all antennas e.g. when doing maintenance from the roof, reducing the need to yell down the roof hatch/over the radio/in general. If a rotor controller is connected to a specific Linux computer, however, it would have to always stay in Linux for other computers to access it. This also puts some requirements on network availability.

The situation is different on Windows. There is no intermediate abstraction level between the serial port and the application, and every single application has their own implementation of every single rotor control protocol. Each application will also lock the serial port, making the rotor available only from one application at the time. At ARK, rotors like the HF rotor and the 6m/4m-rotor have been locked to one of our Windows machines and thus been unavailable from the Linux machines (and also the other Windows machine).

We wanted to solve many of these aspects and enable seamless integration of software rotor control across several machines. This post will go through a robust solution for making all rotor controllers available on the network from a single Raspberry Pi device, including both Linux support and rudimentary abstraction levels for proper Windows support.

N1MM command receiver

As a first step, we wanted to make the rotctld-controlled rotor available from N1MM. We use N1MM as our main logging program. This program has some convenient facilities for e.g. clicking on spotted callsigns from the DX cluster and automatically turning the rotor towards its general location. N1MM is also the main program from which we have any use for software-controlled rotor steering in Windows, whereas most of the other rotor-enabled software is run from Linux. The first step could therefore also potentially be the final step.

Conveniently, N1MM has its own network protocol for enabling rotor control from one N1MM instance to another N1MM instance. The protocol is simple, with N1MM on one end acting as an UDP socket, and N1MM on the other end sending simple XML messages containing the azimuth position and the frequency bands for which N1MM wants to turn the rotor.

During a late Friday evening, LA1BFA and LA6YKA verified that it was possible to receive such commands from N1MM, and wrote a proof of concept for receiving rotor commands from N1MM and forwarding them to a rotctld instance. They also wrote a proof of concept for obtaining the current azimuth heading from rotctl and forwarding it to N1MM, displaying the current rotor heading in N1MM. The rest of us lowly peasants then went to work on extending the former script to allow for connections to be set up to the rotctld connections for several rotor controllers, and making the script select the correct rotor based on the frequency band-information in the N1MM message. The final script can be found here.

rotctld setup

One rotctld instance is necessary for each rotor controller in use. It is desired to make the rotctld instances persistent, and restart them if unexpectedly killed. Facilities should be enabled for automatic startup and easy disabling and re-enabling of each rotor service. Finally, configuration options for each rotor should be collected in a single, readable file.

An extra level of abstraction was therefore designed around rotctld. A configuration file was defined, with one entry for each rotor. The entry is headed by a rotor name, and contains the rotctld startup parameters including e.g. model number and the network port rotctld should listen to.  An optional field defines the frequency bands supported by the corresponding antenna. A simple script was written for parsing the configuration file for a specific rotor name and starting rotctld according to these options.

Systemd has convenient facilities for starting such services. Calls to the script above were put inside a systemd template file. This enables configuration and starting of multiple rotctld instances for various rotor controllers, without having to define one service file for each rotor. Starting the systemd service rotctld@ROT2PROG.service would for instance start the rotctld service defined by the following entry in the configuration file:

rotctld_options=-C az_resolution=2 -C el_resolution=2 -s 600

This would make rotctld start with the following arguments:

rotctld -m 901 -r /dev/tty_ROT2PROG -C az_resolution=2 -C el_resolution=2 -s 600

We let this configuration file be shared by the N1MM command receiver above. The N1MM script uses the n1mm_freqbands field to decide which rotors should be controlled by frequencies specified by N1MM.

The full setup is outlined in the figure below. Systemd starts a rotctld instance for each rotor specified in the configuration file. It also starts an instance of the N1MM command receiver. The command receiver listens for messages from any N1MM instance on the network, which are parsed and converted to the format expected by rotctld. The message is then passed to the correct rotctld instance based on the input frequency band option. For the Linux software, each rotor is identified by its network port, and messages sent here will turn the corresponding rotor.

Rotor control flowgraph. In this specific example, N1MM running on a Windows computer wants to turn the rotor at the 14.0 MHz frequency band to azimuth direction 55 degrees. This is interpreted by n1mm_rotctl to be the rotctld-instance corresponding to the HF rotor, and this rotor is subsequently turned towards a new heading.

Scripts and configuration are collected in this github repository.

Minor extra configuration: iptables and udev rules

The network services enabled by the N1MM command receiver and rotctld are not very secure, and anyone on the same network will be able to control the rotors. Limiting the network traffic can be important. We used iptables for this. An example of an iptables setup can be found on the Debian wiki.

Additionally, we wrote udev rules to force sensible names for the serial-to-usb converters used to connect to each rotor controller. Here, we used a 4-ports-in-one device, and marked each serial port with the name of the rotor controller, and set the corresponding name in the udev rule:

SUBSYSTEM=="tty", SUBSYSTEMS=="usb-serial", ATTRS{port_number}=="0", SYMLINK+="tty_HFROT", GROUP="dialout"
SUBSYSTEM=="tty", SUBSYSTEMS=="usb-serial", ATTRS{port_number}=="1", SYMLINK+="tty_ROT2PROG", GROUP="dialout"
SUBSYSTEM=="tty", SUBSYSTEMS=="usb-serial", ATTRS{port_number}=="2", SYMLINK+="tty_MD-01", GROUP="dialout"
SUBSYSTEM=="tty", SUBSYSTEMS=="usb-serial", ATTRS{port_number}=="3", SYMLINK+="tty_4-6MROT", GROUP="dialout"

The serial port indexed with 0 will consistently appear as /dev/tty_HFROT. Filtering options were found using e.g.

udevadm info -p -a /dev/ttyUSB0

More information on writing udev rules can be found here.

RPi with serial ports. We only had three working RS232 cables at the time of acquisition, and MD-01 was thus not connected.

The rotctld instances are very robust, and connecting/disconnecting/turning on and off the rotor controllers do not seem to have any impact on the stability of the system.

Future work: Virtual serial ports

The above setup enables us to control rotors from any Linux machine connected to the same network as the RPi, and let the services be started automatically and be kept alive by systemd. The udev rules ensure that as long as we connect the rotor controllers to the correct, labeled serial ports, they will be accessed by the correct rotctld instances and everything will work without any difficulties. The setup also enables N1MM to control the HF rotors.

For other Windows applications, however, only direct serial port control is possible. The system can be extended to also allow these programs to control the rotors. Com0com has support for forwarding serial commands from virtual serial ports to TCP/IP sockets. Our proposal is to find the most commonly supported, the simplest serial protocol for rotor control, and forward serial commands to a script similar to the N1MM command receiver. Thus, rotctld support in all Windows applications should be realisable. It also solves the problem when a newer rotor controller protocol is not implemented in a specific application in Windows. The need for this is not that great for our radio club as we mostly work in Linux for this, but could be a nice feature for the future.