Movement: Astronomy and Orrery watch faces (#55)

This commit is contained in:
joeycastillo 2022-03-04 14:52:49 -06:00 committed by GitHub
parent ea988208e1
commit ccdf08da87
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 4559 additions and 3 deletions

View file

@ -0,0 +1,9 @@
Public domain
THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,9 @@
# Astrolib
This is a relatively straightforward C port of Greg Miller's [JavaScript astro library](https://github.com/gmiller123456/astrogreg), done by Joey Castillo, for the Sensor Watch Astronomy watch face.
He released his work into the public domain and so I release this into the public domain as well.
I have tested the output of this library against [NASA's Horizons system](https://ssd.jpl.nasa.gov/horizons/app.html#/) and found the results to match (within reason, as we're using a truncated version of VSOP87). Moon calculations still seem a bit iffy, but it doesn't surprise me seeing as there are three calculations involved and the error could stack up.
THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -0,0 +1,555 @@
/*
* Partial C port of Greg Miller's public domain astro library (gmiller@gregmiller.net) 2019
* https://github.com/gmiller123456/astrogreg
*
* Ported by Joey Castillo for Sensor Watch
* https://github.com/joeycastillo/Sensor-Watch/
*
* Public Domain
*
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <math.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include "astrolib.h"
#include "vsop87a_milli.h"
double astro_convert_utc_to_tt(double jd) ;
double astro_get_GMST(double ut1);
astro_cartesian_coordinates_t astro_subtract_cartesian(astro_cartesian_coordinates_t a, astro_cartesian_coordinates_t b);
astro_cartesian_coordinates_t astro_rotate_from_vsop_to_J2000(astro_cartesian_coordinates_t c);
astro_matrix_t astro_get_x_rotation_matrix(double r);
astro_matrix_t astro_get_y_rotation_matrix(double r);
astro_matrix_t astro_get_z_rotation_matrix(double r);
astro_matrix_t astro_transpose_matrix(astro_matrix_t m);
astro_matrix_t astro_dot_product(astro_matrix_t a, astro_matrix_t b);
astro_matrix_t astro_get_precession_matrix(double jd);
astro_cartesian_coordinates_t astro_matrix_multiply(astro_cartesian_coordinates_t v, astro_matrix_t m);
astro_cartesian_coordinates_t astro_convert_geodedic_latlon_to_ITRF_XYZ(double lat, double lon, double height);
astro_cartesian_coordinates_t astro_convert_ITRF_to_GCRS(astro_cartesian_coordinates_t r, double ut1);
astro_cartesian_coordinates_t astro_convert_coordinates_from_meters_to_AU(astro_cartesian_coordinates_t c);
astro_cartesian_coordinates_t astro_get_observer_geocentric_coords(double jd, double lat, double lon);
astro_cartesian_coordinates_t astro_get_body_coordinates(astro_body_t bodyNum, double et);
astro_cartesian_coordinates_t astro_get_body_coordinates_light_time_adjusted(astro_body_t body, astro_cartesian_coordinates_t origin, double t);
astro_equatorial_coordinates_t astro_convert_cartesian_to_polar(astro_cartesian_coordinates_t xyz);
//Special "Math.floor()" function used by convertDateToJulianDate()
static double _astro_special_floor(double d) {
if(d > 0) {
return floor(d);
}
return floor(d) - 1;
}
double astro_convert_date_to_julian_date(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, uint8_t second) {
if (month < 3){
year = year - 1;
month = month + 12;
}
double b = 0;
if (!(year < 1582 || (year == 1582 && (month < 10 || (month == 10 && day < 5))))) {
double a = _astro_special_floor(year / 100.0);
b = 2 - a + _astro_special_floor(a / 4.0);
}
double jd = _astro_special_floor(365.25 * (year + 4716)) + _astro_special_floor(30.6001 * (month + 1)) + day + b - 1524.5;
jd += hour / 24.0;
jd += minute / 24.0 / 60.0;
jd += second / 24.0 / 60.0 / 60.0;
return jd;
}
//Return all values in radians.
//The positions are adjusted for the parallax of the Earth, and the offset of the observer from the Earth's center
//All input and output angles are in radians!
astro_equatorial_coordinates_t astro_get_ra_dec(double jd, astro_body_t body, double lat, double lon, bool calculate_precession) {
double jdTT = astro_convert_utc_to_tt(jd);
double t = astro_convert_jd_to_julian_millenia_since_j2000(jdTT);
// Get current position of Earth and the target body
astro_cartesian_coordinates_t earth_coords = astro_get_body_coordinates(ASTRO_BODY_EARTH, t);
astro_cartesian_coordinates_t body_coords = astro_get_body_coordinates_light_time_adjusted(body, earth_coords, t);
// Convert to Geocentric coordinate
body_coords = astro_subtract_cartesian(body_coords, earth_coords);
//Rotate ecliptic coordinates to J2000 coordinates
body_coords = astro_rotate_from_vsop_to_J2000(body_coords);
astro_matrix_t precession;
// TODO: rotate body for precession, nutation and bias
if(calculate_precession) {
precession = astro_get_precession_matrix(jdTT);
body_coords = astro_matrix_multiply(body_coords, precession);
}
//Convert to topocentric
astro_cartesian_coordinates_t observerXYZ = astro_get_observer_geocentric_coords(jdTT, lat, lon);
if(calculate_precession) {
//TODO: rotate observerXYZ for precession, nutation and bias
astro_matrix_t precessionInv = astro_transpose_matrix(precession);
observerXYZ = astro_matrix_multiply(observerXYZ, precessionInv);
}
body_coords = astro_subtract_cartesian(body_coords, observerXYZ);
//Convert to topocentric RA DEC by converting from cartesian coordinates to polar coordinates
astro_equatorial_coordinates_t retval = astro_convert_cartesian_to_polar(body_coords);
retval.declination = M_PI/2.0 - retval.declination; //Dec. Offset to make 0 the equator, and the poles +/-90 deg
if(retval.right_ascension < 0) retval.right_ascension += 2*M_PI; //Ensure RA is positive
return retval;
}
//Converts a Julian Date in UTC to Terrestrial Time (TT)
double astro_convert_utc_to_tt(double jd) {
//Leap seconds are hard coded, should be updated from the IERS website for other times
//TAI = UTC + leap seconds (e.g. 32)
//TT=TAI + 32.184
//return jd + (32.0 + 32.184) / 24.0 / 60.0 / 60.0;
return jd + (37.0 + 32.184) / 24.0 / 60.0 / 60.0;
/*
https://data.iana.org/time-zones/tzdb-2018a/leap-seconds.list
2272060800 10 # 1 Jan 1972
2287785600 11 # 1 Jul 1972
2303683200 12 # 1 Jan 1973
2335219200 13 # 1 Jan 1974
2366755200 14 # 1 Jan 1975
2398291200 15 # 1 Jan 1976
2429913600 16 # 1 Jan 1977
2461449600 17 # 1 Jan 1978
2492985600 18 # 1 Jan 1979
2524521600 19 # 1 Jan 1980
2571782400 20 # 1 Jul 1981
2603318400 21 # 1 Jul 1982
2634854400 22 # 1 Jul 1983
2698012800 23 # 1 Jul 1985
2776982400 24 # 1 Jan 1988
2840140800 25 # 1 Jan 1990
2871676800 26 # 1 Jan 1991
2918937600 27 # 1 Jul 1992
2950473600 28 # 1 Jul 1993
2982009600 29 # 1 Jul 1994
3029443200 30 # 1 Jan 1996
3076704000 31 # 1 Jul 1997
3124137600 32 # 1 Jan 1999
3345062400 33 # 1 Jan 2006
3439756800 34 # 1 Jan 2009
3550089600 35 # 1 Jul 2012
3644697600 36 # 1 Jul 2015
3692217600 37 # 1 Jan 2017
*/
}
double astro_convert_jd_to_julian_millenia_since_j2000(double jd) {
return (jd - 2451545.0) / 365250.0;
}
astro_cartesian_coordinates_t astro_subtract_cartesian(astro_cartesian_coordinates_t a, astro_cartesian_coordinates_t b) {
astro_cartesian_coordinates_t retval;
retval.x = a.x - b.x;
retval.y = a.y - b.y;
retval.z = a.z - b.z;
return retval;
}
// Performs the rotation from ecliptic coordinates to J2000 coordinates for the given vector x
astro_cartesian_coordinates_t astro_rotate_from_vsop_to_J2000(astro_cartesian_coordinates_t c) {
/* From VSOP87.doc
X +1.000000000000 +0.000000440360 -0.000000190919 X
Y = -0.000000479966 +0.917482137087 -0.397776982902 Y
Z FK5 0.000000000000 +0.397776982902 +0.917482137087 Z VSOP87A
*/
astro_cartesian_coordinates_t t;
t.x = c.x + c.y * 0.000000440360 + c.z * -0.000000190919;
t.y = c.x * -0.000000479966 + c.y * 0.917482137087 + c.z * -0.397776982902;
t.z = c.y * 0.397776982902 + c.z * 0.917482137087;
return t;
}
double astro_get_GMST(double ut1) {
double D = ut1 - 2451545.0;
double T = D/36525.0;
double gmst = fmod((280.46061837 + 360.98564736629*D + 0.000387933*T*T - T*T*T/38710000.0), 360.0);
if(gmst<0) {
gmst+=360;
}
return gmst/15;
}
static astro_matrix_t _astro_get_empty_matrix() {
astro_matrix_t t;
for(uint8_t i = 0; i < 3 ; i++) {
for(uint8_t j = 0 ; j < 3 ; j++) {
t.elements[i][j] = 0;
}
}
return t;
}
//Gets a rotation matrix about the x axis. Angle R is in radians
astro_matrix_t astro_get_x_rotation_matrix(double r) {
astro_matrix_t t = _astro_get_empty_matrix();
t.elements[0][0]=1;
t.elements[0][1]=0;
t.elements[0][2]=0;
t.elements[1][0]=0;
t.elements[1][1]=cos(r);
t.elements[1][2]=sin(r);
t.elements[2][0]=0;
t.elements[2][1]=-sin(r);
t.elements[2][2]=cos(r);
return t;
}
//Gets a rotation matrix about the y axis. Angle R is in radians
astro_matrix_t astro_get_y_rotation_matrix(double r) {
astro_matrix_t t = _astro_get_empty_matrix();
t.elements[0][0]=cos(r);
t.elements[0][1]=0;
t.elements[0][2]=-sin(r);
t.elements[1][0]=0;
t.elements[1][1]=1;
t.elements[1][2]=0;
t.elements[2][0]=sin(r);
t.elements[2][1]=0;
t.elements[2][2]=cos(r);
return t;
}
//Gets a rotation matrix about the z axis. Angle R is in radians
astro_matrix_t astro_get_z_rotation_matrix(double r) {
astro_matrix_t t = _astro_get_empty_matrix();
t.elements[0][0]=cos(r);
t.elements[0][1]=sin(r);
t.elements[0][2]=0;
t.elements[1][0]=-sin(r);
t.elements[1][1]=cos(r);
t.elements[1][2]=0;
t.elements[2][0]=0;
t.elements[2][1]=0;
t.elements[2][2]=1;
return t;
}
void astro_print_matrix(char * title, astro_matrix_t matrix);
void astro_print_matrix(char * title, astro_matrix_t matrix) {
printf("%s\n", title);
for(uint8_t i = 0; i < 3 ; i++) {
printf("\t");
for(uint8_t j = 0 ; j < 3 ; j++) {
printf("%12f", matrix.elements[i][j]);
}
printf("\n");
}
printf("\n");
}
astro_matrix_t astro_dot_product(astro_matrix_t a, astro_matrix_t b) {
astro_matrix_t retval;
for(uint8_t i = 0; i < 3 ; i++) {
for(uint8_t j = 0 ; j < 3 ; j++) {
double temp = 0;
for(uint8_t k = 0; k < 3 ; k++) {
temp += a.elements[i][k] * b.elements[k][j];
}
retval.elements[i][j]=temp;
}
}
return retval;
}
astro_matrix_t astro_transpose_matrix(astro_matrix_t m) {
astro_matrix_t retval;
for(uint8_t i = 0; i < 3 ; i++) {
for(uint8_t j = 0 ; j < 3 ; j++) {
retval.elements[i][j] = m.elements[j][i];
}
}
return retval;
}
astro_matrix_t astro_get_precession_matrix(double jd) {
//2006 IAU Precession. Implemented from IERS Technical Note No 36 ch5.
//https://www.iers.org/SharedDocs/Publikationen/EN/IERS/Publications/tn/TechnNote36/tn36_043.pdf?__blob=publicationFile&v=1
double t = (jd - 2451545.0) / 36525.0; //5.2
const double Arcsec2Radians = M_PI/180.0/60.0/60.0; //Converts arc seconds used in equations below to radians
double e0 = 84381.406 * Arcsec2Radians; //5.6.4
double omegaA = e0 + ((-0.025754 + (0.0512623 + (-0.00772503 + (-0.000000467 + 0.0000003337*t) * t) * t) * t) * t) * Arcsec2Radians; //5.39
double psiA = ((5038.481507 + (-1.0790069 + (-0.00114045 + (0.000132851 - 0.0000000951*t) * t) * t) * t) * t) * Arcsec2Radians; //5.39
double chiA = ((10.556403 + (-2.3814292 + (-0.00121197 + (0.000170663 - 0.0000000560*t) * t) * t) * t) * t) * Arcsec2Radians; //5.40
//Rotation matrix from 5.4.5
//(R1(e0) · R3(psiA) · R1(omegaA) · R3(chiA))
//Above eq rotates from "of date" to J2000, so we reverse the signs to go from J2000 to "of date"
astro_matrix_t m1 = astro_get_x_rotation_matrix(e0);
astro_matrix_t m2 = astro_get_z_rotation_matrix(-psiA);
astro_matrix_t m3 = astro_get_x_rotation_matrix(-omegaA);
astro_matrix_t m4 = astro_get_z_rotation_matrix(chiA);
astro_matrix_t m5 = astro_dot_product(m4, m3);
astro_matrix_t m6 = astro_dot_product(m5, m2);
astro_matrix_t precessionMatrix = astro_dot_product(m6, m1);
return precessionMatrix;
}
astro_cartesian_coordinates_t astro_matrix_multiply(astro_cartesian_coordinates_t v, astro_matrix_t m) {
astro_cartesian_coordinates_t t;
t.x = v.x*m.elements[0][0] + v.y*m.elements[0][1] + v.z*m.elements[0][2];
t.y = v.x*m.elements[1][0] + v.y*m.elements[1][1] + v.z*m.elements[1][2];
t.z = v.x*m.elements[2][0] + v.y*m.elements[2][1] + v.z*m.elements[2][2];
return t;
}
//Converts cartesian XYZ coordinates to polar (e.g. J2000 xyz to Right Accention and Declication)
astro_equatorial_coordinates_t astro_convert_cartesian_to_polar(astro_cartesian_coordinates_t xyz) {
astro_equatorial_coordinates_t t;
t.distance = sqrt(xyz.x * xyz.x + xyz.y * xyz.y + xyz.z * xyz.z);
t.declination = acos(xyz.z / t.distance);
t.right_ascension = atan2(xyz.y, xyz.x);
if(t.declination < 0) t.declination += 2 * M_PI;
if(t.right_ascension < 0) t.right_ascension += 2 * M_PI;
return t;
}
//Convert Geodedic Lat Lon to geocentric XYZ position vector
//All angles are input as radians
astro_cartesian_coordinates_t astro_convert_geodedic_latlon_to_ITRF_XYZ(double lat, double lon, double height) {
//Algorithm from Explanatory Supplement to the Astronomical Almanac 3rd ed. P294
const double a = 6378136.6;
const double f = 1 / 298.25642;
const double C = sqrt(((cos(lat)*cos(lat)) + (1.0-f)*(1.0-f) * (sin(lat)*sin(lat))));
const double S = (1-f)*(1-f)*C;
double h = height;
astro_cartesian_coordinates_t r;
r.x = (a*C+h) * cos(lat) * cos(lon);
r.y = (a*C+h) * cos(lat) * sin(lon);
r.z = (a*S+h) * sin(lat);
return r;
}
//Convert position vector to celestial "of date" system.
//g(t)=R3(-GAST) r
//(Remember to use UT1 for GAST, not ET)
//All angles are input and output as radians
astro_cartesian_coordinates_t astro_convert_ITRF_to_GCRS(astro_cartesian_coordinates_t r, double ut1) {
//This is a simple rotation matrix implemenation about the Z axis, rotation angle is -GMST
double GMST = astro_get_GMST(ut1);
GMST =- GMST * 15.0 * M_PI / 180.0;
astro_matrix_t m = astro_get_z_rotation_matrix(GMST);
astro_cartesian_coordinates_t t = astro_matrix_multiply(r, m);
return t;
}
astro_cartesian_coordinates_t astro_convert_coordinates_from_meters_to_AU(astro_cartesian_coordinates_t c) {
astro_cartesian_coordinates_t t;
t.x = c.x / 1.49597870691E+11;
t.y = c.y / 1.49597870691E+11;
t.z = c.z / 1.49597870691E+11;
return t;
}
astro_cartesian_coordinates_t astro_get_observer_geocentric_coords(double jd, double lat, double lon) {
astro_cartesian_coordinates_t r = astro_convert_geodedic_latlon_to_ITRF_XYZ(lat, lon,0);
r = astro_convert_ITRF_to_GCRS(r, jd);
r = astro_convert_coordinates_from_meters_to_AU(r);
return r;
}
//Returns a body's cartesian coordinates centered on the Sun.
//Requires vsop87a_milli_js, if you wish to use a different version of VSOP87, replace the class name vsop87a_milli below
astro_cartesian_coordinates_t astro_get_body_coordinates(astro_body_t body, double et) {
astro_cartesian_coordinates_t retval = {0};
double coords[3];
switch(body) {
case ASTRO_BODY_SUN:
return retval; //Sun is at the center for vsop87a
case ASTRO_BODY_MERCURY:
vsop87a_milli_getMercury(et, coords);
break;
case ASTRO_BODY_VENUS:
vsop87a_milli_getVenus(et, coords);
break;
case ASTRO_BODY_EARTH:
vsop87a_milli_getEarth(et, coords);
break;
case ASTRO_BODY_MARS:
vsop87a_milli_getMars(et, coords);
break;
case ASTRO_BODY_JUPITER:
vsop87a_milli_getJupiter(et, coords);
break;
case ASTRO_BODY_SATURN:
vsop87a_milli_getSaturn(et, coords);
break;
case ASTRO_BODY_URANUS:
vsop87a_milli_getUranus(et, coords);
break;
case ASTRO_BODY_NEPTUNE:
vsop87a_milli_getNeptune(et, coords);
break;
case ASTRO_BODY_EMB:
vsop87a_milli_getEmb(et, coords);
break;
case ASTRO_BODY_MOON:
{
double earth_coords[3];
double emb_coords[3];
vsop87a_milli_getEarth(et, earth_coords);
vsop87a_milli_getEmb(et, emb_coords);
vsop87a_milli_getMoon(earth_coords, emb_coords, coords);
}
break;
}
retval.x = coords[0];
retval.y = coords[1];
retval.z = coords[2];
return retval;
}
astro_cartesian_coordinates_t astro_get_body_coordinates_light_time_adjusted(astro_body_t body, astro_cartesian_coordinates_t origin, double t) {
//Get current position of body
astro_cartesian_coordinates_t body_coords = astro_get_body_coordinates(body, t);
double newT = t;
for(uint8_t i = 0 ; i < 2 ; i++) {
//Calculate light time to body
body_coords = astro_subtract_cartesian(body_coords, origin);
double distance = sqrt(body_coords.x*body_coords.x + body_coords.y*body_coords.y + body_coords.z*body_coords.z);
distance *= 1.496e+11; //Convert from AU to meters
double lightTime = distance / 299792458.0;
//Convert light time to Julian Millenia, and subtract it from the original value of t
newT -= lightTime / 24.0 / 60.0 / 60.0 / 365250.0;
//Recalculate body position adjusted for light time
body_coords = astro_get_body_coordinates(body, newT);
}
return body_coords;
}
astro_horizontal_coordinates_t astro_ra_dec_to_alt_az(double jd, double lat, double lon, double ra, double dec) {
double GMST = astro_get_GMST(jd) * M_PI/180.0 * 15.0;
double h = GMST + lon - ra;
double sina = sin(dec)*sin(lat) + cos(dec)*cos(h)*cos(lat);
double a = asin(sina);
double cosAz = (sin(dec)*cos(lat) - cos(dec)*cos(h)*sin(lat)) / cos(a);
double Az = acos(cosAz);
if(sin(h) > 0) Az = 2.0*M_PI - Az;
astro_horizontal_coordinates_t retval;
retval.altitude = a;
retval.azimuth = Az;
return retval;
}
double astro_degrees_to_radians(double degrees) {
return degrees * M_PI / 180;
}
double astro_radians_to_degrees(double radians) {
return radians * 180.0 / M_PI;
}
astro_angle_dms_t astro_radians_to_dms(double radians) {
astro_angle_dms_t retval;
int8_t sign = (radians < 0) ? -1 : 1;
double degrees = fabs(astro_radians_to_degrees(radians));
retval.degrees = (uint16_t)degrees;
double temp = 60.0 * (degrees - retval.degrees);
retval.minutes = (uint8_t)temp;
retval.seconds = (uint8_t)round(60.0 * (temp - retval.minutes));
if (retval.seconds > 59) {
retval.seconds = 0.0;
retval.minutes++;
}
if (retval.minutes > 59) {
retval.minutes = 0;
retval.degrees++;
}
degrees *= sign;
return retval;
}
astro_angle_hms_t astro_radians_to_hms(double radians) {
astro_angle_hms_t retval;
double degrees = astro_radians_to_degrees(radians);
double temp = degrees / 15.0;
retval.hours = (uint8_t)temp;
temp = 60.0 * (temp - retval.hours);
retval.minutes = (uint8_t)temp;
retval.seconds = (uint8_t)round(60.0 * (temp - retval.minutes));
if (retval.seconds > 59) {
retval.seconds = 0;
retval.minutes++;
}
if (retval.minutes > 59) {
retval.minutes = 0;
retval.hours++;
}
return retval;
}

