Files
Home-Assistant-Configs/python_scripts/set_reminder.py
2022-01-13 18:12:10 -05:00

267 lines
8.5 KiB
Python

#------------------------------------------------------------------------------
# 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())
# )