diff --git a/.coveragerc b/.coveragerc
index 22e8116693c..46a945e760b 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -17,6 +17,9 @@ omit =
homeassistant/components/*/tellstick.py
homeassistant/components/*/vera.py
+ homeassistant/components/ecobee.py
+ homeassistant/components/*/ecobee.py
+
homeassistant/components/verisure.py
homeassistant/components/*/verisure.py
diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py
index 4d68227d4d0..41377aadebf 100644
--- a/homeassistant/bootstrap.py
+++ b/homeassistant/bootstrap.py
@@ -82,7 +82,7 @@ def _setup_component(hass, domain, config):
return True
component = loader.get_component(domain)
- missing_deps = [dep for dep in component.DEPENDENCIES
+ missing_deps = [dep for dep in getattr(component, 'DEPENDENCIES', [])
if dep not in hass.config.components]
if missing_deps:
@@ -106,7 +106,7 @@ def _setup_component(hass, domain, config):
# Assumption: if a component does not depend on groups
# it communicates with devices
- if group.DOMAIN not in component.DEPENDENCIES:
+ if group.DOMAIN not in getattr(component, 'DEPENDENCIES', []):
hass.pool.add_worker()
hass.bus.fire(
@@ -133,14 +133,13 @@ def prepare_setup_platform(hass, config, domain, platform_name):
return platform
# Load dependencies
- if hasattr(platform, 'DEPENDENCIES'):
- for component in platform.DEPENDENCIES:
- if not setup_component(hass, component, config):
- _LOGGER.error(
- 'Unable to prepare setup for platform %s because '
- 'dependency %s could not be initialized', platform_path,
- component)
- return None
+ for component in getattr(platform, 'DEPENDENCIES', []):
+ if not setup_component(hass, component, config):
+ _LOGGER.error(
+ 'Unable to prepare setup for platform %s because '
+ 'dependency %s could not be initialized', platform_path,
+ component)
+ return None
if not _handle_requirements(hass, platform, platform_path):
return None
diff --git a/homeassistant/components/alarm_control_panel/__init__.py b/homeassistant/components/alarm_control_panel/__init__.py
index d3289e08e62..3f5e6362fb6 100644
--- a/homeassistant/components/alarm_control_panel/__init__.py
+++ b/homeassistant/components/alarm_control_panel/__init__.py
@@ -15,7 +15,6 @@ from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent
DOMAIN = 'alarm_control_panel'
-DEPENDENCIES = []
SCAN_INTERVAL = 30
ENTITY_ID_FORMAT = DOMAIN + '.{}'
diff --git a/homeassistant/components/alarm_control_panel/manual.py b/homeassistant/components/alarm_control_panel/manual.py
index ca1816db9e6..63bc989f3df 100644
--- a/homeassistant/components/alarm_control_panel/manual.py
+++ b/homeassistant/components/alarm_control_panel/manual.py
@@ -18,8 +18,6 @@ from homeassistant.const import (
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = []
-
DEFAULT_ALARM_NAME = 'HA Alarm'
DEFAULT_PENDING_TIME = 60
DEFAULT_TRIGGER_TIME = 120
diff --git a/homeassistant/components/api.py b/homeassistant/components/api.py
index 7ccc1f745e9..1e6e66baee0 100644
--- a/homeassistant/components/api.py
+++ b/homeassistant/components/api.py
@@ -18,10 +18,10 @@ from homeassistant.bootstrap import ERROR_LOG_FILENAME
from homeassistant.const import (
URL_API, URL_API_STATES, URL_API_EVENTS, URL_API_SERVICES, URL_API_STREAM,
URL_API_EVENT_FORWARD, URL_API_STATES_ENTITY, URL_API_COMPONENTS,
- URL_API_CONFIG, URL_API_BOOTSTRAP, URL_API_ERROR_LOG,
+ URL_API_CONFIG, URL_API_BOOTSTRAP, URL_API_ERROR_LOG, URL_API_LOG_OUT,
EVENT_TIME_CHANGED, EVENT_HOMEASSISTANT_STOP, MATCH_ALL,
HTTP_OK, HTTP_CREATED, HTTP_BAD_REQUEST, HTTP_NOT_FOUND,
- HTTP_UNPROCESSABLE_ENTITY, CONTENT_TYPE_TEXT_PLAIN)
+ HTTP_UNPROCESSABLE_ENTITY)
DOMAIN = 'api'
@@ -36,10 +36,6 @@ _LOGGER = logging.getLogger(__name__)
def setup(hass, config):
""" Register the API with the HTTP interface. """
- if 'http' not in hass.config.components:
- _LOGGER.error('Dependency http is not loaded')
- return False
-
# /api - for validation purposes
hass.http.register_path('GET', URL_API, _handle_get_api)
@@ -93,6 +89,8 @@ def setup(hass, config):
hass.http.register_path('GET', URL_API_ERROR_LOG,
_handle_get_api_error_log)
+ hass.http.register_path('POST', URL_API_LOG_OUT, _handle_post_api_log_out)
+
return True
@@ -108,6 +106,7 @@ def _handle_get_api_stream(handler, path_match, data):
wfile = handler.wfile
write_lock = threading.Lock()
block = threading.Event()
+ session_id = None
restrict = data.get('restrict')
if restrict:
@@ -121,6 +120,7 @@ def _handle_get_api_stream(handler, path_match, data):
try:
wfile.write(msg.encode("UTF-8"))
wfile.flush()
+ handler.server.sessions.extend_validation(session_id)
except IOError:
block.set()
@@ -140,6 +140,7 @@ def _handle_get_api_stream(handler, path_match, data):
handler.send_response(HTTP_OK)
handler.send_header('Content-type', 'text/event-stream')
+ session_id = handler.set_session_cookie_header()
handler.end_headers()
hass.bus.listen(MATCH_ALL, forward_events)
@@ -347,9 +348,15 @@ def _handle_get_api_components(handler, path_match, data):
def _handle_get_api_error_log(handler, path_match, data):
""" Returns the logged errors for this session. """
- error_path = handler.server.hass.config.path(ERROR_LOG_FILENAME)
- with open(error_path, 'rb') as error_log:
- handler.write_file_pointer(CONTENT_TYPE_TEXT_PLAIN, error_log)
+ handler.write_file(handler.server.hass.config.path(ERROR_LOG_FILENAME),
+ False)
+
+
+def _handle_post_api_log_out(handler, path_match, data):
+ """ Log user out. """
+ handler.send_response(HTTP_OK)
+ handler.destroy_session()
+ handler.end_headers()
def _services_json(hass):
diff --git a/homeassistant/components/arduino.py b/homeassistant/components/arduino.py
index 0c278ceee63..88967ec1f74 100644
--- a/homeassistant/components/arduino.py
+++ b/homeassistant/components/arduino.py
@@ -19,7 +19,6 @@ from homeassistant.const import (EVENT_HOMEASSISTANT_START,
EVENT_HOMEASSISTANT_STOP)
DOMAIN = "arduino"
-DEPENDENCIES = []
REQUIREMENTS = ['PyMata==2.07a']
BOARD = None
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/binary_sensor/__init__.py b/homeassistant/components/binary_sensor/__init__.py
index 2ef9e83cc30..ccfd57aff8c 100644
--- a/homeassistant/components/binary_sensor/__init__.py
+++ b/homeassistant/components/binary_sensor/__init__.py
@@ -14,7 +14,6 @@ from homeassistant.helpers.entity import Entity
from homeassistant.const import (STATE_ON, STATE_OFF)
DOMAIN = 'binary_sensor'
-DEPENDENCIES = []
SCAN_INTERVAL = 30
ENTITY_ID_FORMAT = DOMAIN + '.{}'
diff --git a/homeassistant/components/browser.py b/homeassistant/components/browser.py
index db0f3710158..88548e2a1b3 100644
--- a/homeassistant/components/browser.py
+++ b/homeassistant/components/browser.py
@@ -8,7 +8,6 @@ https://home-assistant.io/components/browser/
"""
DOMAIN = "browser"
-DEPENDENCIES = []
SERVICE_BROWSE_URL = "browse_url"
diff --git a/homeassistant/components/camera/__init__.py b/homeassistant/components/camera/__init__.py
index ae5fe28beac..169c97595af 100644
--- a/homeassistant/components/camera/__init__.py
+++ b/homeassistant/components/camera/__init__.py
@@ -80,19 +80,21 @@ def setup(hass, config):
def _proxy_camera_image(handler, path_match, data):
""" Proxies the camera image via the HA server. """
entity_id = path_match.group(ATTR_ENTITY_ID)
+ camera = component.entities.get(entity_id)
- camera = None
- if entity_id in component.entities.keys():
- camera = component.entities[entity_id]
-
- if camera:
- response = camera.camera_image()
- if response is not None:
- handler.wfile.write(response)
- else:
- handler.send_response(HTTP_NOT_FOUND)
- else:
+ if camera is None:
handler.send_response(HTTP_NOT_FOUND)
+ handler.end_headers()
+ return
+
+ response = camera.camera_image()
+
+ if response is None:
+ handler.send_response(HTTP_NOT_FOUND)
+ handler.end_headers()
+ return
+
+ handler.wfile.write(response)
hass.http.register_path(
'GET',
@@ -108,12 +110,9 @@ def setup(hass, config):
stream even with only a still image URL available.
"""
entity_id = path_match.group(ATTR_ENTITY_ID)
+ camera = component.entities.get(entity_id)
- camera = None
- if entity_id in component.entities.keys():
- camera = component.entities[entity_id]
-
- if not camera:
+ if camera is None:
handler.send_response(HTTP_NOT_FOUND)
handler.end_headers()
return
@@ -131,7 +130,6 @@ def setup(hass, config):
# MJPEG_START_HEADER.format()
while True:
-
img_bytes = camera.camera_image()
if img_bytes is None:
continue
@@ -148,12 +146,12 @@ def setup(hass, config):
handler.request.sendall(
bytes('--jpgboundary\r\n', 'utf-8'))
+ time.sleep(0.5)
+
except (requests.RequestException, IOError):
camera.is_streaming = False
camera.update_ha_state()
- camera.is_streaming = False
-
hass.http.register_path(
'GET',
re.compile(
diff --git a/homeassistant/components/camera/demo.py b/homeassistant/components/camera/demo.py
index fc3ec263143..0ad992db86d 100644
--- a/homeassistant/components/camera/demo.py
+++ b/homeassistant/components/camera/demo.py
@@ -4,8 +4,8 @@ homeassistant.components.camera.demo
Demo platform that has a fake camera.
"""
import os
-from random import randint
from homeassistant.components.camera import Camera
+import homeassistant.util.dt as dt_util
def setup_platform(hass, config, add_devices, discovery_info=None):
@@ -24,12 +24,12 @@ class DemoCamera(Camera):
def camera_image(self):
""" Return a faked still image response. """
+ now = dt_util.utcnow()
image_path = os.path.join(os.path.dirname(__file__),
- 'demo_{}.png'.format(randint(1, 5)))
+ 'demo_{}.jpg'.format(now.second % 4))
with open(image_path, 'rb') as file:
- output = file.read()
- return output
+ return file.read()
@property
def name(self):
diff --git a/homeassistant/components/camera/demo_0.jpg b/homeassistant/components/camera/demo_0.jpg
new file mode 100644
index 00000000000..ff87d5179f8
Binary files /dev/null and b/homeassistant/components/camera/demo_0.jpg differ
diff --git a/homeassistant/components/camera/demo_1.jpg b/homeassistant/components/camera/demo_1.jpg
new file mode 100644
index 00000000000..06166fffa85
Binary files /dev/null and b/homeassistant/components/camera/demo_1.jpg differ
diff --git a/homeassistant/components/camera/demo_1.png b/homeassistant/components/camera/demo_1.png
deleted file mode 100644
index fc681fccecd..00000000000
Binary files a/homeassistant/components/camera/demo_1.png and /dev/null differ
diff --git a/homeassistant/components/camera/demo_2.jpg b/homeassistant/components/camera/demo_2.jpg
new file mode 100644
index 00000000000..71356479ab0
Binary files /dev/null and b/homeassistant/components/camera/demo_2.jpg differ
diff --git a/homeassistant/components/camera/demo_2.png b/homeassistant/components/camera/demo_2.png
deleted file mode 100644
index 255cd5c45d4..00000000000
Binary files a/homeassistant/components/camera/demo_2.png and /dev/null differ
diff --git a/homeassistant/components/camera/demo_3.jpg b/homeassistant/components/camera/demo_3.jpg
new file mode 100644
index 00000000000..06166fffa85
Binary files /dev/null and b/homeassistant/components/camera/demo_3.jpg differ
diff --git a/homeassistant/components/camera/demo_3.png b/homeassistant/components/camera/demo_3.png
deleted file mode 100644
index b54c1ffb57c..00000000000
Binary files a/homeassistant/components/camera/demo_3.png and /dev/null differ
diff --git a/homeassistant/components/camera/demo_4.png b/homeassistant/components/camera/demo_4.png
deleted file mode 100644
index 4be36ee42d0..00000000000
Binary files a/homeassistant/components/camera/demo_4.png and /dev/null differ
diff --git a/homeassistant/components/camera/demo_5.png b/homeassistant/components/camera/demo_5.png
deleted file mode 100644
index 874b95ef6a5..00000000000
Binary files a/homeassistant/components/camera/demo_5.png and /dev/null differ
diff --git a/homeassistant/components/configurator.py b/homeassistant/components/configurator.py
index 8bec580abf9..515daffc71c 100644
--- a/homeassistant/components/configurator.py
+++ b/homeassistant/components/configurator.py
@@ -15,7 +15,6 @@ from homeassistant.helpers import generate_entity_id
from homeassistant.const import EVENT_TIME_CHANGED
DOMAIN = "configurator"
-DEPENDENCIES = []
ENTITY_ID_FORMAT = DOMAIN + ".{}"
SERVICE_CONFIGURE = "configure"
diff --git a/homeassistant/components/conversation.py b/homeassistant/components/conversation.py
index d9cba832df7..7cd1193448c 100644
--- a/homeassistant/components/conversation.py
+++ b/homeassistant/components/conversation.py
@@ -14,7 +14,6 @@ from homeassistant.const import (
ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF)
DOMAIN = "conversation"
-DEPENDENCIES = []
SERVICE_PROCESS = "process"
diff --git a/homeassistant/components/device_tracker/nmap_tracker.py b/homeassistant/components/device_tracker/nmap_tracker.py
index 1e45444c74e..bc8e8768be0 100644
--- a/homeassistant/components/device_tracker/nmap_tracker.py
+++ b/homeassistant/components/device_tracker/nmap_tracker.py
@@ -98,7 +98,7 @@ class NmapDeviceScanner(object):
from nmap import PortScanner, PortScannerError
scanner = PortScanner()
- options = "-F --host-timeout 5"
+ options = "-F --host-timeout 5s"
if self.home_interval:
boundary = dt_util.now() - self.home_interval
diff --git a/homeassistant/components/discovery.py b/homeassistant/components/discovery.py
index 3a43c86f58a..cfd6ffd55eb 100644
--- a/homeassistant/components/discovery.py
+++ b/homeassistant/components/discovery.py
@@ -17,8 +17,7 @@ from homeassistant.const import (
ATTR_SERVICE, ATTR_DISCOVERED)
DOMAIN = "discovery"
-DEPENDENCIES = []
-REQUIREMENTS = ['netdisco==0.5.1']
+REQUIREMENTS = ['netdisco==0.5.2']
SCAN_INTERVAL = 300 # seconds
diff --git a/homeassistant/components/downloader.py b/homeassistant/components/downloader.py
index a69a6ca1517..655bf7d4eb6 100644
--- a/homeassistant/components/downloader.py
+++ b/homeassistant/components/downloader.py
@@ -15,7 +15,6 @@ from homeassistant.helpers import validate_config
from homeassistant.util import sanitize_filename
DOMAIN = "downloader"
-DEPENDENCIES = []
SERVICE_DOWNLOAD_FILE = "download_file"
diff --git a/homeassistant/components/ecobee.py b/homeassistant/components/ecobee.py
new file mode 100644
index 00000000000..a41bf7f25cc
--- /dev/null
+++ b/homeassistant/components/ecobee.py
@@ -0,0 +1,154 @@
+"""
+homeassistant.components.ecobee
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Ecobee Component
+
+This component adds support for Ecobee3 Wireless Thermostats.
+You will need to setup developer access to your thermostat,
+and create and API key on the ecobee website.
+
+The first time you run this component you will see a configuration
+component card in Home Assistant. This card will contain a PIN code
+that you will need to use to authorize access to your thermostat. You
+can do this at https://www.ecobee.com/consumerportal/index.html
+Click My Apps, Add application, Enter Pin and click Authorize.
+
+After authorizing the application click the button in the configuration
+card. Now your thermostat and sensors should shown in home-assistant.
+
+You can use the optional hold_temp parameter to set whether or not holds
+are set indefintely or until the next scheduled event.
+
+ecobee:
+ api_key: asdfasdfasdfasdfasdfaasdfasdfasdfasdf
+ hold_temp: True
+
+"""
+
+from homeassistant.loader import get_component
+from homeassistant import bootstrap
+from homeassistant.util import Throttle
+from homeassistant.const import (
+ EVENT_PLATFORM_DISCOVERED, ATTR_SERVICE, ATTR_DISCOVERED, CONF_API_KEY)
+from datetime import timedelta
+import logging
+import os
+
+DOMAIN = "ecobee"
+DISCOVER_THERMOSTAT = "ecobee.thermostat"
+DISCOVER_SENSORS = "ecobee.sensor"
+NETWORK = None
+HOLD_TEMP = 'hold_temp'
+
+REQUIREMENTS = [
+ 'https://github.com/nkgilley/python-ecobee-api/archive/'
+ 'd35596b67c75451fa47001c493a15eebee195e93.zip#python-ecobee==0.0.1']
+
+_LOGGER = logging.getLogger(__name__)
+
+ECOBEE_CONFIG_FILE = 'ecobee.conf'
+_CONFIGURING = {}
+
+# Return cached results if last scan was less then this time ago
+MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=180)
+
+
+def request_configuration(network, hass, config):
+ """ Request configuration steps from the user. """
+ configurator = get_component('configurator')
+ if 'ecobee' in _CONFIGURING:
+ configurator.notify_errors(
+ _CONFIGURING['ecobee'], "Failed to register, please try again.")
+
+ return
+
+ # pylint: disable=unused-argument
+ def ecobee_configuration_callback(callback_data):
+ """ Actions to do when our configuration callback is called. """
+ network.request_tokens()
+ network.update()
+ setup_ecobee(hass, network, config)
+
+ _CONFIGURING['ecobee'] = configurator.request_config(
+ hass, "Ecobee", ecobee_configuration_callback,
+ description=(
+ 'Please authorize this app at https://www.ecobee.com/consumer'
+ 'portal/index.html with pin code: ' + network.pin),
+ description_image="/static/images/config_ecobee_thermostat.png",
+ submit_caption="I have authorized the app."
+ )
+
+
+def setup_ecobee(hass, network, config):
+ """ Setup ecobee thermostat """
+ # If ecobee has a PIN then it needs to be configured.
+ if network.pin is not None:
+ request_configuration(network, hass, config)
+ return
+
+ if 'ecobee' in _CONFIGURING:
+ configurator = get_component('configurator')
+ configurator.request_done(_CONFIGURING.pop('ecobee'))
+
+ # Ensure component is loaded
+ bootstrap.setup_component(hass, 'thermostat', config)
+ bootstrap.setup_component(hass, 'sensor', config)
+
+ hold_temp = config[DOMAIN].get(HOLD_TEMP, False)
+
+ # Fire thermostat discovery event
+ hass.bus.fire(EVENT_PLATFORM_DISCOVERED, {
+ ATTR_SERVICE: DISCOVER_THERMOSTAT,
+ ATTR_DISCOVERED: {'hold_temp': hold_temp}
+ })
+
+ # Fire sensor discovery event
+ hass.bus.fire(EVENT_PLATFORM_DISCOVERED, {
+ ATTR_SERVICE: DISCOVER_SENSORS,
+ ATTR_DISCOVERED: {}
+ })
+
+
+# pylint: disable=too-few-public-methods
+class EcobeeData(object):
+ """ Gets the latest data and update the states. """
+
+ def __init__(self, config_file):
+ from pyecobee import Ecobee
+ self.ecobee = Ecobee(config_file)
+
+ @Throttle(MIN_TIME_BETWEEN_UPDATES)
+ def update(self):
+ """ Get the latest data from pyecobee. """
+ self.ecobee.update()
+ _LOGGER.info("ecobee data updated successfully.")
+
+
+def setup(hass, config):
+ """
+ Setup Ecobee.
+ Will automatically load thermostat and sensor components to support
+ devices discovered on the network.
+ """
+ # pylint: disable=global-statement, import-error
+ global NETWORK
+
+ if 'ecobee' in _CONFIGURING:
+ return
+
+ from pyecobee import config_from_file
+
+ # Create ecobee.conf if it doesn't exist
+ if not os.path.isfile(hass.config.path(ECOBEE_CONFIG_FILE)):
+ if config[DOMAIN].get(CONF_API_KEY) is None:
+ _LOGGER.error("No ecobee api_key found in config.")
+ return
+ jsonconfig = {"API_KEY": config[DOMAIN].get(CONF_API_KEY)}
+ config_from_file(hass.config.path(ECOBEE_CONFIG_FILE), jsonconfig)
+
+ NETWORK = EcobeeData(hass.config.path(ECOBEE_CONFIG_FILE))
+
+ setup_ecobee(hass, NETWORK.ecobee, config)
+
+ return True
diff --git a/homeassistant/components/frontend/__init__.py b/homeassistant/components/frontend/__init__.py
index 5a8fbed34e9..dac2041fa56 100644
--- a/homeassistant/components/frontend/__init__.py
+++ b/homeassistant/components/frontend/__init__.py
@@ -54,8 +54,7 @@ def setup(hass, config):
def _handle_get_root(handler, path_match, data):
- """ Renders the debug interface. """
-
+ """ Renders the frontend. """
handler.send_response(HTTP_OK)
handler.send_header('Content-type', 'text/html; charset=utf-8')
handler.end_headers()
@@ -66,7 +65,7 @@ def _handle_get_root(handler, path_match, data):
app_url = "frontend-{}.html".format(version.VERSION)
# auto login if no password was set, else check api_password param
- auth = ('no_password_set' if handler.server.no_password_set
+ auth = ('no_password_set' if handler.server.api_password is None
else data.get('api_password', ''))
with open(INDEX_PATH) as template_file:
diff --git a/homeassistant/components/frontend/index.html.template b/homeassistant/components/frontend/index.html.template
index 409ea6752db..87c5f6638a7 100644
--- a/homeassistant/components/frontend/index.html.template
+++ b/homeassistant/components/frontend/index.html.template
@@ -4,16 +4,13 @@
Home Assistant
-
-
-
+
+
+ href='/static/favicon-apple-180x180.png'>
-
+
-
-

-
Initializing
-
+
diff --git a/homeassistant/components/frontend/version.py b/homeassistant/components/frontend/version.py
index 0199b05156e..04ccb720f6b 100644
--- a/homeassistant/components/frontend/version.py
+++ b/homeassistant/components/frontend/version.py
@@ -1,2 +1,2 @@
""" DO NOT MODIFY. Auto-generated by build_frontend script """
-VERSION = "dff74f773ea8b0356b0bd8130ed6f0cf"
+VERSION = "36df87bb6c219a2ee59adf416e3abdfa"
diff --git a/homeassistant/components/frontend/www_static/favicon-384x384.png b/homeassistant/components/frontend/www_static/favicon-384x384.png
new file mode 100644
index 00000000000..51f67770790
Binary files /dev/null and b/homeassistant/components/frontend/www_static/favicon-384x384.png differ
diff --git a/homeassistant/components/frontend/www_static/frontend.html b/homeassistant/components/frontend/www_static/frontend.html
index b2a2eb97b3e..e600c4c5583 100644
--- a/homeassistant/components/frontend/www_static/frontend.html
+++ b/homeassistant/components/frontend/www_static/frontend.html
@@ -1,6 +1,6 @@
-
To install Home Assistant, run:
pip3 install homeassistant
hass --open-ui
Here are some resources to get started.
To remove this card, edit your config in configuration.yaml
and disable the introduction
component. To install Home Assistant, run:
pip3 install homeassistant
hass --open-ui
Here are some resources to get started.
To remove this card, edit your config in configuration.yaml
and disable the introduction
component.
[[errorMessage]]AboutThe following errors have been logged this session:
[[errorLog]]
AboutThe following errors have been logged this session:
[[errorLog]]
[[_text]]
{{text}}[[_text]]
{{text}}
[[attribute]]
[[getAttributeValue(stateObj, attribute)]]
Last Action
[[stateObj.attributes.last_action]]
Last Action
[[stateObj.attributes.last_action]]
\ No newline at end of file
+ }