SolarManager unter Versionsverwaltung
Erster Stand der Hintergrundprozesse, die auf der Synology unter /volume1/homes/wagner/SolarManager laufen: der Manager selbst, die Sammler je Geraet, die MQTT-Bruecke, der Wecker und - neu hinzugezogen - der AutoAction-Runner, der als Hintergrundprozess hierher gehoert und nicht ins Web-Verzeichnis. Zugangsdaten stehen nicht mehr im Quelltext, sondern in config.ini, die nicht mit eingecheckt wird. Vorlage ist config.ini.example, gelesen wird sie von konfig.py. Betroffen waren solarManager.py (Datenbank und Wattpilot), zeit.py, gatherWaterData.py, wecker.py und skoda_testdaten.py, das sich das Passwort bisher aus dem Quelltext eines anderen Moduls herausgesucht hat. Die Kia-Anbindung ist mit dem Fahrzeug entfallen: kiaTest.py, gatherCarData.py und hyundai_kia_connect_api sind nicht mehr dabei, ebenso gatherInverterData.py, auf das nur noch eine auskommentierte Zeile zeigte. Die mitgelieferten Bibliotheken bleiben im Repository - die NAS hat kein pip, sie muessen neben den Skripten liegen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,153 @@
|
||||
import math
|
||||
import warnings
|
||||
from datetime import datetime, timedelta, time, timezone
|
||||
|
||||
|
||||
# CONSTANT
|
||||
TO_RAD = math.pi/180.0
|
||||
|
||||
|
||||
class SunTimeException(Exception):
|
||||
|
||||
def __init__(self, message):
|
||||
super(SunTimeException, self).__init__(message)
|
||||
|
||||
|
||||
class Sun:
|
||||
"""
|
||||
Approximated calculation of sunrise and sunset datetimes. Adapted from:
|
||||
https://stackoverflow.com/questions/19615350/calculate-sunrise-and-sunset-times-for-a-given-gps-coordinate-within-postgresql
|
||||
"""
|
||||
def __init__(self, lat, lon):
|
||||
self._lat = lat
|
||||
self._lon = lon
|
||||
|
||||
self.lngHour = self._lon / 15
|
||||
|
||||
def get_sunrise_time(self, at_date=datetime.now(), time_zone=timezone.utc):
|
||||
"""
|
||||
:param at_date: Reference date. datetime.now() if not provided.
|
||||
:param time_zone: pytz object with .tzinfo() or None
|
||||
:return: sunrise datetime.
|
||||
:raises: SunTimeException when there is no sunrise and sunset on given location and date.
|
||||
"""
|
||||
time_delta = self.get_sun_timedelta(at_date, time_zone=time_zone, is_rise_time=True)
|
||||
if time_delta is None:
|
||||
raise SunTimeException('The sun never rises on this location (on the specified date)')
|
||||
else:
|
||||
return datetime.combine(at_date, time(tzinfo=time_zone)) + time_delta
|
||||
|
||||
def get_sunset_time(self, at_date=datetime.now(), time_zone=timezone.utc):
|
||||
"""
|
||||
Calculate the sunset time for given date.
|
||||
:param at_date: Reference date. datetime.now() if not provided.
|
||||
:param time_zone: pytz object with .tzinfo() or None
|
||||
:return: sunset datetime.
|
||||
:raises: SunTimeException when there is no sunrise and sunset on given location and date.
|
||||
"""
|
||||
time_delta = self.get_sun_timedelta(at_date, time_zone=time_zone, is_rise_time=False)
|
||||
if time_delta is None:
|
||||
raise SunTimeException('The sun never rises on this location (on the specified date)')
|
||||
else:
|
||||
return datetime.combine(at_date, time(tzinfo=time_zone)) + time_delta
|
||||
|
||||
def get_local_sunrise_time(self, at_date=datetime.now(), time_zone=None):
|
||||
""" DEPRECATED: Use get_sunrise_time() instead. """
|
||||
warnings.warn("get_local_sunrise_time is deprecated and will be removed in future versions."
|
||||
"Use get_sunrise_time with proper time zone", DeprecationWarning)
|
||||
|
||||
return self.get_sunrise_time(at_date, time_zone)
|
||||
|
||||
def get_local_sunset_time(self, at_date=datetime.now(), time_zone=None):
|
||||
""" DEPRECATED: Use get_sunset_time() instead. """
|
||||
warnings.warn("get_local_sunset_time is deprecated and will be removed in future versions."
|
||||
"Use get_sunset_time with proper time zone.", DeprecationWarning)
|
||||
return self.get_sunset_time(at_date, time_zone)
|
||||
|
||||
def get_sun_timedelta(self, at_date, time_zone, is_rise_time=True, zenith=90.8):
|
||||
"""
|
||||
Calculate sunrise or sunset date.
|
||||
:param at_date: Reference date
|
||||
:param time_zone: pytz object with .tzinfo() or None
|
||||
:param is_rise_time: True if you want to calculate sunrise time.
|
||||
:param zenith: Sun reference zenith
|
||||
:return: timedelta showing hour, minute, and second of sunrise or sunset
|
||||
"""
|
||||
|
||||
# If not set get local timezone from datetime
|
||||
if time_zone is None:
|
||||
time_zone = datetime.now().tzinfo
|
||||
|
||||
# 1. first get the day of the year
|
||||
N = at_date.timetuple().tm_yday
|
||||
|
||||
# 2. convert the longitude to hour value and calculate an approximate time
|
||||
if is_rise_time:
|
||||
t = N + ((6 - self.lngHour) / 24)
|
||||
else: # sunset
|
||||
t = N + ((18 - self.lngHour) / 24)
|
||||
|
||||
# 3a. calculate the Sun's mean anomaly
|
||||
M = (0.9856 * t) - 3.289
|
||||
|
||||
# 3b. calculate the Sun's true longitude
|
||||
L = M + (1.916 * math.sin(TO_RAD*M)) + (0.020 * math.sin(TO_RAD * 2 * M)) + 282.634
|
||||
L = self._force_range(L, 360) # NOTE: L adjusted into the range [0,360)
|
||||
|
||||
# 4a. calculate the Sun's declination
|
||||
sinDec = 0.39782 * math.sin(TO_RAD*L)
|
||||
cosDec = math.cos(math.asin(sinDec))
|
||||
|
||||
# 4b. calculate the Sun's local hour angle
|
||||
cosH = (math.cos(TO_RAD*zenith) - (sinDec * math.sin(TO_RAD*self._lat))) / (cosDec * math.cos(TO_RAD*self._lat))
|
||||
|
||||
if cosH > 1:
|
||||
return None # The sun never rises on this location (on the specified date)
|
||||
if cosH < -1:
|
||||
return None # The sun never sets on this location (on the specified date)
|
||||
|
||||
# 4c. finish calculating H and convert into hours
|
||||
if is_rise_time:
|
||||
H = 360 - (1/TO_RAD) * math.acos(cosH)
|
||||
else: # setting
|
||||
H = (1/TO_RAD) * math.acos(cosH)
|
||||
H = H / 15
|
||||
|
||||
# 5a. calculate the Sun's right ascension
|
||||
RA = (1/TO_RAD) * math.atan(0.91764 * math.tan(TO_RAD*L))
|
||||
RA = self._force_range(RA, 360) # NOTE: RA adjusted into the range [0,360)
|
||||
|
||||
# 5b. right ascension value needs to be in the same quadrant as L
|
||||
Lquadrant = (math.floor(L/90)) * 90
|
||||
RAquadrant = (math.floor(RA/90)) * 90
|
||||
RA = RA + (Lquadrant - RAquadrant)
|
||||
|
||||
# 5c. right ascension value needs to be converted into hours
|
||||
RA = RA / 15
|
||||
|
||||
# 6. calculate local mean time of rising/setting
|
||||
T = H + RA - (0.06571 * t) - 6.622
|
||||
|
||||
# 7a. adjust back to UTC
|
||||
UT = T - self.lngHour
|
||||
|
||||
if time_zone:
|
||||
# 7b. adjust back to local time
|
||||
UT += time_zone.utcoffset(at_date).total_seconds() / 3600
|
||||
|
||||
# 7c. rounding and impose range bounds
|
||||
UT = round(UT, 2)
|
||||
if is_rise_time:
|
||||
UT = self._force_range(UT, 24)
|
||||
|
||||
# 8. return timedelta
|
||||
return timedelta(hours=UT)
|
||||
|
||||
@staticmethod
|
||||
def _force_range(v, max):
|
||||
# force v to be >= 0 and < max
|
||||
if v < 0:
|
||||
return v + max
|
||||
elif v >= max:
|
||||
return v - max
|
||||
return v
|
||||
Reference in New Issue
Block a user