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.
470 lines
14 KiB
470 lines
14 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
|
|
* SPIFFS: put files in data directory, platformio run --target uploadfs
|
|
*/
|
|
|
|
#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 <LoRa.h>
|
|
|
|
#include "driver/rtc_io.h"
|
|
|
|
#include <WiFi.h>
|
|
#include <WiFiUdp.h>
|
|
|
|
#include "main.h"
|
|
#include "hardware.h"
|
|
#include "mp3.h"
|
|
#include "BME280.h"
|
|
#include "rotary.h"
|
|
#include "screen.h"
|
|
#include "led.h"
|
|
#include "AlarmClock.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);
|
|
|
|
unsigned char udprecvbuf[1460];
|
|
|
|
RTC_DATA_ATTR WEBRADIO_STATE_PERSISTANT state_persistant;
|
|
WEBRADIO_STATE state;
|
|
|
|
// Create a new Basecamp instance called iot that will start the ap in open mode and the webserver ui only in setup mode
|
|
Basecamp iot{Basecamp::SetupModeWifiEncryption::none, Basecamp::ConfigurationUI::accessPoint};
|
|
|
|
BME280 bme280;
|
|
MP3 mp3;
|
|
Rotary rotary;
|
|
Screen* screen;
|
|
Led led;
|
|
WiFiUDP udp;
|
|
AlarmClock alarmclock;
|
|
|
|
menuType menuChange = eNone;
|
|
uint32_t lastButtonPress = 0;
|
|
uint32_t lastActive = 0;
|
|
uint32_t lastUpdate = 0;
|
|
uint32_t lastTransmit = 0;
|
|
bool sensorON = false;
|
|
bool alarmRunning = false;
|
|
|
|
//Variables for the mqtt packages and topics
|
|
String commandTopic;
|
|
String weatherTopic;
|
|
String statusTopic;
|
|
String sensorTopic;
|
|
|
|
const IPAddress udpMulticastAddress(239,255,255,245);
|
|
const uint16_t udpMulticastPort = 5555;
|
|
|
|
|
|
|
|
void setup() {
|
|
// gpio configuration
|
|
// rtc_gpio_deinit((gpio_num_t)buttonPin);
|
|
pinMode(buttonPin, INPUT_PULLUP);
|
|
// rtc_gpio_deinit((gpio_num_t)sensorPin);
|
|
pinMode(sensorPin, INPUT_PULLUP);
|
|
pinMode(18, OUTPUT);
|
|
digitalWrite(18, HIGH); // disable LoRa_CS
|
|
|
|
state_persistant.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";
|
|
weatherTopic = "esp32-node/stat/" + iot.hostname + "/weather";
|
|
statusTopic = "esp32-node/stat/" + iot.hostname + "/status";
|
|
sensorTopic = "esp32-node/stat/" + iot.hostname + "/sensor";
|
|
|
|
// LoRa
|
|
SPI.begin(SX1276_SCK, SX1276_MISO, SX1276_MOSI, SX1276_CS);
|
|
LoRa.setPins(SX1276_CS, SX1276_RST, SX1276_IRQ);
|
|
if (!LoRa.begin(SX1276_BAND)) {
|
|
Serial.println("LoRa init failed");
|
|
} else {
|
|
//LoRa.onReceive(onLoRaReceive);
|
|
//LoRa.receive();
|
|
// LoRa.idle();
|
|
LoRa.sleep();
|
|
}
|
|
|
|
/*
|
|
//Use the web object to add elements to the interface
|
|
iot.web.addInterfaceElement("color", "input", "", "#configform", "LampColor");
|
|
iot.web.setInterfaceElementAttribute("color", "type", "text");
|
|
*/
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
Serial.print(" Current time: "); alarmclock.printTimeUnix(TIME_CURRENT);
|
|
|
|
/* 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();
|
|
alarmclock.updateNTPTime();
|
|
}
|
|
|
|
if (!alarmclock.isPending(secondsToSleep)) suspend();
|
|
else if (!alarmclock.isPending(5*60)) suspend(5*60);
|
|
}
|
|
|
|
u8g2.begin();
|
|
// delay(50);
|
|
screen = new WelcomeScreen();
|
|
screen->draw();
|
|
menuChange = eMainScreen;
|
|
u8g2.setContrast(127);
|
|
// U8X8_CA(0x08d, 0x010); /* [2] charge pump setting (p62): 0x014 enable, 0x010 disable */
|
|
// U8X8_CA(0xd9, 0x22); /* [2] pre-charge period 0x022/f1*/
|
|
|
|
//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);
|
|
|
|
iot.mqtt.setWill(sensorTopic.c_str(), 0, false, "OFFLINE");
|
|
|
|
// Wait here if not configured
|
|
if (iot.configuration.get("WifiConfigured") != "True") {
|
|
while (iot.wifi.status() != WL_CONNECTED) {
|
|
delay(500);
|
|
Serial.print(".");
|
|
screen->draw();
|
|
}
|
|
}
|
|
|
|
strcpy(state.timeStr, "--:--:--");
|
|
|
|
if (alarmclock.isNight()) {
|
|
Serial.println("night mode");
|
|
u8g2.setContrast(1);
|
|
u8x8_cad_StartTransfer(u8g2.getU8x8());
|
|
u8x8_cad_SendCmd( u8g2.getU8x8(), 0xD9);
|
|
u8x8_cad_SendArg( u8g2.getU8x8(), 0x01); //max 34 /* [2] pre-charge period 0x022/f1*/
|
|
u8x8_cad_EndTransfer(u8g2.getU8x8());
|
|
// u8x8_cad_StartTransfer(u8g2.getU8x8());
|
|
// u8x8_cad_SendCmd( u8g2.getU8x8(), 0x8D);
|
|
// u8x8_cad_SendArg( u8g2.getU8x8(), 0x10); /* [2] charge pump setting (p62): 0x014 enable, 0x010 disable */
|
|
// u8x8_cad_EndTransfer(u8g2.getU8x8());
|
|
} else u8g2.setContrast(127);
|
|
|
|
mp3.begin();
|
|
led.setup();
|
|
|
|
lastButtonPress = millis();
|
|
rotary.registerCallback(rotation);
|
|
rotary.begin(rotaryPinA, rotaryPinB, rotaryPinButton);
|
|
|
|
/* udp.beginMulticast(udpMulticastAddress, udpMulticastPort);*/
|
|
|
|
lastActive = millis();
|
|
}
|
|
|
|
|
|
//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();
|
|
esp_sleep_wakeup_cause_t wakeup_reason = esp_sleep_get_wakeup_cause();
|
|
if (wakeup_reason == ESP_SLEEP_WAKEUP_EXT1) {
|
|
uint64_t wakeup_pinmask = esp_sleep_get_ext1_wakeup_status();
|
|
if (wakeup_pinmask & (1ULL << ext_wakeup_pin_1)) {
|
|
uint16_t statusPacketIdSub = iot.mqtt.publish(sensorTopic.c_str(), 0, false, state.timeStr);
|
|
sensorON = true;
|
|
}
|
|
}
|
|
lastTransmit = millis();
|
|
}
|
|
|
|
void transmitStatus() {
|
|
DEBUG_PRINTLN(__func__);
|
|
|
|
if (iot.wifi.status() != WL_CONNECTED) {
|
|
return;
|
|
}
|
|
|
|
float temp = bme280.readTemperature();
|
|
float humi = bme280.readHumidity();
|
|
float pres = bme280.readPressure();
|
|
|
|
float internal_temp = temperatureRead();
|
|
int internal_hall = hallRead();
|
|
|
|
if (!bme280.valid) {
|
|
bme280.reinit(); // TODO
|
|
if (!bme280.valid) return;
|
|
temp = bme280.readTemperature();
|
|
humi = bme280.readHumidity();
|
|
pres = bme280.readPressure();
|
|
}
|
|
|
|
StaticJsonBuffer<200> jsonBuffer;
|
|
JsonObject& root = jsonBuffer.createObject();
|
|
if (bme280.valid) {
|
|
root["temperature"] = temp;
|
|
root["humidity"] = humi;
|
|
root["pressure"] = pres;
|
|
} else {
|
|
//root["temperature"] = internal_temp;
|
|
}
|
|
|
|
char weatherBuf[root.measureLength()+1];
|
|
root.printTo(weatherBuf, sizeof(weatherBuf));
|
|
uint16_t statusPacketIdSub;
|
|
statusPacketIdSub = iot.mqtt.publish(weatherTopic.c_str(), 0, false, weatherBuf);
|
|
|
|
StaticJsonBuffer<200> jsonBuffer2;
|
|
JsonObject& root2 = jsonBuffer2.createObject();
|
|
root2["voltage"] = rom_phy_get_vdd33();
|
|
root2["rssi"] = WiFi.RSSI();
|
|
root2["heap"] = ESP.getFreeHeap();
|
|
char statusBuf[root2.measureLength()+1];
|
|
root2.printTo(statusBuf, sizeof(statusBuf));
|
|
statusPacketIdSub = iot.mqtt.publish(statusTopic.c_str(), 0, false, statusBuf);
|
|
}
|
|
|
|
|
|
//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();
|
|
|
|
Serial.printf("going to deep_sleep for %u seconds", secondsToSleep);
|
|
|
|
esp_sleep_enable_timer_wakeup(1000000LL * secondsToSleep);
|
|
const uint64_t ext_wakeup_rotarybtn_mask = 1ULL << rotaryPinButton;
|
|
// const uint64_t ext_wakeup_sensor_mask = 1ULL << sensorPin;
|
|
const uint64_t ext_wakeup_sensor_mask = 0; // disable
|
|
esp_sleep_enable_ext1_wakeup(ext_wakeup_rotarybtn_mask | ext_wakeup_sensor_mask, ESP_EXT1_WAKEUP_ANY_HIGH);
|
|
|
|
////esp_sleep_enable_ext0_wakeup((gpio_num_t)sensorPin, 0);
|
|
//
|
|
////esp_deep_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_OFF); // power down all peripherals
|
|
|
|
if (!state.displaySleep) {
|
|
u8g2.begin();
|
|
u8g2.setPowerSave(0);
|
|
u8g2.clearBuffer();
|
|
u8g2.sendBuffer();
|
|
u8g2.setFont(u8g2_font_inb19_mf);
|
|
u8g2.drawStr(0, 20, state.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() {
|
|
if (!alarmclock.isPending(secondsToSleep)) suspend(secondsToSleep);
|
|
else suspend(5*60);
|
|
}
|
|
|
|
|
|
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();
|
|
lastActive = millis();
|
|
}
|
|
|
|
|
|
void loop()
|
|
{
|
|
if(millis() - lastUpdate >= 1000) {
|
|
lastUpdate = millis();
|
|
|
|
if (iot.wifi.status() == WL_CONNECTED) {
|
|
if (alarmclock.isNTPExpired()) { // will return true first time called
|
|
Serial.println("Updating NTP Time");
|
|
alarmclock.updateNTPTime();
|
|
}
|
|
}
|
|
|
|
double seconds = alarmclock.getSecondsToAlarm();
|
|
|
|
bool stayAwake = false;
|
|
if (alarmclock.isPending(2*5*60)) stayAwake = true;
|
|
if (!mp3.playing && millis() - lastActive >= stayOnTime && !stayAwake) suspend();
|
|
|
|
Serial.printf("alarm in %f seconds (%d)\n", seconds, alarmclock.isAlarmArmed());
|
|
if (seconds <= 0 && alarmclock.isAlarmArmed()) { // alarm is currently running
|
|
if (mp3.aborted) alarmRunning = false;
|
|
if (!mp3.playing && !alarmRunning) {
|
|
led.wakeUpLight(0);
|
|
mp3.setVolume(2);
|
|
Serial.println("WAKEUP TIME!!!!!");
|
|
alarmRunning = true;
|
|
if (!mp3.aborted && iot.wifi.status() == WL_CONNECTED) {
|
|
mp3.start("http://radioessen.cast.addradio.de/radioessen/simulcast/high/stream.mp3");
|
|
} else {
|
|
static const char starwars[] PROGMEM =
|
|
"Cantina:d=8,o=5,b=250:a,p,d6,p,a,p,d6,p,a,d6,p,a,p,g#,4a,a,g#,a,4g,f#,g,f#,4f.,d.,16p,4p.,a,p,d6,p,a,p,d6,p,a,d6,p,a,p,g#,a,p,g,p,4g.,f#,g,p,c6,4a#,4a,4g";
|
|
// Plenty more at: http://mines.lumpylumpy.com/Electronics/Computers/Software/Cpp/MFC/RingTones.RTTTL
|
|
mp3.playRTTTL(starwars, strlen_P(starwars));
|
|
// mp3.start_Progmem();
|
|
}
|
|
led.changeAnimation(2, 0);
|
|
}
|
|
if (lastButtonPress != 0 && millis() - lastButtonPress <= 10*1000) alarmclock.disableAlarm(); // is this okay?
|
|
if (seconds < -360) alarmclock.disableAlarm(); // auto disable after time
|
|
if (alarmclock.isAlarmArmed() == false) mp3.stop();
|
|
lastActive = millis();
|
|
}
|
|
if (alarmclock.isPending(5*60) && seconds >= 0) {
|
|
led.wakeUpLight(255*(-seconds+300)/300);
|
|
}
|
|
|
|
state.voltage = rom_phy_get_vdd33();
|
|
state.rssi = WiFi.RSSI();
|
|
Serial.printf("voltage: %d\n", state.voltage);
|
|
|
|
if (alarmclock.isTimeValid()) alarmclock.getTime(state.timeStr, sizeof(state.timeStr), "%H:%M:%S", TIME_CURRENT);
|
|
sprintf(state.string1, "%.1f°C %.1f%% %.0fhPa", bme280.readTemperature(), bme280.readHumidity(), bme280.readPressure());
|
|
sprintf(state.string2, "%.1f %dmV %ddBm %ukB %s", temperatureRead(), state.voltage, WiFi.RSSI(), ESP.getFreeHeap()/1024, alarmclock.isAlarmArmed()?"A":"_");
|
|
|
|
Serial.print("Free Heap: ");
|
|
Serial.println(ESP.getFreeHeap());
|
|
|
|
Serial.print("lastButtonPress: ");
|
|
Serial.println(millis() - lastButtonPress);
|
|
|
|
/* if (sensorON && digitalRead(sensorPin) == LOW) {
|
|
uint16_t statusPacketIdSub = iot.mqtt.publish(sensorTopic.c_str(), 0, false, "OFF");
|
|
sensorON = false;
|
|
} */
|
|
|
|
/*
|
|
// Send a packet
|
|
udp.beginMulticastPacket();
|
|
udp.printf("Seconds since boot: %u", millis()/1000);
|
|
udp.endPacket();
|
|
*/
|
|
|
|
}
|
|
|
|
/* if (digitalRead(sensorPin) == HIGH) {
|
|
lastActive = millis();
|
|
if (sensorON == false && digitalRead(sensorPin) == HIGH) {
|
|
uint16_t statusPacketIdSub = iot.mqtt.publish(sensorTopic.c_str(), 0, false, state.timeStr);
|
|
sensorON = true;
|
|
}
|
|
} */
|
|
|
|
if(millis() - lastTransmit >= 60*1000) {
|
|
lastTransmit = millis();
|
|
transmitStatus();
|
|
}
|
|
/*
|
|
int lenp = udp.parsePacket();
|
|
//Serial.println(lenp);
|
|
while(udp.available()) {
|
|
int len = udp.read(udprecvbuf, sizeof(udprecvbuf));
|
|
Serial.printf("udp received %d bytes\n", len);
|
|
}
|
|
*/
|
|
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 if (menuChange == eLightScreen) screen = new LightScreen();
|
|
else screen = new MainScreen();
|
|
menuChange = eNone;
|
|
}
|
|
|
|
screen->draw();
|
|
|
|
delay(100);
|
|
}
|
|
|