News feed

  • CARI 2.1 is out, along with new libcari

    Version 2.1 of CARI, the Common Amateur Radio Interface, has just been released. The biggest change as compared with the previous 1.3 is a clean way to end a transmission. The master sends an empty uplink (UL) message, and the device plays every buffered sample, ramps down, and disables its transmitter on its own. The release also turns the remaining open points into firm rules: status precedence, socket binds, supervision edge cases, TX stop, and UL underrun trips. Rebinding or closing the UL socket now discards any unread data. A more complete changelog is available in the commands.md file.

    CARI spec: https://github.com/M17-Project/CARI


    The protocol now has its own C library, libcari. It covers frame encoding and decoding, capabilities, sample scaling and baseband compression (based on FLAC), plus ready-made ZeroMQ slave and master layers. Both tools below are built on it.

    libcari repository: https://github.com/M17-Project/libcari


    cari-dummy-device now emulates the planned SX1255-based RRU v2: one full-duplex I/Q subdevice at 400–510 MHz and 125, 250 or 500 kHz, with FLAC compression and supervision. Its emulated hardware runs in real time, so backpressure and underruns behave as they would on the real board. The tool can be used to develop and test CARI masters without hardware.

    cari-dummy-device repository: https://github.com/M17-Project/cari-dummy-device


    cari-ctrl has been rewritten for CARI 2.x. It can inspect and configure a device, capture RX samples, transmit files or test tones, and monitor supervision data, all from one command line. cari-host is now obsolete and the repository is archived (read-only).

    cari-ctrl repository: https://github.com/M17-Project/cari-ctrl

    Bug reports and pull requests are welcome. Have fun while we are working on RRU v2 🙂

  • OpenRTX v0.4.5 released

    Changes include M17-related fixes.

    Full changelog: https://github.com/OpenRTX/OpenRTX/releases/tag/v0.4.5

    Give it a spin 😉

  • rpi-dashboard v1.2.0 is out

    It is strongly suggested to download the latest installer and run it. From now on, the installer checks for a newer version of itself on each run, so this manual download is needed only once.

    Dashboard:

    • security fixes: changing settings requires a password, shell injection and arbitrary file reading and writing through the config paths are fixed, CSRF protection on all forms
    • RF signal quality is now shown as bit error rate (BER), averaged over the whole voice stream, with color thresholds from a noise-limited test, and the M17 hostfile comes from the new source and is only downloaded when it has changed (thanks to Jim N1ADJ)
    • renamed a couple of menu entries
    • times use the configured timezone
    • the “Max. text messages” setting now works
    • expired map markers are removed
    • TX power is read from the config instead of being hardcoded
    • the reflector and radio status is now correct regardless of when the connection was made
    • added a “Device control” panel with reboot/shutdown buttons
    • the last element on each page no longer sits under the fixed footer

    The installer script:

    • the script automatically sets the modem type based on user input
    • the web server can write only config.php and files/, no longer the whole dashboard directory
    • nginx: it now blocks web access to the dashboard’s files/ directory

  • gr-m17 v1.0.0 is out

    Finally, after all these years, we have reached a point where everything seems to work. That’s the 1.0.0 reference point. I would like to express my sincere gratitude to Jean-Michel Friedt for all the help that he provided.

    Recent changes include:

    • Build: clean on GNU Radio 3.10, with the submodules updated (Codec2-mod, micro-ecc, libm17)
    • Dialogs: tidy, with hex key entry, all META types, and the decoder following the received stream
    • Encoder: correct SOT/EOT/SMS messages handling, a reliable stream loop, “continuous mode” (running without external message control, basically what we had before), multi-block Text Data, and SMS up to 821 bytes
    • Signing, scrambler, and AES now work end to end, and were checked independently against an external ECDSA reference, the spec’s LFSR polynomials, and Python’s cryptography AES
    • Decoder output is defined for unusable frames: Codec2 silence, chosen by listening (replacing invalid frames with zeros or inserting silence both sound worse)
    • Console output is readable and consistent (a lot of cleanups)
    • Categorization: we split the M17 part from Codec2

    Side effects (positive!):

    • Spec: the scrambler section was clarified, and the AES counter contradiction fixed
    • M17_Implementations: the LSF CRC bug in m17-coder-sym is now fixed

    Have fun!

  • M17 at HamExpo 2026, Le Mans

    Niall, F5VCV, has just sent us a photo from the ongoing HamExpo ham fair.

    From left to right: Niall F5VCV, Laurent F1OBK, and Jérôme F4HGI.

    It is always great to see people advocating M17 use! Merci beaucoup!

  • Updates

    • M17 Specification document. Bumped to 2.0.6. We found two problems. There are 369 bits at the output of the puncturer in BERT mode, one of them has to be discarded (the last one). Next, the maximum altitude that can be stored in the META GNSS position field was calculated incorrectly, the value is now fixed.
    • libm17. Viterbi decoder works faster now. All decoders are re-entrant (older API stayed). Caller can decide which way to choose.
    • T9 text entry. Faster execution. Memory optimizations should follow.

  • Codec2-mod and LinHT updates

    Let’s start with Codec2-mod. I brought back another 4kB of otherwise wasted space – complex FFT where real-valued counterpart can be used. Next, in-place FFTs removed – no hidden mallocs. Encoder phase estimation and using arctangent in the synthesis path – gone. I also thoroughly benchmarked the result versus vanilla Codec2 (damn, the repository is such a mess – why do they keep all of the non-codec stuff inside a “codec2” repo?). Code and results are here. See the tables in the readme for more details.

    LinHT Rev C boards have arrived! We are waiting for a few missing parts to show up and we will continue with assembly.

    Photos courtesy of Vlastimil, OK5VAS.

    Stay tuned.

  • Codec2 test suite

    I expanded my old Bash script a bit. It runs ViSQOL-based quality and compatibility tests for Codec2-mod vs. vanilla Codec2 (3200 mode), including cross-encoding between the two (this is the new addition).

    Here’s the script: https://github.com/M17-Project/Codec2-mod-eval

    You need to provide a set of WAV files for the script to work on. Using an English speech corpus such as LibriSpeech or any other from OpenSLR is a good idea.

    Have fun!

  • Circling back at authentication – M17 ECDSA stream signatures

    M17 is probably one of the very few modes allowing users to sign their data streams.

    In June 2024, I tested micro-ecc and ST CMOX library on a Module17. The results were published on Twitter:

    Finally some good news regarding digital signatures. I’ve been experimenting with ST’s CMOX library and just got 160-bit ECDSA to run on the Module17. It takes around 8.25ms to sign a 16-byte M17 voice stream digest. The signature can be appended to the voice stream.

    The MCU is STM32F405RGT6, clocked at 168 MHz.

    Signing with NIST-R P-256 (SECP256R1) curve takes 14.86 ms.

    If I remember correctly, CMOX library was used to get that 15ms result (secp256r1 curve). Micro-ecc yielded much worse execution times.

    Today, I improved the micro-ecc library using comb multiplication. Preliminary tests using Renode (as I don’t have any STM32 Nucleo with me, nor any OpenRTX target) are promising. The signing time reduced from 27 down to 14ms, without CMOX, at 100MHz (emulated STM32F411). That’s great, because CMOX library can not be used on microcontrollers other than the original STM32. The library checks the hardware it is executed on and silently corrupts data without even telling. Great, right? This optimization should allow us to run ECDSA efficiently, even on GD32 MCUs.

    The proposed code uses a uECC_COMB_MULT macro, so the user (developer) can select which calculation path to use – the original one or optimized.

    Getting rid of this roadblock should get us one step closer to having M17 authentication support in OpenRTX.

  • Codec2-mod updates

    You thought we gave up on Codec2 just because we have already improved it considerably? Wrong!

    After a short discussion with Silvano IU2KWO, an interesting proposal emerged: split the whole codec2_t structure into two separate, smaller encoder and decoder parts. That way you no longer have to push the whole structure on the stack and fill it up with FFT twiddle factors, if you only need to use one part of it. Now, if you want to encode or decode speech, just use either encoder or decoder. This simplifies things and optimizes memory usage further. This brings significant savings that are required by OpenRTX (and probably any implementation running on limited-resources MCUs). The old path using a large structure is still available (e.g. for duplex operation).

    On top of that, I also tweaked the KISS FFT internals to use built-in sincosf() calls instead of using standard sin() / cos(). Using doubles is generally not a great idea, unless you really have to do so. We do not, so I took one step further and eliminated all double-precision promotions that were hiding in the code. Yes, there were many such places, especially where M_PI was used, along with some other macros.

    The code is available in the split-no-double branch.

    I prepared a quick benchmark. The results are below.

    Codec2-mod branchcodec2_initcodec2_encoder_initcodec2_decoder_initcodec2_encodecodec2_decode
    main67.905––6.43711.804
    split-no-double8.9585.8243.1365.5558.559
    Speedup7.6x––1.2x1.4x
    Branches comparison. Times are average for a run of 1000, in milliseconds.

    An STM32F411 clocked at 100MHz was used for both tests with the code compiled using -Os flag (gcc 14.3.0).

    Next, using a set of 2,000 wave files, mostly 3 to 20 seconds long, I compared the vanilla Codec2 with our experimental branch. As the last time, I measured ViSQOL scores for both: original wave file -> encoder -> decoder -> decoded wave file. The average score of the new code is +0.005. That suggests the applied changes are inaudible (theoretically – real-life tests will follow). The test also calculated Hamming distance between the encoded files for both implementations – the no-double differs in less than 0.5% of the bits. This means that the new code does not generally agree with Codec2, but that does not make the bitstreams incompatible. Small rounding errors cause this but, as shown by the speech quality metric, all cancel out.

    ViSQOL score / Hamming distance checks (vanilla Codec2 vs Codec2-mod split-no-doubles branch).

    There is one more test that should be performed: encode samples with Codec2 and decode the bitstream with Codec2-mod, then switch the direction and listen to both files.

    Big shout out to Silvano IU2KWO for nudging me in the right direction.