Skip to content

Commit

Permalink
Merge pull request Aircoookie#1085 from garyd9/UserMod_SunRiseAndSet
Browse files Browse the repository at this point in the history
create "sunrise and sunset" WLED usermod.
  • Loading branch information
Aircoookie authored Aug 9, 2020
2 parents 980794e + 18ab2b3 commit 09cf528
Showing 2 changed files with 170 additions and 0 deletions.
15 changes: 15 additions & 0 deletions usermods/UserModv2_SunRiseAndSet/README.md
Original file line number Diff line number Diff line change
@@ -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.
155 changes: 155 additions & 0 deletions usermods/UserModv2_SunRiseAndSet/UserMod_SunRiseAndSet.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
#pragma once

#include "wled.h"
#include <Dusk2Dawn.h>

/*
*
* 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);
}
}
#if 0
{ // 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
#endif
// 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;
}
};



0 comments on commit 09cf528

Please sign in to comment.