├── LICENSE ├── README.md ├── examples ├── EquationOfTime │ └── EquationOfTime.ino ├── SolarCalculatorTimeLib │ └── SolarCalculatorTimeLib.ino ├── SolarTrackingTimeLib │ └── SolarTrackingTimeLib.ino ├── SunriseSunset │ └── SunriseSunset.ino └── SunriseSunsetAltitude │ └── SunriseSunsetAltitude.ino ├── keywords.txt ├── library.properties └── src ├── SolarCalculator.cpp └── SolarCalculator.h /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 jpb10 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SolarCalculator Library for Arduino 2 | 3 | SolarCalculator is inspired by the [NOAA Solar Calculator](https://gml.noaa.gov/grad/solcalc/). 4 | 5 | This library provides functions to calculate the times of sunrise, sunset, solar noon, twilight (dawn and dusk), Sun's 6 | apparent position in the sky, equation of time, etc. 7 | 8 | Most formulae are taken from Astronomical Algorithms by Jean Meeus and optimized for Arduino. 9 | 10 | 11 | ## Installation 12 | 13 | Download and copy SolarCalculator to your local Arduino/libraries directory. 14 | 15 | ### Time 16 | 17 | Date and time inputs are assumed to be in **Universal Coordinated Time** (UTC). 18 | 19 | Although not required, it is recommended to use SolarCalculator along with the 20 | [Time](https://github.com/PaulStoffregen/Time) library or similar. 21 | 22 | 23 | ## Usage 24 | 25 | Include SolarCalculator.h in your sketch: 26 | ```cpp 27 | #include 28 | ``` 29 | 30 | Calculate the times of sunrise, transit (solar noon), and sunset, in hours: 31 | ```cpp 32 | double latitude = 42.36; // Observer's latitude 33 | double longitude = -71.058; // Observer's longitude 34 | int time_zone = -5; // UTC offset 35 | int year = 2000; // Calendar year (1901-2099) 36 | int month = 1; // Calendar month (1-12) 37 | int day = 1; // Calendar day (1-31) 38 | 39 | double transit, sunrise, sunset; 40 | calcSunriseSunset(year, month, day, latitude, longitude, transit, sunrise, sunset); 41 | ``` 42 | 43 | Or, using the Time library (Unix time): 44 | ```cpp 45 | time_t utc = now(); 46 | calcSunriseSunset(utc, latitude, longitude, transit, sunrise, sunset); 47 | ``` 48 | 49 | Convert to local standard time: 50 | ```cpp 51 | double sunrise_local = sunrise + time_zone; 52 | ``` 53 | * Refer to the example sketches for more on rounding and printing the results. 54 | 55 | Similarly, calculate the times of dawn and dusk, in hours: 56 | ```cpp 57 | calcCivilDawnDusk(utc, latitude, longitude, transit, c_dawn, c_dusk); 58 | calcNauticalDawnDusk(utc, latitude, longitude, transit, n_dawn, n_dusk); 59 | calcAstronomicalDawnDusk(utc, latitude, longitude, transit, a_dawn, a_dusk); 60 | ``` 61 | 62 | Sun's equatorial coordinates, in degrees and AUs: 63 | ```cpp 64 | calcEquatorialCoordinates(utc, rt_ascension, declination, radius_vector); 65 | ``` 66 | 67 | Sun's horizontal coordinates, corrected for atmospheric refraction, in degrees: 68 | ```cpp 69 | calcHorizontalCoordinates(utc, latitude, longitude, azimuth, elevation); 70 | ``` 71 | 72 | Equation of time, in minutes of time: 73 | ```cpp 74 | calcEquationOfTime(utc, eq); 75 | ``` 76 | where the results are passed by reference. 77 | 78 | 79 | ## Examples 80 | 81 | The following example sketches are included in this library: 82 | 83 | * `SunriseSunset`: Calculate the times of sunrise, solar noon, and sunset for a given date and location. 84 | 85 | * `SunriseSunsetAltitude`: Calculate the rise and set times at a height above the level of the horizon. 86 | 87 | * `SolarCalculatorTimeLib`: Calculate the rise and set times, equation of time, and current solar coordinates. 88 | 89 | * `SolarTrackingTimeLib`: Monitor the Sun's position in the sky for any location on Earth. 90 | 91 | * `EquationOfTime`: Plot the equation of time for a given year. 92 | 93 | 94 | ## Notes 95 | 96 | ### Accuracy 97 | 98 | Various things to consider: 99 | 100 | * The amount of atmospheric refraction changes with air temperature, pressure, and the elevation of the observer. 101 | Therefore, sunrise and sunset times can only be accurate to the nearest minute (Meeus, 1998). 102 | 103 | * Assuming a purely elliptical motion of the Earth, solar coordinates have a "low accuracy" of 0.01° (Meeus, 1998). To 104 | this precision, we ignore nutation, delta T, and higher-order terms in the relevant expressions. 105 | 106 | 107 | ### `double` vs `float` 108 | 109 | Although this library internally uses type `double` instead of `float`, all expressions have been optimized to work with 110 | single precision floating-point arithmetic. On Arduino Uno, 111 | [avr-libc](https://www.nongnu.org/avr-libc/user-manual/group__avr__math.html) substitutes and carries out the 112 | corresponding single precision operations. 113 | 114 | 115 | ### Sunrise and sunset 116 | 117 | The algorithm for finding the times of sunrise and sunset implemented in this library is valid for all latitudes between 118 | the Arctic and Antarctic circles (about ± 66.5°). Outside this range, a more general algorithm should be used but is not 119 | provided at this time. 120 | 121 | 122 | ## References 123 | 124 | ESRL Global Monitoring Laboratory (n.d.). *NOAA Solar Calculator*. https://gml.noaa.gov/grad/solcalc/ 125 | 126 | Meeus, J. (1998). *Astronomical algorithms* (2nd ed.). Willmann-Bell. 127 | 128 | -------------------------------------------------------------------------------- /examples/EquationOfTime/EquationOfTime.ino: -------------------------------------------------------------------------------- 1 | //====================================================================================================================== 2 | // SolarCalculator Library for Arduino example sketch: EquationOfTime.ino 3 | // 4 | // Plot the equation of time for a given year. 5 | // 6 | // Tested with Arduino IDE 2.3.5 and Arduino Uno 7 | //====================================================================================================================== 8 | 9 | #include 10 | 11 | int year = 2000; 12 | 13 | void setup() 14 | { 15 | Serial.begin(9600); 16 | 17 | // Starting day (January 1) 18 | JulianDay day(year, 1, 1); 19 | 20 | for (int i = 0; i < 365; ++i) 21 | { 22 | double eq; 23 | calcEquationOfTime(day, eq); 24 | 25 | // Print and view with serial plotter 26 | Serial.println(eq); 27 | 28 | // Next day 29 | ++day.JD; 30 | } 31 | } 32 | 33 | void loop() 34 | { 35 | } 36 | -------------------------------------------------------------------------------- /examples/SolarCalculatorTimeLib/SolarCalculatorTimeLib.ino: -------------------------------------------------------------------------------- 1 | //====================================================================================================================== 2 | // SolarCalculator Library for Arduino example sketch: SolarCalculatorTimeLib.ino 3 | // 4 | // Calculate the rise and set times, equation of time, and current solar coordinates. 5 | // 6 | // Tested with Arduino IDE 2.3.5 and Arduino Uno 7 | //====================================================================================================================== 8 | 9 | #include 10 | #include 11 | 12 | // Location 13 | double latitude = 42.36; 14 | double longitude = -71.058; 15 | int utc_offset = -5; 16 | 17 | void setup() 18 | { 19 | Serial.begin(9600); 20 | 21 | double transit, sunrise, sunset; // Event times, in hours (UTC) 22 | double eq; // Equation of time, in minutes 23 | double ra, dec, r; // Equatorial coordinates, in degrees and AUs 24 | double az, el; // Horizontal coordinates, in degrees 25 | 26 | // Set system time to compile time 27 | setTime(compileTime() - utc_offset * 3600L); 28 | 29 | // Set time manually (hr, min, sec, day, mo, yr) 30 | //setTime(0, 0, 0, 1, 1, 2000); 31 | 32 | // Get current time 33 | time_t utc = now(); 34 | 35 | calcEquationOfTime(utc, eq); 36 | calcEquatorialCoordinates(utc, ra, dec, r); 37 | calcHorizontalCoordinates(utc, latitude, longitude, az, el); 38 | calcSunriseSunset(utc, latitude, longitude, transit, sunrise, sunset); 39 | 40 | // Print results 41 | Serial.print(F("Sunrise: ")); 42 | printSunTime24h(sunrise + utc_offset); 43 | Serial.print(F("Transit: ")); 44 | printSunTime24h(transit + utc_offset); 45 | Serial.print(F("Sunset: ")); 46 | printSunTime24h(sunset + utc_offset); 47 | Serial.print(F("Eq of time: ")); 48 | Serial.print(eq); 49 | Serial.println(F(" min")); 50 | Serial.print(F("RA: ")); 51 | Serial.print(ra / 15, 3); 52 | Serial.print(F("h Dec: ")); 53 | Serial.print(dec); 54 | Serial.print(F("° R: ")); 55 | Serial.print(r, 6); 56 | Serial.println(F(" AU")); 57 | Serial.print(F("Az: ")); 58 | Serial.print(az); 59 | Serial.print(F("° El: ")); 60 | Serial.print(el); 61 | Serial.println(F("°")); 62 | } 63 | 64 | void loop() 65 | { 66 | } 67 | 68 | // Code from JChristensen/Timezone Clock example 69 | time_t compileTime() 70 | { 71 | const uint8_t COMPILE_TIME_DELAY = 6; 72 | const char *compDate = __DATE__, *compTime = __TIME__, *months = "JanFebMarAprMayJunJulAugSepOctNovDec"; 73 | char chMon[4], *m; 74 | tmElements_t tm; 75 | 76 | strncpy(chMon, compDate, 3); 77 | chMon[3] = '\0'; 78 | m = strstr(months, chMon); 79 | tm.Month = ((m - months) / 3 + 1); 80 | 81 | tm.Day = atoi(compDate + 4); 82 | tm.Year = atoi(compDate + 7) - 1970; 83 | tm.Hour = atoi(compTime); 84 | tm.Minute = atoi(compTime + 3); 85 | tm.Second = atoi(compTime + 6); 86 | time_t t = makeTime(tm); 87 | return t + COMPILE_TIME_DELAY; 88 | } 89 | 90 | void printSunTime24h(double hours) 91 | { 92 | int m = int(round(hours * 60)); 93 | int hr = (m / 60) % 24; 94 | int mn = m % 60; 95 | printDigits(hr); 96 | Serial.print(':'); 97 | printDigits(mn); 98 | Serial.println(); 99 | } 100 | 101 | void printDigits(int digits) 102 | { 103 | if (digits < 10) 104 | Serial.print('0'); 105 | Serial.print(digits); 106 | } 107 | -------------------------------------------------------------------------------- /examples/SolarTrackingTimeLib/SolarTrackingTimeLib.ino: -------------------------------------------------------------------------------- 1 | //====================================================================================================================== 2 | // SolarCalculator Library for Arduino example sketch: SolarTrackingTimeLib.ino 3 | // 4 | // Monitor the Sun's position in the sky for any location on Earth. 5 | // 6 | // Tested with Arduino IDE 2.3.5 and Arduino Uno 7 | //====================================================================================================================== 8 | 9 | #include 10 | #include 11 | 12 | // Location 13 | double latitude = 42.36; 14 | double longitude = -71.058; 15 | int utc_offset = -5; 16 | 17 | // Refresh interval, in seconds 18 | int interval = 10; 19 | 20 | void setup() 21 | { 22 | Serial.begin(9600); 23 | 24 | // Set system time to compile time 25 | setTime(compileTime() - utc_offset * 3600L); 26 | 27 | // Set time manually (hr, min, sec, day, mo, yr) 28 | //setTime(0, 0, 0, 1, 1, 2000); 29 | } 30 | 31 | void loop() 32 | { 33 | static unsigned long next_millis = 0; 34 | 35 | // At every interval 36 | if (millis() > next_millis) 37 | { 38 | time_t utc = now(); 39 | double az, el; 40 | 41 | // Calculate the solar position, in degrees 42 | calcHorizontalCoordinates(utc, latitude, longitude, az, el); 43 | 44 | // Print results 45 | Serial.print(F("Az: ")); 46 | Serial.print(az); 47 | Serial.print(F("° El: ")); 48 | Serial.print(el); 49 | Serial.println(F("°")); 50 | 51 | next_millis = millis() + interval * 1000L; 52 | } 53 | } 54 | 55 | // Code from JChristensen/Timezone Clock example 56 | time_t compileTime() 57 | { 58 | const uint8_t COMPILE_TIME_DELAY = 6; 59 | const char *compDate = __DATE__, *compTime = __TIME__, *months = "JanFebMarAprMayJunJulAugSepOctNovDec"; 60 | char chMon[4], *m; 61 | tmElements_t tm; 62 | 63 | strncpy(chMon, compDate, 3); 64 | chMon[3] = '\0'; 65 | m = strstr(months, chMon); 66 | tm.Month = ((m - months) / 3 + 1); 67 | 68 | tm.Day = atoi(compDate + 4); 69 | tm.Year = atoi(compDate + 7) - 1970; 70 | tm.Hour = atoi(compTime); 71 | tm.Minute = atoi(compTime + 3); 72 | tm.Second = atoi(compTime + 6); 73 | time_t t = makeTime(tm); 74 | return t + COMPILE_TIME_DELAY; 75 | } 76 | -------------------------------------------------------------------------------- /examples/SunriseSunset/SunriseSunset.ino: -------------------------------------------------------------------------------- 1 | //====================================================================================================================== 2 | // SolarCalculator Library for Arduino example sketch: SunriseSunset.ino 3 | // 4 | // Calculate the times of sunrise, solar noon, and sunset for a given date and location. 5 | // 6 | // Tested with Arduino IDE 2.3.5 and Arduino Uno 7 | //====================================================================================================================== 8 | 9 | #include 10 | 11 | void setup() 12 | { 13 | Serial.begin(9600); 14 | 15 | // Date 16 | int year = 2000; 17 | int month = 1; 18 | int day = 1; 19 | 20 | // Location 21 | double latitude = 42.36; 22 | double longitude = -71.058; 23 | int utc_offset = -5; 24 | 25 | double transit, sunrise, sunset; 26 | 27 | // Calculate the times of sunrise, transit, and sunset, in hours (UTC) 28 | calcSunriseSunset(year, month, day, latitude, longitude, transit, sunrise, sunset); 29 | 30 | // Get the approximate times (minimum program size) (iterations = 0) 31 | //calcSunriseSunset(year, month, day, latitude, longitude, transit, sunrise, sunset, SUNRISESET_STD_ALTITUDE, 0); 32 | 33 | // Print results 34 | char str[6]; 35 | Serial.println(hoursToString(sunrise + utc_offset, str)); 36 | Serial.println(hoursToString(transit + utc_offset, str)); 37 | Serial.println(hoursToString(sunset + utc_offset, str)); 38 | } 39 | 40 | void loop() 41 | { 42 | } 43 | 44 | // Rounded HH:mm format 45 | char* hoursToString(double h, char* str) 46 | { 47 | int m = int(round(h * 60)); 48 | int hr = (m / 60) % 24; 49 | int mn = m % 60; 50 | 51 | str[0] = (hr / 10) % 10 + '0'; 52 | str[1] = (hr % 10) + '0'; 53 | str[2] = ':'; 54 | str[3] = (mn / 10) % 10 + '0'; 55 | str[4] = (mn % 10) + '0'; 56 | str[5] = '\0'; 57 | return str; 58 | } 59 | -------------------------------------------------------------------------------- /examples/SunriseSunsetAltitude/SunriseSunsetAltitude.ino: -------------------------------------------------------------------------------- 1 | //====================================================================================================================== 2 | // SolarCalculator Library for Arduino example sketch: SunriseSunsetAltitude.ino 3 | // 4 | // Calculate the rise and set times at a height above the level of the horizon. 5 | // 6 | // Tested with Arduino IDE 2.3.5 and Arduino Uno 7 | //====================================================================================================================== 8 | 9 | #include 10 | 11 | void setup() 12 | { 13 | Serial.begin(9600); 14 | 15 | // Date 16 | int year = 2000; 17 | int month = 1; 18 | int day = 1; 19 | 20 | // Location 21 | double latitude = 42.2136; 22 | double longitude = -71.1125; 23 | int utc_offset = -5; 24 | 25 | double transit, sunrise, sunset; 26 | 27 | // From the Explanatory Supplement to the Astronomical Almanac (1992), p. 484 28 | // Sunrise or sunset at a height above the level of the horizon occurs when the Sun's altitude is approximately: 29 | 30 | int height = 200; // in meters 31 | double sun_altitude = SUNRISESET_STD_ALTITUDE - 0.0353 * sqrt(height); 32 | 33 | // Calculate the times of sunrise, transit, and sunset, in hours (UTC) 34 | calcSunriseSunset(year, month, day, latitude, longitude, transit, sunrise, sunset, sun_altitude); 35 | 36 | // Print results 37 | char str[6]; 38 | Serial.println(hoursToString(sunrise + utc_offset, str)); 39 | Serial.println(hoursToString(transit + utc_offset, str)); 40 | Serial.println(hoursToString(sunset + utc_offset, str)); 41 | } 42 | 43 | void loop() 44 | { 45 | } 46 | 47 | // Rounded HH:mm format 48 | char* hoursToString(double h, char* str) 49 | { 50 | int m = int(round(h * 60)); 51 | int hr = (m / 60) % 24; 52 | int mn = m % 60; 53 | 54 | str[0] = (hr / 10) % 10 + '0'; 55 | str[1] = (hr % 10) + '0'; 56 | str[2] = ':'; 57 | str[3] = (mn / 10) % 10 + '0'; 58 | str[4] = (mn % 10) + '0'; 59 | str[5] = '\0'; 60 | return str; 61 | } 62 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | # Syntax coloring map for SolarCalculator 2 | 3 | # Datatypes (KEYWORD1) 4 | JulianDay KEYWORD1 5 | 6 | # Methods and functions (KEYWORD2) 7 | wrapTo360 KEYWORD2 8 | wrapTo180 KEYWORD2 9 | interpolateCoordinates KEYWORD2 10 | fractionalDay KEYWORD2 11 | calcJulianDay KEYWORD2 12 | calcJulianCent KEYWORD2 13 | calcGeomMeanLongSun KEYWORD2 14 | calcGeomMeanAnomalySun KEYWORD2 15 | calcSunEqOfCenter KEYWORD2 16 | calcSunRadVector KEYWORD2 17 | calcMeanObliquityOfEcliptic KEYWORD2 18 | calcSolarCoordinates KEYWORD2 19 | calcGrMeanSiderealTime KEYWORD2 20 | equationOfTimeSmart KEYWORD2 21 | calcDeltaT KEYWORD2 22 | equatorial2horizontal KEYWORD2 23 | calcRefraction KEYWORD2 24 | calcEquationOfTime KEYWORD2 25 | calcEquatorialCoordinates KEYWORD2 26 | calcHorizontalCoordinates KEYWORD2 27 | calcRiseSetTimes KEYWORD2 28 | calcSunriseSunset KEYWORD2 29 | calcCivilDawnDusk KEYWORD2 30 | calcNauticalDawnDusk KEYWORD2 31 | calcAstronomicalDawnDusk KEYWORD2 32 | 33 | # Instances (KEYWORD2) 34 | SolarCalculator KEYWORD2 35 | solarcalculator KEYWORD2 36 | 37 | # Constants (LITERAL1) 38 | SUNRISESET_STD_ALTITUDE LITERAL1 39 | CIVIL_DAWNDUSK_STD_ALTITUDE LITERAL1 40 | NAUTICAL_DAWNDUSK_STD_ALTITUDE LITERAL1 41 | ASTRONOMICAL_DAWNDUSK_STD_ALTITUDE LITERAL1 42 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=SolarCalculator 2 | version=2.0.2 3 | author=jpb10 4 | maintainer=jpb10 5 | sentence=A library inspired by the NOAA Solar Calculator. 6 | paragraph=It provides functions to calculate the times of sunrise, sunset, solar noon, twilight (dawn and dusk), solar coordinates, equation of time, etc. 7 | category=Other 8 | url=https://github.com/jpb10/SolarCalculator 9 | architectures=* 10 | includes=SolarCalculator.h 11 | -------------------------------------------------------------------------------- /src/SolarCalculator.cpp: -------------------------------------------------------------------------------- 1 | //====================================================================================================================== 2 | // SolarCalculator Library for Arduino 3 | // 4 | // This library provides functions to calculate the times of sunrise, sunset, solar noon, twilight (dawn and dusk), 5 | // Sun's apparent position in the sky, equation of time, etc. 6 | // 7 | // Most formulae are taken from Astronomical Algorithms by Jean Meeus and optimized for Arduino. 8 | //====================================================================================================================== 9 | 10 | #ifdef ARDUINO 11 | #include 12 | #else 13 | #include 14 | #endif 15 | 16 | #include "SolarCalculator.h" 17 | 18 | //namespace solarcalculator { 19 | 20 | JulianDay::JulianDay(unsigned long utc) 21 | { 22 | JD = static_cast(utc / 86400) + 2440587.5; 23 | m = (utc % 86400) / 86400.0; 24 | } 25 | 26 | // Valid from 1901 to 2099, Van Flandern & Pulkkinen (1979) 27 | JulianDay::JulianDay(int Y, int M, int D, int hour, int minute, int second) 28 | { 29 | JD = 367.0 * Y - static_cast(7 * (Y + (M + 9) / 12) / 4) + static_cast(275 * M / 9) + D + 1721013.5; 30 | m = (hour + minute / 60.0 + second / 3600.0) / 24.0; 31 | } 32 | 33 | //====================================================================================================================== 34 | // Intermediate calculations 35 | // 36 | // Time T is measured in Julian centuries (36525 ephemeris days from the epoch J2000.0) 37 | //====================================================================================================================== 38 | 39 | #ifndef ARDUINO 40 | double radians(double deg) 41 | { 42 | return deg * M_PI / 180; 43 | } 44 | 45 | double degrees(double rad) 46 | { 47 | return rad * 180 / M_PI; 48 | } 49 | #endif 50 | 51 | double wrapTo360(double angle) 52 | { 53 | angle = fmod(angle, 360); 54 | if (angle < 0) angle += 360; 55 | return angle; // [0, 360) 56 | } 57 | 58 | double wrapTo180(double angle) 59 | { 60 | angle = wrapTo360(angle + 180); 61 | return angle - 180; // [-180, 180) 62 | } 63 | 64 | double calcJulianCent(JulianDay jd) 65 | { 66 | return (jd.JD - 2451545 + jd.m) / 36525; 67 | } 68 | 69 | double calcGeomMeanLongSun(double T) 70 | { 71 | return wrapTo360(280.46646 + T * 36000.76983); // in degrees 72 | } 73 | 74 | double calcGeomMeanAnomalySun(double T) 75 | { 76 | return wrapTo360(357.52911 + T * 35999.05029); // in degrees 77 | } 78 | 79 | double calcSunEqOfCenter(double T) 80 | { 81 | double M = calcGeomMeanAnomalySun(T); 82 | return sin(radians(M)) * (1.914602 - 0.004817 * T) + sin(2 * radians(M)) * 0.019993; // in degrees 83 | } 84 | 85 | double calcSunRadVector(double T) 86 | { 87 | double M = calcGeomMeanAnomalySun(T); 88 | return 1.00014 - 0.01671 * cos(radians(M)) - 0.00014 * cos(2 * radians(M)); // in AUs 89 | } 90 | 91 | double calcMeanObliquityOfEcliptic(double T) 92 | { 93 | return 23.4392911 - T * 0.0130042; // in degrees 94 | } 95 | 96 | // Mean geocentric equatorial coordinates, accurate to ~0.01 degree 97 | void calcSolarCoordinates(double T, double& ra, double& dec) 98 | { 99 | double L0 = calcGeomMeanLongSun(T); 100 | double C = calcSunEqOfCenter(T); 101 | double L = L0 + C - 0.00569; // corrected for aberration 102 | 103 | double eps = calcMeanObliquityOfEcliptic(T); 104 | ra = degrees(atan2(cos(radians(eps)) * sin(radians(L)), cos(radians(L)))); // [-180, 180) 105 | dec = degrees(asin(sin(radians(eps)) * sin(radians(L)))); 106 | } 107 | 108 | double calcGrMeanSiderealTime(JulianDay jd) 109 | { 110 | double GMST0 = wrapTo360(100.46061837 + 0.98564736629 * (jd.JD - 2451545)); 111 | return wrapTo360(GMST0 + 360.985647 * jd.m); // in degrees 112 | } 113 | 114 | void equatorial2horizontal(double H, double dec, double lat, double& az, double& el) 115 | { 116 | double xhor = cos(radians(H)) * cos(radians(dec)) * sin(radians(lat)) - sin(radians(dec)) * cos(radians(lat)); 117 | double yhor = sin(radians(H)) * cos(radians(dec)); 118 | double zhor = cos(radians(H)) * cos(radians(dec)) * cos(radians(lat)) + sin(radians(dec)) * sin(radians(lat)); 119 | 120 | az = degrees(atan2(yhor, xhor)); 121 | el = degrees(atan2(zhor, sqrt(xhor * xhor + yhor * yhor))); 122 | } 123 | 124 | // Hour angle at sunrise or sunset, returns NaN if circumpolar 125 | double calcHourAngleRiseSet(double dec, double lat, double h0) 126 | { 127 | return degrees(acos((sin(radians(h0)) - sin(radians(lat)) * sin(radians(dec))) / 128 | (cos(radians(lat)) * cos(radians(dec))))); 129 | } 130 | 131 | // Approximate atmospheric refraction correction, in degrees 132 | double calcRefraction(double el) 133 | { 134 | if (el < -0.575) 135 | return -20.774 / tan(radians(el)) / 3600; // Zimmerman (1981) 136 | else 137 | return 1.02 / tan(radians(el + 10.3 / (el + 5.11))) / 60; // Sæmundsson (1986) 138 | } 139 | 140 | //====================================================================================================================== 141 | // Solar calculator 142 | // 143 | // All calculations assume time inputs in Coordinated Universal Time (UTC) 144 | //====================================================================================================================== 145 | 146 | // Equation of time, in minutes of time 147 | void calcEquationOfTime(JulianDay jd, double& E) 148 | { 149 | double T = calcJulianCent(jd); 150 | double L0 = calcGeomMeanLongSun(T); 151 | 152 | double ra, dec; 153 | calcSolarCoordinates(T, ra, dec); 154 | 155 | E = 4 * wrapTo180(L0 - 0.00569 - ra); 156 | } 157 | 158 | // Sun's geocentric (as seen from the center of the Earth) equatorial coordinates, in degrees and AUs 159 | void calcEquatorialCoordinates(JulianDay jd, double& rt_ascension, double& declination, double& radius_vector) 160 | { 161 | double T = calcJulianCent(jd); 162 | calcSolarCoordinates(T, rt_ascension, declination); 163 | 164 | rt_ascension = wrapTo360(rt_ascension); 165 | radius_vector = calcSunRadVector(T); 166 | } 167 | 168 | // Sun's topocentric (as seen from the observer's place on the Earth's surface) horizontal coordinates, in degrees 169 | void calcHorizontalCoordinates(JulianDay jd, double latitude, double longitude, double& azimuth, double& elevation) 170 | { 171 | double T = calcJulianCent(jd); 172 | double GMST = calcGrMeanSiderealTime(jd); 173 | 174 | double ra, dec; 175 | calcSolarCoordinates(T, ra, dec); 176 | 177 | double H = GMST + longitude - ra; 178 | equatorial2horizontal(H, dec, latitude, azimuth, elevation); 179 | 180 | azimuth += 180; // measured from the North 181 | elevation += calcRefraction(elevation); 182 | } 183 | 184 | // Find the times of sunrise, transit, and sunset, in hours 185 | void calcSunriseSunset(JulianDay jd, double latitude, double longitude, 186 | double& transit, double& sunrise, double& sunset, double altitude, int iterations) 187 | { 188 | double m[3]; 189 | m[0] = 0.5 - longitude / 360; 190 | 191 | for (int i = 0; i <= iterations; ++i) 192 | for (int event = 0; event < 3; ++event) 193 | { 194 | jd.m = m[event]; 195 | double T = calcJulianCent(jd); 196 | double GMST = calcGrMeanSiderealTime(jd); 197 | 198 | double ra, dec; 199 | calcSolarCoordinates(T, ra, dec); 200 | 201 | double m0 = jd.m + wrapTo180(ra - longitude - GMST) / 360; 202 | double d0 = calcHourAngleRiseSet(dec, latitude, altitude) / 360; 203 | 204 | if (event == 0) m[0] = m0; 205 | if (event == 1 || i == 0) m[1] = m0 - d0; 206 | if (event == 2 || i == 0) m[2] = m0 + d0; 207 | if (i == 0) break; 208 | } 209 | 210 | transit = m[0] * 24; 211 | sunrise = m[1] * 24; 212 | sunset = m[2] * 24; 213 | } 214 | 215 | //====================================================================================================================== 216 | // Wrapper functions 217 | // 218 | // All calculations assume time inputs in Coordinated Universal Time (UTC) 219 | //====================================================================================================================== 220 | 221 | void calcEquationOfTime(unsigned long utc, double& E) 222 | { 223 | JulianDay jd(utc); 224 | calcEquationOfTime(jd, E); 225 | } 226 | 227 | void calcEquationOfTime(int year, int month, int day, int hour, int minute, int second, double& E) 228 | { 229 | JulianDay jd(year, month, day, hour, minute, second); 230 | calcEquationOfTime(jd, E); 231 | } 232 | 233 | void calcEquatorialCoordinates(unsigned long utc, double& rt_ascension, double& declination, double& radius_vector) 234 | { 235 | JulianDay jd(utc); 236 | calcEquatorialCoordinates(jd, rt_ascension, declination, radius_vector); 237 | } 238 | 239 | void calcEquatorialCoordinates(int year, int month, int day, int hour, int minute, int second, 240 | double& rt_ascension, double& declination, double& radius_vector) 241 | { 242 | JulianDay jd(year, month, day, hour, minute, second); 243 | calcEquatorialCoordinates(jd, rt_ascension, declination, radius_vector); 244 | } 245 | 246 | void calcHorizontalCoordinates(unsigned long utc, double latitude, double longitude, 247 | double& azimuth, double& elevation) 248 | { 249 | JulianDay jd(utc); 250 | calcHorizontalCoordinates(jd, latitude, longitude, azimuth, elevation); 251 | } 252 | 253 | void calcHorizontalCoordinates(int year, int month, int day, int hour, int minute, int second, 254 | double latitude, double longitude, double& azimuth, double& elevation) 255 | { 256 | JulianDay jd(year, month, day, hour, minute, second); 257 | calcHorizontalCoordinates(jd, latitude, longitude, azimuth, elevation); 258 | } 259 | 260 | void calcSunriseSunset(unsigned long utc, double latitude, double longitude, 261 | double& transit, double& sunrise, double& sunset, double altitude, int iterations) 262 | { 263 | JulianDay jd(utc); 264 | calcSunriseSunset(jd, latitude, longitude, transit, sunrise, sunset, altitude, iterations); 265 | } 266 | 267 | void calcSunriseSunset(int year, int month, int day, double latitude, double longitude, 268 | double& transit, double& sunrise, double& sunset, double altitude, int iterations) 269 | { 270 | JulianDay jd(year, month, day); 271 | calcSunriseSunset(jd, latitude, longitude, transit, sunrise, sunset, altitude, iterations); 272 | } 273 | 274 | void calcCivilDawnDusk(unsigned long utc, double latitude, double longitude, 275 | double& transit, double& dawn, double& dusk) 276 | { 277 | calcSunriseSunset(utc, latitude, longitude, transit, dawn, dusk, CIVIL_DAWNDUSK_STD_ALTITUDE); 278 | } 279 | 280 | void calcCivilDawnDusk(int year, int month, int day, double latitude, double longitude, 281 | double& transit, double& dawn, double& dusk) 282 | { 283 | calcSunriseSunset(year, month, day, latitude, longitude, transit, dawn, dusk, CIVIL_DAWNDUSK_STD_ALTITUDE); 284 | } 285 | 286 | void calcNauticalDawnDusk(unsigned long utc, double latitude, double longitude, 287 | double& transit, double& dawn, double& dusk) 288 | { 289 | calcSunriseSunset(utc, latitude, longitude, transit, dawn, dusk, NAUTICAL_DAWNDUSK_STD_ALTITUDE); 290 | } 291 | 292 | void calcNauticalDawnDusk(int year, int month, int day, double latitude, double longitude, 293 | double& transit, double& dawn, double& dusk) 294 | { 295 | calcSunriseSunset(year, month, day, latitude, longitude, transit, dawn, dusk, NAUTICAL_DAWNDUSK_STD_ALTITUDE); 296 | } 297 | 298 | void calcAstronomicalDawnDusk(unsigned long utc, double latitude, double longitude, 299 | double& transit, double& dawn, double& dusk) 300 | { 301 | calcSunriseSunset(utc, latitude, longitude, transit, dawn, dusk, ASTRONOMICAL_DAWNDUSK_STD_ALTITUDE); 302 | } 303 | 304 | void calcAstronomicalDawnDusk(int year, int month, int day, double latitude, double longitude, 305 | double& transit, double& dawn, double& dusk) 306 | { 307 | calcSunriseSunset(year, month, day, latitude, longitude, transit, dawn, dusk, ASTRONOMICAL_DAWNDUSK_STD_ALTITUDE); 308 | } 309 | 310 | //} // namespace 311 | -------------------------------------------------------------------------------- /src/SolarCalculator.h: -------------------------------------------------------------------------------- 1 | //====================================================================================================================== 2 | // SolarCalculator Library for Arduino 3 | // 4 | // This library provides functions to calculate the times of sunrise, sunset, solar noon, twilight (dawn and dusk), 5 | // Sun's apparent position in the sky, equation of time, etc. 6 | // 7 | // Most formulae are taken from Astronomical Algorithms by Jean Meeus and optimized for Arduino. 8 | //====================================================================================================================== 9 | 10 | #ifndef SOLARCALCULATOR_H 11 | #define SOLARCALCULATOR_H 12 | 13 | //namespace solarcalculator { 14 | 15 | constexpr double SUNRISESET_STD_ALTITUDE = -0.8333; 16 | constexpr double CIVIL_DAWNDUSK_STD_ALTITUDE = -6.0; 17 | constexpr double NAUTICAL_DAWNDUSK_STD_ALTITUDE = -12.0; 18 | constexpr double ASTRONOMICAL_DAWNDUSK_STD_ALTITUDE = -18.0; 19 | 20 | struct JulianDay 21 | { 22 | double JD; // Julian day at 0h UT (JD ending in .5) 23 | double m; // Fractional day, 0h to 24h (decimal number between 0 and 1) 24 | 25 | explicit JulianDay(unsigned long utc); // Unix time, i.e. seconds since 1970 January 1, at 0h UT 26 | JulianDay(int year, int month, int day, int hour = 0, int minute = 0, int second = 0); // Calendar date (UT) 27 | }; 28 | 29 | //====================================================================================================================== 30 | // Intermediate calculations 31 | // 32 | // Time T is measured in Julian centuries (36525 ephemeris days from the epoch J2000.0) 33 | //====================================================================================================================== 34 | 35 | // Utilities 36 | double wrapTo360(double angle); 37 | double wrapTo180(double angle); 38 | 39 | // Julian centuries 40 | double calcJulianCent(JulianDay jd); 41 | 42 | // Solar coordinates 43 | double calcGeomMeanLongSun(double T); 44 | double calcGeomMeanAnomalySun(double T); 45 | double calcSunEqOfCenter(double T); 46 | double calcSunRadVector(double T); 47 | double calcMeanObliquityOfEcliptic(double T); 48 | void calcSolarCoordinates(double T, double& ra, double& dec); 49 | 50 | // Sidereal time at Greenwich 51 | double calcGrMeanSiderealTime(JulianDay jd); 52 | 53 | // Sun's position in the sky 54 | void equatorial2horizontal(double H, double dec, double lat, double& az, double& el); 55 | double calcHourAngleRiseSet(double dec, double lat, double h0); 56 | double calcRefraction(double el); 57 | 58 | //====================================================================================================================== 59 | // Solar calculator 60 | // 61 | // All calculations assume time inputs in Coordinated Universal Time (UTC) 62 | //====================================================================================================================== 63 | 64 | // Equation of time, in minutes of time 65 | void calcEquationOfTime(JulianDay jd, double& E); 66 | 67 | // Sun's geocentric (as seen from the center of the Earth) equatorial coordinates, in degrees and AUs 68 | void calcEquatorialCoordinates(JulianDay jd, double& rt_ascension, double& declination, double& radius_vector); 69 | 70 | // Sun's topocentric (as seen from the observer's place on the Earth's surface) horizontal coordinates, in degrees 71 | void calcHorizontalCoordinates(JulianDay jd, double latitude, double longitude, double& azimuth, double& elevation); 72 | 73 | // Find the times of sunrise, transit, and sunset, in hours 74 | void calcSunriseSunset(JulianDay jd, double latitude, double longitude, 75 | double& transit, double& sunrise, double& sunset, 76 | double altitude = SUNRISESET_STD_ALTITUDE, int iterations = 1); 77 | 78 | //====================================================================================================================== 79 | // Wrapper functions 80 | // 81 | // All calculations assume time inputs in Coordinated Universal Time (UTC) 82 | //====================================================================================================================== 83 | 84 | void calcEquationOfTime(unsigned long utc, double& E); 85 | void calcEquationOfTime(int year, int month, int day, int hour, int minute, int second, double& E); 86 | 87 | void calcEquatorialCoordinates(unsigned long utc, double& rt_ascension, double& declination, double& radius_vector); 88 | void calcEquatorialCoordinates(int year, int month, int day, int hour, int minute, int second, 89 | double& rt_ascension, double& declination, double& radius_vector); 90 | 91 | void calcHorizontalCoordinates(unsigned long utc, double latitude, double longitude, 92 | double& azimuth, double& elevation); 93 | void calcHorizontalCoordinates(int year, int month, int day, int hour, int minute, int second, 94 | double latitude, double longitude, double& azimuth, double& elevation); 95 | 96 | void calcSunriseSunset(unsigned long utc, double latitude, double longitude, 97 | double& transit, double& sunrise, double& sunset, 98 | double altitude = SUNRISESET_STD_ALTITUDE, int iterations = 1); 99 | void calcSunriseSunset(int year, int month, int day, double latitude, double longitude, 100 | double& transit, double& sunrise, double& sunset, 101 | double altitude = SUNRISESET_STD_ALTITUDE, int iterations = 1); 102 | 103 | void calcCivilDawnDusk(unsigned long utc, double latitude, double longitude, 104 | double& transit, double& dawn, double& dusk); 105 | void calcCivilDawnDusk(int year, int month, int day, double latitude, double longitude, 106 | double& transit, double& dawn, double& dusk); 107 | 108 | void calcNauticalDawnDusk(unsigned long utc, double latitude, double longitude, 109 | double& transit, double& dawn, double& dusk); 110 | void calcNauticalDawnDusk(int year, int month, int day, double latitude, double longitude, 111 | double& transit, double& dawn, double& dusk); 112 | 113 | void calcAstronomicalDawnDusk(unsigned long utc, double latitude, double longitude, 114 | double& transit, double& dawn, double& dusk); 115 | void calcAstronomicalDawnDusk(int year, int month, int day, double latitude, double longitude, 116 | double& transit, double& dawn, double& dusk); 117 | 118 | //} // namespace 119 | #endif //SOLARCALCULATOR_H 120 | --------------------------------------------------------------------------------