While writing some software defined radio applications, I have found myself wanting to run them headless on some raspberry pi or similar, but then occasionally connect to it and inspect it live.
I want to stream sample data, and tags data, and drop data on overflow. The main use case is for a UI, so it should not block the rest of the processing if the network or UI is slow, or disconnected.
I also want the protocol to support lossless sources, for example a stream coming from a SigMF file.
I’ve been dragging my heels on this, because surely this protocol already exists. And if I make a new one then nobody else is compatible with it.
GNU Radio has ZeroMQ sink and source blocks, but a UI running Continue reading
Until now, my HF antennas have mostly been off the shelf. I’ve gone out with an AlexLoop (hmm, alexloop.com seems to be down. I don’t know if that’s permanent), and the Elecraft AX1. They’ve both been fine, but have some drawbacks. The AX1 is a bit finicky about getting its radial(s) good enough, and the AlexLoop is a bit tricky to set up in a temporary but stable way.
I have not have good experience with the half wave G5RV, yet, for some reason.
So since all HF antennas are compromises, why not try an inverted V dipole? I had the UniBalun recommended to me, so I went with that. There are some minor things to think about, which is what this blog post is about.
To become an antenna, I also needed antenna wire, and a way to put the middle of the dipole high up, with the antenna wire extending straight diagonally down. For a 20m (14MHz) antenna, I cut two 5.2m pieces, expecting to need to either cut or fold them back a bit, for the right resonance.
On the UniBalun side I could just crimp an M4 ring to attach the antenna wire, Continue reading
Previously I successfully did some receiving and some transmitting over the amateur radio transponder on the satellite QO-100. That is to say, my FT8 was heard by others (but not me, due to my small dish), and I successfully decoded FT8 from some others.
There are three problems with my QO-100 ground station:
I really appreciate the constant FT8 activity when tuning my station. Sure, I could look at the band edge beacons, but I like this too. And strangely, I can not actually find the edge beacons.
Today is about the third issue: Frequency stability. Because the B200 was GPS locked last time, the main suspect was the LNB. The Othernet Bullseye LNB TCXO from Passion-Radio.com is a fine LNB, but it’s Continue reading
I’ve been playing a bit with OFDM in software defined radio lately. And after watching a video that wasn’t even close to correct, I thought I’d write down my understanding in a blog post. I wouldn’t call myself an expert, so worst case someone will correct me, and I’m happy to be corrected.
In my experiments with OFDM my initial goal is understanding, so e.g. I won’t blindly use some hierarchical GNU Radio block.
I think that in order to internalize OFDM, we first need to remind ourselves how a purely “real” wave becomes a complex number, at a high level.
The real signal is a value that’s a function of time. Glossing over details, this is not feasible for most of our signals, as we would need to provide a new value to the transmitter at a rate twice as high as the highest frequency we care about. For 2.4GHz experiments, we’d need 4.8 billion samples per second.
Quadrature sampling allows us to represent a slice of bandwidth at a manageable rate. We can then provide the I/Q data to hardware, instructing it to hold that “until further notice”. Continue reading
As I mentioned in a previous post I wanted to make a WASM version of the RF scanner / RF survey tool, so that it can be run without any drivers or tools installed on the local machine.
Well, now I have. It’s available at https://rf-survey.habets.se/ as a GitHub page, automatically deploying from the github repo.
If you don’t have a compatible SDR, then you can at least enjoy the video here.
Step by step for doing a screen recording on Sway.
I want to capture exact 16x9 dimensions, and exactly the the right content. I do it pretty rarely, but yeah this should be automated.
Run this command, and move the pointer to the window you want to record.
$ sleep 2;swaymsg -t get_tree | jq '.. | objects | select(.focused? == true) | {app_id, id, name, window_properties}'
{
"app_id": "brave-browser",
"id": 802,
"name": "WebUSB RF Survey - Brave",
"window_properties": null
}
swaymsg '[con_id=802] floating enable,move position 200 px 256 px, resize set width 1600 px height 901 px'
(yes, 901. This may be because of my 1.3 scaling on that monitor, giving an off by one pixel due to rounding)
sleep 2;swaymsg -t get_tree | jq '.. | objects | select(.focused? == true) | .rect'
{
"x": 2120,
"y": 256,
"width": 1600,
"height": 900
}
wf-recorder -g '2120,257 1600x900' -f output.mp4
(yes, 257. Add one. Probably same reason)
$ slurp
2163,333 1514x37
Continue readingI have previously blogged about doing RF surveys using software defined radio. The most interesting aspect to me has been to inventory the business of the different WiFi channels, in order to not clash with neighbouring access points.
I’ve now written better software for doing these surveys.
2.4GHz is a mess (overlapping channels), has much less spectrum, and goes through walls more than 5GHz worth. There’s also a bunch of other crap on 2.4GHz polluting the airwaves, including microwave ovens. So if you’ve thought about this at all, you know to stay off of 2.4GHz.
It’s tempting to get a QuadRF in order to add more dimensions to the 5GHz RF survey, but so far I’ve resisted buying this particular toy.
I’m plotting the spectrum power in both dB and linear. With linear scale the busyness really pops.
There’s even a bunch of noise between the channels. Is it microwaves? Or someone running the 1/5/9/13 channel split? No, if anything it looks like channel 14, which is not legal here.
As a power WiFi user I started using U-NII-3 channels (149-165) as soon as they Continue reading
In a previous post I made pipes in unix shells more reliable. Well, it had some drawbacks. I’ll summarize the problem, the failed previous version, and then show the new and improved one.
Downstream processes in a unix shell pipe cannot know if the upstream finished successfully, or exited with an error. This means that it can’t know if it should “commit” the data it received.
Example uses:
$ pg_dumpall | xz -9 | google_cloud_storage_upload gs://bucket/path/postgres.dump
$ generate_data | psql --single-transaction
In both of these cases you want the right hand side to STOP, and not finalize the upload or commit the transaction.
$ goodpipe <<EOF
[
["gsutil", "cat", "gs://example/input-unsorted.txt"],
["sort", "-S300M", "-n"],
["gzip", "-9"],
["gsutil", "cp", "-", "gs://example/input-sorted-numerically.txt.gz"]
]
EOF
This works fine for simple cases, but doesn’t support tee or per-command
environment variables very well.
And I don’t want to invent a complex language, so my replacement took a different path.
wp instead wraps the input and/or output with a very minimal encapsulating
protocol. This allows normal data to pass through, but still allows the
downstream to get EOF as metadata.
This is a follow-up to a previous post about raytracing Quake demos.
But first, the money shot:
And flat shaded and textured videos. Youtube is Very Aggressive™ with its compression, so the quality there is not good. For pixel quality the above images showcase it better.
One of my original reasons for creating the quake demo povray files is that it was a good source of data for 3D experiments. POV-Ray is a great raytracer, though entirely CPU (no GPU) and no longer state of the art.
POV-Ray has plenty of built in options, but takes forever to render the 30-60fps demos I want to play with.
Also POV-Ray is AGPL now, so nope nope nope nope nope. That’s a dead end.
We live in interesting times. We could be living in a time when no two people are running the same email client, or music player, or shell. There used to be a barrier to writing these things custom. I know people who wrote their own shell and use it as a daily driver. I wrote my own email client, and use that.
There are many people out there, me included, who Continue reading
I’ve been able to find some time, lately, to work on my project backlog. And because it’s 2026, I’ve been using AI as a diligent intern.
I’ve ranted before about
seccomp, but
still used it for a project or two. But then, rarely, it triggered an
unexpected openat. That’s exactly the kind of I do want to detect and kill
the binary for, so I don’t just want to allow it. I want to know where it’s
coming from.
strace showed it’s trying to read /proc/sys/vm/overcommit_memory.
It’s certainly not my code. But just the Rust transitive dependency tree is quite a few crates:
$ cargo tree | sed -r 's/^[^a-z]+//;s/ .*//' | sort -u | wc -l
236
Step 1 was to run it in gdb, and reproduce the problem. But it’s a bit
trickier than that, because seccomp fully kills the process, so no backtrace.
And setting breakpoints requires a few more syscalls to work, just for the
process to work under gdb (e.g. sigaltstack).
And turns out some calls fail with EINTR if running under a debugger.
Yes, I can fix all these things. But why not put the AI intern on it?
This is just a short followup to the last RustRadio post. If you came for more rants about C, you’ll be disappointed.
I’ve never been that interested in writing UI code, including HTML. You can see the “programmer art” in the screenshots linked from www.habets.pp.se.
And then the slightly different tech section, that doesn’t serve much of a purpose now that we have github.
I’ve not been happier with GTK, QT, and the others either.
But [RustRadio][rustradio] needs a UI.
I feel like the browser is the most stable and portable UI. So I’d already decided on that. So now I have to manually do a bunch of DOM manipulation, to create an interactive UI? Or worse, learn the React/Angular/Whatever flavor of the day, that will be obsolete by next afternoon? Gag me with a spoon.
For now I’m just continuing to focus on the SDR and architectural parts of RustRadio, and I’m letting the LLM-written code do the HTML manipulation.
Yeah, it’s kinda vibe coding. But doesn’t use unsafe, and it demonstrably
outputs what I want. (I mean, sure it may require some follow-up prompts), so
who cares?
The Continue reading
If he had been a programmer, Cardinal Richelieu would have said “Give me six lines written by the hand of the most expert C programmer in the world, and I will find enough in them to trigger undefined behavior”.
Nobody can write correct C, or C++. And I say that as someone who’s written C and C++ on an almost daily basis for about 30 years. I listen to C++ podcasts. I watch C++ conference talks. I enjoy reading and writing C++.
C++ has served us well, but it’s 2026, and the environment of 1985 (C++) or 1972 (C) is not the environment of today.
I’m definitely not the first to say this. I remember reading a post by someone prominent about a decade ago saying that a good case can be made that use of C++ is a SOX violation. And while I was not onboard with the rest of their rant (nor their confusion about “its” vs “it’s”), I never disagreed about that point.
With time I found it to be more and more true. WAY more things are undefined behavior (UB) than you’d expect.
Everyone knows that double-free, use after free, accessing outside the bounds of an Continue reading
I’ve previously pointed out that the AX.25 implementation in the kernel is pretty poor. It’s not really being maintained, and even when it gets fixes after I reported it, with people running LTS OSs it can take like 5 years before before the fix actually reaches users, if ever. So when writing applications, you still have to work around kernel bugs from a decade ago. This makes it kind of pointless to upstream patches.
The exception is security patches, and reading between the lines of why the AX.25 code is now being removed from the kernel, it sounds like maybe some LLM (like the looming “Mythos” and the related Glasswing) may have found some severe problems. But even if there aren’t any known security problems yet, having code is now more of a liability than ever. Code needs to be removed, or taken responsibility of. (tangent about ffmpeg at the bottom of this post)
With the kernel code removed, say goodbye to the old walkthrough.
Well, not “new”, per se, but “replacement”.
With the socket based API about to be gone, we need some other way for applications to send packets and Continue reading
You may have seen it too. This trend of “I wrote some software to solve a problem. I think it’s pretty great. Does anyone have any feedback?”. Maybe it’s a budget app. Or some company management thingy, tracking sales. Or invoicing.
Maybe you take a look. It looks pretty slick. But then you get a feeling of uncanny valley. It’s just not right. Maybe you can’t even put your finger on it.
I’m not an accountant, so when I see some accounting software do something in a different way, it’s interesting. Why is it that way? What can I learn from the fact that a professional thinks it should be this way?
You already know what’s weird about it, if nothing else because of the title of this post. The software works this way because the LLM wrote it that way. There’s no reason. It’s not even wrong.
How do you give “feedback” on that? My feedback would be that you don’t understand the problem you’re trying to solve, and have shown no sign you intend to understand it, so how could you possibly think you can solve it?
You’re not asking for feedback. You’re asking for someone Continue reading
I’ve previously blogged about RustRadio, my GNU Radio like framework for writing software defined radio applications in Rust. And now there’s more progress of an interesting kind.
Anything that tries to do something similar to GNU Radio needs a few things:
In addition to these, GNU Radio also has the excellent GNU Radio Companion for interactive creation of flowgraphs, but I’m not tackling that yet.
I have a core framework, and some components (blocks). But the UI has been a bit lacking.
I’ve played around with TUI applications, but I always knew I also wanted to support having a UI in the browser. I’m not as interested in adding support for QT or Windows native UI. The browser will do fine.
There are two ways to get the UI in the browser:
While I’ll want (1) eventually, and have some ideas about that, this post is about running everything in the browser, using Wasm.
I know that this is just scratching the surface Continue reading
If you start two threads, bound to run on exactly the same core, and one is SCHED_IDLE, why does it
I’ve previously mentioned my io-uring webserver tarweb. I’ve now added another interesting aspect to it.
As you may or may not be aware, on Linux it’s possible to send a file descriptor from one process to another over a unix domain socket. That’s actually pretty magic if you think about it.
You can also send unix credentials and SELinux security contexts, but that’s a story for another day.
I want to run some domains using my webserver “tarweb”. But not all. And I want to host them on a single IP address, on the normal HTTPS port 443.
Simple, right? Just use nginx’s proxy_pass?
Ah, but I don’t want nginx to stay in the path. After SNI (read: “browser saying which domain it wants”) has been identified I want the TCP connection to go directly from the browser to the correct backend.
I’m sure somewhere on the internet there’s already an SNI router that does this, but all the ones I found stay in line with the request path, adding a hop.
A few reasons:
My last post about Go got some attention.
In fact, two of my posts got attention that day, which broke my nginx since I was running livecount behind nginx, making me run out of file descriptors when thousands of people had the page opened.
It’s a shame that I had to turn off livecount, since it’d be cool to see the stats. But I was out of the country, with unreliable access to both Internet and even electricity in hotels, so I couldn’t implement the real fix until I got back, when it had already mostly died down.
I knew this was a problem with livecount, of course, and I even allude to it in its blog post.
Anyway, back to programming languages.
The reactions to my post can be summarized as:
I respect the first two. The last one has to be from people who are too emotionally invested with their tools, and take articles like this Continue reading
Previous posts Why Go is not my favourite language and Go programs are not portable have me critiquing Go for over a decade.
These things about Go are bugging me more and more. Mostly because they’re so unnecessary. The world knew better, and yet Go was created the way it was.
For readers of previous posts you’ll find some things repeated here. Sorry about that.
Here’s an example of the language forcing you to do the wrong thing. It’s very helpful for the reader of code (and code is read more often than it’s written), to minimize the scope of a variable. If by mere syntax you can tell the reader that a variable is just used in these two lines, then that’s a good thing.
Example:
if err := foo(); err != nil {
return err
}
(enough has been said about this verbose repeated boilerplate that I don’t have to. I also don’t particularly care)
So that’s fine. The reader knows err is here and only here.
But then you encounter this:
bar, err := foo()
if err != nil {
return err
}
if err = Continue readingI bought a GPS Disciplined Oscillator (GPSDO), because I thought it’d be fun for various projects. Specifically I bought this one.
I started by calibrating my ICOM IC-9700. I made sure it got a GPS lock, and connected it to the 9700 10MHz reference port, with a 20dB attenuator inline, just in case. Ok, the receive frequency moved a bit, but how do I know it was improved? My D75 was still about 200Hz off frequency.
Segal’s law parahrased: “Someone with one radio knows what frequency they’re on. Someone with two radios is never sure”.
Unless, of course, that person has two radios with disciplined oscillators. Which I do. I also have a USRP B200 with an added GPSDO accessory.
Sidenote: wow, that’s gotten expensive. Today I’d probably use the same GPSDO from DXPatrol instead. Note that if you do have the GPSDO installed in the B200, then you cannot use an external 10MHz reference. It’s a known issue. Then again if you paid this much, why would you not use it?
First I thought that surely the best reference would be the default, so I should be able to just send Continue reading