diff --git a/usermods/UserModv2_SunRiseAndSet/README.md b/usermods/UserModv2_SunRiseAndSet/README.md new file mode 100644 index 00000000..e989f089 --- /dev/null +++ b/usermods/UserModv2_SunRiseAndSet/README.md @@ -0,0 +1,15 @@ +WLED v2 UserMod for running macros at sunrise and sunset. + +At the time of this text, this user mod requires code to be changed to set certain variables: + 1. To reflect the user's graphical location (latitude/longitude) used for calculating apparent sunrise/sunset + 2. To specify which macros will be run at sunrise and/or sunset. (defaults to 15 at sunrise and 16 at sunset) + 3. To optionally provide an offset from sunrise/sunset, in minutes (max of +/- 2 hours), when the macro will be run. + +In addition, WLED must be configured to get time from NTP (and the time must be retrieved via NTP.) + +Please open the UserMod_SunRiseAndSet.h file for instructions on what needs to be changed, where to copy files, etc. + +If this usermod proves useful enough, the code might eventually be updated to allow prompting for the required information +via the web interface and to store settings in EEPROM instead of hard-coding in the .h file. + +This usermod has only been tested on the esp32dev platform, but there's no reason it wouldn't work on other platforms. diff --git a/usermods/UserModv2_SunRiseAndSet/UserMod_SunRiseAndSet.h b/usermods/UserModv2_SunRiseAndSet/UserMod_SunRiseAndSet.h new file mode 100644 index 00000000..623b4bb4 --- /dev/null +++ b/usermods/UserModv2_SunRiseAndSet/UserMod_SunRiseAndSet.h @@ -0,0 +1,155 @@ +#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 = now(); + 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); + m_nUserSunset = m_pD2D->sunset(tmNow.Year + 1970, tmNow.Month, tmNow.Day, false); + 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); + 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); + } + } + + { // debug block.. + + // for debugging, convert everything to "local" time + updateLocalTime(); + int offset = (localTime - timeUTC) / 60; + int srMin = m_nUserSunrise + offset; + int ssMin = m_nUserSunset + offset; + + Serial.printf("NEXT SUNRISE: %d:%d\n", srMin / 60, srMin % 60); + Serial.printf("NEXT SUNSET: %d:%d\n", ssMin / 60, ssMin % 60); + } // end debug block + + // 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)) + } + + + ~UserMod_SunRiseAndSet(void) + { + if (m_pD2D) delete m_pD2D; + } +}; + + +