Industrial low-power telemetry boilerplate for ESP32 + LoRa-E5 + ADS1115.
4-20 mA acquisition, compact binary payload, LoRaWAN uplink, immediate deep sleep.
This repository provides a production-oriented firmware foundation for industrial IoT nodes where energy budget and deterministic behavior matter.
Each wake cycle does exactly one job:
- Wake from deep sleep (15-minute interval)
- Read a 4-20 mA loop through ADS1115
- Convert to engineering units (
0-100 psi) - Pack data into a 4-byte binary payload
- Send LoRaWAN uplink through Seeed LoRa-E5 (AT modem)
- Enter deep sleep immediately after TX completion
No JSON, no String, no delay() loops.
The Seeed LoRa-E5 module already contains an STM32WLE5 and integrated LoRaWAN stack.
For this hardware profile, the correct architecture is:
- ESP32 handles sensing and system orchestration
- LoRa-E5 handles LoRaWAN via UART AT commands
MCCI LMIC is generally used when the ESP32 directly drives a raw LoRa transceiver (e.g., SX127x/SX126x over SPI).
- Non-blocking event-driven finite state machine
- Deep sleep first design (battery-focused)
- ADS1115 high-resolution acquisition over I2C
- Compact big-endian payload encoding
- Retry strategy for join/uplink with bounded attempts
- Doxygen-style inline code documentation
GPIO21-> SDAGPIO22-> SCL- ADS1115 address:
0x48 - Input channel:
A0
GPIO16<- LoRa-E5 TXGPIO17-> LoRa-E5 RX- UART baudrate:
9600
Default shunt resistor in code:
Rshunt = 165R(0.1%recommended)- 4 mA -> ~0.66 V
- 20 mA -> ~3.30 V
Engineering conversion:
I(mA) = Vshunt / Rshunt * 1000psi = clamp((I - 4) / 16, 0..1) * 100
Uplink payload size: 4 bytes (big-endian)
| Bytes | Field | Unit | Scale |
|---|---|---|---|
| 0..1 | Pressure | psi | value / 100 |
| 2..3 | Loop current | mA | value / 100 |
Example decoding:
0x1388->50.00 psi0x07D0->20.00 mA
stateDiagram-v2
[*] --> ModemPing
ModemPing --> ModemSetMode
ModemSetMode --> ModemSetRegion
ModemSetRegion --> ModemSetAdr
ModemSetAdr --> ModemSetPort
ModemSetPort --> ModemSetAppEui
ModemSetAppEui --> ModemSetDevEui
ModemSetDevEui --> ModemSetAppKey
ModemSetAppKey --> ModemJoin
ModemJoin --> SensorStartConversion
ModemJoin --> JoinRetryWait
JoinRetryWait --> ModemJoin
SensorStartConversion --> SensorWaitConversion
SensorWaitConversion --> SendUplink
SendUplink --> EnterSleep
SendUplink --> UplinkRetryWait
UplinkRetryWait --> ModemJoin
UplinkRetryWait --> SendUplink
EnterSleep --> [*]
src/main.cpp: firmware state machine and integration logicplatformio.ini: board setup, dependencies, build-time credentialsdocs/hardware.md: wiring and electrical integration notesdocs/commissioning.md: LoRaWAN OTAA provisioning checklistdocs/power-optimization.md: power and autonomy guidancedocs/payload-decoder.js: payload decoder for TTN / ChirpStackdocs/assets/industrial-node-logo.svg: README visual asset
Edit platformio.ini build flags:
LORAE5_REGIONLORAE5_APP_EUILORAE5_DEV_EUILORAE5_APP_KEYLORAE5_UPLINK_PORT
pio run
pio run -t upload
pio device monitorExpected command flow:
ATAT+MODE=LWOTAAAT+DR=<REGION>AT+ID=AppEui,...AT+ID=DevEui,...AT+KEY=APPKEY,...AT+JOINAT+MSGHEX="..."
Successful uplink ends with +MSGHEX: Done, then the node enters deep sleep.
Use payload-decoder.js in The Things Stack or adapt it for ChirpStack to decode the 4-byte uplink into:
pressure_psiloop_current_ma
- Wakeup interval defaults to 15 minutes (
esp_sleeptimer) - TX completion leads directly to deep sleep transition
AT+LOWPOWERis sent to the LoRa-E5 before sleeping (best effort)- Bounded retry policy prevents uncontrolled active-time growth
- Persist a lightweight session state strategy to reduce OTAA joins in battery deployments
- Add supply-voltage measurement so the uplink can include node health, not just process value
- Add a production
secretsoverride flow for CI/local builds instead of editing credentials inline - Add calibration constants for the analog front-end to compensate resistor and sensor tolerance
This project is released under the MIT License.