kopia lustrzana https://github.com/peterhinch/micropython-samples
Add package.json and test script.
rodzic
0bc573837a
commit
8281a9cbda
|
@ -20,11 +20,51 @@ The code was ported from C/C++ as presented in "Astronomy on the Personal
|
||||||
Computer" by Montenbruck and Pfleger, with mathematical improvements contributed
|
Computer" by Montenbruck and Pfleger, with mathematical improvements contributed
|
||||||
by Raul Kompaß and Marcus Mendenhall. The sourcecode exists in the book and also
|
by Raul Kompaß and Marcus Mendenhall. The sourcecode exists in the book and also
|
||||||
on an accompanying CD-R. The file `CDR_license.txt` contains a copy of the
|
on an accompanying CD-R. The file `CDR_license.txt` contains a copy of the
|
||||||
license file on the CD-R. I am not a lawyer; I have no idea of the legal status
|
license file on the disk, which contains source, executable code, and databases.
|
||||||
of code translated from that in a published work.
|
This module (obviously) only references the source. I am not a lawyer; I have no
|
||||||
|
idea of the legal status of code translated from sourcecode in a published work.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Installation copies files from the `astronomy` directory to a directory
|
||||||
|
`\lib\sched` on the target. This is for optional use with the
|
||||||
|
[schedule module](https://github.com/peterhinch/micropython-async/blob/master/v3/docs/SCHEDULE.md).
|
||||||
|
This may be done with the official
|
||||||
|
[mpremote](https://docs.micropython.org/en/latest/reference/mpremote.html):
|
||||||
|
```bash
|
||||||
|
$ mpremote mip install "github:peterhinch/micropython-samples/astronomy"
|
||||||
|
```
|
||||||
|
On networked platforms it may alternatively be installed with
|
||||||
|
[mip](https://docs.micropython.org/en/latest/reference/packages.html).
|
||||||
|
```py
|
||||||
|
>>> mip.install("github:peterhinch/micropython-samples/astronomy")
|
||||||
|
```
|
||||||
|
Currently these tools install to `/lib` on the built-in Flash memory. To install
|
||||||
|
to a Pyboard's SD card [rshell](https://github.com/dhylands/rshell) may be used.
|
||||||
|
Move to `micropython-samples` on the PC, run `rshell` and issue:
|
||||||
|
```
|
||||||
|
> rsync astronomy /sd/sched
|
||||||
|
```
|
||||||
|
`mip` installs the following files in the `sched` directory.
|
||||||
|
* `sun_moon.py`
|
||||||
|
* `sun_moon_test.py` A test/demo script.
|
||||||
|
After installation the `RiSet` class may be accessed with
|
||||||
|
```python
|
||||||
|
from sched.sun_moon import RiSet
|
||||||
|
```
|
||||||
|
|
||||||
# The RiSet class
|
# The RiSet class
|
||||||
|
|
||||||
|
This holds the local geographic coordinates and the localtime offset relative to
|
||||||
|
UTC. It is initialised to the current date and can provide the times of rise and
|
||||||
|
set events occurring within a 24 hour window starting at 00:00:00 local time.
|
||||||
|
The `RiSet` instance's date may be changed allowing rise and set times to be
|
||||||
|
retrieved for other 24 hour windows.
|
||||||
|
|
||||||
|
Rise and set times may be retrieved in various formats including seconds from
|
||||||
|
local midnight: this may be used to enable the timing of actions relative to a
|
||||||
|
rise or set event.
|
||||||
|
|
||||||
## Constructor
|
## Constructor
|
||||||
|
|
||||||
Args (float):
|
Args (float):
|
||||||
|
@ -33,17 +73,17 @@ Args (float):
|
||||||
* `lto=0` Local time offset in hours to UTC (-ve is West).
|
* `lto=0` Local time offset in hours to UTC (-ve is West).
|
||||||
|
|
||||||
Methods:
|
Methods:
|
||||||
* `set_day(day: int = 0, relative=True)` `day` is the offset from the current
|
* `set_day(day: int = 0)` `day` is the offset in days from the current system
|
||||||
system date if `relative` is `True` otherwise it is the offset from the platform
|
date. If `day` is changed compared to the object's currently stored value its
|
||||||
epoch. If `day` is changed the rise and set times are updated.
|
rise and set times are updated. Returns the `RiSet` instance.
|
||||||
* `sunrise(variant: int = 0)` See below for details and the `variant` arg.
|
* `sunrise(variant: int = 0)` See below for details and the `variant` arg.
|
||||||
* `sunset(variant: int = 0)`
|
* `sunset(variant: int = 0)`
|
||||||
* `moonrise(variant: int = 0)`
|
* `moonrise(variant: int = 0)`
|
||||||
* `moonset(variant: int = 0)`
|
* `moonset(variant: int = 0)`
|
||||||
* `moonphase()` Return current phase as a float: 0.0 <= result < 1.0. 0.0 is new
|
* `moonphase()` Return current phase as a float: 0.0 <= result < 1.0. 0.0 is new
|
||||||
moon, 0.5 is full.
|
moon, 0.5 is full.
|
||||||
* `set_lto(t)` Update localtime offset to UTC (for daylight saving time). Rise
|
* `set_lto(t)` Set localtime offset in hours relative to UTC. Primarily intended
|
||||||
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 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
|
||||||
|
@ -66,7 +106,7 @@ r = RiSet(lat=-33.87667, long=151.21, lto=11) # Sydney 33°52′04″S 151°12
|
||||||
# The moonphase function
|
# The moonphase function
|
||||||
|
|
||||||
This is a simple function whose provenance is uncertain. I have a lunar clock
|
This is a simple function whose provenance is uncertain. I have a lunar clock
|
||||||
which uses a C version of this which has run for 14 years without issue, but I
|
which uses the original C code. This has run for 14 years without issue, but I
|
||||||
can't vouch for its absolute accuracy over long time intervals. The Montenbruck
|
can't vouch for its absolute accuracy over long time intervals. The Montenbruck
|
||||||
and Pfleger version is very much more involved but they claim accuracy over
|
and Pfleger version is very much more involved but they claim accuracy over
|
||||||
centuries.
|
centuries.
|
||||||
|
@ -79,3 +119,126 @@ Args:
|
||||||
|
|
||||||
Return value:
|
Return value:
|
||||||
A float in range 0.0 <= result < 1.0, 0 being new moon, 0.5 being full moon.
|
A float in range 0.0 <= result < 1.0, 0 being new moon, 0.5 being full moon.
|
||||||
|
|
||||||
|
# Utility functions
|
||||||
|
|
||||||
|
`now_days() -> int` Returns the current time as days since the platform epoch.
|
||||||
|
`abs_to_rel_days(days: int) -> int` Takes a number of days since the Unix epoch
|
||||||
|
(1970,1,1) and returns a number of days relative to the current date. Platform
|
||||||
|
independent. This facilitates testing with pre-determined target dates.
|
||||||
|
|
||||||
|
# Demo script
|
||||||
|
|
||||||
|
This produces output for the fixed date 4th Dec 2023 at three geographical
|
||||||
|
locations. It can therefore be run on platforms where the system time is wrong.
|
||||||
|
To run issue:
|
||||||
|
```python
|
||||||
|
import sched.sun_moon_test
|
||||||
|
```
|
||||||
|
Expected output:
|
||||||
|
```python
|
||||||
|
>>> import sched.sun_moon_test
|
||||||
|
4th Dec 2023: Seattle UTC-8
|
||||||
|
Sun rise 07:40:09 set 16:18:15
|
||||||
|
Moon rise 23:38:11 set 12:53:40
|
||||||
|
|
||||||
|
4th Dec 2023: Sydney UTC+11
|
||||||
|
Sun rise 05:36:24 set 19:53:21
|
||||||
|
Moon rise 00:45:55 set 11:27:14
|
||||||
|
|
||||||
|
From 4th Dec 2023: UK, UTC
|
||||||
|
Day: 0
|
||||||
|
Sun rise 08:04:34 set 15:52:13
|
||||||
|
Moon rise 23:03:15 set 13:01:04
|
||||||
|
Day: 1
|
||||||
|
Sun rise 08:05:54 set 15:51:42
|
||||||
|
Moon rise --:--:-- set 13:10:35
|
||||||
|
Day: 2
|
||||||
|
Sun rise 08:07:13 set 15:51:13
|
||||||
|
Moon rise 00:14:40 set 13:18:59
|
||||||
|
Day: 3
|
||||||
|
Sun rise 08:08:28 set 15:50:49
|
||||||
|
Moon rise 01:27:12 set 13:27:08
|
||||||
|
Day: 4
|
||||||
|
Sun rise 08:09:42 set 15:50:28
|
||||||
|
Moon rise 02:40:34 set 13:35:56
|
||||||
|
Day: 5
|
||||||
|
Sun rise 08:10:53 set 15:50:10
|
||||||
|
Moon rise 03:56:44 set 13:46:27
|
||||||
|
Day: 6
|
||||||
|
Sun rise 08:12:01 set 15:49:56
|
||||||
|
Moon rise 05:18:32 set 14:00:11
|
||||||
|
>>>
|
||||||
|
```
|
||||||
|
Code comments show times retrieved from `timeanddate.com`.
|
||||||
|
|
||||||
|
# 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`.
|
||||||
|
```python
|
||||||
|
from sched.sun_moon import RiSet
|
||||||
|
import time
|
||||||
|
rs = RiSet()
|
||||||
|
tsecs = time.time() # Time now in secs since epoch
|
||||||
|
tsecs -= tsecs % 86400 # Last midnight in secs since epoch
|
||||||
|
tmidnight = tsecs
|
||||||
|
async def do_sunrise():
|
||||||
|
while True:
|
||||||
|
toff = time.time() - tmidnight # Seconds relative to midnight
|
||||||
|
if toff > 0: # Midnight has passed, wait for sunrise
|
||||||
|
twait = rs.sunrise() - toff # Assumes a latitude where sun must rise
|
||||||
|
else: # Wait for tomorrow
|
||||||
|
twait = tmidnight + 86400 + toff
|
||||||
|
await asyncio.sleep(twait)
|
||||||
|
if toff > 0:
|
||||||
|
# Turn the lights off, or whatever
|
||||||
|
```
|
||||||
|
An alternative, particularly suited to more complex cases, is to use the
|
||||||
|
[schedule module](https://github.com/peterhinch/micropython-async/blob/master/v3/docs/SCHEDULE.md).
|
||||||
|
This allows more intuitive coding without the epoch calculations. The following
|
||||||
|
is a minimal example:
|
||||||
|
```python
|
||||||
|
import uasyncio as asyncio
|
||||||
|
from sched.sched import schedule
|
||||||
|
from sched.sun_moon import RiSet
|
||||||
|
|
||||||
|
async def turn_off_lights(rs): # Runs at 00:01:00
|
||||||
|
rs.set_day() # Re-calculate for new daylight
|
||||||
|
asyncio.sleep(rs.sunrise() - 60)
|
||||||
|
# Actually turn them off
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
rs = RiSet() # May need args for your location
|
||||||
|
await schedule(turn_off_lights, rs, hrs=0, mins=1) # Never terminates
|
||||||
|
|
||||||
|
try:
|
||||||
|
asyncio.run(main())
|
||||||
|
finally:
|
||||||
|
_ = asyncio.new_event_loop()
|
||||||
|
```
|
||||||
|
This approach lends itself to additional triggers and events:
|
||||||
|
```python
|
||||||
|
import uasyncio as asyncio
|
||||||
|
from sched.sched import schedule, Sequence
|
||||||
|
from sched.sun_moon import RiSet
|
||||||
|
|
||||||
|
async def turn_off_lights(t):
|
||||||
|
asyncio.sleep(t)
|
||||||
|
# Actually turn them off
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
rs = RiSet() # May need args for your location
|
||||||
|
seq = Sequence() # A Sequence comprises one or more schedule instances
|
||||||
|
asyncio.create_task(schedule(seq, "off", hrs=0, mins=1))
|
||||||
|
# Can schedule other events here
|
||||||
|
async for args in seq:
|
||||||
|
if args[0] == "off": # Triggered at 00:01 hrs (there might be other triggers)
|
||||||
|
rs.set_day() # Re-calculate for new day
|
||||||
|
asyncio.create_task(turn_off_lights(rs.sunrise() - 60))
|
||||||
|
|
||||||
|
try:
|
||||||
|
asyncio.run(main())
|
||||||
|
finally:
|
||||||
|
_ = asyncio.new_event_loop()
|
||||||
|
```
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"urls": [
|
||||||
|
["sched/sun_moon.py", "github:peterhinch/micropython-samples/astronomy/sun_moon.py"],
|
||||||
|
["sched/sun_moon_test.py", "github:peterhinch/micropython-samples/astronomy/sun_moon_test.py"]
|
||||||
|
],
|
||||||
|
"version": "0.1"
|
||||||
|
}
|
|
@ -14,6 +14,30 @@ from math import sin, cos, sqrt, fabs, atan, radians, floor
|
||||||
LAT = 53.29756504536339 # Local defaults
|
LAT = 53.29756504536339 # Local defaults
|
||||||
LONG = -2.102811634540558
|
LONG = -2.102811634540558
|
||||||
|
|
||||||
|
# MicroPython wanton epochs:
|
||||||
|
# time.gmtime(0)[0] = 1970 or 2000 depending on platform.
|
||||||
|
# On CPython:
|
||||||
|
# (date(2000, 1, 1) - date(1970, 1, 1)).days * 24*60*60 = 946684800
|
||||||
|
# (date(2000, 1, 1) - date(1970, 1, 1)).days = 10957
|
||||||
|
|
||||||
|
# Return time now in days relative to platform epoch.
|
||||||
|
def now_days() -> int:
|
||||||
|
secs_per_day = 86400 # 24 * 3600
|
||||||
|
t = time.time()
|
||||||
|
t -= t % secs_per_day # Previous Midnight
|
||||||
|
return round(t / secs_per_day) # Days since datum
|
||||||
|
|
||||||
|
|
||||||
|
# Convert number of days relative to the Unix epoch (1970,1,1) to a number of
|
||||||
|
# days relative to the current date. e.g. 19695 = 4th Dec 2023
|
||||||
|
# Platform independent.
|
||||||
|
def abs_to_rel_days(days: int) -> int:
|
||||||
|
secs_per_day = 86400 # 24 * 3600
|
||||||
|
now = now_days() # Days since platform epoch
|
||||||
|
if time.gmtime(0)[0] == 2000: # Machine epoch
|
||||||
|
now += 10957
|
||||||
|
return days - now
|
||||||
|
|
||||||
|
|
||||||
def quad(ym, yz, yp):
|
def quad(ym, yz, yp):
|
||||||
# See Astronomy on the PC P48-49, plus contribution from Marcus Mendenhall
|
# See Astronomy on the PC P48-49, plus contribution from Marcus Mendenhall
|
||||||
|
@ -45,26 +69,15 @@ def quad(ym, yz, yp):
|
||||||
|
|
||||||
# **** GET MODIFIED JULIAN DATE FOR DAY RELATIVE TO TODAY ****
|
# **** GET MODIFIED JULIAN DATE FOR DAY RELATIVE TO TODAY ****
|
||||||
|
|
||||||
# Takes the system time in seconds from 1 Jan 70 & returns
|
# Returns modified julian day number defined as mjd = jd - 2400000.5
|
||||||
# modified julian day number defined as mjd = jd - 2400000.5
|
|
||||||
# Deals only in integer MJD's: the JD of just after midnight will always end in 0.5
|
# Deals only in integer MJD's: the JD of just after midnight will always end in 0.5
|
||||||
# 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
|
||||||
|
|
||||||
# MicroPython wanton epochs:
|
|
||||||
# time.gmtime(0)[0] = 1970 or 2000 depending on platform.
|
|
||||||
# (date(2000, 1, 1) - date(1970, 1, 1)).days * 24*60*60 = 946684800
|
|
||||||
# (date(2000, 1, 1) - date(1970, 1, 1)).days = 10957
|
|
||||||
|
|
||||||
# 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 on 17 November 1858.
|
||||||
def get_mjd(ndays: int = 0) -> int: # Days offset from today
|
def get_mjd(ndays: int = 0) -> int:
|
||||||
secs_per_day = 86400 # 24 * 3600
|
secs_per_day = 86400 # 24 * 3600
|
||||||
tsecs = time.time() # Time now in secs since epoch
|
days_from_epoch = now_days() + ndays # Days since platform epoch
|
||||||
tsecs -= tsecs % secs_per_day # Time last midnight
|
|
||||||
tsecs += secs_per_day * ndays # Specified day
|
|
||||||
days_from_epoch = round(tsecs / secs_per_day) # Days from 1 Jan 70
|
|
||||||
# tsecs += secs_per_day # 2 # Noon
|
|
||||||
# mjepoch = -10957.5 # 40587 - 51544.5 # Modified Julian date of C epoch (1 Jan 70)
|
|
||||||
mjepoch = 40587 # Modified Julian date of C epoch (1 Jan 70)
|
mjepoch = 40587 # Modified Julian date of C epoch (1 Jan 70)
|
||||||
if time.gmtime(0)[0] == 2000:
|
if time.gmtime(0)[0] == 2000:
|
||||||
mjepoch += 10957
|
mjepoch += 10957
|
||||||
|
@ -196,7 +209,7 @@ class RiSet:
|
||||||
# Examine Julian dates either side of current one to cope with localtime.
|
# Examine Julian dates either side of current one to cope with localtime.
|
||||||
# TODO: Allow localtime offset to be varied at runtime for DST.
|
# TODO: Allow localtime offset to be varied at runtime for DST.
|
||||||
# TODO: relative=True arg for set_day. Allows entering absolute dates e.g. for testing.
|
# TODO: relative=True arg for set_day. Allows entering absolute dates e.g. for testing.
|
||||||
def set_day(self, day: int = 0, relative: bool = True):
|
def set_day(self, day: int = 0):
|
||||||
mjd = get_mjd(day)
|
mjd = get_mjd(day)
|
||||||
if self.mjd is None or self.mjd != mjd:
|
if self.mjd is None or self.mjd != mjd:
|
||||||
spd = 86400 # Secs per day
|
spd = 86400 # Secs per day
|
||||||
|
@ -226,7 +239,10 @@ class RiSet:
|
||||||
return self._phase
|
return self._phase
|
||||||
|
|
||||||
def set_lto(self, t): # Update the offset from UTC
|
def set_lto(self, t): # Update the offset from UTC
|
||||||
pass # TODO
|
lto = round(t * 3600) # Localtime offset in secs
|
||||||
|
if self.lto != lto: # changed
|
||||||
|
self.lto = lto
|
||||||
|
self.update(self.mjd)
|
||||||
|
|
||||||
# ***** API end *****
|
# ***** API end *****
|
||||||
# Re-calculate rise and set times
|
# Re-calculate rise and set times
|
||||||
|
@ -328,21 +344,3 @@ class RiSet:
|
||||||
if t_rise is not None and t_set is not None:
|
if t_rise is not None and t_set is not None:
|
||||||
break # All done
|
break # All done
|
||||||
return to_int(t_rise), to_int(t_set) # Convert to int preserving None values
|
return to_int(t_rise), to_int(t_set) # Convert to int preserving None values
|
||||||
|
|
||||||
|
|
||||||
# r = RiSet()
|
|
||||||
# r = RiSet(lat=47.61, long=-122.35, lto=-8) # Seattle 47°36′35″N 122°19′59″W
|
|
||||||
r = RiSet(lat=-33.86, long=151.21, lto=11) # Sydney 33°52′04″S 151°12′36″E
|
|
||||||
# t = time.ticks_us()
|
|
||||||
# r.set_day()
|
|
||||||
# print("Elapsed us", time.ticks_diff(time.ticks_us(), t))
|
|
||||||
for d in range(7):
|
|
||||||
print(f"Day {d}")
|
|
||||||
r.set_day(d)
|
|
||||||
print(f"Sun rise {r.sunrise(3)} set {r.sunset(3)}")
|
|
||||||
print(f"Moon rise {r.moonrise(3)} set {r.moonset(3)}")
|
|
||||||
|
|
||||||
print(r.set_day().sunrise(0))
|
|
||||||
# for d in range(30):
|
|
||||||
# r.set_day(d)
|
|
||||||
# print(round(r.moonphase() * 1000))
|
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
# sun_moon_test.py
|
||||||
|
|
||||||
|
|
||||||
|
from .sun_moon import RiSet, abs_to_rel_days
|
||||||
|
|
||||||
|
|
||||||
|
def show(rs):
|
||||||
|
print(f"Sun rise {rs.sunrise(3)} set {rs.sunset(3)}")
|
||||||
|
print(f"Moon rise {rs.moonrise(3)} set {rs.moonset(3)}")
|
||||||
|
|
||||||
|
|
||||||
|
print("4th Dec 2023: Seattle UTC-8")
|
||||||
|
rs = RiSet(lat=47.61, long=-122.35, lto=-8) # Seattle 47°36′35″N 122°19′59″W
|
||||||
|
rs.set_day(abs_to_rel_days(19695)) # 4th Dec 2023
|
||||||
|
show(rs)
|
||||||
|
print()
|
||||||
|
|
||||||
|
print("4th Dec 2023: Sydney UTC+11")
|
||||||
|
rs = RiSet(lat=-33.86, long=151.21, lto=11) # Sydney 33°52′04″S 151°12′36″E
|
||||||
|
rs.set_day(abs_to_rel_days(19695)) # 4th Dec 2023
|
||||||
|
show(rs)
|
||||||
|
print()
|
||||||
|
|
||||||
|
print("From 4th Dec 2023: UK, UTC")
|
||||||
|
rs = RiSet()
|
||||||
|
for day in range(7):
|
||||||
|
rs.set_day(abs_to_rel_days(19695 + day)) # Start 4th Dec 2023
|
||||||
|
print(f"Day: {day}")
|
||||||
|
show(rs)
|
||||||
|
|
||||||
|
# Times from timeanddate.com
|
||||||
|
# Seattle
|
||||||
|
# Sunrise 7:40 sunset 16:18 Moonrise 23:37 Moonset 12:53
|
||||||
|
# Sydney
|
||||||
|
# Sunrise 5:37 sunset 19:53 Moonrise 00:45 Moonset 11:28
|
||||||
|
# UK
|
||||||
|
# Sunrise 8:04 sunset 15:52 Moonrise 23:02 Moonset 13:01
|
Ładowanie…
Reference in New Issue