MotionEyeOS Day and Night Python script: Difference between revisions

From WickyWiki
mNo edit summary
 
(2 intermediate revisions by the same user not shown)
Line 3: Line 3:
[[Category:Raspberry Pi]]
[[Category:Raspberry Pi]]
[[Category:201903]]
[[Category:201903]]
This Python script allows you to have day and night -time settings for your MotionEyeOS camera, based on calculated sunrise and sunset time-of-day. The script replaces the settings file (/data/etc/thread-1.conf) for Camera1 with one the files with specific settings:
* /data/etc/thread-1.conf.day
* /data/etc/thread-1.conf.night
This module seems to be created by Michel Anders, source:
* https://blog.ligos.net/2016-04-18/Day-Night-Cycle-For-MotionEye.html


See also:
See also:
* [[MotionEyeOS‎]]
* [[Calculation of sunrise and sunset]]
* [[Calculation of sunrise and sunset]]


Source:
Setup crontab to execute the script every 10 minutes. On MotionEyeOS we can place the files in /data/etc/daynight, this way it is part of the configuration backup. Logging is sent to /data/log/daynight.log.
* https://blog.ligos.net/2016-04-18/Day-Night-Cycle-For-MotionEye.html
 
Setup crontab to execute the script (here every 10 minutes)


<syntaxhighlight lang=bash>
<syntaxhighlight lang=bash>
Line 22: Line 27:
</blockquote>
</blockquote>


Setup [[How To Manage Logfiles with Logrotate|logrotate]] to limit the size of the logfile.
Go [[How To Manage Logfiles with Logrotate|here]] to see how you can setup [[How To Manage Logfiles with Logrotate|LogRotate]] to limit the size of the logfile.
 
= Disable wachdog =
 
Restarting the service may lead to restarting the device when the watchdog process 'thinks' there is a problem. Add '''meyewatch_disable="true"''' to the file '''/data/etc/watch.conf''' to disable this.


= Script =
= Script =
Line 31: Line 40:


<syntaxhighlight lang=python>
<syntaxhighlight lang=python>
# Based on: https://blog.ligos.net/2016-04-18/Day-Night-Cycle-For-MotionEye.html (Michel Anders)
from math import cos,sin,acos,asin,tan
from math import cos,sin,acos,asin,tan
from math import degrees as deg, radians as rad
from math import degrees as deg, radians as rad
Line 38: Line 48:
import os.path
import os.path
import shutil
import shutil
import filecmp
import time as time2


# variant of time zone available on MotionEyeOS
# Variant of time zone available on MotionEyeOS
localTimezone = timezone('Europe/Amsterdam')
localTimezone = timezone('Europe/Amsterdam')


# Location of here in lat / long (consult your favourite maps app to determine this)
# Location of here in lat / long
localLat = 51.5
localLat = 51.49
localLong = 5.5
localLong = 5.48


# Sunrise and sunset fudge factor (in minutes)
# Sunrise and sunset delay (in minutes)
# Positive minutes is later, negative minutes is earlier.
# + is later, - is earlier.
sunriseFudgeMinutes = -20
sunriseDelayMins = -10
sunsetFudgeMinutes = 20
sunsetDelayMins = +10


# Current state of day / night is stored in this file.
# Current state of day / night is stored in this file.
scriptDir = '/data/script_daynight/'
dayNightStateFile = '/data/etc/daynight/daynight.dat'
dayAndNightStateFile = scriptDir + 'daynight.dat'


# sun module
class sun:
class sun:
  """  
  """  
Line 61: Line 71:
  http://www.srrb.noaa.gov/highlights/sunrise/calcdetails.html
  http://www.srrb.noaa.gov/highlights/sunrise/calcdetails.html
  """
  """
  def __init__(self,lat=52.37,long=4.90): # default Amsterdam
  def __init__(self,lat=52.37,long=4.90,when=None):
  """
  default Amsterdam + local time zone
  (including daylight saving if present)
  """
   self.lat=lat
   self.lat=lat
   self.long=long
   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)
   if when is None : when = datetime.now(tz=localTimezone)
   self.__preptime(when)
   self.__preptime(when)
   self.__calc()
   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):
  def dayornight(self,now=None,sunriseFudge=0,sunsetFudge=0):
  """
  The two fudge factors are in minutes. Negative is earlier.
  """
   if now is None : now = datetime.now(tz=localTimezone)
   if now is None : now = datetime.now(tz=localTimezone)
   self.__preptime(when=now)
   self.__preptime(when=now)
Line 122: Line 115:
   a datetime.datetime object.
   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)
   self.day = when.toordinal()-(734124-40529)
   t=when.time()
   t=when.time()
