Skip to content

Commit

Permalink
Add support for time zones and sunrise/sunset calculation (#1840)
Browse files Browse the repository at this point in the history
* Amend SystemClock to store time internally in UTC

Add `Timezone` and `SolarCalculator` classes

Modify `SystemClock_NTP` sample

- Use Timezone class for local/UTC conversions
- Show time of next sunrise/sunset to demonstrate use of SolarCalculator

* Move `SolarCalculator` into Libraries

* Move `Timezone` into Libraries
  • Loading branch information
mikee47 authored and slaff committed Sep 19, 2019
1 parent 1237756 commit 626caec
Show file tree
Hide file tree
Showing 14 changed files with 785 additions and 45 deletions.
12 changes: 6 additions & 6 deletions Sming/Core/SystemClock.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,21 @@

SystemClockClass SystemClock;

time_t SystemClockClass::now(TimeZone timeType)
time_t SystemClockClass::now(TimeZone timeType) const
{
uint32_t systemTime = RTC.getRtcSeconds();

if(timeType == eTZ_UTC) {
systemTime -= timeZoneOffsetSecs;
if(timeType == eTZ_Local) {
systemTime += timeZoneOffsetSecs;
}

return systemTime;
}

bool SystemClockClass::setTime(time_t time, TimeZone timeType)
{
if(timeType == eTZ_UTC) {
time += timeZoneOffsetSecs;
if(timeType == eTZ_Local) {
time -= timeZoneOffsetSecs;
}

bool timeSet = RTC.setRtcSeconds(time);
Expand All @@ -41,7 +41,7 @@ bool SystemClockClass::setTime(time_t time, TimeZone timeType)
return timeSet;
}

String SystemClockClass::getSystemTimeString(TimeZone timeType)
String SystemClockClass::getSystemTimeString(TimeZone timeType) const
{
DateTime dt(now(timeType));
return DateTime(now(timeType)).toFullDateTimeString();
Expand Down
6 changes: 3 additions & 3 deletions Sming/Core/SystemClock.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class SystemClockClass
* @param timeType Time zone to use (UTC / local)
* @retval DateTime Current date and time
*/
time_t now(TimeZone timeType = eTZ_Local);
time_t now(TimeZone timeType = eTZ_Local) const;

/** @brief Set the system clock's time
* @param time Unix time to set clock to (quantity of seconds since 00:00:00 1970-01-01)
Expand All @@ -58,7 +58,7 @@ class SystemClockClass
* @retval String Current time in format: `dd.mm.yy hh:mm:ss`
* @note Date separator may be changed by adding `#define DT_DATE_SEPARATOR "/"` to source code
*/
String getSystemTimeString(TimeZone timeType = eTZ_Local);
String getSystemTimeString(TimeZone timeType = eTZ_Local) const;

/** @brief Sets the local time zone offset
* @param seconds Offset from UTC of local time zone in hours (-12..+12)
Expand All @@ -73,7 +73,7 @@ class SystemClockClass
return setTimeZoneOffset(localTimezoneOffset * SECS_PER_HOUR);
}

int getTimeZoneOffset()
int getTimeZoneOffset() const
{
return timeZoneOffsetSecs;
}
Expand Down
10 changes: 10 additions & 0 deletions Sming/Libraries/SolarCalculator/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Solar Calculator
================

Arduino library to support calculation of sunrise / sunset times

See :sample:`SystemClock_NTP` for example usage.

This is straight port of the code used by https://www.esrl.noaa.gov/gmd/grad/solcalc/

Javascript reference: https://www.esrl.noaa.gov/gmd/grad/solcalc/main.js
220 changes: 220 additions & 0 deletions Sming/Libraries/SolarCalculator/SolarCalculator.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
/****
* Sming Framework Project - Open Source framework for high efficiency native ESP8266 development.
* Created 2015 by Skurydin Alexey
* http://github.com/SmingHub/Sming
* All files of the Sming Core are provided under the LGPL v3 license.
*
* SolarCalculator.cpp - Calculation of apparent time of sunrise and sunset
*
* @author July 2018 mikee47 <[email protected]>
*
* This is straight port of the code used by
* https://www.esrl.noaa.gov/gmd/grad/solcalc/
*
* Javascript reference:
* https://www.esrl.noaa.gov/gmd/grad/solcalc/main.js
*
****/

#include "include/SolarCalculator.h"
#include <algorithm>
#include <math.h>
#include <WConstants.h>

#ifndef M_PI
#define M_PI PI
#endif

namespace solcalc
{
/* --------------------------- UTILITY FUNCTIONS ---------------------------- */

/*
* Convert Gregorian calendar date to Julian Day.
*
* @param year Full year
* @param month Month, 1 - 12
* @param day Day of month, 1-31
*/
float jDay(int year, int month, int day)
{
if(month <= 2) {
year -= 1;
month += 12;
}

int A = floor(year / 100);
int B = 2 - A + floor(A / 4);
return floor(365.25 * (year + 4716)) + floor(30.6001 * (month + 1)) + day + B - 1524.5;
}

/* Return fraction of time elapsed this century, AD 2000–2100.
*
* NOTE: 2,451,545 was the Julian day starting at noon UTC on 1 January AD 2000.
* 36,525 is a Julian century.
*/
float fractionOfCentury(float jd)
{
return (jd - 2451545.0) / 36525.0;
}

float radToDeg(float rad)
{
return 180.0 * rad / M_PI;
}

float degToRad(float deg)
{
return M_PI * deg / 180.0;
}

/* ---------------------------- SHARED FUNCTIONS ---------------------------- */

float meanObliquityOfEcliptic(float t);

float obliquityCorrection(float t)
{
float omega = 125.04 - 1934.136 * t;
// in degrees
return meanObliquityOfEcliptic(t) + 0.00256 * cos(degToRad(omega));
}

float geomMeanLongSun(float t)
{
float L0 = 280.46646 + t * (36000.76983 + t * 0.0003032);

// Get value between 0 and 360 degrees
while(L0 > 360) {
L0 -= 360;
}
while(L0 < 0) {
L0 += 360;
}

return L0;
}

float geomMeanAnomalySun(float t)
{
// in degrees
return 357.52911 + t * (35999.05029 - 0.0001537 * t);
}

/* ---------------------------- EQUATION OF TIME ---------------------------- */

/* Obliquity of the ecliptic is the term used by astronomers for the inclination
* of Earth's equator with respect to the ecliptic, or of Earth's rotation axis
* to a perpendicular to the ecliptic.
*/
float meanObliquityOfEcliptic(float t)
{
float seconds = 21.448 - t * (46.8150 + t * (0.00059 - t * 0.001813));
// in degrees
return 23.0 + (26.0 + (seconds / 60.0)) / 60.0;
}

float eccentricityEarthOrbit(float t)
{
// e is unitless
return 0.016708634 - t * (0.000042037 + 0.0000001267 * t);
}

/* The difference between mean solar time (as shown by clocks) and apparent
* solar time (indicated by sundials), which varies with the time of year.
*/
float equationOfTime(float t)
{
float epsilon = obliquityCorrection(t);
float l0 = geomMeanLongSun(t);
float e = eccentricityEarthOrbit(t);
float m = geomMeanAnomalySun(t);

float y = tan(degToRad(epsilon) / 2);
y *= y;

float sin2l0 = sin(2.0 * degToRad(l0));
float sinm = sin(degToRad(m));
float cos2l0 = cos(2.0 * degToRad(l0));
float sin4l0 = sin(4.0 * degToRad(l0));
float sin2m = sin(2.0 * degToRad(m));

float Etime =
y * sin2l0 - 2.0 * e * sinm + 4.0 * e * y * sinm * cos2l0 - 0.5 * y * y * sin4l0 - 1.25 * e * e * sin2m;
return radToDeg(Etime) * 4.0; // in minutes of time
}

/* --------------------------- SOLAR DECLINATION ---------------------------- */

float sunEqOfCenter(float t)
{
float m = geomMeanAnomalySun(t);
float mrad = degToRad(m);
float sinm = sin(mrad);
float sin2m = sin(mrad * 2);
float sin3m = sin(mrad * 3);
// in degrees
return sinm * (1.914602 - t * (0.004817 + 0.000014 * t)) + sin2m * (0.019993 - 0.000101 * t) + sin3m * 0.000289;
}

float sunTrueLong(float t)
{
// in degrees
return geomMeanLongSun(t) + sunEqOfCenter(t);
}

float sunApparentLong(float t)
{
float omega = 125.04 - 1934.136 * t;
// lambda in degrees
return sunTrueLong(t) - 0.00569 - 0.00478 * sin(degToRad(omega));
}

float sunDeclination(float t)
{
float e = obliquityCorrection(t);
float lambda = sunApparentLong(t);
float sint = sin(degToRad(e)) * sin(degToRad(lambda));
// theta in degrees
return radToDeg(asin(sint));
}

/* ------------------------------- HOUR ANGLE ------------------------------- */

float hourAngleSunrise(float lat, float solarDec)
{
float latRad = degToRad(lat);
float sdRad = degToRad(solarDec);
float HAarg = (cos(degToRad(90.833)) / (cos(latRad) * cos(sdRad)) - tan(latRad) * tan(sdRad));
// HA in radians (for sunset, use -HA)
return acos(HAarg);
}

float sunRiseSetUTC(bool isRise, float jday, float latitude, float longitude)
{
float t = fractionOfCentury(jday);
float eqTime = equationOfTime(t);
float solarDec = sunDeclination(t);
float hourAngle = hourAngleSunrise(latitude, solarDec);
float delta = longitude + radToDeg(isRise ? hourAngle : -hourAngle);
return 720.0 - (4.0 * delta) - eqTime; // in minutes
}

}; // namespace solcalc

int SolarCalculator::sunRiseSet(bool isRise, int y, int m, int d)
{
using namespace solcalc;

float jday = jDay(y, m, d);
float timeUTC = sunRiseSetUTC(isRise, jday, ref.latitude, ref.longitude);

// Advance the calculated time by a fraction of itself; why?
float newTimeUTC = sunRiseSetUTC(isRise, jday + timeUTC / 1440.0, ref.latitude, ref.longitude);

// Check there is a sunrise or sunset, e.g. in the (ant)arctic.
if(std::isnan(newTimeUTC)) {
return -1;
}

return round(newTimeUTC);
}
91 changes: 91 additions & 0 deletions Sming/Libraries/SolarCalculator/include/SolarCalculator.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/****
* Sming Framework Project - Open Source framework for high efficiency native ESP8266 development.
* Created 2015 by Skurydin Alexey
* http://github.com/SmingHub/Sming
* All files of the Sming Core are provided under the LGPL v3 license.
*
* SolarCalculator.h - Calculation of apparent time of sunrise and sunset
*
* Note: Months are 1-based
*
****/

#pragma once

#include <DateTime.h>

/**
* @brief A location is required to compute solar times
*/
struct SolarRef {
float latitude;
float longitude;
};

class SolarCalculator
{
public:
/**
* @brief Default constructor, uses Royal Observatory, Greenwich as default
*/
SolarCalculator()
{
}

/**
* @brief Perform calculations using the given solar reference
*/
SolarCalculator(const SolarRef& ref) : ref(ref)
{
}

/**
* @brief Get the current location reference in use
*/
const SolarRef& getRef() const
{
return ref;
}

/**
* @brief Set the location reference for calculations
*/
void setRef(const SolarRef& ref)
{
this->ref = ref;
}

/**
* @briefCalculate a sunrise or sunset figure for a given day.
* @param isRise true for sunrise, false for sunset
* @param y Absolute year
* @param m Month number (1 - 12)
* @param d Day of month (1 - 31)
* @retval int Minutes since midnight, -1 if there is no sunrise/sunset
* @{
*/
int sunRiseSet(bool isRise, int y, int m, int d);

int sunrise(int y, int m, int d)
{
return sunRiseSet(true, y, m, d);
}

int sunset(int y, int m, int d)
{
return sunRiseSet(false, y, m, d);
}

/**
* @}
*/

private:
/*
* Though most time zones are offset by whole hours, there are a few zones
* offset by 30 or 45 minutes, so the argument must be declared as a float.
*
* Royal Observatory, Greenwich, seems an appropriate default setting
*/
SolarRef ref = {51.4769, 0.0005};
};
8 changes: 8 additions & 0 deletions Sming/Libraries/Timezone/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Timezone
========

Arduino library to support local/UTC time conversions using rules

See :sample:`SystemClock_NTP` for example usage.

Port of https://github.com/JChristensen/Timezone for Sming.
Loading

0 comments on commit 626caec

Please sign in to comment.