← LoRaWAN Payload Bit Packing on an ESP32 PLC (4 Bytes)
Water pumping (sanitation)ESP32 PLC 14 / 38ARLoRaWANCommunication
LoRaWAN Payload Bit Packing on an ESP32 PLC (4 Bytes) — full example
Compress 11 digital inputs, error flags and a 10-bit analog probe into a 4-byte LoRaWAN payload on an ESP32 PLC. Real Arduino bit-packing example.
Complete, runnable program for the ESP32 PLC 14 / 38AR (lorawan-telemetry-bitpacking.ino): wiring header, requirements and integration notes included.
Download the full project pack — freeThis example + the related ones + bill of materials
Read-only preview.
/*
* COMPLETE EXAMPLE — LoRaWAN OTAA telemetry with bit packing (4 bytes)
*
* Hardware: ESP32 PLC 38AR (Industrial Shields) + LoRa RN2xx3 module (EU868)
* Based on: water pumping project (sanitation), bombament-2b-38ar.ino
*
* Wiring:
* I0_0..I0_9 Eleven digital signals of the station (float switches, selectors,
* I0_10 contactor feedback, thermal relays...) — see pins[] table
* I0_11 4-20 mA level sensor (analog, 0-1023)
* Q0_0 Pump 1 contactor Q0_1 Pump 2 contactor
*
* Logic:
* The whole station state fits in a 4-byte frame:
* byte 0 : digital inputs 0..7 (1 bit each)
* byte 1 : inputs 8..10 + padding (bits 7..5)
* byte 2 : confirmation/fault flags (bits 7,6) + sensor bits 10..8
* byte 3 : level sensor, low 8 bits
* Transmitted over LoRaWAN (OTAA, EU868) every 60 s. 4 bytes versus ~200 of
* a JSON: less airtime, duty-cycle compliance even at SF12.
*
* Integration: the frame is the same one decoded by the dual control example
* (ejemplos/bombeo-agua/dual-pump-control-lorawan-38ar.ino); the network
* server decoder only has to undo the bit shifts.
*/
// --- I/O map (ESP32 PLC 38AR pins, industrialshields-arduino library)
#define I_LEVEL_SENSOR I0_11
#define Q_P1 Q0_0
#define Q_P2 Q0_1
// Eleven digital inputs in the same order as the frame in the original project
const int pins[] = {I0_0, I0_1, I0_2, I0_3, I0_4, I0_5,
I0_6, I0_7, I0_8, I0_9, I0_10};
const uint8_t NUM_INPUTS = sizeof(pins) / sizeof(pins[0]); // = 11
// --- State to transmit
bool inputs_d[11]; // copy of the digital inputs
bool confirmation_p1 = false; // flag: contactor confirmation failure
bool fault_p1 = false; // flag: thermal relay tripped
uint16_t sensor_level = 0; // 10-bit analog (0-1023)
const uint32_t SEND_PERIOD_MS = 60000; // 60 s between frames
uint32_t t_last_send = 0;
// ------------------------------------------------- Packing: state -> 4 bytes
void pack_and_send() {
byte message[4] = {0, 0, 0, 0};
// Digital inputs: 1 bit per signal, MSB first (i/8 picks the byte,
// 7 - i%8 places the bit). 11 inputs fill byte 0 and 3 bits of byte 1.
for (uint8_t i = 0; i < NUM_INPUTS; i++)
message[i / 8] |= inputs_d[i] << (7 - i % 8);
// Error flags in the high bits of byte 2
message[2] |= confirmation_p1 << 7;
message[2] |= fault_p1 << 6;
// 10-bit analog sensor split between byte 2 (bits 10..8) and byte 3
message[2] |= (sensor_level & 0x0700) >> 8;
message[3] |= sensor_level & 0xFF;
lora_send_bytes(message, 4); // unconfirmed uplink, default port
Serial.printf("[lora] frame: %02X %02X %02X %02X (sensor=%u)\n",
message[0], message[1], message[2], message[3], sensor_level);
}
// ------------------------------------------------- Signal acquisition
void read_station() {
for (uint8_t i = 0; i < NUM_INPUTS; i++)
inputs_d[i] = digitalRead(pins[i]);
sensor_level = analogRead(I_LEVEL_SENSOR); // 4-20 mA -> 0-1023
// Flag example: thermal relay on I0_8, feedback expected on I0_6 with Q0_0 active
fault_p1 = inputs_d[8];
confirmation_p1 = digitalRead(Q_P1) && !inputs_d[6];
}
void setup() {
Serial.begin(115200);
for (uint8_t i = 0; i < NUM_INPUTS; i++) pinMode(pins[i], INPUT);
pinMode(I_LEVEL_SENSOR, INPUT);
pinMode(Q_P1, OUTPUT);
pinMode(Q_P2, OUTPUT);
// OTAA join: the network server derives the session keys on every join.
// Credentials: placeholders — use the ones from your LoRaWAN application.
lora_init(57600); // RN2xx3 via SerialSC1, EU868 band
while (!lora_join_otaa("APP_EUI", "APP_KEY")) {
Serial.println("[lora] join failed, retrying in 30 s");
delay(30000);
}
Serial.println("[lora] OTAA join successful");
}
void loop() {
read_station();
// The pump control logic would go here — see the state machine and the
// dual control with alternation in the catalog. This example focuses
// on telemetry.
if (millis() - t_last_send >= SEND_PERIOD_MS) {
t_last_send = millis();
pack_and_send();
}
delay(100);
}
Download the full project pack — freeThis example + the related ones + bill of materials