diff --git a/python_scripts/set_reminder.py b/python_scripts/set_reminder.py new file mode 100644 index 0000000..bd5d82f --- /dev/null +++ b/python_scripts/set_reminder.py @@ -0,0 +1,266 @@ +#------------------------------------------------------------------------------ +# Set / update reminder sensor +# +# Data: +# name: Sensor name (required) +# icon_on: Remidner icon on state (optional, default mdi:calendar-alerrt) +# icon_off: Remidner icon off state (optional, default mdi:calendar-star) +# date: Reminder date time D/M/Y-H:M (required, time is optional) +# title: Reminder title (optional) +# recurrence: yearly, montly, daily, does not repeat (optional, default 'yearly') +# every: +# tag: (optional, default 'reminder') +# notifier: (optional) +# script: (optional) +# message: (optional) +# enable: Enable /disable the reminder (optional, default on) +#------------------------------------------------------------------------------ + +# Reminder name +name = data.get('name').replace(" ", "_") +# Icons +icon_off = data.get("icon_off", "mdi:calendar-star") +icon_on = data.get("icon_on", "mdi:calendar-alert") +# Days before to notify (not functional yet) +days_notice = data.get('days_notice', 0) +# Reminder recurrence +recurrence = data.get('recurrence', 'yearly').lower() +# Reminder duration in minutes. The time the reminder will be in 'on' state +duration = data.get('duration', 0) +# Reminder title (will be sensor friendly_name) +title = data.get('title', 'Reminder') +# Reminder tag +tag = data.get('tag', 'reminder') +# Every (recurrence every) +every = int(data.get('every', 1)) +# Reminder action (notify / script) +notifier = data.get('notifier') +script = data.get('script') +# Action message +message = data.get('message', title) +# Split to date and time (input date should be in format: YYYY-MM-DD H:M) +date_time = data.get('date').split(' ') +# Enabled / disabled +enable = data.get('enable', 'on') + +# Sensor name derived from name +sensor_name = "sensor.{}".format(name) + +# Default values +new_state = 'off' +friendly_date = "-\-\-" +remaining = 0 +remaining_days = 0 +remaining_hours = 0 +remaining_minutes = 0 +remaining_seconds = 0 + +# Convert the date +date_split = date_time[0].split("-") +date_year = int(date_split[0]) +date_month = int(date_split[1]) +date_day = int(date_split[2]) + +# Check if time was specified +if len(date_time) == 2: + all_day = False + time_split = date_time[1].split(":") + time_hour = int(time_split[0]) + time_minute = int(time_split[1]) +else: + all_day = True + time_hour = 0 + time_minute = 0 + +# Helper function +def datebuild(year, month, day, hour = 8, minute = 0, offset = 0): + date_str = "{}-{}-{} {}:{}".format( + str(year), str(month), str(day), + str(hour), str(minute), + ) + return datetime.datetime.strptime( + date_str, "%Y-%m-%d %H:%M" + ) + datetime.timedelta(-offset) + +# Helper function +def dateadd(t1, n, type): + if type == 'yearly': + t = t1.replace(t1.year + n) + elif type == 'monthly': + t = t1 + while n > 0: + month = t.month + 1 + year = t.year + if month > 12: + month = 1 + year = year + 1 + t = t.replace(year=year, month=month) + n = n - 1 + elif type == 'daily': + t = t1 + datetime.timedelta(days=n) + elif type == 'weekly': + t = t1 + datetime.timedelta(days=7*n) + else: + logger.error("{} not supported".format(type)) + return t + +# Helper function - diff in recurrence units +def datediff(t1, t2, type): + diff = 0 + if t1 > t2: + t1, t2 = t2, t1 + if type == 'monthly': + while t1 < t2: + month = t1.month + 1 + year = t1.year + if month > 12: + month = 1 + year = year + 1 + t1 = t1.replace(year=year, month=month) + diff = diff + 1 + elif type == 'weekly': + diff = int(((t2 - t1).days + 7) / 7) + elif type == 'yearly': + diff = t2.year - t1.year + if t1.replace(t1.year + diff) < t2: + diff = diff + 1 + elif type == 'daily': + diff = (t2 - t1).days + 1 + else: + logger.error("{} not supported".format(type)) + return diff + +def datenext(t1, t2, n, type): + diff = None + if type == 'does not repeat': + return None, diff + if t1 < t2: + diff = datediff(t1, t2, type) + return dateadd(t1, int(n * (int((diff / n)) + (1 if (diff % n) else 0))), type), diff + return t1, diff + +# Reference date / time for reminder check (for now using sensor date time until +# the issue with datetime returning utc will be solved) +# calc_date = datetime.datetime.strptime(hass.states.get('sensor.date_time').state, "%Y-%m-%d, %H:%M") +calc_date = datetime.datetime.now().replace(second=0, microsecond=0) + +# The remidner date set by user +set_date = datebuild(date_year, date_month, date_day, time_hour, time_minute) + +# Reminder date this year (exclude if no reminder is no repeat) +if recurrence == 'yearly': + reminder_date = datebuild( + calc_date.year, date_month, date_day, + time_hour, time_minute, + days_notice + ) +elif recurrence == 'monthly': + reminder_date = datebuild( + calc_date.year, calc_date.month, date_day, + time_hour, time_minute, + days_notice + ) +elif recurrence == 'weekly': + reminder_date = datebuild( + date_year, date_month, date_day, + time_hour, time_minute, + days_notice + ) +elif recurrence == 'daily': + reminder_date = datebuild( + calc_date.year, calc_date.month, calc_date.day, + time_hour, time_minute, + days_notice + ) +elif recurrence == 'does not repeat': + reminder_date = datebuild( + date_year, date_month, date_day, + time_hour, time_minute, + days_notice + ) + +# Next reminder +next_date, diff_date = datenext(set_date, calc_date, every, recurrence) + +# sensor current state +current_state = hass.states.get(sensor_name).state + +# Start / end of reference date +calc_date_start = calc_date.replace(hour=0, minute=0, second=0, microsecond=0) +calc_date_midnight = calc_date.replace(hour=23, minute=59, second=59, microsecond=0) +if duration == 0: + calc_date_end = calc_date_midnight +else: + calc_date_end = reminder_date + datetime.timedelta(minutes=duration) + # By the end of the day we turn off all reminders + if calc_date_end > calc_date_midnight: + calc_date_end = calc_date_midnight + +# Sensor new state. +if enable == 'on': + # The first date the user set must be before the current date (otherwise first + # reminder is in the future). + if set_date < calc_date: + # We check that every has being fullfiled + if diff_date and (((diff_date - 1) % every) == 0): + if calc_date_start <= reminder_date <= calc_date_end: + if reminder_date <= calc_date <= calc_date_end: + new_state = 'on' + +# Remaining days to next occurence +if next_date and new_state == 'off': + delta = next_date - calc_date + remaining_days = delta.days + remaining_hours = int(delta.seconds / (60 * 60)) + remaining_minutes = int((delta.seconds - (remaining_hours * (60 * 60))) / 60) + remaining_seconds = remaining_days * 60 * 60 * 24 + remaining_hours * 60 * 60 + remaining_minutes * 60 + if remaining_days > 0: + remaining = remaining_days + else: + remaining = "{:02d}:{:02d}".format(remaining_hours, remaining_minutes) + +# Format friendly next reminder date +if next_date: + if all_day: + date_time = "{:04d}-{:02d}-{:02d}".format( + next_date.year, next_date.month, next_date.day) + else: + date_time = "{:04d}-{:02d}-{:02d} {:02d}:{:02d}".format( + next_date.year, next_date.month, next_date.day, next_date.hour, next_date.minute) + +# Send the sensor to homeassistant +hass.states.set(sensor_name, new_state, + { + "icon" : icon_off if new_state == 'off' else icon_on, + "friendly_name" : "{}".format(title), + "next": date_time, + "remaining": remaining, + "days": remaining_days, + "seconds": remaining_seconds, + "enable": enable, + "tag": tag + } +) + +# Actions +if new_state == 'on' and current_state == 'off': + if notifier: + hass.services.call('notify', notifier, + { + "title": "Reminder", + "message": message + } + ) + if script: + hass.services.call('script', script, + { + "message": message + } + ) + +# For debugging +# logger.warn("Reminder current:{} new:{} set:{} reminder:{} next:{} calc:{} start:{} end:{} diff:{} remaining:{} now:{}".format( +# current_state, new_state, set_date, reminder_date, next_date, +# calc_date, calc_date_start, calc_date_end, diff_date, remaining_time, +# datetime.datetime.now()) +# )