Articles

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 www.bunburyradioclub.com

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()
    group.enter()

    DispatchQueue.main.async {
        a = 1
        group.leave()
    }

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

    group.notify(queue: .main) {
        print(a)
    }
}

Or you can wait…

func myFunction() {
    var a: Int?

    let group = DispatchGroup()
    group.enter()

    // avoid deadlocks by not using .main queue here
    DispatchQueue.global(attributes: .qosDefault).async {
        a = 1
        group.leave()
    }

    // wait ...
    group.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](https://developer.apple.com/forums/thread/663768).
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
            guard
                (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
                }
            default:
                return nil
            }
            return result
        }
}