QK4 on Arch Linux AUR repository

I wrote PKGBUILD script for QK4 (Elecraft K4 remote software) and submitted that to the AUR repository. Arch Linux users can install QK4 with yay as follows:

$ yay -S qk4-git

It will create the proper menu / icon entries & decrease pipewire latency so that CW can be used. To update to the latest version use:

$ yay --devel -Suyy

Antenna widget in gtk+

There is no documentation on libqt6c -> decided to switch to gtk+. Cleaned up the code a bit too.

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>
#include <gtk/gtk.h>

// Define the antenna names
#define ANTENNA1 "6m beam"
#define ANTENNA2 "WARC beam"
#define ANTENNA3 "20-15-10 stack"
#define ANTENNA4 "40m beam"
#define ANTENNA5 "No antenna"
#define ANTENNA6 "No antenna"
#define ANTENNA7 "No antenna"
#define ANTENNA8 "No antenna"
#define ANTENNA9 "No antenna"
#define ANTENNA10 "No antenna"
#define ANTENNA11 "No antenna"
#define ANTENNA12 "No antenna"
#define MAX_ANTENNAS 12

// Device for BM5
#define DEVICE "/dev/serial/by-id/usb-FTDI_FT232R_USB_UART_B003ISA1-if00-port0"

// Update every 100 msec
#define UPDATE 100

char *antennas[] = {ANTENNA1, ANTENNA2, ANTENNA3, ANTENNA4, ANTENNA5, ANTENNA6,
ANTENNA7, ANTENNA8, ANTENNA9, ANTENNA10, ANTENNA11, ANTENNA12};

#define MAX_SM 11
char *stack_match[] = {"No antenna", "Top antenna", "Mid antenna", "Top+mid antennas",
"Bottom antenna", "Top+bot antennas", "Mid+bot antennas",
"All antennas", "AUX", "Unknown", "Phase"};

int fd;
GtkWidget *ant, *sm;

struct packet {
unsigned char cmd;
unsigned char dst;
unsigned char src;
unsigned char channel;
int len;
unsigned char data[256];
};

int read_untilcr(int fd, char *buf, int maxlen) {

int pos = 0, bytes_rd;

while(1) {
if((bytes_rd = read(fd, buf + pos, 1)) < 0) return -1;
if(bytes_rd == 0) {
sleep(1); // could be less than 1sec
continue;
}
if(!pos && (*buf == '\r' || *buf == '\n')) continue;
if(pos == maxlen) return -1;
if(buf[pos] == '\r') break;
pos++;
}
buf[pos] = '\0';
return pos;
}

int parse_packet(char *str, struct packet *packet) {

memset((void *) packet, 0, sizeof(struct packet));
if(sscanf(str, "[T%2xR%2x%2xH%2xM%[^]]", &(packet->cmd), &(packet->dst), &(packet->src), &(packet->channel), &(packet->data)) != 5) return -1;
return 0;
}

void set_tty() {

struct termios param;

if(tcgetattr(fd, &param) < 0) {
fprintf(stderr, "Status get failed.\n");
exit(1);
}

param.c_cflag &= ~PARENB;
param.c_cflag &= ~CSTOPB;
param.c_cflag |= CS8;
param.c_cflag &= ~CRTSCTS;
param.c_cflag |= CREAD | CLOCAL;
param.c_lflag &= ~ICANON;
param.c_lflag &= ~ECHO;
param.c_lflag &= ~ECHOE;
param.c_lflag &= ~ECHONL;
param.c_lflag &= ~ISIG;
param.c_iflag &= ~(IXON | IXOFF | IXANY);
param.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL);
param.c_oflag &= ~OPOST;
param.c_oflag &= ~ONLCR;
cfsetispeed(&param, B57600);
cfsetospeed(&param, B57600);

if(tcsetattr(fd, TCSANOW, &param) < 0) {
fprintf(stderr, "Status set failed.\n");
exit(1);
}
}

