/* * * 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 //Include Basecamp in this sketch #include #include #include "esp_wifi.h" #include #ifdef U8X8_HAVE_HW_SPI #include #endif #ifdef U8X8_HAVE_HW_I2C #include #endif #include #include "driver/rtc_io.h" #include #include #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 Basecamp iot; 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(); } Serial.print(" Alarm time: "); alarmclock.printTimeUnix(TIME_ALARM); double seconds = alarmclock.getSecondsToAlarm(); Serial.printf("alarm in %f seconds\n", seconds); 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"); while (iot.wifi.status() != WL_CONNECTED) { delay(500); Serial.print("."); screen->draw(); } WiFi.onEvent(WiFiGotIP, WiFiEvent_t::SYSTEM_EVENT_STA_GOT_IP); IPAddress localIP = iot.wifi.getIP(); char ipStr[16]; localIP.toString().toCharArray(ipStr, 16); if (!alarmclock.isTimeValid()) { ESP_LOGI(TAG, "Time is not set yet. Connecting to WiFi and getting time over NTP."); alarmclock.updateNTPTime(); } alarmclock.getTime(state.timeStr, sizeof(state.timeStr), "%H:%M:%S", TIME_CURRENT); 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(); udp.beginMulticast(udpMulticastAddress, udpMulticastPort); lastButtonPress = millis(); rotary.registerCallback(rotation); rotary.begin(rotaryPinA, rotaryPinB, rotaryPinButton); 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; } } lastActive = millis(); } void WiFiGotIP(WiFiEvent_t event, WiFiEventInfo_t info) { Serial.println("WiFi connected"); Serial.println("IP address: "); Serial.println(IPAddress(info.got_ip.ip_info.ip.addr)); } //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(); 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"] = bme280.readTemperature(); root["humidity"] = bme280.readHumidity(); root["pressure"] = bme280.readPressure(); } 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(); 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(); 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); } int voltage = rom_phy_get_vdd33(); Serial.printf("voltage: %d\n", voltage); 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(), 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); }