A clean and reliable Arduino library for the EByte E22-T series LoRa modules.
- Description
- Key Features
- Supported Modules
- Quick Example
- Installation
- Usage
- Release Status
- License
LoRa-E22T is an Arduino library for EByte E22-T series LoRa modules based on the Semtech SX1262/SX1268 chipsets. It supports the full range of E22-T module variants across the 230 MHz, 400 MHz, and 900 MHz frequency bands, at both 22 dBm and 30 dBm output power levels.
The library provides a complete API to configure the module, send and receive data in Transparent or Fixed addressing modes, use power-saving Wake-on-Radio (WOR), hardware encryption, and RSSI reporting - all with consistent, typed error handling.
- Full model support - All six E22-T variants across 230/400/900 MHz bands and 22/30 dBm power levels.
- Complete configuration API - Individual setters and getters for all registers, plus single-call bulk
setConfig()/getConfig(). - Transparent and Fixed addressing - Transparent mode for simple broadcast links; Fixed mode for addressed unicast and broadcast packets.
- Wake-on-Radio (WOR) - Dedicated send methods that handle the extended preamble timing automatically.
- Hardware encryption - 16-bit hardware encryption key (write-only register; no software overhead).
- RSSI reporting - Both per-packet RSSI (appended byte) and ambient noise floor measurement.
- Informative status codes - All methods return a typed
Statusenum.statusToString()converts it to a human-readable C string. - No dynamic memory - Zero heap allocation. No
new,malloc, orStringanywhere in the library.
| Model | Enum | Band | Max Power |
|---|---|---|---|
| E22-230T22x | Model::E22_230T22 |
230 MHz | 22 dBm |
| E22-230T30x | Model::E22_230T30 |
230 MHz | 30 dBm |
| E22-400T22x | Model::E22_400T22 |
400 MHz | 22 dBm |
| E22-400T30x | Model::E22_400T30 |
400 MHz | 30 dBm |
| E22-900T22x | Model::E22_900T22 |
900 MHz | 22 dBm |
| E22-900T30x | Model::E22_900T30 |
900 MHz | 30 dBm |
The following examples show a minimal point-to-point link in Transparent mode. Both modules must be on the same address and channel.
Sender:
#include <Arduino.h>
#include <LoRa-E22T.h>
using namespace E22;
static LoRaE22T lora;
void setup() {
Serial1.begin(9600, SERIAL_8N1, /*RX*/ 1, /*TX*/ 2);
lora.begin(Model::E22_900T30, Serial1, /*M0*/ 3, /*M1*/ 4, /*AUX*/ 5, /*RESET*/ -1);
}
void loop() {
const char* msg = "Hello!";
lora.sendTransparentData(reinterpret_cast<const uint8_t*>(msg), strlen(msg));
delay(2000);
}Receiver:
#include <Arduino.h>
#include <LoRa-E22T.h>
using namespace E22;
static LoRaE22T lora;
static uint8_t buf[256];
void setup() {
Serial.begin(115200);
Serial1.begin(9600, SERIAL_8N1, /*RX*/ 1, /*TX*/ 2);
lora.begin(Model::E22_900T30, Serial1, /*M0*/ 3, /*M1*/ 4, /*AUX*/ 5, /*RESET*/ -1);
}
void loop() {
if (!lora.isDataAvailable()) return;
size_t received_bytes = 0;
if (lora.readData(/*includes_rssi*/ false, buf, sizeof(buf), received_bytes) == Status::Ok) {
Serial.printf("Received: %.*s\n", received_bytes, buf);
}
}Add to your platformio.ini:
[env:your_env]
; Most recent changes
lib_deps =
https://github.com/alkonosst/LoRa-E22.git
; Pinned release (recommended for production)
lib_deps =
https://github.com/alkonosst/LoRa-E22.git#vx.y.z- Open Arduino IDE.
- Go to Sketch > Manage Libraries...
- Search for "LoRa-E22T".
- Click Install.
A single header includes all public types:
#include <LoRa-E22T.h>All public types live in the E22 namespace. Add using namespace E22; to avoid repeating the prefix:
using namespace E22;
static LoRaE22T lora;
static Model model = Model::E22_900T30;Call begin() once in setup(). Initialize the HardwareSerial port before calling begin(). After a successful begin(), the module is placed in Mode::Transmission.
Serial1.begin(9600, SERIAL_8N1, PIN_RX, PIN_TX);
Status s = lora.begin(
Model::E22_900T30, // module model
Serial1, // initialized serial port
1, // M0 pin
2, // M1 pin
3, // AUX pin
-1 // RESET pin (-1 = not connected)
);
if (s != Status::Ok) {
Serial.printf("Init failed: %s\n", statusToString(s));
while (true);
}If the RESET pin is connected, call reset() at any time to perform a hardware reset:
lora.reset(); // pulses RESET LOW, then waits for module startupThe module has four operating modes, switched via setMode():
| Mode | Description |
|---|---|
Mode::Transmission |
Default mode for sending/receiving data. |
Mode::WakeOnRadio |
Low-power mode where the module wakes on radio activity. |
Mode::Configuration |
Mode for reading/writing configuration registers. |
Mode::DeepSleep |
Lowest power mode; module is unresponsive until mode is changed. |
lora.setMode(Mode::Configuration);
// ... configure registers ...
lora.setMode(Mode::Transmission);Important
All setXxx() and getXxx() configuration methods require Mode::Configuration. All data send/receive methods require Mode::Transmission (or Mode::WakeOnRadio for WOR transmitters).
Individual setters change one parameter at a time. They all require Mode::Configuration:
lora.setMode(Mode::Configuration);
lora.setAddress(0x0001, false);
lora.setNetworkID(0x00, false);
lora.setChannel(18, false); // 900MHz models: 0-80 (frequency [MHz] = 850.125 + channel)
lora.setTransmissionMode(TxMode::Fixed, false);
lora.setAirDataRate400_900(AirDataRate400_900::Kbps2_4, false);
lora.setTransmissionPower30dBm(TxPower30dBm::dBm30, false);
lora.setEncryptionKey(0xABCD, false); // write-only; reads always return 0
lora.setMode(Mode::Transmission);All setters have a persistent boolean parameter. See Persistent vs temporary below for details.
Full list of setters:
| Method | Description |
|---|---|
setFactorySettings() |
Restore all registers to factory defaults. |
setConfig(config, persistent) |
Write all registers at once. |
setWirelessConfig(config, persistent) |
Send a configuration command to a remote module over-the-air. |
setAddress(addr, persistent) |
16-bit module address. |
setNetworkID(id, persistent) |
Network ID (must match on all nodes in the same network). |
setUARTConfig(baud, parity, persistent) |
UART baud rate and parity. |
setAirDataRate230(rate, persistent) |
Air data rate, only for 230 MHz models. |
setAirDataRate400_900(rate, persistent) |
Air data rate, only for 400/900 MHz models. |
setSubpacketLength(len, persistent) |
Maximum subpacket size for fragmented transmissions. |
setAmbientRSSI(enable, persistent) |
Enable ambient noise RSSI reporting. |
setTransmissionPower22dBm(power, persistent) |
TX power, only for 22 dBm models. |
setTransmissionPower30dBm(power, persistent) |
TX power, only for 30 dBm models. |
setChannel(ch, persistent) |
RF channel number. |
setPacketRSSI(enable, persistent) |
Append RSSI byte to each received packet. |
setTransmissionMode(mode, persistent) |
Transparent or Fixed. |
setRelayMode(enable, persistent) |
Enable relay (repeater) mode. |
setListenBeforeTalk(enable, persistent) |
Enable LBT (carrier sense before transmit). |
setWORMode(mode, persistent) |
WOR Receiver or Transmitter. |
setWORCycleTime(cycle, persistent) |
WOR wake cycle period (500 ms – 4000 ms). |
setEncryptionKey(key, persistent) |
16-bit hardware encryption key (0 = disabled). |
setWORDelay(ms, persistent) |
WOR receiver response window delay in milliseconds. |
lora.setMode(Mode::Configuration);
uint16_t addr;
lora.getAddress(addr);
Serial.printf("Address: 0x%04X\n", addr);
uint8_t ch;
lora.getChannel(ch);
Serial.printf("Channel: %u\n", ch);
lora.setMode(Mode::Transmission);Full list of getters:
| Method | Description |
|---|---|
getConfig(config) |
Read all registers at once into a LoRaE22TConfig struct. |
getWirelessConfig(config) |
Request the remote module's full config over-the-air. |
getAddress(addr) |
16-bit address. |
getNetworkID(id) |
Network ID. |
getUARTConfig(baud, parity) |
UART baud rate and parity. |
getAirDataRate(raw) |
Raw air data rate register value. |
getSubpacketLength(len) |
Subpacket length. |
getAmbientRSSI(enabled) |
Whether ambient RSSI is enabled. |
getTransmissionPower(raw) |
Raw TX power register value. |
getChannel(ch) |
RF channel number. |
getPacketRSSI(enabled) |
Whether packet RSSI byte appending is enabled. |
getTransmissionMode(mode) |
Transparent or Fixed. |
getRelayMode(enabled) |
Whether relay mode is enabled. |
getListenBeforeTalk(enabled) |
Whether LBT is enabled. |
getWORMode(mode) |
WOR mode. |
getWORCycleTime(cycle) |
WOR cycle time. |
getWORDelay(ms) |
WOR response window delay. |
Note
The encryption key register is write-only on the hardware. Reading it always returns 0x0000. There is no getEncryptionKey() method.
To read or write the entire configuration in a single UART transaction, use getConfig() and setConfig():
LoRaE22TConfig config;
lora.setMode(Mode::Configuration);
lora.getConfig(config);
// Modify only what you need
config.address = 0x0005;
config.channel = 0x12;
config.tx_mode = TxMode::Fixed;
lora.setConfig(config, false);
lora.setMode(Mode::Transmission);To configure a remote module over-the-air without physical access, use setWirelessConfig() and getWirelessConfig():
// Send a new config to the remote module
LoRaE22TConfig remote;
remote.address = 0x0010;
remote.channel = 0x15;
lora.setMode(Mode::Configuration);
lora.setWirelessConfig(remote, false);
lora.setMode(Mode::Transmission);
// Query the current config of the remote module
LoRaE22TConfig queried;
lora.setMode(Mode::Configuration);
lora.getWirelessConfig(queried);
lora.setMode(Mode::Transmission);
Serial.printf("Remote channel: 0x%02X\n", queried.channel);Every setter accepts a persistent boolean as the last parameter:
false(default): setting is applied immediately and lost after a power cycle or hardware reset.true: setting is written to the module's flash and survives power cycles.
Caution
Flash has a limited write endurance. Use persistent = false during development and testing to avoid unnecessary flash wear.
The RF channel number maps to a frequency depending on the module band:
| Band | Channel range | Frequency formula |
|---|---|---|
| 230 MHz | 0–64 | 220.125 + ch × 0.25 MHz |
| 400 MHz | 0–83 | 410.125 + ch MHz |
| 900 MHz | 0–80 | 850.125 + ch MHz |
Factory default channels:
| Band | Channel | Frequency |
|---|---|---|
| 230 MHz | 40 (0x28) | 230.125 MHz |
| 400 MHz | 23 (0x17) | 433.125 MHz |
| 900 MHz | 18 (0x12) | 868.125 MHz |
All send methods require the module to be in Mode::Transmission (or Mode::WakeOnRadio for WOR transmitters). They block until the module signals AUX HIGH, confirming the packet has been handed off to the RF layer.
All modules sharing the same address and channel receive every packet. No address header is added.
const char* msg = "Hello!";
Status s = lora.sendTransparentData(
reinterpret_cast<const uint8_t*>(msg), strlen(msg));Each packet carries a destination 16-bit address and channel. Only the module with the matching address and channel receives it. Use address 0xFFFF (via sendBroadcastFixedData) to reach all modules on a channel.
// Unicast to address 0x0002, channel 0x12
Status s = lora.sendFixedData(
0x0002, 0x12,
reinterpret_cast<const uint8_t*>(msg), strlen(msg));
// Broadcast to all modules on channel 0x12
Status s = lora.sendBroadcastFixedData(
0x12,
reinterpret_cast<const uint8_t*>(msg), strlen(msg));WOR allows a receiver to sleep between periodic wake windows, dramatically reducing average power consumption. The transmitter must send a preamble at least as long as the WOR cycle before the payload so the receiver wakes in time.
Receiver setup:
lora.setMode(Mode::Configuration);
lora.setWORMode(WORMode::Receiver, false);
lora.setWORCycleTime(WORCycleTime::Ms2000, false);
lora.setMode(Mode::WakeOnRadio); // enter low-power WOR modeTransmitter setup:
lora.setMode(Mode::Configuration);
lora.setWORMode(WORMode::Transmitter, false);
lora.setWORCycleTime(WORCycleTime::Ms2000, false); // must match receiver
lora.setMode(Mode::WakeOnRadio);
// Use the dedicated WOR send methods
Status s = lora.sendWORTransparentData(...);
Status s = lora.sendWORFixedData(address, channel, ...);
Status s = lora.sendWORBroadcastFixedData(channel, ...);Important
Always use the sendWORxxx() variants when operating as a WOR transmitter. The standard sendxxx() methods use a 500 ms AUX timeout, which is shorter than the maximum WOR preamble duration (up to 4000 ms for the longest cycle time).
Poll isDataAvailable() in loop() and call readData() when data arrives:
static uint8_t buf[256];
void loop() {
if (!lora.isDataAvailable()) return;
size_t n = 0;
Status s = lora.readData(false, buf, sizeof(buf), n);
if (s == Status::Ok) {
// process buf[0..n-1]
}
}The first parameter includes_rssi tells the library whether to strip and internally store the trailing RSSI byte appended by the module (only relevant when setPacketRSSI(true) is active):
// Pass true to strip the RSSI byte; retrieve it afterwards with getLastPacketRSSI()
lora.readData(true, buf, sizeof(buf), n);Enable per-packet RSSI to measure the received signal strength of each individual packet:
lora.setMode(Mode::Configuration);
lora.setPacketRSSI(true, false);
lora.setMode(Mode::Transmission);
// In loop():
lora.readData(true, buf, sizeof(buf), n);
int16_t rssi = lora.getLastPacketRSSI(); // dBm (negative); closer to 0 = stronger signalRead the channel noise floor at any time to assess RF conditions without waiting for a packet:
lora.setMode(Mode::Configuration);
lora.setAmbientRSSI(true, false);
lora.setMode(Mode::Transmission);
// In loop():
int16_t noise;
lora.readAmbientRSSI(noise);
Serial.printf("Noise floor: %d dBm\n", noise);Every method returns a Status value. Use statusToString() to convert it to a printable C string:
Status s = lora.setAddress(0x0001, false);
if (s != Status::Ok) {
Serial.printf("Error: %s\n", statusToString(s));
}| Status | Description |
|---|---|
Ok |
Operation completed successfully. |
ModelNotSet |
No model was provided to begin(). |
SerialNotSet |
No serial port was provided to begin(). |
PinsNotSet |
Required control pins were not configured. |
Uninitialized |
begin() has not been called. |
AlreadyInitialized |
begin() was called more than once. |
AuxTimeout |
AUX pin did not return HIGH within the timeout window. |
SerialTimeout |
Module did not respond to a serial command in time. |
WrongMode |
Method called while the module was in an incompatible mode. |
WrongModel |
Method called on a model that does not support the operation. |
InvalidParameter |
A parameter value was out of range or invalid. |
CommandFailed |
Module returned an unexpected or error response. |
BufferTooSmall |
The provided receive buffer is too small for the incoming data. |
Logging is disabled by default and has zero runtime cost when turned off - all macros expand to void(0).
To enable it, define LORA_E22T_LOG_LEVEL to a value between 1 and 5 before the library is compiled:
| Level | Macro | Typical use |
|---|---|---|
| 1 | LORA_E22T_LOGE |
Errors (timeouts, invalid parameters...) |
| 2 | LORA_E22T_LOGW |
Warnings |
| 3 | LORA_E22T_LOGI |
Informational events |
| 4 | LORA_E22T_LOGD |
Debug (function entry, byte counts...) |
| 5 | LORA_E22T_LOGV / LORA_E22T_HEXV |
Verbose + hex dumps |
Each level includes all levels below it (e.g. level 4 also emits errors, warnings, and info).
PlatformIO - set in platformio.ini:
[env:your_env]
build_flags =
-D LORA_E22T_LOG_LEVEL=4Arduino IDE - define before including the library:
#define LORA_E22T_LOG_LEVEL 4
#include <LoRa-E22T.h>| Macro | Default | Description |
|---|---|---|
LORA_E22T_LOG_LEVEL |
0 (disabled) |
Active log level (0-5). |
LORA_E22T_LOG_SERIAL |
Serial |
Serial port used when not on ESP32 (or when ESP32 logs are off). |
LORA_E22T_LOG_TAG |
"LoRaE22T" |
Tag string prepended to every log line. |
LORA_E22T_USE_ESP32_LOGS |
1 (enabled) |
When 1 on ESP32, routes logs through ESP_LOGx() instead of Serial.printf(). Set to 0 to use Serial.printf() even on ESP32. |
This project is in active development. Until reaching version v1.0.0, consider it beta software. APIs may change in future releases, and some features may be incomplete or unstable. Please report any issues on the GitHub Issues page.
This project is licensed under the MIT License.