static gboolean update_band_info(void *asd) {

int len, antenna, relay;
struct packet packet;
char buf[512], tr = 0;

if((len = read_untilcr(fd, buf, sizeof(buf))) < 0) {
fprintf(stderr, "Read error.\n");
exit(1);
}

if(parse_packet(buf, &packet) < 0) {
printf("Incomplete packet -- skipping.\n");
return 1;
}

switch(packet.cmd) {
case 0xc4: // C4 = Bandmaster 5 status
if(sscanf(packet.data, "0%1d01EEEEEEEEEEEE%4d", &antenna, &relay) != 2) {
printf("Error extracting antenna & relay information.");
antenna = 99;
}
if(antenna > 0 && antenna < MAX_ANTENNAS)
sprintf(buf, "<span color='blue'>%s (relays = %d)</span>", antennas[antenna-1], relay);
else
sprintf(buf, "<span color='red'>Unknown</span>");
gtk_label_set_markup(GTK_LABEL(ant), buf);
break;
case 0x45: // Stack match
sscanf(packet.data, "%2x", &antenna);
if(antenna >= 128) {
antenna -= 128; // transmitting
tr = 1;
}
if(antenna >= 0 && antenna < MAX_SM)
sprintf(buf, "<span color='blue'>%s (%s)</span>", stack_match[antenna], tr?"TX":"RX");
else
strcpy(buf, "<span color='red'>Unknown state</span>");
gtk_label_set_markup(GTK_LABEL(sm), buf);
break;
case 0x13: // decoder data
printf("Decoder data ignored.\n");
break;
default:
printf("Unknown packet (%x) - ignored.\n", packet.cmd);
}
return 1;
}

void closewin(GtkWidget *win, gpointer data) {

gtk_main_quit();
}

int main(int argc, char *argv[]) {

GtkWidget *window;
GtkWidget *ant_label, *sm_label, *hbox1, *hbox2, *vbox;

gtk_init(&argc, &argv);
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_title(GTK_WINDOW(window), "Antenna Status");
gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
gtk_window_set_default_size(GTK_WINDOW(window), 350, 60);
g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(closewin), NULL);

ant_label = gtk_label_new("Antenna:");
gtk_label_set_xalign(GTK_LABEL(ant_label), 0.0); // 0 = left, 1 = right
sm_label = gtk_label_new("Stack match:");
gtk_label_set_xalign(GTK_LABEL(sm_label), 0.0);
ant = gtk_label_new("-----");
gtk_label_set_xalign(GTK_LABEL(ant), 0.5);
sm = gtk_label_new("-----");
gtk_label_set_xalign(GTK_LABEL(sm), 0.5);

hbox1 = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 2);
hbox2 = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 2);
vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 2);

gtk_box_pack_start(GTK_BOX(hbox1), ant_label, TRUE, TRUE, 5);
gtk_box_pack_start(GTK_BOX(hbox1), ant, TRUE, TRUE, 5);

gtk_box_pack_start(GTK_BOX(hbox2), sm_label, TRUE, TRUE, 5);
gtk_box_pack_start(GTK_BOX(hbox2), sm, TRUE, TRUE, 5);

gtk_box_pack_start(GTK_BOX(vbox), hbox1, TRUE, TRUE, 5);
gtk_box_pack_start(GTK_BOX(vbox), hbox2, TRUE, TRUE, 5);
gtk_container_add(GTK_CONTAINER(window), vbox);

if((fd = open(DEVICE, O_RDONLY)) < 0) {
fprintf(stderr, "Can't open device.\n");
exit(1);
}
set_tty();

g_timeout_add(UPDATE, update_band_info, NULL);

gtk_widget_show_all(window);
gtk_main();

close(fd);
return 0;
}

Compile with (the above is in ant.c file): gcc ant.c -o ant -march=native `pkg-config –cflags –libs gtk+-3.0`

Switched from VNC(RFB) to RDP for remote access

In order to migrate from XOrg to Wayland, I switched from x11vnc & TigerVNC combo (uses RFB protocol) to krdp & remmina (uses RDP protocol). Remmina scrolling was kind of slow at first but after discovering the “step size for auto-scroll” setting in the preferences really helped (10 -> 30). Now the krdp is very picky but it seems that android app aRDP works with it once H264 is enabled.