Line 139: Line 127:
   """
   """
   Perform the actual calculations for sunrise, sunset and
   Perform the actual calculations for sunrise, sunset and
   a number of related quantities.  
   a number of related quantities.   
    
   The results are stored in the instance variables
   The results are stored in the instance variables
   sunrise_t, sunset_t and solarnoon_t
   sunrise_t, sunset_t and solarnoon_t
Line 184: Line 171:
  f.write(data)
  f.write(data)
  f.close()
  f.close()
# The main program
if __name__ == "__main__":
   
   
if __name__ == "__main__":
# Calculate
  s=sun(lat=localLat,long=localLong)
  s=sun(lat=localLat,long=localLong)
# Calculate
s.solarnoon()
   
   
  # Track day / night state in file.
  # Previous day/night -state from file.
  fileDayOrNight = readLineOfFile(dayAndNightStateFile)
  fileDayOrNight = readLineOfFile(dayNightStateFile)
   
   
  #log
  # Log
  print(datetime.now().strftime('%Y-%m-%d_%H.%M.%S ')  
  print(datetime.now().strftime('%Y-%m-%d_%H.%M.%S ')  
   + 'Day ' + sun.timefromdecimalday(s.sunrise_t).strftime('%H.%M') + ['', '+'][sunriseFudgeMinutes > 0] + str(sunriseFudgeMinutes)
   + 'Day from ' + sun.timefromdecimalday(s.sunrise_t).strftime('%H.%M') + ['(', '(+'][sunriseDelayMins > 0] + str(sunriseDelayMins)+')'
   + ' - ' + sun.timefromdecimalday(s.sunset_t).strftime('%H.%M') + ['', '+'][sunsetFudgeMinutes > 0] + str(sunsetFudgeMinutes)
   + ' to ' + sun.timefromdecimalday(s.sunset_t).strftime('%H.%M') + ['(', '(+'][sunsetDelayMins > 0] + str(sunsetDelayMins)+')'
   + ', state: ' + fileDayOrNight
   + ', current state: ' + fileDayOrNight )
  )
 