View file

@ -0,0 +1,87 @@
/*
* Partial C port of Greg Miller's public domain astro library (gmiller@gregmiller.net) 2019
* https://github.com/gmiller123456/astrogreg
*
* Ported by Joey Castillo for Sensor Watch
* https://github.com/joeycastillo/Sensor-Watch/
*
* Public Domain
*
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef ASTROLIB_H_
#define ASTROLIB_H_
typedef enum {
ASTRO_BODY_SUN = 0,
ASTRO_BODY_MERCURY,
ASTRO_BODY_VENUS,
ASTRO_BODY_EARTH,
ASTRO_BODY_MARS,
ASTRO_BODY_JUPITER,
ASTRO_BODY_SATURN,
ASTRO_BODY_URANUS,
ASTRO_BODY_NEPTUNE,
ASTRO_BODY_EMB,
ASTRO_BODY_MOON
} astro_body_t;
typedef struct {
double elements[3][3];
} astro_matrix_t;
typedef struct {
double x;
double y;
double z;
} astro_cartesian_coordinates_t;
typedef struct {
double right_ascension;
double declination;
double distance;
} astro_equatorial_coordinates_t;
typedef struct {
double altitude;
double azimuth;
} astro_horizontal_coordinates_t;
typedef struct {
int16_t degrees;
uint8_t minutes;
uint8_t seconds; // you may want this to be a float, watch just can't display any more digits
} astro_angle_dms_t;
typedef struct {
uint8_t hours;
uint8_t minutes;
uint8_t seconds; // you may want this to be a float, watch just can't display any more digits
} astro_angle_hms_t;
// Convert a date to a julian date. Must be in UTC+0 time zone!
double astro_convert_date_to_julian_date(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, uint8_t second);
// Converts a Julan Date to Julian Millenia since J2000, which is what VSOP87 expects as input.
double astro_convert_jd_to_julian_millenia_since_j2000(double jd);
// Get right ascension / declination for a given body in the list above.
astro_equatorial_coordinates_t astro_get_ra_dec(double jd, astro_body_t bodyNum, double lat, double lon, bool calculate_precession);
// Convert right ascension / declination to altitude/azimuth for a given location.
astro_horizontal_coordinates_t astro_ra_dec_to_alt_az(double jd, double lat, double lon, double ra, double dec);
// these are self-explanatory
double astro_degrees_to_radians(double degrees);
double astro_radians_to_degrees(double radians);
astro_angle_dms_t astro_radians_to_dms(double radians);
astro_angle_hms_t astro_radians_to_hms(double radians);
#endif // ASTROLIB_H_

View file

@ -17,6 +17,7 @@ Released to the public domain by Paul Schlyter, December 1992
#include <math.h> #include <math.h>
#include "sunriset.h" #include "sunriset.h"
static void sunpos( double d, double *lon, double *r );
/* A macro to compute the number of days elapsed since 2000 Jan 0.0 */ /* A macro to compute the number of days elapsed since 2000 Jan 0.0 */
/* (which is equal to 1999 Dec 31, 0h UT) */ /* (which is equal to 1999 Dec 31, 0h UT) */
@ -199,7 +200,7 @@ double __daylen__( int year, int month, int day, double lon, double lat,
/* This function computes the Sun's position at any instant */ /* This function computes the Sun's position at any instant */
void sunpos( double d, double *lon, double *r ) static void sunpos( double d, double *lon, double *r )
/******************************************************/ /******************************************************/
/* Computes the Sun's ecliptic longitude and distance */ /* Computes the Sun's ecliptic longitude and distance */
/* at an instant given in d, number of days since */ /* at an instant given in d, number of days since */

View file

@ -24,8 +24,6 @@ double __daylen__( int year, int month, int day, double lon, double lat,
int __sunriset__( int year, int month, int day, double lon, double lat, int __sunriset__( int year, int month, int day, double lon, double lat,
double altit, int upper_limb, double *rise, double *set ); double altit, int upper_limb, double *rise, double *set );
void sunpos( double d, double *lon, double *r );
void sun_RA_dec( double d, double *RA, double *dec, double *r ); void sun_RA_dec( double d, double *RA, double *dec, double *r );
double revolution( double x ); double revolution( double x );

View file

@ -0,0 +1,5 @@
Author: Greg A Miller (gmiller@gregmiller.net)
I release all of my work in this project to the public domain.
Note, though, not everything here can be considered my work. The VSOP87 theory is the work of
Bureau des Longitudes.

143
movement/lib/vsop87/README.md Executable file
View file

@ -0,0 +1,143 @@
# Example
An example in JavaScript showing computations is available at http://www.celestialprogramming.com/vsop87-multilang/
### Current Stauts
Versions for many different languages and environments have been created. These have passed all test cases provided by the
VSOP87 authors, a validation program is also included for each language. They are ready for use. See the "Languages" directory for the status
of each language.
Still to come: more languages, documentation, and examples.
# About this Project
The purpose of this project is to create versions of VSOP87 in many different languages. The plan is to generate a initial version in C# truncated to different
precisions, then convert the resulting files into other languages. This project was inspired by the [Neoprogrammics Source Code Generator Tool](http://www.neoprogrammics.com/vsop87/source_code_generator_tool/).
The goal of this project is to provide easier to use (readymade source files), include all data (e.g. the Moon and velocities), are truncated to different accuracy levels,
, for more languages, and have a more permissive license (public domain). It will provide tests against the original VSOP87 test data for each programming language to validate correctness, as well as examples to show common useage.
# Language Status
Below is a list of the planned languages and their current status.
Language |Inline|JSON|CSV|CSV Low Mem|Validation Tests|Alt Az Reduction Example
-------------|------|----|---|-----------|----------------|--------------
Java |Yes | |Yes| |Pass |
C |Yes | | |Yes |Pass |
C# |Yes | |Yes| |Pass |
Python |Yes |Yes |Yes| |Pass |
C++ |Yes | | | |Pass |
VB.Net |Yes | | | |Pass |
JavaScript |Yes |Yes | | |Pass |Yes
PHP |Yes | | | |Pass |
Ruby |Yes | | | |Pass |
Swift |Yes | | | |Pass |
Matlab/Octave|Yes | | | |Pass |
Groovy | | |Yes| |Pass |
Go |Yes | | | |Pass |
Pascal |Yes | | | |0.000009au |
Perl |Yes | | |Yes |Pass |
R |Fail! | | | |Fail! |
Cobol | | | | | |
Fortran | | | | | |
Rust |Yes | | | |Pass |
Arduino |Yes | | | |0.000009au |
# What is VSOP87? Why use it?
VSOP87 is one of many solutions available for predicting the positions of the planets (plus the Sun and the Moon) in our solar system. The actual name is
Variations S<>culaires des Orbites Plan<61>taires, and it was published in 1987 by the Bureau des Longitudes. Even though there have been many other methods
developed before and after VSOP87, it remains one of the most popular methods among amatuers. It provides better accuracy than most amatuers require (.1 arcseconds)
over a period of a few thousand years before and after the year 2000.
# Implementation Types
There are a few different types of implementations: Inline, JSON, CSV, and CSV Low Memory. The inline versions are generally the easiest to use as they will have no
external requirements, they are also the easiest to generate, so they're available for more languages. The JSON versions require a JSON file (located in the
languages/JSON folder) which is loaded into memory. The advantages of the JSON versions are you can compute the velocities with the same data the positions
are generated from, and you can load and dispose of the memory used by the data when you need it. The CSV implementations are similar to the JSON implementations,
but, obviously, read from a CSV file (located in the languages/CSV folder). And the Low Memory CSV implementations read the same CSV files, but the
data is not retained in memory. The JSON versions are located in the Languages/JSON directory, as well as the required JSON files, and the CSV implementations
are in the languages/CSV folder.
# Which Version Should I Use?
For the overwhelming majority of users, the VSOP87A_full version will be all that you need. This is the full version, but should still be fast enough and small enough
for most use cases. Using the full version eliminates any questions of whether it will be accurate enough. If, after trying the full version, the computation isn't
fast enough, from there you should experiment with truncated versions. The VSOP87A versions are the only versions which include both the Earth and Moon. VSOP87A doesn't include the moon directly, but does include the Earth and the Earth-Moon Barrycenter, and all provided code for the VSOP87A versions include a function to compute the Moon's position from the Earth and EMB. Using the versions that provide the velocities is necessary if you want to account for relativistic effects do to the motion of the observer.
There are several versions of the main theory. The first is just called VSOP87, the remainder of them are appended with the letters A, B, C, D, E. Each version
provides the data in a slightly different form.
Version|Mercury|Venus|Earth|EMB|Mars|Jupiter|Saturn|Uranus|Neptune|Sun|Coordinates
-------|-------|-----|-----|---|----|-------|------|------|-------|---|-----------
VSOP87|Yes|Yes|No|Yes|Yes|Yes|Yes|Yes|Yes|No|Keperian Orbital Elements
VSOP87A|Yes|Yes|Yes|Yes|Yes|Yes|Yes|Yes|Yes|No|Heliocentric J2000 Ecliptic Rectangular XYZ
VSOP87B|Yes|Yes|Yes|No|Yes|Yes|Yes|Yes|Yes|No|Heliocentric J2000 Ecliptic Spherical LBR
VSOP87C|Yes|Yes|Yes|No|Yes|Yes|Yes|Yes|Yes|No|Heliocentric Ecliptic of date Rectangular XYZ
VSOP87D|Yes|Yes|Yes|No|Yes|Yes|Yes|Yes|Yes|No|Heliocentric Ecliptic of date Spherical LBR
VSOP87E|Yes|Yes|Yes|No|Yes|Yes|Yes|Yes|Yes|Yes|Barycentric J2000 Ecliptic Rectangular XYZ
# Truncated versions
Since the full VSOP87 provides more accuracy than most amateurs require, the algorithm can be shortened by eliminating terms. This speeds up the computations, and
reduces the overall size of the code at the cost of accuracy. For each programming language, this project supplies VSOP87 truncated at ten different levels. The
effects of accuracy are detailed in the graphs below. Each level of truncation eliminates any terms with a coefficient 1/10 the previous truncation level.
Trunaction Level|Total Terms|Skipped Terms|Percent Skipped
----------------|-----------|-------------|---------------
full |269949|0 |0 %
xx large |269949|20998 |7.7 %
x large |269949|67848 |25.1 %
large |269949|145031|53.7 %
small |269949|218559|80.9 %
x small |269949|250204|92.6 %
milli |269949|262369|97.1 %
micro |269949|266975|98.8 %
nano |269949|268686|99.5 %
pico |269949|269464|99.8 %
# Accuracy
Accuracy graphs are below. They show the error in degrees of each body as viewed from Earth. Each graph shows the error for one body for all truncated versions of
VSOP87. The error is vs. the full version of VSOP87, so the inherent error in VSOP87 also has to be added. Some bodies appear twice, to zoom in on the lower portion
of the graph, as the error of the pico version makes it difficult to see errors amongst the larger versions. The Python script and data to reproduce the graphs is
in the Accuracy folder, by regenerating them you can use the Matplotlib interface to explore the graphs further.
Since the error is computed from the geocenter, the Earth does not appear in the graphs below, nor does the Sun. Graphs are also not present for the Moon, but graphs
are available for the Earth-Moon Barrycenter (EMB), the error for the Moon will be a linear function of the EMB error.
The full VSOP87 accuracy is .1 arcseconds for Saturn, and better for all others. For more details on accuracy of the full theory consult
[Planetary theories in rectangular and spherical variables - VSOP 87 solutions](http://articles.adsabs.harvard.edu/full/1988A%26A...202..309B).
### Mercury
![Mercury](https://raw.githubusercontent.com/gmiller123456/vsop87-multilang/master/utility/Accuracy/mercury.png)
### Venus
![Mercury](https://raw.githubusercontent.com/gmiller123456/vsop87-multilang/master/utility/Accuracy/venus.png)
### Earth-Moon Barrycenter
![Mercury](https://raw.githubusercontent.com/gmiller123456/vsop87-multilang/master/utility/Accuracy/emb.png)
### Earth-Moon Barrycenter (zoomed)
![Mercury](https://raw.githubusercontent.com/gmiller123456/vsop87-multilang/master/utility/Accuracy/emb2.png)
### Mars
![Mercury](https://raw.githubusercontent.com/gmiller123456/vsop87-multilang/master/utility/Accuracy/mars.png)
### Jupiter
![Mercury](https://raw.githubusercontent.com/gmiller123456/vsop87-multilang/master/utility/Accuracy/jupiter.png)
### Saturn
![Mercury](https://raw.githubusercontent.com/gmiller123456/vsop87-multilang/master/utility/Accuracy/saturn.png)
### Saturn (zoomed)
![Mercury](https://raw.githubusercontent.com/gmiller123456/vsop87-multilang/master/utility/Accuracy/saturn2.png)
### Uranus
![Mercury](https://raw.githubusercontent.com/gmiller123456/vsop87-multilang/master/utility/Accuracy/uranus.png)
### Uranus (zoomed)
![Mercury](https://raw.githubusercontent.com/gmiller123456/vsop87-multilang/master/utility/Accuracy/uranus2.png)
### Neptune
![Mercury](https://raw.githubusercontent.com/gmiller123456/vsop87-multilang/master/utility/Accuracy/neptune.png)
### Neptune (zoomed)
![Mercury](https://raw.githubusercontent.com/gmiller123456/vsop87-multilang/master/utility/Accuracy/neptune2.png)

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,17 @@
//VSOP87-Multilang http://www.astrogreg.com/vsop87-multilang/index.html
//Greg Miller (gmiller@gregmiller.net) 2019. Released as Public Domain
#ifndef VSOP87A_MICRO
#define VSOP87A_MICRO
void vsop87a_micro_getEarth(double t,double temp[]);
void vsop87a_micro_getEmb(double t,double temp[]);
void vsop87a_micro_getJupiter(double t,double temp[]);
void vsop87a_micro_getMars(double t,double temp[]);
void vsop87a_micro_getMercury(double t,double temp[]);
void vsop87a_micro_getNeptune(double t,double temp[]);
void vsop87a_micro_getSaturn(double t,double temp[]);
void vsop87a_micro_getUranus(double t,double temp[]);
void vsop87a_micro_getVenus(double t,double temp[]);
void vsop87a_micro_getMoon(double earth[], double emb[],double temp[]);
#endif

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,17 @@
//VSOP87-Multilang http://www.astrogreg.com/vsop87-multilang/index.html
//Greg Miller (gmiller@gregmiller.net) 2019. Released as Public Domain
#ifndef VSOP87A_MILLI
#define VSOP87A_MILLI
void vsop87a_milli_getEarth(double t,double temp[]);
void vsop87a_milli_getEmb(double t,double temp[]);
void vsop87a_milli_getJupiter(double t,double temp[]);
void vsop87a_milli_getMars(double t,double temp[]);
void vsop87a_milli_getMercury(double t,double temp[]);
void vsop87a_milli_getNeptune(double t,double temp[]);
void vsop87a_milli_getSaturn(double t,double temp[]);
void vsop87a_milli_getUranus(double t,double temp[]);
void vsop87a_milli_getVenus(double t,double temp[]);
void vsop87a_milli_getMoon(double earth[], double emb[],double temp[]);
#endif

View file

@ -18,6 +18,8 @@ INCLUDES += \
-I../watch_faces/demo/ \ -I../watch_faces/demo/ \
-I../lib/TOTP-MCU/ \ -I../lib/TOTP-MCU/ \
-I../lib/sunriset/ \ -I../lib/sunriset/ \
-I../lib/vsop87/ \
-I../lib/astrolib/ \
# If you add any other source files you wish to compile, add them after ../app.c # If you add any other source files you wish to compile, add them after ../app.c
# Note that you will need to add a backslash at the end of any line you wish to continue, i.e. # Note that you will need to add a backslash at the end of any line you wish to continue, i.e.
@ -29,6 +31,8 @@ SRCS += \
../lib/TOTP-MCU/sha1.c \ ../lib/TOTP-MCU/sha1.c \
../lib/TOTP-MCU/TOTP.c \ ../lib/TOTP-MCU/TOTP.c \
../lib/sunriset/sunriset.c \ ../lib/sunriset/sunriset.c \
../lib/vsop87/vsop87a_milli.c \
../lib/astrolib/astrolib.c \
../movement.c \ ../movement.c \
../watch_faces/clock/simple_clock_face.c \ ../watch_faces/clock/simple_clock_face.c \
../watch_faces/clock/world_clock_face.c \ ../watch_faces/clock/world_clock_face.c \
@ -50,6 +54,8 @@ SRCS += \
../watch_faces/complication/countdown_face.c \ ../watch_faces/complication/countdown_face.c \
../watch_faces/complication/blinky_face.c \ ../watch_faces/complication/blinky_face.c \
../watch_faces/complication/moon_phase_face.c \ ../watch_faces/complication/moon_phase_face.c \
../watch_faces/complication/orrery_face.c \
../watch_faces/complication/astronomy_face.c \
# New watch faces go above this line. # New watch faces go above this line.
# Leave this line at the bottom of the file; it has all the targets for making your project. # Leave this line at the bottom of the file; it has all the targets for making your project.

View file

@ -45,6 +45,8 @@
#include "countdown_face.h" #include "countdown_face.h"
#include "blinky_face.h" #include "blinky_face.h"
#include "moon_phase_face.h" #include "moon_phase_face.h"
#include "orrery_face.h"
#include "astronomy_face.h"
// New includes go above this line. // New includes go above this line.
#endif // MOVEMENT_FACES_H_ #endif // MOVEMENT_FACES_H_

View file

@ -0,0 +1,280 @@
/*
* MIT License
*
* Copyright (c) 2022 Joey Castillo
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include "astronomy_face.h"
#include "watch_utility.h"
#if __EMSCRIPTEN__
#include <emscripten.h>
#endif
#define NUM_AVAILABLE_BODIES 9
static const char astronomy_available_celestial_bodies[NUM_AVAILABLE_BODIES] = {
ASTRO_BODY_SUN,
ASTRO_BODY_MERCURY,
ASTRO_BODY_VENUS,
ASTRO_BODY_MOON,
ASTRO_BODY_MARS,
ASTRO_BODY_JUPITER,
ASTRO_BODY_SATURN,
ASTRO_BODY_URANUS,
ASTRO_BODY_NEPTUNE
};
static const char astronomy_celestial_body_names[NUM_AVAILABLE_BODIES][3] = {
"SO", // Sol
"ME", // Mercury
"VE", // Venus
"LU", // Moon (Luna)
"MA", // Mars
"JU", // Jupiter
"SA", // Saturn
"UR", // Uranus
"NE" // Neptune
};
static void _astronomy_face_recalculate(movement_settings_t *settings, astronomy_state_t *state) {
#if __EMSCRIPTEN__
int16_t browser_lat = EM_ASM_INT({
return lat;
});
int16_t browser_lon = EM_ASM_INT({
return lon;
});
if ((watch_get_backup_data(1) == 0) && (browser_lat || browser_lon)) {
movement_location_t browser_loc;
browser_loc.bit.latitude = browser_lat;
browser_loc.bit.longitude = browser_lon;
watch_store_backup_data(browser_loc.reg, 1);
double lat = (double)browser_lat / 100.0;
double lon = (double)browser_lon / 100.0;
state->latitude_radians = astro_degrees_to_radians(lat);
state->longitude_radians = astro_degrees_to_radians(lon);
}
#endif
watch_date_time date_time = watch_rtc_get_date_time();
uint32_t timestamp = watch_utility_date_time_to_unix_time(date_time, movement_timezone_offsets[settings->bit.time_zone] * 60);
date_time = watch_utility_date_time_from_unix_time(timestamp, 0);
double jd = astro_convert_date_to_julian_date(date_time.unit.year + WATCH_RTC_REFERENCE_YEAR, date_time.unit.month, date_time.unit.day, date_time.unit.hour, date_time.unit.minute, date_time.unit.second);
astro_equatorial_coordinates_t radec_precession = astro_get_ra_dec(jd, astronomy_available_celestial_bodies[state->active_body_index], state->latitude_radians, state->longitude_radians, true);
printf("\nParams to convert: %f %f %f %f %f\n",
jd,
astro_radians_to_degrees(state->latitude_radians),
astro_radians_to_degrees(state->longitude_radians),
astro_radians_to_degrees(radec_precession.right_ascension),
astro_radians_to_degrees(radec_precession.declination));
astro_horizontal_coordinates_t horiz = astro_ra_dec_to_alt_az(jd, state->latitude_radians, state->longitude_radians, radec_precession.right_ascension, radec_precession.declination);
astro_equatorial_coordinates_t radec = astro_get_ra_dec(jd, astronomy_available_celestial_bodies[state->active_body_index], state->latitude_radians, state->longitude_radians, false);
state->altitude = astro_radians_to_degrees(horiz.altitude);
state->azimuth = astro_radians_to_degrees(horiz.azimuth);
state->right_ascension = astro_radians_to_hms(radec.right_ascension);
state->declination = astro_radians_to_dms(radec.declination);
state->distance = radec.distance;
printf("Calculated coordinates for %s on %f: \n\tRA = %f / %2dh %2dm %2ds\n\tDec = %f / %3d° %3d' %3d\"\n\tAzi = %f\n\tAlt = %f\n\tDst = %f AU\n",
astronomy_celestial_body_names[state->active_body_index],
jd,
astro_radians_to_degrees(radec.right_ascension),
state->right_ascension.hours,
state->right_ascension.minutes,
state->right_ascension.seconds,
astro_radians_to_degrees(radec.declination),
state->declination.degrees,
state->declination.minutes,
state->declination.seconds,
state->altitude,
state->azimuth,
state->distance);
}
static void _astronomy_face_update(movement_event_t event, movement_settings_t *settings, astronomy_state_t *state) {
char buf[16];
switch (state->mode) {
case ASTRONOMY_MODE_SELECTING_BODY:
watch_clear_colon();
watch_display_string(" Astro", 4);
if (event.subsecond % 2) {
watch_display_string((char *)astronomy_celestial_body_names[state->active_body_index], 0);
} else {
watch_display_string(" ", 0);
}
if (event.subsecond == 0) {
watch_display_string(" ", 2);
switch (state->animation_state) {
case 0:
watch_set_pixel(0, 7);
watch_set_pixel(2, 6);
break;
case 1:
watch_set_pixel(1, 7);
watch_set_pixel(2, 9);
break;
case 2:
watch_set_pixel(2, 7);
watch_set_pixel(0, 9);
break;
}
state->animation_state = (state->animation_state + 1) % 3;
}
break;
case ASTRONOMY_MODE_CALCULATING:
watch_clear_display();
// this takes a moment and locks the UI, flash C for "Calculating"
watch_start_character_blink('C', 100);
_astronomy_face_recalculate(settings, state);
watch_stop_blink();
state->mode = ASTRONOMY_MODE_DISPLAYING_ALT;
// fall through
case ASTRONOMY_MODE_DISPLAYING_ALT:
sprintf(buf, "%saL%6d", astronomy_celestial_body_names[state->active_body_index], (int16_t)round(state->altitude * 100));
watch_display_string(buf, 0);
break;
case ASTRONOMY_MODE_DISPLAYING_AZI:
sprintf(buf, "%saZ%6d", astronomy_celestial_body_names[state->active_body_index], (int16_t)round(state->azimuth * 100));
watch_display_string(buf, 0);
break;
case ASTRONOMY_MODE_DISPLAYING_RA:
watch_set_colon();
sprintf(buf, "ra H%02d%02d%02d", state->right_ascension.hours, state->right_ascension.minutes, state->right_ascension.seconds);
watch_display_string(buf, 0);
break;
case ASTRONOMY_MODE_DISPLAYING_DEC:
watch_clear_colon();
sprintf(buf, "de %3d%2d%2d", state->declination.degrees, state->declination.minutes, state->declination.seconds);
watch_display_string(buf, 0);
break;
case ASTRONOMY_MODE_DISPLAYING_DIST:
if (state->distance >= 0.00668456) {
// if >= 1,000,000 kilometers (all planets), we display distance in AU.
sprintf(buf, "diAU%6d", (uint16_t)round(state->distance * 100));
} else {
// otherwise distance in kilometers fits in 6 digits. This mode will only happen for Luna.
sprintf(buf, "di K%6ld", (uint32_t)round(state->distance * 149597871.0));
}
watch_display_string(buf, 0);
break;
case ASTRONOMY_MODE_NUM_MODES:
// this case does not happen, but we need it to silence a warning.
break;
}
}
void astronomy_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) {
(void) settings;
(void) watch_face_index;
if (*context_ptr == NULL) {
*context_ptr = malloc(sizeof(astronomy_state_t));
memset(*context_ptr, 0, sizeof(astronomy_state_t));
}
}
void astronomy_face_activate(movement_settings_t *settings, void *context) {
(void) settings;
astronomy_state_t *state = (astronomy_state_t *)context;
movement_location_t movement_location = (movement_location_t) watch_get_backup_data(1);
int16_t lat_centi = (int16_t)movement_location.bit.latitude;
int16_t lon_centi = (int16_t)movement_location.bit.longitude;
double lat = (double)lat_centi / 100.0;
double lon = (double)lon_centi / 100.0;
state->latitude_radians = astro_degrees_to_radians(lat);
state->longitude_radians = astro_degrees_to_radians(lon);
movement_request_tick_frequency(4);
}
bool astronomy_face_loop(movement_event_t event, movement_settings_t *settings, void *context) {
astronomy_state_t *state = (astronomy_state_t *)context;
switch (event.event_type) {
case EVENT_ACTIVATE:
case EVENT_TICK:
_astronomy_face_update(event, settings, state);
break;
case EVENT_MODE_BUTTON_UP:
// You shouldn't need to change this case; Mode almost always moves to the next watch face.
movement_move_to_next_face();
break;
case EVENT_LIGHT_BUTTON_UP:
// If you have other uses for the Light button, you can opt not to illuminate the LED for this event.
movement_illuminate_led();
break;
case EVENT_ALARM_BUTTON_UP:
switch (state->mode) {
case ASTRONOMY_MODE_SELECTING_BODY:
// advance to next celestial body (move to calculations with a long press)
state->active_body_index = (state->active_body_index + 1) % NUM_AVAILABLE_BODIES;
break;
case ASTRONOMY_MODE_CALCULATING:
// ignore button press during calculations
break;
case ASTRONOMY_MODE_DISPLAYING_DIST:
// at last mode, wrap around
state->mode = ASTRONOMY_MODE_DISPLAYING_ALT;
break;
default:
// otherwise, advance to next mode
state->mode++;
break;
}
_astronomy_face_update(event, settings, state);
break;
case EVENT_ALARM_LONG_PRESS:
if (state->mode == ASTRONOMY_MODE_SELECTING_BODY) {
// celestial body selected! this triggers a calculation in the update method.
state->mode = ASTRONOMY_MODE_CALCULATING;
movement_request_tick_frequency(1);
_astronomy_face_update(event, settings, state);
} else if (state->mode != ASTRONOMY_MODE_CALCULATING) {
// in all modes except "doing a calculation", return to the selection screen.
state->mode = ASTRONOMY_MODE_SELECTING_BODY;
movement_request_tick_frequency(4);
_astronomy_face_update(event, settings, state);
}
break;
case EVENT_TIMEOUT:
movement_move_to_face(0);
break;
case EVENT_LOW_ENERGY_UPDATE:
// TODO?
break;
default:
break;
}
return true;
}
void astronomy_face_resign(movement_settings_t *settings, void *context) {
(void) settings;
astronomy_state_t *state = (astronomy_state_t *)context;
state->mode = ASTRONOMY_MODE_SELECTING_BODY;
}

View file

@ -0,0 +1,68 @@
/*
* MIT License
*
* Copyright (c) 2022 Joey Castillo
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef ASTRONOMY_FACE_H_
#define ASTRONOMY_FACE_H_
#include "movement.h"
#include "astrolib.h"
typedef enum {
ASTRONOMY_MODE_SELECTING_BODY = 0,
ASTRONOMY_MODE_CALCULATING,
ASTRONOMY_MODE_DISPLAYING_ALT,
ASTRONOMY_MODE_DISPLAYING_AZI,
ASTRONOMY_MODE_DISPLAYING_RA,
ASTRONOMY_MODE_DISPLAYING_DEC,
ASTRONOMY_MODE_DISPLAYING_DIST,
ASTRONOMY_MODE_NUM_MODES
} astronomy_mode_t;
typedef struct {
astronomy_mode_t mode;
uint8_t active_body_index;
uint8_t animation_state;
double latitude_radians; // this is the user location
double longitude_radians; // but in radians
astro_angle_hms_t right_ascension;
astro_angle_dms_t declination;
double altitude; // in decimal degrees
double azimuth; // in decimal degrees
double distance; // in AU
} astronomy_state_t;
void astronomy_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr);
void astronomy_face_activate(movement_settings_t *settings, void *context);
bool astronomy_face_loop(movement_event_t event, movement_settings_t *settings, void *context);
void astronomy_face_resign(movement_settings_t *settings, void *context);
#define astronomy_face ((const watch_face_t){ \
astronomy_face_setup, \
astronomy_face_activate, \
astronomy_face_loop, \
astronomy_face_resign, \
NULL, \
})
#endif // ASTRONOMY_FACE_H_

View file

@ -0,0 +1,232 @@
/*
* MIT License
*
* Copyright (c) 2022 Joey Castillo
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "orrery_face.h"
#include "watch.h"
#include "watch_utility.h"
#include "vsop87a_micro.h" // smaller size, less accurate
#include "vsop87a_milli.h"
#include "astrolib.h"
#define NUM_AVAILABLE_BODIES 9
static const char orrery_celestial_body_names[NUM_AVAILABLE_BODIES][3] = {
"ME", // Mercury
"VE", // Venus
"EA", // Earth
"LU", // Moon (Luna)
"MA", // Mars
"JU", // Jupiter
"SA", // Saturn
"UR", // Uranus
"NE" // Neptune
};
static void _orrery_face_recalculate(movement_settings_t *settings, orrery_state_t *state) {
watch_date_time date_time = watch_rtc_get_date_time();
uint32_t timestamp = watch_utility_date_time_to_unix_time(date_time, movement_timezone_offsets[settings->bit.time_zone] * 60);
date_time = watch_utility_date_time_from_unix_time(timestamp, 0);
double jd = astro_convert_date_to_julian_date(date_time.unit.year + WATCH_RTC_REFERENCE_YEAR, date_time.unit.month, date_time.unit.day, date_time.unit.hour, date_time.unit.minute, date_time.unit.second);
double et = astro_convert_jd_to_julian_millenia_since_j2000(jd);
double r[3] = {0};
switch(state->active_body_index) {
case 0:
vsop87a_milli_getMercury(et, r);
break;
case 1:
vsop87a_milli_getVenus(et, r);
break;
case 2:
vsop87a_milli_getEarth(et, r);
break;
case 3:
{
double earth[3];
double emb[3];
vsop87a_milli_getEarth(et, earth);
vsop87a_milli_getEmb(et, emb);
vsop87a_milli_getMoon(earth, emb, r);
}
break;
case 4:
vsop87a_milli_getMars(et, r);
break;
case 5:
vsop87a_milli_getJupiter(et, r);
break;
case 6:
vsop87a_milli_getSaturn(et, r);
break;
case 7:
vsop87a_milli_getUranus(et, r);
break;
case 8:
vsop87a_milli_getNeptune(et, r);
break;
}
state->coords[0] = r[0];
state->coords[1] = r[1];
state->coords[2] = r[2];
}
static void _orrery_face_update(movement_event_t event, movement_settings_t *settings, orrery_state_t *state) {
char buf[11];
switch (state->mode) {
case ORRERY_MODE_SELECTING_BODY:
watch_display_string("Orrery", 4);
if (event.subsecond % 2) {
watch_display_string((char *)orrery_celestial_body_names[state->active_body_index], 0);
} else {
watch_display_string(" ", 0);
}
if (event.subsecond == 0) {
watch_display_string(" ", 2);
switch (state->animation_state) {
case 0:
watch_set_pixel(0, 7);
watch_set_pixel(2, 6);
break;
case 1:
watch_set_pixel(1, 7);
watch_set_pixel(2, 9);
break;
case 2:
watch_set_pixel(2, 7);
watch_set_pixel(0, 9);
break;
}
state->animation_state = (state->animation_state + 1) % 3;
}
break;
case ORRERY_MODE_CALCULATING:
watch_clear_display();
// this takes a moment and locks the UI, flash C for "Calculating"
watch_start_character_blink('C', 100);
_orrery_face_recalculate(settings, state);
watch_stop_blink();
state->mode = ORRERY_MODE_DISPLAYING_X;
// fall through
case ORRERY_MODE_DISPLAYING_X:
sprintf(buf, "%s X%6d", orrery_celestial_body_names[state->active_body_index], (int16_t)round(state->coords[0] * 100));
watch_display_string(buf, 0);
break;
case ORRERY_MODE_DISPLAYING_Y:
sprintf(buf, "%s Y%6d", orrery_celestial_body_names[state->active_body_index], (int16_t)round(state->coords[1] * 100));
watch_display_string(buf, 0);
break;
case ORRERY_MODE_DISPLAYING_Z:
sprintf(buf, "%s Z%6d", orrery_celestial_body_names[state->active_body_index], (int16_t)round(state->coords[2] * 100));
watch_display_string(buf, 0);
break;
case ORRERY_MODE_NUM_MODES:
// this case does not happen, but we need it to silence a warning.
break;
}
}
void orrery_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) {
(void) settings;
(void) watch_face_index;
if (*context_ptr == NULL) {
*context_ptr = malloc(sizeof(orrery_state_t));
memset(*context_ptr, 0, sizeof(orrery_state_t));
}
}
void orrery_face_activate(movement_settings_t *settings, void *context) {
(void) settings;
(void) context;
movement_request_tick_frequency(4);
}
bool orrery_face_loop(movement_event_t event, movement_settings_t *settings, void *context) {
(void) settings;
orrery_state_t *state = (orrery_state_t *)context;
switch (event.event_type) {
case EVENT_ACTIVATE:
case EVENT_TICK:
_orrery_face_update(event, settings, state);
break;
case EVENT_MODE_BUTTON_UP:
movement_move_to_next_face();
break;
case EVENT_LIGHT_BUTTON_DOWN:
movement_illuminate_led();
break;
case EVENT_LIGHT_BUTTON_UP:
break;
case EVENT_ALARM_BUTTON_UP:
switch (state->mode) {
case ORRERY_MODE_SELECTING_BODY:
// advance to next celestial body (move to calculations with a long press)
state->active_body_index = (state->active_body_index + 1) % NUM_AVAILABLE_BODIES;
break;
case ORRERY_MODE_CALCULATING:
// ignore button press during calculations
break;
case ORRERY_MODE_DISPLAYING_Z:
// at last mode, wrap around
state->mode = ORRERY_MODE_DISPLAYING_X;
break;
default:
// otherwise, advance to next mode
state->mode++;
break;
}
_orrery_face_update(event, settings, state);
break;
case EVENT_ALARM_LONG_PRESS:
if (state->mode == ORRERY_MODE_SELECTING_BODY) {
// celestial body selected! this triggers a calculation in the update method.
state->mode = ORRERY_MODE_CALCULATING;
movement_request_tick_frequency(1);
_orrery_face_update(event, settings, state);
} else if (state->mode != ORRERY_MODE_CALCULATING) {
// in all modes except "doing a calculation", return to the selection screen.
state->mode = ORRERY_MODE_SELECTING_BODY;
movement_request_tick_frequency(4);
_orrery_face_update(event, settings, state);
}
break;
case EVENT_TIMEOUT:
movement_move_to_face(0);
break;
default:
break;
}
return true;
}
void orrery_face_resign(movement_settings_t *settings, void *context) {
(void) settings;
orrery_state_t *state = (orrery_state_t *)context;
state->mode = ORRERY_MODE_SELECTING_BODY;
}

View file

@ -0,0 +1,59 @@
/*
* MIT License
*
* Copyright (c) 2022 Joey Castillo
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef ORRERY_FACE_H_
#define ORRERY_FACE_H_
#include "movement.h"
typedef enum {
ORRERY_MODE_SELECTING_BODY = 0,
ORRERY_MODE_CALCULATING,
ORRERY_MODE_DISPLAYING_X,
ORRERY_MODE_DISPLAYING_Y,
ORRERY_MODE_DISPLAYING_Z,
ORRERY_MODE_NUM_MODES
} orrery_mode_t;
typedef struct {
orrery_mode_t mode;
uint8_t active_body_index;
double coords[3];
uint8_t animation_state;
} orrery_state_t;
void orrery_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr);
void orrery_face_activate(movement_settings_t *settings, void *context);
bool orrery_face_loop(movement_event_t event, movement_settings_t *settings, void *context);
void orrery_face_resign(movement_settings_t *settings, void *context);
#define orrery_face ((const watch_face_t){ \
orrery_face_setup, \
orrery_face_activate, \
orrery_face_loop, \
orrery_face_resign, \
NULL, \
})
#endif // ORRERY_FACE_H_

View file

@ -52,6 +52,7 @@ void watch_display_character(uint8_t character, uint8_t position) {
if (character == 'T') character = 't'; // uppercase T only works in positions 0 and 1 if (character == 'T') character = 't'; // uppercase T only works in positions 0 and 1
} }
if (position == 1) { if (position == 1) {
if (character == 'a') character = 'A'; // A needs to be uppercase
if (character == 'o') character = 'O'; // O needs to be uppercase if (character == 'o') character = 'O'; // O needs to be uppercase
if (character == 'i') character = 'l'; // I needs to be uppercase (use an l, it looks the same) if (character == 'i') character = 'l'; // I needs to be uppercase (use an l, it looks the same)
if (character == 'n') character = 'N'; // N needs to be uppercase if (character == 'n') character = 'N'; // N needs to be uppercase