Getting cheap AliExpress LoRa modules working on Linux
One of the things I want to do more of this year is electronics. LoRa and Meshtastic in particular have been on my mind for a while. The idea of resilient, off-grid messaging without internet infrastructure is genuinely interesting to me. The appeal for emergencies, remote areas, and situations where normal infrastructure fails is clear.
To start somewhere, I picked up a pair of DX-Smart LoRa modules from AliExpress. They were cheap, which seemed like a reasonable property for a first experiment. Not much information exists about them online, and all the documentation that does exist assumes Windows. Getting them working on Linux required some digging, mostly with LLM assistance.
What LoRa actually is
LoRa (Long Range) is a radio modulation scheme based on chirp spread spectrum. Rather than transmitting on a fixed frequency, a LoRa signal sweeps across a range of frequencies in a chirp pattern. The receiver knows the pattern and can decode the signal even when it is far below the noise floor. This gives LoRa its characteristic long range and interference resistance at the cost of low data throughput.
Spreading factor (SF) controls how many chirps encode each bit. Higher SF means more redundancy: each symbol takes longer to transmit and the signal is easier to decode at long range, but each step up roughly doubles the time on air and halves the data rate, while gaining around 2.5 dB of link budget.
Bandwidth (BW) is the frequency range the chirp sweeps across. Wider bandwidth means faster data rate but shorter range. Common values are 125 kHz, 250 kHz, and 500 kHz.
Coding rate (CR) adds forward error correction. CR 4/5 is the most efficient; CR 4/8 adds the most redundancy at the cost of throughput.
Time on air matters because LoRa is half-duplex: only one module transmits at a time on a given channel, and in most regions there are duty cycle limits on how long you can transmit.
The channel selection determines the centre frequency. Different regions have different allocations: EU uses 868 MHz, Australia uses AU915 (915–928 MHz), North America uses US915. Using the wrong frequency band either means no communication or operating illegally.
The modules
The two modules in this kit are both from DX-Smart, both based on the ASR6601 chip:
| Module | Form factor | Notes |
|---|---|---|
| LR01-900T22S | 16×16 mm SMD, 12 pins | Basic: transparent and fixed-point modes, direct SF/BW/CR control |
| LR02-900T22D | 16×16 mm SMD, 12 pins | Extended: adds key encryption, subpacket mode, LBT, RSSI reporting |
Both support 850–930 MHz with 22 dBm transmit power. They are configured via AT commands over a UART serial connection rather than directly via SPI or I2C, which makes them easy to use from a Raspberry Pi or any machine with a USB-serial adapter.
The difference between the two matters less than expected. The LR02 is supposed to have additional features, but the firmware version on both modules in this kit turned out to be V2.3.1, which implements LR01-style firmware on both. LR02-specific AT commands return ERROR=101 on this firmware. Worth checking if buying these: what you actually get may not match the listing.
Wiring
The modules use 3.3V logic but accept 5V on VCC. A CH340 USB-serial adapter connects them directly to a computer without a level shifter:
CH340 TX → Module pin 3 (UART_RX)
CH340 RX ← Module pin 4 (UART_TX)
GND → Module pin 7 (GND)
CH340 5V → Module pin 6 (VCC)
Pins 1 and 2 (M0, M1) are mode selection pins with internal pull-ups. Left unconnected they float high, which nominally puts the module in sleep mode (M0=H, M1=H). In practice the firmware responds to AT commands regardless, because AT+SWITCH=0 disables pin-based mode switching by default. Pin 5 (AUX) is an output that goes high before data is ready to read. Not connected in this setup.
On Linux the CH340 appears as /dev/ttyUSB0. Default serial parameters are 9600 8N1.
AT commands
Send +++ to enter command mode (the module responds with Entry AT), issue commands, send +++ again to exit (Exit AT). Settings do not apply until the module is power-cycled. This caught me out initially.
AT Test connection
AT+HELP Dump full configuration
AT+VERSION Query firmware version
AT+DEFAULT Reset to factory defaults
AT+CHANNEL<n> Set working channel (hex, no 0x prefix)
AT+LEVEL<n> Set air rate / spreading factor
AT+POWE<n> Set transmit power in dBm (0–22)
AT+BAUD<n> Set serial baud rate
AT+SF<n> Set spreading factor directly (LR01 firmware)
AT+BW<n> Set bandwidth (LR01 firmware)
AT+CR<n> Set coding rate (LR01 firmware)
The channel format tripped me up. The module expects a hex value with no prefix and no equals sign: AT+CHANNEL82, not AT+CHANNEL=130 or AT+CHANNEL0x82. Sending the decimal value 130 returns ERROR=101. This is not clearly documented.
Configuring for AU915
The modules shipped configured for EU 868 MHz by default. For Meshtastic use in Australia the target is AU915 (915 MHz). The LR01 firmware uses 162 channels with 500 kHz spacing starting at 850 MHz:
frequency = 850 + channel × 0.5 MHz
channel = (frequency - 850) / 0.5
For 915 MHz: (915 - 850) / 0.5 = 130 decimal = 0x82 hex. So the command is AT+CHANNEL82.
import serial, time
ser = serial.Serial("/dev/ttyUSB0", 9600, timeout=2)
time.sleep(1)
ser.write(b"+++\r\n")
time.sleep(1)
resp = ser.read(200) # Entry AT
ser.write(b"AT+CHANNEL82\r\n") # 915 MHz
time.sleep(0.5)
ser.write(b"AT+LEVEL1\r\n") # SF11, 447 bps
time.sleep(0.5)
ser.write(b"+++\r\n") # Exit and apply
time.sleep(1)
# Power cycle the module here (unplug/replug CH340)
AT+HELP after power-cycling:
+VERSION=V2.3.1
MODE:0
LEVEL:1 >> 447.591156bps
Frequency:915000000hz >> 82
Spreading Factor:11
Bandwidth:0
Coding rate:2
Power:22dBm
The spreading factor tradeoff in practice
The default configuration on these modules is LEVEL 0, which is SF12 at 244 bps. That is the most conservative setting: maximum range, minimum speed. For a stationary node in a fixed location passing occasional messages it is fine. For anything that needs to exchange more data or respond quickly it is worth stepping up.
| Level | Air rate | Rough SF | Notes |
|---|---|---|---|
| 0 | 244 bps | SF12 | Maximum range, very slow |
| 1 | 447 bps | SF11 | Good balance for mesh |
| 2 | 2148 bps | SF9 | Faster, shorter range |
| 3 | higher | SF7/8 | Short range only |
For Meshtastic specifically, the network works best when all nodes use the same spreading factor and channel. Since AU915 nodes are typically running SF11 or SF12, LEVEL 1 is the right starting point.
Binary data and the +++ problem
These modules pass arbitrary binary data cleanly in transparent mode: null bytes, high-bit bytes, and long payloads all work. This matters for encrypted or encoded payloads.
The catch is the AT mode escape sequence. The module uses +++ as the trigger to enter AT command mode, and it watches for this sequence even during active transmission. Three consecutive + bytes (0x2B 0x2B 0x2B) in a payload will accidentally switch the module out of transparent mode mid-stream.
For random encrypted data this is statistically unlikely at roughly one in sixteen million byte triplets. But it is a known failure mode. The clean solution is fixed-point mode (MODE 1), where payloads are prefixed with a target address and channel, and the +++ sequence inside the framed data is not interpreted as an escape. Fixed-point mode is the safer choice when payloads are arbitrary binary data.
Where things stand
Both modules are configured and responding correctly at 915 MHz. The next step is to get two of them talking to each other to verify the radio link, then look at what Meshtastic integration looks like. The bigger picture goal is understanding enough about the stack to do something useful with it, whether that is a simple sensor node, a message relay, or eventually contributing to a local mesh network.