#pragma once #include "wled.h" #include /* * * REQUIREMENTS: * The Dusk2Dawn library must be installed. This can be found at https://github.com/dmkishi/Dusk2Dawn. The 1.0.1 version of this library found via * Arduino or platformio library managers is buggy and won't compile. The latest version from github should be used. * * NTP must be enabled and functional. It simply makes no sense to have events on sunrise/sunset when an accurate time isn't available. * * The user's geographical latitude and longitude must be configured (in decimal, not degrees/minutes/etc) using m_fLatitude and m_fLongitude * * if desired, an offset of up to +/- 2 hours can be specified for each of sunrise/sunset using m_sunriseOffset and m_sunsetOffset (defaults to 0) * * The specific macro to run at sunrise and/or sunset can be changed using m_sunriseMacro and m_sunsetMacro. (defaults to 15 and 16) * * From the Dusk2Dawn library: * HINT: An easy way to find the longitude and latitude for any location is * to find the spot in Google Maps, right click the place on the map, and * select "What's here?". At the bottom, you’ll see a card with the * coordinates. * * Once configured, copy UserMod_SunRiseAndSet.h to the sketch file (the same folder as wled00.ino exists), * and then edit "usermods_list.cpp": * Add '#include "UserMod_SunRiseAndSet.h"' in the 'includes' area * Add 'usermods.add(new UserMod_SunRiseAndSet());' in the registerUsermods() area * */ class UserMod_SunRiseAndSet : public Usermod { private: /**** USER SETTINGS ****/ float m_fLatitude = 40.6; // latitude where sunrise/set are calculated float m_fLongitude = -79.80; // longitude where sunrise/set are calculated int8_t m_sunriseOffset = 0; // offset from sunrise, in minutes, when macro should be run (negative for before sunrise, positive for after sunrise) int8_t m_sunsetOffset = 0; // offset from sunset, in minutes, when macro should be run (negative for before sunset, positive for after sunset) uint8_t m_sunriseMacro = 15; // macro number to run at sunrise uint8_t m_sunsetMacro = 16; // macro number to run at sunset /**** END OF USER SETTINGS. DO NOT EDIT BELOW THIS LINE! ****/ Dusk2Dawn *m_pD2D = NULL; // this must be dynamically allocated in order for parameters to be loaded from EEPROM int m_nUserSunrise = -1; // time, in minutes from midnight, of sunrise int m_nUserSunset = -1; // time, in minutes from midnight, of sunset byte m_nLastRunMinute = -1; // indicates what minute the userloop was last run - used so that the code only runs once per minute public: virtual void setup(void) { /* TODO: From EEPROM, load the following variables: * * int16_t latitude16 = 4060; // user provided latitude, multiplied by 100 and rounded * int16_t longitude16 = -7980; // user provided longitude, multiplied by 100 and rounded. * int8_t sunrise_offset = 0; // number of minutes to offset the sunrise macro trigger (positive for minutes after sunrise, negative for minutes before) * int8_t sunset_offset = 0; // number of minutes to offset the sunset macro trigger (positive for minutes after sunset, negative for minutes before) * * then: * m_fLatitude = (float)latitude / 100.0; * m_fLongitude = (float)longitude / 100.0; * m_sunriseOffset = sunrise_offset; * m_sunsetOffset = sunset_offset; */ if ((0.0 != m_fLatitude) || (0.0 != m_fLongitude)) { m_pD2D = new Dusk2Dawn (m_fLatitude, m_fLongitude, 0 /* UTC */); // can't really check for failures. if the alloc fails, the mod just doesn't work. } } void loop(void) { // without NTP, or a configured lat/long, none of this stuff is going to work... // As an alternative, need to figure out how to determine if the user has manually set the clock or not. if (m_pD2D && (999000000L != ntpLastSyncTime)) { // to prevent needing to import all the timezone stuff from other modules, work completely in UTC time_t timeUTC = toki.second(); tmElements_t tmNow; breakTime(timeUTC, tmNow); int nCurMinute = tmNow.Minute; if (m_nLastRunMinute != nCurMinute) //only check once a new minute begins { m_nLastRunMinute = nCurMinute; int numMinutes = (60 * tmNow.Hour) + m_nLastRunMinute; // how many minutes into the day are we? // check to see if sunrise/sunset should be re-determined. Only do this if neither sunrise nor sunset // are set. That happens when the device has just stated, and after both sunrise/sunset have already run. if ((-1 == m_nUserSunrise) && (-1 == m_nUserSunset)) { m_nUserSunrise = m_pD2D->sunrise(tmNow.Year + 1970, tmNow.Month, tmNow.Day, false) % 1440; m_nUserSunset = m_pD2D->sunset(tmNow.Year + 1970, tmNow.Month, tmNow.Day, false) % 1440; if (m_nUserSunrise > numMinutes) // has sunrise already passed? if so, recompute for tomorrow { breakTime(timeUTC + (60*60*24), tmNow); m_nUserSunrise = m_pD2D->sunrise(tmNow.Year + 1970, tmNow.Month, tmNow.Day, false) % 1440; if (m_nUserSunset > numMinutes) // if sunset has also passed, recompute that as well { m_nUserSunset = m_pD2D->sunset(tmNow.Year + 1970, tmNow.Month, tmNow.Day, false) % 1440; } } // offset by user provided values. becuase the offsets are signed bytes, the max offset is just over 2 hours. m_nUserSunrise += m_sunriseOffset; m_nUserSunset += m_sunsetOffset; } if (numMinutes == m_nUserSunrise) // Good Morning! { if (m_sunriseMacro) applyMacro(m_sunriseMacro); // run macro 15 m_nUserSunrise = -1; } else if (numMinutes == m_nUserSunset) // Good Night! { if (m_sunsetMacro) applyMacro(m_sunsetMacro); // run macro 16 m_nUserSunset = -1; } } // if (m_nLastRunMinute != nCurMinute) } // if (m_pD2D && (999000000L != ntpLastSyncTime)) } void addToJsonState(JsonObject& root) { JsonObject user = root["SunRiseAndSet"]; if (user.isNull()) user = root.createNestedObject("SunRiseAndSet"); char buf[10]; if (-1 != m_nUserSunrise) { snprintf(buf, 10, "%02d:%02d UTC", m_nUserSunrise / 60, m_nUserSunrise % 60); user["rise"] = buf; } if (-1 != m_nUserSunset) { snprintf(buf, 10, "%02d:%02d UTC", m_nUserSunset / 60, m_nUserSunset % 60); user["set"] = buf; } JsonObject vars = user.createNestedObject("vars"); vars["lat"] = m_fLatitude; vars["long"] = m_fLongitude; vars["rise_mac"] = m_sunriseMacro; vars["set_mac"] = m_sunsetMacro; vars["rise_off"] = m_sunriseOffset; vars["set_off"] = m_sunsetOffset; } ~UserMod_SunRiseAndSet(void) { if (m_pD2D) delete m_pD2D; } };