Building an AX.25 Messenger App with C#, a TNC, and a Bit of Madness
1,046 words, 6 minutes read time.
YT MD-UV390 Digital Dual Band VHF UHF DMR Radio Waterproof Dustproof IP67 Walkie Talkie
It started with a simple idea ā or at least it seemed simple. I was chatting with AI about ways to send text messages from device to device using nothing but the speakers and microphones already built into our phones or laptops. The idea was to modulate the data into frequencies just outside the human hearing range and transmit them. AI made it sound easy ā encode, send, decode. But once I actually started trying to do it, it quickly became clear: it wasnāt simple at all. It wasnāt even close.
That failure wasnāt the end ā it was the beginning. Because during that process, I remembered that I already owned something built for this kind of thing: a Mobilinkd TNC3. It connects via Bluetooth or USB, speaks the AX.25 protocol over KISS, and communicates via AFSK. And so, a new plan was born ā use existing amateur radio tech to send messages between devices. But I didnāt just want to use the TNC ā I wanted to understand what it was doing. That meant learning AX.25, digging into KISS, and writing everything from scratch in C#.
The Stack: Tools and Hardware
For this build, I used:
- C# as the language of choice.
- A Mobilinkd TNC3, connected via USB.
- A TYT MD-UV390 radio, operating in analog mode.
- Cables to connect the TNC to the Radio.
Why AX.25?
The Mobilinkd TNC3 operates using the AX.25 protocol layered over KISS. If you want to send messages through this hardware, you have to understand those protocols. AX.25, originally developed for packet radio in amateur radio use, is efficient, compact, and battle-tested.
I dove into it because the hardware required it ā but what I found was something elegant. The structure is simple enough to understand but flexible enough to do real work. That said, actually implementing it in C# was another story.
Writing the AX.25 Messenger
The goal of the app was simple: send and receive short text messages over radio using the Mobilinkd TNC3 and AX.25 protocol.
The project is open source. You can find the code here:
š GitHub Repository: AX25Messenger
Letās walk through some of the key components.
1. Encoding an AX.25 Frame
Hereās what the code looks like when we build the raw AX.25 frame. We start with destination and source callsigns, then add control and protocol fields, and finally append the actual payload.
public byte[] CreateAX25Frame(string destination, string source, byte[] payload){ var frame = new List<byte>(); // Encode callsigns frame.AddRange(EncodeCallsign(destination, false)); frame.AddRange(EncodeCallsign(source, true)); // Control field (0x03 = UI frame) frame.Add(0x03); // Protocol ID (0xF0 = no layer 3 protocol) frame.Add(0xF0); // Add payload frame.AddRange(payload); return frame.ToArray();}
This code constructs the complete AX.25 frame for a UI (Unnumbered Information) packet. The EncodeCallsign
method handles shifting and bit manipulation, making it compatible with the standard.
2. Interfacing with the TNC over KISS
KISS is a framing protocol that wraps the AX.25 data for transmission over a serial port. Hereās how we encapsulate an AX.25 frame into a KISS frame.
public byte[] WrapKISSFrame(byte[] ax25Frame){ var kissFrame = new List<byte>(); kissFrame.Add(0xC0); // Start delimiter kissFrame.Add(0x00); // Command byte: TNC Data frame foreach (var b in ax25Frame) { if (b == 0xC0) { kissFrame.Add(0xDB); kissFrame.Add(0xDC); } else if (b == 0xDB) { kissFrame.Add(0xDB); kissFrame.Add(0xDD); } else { kissFrame.Add(b); } } kissFrame.Add(0xC0); // End delimiter return kissFrame.ToArray();}
This makes the AX.25 message digestible by the TNC.
3. Sending the Data
Once the KISS frame is ready, itās sent out the serial port like this:
_serialPort.Write(kissFrame, 0, kissFrame.Length);
Where _serialPort
is an instance of SerialPort
in .NET. Simple, fast, and effective.
The Frustrations of Learning While Building
There were many moments during this project that made me want to throw the whole setup out the window. Trying to learn AX.25 and KISS while building an app around them is painful. The documentation is scattered, and much of whatās out there is old, inconsistent, or assumes a Linux environment.
It didnāt help that most of the time, you canāt even tell if your code is broken or if the radio just didnāt key up fast enough. Debugging means digging into the bits of a failed frame, then trying again and hoping for a clean transmission.
And then thereās AI ā which was extremely helpful in doing early code research, mocking up skeletons, and pointing me toward technical docs. But it also hallucinated like crazy when it came to niche protocols. AI tends to guess at how it should be coded when it doesnāt have enough training data, and this project was definitely in that category. I had to train the AI by teaching it the protocol myself before it became useful.
What Didnāt Work
One major limitation of this build: I wasnāt able to test incoming AX.25 messages yet. That part of the project is still under construction. The receive pipeline, decoding the AX.25 frame, and confirming messages are cleanly parsed is a work in progress. Itās the next big hurdle.
Conclusion: A Love Letter to Low-Level Protocols
This whole build was a reminder that sometimes the best learning comes from the weirdest places. I didnāt plan to learn how AX.25 worked. I definitely didnāt plan to write a whole C# interface to a hardware modem. But here we are.
This was a project of persistence, curiosity, and a refusal to let a good idea die. If youāre into coding, radios, or just learning things the hard way, I canāt recommend it enough.
Follow the project on GitHub: AX25Messenger.
And if you enjoyed this write-up or want to follow along as I dive deeper into protocols, microcontrollers, or strange networking ideas, be sure to subscribe to the newsletter.
D. Bryan King
Sources
Disclaimer:
The views and opinions expressed in this post are solely those of the author. The information provided is based on personal research, experience, and understanding of the subject matter at the time of writing. Readers should consult relevant experts or authorities for specific guidance related to their unique situations.
Related Posts
#AFSKModem #AIInRadioProjects #AmateurRadio #amateurRadioTNC #APRSMessaging #AX25 #AX25CCode #AX25Examples #AX25ForProgrammers #AX25LearnByCoding #AX25RawFrames #AX25WithMobilinkd #AX25CMessenger #AX25DigitalMode #AX25Modem #AX25ParserC_ #AX25Protocol #ax25TestSetup #buildingAX25Apps #CAFSKDecoding #CAndKissProtocol #CAndPacketRadio #CAX25 #CHamToolkit #CProjectAX25 #CRadioApp #digitalModeCCode #digitalRadioMessage #hamRadioAndProgramming #hamRadioCExamples #hamRadioCoding #hamRadioCodingTutorial #hamRadioProgramming #hamRadioProtocol #kissAndAfsk #kissCProtocol #kissDecoderTutorial #kissInterface #KISSProtocol #kissProtocolC_ #kissProtocolExample #kissTnc #learnAX25 #learningPacketRadio #MobilinkdTNC3 #packetRadio #packetRadioDev #packetRadioOverAudio #radioMessageApp #radioTextOverAfsk #radioToRadioMessaging #realTimeRadioMessaging #serialRadioCommunication #TNC3AndKiss #TNC3HardwareGuide