astronomy: Update docs and code.

master
Peter Hinch 2023-12-06 11:49:04 +00:00
rodzic baf4251274
commit b3f27dfae0
2 zmienionych plików z 77 dodań i 24 usunięć

Wyświetl plik

@ -7,6 +7,7 @@
2. [The RiSet class](./README.md#2-the-riset-class) 2. [The RiSet class](./README.md#2-the-riset-class)
2.1 [Constructor](./README.md#21-constructor) 2.1 [Constructor](./README.md#21-constructor)
2.2 [Methods](./README.md#22-methods) 2.2 [Methods](./README.md#22-methods)
2.3 [Effect of local time](./README.md#23-effect-of-local-time)
3. [The moonphase function](./README.md#3-the-moonphase-function) 3. [The moonphase function](./README.md#3-the-moonphase-function)
4. [Utility functions](./README.md#4-utility-functions) 4. [Utility functions](./README.md#4-utility-functions)
5. [Demo script](./README.md#5-demo-script) 5. [Demo script](./README.md#5-demo-script)
@ -94,7 +95,8 @@ rise or set event.
Args (float): Args (float):
* `lat=LAT` Latitude in degrees (-ve is South). Defaults are my location. :) * `lat=LAT` Latitude in degrees (-ve is South). Defaults are my location. :)
* `long=LONG` Longitude in degrees (-ve is West). * `long=LONG` Longitude in degrees (-ve is West).
* `lto=0` Local time offset in hours to UTC (-ve is West). * `lto=0` Local time offset in hours to UTC (-ve is West); the value is checked
to ensure `-12 < lto < 12`. See [section 2.3](./README.md#23-effect-of-local-time).
## 2.2 Methods ## 2.2 Methods
@ -114,6 +116,7 @@ moon, 0.5 is full. See [section 3](./README.md#3-the-moonphase-function) for
observations about this. observations about this.
* `set_lto(t)` Set localtime offset in hours relative to UTC. Primarily intended * `set_lto(t)` Set localtime offset in hours relative to UTC. Primarily intended
for daylight saving time. Rise and set times are updated if the lto is changed. for daylight saving time. Rise and set times are updated if the lto is changed.
The value is checked to ensure `-12 < lto < 12`. See [section 2.3](./README.md#23-effect-of-local-time).
The return value of the rise and set method is determined by the `variant` arg. The return value of the rise and set method is determined by the `variant` arg.
In all cases rise and set events are identified which occur in the current 24 In all cases rise and set events are identified which occur in the current 24
@ -132,6 +135,27 @@ r = RiSet() # UK near Manchester
r = RiSet(lat=47.609722, long=-122.3306, lto=-8) # Seattle 47°3635″N 122°1959″W r = RiSet(lat=47.609722, long=-122.3306, lto=-8) # Seattle 47°3635″N 122°1959″W
r = RiSet(lat=-33.87667, long=151.21, lto=11) # Sydney 33°5204″S 151°1236″E r = RiSet(lat=-33.87667, long=151.21, lto=11) # Sydney 33°5204″S 151°1236″E
``` ```
## 2.3 Effect of local time
MicroPython has no concept of local time. A hardware platform has a clock;
depending on application this might be permanently set to local winter time, or
it might be adjusted twice per year for local daylight saving time. It is the
responsibility of the application to do this if it is considered necessary.
Rise and set times are computed relative to UTC and then adjusted using the
`RiSet` instance's local time offset before being returned (see `.adjust()`).
This applies to all variants - note that the local platform epoch is on a fixed
date at 00:00:00 local time.
If the machine clock has a fixed relationship to UTC, `RiSet` instances should
have a corresponding fixed local time offset: rise and set times will be
relative to that time. If the application implements daylight saving time, the
local time offsets should be adjusted accordingly.
The constructor and the `set_day()` method set the instance's date relative to
the machine clock. They use only the date component of system time, hence they
may be run at any time of day. In continuously-running applications, `set_day()`
may be run each day just after midnight to keep a `RiSet` instance up to date.
# 3. The moonphase function # 3. The moonphase function
@ -203,22 +227,31 @@ Code comments show times retrieved from `timeanddate.com`.
# 6. Scheduling events # 6. Scheduling events
A likely use case is to enable events to be timed relative to sunrise and set. A likely use case is to enable events to be timed relative to sunrise and set.
In simple cases this can be done with `asyncio`. This routine, called before In simple cases this can be done with `asyncio`. This coroutine will execute a
sunrise, will perform some action at dawn and quit: payload at sunrise every day. A similar coroutine might handle sunsets.
```python ```python
from sched.sun_moon import RiSet import uasyncio as asyncio
import time import time
rs = RiSet() from sched.sun_moon import RiSet
async def wait_for_sunrise():
tsecs = time.time() % 86400 # Time now in secs since midnight async def tomorrow(): # Wait until 1 minute past midnight
rs.set_day() # Ensure today's date now = round(time.time())
twait = rs.sunrise() - tsecs # Time before Sunrise tw = 86400 + 60 - (now % 86400) # Time from now to one minute past next midnight
if twait > 0: # Sunrise has not yet occurred await asyncio.sleep(tw)
await asyncio.sleep(twait)
# Turn the lights off, or whatever async def do_sunrise():
rs = RiSet() # May need args
while True:
if (now := round(time.time())) < rs.sunrise(1): # Sun has not yet risen
await asyncio.sleep(rs.sunrise(1) - now) # Wait for it to rise
# Sun has risen, execute payload
await tomorrow()
rs.set_day() # Update to new day
``` ```
The problem with the above is ensuring that `wait_for_sunrise` is called shortly This code assumes that `.sunrise()` will never return `None`. At polar latitudes
after midnight. A simple solution is to use the waiting for sunrise in winter would require changes.
Code may be simplified by using the
[schedule module](https://github.com/peterhinch/micropython-async/blob/master/v3/docs/SCHEDULE.md). [schedule module](https://github.com/peterhinch/micropython-async/blob/master/v3/docs/SCHEDULE.md).
This may be installed with This may be installed with
```bash ```bash

Wyświetl plik

@ -23,6 +23,10 @@ LONG = -2.102811634540558
# (date(2000, 1, 1) - date(1970, 1, 1)).days = 10957 # (date(2000, 1, 1) - date(1970, 1, 1)).days = 10957
# Return time now in days relative to platform epoch. # Return time now in days relative to platform epoch.
# System time is set to local time, and MP has no concept of this. Hence
# time.time() returns secs since epoch 00:00:00 local time. If lto is local time
# offset to UTC, provided -12 < lto < 12, the effect of rounding ensures the
# right number of days for platform epoch at UTC.
def now_days() -> int: def now_days() -> int:
secs_per_day = 86400 # 24 * 3600 secs_per_day = 86400 # 24 * 3600
t = time.time() t = time.time()
@ -77,7 +81,7 @@ def quad(ym, yz, yp):
# hence the MJD of an integer day number will always be an integer # hence the MJD of an integer day number will always be an integer
# Re platform comparisons get_mjd returns the same value regardless of # Re platform comparisons get_mjd returns the same value regardless of
# the platform's epoch: integer days since 00:00 on 17 November 1858. # the platform's epoch: integer days since 00:00 UTC on 17 November 1858.
def get_mjd(ndays: int = 0) -> int: def get_mjd(ndays: int = 0) -> int:
secs_per_day = 86400 # 24 * 3600 secs_per_day = 86400 # 24 * 3600
days_from_epoch = now_days() + ndays # Days since platform epoch days_from_epoch = now_days() + ndays # Days since platform epoch
@ -198,6 +202,8 @@ class RiSet:
self.sglat = sin(radians(lat)) self.sglat = sin(radians(lat))
self.cglat = cos(radians(lat)) self.cglat = cos(radians(lat))
self.long = long self.long = long
if not -12 < lto < 12:
raise ValueError("Invalid local time offset.")
self.lto = round(lto * 3600) # Localtime offset in secs self.lto = round(lto * 3600) # Localtime offset in secs
self.mjd = None # Current integer MJD self.mjd = None # Current integer MJD
# Times in integer secs from midnight on current day (in local time) # Times in integer secs from midnight on current day (in local time)
@ -237,23 +243,37 @@ class RiSet:
def moonphase(self) -> float: def moonphase(self) -> float:
return self._phase return self._phase
# May need to temporarily adjust self.mjd
# def is_up(self, sun=True):
# t = ((time.time() % 86400) + self.lto) / 3600
# return sin_alt(t, sun) > 0
def set_lto(self, t): # Update the offset from UTC def set_lto(self, t): # Update the offset from UTC
if not -12 < t < 12:
raise ValueError("Invalid local time offset.")
lto = round(t * 3600) # Localtime offset in secs lto = round(t * 3600) # Localtime offset in secs
if self.lto != lto: # changed if self.lto != lto: # changed
self.lto = lto self.lto = lto
self.update(self.mjd) self.update(self.mjd)
def is_up(self, sun: bool): # Return current state of sun or moon def is_up(self, sun: bool): # Return current state of sun or moon
t = time.time() + self.lto # UTC
t %= 86400
t /= 3600 # UTC Hour of day
self.set_day() # Ensure today's date self.set_day() # Ensure today's date
return self.sin_alt(t, sun) > 0 now = round(time.time()) + self.lto # UTC
rt = self.sunrise(1) if sun else self.moonrise(1)
st = self.sunset(1) if sun else self.moonset(1)
if rt is None:
if st is None:
t = (now % 86400) / 3600 # Time as UTC hours (float)
return self.sin_alt(t, sun) > 0
return st > now
if st is None:
return rt < now
print(rt, now, st)
return rt < now < st
# This is in error by ~12 minutes: sin_alt() produces incorrect values
# unless t corresponds to an exact hour. Odd.
# def is_up(self, sun: bool):
# t = time.time() + self.lto # UTC
# t %= 86400
# t /= 3600 # UTC Hour of day
# self.set_day() # Ensure today's date
# return self.sin_alt(t, sun) > 0
# ***** API end ***** # ***** API end *****
# Re-calculate rise and set times # Re-calculate rise and set times