Rule creation for IR remotes

This commit is contained in:
Martin Bauer 2019-12-22 19:21:12 +01:00
parent bf0a15bf5f
commit 08c18b3dee
7 changed files with 400 additions and 23 deletions

View File

@ -1,8 +1,8 @@
# Add only entities here that are auto-discovered (not FHEM and KNX devices)
default_view:
view: true
icon: mdi:home
#default_view:
# view: true
# icon: mdi:home
living_area:
name: Wohnbereich
@ -30,9 +30,9 @@ hallway:
- light.gang_bogen
- light.gang_einganglicht
- light.gang_licht
- switch.bewegungsmelder_west_led
- switch.bewegungsmelder_ost_led
- switch.bewegungsmelder_mitte_led
- light.bewegungsmelder_west_led
- light.bewegungsmelder_ost_led
- light.bewegungsmelder_mitte_led
outside:
name: Außen
@ -66,5 +66,7 @@ other:
first_floor:
name: Oben
entities:
- light.wohnzimmer_stehlampe_oben

View File

@ -0,0 +1,226 @@
import re
import os
from ruamel.yaml import YAML
yaml = YAML()
# -------------------------------------- put the config here -----------------------------------------------------------
def get_config():
return {
'bedroom': {
'ir_host': 'bedroompi',
'media_player': 'media_player.bedroompi',
'group': 'group.bedroom',
'mapping': {
'key_1': '[playlist] Good Morning',
'key_2': '[playlist] Good Night Long',
'key_3': '[playlist] Good Night',
'key_4': '[playlist] Bar Classics',
'key_5': '[playlist] Sentimental Moods',
'key_6': '[playlist] Pop',
'key_7': '[radio] B 5 aktuell',
'key_8': '[radio] BR-Klassik',
'key_9': '[playlist] http://opml.radiotime.com/Tune.ashx?id=s25028', # Klassik Radio
'key_numeric_star': '[radio] Antenne Bayern',
'key_0': '[radio] Bayern 3',
'key_numeric_pound': '[radio] Bayern 2',
'key_red': '[scene] schlafzimmer_orange',
'key_green': '[scene] schlafzimmer_rot',
'key_yellow': '[scene] schlafzimmer_ganz_hell',
'key_blue': '[scene] schlafzimmer_blau',
'key_tv': '[timed_light_off] 30',
'key_video': '[timed_light_off] 15',
'key_music': '[timed_light_off] 10',
'key_pictures': '[timed_light_off] 5',
'key_power': [ # Music & Lights off
service('media_player.media_pause', 'media_player.bedroompi'),
service('light.turn_off', 'group.bedroom'),
],
'key_ok': [ # Grosser Rollo zu, kleiner halb zu
service('cover.close_cover', 'cover.schlafzimmer_rollo_gross'),
service('cover_half.set_half', 'cover.schlafzimmer_rollo_klein'),
],
'key_mute': [service('light.turn_off', 'group.all_downstairs_but_bedroom_and_outside')],
'key_channel': [service('light.turn_off', 'group.all_downstairs_but_bedroom')],
}
},
'living_area': {
'ir_host': 'kitchenpi',
'media_player': 'media_player.kitchenpi',
'group': 'group.living_area',
'mapping': {
'key_4': '[playlist] Bar Classics',
'key_5': '[playlist] Sentimental Moods',
'key_6': '[playlist] Pop',
'key_7': '[radio] B 5 aktuell',
'key_8': '[radio] BR-Klassik',
'key_9': '[playlist] http://opml.radiotime.com/Tune.ashx?id=s25028', # Klassik Radio
'key_numeric_star': '[radio] Antenne Bayern',
'key_0': '[radio] Bayern 3',
'key_numeric_pound': '[radio] Bayern 2',
'key_red': '[scene] wohnbereich_orange',
'key_green': '[scene] wohnbereich_grun',
'key_yellow': '[scene] wohnbereich_hell',
'key_blue': '[scene] wohnbereich_blau_grun',
}
}
}
# ----------------------------------------------------------------------------------------------------------------------
description_regex = re.compile(r'\[\s*(.*)\s*\](.*)')
def split_description(d):
res = description_regex.match(d)
return res.group(1).strip(), res.group(2).strip()
def automation_from_config(ir_description):
ir_host = ir_description['ir_host']
media_player = ir_description['media_player']
group = ir_description['group']
result = []
for key, description in ir_description['mapping'].items():
automation = {'alias': f'IR {ir_host} {key}',
'trigger': ir_trigger(ir_host, key)}
if isinstance(description, list):
action = description
elif isinstance(description, str):
function, value = split_description(description)
if function == 'playlist':
action = service('media_player.play_media', media_player, media_content_id=value)
elif function == 'radio':
action = service('media_player.play_media', media_player,
media_content_id=value, media_content_type='channel')
elif function == 'scene':
action = service('scene.turn_on', 'scene.' + value)
elif function == 'timed_light_off':
action = service('light.turn_off', group, transition=str(60 * int(value)))
else:
raise ValueError("Invalid prefix " + function)
else:
raise ValueError("Invalid type for entry " + key)
automation['action'] = action
result.append(automation)
return result
def ir_trigger(ir_host, button_name):
return {
'platform': 'event',
'event_type': 'ir_command_received',
'button_name': button_name,
'repeat_counter': 0,
'host': ir_host,
}
def service(service_name, entity_id, **kwargs):
kwargs['entity_id'] = entity_id
return {
'service': service_name,
'data': kwargs
}
def default_shutter_controls(device_group, ir_host):
"""Default rules for Hauppauge IR for Shutter control with up, down, left, right buttons"""
return [
{
'alias': f'IR {ir_host} Rollo auf',
'trigger': ir_trigger(ir_host, 'key_up'),
'action': service('cover.open_cover', device_group),
},
{
'alias': f'IR {ir_host} Rollo zu',
'trigger': ir_trigger(ir_host, 'key_down'),
'action': service('cover.close_cover', device_group),
},
{
'alias': f'IR {ir_host} Rollo halb',
'trigger': [ir_trigger(ir_host, 'key_left'), ir_trigger(ir_host, 'key_right')],
'action': service('cover_half.set_half', device_group),
},
]
def default_light_controls(device_group, ir_host):
"""Default light rules for Hauppauge IR for light dimming with channel up/down and light off with stop button"""
return [
{
'alias': f'IR {ir_host} Licht heller',
'trigger': ir_trigger(ir_host, 'key_channelup'),
'action': service('dimmer.dim', device_group, offset=30),
},
{
'alias': f'IR {ir_host} Licht dunkler',
'trigger': ir_trigger(ir_host, 'key_channeldown'),
'action': service('dimmer.dim', device_group, offset=-30),
},
{
'alias': f'IR {ir_host} Licht viel heller',
'trigger': ir_trigger(ir_host, 'key_menu'),
'action': service('dimmer.dim', device_group, offset=130),
},
{
'alias': f'IR {ir_host} Licht viel dunkler',
'trigger': ir_trigger(ir_host, 'key_stop'),
'action': service('dimmer.dim', device_group, offset=-130),
},
{
'alias': f'IR {ir_host} Licht aus',
'trigger': ir_trigger(ir_host, 'key_goto'),
'action': service('light.turn_off', device_group),
},
]
def default_music_controls(device_group, ir_host):
"""Default music control (play, pause, next) for Hauppauge IR"""
return [
{
'alias': f'IR {ir_host} Musik Play/Pause',
'trigger': [ir_trigger(ir_host, 'key_play'), ir_trigger(ir_host, 'key_pause')],
'action': service('media_player.media_play_pause', device_group),
},
{
'alias': f'IR {ir_host} Musik Next',
'trigger': [ir_trigger(ir_host, 'key_forward'), ir_trigger(ir_host, 'key_fastforward')],
'action': service('media_player.media_next_track', device_group),
},
{
'alias': f'IR {ir_host} Musik Prev',
'trigger': [ir_trigger(ir_host, 'key_previous'), ir_trigger(ir_host, 'key_rewind')],
'action': service('media_player.media_next_track', device_group),
},
]
def create_rules(folder):
for name, data in get_config().items():
rules = automation_from_config(data)
rules += default_light_controls(data['group'], data['ir_host'])
rules += default_music_controls(data['media_player'], data['ir_host'])
rules += default_shutter_controls(data['group'], data['ir_host'])
file_name = os.path.join(folder, name + '.yaml')
with open(file_name, 'w') as f:
f.write("# Dont' edit manually! this is generated!\n\n")
yaml.dump(rules, f)

