You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
296 lines
9.5 KiB
296 lines
9.5 KiB
//Define DEBUG to get the Output from DEBUG_PRINTLN
|
|
#define DEBUG 1
|
|
|
|
#include <Arduino.h>
|
|
|
|
|
|
/* Wiring (Heltec OLED Lora)
|
|
* GND O O GND
|
|
* 5V O O 5V
|
|
* 3V3 O O 3V3
|
|
* GND O < 36
|
|
* RX * * 17
|
|
* TX * < 38
|
|
* RST * BUTTON < 39
|
|
* 0 * < 34
|
|
* 22 * < 35
|
|
* BME280_SDO 19 * LoRa_MISO * 32
|
|
* BME280_CS 23 * * 33
|
|
* 18 x LoRa_CS * 25 MAX98_DIN
|
|
* BME280_SCL/SCK 5 * LoRa_SCK LoRa_IRQ * 26
|
|
* 15 * OLED_SCL LoRa_MOSI * 27 BME280_SDA/SDI
|
|
* 2 * LoRa_RST * 14
|
|
* 4 * OLED_SDA * 12 MAX98_BCLK
|
|
* 17 * * 13 MAX98_LRC
|
|
* 16 * OLED_RST * 21
|
|
*/
|
|
|
|
//Include Basecamp in this sketch
|
|
#include <Basecamp.hpp>
|
|
#include <Configuration.hpp>
|
|
|
|
#include <U8g2lib.h>
|
|
#ifdef U8X8_HAVE_HW_SPI
|
|
#include <SPI.h>
|
|
#endif
|
|
#ifdef U8X8_HAVE_HW_I2C
|
|
#include <Wire.h>
|
|
#endif
|
|
|
|
#include <WiFiUdp.h>
|
|
#include <NTPClient.h>
|
|
|
|
#include "main.h"
|
|
#include "mp3.h"
|
|
#include "BME280.h"
|
|
#include "image.h"
|
|
|
|
U8G2_SSD1306_128X64_NONAME_F_SW_I2C u8g2(U8G2_R0, /* clock=*/ 15, /* data=*/ 4, /* reset=*/ 16);
|
|
|
|
//Create a new Basecamp instance called iot
|
|
Basecamp iot;
|
|
|
|
WiFiUDP ntpUDP;
|
|
NTPClient timeClient(ntpUDP, "de.pool.ntp.org", 3600, 60000);
|
|
char timeStr[20];
|
|
|
|
BME280 bme280;
|
|
MP3 mp3;
|
|
|
|
uint32_t lastButtonPress = 0;
|
|
|
|
//Variables for the sensor and the battery
|
|
static const int buttonPin = 0;
|
|
static const int ResetPin = 17;
|
|
static const int SensorPin = 32;
|
|
static const int BatteryPin = 34;
|
|
int sensorValue = 0;
|
|
//The batteryLimit defines the point at which the battery is considered empty.
|
|
int batteryLimit = 3300;
|
|
|
|
//This is used to control if the ESP should enter sleep mode or not
|
|
bool delaySleep = true;
|
|
|
|
//Variables for the mqtt packages and topics
|
|
uint16_t statusPacketIdSub = 0;
|
|
String delaySleepTopic;
|
|
String statusTopic;
|
|
String batteryTopic;
|
|
String batteryValueTopic;
|
|
|
|
void setup() {
|
|
// #include "soc/rtc_io_reg.h"
|
|
// #include "soc/sens_reg.h"
|
|
//
|
|
// SET_PERI_REG_MASK(RTC_IO_XTAL_32K_PAD_REG, RTC_IO_X32N_MUX_SEL | RTC_IO_X32P_MUX_SEL);
|
|
// CLEAR_PERI_REG_MASK(RTC_IO_XTAL_32K_PAD_REG, RTC_IO_X32P_RDE | RTC_IO_X32P_RUE | RTC_IO_X32N_RUE | RTC_IO_X32N_RDE);
|
|
// CLEAR_PERI_REG_MASK(RTC_IO_XTAL_32K_PAD_REG, RTC_IO_X32N_MUX_SEL | RTC_IO_X32P_MUX_SEL);
|
|
// SET_PERI_REG_BITS(RTC_IO_XTAL_32K_PAD_REG, RTC_IO_DAC_XTAL_32K, 1, RTC_IO_DAC_XTAL_32K_S);
|
|
// SET_PERI_REG_BITS(RTC_IO_XTAL_32K_PAD_REG, RTC_IO_DRES_XTAL_32K, 3, RTC_IO_DRES_XTAL_32K_S);
|
|
// SET_PERI_REG_BITS(RTC_IO_XTAL_32K_PAD_REG, RTC_IO_DBIAS_XTAL_32K, 0, RTC_IO_DBIAS_XTAL_32K_S);
|
|
// SET_PERI_REG_MASK(RTC_IO_XTAL_32K_PAD_REG, RTC_IO_XPD_XTAL_32K);
|
|
// REG_SET_BIT(RTC_IO_PAD_DAC1_REG, RTC_IO_PDAC1_MUX_SEL_M);
|
|
// REG_CLR_BIT(RTC_IO_PAD_DAC1_REG, RTC_IO_PDAC1_RDE_M | RTC_IO_PDAC1_RUE_M);
|
|
// REG_SET_FIELD(RTC_IO_PAD_DAC1_REG, RTC_IO_PDAC1_FUN_SEL, 1);
|
|
// REG_SET_FIELD(SENS_SAR_DAC_CTRL1_REG, SENS_DEBUG_BIT_SEL, 0);
|
|
// const uint8_t sel = 4; /* sel = 4 : 32k XTAL; sel = 5 : internal 150k RC */
|
|
// REG_SET_FIELD(RTC_IO_RTC_DEBUG_SEL_REG, RTC_IO_DEBUG_SEL0, sel);
|
|
//
|
|
|
|
|
|
//configuration of the battery and sensor pins
|
|
// pinMode(ResetPin, INPUT_PULLDOWN);
|
|
// pinMode(SensorPin, INPUT_PULLDOWN);
|
|
// pinMode(BatteryPin, INPUT);
|
|
pinMode(buttonPin, INPUT_PULLUP);
|
|
|
|
pinMode(18, OUTPUT);
|
|
digitalWrite(18, HIGH); // disable LoRa_CS
|
|
|
|
//read the status of the doorsensor as soon as possible to determine the state that triggered it
|
|
// sensorValue = digitalRead(SensorPin);
|
|
|
|
//Initialize Basecamp
|
|
iot.begin();
|
|
|
|
bme280.begin();
|
|
for (int i=0; i<5; i++) {
|
|
delay(500);
|
|
bme280.printValues();
|
|
}
|
|
|
|
u8g2.begin();
|
|
delay(50);
|
|
u8g2.clearBuffer(); // clear the internal memory
|
|
u8g2.setFont(u8g2_font_ncenB08_tr); // choose a suitable font
|
|
u8g2.drawStr(2,10,"esp32-node by hendrik"); // write something to the internal memory
|
|
if (iot.configuration.get("WifiConfigured") != "True") {
|
|
u8g2.drawStr(2,30,"NOT CONFIGURED!");
|
|
u8g2.drawStr(12,40,"SSID: \"ESP32\"");
|
|
u8g2.drawStr(12,55,"http://192.168.4.1");
|
|
} else {
|
|
u8g2.drawStr(2,30,"Welcome!");
|
|
u8g2.drawStr(12,40,"connecting..");
|
|
}
|
|
u8g2.drawFrame(0,0,u8g2.getDisplayWidth(),u8g2.getDisplayHeight() );
|
|
u8g2.sendBuffer(); // transfer internal memory to the display
|
|
|
|
//Configure the MQTT topics
|
|
delaySleepTopic = "esp32-node/cmd/" + iot.hostname + "/delaysleep";
|
|
statusTopic = "esp32-node/stat/" + iot.hostname + "/status";
|
|
batteryTopic = "esp32-node/stat/" + iot.hostname + "/battery";
|
|
batteryValueTopic = "esp32-node/stat/" + iot.hostname + "/batteryvalue";
|
|
|
|
//Set up the Callbacks for the MQTT instance. Refer to the Async MQTT Client documentation
|
|
// TODO: We should do this actually _before_ connecting the mqtt client...
|
|
iot.mqtt.onConnect(onMqttConnect);
|
|
iot.mqtt.onPublish(suspendESP);
|
|
iot.mqtt.onMessage(onMqttMessage);
|
|
|
|
while (iot.wifi.status() != WL_CONNECTED) {
|
|
delay(500);
|
|
Serial.print(".");
|
|
}
|
|
|
|
IPAddress localIP = iot.wifi.getIP();
|
|
char ipStr[16];
|
|
localIP.toString().toCharArray(ipStr, 16);
|
|
|
|
timeClient.begin();
|
|
timeClient.update();
|
|
timeClient.getFormattedTime().toCharArray(timeStr, 50);
|
|
u8g2.clearBuffer(); // clear the internal memory
|
|
u8g2.setFont(u8g2_font_ncenB08_tr); // choose a suitable font
|
|
// u8g2.setDrawColor(2);
|
|
u8g2.drawStr(30, 30, timeStr);
|
|
u8g2.drawStr(30, 50, ipStr);
|
|
u8g2.sendBuffer();
|
|
|
|
mp3.begin();
|
|
|
|
u8g2.setContrast(127);
|
|
}
|
|
|
|
|
|
//This function is called when the MQTT-Server is connected
|
|
void onMqttConnect(bool sessionPresent) {
|
|
DEBUG_PRINTLN(__func__);
|
|
|
|
//Subscribe to the delay topic
|
|
iot.mqtt.subscribe(delaySleepTopic.c_str(), 0);
|
|
//Trigger the transmission of the current state.
|
|
transmitStatus();
|
|
}
|
|
|
|
|
|
//This function transfers the state of the sensor. That includes the door status, battery status and level
|
|
void transmitStatus() {
|
|
DEBUG_PRINTLN(__func__);
|
|
|
|
if (sensorValue == 0) {
|
|
DEBUG_PRINTLN("Door open");
|
|
//Transfer the current state of the sensor to the MQTT broker
|
|
statusPacketIdSub = iot.mqtt.publish(statusTopic.c_str(), 1, true, "open" );
|
|
//Configure the wakeup pin to wake if the door is closed
|
|
esp_sleep_enable_ext0_wakeup((gpio_num_t)SensorPin, 1);
|
|
} else {
|
|
DEBUG_PRINTLN("Door closed");
|
|
//Transfer the current state of the sensor to the MQTT broker
|
|
statusPacketIdSub = iot.mqtt.publish(statusTopic.c_str(), 1, true, "closed" );
|
|
//Configure the wakeup pin to wake if the door is closed
|
|
esp_sleep_enable_ext0_wakeup((gpio_num_t)SensorPin, 0);
|
|
}
|
|
|
|
//Read the current analog battery value
|
|
// sensorValue = analogRead(BatteryPin);
|
|
//sensorC stores the battery value as a char
|
|
char sensorC[6];
|
|
//convert the sensor value to a string
|
|
sprintf(sensorC, "%04i", sensorValue);
|
|
//Send the sensor value to the MQTT broker
|
|
iot.mqtt.publish(batteryValueTopic.c_str(), 1, true, sensorC);
|
|
//Check the battery level and publish the state
|
|
if (sensorValue < batteryLimit) {
|
|
DEBUG_PRINTLN("Battery empty");
|
|
iot.mqtt.publish(batteryTopic.c_str(), 1, true, "empty" );
|
|
} else {
|
|
DEBUG_PRINTLN("Battery full");
|
|
iot.mqtt.publish(batteryTopic.c_str(), 1, true, "full" );
|
|
}
|
|
DEBUG_PRINTLN("Data published");
|
|
}
|
|
|
|
//This topic is called if an MQTT message is received
|
|
void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) {
|
|
DEBUG_PRINTLN(__func__);
|
|
|
|
//Check if the payload eqals "true" and set delaySleep correspondigly
|
|
//Since we only subscribed to one topic, we only have to compare the payload
|
|
if (strcmp(payload, "true") == 0) {
|
|
delaySleep = true;
|
|
} else {
|
|
delaySleep = false;
|
|
}
|
|
}
|
|
|
|
void suspendESP(uint16_t packetId) {
|
|
DEBUG_PRINTLN(__func__);
|
|
|
|
//Check if the published package is the one of the door sensor
|
|
if (packetId == statusPacketIdSub) {
|
|
|
|
if (delaySleep == true) {
|
|
DEBUG_PRINTLN("Delaying Sleep");
|
|
return;
|
|
}
|
|
DEBUG_PRINTLN("Entering deep sleep");
|
|
//properly disconnect from the MQTT broker
|
|
iot.mqtt.disconnect();
|
|
//send the ESP into deep sleep
|
|
esp_deep_sleep_start();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void loop()
|
|
{
|
|
timeClient.update();
|
|
timeClient.getFormattedTime().toCharArray(timeStr, 50);
|
|
|
|
if (digitalRead(buttonPin) == LOW) {
|
|
if(millis() - lastButtonPress >= 1000) {
|
|
Serial.println("Button pressed");
|
|
lastButtonPress = millis();
|
|
if (mp3.playing) {
|
|
mp3.stop();
|
|
} else {
|
|
mp3.start();
|
|
}
|
|
}
|
|
}
|
|
|
|
u8g2.clearBuffer(); // clear the internal memory
|
|
|
|
if (digitalRead(buttonPin) == HIGH) {
|
|
u8g2.setFont(u8g2_font_inb19_mf);
|
|
u8g2.drawStr(0, 20, timeStr);
|
|
|
|
char title1[32]; char title2[32];
|
|
strncpy(title1, titleStr, 22); title1[22] = '\0';
|
|
strncpy(title2, titleStr+22, 32); title2[31] = '\0';
|
|
char weather[32];
|
|
u8g2.setFont(u8g2_font_profont12_mf); // choose a suitable font
|
|
sprintf(weather, "%.1f°C %.1f%% %.0fhPa", bme280.readTemperature(), bme280.readHumidity(), bme280.readPressure());
|
|
u8g2.drawUTF8(0, 30, weather);
|
|
u8g2.setFont(u8g2_font_prospero_bold_nbp_tf); // choose a suitable font
|
|
u8g2.drawUTF8(0, 54, title1);
|
|
u8g2.drawUTF8(0, 64, title2);
|
|
} else {
|
|
u8g2.drawXBMP(0,0, IMG_1872_width, IMG_1872_height, IMG_1872_bits);
|
|
}
|
|
u8g2.sendBuffer();
|
|
// bme280.printValues();
|
|
|
|
delay(100);
|
|
}
|
|
|