Hello. If you want to have truly low power sensor, forget WiFi and go LoRa. I would like to share my experience and examples here.
I built LoRa sender sensor, that sends incremental counter and battery status every hour. Hardware is LoRa Radio Node v1.0, compatible with Arduino Mini Pro 3.3V. The cost is about 15$. In order to measure battery as chip power, I bypassed LDO and I connected LiIon battery directly to 3.3V. I know it is not recommended but in years I never had single problem with powering Atmel or ESP8266 or ESP32 by LiIon battery directly connected to 3.3V, even if it has max 4.2V. The sensor takes less than 80ĀµA in deep sleep mode and duty cycle is very short because it does not need to connect to WiFi, it just sends out LoRa packet. I estimate that duty cycle takes about 1s. In the sensor, you have to manually put wire jumper between DIO0 and GPIO2 terminals. Range of sensor is about 300m in heavy built street, with receiver behind several walls.
Then I built LoRa to MQTT gateway based on LilyGo LoRa32. That gateway takes string sent by LoRa sensor, strips out first byte and uses that byte as MQTT topic.
Code for LoRa sensor. You have to tweak it - check frequency of LoRa, frequency how often you want to send measurements and battery data. You also may need to change calibration contstant in Vcc read routine.
// Compile for Arduino Pro or Pro Mini
#include <SPI.h>
#include <LoRa.h>
#include <Wire.h>
#include "LowPower.h"
//############################## User definitions ###################
#define sensor_loop_min 60 // How often to check sensor
#define battery_loop_min 60 // How often to check and report battery
char BN = 255; // Battery number, 255 is test
char SN = 254; // Sensor number, 254 is test
int counter = 1;
unsigned long stopwatch_counter = 0;
boolean debug = false;
//###################################################################
unsigned long prev_millis_b = millis(); //period to send battery readings
unsigned long prev_millis_s = millis(); //period to send sensor readings
unsigned long rt_millis;
boolean just_booted = true;
#define SS 10 // SX1278's CS
#define RST 9 // SX1278's RESET
#define DI0 2 // SX1278's IRQ(Interrupt Request)
#define BAND 868E6 // Frequency for Europe
#define INTERNAL_LED 13
String rssi = "RSSI --";
String packSize = "--";
String packet ;
void setup() {
Serial.begin(115200);
while (!Serial);
Serial.println();
Serial.println("LoRa Sender Test");
SPI.begin(); //SCK,MISO,MOSI,SS);
LoRa.setPins(SS,RST,DI0);
if (!LoRa.begin(BAND)) {
Serial.println("Starting LoRa failed!");
while (1);
}
Serial.println("init ok");
delay(1500);
rt_millis = stopWatch();
}
//############################### START ##############################
void loop() {
if (debug) Serial.println("Millis: "+String((rt_millis/1000.0),3));
if (just_booted || ((rt_millis - prev_millis_b) > battery_loop_min*6E4)) {
Serial.println(" Battery run!");
String SVcc = String(readVcc()/1000.0);
dispatchPacket(BN+SVcc);
prev_millis_b = rt_millis;
}
// Sensor loop can only do check and if values are outside range then send packet
if (just_booted || ((rt_millis - prev_millis_s) > sensor_loop_min*6E4)) {
Serial.println(" Sensor run!");
float sensor_value = 0;
// ############ Insert sensor reading and logic here #############
// Here we only send counter, replace it with real sensor value
String Ssensor = String(counter); //sensor_value);
dispatchPacket(SN+Ssensor);
counter++;
prev_millis_s = rt_millis;
}
if (debug) Serial.flush();
just_booted = false;
LowPower.powerDown(SLEEP_8S,ADC_OFF, BOD_OFF);
rt_millis = rt_millis + stopWatch() + 8000;
}
// ############################# END ###############################
long readVcc() {
long result;
// Read 1.1V reference against AVcc
ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1); // orig for Pro Mini
//ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1); // modified for 1284P
delay(2); // Wait for Vref to settle
ADCSRA |= _BV(ADSC); // Convert
while (bit_is_set(ADCSRA,ADSC));
result = ADCL;
result |= ADCH<<8;
result = 1105000L / result; // Back-calculate AVcc in mV
return result;
}
void shutdownLoRa() {
LoRa.end();
//digitalWrite(RST, LOW); // I tried to lower current by messing up with pins but it did not work
//pinMode(RST, INPUT);
//pinMode(SS, INPUT);
}
void activateLoRa() {
//pinMode(RST, OUTPUT);
//pinMode(SS, OUTPUT);
//digitalWrite(RST, HIGH);
LoRa.begin(BAND);
}
void dispatchPacket( String pkt) {
activateLoRa();
//delay(500);
//rt_millis = rt_millis + 500;
Serial.print("Begin packet...");
LoRa.beginPacket();
Serial.println("...done");
Serial.print("LoRa print...");
LoRa.print(pkt);
Serial.println("...done");
Serial.print("battery...");
//LoRa.print(SVcc);
Serial.println("...done");
Serial.print("endPacket...");
LoRa.endPacket();
Serial.println("...done");
Serial.flush();
shutdownLoRa();
}
unsigned long stopWatch() {
unsigned long difference = millis() - stopwatch_counter;
stopwatch_counter = millis();
return difference;
}
Code for LoRa to MQTT gateway is here, working on LilyGo LORA32 with OLED display. Again, tweak it as you need, change WiFi, gateway IP address, accesses etc.
#include <SPI.h>
#include <LoRa.h>
#include <Wire.h>
//#include "SSD1306.h"
#include "SSD1306Wire.h"
#include "images.h" // get this file from https://github.com/imelekhin/LoRatoMQTT
#include <WiFi.h>
#include <PubSubClient.h>
// Pin definetion of WIFI LoRa 32
// HelTec AutoMation 2017 [email protected]
#define SCK 5 // GPIO5 -- SX127x's SCK
#define MISO 19 // GPIO19 -- SX127x's MISO
#define MOSI 27 // GPIO27 -- SX127x's MOSI
#define SS 18 // GPIO18 -- SX127x's CS
#define RST 14 // GPIO14 -- SX127x's RESET
#define DI00 26 // GPIO26 -- SX127x's IRQ(Interrupt Request)
#define BAND 868E6 //you can set band here directly,e.g. 868E6,915E6
#define PABOOST true
typedef struct {
byte src_addr;
char msg[255];
unsigned long count;
} _l_packet;
_l_packet pkt;
boolean gotpacket;
SSD1306Wire display(0x3c, 21, 22); //4, 15);
String displaymessages[5] = {"str1", "str2", "str3", "str4", "str5"};
void logo() {
display.clear();
display.drawXbm(0, 5, logo_width, logo_height, logo_bits);
display.display();
}
const char* ssid = "yourwifi";
const char* password = "yourwifipass";
const char* mqttServer = "10.0.0.54";
const int mqttPort = 1883;
const char* mqttUser = "mqttgwuser";
const char* mqttPassword = "mqttgwpass";
WiFiClient espClient;
PubSubClient client(espClient);
void setup() {
Serial.begin(115200);
gotpacket = false;
pkt.src_addr = 0;
//pkt.msg;
pkt.count = 0;
initdisplay();
initwifi();
initlora();
initmqtt();
//LoRa.setSyncWord(0x12); //priv network. WAN: 0x34); //0x3444 for SX126x
LoRa.receive();
}
void loop() {
String topic;
client.loop();
if (WiFi.status() == WL_CONNECTED) {
displaymessages[0] = "WiFi OK, RSSI :" + String(WiFi.RSSI());
} else {
displaymessages[0] = "WiFi disconnected, restart in 15sec";
delay(15000);
ESP.restart();
}
if (client.connected()) {
displaymessages[1] = "MQTT connected";
} else {
displaymessages[1] = "MQTT disconnected, restart in 15sec";
delay(15000);
ESP.restart();
}
if (gotpacket) {
gotpacket = false;
topic = "lora_mqtt_gw/" + String(pkt.src_addr);
Serial.print("###");
Serial.print(topic.c_str());
Serial.println("###");
Serial.print("###");
Serial.print((const char *)&pkt.msg[0]);
Serial.println("###");
Serial.println();
Serial.flush();
client.publish(topic.c_str(), (const char *)&pkt.msg[0], true);
//client.publish("", (const char *)&pkt.msg[0]);
displaymessages[4] = pkt.msg;
displaymessages[3] = "Got pkt#" + String(pkt.count) + " RSSI:" + String(LoRa.packetRssi());
displaymessages[2] = "LoRa SNR" + String(LoRa.packetSnr());
}
statusDisplay();
}
void messageLog(const char *msg) {
display.clear();
display.setTextAlignment(TEXT_ALIGN_LEFT);
display.setFont(ArialMT_Plain_10);
display.drawString(0, 0, msg);
display.display();
}
void statusDisplay() {
display.clear();
display.setTextAlignment(TEXT_ALIGN_LEFT);
display.setFont(ArialMT_Plain_10);
for (int i = 0; i <= 3; i++) {
display.drawString(1 , 12 * i , displaymessages[i]);
}
display.drawStringMaxWidth(0 , 48 , 128, displaymessages[4]);
display.display();
}
void initdisplay() {
pinMode(16, OUTPUT);
pinMode(25, OUTPUT);
digitalWrite(16, LOW); // set GPIO16 low to reset OLED
delay(50);
digitalWrite(16, HIGH); // while OLED is running, must set GPIO16 in high
display.init();
display.flipScreenVertically();
display.setFont(ArialMT_Plain_10);
logo();
delay(1500);
display.clear();
}
void initwifi() {
//WiFi.setTimeout(30*1000);
WiFi.begin(ssid, password);
long timeout = millis()+30000;
while ( WiFi.status() != WL_CONNECTED) {
messageLog("Connecting to WiFi..");
if (millis() > timeout) {
messageLog("No WiFi, restarting...");
delay(5000);
ESP.restart();
}
}
messageLog("Connected to the WiFi network");
delay(1000);
}
void initmqtt() {
client.setServer(mqttServer, mqttPort);
while (!client.connect("ESP32Client", mqttUser, mqttPassword )) {
messageLog("Connecting to MQTT...");
}
messageLog("Connected to MQTT... ");
delay(1000);
}
void initlora() {
SPI.begin(SCK, MISO, MOSI, SS);
LoRa.setPins(SS, RST, DI00);
while (!LoRa.begin(BAND)); //, PABOOST))
{
messageLog("Initialising LoRa module....");
}
messageLog("LoRa Init success!");
delay(1000);
LoRa.onReceive(onReceive);
}
void onReceive(int packetSize)
{
LoRa.readBytes((uint8_t *)&pkt, packetSize);
gotpacket = true;
pkt.msg[packetSize - 1] = '\0';
pkt.count++;
}