# Determine current day / night state based on current time of day.
currentDayOrNight = s.dayornight(sunriseFudge=sunriseFudgeMinutes,sunsetFudge=sunsetFudgeMinutes)
   
   
# Current day/night -state based on time-of-day.
currentDayOrNight = s.dayornight(sunriseFudge=sunriseDelayMins,sunsetFudge=sunsetDelayMins)
# New day/night -state?
  if fileDayOrNight != currentDayOrNight:
  if fileDayOrNight != currentDayOrNight:
   print(datetime.now().strftime('%Y-%m-%d_%H:%M:%S ') + 'Changing state to: ' + 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
   # Backup the old settings if the selected config is newer 
   if not filecmp.cmp("/data/etc/thread-1.conf", scriptDir + "thread-1.conf." + fileDayOrNight):
   if os.stat("/data/etc/thread-1.conf").st_mtime > os.stat("/data/etc/thread-1.conf." + fileDayOrNight).st_mtime:
     # backup old day/night file
     # Backup old day/night settings file
     shutil.move(scriptDir + "thread-1.conf." + fileDayOrNight
     shutil.copy2("/data/etc/thread-1.conf." + fileDayOrNight
       , scriptDir + "thread-1.conf." + fileDayOrNight + "." + datetime.now().strftime('%Y-%m-%d_%H.%M'))
       , "/data/etc/thread-1.conf." + fileDayOrNight + "." + datetime.now().strftime('%Y-%m-%d_%H.%M'))
     # store updated day/night file
     # Store new day/night settings file
     shutil.copyfile("/data/etc/thread-1.conf", scriptDir + "thread-1.conf." + fileDayOrNight)
     shutil.copy2("/data/etc/thread-1.conf", "/data/etc/thread-1.conf." + fileDayOrNight)


   # select the other day/night file
   # Select the other day/night settings file
   shutil.copyfile(scriptDir + "thread-1.conf." + currentDayOrNight, "/data/etc/thread-1.conf")
   shutil.copy2("/data/etc/thread-1.conf." + currentDayOrNight, "/data/etc/thread-1.conf")


   print(datetime.now().strftime('%Y-%m-%d_%H:%M:%S ') + 'Restart motionEye...')
  # Restart MotionEye
   os.system("/etc/init.d/S85motioneye restart")
   print(datetime.now().strftime('%Y-%m-%d_%H:%M:%S ') + 'Stop MotionEye...')
   print(datetime.now().strftime('%Y-%m-%d_%H:%M:%S ') + 'MotionEye restarted.')
  os.system("/etc/init.d/S85motioneye stop")
  time2.sleep(10)
  print(datetime.now().strftime('%Y-%m-%d_%H:%M:%S ') + 'Start MotionEye...')
   os.system("/etc/init.d/S85motioneye start")
   print(datetime.now().strftime('%Y-%m-%d_%H:%M:%S ') + 'MotionEye Started.')
    
    
   # Update the file day / night state.
   # Update day/night -state file
   updateContentsOfFile(dayAndNightStateFile, currentDayOrNight)
   updateContentsOfFile(dayNightStateFile, currentDayOrNight)
</syntaxhighlight>
</syntaxhighlight>

Latest revision as of 19:27, 7 March 2019


This Python script allows you to have day and night -time settings for your MotionEyeOS camera, based on calculated sunrise and sunset time-of-day. The script replaces the settings file (/data/etc/thread-1.conf) for Camera1 with one the files with specific settings:

  • /data/etc/thread-1.conf.day
  • /data/etc/thread-1.conf.night

This module seems to be created by Michel Anders, source:

See also:

Setup crontab to execute the script every 10 minutes. On MotionEyeOS we can place the files in /data/etc/daynight, this way it is part of the configuration backup. Logging is sent to /data/log/daynight.log.

crontab -e
  02,12,22,32,42,52 * * * * /usr/bin/python /data/etc/daynight/daynight.py >> /data/log/daynight.log

Go here to see how you can setup LogRotate to limit the size of the logfile.

Disable wachdog

Restarting the service may lead to restarting the device when the watchdog process 'thinks' there is a problem. Add meyewatch_disable="true" to the file /data/etc/watch.conf to disable this.

Script

nano /data/etc/daynight/daynight.py
# Based on: https://blog.ligos.net/2016-04-18/Day-Night-Cycle-For-MotionEye.html (Michel Anders)
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 time as time2

# Variant of time zone available on MotionEyeOS
localTimezone = timezone('Europe/Amsterdam')

# Location of here in lat / long
localLat = 51.49
localLong = 5.48

# Sunrise and sunset delay (in minutes)
# + is later, - is earlier.
sunriseDelayMins = -10
sunsetDelayMins = +10

# Current state of day / night is stored in this file.
dayNightStateFile = '/data/etc/daynight/daynight.dat'

# sun module
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,when=None):
  """
  default Amsterdam + local time zone
  (including daylight saving if present)
  """
  self.lat=lat
  self.long=long
  if when is None : when = datetime.now(tz=localTimezone)
  self.__preptime(when)
  self.__calc()
   
 def dayornight(self,now=None,sunriseFudge=0,sunsetFudge=0):
  """
  The two fudge factors are in minutes. Negative is earlier. 
  """
  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.
  """
  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()

# The main program
if __name__ == "__main__":
 
 # Calculate
 s=sun(lat=localLat,long=localLong)
 
 # Previous day/night -state from file.
 fileDayOrNight = readLineOfFile(dayNightStateFile)
 
 # Log
 print(datetime.now().strftime('%Y-%m-%d_%H.%M.%S ') 
  + 'Day from ' + sun.timefromdecimalday(s.sunrise_t).strftime('%H.%M') + ['(', '(+'][sunriseDelayMins > 0] + str(sunriseDelayMins)+')'
  + ' to ' + sun.timefromdecimalday(s.sunset_t).strftime('%H.%M') + ['(', '(+'][sunsetDelayMins > 0] + str(sunsetDelayMins)+')'
  + ', current state: ' + fileDayOrNight )
 
 # Current day/night -state based on time-of-day.
 currentDayOrNight = s.dayornight(sunriseFudge=sunriseDelayMins,sunsetFudge=sunsetDelayMins)

 # New day/night -state?
 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 is newer  
  if os.stat("/data/etc/thread-1.conf").st_mtime > os.stat("/data/etc/thread-1.conf." + fileDayOrNight).st_mtime:
    # Backup old day/night settings file
    shutil.copy2("/data/etc/thread-1.conf." + fileDayOrNight
      , "/data/etc/thread-1.conf." + fileDayOrNight + "." + datetime.now().strftime('%Y-%m-%d_%H.%M'))
    # Store new day/night settings file
    shutil.copy2("/data/etc/thread-1.conf", "/data/etc/thread-1.conf." + fileDayOrNight)

  # Select the other day/night settings file
  shutil.copy2("/data/etc/thread-1.conf." + currentDayOrNight, "/data/etc/thread-1.conf")

  # Restart MotionEye
  print(datetime.now().strftime('%Y-%m-%d_%H:%M:%S ') + 'Stop MotionEye...')
  os.system("/etc/init.d/S85motioneye stop")
  time2.sleep(10)
  print(datetime.now().strftime('%Y-%m-%d_%H:%M:%S ') + 'Start MotionEye...')
  os.system("/etc/init.d/S85motioneye start")  
  print(datetime.now().strftime('%Y-%m-%d_%H:%M:%S ') + 'MotionEye Started.')
  
  # Update day/night -state file
  updateContentsOfFile(dayNightStateFile, currentDayOrNight)