Beverage on ground (BOG) testing

Made 200′ long beverage on ground (BOG) for receiving on 160m and 80m bands:

Feed point connected to 50 Ohm coax (no impedance transformer). Coax shield connected to ground and hot to the BOG wire.

End of the BOG is ground terminated (ground rod) with a 320 Ohm resistor (white).

The antenna is oriented approximately to Bouvet Island (3Y0K). In some cases seems to give better signal to noise as compared to the transmission antenna (inverted L with 45 full length radials).

Bandmaster V info widget

Wrote the following info widget for Bandmaster V (Linux):

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>
#include <qt6/libqt6c.h>

// Define the antenna names
#define ANTENNA1 "6m beam"
#define ANTENNA2 "WARC beam"
#define ANTENNA3 "Stack"
#define ANTENNA4 "40m beam"
#define ANTENNA5 "No antenna"
#define ANTENNA6 "No antenna"
#define ANTENNA7 "No antenna"
#define ANTENNA8 "No antenna"
#define ANTENNA9 "No antenna"
#define ANTENNA10 "No antenna"
#define ANTENNA11 "No antenna"
#define ANTENNA12 "No antenna"

#define DEVICE "/dev/serial/by-id/usb-FTDI_FT232R_USB_UART_B003ISA1-if00-port0"

int fd;
void *widget, *label;

int read_untilcr(int fd, char *buf, int maxlen) {

int pos = 0, bytes_rd;

while(1) {
if((bytes_rd = read(fd, buf + pos, 1)) < 0) return -1;
if(bytes_rd == 0) {
sleep(1); // could be less than 1sec
continue;
}
if(!pos && (*buf == '\r' || *buf == '\n')) continue;
if(pos == maxlen) return -1;
if(buf[pos] == '\r') break;
pos++;
}
buf[pos] = '\0';
return pos;
}

void set_tty() {

struct termios param;

if(tcgetattr(fd, &param) < 0) {
fprintf(stderr, "Status get failed.\n");
exit(1);
}

param.c_cflag &= ~PARENB;
param.c_cflag &= ~CSTOPB;
param.c_cflag |= CS8;
param.c_cflag &= ~CRTSCTS;
param.c_cflag |= CREAD | CLOCAL;
param.c_lflag &= ~ICANON;
param.c_lflag &= ~ECHO;
param.c_lflag &= ~ECHOE;
param.c_lflag &= ~ECHONL;
param.c_lflag &= ~ISIG;
param.c_iflag &= ~(IXON | IXOFF | IXANY);
param.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL);
param.c_oflag &= ~OPOST;
param.c_oflag &= ~ONLCR;
cfsetispeed(&param, B57600);
cfsetospeed(&param, B57600);

if(tcsetattr(fd, TCSANOW, &param) < 0) {
fprintf(stderr, "Status set failed.\n");
exit(1);
}
}

char *txt_noant = "<b><font color='red'>No antenna.</font></b>",
*txt_40m = "<b><font color='blue'>40m beam.</font></b>",
*txt_warc = "<b><font color='blue'>WARC beam.</font></b>",
*txt_stack = "<b><font color='blue'>Stack.</font></b>",
*txt_6m = "<b><font color='blue'>6m beam.</font></b>";

