From f0a05612bd443d8229d9d63ac53933be84bd2e8e Mon Sep 17 00:00:00 2001 From: Dirk Alders Date: Sat, 18 Feb 2023 17:16:23 +0100 Subject: [PATCH] day_state and day_events implemented as helpers --- function/first_floor_east.py | 13 +++++ function/helpers.py | 106 +++++++++++++++++++++++++++++++++-- function/modules.py | 5 +- smart_brain.py | 3 +- 4 files changed, 118 insertions(+), 9 deletions(-) diff --git a/function/first_floor_east.py b/function/first_floor_east.py index ff80852..fe15422 100644 --- a/function/first_floor_east.py +++ b/function/first_floor_east.py @@ -4,6 +4,7 @@ import config import devices +from function.helpers import day_event from function.modules import brightness_choose_n_action, timer_on_activation, heating_function from function.rooms import room, room_collection from function.videv import videv_switching, videv_switch_brightness, videv_switching_timer, videv_switch_brightness_color_temp, videv_heating, videv_multistate @@ -81,6 +82,8 @@ class first_floor_east_dining(room): # # Device initialisation # + self.day_events = day_event((6, 0), (22, 0), 30, -30) + # http://shelly1l-84CCA8ADD055 self.main_light_shelly = devices.shelly(mqtt_client, config.TOPIC_FFE_DININGROOM_MAIN_LIGHT_SHELLY) self.floorlamp_powerplug = devices.silvercrest_powerplug(mqtt_client, config.TOPIC_FFE_DININGROOM_FLOOR_LAMP_POWERPLUG) @@ -91,6 +94,8 @@ class first_floor_east_dining(room): # # Functionality initialisation # + self.day_events.add_callback(None, True, self.__day_events__, True) + self.main_light_shelly.add_callback(devices.shelly.KEY_OUTPUT_0, None, self.floorlamp_powerplug.set_output_0_mcb, True) # @@ -110,6 +115,14 @@ class first_floor_east_dining(room): self.garland_powerplug, devices.silvercrest_powerplug.KEY_OUTPUT_0 ) + def __day_events__(self, device, key, data): + if key in (self.day_events.KEY_SUNSET, self.day_events.KEY_START_OF_DAY): + if config.CHRISTMAS: + self.garland_powerplug.set_output_0(True) + elif key in (self.day_events.KEY_START_OF_NIGHT, self.day_events.KEY_SUNRISE): + if config.CHRISTMAS: + self.garland_powerplug.set_output_0(False) + class first_floor_east_sleep(room): def __init__(self, mqtt_client): diff --git a/function/helpers.py b/function/helpers.py index 046f15c..3efbb52 100644 --- a/function/helpers.py +++ b/function/helpers.py @@ -1,9 +1,10 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- # +from base import common_base import config import geo -import inspect +import task import time @@ -11,9 +12,104 @@ def now(): return time.mktime(time.localtime()) -def sunrise_time(time_offs_min=30): - return time.mktime(geo.sun.sunrise(config.GEO_POSITION)) + time_offs_min * 60 +def next_sunrise_time(time_offs_min=30): + tm = now() + rv = time.mktime(geo.sun.sunrise(config.GEO_POSITION)) + time_offs_min * 60 + if tm > rv: + rv = time.mktime(geo.sun.sunrise(config.GEO_POSITION, date=time.localtime(tm + 24 * 60 * 60))) + time_offs_min * 60 + return rv -def sunset_time(time_offs_min=-30): - return time.mktime(geo.sun.sunset(config.GEO_POSITION)) + time_offs_min * 60 +def next_sunset_time(time_offs_min=-30): + tm = now() + rv = time.mktime(geo.sun.sunset(config.GEO_POSITION)) + time_offs_min * 60 + if tm > rv: + rv = time.mktime(geo.sun.sunset(config.GEO_POSITION, date=time.localtime(tm + 24 * 60 * 60))) + time_offs_min * 60 + return rv + + +def next_user_time(hh, mm): + ts = time.localtime() + tm = time.mktime(ts) + ut_ts = list(ts) + ut_ts[3] = hh + ut_ts[4] = mm + ut = time.mktime(time.struct_time(list(ts[:3]) + [hh, mm, 0] + list(ts[6:]))) + if ts[3] > hh or (ts[3] == hh and ts[4] >= mm): + ut += 24 * 60 * 60 + # + return ut + + +class day_state(common_base): + """ + Class to subscribe day events as a callback (see add_callback) + + :param time_start_of_day: Time of a day (tuple including hour and minute) for start of day or None for no start of day state. + :type time_start_of_day: tuple + :param time_start_of_night: Time of a day (tuple including hour and minute) for start of night or None for no end of day state. + :type time_start_of_night: tuple + :param time_offset_sunrise: time offset for sunrise in minutes (negative values lead to earlier sunrise state) or None for no sunrise state. + :type time_start_of_day: int + :param time_offset_sunset: time offset for sunset in minutes (negative values lead to earlier sunset state) or None for no sunrise state. + :type time_start_of_day: int + """ + KEY_SUNRISE = 'sunrise' + KEY_SUNSET = 'sunset' + KEY_START_OF_NIGHT = 'start_of_night' + KEY_START_OF_DAY = 'start_of_day' + # + STATES = (KEY_START_OF_DAY, KEY_SUNRISE, KEY_SUNSET, KEY_START_OF_NIGHT) + + def __init__(self, time_start_of_day, time_start_of_night, time_offset_sunrise, time_offset_sunset): + self.__time_start_of_day__ = time_start_of_day + self.__time_start_of_night__ = time_start_of_night + self.__time_offset_sunrise__ = time_offset_sunrise + self.__time_offset_sunset__ = time_offset_sunset + super().__init__() + # + + def get_state(self): + tm = {} + if self.__time_offset_sunrise__ is not None: + tm[next_sunrise_time(self.__time_offset_sunrise__)] = self.KEY_SUNRISE + if self.__time_start_of_day__ is not None: + tm[next_user_time(*(self.__time_start_of_day__))] = self.KEY_START_OF_DAY + if self.__time_offset_sunset__ is not None: + tm[next_sunset_time(self.__time_offset_sunset__)] = self.KEY_SUNSET + if self.__time_start_of_night__ is not None: + tm[next_user_time(*(self.__time_start_of_night__))] = self.KEY_START_OF_NIGHT + # + tms = list(tm.keys()) + tms.sort() + return tm[tms[-1]] + + +class day_event(day_state): + """ + Class to subscribe day events as a callback (see add_callback) + + :param time_start_of_day: Time of a day (tuple including hour and minute) for start of day or None for no start of day state. + :type time_start_of_day: tuple + :param time_start_of_night: Time of a day (tuple including hour and minute) for start of night or None for no end of day state. + :type time_start_of_night: tuple + :param time_offset_sunrise: time offset for sunrise in seconds (negative values lead to earlier sunrise state) or None for no sunrise state. + :type time_start_of_day: int + :param time_offset_sunset: time offset for sunset in seconds (negative values lead to earlier sunset state) or None for no sunrise state. + :type time_start_of_day: int + """ + + def __init__(self, time_start_of_day=(6, 0), time_start_of_night=(22, 0), time_offset_sunrise=30, time_offset_sunset=-30): + super().__init__(time_start_of_day, time_start_of_night, time_offset_sunrise, time_offset_sunset) + # + current_day_state = self.get_state() + for key in self.STATES: + self[key] = current_day_state == key + # + cyclic = task.periodic(30, self.__cyclic__) + cyclic.run() + + def __cyclic__(self, a): + current_day_state = self.get_state() + for key in self.STATES: + self.set(key, current_day_state == key) diff --git a/function/modules.py b/function/modules.py index c4cb32a..cc7f71b 100644 --- a/function/modules.py +++ b/function/modules.py @@ -15,7 +15,7 @@ from base import common_base import config import devices from function.db import get_radiator_data, set_radiator_data -from function.helpers import now, sunset_time, sunrise_time +from function.helpers import day_state import logging import task @@ -300,7 +300,8 @@ class motion_sensor_light(common_base): if arg_device.topic == device.topic: break self.set(self.KEY_MOTION_SENSOR % sensor_index, data) - if now() < sunrise_time(60) or now() > sunset_time(-60): + # auto light on with state sunset -> time_offset_sunrise=60 (longer sunset) and time_offset_sunset=-60 (longer sunset) + if day_state(None, None, 60, -60).get_state() == self.day_status.KEY_SUNSET: if data is True: logger.info("%s: Motion detected - Switching on main light %s", device.topic, self.sw_device.topic) self.sw_method(True) diff --git a/smart_brain.py b/smart_brain.py index b806985..8a88366 100644 --- a/smart_brain.py +++ b/smart_brain.py @@ -10,11 +10,10 @@ import time logger = logging.getLogger(config.APP_NAME) -# TODO: implement garland (incl. day events like sunset, sunrise, ...) VERS_MAJOR = 1 VERS_MINOR = 2 -VERS_PATCH = 1 +VERS_PATCH = 2 INFO_TOPIC = "__info__" INFO_DATA = {