tcollen

tcollen

Open890: A web-based ham radio UI built with Phoenix LiveView

I was asked in another thread to share some details about my project so here goes!

open890: A web-based ham radio remote control app built with Phoenix & LiveView.

Introduction

open890 is an open source, browser-based remote control app for the Kenwood TS-890 ham radio. It is written in Phoenix and LiveView. Binary builds are also provided for Ubuntu and Windows.

The TS-890 is an HF (high-frequency, think shortwave) ham radio transceiver. The radio provides connectivity to a computer via a USB serial or ethernet connection. It also has a well-documented command interface supplied by the manufacturer. Once properly connected, the radio can be told to push data to the client as things change (think: knobs turned, buttons pushed, etc), as well as be completely remotely controlled via software.

Front display of the TS-890

Screenshot of the onboard display. The radio is tuned in to a pirate shortwave station playing music.

The radio can also send high-speed spectrum data to clients, as well as stream audio to and from the radio via PCM samples streamed over UDP. While not strictly open source, the radio’s programming interface is quite nicely documented, and almost every function of this radio is remotely controllable – this includes changing frequency, reading various meters in real-time, accessing menu items, etc.

My main goal with this project was to build a web-based UI that allowed me to use it as a companion on my desk while I operate the radio. The software provided by Kenwood does not provide a very good user experience, and left a lot of room for improvement.

General Architecture

open890 is a Phoenix application, with a small collection of supervisors and genservers for maintaining a TCP connection to the radio. There is a RadioConnectionSupervisor which uses DynamicSupervisor to maintain connections to radios.

From there, the RadioConnectionSupervisor starts and stops TCPClients which use :gen_tcp and handles various socket state management, and knows how to “sign in” to the radio and start the various data streams. Elixir (and :gen_tcp) are very well suited to this task, and the callback-oriented style of reacting to incoming messages has made it a very natural fit for interfacing with the radio.

Once the connection is properly started, individual messages from the radio are broadcast to the app’s endpoint and picked up on the other side in the main LiveView. On the LiveView side, broadcast messages are parsed out, funneled through a single Dispatch.dispatch function, albeit with many many function heads.

Following the Single-Responsibility Principle here has paid off, with perhaps 50-ish odd messages currently implemented, it’s very easy to add new messages as I build out features. Dispatch’s single responsibility is to determine which messages are valid (i.e., the ones I have implemented), use the Extract module to get a usable data structure from the message in question, and assign the result to the socket. Extract’s sole responsibility is to take a string message from the radio and turn it into a useful piece of data for the LiveView.

def dispatch("KS" <> _ = msg, socket) do
  key_speed = Extract.key_speed(msg)
  socket |> assign(:cw_key_speed, key_speed)
end

Example dispatching the “key speed” message that determines how fast the radio sends morse code.

High-speed spectrum scope via SVG & HTML Canvas

One of the challenges with the project was the high speed of data, particularly over the ethernet connection. The TS-890 has two different spectra streams, the main one being a list of 640 integer values representing the radio spectrum that the radio is currently receiving. At its highest data speed, the radio sends 30 updates per second. These 640 values can represent a span of between 5 and 500 kHz of radio bandwidth, and so at lower spans, you therefore see a more detailed picture of the radio signal.

Once parsed out into a usable list of values, the list of values for the spectrum are split off into two directions. One, they are simply set as an assign on the socket. In the LiveView template, I render an SVG, and use a couple of small helper functions to translate the values to SVG coordinates. Modern browsers are pretty good at drawing SVG quickly, and to make it work, you can simply render e.g. a <polygon> element and dynamically update the points inside it:

<polygon id="bandSpectrum" class="spectrum"
  points={RadioViewHelpers.scope_data_to_svg(@band_scope_data)} />

Here @band_scope_data contains that list of spectra values, and RadioViewHelpers.scope_data_to_svg just transforms it to the specific SVG coordinates. It’s that easy!

Secondly, to draw the “waterfall” display (the area under the spectrum), I create an HTML canvas element, and use LiveView hooks to subscribe to the spectrum data. For each new line of spectrum data, I shift the current set of pixels in the canvas down one line, and draw a new line of pixels at the top. Instead of something that looks like a chart, the waterfall simply maps the signal strength (the height in the spectrum display) to a color, using the HSL color space. “Low” signal levels are blue, around to green, then yellow, and red.

https://i.imgur.com/23azWon.png

The end result is an animated display that updates quickly. The screenshot here doesn’t quite capture the full speed of updates, but here’s the general idea:

