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.1 [Constructor](./README.md#21-constructor)
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)
4. [Utility functions](./README.md#4-utility-functions)
5. [Demo script](./README.md#5-demo-script)
@ -94,7 +95,8 @@ rise or set event.
Args (float):
* `lat=LAT` Latitude in degrees (-ve is South). Defaults are my location. :)
* `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
@ -114,6 +116,7 @@ moon, 0.5 is full. See [section 3](./README.md#3-the-moonphase-function) for
observations about this.
* `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.
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.
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=-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
@ -203,22 +227,31 @@ Code comments show times retrieved from `timeanddate.com`.
# 6. Scheduling events
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
sunrise, will perform some action at dawn and quit:
In simple cases this can be done with `asyncio`. This coroutine will execute a
payload at sunrise every day. A similar coroutine might handle sunsets.
```python
from sched.sun_moon import RiSet
import uasyncio as asyncio
import time
rs = RiSet()
async def wait_for_sunrise():
tsecs = time.time() % 86400 # Time now in secs since midnight
rs.set_day() # Ensure today's date
twait = rs.sunrise() - tsecs # Time before Sunrise
if twait > 0: # Sunrise has not yet occurred
await asyncio.sleep(twait)
# Turn the lights off, or whatever
from sched.sun_moon import RiSet
async def tomorrow(): # Wait until 1 minute past midnight
now = round(time.time())
tw = 86400 + 60 - (now % 86400) # Time from now to one minute past next midnight
await asyncio.sleep(tw)
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
after midnight. A simple solution is to use the
This code assumes that `.sunrise()` will never return `None`. At polar latitudes
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).
This may be installed with
```bash

Wyświetl plik

@ -23,6 +23,10 @@ LONG = -2.102811634540558
# (date(2000, 1, 1) - date(1970, 1, 1)).days = 10957
# 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:
secs_per_day = 86400 # 24 * 3600
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
# 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:
secs_per_day = 86400 # 24 * 3600
days_from_epoch = now_days() + ndays # Days since platform epoch
@ -198,6 +202,8 @@ class RiSet:
self.sglat = sin(radians(lat))
self.cglat = cos(radians(lat))
self.long = long
if not -12 < lto < 12:
raise ValueError("Invalid local time offset.")
self.lto = round(lto * 3600) # Localtime offset in secs
self.mjd = None # Current integer MJD
# Times in integer secs from midnight on current day (in local time)
@ -237,23 +243,37 @@ class RiSet:
def moonphase(self) -> float:
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
if not -12 < t < 12:
raise ValueError("Invalid local time offset.")
lto = round(t * 3600) # Localtime offset in secs
if self.lto != lto: # changed
self.lto = lto
self.update(self.mjd)
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
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 *****
# Re-calculate rise and set times