Meshtastic modulator plugin
Introduction
This plugin can be used to code and modulate a transmission signal based the LoRa Chirp Spread Spectrum (CSS) modulation scheme with a Meshtastic payload.
The basic idea of the CSS modulation is to transform each symbol of a MFSK modulation to an ascending frequency ramp shifted in time. It could equally be a descending ramp but this one is reserved to detect a break in the preamble sequence (synchronization). This plugin has been designed to work in conjunction with the Modmeshtastic demodulator plugin that should be used ideally on the reception side.
LoRa is a property of Semtech and the details of the protocol are not made public. However a LoRa compatible protocol has been implemented based on the reverse engineering performed by the community. It is mainly based on the work done in https://github.com/myriadrf/LoRa-SDR. You can find more information about LoRa and chirp modulation here:
This LoRa encoder is designed for experimentation. For production grade applications it is recommended to use dedicated hardware instead.
Modulation characteristics from LoRa have been augmented with more bandwidths and FFT bin collations (DE factor) but this should be rendered ineffective with Meshtastic presets.
Fixes done by Copilot (GPT 5.3 Codex) from first PR
- TX header explicit-header sizing (SF-2 first block)
- TX backend Meshtastic auto-radio-derive (sync word 0x2B, PHY params)
- RX robust header lock (offset 0..2 + delta -2..+2 scan with realignment)
- This is on Rx side (MeshtasticDemodSink)
- Diagnostic loopback logs with token correlation (commented out)
- Type mismatches resolved
Now a full end to end test with a Meshtastic text message works:
- ✅ TX emits Meshtastic-compatible sync word (0x2B)
- ✅ RX header lock resumes with offset/delta scan recovery
- ✅ Payload CRC validation passes end-to-end
- ✅ Full decode chain: preamble → sync → header → payload → message text displayed
Meshtastic frame mode
In LoRa coding mode, if the message text starts with MESH: the plugin will build a full Meshtastic over-the-air frame (16-byte header + protobuf Data payload) and encrypt it with AES-CTR when enabled.
Quick example (Text mode):
MESH:from=0x11223344;to=0xffffffff;id=0x1234;channel_name=LongFast;key=default;port=TEXT;text=hello mesh;want_ack=1;hop_limit=3
Supported fields:
- Header:
to,from,id,hop_limit,hop_start,want_ack,via_mqtt,channel_hash(orchannel),channel_name,next_hop,relay_node - Preset helper:
preset/modem_preset(LONG_FAST,LONG_SLOW,LONG_TURBO,LONG_MODERATE,MEDIUM_FAST,MEDIUM_SLOW,SHORT_FAST,SHORT_SLOW,SHORT_TURBO) maps to channel naming/hash defaults - Radio planner:
region,channel_num,frequency/freq/freq_hz,frequency_offset/frequency_offset_hz - Data protobuf:
port/portnum,text,payload_hex,payload_base64/payload_b64,want_response,dest,source,request_id,reply_id,emoji,bitfield - Crypto:
key/psk(default,none,simple0..10,hex:<...>,base64:<...>),encrypt(true|false|auto)
Notes:
- If
encrypt=truethenkeymust resolve to 16 or 32 bytes. - If
channel_hashis not provided, it is derived fromchannel_nameandkeyusing Meshtastic hash rules. - In GUI mode, when the active message is a valid
MESH:command, the plugin auto-applies LoRa settings (BW/SF/CR/DE,syncword=0x2B) frommodem_preset. - If
region(+ optionalchannel_num) or explicitfrequencyis present, GUI mode also auto-centers the channel when device center frequency is known. - If a
MESH:command is invalid it is rejected (no payload is emitted), and an error is logged.
Interface
The top and bottom bars of the channel window are described here
1: Frequency shift from center frequency of reception
Use the wheels to adjust the frequency shift in Hz from the center frequency of reception. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2.
2: Channel power
The signal is frequency modulated with a constant envelope hence this value should be constant. To prevent possible overshoots the signal is reduced by 1 dB from the full scale. Thus this should always display -1 dB.
3: Channel mute
Use this button to mute/unmute transmission.
4: Bandwidth
This is the bandwidth of the ChirpChat signal. Similarly to LoRa the signal sweeps between the lower and the upper frequency of this bandwidth. The sample rate of the ChirpChat signal in seconds is exactly one over this bandwidth in Hertz.
In the LoRa standard there are 2 base bandwidths: 500 and 333.333 kHz. A 400 kHz base has been added. Possible bandwidths are obtained by a division of these base bandwidths by a power of two from 1 to 64. Extra divisor of 128 is provided to achieve smaller bandwidths that can fit in a SSB channel. Finally special divisors from a 384 kHz base are provided to allow even more narrow bandwidths.
Thus available bandwidths are:
- 500000 (500000 / 1) Hz
- 400000 (400000 / 1) Hz not in LoRa standard
- 333333 (333333 / 1) Hz
- 250000 (500000 / 2) Hz
- 200000 (400000 / 2) Hz not in LoRa standard
- 166667 (333333 / 2) Hz
- 125000 (500000 / 4) Hz
- 100000 (400000 / 4) Hz not in LoRa standard
- 83333 (333333 / 4) Hz
- 62500 (500000 / 8) Hz
- 50000 (400000 / 8) Hz not in LoRa standard
- 41667 (333333 / 8) Hz
- 31250 (500000 / 16) Hz
- 25000 (400000 / 16) Hz not in LoRa standard
- 20833 (333333 / 16) Hz
- 15625 (500000 / 32) Hz
- 12500 (400000 / 32) Hz not in LoRa standard
- 10417 (333333 / 32) Hz
- 7813 (500000 / 64) Hz
- 6250 (400000 / 64) Hz not in LoRa standard
- 5208 (333333 / 64) Hz
- 3906 (500000 / 128) Hz not in LoRa standard
- 3125 (400000 / 128) Hz not in LoRa standard
- 2604 (333333 / 128) Hz not in LoRa standard
- 1500 (384000 / 256) Hz not in LoRa standard
- 750 (384000 / 512) Hz not in LoRa standard
- 488 (500000 / 1024) Hz not in LoRa standard
- 375 (384000 / 1024) Hz not in LoRa standard
The ChirpChat signal is oversampled by four therefore it needs a baseband of at least four times the bandwidth. This drives the maximum value on the slider automatically. When using Meshtastic presets you have to make sure this condition is set yourself.
16: Invert chirp ramps
The LoRa standard is up-chirps for the preamble, down-chirps for the SFD and up-chirps for the payload.
When you check this option it inverts the direction of the chirps thus becoming down-chirps for the preamble, up-chirps for the SFD and down-chirps for the payload.
5: Spread Factor
This is the Spread Factor parameter of the ChirpChat signal. This is the log2 of the possible frequency shifts used over the bandwidth (3). The number of symbols is 2SF-DE where SF is the spread factor and DE the Distance Enhancement factor (6).
6: Distance Enhancement factor
The LoRa standard specifies 0 (no DE) or 2 (DE active). The ChirpChat range is extended to all values between 0 and 4 bits.
The LoRa standard also specifies that the LowDataRateOptimization flag (thus DE=2 vs DE=0 here) should be set when the symbol time defined as BW / 2^SF exceeds 16 ms (See section 4.1.1.6 of the SX127x datasheet). In practice this happens for SF=11 and SF=12 and large enough bandwidths (you can do the maths).
Here this value is the log2 of the number of frequency shifts separating two consecutive shifts that represent a symbol. On the receiving side this decreases the probability to detect the wrong symbol as an adjacent FFT bin. It can also overcome frequency or sampling time drift on long messages particularly for small bandwidths.
In practice it is difficult on the Rx side to make correct decodes if only one FFT bin is used to code one symbol (DE=0). It is therefore recommended to use a factor of 1 or more.
8: Number of preamble chirps
This is the number of preamble chirps to transmit that are used for the Rx to synchronize. The LoRa standard specifies it can be between 2 and 65535. Here it is limited to the 4 to 20 range that corresponds to realistic values. The RN2483 uses 6 preamble chirps. You may use 12 preamble chirps or more to facilitate signal acquisition with poor SNR on the Rx side. Meshtastic imposes a number of 17 preamble chirps.
9: Idle time between transmissions
When sending a message repeatedly this is the time between the end of one transmission and the start of the next transmission.
10: Message and encoding details
10.1: Modulation scheme
- LoRa: LoRa compatible for Meshtastic
10.2: Number of FEC parity bits
Each byte of the payload is split into two four bit nibbles and Hamming code of various "strength" in number of parity bits can be applied to these nibbles. The number of parity bits can vary from 1 to 4. 0 (no FEC) has been added but is not part of the LoRa original standard:
- 0: no FEC
- 1: 1 bit parity thus Hamming H(4,5) applies
- 2: 2 bit parity thus Hamming H(4,6) applies
- 3: 3 bit parity thus Hamming H(4,7) applies
- 4: 4 bit parity thus Hamming H(4,8) applies
10.9: Message selector
This lets you choose which pre-formatted message to send:
- Text: plain text, either just text or formatted text for Meshtastic transmission (see above)
10.11 Play current message immediately
This starts playing the current selected message immediately. It may be necessary to use this button after a change of modulation and/or coding parameters.
10.12: Number of message repetitions
The message is repeated this number of times (use 0 for infinite). The end of one message sequence and the start of the next is separated by the delay specified with the "Idle" slidebar (9)
10.14: Sync word
This is a LoRa specific feature and is the sync word (byte) to transmit entered as a 2 nibble hexadecimal number. For Meshtastic this is 0x2B.
11: Message text
This window lets you edit the message selected in (10.9). You can use %n placeholders that depend on the type of message selected.
- Text: free text message no placeholders
13: Symbol time and message length
This is the duration of a symbol or chirp in milliseconds followed by the message length in the number of symbols
14: Payload time
This is the duration of the message payload in milliseconds. It excludes the preamble, the sync word and synchronization (SFD) sequence.
15: Total time
This is the duration of the full message in milliseconds.

