MotionEyeOS Day and Night Python script: Difference between revisions
From WickyWiki
mNo edit summary |
mNo edit summary |
||
| Line 4: | Line 4: | ||
[[Category:201903]] | [[Category:201903]] | ||
See also | See also: | ||
* [[Calculation of sunrise and sunset]] | * [[Calculation of sunrise and sunset]] | ||
Source | Source: | ||
* https://blog.ligos.net/2016-04-18/Day-Night-Cycle-For-MotionEye.html | * https://blog.ligos.net/2016-04-18/Day-Night-Cycle-For-MotionEye.html | ||
<syntaxhighlight lang=python> | <syntaxhighlight lang=python> | ||
# | from math import cos,sin,acos,asin,tan | ||
from math import degrees as deg, radians as rad | |||
from datetime import date,datetime,time | |||
from pytz import timezone | |||
import os | |||
import os.path | |||
import shutil | |||
import filecmp | |||
# variant of time zone available on MotionEyeOS | |||
localTimezone = timezone('Europe/Amsterdam') | |||
# Location of here in lat / long (consult your favourite maps app to determine this) | |||
localLat = 51.5 | |||
localLong = 5.5 | |||
# Sunrise and sunset fudge factor (in minutes) | |||
# Positive minutes is later, negative minutes is earlier. | |||
sunriseFudgeMinutes = -20 | |||
sunsetFudgeMinutes = 20 | |||
# Current state of day / night is stored in this file. | |||
scriptDir = '/data/script_daynight/' | |||
dayAndNightStateFile = scriptDir + 'daynight.dat' | |||
class sun: | |||
""" | |||
Calculate sunrise and sunset based on equations from NOAA | |||
http://www.srrb.noaa.gov/highlights/sunrise/calcdetails.html | |||
""" | |||
def __init__(self,lat=52.37,long=4.90): # default Amsterdam | |||
self.lat=lat | |||
self.long=long | |||
def sunrise(self,when=None): | |||
""" | |||
return the time of sunrise as a datetime.time object | |||
when is a datetime.datetime object. If none is given | |||
a local time zone is assumed (including daylight saving | |||
if present) | |||
""" | |||
if when is None : when = datetime.now(tz=localTimezone) | |||
self.__preptime(when) | |||
self.__calc() | |||
return sun.timefromdecimalday(self.sunrise_t) | |||
def sunset(self,when=None): | |||
if when is None : when = datetime.now(tz=localTimezone) | |||
self.__preptime(when) | |||
self.__calc() | |||
return sun.timefromdecimalday(self.sunset_t) | |||
def solarnoon(self,when=None): | |||
if when is None : when = datetime.now(tz=localTimezone) | |||
self.__preptime(when) | |||
self.__calc() | |||
return sun.timefromdecimalday(self.solarnoon_t) | |||
""" | |||
The two fudge factors are in minutes. Negative is earlier. | |||
""" | |||
def dayornight(self,now=None,sunriseFudge=0,sunsetFudge=0): | |||
if now is None : now = datetime.now(tz=localTimezone) | |||
self.__preptime(when=now) | |||
self.__calc() | |||
sunriseFudge = sunriseFudge/60.0/24.0 | |||
sunsetFudge = sunsetFudge/60.0/24.0 | |||
if (self.time >= (self.sunrise_t + sunriseFudge) and self.time <= (self.sunset_t + sunsetFudge)): | |||
return 'day' | |||
else: | |||
return 'night' | |||
@staticmethod | |||
def timefromdecimalday(day): | |||
""" | |||
returns a datetime.time object. | |||
day is a decimal day between 0.0 and 1.0, e.g. noon = 0.5 | |||
""" | |||
hours = 24.0*day | |||
h = int(hours) | |||
minutes= (hours-h)*60 | |||
m = int(minutes) | |||
seconds= (minutes-m)*60 | |||
s = int(seconds) | |||
return time(hour=h,minute=m,second=s) | |||
def __preptime(self,when): | |||
""" | |||
Extract information in a suitable format from when, | |||
a datetime.datetime object. | |||
""" | |||
# datetime days are numbered in the Gregorian calendar | |||
# while the calculations from NOAA are distibuted as | |||
# OpenOffice spreadsheets with days numbered from | |||
# 1/1/1900. The difference are those numbers taken for | |||
# 18/12/2010 | |||
self.day = when.toordinal()-(734124-40529) | |||
t=when.time() | |||
self.time= (t.hour + t.minute/60.0 + t.second/3600.0)/24.0 | |||
self.timezone=0 | |||
offset=when.utcoffset() | |||
if not offset is None: | |||
self.timezone=offset.seconds/3600.0 | |||
def __calc(self): | |||
""" | |||
Perform the actual calculations for sunrise, sunset and | |||
a number of related quantities. | |||
The results are stored in the instance variables | |||
sunrise_t, sunset_t and solarnoon_t | |||
""" | |||
timezone = self.timezone # in hours, east is positive | |||
longitude= self.long # in decimal degrees, east is positive | |||
latitude = self.lat # in decimal degrees, north is positive | |||
time = self.time # percentage past midnight, i.e. noon is 0.5 | |||
day = self.day # daynumber 1=1/1/1900 | |||
Jday = day+2415018.5+time-timezone/24 # Julian day | |||
Jcent = (Jday-2451545)/36525 # Julian century | |||
Manom = 357.52911+Jcent*(35999.05029-0.0001537*Jcent) | |||
Mlong = 280.46646+Jcent*(36000.76983+Jcent*0.0003032)%360 | |||
Eccent = 0.016708634-Jcent*(0.000042037+0.0001537*Jcent) | |||
Mobliq = 23+(26+((21.448-Jcent*(46.815+Jcent*(0.00059-Jcent*0.001813))))/60)/60 | |||
obliq = Mobliq+0.00256*cos(rad(125.04-1934.136*Jcent)) | |||
vary = tan(rad(obliq/2))*tan(rad(obliq/2)) | |||
Seqcent = sin(rad(Manom))*(1.914602-Jcent*(0.004817+0.000014*Jcent))+sin(rad(2*Manom))*(0.019993-0.000101*Jcent)+sin(rad(3*Manom))*0.000289 | |||
Struelong= Mlong+Seqcent | |||
Sapplong = Struelong-0.00569-0.00478*sin(rad(125.04-1934.136*Jcent)) | |||
declination = deg(asin(sin(rad(obliq))*sin(rad(Sapplong)))) | |||
eqtime = 4*deg(vary*sin(2*rad(Mlong))-2*Eccent*sin(rad(Manom))+4*Eccent*vary*sin(rad(Manom))*cos(2*rad(Mlong))-0.5*vary*vary*sin(4*rad(Mlong))-1.25*Eccent*Eccent*sin(2*rad(Manom))) | |||
hourangle= deg(acos(cos(rad(90.833))/(cos(rad(latitude))*cos(rad(declination)))-tan(rad(latitude))*tan(rad(declination)))) | |||
self.solarnoon_t=(720-4*longitude-eqtime+timezone*60)/1440 | |||
self.sunrise_t =self.solarnoon_t-hourangle*4/1440 | |||
self.sunset_t =self.solarnoon_t+hourangle*4/1440 | |||
def readLineOfFile(path): | |||
if not os.path.isfile(path): return "" | |||
f = open(path, "r") | |||
result = f.readline() | |||
f.close() | |||
return result.rstrip() | |||
def updateContentsOfFile(path, data): | |||
f = open(path, "w") | |||
f.write(data) | |||
f.close() | |||
if __name__ == "__main__": | |||
s=sun(lat=localLat,long=localLong) | |||
# Calculate | |||
s.solarnoon() | |||
# Debug stuff | |||
##print('today', datetime.today()) | |||
##print('sunrise/noon/sunset', s.sunrise(), s.solarnoon(), s.sunset()) | |||
# Track day / night state in file. | |||
fileDayOrNight = readLineOfFile(dayAndNightStateFile) | |||
#log | |||
print(datetime.now().strftime('%Y-%m-%d_%H.%M.%S ') | |||
+ 'Sunrise: ' + str(sun.timefromdecimalday(s.sunrise_t)) + [' ', ' +'][sunriseFudgeMinutes > 0] + str(sunriseFudgeMinutes) | |||
+ ', sunset: ' + str(sun.timefromdecimalday(s.sunset_t )) + [' ', ' +'][sunsetFudgeMinutes > 0] + str(sunsetFudgeMinutes) | |||
+ ', state: ' + fileDayOrNight | |||
) | |||
# Determine current day / night state based on current time of day. | |||
currentDayOrNight = s.dayornight(sunriseFudge=sunriseFudgeMinutes,sunsetFudge=sunsetFudgeMinutes) | |||
if fileDayOrNight != currentDayOrNight: | |||
print(datetime.now().strftime('%Y-%m-%d_%H:%M:%S ') + 'Changing state to: ' + currentDayOrNight) | |||
# backup the old settings if the selected config was updated | |||
if not filecmp.cmp("/data/etc/thread-1.conf", scriptDir + "thread-1.conf." + fileDayOrNight): | |||
# backup old day/night file | |||
shutil.move(scriptDir + "thread-1.conf." + fileDayOrNight | |||
, scriptDir + "thread-1.conf." + fileDayOrNight + "." + datetime.now().strftime('%Y-%m-%d_%H.%M')) | |||
# select other day/night file | |||
shutil.copyfile("/data/etc/thread-1.conf", scriptDir + "thread-1.conf." + fileDayOrNight) | |||
# select the other day/night file | |||
shutil.copyfile(scriptDir + "thread-1.conf." + currentDayOrNight, "/data/etc/thread-1.conf") | |||
print(datetime.now().strftime('%Y-%m-%d_%H:%M:%S ') + 'Restart motionEye...') | |||
os.system("/etc/init.d/S85motioneye restart") | |||
print(datetime.now().strftime('%Y-%m-%d_%H:%M:%S ') + 'MotionEye restarted.') | |||
# Update the file day / night state. | |||
updateContentsOfFile(dayAndNightStateFile, currentDayOrNight) | |||
</syntaxhighlight> | </syntaxhighlight> | ||
Setup crontab to execute the script (here every 10 minutes) | |||
<syntaxhighlight> | |||
crontab -e | |||
</syntaxhighlight> | |||
02,12,22,32,42,52 * * * * /usr/bin/python /data/script_daynight/daynight.py >> /data/script_daynight/daynight.log | |||
Setup [[How To Manage Logfiles with Logrotate|logrotate]] to limit the size of the logfile. | |||
Revision as of 17:32, 5 March 2019
See also:
Source:
from math import cos,sin,acos,asin,tan
from math import degrees as deg, radians as rad
from datetime import date,datetime,time
from pytz import timezone
import os
import os.path
import shutil
import filecmp
# variant of time zone available on MotionEyeOS
localTimezone = timezone('Europe/Amsterdam')
# Location of here in lat / long (consult your favourite maps app to determine this)
localLat = 51.5
localLong = 5.5
# Sunrise and sunset fudge factor (in minutes)
# Positive minutes is later, negative minutes is earlier.
sunriseFudgeMinutes = -20
sunsetFudgeMinutes = 20
# Current state of day / night is stored in this file.
scriptDir = '/data/script_daynight/'
dayAndNightStateFile = scriptDir + 'daynight.dat'
class sun:
"""
Calculate sunrise and sunset based on equations from NOAA
http://www.srrb.noaa.gov/highlights/sunrise/calcdetails.html
"""
def __init__(self,lat=52.37,long=4.90): # default Amsterdam
self.lat=lat
self.long=long
def sunrise(self,when=None):
"""
return the time of sunrise as a datetime.time object
when is a datetime.datetime object. If none is given
a local time zone is assumed (including daylight saving
if present)
"""
if when is None : when = datetime.now(tz=localTimezone)
self.__preptime(when)
self.__calc()
return sun.timefromdecimalday(self.sunrise_t)
def sunset(self,when=None):
if when is None : when = datetime.now(tz=localTimezone)
self.__preptime(when)
self.__calc()
return sun.timefromdecimalday(self.sunset_t)
def solarnoon(self,when=None):
if when is None : when = datetime.now(tz=localTimezone)
self.__preptime(when)
self.__calc()
return sun.timefromdecimalday(self.solarnoon_t)
"""
The two fudge factors are in minutes. Negative is earlier.
"""
def dayornight(self,now=None,sunriseFudge=0,sunsetFudge=0):
if now is None : now = datetime.now(tz=localTimezone)
self.__preptime(when=now)
self.__calc()
sunriseFudge = sunriseFudge/60.0/24.0
sunsetFudge = sunsetFudge/60.0/24.0
if (self.time >= (self.sunrise_t + sunriseFudge) and self.time <= (self.sunset_t + sunsetFudge)):
return 'day'
else:
return 'night'
@staticmethod
def timefromdecimalday(day):
"""
returns a datetime.time object.
day is a decimal day between 0.0 and 1.0, e.g. noon = 0.5
"""
hours = 24.0*day
h = int(hours)
minutes= (hours-h)*60
m = int(minutes)
seconds= (minutes-m)*60
s = int(seconds)
return time(hour=h,minute=m,second=s)
def __preptime(self,when):
"""
Extract information in a suitable format from when,
a datetime.datetime object.
"""
# datetime days are numbered in the Gregorian calendar
# while the calculations from NOAA are distibuted as
# OpenOffice spreadsheets with days numbered from
# 1/1/1900. The difference are those numbers taken for
# 18/12/2010
self.day = when.toordinal()-(734124-40529)
t=when.time()
self.time= (t.hour + t.minute/60.0 + t.second/3600.0)/24.0
self.timezone=0
offset=when.utcoffset()
if not offset is None:
self.timezone=offset.seconds/3600.0
def __calc(self):
"""
Perform the actual calculations for sunrise, sunset and
a number of related quantities.
The results are stored in the instance variables
sunrise_t, sunset_t and solarnoon_t
"""
timezone = self.timezone # in hours, east is positive
longitude= self.long # in decimal degrees, east is positive
latitude = self.lat # in decimal degrees, north is positive
time = self.time # percentage past midnight, i.e. noon is 0.5
day = self.day # daynumber 1=1/1/1900
Jday = day+2415018.5+time-timezone/24 # Julian day
Jcent = (Jday-2451545)/36525 # Julian century
Manom = 357.52911+Jcent*(35999.05029-0.0001537*Jcent)
Mlong = 280.46646+Jcent*(36000.76983+Jcent*0.0003032)%360
Eccent = 0.016708634-Jcent*(0.000042037+0.0001537*Jcent)
Mobliq = 23+(26+((21.448-Jcent*(46.815+Jcent*(0.00059-Jcent*0.001813))))/60)/60
obliq = Mobliq+0.00256*cos(rad(125.04-1934.136*Jcent))
vary = tan(rad(obliq/2))*tan(rad(obliq/2))
Seqcent = sin(rad(Manom))*(1.914602-Jcent*(0.004817+0.000014*Jcent))+sin(rad(2*Manom))*(0.019993-0.000101*Jcent)+sin(rad(3*Manom))*0.000289
Struelong= Mlong+Seqcent
Sapplong = Struelong-0.00569-0.00478*sin(rad(125.04-1934.136*Jcent))
declination = deg(asin(sin(rad(obliq))*sin(rad(Sapplong))))
eqtime = 4*deg(vary*sin(2*rad(Mlong))-2*Eccent*sin(rad(Manom))+4*Eccent*vary*sin(rad(Manom))*cos(2*rad(Mlong))-0.5*vary*vary*sin(4*rad(Mlong))-1.25*Eccent*Eccent*sin(2*rad(Manom)))
hourangle= deg(acos(cos(rad(90.833))/(cos(rad(latitude))*cos(rad(declination)))-tan(rad(latitude))*tan(rad(declination))))
self.solarnoon_t=(720-4*longitude-eqtime+timezone*60)/1440
self.sunrise_t =self.solarnoon_t-hourangle*4/1440
self.sunset_t =self.solarnoon_t+hourangle*4/1440
def readLineOfFile(path):
if not os.path.isfile(path): return ""
f = open(path, "r")
result = f.readline()
f.close()
return result.rstrip()
def updateContentsOfFile(path, data):
f = open(path, "w")
f.write(data)
f.close()
if __name__ == "__main__":
s=sun(lat=localLat,long=localLong)
# Calculate
s.solarnoon()
# Debug stuff
##print('today', datetime.today())
##print('sunrise/noon/sunset', s.sunrise(), s.solarnoon(), s.sunset())
# Track day / night state in file.
fileDayOrNight = readLineOfFile(dayAndNightStateFile)
#log
print(datetime.now().strftime('%Y-%m-%d_%H.%M.%S ')
+ 'Sunrise: ' + str(sun.timefromdecimalday(s.sunrise_t)) + [' ', ' +'][sunriseFudgeMinutes > 0] + str(sunriseFudgeMinutes)
+ ', sunset: ' + str(sun.timefromdecimalday(s.sunset_t )) + [' ', ' +'][sunsetFudgeMinutes > 0] + str(sunsetFudgeMinutes)
+ ', state: ' + fileDayOrNight
)
# Determine current day / night state based on current time of day.
currentDayOrNight = s.dayornight(sunriseFudge=sunriseFudgeMinutes,sunsetFudge=sunsetFudgeMinutes)
if fileDayOrNight != currentDayOrNight:
print(datetime.now().strftime('%Y-%m-%d_%H:%M:%S ') + 'Changing state to: ' + currentDayOrNight)
# backup the old settings if the selected config was updated
if not filecmp.cmp("/data/etc/thread-1.conf", scriptDir + "thread-1.conf." + fileDayOrNight):
# backup old day/night file
shutil.move(scriptDir + "thread-1.conf." + fileDayOrNight
, scriptDir + "thread-1.conf." + fileDayOrNight + "." + datetime.now().strftime('%Y-%m-%d_%H.%M'))
# select other day/night file
shutil.copyfile("/data/etc/thread-1.conf", scriptDir + "thread-1.conf." + fileDayOrNight)
# select the other day/night file
shutil.copyfile(scriptDir + "thread-1.conf." + currentDayOrNight, "/data/etc/thread-1.conf")
print(datetime.now().strftime('%Y-%m-%d_%H:%M:%S ') + 'Restart motionEye...')
os.system("/etc/init.d/S85motioneye restart")
print(datetime.now().strftime('%Y-%m-%d_%H:%M:%S ') + 'MotionEye restarted.')
# Update the file day / night state.
updateContentsOfFile(dayAndNightStateFile, currentDayOrNight)
Setup crontab to execute the script (here every 10 minutes)
crontab -e
02,12,22,32,42,52 * * * * /usr/bin/python /data/script_daynight/daynight.py >> /data/script_daynight/daynight.log
Setup logrotate to limit the size of the logfile.