void update_band_info(void *asd) {

int len, antenna, relay;
char buf[512];

if((len = read_untilcr(fd, buf, sizeof(buf))) < 0) {
fprintf(stderr, "Read error.\n");
exit(1);
}
if(sscanf(buf, "[TC4RFA29H00M0%1d01EEEEEEEEEEEE%4d]", &antenna, &relay) != 2) return; // Some other mesg

switch(antenna) {
case 1:
sprintf(buf, "<b><font color='blue'>%s (%d)</font></b>", ANTENNA1, relay);
q_label_set_text(label, buf);
break;
case 2:
sprintf(buf, "<b><font color='blue'>%s (%d)</font></b>", ANTENNA2, relay);
q_label_set_text(label, buf);
break;
case 3:
sprintf(buf, "<b><font color='blue'>%s (%d)</font></b>", ANTENNA3, relay);
q_label_set_text(label, buf);
break;
case 4:
sprintf(buf, "<b><font color='blue'>%s (%d)</font></b>", ANTENNA4, relay);
q_label_set_text(label, buf);
break;
case 5:
sprintf(buf, "<b><font color='blue'>%s (%d)</font></b>", ANTENNA5, relay);
q_label_set_text(label, buf);
break;
case 6:
sprintf(buf, "<b><font color='blue'>%s (%d)</font></b>", ANTENNA6, relay);
q_label_set_text(label, buf);
break;
case 7:
sprintf(buf, "<b><font color='blue'>%s (%d)</font></b>", ANTENNA7, relay);
q_label_set_text(label, buf);
break;
case 8:
sprintf(buf, "<b><font color='blue'>%s (%d)</font></b>", ANTENNA8, relay);
q_label_set_text(label, buf);
break;
case 9:
sprintf(buf, "<b><font color='blue'>%s (%d)</font></b>", ANTENNA9, relay);
q_label_set_text(label, buf);
break;
case 10:
sprintf(buf, "<b><font color='blue'>%s (%d)</font></b>", ANTENNA10, relay);
q_label_set_text(label, buf);
break;
case 11:
sprintf(buf, "<b><font color='blue'>%s (%d)</font></b>", ANTENNA11, relay);
q_label_set_text(label, buf);
break;
case 12:
sprintf(buf, "<b><font color='blue'>%s (%d)</font></b>", ANTENNA12, relay);
q_label_set_text(label, buf);
break;
default:
q_label_set_text(label, "<b><font color='red'>Unknown antennan.</font></b>");
}
}

int main(int argc, char *argv[]) {

QApplication *app;
QTimer *timer;

if((fd = open(DEVICE, O_RDONLY)) < 0) {
fprintf(stderr, "Can't open device.\n");
exit(1);
}
set_tty();

app = q_application_new(&argc, argv);
widget = q_widget_new2();
timer = q_timer_new2(widget);
q_widget_set_window_title(widget, "Antenna selection");
q_widget_resize(widget, 150, 30);
label = q_label_new5("------------------------------", widget);
q_label_set_alignment(label, QT_ALIGNMENTFLAG_ALIGNCENTER);
q_label_set_text_format(label, QT_TEXTFORMAT_RICHTEXT);
q_timer_start(timer, 100);
q_timer_on_timeout(timer, update_band_info);
q_widget_show(widget);
(void) q_application_exec();
q_widget_delete(widget);
q_application_delete(app);

close(fd);
}

Uses libqt6c (QT bindings for C).

Tower and antenna installation

Some photos from the project.

Time to get started!

Sections of Rohn-55g on the ground.

Bearing installation on the tower.

Theodore inspecting the foundation and the rotor

The first piece of Rohn-55g on the rotor

All 140′ of Rohn-55g up!

Antennas going up!

All done!

Final rotor setup.

CQ WW DX Contest CW 2025

I worked 15m single band (high power, unassisted); about 30 hours. Some issues during the contest:

  • 5 hour power outage on Sunday morning. Of course, there was a great EU opening during that time! G E N E R A T O R next time…
  • hamlib/rigctld keying problems that required frequent restarting of both not1mm and rigctld. Switching to cwdaemon for the next contest.
  • It was cold in the shack! I need to work on the insulation!

But still the contest went much better than last year (… new antennas …): 1344 QSOs, 122 countries, and 35 zones.

Ham radio on Linux

The following software works great (on Arch Linux):

  • General logging: qlog which can be found from both AUR and Flatpak
  • Digital modes: WSJT-X_improved from AUR
  • Contesting: not1mm. I recommend using cwdaemon for generating CW rather than CAT with rigctld. The latter is buggy and can crash frequently. Install using the uv system (python).