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.
 
 

415 lines
11 KiB

/*
*
* Compile and flash:
* platformio run --target upload && platformio device monitor -b 115200
* Decode stack trace:
* addr2line -pfiaC -e .pioenvs/heltec_wifi_lora_32/firmware.elf 0x4013abed:0x3ffe35d0
*/
#include <Arduino.h>
//Include Basecamp in this sketch
#include <Basecamp.hpp>
#include <Configuration.hpp>
#include "esp_wifi.h"
#include <U8g2lib.h>
#ifdef U8X8_HAVE_HW_SPI
#include <SPI.h>
#endif
#ifdef U8X8_HAVE_HW_I2C
#include <Wire.h>
#endif
#include <time.h>
#include <sys/time.h>
#include "apps/sntp/sntp.h"
//#include <LoRa.h>
#include "driver/rtc_io.h"
#include "main.h"
#include "hardware.h"
#include "mp3.h"
#include "BME280.h"
#include "rotary.h"
#include "screen.h"
#include "led.h"
extern "C" {
int rom_phy_get_vdd33();
//uint8_t temprature_sens_read();
//uint32_t hall_sens_read();
}
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ 16, /* clock=*/ 15, /* data=*/ 4);
char timeStr[20];
char weatherStr[32];
RTC_DATA_ATTR static int boot_count = 0;
RTC_DATA_ATTR struct tm alarmTime;
RTC_DATA_ATTR bool alarmArmed = false;
RTC_DATA_ATTR bool displaySleep = true;
//Create a new Basecamp instance called iot
Basecamp iot;
BME280 bme280;
MP3 mp3;
Rotary rotary;
Screen* screen;
Led led;
menuType menuChange = eNone;
uint32_t lastButtonPress = 0;
uint32_t lastUpdate = 0;
uint32_t lastTransmit = 0;
//Variables for the mqtt packages and topics
String commandTopic;
String bme280Topic;
String batteryTopic;
void setup() {
// gpio configuration
pinMode(buttonPin, INPUT_PULLUP);
pinMode(18, OUTPUT);
digitalWrite(18, HIGH); // disable LoRa_CS
boot_count++;
// static wifi_country_t wifi_country = {.cc="EU", .schan=1, .nchan=13, .policy=WIFI_COUNTRY_POLICY_AUTO};
// esp_wifi_set_country(&wifi_country);
//Initialize Basecamp
iot.begin();
//Configure the MQTT topics
commandTopic = "esp32-node/cmd/" + iot.hostname + "/play";
bme280Topic = "esp32-node/stat/" + iot.hostname + "/bme280";
batteryTopic = "esp32-node/stat/" + iot.hostname + "/battery";
esp_sleep_wakeup_cause_t wakeup_reason = esp_sleep_get_wakeup_cause();
switch(wakeup_reason) {
case ESP_SLEEP_WAKEUP_EXT0:
Serial.println("wakeup by RTC_IO");
break;
case ESP_SLEEP_WAKEUP_EXT1:
Serial.println("wakeup by RTC_CNTL");
break;
case ESP_SLEEP_WAKEUP_TIMER:
Serial.println("wakeup by timer");
break;
case ESP_SLEEP_WAKEUP_TOUCHPAD:
Serial.println("wakeup by touchpad");
break;
case ESP_SLEEP_WAKEUP_ULP:
Serial.println("wakeup by ULP");
break;
default: // ESP_SLEEP_WAKEUP_UNDEFINED
Serial.println("wakeup not by deep sleep");
break;
}
time_t now;
struct tm timeinfo;
time(&now);
setenv("TZ", "CET-1CEST,M3.5.0/2,M10.5.0/3", 1);
tzset();
localtime_r(&now, &timeinfo);
if (timeinfo.tm_year < (2016 - 1900)) {
// time not set
}
strftime(timeStr, sizeof(timeStr), "%H:%M", &timeinfo);
char strftime_buf[64];
strftime(strftime_buf, sizeof(strftime_buf), "%c", &timeinfo);
Serial.print("Current time: "); Serial.println(strftime_buf);
/* SPI.begin(SX1276_SCK, SX1276_MISO, SX1276_MOSI, SX1276_CS);
LoRa.setPins(SX1276_CS, SX1276_RST, SX1276_IRQ);// set CS, reset, IRQ pin
if (!LoRa.begin(SX1276_BAND)) { // initialize ratio at 915 MHz
Serial.println("LoRa init failed. Check your connections.");
while (true); // if failed, do nothing
}*/
bme280.begin();
// bme280.printValues();
if (wakeup_reason == ESP_SLEEP_WAKEUP_TIMER) {
for (int i=0; i<15 && iot.wifi.status() != WL_CONNECTED; i++) delay(500);
if (iot.wifi.status() == WL_CONNECTED) {
transmitStatus();
obtain_time();
}
double seconds = difftime(now, mktime(&alarmTime));
Serial.printf("alarm in %f seconds\n", seconds);
strftime(strftime_buf, sizeof(strftime_buf), "%c", &alarmTime);
Serial.print(" Alarm time: "); Serial.println(strftime_buf);
if (!alarmArmed || -seconds > secondsToSleep) suspend();
else if (-seconds > 5*60) suspend(5*60);
}
u8g2.begin();
// delay(50);
screen = new WelcomeScreen();
screen->draw();
menuChange = eMainScreen;
u8g2.setContrast(127);
//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(NULL);
iot.mqtt.onMessage(onMqttMessage);
while (iot.wifi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
screen->draw();
}
IPAddress localIP = iot.wifi.getIP();
char ipStr[16];
localIP.toString().toCharArray(ipStr, 16);
if (timeinfo.tm_year < (2016 - 1900)) {
ESP_LOGI(TAG, "Time is not set yet. Connecting to WiFi and getting time over NTP.");
obtain_time();
// update 'now' variable with current time
time(&now);
}
if (timeinfo.tm_hour < 7 || timeinfo.tm_hour > 23) u8g2.setContrast(1);
else u8g2.setContrast(127);
mp3.begin();
led.setup();
lastButtonPress = millis();
rotary.registerCallback(rotation);
rotary.begin(rotaryPinA, rotaryPinB, rotaryPinButton);
}
//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(commandTopic.c_str(), 0);
//Trigger the transmission of the current state.
transmitStatus();
lastTransmit = millis();
}
void transmitStatus() {
DEBUG_PRINTLN(__func__);
float temp = bme280.readTemperature();
float humi = bme280.readHumidity();
float pres = bme280.readPressure();
float internal_temp = temperatureRead();
int internal_hall = hallRead();
for (int retry=0; retry<3 && !bme280.valid; retry++) {
bme280.reinit();
delay(250);
humi = bme280.readHumidity();
bme280.readPressure();
temp = bme280.readTemperature();
}
StaticJsonBuffer<200> jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
if (bme280.valid) {
root["temperature"] = bme280.readTemperature();
root["humidity"] = bme280.readHumidity();
root["pressure"] = bme280.readPressure();
} else {
root["temperature"] = internal_temp;
}
char sensorBuf[root.measureLength()+1];
root.printTo(sensorBuf, sizeof(sensorBuf));
uint16_t statusPacketIdSub;
statusPacketIdSub = iot.mqtt.publish(bme280Topic.c_str(), 0, false, sensorBuf);
// statusPacketIdSub = iot.mqtt.publish(bme280Topic.c_str(), 1, true, sensorBuf);
int voltage = rom_phy_get_vdd33();
char batteryBuf[9];
sprintf(batteryBuf, "%d", voltage);
statusPacketIdSub = iot.mqtt.publish(batteryTopic.c_str(), 0, false, batteryBuf);
}
//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__);
//Since we only subscribed to one topic, we only have to compare the payload
if (strcmp(payload, "ON") == 0) {
mp3.start();
} else if (strcmp(payload, "OFF") == 0) {
mp3.stop();
}
}
void suspend(uint32_t secondsToSleep) {
DEBUG_PRINTLN("Entering deep sleep");
mp3.stop();
led.stop();
bme280.sleep();
delay(1000);
u8g2.setPowerSave(1);
//properly disconnect from the MQTT broker
iot.mqtt.disconnect();
esp_sleep_enable_timer_wakeup(1000000LL * secondsToSleep);
const uint64_t ext_wakeup_rotarybtn_mask = 1ULL << rotaryPinButton;
esp_sleep_enable_ext1_wakeup(ext_wakeup_rotarybtn_mask, ESP_EXT1_WAKEUP_ANY_HIGH);
////esp_sleep_enable_ext0_wakeup((gpio_num_t)sensorPin, 0);
// const uint64_t ext_wakeup_pin_1_mask = 1ULL << ext_wakeup_pin_1;
// const uint64_t ext_wakeup_pin_2_mask = 1ULL << ext_wakeup_pin_2;
//// esp_sleep_enable_ext1_wakeup(ext_wakeup_pin_1_mask | ext_wakeup_pin_2_mask, ESP_EXT1_WAKEUP_ANY_HIGH);
//
////esp_deep_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_OFF); // power down all peripherals
if (!displaySleep) {
u8g2.begin();
u8g2.setPowerSave(0);
u8g2.clearBuffer();
u8g2.sendBuffer();
u8g2.setFont(u8g2_font_inb19_mf);
u8g2.drawStr(0, 20, timeStr);
rtc_gpio_hold_en((gpio_num_t)16); // 16 not connected to ulp processor :(
esp_sleep_enable_timer_wakeup(1000000LL * 60);
}
//send the ESP into deep sleep
esp_deep_sleep_start();
}
void suspend() {
time_t now;
time(&now);
double seconds = difftime(now, mktime(&alarmTime));
if (-seconds > secondsToSleep || !alarmArmed) suspend(secondsToSleep);
else suspend(5*60);
}
void setAlarmTime(struct tm time) {
alarmTime = time;
alarmArmed = true;
char strftime_buf[64];
strftime(strftime_buf, sizeof(strftime_buf), "%c", &alarmTime);
Serial.print("Setting Alarm time: "); Serial.println(strftime_buf);
}
struct tm getAlarmTime() {
return alarmTime;
}
void obtain_time(void) {
sntp_setoperatingmode(SNTP_OPMODE_POLL);
sntp_setservername(0, "de.pool.ntp.org");
sntp_init();
// wait for time to be set
time_t now = 0;
struct tm timeinfo = { 0 };
int retry = 0;
const int retry_count = 10;
while(timeinfo.tm_year < (2016 - 1900) && ++retry < retry_count) {
ESP_LOGI(TAG, "Waiting for system time to be set... (%d/%d)", retry, retry_count);
vTaskDelay(2000 / portTICK_PERIOD_MS);
time(&now);
localtime_r(&now, &timeinfo);
}
}
void rotation(int i, int direction, int buttonPressed) {
if(millis() - lastButtonPress >= 300) {
lastButtonPress = millis();
if (buttonPressed == 1) screen->select();
}
if (direction == 1) screen->next();
else if (direction == -1) screen->previous();
}
void loop()
{
if(millis() - lastUpdate >= 1000) {
lastUpdate = millis();
time_t now;
struct tm timeinfo;
time(&now);
localtime_r(&now, &timeinfo);
double seconds = difftime(now, mktime(&alarmTime));
bool stayAwake = false;
if (alarmArmed && seconds >= -2*5*60) stayAwake = true;
if (!mp3.playing && millis() - lastButtonPress >= 60*1000 && !stayAwake) suspend();
Serial.printf("alarm in %f seconds (%d)\n", seconds, alarmArmed);
if (seconds >= 0 && alarmArmed) {
alarmArmed = false;
lastButtonPress = millis();
led.wakeUpLight(0);
Serial.println("WAKEUP TIME!!!!!");
mp3.setVolume(2);
mp3.start("http://radioessen.cast.addradio.de/radioessen/simulcast/high/stream.mp3");
led.changeAnimation(2, 0);
}
if (alarmArmed && seconds >= -5*60 && seconds <= 0) {
led.wakeUpLight(255*(seconds+300)/300);
}
int voltage = rom_phy_get_vdd33();
Serial.printf("voltage: %d\n", voltage);
strftime(timeStr, sizeof(timeStr), "%H:%M:%S", &timeinfo);
sprintf(weatherStr, "%.1f°C %.1f%% %dmV", bme280.readTemperature(), bme280.readHumidity(), voltage);
// sprintf(weatherStr, "%.1f°C %.1f%% %.0fhPa", bme280.readTemperature(), bme280.readHumidity(), bme280.readPressure());
Serial.print("Free Heap: ");
Serial.println(ESP.getFreeHeap());
}
if(millis() - lastTransmit >= 60*1000) {
lastTransmit = millis();
transmitStatus();
}
if (menuChange != eNone) {
delete screen;
if (menuChange == eMainScreen) screen = new MainScreen();
else if (menuChange == eMainMenu) screen = new MainMenu();
else if (menuChange == eStationMenu) screen = new StationMenu();
else if (menuChange == eAlarmClockScreen) screen = new AlarmClockScreen();
else if (menuChange == eSuspendScreen) screen = new SuspendScreen();
else screen = new MainScreen();
menuChange = eNone;
}
screen->draw();
delay(100);
}