added lovelace

This commit is contained in:
monster
2019-02-09 19:15:09 +03:00
parent 3116c2378e
commit c1db46839e
10 changed files with 1146 additions and 3 deletions
+2
View File
@@ -26,6 +26,7 @@ lovelace:
# Enables configuration UI # Enables configuration UI
config: config:
discovery: discovery:
system_health:
# Enables support for tracking state changes over time # Enables support for tracking state changes over time
recorder: !include configuration/recorder.yaml recorder: !include configuration/recorder.yaml
@@ -64,3 +65,4 @@ homekit: !include configuration/homekit/homekit.yaml
modbus: !include configuration/modbus.yaml modbus: !include configuration/modbus.yaml
mysensors: !include configuration/mysensors.yaml mysensors: !include configuration/mysensors.yaml
utility_meter: !include_dir_merge_list configuration/utility_meter
@@ -40,6 +40,14 @@ alarmclock:
- group.alarmclock_weekdays - group.alarmclock_weekdays
- group.alarmclock_weekend - group.alarmclock_weekend
stats:
view: yes
name: Статистика
icon: mdi:chart-areaspline
entities:
- group.power_stats
- group.water_stats
settings: settings:
view: yes view: yes
name: Настройки name: Настройки
@@ -0,0 +1,6 @@
power_stats:
name: Статистика электропитания
entities:
- utility_meter.power_dialy
- utility_meter.power_monthly
- utility_meter.power_hourly
@@ -0,0 +1,9 @@
water_stats:
name: Статистика потребления воды
entities:
- utility_meter.counter_cold_hourly
- utility_meter.counter_cold_dialy
- utility_meter.counter_cold_monthly
- utility_meter.counter_hot_hourly
- utility_meter.counter_hot_dialy
- utility_meter.counter_hot_monthly
@@ -0,0 +1,9 @@
power_hourly:
source: sensor.power_total
cycle: hourly
power_dialy:
source: sensor.power_total
cycle: daily
power_monthly:
source: sensor.power_total
cycle: monthly
@@ -0,0 +1,9 @@
counter_cold_hourly:
source: sensor.counter_cold
cycle: hourly
counter_cold_dialy:
source: sensor.counter_cold
cycle: daily
counter_cold_monthly:
source: sensor.counter_cold
cycle: monthly
@@ -0,0 +1,9 @@
counter_hot_hourly:
source: sensor.counter_hot
cycle: hourly
counter_hot_dialy:
source: sensor.counter_hot
cycle: daily
counter_hot_monthly:
source: sensor.counter_hot
cycle: monthly
+844
View File
@@ -0,0 +1,844 @@
"""
Migration tool for Home Assistant Lovelace UI.
"""
import argparse
import logging
import sys
import json
import os
import shutil
from collections import OrderedDict
from getpass import getpass
import requests
import yaml
_LOGGER = logging.getLogger(__name__)
# Build arguments parser (argdown needs this at the beginning of the file)
parser = argparse.ArgumentParser(
description="Home Assistant Lovelace migration tool")
# Positional arguments
parser.add_argument(
'input', metavar='<api-url|file>', nargs='?',
help="Home Assistant REST API URL or states JSON file")
# Optional arguments
parser.add_argument(
'-o', '--output', metavar='<file>', default='ui-lovelace.yaml',
help="write output to <file> (default: ui-lovelace.yaml)")
parser.add_argument(
'-p', '--password', metavar='<password>', nargs='?',
default=False, const=None,
help="Home Assistant API password")
parser.add_argument(
'-t', '--title', metavar='<title>', default='Home',
help="title of the Lovelace UI (default: Home)")
parser.add_argument(
'--debug', action='store_true',
help="set log level to DEBUG")
parser.add_argument(
'--dry-run', action='store_true',
help="do not write to output file")
# Parse the args
args = parser.parse_args()
# Input was not provided, so we need to check a few other things
if args.input is None:
if args.password:
# User expects a password prompt
args.input = args.password
args.password = None
elif os.getenv('HASSIO_TOKEN') is not None:
# Script is running in Hass.io environment
args.input = 'http://hassio/homeassistant/api'
args.password = os.getenv('HASSIO_TOKEN')
else:
# Other defaults were not found
args.input = 'http://localhost:8123/api'
def dd(msg=None, j=None, *args):
if j is None and len(args) == 0:
j = msg
msg = "{}"
if j is not None:
_LOGGER.debug(msg.format(json.dumps(j, indent=2)))
else:
_LOGGER.debug(msg.format(*args))
class LovelaceBase(OrderedDict):
"""
Base class for Lovelace objects.
Derivitives should set `key_order`:
self.key_order = ['first', 'second', '...', 'last']
"""
def __init__(self, **kwargs):
"""Initialize the object."""
self.update(kwargs)
for key, value in list(self.items()):
if value is None:
del self[key]
def __setitem__(self, key, value):
sort = key not in self.keys()
super().__setitem__(key, value)
if sort:
self.sortkeys()
@classmethod
def from_config(cls, config):
"""
Subclass should implement config conversion methods `from_xxx_config`:
from_camera_config(cls, config)
from_media_player_config(cls, config)
from_group_config(cls, config)
"""
def invalid_config(cls, config={}, exception=None):
"""Display an error about invalid config."""
_LOGGER.error("Invalid config for conversion to '{}': {}"
"".format(cls.__name__, exception))
if config is not None:
output = json.dumps(config, indent=2)
else:
output = config
_LOGGER.debug("Invalid config: {}".format(output))
if 'entity_id' not in config:
invalid_config(cls, config, "Config is missing 'entity_id'")
return None
entity_id = config['entity_id']
domain, object_id = entity_id.split('.', 1)
fx = getattr(cls, "from_" + domain + "_config", None)
if fx is None:
_LOGGER.error("Class '{}' does not support conversion from "
"'{}' config".format(cls.__name__, domain))
return None
return fx(config)
def add_item(self, key, item):
"""Add item(s) to the object."""
if item is not None:
if key not in self.keys():
self[key] = []
if type(item) is list:
self[key].extend(item)
else:
self[key].append(item)
def sortkeys(self, key_order=None, delim='...'):
"""Iterate keys of OrderedDict and move to front/back as necessary."""
# Get `keys` from self, but fallback on parent
if key_order is None:
try:
key_order = self.key_order
except AttributeError:
try:
key_order = super(OrderedDict, self).key_order
except AttributeError:
pass
if key_order is None:
return
# Make a copy so that we're not changing the original
key_order = key_order[:]
# Check to see if delimiter is in `key_order`
if delim in key_order:
mid = key_order.index(delim)
else:
mid = len(key_order)
# Reverse the front keys
key_order[:mid] = key_order[:mid][::-1]
# Iterate keys and move them accordingly
for i, key in enumerate(key_order):
# Skip delimiter and missing keys
if i == mid or key not in self:
continue
# Move to front/back
self.move_to_end(key, last=i > mid)
class Lovelace(LovelaceBase):
"""Lovelace migration class."""
class View(LovelaceBase):
"""Lovelace UI view representation."""
def __init__(self, **kwargs):
"""Init view."""
self.key_order = ['title', 'id', 'icon', 'panel', 'theme', '...',
'cards']
super().__init__(**kwargs)
def add_card(self, card):
"""Add a card to the view."""
return self.add_item('cards', card)
@classmethod
def from_group_config(cls, group):
"""Build the view from `group` config."""
if not group['attributes'].get('view', False):
return None
view = cls(title=group['attributes'].get('friendly_name'),
icon=group['attributes'].get('icon'))
cards, nocards = [], []
for entity in group.get('entities', {}).values():
card = Lovelace.Card.from_config(entity)
if type(card) is list:
cards.extend(card)
elif card is not None:
cards.append(card)
else:
nocards.append(entity['entity_id'])
if len(nocards):
cards = [Lovelace.EntitiesCard(entities=nocards)] + cards
view.add_card(cards)
return view
class Card(LovelaceBase):
"""Lovelace UI card representation."""
@classmethod
def from_config(cls, config):
"""Convert a config object to Lovelace UI."""
if config is None:
return None
if cls is not Lovelace.Card:
return super().from_config(config)
domain = config['domain']
if domain in Lovelace.CARD_DOMAINS:
cls = Lovelace.CARD_DOMAINS[domain]
return cls.from_config(config)
return None
# @todo Implement use of this in `add_entity`
class Entity(LovelaceBase):
"""Lovelace UI entity representation."""
def __init__(self, **kwargs):
"""Init entity."""
self.key_order = ['entity', 'name']
super().__init__(**kwargs)
class Resource(LovelaceBase):
"""Lovelace UI resource representation."""
def __init__(self, **kwargs):
"""Init resource."""
self.key_order = ['url', 'type']
kwargs.setdefault('type', 'js')
super().__init__(**kwargs)
class EntitiesCard(Card):
"""Lovelove UI `entities` card representation."""
def __init__(self, **kwargs):
"""Init card."""
self['type'] = 'entities'
self.key_order = ['type', 'title', 'show_header_toggle', '...',
'entities']
super().__init__(**kwargs)
def add_entity(self, entity):
"""Add an entity to the card."""
return self.add_item('entities', entity)
@classmethod
def from_group_config(cls, group):
"""Build the card from `group` config."""
control = group['attributes'].get('control') != 'hidden'
cards, nocards = [], []
for entity in group.get('entities', {}).values():
card = Lovelace.Card.from_config(entity)
if type(card) is list:
cards.extend(card)
elif card is not None:
cards.append(card)
else:
nocards.append(entity['entity_id'])
if len(nocards):
primary = cls(title=group['attributes'].get('friendly_name'),
show_header_toggle=control,
entities=nocards)
return [primary] + cards
return cards
class EntityFilterCard(Card):
"""Lovelove UI `entity-filter` card representation."""
def __init__(self, **kwargs):
"""Init card."""
self['type'] = 'entity-filter'
self.key_order = ['type', 'entities', 'state_filter', 'card',
'show_empty']
super().__init__(**kwargs)
def add_entity(self, entity):
"""Add an entity to the card."""
return self.add_item('entities', entity)
def add_state_filter(self, state_filter):
"""Add a state filter to the card."""
return self.add_item('state_filter', state_filter)
class GlanceCard(Card):
"""Lovelove UI `glance` card representation."""
def __init__(self, **kwargs):
"""Init card."""
self['type'] = 'glance'
self.key_order = ['type', 'title', '...', 'entities']
super().__init__(**kwargs)
def add_entity(self, entity):
"""Add an entity to the card."""
return self.add_item('entities', entity)
class HistoryGraphCard(Card):
"""Lovelove UI `history-graph` card representation."""
def __init__(self, **kwargs):
"""Init card."""
self['type'] = 'history-graph'
self.key_order = ['type', 'title', 'hours_to_show',
'refresh_interval', '...', 'entities']
super().__init__(**kwargs)
def add_entity(self, entity):
"""Add an entity to the card."""
return self.add_item('entities', entity)
@classmethod
def from_history_graph_config(cls, config):
"""Build the card from `history_graph` config."""
return cls(title=config['attributes'].get('friendly_name'),
hours_to_show=config['attributes'].get('hours_to_show'),
refresh_interval=config['attributes'].get('refresh'),
entities=config['attributes']['entity_id'])
class HorizontalStackCard(Card):
"""Lovelove UI `horizontal-stack` card representation."""
def __init__(self, **kwargs):
"""Init card."""
self['type'] = 'horizontal-stack'
self.key_order = ['type', '...', 'cards']
super().__init__(**kwargs)
def add_card(self, card):
"""Add a card to the card."""
return self.add_item('cards', card)
class IframeCard(Card):
"""Lovelove UI `iframe` card representation."""
def __init__(self, **kwargs):
"""Init card."""
self['type'] = 'iframe'
self.key_order = ['type', 'title', 'url', 'aspect_ratio']
super().__init__(**kwargs)
class MapCard(Card):
"""Lovelove UI `map` card representation."""
def __init__(self, **kwargs):
"""Init card."""
self['type'] = 'map'
self.key_order = ['type', 'title', 'aspect_ratio', '...',
'entities']
super().__init__(**kwargs)
def add_entity(self, entity):
"""Add an entity to the card."""
return self.add_item('entities', entity)
class MarkdownCard(Card):
"""Lovelove UI `markdown` card representation."""
def __init__(self, **kwargs):
"""Init card."""
self['type'] = 'markdown'
self.key_order = ['type', 'title', '...', 'content']
super().__init__(**kwargs)
class MediaControlCard(Card):
"""Lovelove UI `media-control` card representation."""
def __init__(self, **kwargs):
"""Init card."""
self['type'] = 'media-control'
self.key_order = ['type', 'entity']
super().__init__(**kwargs)
@classmethod
def from_media_player_config(cls, config):
"""Build the card from `media_player` config."""
return cls(entity=config['entity_id'])
class PictureCard(Card):
"""Lovelove UI `picture` card representation."""
def __init__(self, **kwargs):
"""Init card."""
self['type'] = 'picture'
self.key_order = ['type', 'image', 'navigation_path', 'service',
'service_data']
super().__init__(**kwargs)
class PictureElementsCard(Card):
"""Lovelove UI `picture-elements` card representation."""
def __init__(self, **kwargs):
"""Init card."""
self['type'] = 'picture-elements'
self.key_order = ['type', 'title', 'image', 'elements']
super().__init__(**kwargs)
def add_element(self, element):
"""Add an element to the card."""
return self.add_item('elements', element)
class PictureEntityCard(Card):
"""Lovelove UI `picture-entity` card representation."""
def __init__(self, **kwargs):
"""Init card."""
self['type'] = 'picture-entity'
self.key_order = ['type', 'title', 'entity', 'camera_image',
'image', 'state_image', 'show_info',
'tap_action']
super().__init__(**kwargs)
@classmethod
def from_camera_config(cls, config):
"""Build the card from `camera` config."""
return cls(title=config['attributes'].get('friendly_name'),
entity=config['entity_id'],
camera_image=config['entity_id'],
show_info=True,
tap_action='dialog')
class PictureGlanceCard(Card):
"""Lovelove UI `picture-glance` card representation."""
def __init__(self, **kwargs):
"""Init card."""
self['type'] = 'picture-glance'
self.key_order = ['type', 'title', '...', 'entities']
super().__init__(**kwargs)
class PlantStatusCard(Card):
"""Lovelove UI `plant-status` card representation."""
def __init__(self, **kwargs):
"""Init card."""
self['type'] = 'plant-status'
self.key_order = ['type', 'entity']
super().__init__(**kwargs)
@classmethod
def from_plant_config(cls, config):
"""Build the card from `plant` config."""
return cls(entity=config['entity_id'])
class VerticalStackCard(Card):
"""Lovelove UI `vertical-stack` card representation."""
def __init__(self, **kwargs):
"""Init card."""
self['type'] = 'vertical-stack'
self.key_order = ['type', '...', 'cards']
super().__init__(**kwargs)
def add_card(self, card):
"""Add a card to the card."""
return self.add_item('cards', card)
class WeatherForecastCard(Card):
"""Lovelove UI `weather-forecast` card representation."""
def __init__(self, **kwargs):
"""Init card."""
self['type'] = 'weather-forecast'
self.key_order = ['type', 'entity']
super().__init__(**kwargs)
@classmethod
def from_weather_config(cls, config):
"""Build the card from `weather` config."""
return cls(entity=config['entity_id'])
class CustomCard(Card):
"""Lovelace UI `custom` card representation."""
def __init__(self, card_type, resource=None, key_order=None, **kwargs):
"""Init card."""
if card_type in Lovelace.CUSTOM_CARDS:
custom = Lovelace.CUSTOM_CARDS[card_type]
if resource is None and 'resource' in custom:
resource = custom['resource']
if key_order is None and 'key_order' in custom:
key_order = custom['key_order']
self['type'] = 'custom:' + card_type
self.key_order = key_order or ['type', '...']
self.resource = resource
super().__init__(**kwargs)
# @todo Possibly move this into CARD_DOMAINS and `from_config`
# AUTO_DOMAINS = {
# 'all_lights': 'light',
# 'all_automations': 'automation',
# 'all_devices': 'device_tracker',
# 'all_fans': 'fan',
# 'all_locks': 'lock',
# 'all_covers': 'cover',
# 'all_remotes': 'remote',
# 'all_switches': 'switch',
# 'all_vacuum_cleaners': 'vacuum',
# 'all_scripts': 'script',
# }
CARD_DOMAINS = {
'camera': PictureEntityCard,
'group': EntitiesCard,
'history_graph': HistoryGraphCard,
'media_player': MediaControlCard,
'plant': PlantStatusCard,
'weather': WeatherForecastCard,
}
CUSTOM_CARDS = {
'monster-card': {
'resource': 'https://cdn.rawgit.com/ciotlosm/custom-lovelace/c9465a72a2f484fce135dce86c35412f099d493f/monster-card/monster-card.js',
'key_order': ['type', 'card', 'filter', 'when', '...']
},
}
def __init__(self, states_json, title=None):
"""Convert existing Home Assistant config to Lovelace UI."""
self.key_order = ['title', 'resources', 'excluded_entities',
'...', 'views']
super().__init__()
self['title'] = title or "Home"
# Build states and entities objects from the states JSON
self._states = states = self.build_states(states_json)
groups = states.get('group', {})
views = {k: v for k, v in groups.items()
if v['attributes'].get('view', False)}
if 'default_view' in views:
self.add_view(Lovelace.View.from_config(
views.pop('default_view')))
else:
view = Lovelace.View(title='Home')
for domain in Lovelace.CARD_DOMAINS.keys():
for e in states.get(domain, {}).values():
if (domain == 'group' and
e['attributes'].get('view', False)):
continue
card = Lovelace.Card.from_config(e)
if card is not None:
view.add_card(card)
if view.get('cards') is not None:
self.add_view(view)
for view in views.values():
self.add_view(Lovelace.View.from_config(view))
def add_resource(self, resource):
"""Add a resource to the UI."""
if type(resource) is str:
resource = Lovelace.Resource(url=resource)
elif type(resource) is dict:
resource = Lovelace.Resource(resource)
return self.add_item('resources', resource)
def add_view(self, view):
"""Add a view to the UI."""
return self.add_item('views', view)
def build_states(self, states_json):
"""Build a states object from states JSON."""
all_entities = self.build_entities(states_json)
states = {}
for e in all_entities.values():
if 'entity_id' in e['attributes']:
e['entities'] = {}
for x in e['attributes']['entity_id']:
if x in all_entities:
e['entities'].update({
x: all_entities[x]
})
if e['domain'] not in states:
states[e['domain']] = {}
states[e['domain']].update({
e['object_id']: e
})
return states
def build_entities(self, states_json):
"""Build a list of entities from states JSON."""
entities = {}
for e in states_json:
# Add domain and object_id
e['domain'], e['object_id'] = e['entity_id'].split('.', 1)
# Add name from `friendly_name` or build from `object_id`
e['attributes']['friendly_name'] = e['attributes'].get(
'friendly_name', e['object_id'].replace('_', ' ').title())
# Add entity to the entities object
entities[e['entity_id']] = e
return entities
def dump(self):
"""Dump YAML for the Lovelace UI."""
def ordered_dump(data, stream=None, Dumper=yaml.Dumper, **kwargs):
"""YAML dumper for OrderedDict."""
class OrderedDumper(Dumper):
"""Wrapper class for YAML dumper."""
def ignore_aliases(self, data):
"""Disable aliases in YAML dump."""
return True
def increase_indent(self, flow=False, indentless=False):
"""Increase indent on YAML lists."""
return super(OrderedDumper, self).increase_indent(
flow, False)
def _dict_representer(dumper, data):
"""Function to represent OrderDict and derivitives."""
return dumper.represent_mapping(
yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG,
data.items())
OrderedDumper.add_multi_representer(OrderedDict, _dict_representer)
return yaml.dump(data, stream, OrderedDumper, **kwargs)
return ordered_dump(self, Dumper=yaml.SafeDumper,
default_flow_style=False).strip()
class HomeAssistantAPI(object):
"""Class to access Home Assistant REST API."""
def __init__(self, api_url, password=None):
"""Initialize the class object."""
self.cache = {}
self.api_url = api_url
if password is None:
password = self.auth()
self.password = password
def auth(self):
"""Prompt user to enter a password."""
try:
return getpass("Enter password: ")
except KeyboardInterrupt:
print()
sys.exit(130)
def get(self, endpoint='/', refresh=False):
"""Wrapper to send a GET request to Home Assistant API."""
if endpoint in self.cache and not refresh:
return self.cache[endpoint]
url = self.api_url + endpoint
headers = {'x-ha-access': self.password or '',
'content-type': 'application/json'}
request = requests.get(url, headers=headers)
if request.status_code == requests.codes.unauthorized:
self.password = self.auth()
return self.get(endpoint=endpoint, refresh=refresh)
else:
request.raise_for_status()
self.cache[endpoint] = request
return request
def get_config(self, **kwargs):
"""Get config from Home Assistant REST API."""
request = self.get('/config', **kwargs)
return request.json()
def get_states(self, **kwargs):
"""Get states from Home Assistant REST API."""
request = self.get('/states', **kwargs)
return request.json()
def backup_file(filepath, dry_run=False):
"""Automatically create a rotating backup of a file."""
# Return None if original file does not exist
if not os.path.exists(filepath):
return None
# Find next backup file
c = 0
while True:
backupfile = "{}.{}".format(filepath, c)
if not os.path.exists(backupfile):
break
c += 1
# Only move the file if this is not a dry run
if not dry_run:
shutil.move(filepath, backupfile)
# Return the backup filename
return backupfile
def main():
"""Main program function."""
global args
if args.debug:
log_level = logging.DEBUG
else:
log_level = logging.INFO
logging.basicConfig(level=log_level)
try:
from colorlog import ColoredFormatter
logging.getLogger().handlers[0].setFormatter(ColoredFormatter(
"%(log_color)s[%(levelname)s] %(message)s%(reset)s",
datefmt="",
reset=True,
log_colors={
'DEBUG': 'cyan',
'INFO': 'green',
'WARNING': 'yellow',
'ERROR': 'red',
'CRITICAL': 'red',
}
))
except ImportError:
pass
# Detect input source (file, API URL, or - [stdin])
if args.input == '-':
# Input is stdin
_LOGGER.debug("Reading input from stdin")
if not sys.stdin.isatty():
states_json = json.load(sys.stdin)
else:
_LOGGER.error("Cannot read input from stdin")
return 1
elif (args.input.lower().startswith('http://') or
args.input.lower().startswith('https://')):
# Input is API URL
_LOGGER.debug("Reading input from URL: {}".format(args.input))
hass = HomeAssistantAPI(args.input, args.password)
try:
states_json = hass.get_states()
except requests.exceptions.ConnectionError:
_LOGGER.error("Could not connect to API URL: "
"{}".format(args.input))
return 1
else:
# Input is file
_LOGGER.debug("Reading input from file: {}".format(args.input))
try:
with open(args.input, 'r') as f:
states_json = json.load(f)
except FileNotFoundError:
_LOGGER.error("{}: No such file".format(args.input))
return 1
except PermissionError:
_LOGGER.error("{}: Permission denied".format(args.input))
return 1
# Convert to Lovelace UI
lovelace = Lovelace(states_json, title=args.title)
# Get YAML dump of Lovelace UI
dump = lovelace.dump()
# Set our output file
outfile = args.output
# Try to create a backup
try:
backupfile = backup_file(outfile, dry_run=args.dry_run)
if backupfile:
_LOGGER.error("{}: file exists, backed up to: {}"
"".format(outfile, backupfile))
except PermissionError:
_LOGGER.error("Could not create backup: {}: Permission denied"
"".format(outfile))
return 1
if not args.dry_run:
# Try to output to file
try:
with open(outfile, 'w') as f:
f.write("""
# This file was automatically generated by lovelace_migrate.py
# https://github.com/dale3h/python-lovelace
""")
f.write(dump)
_LOGGER.info("Lovelace UI successfully written to: {}"
"".format(outfile))
except PermissionError:
_LOGGER.error("Could not write to file: {}: Permission denied"
"".format(outfile))
return 1
else:
# Output Lovelace YAML to stdout
print(lovelace.dump())
# Return with a normal exit code
return 0
if __name__ == '__main__':
sys.exit(main())
+245
View File
@@ -0,0 +1,245 @@
# This file was automatically generated by lovelace_migrate.py
# https://github.com/dale3h/python-lovelace
title: Home
views:
- title: "Статус"
icon: mdi:home
cards:
- type: entities
title: "Дом"
show_header_toggle: false
entities:
- alarm_control_panel.ha_alarm
- switch.water_valve
- vacuum.xiaomi_vacuum_cleaner
- switch.domofon_open
- switch.wifi_socket
- type: entities
title: "Гостиная"
show_header_toggle: false
entities:
- sensor.temperature_158d0001149b3c
- sensor.humidity_158d0001149b3c
- light.yeelight_rgb_34ce008fe328
- light.yeelight_rgb_286c071065c7
- light.yeelight_white_34ce0087ff33
- light.yeelight_white_f0b4290e5444
- light.yeelight_white_f0b429a8f6df
- cover.curtain_158d0002b0c46a
- switch.plug_158d0001104a0c
- switch.plug_158d00010dd98d
- climate.living_room
- type: entities
title: "Кухня"
show_header_toggle: false
entities:
- light.kitchen
- switch.kitchen_mini_light
- switch.plug_158d0001a68b18
- switch.plug_158d0001a68c64
- switch.plug_158d0001a4cfb4
- switch.xiaomi_miio_switch
- type: entities
title: "Ванная"
show_header_toggle: false
entities:
- sensor.temperature_158d00012d9ad7
- sensor.humidity_158d00012d9ad7
- light.bath
- light.philips_led
- sensor.airfresher_remain
- type: entities
title: "Прихожая"
show_header_toggle: false
entities:
- light.hall
- type: entities
title: "Балкон"
show_header_toggle: false
entities:
- light.yeelight_white_286c07f12bed
- light.yeelight_white_f0b429a8d936
- sensor.temperature_158d000117d37a
- sensor.humidity_158d000117d37a
- type: entities
title: "Улица"
show_header_toggle: false
entities:
- sensor.temperature_158d000149c128
- sensor.humidity_158d000149c128
- title: "Датчики"
icon: mdi:nfc
cards:
- type: entities
title: "Движение"
show_header_toggle: true
entities:
- binary_sensor.motion_sensor_158d0000e7c7ad
- binary_sensor.motion_sensor_158d0002044548
- binary_sensor.motion_sensor_158d0000fbc664
- binary_sensor.motion_sensor_158d0001535c97
- binary_sensor.motion_sensor_158d0000f9546b
- binary_sensor.motion_sensor_158d000165fed5
- binary_sensor.motion_sensor_158d0001537e21
- binary_sensor.motion_sensor_158d0000f6b2fa
- binary_sensor.motion_sensor_158d0001e50ce4
- binary_sensor.motion_sensor_158d0001e80f0d
- type: entities
title: "Протечка"
show_header_toggle: true
entities:
- binary_sensor.water_leak_sensor_158d0001d594fa
- binary_sensor.water_leak_sensor_158d0001c34960
- binary_sensor.water_leak_sensor_158d0002150c07
- binary_sensor.water_leak_sensor_158d0002112b98
- binary_sensor.water_leak_sensor_158d0002113158
- binary_sensor.water_leak_sensor_158d00023a48f0
- type: entities
title: "Датчики дыма"
show_header_toggle: true
entities:
- binary_sensor.smoke_sensor_158d00023115ad
- type: entities
title: "Двери"
show_header_toggle: true
entities:
- binary_sensor.door_window_sensor_158d0001130141
- binary_sensor.door_window_sensor_158d000111a469
- binary_sensor.door_window_sensor_158d00010f7e19
- binary_sensor.door_window_sensor_158d0002254373
- binary_sensor.door_window_sensor_158d0001b77b1b
- binary_sensor.door_window_sensor_158d0001c0e46d
- binary_sensor.door_window_sensor_158d0001e74660
- type: entities
title: "Счетчики вод\
ы"
show_header_toggle: true
entities:
- sensor.epd_balance
- sensor.counter_cold
- sensor.counter_hot
- sensor.counter_mosru_cold
- sensor.counter_mosru_hot
- type: entities
title: "Метки"
show_header_toggle: true
entities:
- sensor.tag_mamik_band
- sensor.tag_monster_nut
- sensor.tag_nut_blue
- sensor.tag_nut_white
- sensor.tag_nut_guest
- type: entities
title: "Пристутстви\
е"
show_header_toggle: true
entities:
- sensor.presence_hall
- sensor.presence_kitchen
- sensor.presence_balcony
- type: entities
title: "Электропита\
ние"
show_header_toggle: true
entities:
- sensor.power_mosenergosbyt_balance
- sensor.power_main_total
- sensor.power_mosenergosbyt_total
- sensor.power_total
- sensor.power_voltage
- sensor.power_current
- sensor.power_frequency
- sensor.power_active_power
- sensor.power_reactive_power
- sensor.power_power_factor
- title: "Камеры"
icon: mdi:cctv
cards:
- type: picture-entity
title: enterance
entity: camera.enterance
camera_image: camera.enterance
show_info: true
tap_action: dialog
- title: "Будильники"
icon: mdi:alarm
cards:
- type: entities
title: "Будильник (бу\
дни)"
show_header_toggle: false
entities:
- input_boolean.alarmclock_weekdays
- input_datetime.alarmclock_weekdays
- type: entities
title: "Будильник (вы\
ходные)"
show_header_toggle: false
entities:
- input_boolean.alarmclock_weekend
- input_datetime.alarmclock_weekend
- title: "Статистика"
icon: mdi:chart-areaspline
cards: []
- title: "Настройки"
icon: mdi:settings
cards:
- type: entities
title: "Контроль света"
show_header_toggle: true
entities:
- input_boolean.mainroom_light_control
- input_boolean.hall_light_control
- input_boolean.bath_light_control
- input_boolean.kitchen_light_control
- input_boolean.balcony_light_control
- type: entities
title: "Настройки автоматики"
show_header_toggle: true
entities:
- input_boolean.presence_control
- input_boolean.water_control
- input_boolean.water_leak_control
- input_boolean.mainroom_climate_control
- switch.domofon_mode
- type: entities
title: "Настройки отчетов"
show_header_toggle: true
entities:
- input_boolean.security_sensors_report
- input_boolean.battery_report
- input_boolean.water_counters_report
- input_boolean.work_presence_report
- input_boolean.lock_report
- type: entities
title: "Метки"
show_header_toggle: true
entities:
- input_boolean.tag_mamik_band
- input_boolean.tag_monster_nut
- input_boolean.tag_nut_blue
- input_boolean.tag_nut_white
- input_boolean.tag_nut_guest
- title: "Информация"
icon: mdi:monitor-multiple
cards:
- type: entities
title: "ОС"
show_header_toggle: true
entities:
- sensor.processor_use
- sensor.memory_free
- sensor.swap_use_percent
- sensor.disk_use_percent
- sensor.ssl_certificate_expiry
- type: entities
title: "Сеть"
show_header_toggle: true
entities:
- sensor.speedtest_ping
- sensor.speedtest_download
- sensor.speedtest_upload
@@ -10,7 +10,6 @@ serial: xxxxxxxxx
disable_led: xxxxxxxxx disable_led: xxxxxxxxx
advanced: xxxxxxxxx advanced: xxxxxxxxx
cache_state: xxxxxxxxx cache_state: xxxxxxxxx
# log_level: xxxxxxxxx
devices: xxxxxxxxx devices: xxxxxxxxx
'0x00158d0001e065e8': xxxxxxxxx '0x00158d0001e065e8': xxxxxxxxx
friendly_name: xxxxxxxxx friendly_name: xxxxxxxxx
@@ -18,3 +17,6 @@ devices: xxxxxxxxx
'0x00158d0002a55439': xxxxxxxxx '0x00158d0002a55439': xxxxxxxxx
friendly_name: xxxxxxxxx friendly_name: xxxxxxxxx
retain: xxxxxxxxx retain: xxxxxxxxx
'0x00158d0001e50ce4': xxxxxxxxx
friendly_name: xxxxxxxxx
retain: xxxxxxxxx