WLED/usermods/UserModv2_SunRiseAndSet/UserMod_SunRiseAndSet.h
garyd9 1da539ef05 couple of bug fixes
Fix a typo in one of the json vars
Fix an odd issue that would occur if sunrise or sunset occurs in the first hour after midnight UTC.  (The Dusk2Dawn lib would return a value greater than 1440.)
2020-08-09 21:58:27 -04:00

167 lines
7.7 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#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, youll 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) % 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;
}
};