https://i.imgur.com/TVkGgna.gif

LiveView allows you to change any HTML element or attribute very quickly (and efficiently!), and with SVG this is extremely powerful. Browsers animate changes to coordinates, and so any bit of data that you can translate into screen coordinates, you can easily animate. I use basic SVG shapes to draw and animate grids, polygons representing the exact frequencies the radio is tuned to, audio filter characteristics, and so on.

The final result. Frequency readout, spectrum display, waterfall, and various meters all update in real time, thanks to Phoenix and LiveView.

Future updates

I have a laundry list of features to keep implementing before I release version 1.0, and I’ve been slowly working on the app in some fashion over the past year in my free time.

I’ve also managed to reverse engineer the audio data stream that the radio sends, and I’ve managed to get the audio playing in the browser - all using Phoenix.PubSub etc. I hope to eventually integrate that work into the main codebase and get it to a bunch of eager ham radio operators who are using the app :slight_smile:

Code

The code is available at GitHub - tonyc/open890: A web-based remote UI for the Kenwood TS-890. · GitHub, although without a physical radio on your desk, you will probably not be able to start it up and really check it out.

There’s a lot more to the app (I could go on and on for pages, magazine-style), so if you’ve got any questions on general approach, lessons learned, etc, feel free to ask, and I’ll do my best to answer!

Most Liked

kip

kip

ex_cldr Core Team

WOW! Thats amazing!

f0rest8

f0rest8

What @kip said:

Thank you for making this and sharing! :heart:

tcollen

tcollen

Yeah. In reality, it’s typically only one person at a time, but with multiple browser windows open, they all stay synced together, and one person making changes causes the other clients to see the updates as well.

The app also supports multiple connections to radios, so in theory if you had a bunch of them going at once, it would maintain all the connections.

Where Next?

Popular in Discussions Top

scouten
I’m looking for a host for the server part of a small (personal) side project that I’m working on. It’s currently written in Node.js and ...
New
kostonstyle
Hi all How can I compare haskell with elixir, included tools, webservices, ect. Thanks
New
sergio
There’s a new TIOBE index report that came out that shows Elixir is still not in the top 50 used languages. It also goes on to call Elix...
New
Crowdhailer
I’ve been hearing much about the new formatter and it’s something I have been keen to try. I find examples buy far the most illuminating...
248 19327 150
New
rower687
Hi all, I’ve been reading a lot about the “let it crash” term and how supervising processes and the whole messaging passing make an elixi...
New
jer
I’ve been using umbrellas for a while, and generally started off (on greenfield projects at least) by isolating subapps based on clearly ...
New
nburkley
AWS re:Invent is on at the moment with some interesting announcements. One new feature in particular is the Lambda Runtime API for AWS La...
New
Nvim
Anybody knows a comprehensive comparison of Django and Phoenix, thanks for the help. Where are they similar? Where do they differ the m...
New
Fl4m3Ph03n1x
Background A few days ago I was listening to The future of Elixir from Elixir Talks, with Dave Thomas (@pragdave ) and Brian Mitchell. I...
New
paulanthonywilson
I like Umbrella projects and pretty much always use them for personal Elixir stuff, especially Nerves things. But I don’t think this is ...
New

Other popular topics Top

Qqwy
Update: How to use the Blogs &amp; Podcasts section You can post links to your blog posts or podcasts either in one of the Official Blog...
3271 127089 1222
New
rms.mrcs
Hi, I need to transform a list of numbers into a map where the keys are the indexes and the values are the original values of the list. ...
New
josevalim
Hi everyone, One of the features added to Elixir early on to help integration with Erlang code was the idea of overridable function defi...
New
gshaw
What is the idiomatic way of matching for not nil in Elixir? E.g., First way: defp halt_if_not_signed_in(conn, signed_in_account) when...
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
klo
Got a question about when to concat vs. prepending items to list then reversing to achieve appending. So i know lists boil down to [1 | ...
New
saif
Hello everyone, Long time lurker first time poster here. I’ve recently begun working on Elixir full-time again! :raised_hands: It’s been...
New
svb
Hi! Currently I want to submit a form by pressing the Enter key. However, since my input field is of type “textarea” this is just adds a...
New
sergio
Kind of like when jquery came out, it was super necessary. Existing drag and drop libraries have a bunch of baggage to support old browse...
New
AstonJ
Posting this to see if we can make things easier for people to get into Neovim. If you use Neovim and have a favourite distro please let ...
New

Latest on Elixir Forum

Elixir Forum

We're in Beta

About us Mission Statement