{"id":135,"date":"2026-04-12T16:47:01","date_gmt":"2026-04-12T22:47:01","guid":{"rendered":"http:\/\/aa6kj.hopto.org\/wp\/?p=135"},"modified":"2026-04-12T20:17:04","modified_gmt":"2026-04-13T02:17:04","slug":"bandmaster-v-hacking","status":"publish","type":"post","link":"http:\/\/aa6kj.hopto.org\/wp\/?p=135","title":{"rendered":"Bandmaster V hacking"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">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. Here is the new code:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>#include &lt;stdio.h&gt;\n#include &lt;string.h&gt;\n#include &lt;stdlib.h&gt;\n#include &lt;fcntl.h&gt;\n#include &lt;unistd.h&gt;\n#include &lt;termios.h&gt;\n#include &lt;gtk\/gtk.h&gt;\n\n\/\/ Define the antenna names\n#define ANTENNA1 \"6m beam\"\n#define ANTENNA2 \"WARC beam\"\n#define ANTENNA3 \"20-15-10 stack\"\n#define ANTENNA4 \"40m beam\"\n#define ANTENNA5 \"No antenna\"\n#define ANTENNA6 \"BOG NE\"\n#define ANTENNA7 \"BOG SW\"\n#define ANTENNA8 \"BOG NW\"\n#define ANTENNA9 \"BOG SE\"\n#define ANTENNA10 \"No antenna\"\n#define ANTENNA11 \"No antenna\"\n#define ANTENNA12 \"No antenna\"\n#define MAX_ANTENNAS 12\n\n\/\/ Device for BM5\n#define DEVICE \"\/dev\/serial\/by-id\/usb-FTDI_FT232R_USB_UART_B003ISA1-if00-port0\"\n\n\/\/ Update every 100 msec\n#define UPDATE 100\n\nchar *antennas&#91;] = {ANTENNA1, ANTENNA2, ANTENNA3, ANTENNA4, ANTENNA5, ANTENNA6, \n                    ANTENNA7, ANTENNA8, ANTENNA9, ANTENNA10, ANTENNA11, ANTENNA12};\n\n#define MAX_SM 11\nchar *stack_match&#91;] = {\"No antenna\", \"Top antenna\", \"Mid antenna\", \"Top+mid antennas\",\n                       \"Bottom antenna\", \"Top+bot antennas\", \"Mid+bot antennas\",\n                       \"All antennas\", \"AUX\", \"Unknown\", \"Phase\"}; \n\n\/*\n * RX antenna control:\n *\n * BOG NE = antenna 6 (no antenna; no relays selected)\n * BOG SW = antenna 7 (select relay 9)\n * BOG NW = antenna 8 (select relay 10)\n * BOG SE = antenna 9 (select relay 11)\n *\n * BOG selected for can be selected for 160, 80, 75, and 60. TX antenna is unaffected (radio -&gt; acom -&gt; inv L)\n *\n * Numbers below (6, 7, 8, 9) refer to antenna numbers defined in bandmaster V. Bands 40 - 6m are always\n * unchanged. Remember bandmaster uses hexadecimal everywhere.\n *\n *\/\nchar *bogs&#91;] = {\"&#91;R29T09H060606060402030203020301]\\r\", \"&#91;R29T09H070707070402030203020301]\\r\",\n                \"&#91;R29T09H080808080402030203020301]\\r\", \"&#91;R29T09H090909090402030203020301]\\r\"};\n\nint fd, tx_on = 0;\nGtkWidget *ant, *sm;\n\nstruct packet {\n  unsigned char cmd;\n  unsigned char dst;\n  unsigned char src;\n  unsigned char channel;\n  int len;\n  unsigned char data&#91;256];\n};\n\nint read_untilcr(int fd, char *buf, int maxlen) {\n  \n  int pos = 0, bytes_rd;\n  \n  while(1) {\n    if((bytes_rd = read(fd, buf + pos, 1)) &lt; 0) return -1;\n    if(bytes_rd == 0) {\n      sleep(1); \/\/ could be less than 1sec\n      continue;\n    }\n    if(!pos &amp;&amp; (*buf == '\\r' || *buf == '\\n')) continue;\n    if(pos == maxlen) return -1;\n    if(buf&#91;pos] == '\\r') break;\n    pos++;\n  }\n  buf&#91;pos] = '\\0';\n  return pos;\n}\n\nint parse_packet(char *str, struct packet *packet) {\n  \n  memset((void *) packet, 0, sizeof(struct packet));\n  if(sscanf(str, \"&#91;T%2xR%2x%2xH%2xM%&#91;^]]\", &amp;(packet-&gt;cmd), &amp;(packet-&gt;dst), &amp;(packet-&gt;src), &amp;(packet-&gt;channel), &amp;(packet-&gt;data)) != 5) return -1;\n  return 0;\n}\n\nvoid set_tty() {\n  \n  struct termios param;\n  \n  if(tcgetattr(fd, &amp;param) &lt; 0) {\n    fprintf(stderr, \"Status get failed.\\n\");\n    exit(1);\n  }\n  \n  param.c_cflag &amp;= ~PARENB;\n  param.c_cflag &amp;= ~CSTOPB;\n  param.c_cflag |= CS8;\n  param.c_cflag &amp;= ~CRTSCTS;\n  param.c_cflag |= CREAD | CLOCAL;\n  param.c_lflag &amp;= ~ICANON;\n  param.c_lflag &amp;= ~ECHO;\n  param.c_lflag &amp;= ~ECHOE;\n  param.c_lflag &amp;= ~ECHONL;\n  param.c_lflag &amp;= ~ISIG;\n  param.c_iflag &amp;= ~(IXON | IXOFF | IXANY);\n  param.c_iflag &amp;= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL);  \n  param.c_oflag &amp;= ~OPOST;\n  param.c_oflag &amp;= ~ONLCR;\n  cfsetispeed(&amp;param, B57600);\n  cfsetospeed(&amp;param, B57600);\n  \n  if(tcsetattr(fd, TCSANOW, &amp;param) &lt; 0) {\n    fprintf(stderr, \"Status set failed.\\n\");\n    exit(1);\n  }\n}\n\nstatic gboolean update_band_info(void *asd) {\n  \n  int len, antenna, relay;\n  struct packet packet;\n  char buf&#91;512];\n  \n  if((len = read_untilcr(fd, buf, sizeof(buf))) &lt; 0) {\n    fprintf(stderr, \"Read error.\\n\");\n    exit(1);\n  }\n  \n  if(parse_packet(buf, &amp;packet) &lt; 0) {\n    printf(\"Incomplete packet -- skipping.\\n\");\n    return 1;\n  }\n  \n  switch(packet.cmd) {\n  case 0xc4:  \/\/ C4 = Bandmaster 5 status\n    if(sscanf(packet.data, \"%2x01EEEEEEEEEEEE%4x\", &amp;antenna, &amp;relay) != 2) {\n      printf(\"Error extracting antenna &amp; relay information.\");\n      antenna = 99;\n    }\n    if(antenna &gt; 0 &amp;&amp; antenna &lt; MAX_ANTENNAS)\n      sprintf(buf, \"&lt;span color='blue'&gt;%s (relays = %x)&lt;\/span&gt;\", antennas&#91;antenna-1], relay);\n    else\n      sprintf(buf, \"&lt;span color='red'&gt;Unknown&lt;\/span&gt;\");\n    gtk_label_set_markup(GTK_LABEL(ant), buf);\n    break;\n  case 0x45: \/\/ Stack match\n    sscanf(packet.data, \"%2x\", &amp;antenna);\n    if(antenna &gt;= 128) {\n      antenna -= 128; \/\/ transmitting\n      tx_on = 1;\n    } else tx_on = 0;\n    if(antenna &gt;= 0 &amp;&amp; antenna &lt; MAX_SM)\n      sprintf(buf, \"&lt;span color='blue'&gt;%s (%s)&lt;\/span&gt;\", stack_match&#91;antenna], tx_on?\"TX\":\"RX\");\n    else\n      strcpy(buf, \"&lt;span color='red'&gt;Unknown state&lt;\/span&gt;\");\n    gtk_label_set_markup(GTK_LABEL(sm), buf);\n    break;\n  case 0x13: \/\/ decoder data\n    printf(\"Decoder data ignored.\\n\");\n    break;\n  default:\n    printf(\"Unknown packet (%x) - ignored.\\n\", packet.cmd);\n  }\n  return 1;\n}\n\nvoid closewin(GtkWidget *win, gpointer data) {\n\n  gtk_main_quit();\n}\n\nvoid bog_ne(GtkWidget *win, gpointer data) {\n\n  if(!tx_on) {\n    printf(\"BOG NE selected\\n\");\n    write(fd, bogs&#91;0], strlen(bogs&#91;0]));\n  }\n}\n\nvoid bog_sw(GtkWidget *win, gpointer data) {\n\n  if(!tx_on) {\n    printf(\"BOG SW selected\\n\");\n    write(fd, bogs&#91;1], strlen(bogs&#91;1]));\n  }\n}\n\nvoid bog_nw(GtkWidget *win, gpointer data) {\n\n  if(!tx_on) {\n    printf(\"BOG NW selected\\n\");\n    write(fd, bogs&#91;2], strlen(bogs&#91;2]));\n  }\n}\n\nvoid bog_se(GtkWidget *win, gpointer data) {\n\n  if(!tx_on) {\n    printf(\"BOG SE selected\\n\");\n    write(fd, bogs&#91;3], strlen(bogs&#91;3]));\n  }\n}\n\nint main(int argc, char *argv&#91;]) {\n\n  GtkWidget *window;\n  GtkWidget *ant_label, *sm_label, *hbox1, *hbox2, *hbox3, *vbox, *button1, *button2, *button3, *button4;\n\n  gtk_init(&amp;argc, &amp;argv);\n  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);\n  gtk_window_set_title(GTK_WINDOW(window), \"Antenna Status\");\n  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);\n  gtk_window_set_default_size(GTK_WINDOW(window), 350, 80);\n  g_signal_connect(G_OBJECT(window), \"destroy\", G_CALLBACK(closewin), NULL);\n\n  ant_label = gtk_label_new(\"Antenna:\");\n  gtk_label_set_xalign(GTK_LABEL(ant_label), 0.0); \/\/ 0 = left, 1 = right\n  sm_label =  gtk_label_new(\"Stack match:\");\n  gtk_label_set_xalign(GTK_LABEL(sm_label), 0.0);\n  ant = gtk_label_new(\"-----\");\n  gtk_label_set_xalign(GTK_LABEL(ant), 0.5);\n  sm = gtk_label_new(\"-----\");\n  gtk_label_set_xalign(GTK_LABEL(sm), 0.5);\n\n  hbox1 = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 2);\n  hbox2 = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 2);\n  hbox3 = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 2);\n  \n  vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 2);\n\n  gtk_box_pack_start(GTK_BOX(hbox1), ant_label, TRUE, TRUE, 5);\n  gtk_box_pack_start(GTK_BOX(hbox1), ant, TRUE, TRUE, 5);\n\n  gtk_box_pack_start(GTK_BOX(hbox2), sm_label, TRUE, TRUE, 5);\n  gtk_box_pack_start(GTK_BOX(hbox2), sm, TRUE, TRUE, 5);\n\n  gtk_box_pack_start(GTK_BOX(vbox), hbox1, TRUE, TRUE, 5);\n  gtk_box_pack_start(GTK_BOX(vbox), hbox2, TRUE, TRUE, 5);\n\n  button1 = gtk_button_new_with_label(\"BOG NE\");\n  button2 = gtk_button_new_with_label(\"BOG SW\");\n  button3 = gtk_button_new_with_label(\"BOG NW\");\n  button4 = gtk_button_new_with_label(\"BOG SE\");\n  gtk_box_pack_start(GTK_BOX(hbox3), button1, TRUE, TRUE, 5);\n  gtk_box_pack_start(GTK_BOX(hbox3), button2, TRUE, TRUE, 5);\n  gtk_box_pack_start(GTK_BOX(hbox3), button3, TRUE, TRUE, 5);\n  gtk_box_pack_start(GTK_BOX(hbox3), button4, TRUE, TRUE, 5);\n\n  gtk_box_pack_start(GTK_BOX(vbox), hbox3, TRUE, TRUE, 5);\n\n  gtk_container_add(GTK_CONTAINER(window), vbox);\n  g_signal_connect(button1, \"clicked\", G_CALLBACK(bog_ne), NULL);\n  g_signal_connect(button2, \"clicked\", G_CALLBACK(bog_sw), NULL);\n  g_signal_connect(button3, \"clicked\", G_CALLBACK(bog_nw), NULL);\n  g_signal_connect(button4, \"clicked\", G_CALLBACK(bog_se), NULL);\n\n  if((fd = open(DEVICE, O_RDWR)) &lt; 0) {\n    fprintf(stderr, \"Can't open device.\\n\");\n    exit(1);\n  }\n  set_tty();\n\n  bog_ne(NULL, NULL); \/\/ BOG NE is the default on start up\n\n  g_timeout_add(UPDATE, update_band_info, NULL);\n\n  gtk_widget_show_all(window);\n  gtk_main();\n\n  close(fd);\n  return 0;\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">In addition to performing automatic antenna switching for my tower (40 m &#8211; 6 m), it now can switch between four receive antennas for 160 m &#8211; 60m. You probably guessed that these are four BOGs oriented NE, SW, NW, SE.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The previously described ft8cmp code and the above code are now both on github.com:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><a href=\"https:\/\/github.com\/jmeloranta\/ft8cmp.git\n\">https:\/\/github.com\/jmeloranta\/ft8cmp.git\n<\/a><a href=\"https:\/\/github.com\/jmeloranta\/antenna-widget.git\">https:\/\/github.com\/jmeloranta\/antenna-widget.git<\/a><\/code><\/pre>\n","protected":false},"excerpt":{"rendered":"<p>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. Here is the new code: In addition to performing automatic antenna switching for my tower (40 m [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[5],"tags":[],"class_list":["post-135","post","type-post","status-publish","format-standard","hentry","category-ham_radio"],"_links":{"self":[{"href":"http:\/\/aa6kj.hopto.org\/wp\/index.php?rest_route=\/wp\/v2\/posts\/135","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/aa6kj.hopto.org\/wp\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/aa6kj.hopto.org\/wp\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/aa6kj.hopto.org\/wp\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/aa6kj.hopto.org\/wp\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=135"}],"version-history":[{"count":4,"href":"http:\/\/aa6kj.hopto.org\/wp\/index.php?rest_route=\/wp\/v2\/posts\/135\/revisions"}],"predecessor-version":[{"id":140,"href":"http:\/\/aa6kj.hopto.org\/wp\/index.php?rest_route=\/wp\/v2\/posts\/135\/revisions\/140"}],"wp:attachment":[{"href":"http:\/\/aa6kj.hopto.org\/wp\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=135"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/aa6kj.hopto.org\/wp\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=135"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/aa6kj.hopto.org\/wp\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=135"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}