Geocentric lunar ephemerides from a Unix timestamp: the Moon's ecliptic position, distance, and phase.
This is a Python port of msts, the OCaml library, rewritten for MicroPython on resource-constrained microcontrollers. It implements the same algorithm with the same accuracy bounds. Use this version on-device, and the OCaml original wherever a full OCaml toolchain is available.
The implementation follows Paul Schlyter's algorithm: Keplerian orbital elements propagated from a fixed epoch with empirical perturbation corrections.
The library depends only on the standard math module. It performs no
I/O, reads no files, keeps no global state, and is fully deterministic.
compute is total for finite inputs; invalid arguments raise
ValueError naming the offending parameter.
Results are validated against JPL Horizons (DE441) over the interval 1900-01-01 through 2100-12-31.
Guaranteed error bounds:
- 2° in ecliptic longitude
- 0.5° in ecliptic latitude
- 1% in distance
- 0.02 in illuminated fraction
Typical errors are substantially smaller.
- MicroPython, or
- CPython 3
Only the standard math module is required.
Either:
- Install
micropython-mstsfrom Tools → Manage packages… in Thonny, or - Install directly from this repo, which doesn't depend on PyPI's package-search index:
mpremote mip install https://codeberg.org/duras/msts-micropython/raw/branch/main/- Or copy
msts.pyonto the target device by hand, for example:
mpremote cp msts.py :Or simply place it alongside your own modules.
Install from PyPI:
pip install micropython-mstsimport msts
# J2000.0 = 2000-01-01T12:00:00Z
e = msts.compute(946728000.0)
print("%s %.1f%% %.0f km" % (
msts.phase_name_to_string(e.phase.name),
e.phase.illuminated_fraction * 100.0,
e.position.distance_km,
))compute takes a Unix timestamp and returns a small result structure. It
performs no I/O and allocates only three small objects, making it
inexpensive enough to call on every wake cycle of a battery-powered
device.
Some applications:
- Clock or watch firmware — moon-phase complications for MicroPython-based clocks and smartwatches.
- E-ink or OLED displays — ESP32 or RP2040 boards that wake on a timer, compute the current phase, and update a display without network access.
- Garden automation — irrigation or planting reminders keyed to the lunar phase alongside existing sensor data.
- Outdoor lighting — dim or disable solar- or LoRa-powered lighting near the full moon when ambient light is already higher.
- Education — classroom demonstrations of basic orbital mechanics by varying the timestamp and observing the resulting position and phase.
- Astrophotography helpers — approximate moon phase and brightness for scheduling. Not suitable where precise rise/set times or arcsecond-level accuracy are required.
phase_nameis represented by small integer constants (msts.NEW_MOON,msts.WAXING_CRESCENT, ...) instead of an OCaml variant type or Pythonenum, since MicroPython has no standardenummodule.Invalid_argumentbecomesValueError.- Field names, function names, formulas, constants, and documented accuracy bounds otherwise match the OCaml implementation one-for-one.
ISC. See LICENSE.