Amateur Radio – FT8 Digital Mode

This post is a work in progress. It will be updated over time as I find more relevant information.

What is FT8?

FT8 is a popular form of digital weak signal communication used primarily by amateur radio operators to communicate on amateur radio bands with a majority of traffic occurring on the HF amateur bands.

How to use FT8?

To use FT8 you will need a transceiver with the ability to be controlled by your computer. I use my Icom IC-705 connected to a Microsoft Surface Pro by USB to “play” FT8.

Tuning your rig (Icom IC-705) and setup for best results.

What constitutes an FT8 contact?

An FT8 Contact is a series of messages passed between 2 amateur radio operators. The six standard messages are:


Each call is usually followed by the next in order with the two parties alternating until both have sent a 73.

Variations – Sometimes an operator may use FT8 in turbo mode and short cut some of the messages to speed up a contact.

  • In response to a CQ (Call 1) the replying OP may reply with Call 3 instead of Call 2
  • In Response Call 4, call 5 may be shortened to (M7GBS VK6DEV R73)


Wikipedia – FT8 (

First HAM signals on my SDR

Last night I setup my new Nooelec StartXR SDR on the tables outside and connected the Ham-it-up module and antenna.

I successfully received my first HF digital signals. It was very noisy as the telescopic antenna supplied with the kits is not the best for HF receiving. So the next project will be to build (or maybe buy) myself a better antenna for receiving HF.

There seems to be quite a bit of QRM at my place, so that seems like something I’ll need to track down.

My First QSL via email

Making contacts between Ham’s is one of the most interesting parts of Amateur Radio. But don’t let the lack of a license hold you back. You can QSL via email and and WebSDR.

I’m currently studying for my foundation license so transmitting in not an option. I’ve been using several software defined radios that are available on the Internet.

Last weekend I was listening on the 10m band using a KiwiSDR located near Westdale WA. (South East of Perth, grid square: OF87hp)

While listening, I heard a CQ call from VZ1UOK. Not having a way of talking back, I looked up his details and shot him an email with my QSL to his call.

After 3 days he do back to me. I finally completed my first QSL via email. I can’t wait to get licensed and setup some gear so I can QSL on the air.

I’m becoming an Amateur Radio Operator

Over the last few weeks I decided I would finally go ahead and get my Amateur Radio License. It’s not the first time I’ve though about it. Back in 2014 I was looking at it but circumstances at the time meant it was not to be. So now I’m determined to become an amateur radio operator

Last weekend I made contact the the Bunbury Radio Club and Monday evening I attended a meeting at the Parade Hotel. The club has quite the history and has been regularly meeting for more than 30 years. There were 4 members present and my application for membership was accepted.

Check out the site at

There’s so much to learn and I’ve booked into the Ham College in August to study and take the test for my foundation license.

WebSDR’s are a great thing. I’ve spent. few hours this week listening around the bands.

I look forward to getting on the air shortly and get some equipment set up soon.

Waiting for Async code to complete

Use DispatchGroups to achieve this. You can either get notified when the group’s enter() nand leave() calls are balanced:

func myFunction() {
    var a: Int?

    let group = DispatchGroup()

    DispatchQueue.main.async {
        a = 1

    // does not wait. But the code in notify() gets run 
    // after enter() and leave() calls are balanced

    group.notify(queue: .main) {

Or you can wait…

func myFunction() {
    var a: Int?

    let group = DispatchGroup()

    // avoid deadlocks by not using .main queue here .qosDefault).async {
        a = 1

    // wait ...

    print(a) // you could also `return a` here

Trigger IOS Network Permission Check

These two functions attempt to trigger the network privacy policy dialogue on an IOS device.

/// Does a best effort attempt to trigger the local network privacy alert.
/// It works by sending a UDP datagram to the discard service (port 9) of every
/// IP address associated with a broadcast-capable interface. This should
/// trigger the local network privacy alert, assuming the alert hasn’t already
/// been displayed for this app.
/// This code takes a ‘best effort’. It handles errors by ignoring them. As
/// such, there’s guarantee that it’ll actually trigger the alert.
/// - note: iOS devices don’t actually run the discard service. I’m using it
/// here because I need a port to send the UDP datagram to and port 9 is
/// always going to be safe (either the discard service is running, in which
/// case it will discard the datagram, or it’s not, in which case the TCP/IP
/// stack will discard it).
/// There should be a proper API for this (r. 69157424).
/// For more background on this, see [Triggering the Local Network Privacy Alert](
func triggerLocalNetworkPrivacyAlert() {
    let sock4 = socket(AF_INET, SOCK_DGRAM, 0)
    guard sock4 >= 0 else { return }
    defer { close(sock4) }
    let sock6 = socket(AF_INET6, SOCK_DGRAM, 0)
    guard sock6 >= 0 else { return }
    defer { close(sock6) }
    let addresses = addressesOfDiscardServiceOnBroadcastCapableInterfaces()
    var message = [UInt8]("!".utf8)
    for address in addresses {
        address.withUnsafeBytes { buf in
            let sa = buf.baseAddress!.assumingMemoryBound(to: sockaddr.self)
            let saLen = socklen_t(buf.count)
            let sock = sa.pointee.sa_family == AF_INET ? sock4 : sock6
            _ = sendto(sock, &message, message.count, MSG_DONTWAIT, sa, saLen)
/// Returns the addresses of the discard service (port 9) on every
/// broadcast-capable interface.
/// Each array entry is contains either a `sockaddr_in` or `sockaddr_in6`.
private func addressesOfDiscardServiceOnBroadcastCapableInterfaces() -> [Data] {
    var addrList: UnsafeMutablePointer<ifaddrs>? = nil
    let err = getifaddrs(&addrList)
    guard err == 0, let start = addrList else { return [] }
    defer { freeifaddrs(start) }
    return sequence(first: start, next: { $0.pointee.ifa_next })
        .compactMap { i -> Data? in
                (i.pointee.ifa_flags & UInt32(bitPattern: IFF_BROADCAST)) != 0,
                let sa = i.pointee.ifa_addr
            else { return nil }
            var result = Data(UnsafeRawBufferPointer(start: sa, count: Int(sa.pointee.sa_len)))
            switch CInt(sa.pointee.sa_family) {
            case AF_INET:
                result.withUnsafeMutableBytes { buf in
                    let sin = buf.baseAddress!.assumingMemoryBound(to: sockaddr_in.self)
                    sin.pointee.sin_port = UInt16(9).bigEndian
            case AF_INET6:
                result.withUnsafeMutableBytes { buf in
                    let sin6 = buf.baseAddress!.assumingMemoryBound(to: sockaddr_in6.self)
                    sin6.pointee.sin6_port = UInt16(9).bigEndian
                return nil
            return result