View File

@ -123,7 +123,9 @@ def create_lights(device_info: List[DeviceInfo],
result = []
for entry in device_info:
try:
on_off_write_addr = csv_contents[entry.csv_name + postfix_on_off_write]
on_off_write_addr = csv_contents.get(entry.csv_name + postfix_on_off_write, None)
if on_off_write_addr is None:
on_off_write_addr = csv_contents[entry.csv_name]
on_off_read_addr = csv_contents.get(entry.csv_name + postfix_on_off_read, None)
brightness_write_addr = csv_contents.get(entry.csv_name + postfix_brightness_write, None)
brightness_read_addr = csv_contents.get(entry.csv_name + postfix_brightness_read, None)

View File

@ -3,6 +3,7 @@ import argparse
from util import DeviceInfo, add_to_group, name_to_id
from ruamel.yaml import YAML
import knx_conf as knx
from ir_automations import create_rules as create_automation_rules
script_path = os.path.dirname(os.path.realpath(__file__))
yaml = YAML()
@ -48,10 +49,14 @@ def add_knx_devices(devices, groups):
DeviceInfo("LichtWaschküche", "Waschküche Licht", 'hallway'),
# Normal lights
DeviceInfo('AussenleuchteHaustüren', 'Haustür Licht', 'outside'),
DeviceInfo('AussenleuchteObenNW', 'Haustür Licht NW', 'outside'),
DeviceInfo('AussenleuchteObenNW', 'Haustür Licht NW', 'first_floor'),
DeviceInfo('TreppenhausLicht', "Treppenhaus Licht", 'first_floor'),
DeviceInfo('WCLicht', "WC Licht", 'other'),
DeviceInfo('LampeVorratsraum', "Vorratsraum Licht", 'other'),
# Bewegungsmelder LEDs
DeviceInfo("BewegungsmelderMitte LED", "Bewegungsmelder Mitte LED", 'hallway'),
DeviceInfo("BewegungsmelderWest LED", "Bewegungsmelder West LED", 'hallway'),
DeviceInfo("BewegungsmelderOst LED", "Bewegungsmelder Ost LED", 'hallway'),
]
shutters = [
@ -66,10 +71,6 @@ def add_knx_devices(devices, groups):
DeviceInfo("KlingelOben", "Klingel Oben", 'first_floor'),
DeviceInfo("Klingel Innen", "Klingel Innentür", 'other'),
DeviceInfo("Klingel Aussen", "Klingen Außentür", 'other'),
# Bewegungsmelder LEDs
DeviceInfo("BewegungsmelderMitte LED", "Bewegungsmelder Mitte LED", 'hallway'),
DeviceInfo("BewegungsmelderWest LED", "Bewegungsmelder West LED", 'hallway'),
DeviceInfo("BewegungsmelderOst LED", "Bewegungsmelder Ost LED", 'hallway'),
]
scene_button_names = ['ObenLinks', 'ObenRechts', 'MitteLinks', 'MitteRechts', 'UntenLinks', 'UntenRechts']
scene_button_names = [(i, e) for i, e in enumerate(scene_button_names)]
@ -174,15 +175,33 @@ def add_fhem_devices(devices, groups):
devices[device_type].append(device)
def add_light_groups(groups):
light_groups = {
f"light_{group_id}": {
'name': content['name'] + " Lichter",
'entities': [e for e in content['entities'] if e.startswith('light.')],
}
for group_id, content in groups.items() if 'name' in content
def add_meta_groups(groups):
all_devices = set()
for group in groups.values():
all_devices.update(group['entities'])
first_floor = set(groups['first_floor']['entities'])
bedroom = set(groups['bedroom']['entities'])
outside = set(groups['outside']['entities'])
groups['all_downstairs'] = {
'name': 'Unten Alles',
'entities': [e for e in all_devices - first_floor]
}
groups['all_downstairs_but_outside'] = {
'name': 'Unten Alles Ausser Draussen',
'entities': [e for e in all_devices - first_floor - outside]
}
groups['all_downstairs_but_bedroom'] = {
'name': 'Unten Alles Ausser Schlafzimmer',
'entities': [e for e in all_devices - first_floor - bedroom],
}
groups['all_downstairs_but_bedroom_and_outside'] = {
'name': 'Unten Alles Ausser Schlafzimmer und Draussen',
'entities': [e for e in all_devices - first_floor - bedroom - outside],
}
groups.update(light_groups)
def make_sensor_exclude_list(all_devices, name_fragments):
@ -256,8 +275,9 @@ def create_config(target_directory, development=False):
yaml.dump(all_devices, output)
yaml.dump(logbook_config(all_devices), output)
yaml.dump(recorder_config(all_devices), output)
output.write("automation manual: !include_dir_merge_list automations\n")
add_light_groups(group_dict)
add_meta_groups(group_dict)
with open(os.path.join(target_directory, 'groups.yaml'), 'w') as output:
output.write("# Dont' edit manually! this is generated!\n\n")
@ -270,6 +290,8 @@ def create_config(target_directory, development=False):
additional_file = 'secrets_development.yaml' if development else 'secrets_deploy.yaml'
output.write(open(os.path.join(script_path, additional_file), 'r').read())
create_automation_rules(os.path.join(target_directory, 'automations'))
if __name__ == '__main__':
parser = argparse.ArgumentParser()

View File

@ -6,6 +6,7 @@ import sys
from typing import List, Tuple
import voluptuous as vol
from ..reconnecting_client import ReconnectingClient
from .radio import find_local_radio_url_by_name
from homeassistant.components.media_player import (
MediaPlayerDevice, PLATFORM_SCHEMA)
@ -485,6 +486,9 @@ class SqueezeBoxDevice(MediaPlayerDevice):
If ATTR_MEDIA_ENQUEUE is True, add `media_id` to the current playlist.
This method must be run in the event loop and returns a coroutine.
"""
if media_type == 'channel':
media_id = find_local_radio_url_by_name(media_id)
if kwargs.get(ATTR_MEDIA_ENQUEUE):
return self._add_uri_to_playlist(media_id)

View File

@ -0,0 +1,68 @@
import xml.etree.ElementTree as ET
from urllib.request import urlopen
ompl_radio_browse_url = 'http://opml.radiotime.com/Browse.ashx'
def radio_name_cleanup(radio_name):
"""Removes tokens with brackets or points
"Radio Bamberg 106.1 (Top 40/Pop)" -> "Radio Bamberg 106.1"
"""
radio_name = radio_name.split()
res = []
for token in radio_name:
if '(' in token or ')' in token:
continue
res.append(token)
return " ".join(res)
def get_sender_information(queryURL):
"""
Example Query: GET http://opml.radiotime.com/Browse.ashx?partnerId=<partnerid>&serial=<serial>
partnerid and serial does not seem to be necessary - so we do not use it here,
"""
xmlfile = urlopen(queryURL)
tree = ET.ElementTree(file=xmlfile)
rootnode = tree.getroot()
result = dict()
for node in rootnode.iter('outline'):
if 'type' not in node.attrib or node.attrib['type'] != "audio":
continue
station_name = radio_name_cleanup(node.attrib['text'])
station_url = node.attrib['URL']
station_id = node.attrib['guide_id']
print(station_name)
result[station_id] = {'name': station_name, 'url': station_url}
return result
def get_related_radio_stations(stationID):
query_url = ompl_radio_browse_url + '?id=' + stationID
return get_sender_information(query_url)
def get_local_radio_stations():
# goto ompl.radiotime with parameter ?c=local and look for your city there then paste id here
query_url = ompl_radio_browse_url + '?id=r100765'
result = get_sender_information(query_url)
print(result)
return result
def find_local_radio_url_by_name(search_name):
"""Searches at the Radiotime database for a local radiostation which contains the searchName """
search_name = search_name.lower().strip()
if search_name.startswith('http'):
return search_name
for radioId, radio in find_local_radio_url_by_name.stations.items():
if search_name in radio['name'].lower():
return radio['url']
raise ValueError("Could not find radio station " + search_name)
find_local_radio_url_by_name.stations = get_local_radio_stations()

View File

@ -202,6 +202,51 @@
xy_color: [0.2075, 0.6584]
- name: Wohnbereich Hell
entities:
light.esszimmer_deckenlampe_west:
state: on
brightness: 255
light.wohnzimmer_deckenlampe:
state: on
brightness: 255
light.kuche_deckenlampe:
state: on
brightness: 255
light.wohnzimmer_regal_links:
state: on
brightness: 255
xy_color: [0.527, 0.447]
light.kuche_links:
state: on
brightness: 255
xy_color: [0.537, 0.438]
light.kuche_rechts:
state: on
brightness: 255
xy_color: [0.557, 0.403]
light.wohnzimmer_regal_rechts:
state: on
brightness: 255
xy_color: [0.616, 0.371]
light.wohnzimmer_stehlampe_oben:
state: on
brightness: 255
color_temp: 492
light.wohnzimmer_stehlampe:
state: on
brightness: 255
color_temp: 492
light.wohnzimmer_kugel:
state: on
brightness: 255
color_temp: 492
light.kuche_vorne:
state: on
brightness: 255
color_temp: 492
- name: Wohnbereich Meditation
entities:
light.kuche_deckenlampe:
@ -330,12 +375,20 @@
- name: Schlafzimmer Bettlicht dunkel
entities:
light.schlafzimmer_deckenlampe:
state: off
light.schlafzimmer_schrank:
state: off
light.bett_martin:
state: on
brightness: 2
brightness: 5
xy_color: [0.502, 0.414]
light.bett_rebecca:
state: on
brightness: 2
brightness: 5
xy_color: [0.502, 0.414]
light.schlafzimmer_fluter:
state: off
- name: Schlafzimmer Orange