Author Archives: eloranta

NW / SE BOG area cleared

Cleared up & installed another 300′ reversible BOG along NW / SE direction (black/red antenna wire on the ground):

Still need to apply some round up to keep the greenies at bay. At this point, I lead my dog in the number of ticks 5 to 4 ! Won’t be going back there before I get proper tick spray… Except I could not stay away. Installed the ground rod and radials. Next remote RX antenna switch.

Bandmaster V hacking

I improved the antenna status widget to also change antennas for bands. The protocol for this device has not been published, so I had to snoop on the RS-232 traffic to find out how to talk to it.

In addition to performing automatic antenna switching for my tower (40 m – 6 m), it now can switch between four receive antennas for 160 m – 60m. You probably guessed that these are four BOGs oriented NE, SW, NW, SE.

The previously described ft8cmp code and the bandmaster code are now both on github.com:

https://github.com/jmeloranta/ft8cmp.git
https://github.com/jmeloranta/antenna-widget.git

My BOG antenna finally works!

The KD9SV reversible beverage on ground (RBOG) now finally works! Tested on 160 m, 80 m, and 60 m towards EU. It beats my inverted L on both 160 and 80 m bands. The antenna also now shows proper F/B (switching between forward and reverse; 10-12 dB).

The antenna is 300′ long made out of non-twisted pair cable (18 AWG) laid along the ground. Both ends have 4′ ground rod + three 40′ radials. The feed points have mix 31 ferrite rings with 9 rounds of the RG6 coax wrapped around them (choke).

Here is a quick test on 80 m FT8 (evening; band open to EU):

Some stations that RBOG did not hear (pointed towards EU) were located in south America. The current RBOG is oriented NE/SW and I need to add another one for NW/SE. For explanation of the graph, see the earlier blog post.

Antenna comparison using dual RX FT8

Using the two independent receivers on my Elecraft K4D, I can compare the performance of two receive antennas. RX1 is set to antenna 1 (inverted L) and RX2 to beverage on ground (BOG; 160′ long pointed towards EU). Then putting the radio in diversity mode, the output is set up such that RX1 audio appears on the left stereo channel and RX2 on the right channel. On the computer I run two independent WSJT-X instances which read the audio from the left and right channels. Note that the second WSJT-X must be started with the -r option to avoid conflict with the first instance. After running the set up for a couple of hours, I then collect the relevant data from the corresponding ALL.TXT files (.local/share/WSJT-X*) and feed them to the following program (at https://github.com/jmeloranta/ft8cmp.git) to analyze.

The cty.dat file above is the AD1C cty.dat ASCII file that includes the countries of interest for the analysis. In my case I excluded the US and Canada (removed them from the cty.dat file), so the test focused on DX receive. Since BOG is directional, I should also exclude DX stations that are not in the direction of the BOG (to be done). Note that the program compares data obtained from the two receivers during the exact same time slice. This eliminates the QSB and other effects that could play a significant role if different time slices were used in the comparison. The two RXs are identical, so the RX bias is minimal. This could be tested by reversing the roles of the two RXs. Of course, the test yields the data within the 15 second FT8 transmission cycle but both antennas have the exact same change of decoding the signals during that time slice.

The first test was on 80 m FT8 (evening time; band open to EU). The x-axis is the decoded signal strength in dB from the RX that was connected to the inverted L and y-axis signal strength from the RX connected to the BOG. The red line shows where inv L and BOG would be equally good in receiving. Points representing a single decode above the red line has BOG obtaining better signal to noise (S/N) where as below it inverted L has better S/N. Results from collecting data for a couple of hours is shown below.

Well, BOG seems to work (very short test!!!) but another thing that this highlights the fact that running two independent WSJT-X processes with different types of antennas can improve FT8 receive a lot. There were many instances where BOG was able to decode but not inverted L and vice versa. The stations that BOG could not copy were mostly in South America (BOG was oriented towards EU). The “BOG only copy” stations on the other hand were in EU.

Below is another comparison between 60 m dipole and the same BOG as above (band open to EU):

On 60 m band BOG beat dipole hands down. There were many stations that BOG heard but dipole did not. And dipole was not able to hear anything that BOG did not.

NOTE: While this can still be used, multi program (see another blog post) can collect the statistics for direct visualization.

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.

UPDATE: krdb etc. are just to slow and clumsy (can’t start automatically before login), so had to switch back to VNC (x11vnc).

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).